]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * ASoC simple sound card support | |
3 | * | |
4 | * Copyright (C) 2012 Renesas Solutions Corp. | |
5 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License version 2 as | |
9 | * published by the Free Software Foundation. | |
10 | */ | |
11 | #include <linux/clk.h> | |
12 | #include <linux/module.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/string.h> | |
16 | #include <sound/simple_card.h> | |
17 | ||
18 | struct simple_card_data { | |
19 | struct snd_soc_card snd_card; | |
20 | unsigned int daifmt; | |
21 | struct asoc_simple_dai cpu_dai; | |
22 | struct asoc_simple_dai codec_dai; | |
23 | struct snd_soc_dai_link snd_link; | |
24 | }; | |
25 | ||
26 | static int __asoc_simple_card_dai_init(struct snd_soc_dai *dai, | |
27 | struct asoc_simple_dai *set) | |
28 | { | |
29 | int ret; | |
30 | ||
31 | if (set->fmt) { | |
32 | ret = snd_soc_dai_set_fmt(dai, set->fmt); | |
33 | if (ret && ret != -ENOTSUPP) { | |
34 | dev_err(dai->dev, "simple-card: set_fmt error\n"); | |
35 | goto err; | |
36 | } | |
37 | } | |
38 | ||
39 | if (set->sysclk) { | |
40 | ret = snd_soc_dai_set_sysclk(dai, 0, set->sysclk, 0); | |
41 | if (ret && ret != -ENOTSUPP) { | |
42 | dev_err(dai->dev, "simple-card: set_sysclk error\n"); | |
43 | goto err; | |
44 | } | |
45 | } | |
46 | ||
47 | ret = 0; | |
48 | ||
49 | err: | |
50 | return ret; | |
51 | } | |
52 | ||
53 | static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd) | |
54 | { | |
55 | struct simple_card_data *priv = | |
56 | snd_soc_card_get_drvdata(rtd->card); | |
57 | struct snd_soc_dai *codec = rtd->codec_dai; | |
58 | struct snd_soc_dai *cpu = rtd->cpu_dai; | |
59 | int ret; | |
60 | ||
61 | ret = __asoc_simple_card_dai_init(codec, &priv->codec_dai); | |
62 | if (ret < 0) | |
63 | return ret; | |
64 | ||
65 | ret = __asoc_simple_card_dai_init(cpu, &priv->cpu_dai); | |
66 | if (ret < 0) | |
67 | return ret; | |
68 | ||
69 | return 0; | |
70 | } | |
71 | ||
72 | static int | |
73 | asoc_simple_card_sub_parse_of(struct device_node *np, | |
74 | unsigned int daifmt, | |
75 | struct asoc_simple_dai *dai, | |
76 | const struct device_node **p_node, | |
77 | const char **name) | |
78 | { | |
79 | struct device_node *node; | |
80 | struct clk *clk; | |
81 | int ret; | |
82 | ||
83 | /* | |
84 | * get node via "sound-dai = <&phandle port>" | |
85 | * it will be used as xxx_of_node on soc_bind_dai_link() | |
86 | */ | |
87 | node = of_parse_phandle(np, "sound-dai", 0); | |
88 | if (!node) | |
89 | return -ENODEV; | |
90 | *p_node = node; | |
91 | ||
92 | /* get dai->name */ | |
93 | ret = snd_soc_of_get_dai_name(np, name); | |
94 | if (ret < 0) | |
95 | goto parse_error; | |
96 | ||
97 | /* | |
98 | * bitclock-inversion, frame-inversion | |
99 | * bitclock-master, frame-master | |
100 | * and specific "format" if it has | |
101 | */ | |
102 | dai->fmt = snd_soc_of_parse_daifmt(np, NULL); | |
103 | dai->fmt |= daifmt; | |
104 | ||
105 | /* | |
106 | * dai->sysclk come from | |
107 | * "clocks = <&xxx>" (if system has common clock) | |
108 | * or "system-clock-frequency = <xxx>" | |
109 | * or device's module clock. | |
110 | */ | |
111 | if (of_property_read_bool(np, "clocks")) { | |
112 | clk = of_clk_get(np, 0); | |
113 | if (IS_ERR(clk)) { | |
114 | ret = PTR_ERR(clk); | |
115 | goto parse_error; | |
116 | } | |
117 | ||
118 | dai->sysclk = clk_get_rate(clk); | |
119 | } else if (of_property_read_bool(np, "system-clock-frequency")) { | |
120 | of_property_read_u32(np, | |
121 | "system-clock-frequency", | |
122 | &dai->sysclk); | |
123 | } else { | |
124 | clk = of_clk_get(node, 0); | |
125 | if (!IS_ERR(clk)) | |
126 | dai->sysclk = clk_get_rate(clk); | |
127 | } | |
128 | ||
129 | ret = 0; | |
130 | ||
131 | parse_error: | |
132 | of_node_put(node); | |
133 | ||
134 | return ret; | |
135 | } | |
136 | ||
137 | static int asoc_simple_card_parse_of(struct device_node *node, | |
138 | struct simple_card_data *priv, | |
139 | struct device *dev) | |
140 | { | |
141 | struct snd_soc_dai_link *dai_link = priv->snd_card.dai_link; | |
142 | struct device_node *np; | |
143 | char *name; | |
144 | int ret; | |
145 | ||
146 | /* get CPU/CODEC common format via simple-audio-card,format */ | |
147 | priv->daifmt = snd_soc_of_parse_daifmt(node, "simple-audio-card,") & | |
148 | (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK); | |
149 | ||
150 | /* DAPM routes */ | |
151 | if (of_property_read_bool(node, "simple-audio-card,routing")) { | |
152 | ret = snd_soc_of_parse_audio_routing(&priv->snd_card, | |
153 | "simple-audio-card,routing"); | |
154 | if (ret) | |
155 | return ret; | |
156 | } | |
157 | ||
158 | /* CPU sub-node */ | |
159 | ret = -EINVAL; | |
160 | np = of_get_child_by_name(node, "simple-audio-card,cpu"); | |
161 | if (np) | |
162 | ret = asoc_simple_card_sub_parse_of(np, priv->daifmt, | |
163 | &priv->cpu_dai, | |
164 | &dai_link->cpu_of_node, | |
165 | &dai_link->cpu_dai_name); | |
166 | if (ret < 0) | |
167 | return ret; | |
168 | ||
169 | /* CODEC sub-node */ | |
170 | ret = -EINVAL; | |
171 | np = of_get_child_by_name(node, "simple-audio-card,codec"); | |
172 | if (np) | |
173 | ret = asoc_simple_card_sub_parse_of(np, priv->daifmt, | |
174 | &priv->codec_dai, | |
175 | &dai_link->codec_of_node, | |
176 | &dai_link->codec_dai_name); | |
177 | if (ret < 0) | |
178 | return ret; | |
179 | ||
180 | if (!dai_link->cpu_dai_name || !dai_link->codec_dai_name) | |
181 | return -EINVAL; | |
182 | ||
183 | /* card name is created from CPU/CODEC dai name */ | |
184 | name = devm_kzalloc(dev, | |
185 | strlen(dai_link->cpu_dai_name) + | |
186 | strlen(dai_link->codec_dai_name) + 2, | |
187 | GFP_KERNEL); | |
188 | sprintf(name, "%s-%s", dai_link->cpu_dai_name, | |
189 | dai_link->codec_dai_name); | |
190 | priv->snd_card.name = name; | |
191 | dai_link->name = dai_link->stream_name = name; | |
192 | ||
193 | /* simple-card assumes platform == cpu */ | |
194 | dai_link->platform_of_node = dai_link->cpu_of_node; | |
195 | ||
196 | dev_dbg(dev, "card-name : %s\n", name); | |
197 | dev_dbg(dev, "platform : %04x\n", priv->daifmt); | |
198 | dev_dbg(dev, "cpu : %s / %04x / %d\n", | |
199 | dai_link->cpu_dai_name, | |
200 | priv->cpu_dai.fmt, | |
201 | priv->cpu_dai.sysclk); | |
202 | dev_dbg(dev, "codec : %s / %04x / %d\n", | |
203 | dai_link->codec_dai_name, | |
204 | priv->codec_dai.fmt, | |
205 | priv->codec_dai.sysclk); | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static int asoc_simple_card_probe(struct platform_device *pdev) | |
211 | { | |
212 | struct simple_card_data *priv; | |
213 | struct snd_soc_dai_link *dai_link; | |
214 | struct device_node *np = pdev->dev.of_node; | |
215 | struct device *dev = &pdev->dev; | |
216 | int ret; | |
217 | ||
218 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
219 | if (!priv) | |
220 | return -ENOMEM; | |
221 | ||
222 | /* | |
223 | * init snd_soc_card | |
224 | */ | |
225 | priv->snd_card.owner = THIS_MODULE; | |
226 | priv->snd_card.dev = dev; | |
227 | dai_link = &priv->snd_link; | |
228 | priv->snd_card.dai_link = dai_link; | |
229 | priv->snd_card.num_links = 1; | |
230 | ||
231 | if (np && of_device_is_available(np)) { | |
232 | ||
233 | ret = asoc_simple_card_parse_of(np, priv, dev); | |
234 | if (ret < 0) { | |
235 | if (ret != -EPROBE_DEFER) | |
236 | dev_err(dev, "parse error %d\n", ret); | |
237 | return ret; | |
238 | } | |
239 | } else { | |
240 | struct asoc_simple_card_info *cinfo; | |
241 | ||
242 | cinfo = dev->platform_data; | |
243 | if (!cinfo) { | |
244 | dev_err(dev, "no info for asoc-simple-card\n"); | |
245 | return -EINVAL; | |
246 | } | |
247 | ||
248 | if (!cinfo->name || | |
249 | !cinfo->card || | |
250 | !cinfo->codec_dai.name || | |
251 | !cinfo->codec || | |
252 | !cinfo->platform || | |
253 | !cinfo->cpu_dai.name) { | |
254 | dev_err(dev, "insufficient asoc_simple_card_info settings\n"); | |
255 | return -EINVAL; | |
256 | } | |
257 | ||
258 | priv->snd_card.name = cinfo->card; | |
259 | dai_link->name = cinfo->name; | |
260 | dai_link->stream_name = cinfo->name; | |
261 | dai_link->platform_name = cinfo->platform; | |
262 | dai_link->codec_name = cinfo->codec; | |
263 | dai_link->cpu_dai_name = cinfo->cpu_dai.name; | |
264 | dai_link->codec_dai_name = cinfo->codec_dai.name; | |
265 | memcpy(&priv->cpu_dai, &cinfo->cpu_dai, | |
266 | sizeof(priv->cpu_dai)); | |
267 | memcpy(&priv->codec_dai, &cinfo->codec_dai, | |
268 | sizeof(priv->codec_dai)); | |
269 | } | |
270 | ||
271 | /* | |
272 | * init snd_soc_dai_link | |
273 | */ | |
274 | dai_link->init = asoc_simple_card_dai_init; | |
275 | ||
276 | snd_soc_card_set_drvdata(&priv->snd_card, priv); | |
277 | ||
278 | return devm_snd_soc_register_card(&pdev->dev, &priv->snd_card); | |
279 | } | |
280 | ||
281 | static const struct of_device_id asoc_simple_of_match[] = { | |
282 | { .compatible = "simple-audio-card", }, | |
283 | {}, | |
284 | }; | |
285 | MODULE_DEVICE_TABLE(of, asoc_simple_of_match); | |
286 | ||
287 | static struct platform_driver asoc_simple_card = { | |
288 | .driver = { | |
289 | .name = "asoc-simple-card", | |
290 | .owner = THIS_MODULE, | |
291 | .of_match_table = asoc_simple_of_match, | |
292 | }, | |
293 | .probe = asoc_simple_card_probe, | |
294 | }; | |
295 | ||
296 | module_platform_driver(asoc_simple_card); | |
297 | ||
298 | MODULE_ALIAS("platform:asoc-simple-card"); | |
299 | MODULE_LICENSE("GPL"); | |
300 | MODULE_DESCRIPTION("ASoC Simple Sound Card"); | |
301 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |