aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound
diff options
context:
space:
mode:
authorCharles Keepax <ckeepax@opensource.cirrus.com>2025-05-16 14:10:11 +0100
committerMark Brown <broonie@kernel.org>2025-05-20 11:15:40 +0100
commit108f878d435437f649da32dea9f0d4f684eba3ba (patch)
treef3250be90be7cd886bd6bba501b991c28ccfd916 /sound
parentASoC: SDCA: Create ALSA controls from DisCo (diff)
downloadwireguard-linux-108f878d435437f649da32dea9f0d4f684eba3ba.tar.xz
wireguard-linux-108f878d435437f649da32dea9f0d4f684eba3ba.zip
ASoC: SDCA: Create DAI drivers from DisCo
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> Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.dev> Link: https://patch.msgid.link/20250516131011.221310-8-ckeepax@opensource.cirrus.com Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/sdca/sdca_asoc.c227
1 files changed, 223 insertions, 4 deletions
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index bf8dbccde526..7bc8f6069f3d 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -22,6 +22,7 @@
#include <sound/sdca_function.h>
#include <sound/soc.h>
#include <sound/soc-component.h>
+#include <sound/soc-dai.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
@@ -99,6 +100,8 @@ static bool readonly_control(struct sdca_control *control)
* 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.
+ * @num_dais: Output integer pointer, will be filled with the
+ * required number of ASoC DAIs for the Function.
*
* This function counts various things within the SDCA Function such
* that the calling driver can allocate appropriate space before
@@ -107,13 +110,15 @@ static bool readonly_control(struct sdca_control *control)
* 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_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];
@@ -125,6 +130,7 @@ int sdca_asoc_count_component(struct device *dev, struct sdca_function_data *fun
*num_routes += !!entity->iot.clock;
*num_routes += !!entity->iot.is_dataport;
*num_controls += !entity->iot.is_dataport;
+ *num_dais += !!entity->iot.is_dataport;
break;
case SDCA_ENTITY_TYPE_PDE:
*num_routes += entity->pde.num_managed;
@@ -1033,6 +1039,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;
+}
+
+/**
+ * sdca_asoc_populate_dais - fill in an array of DAI drivers for a Function
+ * @dev: Pointer to the device against which allocations will be done.
+ * @function: Pointer to the Function information.
+ * @dais: Array of DAI drivers to be populated.
+ * @ops: DAI ops to be attached to each of the created DAI drivers.
+ *
+ * This function populates an array of ASoC DAI drivers 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_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;
+ }
+
+ /* Can't check earlier as only terminals have an iot member. */
+ 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");
+
/**
* sdca_asoc_populate_component - fill in a component driver for a Function
* @dev: Pointer to the device against which allocations will be done.
@@ -1047,16 +1252,19 @@ EXPORT_SYMBOL_NS(sdca_asoc_populate_controls, "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;
@@ -1072,6 +1280,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;
@@ -1080,6 +1292,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;
@@ -1087,6 +1303,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");