Commit | Line | Data |
---|---|---|
415f1cb2 | 1 | /* |
53e682b6 | 2 | * ASoC simple SCU sound card support |
415f1cb2 KM |
3 | * |
4 | * Copyright (C) 2015 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/module.h> | |
16 | #include <linux/of.h> | |
17 | #include <linux/of_device.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/string.h> | |
20 | #include <sound/jack.h> | |
21 | #include <sound/soc.h> | |
22 | #include <sound/soc-dai.h> | |
d6a4a9a4 | 23 | #include <sound/simple_card_utils.h> |
415f1cb2 | 24 | |
6910e867 | 25 | struct simple_card_data { |
415f1cb2 | 26 | struct snd_soc_card snd_card; |
415f1cb2 | 27 | struct snd_soc_codec_conf codec_conf; |
303c3be4 | 28 | struct asoc_simple_dai *dai_props; |
3433bf07 | 29 | struct snd_soc_dai_link *dai_link; |
cd8957f5 | 30 | struct asoc_simple_card_data adata; |
415f1cb2 KM |
31 | }; |
32 | ||
d27f3b4a | 33 | #define simple_priv_to_card(priv) (&(priv)->snd_card) |
53e682b6 | 34 | #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) |
d27f3b4a KM |
35 | #define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev) |
36 | #define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i)) | |
415f1cb2 | 37 | |
5bbf3866 KM |
38 | #define DAI "sound-dai" |
39 | #define CELL "#sound-dai-cells" | |
64df0e68 | 40 | #define PREFIX "simple-audio-card," |
5bbf3866 | 41 | |
53e682b6 | 42 | static int asoc_simple_card_startup(struct snd_pcm_substream *substream) |
415f1cb2 KM |
43 | { |
44 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
6910e867 | 45 | struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); |
303c3be4 | 46 | struct asoc_simple_dai *dai_props = |
53e682b6 | 47 | simple_priv_to_props(priv, rtd->num); |
415f1cb2 | 48 | |
bb24a3ba | 49 | return asoc_simple_card_clk_enable(dai_props); |
415f1cb2 KM |
50 | } |
51 | ||
53e682b6 | 52 | static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream) |
415f1cb2 KM |
53 | { |
54 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
6910e867 | 55 | struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); |
303c3be4 | 56 | struct asoc_simple_dai *dai_props = |
53e682b6 | 57 | simple_priv_to_props(priv, rtd->num); |
415f1cb2 | 58 | |
bb24a3ba | 59 | asoc_simple_card_clk_disable(dai_props); |
415f1cb2 KM |
60 | } |
61 | ||
9b6fdef6 | 62 | static const struct snd_soc_ops asoc_simple_card_ops = { |
53e682b6 KM |
63 | .startup = asoc_simple_card_startup, |
64 | .shutdown = asoc_simple_card_shutdown, | |
415f1cb2 KM |
65 | }; |
66 | ||
53e682b6 | 67 | static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) |
415f1cb2 | 68 | { |
6910e867 | 69 | struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); |
04700027 KM |
70 | struct snd_soc_dai *dai; |
71 | struct snd_soc_dai_link *dai_link; | |
303c3be4 | 72 | struct asoc_simple_dai *dai_props; |
1a497983 | 73 | int num = rtd->num; |
415f1cb2 | 74 | |
53e682b6 KM |
75 | dai_link = simple_priv_to_link(priv, num); |
76 | dai_props = simple_priv_to_props(priv, num); | |
04700027 KM |
77 | dai = dai_link->dynamic ? |
78 | rtd->cpu_dai : | |
79 | rtd->codec_dai; | |
80 | ||
600ee208 | 81 | return asoc_simple_card_init_dai(dai, dai_props); |
415f1cb2 KM |
82 | } |
83 | ||
53e682b6 | 84 | static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, |
af7e2be9 KM |
85 | struct snd_pcm_hw_params *params) |
86 | { | |
6910e867 | 87 | struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card); |
af7e2be9 | 88 | |
cd8957f5 | 89 | asoc_simple_card_convert_fixup(&priv->adata, params); |
af7e2be9 KM |
90 | |
91 | return 0; | |
92 | } | |
93 | ||
15a190ff | 94 | static int asoc_simple_card_dai_link_of(struct device_node *np, |
6910e867 | 95 | struct simple_card_data *priv, |
83216f3a | 96 | unsigned int daifmt, |
53e682b6 | 97 | int idx, bool is_fe) |
415f1cb2 | 98 | { |
53e682b6 KM |
99 | struct device *dev = simple_priv_to_dev(priv); |
100 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); | |
101 | struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); | |
d27f3b4a | 102 | struct snd_soc_card *card = simple_priv_to_card(priv); |
415f1cb2 KM |
103 | int ret; |
104 | ||
04700027 | 105 | if (is_fe) { |
27b01081 KM |
106 | int is_single_links = 0; |
107 | ||
04700027 KM |
108 | /* BE is dummy */ |
109 | dai_link->codec_of_node = NULL; | |
110 | dai_link->codec_dai_name = "snd-soc-dummy-dai"; | |
111 | dai_link->codec_name = "snd-soc-dummy"; | |
112 | ||
113 | /* FE settings */ | |
114 | dai_link->dynamic = 1; | |
115 | dai_link->dpcm_merged_format = 1; | |
5bbf3866 KM |
116 | |
117 | ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL, | |
118 | &is_single_links); | |
119 | if (ret) | |
575f1f92 | 120 | return ret; |
04700027 | 121 | |
e984fd61 | 122 | ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai_props); |
c9a235da KM |
123 | if (ret < 0) |
124 | return ret; | |
125 | ||
8a99a6bd KM |
126 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, |
127 | "fe.%s", | |
128 | dai_link->cpu_dai_name); | |
129 | if (ret < 0) | |
130 | return ret; | |
04700027 | 131 | |
27b01081 | 132 | asoc_simple_card_canonicalize_cpu(dai_link, is_single_links); |
04700027 | 133 | } else { |
04700027 KM |
134 | /* FE is dummy */ |
135 | dai_link->cpu_of_node = NULL; | |
136 | dai_link->cpu_dai_name = "snd-soc-dummy-dai"; | |
137 | dai_link->cpu_name = "snd-soc-dummy"; | |
415f1cb2 | 138 | |
04700027 KM |
139 | /* BE settings */ |
140 | dai_link->no_pcm = 1; | |
53e682b6 | 141 | dai_link->be_hw_params_fixup = asoc_simple_card_be_hw_params_fixup; |
5bbf3866 KM |
142 | |
143 | ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL); | |
575f1f92 KM |
144 | if (ret < 0) |
145 | return ret; | |
04700027 | 146 | |
e984fd61 | 147 | ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai_props); |
c9a235da KM |
148 | if (ret < 0) |
149 | return ret; | |
150 | ||
8a99a6bd KM |
151 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, |
152 | "be.%s", | |
153 | dai_link->codec_dai_name); | |
154 | if (ret < 0) | |
155 | return ret; | |
156 | ||
d27f3b4a | 157 | snd_soc_of_parse_audio_prefix(card, |
64df0e68 KM |
158 | &priv->codec_conf, |
159 | dai_link->codec_of_node, | |
160 | PREFIX "prefix"); | |
415f1cb2 KM |
161 | } |
162 | ||
77b713b5 | 163 | ret = asoc_simple_card_of_parse_tdm(np, dai_props); |
9f645421 KM |
164 | if (ret) |
165 | return ret; | |
166 | ||
a09f383e KM |
167 | ret = asoc_simple_card_canonicalize_dailink(dai_link); |
168 | if (ret < 0) | |
169 | return ret; | |
170 | ||
83216f3a | 171 | dai_link->dai_fmt = daifmt; |
04700027 KM |
172 | dai_link->dpcm_playback = 1; |
173 | dai_link->dpcm_capture = 1; | |
53e682b6 KM |
174 | dai_link->ops = &asoc_simple_card_ops; |
175 | dai_link->init = asoc_simple_card_dai_init; | |
04700027 | 176 | |
c9a235da | 177 | return 0; |
415f1cb2 KM |
178 | } |
179 | ||
0d6b3521 | 180 | static int asoc_simple_card_parse_of(struct simple_card_data *priv) |
15a190ff | 181 | |
af998f85 | 182 | { |
53e682b6 | 183 | struct device *dev = simple_priv_to_dev(priv); |
af998f85 | 184 | struct device_node *np; |
d27f3b4a | 185 | struct snd_soc_card *card = simple_priv_to_card(priv); |
0d6b3521 | 186 | struct device_node *node = dev->of_node; |
af998f85 | 187 | unsigned int daifmt = 0; |
af998f85 | 188 | bool is_fe; |
15a190ff KM |
189 | int ret, i; |
190 | ||
191 | if (!node) | |
192 | return -EINVAL; | |
193 | ||
d0148eb4 DB |
194 | ret = asoc_simple_card_of_parse_widgets(card, PREFIX); |
195 | if (ret < 0) | |
196 | return ret; | |
197 | ||
bfe6b589 | 198 | ret = asoc_simple_card_of_parse_routing(card, PREFIX, 0); |
15a190ff KM |
199 | if (ret < 0) |
200 | return ret; | |
201 | ||
cd8957f5 | 202 | asoc_simple_card_parse_convert(dev, PREFIX, &priv->adata); |
af998f85 KM |
203 | |
204 | /* find 1st codec */ | |
112a2ab5 KM |
205 | np = of_get_child_by_name(node, PREFIX "codec"); |
206 | if (!np) | |
207 | return -ENODEV; | |
208 | ||
15a190ff | 209 | ret = asoc_simple_card_parse_daifmt(dev, node, np, PREFIX, &daifmt); |
112a2ab5 KM |
210 | if (ret < 0) |
211 | return ret; | |
af998f85 KM |
212 | |
213 | i = 0; | |
214 | for_each_child_of_node(node, np) { | |
af998f85 | 215 | is_fe = false; |
64df0e68 | 216 | if (strcmp(np->name, PREFIX "cpu") == 0) |
af998f85 KM |
217 | is_fe = true; |
218 | ||
15a190ff | 219 | ret = asoc_simple_card_dai_link_of(np, priv, daifmt, i, is_fe); |
af998f85 KM |
220 | if (ret < 0) |
221 | return ret; | |
222 | i++; | |
223 | } | |
224 | ||
d27f3b4a | 225 | ret = asoc_simple_card_parse_card_name(card, PREFIX); |
53ae918f KM |
226 | if (ret < 0) |
227 | return ret; | |
415f1cb2 KM |
228 | |
229 | return 0; | |
230 | } | |
231 | ||
53e682b6 | 232 | static int asoc_simple_card_probe(struct platform_device *pdev) |
415f1cb2 | 233 | { |
6910e867 | 234 | struct simple_card_data *priv; |
19359926 KM |
235 | struct snd_soc_dai_link *dai_link; |
236 | struct asoc_simple_dai *dai_props; | |
d27f3b4a | 237 | struct snd_soc_card *card; |
415f1cb2 | 238 | struct device *dev = &pdev->dev; |
40b68dac | 239 | struct device_node *np = dev->of_node; |
15a190ff | 240 | int num, ret; |
415f1cb2 KM |
241 | |
242 | /* Allocate the private data */ | |
243 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
244 | if (!priv) | |
245 | return -ENOMEM; | |
246 | ||
15a190ff KM |
247 | num = of_get_child_count(np); |
248 | ||
19359926 KM |
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) | |
15a190ff KM |
252 | return -ENOMEM; |
253 | ||
19359926 KM |
254 | priv->dai_props = dai_props; |
255 | priv->dai_link = dai_link; | |
15a190ff KM |
256 | |
257 | /* Init snd_soc_card */ | |
d27f3b4a KM |
258 | card = simple_priv_to_card(priv); |
259 | card->owner = THIS_MODULE; | |
260 | card->dev = dev; | |
261 | card->dai_link = priv->dai_link; | |
262 | card->num_links = num; | |
263 | card->codec_conf = &priv->codec_conf; | |
264 | card->num_configs = 1; | |
15a190ff | 265 | |
0d6b3521 | 266 | ret = asoc_simple_card_parse_of(priv); |
415f1cb2 KM |
267 | if (ret < 0) { |
268 | if (ret != -EPROBE_DEFER) | |
269 | dev_err(dev, "parse error %d\n", ret); | |
270 | goto err; | |
271 | } | |
272 | ||
d27f3b4a | 273 | snd_soc_card_set_drvdata(card, priv); |
415f1cb2 | 274 | |
d27f3b4a | 275 | ret = devm_snd_soc_register_card(dev, card); |
c73df77d KM |
276 | if (ret < 0) |
277 | goto err; | |
278 | ||
279 | return 0; | |
415f1cb2 | 280 | err: |
d27f3b4a | 281 | asoc_simple_card_clean_reference(card); |
415f1cb2 KM |
282 | |
283 | return ret; | |
284 | } | |
285 | ||
53e682b6 | 286 | static int asoc_simple_card_remove(struct platform_device *pdev) |
415f1cb2 KM |
287 | { |
288 | struct snd_soc_card *card = platform_get_drvdata(pdev); | |
289 | ||
239486ba | 290 | return asoc_simple_card_clean_reference(card); |
415f1cb2 KM |
291 | } |
292 | ||
f4d70709 KM |
293 | static const struct of_device_id asoc_simple_of_match[] = { |
294 | { .compatible = "renesas,rsrc-card", }, | |
295 | { .compatible = "simple-scu-audio-card", }, | |
296 | {}, | |
297 | }; | |
298 | MODULE_DEVICE_TABLE(of, asoc_simple_of_match); | |
299 | ||
53e682b6 | 300 | static struct platform_driver asoc_simple_card = { |
415f1cb2 | 301 | .driver = { |
64df0e68 | 302 | .name = "simple-scu-audio-card", |
f4d70709 | 303 | .of_match_table = asoc_simple_of_match, |
415f1cb2 | 304 | }, |
53e682b6 KM |
305 | .probe = asoc_simple_card_probe, |
306 | .remove = asoc_simple_card_remove, | |
415f1cb2 KM |
307 | }; |
308 | ||
53e682b6 | 309 | module_platform_driver(asoc_simple_card); |
415f1cb2 | 310 | |
53e682b6 | 311 | MODULE_ALIAS("platform:asoc-simple-scu-card"); |
93bc047d | 312 | MODULE_LICENSE("GPL v2"); |
53e682b6 | 313 | MODULE_DESCRIPTION("ASoC Simple SCU Sound Card"); |
415f1cb2 | 314 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |