mirror of
https://github.com/torvalds/linux.git
synced 2026-05-24 07:03:03 +02:00
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:
commit
1f4db3cb1a
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user