[PATCH 3/3] ASoC: SDCA: Create DAI drivers from DisCo

Charles Keepax
2025-03-21T16:39:57Z

Use the previously parsed DisCo information from ACPI to create the DAI
drivers required to connect an SDCA Function into an ASoC soundcard.

Create DAI driver structures and populate the supported sample rates
and sample widths into them based on the Input/Output Terminal and any
attach Clock Source entities. More complex relationships with channels
etc. will be added later as constraints as part of the DAI startup.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---
include/sound/sdca_asoc.h | 12 +-
include/sound/sdca_function.h | 23 ++++
sound/soc/sdca/sdca_asoc.c | 209 +++++++++++++++++++++++++++++++++-
3 files changed, 238 insertions(+), 6 deletions(-)

diff --git a/include/sound/sdca_asoc.h b/include/sound/sdca_asoc.h
index d19e7e969283a..9121531f08260 100644
--- a/include/sound/sdca_asoc.h
+++ b/include/sound/sdca_asoc.h
@@ -14,11 +14,14 @@ struct device;
struct sdca_function_data;
struct snd_kcontrol_new;
struct snd_soc_component_driver;
+struct snd_soc_dai_driver;
+struct snd_soc_dai_ops;
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 *num_controls);
+ int *num_widgets, int *num_routes, int *num_controls,
+ int *num_dais);

int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *function,
struct snd_soc_dapm_widget *widgets,
@@ -26,9 +29,14 @@ int sdca_asoc_populate_dapm(struct device *dev, struct sdca_function_data *funct
int sdca_asoc_populate_controls(struct device *dev,
struct sdca_function_data *function,
struct snd_kcontrol_new *kctl);
+int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *function,
+ struct snd_soc_dai_driver *dais,
+ const struct snd_soc_dai_ops *ops);

int sdca_asoc_populate_component(struct device *dev,
struct sdca_function_data *function,
- struct snd_soc_component_driver *component_drv);
+ struct snd_soc_component_driver *component_drv,
+ struct snd_soc_dai_driver **dai_drv, int *num_dai_drv,
+ const struct snd_soc_dai_ops *ops);

#endif // __SDCA_ASOC_H__
diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h
index 358caacc28764..c995cf51a9f21 100644
--- a/include/sound/sdca_function.h
+++ b/include/sound/sdca_function.h
@@ -168,6 +168,20 @@ enum sdca_ot_controls {
SDCA_CTL_OT_NDAI_PACKETTYPE = 0x17,
};

+/**
+ * enum sdca_usage_range - Column definitions for Usage
+ */
+enum sdca_usage_range {
+ SDCA_USAGE_NUMBER = 0,
+ SDCA_USAGE_CBN = 1,
+ SDCA_USAGE_SAMPLE_RATE = 2,
+ SDCA_USAGE_SAMPLE_WIDTH = 3,
+ SDCA_USAGE_FULL_SCALE = 4,
+ SDCA_USAGE_NOISE_FLOOR = 5,
+ SDCA_USAGE_TAG = 6,
+ SDCA_USAGE_NCOLS = 7,
+};
+
/**
* enum sdca_mu_controls - SDCA Controls for Mixer Unit
*
@@ -246,6 +260,15 @@ enum sdca_cs_controls {
SDCA_CTL_CS_SAMPLERATEINDEX = 0x10,
};

+/**
+ * enum sdca_samplerateindex_range - Column definitions for SampleRateIndex
+ */
+enum sdca_samplerateindex_range {
+ SDCA_SAMPLERATEINDEX_INDEX = 0,
+ SDCA_SAMPLERATEINDEX_RATE = 1,
+ SDCA_SAMPLERATEINDEX_NCOLS = 2,
+};
+
/**
* enum sdca_cx_controls - SDCA Controls for Clock Selector
*
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index aced9cc27a520..67a182a7616ef 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -81,13 +81,15 @@ static bool exported_control(struct sdca_control *control)
}

int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *function,
- int *num_widgets, int *num_routes, int *num_controls)
+ int *num_widgets, int *num_routes, int *num_controls,
+ int *num_dais)
{
int i, j;

*num_widgets = function->num_entities - 1;
*num_routes = 0;
*num_controls = 0;
+ *num_dais = 0;

for (i = 0; i < function->num_entities - 1; i++) {
struct sdca_entity *entity = &function->entities[i];
@@ -97,6 +99,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_dais += !!entity->iot.is_dataport;
break;
case SDCA_ENTITY_TYPE_PDE:
*num_routes += entity->pde.num_managed;
@@ -932,18 +935,205 @@ int sdca_asoc_populate_controls(struct device *dev,
}
EXPORT_SYMBOL_NS(sdca_asoc_populate_controls, "SND_SOC_SDCA");

+static unsigned int rate_find_mask(unsigned int rate)
+{
+ switch (rate) {
+ case 0:
+ return SNDRV_PCM_RATE_8000_768000;
+ case 5512:
+ return SNDRV_PCM_RATE_5512;
+ case 8000:
+ return SNDRV_PCM_RATE_8000;
+ case 11025:
+ return SNDRV_PCM_RATE_11025;
+ case 16000:
+ return SNDRV_PCM_RATE_16000;
+ case 22050:
+ return SNDRV_PCM_RATE_22050;
+ case 32000:
+ return SNDRV_PCM_RATE_32000;
+ case 44100:
+ return SNDRV_PCM_RATE_44100;
+ case 48000:
+ return SNDRV_PCM_RATE_48000;
+ case 64000:
+ return SNDRV_PCM_RATE_64000;
+ case 88200:
+ return SNDRV_PCM_RATE_88200;
+ case 96000:
+ return SNDRV_PCM_RATE_96000;
+ case 176400:
+ return SNDRV_PCM_RATE_176400;
+ case 192000:
+ return SNDRV_PCM_RATE_192000;
+ case 352800:
+ return SNDRV_PCM_RATE_352800;
+ case 384000:
+ return SNDRV_PCM_RATE_384000;
+ case 705600:
+ return SNDRV_PCM_RATE_705600;
+ case 768000:
+ return SNDRV_PCM_RATE_768000;
+ case 12000:
+ return SNDRV_PCM_RATE_12000;
+ case 24000:
+ return SNDRV_PCM_RATE_24000;
+ case 128000:
+ return SNDRV_PCM_RATE_128000;
+ default:
+ return 0;
+ }
+}
+
+static u64 width_find_mask(unsigned int bits)
+{
+ switch (bits) {
+ case 0:
+ return SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE;
+ case 8:
+ return SNDRV_PCM_FMTBIT_S8;
+ case 16:
+ return SNDRV_PCM_FMTBIT_S16_LE;
+ case 20:
+ return SNDRV_PCM_FMTBIT_S20_LE;
+ case 24:
+ return SNDRV_PCM_FMTBIT_S24_LE;
+ case 32:
+ return SNDRV_PCM_FMTBIT_S32_LE;
+ default:
+ return 0;
+ }
+}
+
+static int populate_rate_format(struct device *dev,
+ struct sdca_function_data *function,
+ struct sdca_entity *entity,
+ struct snd_soc_pcm_stream *stream)
+{
+ struct sdca_control_range *range;
+ unsigned int sample_rate, sample_width;
+ unsigned int clock_rates = 0;
+ unsigned int rates = 0;
+ u64 formats = 0;
+ int sel, i;
+
+ switch (entity->type) {
+ case SDCA_ENTITY_TYPE_IT:
+ sel = SDCA_CTL_IT_USAGE;
+ break;
+ case SDCA_ENTITY_TYPE_OT:
+ sel = SDCA_CTL_OT_USAGE;
+ break;
+ default:
+ dev_err(dev, "%s: entity type has no usage control\n",
+ entity->label);
+ return -EINVAL;
+ }
+
+ if (entity->iot.clock) {
+ range = selector_find_range(dev, entity->iot.clock,
+ SDCA_CTL_CS_SAMPLERATEINDEX,
+ SDCA_SAMPLERATEINDEX_NCOLS, 0);
+ if (!range)
+ return -EINVAL;
+
+ for (i = 0; i < range->rows; i++) {
+ sample_rate = sdca_range(range, SDCA_SAMPLERATEINDEX_RATE, i);
+ clock_rates |= rate_find_mask(sample_rate);
+ }
+ } else {
+ clock_rates = UINT_MAX;
+ }
+
+ range = selector_find_range(dev, entity, sel, SDCA_USAGE_NCOLS, 0);
+ if (!range)
+ return -EINVAL;
+
+ for (i = 0; i < range->rows; i++) {
+ sample_rate = sdca_range(range, SDCA_USAGE_SAMPLE_RATE, i);
+ sample_rate = rate_find_mask(sample_rate);
+
+ if (sample_rate & clock_rates) {
+ rates |= sample_rate;
+
+ sample_width = sdca_range(range, SDCA_USAGE_SAMPLE_WIDTH, i);
+ formats |= width_find_mask(sample_width);
+ }
+ }
+
+ stream->formats = formats;
+ stream->rates = rates;
+
+ return 0;
+}
+
+int sdca_asoc_populate_dais(struct device *dev, struct sdca_function_data *function,
+ struct snd_soc_dai_driver *dais,
+ const struct snd_soc_dai_ops *ops)
+{
+ int i, j;
+ int ret;
+
+ for (i = 0, j = 0; i < function->num_entities - 1; i++) {
+ struct sdca_entity *entity = &function->entities[i];
+ struct snd_soc_pcm_stream *stream;
+ const char *stream_suffix;
+
+ switch (entity->type) {
+ case SDCA_ENTITY_TYPE_IT:
+ stream = &dais[j].playback;
+ stream_suffix = "Playback";
+ break;
+ case SDCA_ENTITY_TYPE_OT:
+ stream = &dais[j].capture;
+ stream_suffix = "Capture";
+ break;
+ default:
+ continue;
+ }
+
+ if (!entity->iot.is_dataport)
+ continue;
+
+ stream->stream_name = devm_kasprintf(dev, GFP_KERNEL, "%s %s",
+ entity->label, stream_suffix);
+ if (!stream->stream_name)
+ return -ENOMEM;
+ /* Channels will be further limited by constraints */
+ stream->channels_min = 1;
+ stream->channels_max = SDCA_MAX_CHANNEL_COUNT;
+
+ ret = populate_rate_format(dev, function, entity, stream);
+ if (ret)
+ return ret;
+
+ dais[j].id = i;
+ dais[j].name = entity->label;
+ dais[j].ops = ops;
+ j++;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(sdca_asoc_populate_dais, "SND_SOC_SDCA");
+
int sdca_asoc_populate_component(struct device *dev,
struct sdca_function_data *function,
- struct snd_soc_component_driver *component_drv)
+ struct snd_soc_component_driver *component_drv,
+ struct snd_soc_dai_driver **dai_drv, int *num_dai_drv,
+ const struct snd_soc_dai_ops *ops)
{
struct snd_soc_dapm_widget *widgets;
struct snd_soc_dapm_route *routes;
struct snd_kcontrol_new *controls;
- int num_widgets, num_routes, num_controls;
+ struct snd_soc_dai_driver *dais;
+ int num_widgets, num_routes, num_controls, num_dais;
int ret;

ret = sdca_asoc_count_component(dev, function, &num_widgets, &num_routes,
- &num_controls);
+ &num_controls, &num_dais);
if (ret)
return ret;

@@ -959,6 +1149,10 @@ int sdca_asoc_populate_component(struct device *dev,
if (!controls)
return -ENOMEM;

+ dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
+ if (!dais)
+ return -ENOMEM;
+
ret = sdca_asoc_populate_dapm(dev, function, widgets, routes);
if (ret)
return ret;
@@ -967,6 +1161,10 @@ int sdca_asoc_populate_component(struct device *dev,
if (ret)
return ret;

+ ret = sdca_asoc_populate_dais(dev, function, dais, ops);
+ if (ret)
+ return ret;
+
component_drv->dapm_widgets = widgets;
component_drv->num_dapm_widgets = num_widgets;
component_drv->dapm_routes = routes;
@@ -974,6 +1172,9 @@ int sdca_asoc_populate_component(struct device *dev,
component_drv->controls = controls;
component_drv->num_controls = num_controls;

+ *dai_drv = dais;
+ *num_dai_drv = num_dais;
+
return 0;
}
EXPORT_SYMBOL_NS(sdca_asoc_populate_component, "SND_SOC_SDCA");
--
2.39.5