aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
authorBen Dooks <ben-linux@fluff.org>2008-01-10 08:47:21 -0500
committerJaroslav Kysela <perex@perex.cz>2008-01-31 11:29:53 -0500
commit49646dfa2ae81e770da1c12c4fce227062ce4612 (patch)
treeaf0e1464fdf5e8beda908b1c604bed2f72f95944 /sound
parent5cd919a2144f1beee7fb2e18ef6b8bee7105f554 (diff)
[ALSA] ASoC: S3C2412 IIS driver
S3C2412 SoC IIS support for ALSA/ASoC Signed-off-by: Ben Dooks <ben-linux@fluff.org> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/s3c24xx/Kconfig3
-rw-r--r--sound/soc/s3c24xx/Makefile2
-rw-r--r--sound/soc/s3c24xx/s3c2412-i2s.c681
-rw-r--r--sound/soc/s3c24xx/s3c2412-i2s.h38
4 files changed, 724 insertions, 0 deletions
diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig
index 14def2e5ada5..1f6dbfc4caa8 100644
--- a/sound/soc/s3c24xx/Kconfig
+++ b/sound/soc/s3c24xx/Kconfig
@@ -10,6 +10,9 @@ config SND_S3C24XX_SOC
10config SND_S3C24XX_SOC_I2S 10config SND_S3C24XX_SOC_I2S
11 tristate 11 tristate
12 12
13config SND_S3C2412_SOC_I2S
14 tristate
15
13config SND_S3C2443_SOC_AC97 16config SND_S3C2443_SOC_AC97
14 tristate 17 tristate
15 select AC97_BUS 18 select AC97_BUS
diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile
index 947191961673..0aa5fb0b9700 100644
--- a/sound/soc/s3c24xx/Makefile
+++ b/sound/soc/s3c24xx/Makefile
@@ -1,11 +1,13 @@
1# S3c24XX Platform Support 1# S3c24XX Platform Support
2snd-soc-s3c24xx-objs := s3c24xx-pcm.o 2snd-soc-s3c24xx-objs := s3c24xx-pcm.o
3snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o 3snd-soc-s3c24xx-i2s-objs := s3c24xx-i2s.o
4snd-soc-s3c2412-i2s-objs := s3c2412-i2s.o
4snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o 5snd-soc-s3c2443-ac97-objs := s3c2443-ac97.o
5 6
6obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o 7obj-$(CONFIG_SND_S3C24XX_SOC) += snd-soc-s3c24xx.o
7obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o 8obj-$(CONFIG_SND_S3C24XX_SOC_I2S) += snd-soc-s3c24xx-i2s.o
8obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o 9obj-$(CONFIG_SND_S3C2443_SOC_AC97) += snd-soc-s3c2443-ac97.o
10obj-$(CONFIG_SND_S3C2412_SOC_I2S) += snd-soc-s3c2412-i2s.o
9 11
10# S3C24XX Machine Support 12# S3C24XX Machine Support
11snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o 13snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o
diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c
new file mode 100644
index 000000000000..b10f7ffa9424
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c2412-i2s.c
@@ -0,0 +1,681 @@
1/* sound/soc/s3c24xx/s3c2412-i2s.c
2 *
3 * ALSA Soc Audio Layer - S3C2412 I2S driver
4 *
5 * Copyright (c) 2006 Wolfson Microelectronics PLC.
6 * Graeme Gregory graeme.gregory@wolfsonmicro.com
7 * linux@wolfsonmicro.com
8 *
9 * Copyright (c) 2007, 2004-2005 Simtec Electronics
10 * http://armlinux.simtec.co.uk/
11 * Ben Dooks <ben@simtec.co.uk>
12 *
13 * This program is free software; you can redistribute it and/or modify it
14 * under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version.
17 */
18
19#include <linux/init.h>
20#include <linux/module.h>
21#include <linux/device.h>
22#include <linux/delay.h>
23#include <linux/clk.h>
24#include <linux/kernel.h>
25
26#include <sound/core.h>
27#include <sound/pcm.h>
28#include <sound/pcm_params.h>
29#include <sound/initval.h>
30#include <sound/soc.h>
31#include <asm/hardware.h>
32
33#include <linux/io.h>
34#include <asm/dma.h>
35
36#include <asm/plat-s3c24xx/regs-s3c2412-iis.h>
37
38#include <asm/arch/regs-gpio.h>
39#include <asm/arch/audio.h>
40#include <asm/arch/dma.h>
41
42#include "s3c24xx-pcm.h"
43#include "s3c2412-i2s.h"
44
45#define S3C2412_I2S_DEBUG 0
46#define S3C2412_I2S_DEBUG_CON 0
47
48#if S3C2412_I2S_DEBUG
49#define DBG(x...) printk(KERN_INFO x)
50#else
51#define DBG(x...) do { } while (0)
52#endif
53
54static struct s3c2410_dma_client s3c2412_dma_client_out = {
55 .name = "I2S PCM Stereo out"
56};
57
58static struct s3c2410_dma_client s3c2412_dma_client_in = {
59 .name = "I2S PCM Stereo in"
60};
61
62static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_out = {
63 .client = &s3c2412_dma_client_out,
64 .channel = DMACH_I2S_OUT,
65 .dma_addr = S3C2410_PA_IIS + S3C2412_IISTXD,
66 .dma_size = 4,
67};
68
69static struct s3c24xx_pcm_dma_params s3c2412_i2s_pcm_stereo_in = {
70 .client = &s3c2412_dma_client_in,
71 .channel = DMACH_I2S_IN,
72 .dma_addr = S3C2410_PA_IIS + S3C2412_IISRXD,
73 .dma_size = 4,
74};
75
76struct s3c2412_i2s_info {
77 struct device *dev;
78 void __iomem *regs;
79 struct clk *iis_clk;
80 struct clk *iis_pclk;
81 struct clk *iis_cclk;
82};
83
84static struct s3c2412_i2s_info s3c2412_i2s;
85
86#define bit_set(v, b) (((v) & (b)) ? 1 : 0)
87
88#if S3C2412_I2S_DEBUG_CON
89static void dbg_showcon(const char *fn, u32 con)
90{
91 printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn,
92 bit_set(con, S3C2412_IISCON_LRINDEX),
93 bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY),
94 bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY),
95 bit_set(con, S3C2412_IISCON_TXFIFO_FULL),
96 bit_set(con, S3C2412_IISCON_RXFIFO_FULL));
97
98 printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n",
99 fn,
100 bit_set(con, S3C2412_IISCON_TXDMA_PAUSE),
101 bit_set(con, S3C2412_IISCON_RXDMA_PAUSE),
102 bit_set(con, S3C2412_IISCON_TXCH_PAUSE),
103 bit_set(con, S3C2412_IISCON_RXCH_PAUSE));
104 printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn,
105 bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE),
106 bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE),
107 bit_set(con, S3C2412_IISCON_IIS_ACTIVE));
108}
109#else
110static inline void dbg_showcon(const char *fn, u32 con)
111{
112}
113#endif
114
115/* Turn on or off the transmission path. */
116static void s3c2412_snd_txctrl(int on)
117{
118 struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
119 void __iomem *regs = i2s->regs;
120 u32 fic, con, mod;
121
122 DBG("%s(%d)\n", __func__, on);
123
124 fic = readl(regs + S3C2412_IISFIC);
125 con = readl(regs + S3C2412_IISCON);
126 mod = readl(regs + S3C2412_IISMOD);
127
128 DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
129
130 if (on) {
131 con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
132 con &= ~S3C2412_IISCON_TXDMA_PAUSE;
133 con &= ~S3C2412_IISCON_TXCH_PAUSE;
134
135 switch (mod & S3C2412_IISMOD_MODE_MASK) {
136 case S3C2412_IISMOD_MODE_TXONLY:
137 case S3C2412_IISMOD_MODE_TXRX:
138 /* do nothing, we are in the right mode */
139 break;
140
141 case S3C2412_IISMOD_MODE_RXONLY:
142 mod &= ~S3C2412_IISMOD_MODE_MASK;
143 mod |= S3C2412_IISMOD_MODE_TXRX;
144 break;
145
146 default:
147 dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n");
148 }
149
150 writel(con, regs + S3C2412_IISCON);
151 writel(mod, regs + S3C2412_IISMOD);
152 } else {
153 /* Note, we do not have any indication that the FIFO problems
154 * tha the S3C2410/2440 had apply here, so we should be able
155 * to disable the DMA and TX without resetting the FIFOS.
156 */
157
158 con |= S3C2412_IISCON_TXDMA_PAUSE;
159 con |= S3C2412_IISCON_TXCH_PAUSE;
160 con &= ~S3C2412_IISCON_TXDMA_ACTIVE;
161
162 switch (mod & S3C2412_IISMOD_MODE_MASK) {
163 case S3C2412_IISMOD_MODE_TXRX:
164 mod &= ~S3C2412_IISMOD_MODE_MASK;
165 mod |= S3C2412_IISMOD_MODE_RXONLY;
166 break;
167
168 case S3C2412_IISMOD_MODE_TXONLY:
169 mod &= ~S3C2412_IISMOD_MODE_MASK;
170 con &= ~S3C2412_IISCON_IIS_ACTIVE;
171 break;
172
173 default:
174 dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n");
175 }
176
177 writel(mod, regs + S3C2412_IISMOD);
178 writel(con, regs + S3C2412_IISCON);
179 }
180
181 fic = readl(regs + S3C2412_IISFIC);
182 dbg_showcon(__func__, con);
183 DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
184}
185
186static void s3c2412_snd_rxctrl(int on)
187{
188 struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
189 void __iomem *regs = i2s->regs;
190 u32 fic, con, mod;
191
192 DBG("%s(%d)\n", __func__, on);
193
194 fic = readl(regs + S3C2412_IISFIC);
195 con = readl(regs + S3C2412_IISCON);
196 mod = readl(regs + S3C2412_IISMOD);
197
198 DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
199
200 if (on) {
201 con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE;
202 con &= ~S3C2412_IISCON_RXDMA_PAUSE;
203 con &= ~S3C2412_IISCON_RXCH_PAUSE;
204
205 switch (mod & S3C2412_IISMOD_MODE_MASK) {
206 case S3C2412_IISMOD_MODE_TXRX:
207 case S3C2412_IISMOD_MODE_RXONLY:
208 /* do nothing, we are in the right mode */
209 break;
210
211 case S3C2412_IISMOD_MODE_TXONLY:
212 mod &= ~S3C2412_IISMOD_MODE_MASK;
213 mod |= S3C2412_IISMOD_MODE_TXRX;
214 break;
215
216 default:
217 dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
218 }
219
220 writel(mod, regs + S3C2412_IISMOD);
221 writel(con, regs + S3C2412_IISCON);
222 } else {
223 /* See txctrl notes on FIFOs. */
224
225 con &= ~S3C2412_IISCON_RXDMA_ACTIVE;
226 con |= S3C2412_IISCON_RXDMA_PAUSE;
227 con |= S3C2412_IISCON_RXCH_PAUSE;
228
229 switch (mod & S3C2412_IISMOD_MODE_MASK) {
230 case S3C2412_IISMOD_MODE_RXONLY:
231 con &= ~S3C2412_IISCON_IIS_ACTIVE;
232 mod &= ~S3C2412_IISMOD_MODE_MASK;
233 break;
234
235 case S3C2412_IISMOD_MODE_TXRX:
236 mod &= ~S3C2412_IISMOD_MODE_MASK;
237 mod |= S3C2412_IISMOD_MODE_TXONLY;
238 break;
239
240 default:
241 dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n");
242 }
243
244 writel(con, regs + S3C2412_IISCON);
245 writel(mod, regs + S3C2412_IISMOD);
246 }
247
248 fic = readl(regs + S3C2412_IISFIC);
249 DBG("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic);
250}
251
252
253/*
254 * Wait for the LR signal to allow synchronisation to the L/R clock
255 * from the codec. May only be needed for slave mode.
256 */
257static int s3c2412_snd_lrsync(void)
258{
259 u32 iiscon;
260 unsigned long timeout = jiffies + msecs_to_jiffies(5);
261
262 DBG("Entered %s\n", __func__);
263
264 while (1) {
265 iiscon = readl(s3c2412_i2s.regs + S3C2412_IISCON);
266 if (iiscon & S3C2412_IISCON_LRINDEX)
267 break;
268
269 if (timeout < jiffies) {
270 printk(KERN_ERR "%s: timeout\n", __func__);
271 return -ETIMEDOUT;
272 }
273 }
274
275 return 0;
276}
277
278/*
279 * Check whether CPU is the master or slave
280 */
281static inline int s3c2412_snd_is_clkmaster(void)
282{
283 u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
284
285 DBG("Entered %s\n", __func__);
286
287 iismod &= S3C2412_IISMOD_MASTER_MASK;
288 return !(iismod == S3C2412_IISMOD_SLAVE);
289}
290
291/*
292 * Set S3C2412 I2S DAI format
293 */
294static int s3c2412_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai,
295 unsigned int fmt)
296{
297 u32 iismod;
298
299
300 DBG("Entered %s\n", __func__);
301
302 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
303 DBG("hw_params r: IISMOD: %x \n", iismod);
304
305 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
306 case SND_SOC_DAIFMT_CBM_CFM:
307 iismod &= ~S3C2412_IISMOD_MASTER_MASK;
308 iismod |= S3C2412_IISMOD_SLAVE;
309 break;
310 case SND_SOC_DAIFMT_CBS_CFS:
311 iismod &= ~S3C2412_IISMOD_MASTER_MASK;
312 iismod |= S3C2412_IISMOD_MASTER_INTERNAL;
313 break;
314 default:
315 DBG("unknwon master/slave format\n");
316 return -EINVAL;
317 }
318
319 iismod &= ~S3C2412_IISMOD_SDF_MASK;
320
321 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
322 case SND_SOC_DAIFMT_RIGHT_J:
323 iismod |= S3C2412_IISMOD_SDF_MSB;
324 break;
325 case SND_SOC_DAIFMT_LEFT_J:
326 iismod |= S3C2412_IISMOD_SDF_LSB;
327 break;
328 case SND_SOC_DAIFMT_I2S:
329 iismod |= S3C2412_IISMOD_SDF_IIS;
330 break;
331 default:
332 DBG("Unknown data format\n");
333 return -EINVAL;
334 }
335
336 writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
337 DBG("hw_params w: IISMOD: %x \n", iismod);
338 return 0;
339}
340
341static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream,
342 struct snd_pcm_hw_params *params)
343{
344 struct snd_soc_pcm_runtime *rtd = substream->private_data;
345 u32 iismod;
346
347 DBG("Entered %s\n", __func__);
348
349 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
350 rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_out;
351 else
352 rtd->dai->cpu_dai->dma_data = &s3c2412_i2s_pcm_stereo_in;
353
354 /* Working copies of register */
355 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
356 DBG("%s: r: IISMOD: %x\n", __func__, iismod);
357
358 switch (params_format(params)) {
359 case SNDRV_PCM_FORMAT_S8:
360 iismod |= S3C2412_IISMOD_8BIT;
361 break;
362 case SNDRV_PCM_FORMAT_S16_LE:
363 iismod &= ~S3C2412_IISMOD_8BIT;
364 break;
365 }
366
367 writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
368 DBG("%s: w: IISMOD: %x\n", __func__, iismod);
369 return 0;
370}
371
372static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
373{
374 int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE);
375 unsigned long irqs;
376 int ret = 0;
377
378 DBG("Entered %s\n", __func__);
379
380 switch (cmd) {
381 case SNDRV_PCM_TRIGGER_START:
382 /* On start, ensure that the FIFOs are cleared and reset. */
383
384 writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH,
385 s3c2412_i2s.regs + S3C2412_IISFIC);
386
387 /* clear again, just in case */
388 writel(0x0, s3c2412_i2s.regs + S3C2412_IISFIC);
389
390 case SNDRV_PCM_TRIGGER_RESUME:
391 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
392 if (!s3c2412_snd_is_clkmaster()) {
393 ret = s3c2412_snd_lrsync();
394 if (ret)
395 goto exit_err;
396 }
397
398 local_irq_save(irqs);
399
400 if (capture)
401 s3c2412_snd_rxctrl(1);
402 else
403 s3c2412_snd_txctrl(1);
404
405 local_irq_restore(irqs);
406 break;
407
408 case SNDRV_PCM_TRIGGER_STOP:
409 case SNDRV_PCM_TRIGGER_SUSPEND:
410 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
411 local_irq_save(irqs);
412
413 if (capture)
414 s3c2412_snd_rxctrl(0);
415 else
416 s3c2412_snd_txctrl(0);
417
418 local_irq_restore(irqs);
419 break;
420 default:
421 ret = -EINVAL;
422 break;
423 }
424
425exit_err:
426 return ret;
427}
428
429/* default table of all avaialable root fs divisors */
430static unsigned int s3c2412_iis_fs[] = { 256, 512, 384, 768, 0 };
431
432int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info,
433 unsigned int *fstab,
434 unsigned int rate, struct clk *clk)
435{
436 unsigned long clkrate = clk_get_rate(clk);
437 unsigned int div;
438 unsigned int fsclk;
439 unsigned int actual;
440 unsigned int fs;
441 unsigned int fsdiv;
442 signed int deviation = 0;
443 unsigned int best_fs = 0;
444 unsigned int best_div = 0;
445 unsigned int best_rate = 0;
446 unsigned int best_deviation = INT_MAX;
447
448
449 if (fstab == NULL)
450 fstab = s3c2412_iis_fs;
451
452 for (fs = 0;; fs++) {
453 fsdiv = s3c2412_iis_fs[fs];
454
455 if (fsdiv == 0)
456 break;
457
458 fsclk = clkrate / fsdiv;
459 div = fsclk / rate;
460
461 if ((fsclk % rate) > (rate / 2))
462 div++;
463
464 if (div <= 1)
465 continue;
466
467 actual = clkrate / (fsdiv * div);
468 deviation = actual - rate;
469
470 printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n",
471 fsdiv, div, actual, deviation);
472
473 deviation = abs(deviation);
474
475 if (deviation < best_deviation) {
476 best_fs = fsdiv;
477 best_div = div;
478 best_rate = actual;
479 best_deviation = deviation;
480 }
481
482 if (deviation == 0)
483 break;
484 }
485
486 printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n",
487 best_fs, best_div, best_rate);
488
489 info->fs_div = best_fs;
490 info->clk_div = best_div;
491
492 return 0;
493}
494EXPORT_SYMBOL_GPL(s3c2412_iis_calc_rate);
495
496/*
497 * Set S3C2412 Clock source
498 */
499static int s3c2412_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
500 int clk_id, unsigned int freq, int dir)
501{
502 u32 iismod = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
503
504 DBG("%s(%p, %d, %u, %d)\n", __func__, cpu_dai, clk_id,
505 freq, dir);
506
507 switch (clk_id) {
508 case S3C2412_CLKSRC_PCLK:
509 iismod &= ~S3C2412_IISMOD_MASTER_MASK;
510 iismod |= S3C2412_IISMOD_MASTER_INTERNAL;
511 break;
512 case S3C2412_CLKSRC_I2SCLK:
513 iismod &= ~S3C2412_IISMOD_MASTER_MASK;
514 iismod |= S3C2412_IISMOD_MASTER_EXTERNAL;
515 break;
516 default:
517 return -EINVAL;
518 }
519
520 writel(iismod, s3c2412_i2s.regs + S3C2412_IISMOD);
521 return 0;
522}
523
524/*
525 * Set S3C2412 Clock dividers
526 */
527static int s3c2412_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
528 int div_id, int div)
529{
530 struct s3c2412_i2s_info *i2s = &s3c2412_i2s;
531 u32 reg;
532
533 DBG("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div);
534
535 switch (div_id) {
536 case S3C2412_DIV_BCLK:
537 reg = readl(i2s->regs + S3C2412_IISMOD);
538 reg &= ~S3C2412_IISMOD_BCLK_MASK;
539 writel(reg | div, i2s->regs + S3C2412_IISMOD);
540
541 DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
542 break;
543
544 case S3C2412_DIV_RCLK:
545 if (div > 3) {
546 /* convert value to bit field */
547
548 switch (div) {
549 case 256:
550 div = S3C2412_IISMOD_RCLK_256FS;
551 break;
552
553 case 384:
554 div = S3C2412_IISMOD_RCLK_384FS;
555 break;
556
557 case 512:
558 div = S3C2412_IISMOD_RCLK_512FS;
559 break;
560
561 case 768:
562 div = S3C2412_IISMOD_RCLK_768FS;
563 break;
564
565 default:
566 return -EINVAL;
567 }
568 }
569
570 reg = readl(s3c2412_i2s.regs + S3C2412_IISMOD);
571 reg &= ~S3C2412_IISMOD_RCLK_MASK;
572 writel(reg | div, i2s->regs + S3C2412_IISMOD);
573 DBG("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD));
574 break;
575
576 case S3C2412_DIV_PRESCALER:
577 if (div >= 0) {
578 writel((div << 8) | S3C2412_IISPSR_PSREN,
579 i2s->regs + S3C2412_IISPSR);
580 } else {
581 writel(0x0, i2s->regs + S3C2412_IISPSR);
582 }
583 DBG("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR));
584 break;
585
586 default:
587 return -EINVAL;
588 }
589
590 return 0;
591}
592
593struct clk *s3c2412_get_iisclk(void)
594{
595 return s3c2412_i2s.iis_clk;
596}
597EXPORT_SYMBOL_GPL(s3c2412_get_iisclk);
598
599
600static int s3c2412_i2s_probe(struct platform_device *pdev)
601{
602 DBG("Entered %s\n", __func__);
603
604 s3c2412_i2s.dev = &pdev->dev;
605
606 s3c2412_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
607 if (s3c2412_i2s.regs == NULL)
608 return -ENXIO;
609
610 s3c2412_i2s.iis_pclk = clk_get(&pdev->dev, "iis");
611 if (s3c2412_i2s.iis_pclk == NULL) {
612 DBG("failed to get iis_clock\n");
613 iounmap(s3c2412_i2s.regs);
614 return -ENODEV;
615 }
616
617 s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk");
618 if (s3c2412_i2s.iis_cclk == NULL) {
619 DBG("failed to get i2sclk clock\n");
620 iounmap(s3c2412_i2s.regs);
621 return -ENODEV;
622 }
623
624 clk_set_parent(s3c2412_i2s.iis_cclk, clk_get(NULL, "mpll"));
625
626 clk_enable(s3c2412_i2s.iis_pclk);
627 clk_enable(s3c2412_i2s.iis_cclk);
628
629 s3c2412_i2s.iis_clk = s3c2412_i2s.iis_pclk;
630
631 /* Configure the I2S pins in correct mode */
632 s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
633 s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
634 s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
635 s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
636 s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
637
638 s3c2412_snd_txctrl(0);
639 s3c2412_snd_rxctrl(0);
640
641 return 0;
642}
643
644#define S3C2412_I2S_RATES \
645 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
646 SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
647 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
648
649struct snd_soc_cpu_dai s3c2412_i2s_dai = {
650 .name = "s3c2412-i2s",
651 .id = 0,
652 .type = SND_SOC_DAI_I2S,
653 .probe = s3c2412_i2s_probe,
654 .playback = {
655 .channels_min = 2,
656 .channels_max = 2,
657 .rates = S3C2412_I2S_RATES,
658 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
659 },
660 .capture = {
661 .channels_min = 2,
662 .channels_max = 2,
663 .rates = S3C2412_I2S_RATES,
664 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,
665 },
666 .ops = {
667 .trigger = s3c2412_i2s_trigger,
668 .hw_params = s3c2412_i2s_hw_params,
669 },
670 .dai_ops = {
671 .set_fmt = s3c2412_i2s_set_fmt,
672 .set_clkdiv = s3c2412_i2s_set_clkdiv,
673 .set_sysclk = s3c2412_i2s_set_sysclk,
674 },
675};
676EXPORT_SYMBOL_GPL(s3c2412_i2s_dai);
677
678/* Module information */
679MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
680MODULE_DESCRIPTION("S3C2412 I2S SoC Interface");
681MODULE_LICENSE("GPL");
diff --git a/sound/soc/s3c24xx/s3c2412-i2s.h b/sound/soc/s3c24xx/s3c2412-i2s.h
new file mode 100644
index 000000000000..27f48e1ffa86
--- /dev/null
+++ b/sound/soc/s3c24xx/s3c2412-i2s.h
@@ -0,0 +1,38 @@
1/* sound/soc/s3c24xx/s3c2412-i2s.c
2 *
3 * ALSA Soc Audio Layer - S3C2412 I2S driver
4 *
5 * Copyright (c) 2007 Simtec Electronics
6 * http://armlinux.simtec.co.uk/
7 * Ben Dooks <ben@simtec.co.uk>
8 *
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; either version 2 of the License, or (at your
12 * option) any later version.
13*/
14
15#ifndef __SND_SOC_S3C24XX_S3C2412_I2S_H
16#define __SND_SOC_S3C24XX_S3C2412_I2S_H __FILE__
17
18#define S3C2412_DIV_BCLK (1)
19#define S3C2412_DIV_RCLK (2)
20#define S3C2412_DIV_PRESCALER (3)
21
22#define S3C2412_CLKSRC_PCLK (0)
23#define S3C2412_CLKSRC_I2SCLK (1)
24
25extern struct clk *s3c2412_get_iisclk(void);
26
27extern struct snd_soc_cpu_dai s3c2412_i2s_dai;
28
29struct s3c2412_rate_calc {
30 unsigned int clk_div; /* for prescaler */
31 unsigned int fs_div; /* for root frame clock */
32};
33
34extern int s3c2412_iis_calc_rate(struct s3c2412_rate_calc *info,
35 unsigned int *fstab,
36 unsigned int rate, struct clk *clk);
37
38#endif /* __SND_SOC_S3C24XX_S3C2412_I2S_H */