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