1 // SPDX-License-Identifier: GPL-2.0
3 // ASoC audio graph SCU sound card support
5 // Copyright (C) 2017 Renesas Solutions Corp.
6 // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
9 // ${LINUX}/sound/soc/generic/simple-scu-card.c
10 // ${LINUX}/sound/soc/generic/audio-graph-card.c
12 #include <linux/clk.h>
13 #include <linux/device.h>
14 #include <linux/gpio.h>
15 #include <linux/module.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>
25 struct graph_card_data
{
26 struct snd_soc_card snd_card
;
27 struct snd_soc_codec_conf codec_conf
;
28 struct asoc_simple_dai
*dai_props
;
29 struct snd_soc_dai_link
*dai_link
;
30 struct asoc_simple_card_data adata
;
33 #define graph_priv_to_card(priv) (&(priv)->snd_card)
34 #define graph_priv_to_props(priv, i) ((priv)->dai_props + (i))
35 #define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev)
36 #define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i))
38 static int asoc_graph_card_startup(struct snd_pcm_substream
*substream
)
40 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
41 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
42 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, rtd
->num
);
44 return asoc_simple_card_clk_enable(dai_props
);
47 static void asoc_graph_card_shutdown(struct snd_pcm_substream
*substream
)
49 struct snd_soc_pcm_runtime
*rtd
= substream
->private_data
;
50 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
51 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, rtd
->num
);
53 asoc_simple_card_clk_disable(dai_props
);
56 static const struct snd_soc_ops asoc_graph_card_ops
= {
57 .startup
= asoc_graph_card_startup
,
58 .shutdown
= asoc_graph_card_shutdown
,
61 static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime
*rtd
)
63 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
64 struct snd_soc_dai
*dai
;
65 struct snd_soc_dai_link
*dai_link
;
66 struct asoc_simple_dai
*dai_props
;
69 dai_link
= graph_priv_to_link(priv
, num
);
70 dai_props
= graph_priv_to_props(priv
, num
);
71 dai
= dai_link
->dynamic
?
75 return asoc_simple_card_init_dai(dai
, dai_props
);
78 static int asoc_graph_card_be_hw_params_fixup(struct snd_soc_pcm_runtime
*rtd
,
79 struct snd_pcm_hw_params
*params
)
81 struct graph_card_data
*priv
= snd_soc_card_get_drvdata(rtd
->card
);
83 asoc_simple_card_convert_fixup(&priv
->adata
, params
);
88 static int asoc_graph_card_dai_link_of(struct device_node
*ep
,
89 struct graph_card_data
*priv
,
93 struct device
*dev
= graph_priv_to_dev(priv
);
94 struct snd_soc_dai_link
*dai_link
= graph_priv_to_link(priv
, idx
);
95 struct asoc_simple_dai
*dai_props
= graph_priv_to_props(priv
, idx
);
96 struct snd_soc_card
*card
= graph_priv_to_card(priv
);
101 dai_link
->codec_of_node
= NULL
;
102 dai_link
->codec_dai_name
= "snd-soc-dummy-dai";
103 dai_link
->codec_name
= "snd-soc-dummy";
106 dai_link
->dynamic
= 1;
107 dai_link
->dpcm_merged_format
= 1;
109 ret
= asoc_simple_card_parse_graph_cpu(ep
, dai_link
);
113 ret
= asoc_simple_card_parse_clk_cpu(dev
, ep
, dai_link
, dai_props
);
117 ret
= asoc_simple_card_set_dailink_name(dev
, dai_link
,
119 dai_link
->cpu_dai_name
);
123 /* card->num_links includes Codec */
124 asoc_simple_card_canonicalize_cpu(dai_link
,
125 of_graph_get_endpoint_count(dai_link
->cpu_of_node
) == 1);
128 dai_link
->cpu_of_node
= NULL
;
129 dai_link
->cpu_dai_name
= "snd-soc-dummy-dai";
130 dai_link
->cpu_name
= "snd-soc-dummy";
133 dai_link
->no_pcm
= 1;
134 dai_link
->be_hw_params_fixup
= asoc_graph_card_be_hw_params_fixup
;
136 ret
= asoc_simple_card_parse_graph_codec(ep
, dai_link
);
140 ret
= asoc_simple_card_parse_clk_codec(dev
, ep
, dai_link
, dai_props
);
144 ret
= asoc_simple_card_set_dailink_name(dev
, dai_link
,
146 dai_link
->codec_dai_name
);
150 snd_soc_of_parse_audio_prefix(card
,
152 dai_link
->codec_of_node
,
156 ret
= asoc_simple_card_of_parse_tdm(ep
, dai_props
);
160 ret
= asoc_simple_card_canonicalize_dailink(dai_link
);
164 dai_link
->dai_fmt
= daifmt
;
165 dai_link
->dpcm_playback
= 1;
166 dai_link
->dpcm_capture
= 1;
167 dai_link
->ops
= &asoc_graph_card_ops
;
168 dai_link
->init
= asoc_graph_card_dai_init
;
173 static int asoc_graph_card_parse_of(struct graph_card_data
*priv
)
175 struct of_phandle_iterator it
;
176 struct device
*dev
= graph_priv_to_dev(priv
);
177 struct snd_soc_card
*card
= graph_priv_to_card(priv
);
178 struct device_node
*node
= dev
->of_node
;
179 struct device_node
*cpu_port
;
180 struct device_node
*cpu_ep
;
181 struct device_node
*codec_ep
;
182 struct device_node
*rcpu_ep
;
183 struct device_node
*codec_port
;
184 struct device_node
*codec_port_old
;
185 unsigned int daifmt
= 0;
193 * we need to consider "widgets", "mclk-fs" around here
197 ret
= asoc_simple_card_of_parse_routing(card
, NULL
, 0);
201 asoc_simple_card_parse_convert(dev
, NULL
, &priv
->adata
);
204 * it supports multi CPU, single CODEC only here
205 * see asoc_graph_get_dais_count
209 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
211 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
212 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
213 rcpu_ep
= of_graph_get_remote_endpoint(codec_ep
);
216 of_node_put(codec_ep
);
217 of_node_put(rcpu_ep
);
222 if (rcpu_ep
!= cpu_ep
) {
223 dev_err(dev
, "remote-endpoint missmatch (%s/%s/%s)\n",
224 cpu_ep
->name
, codec_ep
->name
, rcpu_ep
->name
);
229 ret
= asoc_simple_card_parse_daifmt(dev
, cpu_ep
, codec_ep
,
232 of_node_put(cpu_port
);
238 codec_port_old
= NULL
;
239 for (codec
= 0; codec
< 2; codec
++) {
241 * To listup valid sounds continuously,
242 * detect all CPU-dummy first, and
243 * detect all dummy-Codec second
245 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
247 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
248 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
249 codec_port
= of_graph_get_port_parent(codec_ep
);
252 of_node_put(codec_ep
);
253 of_node_put(codec_port
);
259 if (codec_port_old
== codec_port
)
262 codec_port_old
= codec_port
;
264 /* Back-End (= Codec) */
265 ret
= asoc_graph_card_dai_link_of(codec_ep
, priv
, daifmt
, dai_idx
++, 0);
267 of_node_put(cpu_port
);
271 /* Front-End (= CPU) */
272 ret
= asoc_graph_card_dai_link_of(cpu_ep
, priv
, daifmt
, dai_idx
++, 1);
274 of_node_put(cpu_port
);
281 ret
= asoc_simple_card_parse_card_name(card
, NULL
);
291 static int asoc_graph_get_dais_count(struct device
*dev
)
293 struct of_phandle_iterator it
;
294 struct device_node
*node
= dev
->of_node
;
295 struct device_node
*cpu_port
;
296 struct device_node
*cpu_ep
;
297 struct device_node
*codec_ep
;
298 struct device_node
*codec_port
;
299 struct device_node
*codec_port_old
;
303 codec_port_old
= NULL
;
304 of_for_each_phandle(&it
, rc
, node
, "dais", NULL
, 0) {
306 cpu_ep
= of_get_next_child(cpu_port
, NULL
);
307 codec_ep
= of_graph_get_remote_endpoint(cpu_ep
);
308 codec_port
= of_graph_get_port_parent(codec_ep
);
311 of_node_put(codec_ep
);
312 of_node_put(codec_port
);
320 if (codec_port_old
== codec_port
)
324 codec_port_old
= codec_port
;
330 static int asoc_graph_card_probe(struct platform_device
*pdev
)
332 struct graph_card_data
*priv
;
333 struct snd_soc_dai_link
*dai_link
;
334 struct asoc_simple_dai
*dai_props
;
335 struct device
*dev
= &pdev
->dev
;
336 struct snd_soc_card
*card
;
339 /* Allocate the private data and the DAI link array */
340 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
344 num
= asoc_graph_get_dais_count(dev
);
348 dai_props
= devm_kcalloc(dev
, num
, sizeof(*dai_props
), GFP_KERNEL
);
349 dai_link
= devm_kcalloc(dev
, num
, sizeof(*dai_link
), GFP_KERNEL
);
350 if (!dai_props
|| !dai_link
)
353 priv
->dai_props
= dai_props
;
354 priv
->dai_link
= dai_link
;
356 /* Init snd_soc_card */
357 card
= graph_priv_to_card(priv
);
358 card
->owner
= THIS_MODULE
;
360 card
->dai_link
= priv
->dai_link
;
361 card
->num_links
= num
;
362 card
->codec_conf
= &priv
->codec_conf
;
363 card
->num_configs
= 1;
365 ret
= asoc_graph_card_parse_of(priv
);
367 if (ret
!= -EPROBE_DEFER
)
368 dev_err(dev
, "parse error %d\n", ret
);
372 snd_soc_card_set_drvdata(card
, priv
);
374 ret
= devm_snd_soc_register_card(dev
, card
);
380 asoc_simple_card_clean_reference(card
);
385 static int asoc_graph_card_remove(struct platform_device
*pdev
)
387 struct snd_soc_card
*card
= platform_get_drvdata(pdev
);
389 return asoc_simple_card_clean_reference(card
);
392 static const struct of_device_id asoc_graph_of_match
[] = {
393 { .compatible
= "audio-graph-scu-card", },
396 MODULE_DEVICE_TABLE(of
, asoc_graph_of_match
);
398 static struct platform_driver asoc_graph_card
= {
400 .name
= "asoc-audio-graph-scu-card",
401 .pm
= &snd_soc_pm_ops
,
402 .of_match_table
= asoc_graph_of_match
,
404 .probe
= asoc_graph_card_probe
,
405 .remove
= asoc_graph_card_remove
,
407 module_platform_driver(asoc_graph_card
);
409 MODULE_ALIAS("platform:asoc-audio-graph-scu-card");
410 MODULE_LICENSE("GPL v2");
411 MODULE_DESCRIPTION("ASoC Audio Graph SCU Sound Card");
412 MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");