blob: 2c09e93dd566f2a66b46dc5886df5119a017bec1 [file] [log] [blame]
Christian Pellegrin7ad933d2008-11-15 08:58:32 +01001/*
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>
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010021#include <sound/s3c24xx_uda134x.h>
22#include <sound/uda134x.h>
23
Ben Dooks8150bc82009-03-04 00:49:26 +000024#include <plat/regs-iis.h>
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010025
Jassi Brar4b640cf2010-11-22 15:35:57 +090026#include "dma.h"
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010027#include "s3c24xx-i2s.h"
Mark Brown72f2b892008-11-18 12:25:46 +000028#include "../codecs/uda134x.h"
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010029
30
31/* #define ENFORCE_RATES 1 */
32/*
33 Unfortunately the S3C24XX in master mode has a limited capacity of
34 generating the clock for the codec. If you define this only rates
35 that are really available will be enforced. But be careful, most
36 user level application just want the usual sampling frequencies (8,
37 11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
38 operation for embedded systems. So if you aren't very lucky or your
39 hardware engineer wasn't very forward-looking it's better to leave
40 this undefined. If you do so an approximate value for the requested
41 sampling rate in the range -/+ 5% will be chosen. If this in not
42 possible an error will be returned.
43*/
44
45static struct clk *xtal;
46static struct clk *pclk;
47/* this is need because we don't have a place where to keep the
48 * pointers to the clocks in each substream. We get the clocks only
49 * when we are actually using them so we don't block stuff like
50 * frequency change or oscillator power-off */
51static int clk_users;
52static DEFINE_MUTEX(clk_lock);
53
54static unsigned int rates[33 * 2];
55#ifdef ENFORCE_RATES
56static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
57 .count = ARRAY_SIZE(rates),
58 .list = rates,
59 .mask = 0,
60};
61#endif
62
63static struct platform_device *s3c24xx_uda134x_snd_device;
64
Mark Brownd0c36632008-11-18 21:57:17 +000065static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010066{
67 int ret = 0;
68#ifdef ENFORCE_RATES
Joe Perchesa419aef2009-08-18 11:18:35 -070069 struct snd_pcm_runtime *runtime = substream->runtime;
Christian Pellegrin7ad933d2008-11-15 08:58:32 +010070#endif
71
72 mutex_lock(&clk_lock);
73 pr_debug("%s %d\n", __func__, clk_users);
74 if (clk_users == 0) {
75 xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
76 if (!xtal) {
77 printk(KERN_ERR "%s cannot get xtal\n", __func__);
78 ret = -EBUSY;
79 } else {
80 pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
81 "pclk");
82 if (!pclk) {
83 printk(KERN_ERR "%s cannot get pclk\n",
84 __func__);
85 clk_put(xtal);
86 ret = -EBUSY;
87 }
88 }
89 if (!ret) {
90 int i, j;
91
92 for (i = 0; i < 2; i++) {
93 int fs = i ? 256 : 384;
94
95 rates[i*33] = clk_get_rate(xtal) / fs;
96 for (j = 1; j < 33; j++)
97 rates[i*33 + j] = clk_get_rate(pclk) /
98 (j * fs);
99 }
100 }
101 }
102 clk_users += 1;
103 mutex_unlock(&clk_lock);
104 if (!ret) {
105#ifdef ENFORCE_RATES
106 ret = snd_pcm_hw_constraint_list(runtime, 0,
107 SNDRV_PCM_HW_PARAM_RATE,
108 &hw_constraints_rates);
109 if (ret < 0)
110 printk(KERN_ERR "%s cannot set constraints\n",
111 __func__);
112#endif
113 }
114 return ret;
115}
116
Mark Brownd0c36632008-11-18 21:57:17 +0000117static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100118{
119 mutex_lock(&clk_lock);
120 pr_debug("%s %d\n", __func__, clk_users);
121 clk_users -= 1;
122 if (clk_users == 0) {
123 clk_put(xtal);
124 xtal = NULL;
125 clk_put(pclk);
126 pclk = NULL;
127 }
128 mutex_unlock(&clk_lock);
129}
130
131static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
132 struct snd_pcm_hw_params *params)
133{
134 struct snd_soc_pcm_runtime *rtd = substream->private_data;
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000135 struct snd_soc_dai *codec_dai = rtd->codec_dai;
136 struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100137 unsigned int clk = 0;
138 int ret = 0;
139 int clk_source, fs_mode;
140 unsigned long rate = params_rate(params);
141 long err, cerr;
142 unsigned int div;
143 int i, bi;
144
145 err = 999999;
146 bi = 0;
147 for (i = 0; i < 2*33; i++) {
148 cerr = rates[i] - rate;
149 if (cerr < 0)
150 cerr = -cerr;
151 if (cerr < err) {
152 err = cerr;
153 bi = i;
154 }
155 }
156 if (bi / 33 == 1)
157 fs_mode = S3C2410_IISMOD_256FS;
158 else
159 fs_mode = S3C2410_IISMOD_384FS;
160 if (bi % 33 == 0) {
161 clk_source = S3C24XX_CLKSRC_MPLL;
162 div = 1;
163 } else {
164 clk_source = S3C24XX_CLKSRC_PCLK;
165 div = bi % 33;
166 }
167 pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi);
168
169 clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
170 pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__,
171 fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
172 clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
173 div, clk, err);
174
175 if ((err * 100 / rate) > 5) {
176 printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
177 "too different from desired (%ld%%)\n",
178 err * 100 / rate);
179 return -EINVAL;
180 }
181
Mark Brownd0c36632008-11-18 21:57:17 +0000182 ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100183 SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
184 if (ret < 0)
185 return ret;
186
Mark Brownd0c36632008-11-18 21:57:17 +0000187 ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100188 SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
189 if (ret < 0)
190 return ret;
191
Mark Brownd0c36632008-11-18 21:57:17 +0000192 ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
193 SND_SOC_CLOCK_IN);
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100194 if (ret < 0)
195 return ret;
196
Mark Brownd0c36632008-11-18 21:57:17 +0000197 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100198 if (ret < 0)
199 return ret;
200
Mark Brownd0c36632008-11-18 21:57:17 +0000201 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
202 S3C2410_IISMOD_32FS);
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100203 if (ret < 0)
204 return ret;
205
Mark Brownd0c36632008-11-18 21:57:17 +0000206 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
207 S3C24XX_PRESCALE(div, div));
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100208 if (ret < 0)
209 return ret;
210
211 /* set the codec system clock for DAC and ADC */
Mark Brownd0c36632008-11-18 21:57:17 +0000212 ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
213 SND_SOC_CLOCK_OUT);
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100214 if (ret < 0)
215 return ret;
216
217 return 0;
218}
219
220static struct snd_soc_ops s3c24xx_uda134x_ops = {
221 .startup = s3c24xx_uda134x_startup,
222 .shutdown = s3c24xx_uda134x_shutdown,
223 .hw_params = s3c24xx_uda134x_hw_params,
224};
225
226static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
227 .name = "UDA134X",
228 .stream_name = "UDA134X",
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000229 .codec_name = "uda134x-hifi",
230 .codec_dai_name = "uda134x-hifi",
Lars-Peter Clausen518aa592011-01-24 22:12:42 +0100231 .cpu_dai_name = "s3c24xx-iis",
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100232 .ops = &s3c24xx_uda134x_ops,
Jassi Brar58bb4072010-11-22 15:35:50 +0900233 .platform_name = "samsung-audio",
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100234};
235
Mark Brown87506542008-11-18 20:50:34 +0000236static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100237 .name = "S3C24XX_UDA134X",
238 .dai_link = &s3c24xx_uda134x_dai_link,
239 .num_links = 1,
240};
241
242static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
243
244static void setdat(int v)
245{
246 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
247}
248
249static void setclk(int v)
250{
251 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
252}
253
254static void setmode(int v)
255{
256 gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
257}
258
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000259/* FIXME - This must be codec platform data but in which board file ?? */
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100260static struct uda134x_platform_data s3c24xx_uda134x = {
261 .l3 = {
262 .setdat = setdat,
263 .setclk = setclk,
264 .setmode = setmode,
265 .data_hold = 1,
266 .data_setup = 1,
267 .clock_high = 1,
268 .mode_hold = 1,
269 .mode = 1,
270 .mode_setup = 1,
271 },
272};
273
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100274static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
275{
276 if (gpio_request(pin, "s3c24xx_uda134x") < 0) {
277 printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
278 "l3 %s pin already in use", fun);
279 return -EBUSY;
280 }
281 gpio_direction_output(pin, 0);
282 return 0;
283}
284
285static int s3c24xx_uda134x_probe(struct platform_device *pdev)
286{
287 int ret;
288
289 printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");
290
291 s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
292 if (s3c24xx_uda134x_l3_pins == NULL) {
293 printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
294 "unable to find platform data\n");
295 return -ENODEV;
296 }
297 s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
298 s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
299
300 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
301 "data") < 0)
302 return -EBUSY;
303 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
304 "clk") < 0) {
305 gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
306 return -EBUSY;
307 }
308 if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
309 "mode") < 0) {
310 gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
311 gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
312 return -EBUSY;
313 }
314
315 s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
316 if (!s3c24xx_uda134x_snd_device) {
317 printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
318 "Unable to register\n");
319 return -ENOMEM;
320 }
321
322 platform_set_drvdata(s3c24xx_uda134x_snd_device,
Liam Girdwoodf0fba2a2010-03-17 20:15:21 +0000323 &snd_soc_s3c24xx_uda134x);
Christian Pellegrin7ad933d2008-11-15 08:58:32 +0100324 ret = platform_device_add(s3c24xx_uda134x_snd_device);
325 if (ret) {
326 printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");
327 platform_device_put(s3c24xx_uda134x_snd_device);
328 }
329
330 return ret;
331}
332
333static int s3c24xx_uda134x_remove(struct platform_device *pdev)
334{
335 platform_device_unregister(s3c24xx_uda134x_snd_device);
336 gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
337 gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
338 gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
339 return 0;
340}
341
342static struct platform_driver s3c24xx_uda134x_driver = {
343 .probe = s3c24xx_uda134x_probe,
344 .remove = s3c24xx_uda134x_remove,
345 .driver = {
346 .name = "s3c24xx_uda134x",
347 .owner = THIS_MODULE,
348 },
349};
350
351static int __init s3c24xx_uda134x_init(void)
352{
353 return platform_driver_register(&s3c24xx_uda134x_driver);
354}
355
356static void __exit s3c24xx_uda134x_exit(void)
357{
358 platform_driver_unregister(&s3c24xx_uda134x_driver);
359}
360
361
362module_init(s3c24xx_uda134x_init);
363module_exit(s3c24xx_uda134x_exit);
364
365MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
366MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
367MODULE_LICENSE("GPL");