diff options
author | Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | 2017-04-19 21:36:08 -0400 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2017-05-17 05:21:21 -0400 |
commit | 2692c1c63c29cad3bec090c3e6ed9e692ca66683 (patch) | |
tree | 6ad6474be82a80a91964a7ee49f5ecea0b111235 | |
parent | 2d4e31de5bb2b5fbdbcd8a3bfec0eae0bd4ca409 (diff) |
ASoC: add audio-graph-card support
OF-graph base DT binding are used on V4L2, and ALSA SoC is using
different style of DT today. Now ALSA SoC supports simple-card driver
for generic/simple sound card.
In the future, V4L2 / ALSA will support HDMI, and then, DT bindings
between V4L2 / ALSA should be merged.
This patch adds new Audio Graph Card which is OF-graph base of
simple-card
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r-- | sound/soc/generic/Kconfig | 8 | ||||
-rw-r--r-- | sound/soc/generic/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/generic/audio-graph-card.c | 308 |
3 files changed, 318 insertions, 0 deletions
diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index d023959b8cd6..121a48e8bb7d 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig | |||
@@ -14,3 +14,11 @@ config SND_SIMPLE_SCU_CARD | |||
14 | help | 14 | help |
15 | This option enables generic simple SCU sound card support. | 15 | This option enables generic simple SCU sound card support. |
16 | It supports DPCM of multi CPU single Codec system. | 16 | It supports DPCM of multi CPU single Codec system. |
17 | |||
18 | config SND_AUDIO_GRAPH_CARD | ||
19 | tristate "ASoC Audio Graph sound card support" | ||
20 | depends on OF | ||
21 | select SND_SIMPLE_CARD_UTILS | ||
22 | help | ||
23 | This option enables generic simple simple sound card support | ||
24 | with OF-graph DT bindings. | ||
diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index ee750f3023ba..670068f257b9 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile | |||
@@ -1,7 +1,9 @@ | |||
1 | snd-soc-simple-card-utils-objs := simple-card-utils.o | 1 | snd-soc-simple-card-utils-objs := simple-card-utils.o |
2 | snd-soc-simple-card-objs := simple-card.o | 2 | snd-soc-simple-card-objs := simple-card.o |
3 | snd-soc-simple-scu-card-objs := simple-scu-card.o | 3 | snd-soc-simple-scu-card-objs := simple-scu-card.o |
4 | snd-soc-audio-graph-card-objs := audio-graph-card.o | ||
4 | 5 | ||
5 | obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o | 6 | obj-$(CONFIG_SND_SIMPLE_CARD_UTILS) += snd-soc-simple-card-utils.o |
6 | obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o | 7 | obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o |
7 | obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o | 8 | obj-$(CONFIG_SND_SIMPLE_SCU_CARD) += snd-soc-simple-scu-card.o |
9 | obj-$(CONFIG_SND_AUDIO_GRAPH_CARD) += snd-soc-audio-graph-card.o | ||
diff --git a/sound/soc/generic/audio-graph-card.c b/sound/soc/generic/audio-graph-card.c new file mode 100644 index 000000000000..07e010d38596 --- /dev/null +++ b/sound/soc/generic/audio-graph-card.c | |||
@@ -0,0 +1,308 @@ | |||
1 | /* | ||
2 | * ASoC audio graph sound card support | ||
3 | * | ||
4 | * Copyright (C) 2016 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/gpio.h> | ||
16 | #include <linux/module.h> | ||
17 | #include <linux/of.h> | ||
18 | #include <linux/of_device.h> | ||
19 | #include <linux/of_gpio.h> | ||
20 | #include <linux/of_graph.h> | ||
21 | #include <linux/platform_device.h> | ||
22 | #include <linux/string.h> | ||
23 | #include <sound/jack.h> | ||
24 | #include <sound/simple_card_utils.h> | ||
25 | |||
26 | struct graph_card_data { | ||
27 | struct snd_soc_card snd_card; | ||
28 | struct graph_dai_props { | ||
29 | struct asoc_simple_dai cpu_dai; | ||
30 | struct asoc_simple_dai codec_dai; | ||
31 | } *dai_props; | ||
32 | struct snd_soc_dai_link *dai_link; | ||
33 | }; | ||
34 | |||
35 | #define graph_priv_to_card(priv) (&(priv)->snd_card) | ||
36 | #define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) | ||
37 | #define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) | ||
38 | #define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) | ||
39 | |||
40 | static int asoc_graph_card_startup(struct snd_pcm_substream *substream) | ||
41 | { | ||
42 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
43 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | ||
44 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); | ||
45 | int ret; | ||
46 | |||
47 | ret = clk_prepare_enable(dai_props->cpu_dai.clk); | ||
48 | if (ret) | ||
49 | return ret; | ||
50 | |||
51 | ret = clk_prepare_enable(dai_props->codec_dai.clk); | ||
52 | if (ret) | ||
53 | clk_disable_unprepare(dai_props->cpu_dai.clk); | ||
54 | |||
55 | return ret; | ||
56 | } | ||
57 | |||
58 | static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream) | ||
59 | { | ||
60 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
61 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | ||
62 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); | ||
63 | |||
64 | clk_disable_unprepare(dai_props->cpu_dai.clk); | ||
65 | |||
66 | clk_disable_unprepare(dai_props->codec_dai.clk); | ||
67 | } | ||
68 | |||
69 | static struct snd_soc_ops asoc_graph_card_ops = { | ||
70 | .startup = asoc_graph_card_startup, | ||
71 | .shutdown = asoc_graph_card_shutdown, | ||
72 | }; | ||
73 | |||
74 | static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd) | ||
75 | { | ||
76 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | ||
77 | struct snd_soc_dai *codec = rtd->codec_dai; | ||
78 | struct snd_soc_dai *cpu = rtd->cpu_dai; | ||
79 | struct graph_dai_props *dai_props = | ||
80 | graph_priv_to_props(priv, rtd->num); | ||
81 | int ret; | ||
82 | |||
83 | ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); | ||
84 | if (ret < 0) | ||
85 | return ret; | ||
86 | |||
87 | ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); | ||
88 | if (ret < 0) | ||
89 | return ret; | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static int asoc_graph_card_dai_link_of(struct device_node *cpu_port, | ||
95 | struct graph_card_data *priv, | ||
96 | int idx) | ||
97 | { | ||
98 | struct device *dev = graph_priv_to_dev(priv); | ||
99 | struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx); | ||
100 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, idx); | ||
101 | struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; | ||
102 | struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; | ||
103 | struct snd_soc_card *card = graph_priv_to_card(priv); | ||
104 | struct device_node *cpu_ep = of_get_next_child(cpu_port, NULL); | ||
105 | struct device_node *codec_ep = of_graph_get_remote_endpoint(cpu_ep); | ||
106 | struct device_node *rcpu_ep = of_graph_get_remote_endpoint(codec_ep); | ||
107 | int ret; | ||
108 | |||
109 | if (rcpu_ep != cpu_ep) { | ||
110 | dev_err(dev, "remote-endpoint missmatch (%s/%s/%s)\n", | ||
111 | cpu_ep->name, codec_ep->name, rcpu_ep->name); | ||
112 | ret = -EINVAL; | ||
113 | goto dai_link_of_err; | ||
114 | } | ||
115 | |||
116 | ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, | ||
117 | NULL, &dai_link->dai_fmt); | ||
118 | if (ret < 0) | ||
119 | goto dai_link_of_err; | ||
120 | |||
121 | /* | ||
122 | * we need to consider "mclk-fs" around here | ||
123 | * see simple-card | ||
124 | */ | ||
125 | |||
126 | ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); | ||
127 | if (ret < 0) | ||
128 | goto dai_link_of_err; | ||
129 | |||
130 | ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); | ||
131 | if (ret < 0) | ||
132 | goto dai_link_of_err; | ||
133 | |||
134 | ret = snd_soc_of_parse_tdm_slot(cpu_ep, | ||
135 | &cpu_dai->tx_slot_mask, | ||
136 | &cpu_dai->rx_slot_mask, | ||
137 | &cpu_dai->slots, | ||
138 | &cpu_dai->slot_width); | ||
139 | if (ret < 0) | ||
140 | goto dai_link_of_err; | ||
141 | |||
142 | ret = snd_soc_of_parse_tdm_slot(codec_ep, | ||
143 | &codec_dai->tx_slot_mask, | ||
144 | &codec_dai->rx_slot_mask, | ||
145 | &codec_dai->slots, | ||
146 | &codec_dai->slot_width); | ||
147 | if (ret < 0) | ||
148 | goto dai_link_of_err; | ||
149 | |||
150 | ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); | ||
151 | if (ret < 0) | ||
152 | goto dai_link_of_err; | ||
153 | |||
154 | ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); | ||
155 | if (ret < 0) | ||
156 | goto dai_link_of_err; | ||
157 | |||
158 | ret = asoc_simple_card_canonicalize_dailink(dai_link); | ||
159 | if (ret < 0) | ||
160 | goto dai_link_of_err; | ||
161 | |||
162 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, | ||
163 | "%s-%s", | ||
164 | dai_link->cpu_dai_name, | ||
165 | dai_link->codec_dai_name); | ||
166 | if (ret < 0) | ||
167 | goto dai_link_of_err; | ||
168 | |||
169 | dai_link->ops = &asoc_graph_card_ops; | ||
170 | dai_link->init = asoc_graph_card_dai_init; | ||
171 | |||
172 | dev_dbg(dev, "\tname : %s\n", dai_link->stream_name); | ||
173 | dev_dbg(dev, "\tformat : %04x\n", dai_link->dai_fmt); | ||
174 | dev_dbg(dev, "\tcpu : %s / %d\n", | ||
175 | dai_link->cpu_dai_name, | ||
176 | cpu_dai->sysclk); | ||
177 | dev_dbg(dev, "\tcodec : %s / %d\n", | ||
178 | dai_link->codec_dai_name, | ||
179 | codec_dai->sysclk); | ||
180 | |||
181 | asoc_simple_card_canonicalize_cpu(dai_link, | ||
182 | card->num_links == 1); | ||
183 | |||
184 | dai_link_of_err: | ||
185 | of_node_put(cpu_ep); | ||
186 | of_node_put(rcpu_ep); | ||
187 | of_node_put(codec_ep); | ||
188 | |||
189 | return ret; | ||
190 | } | ||
191 | |||
192 | static int asoc_graph_card_parse_of(struct graph_card_data *priv) | ||
193 | { | ||
194 | struct of_phandle_iterator it; | ||
195 | struct device *dev = graph_priv_to_dev(priv); | ||
196 | struct snd_soc_card *card = graph_priv_to_card(priv); | ||
197 | struct device_node *node = dev->of_node; | ||
198 | int rc, idx = 0; | ||
199 | int ret; | ||
200 | |||
201 | /* | ||
202 | * we need to consider "widgets", "routing", "mclk-fs" around here | ||
203 | * see simple-card | ||
204 | */ | ||
205 | |||
206 | of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { | ||
207 | ret = asoc_graph_card_dai_link_of(it.node, priv, idx++); | ||
208 | of_node_put(it.node); | ||
209 | if (ret < 0) | ||
210 | return ret; | ||
211 | } | ||
212 | |||
213 | return asoc_simple_card_parse_card_name(card, NULL); | ||
214 | } | ||
215 | |||
216 | static int asoc_graph_get_dais_count(struct device *dev) | ||
217 | { | ||
218 | struct of_phandle_iterator it; | ||
219 | struct device_node *node = dev->of_node; | ||
220 | int count = 0; | ||
221 | int rc; | ||
222 | |||
223 | of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { | ||
224 | count++; | ||
225 | of_node_put(it.node); | ||
226 | } | ||
227 | |||
228 | return count; | ||
229 | } | ||
230 | |||
231 | static int asoc_graph_card_probe(struct platform_device *pdev) | ||
232 | { | ||
233 | struct graph_card_data *priv; | ||
234 | struct snd_soc_dai_link *dai_link; | ||
235 | struct graph_dai_props *dai_props; | ||
236 | struct device *dev = &pdev->dev; | ||
237 | struct snd_soc_card *card; | ||
238 | int num, ret; | ||
239 | |||
240 | /* Allocate the private data and the DAI link array */ | ||
241 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
242 | if (!priv) | ||
243 | return -ENOMEM; | ||
244 | |||
245 | num = asoc_graph_get_dais_count(dev); | ||
246 | if (num == 0) | ||
247 | return -EINVAL; | ||
248 | |||
249 | dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL); | ||
250 | dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL); | ||
251 | if (!dai_props || !dai_link) | ||
252 | return -ENOMEM; | ||
253 | |||
254 | priv->dai_props = dai_props; | ||
255 | priv->dai_link = dai_link; | ||
256 | |||
257 | /* Init snd_soc_card */ | ||
258 | card = graph_priv_to_card(priv); | ||
259 | card->owner = THIS_MODULE; | ||
260 | card->dev = dev; | ||
261 | card->dai_link = dai_link; | ||
262 | card->num_links = num; | ||
263 | |||
264 | ret = asoc_graph_card_parse_of(priv); | ||
265 | if (ret < 0) { | ||
266 | if (ret != -EPROBE_DEFER) | ||
267 | dev_err(dev, "parse error %d\n", ret); | ||
268 | goto err; | ||
269 | } | ||
270 | |||
271 | snd_soc_card_set_drvdata(card, priv); | ||
272 | |||
273 | ret = devm_snd_soc_register_card(dev, card); | ||
274 | if (ret >= 0) | ||
275 | return ret; | ||
276 | err: | ||
277 | asoc_simple_card_clean_reference(card); | ||
278 | |||
279 | return ret; | ||
280 | } | ||
281 | |||
282 | static int asoc_graph_card_remove(struct platform_device *pdev) | ||
283 | { | ||
284 | struct snd_soc_card *card = platform_get_drvdata(pdev); | ||
285 | |||
286 | return asoc_simple_card_clean_reference(card); | ||
287 | } | ||
288 | |||
289 | static const struct of_device_id asoc_graph_of_match[] = { | ||
290 | { .compatible = "audio-graph-card", }, | ||
291 | {}, | ||
292 | }; | ||
293 | MODULE_DEVICE_TABLE(of, asoc_graph_of_match); | ||
294 | |||
295 | static struct platform_driver asoc_graph_card = { | ||
296 | .driver = { | ||
297 | .name = "asoc-audio-graph-card", | ||
298 | .of_match_table = asoc_graph_of_match, | ||
299 | }, | ||
300 | .probe = asoc_graph_card_probe, | ||
301 | .remove = asoc_graph_card_remove, | ||
302 | }; | ||
303 | module_platform_driver(asoc_graph_card); | ||
304 | |||
305 | MODULE_ALIAS("platform:asoc-audio-graph-card"); | ||
306 | MODULE_LICENSE("GPL v2"); | ||
307 | MODULE_DESCRIPTION("ASoC Audio Graph Sound Card"); | ||
308 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | ||