diff options
author | 2025-05-16 14:10:10 +0100 | |
---|---|---|
committer | 2025-05-20 11:15:40 +0100 | |
commit | c3ca24e3fcb656c6afe702b69e6e9efcc3ff1128 (patch) | |
tree | eb8a327d45c032fa29facc35dd5ffa480e5f72b5 /sound | |
parent | ASoC: SDCA: Create DAPM widgets and routes from DisCo (diff) | |
download | linux-rng-c3ca24e3fcb656c6afe702b69e6e9efcc3ff1128.tar.xz linux-rng-c3ca24e3fcb656c6afe702b69e6e9efcc3ff1128.zip |
ASoC: SDCA: Create ALSA controls from DisCo
Use the previously parsed DisCo information from ACPI to create the
ALSA controls required by an SDCA Function. This maps all User and
Application level SDCA Controls to ALSA controls. Typically controls
marked with those access levels are just volumes and mutes.
SDCA defines volume controls as an integer in 1/256ths of a dB and
then provides a mechanism to specify what values are valid (range
templates). Currently only a simple case of a single linear volume
range with a power of 2 step size is supported. This allows the code
to expose the volume control using a simple shift. This will need
expanded in the future, to support more complex ranges and probably
also some additional control types but this should be sufficient to
for a first pass.
For non-dataport terminal widgets also add a pin switch to allow
that endpoint to be turned on/off.
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>
Link: https://patch.msgid.link/20250516131011.221310-7-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/sdca/sdca_asoc.c | 256 |
1 files changed, 252 insertions, 4 deletions
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c index fe24c897def7..bf8dbccde526 100644 --- a/sound/soc/sdca/sdca_asoc.c +++ b/sound/soc/sdca/sdca_asoc.c @@ -11,7 +11,9 @@ #include <linux/delay.h> #include <linux/dev_printk.h> #include <linux/device.h> +#include <linux/minmax.h> #include <linux/module.h> +#include <linux/overflow.h> #include <linux/soundwire/sdw_registers.h> #include <linux/string_helpers.h> #include <sound/control.h> @@ -21,6 +23,7 @@ #include <sound/soc.h> #include <sound/soc-component.h> #include <sound/soc-dapm.h> +#include <sound/tlv.h> static struct sdca_control *selector_find_control(struct device *dev, struct sdca_entity *entity, @@ -69,6 +72,24 @@ static struct sdca_control_range *selector_find_range(struct device *dev, return control_find_range(dev, entity, control, cols, rows); } +static bool exported_control(struct sdca_entity *entity, struct sdca_control *control) +{ + switch (SDCA_CTL_TYPE(entity->type, control->sel)) { + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE): + return true; + default: + break; + } + + return control->layers & (SDCA_ACCESS_LAYER_USER | + SDCA_ACCESS_LAYER_APPLICATION); +} + +static bool readonly_control(struct sdca_control *control) +{ + return control->has_fixed || control->mode == SDCA_ACCESS_MODE_RO; +} + /** * sdca_asoc_count_component - count the various component parts * @function: Pointer to the Function information. @@ -76,6 +97,8 @@ static struct sdca_control_range *selector_find_range(struct device *dev, * required number of DAPM widgets for the Function. * @num_routes: Output integer pointer, will be filled with the * required number of DAPM routes for the Function. + * @num_controls: Output integer pointer, will be filled with the + * required number of ALSA controls for the Function. * * This function counts various things within the SDCA Function such * that the calling driver can allocate appropriate space before @@ -84,12 +107,13 @@ static struct sdca_control_range *selector_find_range(struct device *dev, * Return: Returns zero on success, and a negative error code on failure. */ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *function, - int *num_widgets, int *num_routes) + int *num_widgets, int *num_routes, int *num_controls) { - int i; + int i, j; *num_widgets = function->num_entities - 1; *num_routes = 0; + *num_controls = 0; for (i = 0; i < function->num_entities - 1; i++) { struct sdca_entity *entity = &function->entities[i]; @@ -100,6 +124,7 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun case SDCA_ENTITY_TYPE_OT: *num_routes += !!entity->iot.clock; *num_routes += !!entity->iot.is_dataport; + *num_controls += !entity->iot.is_dataport; break; case SDCA_ENTITY_TYPE_PDE: *num_routes += entity->pde.num_managed; @@ -113,6 +138,11 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun /* Add primary entity connections from DisCo */ *num_routes += entity->num_sources; + + for (j = 0; j < entity->num_controls; j++) { + if (exported_control(entity, &entity->controls[j])) + (*num_controls)++; + } } return 0; @@ -797,6 +827,212 @@ int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *funct } EXPORT_SYMBOL_NS(sdca_asoc_populate_dapm, "SND_SOC_SDCA"); +static int control_limit_kctl(struct device *dev, + struct sdca_entity *entity, + struct sdca_control *control, + struct snd_kcontrol_new *kctl) +{ + struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value; + struct sdca_control_range *range; + int min, max, step; + unsigned int *tlv; + int shift; + + if (control->type != SDCA_CTL_DATATYPE_Q7P8DB) + return 0; + + /* + * FIXME: For now only handle the simple case of a single linear range + */ + range = control_find_range(dev, entity, control, SDCA_VOLUME_LINEAR_NCOLS, 1); + if (!range) + return -EINVAL; + + min = sdca_range(range, SDCA_VOLUME_LINEAR_MIN, 0); + max = sdca_range(range, SDCA_VOLUME_LINEAR_MAX, 0); + step = sdca_range(range, SDCA_VOLUME_LINEAR_STEP, 0); + + min = sign_extend32(min, control->nbits - 1); + max = sign_extend32(max, control->nbits - 1); + + /* + * FIXME: Only support power of 2 step sizes as this can be supported + * by a simple shift. + */ + if (hweight32(step) != 1) { + dev_err(dev, "%s: %s: currently unsupported step size\n", + entity->label, control->label); + return -EINVAL; + } + + /* + * The SDCA volumes are in steps of 1/256th of a dB, a step down of + * 64 (shift of 6) gives 1/4dB. 1/4dB is the smallest unit that is also + * representable in the ALSA TLVs which are in 1/100ths of a dB. + */ + shift = max(ffs(step) - 1, 6); + + tlv = devm_kcalloc(dev, 4, sizeof(*tlv), GFP_KERNEL); + if (!tlv) + return -ENOMEM; + + tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[1] = 2 * sizeof(*tlv); + tlv[2] = (min * 100) >> 8; + tlv[3] = ((1 << shift) * 100) >> 8; + + mc->min = min >> shift; + mc->max = max >> shift; + mc->shift = shift; + mc->rshift = shift; + mc->sign_bit = 15 - shift; + + kctl->tlv.p = tlv; + kctl->access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + + return 0; +} + +static int populate_control(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct sdca_control *control, + struct snd_kcontrol_new **kctl) +{ + const char *control_suffix = ""; + const char *control_name; + struct soc_mixer_control *mc; + int index = 0; + int ret; + int cn; + + if (!exported_control(entity, control)) + return 0; + + if (control->type == SDCA_CTL_DATATYPE_ONEBIT) + control_suffix = " Switch"; + + control_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s%s", entity->label, + control->label, control_suffix); + if (!control_name) + return -ENOMEM; + + mc = devm_kmalloc(dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + for_each_set_bit(cn, (unsigned long *)&control->cn_list, + BITS_PER_TYPE(control->cn_list)) { + switch (index++) { + case 0: + mc->reg = SDW_SDCA_CTL(function->desc->adr, entity->id, + control->sel, cn); + mc->rreg = mc->reg; + break; + case 1: + mc->rreg = SDW_SDCA_CTL(function->desc->adr, entity->id, + control->sel, cn); + break; + default: + dev_err(dev, "%s: %s: only mono/stereo controls supported\n", + entity->label, control->label); + return -EINVAL; + } + } + + mc->min = 0; + mc->max = clamp((0x1ull << control->nbits) - 1, 0, type_max(mc->max)); + + (*kctl)->name = control_name; + (*kctl)->private_value = (unsigned long)mc; + (*kctl)->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + (*kctl)->info = snd_soc_info_volsw; + (*kctl)->get = snd_soc_get_volsw; + (*kctl)->put = snd_soc_put_volsw; + + if (readonly_control(control)) + (*kctl)->access = SNDRV_CTL_ELEM_ACCESS_READ; + else + (*kctl)->access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + + ret = control_limit_kctl(dev, entity, control, *kctl); + if (ret) + return ret; + + (*kctl)++; + + return 0; +} + +static int populate_pin_switch(struct device *dev, + struct sdca_entity *entity, + struct snd_kcontrol_new **kctl) +{ + const char *control_name; + + control_name = devm_kasprintf(dev, GFP_KERNEL, "%s Switch", entity->label); + if (!control_name) + return -ENOMEM; + + (*kctl)->name = control_name; + (*kctl)->private_value = (unsigned long)entity->label; + (*kctl)->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + (*kctl)->info = snd_soc_dapm_info_pin_switch; + (*kctl)->get = snd_soc_dapm_get_component_pin_switch; + (*kctl)->put = snd_soc_dapm_put_component_pin_switch; + (*kctl)++; + + return 0; +} + +/** + * sdca_asoc_populate_controls - fill in an array of ALSA controls for a Function + * @dev: Pointer to the device against which allocations will be done. + * @function: Pointer to the Function information. + * @route: Array of ALSA controls to be populated. + * + * This function populates an array of ALSA controls from the DisCo + * information for a particular SDCA Function. Typically, + * snd_soc_asoc_count_component will be used to allocate an + * appropriately sized array before calling this function. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_asoc_populate_controls(struct device *dev, + struct sdca_function_data *function, + struct snd_kcontrol_new *kctl) +{ + int i, j; + int ret; + + for (i = 0; i < function->num_entities; i++) { + struct sdca_entity *entity = &function->entities[i]; + + switch (entity->type) { + case SDCA_ENTITY_TYPE_IT: + case SDCA_ENTITY_TYPE_OT: + if (!entity->iot.is_dataport) { + ret = populate_pin_switch(dev, entity, &kctl); + if (ret) + return ret; + } + break; + default: + break; + } + + for (j = 0; j < entity->num_controls; j++) { + ret = populate_control(dev, function, entity, + &entity->controls[j], &kctl); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_NS(sdca_asoc_populate_controls, "SND_SOC_SDCA"); + /** * sdca_asoc_populate_component - fill in a component driver for a Function * @dev: Pointer to the device against which allocations will be done. @@ -815,10 +1051,12 @@ int sdca_asoc_populate_component(struct device *dev, { struct snd_soc_dapm_widget *widgets; struct snd_soc_dapm_route *routes; - int num_widgets, num_routes; + struct snd_kcontrol_new *controls; + int num_widgets, num_routes, num_controls; int ret; - ret = sdca_asoc_count_component(dev, function, &num_widgets, &num_routes); + ret = sdca_asoc_count_component(dev, function, &num_widgets, &num_routes, + &num_controls); if (ret) return ret; @@ -830,14 +1068,24 @@ int sdca_asoc_populate_component(struct device *dev, if (!routes) return -ENOMEM; + controls = devm_kcalloc(dev, num_controls, sizeof(*controls), GFP_KERNEL); + if (!controls) + return -ENOMEM; + ret = sdca_asoc_populate_dapm(dev, function, widgets, routes); if (ret) return ret; + ret = sdca_asoc_populate_controls(dev, function, controls); + if (ret) + return ret; + component_drv->dapm_widgets = widgets; component_drv->num_dapm_widgets = num_widgets; component_drv->dapm_routes = routes; component_drv->num_dapm_routes = num_routes; + component_drv->controls = controls; + component_drv->num_controls = num_controls; return 0; } |