diff options
Diffstat (limited to 'sound/soc/sh/rcar/rsrc-card.c')
-rw-r--r-- | sound/soc/sh/rcar/rsrc-card.c | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/sound/soc/sh/rcar/rsrc-card.c b/sound/soc/sh/rcar/rsrc-card.c new file mode 100644 index 000000000000..a68517afe615 --- /dev/null +++ b/sound/soc/sh/rcar/rsrc-card.c | |||
@@ -0,0 +1,512 @@ | |||
1 | /* | ||
2 | * Renesas Sampling Rate Convert Sound Card for DPCM | ||
3 | * | ||
4 | * Copyright (C) 2015 Renesas Solutions Corp. | ||
5 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | ||
6 | * | ||
7 | * based on ${LINUX}/sound/soc/generic/simple-card.c | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | #include <linux/clk.h> | ||
14 | #include <linux/device.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/of.h> | ||
17 | #include <linux/of_device.h> | ||
18 | #include <linux/platform_device.h> | ||
19 | #include <linux/string.h> | ||
20 | #include <sound/jack.h> | ||
21 | #include <sound/soc.h> | ||
22 | #include <sound/soc-dai.h> | ||
23 | |||
24 | struct rsrc_card_of_data { | ||
25 | const char *prefix; | ||
26 | const struct snd_soc_dapm_route *routes; | ||
27 | int num_routes; | ||
28 | }; | ||
29 | |||
30 | static const struct snd_soc_dapm_route routes_ssi0_ak4642[] = { | ||
31 | {"ak4642 Playback", NULL, "DAI0 Playback"}, | ||
32 | {"DAI0 Capture", NULL, "ak4642 Capture"}, | ||
33 | }; | ||
34 | |||
35 | static const struct rsrc_card_of_data routes_of_ssi0_ak4642 = { | ||
36 | .prefix = "ak4642", | ||
37 | .routes = routes_ssi0_ak4642, | ||
38 | .num_routes = ARRAY_SIZE(routes_ssi0_ak4642), | ||
39 | }; | ||
40 | |||
41 | static const struct of_device_id rsrc_card_of_match[] = { | ||
42 | { .compatible = "renesas,rsrc-card,lager", .data = &routes_of_ssi0_ak4642 }, | ||
43 | { .compatible = "renesas,rsrc-card,koelsch", .data = &routes_of_ssi0_ak4642 }, | ||
44 | {}, | ||
45 | }; | ||
46 | MODULE_DEVICE_TABLE(of, rsrc_card_of_match); | ||
47 | |||
48 | struct rsrc_card_dai { | ||
49 | const char *name; | ||
50 | unsigned int fmt; | ||
51 | unsigned int sysclk; | ||
52 | struct clk *clk; | ||
53 | }; | ||
54 | |||
55 | #define RSRC_FB_NUM 2 /* FE/BE */ | ||
56 | #define IDX_CPU 0 | ||
57 | #define IDX_CODEC 1 | ||
58 | struct rsrc_card_priv { | ||
59 | struct snd_soc_card snd_card; | ||
60 | struct rsrc_card_dai_props { | ||
61 | struct rsrc_card_dai cpu_dai; | ||
62 | struct rsrc_card_dai codec_dai; | ||
63 | } dai_props[RSRC_FB_NUM]; | ||
64 | struct snd_soc_codec_conf codec_conf; | ||
65 | struct snd_soc_dai_link dai_link[RSRC_FB_NUM]; | ||
66 | u32 convert_rate; | ||
67 | }; | ||
68 | |||
69 | #define rsrc_priv_to_dev(priv) ((priv)->snd_card.dev) | ||
70 | #define rsrc_priv_to_link(priv, i) ((priv)->snd_card.dai_link + i) | ||
71 | #define rsrc_priv_to_props(priv, i) ((priv)->dai_props + i) | ||
72 | #define rsrc_dev_to_of_data(dev) (of_match_device(rsrc_card_of_match, (dev))->data) | ||
73 | |||
74 | static int rsrc_card_startup(struct snd_pcm_substream *substream) | ||
75 | { | ||
76 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
77 | struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); | ||
78 | struct rsrc_card_dai_props *dai_props = | ||
79 | &priv->dai_props[rtd - rtd->card->rtd]; | ||
80 | int ret; | ||
81 | |||
82 | ret = clk_prepare_enable(dai_props->cpu_dai.clk); | ||
83 | if (ret) | ||
84 | return ret; | ||
85 | |||
86 | ret = clk_prepare_enable(dai_props->codec_dai.clk); | ||
87 | if (ret) | ||
88 | clk_disable_unprepare(dai_props->cpu_dai.clk); | ||
89 | |||
90 | return ret; | ||
91 | } | ||
92 | |||
93 | static void rsrc_card_shutdown(struct snd_pcm_substream *substream) | ||
94 | { | ||
95 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
96 | struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); | ||
97 | struct rsrc_card_dai_props *dai_props = | ||
98 | &priv->dai_props[rtd - rtd->card->rtd]; | ||
99 | |||
100 | clk_disable_unprepare(dai_props->cpu_dai.clk); | ||
101 | |||
102 | clk_disable_unprepare(dai_props->codec_dai.clk); | ||
103 | } | ||
104 | |||
105 | static struct snd_soc_ops rsrc_card_ops = { | ||
106 | .startup = rsrc_card_startup, | ||
107 | .shutdown = rsrc_card_shutdown, | ||
108 | }; | ||
109 | |||
110 | static int __rsrc_card_dai_init(struct snd_soc_dai *dai, | ||
111 | struct rsrc_card_dai *set) | ||
112 | { | ||
113 | int ret; | ||
114 | |||
115 | if (set->fmt) { | ||
116 | ret = snd_soc_dai_set_fmt(dai, set->fmt); | ||
117 | if (ret && ret != -ENOTSUPP) { | ||
118 | dev_err(dai->dev, "set_fmt error\n"); | ||
119 | goto err; | ||
120 | } | ||
121 | } | ||
122 | |||
123 | if (set->sysclk) { | ||
124 | ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0); | ||
125 | if (ret && ret != -ENOTSUPP) { | ||
126 | dev_err(dai->dev, "set_sysclk error\n"); | ||
127 | goto err; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | ret = 0; | ||
132 | |||
133 | err: | ||
134 | return ret; | ||
135 | } | ||
136 | |||
137 | static int rsrc_card_dai_init(struct snd_soc_pcm_runtime *rtd) | ||
138 | { | ||
139 | struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); | ||
140 | struct snd_soc_dai *codec = rtd->codec_dai; | ||
141 | struct snd_soc_dai *cpu = rtd->cpu_dai; | ||
142 | struct rsrc_card_dai_props *dai_props; | ||
143 | int num, ret; | ||
144 | |||
145 | num = rtd - rtd->card->rtd; | ||
146 | dai_props = &priv->dai_props[num]; | ||
147 | ret = __rsrc_card_dai_init(codec, &dai_props->codec_dai); | ||
148 | if (ret < 0) | ||
149 | return ret; | ||
150 | |||
151 | ret = __rsrc_card_dai_init(cpu, &dai_props->cpu_dai); | ||
152 | if (ret < 0) | ||
153 | return ret; | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static int rsrc_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, | ||
159 | struct snd_pcm_hw_params *params) | ||
160 | { | ||
161 | struct rsrc_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); | ||
162 | struct snd_interval *rate = hw_param_interval(params, | ||
163 | SNDRV_PCM_HW_PARAM_RATE); | ||
164 | |||
165 | if (!priv->convert_rate) | ||
166 | return 0; | ||
167 | |||
168 | rate->min = rate->max = priv->convert_rate; | ||
169 | |||
170 | return 0; | ||
171 | } | ||
172 | |||
173 | static int | ||
174 | rsrc_card_sub_parse_of(struct rsrc_card_priv *priv, | ||
175 | struct device_node *np, | ||
176 | struct rsrc_card_dai *dai, | ||
177 | struct snd_soc_dai_link *dai_link, | ||
178 | int *args_count) | ||
179 | { | ||
180 | struct device *dev = rsrc_priv_to_dev(priv); | ||
181 | const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev); | ||
182 | struct of_phandle_args args; | ||
183 | struct device_node **p_node; | ||
184 | struct clk *clk; | ||
185 | const char **dai_name; | ||
186 | const char **name; | ||
187 | u32 val; | ||
188 | int ret; | ||
189 | |||
190 | if (args_count) { | ||
191 | p_node = &dai_link->cpu_of_node; | ||
192 | dai_name = &dai_link->cpu_dai_name; | ||
193 | name = &dai_link->cpu_name; | ||
194 | } else { | ||
195 | p_node = &dai_link->codec_of_node; | ||
196 | dai_name = &dai_link->codec_dai_name; | ||
197 | name = &dai_link->codec_name; | ||
198 | } | ||
199 | |||
200 | if (!np) { | ||
201 | /* use snd-soc-dummy */ | ||
202 | *p_node = NULL; | ||
203 | *dai_name = "snd-soc-dummy-dai"; | ||
204 | *name = "snd-soc-dummy"; | ||
205 | return 0; | ||
206 | } | ||
207 | |||
208 | /* | ||
209 | * Get node via "sound-dai = <&phandle port>" | ||
210 | * it will be used as xxx_of_node on soc_bind_dai_link() | ||
211 | */ | ||
212 | ret = of_parse_phandle_with_args(np, "sound-dai", | ||
213 | "#sound-dai-cells", 0, &args); | ||
214 | if (ret) | ||
215 | return ret; | ||
216 | |||
217 | *p_node = args.np; | ||
218 | |||
219 | /* Get dai->name */ | ||
220 | ret = snd_soc_of_get_dai_name(np, dai_name); | ||
221 | if (ret < 0) | ||
222 | return ret; | ||
223 | |||
224 | /* | ||
225 | * FIXME | ||
226 | * | ||
227 | * rsrc assumes DPCM playback/capture | ||
228 | */ | ||
229 | dai_link->dpcm_playback = 1; | ||
230 | dai_link->dpcm_capture = 1; | ||
231 | |||
232 | if (args_count) { | ||
233 | *args_count = args.args_count; | ||
234 | dai_link->dynamic = 1; | ||
235 | } else { | ||
236 | dai_link->no_pcm = 1; | ||
237 | priv->codec_conf.of_node = (*p_node); | ||
238 | priv->codec_conf.name_prefix = of_data->prefix; | ||
239 | } | ||
240 | |||
241 | /* | ||
242 | * Parse dai->sysclk come from "clocks = <&xxx>" | ||
243 | * (if system has common clock) | ||
244 | * or "system-clock-frequency = <xxx>" | ||
245 | * or device's module clock. | ||
246 | */ | ||
247 | if (of_property_read_bool(np, "clocks")) { | ||
248 | clk = of_clk_get(np, 0); | ||
249 | if (IS_ERR(clk)) { | ||
250 | ret = PTR_ERR(clk); | ||
251 | return ret; | ||
252 | } | ||
253 | |||
254 | dai->sysclk = clk_get_rate(clk); | ||
255 | dai->clk = clk; | ||
256 | } else if (!of_property_read_u32(np, "system-clock-frequency", &val)) { | ||
257 | dai->sysclk = val; | ||
258 | } else { | ||
259 | clk = of_clk_get(args.np, 0); | ||
260 | if (!IS_ERR(clk)) | ||
261 | dai->sysclk = clk_get_rate(clk); | ||
262 | } | ||
263 | |||
264 | return 0; | ||
265 | } | ||
266 | |||
267 | static int rsrc_card_parse_daifmt(struct device_node *node, | ||
268 | struct rsrc_card_priv *priv, | ||
269 | struct device_node *codec, | ||
270 | int idx) | ||
271 | { | ||
272 | struct device_node *bitclkmaster = NULL; | ||
273 | struct device_node *framemaster = NULL; | ||
274 | struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx); | ||
275 | struct rsrc_card_dai *cpu_dai = &dai_props->cpu_dai; | ||
276 | struct rsrc_card_dai *codec_dai = &dai_props->codec_dai; | ||
277 | unsigned int daifmt; | ||
278 | |||
279 | daifmt = snd_soc_of_parse_daifmt(node, NULL, | ||
280 | &bitclkmaster, &framemaster); | ||
281 | daifmt &= ~SND_SOC_DAIFMT_MASTER_MASK; | ||
282 | |||
283 | if (!bitclkmaster && !framemaster) | ||
284 | return -EINVAL; | ||
285 | |||
286 | if (codec == bitclkmaster) | ||
287 | daifmt |= (codec == framemaster) ? | ||
288 | SND_SOC_DAIFMT_CBM_CFM : SND_SOC_DAIFMT_CBM_CFS; | ||
289 | else | ||
290 | daifmt |= (codec == framemaster) ? | ||
291 | SND_SOC_DAIFMT_CBS_CFM : SND_SOC_DAIFMT_CBS_CFS; | ||
292 | |||
293 | cpu_dai->fmt = daifmt; | ||
294 | codec_dai->fmt = daifmt; | ||
295 | |||
296 | of_node_put(bitclkmaster); | ||
297 | of_node_put(framemaster); | ||
298 | |||
299 | return 0; | ||
300 | } | ||
301 | |||
302 | static int rsrc_card_dai_link_of(struct device_node *node, | ||
303 | struct rsrc_card_priv *priv, | ||
304 | int idx) | ||
305 | { | ||
306 | struct device *dev = rsrc_priv_to_dev(priv); | ||
307 | struct snd_soc_dai_link *dai_link = rsrc_priv_to_link(priv, idx); | ||
308 | struct rsrc_card_dai_props *dai_props = rsrc_priv_to_props(priv, idx); | ||
309 | struct device_node *cpu = NULL; | ||
310 | struct device_node *codec = NULL; | ||
311 | char *name; | ||
312 | char prop[128]; | ||
313 | int ret, cpu_args; | ||
314 | |||
315 | cpu = of_get_child_by_name(node, "cpu"); | ||
316 | codec = of_get_child_by_name(node, "codec"); | ||
317 | |||
318 | if (!cpu || !codec) { | ||
319 | ret = -EINVAL; | ||
320 | dev_err(dev, "%s: Can't find %s DT node\n", __func__, prop); | ||
321 | goto dai_link_of_err; | ||
322 | } | ||
323 | |||
324 | ret = rsrc_card_parse_daifmt(node, priv, codec, idx); | ||
325 | if (ret < 0) | ||
326 | goto dai_link_of_err; | ||
327 | |||
328 | ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CPU) ? cpu : NULL, | ||
329 | &dai_props->cpu_dai, | ||
330 | dai_link, | ||
331 | &cpu_args); | ||
332 | if (ret < 0) | ||
333 | goto dai_link_of_err; | ||
334 | |||
335 | ret = rsrc_card_sub_parse_of(priv, (idx == IDX_CODEC) ? codec : NULL, | ||
336 | &dai_props->codec_dai, | ||
337 | dai_link, | ||
338 | NULL); | ||
339 | if (ret < 0) | ||
340 | goto dai_link_of_err; | ||
341 | |||
342 | if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) { | ||
343 | ret = -EINVAL; | ||
344 | goto dai_link_of_err; | ||
345 | } | ||
346 | |||
347 | /* Simple Card assumes platform == cpu */ | ||
348 | dai_link->platform_of_node = dai_link->cpu_of_node; | ||
349 | |||
350 | /* DAI link name is created from CPU/CODEC dai name */ | ||
351 | name = devm_kzalloc(dev, | ||
352 | strlen(dai_link->cpu_dai_name) + | ||
353 | strlen(dai_link->codec_dai_name) + 2, | ||
354 | GFP_KERNEL); | ||
355 | if (!name) { | ||
356 | ret = -ENOMEM; | ||
357 | goto dai_link_of_err; | ||
358 | } | ||
359 | |||
360 | sprintf(name, "%s-%s", dai_link->cpu_dai_name, | ||
361 | dai_link->codec_dai_name); | ||
362 | dai_link->name = dai_link->stream_name = name; | ||
363 | dai_link->ops = &rsrc_card_ops; | ||
364 | dai_link->init = rsrc_card_dai_init; | ||
365 | |||
366 | if (idx == IDX_CODEC) | ||
367 | dai_link->be_hw_params_fixup = rsrc_card_be_hw_params_fixup; | ||
368 | |||
369 | dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); | ||
370 | dev_dbg(dev, "\tcpu : %s / %04x / %d\n", | ||
371 | dai_link->cpu_dai_name, | ||
372 | dai_props->cpu_dai.fmt, | ||
373 | dai_props->cpu_dai.sysclk); | ||
374 | dev_dbg(dev, "\tcodec : %s / %04x / %d\n", | ||
375 | dai_link->codec_dai_name, | ||
376 | dai_props->codec_dai.fmt, | ||
377 | dai_props->codec_dai.sysclk); | ||
378 | |||
379 | /* | ||
380 | * In soc_bind_dai_link() will check cpu name after | ||
381 | * of_node matching if dai_link has cpu_dai_name. | ||
382 | * but, it will never match if name was created by | ||
383 | * fmt_single_name() remove cpu_dai_name if cpu_args | ||
384 | * was 0. See: | ||
385 | * fmt_single_name() | ||
386 | * fmt_multiple_name() | ||
387 | */ | ||
388 | if (!cpu_args) | ||
389 | dai_link->cpu_dai_name = NULL; | ||
390 | |||
391 | dai_link_of_err: | ||
392 | of_node_put(cpu); | ||
393 | of_node_put(codec); | ||
394 | |||
395 | return ret; | ||
396 | } | ||
397 | |||
398 | static int rsrc_card_parse_of(struct device_node *node, | ||
399 | struct rsrc_card_priv *priv) | ||
400 | { | ||
401 | struct device *dev = rsrc_priv_to_dev(priv); | ||
402 | const struct rsrc_card_of_data *of_data = rsrc_dev_to_of_data(dev); | ||
403 | int ret; | ||
404 | int i; | ||
405 | |||
406 | if (!node) | ||
407 | return -EINVAL; | ||
408 | |||
409 | /* Parse the card name from DT */ | ||
410 | snd_soc_of_parse_card_name(&priv->snd_card, "card-name"); | ||
411 | |||
412 | /* DAPM routes */ | ||
413 | priv->snd_card.of_dapm_routes = of_data->routes; | ||
414 | priv->snd_card.num_of_dapm_routes = of_data->num_routes; | ||
415 | |||
416 | /* sampling rate convert */ | ||
417 | of_property_read_u32(node, "convert-rate", &priv->convert_rate); | ||
418 | |||
419 | dev_dbg(dev, "New rsrc-audio-card: %s (%d)\n", | ||
420 | priv->snd_card.name ? priv->snd_card.name : "", | ||
421 | priv->convert_rate); | ||
422 | |||
423 | /* FE/BE */ | ||
424 | for (i = 0; i < RSRC_FB_NUM; i++) { | ||
425 | ret = rsrc_card_dai_link_of(node, priv, i); | ||
426 | if (ret < 0) | ||
427 | return ret; | ||
428 | } | ||
429 | |||
430 | if (!priv->snd_card.name) | ||
431 | priv->snd_card.name = priv->snd_card.dai_link->name; | ||
432 | |||
433 | return 0; | ||
434 | } | ||
435 | |||
436 | /* Decrease the reference count of the device nodes */ | ||
437 | static int rsrc_card_unref(struct snd_soc_card *card) | ||
438 | { | ||
439 | struct snd_soc_dai_link *dai_link; | ||
440 | int num_links; | ||
441 | |||
442 | for (num_links = 0, dai_link = card->dai_link; | ||
443 | num_links < card->num_links; | ||
444 | num_links++, dai_link++) { | ||
445 | of_node_put(dai_link->cpu_of_node); | ||
446 | of_node_put(dai_link->codec_of_node); | ||
447 | } | ||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | static int rsrc_card_probe(struct platform_device *pdev) | ||
452 | { | ||
453 | struct rsrc_card_priv *priv; | ||
454 | struct snd_soc_dai_link *dai_link; | ||
455 | struct device_node *np = pdev->dev.of_node; | ||
456 | struct device *dev = &pdev->dev; | ||
457 | int ret; | ||
458 | |||
459 | /* Allocate the private data */ | ||
460 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
461 | if (!priv) | ||
462 | return -ENOMEM; | ||
463 | |||
464 | /* Init snd_soc_card */ | ||
465 | priv->snd_card.owner = THIS_MODULE; | ||
466 | priv->snd_card.dev = dev; | ||
467 | dai_link = priv->dai_link; | ||
468 | priv->snd_card.dai_link = dai_link; | ||
469 | priv->snd_card.num_links = RSRC_FB_NUM; | ||
470 | priv->snd_card.codec_conf = &priv->codec_conf; | ||
471 | priv->snd_card.num_configs = 1; | ||
472 | |||
473 | ret = rsrc_card_parse_of(np, priv); | ||
474 | if (ret < 0) { | ||
475 | if (ret != -EPROBE_DEFER) | ||
476 | dev_err(dev, "parse error %d\n", ret); | ||
477 | goto err; | ||
478 | } | ||
479 | |||
480 | snd_soc_card_set_drvdata(&priv->snd_card, priv); | ||
481 | |||
482 | ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); | ||
483 | if (ret >= 0) | ||
484 | return ret; | ||
485 | err: | ||
486 | rsrc_card_unref(&priv->snd_card); | ||
487 | |||
488 | return ret; | ||
489 | } | ||
490 | |||
491 | static int rsrc_card_remove(struct platform_device *pdev) | ||
492 | { | ||
493 | struct snd_soc_card *card = platform_get_drvdata(pdev); | ||
494 | |||
495 | return rsrc_card_unref(card); | ||
496 | } | ||
497 | |||
498 | static struct platform_driver rsrc_card = { | ||
499 | .driver = { | ||
500 | .name = "renesas-src-audio-card", | ||
501 | .of_match_table = rsrc_card_of_match, | ||
502 | }, | ||
503 | .probe = rsrc_card_probe, | ||
504 | .remove = rsrc_card_remove, | ||
505 | }; | ||
506 | |||
507 | module_platform_driver(rsrc_card); | ||
508 | |||
509 | MODULE_ALIAS("platform:renesas-src-audio-card"); | ||
510 | MODULE_LICENSE("GPL"); | ||
511 | MODULE_DESCRIPTION("Renesas Sampling Rate Convert Sound Card"); | ||
512 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | ||