]>
Commit | Line | Data |
---|---|---|
2692c1c6 KM |
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> | |
f986907c | 16 | #include <linux/gpio/consumer.h> |
2692c1c6 KM |
17 | #include <linux/module.h> |
18 | #include <linux/of.h> | |
19 | #include <linux/of_device.h> | |
20 | #include <linux/of_gpio.h> | |
21 | #include <linux/of_graph.h> | |
22 | #include <linux/platform_device.h> | |
23 | #include <linux/string.h> | |
24 | #include <sound/jack.h> | |
25 | #include <sound/simple_card_utils.h> | |
26 | ||
27 | struct graph_card_data { | |
28 | struct snd_soc_card snd_card; | |
29 | struct graph_dai_props { | |
30 | struct asoc_simple_dai cpu_dai; | |
31 | struct asoc_simple_dai codec_dai; | |
32 | } *dai_props; | |
33 | struct snd_soc_dai_link *dai_link; | |
f986907c SG |
34 | struct gpio_desc *pa_gpio; |
35 | }; | |
36 | ||
37 | static int asoc_graph_card_outdrv_event(struct snd_soc_dapm_widget *w, | |
38 | struct snd_kcontrol *kcontrol, | |
39 | int event) | |
40 | { | |
41 | struct snd_soc_dapm_context *dapm = w->dapm; | |
42 | struct graph_card_data *priv = snd_soc_card_get_drvdata(dapm->card); | |
43 | ||
44 | switch (event) { | |
45 | case SND_SOC_DAPM_POST_PMU: | |
46 | gpiod_set_value_cansleep(priv->pa_gpio, 1); | |
47 | break; | |
48 | case SND_SOC_DAPM_PRE_PMD: | |
49 | gpiod_set_value_cansleep(priv->pa_gpio, 0); | |
50 | break; | |
51 | default: | |
52 | return -EINVAL; | |
53 | } | |
54 | ||
55 | return 0; | |
56 | } | |
57 | ||
58 | static const struct snd_soc_dapm_widget asoc_graph_card_dapm_widgets[] = { | |
59 | SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM, | |
60 | 0, 0, NULL, 0, asoc_graph_card_outdrv_event, | |
61 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), | |
2692c1c6 KM |
62 | }; |
63 | ||
64 | #define graph_priv_to_card(priv) (&(priv)->snd_card) | |
65 | #define graph_priv_to_props(priv, i) ((priv)->dai_props + (i)) | |
66 | #define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev) | |
67 | #define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i)) | |
68 | ||
69 | static int asoc_graph_card_startup(struct snd_pcm_substream *substream) | |
70 | { | |
71 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
72 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | |
73 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); | |
74 | int ret; | |
75 | ||
d471d559 | 76 | ret = asoc_simple_card_clk_enable(&dai_props->cpu_dai); |
2692c1c6 KM |
77 | if (ret) |
78 | return ret; | |
79 | ||
d471d559 | 80 | ret = asoc_simple_card_clk_enable(&dai_props->codec_dai); |
2692c1c6 | 81 | if (ret) |
d471d559 | 82 | asoc_simple_card_clk_disable(&dai_props->cpu_dai); |
2692c1c6 KM |
83 | |
84 | return ret; | |
85 | } | |
86 | ||
87 | static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream) | |
88 | { | |
89 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
90 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | |
91 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, rtd->num); | |
92 | ||
d471d559 | 93 | asoc_simple_card_clk_disable(&dai_props->cpu_dai); |
2692c1c6 | 94 | |
d471d559 | 95 | asoc_simple_card_clk_disable(&dai_props->codec_dai); |
2692c1c6 KM |
96 | } |
97 | ||
98 | static struct snd_soc_ops asoc_graph_card_ops = { | |
99 | .startup = asoc_graph_card_startup, | |
100 | .shutdown = asoc_graph_card_shutdown, | |
101 | }; | |
102 | ||
103 | static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd) | |
104 | { | |
105 | struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card); | |
106 | struct snd_soc_dai *codec = rtd->codec_dai; | |
107 | struct snd_soc_dai *cpu = rtd->cpu_dai; | |
108 | struct graph_dai_props *dai_props = | |
109 | graph_priv_to_props(priv, rtd->num); | |
110 | int ret; | |
111 | ||
112 | ret = asoc_simple_card_init_dai(codec, &dai_props->codec_dai); | |
113 | if (ret < 0) | |
114 | return ret; | |
115 | ||
116 | ret = asoc_simple_card_init_dai(cpu, &dai_props->cpu_dai); | |
117 | if (ret < 0) | |
118 | return ret; | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | static int asoc_graph_card_dai_link_of(struct device_node *cpu_port, | |
124 | struct graph_card_data *priv, | |
125 | int idx) | |
126 | { | |
127 | struct device *dev = graph_priv_to_dev(priv); | |
128 | struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx); | |
129 | struct graph_dai_props *dai_props = graph_priv_to_props(priv, idx); | |
130 | struct asoc_simple_dai *cpu_dai = &dai_props->cpu_dai; | |
131 | struct asoc_simple_dai *codec_dai = &dai_props->codec_dai; | |
2692c1c6 KM |
132 | struct device_node *cpu_ep = of_get_next_child(cpu_port, NULL); |
133 | struct device_node *codec_ep = of_graph_get_remote_endpoint(cpu_ep); | |
134 | struct device_node *rcpu_ep = of_graph_get_remote_endpoint(codec_ep); | |
135 | int ret; | |
136 | ||
137 | if (rcpu_ep != cpu_ep) { | |
a619f049 | 138 | dev_err(dev, "remote-endpoint mismatch (%s/%s/%s)\n", |
2692c1c6 KM |
139 | cpu_ep->name, codec_ep->name, rcpu_ep->name); |
140 | ret = -EINVAL; | |
141 | goto dai_link_of_err; | |
142 | } | |
143 | ||
144 | ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep, | |
145 | NULL, &dai_link->dai_fmt); | |
146 | if (ret < 0) | |
147 | goto dai_link_of_err; | |
148 | ||
149 | /* | |
150 | * we need to consider "mclk-fs" around here | |
151 | * see simple-card | |
152 | */ | |
153 | ||
154 | ret = asoc_simple_card_parse_graph_cpu(cpu_ep, dai_link); | |
155 | if (ret < 0) | |
156 | goto dai_link_of_err; | |
157 | ||
158 | ret = asoc_simple_card_parse_graph_codec(codec_ep, dai_link); | |
159 | if (ret < 0) | |
160 | goto dai_link_of_err; | |
161 | ||
c98907d5 | 162 | ret = asoc_simple_card_of_parse_tdm(cpu_ep, cpu_dai); |
2692c1c6 KM |
163 | if (ret < 0) |
164 | goto dai_link_of_err; | |
165 | ||
c98907d5 | 166 | ret = asoc_simple_card_of_parse_tdm(codec_ep, codec_dai); |
2692c1c6 KM |
167 | if (ret < 0) |
168 | goto dai_link_of_err; | |
169 | ||
170 | ret = asoc_simple_card_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); | |
171 | if (ret < 0) | |
172 | goto dai_link_of_err; | |
173 | ||
174 | ret = asoc_simple_card_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); | |
175 | if (ret < 0) | |
176 | goto dai_link_of_err; | |
177 | ||
178 | ret = asoc_simple_card_canonicalize_dailink(dai_link); | |
179 | if (ret < 0) | |
180 | goto dai_link_of_err; | |
181 | ||
182 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, | |
183 | "%s-%s", | |
184 | dai_link->cpu_dai_name, | |
185 | dai_link->codec_dai_name); | |
186 | if (ret < 0) | |
187 | goto dai_link_of_err; | |
188 | ||
189 | dai_link->ops = &asoc_graph_card_ops; | |
190 | dai_link->init = asoc_graph_card_dai_init; | |
191 | ||
2692c1c6 | 192 | asoc_simple_card_canonicalize_cpu(dai_link, |
47ca9593 | 193 | of_graph_get_endpoint_count(dai_link->cpu_of_node) == 1); |
2692c1c6 KM |
194 | |
195 | dai_link_of_err: | |
196 | of_node_put(cpu_ep); | |
197 | of_node_put(rcpu_ep); | |
198 | of_node_put(codec_ep); | |
199 | ||
200 | return ret; | |
201 | } | |
202 | ||
203 | static int asoc_graph_card_parse_of(struct graph_card_data *priv) | |
204 | { | |
205 | struct of_phandle_iterator it; | |
206 | struct device *dev = graph_priv_to_dev(priv); | |
207 | struct snd_soc_card *card = graph_priv_to_card(priv); | |
208 | struct device_node *node = dev->of_node; | |
209 | int rc, idx = 0; | |
210 | int ret; | |
211 | ||
f986907c SG |
212 | ret = asoc_simple_card_of_parse_widgets(card, NULL); |
213 | if (ret < 0) | |
214 | return ret; | |
215 | ||
216 | ret = asoc_simple_card_of_parse_routing(card, NULL, 1); | |
217 | if (ret < 0) | |
218 | return ret; | |
219 | ||
2692c1c6 | 220 | /* |
f986907c | 221 | * we need to consider "mclk-fs" around here |
2692c1c6 KM |
222 | * see simple-card |
223 | */ | |
224 | ||
225 | of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { | |
226 | ret = asoc_graph_card_dai_link_of(it.node, priv, idx++); | |
c0a480d1 TL |
227 | if (ret < 0) { |
228 | of_node_put(it.node); | |
229 | ||
2692c1c6 | 230 | return ret; |
c0a480d1 | 231 | } |
2692c1c6 KM |
232 | } |
233 | ||
234 | return asoc_simple_card_parse_card_name(card, NULL); | |
235 | } | |
236 | ||
237 | static int asoc_graph_get_dais_count(struct device *dev) | |
238 | { | |
239 | struct of_phandle_iterator it; | |
240 | struct device_node *node = dev->of_node; | |
241 | int count = 0; | |
242 | int rc; | |
243 | ||
c0a480d1 | 244 | of_for_each_phandle(&it, rc, node, "dais", NULL, 0) |
2692c1c6 | 245 | count++; |
2692c1c6 KM |
246 | |
247 | return count; | |
248 | } | |
249 | ||
250 | static int asoc_graph_card_probe(struct platform_device *pdev) | |
251 | { | |
252 | struct graph_card_data *priv; | |
253 | struct snd_soc_dai_link *dai_link; | |
254 | struct graph_dai_props *dai_props; | |
255 | struct device *dev = &pdev->dev; | |
256 | struct snd_soc_card *card; | |
257 | int num, ret; | |
258 | ||
259 | /* Allocate the private data and the DAI link array */ | |
260 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
261 | if (!priv) | |
262 | return -ENOMEM; | |
263 | ||
264 | num = asoc_graph_get_dais_count(dev); | |
265 | if (num == 0) | |
266 | return -EINVAL; | |
267 | ||
268 | dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL); | |
269 | dai_link = devm_kzalloc(dev, sizeof(*dai_link) * num, GFP_KERNEL); | |
270 | if (!dai_props || !dai_link) | |
271 | return -ENOMEM; | |
272 | ||
f986907c SG |
273 | priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); |
274 | if (IS_ERR(priv->pa_gpio)) { | |
275 | ret = PTR_ERR(priv->pa_gpio); | |
276 | dev_err(dev, "failed to get amplifier gpio: %d\n", ret); | |
277 | return ret; | |
278 | } | |
279 | ||
2692c1c6 KM |
280 | priv->dai_props = dai_props; |
281 | priv->dai_link = dai_link; | |
282 | ||
283 | /* Init snd_soc_card */ | |
284 | card = graph_priv_to_card(priv); | |
285 | card->owner = THIS_MODULE; | |
286 | card->dev = dev; | |
287 | card->dai_link = dai_link; | |
288 | card->num_links = num; | |
f986907c SG |
289 | card->dapm_widgets = asoc_graph_card_dapm_widgets; |
290 | card->num_dapm_widgets = ARRAY_SIZE(asoc_graph_card_dapm_widgets); | |
2692c1c6 KM |
291 | |
292 | ret = asoc_graph_card_parse_of(priv); | |
293 | if (ret < 0) { | |
294 | if (ret != -EPROBE_DEFER) | |
295 | dev_err(dev, "parse error %d\n", ret); | |
296 | goto err; | |
297 | } | |
298 | ||
299 | snd_soc_card_set_drvdata(card, priv); | |
300 | ||
301 | ret = devm_snd_soc_register_card(dev, card); | |
ecea9313 KM |
302 | if (ret < 0) |
303 | goto err; | |
304 | ||
305 | return 0; | |
2692c1c6 KM |
306 | err: |
307 | asoc_simple_card_clean_reference(card); | |
308 | ||
309 | return ret; | |
310 | } | |
311 | ||
312 | static int asoc_graph_card_remove(struct platform_device *pdev) | |
313 | { | |
314 | struct snd_soc_card *card = platform_get_drvdata(pdev); | |
315 | ||
316 | return asoc_simple_card_clean_reference(card); | |
317 | } | |
318 | ||
319 | static const struct of_device_id asoc_graph_of_match[] = { | |
320 | { .compatible = "audio-graph-card", }, | |
321 | {}, | |
322 | }; | |
323 | MODULE_DEVICE_TABLE(of, asoc_graph_of_match); | |
324 | ||
325 | static struct platform_driver asoc_graph_card = { | |
326 | .driver = { | |
327 | .name = "asoc-audio-graph-card", | |
328 | .of_match_table = asoc_graph_of_match, | |
329 | }, | |
330 | .probe = asoc_graph_card_probe, | |
331 | .remove = asoc_graph_card_remove, | |
332 | }; | |
333 | module_platform_driver(asoc_graph_card); | |
334 | ||
335 | MODULE_ALIAS("platform:asoc-audio-graph-card"); | |
336 | MODULE_LICENSE("GPL v2"); | |
337 | MODULE_DESCRIPTION("ASoC Audio Graph Sound Card"); | |
338 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |