Unverified Commit 1f4db3cb authored by Mark Brown's avatar Mark Brown
Browse files

ASoC: Intel: avs: 16 channels support

Merge series from Cezary Rojewski <cezary.rojewski@intel.com>:

Relatively small delta-wise patchset which raises max channels supported
from 8 to 16.  The existing limitation is software-based, not hardware
based.  The hardware, as per HDAudio specification, section 1.2.2,
(relevant register at SDnFMT, section 3.3.41) supports the
configurations for years.  The avs-driver becomes the first consumer of
that configuration on the Linux kernel side.

Set starts off with update to string_helpers so that functionality added
with parse_int_array_user() can be utilized in kernel-kernel
interactions.

Follow up is rasing the cap on HDAudio-library side.  The format
selection procedure found in the library is good-to-go as is.

Everything that follows these two patches is avs-driver specific:
- raise channels_max for every DAI-driver template
- provide i2s_test module parameter for testing purposes.  When combined
  with I2S loopback card, allows to test 16ch on most Intel hardware post
  Broadwell era
- adjust TDM masks to reflect the 8 -> 16 channels change
parents aa446b5d 8d18e67a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ enum string_size_units {
int string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
		    char *buf, int len);

int parse_int_array(const char *buf, size_t count, int **array);
int parse_int_array_user(const char __user *from, size_t count, int **array);

#define UNESCAPE_SPACE		BIT(0)
+21 −18
Original line number Diff line number Diff line
@@ -138,6 +138,25 @@ int string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
}
EXPORT_SYMBOL(string_get_size);

int parse_int_array(const char *buf, size_t count, int **array)
{
	int *ints, nints;

	get_options(buf, 0, &nints);
	if (!nints)
		return -ENOENT;

	ints = kcalloc(nints + 1, sizeof(*ints), GFP_KERNEL);
	if (!ints)
		return -ENOMEM;

	get_options(buf, nints + 1, ints);
	*array = ints;

	return 0;
}
EXPORT_SYMBOL(parse_int_array);

/**
 * parse_int_array_user - Split string into a sequence of integers
 * @from:	The user space buffer to read from
@@ -153,30 +172,14 @@ EXPORT_SYMBOL(string_get_size);
 */
int parse_int_array_user(const char __user *from, size_t count, int **array)
{
	int *ints, nints;
	char *buf;
	int ret = 0;
	int ret;

	buf = memdup_user_nul(from, count);
	if (IS_ERR(buf))
		return PTR_ERR(buf);

	get_options(buf, 0, &nints);
	if (!nints) {
		ret = -ENOENT;
		goto free_buf;
	}

	ints = kcalloc(nints + 1, sizeof(*ints), GFP_KERNEL);
	if (!ints) {
		ret = -ENOMEM;
		goto free_buf;
	}

	get_options(buf, nints + 1, ints);
	*array = ints;

free_buf:
	ret = parse_int_array(buf, count, array);
	kfree(buf);
	return ret;
}
+1 −1
Original line number Diff line number Diff line
@@ -801,7 +801,7 @@ unsigned int snd_hdac_stream_format(unsigned int channels, unsigned int bits, un
	if (!rate_bits[i].hz)
		return 0;

	if (channels == 0 || channels > 8)
	if (channels == 0 || channels > 16)
		return 0;
	val |= channels - 1;

+73 −68
Original line number Diff line number Diff line
@@ -19,9 +19,9 @@
#include "avs.h"
#include "utils.h"

static bool i2s_test;
module_param(i2s_test, bool, 0444);
MODULE_PARM_DESC(i2s_test, "Probe I2S test-board and skip all other I2S boards");
static char *i2s_test;
module_param(i2s_test, charp, 0444);
MODULE_PARM_DESC(i2s_test, "Use I2S test-board instead of ACPI, i2s_test=ssp0tdm,ssp1tdm,... 0 to ignore port");

bool obsolete_card_names = IS_ENABLED(CONFIG_SND_SOC_INTEL_AVS_CARDNAME_OBSOLETE);
module_param_named(obsolete_card_names, obsolete_card_names, bool, 0444);
@@ -331,52 +331,6 @@ static struct snd_soc_acpi_mach avs_mbl_i2s_machines[] = {
	{}
};

static struct snd_soc_acpi_mach avs_test_i2s_machines[] = {
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(0),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(1),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(2),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(3),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(4),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	{
		.drv_name = "avs_i2s_test",
		.mach_params = {
			.i2s_link_mask = AVS_SSP(5),
		},
		.tplg_filename = "i2s-test-tplg.bin",
	},
	/* no NULL terminator, as we depend on ARRAY SIZE due to .id == NULL */
};

struct avs_acpi_boards {
	int id;
	struct snd_soc_acpi_mach *machs;
@@ -508,6 +462,7 @@ static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach
	int num_ssps;
	char *name;
	int ret;
	int uid;

	num_ssps = adev->hw_cfg.i2s_caps.ctrl_count;
	if (fls(mach->mach_params.i2s_link_mask) > num_ssps) {
@@ -517,8 +472,11 @@ static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach
		return -ENODEV;
	}

	name = devm_kasprintf(adev->dev, GFP_KERNEL, "%s.%d-platform", mach->drv_name,
			      mach->mach_params.i2s_link_mask);
	uid = mach->mach_params.i2s_link_mask;
	if (avs_mach_singular_ssp(mach))
		uid = (uid << AVS_CHANNELS_MAX) + avs_mach_ssp_tdm(mach, avs_mach_ssp_port(mach));

	name = devm_kasprintf(adev->dev, GFP_KERNEL, "%s.%d-platform", mach->drv_name, uid);
	if (!name)
		return -ENOMEM;

@@ -536,7 +494,7 @@ static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach

	mach->mach_params.platform = name;

	board = platform_device_register_data(NULL, mach->drv_name, mach->mach_params.i2s_link_mask,
	board = platform_device_register_data(NULL, mach->drv_name, uid,
					      (const void *)mach, sizeof(*mach));
	if (IS_ERR(board)) {
		dev_err(adev->dev, "ssp board register failed\n");
@@ -552,35 +510,82 @@ static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach
	return 0;
}

static int avs_register_i2s_boards(struct avs_dev *adev)
static int avs_register_i2s_test_board(struct avs_dev *adev, int ssp_port, int tdm_slot)
{
	const struct avs_acpi_boards *boards;
	struct snd_soc_acpi_mach *mach;
	int tdm_mask = BIT(tdm_slot);
	unsigned long *tdm_cfg;
	char *tplg_name;
	int ret;

	if (!acpi_nhlt_find_endpoint(ACPI_NHLT_LINKTYPE_SSP, -1, -1, -1)) {
		dev_dbg(adev->dev, "no I2S endpoints present\n");
	mach = devm_kzalloc(adev->dev, sizeof(*mach), GFP_KERNEL);
	tdm_cfg = devm_kcalloc(adev->dev, ssp_port + 1, sizeof(unsigned long), GFP_KERNEL);
	tplg_name = devm_kasprintf(adev->dev, GFP_KERNEL, AVS_STRING_FMT("i2s", "-test-tplg.bin",
				   ssp_port, tdm_slot));
	if (!mach || !tdm_cfg || !tplg_name)
		return -ENOMEM;

	mach->drv_name = "avs_i2s_test";
	mach->mach_params.i2s_link_mask = AVS_SSP(ssp_port);
	tdm_cfg[ssp_port] = tdm_mask;
	mach->pdata = tdm_cfg;
	mach->tplg_filename = tplg_name;

	ret = avs_register_i2s_board(adev, mach);
	if (ret < 0) {
		dev_warn(adev->dev, "register i2s %s failed: %d\n", mach->drv_name, ret);
		return ret;
	}

	return 0;
}

	if (i2s_test) {
		int i, num_ssps;
static int avs_register_i2s_test_boards(struct avs_dev *adev)
{
	int max_ssps = adev->hw_cfg.i2s_caps.ctrl_count;
	int ssp_port, tdm_slot, ret;
	unsigned long tdm_slots;
	u32 *array, num_elems;

		num_ssps = adev->hw_cfg.i2s_caps.ctrl_count;
		/* constrain just in case FW says there can be more SSPs than possible */
		num_ssps = min_t(int, ARRAY_SIZE(avs_test_i2s_machines), num_ssps);
	ret = parse_int_array(i2s_test, strlen(i2s_test), (int **)&array);
	if (ret < 0) {
		dev_err(adev->dev, "failed to parse i2s_test parameter\n");
		return ret;
	}

		mach = avs_test_i2s_machines;
	num_elems = *array;
	if (num_elems > max_ssps) {
		dev_err(adev->dev, "board supports only %d SSP, %d specified\n",
			max_ssps, num_elems);
		return -EINVAL;
	}

		for (i = 0; i < num_ssps; i++) {
			ret = avs_register_i2s_board(adev, &mach[i]);
			if (ret < 0)
				dev_warn(adev->dev, "register i2s %s failed: %d\n", mach->drv_name,
					 ret);
	for (ssp_port = 0; ssp_port < num_elems; ssp_port++) {
		tdm_slots = array[1 + ssp_port];
		for_each_set_bit(tdm_slot, &tdm_slots, 16) {
			ret = avs_register_i2s_test_board(adev, ssp_port, tdm_slot);
			if (ret)
				return ret;
		}
	}

	return 0;
}

static int avs_register_i2s_boards(struct avs_dev *adev)
{
	const struct avs_acpi_boards *boards;
	struct snd_soc_acpi_mach *mach;
	int ret;

	if (!acpi_nhlt_find_endpoint(ACPI_NHLT_LINKTYPE_SSP, -1, -1, -1)) {
		dev_dbg(adev->dev, "no I2S endpoints present\n");
		return 0;
	}

	if (i2s_test)
		return avs_register_i2s_test_boards(adev);

	boards = avs_get_i2s_boards(adev);
	if (!boards) {
		dev_dbg(adev->dev, "no I2S endpoints supported\n");
+3 −2
Original line number Diff line number Diff line
@@ -699,8 +699,9 @@ enum avs_sample_type {
	AVS_SAMPLE_TYPE_FLOAT = 4,
};

#define AVS_CHANNELS_MAX	8
#define AVS_COEFF_CHANNELS_MAX	8
#define AVS_ALL_CHANNELS_MASK	UINT_MAX
#define AVS_CHANNELS_MAX	16

struct avs_audio_format {
	u32 sampling_freq;
@@ -875,7 +876,7 @@ struct avs_updown_mixer_cfg {
	struct avs_modcfg_base base;
	u32 out_channel_config;
	u32 coefficients_select;
	s32 coefficients[AVS_CHANNELS_MAX];
	s32 coefficients[AVS_COEFF_CHANNELS_MAX];
	u32 channel_map;
} __packed;
static_assert(sizeof(struct avs_updown_mixer_cfg) == 84);
Loading