Commit 7ad933d7a6677c20ce1bdb17425e732cf1ebee8a
Committed by
Mark Brown
1 parent
1cad1de1b2
Exists in
master
and in
20 other branches
ASoC: Machine driver for for s3c24xx with uda134x
Signed-off-by: Christian Pellegrin <chripell@fsfe.org> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Showing 4 changed files with 395 additions and 0 deletions Side-by-side Diff
include/sound/s3c24xx_uda134x.h
sound/soc/s3c24xx/Kconfig
... | ... | @@ -43,4 +43,10 @@ |
43 | 43 | help |
44 | 44 | Say Y if you want to add support for SoC audio on ln2440sbc |
45 | 45 | with the ALC650. |
46 | + | |
47 | +config SND_S3C24XX_SOC_S3C24XX_UDA134X | |
48 | + tristate "SoC I2S Audio support UDA134X wired to a S3C24XX" | |
49 | + depends on SND_S3C24XX_SOC | |
50 | + select SND_S3C24XX_SOC_I2S | |
51 | + select SND_SOC_UDA134X |
sound/soc/s3c24xx/Makefile
... | ... | @@ -13,8 +13,10 @@ |
13 | 13 | snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o |
14 | 14 | snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o |
15 | 15 | snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o |
16 | +snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o | |
16 | 17 | |
17 | 18 | obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o |
18 | 19 | obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o |
19 | 20 | obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o |
21 | +obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o |
sound/soc/s3c24xx/s3c24xx_uda134x.c
1 | +/* | |
2 | + * Modifications by Christian Pellegrin <chripell@evolware.org> | |
3 | + * | |
4 | + * s3c24xx_uda134x.c -- S3C24XX_UDA134X ALSA SoC Audio board driver | |
5 | + * | |
6 | + * Copyright 2007 Dension Audio Systems Ltd. | |
7 | + * Author: Zoltan Devai | |
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 | + | |
14 | +#include <linux/module.h> | |
15 | +#include <linux/clk.h> | |
16 | +#include <linux/mutex.h> | |
17 | +#include <linux/gpio.h> | |
18 | +#include <sound/pcm.h> | |
19 | +#include <sound/pcm_params.h> | |
20 | +#include <sound/soc.h> | |
21 | +#include <sound/soc-dapm.h> | |
22 | +#include <sound/s3c24xx_uda134x.h> | |
23 | +#include <sound/uda134x.h> | |
24 | + | |
25 | +#include <asm/plat-s3c24xx/regs-iis.h> | |
26 | + | |
27 | +#include "s3c24xx-pcm.h" | |
28 | +#include "s3c24xx-i2s.h" | |
29 | +#include "../codecs/uda134x_codec.h" | |
30 | + | |
31 | + | |
32 | +/* #define ENFORCE_RATES 1 */ | |
33 | +/* | |
34 | + Unfortunately the S3C24XX in master mode has a limited capacity of | |
35 | + generating the clock for the codec. If you define this only rates | |
36 | + that are really available will be enforced. But be careful, most | |
37 | + user level application just want the usual sampling frequencies (8, | |
38 | + 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly | |
39 | + operation for embedded systems. So if you aren't very lucky or your | |
40 | + hardware engineer wasn't very forward-looking it's better to leave | |
41 | + this undefined. If you do so an approximate value for the requested | |
42 | + sampling rate in the range -/+ 5% will be chosen. If this in not | |
43 | + possible an error will be returned. | |
44 | +*/ | |
45 | + | |
46 | +static struct clk *xtal; | |
47 | +static struct clk *pclk; | |
48 | +/* this is need because we don't have a place where to keep the | |
49 | + * pointers to the clocks in each substream. We get the clocks only | |
50 | + * when we are actually using them so we don't block stuff like | |
51 | + * frequency change or oscillator power-off */ | |
52 | +static int clk_users; | |
53 | +static DEFINE_MUTEX(clk_lock); | |
54 | + | |
55 | +static unsigned int rates[33 * 2]; | |
56 | +#ifdef ENFORCE_RATES | |
57 | +static struct snd_pcm_hw_constraint_list hw_constraints_rates = { | |
58 | + .count = ARRAY_SIZE(rates), | |
59 | + .list = rates, | |
60 | + .mask = 0, | |
61 | +}; | |
62 | +#endif | |
63 | + | |
64 | +static struct platform_device *s3c24xx_uda134x_snd_device; | |
65 | + | |
66 | +int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream) | |
67 | +{ | |
68 | + int ret = 0; | |
69 | +#ifdef ENFORCE_RATES | |
70 | + struct snd_pcm_runtime *runtime = substream->runtime;; | |
71 | +#endif | |
72 | + | |
73 | + mutex_lock(&clk_lock); | |
74 | + pr_debug("%s %d\n", __func__, clk_users); | |
75 | + if (clk_users == 0) { | |
76 | + xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal"); | |
77 | + if (!xtal) { | |
78 | + printk(KERN_ERR "%s cannot get xtal\n", __func__); | |
79 | + ret = -EBUSY; | |
80 | + } else { | |
81 | + pclk = clk_get(&s3c24xx_uda134x_snd_device->dev, | |
82 | + "pclk"); | |
83 | + if (!pclk) { | |
84 | + printk(KERN_ERR "%s cannot get pclk\n", | |
85 | + __func__); | |
86 | + clk_put(xtal); | |
87 | + ret = -EBUSY; | |
88 | + } | |
89 | + } | |
90 | + if (!ret) { | |
91 | + int i, j; | |
92 | + | |
93 | + for (i = 0; i < 2; i++) { | |
94 | + int fs = i ? 256 : 384; | |
95 | + | |
96 | + rates[i*33] = clk_get_rate(xtal) / fs; | |
97 | + for (j = 1; j < 33; j++) | |
98 | + rates[i*33 + j] = clk_get_rate(pclk) / | |
99 | + (j * fs); | |
100 | + } | |
101 | + } | |
102 | + } | |
103 | + clk_users += 1; | |
104 | + mutex_unlock(&clk_lock); | |
105 | + if (!ret) { | |
106 | +#ifdef ENFORCE_RATES | |
107 | + ret = snd_pcm_hw_constraint_list(runtime, 0, | |
108 | + SNDRV_PCM_HW_PARAM_RATE, | |
109 | + &hw_constraints_rates); | |
110 | + if (ret < 0) | |
111 | + printk(KERN_ERR "%s cannot set constraints\n", | |
112 | + __func__); | |
113 | +#endif | |
114 | + } | |
115 | + return ret; | |
116 | +} | |
117 | + | |
118 | +void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream) | |
119 | +{ | |
120 | + mutex_lock(&clk_lock); | |
121 | + pr_debug("%s %d\n", __func__, clk_users); | |
122 | + clk_users -= 1; | |
123 | + if (clk_users == 0) { | |
124 | + clk_put(xtal); | |
125 | + xtal = NULL; | |
126 | + clk_put(pclk); | |
127 | + pclk = NULL; | |
128 | + } | |
129 | + mutex_unlock(&clk_lock); | |
130 | +} | |
131 | + | |
132 | +static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream, | |
133 | + struct snd_pcm_hw_params *params) | |
134 | +{ | |
135 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
136 | + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; | |
137 | + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | |
138 | + unsigned int clk = 0; | |
139 | + int ret = 0; | |
140 | + int clk_source, fs_mode; | |
141 | + unsigned long rate = params_rate(params); | |
142 | + long err, cerr; | |
143 | + unsigned int div; | |
144 | + int i, bi; | |
145 | + | |
146 | + err = 999999; | |
147 | + bi = 0; | |
148 | + for (i = 0; i < 2*33; i++) { | |
149 | + cerr = rates[i] - rate; | |
150 | + if (cerr < 0) | |
151 | + cerr = -cerr; | |
152 | + if (cerr < err) { | |
153 | + err = cerr; | |
154 | + bi = i; | |
155 | + } | |
156 | + } | |
157 | + if (bi / 33 == 1) | |
158 | + fs_mode = S3C2410_IISMOD_256FS; | |
159 | + else | |
160 | + fs_mode = S3C2410_IISMOD_384FS; | |
161 | + if (bi % 33 == 0) { | |
162 | + clk_source = S3C24XX_CLKSRC_MPLL; | |
163 | + div = 1; | |
164 | + } else { | |
165 | + clk_source = S3C24XX_CLKSRC_PCLK; | |
166 | + div = bi % 33; | |
167 | + } | |
168 | + pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi); | |
169 | + | |
170 | + clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate; | |
171 | + pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__, | |
172 | + fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS", | |
173 | + clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK", | |
174 | + div, clk, err); | |
175 | + | |
176 | + if ((err * 100 / rate) > 5) { | |
177 | + printk(KERN_ERR "S3C24XX_UDA134X: effective frequency " | |
178 | + "too different from desired (%ld%%)\n", | |
179 | + err * 100 / rate); | |
180 | + return -EINVAL; | |
181 | + } | |
182 | + | |
183 | + ret = codec_dai->dai_ops.set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | | |
184 | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | |
185 | + if (ret < 0) | |
186 | + return ret; | |
187 | + | |
188 | + ret = cpu_dai->dai_ops.set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | | |
189 | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); | |
190 | + if (ret < 0) | |
191 | + return ret; | |
192 | + | |
193 | + ret = cpu_dai->dai_ops.set_sysclk(cpu_dai, clk_source , clk, | |
194 | + SND_SOC_CLOCK_IN); | |
195 | + if (ret < 0) | |
196 | + return ret; | |
197 | + | |
198 | + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, | |
199 | + fs_mode); | |
200 | + if (ret < 0) | |
201 | + return ret; | |
202 | + | |
203 | + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK, | |
204 | + S3C2410_IISMOD_32FS); | |
205 | + if (ret < 0) | |
206 | + return ret; | |
207 | + | |
208 | + ret = cpu_dai->dai_ops.set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, | |
209 | + S3C24XX_PRESCALE(div, div)); | |
210 | + if (ret < 0) | |
211 | + return ret; | |
212 | + | |
213 | + /* set the codec system clock for DAC and ADC */ | |
214 | + ret = codec_dai->dai_ops.set_sysclk(codec_dai, 0, clk, | |
215 | + SND_SOC_CLOCK_OUT); | |
216 | + if (ret < 0) | |
217 | + return ret; | |
218 | + | |
219 | + return 0; | |
220 | +} | |
221 | + | |
222 | +static struct snd_soc_ops s3c24xx_uda134x_ops = { | |
223 | + .startup = s3c24xx_uda134x_startup, | |
224 | + .shutdown = s3c24xx_uda134x_shutdown, | |
225 | + .hw_params = s3c24xx_uda134x_hw_params, | |
226 | +}; | |
227 | + | |
228 | +static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = { | |
229 | + .name = "UDA134X", | |
230 | + .stream_name = "UDA134X", | |
231 | + .codec_dai = &uda134x_dai, | |
232 | + .cpu_dai = &s3c24xx_i2s_dai, | |
233 | + .ops = &s3c24xx_uda134x_ops, | |
234 | +}; | |
235 | + | |
236 | +static struct snd_soc_machine snd_soc_machine_s3c24xx_uda134x = { | |
237 | + .name = "S3C24XX_UDA134X", | |
238 | + .dai_link = &s3c24xx_uda134x_dai_link, | |
239 | + .num_links = 1, | |
240 | +}; | |
241 | + | |
242 | +static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins; | |
243 | + | |
244 | +static void setdat(int v) | |
245 | +{ | |
246 | + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0); | |
247 | +} | |
248 | + | |
249 | +static void setclk(int v) | |
250 | +{ | |
251 | + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0); | |
252 | +} | |
253 | + | |
254 | +static void setmode(int v) | |
255 | +{ | |
256 | + gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0); | |
257 | +} | |
258 | + | |
259 | +static struct uda134x_platform_data s3c24xx_uda134x = { | |
260 | + .l3 = { | |
261 | + .setdat = setdat, | |
262 | + .setclk = setclk, | |
263 | + .setmode = setmode, | |
264 | + .data_hold = 1, | |
265 | + .data_setup = 1, | |
266 | + .clock_high = 1, | |
267 | + .mode_hold = 1, | |
268 | + .mode = 1, | |
269 | + .mode_setup = 1, | |
270 | + }, | |
271 | +}; | |
272 | + | |
273 | +static struct snd_soc_device s3c24xx_uda134x_snd_devdata = { | |
274 | + .machine = &snd_soc_machine_s3c24xx_uda134x, | |
275 | + .platform = &s3c24xx_soc_platform, | |
276 | + .codec_dev = &soc_codec_dev_uda134x, | |
277 | + .codec_data = &s3c24xx_uda134x, | |
278 | +}; | |
279 | + | |
280 | +static int s3c24xx_uda134x_setup_pin(int pin, char *fun) | |
281 | +{ | |
282 | + if (gpio_request(pin, "s3c24xx_uda134x") < 0) { | |
283 | + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " | |
284 | + "l3 %s pin already in use", fun); | |
285 | + return -EBUSY; | |
286 | + } | |
287 | + gpio_direction_output(pin, 0); | |
288 | + return 0; | |
289 | +} | |
290 | + | |
291 | +static int s3c24xx_uda134x_probe(struct platform_device *pdev) | |
292 | +{ | |
293 | + int ret; | |
294 | + | |
295 | + printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n"); | |
296 | + | |
297 | + s3c24xx_uda134x_l3_pins = pdev->dev.platform_data; | |
298 | + if (s3c24xx_uda134x_l3_pins == NULL) { | |
299 | + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " | |
300 | + "unable to find platform data\n"); | |
301 | + return -ENODEV; | |
302 | + } | |
303 | + s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power; | |
304 | + s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model; | |
305 | + | |
306 | + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data, | |
307 | + "data") < 0) | |
308 | + return -EBUSY; | |
309 | + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk, | |
310 | + "clk") < 0) { | |
311 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); | |
312 | + return -EBUSY; | |
313 | + } | |
314 | + if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode, | |
315 | + "mode") < 0) { | |
316 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); | |
317 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_clk); | |
318 | + return -EBUSY; | |
319 | + } | |
320 | + | |
321 | + s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1); | |
322 | + if (!s3c24xx_uda134x_snd_device) { | |
323 | + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: " | |
324 | + "Unable to register\n"); | |
325 | + return -ENOMEM; | |
326 | + } | |
327 | + | |
328 | + platform_set_drvdata(s3c24xx_uda134x_snd_device, | |
329 | + &s3c24xx_uda134x_snd_devdata); | |
330 | + s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev; | |
331 | + ret = platform_device_add(s3c24xx_uda134x_snd_device); | |
332 | + if (ret) { | |
333 | + printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n"); | |
334 | + platform_device_put(s3c24xx_uda134x_snd_device); | |
335 | + } | |
336 | + | |
337 | + return ret; | |
338 | +} | |
339 | + | |
340 | +static int s3c24xx_uda134x_remove(struct platform_device *pdev) | |
341 | +{ | |
342 | + platform_device_unregister(s3c24xx_uda134x_snd_device); | |
343 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_data); | |
344 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_clk); | |
345 | + gpio_free(s3c24xx_uda134x_l3_pins->l3_mode); | |
346 | + return 0; | |
347 | +} | |
348 | + | |
349 | +static struct platform_driver s3c24xx_uda134x_driver = { | |
350 | + .probe = s3c24xx_uda134x_probe, | |
351 | + .remove = s3c24xx_uda134x_remove, | |
352 | + .driver = { | |
353 | + .name = "s3c24xx_uda134x", | |
354 | + .owner = THIS_MODULE, | |
355 | + }, | |
356 | +}; | |
357 | + | |
358 | +static int __init s3c24xx_uda134x_init(void) | |
359 | +{ | |
360 | + return platform_driver_register(&s3c24xx_uda134x_driver); | |
361 | +} | |
362 | + | |
363 | +static void __exit s3c24xx_uda134x_exit(void) | |
364 | +{ | |
365 | + platform_driver_unregister(&s3c24xx_uda134x_driver); | |
366 | +} | |
367 | + | |
368 | + | |
369 | +module_init(s3c24xx_uda134x_init); | |
370 | +module_exit(s3c24xx_uda134x_exit); | |
371 | + | |
372 | +MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>"); | |
373 | +MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver"); | |
374 | +MODULE_LICENSE("GPL"); |