Commit | Line | Data |
---|---|---|
decd8961 KM |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // | |
3 | // ASoC audio graph sound card support | |
4 | // | |
5 | // Copyright (C) 2016 Renesas Solutions Corp. | |
6 | // Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
7 | // | |
8 | // based on ${LINUX}/sound/soc/generic/simple-card.c | |
9 | ||
2692c1c6 KM |
10 | #include <linux/clk.h> |
11 | #include <linux/device.h> | |
12 | #include <linux/gpio.h> | |
f986907c | 13 | #include <linux/gpio/consumer.h> |
2692c1c6 KM |
14 | #include <linux/module.h> |
15 | #include <linux/of.h> | |
16 | #include <linux/of_device.h> | |
17 | #include <linux/of_gpio.h> | |
18 | #include <linux/of_graph.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/string.h> | |
2692c1c6 KM |
21 | #include <sound/simple_card_utils.h> |
22 | ||
c2c61602 KM |
23 | #define DPCM_SELECTABLE 1 |
24 | ||
ae3cb579 KM |
25 | #define PREFIX "audio-graph-card," |
26 | ||
97fe6ca4 KM |
27 | static int graph_outdrv_event(struct snd_soc_dapm_widget *w, |
28 | struct snd_kcontrol *kcontrol, | |
29 | int event) | |
f986907c SG |
30 | { |
31 | struct snd_soc_dapm_context *dapm = w->dapm; | |
e59289cd | 32 | struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(dapm->card); |
f986907c SG |
33 | |
34 | switch (event) { | |
35 | case SND_SOC_DAPM_POST_PMU: | |
36 | gpiod_set_value_cansleep(priv->pa_gpio, 1); | |
37 | break; | |
38 | case SND_SOC_DAPM_PRE_PMD: | |
39 | gpiod_set_value_cansleep(priv->pa_gpio, 0); | |
40 | break; | |
41 | default: | |
42 | return -EINVAL; | |
43 | } | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
97fe6ca4 | 48 | static const struct snd_soc_dapm_widget graph_dapm_widgets[] = { |
f986907c | 49 | SND_SOC_DAPM_OUT_DRV_E("Amplifier", SND_SOC_NOPM, |
97fe6ca4 | 50 | 0, 0, NULL, 0, graph_outdrv_event, |
f986907c | 51 | SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
2692c1c6 KM |
52 | }; |
53 | ||
97fe6ca4 | 54 | static const struct snd_soc_ops graph_ops = { |
f38df5bf | 55 | .startup = asoc_simple_startup, |
686911b4 | 56 | .shutdown = asoc_simple_shutdown, |
f48dcbb6 | 57 | .hw_params = asoc_simple_hw_params, |
2692c1c6 KM |
58 | }; |
59 | ||
8f7f298a KM |
60 | static int graph_get_dai_id(struct device_node *ep) |
61 | { | |
62 | struct device_node *node; | |
63 | struct device_node *endpoint; | |
64 | struct of_endpoint info; | |
65 | int i, id; | |
ec3042ad | 66 | const u32 *reg; |
8f7f298a KM |
67 | int ret; |
68 | ||
69 | /* use driver specified DAI ID if exist */ | |
70 | ret = snd_soc_get_dai_id(ep); | |
71 | if (ret != -ENOTSUPP) | |
72 | return ret; | |
73 | ||
74 | /* use endpoint/port reg if exist */ | |
75 | ret = of_graph_parse_endpoint(ep, &info); | |
76 | if (ret == 0) { | |
77 | /* | |
78 | * Because it will count port/endpoint if it doesn't have "reg". | |
79 | * But, we can't judge whether it has "no reg", or "reg = <0>" | |
80 | * only of_graph_parse_endpoint(). | |
81 | * We need to check "reg" property | |
82 | */ | |
83 | if (of_get_property(ep, "reg", NULL)) | |
84 | return info.id; | |
85 | ||
86 | node = of_get_parent(ep); | |
c152f849 | 87 | reg = of_get_property(node, "reg", NULL); |
8f7f298a | 88 | of_node_put(node); |
c152f849 | 89 | if (reg) |
8f7f298a KM |
90 | return info.port; |
91 | } | |
92 | node = of_graph_get_port_parent(ep); | |
93 | ||
94 | /* | |
95 | * Non HDMI sound case, counting port/endpoint on its DT | |
96 | * is enough. Let's count it. | |
97 | */ | |
98 | i = 0; | |
99 | id = -1; | |
100 | for_each_endpoint_of_node(node, endpoint) { | |
101 | if (endpoint == ep) | |
102 | id = i; | |
103 | i++; | |
104 | } | |
105 | ||
106 | of_node_put(node); | |
107 | ||
108 | if (id < 0) | |
109 | return -ENODEV; | |
110 | ||
111 | return id; | |
112 | } | |
113 | ||
ad11e59f KM |
114 | static int asoc_simple_parse_dai(struct device_node *ep, |
115 | struct snd_soc_dai_link_component *dlc, | |
ad11e59f | 116 | int *is_single_link) |
8f7f298a KM |
117 | { |
118 | struct device_node *node; | |
119 | struct of_phandle_args args; | |
120 | int ret; | |
121 | ||
8f7f298a KM |
122 | if (!ep) |
123 | return 0; | |
8f7f298a KM |
124 | |
125 | node = of_graph_get_port_parent(ep); | |
126 | ||
127 | /* Get dai->name */ | |
128 | args.np = node; | |
129 | args.args[0] = graph_get_dai_id(ep); | |
130 | args.args_count = (of_graph_get_endpoint_count(node) > 1); | |
131 | ||
157ab712 KM |
132 | /* |
133 | * FIXME | |
134 | * | |
135 | * Here, dlc->dai_name is pointer to CPU/Codec DAI name. | |
136 | * If user unbinded CPU or Codec driver, but not for Sound Card, | |
137 | * dlc->dai_name is keeping unbinded CPU or Codec | |
138 | * driver's pointer. | |
139 | * | |
140 | * If user re-bind CPU or Codec driver again, ALSA SoC will try | |
141 | * to rebind Card via snd_soc_try_rebind_card(), but because of | |
142 | * above reason, it might can't bind Sound Card. | |
143 | * Because Sound Card is pointing to released dai_name pointer. | |
144 | * | |
145 | * To avoid this rebind Card issue, | |
146 | * 1) It needs to alloc memory to keep dai_name eventhough | |
147 | * CPU or Codec driver was unbinded, or | |
148 | * 2) user need to rebind Sound Card everytime | |
149 | * if he unbinded CPU or Codec. | |
150 | */ | |
f107294c | 151 | ret = snd_soc_get_dai_name(&args, &dlc->dai_name); |
8f7f298a KM |
152 | if (ret < 0) |
153 | return ret; | |
154 | ||
f107294c | 155 | dlc->of_node = node; |
8f7f298a KM |
156 | |
157 | if (is_single_link) | |
158 | *is_single_link = of_graph_get_endpoint_count(node) == 1; | |
159 | ||
160 | return 0; | |
161 | } | |
162 | ||
d2bf008a KM |
163 | static void graph_parse_convert(struct device *dev, |
164 | struct device_node *ep, | |
ad11e59f | 165 | struct asoc_simple_data *adata) |
40dfae16 KM |
166 | { |
167 | struct device_node *top = dev->of_node; | |
168 | struct device_node *port = of_get_parent(ep); | |
169 | struct device_node *ports = of_get_parent(port); | |
170 | struct device_node *node = of_graph_get_port_parent(ep); | |
171 | ||
ad11e59f KM |
172 | asoc_simple_parse_convert(dev, top, NULL, adata); |
173 | asoc_simple_parse_convert(dev, node, PREFIX, adata); | |
174 | asoc_simple_parse_convert(dev, ports, NULL, adata); | |
175 | asoc_simple_parse_convert(dev, port, NULL, adata); | |
176 | asoc_simple_parse_convert(dev, ep, NULL, adata); | |
d2bf008a KM |
177 | |
178 | of_node_put(port); | |
179 | of_node_put(ports); | |
180 | of_node_put(node); | |
40dfae16 KM |
181 | } |
182 | ||
4346a745 KM |
183 | static void graph_parse_mclk_fs(struct device_node *top, |
184 | struct device_node *ep, | |
e59289cd | 185 | struct simple_dai_props *props) |
4346a745 KM |
186 | { |
187 | struct device_node *port = of_get_parent(ep); | |
188 | struct device_node *ports = of_get_parent(port); | |
189 | struct device_node *node = of_graph_get_port_parent(ep); | |
190 | ||
191 | of_property_read_u32(top, "mclk-fs", &props->mclk_fs); | |
192 | of_property_read_u32(ports, "mclk-fs", &props->mclk_fs); | |
193 | of_property_read_u32(port, "mclk-fs", &props->mclk_fs); | |
194 | of_property_read_u32(ep, "mclk-fs", &props->mclk_fs); | |
195 | ||
196 | of_node_put(port); | |
197 | of_node_put(ports); | |
198 | of_node_put(node); | |
199 | } | |
200 | ||
e59289cd | 201 | static int graph_dai_link_of_dpcm(struct asoc_simple_priv *priv, |
97fe6ca4 KM |
202 | struct device_node *cpu_ep, |
203 | struct device_node *codec_ep, | |
204 | struct link_info *li, | |
205 | int dup_codec) | |
ae3cb579 | 206 | { |
e59289cd KM |
207 | struct device *dev = simple_priv_to_dev(priv); |
208 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); | |
209 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); | |
dd98fbc5 | 210 | struct device_node *top = dev->of_node; |
1e4771a6 | 211 | struct device_node *ep = li->cpu ? cpu_ep : codec_ep; |
dd98fbc5 KM |
212 | struct device_node *port; |
213 | struct device_node *ports; | |
214 | struct device_node *node; | |
ae3cb579 | 215 | struct asoc_simple_dai *dai; |
f107294c | 216 | struct snd_soc_dai_link_component *cpus = dai_link->cpus; |
66164a4d | 217 | struct snd_soc_dai_link_component *codecs = dai_link->codecs; |
ae3cb579 KM |
218 | int ret; |
219 | ||
dd98fbc5 KM |
220 | /* Do it all CPU endpoint, and 1st Codec endpoint */ |
221 | if (!li->cpu && dup_codec) | |
222 | return 0; | |
223 | ||
224 | port = of_get_parent(ep); | |
225 | ports = of_get_parent(port); | |
226 | node = of_graph_get_port_parent(ep); | |
227 | ||
1e4771a6 KM |
228 | li->link++; |
229 | ||
230 | dev_dbg(dev, "link_of DPCM (%pOF)\n", ep); | |
ae3cb579 | 231 | |
1e4771a6 | 232 | if (li->cpu) { |
8f7f298a | 233 | int is_single_links = 0; |
ae3cb579 | 234 | |
9764beea | 235 | /* Codec is dummy */ |
ae3cb579 KM |
236 | codecs->of_node = NULL; |
237 | codecs->dai_name = "snd-soc-dummy-dai"; | |
238 | codecs->name = "snd-soc-dummy"; | |
239 | ||
240 | /* FE settings */ | |
241 | dai_link->dynamic = 1; | |
242 | dai_link->dpcm_merged_format = 1; | |
243 | ||
244 | dai = | |
1e4771a6 | 245 | dai_props->cpu_dai = &priv->dais[li->dais++]; |
ae3cb579 | 246 | |
ad11e59f | 247 | ret = asoc_simple_parse_cpu(ep, dai_link, &is_single_links); |
ae3cb579 | 248 | if (ret) |
aa2e362c | 249 | goto out_put_node; |
ae3cb579 | 250 | |
ad11e59f | 251 | ret = asoc_simple_parse_clk_cpu(dev, ep, dai_link, dai); |
ae3cb579 | 252 | if (ret < 0) |
aa2e362c | 253 | goto out_put_node; |
ae3cb579 | 254 | |
ad11e59f KM |
255 | ret = asoc_simple_set_dailink_name(dev, dai_link, |
256 | "fe.%s", | |
f107294c | 257 | cpus->dai_name); |
ae3cb579 | 258 | if (ret < 0) |
aa2e362c | 259 | goto out_put_node; |
ae3cb579 KM |
260 | |
261 | /* card->num_links includes Codec */ | |
ad11e59f | 262 | asoc_simple_canonicalize_cpu(dai_link, is_single_links); |
ae3cb579 KM |
263 | } else { |
264 | struct snd_soc_codec_conf *cconf; | |
265 | ||
9764beea | 266 | /* CPU is dummy */ |
f107294c KM |
267 | cpus->of_node = NULL; |
268 | cpus->dai_name = "snd-soc-dummy-dai"; | |
269 | cpus->name = "snd-soc-dummy"; | |
ae3cb579 KM |
270 | |
271 | /* BE settings */ | |
272 | dai_link->no_pcm = 1; | |
629f7544 | 273 | dai_link->be_hw_params_fixup = asoc_simple_be_hw_params_fixup; |
ae3cb579 KM |
274 | |
275 | dai = | |
1e4771a6 | 276 | dai_props->codec_dai = &priv->dais[li->dais++]; |
ae3cb579 KM |
277 | |
278 | cconf = | |
1e4771a6 | 279 | dai_props->codec_conf = &priv->codec_conf[li->conf++]; |
ae3cb579 | 280 | |
ad11e59f | 281 | ret = asoc_simple_parse_codec(ep, dai_link); |
ae3cb579 | 282 | if (ret < 0) |
aa2e362c | 283 | goto out_put_node; |
ae3cb579 | 284 | |
ad11e59f | 285 | ret = asoc_simple_parse_clk_codec(dev, ep, dai_link, dai); |
ae3cb579 | 286 | if (ret < 0) |
aa2e362c | 287 | goto out_put_node; |
ae3cb579 | 288 | |
ad11e59f KM |
289 | ret = asoc_simple_set_dailink_name(dev, dai_link, |
290 | "be.%s", | |
291 | codecs->dai_name); | |
ae3cb579 | 292 | if (ret < 0) |
aa2e362c | 293 | goto out_put_node; |
ae3cb579 KM |
294 | |
295 | /* check "prefix" from top node */ | |
66164a4d | 296 | snd_soc_of_parse_node_prefix(top, cconf, codecs->of_node, |
ae3cb579 | 297 | "prefix"); |
66164a4d KM |
298 | snd_soc_of_parse_node_prefix(node, cconf, codecs->of_node, |
299 | PREFIX "prefix"); | |
300 | snd_soc_of_parse_node_prefix(ports, cconf, codecs->of_node, | |
301 | "prefix"); | |
302 | snd_soc_of_parse_node_prefix(port, cconf, codecs->of_node, | |
303 | "prefix"); | |
ae3cb579 KM |
304 | } |
305 | ||
4346a745 KM |
306 | graph_parse_convert(dev, ep, &dai_props->adata); |
307 | graph_parse_mclk_fs(top, ep, dai_props); | |
308 | ||
ad11e59f | 309 | asoc_simple_canonicalize_platform(dai_link); |
fe7ed4de | 310 | |
ad11e59f | 311 | ret = asoc_simple_parse_tdm(ep, dai); |
ae3cb579 | 312 | if (ret) |
aa2e362c | 313 | goto out_put_node; |
ae3cb579 | 314 | |
ad11e59f KM |
315 | ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, |
316 | NULL, &dai_link->dai_fmt); | |
ae3cb579 | 317 | if (ret < 0) |
aa2e362c | 318 | goto out_put_node; |
ae3cb579 KM |
319 | |
320 | dai_link->dpcm_playback = 1; | |
321 | dai_link->dpcm_capture = 1; | |
97fe6ca4 | 322 | dai_link->ops = &graph_ops; |
ad934ca8 | 323 | dai_link->init = asoc_simple_dai_init; |
ae3cb579 | 324 | |
aa2e362c WY |
325 | out_put_node: |
326 | of_node_put(ports); | |
327 | of_node_put(port); | |
328 | of_node_put(node); | |
329 | return ret; | |
ae3cb579 KM |
330 | } |
331 | ||
e59289cd | 332 | static int graph_dai_link_of(struct asoc_simple_priv *priv, |
97fe6ca4 KM |
333 | struct device_node *cpu_ep, |
334 | struct device_node *codec_ep, | |
335 | struct link_info *li) | |
2692c1c6 | 336 | { |
e59289cd KM |
337 | struct device *dev = simple_priv_to_dev(priv); |
338 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, li->link); | |
339 | struct simple_dai_props *dai_props = simple_priv_to_props(priv, li->link); | |
dd98fbc5 | 340 | struct device_node *top = dev->of_node; |
0e3460bc KM |
341 | struct asoc_simple_dai *cpu_dai; |
342 | struct asoc_simple_dai *codec_dai; | |
8f7f298a | 343 | int ret, single_cpu; |
2692c1c6 | 344 | |
dd98fbc5 KM |
345 | /* Do it only CPU turn */ |
346 | if (!li->cpu) | |
347 | return 0; | |
348 | ||
1e4771a6 KM |
349 | dev_dbg(dev, "link_of (%pOF)\n", cpu_ep); |
350 | ||
351 | li->link++; | |
ae3cb579 | 352 | |
0e3460bc | 353 | cpu_dai = |
1e4771a6 | 354 | dai_props->cpu_dai = &priv->dais[li->dais++]; |
0e3460bc | 355 | codec_dai = |
1e4771a6 | 356 | dai_props->codec_dai = &priv->dais[li->dais++]; |
0e3460bc | 357 | |
56eb8181 | 358 | /* Factor to mclk, used in hw_params() */ |
4346a745 KM |
359 | graph_parse_mclk_fs(top, cpu_ep, dai_props); |
360 | graph_parse_mclk_fs(top, codec_ep, dai_props); | |
56eb8181 | 361 | |
ad11e59f KM |
362 | ret = asoc_simple_parse_daifmt(dev, cpu_ep, codec_ep, |
363 | NULL, &dai_link->dai_fmt); | |
2692c1c6 | 364 | if (ret < 0) |
ae3cb579 | 365 | return ret; |
2692c1c6 | 366 | |
ad11e59f | 367 | ret = asoc_simple_parse_cpu(cpu_ep, dai_link, &single_cpu); |
2692c1c6 | 368 | if (ret < 0) |
ae3cb579 | 369 | return ret; |
2692c1c6 | 370 | |
ad11e59f | 371 | ret = asoc_simple_parse_codec(codec_ep, dai_link); |
2692c1c6 | 372 | if (ret < 0) |
ae3cb579 | 373 | return ret; |
2692c1c6 | 374 | |
ad11e59f | 375 | ret = asoc_simple_parse_tdm(cpu_ep, cpu_dai); |
2692c1c6 | 376 | if (ret < 0) |
ae3cb579 | 377 | return ret; |
2692c1c6 | 378 | |
ad11e59f | 379 | ret = asoc_simple_parse_tdm(codec_ep, codec_dai); |
2692c1c6 | 380 | if (ret < 0) |
ae3cb579 | 381 | return ret; |
2692c1c6 | 382 | |
ad11e59f | 383 | ret = asoc_simple_parse_clk_cpu(dev, cpu_ep, dai_link, cpu_dai); |
2692c1c6 | 384 | if (ret < 0) |
ae3cb579 | 385 | return ret; |
2692c1c6 | 386 | |
ad11e59f | 387 | ret = asoc_simple_parse_clk_codec(dev, codec_ep, dai_link, codec_dai); |
2692c1c6 | 388 | if (ret < 0) |
ae3cb579 | 389 | return ret; |
2692c1c6 | 390 | |
ad11e59f KM |
391 | ret = asoc_simple_set_dailink_name(dev, dai_link, |
392 | "%s-%s", | |
f107294c | 393 | dai_link->cpus->dai_name, |
ad11e59f | 394 | dai_link->codecs->dai_name); |
2692c1c6 | 395 | if (ret < 0) |
ae3cb579 | 396 | return ret; |
2692c1c6 | 397 | |
97fe6ca4 | 398 | dai_link->ops = &graph_ops; |
ad934ca8 | 399 | dai_link->init = asoc_simple_dai_init; |
2692c1c6 | 400 | |
ad11e59f KM |
401 | asoc_simple_canonicalize_cpu(dai_link, single_cpu); |
402 | asoc_simple_canonicalize_platform(dai_link); | |
2692c1c6 | 403 | |
ae3cb579 | 404 | return 0; |
2692c1c6 KM |
405 | } |
406 | ||
e59289cd | 407 | static int graph_for_each_link(struct asoc_simple_priv *priv, |
fce9b90c | 408 | struct link_info *li, |
e59289cd | 409 | int (*func_noml)(struct asoc_simple_priv *priv, |
fce9b90c KM |
410 | struct device_node *cpu_ep, |
411 | struct device_node *codec_ep, | |
412 | struct link_info *li), | |
e59289cd | 413 | int (*func_dpcm)(struct asoc_simple_priv *priv, |
fce9b90c KM |
414 | struct device_node *cpu_ep, |
415 | struct device_node *codec_ep, | |
416 | struct link_info *li, int dup_codec)) | |
2692c1c6 KM |
417 | { |
418 | struct of_phandle_iterator it; | |
e59289cd | 419 | struct device *dev = simple_priv_to_dev(priv); |
fce9b90c | 420 | struct device_node *node = dev->of_node; |
ae3cb579 | 421 | struct device_node *cpu_port; |
fce9b90c KM |
422 | struct device_node *cpu_ep; |
423 | struct device_node *codec_ep; | |
424 | struct device_node *codec_port; | |
425 | struct device_node *codec_port_old = NULL; | |
ad11e59f | 426 | struct asoc_simple_data adata; |
c2c61602 | 427 | uintptr_t dpcm_selectable = (uintptr_t)of_device_get_match_data(dev); |
0e3460bc | 428 | int rc, ret; |
2692c1c6 | 429 | |
fce9b90c KM |
430 | /* loop for all listed CPU port */ |
431 | of_for_each_phandle(&it, rc, node, "dais", NULL, 0) { | |
432 | cpu_port = it.node; | |
433 | cpu_ep = NULL; | |
434 | ||
435 | /* loop for all CPU endpoint */ | |
436 | while (1) { | |
437 | cpu_ep = of_get_next_child(cpu_port, cpu_ep); | |
438 | if (!cpu_ep) | |
439 | break; | |
440 | ||
441 | /* get codec */ | |
442 | codec_ep = of_graph_get_remote_endpoint(cpu_ep); | |
443 | codec_port = of_get_parent(codec_ep); | |
444 | ||
fce9b90c KM |
445 | /* get convert-xxx property */ |
446 | memset(&adata, 0, sizeof(adata)); | |
d2bf008a KM |
447 | graph_parse_convert(dev, codec_ep, &adata); |
448 | graph_parse_convert(dev, cpu_ep, &adata); | |
fce9b90c KM |
449 | |
450 | /* | |
451 | * It is DPCM | |
452 | * if Codec port has many endpoints, | |
453 | * or has convert-xxx property | |
454 | */ | |
c2c61602 KM |
455 | if (dpcm_selectable && |
456 | ((of_get_child_count(codec_port) > 1) || | |
457 | adata.convert_rate || adata.convert_channels)) | |
fce9b90c KM |
458 | ret = func_dpcm(priv, cpu_ep, codec_ep, li, |
459 | (codec_port_old == codec_port)); | |
460 | /* else normal sound */ | |
461 | else | |
462 | ret = func_noml(priv, cpu_ep, codec_ep, li); | |
463 | ||
1bcc1fd6 WY |
464 | of_node_put(codec_ep); |
465 | of_node_put(codec_port); | |
466 | ||
fce9b90c KM |
467 | if (ret < 0) |
468 | return ret; | |
469 | ||
470 | codec_port_old = codec_port; | |
471 | } | |
472 | } | |
473 | ||
474 | return 0; | |
475 | } | |
476 | ||
e59289cd | 477 | static int graph_parse_of(struct asoc_simple_priv *priv) |
fce9b90c | 478 | { |
e59289cd | 479 | struct snd_soc_card *card = simple_priv_to_card(priv); |
fce9b90c KM |
480 | struct link_info li; |
481 | int ret; | |
482 | ||
ad11e59f | 483 | ret = asoc_simple_parse_widgets(card, NULL); |
f986907c SG |
484 | if (ret < 0) |
485 | return ret; | |
486 | ||
ad11e59f | 487 | ret = asoc_simple_parse_routing(card, NULL); |
f986907c SG |
488 | if (ret < 0) |
489 | return ret; | |
490 | ||
1e4771a6 | 491 | memset(&li, 0, sizeof(li)); |
1e4771a6 | 492 | for (li.cpu = 1; li.cpu >= 0; li.cpu--) { |
ae3cb579 KM |
493 | /* |
494 | * Detect all CPU first, and Detect all Codec 2nd. | |
495 | * | |
496 | * In Normal sound case, all DAIs are detected | |
497 | * as "CPU-Codec". | |
498 | * | |
499 | * In DPCM sound case, | |
500 | * all CPUs are detected as "CPU-dummy", and | |
501 | * all Codecs are detected as "dummy-Codec". | |
502 | * To avoid random sub-device numbering, | |
503 | * detect "dummy-Codec" in last; | |
504 | */ | |
97fe6ca4 KM |
505 | ret = graph_for_each_link(priv, &li, |
506 | graph_dai_link_of, | |
507 | graph_dai_link_of_dpcm); | |
fce9b90c KM |
508 | if (ret < 0) |
509 | return ret; | |
2692c1c6 KM |
510 | } |
511 | ||
ad11e59f | 512 | return asoc_simple_parse_card_name(card, NULL); |
2692c1c6 KM |
513 | } |
514 | ||
e59289cd | 515 | static int graph_count_noml(struct asoc_simple_priv *priv, |
97fe6ca4 KM |
516 | struct device_node *cpu_ep, |
517 | struct device_node *codec_ep, | |
518 | struct link_info *li) | |
2692c1c6 | 519 | { |
e59289cd | 520 | struct device *dev = simple_priv_to_dev(priv); |
dd98fbc5 KM |
521 | |
522 | li->link += 1; /* 1xCPU-Codec */ | |
523 | li->dais += 2; /* 1xCPU + 1xCodec */ | |
524 | ||
525 | dev_dbg(dev, "Count As Normal\n"); | |
526 | ||
527 | return 0; | |
528 | } | |
529 | ||
e59289cd | 530 | static int graph_count_dpcm(struct asoc_simple_priv *priv, |
97fe6ca4 KM |
531 | struct device_node *cpu_ep, |
532 | struct device_node *codec_ep, | |
533 | struct link_info *li, | |
534 | int dup_codec) | |
dd98fbc5 | 535 | { |
e59289cd | 536 | struct device *dev = simple_priv_to_dev(priv); |
dd98fbc5 KM |
537 | |
538 | li->link++; /* 1xCPU-dummy */ | |
539 | li->dais++; /* 1xCPU */ | |
540 | ||
541 | if (!dup_codec) { | |
542 | li->link++; /* 1xdummy-Codec */ | |
543 | li->conf++; /* 1xdummy-Codec */ | |
544 | li->dais++; /* 1xCodec */ | |
545 | } | |
546 | ||
547 | dev_dbg(dev, "Count As DPCM\n"); | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
e59289cd | 552 | static void graph_get_dais_count(struct asoc_simple_priv *priv, |
97fe6ca4 | 553 | struct link_info *li) |
dd98fbc5 | 554 | { |
e59289cd | 555 | struct device *dev = simple_priv_to_dev(priv); |
2692c1c6 | 556 | |
ae3cb579 KM |
557 | /* |
558 | * link_num : number of links. | |
559 | * CPU-Codec / CPU-dummy / dummy-Codec | |
560 | * dais_num : number of DAIs | |
561 | * ccnf_num : number of codec_conf | |
562 | * same number for "dummy-Codec" | |
563 | * | |
564 | * ex1) | |
565 | * CPU0 --- Codec0 link : 5 | |
566 | * CPU1 --- Codec1 dais : 7 | |
567 | * CPU2 -/ ccnf : 1 | |
568 | * CPU3 --- Codec2 | |
569 | * | |
570 | * => 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec | |
571 | * => 7 DAIs = 4xCPU + 3xCodec | |
572 | * => 1 ccnf = 1xdummy-Codec | |
573 | * | |
574 | * ex2) | |
575 | * CPU0 --- Codec0 link : 5 | |
576 | * CPU1 --- Codec1 dais : 6 | |
577 | * CPU2 -/ ccnf : 1 | |
578 | * CPU3 -/ | |
579 | * | |
580 | * => 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec | |
581 | * => 6 DAIs = 4xCPU + 2xCodec | |
582 | * => 1 ccnf = 1xdummy-Codec | |
583 | * | |
584 | * ex3) | |
585 | * CPU0 --- Codec0 link : 6 | |
586 | * CPU1 -/ dais : 6 | |
587 | * CPU2 --- Codec1 ccnf : 2 | |
588 | * CPU3 -/ | |
589 | * | |
590 | * => 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec | |
591 | * => 6 DAIs = 4xCPU + 2xCodec | |
592 | * => 2 ccnf = 2xdummy-Codec | |
de2949fe KM |
593 | * |
594 | * ex4) | |
595 | * CPU0 --- Codec0 (convert-rate) link : 3 | |
596 | * CPU1 --- Codec1 dais : 4 | |
597 | * ccnf : 1 | |
598 | * | |
599 | * => 3 links = 1xCPU-Codec + 1xCPU-dummy + 1xdummy-Codec | |
600 | * => 4 DAIs = 2xCPU + 2xCodec | |
601 | * => 1 ccnf = 1xdummy-Codec | |
ae3cb579 | 602 | */ |
97fe6ca4 KM |
603 | graph_for_each_link(priv, li, |
604 | graph_count_noml, | |
605 | graph_count_dpcm); | |
fce9b90c KM |
606 | dev_dbg(dev, "link %d, dais %d, ccnf %d\n", |
607 | li->link, li->dais, li->conf); | |
2692c1c6 KM |
608 | } |
609 | ||
97fe6ca4 | 610 | static int graph_card_probe(struct snd_soc_card *card) |
f6de35cc | 611 | { |
e59289cd | 612 | struct asoc_simple_priv *priv = snd_soc_card_get_drvdata(card); |
f6de35cc KS |
613 | int ret; |
614 | ||
ad11e59f | 615 | ret = asoc_simple_init_hp(card, &priv->hp_jack, NULL); |
f6de35cc KS |
616 | if (ret < 0) |
617 | return ret; | |
618 | ||
ad11e59f | 619 | ret = asoc_simple_init_mic(card, &priv->mic_jack, NULL); |
f6de35cc KS |
620 | if (ret < 0) |
621 | return ret; | |
622 | ||
623 | return 0; | |
624 | } | |
625 | ||
97fe6ca4 | 626 | static int graph_probe(struct platform_device *pdev) |
2692c1c6 | 627 | { |
e59289cd | 628 | struct asoc_simple_priv *priv; |
2692c1c6 KM |
629 | struct device *dev = &pdev->dev; |
630 | struct snd_soc_card *card; | |
1e4771a6 | 631 | struct link_info li; |
65a5056b | 632 | int ret; |
2692c1c6 KM |
633 | |
634 | /* Allocate the private data and the DAI link array */ | |
635 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
636 | if (!priv) | |
637 | return -ENOMEM; | |
638 | ||
e59289cd | 639 | card = simple_priv_to_card(priv); |
dd98fbc5 KM |
640 | card->owner = THIS_MODULE; |
641 | card->dev = dev; | |
97fe6ca4 KM |
642 | card->dapm_widgets = graph_dapm_widgets; |
643 | card->num_dapm_widgets = ARRAY_SIZE(graph_dapm_widgets); | |
644 | card->probe = graph_card_probe; | |
dd98fbc5 | 645 | |
1e4771a6 | 646 | memset(&li, 0, sizeof(li)); |
97fe6ca4 | 647 | graph_get_dais_count(priv, &li); |
1e4771a6 | 648 | if (!li.link || !li.dais) |
2692c1c6 KM |
649 | return -EINVAL; |
650 | ||
ad11e59f | 651 | ret = asoc_simple_init_priv(priv, &li); |
65a5056b KM |
652 | if (ret < 0) |
653 | return ret; | |
8e6746db | 654 | |
f986907c SG |
655 | priv->pa_gpio = devm_gpiod_get_optional(dev, "pa", GPIOD_OUT_LOW); |
656 | if (IS_ERR(priv->pa_gpio)) { | |
657 | ret = PTR_ERR(priv->pa_gpio); | |
658 | dev_err(dev, "failed to get amplifier gpio: %d\n", ret); | |
659 | return ret; | |
660 | } | |
661 | ||
97fe6ca4 | 662 | ret = graph_parse_of(priv); |
2692c1c6 KM |
663 | if (ret < 0) { |
664 | if (ret != -EPROBE_DEFER) | |
665 | dev_err(dev, "parse error %d\n", ret); | |
666 | goto err; | |
667 | } | |
668 | ||
669 | snd_soc_card_set_drvdata(card, priv); | |
670 | ||
0580dde5 KM |
671 | asoc_simple_debug_info(priv); |
672 | ||
2692c1c6 | 673 | ret = devm_snd_soc_register_card(dev, card); |
ecea9313 KM |
674 | if (ret < 0) |
675 | goto err; | |
676 | ||
677 | return 0; | |
2692c1c6 | 678 | err: |
ad11e59f | 679 | asoc_simple_clean_reference(card); |
2692c1c6 KM |
680 | |
681 | return ret; | |
682 | } | |
683 | ||
97fe6ca4 | 684 | static int graph_remove(struct platform_device *pdev) |
2692c1c6 KM |
685 | { |
686 | struct snd_soc_card *card = platform_get_drvdata(pdev); | |
687 | ||
ad11e59f | 688 | return asoc_simple_clean_reference(card); |
2692c1c6 KM |
689 | } |
690 | ||
97fe6ca4 | 691 | static const struct of_device_id graph_of_match[] = { |
2692c1c6 | 692 | { .compatible = "audio-graph-card", }, |
c2c61602 KM |
693 | { .compatible = "audio-graph-scu-card", |
694 | .data = (void *)DPCM_SELECTABLE }, | |
2692c1c6 KM |
695 | {}, |
696 | }; | |
97fe6ca4 | 697 | MODULE_DEVICE_TABLE(of, graph_of_match); |
2692c1c6 | 698 | |
97fe6ca4 | 699 | static struct platform_driver graph_card = { |
2692c1c6 KM |
700 | .driver = { |
701 | .name = "asoc-audio-graph-card", | |
7b828a35 | 702 | .pm = &snd_soc_pm_ops, |
97fe6ca4 | 703 | .of_match_table = graph_of_match, |
2692c1c6 | 704 | }, |
97fe6ca4 KM |
705 | .probe = graph_probe, |
706 | .remove = graph_remove, | |
2692c1c6 | 707 | }; |
97fe6ca4 | 708 | module_platform_driver(graph_card); |
2692c1c6 KM |
709 | |
710 | MODULE_ALIAS("platform:asoc-audio-graph-card"); | |
711 | MODULE_LICENSE("GPL v2"); | |
712 | MODULE_DESCRIPTION("ASoC Audio Graph Sound Card"); | |
713 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |