]>
Commit | Line | Data |
---|---|---|
7864a79f JB |
1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | // | |
3 | // Copyright (c) 2018 BayLibre, SAS. | |
4 | // Author: Jerome Brunet <[email protected]> | |
5 | ||
6 | #include <linux/module.h> | |
7 | #include <linux/of_platform.h> | |
8 | #include <sound/soc.h> | |
9 | #include <sound/soc-dai.h> | |
10 | ||
11 | #include "axg-tdm.h" | |
aa9c3b72 | 12 | #include "meson-card.h" |
7864a79f JB |
13 | |
14 | struct axg_dai_link_tdm_mask { | |
15 | u32 tx; | |
16 | u32 rx; | |
17 | }; | |
18 | ||
19 | struct axg_dai_link_tdm_data { | |
20 | unsigned int mclk_fs; | |
21 | unsigned int slots; | |
22 | unsigned int slot_width; | |
23 | u32 *tx_mask; | |
24 | u32 *rx_mask; | |
25 | struct axg_dai_link_tdm_mask *codec_masks; | |
26 | }; | |
27 | ||
0a8f1117 JB |
28 | /* |
29 | * Base params for the codec to codec links | |
30 | * Those will be over-written by the CPU side of the link | |
31 | */ | |
32 | static const struct snd_soc_pcm_stream codec_params = { | |
33 | .formats = SNDRV_PCM_FMTBIT_S24_LE, | |
34 | .rate_min = 5525, | |
35 | .rate_max = 192000, | |
36 | .channels_min = 1, | |
37 | .channels_max = 8, | |
38 | }; | |
39 | ||
7864a79f JB |
40 | static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream, |
41 | struct snd_pcm_hw_params *params) | |
42 | { | |
43 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
aa9c3b72 | 44 | struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); |
7864a79f JB |
45 | struct axg_dai_link_tdm_data *be = |
46 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | |
7864a79f | 47 | |
aa9c3b72 | 48 | return meson_card_i2s_set_sysclk(substream, params, be->mclk_fs); |
7864a79f JB |
49 | } |
50 | ||
51 | static const struct snd_soc_ops axg_card_tdm_be_ops = { | |
52 | .hw_params = axg_card_tdm_be_hw_params, | |
53 | }; | |
54 | ||
55 | static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd) | |
56 | { | |
aa9c3b72 | 57 | struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); |
7864a79f JB |
58 | struct axg_dai_link_tdm_data *be = |
59 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | |
60 | struct snd_soc_dai *codec_dai; | |
61 | int ret, i; | |
62 | ||
b5c52f58 | 63 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
7864a79f JB |
64 | ret = snd_soc_dai_set_tdm_slot(codec_dai, |
65 | be->codec_masks[i].tx, | |
66 | be->codec_masks[i].rx, | |
67 | be->slots, be->slot_width); | |
68 | if (ret && ret != -ENOTSUPP) { | |
69 | dev_err(codec_dai->dev, | |
70 | "setting tdm link slots failed\n"); | |
71 | return ret; | |
72 | } | |
73 | } | |
74 | ||
385a5c60 | 75 | ret = axg_tdm_set_tdm_slots(asoc_rtd_to_cpu(rtd, 0), be->tx_mask, be->rx_mask, |
7864a79f JB |
76 | be->slots, be->slot_width); |
77 | if (ret) { | |
385a5c60 | 78 | dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); |
7864a79f JB |
79 | return ret; |
80 | } | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
85 | static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd) | |
86 | { | |
aa9c3b72 | 87 | struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); |
7864a79f JB |
88 | struct axg_dai_link_tdm_data *be = |
89 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | |
90 | int ret; | |
91 | ||
92 | /* The loopback rx_mask is the pad tx_mask */ | |
385a5c60 | 93 | ret = axg_tdm_set_tdm_slots(asoc_rtd_to_cpu(rtd, 0), NULL, be->tx_mask, |
7864a79f JB |
94 | be->slots, be->slot_width); |
95 | if (ret) { | |
385a5c60 | 96 | dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); |
7864a79f JB |
97 | return ret; |
98 | } | |
99 | ||
100 | return 0; | |
101 | } | |
102 | ||
103 | static int axg_card_add_tdm_loopback(struct snd_soc_card *card, | |
104 | int *index) | |
105 | { | |
aa9c3b72 | 106 | struct meson_card *priv = snd_soc_card_get_drvdata(card); |
7864a79f JB |
107 | struct snd_soc_dai_link *pad = &card->dai_link[*index]; |
108 | struct snd_soc_dai_link *lb; | |
c84836d7 | 109 | struct snd_soc_dai_link_component *dlc; |
7864a79f JB |
110 | int ret; |
111 | ||
112 | /* extend links */ | |
aa9c3b72 | 113 | ret = meson_card_reallocate_links(card, card->num_links + 1); |
7864a79f JB |
114 | if (ret) |
115 | return ret; | |
116 | ||
117 | lb = &card->dai_link[*index + 1]; | |
118 | ||
119 | lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name); | |
120 | if (!lb->name) | |
121 | return -ENOMEM; | |
122 | ||
c84836d7 KM |
123 | dlc = devm_kzalloc(card->dev, 2 * sizeof(*dlc), GFP_KERNEL); |
124 | if (!dlc) | |
125 | return -ENOMEM; | |
126 | ||
127 | lb->cpus = &dlc[0]; | |
128 | lb->codecs = &dlc[1]; | |
129 | lb->num_cpus = 1; | |
130 | lb->num_codecs = 1; | |
131 | ||
7864a79f | 132 | lb->stream_name = lb->name; |
c84836d7 KM |
133 | lb->cpus->of_node = pad->cpus->of_node; |
134 | lb->cpus->dai_name = "TDM Loopback"; | |
135 | lb->codecs->name = "snd-soc-dummy"; | |
136 | lb->codecs->dai_name = "snd-soc-dummy-dai"; | |
7864a79f JB |
137 | lb->dpcm_capture = 1; |
138 | lb->no_pcm = 1; | |
139 | lb->ops = &axg_card_tdm_be_ops; | |
140 | lb->init = axg_card_tdm_dai_lb_init; | |
141 | ||
142 | /* Provide the same link data to the loopback */ | |
143 | priv->link_data[*index + 1] = priv->link_data[*index]; | |
144 | ||
145 | /* | |
146 | * axg_card_clean_references() will iterate over this link, | |
147 | * make sure the node count is balanced | |
148 | */ | |
c84836d7 | 149 | of_node_get(lb->cpus->of_node); |
7864a79f JB |
150 | |
151 | /* Let add_links continue where it should */ | |
152 | *index += 1; | |
153 | ||
154 | return 0; | |
155 | } | |
156 | ||
7864a79f JB |
157 | static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, |
158 | struct snd_soc_dai_link *link, | |
159 | struct device_node *node, | |
160 | struct axg_dai_link_tdm_data *be) | |
161 | { | |
162 | char propname[32]; | |
163 | u32 tx, rx; | |
164 | int i; | |
165 | ||
166 | be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, | |
167 | sizeof(*be->tx_mask), GFP_KERNEL); | |
168 | be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, | |
169 | sizeof(*be->rx_mask), GFP_KERNEL); | |
170 | if (!be->tx_mask || !be->rx_mask) | |
171 | return -ENOMEM; | |
172 | ||
173 | for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { | |
174 | snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); | |
175 | snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); | |
176 | tx = max(tx, be->tx_mask[i]); | |
177 | } | |
178 | ||
179 | /* Disable playback is the interface has no tx slots */ | |
180 | if (!tx) | |
181 | link->dpcm_playback = 0; | |
182 | ||
183 | for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { | |
184 | snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); | |
185 | snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); | |
186 | rx = max(rx, be->rx_mask[i]); | |
187 | } | |
188 | ||
189 | /* Disable capture is the interface has no rx slots */ | |
190 | if (!rx) | |
191 | link->dpcm_capture = 0; | |
192 | ||
193 | /* ... but the interface should at least have one of them */ | |
194 | if (!tx && !rx) { | |
195 | dev_err(card->dev, "tdm link has no cpu slots\n"); | |
196 | return -EINVAL; | |
197 | } | |
198 | ||
199 | of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); | |
200 | if (!be->slots) { | |
201 | /* | |
202 | * If the slot number is not provided, set it such as it | |
203 | * accommodates the largest mask | |
204 | */ | |
205 | be->slots = fls(max(tx, rx)); | |
206 | } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { | |
207 | /* | |
208 | * Error if the slots can't accommodate the largest mask or | |
209 | * if it is just too big | |
210 | */ | |
211 | dev_err(card->dev, "bad slot number\n"); | |
212 | return -EINVAL; | |
213 | } | |
214 | ||
215 | of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static int axg_card_parse_codecs_masks(struct snd_soc_card *card, | |
221 | struct snd_soc_dai_link *link, | |
222 | struct device_node *node, | |
223 | struct axg_dai_link_tdm_data *be) | |
224 | { | |
225 | struct axg_dai_link_tdm_mask *codec_mask; | |
226 | struct device_node *np; | |
227 | ||
228 | codec_mask = devm_kcalloc(card->dev, link->num_codecs, | |
229 | sizeof(*codec_mask), GFP_KERNEL); | |
230 | if (!codec_mask) | |
231 | return -ENOMEM; | |
232 | ||
233 | be->codec_masks = codec_mask; | |
234 | ||
235 | for_each_child_of_node(node, np) { | |
236 | snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", | |
237 | &codec_mask->rx); | |
238 | snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", | |
239 | &codec_mask->tx); | |
240 | ||
241 | codec_mask++; | |
242 | } | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | static int axg_card_parse_tdm(struct snd_soc_card *card, | |
248 | struct device_node *node, | |
249 | int *index) | |
250 | { | |
aa9c3b72 | 251 | struct meson_card *priv = snd_soc_card_get_drvdata(card); |
7864a79f JB |
252 | struct snd_soc_dai_link *link = &card->dai_link[*index]; |
253 | struct axg_dai_link_tdm_data *be; | |
254 | int ret; | |
255 | ||
256 | /* Allocate tdm link parameters */ | |
257 | be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); | |
258 | if (!be) | |
259 | return -ENOMEM; | |
260 | priv->link_data[*index] = be; | |
261 | ||
262 | /* Setup tdm link */ | |
263 | link->ops = &axg_card_tdm_be_ops; | |
264 | link->init = axg_card_tdm_dai_init; | |
aa9c3b72 | 265 | link->dai_fmt = meson_card_parse_daifmt(node, link->cpus->of_node); |
7864a79f JB |
266 | |
267 | of_property_read_u32(node, "mclk-fs", &be->mclk_fs); | |
268 | ||
269 | ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); | |
270 | if (ret) { | |
271 | dev_err(card->dev, "error parsing tdm link slots\n"); | |
272 | return ret; | |
273 | } | |
274 | ||
275 | ret = axg_card_parse_codecs_masks(card, link, node, be); | |
276 | if (ret) | |
277 | return ret; | |
278 | ||
279 | /* Add loopback if the pad dai has playback */ | |
280 | if (link->dpcm_playback) { | |
281 | ret = axg_card_add_tdm_loopback(card, index); | |
282 | if (ret) | |
283 | return ret; | |
284 | } | |
285 | ||
286 | return 0; | |
287 | } | |
288 | ||
7864a79f JB |
289 | static int axg_card_cpu_is_capture_fe(struct device_node *np) |
290 | { | |
aa9c3b72 | 291 | return of_device_is_compatible(np, DT_PREFIX "axg-toddr"); |
7864a79f JB |
292 | } |
293 | ||
294 | static int axg_card_cpu_is_playback_fe(struct device_node *np) | |
295 | { | |
aa9c3b72 | 296 | return of_device_is_compatible(np, DT_PREFIX "axg-frddr"); |
7864a79f JB |
297 | } |
298 | ||
299 | static int axg_card_cpu_is_tdm_iface(struct device_node *np) | |
300 | { | |
aa9c3b72 | 301 | return of_device_is_compatible(np, DT_PREFIX "axg-tdm-iface"); |
7864a79f JB |
302 | } |
303 | ||
0a8f1117 JB |
304 | static int axg_card_cpu_is_codec(struct device_node *np) |
305 | { | |
b38c4a8a JB |
306 | return of_device_is_compatible(np, DT_PREFIX "g12a-tohdmitx") || |
307 | of_device_is_compatible(np, DT_PREFIX "g12a-toacodec"); | |
0a8f1117 JB |
308 | } |
309 | ||
7864a79f JB |
310 | static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, |
311 | int *index) | |
312 | { | |
313 | struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; | |
c84836d7 | 314 | struct snd_soc_dai_link_component *cpu; |
7864a79f JB |
315 | int ret; |
316 | ||
c84836d7 KM |
317 | cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); |
318 | if (!cpu) | |
319 | return -ENOMEM; | |
320 | ||
321 | dai_link->cpus = cpu; | |
322 | dai_link->num_cpus = 1; | |
323 | ||
aa9c3b72 JB |
324 | ret = meson_card_parse_dai(card, np, &dai_link->cpus->of_node, |
325 | &dai_link->cpus->dai_name); | |
7864a79f JB |
326 | if (ret) |
327 | return ret; | |
328 | ||
c84836d7 | 329 | if (axg_card_cpu_is_playback_fe(dai_link->cpus->of_node)) |
aa9c3b72 | 330 | ret = meson_card_set_fe_link(card, dai_link, np, true); |
c84836d7 | 331 | else if (axg_card_cpu_is_capture_fe(dai_link->cpus->of_node)) |
aa9c3b72 | 332 | ret = meson_card_set_fe_link(card, dai_link, np, false); |
7864a79f | 333 | else |
aa9c3b72 | 334 | ret = meson_card_set_be_link(card, dai_link, np); |
7864a79f JB |
335 | |
336 | if (ret) | |
337 | return ret; | |
338 | ||
c84836d7 | 339 | if (axg_card_cpu_is_tdm_iface(dai_link->cpus->of_node)) |
7864a79f | 340 | ret = axg_card_parse_tdm(card, np, index); |
11642842 | 341 | else if (axg_card_cpu_is_codec(dai_link->cpus->of_node)) { |
0a8f1117 | 342 | dai_link->params = &codec_params; |
11642842 JB |
343 | dai_link->no_pcm = 0; /* link is not a DPCM BE */ |
344 | } | |
7864a79f JB |
345 | |
346 | return ret; | |
347 | } | |
348 | ||
aa9c3b72 JB |
349 | static const struct meson_card_match_data axg_card_match_data = { |
350 | .add_link = axg_card_add_link, | |
351 | }; | |
7864a79f JB |
352 | |
353 | static const struct of_device_id axg_card_of_match[] = { | |
aa9c3b72 JB |
354 | { |
355 | .compatible = "amlogic,axg-sound-card", | |
356 | .data = &axg_card_match_data, | |
357 | }, {} | |
7864a79f JB |
358 | }; |
359 | MODULE_DEVICE_TABLE(of, axg_card_of_match); | |
360 | ||
7864a79f | 361 | static struct platform_driver axg_card_pdrv = { |
aa9c3b72 JB |
362 | .probe = meson_card_probe, |
363 | .remove = meson_card_remove, | |
7864a79f JB |
364 | .driver = { |
365 | .name = "axg-sound-card", | |
366 | .of_match_table = axg_card_of_match, | |
367 | }, | |
368 | }; | |
369 | module_platform_driver(axg_card_pdrv); | |
370 | ||
371 | MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); | |
372 | MODULE_AUTHOR("Jerome Brunet <[email protected]>"); | |
373 | MODULE_LICENSE("GPL v2"); |