1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | // |
3 | // Copyright (c) 2018 BayLibre, SAS. |
4 | // Author: Jerome Brunet <jbrunet@baylibre.com> |
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" |
12 | #include "meson-card.h" |
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 | |
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 | |
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 = snd_soc_substream_to_rtd(substream); |
44 | struct meson_card *priv = snd_soc_card_get_drvdata(card: rtd->card); |
45 | struct axg_dai_link_tdm_data *be = |
46 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; |
47 | |
48 | return meson_card_i2s_set_sysclk(substream, params, mclk_fs: be->mclk_fs); |
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 | { |
57 | struct meson_card *priv = snd_soc_card_get_drvdata(card: rtd->card); |
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 | |
63 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
64 | ret = snd_soc_dai_set_tdm_slot(dai: codec_dai, |
65 | tx_mask: be->codec_masks[i].tx, |
66 | rx_mask: be->codec_masks[i].rx, |
67 | slots: be->slots, slot_width: 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 | |
75 | ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), tx_mask: be->tx_mask, rx_mask: be->rx_mask, |
76 | slots: be->slots, slot_width: be->slot_width); |
77 | if (ret) { |
78 | dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n" ); |
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 | { |
87 | struct meson_card *priv = snd_soc_card_get_drvdata(card: rtd->card); |
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 */ |
93 | ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), NULL, rx_mask: be->tx_mask, |
94 | slots: be->slots, slot_width: be->slot_width); |
95 | if (ret) { |
96 | dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n" ); |
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 | { |
106 | struct meson_card *priv = snd_soc_card_get_drvdata(card); |
107 | struct snd_soc_dai_link *pad = &card->dai_link[*index]; |
108 | struct snd_soc_dai_link *lb; |
109 | struct snd_soc_dai_link_component *dlc; |
110 | int ret; |
111 | |
112 | /* extend links */ |
113 | ret = meson_card_reallocate_links(card, num_links: card->num_links + 1); |
114 | if (ret) |
115 | return ret; |
116 | |
117 | lb = &card->dai_link[*index + 1]; |
118 | |
119 | lb->name = devm_kasprintf(dev: card->dev, GFP_KERNEL, fmt: "%s-lb" , pad->name); |
120 | if (!lb->name) |
121 | return -ENOMEM; |
122 | |
123 | dlc = devm_kzalloc(dev: card->dev, size: sizeof(*dlc), GFP_KERNEL); |
124 | if (!dlc) |
125 | return -ENOMEM; |
126 | |
127 | lb->cpus = dlc; |
128 | lb->codecs = &snd_soc_dummy_dlc; |
129 | lb->num_cpus = 1; |
130 | lb->num_codecs = 1; |
131 | |
132 | lb->stream_name = lb->name; |
133 | lb->cpus->of_node = pad->cpus->of_node; |
134 | lb->cpus->dai_name = "TDM Loopback" ; |
135 | lb->dpcm_capture = 1; |
136 | lb->no_pcm = 1; |
137 | lb->ops = &axg_card_tdm_be_ops; |
138 | lb->init = axg_card_tdm_dai_lb_init; |
139 | |
140 | /* Provide the same link data to the loopback */ |
141 | priv->link_data[*index + 1] = priv->link_data[*index]; |
142 | |
143 | /* |
144 | * axg_card_clean_references() will iterate over this link, |
145 | * make sure the node count is balanced |
146 | */ |
147 | of_node_get(node: lb->cpus->of_node); |
148 | |
149 | /* Let add_links continue where it should */ |
150 | *index += 1; |
151 | |
152 | return 0; |
153 | } |
154 | |
155 | static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, |
156 | struct snd_soc_dai_link *link, |
157 | struct device_node *node, |
158 | struct axg_dai_link_tdm_data *be) |
159 | { |
160 | char propname[32]; |
161 | u32 tx, rx; |
162 | int i; |
163 | |
164 | be->tx_mask = devm_kcalloc(dev: card->dev, AXG_TDM_NUM_LANES, |
165 | size: sizeof(*be->tx_mask), GFP_KERNEL); |
166 | be->rx_mask = devm_kcalloc(dev: card->dev, AXG_TDM_NUM_LANES, |
167 | size: sizeof(*be->rx_mask), GFP_KERNEL); |
168 | if (!be->tx_mask || !be->rx_mask) |
169 | return -ENOMEM; |
170 | |
171 | for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { |
172 | snprintf(buf: propname, size: 32, fmt: "dai-tdm-slot-tx-mask-%d" , i); |
173 | snd_soc_of_get_slot_mask(np: node, prop_name: propname, mask: &be->tx_mask[i]); |
174 | tx = max(tx, be->tx_mask[i]); |
175 | } |
176 | |
177 | /* Disable playback is the interface has no tx slots */ |
178 | if (!tx) |
179 | link->dpcm_playback = 0; |
180 | |
181 | for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { |
182 | snprintf(buf: propname, size: 32, fmt: "dai-tdm-slot-rx-mask-%d" , i); |
183 | snd_soc_of_get_slot_mask(np: node, prop_name: propname, mask: &be->rx_mask[i]); |
184 | rx = max(rx, be->rx_mask[i]); |
185 | } |
186 | |
187 | /* Disable capture is the interface has no rx slots */ |
188 | if (!rx) |
189 | link->dpcm_capture = 0; |
190 | |
191 | /* ... but the interface should at least have one of them */ |
192 | if (!tx && !rx) { |
193 | dev_err(card->dev, "tdm link has no cpu slots\n" ); |
194 | return -EINVAL; |
195 | } |
196 | |
197 | of_property_read_u32(np: node, propname: "dai-tdm-slot-num" , out_value: &be->slots); |
198 | if (!be->slots) { |
199 | /* |
200 | * If the slot number is not provided, set it such as it |
201 | * accommodates the largest mask |
202 | */ |
203 | be->slots = fls(max(tx, rx)); |
204 | } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { |
205 | /* |
206 | * Error if the slots can't accommodate the largest mask or |
207 | * if it is just too big |
208 | */ |
209 | dev_err(card->dev, "bad slot number\n" ); |
210 | return -EINVAL; |
211 | } |
212 | |
213 | of_property_read_u32(np: node, propname: "dai-tdm-slot-width" , out_value: &be->slot_width); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static int axg_card_parse_codecs_masks(struct snd_soc_card *card, |
219 | struct snd_soc_dai_link *link, |
220 | struct device_node *node, |
221 | struct axg_dai_link_tdm_data *be) |
222 | { |
223 | struct axg_dai_link_tdm_mask *codec_mask; |
224 | struct device_node *np; |
225 | |
226 | codec_mask = devm_kcalloc(dev: card->dev, n: link->num_codecs, |
227 | size: sizeof(*codec_mask), GFP_KERNEL); |
228 | if (!codec_mask) |
229 | return -ENOMEM; |
230 | |
231 | be->codec_masks = codec_mask; |
232 | |
233 | for_each_child_of_node(node, np) { |
234 | snd_soc_of_get_slot_mask(np, prop_name: "dai-tdm-slot-rx-mask" , |
235 | mask: &codec_mask->rx); |
236 | snd_soc_of_get_slot_mask(np, prop_name: "dai-tdm-slot-tx-mask" , |
237 | mask: &codec_mask->tx); |
238 | |
239 | codec_mask++; |
240 | } |
241 | |
242 | return 0; |
243 | } |
244 | |
245 | static int axg_card_parse_tdm(struct snd_soc_card *card, |
246 | struct device_node *node, |
247 | int *index) |
248 | { |
249 | struct meson_card *priv = snd_soc_card_get_drvdata(card); |
250 | struct snd_soc_dai_link *link = &card->dai_link[*index]; |
251 | struct axg_dai_link_tdm_data *be; |
252 | int ret; |
253 | |
254 | /* Allocate tdm link parameters */ |
255 | be = devm_kzalloc(dev: card->dev, size: sizeof(*be), GFP_KERNEL); |
256 | if (!be) |
257 | return -ENOMEM; |
258 | priv->link_data[*index] = be; |
259 | |
260 | /* Setup tdm link */ |
261 | link->ops = &axg_card_tdm_be_ops; |
262 | link->init = axg_card_tdm_dai_init; |
263 | link->dai_fmt = meson_card_parse_daifmt(node, cpu_node: link->cpus->of_node); |
264 | |
265 | of_property_read_u32(np: node, propname: "mclk-fs" , out_value: &be->mclk_fs); |
266 | |
267 | ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); |
268 | if (ret) { |
269 | dev_err(card->dev, "error parsing tdm link slots\n" ); |
270 | return ret; |
271 | } |
272 | |
273 | ret = axg_card_parse_codecs_masks(card, link, node, be); |
274 | if (ret) |
275 | return ret; |
276 | |
277 | /* Add loopback if the pad dai has playback */ |
278 | if (link->dpcm_playback) { |
279 | ret = axg_card_add_tdm_loopback(card, index); |
280 | if (ret) |
281 | return ret; |
282 | } |
283 | |
284 | return 0; |
285 | } |
286 | |
287 | static int axg_card_cpu_is_capture_fe(struct device_node *np) |
288 | { |
289 | return of_device_is_compatible(device: np, DT_PREFIX "axg-toddr" ); |
290 | } |
291 | |
292 | static int axg_card_cpu_is_playback_fe(struct device_node *np) |
293 | { |
294 | return of_device_is_compatible(device: np, DT_PREFIX "axg-frddr" ); |
295 | } |
296 | |
297 | static int axg_card_cpu_is_tdm_iface(struct device_node *np) |
298 | { |
299 | return of_device_is_compatible(device: np, DT_PREFIX "axg-tdm-iface" ); |
300 | } |
301 | |
302 | static int axg_card_cpu_is_codec(struct device_node *np) |
303 | { |
304 | return of_device_is_compatible(device: np, DT_PREFIX "g12a-tohdmitx" ) || |
305 | of_device_is_compatible(device: np, DT_PREFIX "g12a-toacodec" ); |
306 | } |
307 | |
308 | static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, |
309 | int *index) |
310 | { |
311 | struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; |
312 | struct snd_soc_dai_link_component *cpu; |
313 | int ret; |
314 | |
315 | cpu = devm_kzalloc(dev: card->dev, size: sizeof(*cpu), GFP_KERNEL); |
316 | if (!cpu) |
317 | return -ENOMEM; |
318 | |
319 | dai_link->cpus = cpu; |
320 | dai_link->num_cpus = 1; |
321 | |
322 | ret = meson_card_parse_dai(card, node: np, dlc: dai_link->cpus); |
323 | if (ret) |
324 | return ret; |
325 | |
326 | if (axg_card_cpu_is_playback_fe(np: dai_link->cpus->of_node)) |
327 | return meson_card_set_fe_link(card, link: dai_link, node: np, is_playback: true); |
328 | else if (axg_card_cpu_is_capture_fe(np: dai_link->cpus->of_node)) |
329 | return meson_card_set_fe_link(card, link: dai_link, node: np, is_playback: false); |
330 | |
331 | |
332 | ret = meson_card_set_be_link(card, link: dai_link, node: np); |
333 | if (ret) |
334 | return ret; |
335 | |
336 | if (axg_card_cpu_is_codec(np: dai_link->cpus->of_node)) { |
337 | dai_link->c2c_params = &codec_params; |
338 | dai_link->num_c2c_params = 1; |
339 | } else { |
340 | dai_link->no_pcm = 1; |
341 | snd_soc_dai_link_set_capabilities(dai_link); |
342 | if (axg_card_cpu_is_tdm_iface(np: dai_link->cpus->of_node)) |
343 | ret = axg_card_parse_tdm(card, node: np, index); |
344 | } |
345 | |
346 | return ret; |
347 | } |
348 | |
349 | static const struct meson_card_match_data axg_card_match_data = { |
350 | .add_link = axg_card_add_link, |
351 | }; |
352 | |
353 | static const struct of_device_id axg_card_of_match[] = { |
354 | { |
355 | .compatible = "amlogic,axg-sound-card" , |
356 | .data = &axg_card_match_data, |
357 | }, {} |
358 | }; |
359 | MODULE_DEVICE_TABLE(of, axg_card_of_match); |
360 | |
361 | static struct platform_driver axg_card_pdrv = { |
362 | .probe = meson_card_probe, |
363 | .remove_new = meson_card_remove, |
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 <jbrunet@baylibre.com>" ); |
373 | MODULE_LICENSE("GPL v2" ); |
374 | |