diff options
author | Jerome Brunet <jbrunet@baylibre.com> | 2018-07-17 11:43:04 -0400 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2018-07-20 12:40:12 -0400 |
commit | 7864a79f37b55769b817d5e6c5ae0ca4bfdba93b (patch) | |
tree | 1de91d66557855471125e139f0979ea2848b4f44 /sound/soc/meson | |
parent | 2a05c71ea17b09c88a212e8fa6be1ccddd4613ab (diff) |
ASoC: meson: add axg sound card support
Add the axg sound card to handle the specifities of the axg audio
sub system.
This card is required to:
* setup the dpcm links specific to the AXG (with a cpu sound dai)
* handle the 4 lanes masks of the tdm interfaces
* add the loopback link when a tdm pad interface has a playback
stream
* handle multi-codec links
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
Diffstat (limited to 'sound/soc/meson')
-rw-r--r-- | sound/soc/meson/Kconfig | 11 | ||||
-rw-r--r-- | sound/soc/meson/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/meson/axg-card.c | 671 |
3 files changed, 684 insertions, 0 deletions
diff --git a/sound/soc/meson/Kconfig b/sound/soc/meson/Kconfig index 00d05df67b52..4cf93c05a982 100644 --- a/sound/soc/meson/Kconfig +++ b/sound/soc/meson/Kconfig | |||
@@ -43,6 +43,17 @@ config SND_MESON_AXG_TDMOUT | |||
43 | Select Y or M to add support for TDM output formatter embedded | 43 | Select Y or M to add support for TDM output formatter embedded |
44 | in the Amlogic AXG SoC family | 44 | in the Amlogic AXG SoC family |
45 | 45 | ||
46 | config SND_MESON_AXG_SOUND_CARD | ||
47 | tristate "Amlogic AXG Sound Card Support" | ||
48 | select SND_MESON_AXG_TDM_INTERFACE | ||
49 | imply SND_MESON_AXG_FRDDR | ||
50 | imply SND_MESON_AXG_TODDR | ||
51 | imply SND_MESON_AXG_TDMIN | ||
52 | imply SND_MESON_AXG_TDMOUT | ||
53 | imply SND_MESON_AXG_SPDIFOUT | ||
54 | help | ||
55 | Select Y or M to add support for the AXG SoC sound card | ||
56 | |||
46 | config SND_MESON_AXG_SPDIFOUT | 57 | config SND_MESON_AXG_SPDIFOUT |
47 | tristate "Amlogic AXG SPDIF Output Support" | 58 | tristate "Amlogic AXG SPDIF Output Support" |
48 | imply SND_SOC_SPDIF | 59 | imply SND_SOC_SPDIF |
diff --git a/sound/soc/meson/Makefile b/sound/soc/meson/Makefile index f62833fb44d8..c5e003b093db 100644 --- a/sound/soc/meson/Makefile +++ b/sound/soc/meson/Makefile | |||
@@ -7,6 +7,7 @@ snd-soc-meson-axg-tdm-formatter-objs := axg-tdm-formatter.o | |||
7 | snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o | 7 | snd-soc-meson-axg-tdm-interface-objs := axg-tdm-interface.o |
8 | snd-soc-meson-axg-tdmin-objs := axg-tdmin.o | 8 | snd-soc-meson-axg-tdmin-objs := axg-tdmin.o |
9 | snd-soc-meson-axg-tdmout-objs := axg-tdmout.o | 9 | snd-soc-meson-axg-tdmout-objs := axg-tdmout.o |
10 | snd-soc-meson-axg-sound-card-objs := axg-card.o | ||
10 | snd-soc-meson-axg-spdifout-objs := axg-spdifout.o | 11 | snd-soc-meson-axg-spdifout-objs := axg-spdifout.o |
11 | 12 | ||
12 | obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o | 13 | obj-$(CONFIG_SND_MESON_AXG_FIFO) += snd-soc-meson-axg-fifo.o |
@@ -16,4 +17,5 @@ obj-$(CONFIG_SND_MESON_AXG_TDM_FORMATTER) += snd-soc-meson-axg-tdm-formatter.o | |||
16 | obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o | 17 | obj-$(CONFIG_SND_MESON_AXG_TDM_INTERFACE) += snd-soc-meson-axg-tdm-interface.o |
17 | obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o | 18 | obj-$(CONFIG_SND_MESON_AXG_TDMIN) += snd-soc-meson-axg-tdmin.o |
18 | obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o | 19 | obj-$(CONFIG_SND_MESON_AXG_TDMOUT) += snd-soc-meson-axg-tdmout.o |
20 | obj-$(CONFIG_SND_MESON_AXG_SOUND_CARD) += snd-soc-meson-axg-sound-card.o | ||
19 | obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o | 21 | obj-$(CONFIG_SND_MESON_AXG_SPDIFOUT) += snd-soc-meson-axg-spdifout.o |
diff --git a/sound/soc/meson/axg-card.c b/sound/soc/meson/axg-card.c new file mode 100644 index 000000000000..d6d1081d94ad --- /dev/null +++ b/sound/soc/meson/axg-card.c | |||
@@ -0,0 +1,671 @@ | |||
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 | |||
13 | struct axg_card { | ||
14 | struct snd_soc_card card; | ||
15 | void **link_data; | ||
16 | }; | ||
17 | |||
18 | struct axg_dai_link_tdm_mask { | ||
19 | u32 tx; | ||
20 | u32 rx; | ||
21 | }; | ||
22 | |||
23 | struct axg_dai_link_tdm_data { | ||
24 | unsigned int mclk_fs; | ||
25 | unsigned int slots; | ||
26 | unsigned int slot_width; | ||
27 | u32 *tx_mask; | ||
28 | u32 *rx_mask; | ||
29 | struct axg_dai_link_tdm_mask *codec_masks; | ||
30 | }; | ||
31 | |||
32 | #define PREFIX "amlogic," | ||
33 | |||
34 | static int axg_card_reallocate_links(struct axg_card *priv, | ||
35 | unsigned int num_links) | ||
36 | { | ||
37 | struct snd_soc_dai_link *links; | ||
38 | void **ldata; | ||
39 | |||
40 | links = krealloc(priv->card.dai_link, | ||
41 | num_links * sizeof(*priv->card.dai_link), | ||
42 | GFP_KERNEL | __GFP_ZERO); | ||
43 | ldata = krealloc(priv->link_data, | ||
44 | num_links * sizeof(*priv->link_data), | ||
45 | GFP_KERNEL | __GFP_ZERO); | ||
46 | |||
47 | if (!links || !ldata) { | ||
48 | dev_err(priv->card.dev, "failed to allocate links\n"); | ||
49 | return -ENOMEM; | ||
50 | } | ||
51 | |||
52 | priv->card.dai_link = links; | ||
53 | priv->link_data = ldata; | ||
54 | priv->card.num_links = num_links; | ||
55 | return 0; | ||
56 | } | ||
57 | |||
58 | static int axg_card_parse_dai(struct snd_soc_card *card, | ||
59 | struct device_node *node, | ||
60 | struct device_node **dai_of_node, | ||
61 | const char **dai_name) | ||
62 | { | ||
63 | struct of_phandle_args args; | ||
64 | int ret; | ||
65 | |||
66 | if (!dai_name || !dai_of_node || !node) | ||
67 | return -EINVAL; | ||
68 | |||
69 | ret = of_parse_phandle_with_args(node, "sound-dai", | ||
70 | "#sound-dai-cells", 0, &args); | ||
71 | if (ret) { | ||
72 | if (ret != -EPROBE_DEFER) | ||
73 | dev_err(card->dev, "can't parse dai %d\n", ret); | ||
74 | return ret; | ||
75 | } | ||
76 | *dai_of_node = args.np; | ||
77 | |||
78 | return snd_soc_get_dai_name(&args, dai_name); | ||
79 | } | ||
80 | |||
81 | static int axg_card_set_link_name(struct snd_soc_card *card, | ||
82 | struct snd_soc_dai_link *link, | ||
83 | const char *prefix) | ||
84 | { | ||
85 | char *name = devm_kasprintf(card->dev, GFP_KERNEL, "%s.%s", | ||
86 | prefix, link->cpu_of_node->full_name); | ||
87 | if (!name) | ||
88 | return -ENOMEM; | ||
89 | |||
90 | link->name = name; | ||
91 | link->stream_name = name; | ||
92 | |||
93 | return 0; | ||
94 | } | ||
95 | |||
96 | static void axg_card_clean_references(struct axg_card *priv) | ||
97 | { | ||
98 | struct snd_soc_card *card = &priv->card; | ||
99 | struct snd_soc_dai_link *link; | ||
100 | int i, j; | ||
101 | |||
102 | if (card->dai_link) { | ||
103 | for (i = 0; i < card->num_links; i++) { | ||
104 | link = &card->dai_link[i]; | ||
105 | of_node_put(link->cpu_of_node); | ||
106 | for (j = 0; j < link->num_codecs; j++) | ||
107 | of_node_put(link->codecs[j].of_node); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | if (card->aux_dev) { | ||
112 | for (i = 0; i < card->num_aux_devs; i++) | ||
113 | of_node_put(card->aux_dev[i].codec_of_node); | ||
114 | } | ||
115 | |||
116 | kfree(card->dai_link); | ||
117 | kfree(priv->link_data); | ||
118 | } | ||
119 | |||
120 | static int axg_card_add_aux_devices(struct snd_soc_card *card) | ||
121 | { | ||
122 | struct device_node *node = card->dev->of_node; | ||
123 | struct snd_soc_aux_dev *aux; | ||
124 | int num, i; | ||
125 | |||
126 | num = of_count_phandle_with_args(node, PREFIX "aux-devs", NULL); | ||
127 | if (num == -ENOENT) { | ||
128 | /* | ||
129 | * It is ok to have no auxiliary devices but for this card it | ||
130 | * is a strange situtation. Let's warn the about it. | ||
131 | */ | ||
132 | dev_warn(card->dev, "card has no auxiliary devices\n"); | ||
133 | return 0; | ||
134 | } else if (num < 0) { | ||
135 | dev_err(card->dev, "error getting auxiliary devices: %d\n", | ||
136 | num); | ||
137 | return num; | ||
138 | } | ||
139 | |||
140 | aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL); | ||
141 | if (!aux) | ||
142 | return -ENOMEM; | ||
143 | card->aux_dev = aux; | ||
144 | card->num_aux_devs = num; | ||
145 | |||
146 | for (i = 0; i < card->num_aux_devs; i++, aux++) { | ||
147 | aux->codec_of_node = of_parse_phandle(node, | ||
148 | PREFIX "aux-devs", i); | ||
149 | if (!aux->codec_of_node) | ||
150 | return -EINVAL; | ||
151 | } | ||
152 | |||
153 | return 0; | ||
154 | } | ||
155 | |||
156 | static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream, | ||
157 | struct snd_pcm_hw_params *params) | ||
158 | { | ||
159 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
160 | struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); | ||
161 | struct axg_dai_link_tdm_data *be = | ||
162 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | ||
163 | struct snd_soc_dai *codec_dai; | ||
164 | unsigned int mclk; | ||
165 | int ret, i; | ||
166 | |||
167 | if (be->mclk_fs) { | ||
168 | mclk = params_rate(params) * be->mclk_fs; | ||
169 | |||
170 | for (i = 0; i < rtd->num_codecs; i++) { | ||
171 | codec_dai = rtd->codec_dais[i]; | ||
172 | ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, | ||
173 | SND_SOC_CLOCK_IN); | ||
174 | if (ret && ret != -ENOTSUPP) | ||
175 | return ret; | ||
176 | } | ||
177 | |||
178 | ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, 0, mclk, | ||
179 | SND_SOC_CLOCK_OUT); | ||
180 | if (ret && ret != -ENOTSUPP) | ||
181 | return ret; | ||
182 | } | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | static const struct snd_soc_ops axg_card_tdm_be_ops = { | ||
188 | .hw_params = axg_card_tdm_be_hw_params, | ||
189 | }; | ||
190 | |||
191 | static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd) | ||
192 | { | ||
193 | struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); | ||
194 | struct axg_dai_link_tdm_data *be = | ||
195 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | ||
196 | struct snd_soc_dai *codec_dai; | ||
197 | int ret, i; | ||
198 | |||
199 | for (i = 0; i < rtd->num_codecs; i++) { | ||
200 | codec_dai = rtd->codec_dais[i]; | ||
201 | ret = snd_soc_dai_set_tdm_slot(codec_dai, | ||
202 | be->codec_masks[i].tx, | ||
203 | be->codec_masks[i].rx, | ||
204 | be->slots, be->slot_width); | ||
205 | if (ret && ret != -ENOTSUPP) { | ||
206 | dev_err(codec_dai->dev, | ||
207 | "setting tdm link slots failed\n"); | ||
208 | return ret; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, be->tx_mask, be->rx_mask, | ||
213 | be->slots, be->slot_width); | ||
214 | if (ret) { | ||
215 | dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); | ||
216 | return ret; | ||
217 | } | ||
218 | |||
219 | return 0; | ||
220 | } | ||
221 | |||
222 | static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd) | ||
223 | { | ||
224 | struct axg_card *priv = snd_soc_card_get_drvdata(rtd->card); | ||
225 | struct axg_dai_link_tdm_data *be = | ||
226 | (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; | ||
227 | int ret; | ||
228 | |||
229 | /* The loopback rx_mask is the pad tx_mask */ | ||
230 | ret = axg_tdm_set_tdm_slots(rtd->cpu_dai, NULL, be->tx_mask, | ||
231 | be->slots, be->slot_width); | ||
232 | if (ret) { | ||
233 | dev_err(rtd->cpu_dai->dev, "setting tdm link slots failed\n"); | ||
234 | return ret; | ||
235 | } | ||
236 | |||
237 | return 0; | ||
238 | } | ||
239 | |||
240 | static int axg_card_add_tdm_loopback(struct snd_soc_card *card, | ||
241 | int *index) | ||
242 | { | ||
243 | struct axg_card *priv = snd_soc_card_get_drvdata(card); | ||
244 | struct snd_soc_dai_link *pad = &card->dai_link[*index]; | ||
245 | struct snd_soc_dai_link *lb; | ||
246 | int ret; | ||
247 | |||
248 | /* extend links */ | ||
249 | ret = axg_card_reallocate_links(priv, card->num_links + 1); | ||
250 | if (ret) | ||
251 | return ret; | ||
252 | |||
253 | lb = &card->dai_link[*index + 1]; | ||
254 | |||
255 | lb->name = kasprintf(GFP_KERNEL, "%s-lb", pad->name); | ||
256 | if (!lb->name) | ||
257 | return -ENOMEM; | ||
258 | |||
259 | lb->stream_name = lb->name; | ||
260 | lb->cpu_of_node = pad->cpu_of_node; | ||
261 | lb->cpu_dai_name = "TDM Loopback"; | ||
262 | lb->codec_name = "snd-soc-dummy"; | ||
263 | lb->codec_dai_name = "snd-soc-dummy-dai"; | ||
264 | lb->dpcm_capture = 1; | ||
265 | lb->no_pcm = 1; | ||
266 | lb->ops = &axg_card_tdm_be_ops; | ||
267 | lb->init = axg_card_tdm_dai_lb_init; | ||
268 | |||
269 | /* Provide the same link data to the loopback */ | ||
270 | priv->link_data[*index + 1] = priv->link_data[*index]; | ||
271 | |||
272 | /* | ||
273 | * axg_card_clean_references() will iterate over this link, | ||
274 | * make sure the node count is balanced | ||
275 | */ | ||
276 | of_node_get(lb->cpu_of_node); | ||
277 | |||
278 | /* Let add_links continue where it should */ | ||
279 | *index += 1; | ||
280 | |||
281 | return 0; | ||
282 | } | ||
283 | |||
284 | static unsigned int axg_card_parse_daifmt(struct device_node *node, | ||
285 | struct device_node *cpu_node) | ||
286 | { | ||
287 | struct device_node *bitclkmaster = NULL; | ||
288 | struct device_node *framemaster = NULL; | ||
289 | unsigned int daifmt; | ||
290 | |||
291 | daifmt = snd_soc_of_parse_daifmt(node, PREFIX, | ||
292 | &bitclkmaster, &framemaster); | ||
293 | daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; | ||
294 | |||
295 | /* If no master is provided, default to cpu master */ | ||
296 | if (!bitclkmaster || bitclkmaster == cpu_node) { | ||
297 | daifmt |= (!framemaster || framemaster == cpu_node) ? | ||
298 | SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBS_CFM; | ||
299 | } else { | ||
300 | daifmt |= (!framemaster || framemaster == cpu_node) ? | ||
301 | SND_SOC_DAIFMT_CBM_CFS : SND_SOC_DAIFMT_CBM_CFM; | ||
302 | } | ||
303 | |||
304 | of_node_put(bitclkmaster); | ||
305 | of_node_put(framemaster); | ||
306 | |||
307 | return daifmt; | ||
308 | } | ||
309 | |||
310 | static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, | ||
311 | struct snd_soc_dai_link *link, | ||
312 | struct device_node *node, | ||
313 | struct axg_dai_link_tdm_data *be) | ||
314 | { | ||
315 | char propname[32]; | ||
316 | u32 tx, rx; | ||
317 | int i; | ||
318 | |||
319 | be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, | ||
320 | sizeof(*be->tx_mask), GFP_KERNEL); | ||
321 | be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, | ||
322 | sizeof(*be->rx_mask), GFP_KERNEL); | ||
323 | if (!be->tx_mask || !be->rx_mask) | ||
324 | return -ENOMEM; | ||
325 | |||
326 | for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { | ||
327 | snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); | ||
328 | snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); | ||
329 | tx = max(tx, be->tx_mask[i]); | ||
330 | } | ||
331 | |||
332 | /* Disable playback is the interface has no tx slots */ | ||
333 | if (!tx) | ||
334 | link->dpcm_playback = 0; | ||
335 | |||
336 | for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { | ||
337 | snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); | ||
338 | snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); | ||
339 | rx = max(rx, be->rx_mask[i]); | ||
340 | } | ||
341 | |||
342 | /* Disable capture is the interface has no rx slots */ | ||
343 | if (!rx) | ||
344 | link->dpcm_capture = 0; | ||
345 | |||
346 | /* ... but the interface should at least have one of them */ | ||
347 | if (!tx && !rx) { | ||
348 | dev_err(card->dev, "tdm link has no cpu slots\n"); | ||
349 | return -EINVAL; | ||
350 | } | ||
351 | |||
352 | of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); | ||
353 | if (!be->slots) { | ||
354 | /* | ||
355 | * If the slot number is not provided, set it such as it | ||
356 | * accommodates the largest mask | ||
357 | */ | ||
358 | be->slots = fls(max(tx, rx)); | ||
359 | } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { | ||
360 | /* | ||
361 | * Error if the slots can't accommodate the largest mask or | ||
362 | * if it is just too big | ||
363 | */ | ||
364 | dev_err(card->dev, "bad slot number\n"); | ||
365 | return -EINVAL; | ||
366 | } | ||
367 | |||
368 | of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); | ||
369 | |||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | static int axg_card_parse_codecs_masks(struct snd_soc_card *card, | ||
374 | struct snd_soc_dai_link *link, | ||
375 | struct device_node *node, | ||
376 | struct axg_dai_link_tdm_data *be) | ||
377 | { | ||
378 | struct axg_dai_link_tdm_mask *codec_mask; | ||
379 | struct device_node *np; | ||
380 | |||
381 | codec_mask = devm_kcalloc(card->dev, link->num_codecs, | ||
382 | sizeof(*codec_mask), GFP_KERNEL); | ||
383 | if (!codec_mask) | ||
384 | return -ENOMEM; | ||
385 | |||
386 | be->codec_masks = codec_mask; | ||
387 | |||
388 | for_each_child_of_node(node, np) { | ||
389 | snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", | ||
390 | &codec_mask->rx); | ||
391 | snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", | ||
392 | &codec_mask->tx); | ||
393 | |||
394 | codec_mask++; | ||
395 | } | ||
396 | |||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | static int axg_card_parse_tdm(struct snd_soc_card *card, | ||
401 | struct device_node *node, | ||
402 | int *index) | ||
403 | { | ||
404 | struct axg_card *priv = snd_soc_card_get_drvdata(card); | ||
405 | struct snd_soc_dai_link *link = &card->dai_link[*index]; | ||
406 | struct axg_dai_link_tdm_data *be; | ||
407 | int ret; | ||
408 | |||
409 | /* Allocate tdm link parameters */ | ||
410 | be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); | ||
411 | if (!be) | ||
412 | return -ENOMEM; | ||
413 | priv->link_data[*index] = be; | ||
414 | |||
415 | /* Setup tdm link */ | ||
416 | link->ops = &axg_card_tdm_be_ops; | ||
417 | link->init = axg_card_tdm_dai_init; | ||
418 | link->dai_fmt = axg_card_parse_daifmt(node, link->cpu_of_node); | ||
419 | |||
420 | of_property_read_u32(node, "mclk-fs", &be->mclk_fs); | ||
421 | |||
422 | ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); | ||
423 | if (ret) { | ||
424 | dev_err(card->dev, "error parsing tdm link slots\n"); | ||
425 | return ret; | ||
426 | } | ||
427 | |||
428 | ret = axg_card_parse_codecs_masks(card, link, node, be); | ||
429 | if (ret) | ||
430 | return ret; | ||
431 | |||
432 | /* Add loopback if the pad dai has playback */ | ||
433 | if (link->dpcm_playback) { | ||
434 | ret = axg_card_add_tdm_loopback(card, index); | ||
435 | if (ret) | ||
436 | return ret; | ||
437 | } | ||
438 | |||
439 | return 0; | ||
440 | } | ||
441 | |||
442 | static int axg_card_set_be_link(struct snd_soc_card *card, | ||
443 | struct snd_soc_dai_link *link, | ||
444 | struct device_node *node) | ||
445 | { | ||
446 | struct snd_soc_dai_link_component *codec; | ||
447 | struct device_node *np; | ||
448 | int ret, num_codecs; | ||
449 | |||
450 | link->no_pcm = 1; | ||
451 | link->dpcm_playback = 1; | ||
452 | link->dpcm_capture = 1; | ||
453 | |||
454 | num_codecs = of_get_child_count(node); | ||
455 | if (!num_codecs) { | ||
456 | dev_err(card->dev, "be link %s has no codec\n", | ||
457 | node->full_name); | ||
458 | return -EINVAL; | ||
459 | } | ||
460 | |||
461 | codec = devm_kcalloc(card->dev, num_codecs, sizeof(*codec), GFP_KERNEL); | ||
462 | if (!codec) | ||
463 | return -ENOMEM; | ||
464 | |||
465 | link->codecs = codec; | ||
466 | link->num_codecs = num_codecs; | ||
467 | |||
468 | for_each_child_of_node(node, np) { | ||
469 | ret = axg_card_parse_dai(card, np, &codec->of_node, | ||
470 | &codec->dai_name); | ||
471 | if (ret) { | ||
472 | of_node_put(np); | ||
473 | return ret; | ||
474 | } | ||
475 | |||
476 | codec++; | ||
477 | } | ||
478 | |||
479 | ret = axg_card_set_link_name(card, link, "be"); | ||
480 | if (ret) | ||
481 | dev_err(card->dev, "error setting %s link name\n", np->name); | ||
482 | |||
483 | return ret; | ||
484 | } | ||
485 | |||
486 | static int axg_card_set_fe_link(struct snd_soc_card *card, | ||
487 | struct snd_soc_dai_link *link, | ||
488 | bool is_playback) | ||
489 | { | ||
490 | link->dynamic = 1; | ||
491 | link->dpcm_merged_format = 1; | ||
492 | link->dpcm_merged_chan = 1; | ||
493 | link->dpcm_merged_rate = 1; | ||
494 | link->codec_dai_name = "snd-soc-dummy-dai"; | ||
495 | link->codec_name = "snd-soc-dummy"; | ||
496 | |||
497 | if (is_playback) | ||
498 | link->dpcm_playback = 1; | ||
499 | else | ||
500 | link->dpcm_capture = 1; | ||
501 | |||
502 | return axg_card_set_link_name(card, link, "fe"); | ||
503 | } | ||
504 | |||
505 | static int axg_card_cpu_is_capture_fe(struct device_node *np) | ||
506 | { | ||
507 | return of_device_is_compatible(np, PREFIX "axg-toddr"); | ||
508 | } | ||
509 | |||
510 | static int axg_card_cpu_is_playback_fe(struct device_node *np) | ||
511 | { | ||
512 | return of_device_is_compatible(np, PREFIX "axg-frddr"); | ||
513 | } | ||
514 | |||
515 | static int axg_card_cpu_is_tdm_iface(struct device_node *np) | ||
516 | { | ||
517 | return of_device_is_compatible(np, PREFIX "axg-tdm-iface"); | ||
518 | } | ||
519 | |||
520 | static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, | ||
521 | int *index) | ||
522 | { | ||
523 | struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; | ||
524 | int ret; | ||
525 | |||
526 | ret = axg_card_parse_dai(card, np, &dai_link->cpu_of_node, | ||
527 | &dai_link->cpu_dai_name); | ||
528 | if (ret) | ||
529 | return ret; | ||
530 | |||
531 | if (axg_card_cpu_is_playback_fe(dai_link->cpu_of_node)) | ||
532 | ret = axg_card_set_fe_link(card, dai_link, true); | ||
533 | else if (axg_card_cpu_is_capture_fe(dai_link->cpu_of_node)) | ||
534 | ret = axg_card_set_fe_link(card, dai_link, false); | ||
535 | else | ||
536 | ret = axg_card_set_be_link(card, dai_link, np); | ||
537 | |||
538 | if (ret) | ||
539 | return ret; | ||
540 | |||
541 | if (axg_card_cpu_is_tdm_iface(dai_link->cpu_of_node)) | ||
542 | ret = axg_card_parse_tdm(card, np, index); | ||
543 | |||
544 | return ret; | ||
545 | } | ||
546 | |||
547 | static int axg_card_add_links(struct snd_soc_card *card) | ||
548 | { | ||
549 | struct axg_card *priv = snd_soc_card_get_drvdata(card); | ||
550 | struct device_node *node = card->dev->of_node; | ||
551 | struct device_node *np; | ||
552 | int num, i, ret; | ||
553 | |||
554 | num = of_get_child_count(node); | ||
555 | if (!num) { | ||
556 | dev_err(card->dev, "card has no links\n"); | ||
557 | return -EINVAL; | ||
558 | } | ||
559 | |||
560 | ret = axg_card_reallocate_links(priv, num); | ||
561 | if (ret) | ||
562 | return ret; | ||
563 | |||
564 | i = 0; | ||
565 | for_each_child_of_node(node, np) { | ||
566 | ret = axg_card_add_link(card, np, &i); | ||
567 | if (ret) { | ||
568 | of_node_put(np); | ||
569 | return ret; | ||
570 | } | ||
571 | |||
572 | i++; | ||
573 | } | ||
574 | |||
575 | return 0; | ||
576 | } | ||
577 | |||
578 | static int axg_card_parse_of_optional(struct snd_soc_card *card, | ||
579 | const char *propname, | ||
580 | int (*func)(struct snd_soc_card *c, | ||
581 | const char *p)) | ||
582 | { | ||
583 | /* If property is not provided, don't fail ... */ | ||
584 | if (!of_property_read_bool(card->dev->of_node, propname)) | ||
585 | return 0; | ||
586 | |||
587 | /* ... but do fail if it is provided and the parsing fails */ | ||
588 | return func(card, propname); | ||
589 | } | ||
590 | |||
591 | static const struct of_device_id axg_card_of_match[] = { | ||
592 | { .compatible = "amlogic,axg-sound-card", }, | ||
593 | {} | ||
594 | }; | ||
595 | MODULE_DEVICE_TABLE(of, axg_card_of_match); | ||
596 | |||
597 | static int axg_card_probe(struct platform_device *pdev) | ||
598 | { | ||
599 | struct device *dev = &pdev->dev; | ||
600 | struct axg_card *priv; | ||
601 | int ret; | ||
602 | |||
603 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
604 | if (!priv) | ||
605 | return -ENOMEM; | ||
606 | |||
607 | platform_set_drvdata(pdev, priv); | ||
608 | snd_soc_card_set_drvdata(&priv->card, priv); | ||
609 | |||
610 | priv->card.owner = THIS_MODULE; | ||
611 | priv->card.dev = dev; | ||
612 | |||
613 | ret = snd_soc_of_parse_card_name(&priv->card, PREFIX "name"); | ||
614 | if (ret < 0) | ||
615 | return ret; | ||
616 | |||
617 | ret = axg_card_parse_of_optional(&priv->card, PREFIX "routing", | ||
618 | snd_soc_of_parse_audio_routing); | ||
619 | if (ret) { | ||
620 | dev_err(dev, "error while parsing routing\n"); | ||
621 | return ret; | ||
622 | } | ||
623 | |||
624 | ret = axg_card_parse_of_optional(&priv->card, PREFIX "widgets", | ||
625 | snd_soc_of_parse_audio_simple_widgets); | ||
626 | if (ret) { | ||
627 | dev_err(dev, "error while parsing widgets\n"); | ||
628 | return ret; | ||
629 | } | ||
630 | |||
631 | ret = axg_card_add_links(&priv->card); | ||
632 | if (ret) | ||
633 | goto out_err; | ||
634 | |||
635 | ret = axg_card_add_aux_devices(&priv->card); | ||
636 | if (ret) | ||
637 | goto out_err; | ||
638 | |||
639 | ret = devm_snd_soc_register_card(dev, &priv->card); | ||
640 | if (ret) | ||
641 | goto out_err; | ||
642 | |||
643 | return 0; | ||
644 | |||
645 | out_err: | ||
646 | axg_card_clean_references(priv); | ||
647 | return ret; | ||
648 | } | ||
649 | |||
650 | static int axg_card_remove(struct platform_device *pdev) | ||
651 | { | ||
652 | struct axg_card *priv = platform_get_drvdata(pdev); | ||
653 | |||
654 | axg_card_clean_references(priv); | ||
655 | |||
656 | return 0; | ||
657 | } | ||
658 | |||
659 | static struct platform_driver axg_card_pdrv = { | ||
660 | .probe = axg_card_probe, | ||
661 | .remove = axg_card_remove, | ||
662 | .driver = { | ||
663 | .name = "axg-sound-card", | ||
664 | .of_match_table = axg_card_of_match, | ||
665 | }, | ||
666 | }; | ||
667 | module_platform_driver(axg_card_pdrv); | ||
668 | |||
669 | MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); | ||
670 | MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); | ||
671 | MODULE_LICENSE("GPL v2"); | ||