blob: b83bb31021a9fb9d906c1fee32e3eed01b4b85cd [file] [log] [blame]
Kuninori Morimotoac204c92018-07-02 06:31:33 +00001// SPDX-License-Identifier: GPL-2.0
2//
3// ASoC audio graph SCU sound card support
4//
5// Copyright (C) 2017 Renesas Solutions Corp.
6// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
7//
8// based on
9// ${LINUX}/sound/soc/generic/simple-scu-card.c
10// ${LINUX}/sound/soc/generic/audio-graph-card.c
11
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000012#include <linux/clk.h>
13#include <linux/device.h>
14#include <linux/gpio.h>
15#include <linux/module.h>
16#include <linux/of.h>
17#include <linux/of_device.h>
18#include <linux/of_gpio.h>
19#include <linux/of_graph.h>
20#include <linux/platform_device.h>
21#include <linux/string.h>
22#include <sound/jack.h>
23#include <sound/simple_card_utils.h>
24
25struct graph_card_data {
26 struct snd_soc_card snd_card;
27 struct snd_soc_codec_conf codec_conf;
Kuninori Morimoto13407392018-08-31 03:09:20 +000028 struct graph_dai_props {
29 struct asoc_simple_dai dai;
Kuninori Morimoto04f72672018-08-31 03:09:33 +000030 struct snd_soc_dai_link_component codecs;
Kuninori Morimoto77b9b842018-08-31 03:11:12 +000031 struct snd_soc_dai_link_component platform;
Kuninori Morimoto13407392018-08-31 03:09:20 +000032 } *dai_props;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000033 struct snd_soc_dai_link *dai_link;
Kuninori Morimotoc564a5b2017-06-15 00:24:43 +000034 struct asoc_simple_card_data adata;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000035};
36
37#define graph_priv_to_card(priv) (&(priv)->snd_card)
38#define graph_priv_to_props(priv, i) ((priv)->dai_props + (i))
39#define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev)
40#define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i))
41
42static int asoc_graph_card_startup(struct snd_pcm_substream *substream)
43{
44 struct snd_soc_pcm_runtime *rtd = substream->private_data;
45 struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
Kuninori Morimoto13407392018-08-31 03:09:20 +000046 struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000047
Kuninori Morimoto13407392018-08-31 03:09:20 +000048 return asoc_simple_card_clk_enable(&dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000049}
50
51static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream)
52{
53 struct snd_soc_pcm_runtime *rtd = substream->private_data;
54 struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
Kuninori Morimoto13407392018-08-31 03:09:20 +000055 struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000056
Kuninori Morimoto13407392018-08-31 03:09:20 +000057 asoc_simple_card_clk_disable(&dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000058}
59
Bhumika Goyale23d8342017-08-16 22:29:26 +053060static const struct snd_soc_ops asoc_graph_card_ops = {
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000061 .startup = asoc_graph_card_startup,
62 .shutdown = asoc_graph_card_shutdown,
63};
64
65static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd)
66{
67 struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
68 struct snd_soc_dai *dai;
69 struct snd_soc_dai_link *dai_link;
Kuninori Morimoto13407392018-08-31 03:09:20 +000070 struct graph_dai_props *dai_props;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000071 int num = rtd->num;
72
73 dai_link = graph_priv_to_link(priv, num);
74 dai_props = graph_priv_to_props(priv, num);
75 dai = dai_link->dynamic ?
76 rtd->cpu_dai :
77 rtd->codec_dai;
78
Kuninori Morimoto13407392018-08-31 03:09:20 +000079 return asoc_simple_card_init_dai(dai, &dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000080}
81
82static int asoc_graph_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
83 struct snd_pcm_hw_params *params)
84{
85 struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000086
Kuninori Morimotoc564a5b2017-06-15 00:24:43 +000087 asoc_simple_card_convert_fixup(&priv->adata, params);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +000088
89 return 0;
90}
91
92static int asoc_graph_card_dai_link_of(struct device_node *ep,
93 struct graph_card_data *priv,
94 unsigned int daifmt,
95 int idx, int is_fe)
96{
97 struct device *dev = graph_priv_to_dev(priv);
98 struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx);
Kuninori Morimoto13407392018-08-31 03:09:20 +000099 struct graph_dai_props *dai_props = graph_priv_to_props(priv, idx);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000100 struct snd_soc_card *card = graph_priv_to_card(priv);
101 int ret;
102
103 if (is_fe) {
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000104 struct snd_soc_dai_link_component *codecs;
105
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000106 /* BE is dummy */
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000107 codecs = dai_link->codecs;
108 codecs->of_node = NULL;
109 codecs->dai_name = "snd-soc-dummy-dai";
110 codecs->name = "snd-soc-dummy";
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000111
112 /* FE settings */
113 dai_link->dynamic = 1;
114 dai_link->dpcm_merged_format = 1;
115
116 ret = asoc_simple_card_parse_graph_cpu(ep, dai_link);
117 if (ret)
118 return ret;
119
Kuninori Morimoto13407392018-08-31 03:09:20 +0000120 ret = asoc_simple_card_parse_clk_cpu(dev, ep, dai_link, &dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000121 if (ret < 0)
122 return ret;
123
124 ret = asoc_simple_card_set_dailink_name(dev, dai_link,
125 "fe.%s",
126 dai_link->cpu_dai_name);
127 if (ret < 0)
128 return ret;
129
130 /* card->num_links includes Codec */
131 asoc_simple_card_canonicalize_cpu(dai_link,
Kuninori Morimoto32f2bcc2017-06-22 06:22:14 +0000132 of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000133 } else {
134 /* FE is dummy */
135 dai_link->cpu_of_node = NULL;
136 dai_link->cpu_dai_name = "snd-soc-dummy-dai";
137 dai_link->cpu_name = "snd-soc-dummy";
138
139 /* BE settings */
140 dai_link->no_pcm = 1;
141 dai_link->be_hw_params_fixup = asoc_graph_card_be_hw_params_fixup;
142
143 ret = asoc_simple_card_parse_graph_codec(ep, dai_link);
144 if (ret < 0)
145 return ret;
146
Kuninori Morimoto13407392018-08-31 03:09:20 +0000147 ret = asoc_simple_card_parse_clk_codec(dev, ep, dai_link, &dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000148 if (ret < 0)
149 return ret;
150
151 ret = asoc_simple_card_set_dailink_name(dev, dai_link,
152 "be.%s",
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000153 dai_link->codecs->dai_name);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000154 if (ret < 0)
155 return ret;
156
157 snd_soc_of_parse_audio_prefix(card,
158 &priv->codec_conf,
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000159 dai_link->codecs->of_node,
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000160 "prefix");
161 }
162
Kuninori Morimoto13407392018-08-31 03:09:20 +0000163 ret = asoc_simple_card_of_parse_tdm(ep, &dai_props->dai);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000164 if (ret)
165 return ret;
166
167 ret = asoc_simple_card_canonicalize_dailink(dai_link);
168 if (ret < 0)
169 return ret;
170
171 dai_link->dai_fmt = daifmt;
172 dai_link->dpcm_playback = 1;
173 dai_link->dpcm_capture = 1;
174 dai_link->ops = &asoc_graph_card_ops;
175 dai_link->init = asoc_graph_card_dai_init;
176
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000177 return 0;
178}
179
180static int asoc_graph_card_parse_of(struct graph_card_data *priv)
181{
182 struct of_phandle_iterator it;
183 struct device *dev = graph_priv_to_dev(priv);
184 struct snd_soc_card *card = graph_priv_to_card(priv);
185 struct device_node *node = dev->of_node;
186 struct device_node *cpu_port;
187 struct device_node *cpu_ep;
188 struct device_node *codec_ep;
189 struct device_node *rcpu_ep;
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000190 struct device_node *codec_port;
191 struct device_node *codec_port_old;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000192 unsigned int daifmt = 0;
193 int dai_idx, ret;
194 int rc, codec;
195
196 if (!node)
197 return -EINVAL;
198
199 /*
200 * we need to consider "widgets", "mclk-fs" around here
201 * see simple-card
202 */
203
Kuninori Morimoto9fb9b2f2017-06-15 00:25:51 +0000204 ret = asoc_simple_card_of_parse_routing(card, NULL, 0);
205 if (ret < 0)
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000206 return ret;
207
Kuninori Morimotoc564a5b2017-06-15 00:24:43 +0000208 asoc_simple_card_parse_convert(dev, NULL, &priv->adata);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000209
210 /*
211 * it supports multi CPU, single CODEC only here
212 * see asoc_graph_get_dais_count
213 */
214
215 /* find 1st codec */
216 of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
217 cpu_port = it.node;
218 cpu_ep = of_get_next_child(cpu_port, NULL);
219 codec_ep = of_graph_get_remote_endpoint(cpu_ep);
220 rcpu_ep = of_graph_get_remote_endpoint(codec_ep);
221
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000222 of_node_put(cpu_ep);
223 of_node_put(codec_ep);
224 of_node_put(rcpu_ep);
225
226 if (!codec_ep)
227 continue;
228
229 if (rcpu_ep != cpu_ep) {
230 dev_err(dev, "remote-endpoint missmatch (%s/%s/%s)\n",
231 cpu_ep->name, codec_ep->name, rcpu_ep->name);
232 ret = -EINVAL;
233 goto parse_of_err;
234 }
235
236 ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep,
237 NULL, &daifmt);
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700238 if (ret < 0) {
239 of_node_put(cpu_port);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000240 goto parse_of_err;
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700241 }
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000242 }
243
244 dai_idx = 0;
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000245 codec_port_old = NULL;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000246 for (codec = 0; codec < 2; codec++) {
247 /*
248 * To listup valid sounds continuously,
249 * detect all CPU-dummy first, and
250 * detect all dummy-Codec second
251 */
252 of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
253 cpu_port = it.node;
254 cpu_ep = of_get_next_child(cpu_port, NULL);
255 codec_ep = of_graph_get_remote_endpoint(cpu_ep);
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000256 codec_port = of_graph_get_port_parent(codec_ep);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000257
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000258 of_node_put(cpu_ep);
259 of_node_put(codec_ep);
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000260 of_node_put(codec_port);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000261
262 if (codec) {
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000263 if (!codec_port)
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000264 continue;
265
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000266 if (codec_port_old == codec_port)
267 continue;
268
269 codec_port_old = codec_port;
270
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000271 /* Back-End (= Codec) */
272 ret = asoc_graph_card_dai_link_of(codec_ep, priv, daifmt, dai_idx++, 0);
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700273 if (ret < 0) {
274 of_node_put(cpu_port);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000275 goto parse_of_err;
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700276 }
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000277 } else {
278 /* Front-End (= CPU) */
279 ret = asoc_graph_card_dai_link_of(cpu_ep, priv, daifmt, dai_idx++, 1);
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700280 if (ret < 0) {
281 of_node_put(cpu_port);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000282 goto parse_of_err;
Tony Lindgrenc0a480d2017-07-28 01:23:15 -0700283 }
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000284 }
285 }
286 }
287
288 ret = asoc_simple_card_parse_card_name(card, NULL);
289 if (ret)
290 goto parse_of_err;
291
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000292 ret = 0;
293
294parse_of_err:
295 return ret;
296}
297
298static int asoc_graph_get_dais_count(struct device *dev)
299{
300 struct of_phandle_iterator it;
301 struct device_node *node = dev->of_node;
302 struct device_node *cpu_port;
303 struct device_node *cpu_ep;
304 struct device_node *codec_ep;
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000305 struct device_node *codec_port;
306 struct device_node *codec_port_old;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000307 int count = 0;
308 int rc;
309
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000310 codec_port_old = NULL;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000311 of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
312 cpu_port = it.node;
313 cpu_ep = of_get_next_child(cpu_port, NULL);
314 codec_ep = of_graph_get_remote_endpoint(cpu_ep);
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000315 codec_port = of_graph_get_port_parent(codec_ep);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000316
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000317 of_node_put(cpu_ep);
318 of_node_put(codec_ep);
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000319 of_node_put(codec_port);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000320
321 if (cpu_ep)
322 count++;
Kuninori Morimotof1f94042017-06-22 06:22:49 +0000323
324 if (!codec_port)
325 continue;
326
327 if (codec_port_old == codec_port)
328 continue;
329
330 count++;
331 codec_port_old = codec_port;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000332 }
333
334 return count;
335}
336
337static int asoc_graph_card_probe(struct platform_device *pdev)
338{
339 struct graph_card_data *priv;
340 struct snd_soc_dai_link *dai_link;
Kuninori Morimoto13407392018-08-31 03:09:20 +0000341 struct graph_dai_props *dai_props;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000342 struct device *dev = &pdev->dev;
343 struct snd_soc_card *card;
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000344 int num, ret, i;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000345
346 /* Allocate the private data and the DAI link array */
347 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
348 if (!priv)
349 return -ENOMEM;
350
351 num = asoc_graph_get_dais_count(dev);
352 if (num == 0)
353 return -EINVAL;
354
Kees Cooka86854d2018-06-12 14:07:58 -0700355 dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL);
356 dai_link = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL);
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000357 if (!dai_props || !dai_link)
358 return -ENOMEM;
359
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000360 /*
361 * Use snd_soc_dai_link_component instead of legacy style
362 * It is codec only. but cpu/platform will be supported in the future.
363 * see
364 * soc-core.c :: snd_soc_init_multicodec()
365 */
366 for (i = 0; i < num; i++) {
367 dai_link[i].codecs = &dai_props[i].codecs;
368 dai_link[i].num_codecs = 1;
Kuninori Morimoto77b9b842018-08-31 03:11:12 +0000369 dai_link[i].platform = &dai_props[i].platform;
Kuninori Morimoto04f72672018-08-31 03:09:33 +0000370 }
371
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000372 priv->dai_props = dai_props;
373 priv->dai_link = dai_link;
374
375 /* Init snd_soc_card */
376 card = graph_priv_to_card(priv);
377 card->owner = THIS_MODULE;
378 card->dev = dev;
379 card->dai_link = priv->dai_link;
380 card->num_links = num;
381 card->codec_conf = &priv->codec_conf;
382 card->num_configs = 1;
383
384 ret = asoc_graph_card_parse_of(priv);
385 if (ret < 0) {
386 if (ret != -EPROBE_DEFER)
387 dev_err(dev, "parse error %d\n", ret);
388 goto err;
389 }
390
391 snd_soc_card_set_drvdata(card, priv);
392
393 ret = devm_snd_soc_register_card(dev, card);
Kuninori Morimoto1a2af562017-05-19 00:58:19 +0000394 if (ret < 0)
395 goto err;
396
397 return 0;
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000398err:
399 asoc_simple_card_clean_reference(card);
400
401 return ret;
402}
403
404static int asoc_graph_card_remove(struct platform_device *pdev)
405{
406 struct snd_soc_card *card = platform_get_drvdata(pdev);
407
408 return asoc_simple_card_clean_reference(card);
409}
410
411static const struct of_device_id asoc_graph_of_match[] = {
412 { .compatible = "audio-graph-scu-card", },
413 {},
414};
415MODULE_DEVICE_TABLE(of, asoc_graph_of_match);
416
417static struct platform_driver asoc_graph_card = {
418 .driver = {
419 .name = "asoc-audio-graph-scu-card",
Kuninori Morimoto0995fb72017-08-22 04:57:12 +0000420 .pm = &snd_soc_pm_ops,
Kuninori Morimoto87f937b2017-05-18 01:45:37 +0000421 .of_match_table = asoc_graph_of_match,
422 },
423 .probe = asoc_graph_card_probe,
424 .remove = asoc_graph_card_remove,
425};
426module_platform_driver(asoc_graph_card);
427
428MODULE_ALIAS("platform:asoc-audio-graph-scu-card");
429MODULE_LICENSE("GPL v2");
430MODULE_DESCRIPTION("ASoC Audio Graph SCU Sound Card");
431MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");