]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - sound/soc/generic/audio-graph-scu-card.c
Merge remote-tracking branches 'asoc/topic/ak4613', 'asoc/topic/ak4642', 'asoc/topic...
[karo-tx-linux.git] / sound / soc / generic / audio-graph-scu-card.c
1 /*
2  * ASoC audio graph SCU sound card support
3  *
4  * Copyright (C) 2017 Renesas Solutions Corp.
5  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
6  *
7  * based on
8  *      ${LINUX}/sound/soc/generic/simple-scu-card.c
9  *      ${LINUX}/sound/soc/generic/audio-graph-card.c
10  *
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.
14  */
15 #include <linux/clk.h>
16 #include <linux/device.h>
17 #include <linux/gpio.h>
18 #include <linux/module.h>
19 #include <linux/of.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>
27
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         u32 convert_rate;
34         u32 convert_channels;
35 };
36
37 #define graph_priv_to_card(priv) (&(priv)->snd_card)
38 #define graph_priv_to_props(priv, i) ((priv)->dai_props + (i))
39 #define graph_priv_to_dev(priv) (graph_priv_to_card(priv)->dev)
40 #define graph_priv_to_link(priv, i) (graph_priv_to_card(priv)->dai_link + (i))
41
42 static int asoc_graph_card_startup(struct snd_pcm_substream *substream)
43 {
44         struct snd_soc_pcm_runtime *rtd = substream->private_data;
45         struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
46         struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, rtd->num);
47
48         return clk_prepare_enable(dai_props->clk);
49 }
50
51 static void asoc_graph_card_shutdown(struct snd_pcm_substream *substream)
52 {
53         struct snd_soc_pcm_runtime *rtd = substream->private_data;
54         struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
55         struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, rtd->num);
56
57         clk_disable_unprepare(dai_props->clk);
58 }
59
60 static struct snd_soc_ops asoc_graph_card_ops = {
61         .startup = asoc_graph_card_startup,
62         .shutdown = asoc_graph_card_shutdown,
63 };
64
65 static int asoc_graph_card_dai_init(struct snd_soc_pcm_runtime *rtd)
66 {
67         struct graph_card_data *priv =  snd_soc_card_get_drvdata(rtd->card);
68         struct snd_soc_dai *dai;
69         struct snd_soc_dai_link *dai_link;
70         struct asoc_simple_dai *dai_props;
71         int num = rtd->num;
72
73         dai_link        = graph_priv_to_link(priv, num);
74         dai_props       = graph_priv_to_props(priv, num);
75         dai             = dai_link->dynamic ?
76                                 rtd->cpu_dai :
77                                 rtd->codec_dai;
78
79         return asoc_simple_card_init_dai(dai, dai_props);
80 }
81
82 static int asoc_graph_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
83                                                struct snd_pcm_hw_params *params)
84 {
85         struct graph_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
86         struct snd_interval *rate = hw_param_interval(params,
87                                                       SNDRV_PCM_HW_PARAM_RATE);
88         struct snd_interval *channels = hw_param_interval(params,
89                                                           SNDRV_PCM_HW_PARAM_CHANNELS);
90
91         if (priv->convert_rate)
92                 rate->min =
93                 rate->max = priv->convert_rate;
94
95         if (priv->convert_channels)
96                 channels->min =
97                 channels->max = priv->convert_channels;
98
99         return 0;
100 }
101
102 static int asoc_graph_card_dai_link_of(struct device_node *ep,
103                                        struct graph_card_data *priv,
104                                        unsigned int daifmt,
105                                        int idx, int is_fe)
106 {
107         struct device *dev = graph_priv_to_dev(priv);
108         struct snd_soc_dai_link *dai_link = graph_priv_to_link(priv, idx);
109         struct asoc_simple_dai *dai_props = graph_priv_to_props(priv, idx);
110         struct snd_soc_card *card = graph_priv_to_card(priv);
111         int ret;
112
113         if (is_fe) {
114                 /* BE is dummy */
115                 dai_link->codec_of_node         = NULL;
116                 dai_link->codec_dai_name        = "snd-soc-dummy-dai";
117                 dai_link->codec_name            = "snd-soc-dummy";
118
119                 /* FE settings */
120                 dai_link->dynamic               = 1;
121                 dai_link->dpcm_merged_format    = 1;
122
123                 ret = asoc_simple_card_parse_graph_cpu(ep, dai_link);
124                 if (ret)
125                         return ret;
126
127                 ret = asoc_simple_card_parse_clk_cpu(dev, ep, dai_link, dai_props);
128                 if (ret < 0)
129                         return ret;
130
131                 ret = asoc_simple_card_set_dailink_name(dev, dai_link,
132                                                         "fe.%s",
133                                                         dai_link->cpu_dai_name);
134                 if (ret < 0)
135                         return ret;
136
137                 /* card->num_links includes Codec */
138                 asoc_simple_card_canonicalize_cpu(dai_link,
139                                         (card->num_links - 1) == 1);
140         } else {
141                 /* FE is dummy */
142                 dai_link->cpu_of_node           = NULL;
143                 dai_link->cpu_dai_name          = "snd-soc-dummy-dai";
144                 dai_link->cpu_name              = "snd-soc-dummy";
145
146                 /* BE settings */
147                 dai_link->no_pcm                = 1;
148                 dai_link->be_hw_params_fixup    = asoc_graph_card_be_hw_params_fixup;
149
150                 ret = asoc_simple_card_parse_graph_codec(ep, dai_link);
151                 if (ret < 0)
152                         return ret;
153
154                 ret = asoc_simple_card_parse_clk_codec(dev, ep, dai_link, dai_props);
155                 if (ret < 0)
156                         return ret;
157
158                 ret = asoc_simple_card_set_dailink_name(dev, dai_link,
159                                                         "be.%s",
160                                                         dai_link->codec_dai_name);
161                 if (ret < 0)
162                         return ret;
163
164                 snd_soc_of_parse_audio_prefix(card,
165                                               &priv->codec_conf,
166                                               dai_link->codec_of_node,
167                                               "prefix");
168         }
169
170         ret = snd_soc_of_parse_tdm_slot(ep,
171                                         &dai_props->tx_slot_mask,
172                                         &dai_props->rx_slot_mask,
173                                         &dai_props->slots,
174                                         &dai_props->slot_width);
175         if (ret)
176                 return ret;
177
178         ret = asoc_simple_card_canonicalize_dailink(dai_link);
179         if (ret < 0)
180                 return ret;
181
182         dai_link->dai_fmt               = daifmt;
183         dai_link->dpcm_playback         = 1;
184         dai_link->dpcm_capture          = 1;
185         dai_link->ops                   = &asoc_graph_card_ops;
186         dai_link->init                  = asoc_graph_card_dai_init;
187
188         return 0;
189 }
190
191 static int asoc_graph_card_parse_of(struct graph_card_data *priv)
192 {
193         struct of_phandle_iterator it;
194         struct device *dev = graph_priv_to_dev(priv);
195         struct snd_soc_card *card = graph_priv_to_card(priv);
196         struct device_node *node = dev->of_node;
197         struct device_node *cpu_port;
198         struct device_node *cpu_ep;
199         struct device_node *codec_ep;
200         struct device_node *rcpu_ep;
201         unsigned int daifmt = 0;
202         int dai_idx, ret;
203         int rc, codec;
204
205         if (!node)
206                 return -EINVAL;
207
208         /*
209          * we need to consider "widgets", "mclk-fs" around here
210          * see simple-card
211          */
212
213         ret = snd_soc_of_parse_audio_routing(card, "routing");
214         if (ret)
215                 return ret;
216
217         /* sampling rate convert */
218         of_property_read_u32(node, "convert-rate", &priv->convert_rate);
219
220         /* channels transfer */
221         of_property_read_u32(node, "convert-channels", &priv->convert_channels);
222
223         /*
224          * it supports multi CPU, single CODEC only here
225          * see asoc_graph_get_dais_count
226          */
227
228         /* find 1st codec */
229         of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
230                 cpu_port = it.node;
231                 cpu_ep   = of_get_next_child(cpu_port, NULL);
232                 codec_ep = of_graph_get_remote_endpoint(cpu_ep);
233                 rcpu_ep  = of_graph_get_remote_endpoint(codec_ep);
234
235                 of_node_put(cpu_port);
236                 of_node_put(cpu_ep);
237                 of_node_put(codec_ep);
238                 of_node_put(rcpu_ep);
239
240                 if (!codec_ep)
241                         continue;
242
243                 if (rcpu_ep != cpu_ep) {
244                         dev_err(dev, "remote-endpoint missmatch (%s/%s/%s)\n",
245                                 cpu_ep->name, codec_ep->name, rcpu_ep->name);
246                         ret = -EINVAL;
247                         goto parse_of_err;
248                 }
249
250                 ret = asoc_simple_card_parse_daifmt(dev, cpu_ep, codec_ep,
251                                                             NULL, &daifmt);
252                 if (ret < 0)
253                         goto parse_of_err;
254         }
255
256         dai_idx = 0;
257         for (codec = 0; codec < 2; codec++) {
258                 /*
259                  * To listup valid sounds continuously,
260                  * detect all CPU-dummy first, and
261                  * detect all dummy-Codec second
262                  */
263                 of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
264                         cpu_port = it.node;
265                         cpu_ep   = of_get_next_child(cpu_port, NULL);
266                         codec_ep = of_graph_get_remote_endpoint(cpu_ep);
267
268                         of_node_put(cpu_port);
269                         of_node_put(cpu_ep);
270                         of_node_put(codec_ep);
271
272                         if (codec) {
273                                 if (!codec_ep)
274                                         continue;
275
276                                 /* Back-End (= Codec) */
277                                 ret = asoc_graph_card_dai_link_of(codec_ep, priv, daifmt, dai_idx++, 0);
278                                 if (ret < 0)
279                                         goto parse_of_err;
280                         } else {
281                                 /* Front-End (= CPU) */
282                                 ret = asoc_graph_card_dai_link_of(cpu_ep, priv, daifmt, dai_idx++, 1);
283                                 if (ret < 0)
284                                         goto parse_of_err;
285                         }
286                 }
287         }
288
289         ret = asoc_simple_card_parse_card_name(card, NULL);
290         if (ret)
291                 goto parse_of_err;
292
293         dev_dbg(dev, "convert_rate     %d\n", priv->convert_rate);
294         dev_dbg(dev, "convert_channels %d\n", priv->convert_channels);
295
296         ret = 0;
297
298 parse_of_err:
299         return ret;
300 }
301
302 static int asoc_graph_get_dais_count(struct device *dev)
303 {
304         struct of_phandle_iterator it;
305         struct device_node *node = dev->of_node;
306         struct device_node *cpu_port;
307         struct device_node *cpu_ep;
308         struct device_node *codec_ep;
309         int count = 0;
310         int rc;
311
312         of_for_each_phandle(&it, rc, node, "dais", NULL, 0) {
313                 cpu_port = it.node;
314                 cpu_ep   = of_get_next_child(cpu_port, NULL);
315                 codec_ep = of_graph_get_remote_endpoint(cpu_ep);
316
317                 of_node_put(cpu_port);
318                 of_node_put(cpu_ep);
319                 of_node_put(codec_ep);
320
321                 if (cpu_ep)
322                         count++;
323                 if (codec_ep)
324                         count++;
325         }
326
327         return count;
328 }
329
330 static int asoc_graph_card_probe(struct platform_device *pdev)
331 {
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;
337         int num, ret;
338
339         /* Allocate the private data and the DAI link array */
340         priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
341         if (!priv)
342                 return -ENOMEM;
343
344         num = asoc_graph_get_dais_count(dev);
345         if (num == 0)
346                 return -EINVAL;
347
348         dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL);
349         dai_link  = devm_kzalloc(dev, sizeof(*dai_link)  * num, GFP_KERNEL);
350         if (!dai_props || !dai_link)
351                 return -ENOMEM;
352
353         priv->dai_props                 = dai_props;
354         priv->dai_link                  = dai_link;
355
356         /* Init snd_soc_card */
357         card = graph_priv_to_card(priv);
358         card->owner             = THIS_MODULE;
359         card->dev               = dev;
360         card->dai_link          = priv->dai_link;
361         card->num_links         = num;
362         card->codec_conf        = &priv->codec_conf;
363         card->num_configs       = 1;
364
365         ret = asoc_graph_card_parse_of(priv);
366         if (ret < 0) {
367                 if (ret != -EPROBE_DEFER)
368                         dev_err(dev, "parse error %d\n", ret);
369                 goto err;
370         }
371
372         snd_soc_card_set_drvdata(card, priv);
373
374         ret = devm_snd_soc_register_card(dev, card);
375         if (ret < 0)
376                 goto err;
377
378         return 0;
379 err:
380         asoc_simple_card_clean_reference(card);
381
382         return ret;
383 }
384
385 static int asoc_graph_card_remove(struct platform_device *pdev)
386 {
387         struct snd_soc_card *card = platform_get_drvdata(pdev);
388
389         return asoc_simple_card_clean_reference(card);
390 }
391
392 static const struct of_device_id asoc_graph_of_match[] = {
393         { .compatible = "audio-graph-scu-card", },
394         {},
395 };
396 MODULE_DEVICE_TABLE(of, asoc_graph_of_match);
397
398 static struct platform_driver asoc_graph_card = {
399         .driver = {
400                 .name = "asoc-audio-graph-scu-card",
401                 .of_match_table = asoc_graph_of_match,
402         },
403         .probe = asoc_graph_card_probe,
404         .remove = asoc_graph_card_remove,
405 };
406 module_platform_driver(asoc_graph_card);
407
408 MODULE_ALIAS("platform:asoc-audio-graph-scu-card");
409 MODULE_LICENSE("GPL v2");
410 MODULE_DESCRIPTION("ASoC Audio Graph SCU Sound Card");
411 MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");