diff options
-rw-r--r-- | include/sound/sdca_asoc.h | 30 | ||||
-rw-r--r-- | include/sound/sdca_function.h | 36 | ||||
-rw-r--r-- | sound/soc/sdca/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/sdca/sdca_asoc.c | 844 |
4 files changed, 911 insertions, 1 deletions
diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h new file mode 100644 index 000000000000..414d461b6fc4 --- /dev/null +++ b/include/sound/sdca_asoc.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + * + * Copyright (C) 2025 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __SDCA_ASOC_H__ +#define __SDCA_ASOC_H__ + +struct device; +struct sdca_function_data; +struct snd_soc_component_driver; +struct snd_soc_dapm_route; +struct snd_soc_dapm_widget; + +int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *function, + int *num_widgets, int *num_routes); + +int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *function, + struct snd_soc_dapm_widget *widgets, + struct snd_soc_dapm_route *routes); + +int sdca_asoc_populate_component(struct device *dev, + struct sdca_function_data *function, + struct snd_soc_component_driver *component_drv); + +#endif // __SDCA_ASOC_H__ diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h index e7165e4f15c4..008e23882de8 100644 --- a/include/sound/sdca_function.h +++ b/include/sound/sdca_function.h @@ -258,6 +258,14 @@ enum sdca_pde_controls { }; /** + * enum sdca_requested_ps_range - Column definitions for Requested PS + */ +enum sdca_requested_ps_range { + SDCA_REQUESTED_PS_STATE = 0, + SDCA_REQUESTED_PS_NCOLS = 1, +}; + +/** * enum sdca_ge_controls - SDCA Controls for Group Unit * * Control Selectors for Group Unit from SDCA specification v1.0 @@ -269,6 +277,15 @@ enum sdca_ge_controls { }; /** + * enum sdca_selected_mode_range - Column definitions for Selected Mode + */ +enum sdca_selected_mode_range { + SDCA_SELECTED_MODE_INDEX = 0, + SDCA_SELECTED_MODE_TERM_TYPE = 1, + SDCA_SELECTED_MODE_NCOLS = 2, +}; + +/** * enum sdca_spe_controls - SDCA Controls for Security & Privacy Unit * * Control Selectors for Security & Privacy Unit from SDCA @@ -773,6 +790,25 @@ enum sdca_terminal_type { SDCA_TERM_TYPE_PRIVACY_INDICATORS = 0x747, }; +#define SDCA_TERM_TYPE_LINEIN_STEREO_NAME "LineIn Stereo" +#define SDCA_TERM_TYPE_LINEIN_FRONT_LR_NAME "LineIn Front-LR" +#define SDCA_TERM_TYPE_LINEIN_CENTER_LFE_NAME "LineIn Center-LFE" +#define SDCA_TERM_TYPE_LINEIN_SURROUND_LR_NAME "LineIn Surround-LR" +#define SDCA_TERM_TYPE_LINEIN_REAR_LR_NAME "LineIn Rear-LR" +#define SDCA_TERM_TYPE_LINEOUT_STEREO_NAME "LineOut Stereo" +#define SDCA_TERM_TYPE_LINEOUT_FRONT_LR_NAME "LineOut Front-LR" +#define SDCA_TERM_TYPE_LINEOUT_CENTER_LFE_NAME "LineOut Center-LFE" +#define SDCA_TERM_TYPE_LINEOUT_SURROUND_LR_NAME "LineOut Surround-LR" +#define SDCA_TERM_TYPE_LINEOUT_REAR_LR_NAME "LineOut Rear-LR" +#define SDCA_TERM_TYPE_MIC_JACK_NAME "Microphone" +#define SDCA_TERM_TYPE_STEREO_JACK_NAME "Speaker Stereo" +#define SDCA_TERM_TYPE_FRONT_LR_JACK_NAME "Speaker Front-LR" +#define SDCA_TERM_TYPE_CENTER_LFE_JACK_NAME "Speaker Center-LFE" +#define SDCA_TERM_TYPE_SURROUND_LR_JACK_NAME "Speaker Surround-LR" +#define SDCA_TERM_TYPE_REAR_LR_JACK_NAME "Speaker Rear-LR" +#define SDCA_TERM_TYPE_HEADPHONE_JACK_NAME "Headphone" +#define SDCA_TERM_TYPE_HEADSET_JACK_NAME "Headset" + /** * enum sdca_connector_type - SDCA Connector Types * diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile index dddc3e694256..53344f108ca6 100644 --- a/sound/soc/sdca/Makefile +++ b/sound/soc/sdca/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only -snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_regmap.o +snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_regmap.o sdca_asoc.o obj-$(CONFIG_SND_SOC_SDCA) += snd-soc-sdca.o diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c new file mode 100644 index 000000000000..fe24c897def7 --- /dev/null +++ b/sound/soc/sdca/sdca_asoc.c @@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Cirrus Logic, Inc. and +// Cirrus Logic International Semiconductor Ltd. + +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + */ + +#include <linux/bitmap.h> +#include <linux/delay.h> +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/string_helpers.h> +#include <sound/control.h> +#include <sound/sdca.h> +#include <sound/sdca_asoc.h> +#include <sound/sdca_function.h> +#include <sound/soc.h> +#include <sound/soc-component.h> +#include <sound/soc-dapm.h> + +static struct sdca_control *selector_find_control(struct device *dev, + struct sdca_entity *entity, + const int sel) +{ + int i; + + for (i = 0; i < entity->num_controls; i++) { + struct sdca_control *control = &entity->controls[i]; + + if (control->sel == sel) + return control; + } + + dev_err(dev, "%s: control %#x: missing\n", entity->label, sel); + return NULL; +} + +static struct sdca_control_range *control_find_range(struct device *dev, + struct sdca_entity *entity, + struct sdca_control *control, + int cols, int rows) +{ + struct sdca_control_range *range = &control->range; + + if ((cols && range->cols != cols) || (rows && range->rows != rows) || + !range->data) { + dev_err(dev, "%s: control %#x: ranges invalid (%d,%d)\n", + entity->label, control->sel, range->cols, range->rows); + return NULL; + } + + return range; +} + +static struct sdca_control_range *selector_find_range(struct device *dev, + struct sdca_entity *entity, + int sel, int cols, int rows) +{ + struct sdca_control *control; + + control = selector_find_control(dev, entity, sel); + if (!control) + return NULL; + + return control_find_range(dev, entity, control, cols, rows); +} + +/** + * sdca_asoc_count_component - count the various component parts + * @function: Pointer to the Function information. + * @num_widgets: Output integer pointer, will be filled with the + * 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. + * + * This function counts various things within the SDCA Function such + * that the calling driver can allocate appropriate space before + * calling the appropriate population functions. + * + * 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 i; + + *num_widgets = function->num_entities - 1; + *num_routes = 0; + + for (i = 0; i < function->num_entities - 1; i++) { + struct sdca_entity *entity = &function->entities[i]; + + /* Add supply/DAI widget connections */ + switch (entity->type) { + case SDCA_ENTITY_TYPE_IT: + case SDCA_ENTITY_TYPE_OT: + *num_routes += !!entity->iot.clock; + *num_routes += !!entity->iot.is_dataport; + break; + case SDCA_ENTITY_TYPE_PDE: + *num_routes += entity->pde.num_managed; + break; + default: + break; + } + + if (entity->group) + (*num_routes)++; + + /* Add primary entity connections from DisCo */ + *num_routes += entity->num_sources; + } + + return 0; +} +EXPORT_SYMBOL_NS(sdca_asoc_count_component, "SND_SOC_SDCA"); + +static const char *get_terminal_name(enum sdca_terminal_type type) +{ + switch (type) { + case SDCA_TERM_TYPE_LINEIN_STEREO: + return SDCA_TERM_TYPE_LINEIN_STEREO_NAME; + case SDCA_TERM_TYPE_LINEIN_FRONT_LR: + return SDCA_TERM_TYPE_LINEIN_FRONT_LR_NAME; + case SDCA_TERM_TYPE_LINEIN_CENTER_LFE: + return SDCA_TERM_TYPE_LINEIN_CENTER_LFE_NAME; + case SDCA_TERM_TYPE_LINEIN_SURROUND_LR: + return SDCA_TERM_TYPE_LINEIN_SURROUND_LR_NAME; + case SDCA_TERM_TYPE_LINEIN_REAR_LR: + return SDCA_TERM_TYPE_LINEIN_REAR_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_STEREO: + return SDCA_TERM_TYPE_LINEOUT_STEREO_NAME; + case SDCA_TERM_TYPE_LINEOUT_FRONT_LR: + return SDCA_TERM_TYPE_LINEOUT_FRONT_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_CENTER_LFE: + return SDCA_TERM_TYPE_LINEOUT_CENTER_LFE_NAME; + case SDCA_TERM_TYPE_LINEOUT_SURROUND_LR: + return SDCA_TERM_TYPE_LINEOUT_SURROUND_LR_NAME; + case SDCA_TERM_TYPE_LINEOUT_REAR_LR: + return SDCA_TERM_TYPE_LINEOUT_REAR_LR_NAME; + case SDCA_TERM_TYPE_MIC_JACK: + return SDCA_TERM_TYPE_MIC_JACK_NAME; + case SDCA_TERM_TYPE_STEREO_JACK: + return SDCA_TERM_TYPE_STEREO_JACK_NAME; + case SDCA_TERM_TYPE_FRONT_LR_JACK: + return SDCA_TERM_TYPE_FRONT_LR_JACK_NAME; + case SDCA_TERM_TYPE_CENTER_LFE_JACK: + return SDCA_TERM_TYPE_CENTER_LFE_JACK_NAME; + case SDCA_TERM_TYPE_SURROUND_LR_JACK: + return SDCA_TERM_TYPE_SURROUND_LR_JACK_NAME; + case SDCA_TERM_TYPE_REAR_LR_JACK: + return SDCA_TERM_TYPE_REAR_LR_JACK_NAME; + case SDCA_TERM_TYPE_HEADPHONE_JACK: + return SDCA_TERM_TYPE_HEADPHONE_JACK_NAME; + case SDCA_TERM_TYPE_HEADSET_JACK: + return SDCA_TERM_TYPE_HEADSET_JACK_NAME; + default: + return NULL; + } +} + +static int entity_early_parse_ge(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity) +{ + struct sdca_control_range *range; + struct sdca_control *control; + struct snd_kcontrol_new *kctl; + struct soc_enum *soc_enum; + const char *control_name; + unsigned int *values; + const char **texts; + int i; + + control = selector_find_control(dev, entity, SDCA_CTL_GE_SELECTED_MODE); + if (!control) + return -EINVAL; + + if (control->layers != SDCA_ACCESS_LAYER_CLASS) + dev_warn(dev, "%s: unexpected access layer: %x\n", + entity->label, control->layers); + + range = control_find_range(dev, entity, control, SDCA_SELECTED_MODE_NCOLS, 0); + if (!range) + return -EINVAL; + + control_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + entity->label, control->label); + if (!control_name) + return -ENOMEM; + + kctl = devm_kmalloc(dev, sizeof(*kctl), GFP_KERNEL); + if (!kctl) + return -ENOMEM; + + soc_enum = devm_kmalloc(dev, sizeof(*soc_enum), GFP_KERNEL); + if (!soc_enum) + return -ENOMEM; + + texts = devm_kcalloc(dev, range->rows + 3, sizeof(*texts), GFP_KERNEL); + if (!texts) + return -ENOMEM; + + values = devm_kcalloc(dev, range->rows + 3, sizeof(*values), GFP_KERNEL); + if (!values) + return -ENOMEM; + + texts[0] = "No Jack"; + texts[1] = "Jack Unknown"; + texts[2] = "Detection in Progress"; + values[0] = 0; + values[1] = 1; + values[2] = 2; + for (i = 0; i < range->rows; i++) { + enum sdca_terminal_type type; + + type = sdca_range(range, SDCA_SELECTED_MODE_TERM_TYPE, i); + + values[i + 3] = sdca_range(range, SDCA_SELECTED_MODE_INDEX, i); + texts[i + 3] = get_terminal_name(type); + if (!texts[i + 3]) { + dev_err(dev, "%s: unrecognised terminal type: %#x\n", + entity->label, type); + return -EINVAL; + } + } + + soc_enum->reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0); + soc_enum->items = range->rows + 3; + soc_enum->mask = roundup_pow_of_two(soc_enum->items) - 1; + soc_enum->texts = texts; + soc_enum->values = values; + + kctl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kctl->name = control_name; + kctl->info = snd_soc_info_enum_double; + kctl->get = snd_soc_dapm_get_enum_double; + kctl->put = snd_soc_dapm_put_enum_double; + kctl->private_value = (unsigned long)soc_enum; + + entity->ge.kctl = kctl; + + return 0; +} + +static void add_route(struct snd_soc_dapm_route **route, const char *sink, + const char *control, const char *source) +{ + (*route)->sink = sink; + (*route)->control = control; + (*route)->source = source; + (*route)++; +} + +static int entity_parse_simple(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route, + enum snd_soc_dapm_type id) +{ + int i; + + (*widget)->id = id; + (*widget)++; + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, NULL, entity->sources[i]->label); + + return 0; +} + +static int entity_parse_it(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + int i; + + if (entity->iot.is_dataport) { + const char *aif_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + entity->label, "Playback"); + if (!aif_name) + return -ENOMEM; + + (*widget)->id = snd_soc_dapm_aif_in; + + add_route(route, entity->label, NULL, aif_name); + } else { + (*widget)->id = snd_soc_dapm_mic; + } + + if (entity->iot.clock) + add_route(route, entity->label, NULL, entity->iot.clock->label); + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, NULL, entity->sources[i]->label); + + (*widget)++; + + return 0; +} + +static int entity_parse_ot(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + int i; + + if (entity->iot.is_dataport) { + const char *aif_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s", + entity->label, "Capture"); + if (!aif_name) + return -ENOMEM; + + (*widget)->id = snd_soc_dapm_aif_out; + + add_route(route, aif_name, NULL, entity->label); + } else { + (*widget)->id = snd_soc_dapm_spk; + } + + if (entity->iot.clock) + add_route(route, entity->label, NULL, entity->iot.clock->label); + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, NULL, entity->sources[i]->label); + + (*widget)++; + + return 0; +} + +static int entity_pde_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_component *component = widget->dapm->component; + struct sdca_entity *entity = widget->priv; + static const int polls = 100; + unsigned int reg, val; + int from, to, i; + int poll_us; + int ret; + + if (!component) + return -EIO; + + switch (event) { + case SND_SOC_DAPM_POST_PMD: + from = widget->on_val; + to = widget->off_val; + break; + case SND_SOC_DAPM_POST_PMU: + from = widget->off_val; + to = widget->on_val; + break; + } + + for (i = 0; i < entity->pde.num_max_delay; i++) { + struct sdca_pde_delay *delay = &entity->pde.max_delay[i]; + + if (delay->from_ps == from && delay->to_ps == to) { + poll_us = delay->us / polls; + break; + } + } + + reg = SDW_SDCA_CTL(SDW_SDCA_CTL_FUNC(widget->reg), + SDW_SDCA_CTL_ENT(widget->reg), + SDCA_CTL_PDE_ACTUAL_PS, 0); + + for (i = 0; i < polls; i++) { + if (i) + fsleep(poll_us); + + ret = regmap_read(component->regmap, reg, &val); + if (ret) + return ret; + else if (val == to) + return 0; + } + + dev_err(component->dev, "%s: power transition failed: %x\n", + entity->label, val); + return -ETIMEDOUT; +} + +static int entity_parse_pde(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + unsigned int target = (1 << SDCA_PDE_PS0) | (1 << SDCA_PDE_PS3); + struct sdca_control_range *range; + struct sdca_control *control; + unsigned int mask = 0; + int i; + + control = selector_find_control(dev, entity, SDCA_CTL_PDE_REQUESTED_PS); + if (!control) + return -EINVAL; + + /* Power should only be controlled by the driver */ + if (control->layers != SDCA_ACCESS_LAYER_CLASS) + dev_warn(dev, "%s: unexpected access layer: %x\n", + entity->label, control->layers); + + range = control_find_range(dev, entity, control, SDCA_REQUESTED_PS_NCOLS, 0); + if (!range) + return -EINVAL; + + for (i = 0; i < range->rows; i++) + mask |= 1 << sdca_range(range, SDCA_REQUESTED_PS_STATE, i); + + if ((mask & target) != target) { + dev_err(dev, "%s: power control missing states\n", entity->label); + return -EINVAL; + } + + (*widget)->id = snd_soc_dapm_supply; + (*widget)->reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0); + (*widget)->mask = GENMASK(control->nbits - 1, 0); + (*widget)->on_val = SDCA_PDE_PS0; + (*widget)->off_val = SDCA_PDE_PS3; + (*widget)->event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD; + (*widget)->event = entity_pde_event; + (*widget)->priv = entity; + (*widget)++; + + for (i = 0; i < entity->pde.num_managed; i++) + add_route(route, entity->pde.managed[i]->label, NULL, entity->label); + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, NULL, entity->sources[i]->label); + + return 0; +} + +/* Device selector units are controlled through a group entity */ +static int entity_parse_su_device(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + struct sdca_control_range *range; + int num_routes = 0; + int i, j; + + if (!entity->group) { + dev_err(dev, "%s: device selector unit missing group\n", entity->label); + return -EINVAL; + } + + range = selector_find_range(dev, entity->group, SDCA_CTL_GE_SELECTED_MODE, + SDCA_SELECTED_MODE_NCOLS, 0); + if (!range) + return -EINVAL; + + (*widget)->id = snd_soc_dapm_mux; + (*widget)->kcontrol_news = entity->group->ge.kctl; + (*widget)->num_kcontrols = 1; + (*widget)++; + + for (i = 0; i < entity->group->ge.num_modes; i++) { + struct sdca_ge_mode *mode = &entity->group->ge.modes[i]; + + for (j = 0; j < mode->num_controls; j++) { + struct sdca_ge_control *affected = &mode->controls[j]; + int term; + + if (affected->id != entity->id || + affected->sel != SDCA_CTL_SU_SELECTOR || + !affected->val) + continue; + + if (affected->val - 1 >= entity->num_sources) { + dev_err(dev, "%s: bad control value: %#x\n", + entity->label, affected->val); + return -EINVAL; + } + + if (++num_routes > entity->num_sources) { + dev_err(dev, "%s: too many input routes\n", entity->label); + return -EINVAL; + } + + term = sdca_range_search(range, SDCA_SELECTED_MODE_INDEX, + mode->val, SDCA_SELECTED_MODE_TERM_TYPE); + if (!term) { + dev_err(dev, "%s: mode not found: %#x\n", + entity->label, mode->val); + return -EINVAL; + } + + add_route(route, entity->label, get_terminal_name(term), + entity->sources[affected->val - 1]->label); + } + } + + return 0; +} + +/* Class selector units will be exported as an ALSA control */ +static int entity_parse_su_class(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct sdca_control *control, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + struct snd_kcontrol_new *kctl; + struct soc_enum *soc_enum; + const char **texts; + int i; + + kctl = devm_kmalloc(dev, sizeof(*kctl), GFP_KERNEL); + if (!kctl) + return -ENOMEM; + + soc_enum = devm_kmalloc(dev, sizeof(*soc_enum), GFP_KERNEL); + if (!soc_enum) + return -ENOMEM; + + texts = devm_kcalloc(dev, entity->num_sources + 1, sizeof(*texts), GFP_KERNEL); + if (!texts) + return -ENOMEM; + + texts[0] = "No Signal"; + for (i = 0; i < entity->num_sources; i++) + texts[i + 1] = entity->sources[i]->label; + + soc_enum->reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, 0); + soc_enum->items = entity->num_sources + 1; + soc_enum->mask = roundup_pow_of_two(soc_enum->items) - 1; + soc_enum->texts = texts; + + kctl->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kctl->name = "Route"; + kctl->info = snd_soc_info_enum_double; + kctl->get = snd_soc_dapm_get_enum_double; + kctl->put = snd_soc_dapm_put_enum_double; + kctl->private_value = (unsigned long)soc_enum; + + (*widget)->id = snd_soc_dapm_mux; + (*widget)->kcontrol_news = kctl; + (*widget)->num_kcontrols = 1; + (*widget)++; + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, texts[i + 1], entity->sources[i]->label); + + return 0; +} + +static int entity_parse_su(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + struct sdca_control *control; + + if (!entity->num_sources) { + dev_err(dev, "%s: selector with no inputs\n", entity->label); + return -EINVAL; + } + + control = selector_find_control(dev, entity, SDCA_CTL_SU_SELECTOR); + if (!control) + return -EINVAL; + + if (control->layers == SDCA_ACCESS_LAYER_DEVICE) + return entity_parse_su_device(dev, function, entity, widget, route); + + if (control->layers != SDCA_ACCESS_LAYER_CLASS) + dev_warn(dev, "%s: unexpected access layer: %x\n", + entity->label, control->layers); + + return entity_parse_su_class(dev, function, entity, control, widget, route); +} + +static int entity_parse_mu(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + struct sdca_control *control; + struct snd_kcontrol_new *kctl; + int cn; + int i; + + if (!entity->num_sources) { + dev_err(dev, "%s: selector 1 or more inputs\n", entity->label); + return -EINVAL; + } + + control = selector_find_control(dev, entity, SDCA_CTL_MU_MIXER); + if (!control) + return -EINVAL; + + /* MU control should be through DAPM */ + if (control->layers != SDCA_ACCESS_LAYER_CLASS) + dev_warn(dev, "%s: unexpected access layer: %x\n", + entity->label, control->layers); + + if (entity->num_sources != hweight64(control->cn_list)) { + dev_err(dev, "%s: mismatched control and sources\n", entity->label); + return -EINVAL; + } + + kctl = devm_kcalloc(dev, entity->num_sources, sizeof(*kctl), GFP_KERNEL); + if (!kctl) + return -ENOMEM; + + i = 0; + for_each_set_bit(cn, (unsigned long *)&control->cn_list, + BITS_PER_TYPE(control->cn_list)) { + const char *control_name; + struct soc_mixer_control *mc; + + control_name = devm_kasprintf(dev, GFP_KERNEL, "%s %d", + control->label, i + 1); + if (!control_name) + return -ENOMEM; + + mc = devm_kmalloc(dev, sizeof(*mc), GFP_KERNEL); + if (!mc) + return -ENOMEM; + + mc->reg = SND_SOC_NOPM; + mc->rreg = SND_SOC_NOPM; + mc->invert = 1; // Ensure default is connected + mc->min = 0; + mc->max = 1; + + kctl[i].name = control_name; + kctl[i].private_value = (unsigned long)mc; + kctl[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kctl[i].info = snd_soc_info_volsw; + kctl[i].get = snd_soc_dapm_get_volsw; + kctl[i].put = snd_soc_dapm_put_volsw; + i++; + } + + (*widget)->id = snd_soc_dapm_mixer; + (*widget)->kcontrol_news = kctl; + (*widget)->num_kcontrols = entity->num_sources; + (*widget)++; + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, kctl[i].name, entity->sources[i]->label); + + return 0; +} + +static int entity_cs_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_component *component = widget->dapm->component; + struct sdca_entity *entity = widget->priv; + + if (!component) + return -EIO; + + if (entity->cs.max_delay) + fsleep(entity->cs.max_delay); + + return 0; +} + +static int entity_parse_cs(struct device *dev, + struct sdca_function_data *function, + struct sdca_entity *entity, + struct snd_soc_dapm_widget **widget, + struct snd_soc_dapm_route **route) +{ + int i; + + (*widget)->id = snd_soc_dapm_supply; + (*widget)->subseq = 1; /* Ensure these run after PDEs */ + (*widget)->event_flags = SND_SOC_DAPM_POST_PMU; + (*widget)->event = entity_cs_event; + (*widget)->priv = entity; + (*widget)++; + + for (i = 0; i < entity->num_sources; i++) + add_route(route, entity->label, NULL, entity->sources[i]->label); + + return 0; +} + +/** + * sdca_asoc_populate_dapm - fill in arrays of DAPM widgets and routes + * @dev: Pointer to the device against which allocations will be done. + * @function: Pointer to the Function information. + * @widget: Array of DAPM widgets to be populated. + * @route: Array of DAPM routes to be populated. + * + * This function populates arrays of DAPM widgets and routes from the + * DisCo information for a particular SDCA Function. Typically, + * snd_soc_asoc_count_component will be used to allocate appropriately + * sized arrays before calling this function. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *function, + struct snd_soc_dapm_widget *widget, + struct snd_soc_dapm_route *route) +{ + int ret; + int i; + + for (i = 0; i < function->num_entities - 1; i++) { + struct sdca_entity *entity = &function->entities[i]; + + /* + * Some entities need to add controls "early" as they are + * referenced by other entities. + */ + switch (entity->type) { + case SDCA_ENTITY_TYPE_GE: + ret = entity_early_parse_ge(dev, function, entity); + if (ret) + return ret; + break; + default: + break; + } + } + + for (i = 0; i < function->num_entities - 1; i++) { + struct sdca_entity *entity = &function->entities[i]; + + widget->name = entity->label; + widget->reg = SND_SOC_NOPM; + + switch (entity->type) { + case SDCA_ENTITY_TYPE_IT: + ret = entity_parse_it(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_OT: + ret = entity_parse_ot(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_PDE: + ret = entity_parse_pde(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_SU: + ret = entity_parse_su(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_MU: + ret = entity_parse_mu(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_CS: + ret = entity_parse_cs(dev, function, entity, &widget, &route); + break; + case SDCA_ENTITY_TYPE_CX: + /* + * FIXME: For now we will just treat these as a supply, + * meaning all options are enabled. + */ + dev_warn(dev, "%s: clock selectors not fully supported yet\n", + entity->label); + ret = entity_parse_simple(dev, function, entity, &widget, + &route, snd_soc_dapm_supply); + break; + case SDCA_ENTITY_TYPE_TG: + ret = entity_parse_simple(dev, function, entity, &widget, + &route, snd_soc_dapm_siggen); + break; + case SDCA_ENTITY_TYPE_GE: + ret = entity_parse_simple(dev, function, entity, &widget, + &route, snd_soc_dapm_supply); + break; + default: + ret = entity_parse_simple(dev, function, entity, &widget, + &route, snd_soc_dapm_pga); + break; + } + if (ret) + return ret; + + if (entity->group) + add_route(&route, entity->label, NULL, entity->group->label); + } + + return 0; +} +EXPORT_SYMBOL_NS(sdca_asoc_populate_dapm, "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. + * @function: Pointer to the Function information. + * @copmonent_drv: Pointer to the component driver to be populated. + * + * This function populates a snd_soc_component_driver structure based + * on the DisCo information for a particular SDCA Function. It does + * all allocation internally. + * + * Return: Returns zero on success, and a negative error code on failure. + */ +int sdca_asoc_populate_component(struct device *dev, + struct sdca_function_data *function, + struct snd_soc_component_driver *component_drv) +{ + struct snd_soc_dapm_widget *widgets; + struct snd_soc_dapm_route *routes; + int num_widgets, num_routes; + int ret; + + ret = sdca_asoc_count_component(dev, function, &num_widgets, &num_routes); + if (ret) + return ret; + + widgets = devm_kcalloc(dev, num_widgets, sizeof(*widgets), GFP_KERNEL); + if (!widgets) + return -ENOMEM; + + routes = devm_kcalloc(dev, num_routes, sizeof(*routes), GFP_KERNEL); + if (!routes) + return -ENOMEM; + + ret = sdca_asoc_populate_dapm(dev, function, widgets, routes); + 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; + + return 0; +} +EXPORT_SYMBOL_NS(sdca_asoc_populate_component, "SND_SOC_SDCA"); |