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