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
This commit is contained in:
Mark Brown 2025-04-08 10:25:08 +01:00
commit 1f4db3cb1a
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
9 changed files with 109 additions and 99 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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,6 +510,68 @@ static int avs_register_i2s_board(struct avs_dev *adev, struct snd_soc_acpi_mach
return 0;
}
static int avs_register_i2s_test_board(struct avs_dev *adev, int ssp_port, int tdm_slot)
{
struct snd_soc_acpi_mach *mach;
int tdm_mask = BIT(tdm_slot);
unsigned long *tdm_cfg;
char *tplg_name;
int ret;
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;
}
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;
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;
}
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 (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;
@ -563,23 +583,8 @@ static int avs_register_i2s_boards(struct avs_dev *adev)
return 0;
}
if (i2s_test) {
int i, num_ssps;
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);
mach = avs_test_i2s_machines;
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);
}
return 0;
}
if (i2s_test)
return avs_register_i2s_test_boards(adev);
boards = avs_get_i2s_boards(adev);
if (!boards) {

View File

@ -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);

View File

@ -495,7 +495,7 @@ static int avs_updown_mix_create(struct avs_dev *adev, struct avs_path_module *m
cfg.base.audio_fmt = *t->in_fmt;
cfg.out_channel_config = t->cfg_ext->updown_mix.out_channel_config;
cfg.coefficients_select = t->cfg_ext->updown_mix.coefficients_select;
for (i = 0; i < AVS_CHANNELS_MAX; i++)
for (i = 0; i < AVS_COEFF_CHANNELS_MAX; i++)
cfg.coefficients[i] = t->cfg_ext->updown_mix.coefficients[i];
cfg.channel_map = t->cfg_ext->updown_mix.channel_map;

View File

@ -1398,7 +1398,7 @@ int avs_dmic_platform_register(struct avs_dev *adev, const char *name)
static const struct snd_soc_dai_driver i2s_dai_template = {
.playback = {
.channels_min = 1,
.channels_max = 8,
.channels_max = AVS_CHANNELS_MAX,
.rates = SNDRV_PCM_RATE_8000_192000 |
SNDRV_PCM_RATE_12000 |
SNDRV_PCM_RATE_24000 |
@ -1411,7 +1411,7 @@ static const struct snd_soc_dai_driver i2s_dai_template = {
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.channels_max = AVS_CHANNELS_MAX,
.rates = SNDRV_PCM_RATE_8000_192000 |
SNDRV_PCM_RATE_12000 |
SNDRV_PCM_RATE_24000 |
@ -1473,7 +1473,7 @@ int avs_i2s_platform_register(struct avs_dev *adev, const char *name, unsigned l
goto plat_register;
for_each_set_bit(i, &port_mask, ssp_count) {
for_each_set_bit(j, &tdms[i], ssp_count) {
for_each_set_bit(j, &tdms[i], AVS_CHANNELS_MAX) {
memcpy(dai, &i2s_dai_template, sizeof(*dai));
dai->name =
@ -1499,7 +1499,7 @@ static const struct snd_soc_dai_driver hda_cpu_dai = {
.ops = &avs_dai_hda_be_ops,
.playback = {
.channels_min = 1,
.channels_max = 8,
.channels_max = AVS_CHANNELS_MAX,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,
@ -1509,7 +1509,7 @@ static const struct snd_soc_dai_driver hda_cpu_dai = {
},
.capture = {
.channels_min = 1,
.channels_max = 8,
.channels_max = AVS_CHANNELS_MAX,
.rates = SNDRV_PCM_RATE_8000_192000,
.formats = SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_S32_LE,

View File

@ -1668,8 +1668,8 @@ static int avs_widget_load(struct snd_soc_component *comp, int index,
/* See parse_link_formatted_string() for dynamic naming when(s). */
if (avs_mach_singular_tdm(mach, ssp_port)) {
/* size is based on possible %d -> SSP:TDM, where SSP and TDM < 10 + '\0' */
size_t size = strlen(dw->name) + 2;
/* size is based on possible %d -> SSP:TDM, where SSP and TDM < 16 + '\0' */
size_t size = strlen(dw->name) + 3;
char *buf;
tdm_slot = avs_mach_ssp_tdm(mach, ssp_port);

View File

@ -87,7 +87,7 @@ struct avs_tplg_modcfg_ext {
struct {
u32 out_channel_config;
u32 coefficients_select;
s32 coefficients[AVS_CHANNELS_MAX];
s32 coefficients[AVS_COEFF_CHANNELS_MAX];
u32 channel_map;
} updown_mix;
struct {