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