2 * ASoC audio graph SCU sound card support
4 * Copyright (C) 2017 Renesas Solutions Corp.
5 * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
8 * ${LINUX}/sound/soc/generic/simple-scu-card.c
9 * ${LINUX}/sound/soc/generic/audio-graph-card.c
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 #include <linux/clk.h>
16 #include <linux/device.h>
17 #include <linux/gpio.h>
18 #include <linux/module.h>
20 #include <linux/of_device.h>
21 #include <linux/of_gpio.h>
22 #include <linux/of_graph.h>
23 #include <linux/platform_device.h>
24 #include <linux/string.h>
25 #include <sound/jack.h>
26 #include <sound/simple_card_utils.h>
28 struct graph_card_data
{
29 struct snd_soc_card snd_card
;
30 struct snd_soc_codec_conf codec_conf
;
31 struct asoc_simple_dai
*dai_props
;
32 struct snd_soc_dai_link
*dai_link
;
33 struct asoc_simple_card_data adata
;
36 #define graph_priv_to_card(priv) (&(priv)->snd_card)
37 #define graph_priv_to_props(priv, i) ((priv)->dai_props + (i))
38 #define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev)
39 #define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i))
41 static int asoc_graph_card_startup(struct snd_pcm_substream
*substream
)
43 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
44 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
45 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, rtd
->num
);
47 return asoc_simple_card_clk_enable(dai_props
);
50 static void asoc_graph_card_shutdown(struct snd_pcm_substream
*substream
)
52 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
53 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
54 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, rtd
->num
);
56 asoc_simple_card_clk_disable(dai_props
);
59 static const struct snd_soc_ops asoc_graph_card_ops
= {
60 .startup
= asoc_graph_card_startup
,
61 .shutdown
= asoc_graph_card_shutdown
,
64 static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime
*rtd
)
66 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
67 struct snd_soc_dai
*dai
;
68 struct snd_soc_dai_link
*dai_link
;
69 struct asoc_simple_dai
*dai_props
;
72 dai_link
= graph_priv_to_link(priv
, num
);
73 dai_props
= graph_priv_to_props(priv
, num
);
74 dai
= dai_link
->dynamic
?
78 return asoc_simple_card_init_dai(dai
, dai_props
);
81 static int asoc_graph_card_be_hw_params_fixup(struct snd_soc_pcm_runtime
*rtd
,
82 struct snd_pcm_hw_params
*params
)
84 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
86 asoc_simple_card_convert_fixup(&priv
->adata
, params
);
91 static int asoc_graph_card_dai_link_of(struct device_node
*ep
,
92 struct graph_card_data
*priv
,
96 struct device
*dev
= graph_priv_to_dev(priv
);
97 struct snd_soc_dai_link
*dai_link
= graph_priv_to_link(priv
, idx
);
98 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, idx
);
99 struct snd_soc_card
*card
= graph_priv_to_card(priv
);
104 dai_link
->codec_of_node
= NULL
;
105 dai_link
->codec_dai_name
= "snd-soc-dummy-dai";
106 dai_link
->codec_name
= "snd-soc-dummy";
109 dai_link
->dynamic
= 1;
110 dai_link
->dpcm_merged_format
= 1;
112 ret
= asoc_simple_card_parse_graph_cpu(ep
, dai_link
);
116 ret
= asoc_simple_card_parse_clk_cpu(dev
, ep
, dai_link
, dai_props
);
120 ret
= asoc_simple_card_set_dailink_name(dev
, dai_link
,
122 dai_link
->cpu_dai_name
);
126 /* card->num_links includes Codec */
127 asoc_simple_card_canonicalize_cpu(dai_link
,
128 of_graph_get_endpoint_count(dai_link
->cpu_of_node
) == 1);
131 dai_link
->cpu_of_node
= NULL
;
132 dai_link
->cpu_dai_name
= "snd-soc-dummy-dai";
133 dai_link
->cpu_name
= "snd-soc-dummy";
136 dai_link
->no_pcm
= 1;
137 dai_link
->be_hw_params_fixup
= asoc_graph_card_be_hw_params_fixup
;
139 ret
= asoc_simple_card_parse_graph_codec(ep
, dai_link
);
143 ret
= asoc_simple_card_parse_clk_codec(dev
, ep
, dai_link
, dai_props
);
147 ret
= asoc_simple_card_set_dailink_name(dev
, dai_link
,
149 dai_link
->codec_dai_name
);
153 snd_soc_of_parse_audio_prefix(card
,
155 dai_link
->codec_of_node
,
159 ret
= asoc_simple_card_of_parse_tdm(ep
, dai_props
);
163 ret
= asoc_simple_card_canonicalize_dailink(dai_link
);
167 dai_link
->dai_fmt
= daifmt
;
168 dai_link
->dpcm_playback
= 1;
169 dai_link
->dpcm_capture
= 1;
170 dai_link
->ops
= &asoc_graph_card_ops
;
171 dai_link
->init
= asoc_graph_card_dai_init
;
176 static int asoc_graph_card_parse_of(struct graph_card_data
*priv
)
178 struct of_phandle_iterator it
;
179 struct device
*dev
= graph_priv_to_dev(priv
);
180 struct snd_soc_card
*card
= graph_priv_to_card(priv
);
181 struct device_node
*node
= dev
->of_node
;
182 struct device_node
*cpu_port
;
183 struct device_node
*cpu_ep
;
184 struct device_node
*codec_ep
;
185 struct device_node
*rcpu_ep
;
186 struct device_node
*codec_port
;
187 struct device_node
*codec_port_old
;
188 unsigned int daifmt
= 0;
196 * we need to consider "widgets", "mclk-fs" around here
200 ret
= asoc_simple_card_of_parse_routing(card
, NULL
, 0);
204 asoc_simple_card_parse_convert(dev
, NULL
, &priv
->adata
);
207 * it supports multi CPU, single CODEC only here
208 * see asoc_graph_get_dais_count
212 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
214 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
215 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
216 rcpu_ep
= of_graph_get_remote_endpoint(codec_ep
);
219 of_node_put(codec_ep
);
220 of_node_put(rcpu_ep
);
225 if (rcpu_ep
!= cpu_ep
) {
226 dev_err(dev
, "remote-endpoint missmatch (%s/%s/%s)\n",
227 cpu_ep
->name
, codec_ep
->name
, rcpu_ep
->name
);
232 ret
= asoc_simple_card_parse_daifmt(dev
, cpu_ep
, codec_ep
,
235 of_node_put(cpu_port
);
241 codec_port_old
= NULL
;
242 for (codec
= 0; codec
< 2; codec
++) {
244 * To listup valid sounds continuously,
245 * detect all CPU-dummy first, and
246 * detect all dummy-Codec second
248 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
250 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
251 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
252 codec_port
= of_graph_get_port_parent(codec_ep
);
255 of_node_put(codec_ep
);
256 of_node_put(codec_port
);
262 if (codec_port_old
== codec_port
)
265 codec_port_old
= codec_port
;
267 /* Back-End (= Codec) */
268 ret
= asoc_graph_card_dai_link_of(codec_ep
, priv
, daifmt
, dai_idx
++, 0);
270 of_node_put(cpu_port
);
274 /* Front-End (= CPU) */
275 ret
= asoc_graph_card_dai_link_of(cpu_ep
, priv
, daifmt
, dai_idx
++, 1);
277 of_node_put(cpu_port
);
284 ret
= asoc_simple_card_parse_card_name(card
, NULL
);
294 static int asoc_graph_get_dais_count(struct device
*dev
)
296 struct of_phandle_iterator it
;
297 struct device_node
*node
= dev
->of_node
;
298 struct device_node
*cpu_port
;
299 struct device_node
*cpu_ep
;
300 struct device_node
*codec_ep
;
301 struct device_node
*codec_port
;
302 struct device_node
*codec_port_old
;
306 codec_port_old
= NULL
;
307 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
309 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
310 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
311 codec_port
= of_graph_get_port_parent(codec_ep
);
314 of_node_put(codec_ep
);
315 of_node_put(codec_port
);
323 if (codec_port_old
== codec_port
)
327 codec_port_old
= codec_port
;
333 static int asoc_graph_card_probe(struct platform_device
*pdev
)
335 struct graph_card_data
*priv
;
336 struct snd_soc_dai_link
*dai_link
;
337 struct asoc_simple_dai
*dai_props
;
338 struct device
*dev
= &pdev
->dev
;
339 struct snd_soc_card
*card
;
342 /* Allocate the private data and the DAI link array */
343 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
347 num
= asoc_graph_get_dais_count(dev
);
351 dai_props
= devm_kzalloc(dev
, sizeof(*dai_props
) * num
, GFP_KERNEL
);
352 dai_link
= devm_kzalloc(dev
, sizeof(*dai_link
) * num
, GFP_KERNEL
);
353 if (!dai_props
|| !dai_link
)
356 priv
->dai_props
= dai_props
;
357 priv
->dai_link
= dai_link
;
359 /* Init snd_soc_card */
360 card
= graph_priv_to_card(priv
);
361 card
->owner
= THIS_MODULE
;
363 card
->dai_link
= priv
->dai_link
;
364 card
->num_links
= num
;
365 card
->codec_conf
= &priv
->codec_conf
;
366 card
->num_configs
= 1;
368 ret
= asoc_graph_card_parse_of(priv
);
370 if (ret
!= -EPROBE_DEFER
)
371 dev_err(dev
, "parse error %d\n", ret
);
375 snd_soc_card_set_drvdata(card
, priv
);
377 ret
= devm_snd_soc_register_card(dev
, card
);
383 asoc_simple_card_clean_reference(card
);
388 static int asoc_graph_card_remove(struct platform_device
*pdev
)
390 struct snd_soc_card
*card
= platform_get_drvdata(pdev
);
392 return asoc_simple_card_clean_reference(card
);
395 static const struct of_device_id asoc_graph_of_match
[] = {
396 { .compatible
= "audio-graph-scu-card", },
399 MODULE_DEVICE_TABLE(of
, asoc_graph_of_match
);
401 static struct platform_driver asoc_graph_card
= {
403 .name
= "asoc-audio-graph-scu-card",
404 .pm
= &snd_soc_pm_ops
,
405 .of_match_table
= asoc_graph_of_match
,
407 .probe
= asoc_graph_card_probe
,
408 .remove
= asoc_graph_card_remove
,
410 module_platform_driver(asoc_graph_card
);
412 MODULE_ALIAS("platform:asoc-audio-graph-scu-card");
413 MODULE_LICENSE("GPL v2");
414 MODULE_DESCRIPTION("ASoC Audio Graph SCU Sound Card");
415 MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");