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 | |
53e682b6 | 25 | struct asoc_simple_card_of_data { |
415f1cb2 KM |
26 | const char *prefix; |
27 | const struct snd_soc_dapm_route *routes; | |
28 | int num_routes; | |
29 | }; | |
30 | ||
53e682b6 | 31 | static const struct of_device_id asoc_simple_card_of_match[] = { |
b7419dd7 | 32 | { .compatible = "renesas,rsrc-card", }, |
415f1cb2 KM |
33 | {}, |
34 | }; | |
53e682b6 | 35 | MODULE_DEVICE_TABLE(of, asoc_simple_card_of_match); |
415f1cb2 | 36 | |
415f1cb2 KM |
37 | #define IDX_CPU 0 |
38 | #define IDX_CODEC 1 | |
53e682b6 | 39 | struct asoc_simple_card_priv { |
415f1cb2 | 40 | struct snd_soc_card snd_card; |
415f1cb2 | 41 | struct snd_soc_codec_conf codec_conf; |
303c3be4 | 42 | struct asoc_simple_dai *dai_props; |
3433bf07 | 43 | struct snd_soc_dai_link *dai_link; |
af7e2be9 | 44 | u32 convert_rate; |
f90432fc | 45 | u32 convert_channels; |
415f1cb2 KM |
46 | }; |
47 | ||
53e682b6 KM |
48 | #define simple_priv_to_dev(priv) ((priv)->snd_card.dev) |
49 | #define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i)) | |
50 | #define simple_priv_to_props(priv, i) ((priv)->dai_props + (i)) | |
415f1cb2 | 51 | |
5bbf3866 KM |
52 | #define DAI "sound-dai" |
53 | #define CELL "#sound-dai-cells" | |
54 | ||
53e682b6 | 55 | static int asoc_simple_card_startup(struct snd_pcm_substream *substream) |
415f1cb2 KM |
56 | { |
57 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
53e682b6 | 58 | struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); |
303c3be4 | 59 | struct asoc_simple_dai *dai_props = |
53e682b6 | 60 | simple_priv_to_props(priv, rtd->num); |
415f1cb2 | 61 | |
04700027 | 62 | return clk_prepare_enable(dai_props->clk); |
415f1cb2 KM |
63 | } |
64 | ||
53e682b6 | 65 | static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream) |
415f1cb2 KM |
66 | { |
67 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
53e682b6 | 68 | struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); |
303c3be4 | 69 | struct asoc_simple_dai *dai_props = |
53e682b6 | 70 | simple_priv_to_props(priv, rtd->num); |
415f1cb2 | 71 | |
04700027 | 72 | clk_disable_unprepare(dai_props->clk); |
415f1cb2 KM |
73 | } |
74 | ||
53e682b6 KM |
75 | static struct snd_soc_ops asoc_simple_card_ops = { |
76 | .startup = asoc_simple_card_startup, | |
77 | .shutdown = asoc_simple_card_shutdown, | |
415f1cb2 KM |
78 | }; |
79 | ||
53e682b6 | 80 | static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) |
415f1cb2 | 81 | { |
53e682b6 | 82 | struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); |
04700027 KM |
83 | struct snd_soc_dai *dai; |
84 | struct snd_soc_dai_link *dai_link; | |
303c3be4 | 85 | struct asoc_simple_dai *dai_props; |
1a497983 | 86 | int num = rtd->num; |
415f1cb2 | 87 | |
53e682b6 KM |
88 | dai_link = simple_priv_to_link(priv, num); |
89 | dai_props = simple_priv_to_props(priv, num); | |
04700027 KM |
90 | dai = dai_link->dynamic ? |
91 | rtd->cpu_dai : | |
92 | rtd->codec_dai; | |
93 | ||
600ee208 | 94 | return asoc_simple_card_init_dai(dai, dai_props); |
415f1cb2 KM |
95 | } |
96 | ||
53e682b6 | 97 | static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, |
af7e2be9 KM |
98 | struct snd_pcm_hw_params *params) |
99 | { | |
53e682b6 | 100 | struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card); |
af7e2be9 KM |
101 | struct snd_interval *rate = hw_param_interval(params, |
102 | SNDRV_PCM_HW_PARAM_RATE); | |
f90432fc KM |
103 | struct snd_interval *channels = hw_param_interval(params, |
104 | SNDRV_PCM_HW_PARAM_CHANNELS); | |
af7e2be9 | 105 | |
f90432fc KM |
106 | if (priv->convert_rate) |
107 | rate->min = | |
108 | rate->max = priv->convert_rate; | |
af7e2be9 | 109 | |
f90432fc KM |
110 | if (priv->convert_channels) |
111 | channels->min = | |
112 | channels->max = priv->convert_channels; | |
af7e2be9 KM |
113 | |
114 | return 0; | |
115 | } | |
116 | ||
53e682b6 KM |
117 | static int asoc_simple_card_parse_links(struct device_node *np, |
118 | struct asoc_simple_card_priv *priv, | |
119 | int idx, bool is_fe) | |
415f1cb2 | 120 | { |
53e682b6 KM |
121 | struct device *dev = simple_priv_to_dev(priv); |
122 | struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx); | |
123 | struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx); | |
415f1cb2 KM |
124 | int ret; |
125 | ||
6dad9758 KM |
126 | /* Parse TDM slot */ |
127 | ret = snd_soc_of_parse_tdm_slot(np, | |
128 | &dai_props->tx_slot_mask, | |
129 | &dai_props->rx_slot_mask, | |
130 | &dai_props->slots, | |
131 | &dai_props->slot_width); | |
132 | if (ret) | |
133 | return ret; | |
134 | ||
04700027 | 135 | if (is_fe) { |
27b01081 KM |
136 | int is_single_links = 0; |
137 | ||
04700027 KM |
138 | /* BE is dummy */ |
139 | dai_link->codec_of_node = NULL; | |
140 | dai_link->codec_dai_name = "snd-soc-dummy-dai"; | |
141 | dai_link->codec_name = "snd-soc-dummy"; | |
142 | ||
143 | /* FE settings */ | |
144 | dai_link->dynamic = 1; | |
145 | dai_link->dpcm_merged_format = 1; | |
5bbf3866 KM |
146 | |
147 | ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL, | |
148 | &is_single_links); | |
149 | if (ret) | |
575f1f92 | 150 | return ret; |
04700027 | 151 | |
c9a235da KM |
152 | ret = asoc_simple_card_parse_clk_cpu(np, dai_link, dai_props); |
153 | if (ret < 0) | |
154 | return ret; | |
155 | ||
8a99a6bd KM |
156 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, |
157 | "fe.%s", | |
158 | dai_link->cpu_dai_name); | |
159 | if (ret < 0) | |
160 | return ret; | |
04700027 | 161 | |
27b01081 | 162 | asoc_simple_card_canonicalize_cpu(dai_link, is_single_links); |
04700027 | 163 | } else { |
53e682b6 | 164 | const struct asoc_simple_card_of_data *of_data; |
415f1cb2 | 165 | |
6a16c176 | 166 | of_data = of_device_get_match_data(dev); |
415f1cb2 | 167 | |
04700027 KM |
168 | /* FE is dummy */ |
169 | dai_link->cpu_of_node = NULL; | |
170 | dai_link->cpu_dai_name = "snd-soc-dummy-dai"; | |
171 | dai_link->cpu_name = "snd-soc-dummy"; | |
415f1cb2 | 172 | |
04700027 KM |
173 | /* BE settings */ |
174 | dai_link->no_pcm = 1; | |
53e682b6 | 175 | dai_link->be_hw_params_fixup = asoc_simple_card_be_hw_params_fixup; |
5bbf3866 KM |
176 | |
177 | ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL); | |
575f1f92 KM |
178 | if (ret < 0) |
179 | return ret; | |
04700027 | 180 | |
c9a235da KM |
181 | ret = asoc_simple_card_parse_clk_codec(np, dai_link, dai_props); |
182 | if (ret < 0) | |
183 | return ret; | |
184 | ||
8a99a6bd KM |
185 | ret = asoc_simple_card_set_dailink_name(dev, dai_link, |
186 | "be.%s", | |
187 | dai_link->codec_dai_name); | |
188 | if (ret < 0) | |
189 | return ret; | |
190 | ||
04700027 | 191 | /* additional name prefix */ |
b7419dd7 KM |
192 | if (of_data) { |
193 | priv->codec_conf.of_node = dai_link->codec_of_node; | |
194 | priv->codec_conf.name_prefix = of_data->prefix; | |
195 | } else { | |
196 | snd_soc_of_parse_audio_prefix(&priv->snd_card, | |
197 | &priv->codec_conf, | |
198 | dai_link->codec_of_node, | |
199 | "audio-prefix"); | |
200 | } | |
415f1cb2 KM |
201 | } |
202 | ||
a09f383e KM |
203 | ret = asoc_simple_card_canonicalize_dailink(dai_link); |
204 | if (ret < 0) | |
205 | return ret; | |
206 | ||
04700027 KM |
207 | dai_link->dpcm_playback = 1; |
208 | dai_link->dpcm_capture = 1; | |
53e682b6 KM |
209 | dai_link->ops = &asoc_simple_card_ops; |
210 | dai_link->init = asoc_simple_card_dai_init; | |
04700027 | 211 | |
04700027 | 212 | dev_dbg(dev, "\t%s / %04x / %d\n", |
8a99a6bd | 213 | dai_link->name, |
ae638b72 | 214 | dai_link->dai_fmt, |
04700027 | 215 | dai_props->sysclk); |
415f1cb2 | 216 | |
c9a235da | 217 | return 0; |
415f1cb2 KM |
218 | } |
219 | ||
53e682b6 KM |
220 | static int asoc_simple_card_dai_link_of(struct device_node *node, |
221 | struct asoc_simple_card_priv *priv) | |
af998f85 | 222 | { |
53e682b6 | 223 | struct device *dev = simple_priv_to_dev(priv); |
af998f85 KM |
224 | struct snd_soc_dai_link *dai_link; |
225 | struct device_node *np; | |
226 | unsigned int daifmt = 0; | |
227 | int ret, i; | |
228 | bool is_fe; | |
229 | ||
230 | /* find 1st codec */ | |
231 | i = 0; | |
232 | for_each_child_of_node(node, np) { | |
53e682b6 | 233 | dai_link = simple_priv_to_link(priv, i); |
af998f85 KM |
234 | |
235 | if (strcmp(np->name, "codec") == 0) { | |
d6a4a9a4 KM |
236 | ret = asoc_simple_card_parse_daifmt(dev, node, np, |
237 | NULL, &daifmt); | |
af998f85 KM |
238 | if (ret < 0) |
239 | return ret; | |
240 | break; | |
241 | } | |
242 | i++; | |
243 | } | |
244 | ||
245 | i = 0; | |
246 | for_each_child_of_node(node, np) { | |
53e682b6 | 247 | dai_link = simple_priv_to_link(priv, i); |
af998f85 KM |
248 | dai_link->dai_fmt = daifmt; |
249 | ||
250 | is_fe = false; | |
251 | if (strcmp(np->name, "cpu") == 0) | |
252 | is_fe = true; | |
253 | ||
53e682b6 | 254 | ret = asoc_simple_card_parse_links(np, priv, i, is_fe); |
af998f85 KM |
255 | if (ret < 0) |
256 | return ret; | |
257 | i++; | |
258 | } | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
53e682b6 KM |
263 | static int asoc_simple_card_parse_of(struct device_node *node, |
264 | struct asoc_simple_card_priv *priv, | |
3c7e64dd | 265 | struct device *dev) |
415f1cb2 | 266 | { |
53e682b6 | 267 | const struct asoc_simple_card_of_data *of_data = of_device_get_match_data(dev); |
303c3be4 | 268 | struct asoc_simple_dai *props; |
3433bf07 | 269 | struct snd_soc_dai_link *links; |
415f1cb2 | 270 | int ret; |
af998f85 | 271 | int num; |
415f1cb2 KM |
272 | |
273 | if (!node) | |
274 | return -EINVAL; | |
275 | ||
3433bf07 KM |
276 | num = of_get_child_count(node); |
277 | props = devm_kzalloc(dev, sizeof(*props) * num, GFP_KERNEL); | |
278 | links = devm_kzalloc(dev, sizeof(*links) * num, GFP_KERNEL); | |
279 | if (!props || !links) | |
280 | return -ENOMEM; | |
281 | ||
282 | priv->dai_props = props; | |
283 | priv->dai_link = links; | |
3433bf07 | 284 | |
3c7e64dd KM |
285 | /* Init snd_soc_card */ |
286 | priv->snd_card.owner = THIS_MODULE; | |
287 | priv->snd_card.dev = dev; | |
288 | priv->snd_card.dai_link = priv->dai_link; | |
3433bf07 | 289 | priv->snd_card.num_links = num; |
3c7e64dd KM |
290 | priv->snd_card.codec_conf = &priv->codec_conf; |
291 | priv->snd_card.num_configs = 1; | |
b7419dd7 KM |
292 | |
293 | if (of_data) { | |
294 | priv->snd_card.of_dapm_routes = of_data->routes; | |
295 | priv->snd_card.num_of_dapm_routes = of_data->num_routes; | |
296 | } else { | |
297 | snd_soc_of_parse_audio_routing(&priv->snd_card, | |
298 | "audio-routing"); | |
299 | } | |
415f1cb2 | 300 | |
af7e2be9 KM |
301 | /* sampling rate convert */ |
302 | of_property_read_u32(node, "convert-rate", &priv->convert_rate); | |
303 | ||
f90432fc KM |
304 | /* channels transfer */ |
305 | of_property_read_u32(node, "convert-channels", &priv->convert_channels); | |
306 | ||
307 | dev_dbg(dev, "New rsrc-audio-card: %s\n", | |
308 | priv->snd_card.name ? priv->snd_card.name : ""); | |
309 | dev_dbg(dev, "SRC : convert_rate %d\n", priv->convert_rate); | |
310 | dev_dbg(dev, "CTU : convert_channels %d\n", priv->convert_channels); | |
415f1cb2 | 311 | |
53e682b6 | 312 | ret = asoc_simple_card_dai_link_of(node, priv); |
af998f85 KM |
313 | if (ret < 0) |
314 | return ret; | |
415f1cb2 | 315 | |
53ae918f KM |
316 | ret = asoc_simple_card_parse_card_name(&priv->snd_card, "card-"); |
317 | if (ret < 0) | |
318 | return ret; | |
415f1cb2 KM |
319 | |
320 | return 0; | |
321 | } | |
322 | ||
53e682b6 | 323 | static int asoc_simple_card_probe(struct platform_device *pdev) |
415f1cb2 | 324 | { |
53e682b6 | 325 | struct asoc_simple_card_priv *priv; |
415f1cb2 KM |
326 | struct device_node *np = pdev->dev.of_node; |
327 | struct device *dev = &pdev->dev; | |
328 | int ret; | |
329 | ||
330 | /* Allocate the private data */ | |
331 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
332 | if (!priv) | |
333 | return -ENOMEM; | |
334 | ||
53e682b6 | 335 | ret = asoc_simple_card_parse_of(np, priv, dev); |
415f1cb2 KM |
336 | if (ret < 0) { |
337 | if (ret != -EPROBE_DEFER) | |
338 | dev_err(dev, "parse error %d\n", ret); | |
339 | goto err; | |
340 | } | |
341 | ||
342 | snd_soc_card_set_drvdata(&priv->snd_card, priv); | |
343 | ||
344 | ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); | |
345 | if (ret >= 0) | |
346 | return ret; | |
347 | err: | |
239486ba | 348 | asoc_simple_card_clean_reference(&priv->snd_card); |
415f1cb2 KM |
349 | |
350 | return ret; | |
351 | } | |
352 | ||
53e682b6 | 353 | static int asoc_simple_card_remove(struct platform_device *pdev) |
415f1cb2 KM |
354 | { |
355 | struct snd_soc_card *card = platform_get_drvdata(pdev); | |
356 | ||
239486ba | 357 | return asoc_simple_card_clean_reference(card); |
415f1cb2 KM |
358 | } |
359 | ||
53e682b6 | 360 | static struct platform_driver asoc_simple_card = { |
415f1cb2 KM |
361 | .driver = { |
362 | .name = "renesas-src-audio-card", | |
53e682b6 | 363 | .of_match_table = asoc_simple_card_of_match, |
415f1cb2 | 364 | }, |
53e682b6 KM |
365 | .probe = asoc_simple_card_probe, |
366 | .remove = asoc_simple_card_remove, | |
415f1cb2 KM |
367 | }; |
368 | ||
53e682b6 | 369 | module_platform_driver(asoc_simple_card); |
415f1cb2 | 370 | |
53e682b6 | 371 | MODULE_ALIAS("platform:asoc-simple-scu-card"); |
415f1cb2 | 372 | MODULE_LICENSE("GPL"); |
53e682b6 | 373 | MODULE_DESCRIPTION("ASoC Simple SCU Sound Card"); |
415f1cb2 | 374 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |