1// SPDX-License-Identifier: GPL-2.0-only
2// This file incorporates work covered by the following copyright notice:
3// Copyright (c) 2024 Intel Corporation
4// Copyright (c) 2024 Advanced Micro Devices, Inc.
5
6/*
7 * soc_sdw_bridge_cs35l56 - codec helper functions for handling CS35L56 Smart AMP
8 */
9
10#include <linux/module.h>
11#include <linux/platform_device.h>
12#include <sound/core.h>
13#include <sound/pcm.h>
14#include <sound/pcm_params.h>
15#include <sound/soc.h>
16#include <sound/soc-acpi.h>
17#include <sound/soc_sdw_utils.h>
18
19static const struct snd_soc_dapm_widget bridge_widgets[] = {
20 SND_SOC_DAPM_SPK("Bridge Speaker", NULL),
21};
22
23static const struct snd_soc_dapm_route bridge_map[] = {
24 {"Bridge Speaker", NULL, "AMPL SPK"},
25 {"Bridge Speaker", NULL, "AMPR SPK"},
26};
27
28static const char * const bridge_cs35l56_name_prefixes[] = {
29 "AMPL",
30 "AMPR",
31};
32
33static int asoc_sdw_bridge_cs35l56_asp_init(struct snd_soc_pcm_runtime *rtd)
34{
35 struct snd_soc_card *card = rtd->card;
36 struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
37 int i, ret;
38 unsigned int rx_mask = 3; // ASP RX1, RX2
39 unsigned int tx_mask = 3; // ASP TX1, TX2
40 struct snd_soc_dai *codec_dai;
41 struct snd_soc_dai *cpu_dai;
42
43 card->components = devm_kasprintf(dev: card->dev, GFP_KERNEL,
44 fmt: "%s spk:cs35l56-bridge",
45 card->components);
46 if (!card->components)
47 return -ENOMEM;
48
49 ret = snd_soc_dapm_new_controls(dapm, widget: bridge_widgets,
50 ARRAY_SIZE(bridge_widgets));
51 if (ret) {
52 dev_err(card->dev, "widgets addition failed: %d\n", ret);
53 return ret;
54 }
55
56 ret = snd_soc_dapm_add_routes(dapm, route: bridge_map, ARRAY_SIZE(bridge_map));
57 if (ret) {
58 dev_err(card->dev, "map addition failed: %d\n", ret);
59 return ret;
60 }
61
62 /* 4 x 16-bit sample slots and FSYNC=48000, BCLK=3.072 MHz */
63 for_each_rtd_codec_dais(rtd, i, codec_dai) {
64 ret = asoc_sdw_cs35l56_volume_limit(card, name_prefix: codec_dai->component->name_prefix);
65 if (ret)
66 return ret;
67
68 ret = snd_soc_dai_set_tdm_slot(dai: codec_dai, tx_mask, rx_mask, slots: 4, slot_width: 16);
69 if (ret < 0)
70 return ret;
71
72 ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: 3072000, SND_SOC_CLOCK_IN);
73 if (ret < 0)
74 return ret;
75 }
76
77 for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
78 ret = snd_soc_dai_set_tdm_slot(dai: cpu_dai, tx_mask, rx_mask, slots: 4, slot_width: 16);
79 if (ret < 0)
80 return ret;
81 }
82
83 return 0;
84}
85
86static const struct snd_soc_pcm_stream asoc_sdw_bridge_params = {
87 .formats = SNDRV_PCM_FMTBIT_S16_LE,
88 .rate_min = 48000,
89 .rate_max = 48000,
90 .channels_min = 2,
91 .channels_max = 2,
92};
93
94SND_SOC_DAILINK_DEFS(asoc_sdw_bridge_dai,
95 DAILINK_COMP_ARRAY(COMP_CODEC("cs42l43-codec", "cs42l43-asp")),
96 DAILINK_COMP_ARRAY(COMP_CODEC("spi-cs35l56-left", "cs35l56-asp1"),
97 COMP_CODEC("spi-cs35l56-right", "cs35l56-asp1")),
98 DAILINK_COMP_ARRAY(COMP_PLATFORM("cs42l43-codec")));
99
100static const struct snd_soc_dai_link bridge_dai_template = {
101 .name = "cs42l43-cs35l56",
102 .init = asoc_sdw_bridge_cs35l56_asp_init,
103 .c2c_params = &asoc_sdw_bridge_params,
104 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBC_CFC,
105 SND_SOC_DAILINK_REG(asoc_sdw_bridge_dai),
106};
107
108int asoc_sdw_bridge_cs35l56_count_sidecar(struct snd_soc_card *card,
109 int *num_dais, int *num_devs)
110{
111 struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
112
113 if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) {
114 (*num_dais)++;
115 (*num_devs) += ARRAY_SIZE(bridge_cs35l56_name_prefixes);
116 }
117
118 return 0;
119}
120EXPORT_SYMBOL_NS(asoc_sdw_bridge_cs35l56_count_sidecar, "SND_SOC_SDW_UTILS");
121
122int asoc_sdw_bridge_cs35l56_add_sidecar(struct snd_soc_card *card,
123 struct snd_soc_dai_link **dai_links,
124 struct snd_soc_codec_conf **codec_conf)
125{
126 struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
127
128 if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS) {
129 **dai_links = bridge_dai_template;
130
131 for (int i = 0; i < ARRAY_SIZE(bridge_cs35l56_name_prefixes); i++) {
132 (*codec_conf)->dlc.name = (*dai_links)->codecs[i].name;
133 (*codec_conf)->name_prefix = bridge_cs35l56_name_prefixes[i];
134 (*codec_conf)++;
135 }
136
137 (*dai_links)++;
138 }
139
140 return 0;
141}
142EXPORT_SYMBOL_NS(asoc_sdw_bridge_cs35l56_add_sidecar, "SND_SOC_SDW_UTILS");
143
144int asoc_sdw_bridge_cs35l56_spk_init(struct snd_soc_card *card,
145 struct snd_soc_dai_link *dai_links,
146 struct asoc_sdw_codec_info *info,
147 bool playback)
148{
149 struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
150
151 if (ctx->mc_quirk & SOC_SDW_SIDECAR_AMPS)
152 info->amp_num += ARRAY_SIZE(bridge_cs35l56_name_prefixes);
153
154 return 0;
155}
156EXPORT_SYMBOL_NS(asoc_sdw_bridge_cs35l56_spk_init, "SND_SOC_SDW_UTILS");
157

source code of linux/sound/soc/sdw_utils/soc_sdw_bridge_cs35l56.c