]> pilppa.org Git - linux-2.6-omap-h63xx.git/blob - sound/soc/s3c24xx/s3c24xx-i2s.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/sparc-2.6
[linux-2.6-omap-h63xx.git] / sound / soc / s3c24xx / s3c24xx-i2s.c
1 /*
2  * s3c24xx-i2s.c  --  ALSA Soc Audio Layer
3  *
4  * (c) 2006 Wolfson Microelectronics PLC.
5  * Graeme Gregory graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com
6  *
7  * (c) 2004-2005 Simtec Electronics
8  *      http://armlinux.simtec.co.uk/
9  *      Ben Dooks <ben@simtec.co.uk>
10  *
11  *  This program is free software; you can redistribute  it and/or modify it
12  *  under  the terms of  the GNU General  Public License as published by the
13  *  Free Software Foundation;  either version 2 of the  License, or (at your
14  *  option) any later version.
15  *
16  *
17  *  Revision history
18  *    11th Dec 2006   Merged with Simtec driver
19  *    10th Nov 2006   Initial version.
20  */
21
22 #include <linux/init.h>
23 #include <linux/module.h>
24 #include <linux/device.h>
25 #include <linux/delay.h>
26 #include <linux/clk.h>
27 #include <linux/jiffies.h>
28 #include <linux/io.h>
29 #include <sound/core.h>
30 #include <sound/pcm.h>
31 #include <sound/pcm_params.h>
32 #include <sound/initval.h>
33 #include <sound/soc.h>
34
35 #include <asm/hardware.h>
36 #include <asm/arch/regs-gpio.h>
37 #include <asm/arch/regs-clock.h>
38 #include <asm/arch/audio.h>
39 #include <asm/dma.h>
40 #include <asm/arch/dma.h>
41
42 #include <asm/plat-s3c24xx/regs-iis.h>
43
44 #include "s3c24xx-pcm.h"
45 #include "s3c24xx-i2s.h"
46
47 #define S3C24XX_I2S_DEBUG 0
48 #if S3C24XX_I2S_DEBUG
49 #define DBG(x...) printk(KERN_DEBUG "s3c24xx-i2s: " x)
50 #else
51 #define DBG(x...)
52 #endif
53
54 static struct s3c2410_dma_client s3c24xx_dma_client_out = {
55         .name = "I2S PCM Stereo out"
56 };
57
58 static struct s3c2410_dma_client s3c24xx_dma_client_in = {
59         .name = "I2S PCM Stereo in"
60 };
61
62 static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = {
63         .client         = &s3c24xx_dma_client_out,
64         .channel        = DMACH_I2S_OUT,
65         .dma_addr       = S3C2410_PA_IIS + S3C2410_IISFIFO,
66         .dma_size       = 2,
67 };
68
69 static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_in = {
70         .client         = &s3c24xx_dma_client_in,
71         .channel        = DMACH_I2S_IN,
72         .dma_addr       = S3C2410_PA_IIS + S3C2410_IISFIFO,
73         .dma_size       = 2,
74 };
75
76 struct s3c24xx_i2s_info {
77         void __iomem    *regs;
78         struct clk      *iis_clk;
79         u32             iiscon;
80         u32             iismod;
81         u32             iisfcon;
82         u32             iispsr;
83 };
84 static struct s3c24xx_i2s_info s3c24xx_i2s;
85
86 static void s3c24xx_snd_txctrl(int on)
87 {
88         u32 iisfcon;
89         u32 iiscon;
90         u32 iismod;
91
92         DBG("Entered %s\n", __func__);
93
94         iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
95         iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
96         iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
97
98         DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
99
100         if (on) {
101                 iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
102                 iiscon  |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
103                 iiscon  &= ~S3C2410_IISCON_TXIDLE;
104                 iismod  |= S3C2410_IISMOD_TXMODE;
105
106                 writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
107                 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
108                 writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
109         } else {
110                 /* note, we have to disable the FIFOs otherwise bad things
111                  * seem to happen when the DMA stops. According to the
112                  * Samsung supplied kernel, this should allow the DMA
113                  * engine and FIFOs to reset. If this isn't allowed, the
114                  * DMA engine will simply freeze randomly.
115                  */
116
117                 iisfcon &= ~S3C2410_IISFCON_TXENABLE;
118                 iisfcon &= ~S3C2410_IISFCON_TXDMA;
119                 iiscon  |=  S3C2410_IISCON_TXIDLE;
120                 iiscon  &= ~S3C2410_IISCON_TXDMAEN;
121                 iismod  &= ~S3C2410_IISMOD_TXMODE;
122
123                 writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
124                 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
125                 writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
126         }
127
128         DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
129 }
130
131 static void s3c24xx_snd_rxctrl(int on)
132 {
133         u32 iisfcon;
134         u32 iiscon;
135         u32 iismod;
136
137         DBG("Entered %s\n", __func__);
138
139         iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
140         iiscon  = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
141         iismod  = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
142
143         DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
144
145         if (on) {
146                 iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
147                 iiscon  |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
148                 iiscon  &= ~S3C2410_IISCON_RXIDLE;
149                 iismod  |= S3C2410_IISMOD_RXMODE;
150
151                 writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
152                 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
153                 writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
154         } else {
155                 /* note, we have to disable the FIFOs otherwise bad things
156                  * seem to happen when the DMA stops. According to the
157                  * Samsung supplied kernel, this should allow the DMA
158                  * engine and FIFOs to reset. If this isn't allowed, the
159                  * DMA engine will simply freeze randomly.
160                  */
161
162                 iisfcon &= ~S3C2410_IISFCON_RXENABLE;
163                 iisfcon &= ~S3C2410_IISFCON_RXDMA;
164                 iiscon  |= S3C2410_IISCON_RXIDLE;
165                 iiscon  &= ~S3C2410_IISCON_RXDMAEN;
166                 iismod  &= ~S3C2410_IISMOD_RXMODE;
167
168                 writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
169                 writel(iiscon,  s3c24xx_i2s.regs + S3C2410_IISCON);
170                 writel(iismod,  s3c24xx_i2s.regs + S3C2410_IISMOD);
171         }
172
173         DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon);
174 }
175
176 /*
177  * Wait for the LR signal to allow synchronisation to the L/R clock
178  * from the codec. May only be needed for slave mode.
179  */
180 static int s3c24xx_snd_lrsync(void)
181 {
182         u32 iiscon;
183         unsigned long timeout = jiffies + msecs_to_jiffies(5);
184
185         DBG("Entered %s\n", __func__);
186
187         while (1) {
188                 iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
189                 if (iiscon & S3C2410_IISCON_LRINDEX)
190                         break;
191
192                 if (time_after(jiffies, timeout))
193                         return -ETIMEDOUT;
194         }
195
196         return 0;
197 }
198
199 /*
200  * Check whether CPU is the master or slave
201  */
202 static inline int s3c24xx_snd_is_clkmaster(void)
203 {
204         DBG("Entered %s\n", __func__);
205
206         return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
207 }
208
209 /*
210  * Set S3C24xx I2S DAI format
211  */
212 static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai,
213                 unsigned int fmt)
214 {
215         u32 iismod;
216
217         DBG("Entered %s\n", __func__);
218
219         iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
220         DBG("hw_params r: IISMOD: %lx \n", iismod);
221
222         switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
223         case SND_SOC_DAIFMT_CBM_CFM:
224                 iismod |= S3C2410_IISMOD_SLAVE;
225                 break;
226         case SND_SOC_DAIFMT_CBS_CFS:
227                 iismod &= ~S3C2410_IISMOD_SLAVE;
228                 break;
229         default:
230                 return -EINVAL;
231         }
232
233         switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
234         case SND_SOC_DAIFMT_LEFT_J:
235                 iismod |= S3C2410_IISMOD_MSB;
236                 break;
237         case SND_SOC_DAIFMT_I2S:
238                 iismod &= ~S3C2410_IISMOD_MSB;
239                 break;
240         default:
241                 return -EINVAL;
242         }
243
244         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
245         DBG("hw_params w: IISMOD: %lx \n", iismod);
246         return 0;
247 }
248
249 static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
250                                 struct snd_pcm_hw_params *params)
251 {
252         struct snd_soc_pcm_runtime *rtd = substream->private_data;
253         u32 iismod;
254
255         DBG("Entered %s\n", __func__);
256
257         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
258                 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out;
259         else
260                 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in;
261
262         /* Working copies of register */
263         iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
264         DBG("hw_params r: IISMOD: %lx\n", iismod);
265
266         switch (params_format(params)) {
267         case SNDRV_PCM_FORMAT_S8:
268                 break;
269         case SNDRV_PCM_FORMAT_S16_LE:
270                 iismod |= S3C2410_IISMOD_16BIT;
271                 break;
272         }
273
274         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
275         DBG("hw_params w: IISMOD: %lx\n", iismod);
276         return 0;
277 }
278
279 static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
280 {
281         int ret = 0;
282
283         DBG("Entered %s\n", __func__);
284
285         switch (cmd) {
286         case SNDRV_PCM_TRIGGER_START:
287         case SNDRV_PCM_TRIGGER_RESUME:
288         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
289                 if (!s3c24xx_snd_is_clkmaster()) {
290                         ret = s3c24xx_snd_lrsync();
291                         if (ret)
292                                 goto exit_err;
293                 }
294
295                 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
296                         s3c24xx_snd_rxctrl(1);
297                 else
298                         s3c24xx_snd_txctrl(1);
299                 break;
300         case SNDRV_PCM_TRIGGER_STOP:
301         case SNDRV_PCM_TRIGGER_SUSPEND:
302         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
303                 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
304                         s3c24xx_snd_rxctrl(0);
305                 else
306                         s3c24xx_snd_txctrl(0);
307                 break;
308         default:
309                 ret = -EINVAL;
310                 break;
311         }
312
313 exit_err:
314         return ret;
315 }
316
317 /*
318  * Set S3C24xx Clock source
319  */
320 static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
321         int clk_id, unsigned int freq, int dir)
322 {
323         u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
324
325         DBG("Entered %s\n", __func__);
326
327         iismod &= ~S3C2440_IISMOD_MPLL;
328
329         switch (clk_id) {
330         case S3C24XX_CLKSRC_PCLK:
331                 break;
332         case S3C24XX_CLKSRC_MPLL:
333                 iismod |= S3C2440_IISMOD_MPLL;
334                 break;
335         default:
336                 return -EINVAL;
337         }
338
339         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
340         return 0;
341 }
342
343 /*
344  * Set S3C24xx Clock dividers
345  */
346 static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
347         int div_id, int div)
348 {
349         u32 reg;
350
351         DBG("Entered %s\n", __func__);
352
353         switch (div_id) {
354         case S3C24XX_DIV_BCLK:
355                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
356                 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
357                 break;
358         case S3C24XX_DIV_MCLK:
359                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
360                 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
361                 break;
362         case S3C24XX_DIV_PRESCALER:
363                 writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
364                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
365                 writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
366                 break;
367         default:
368                 return -EINVAL;
369         }
370
371         return 0;
372 }
373
374 /*
375  * To avoid duplicating clock code, allow machine driver to
376  * get the clockrate from here.
377  */
378 u32 s3c24xx_i2s_get_clockrate(void)
379 {
380         return clk_get_rate(s3c24xx_i2s.iis_clk);
381 }
382 EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
383
384 static int s3c24xx_i2s_probe(struct platform_device *pdev)
385 {
386         DBG("Entered %s\n", __func__);
387
388         s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
389         if (s3c24xx_i2s.regs == NULL)
390                 return -ENXIO;
391
392         s3c24xx_i2s.iis_clk = clk_get(&pdev->dev, "iis");
393         if (s3c24xx_i2s.iis_clk == NULL) {
394                 DBG("failed to get iis_clock\n");
395                 iounmap(s3c24xx_i2s.regs);
396                 return -ENODEV;
397         }
398         clk_enable(s3c24xx_i2s.iis_clk);
399
400         /* Configure the I2S pins in correct mode */
401         s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
402         s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
403         s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
404         s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
405         s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
406
407         writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
408
409         s3c24xx_snd_txctrl(0);
410         s3c24xx_snd_rxctrl(0);
411
412         return 0;
413 }
414
415 #ifdef CONFIG_PM
416 static int s3c24xx_i2s_suspend(struct platform_device *pdev,
417                 struct snd_soc_cpu_dai *cpu_dai)
418 {
419         DBG("Entered %s\n", __func__);
420
421         s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
422         s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
423         s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
424         s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
425
426         clk_disable(s3c24xx_i2s.iis_clk);
427
428         return 0;
429 }
430
431 static int s3c24xx_i2s_resume(struct platform_device *pdev,
432                 struct snd_soc_cpu_dai *cpu_dai)
433 {
434         DBG("Entered %s\n", __func__);
435         clk_enable(s3c24xx_i2s.iis_clk);
436
437         writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
438         writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
439         writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
440         writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
441
442         return 0;
443 }
444 #else
445 #define s3c24xx_i2s_suspend NULL
446 #define s3c24xx_i2s_resume NULL
447 #endif
448
449
450 #define S3C24XX_I2S_RATES \
451         (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
452         SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
453         SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
454
455 struct snd_soc_cpu_dai s3c24xx_i2s_dai = {
456         .name = "s3c24xx-i2s",
457         .id = 0,
458         .type = SND_SOC_DAI_I2S,
459         .probe = s3c24xx_i2s_probe,
460         .suspend = s3c24xx_i2s_suspend,
461         .resume = s3c24xx_i2s_resume,
462         .playback = {
463                 .channels_min = 2,
464                 .channels_max = 2,
465                 .rates = S3C24XX_I2S_RATES,
466                 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
467         .capture = {
468                 .channels_min = 2,
469                 .channels_max = 2,
470                 .rates = S3C24XX_I2S_RATES,
471                 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
472         .ops = {
473                 .trigger = s3c24xx_i2s_trigger,
474                 .hw_params = s3c24xx_i2s_hw_params,},
475         .dai_ops = {
476                 .set_fmt = s3c24xx_i2s_set_fmt,
477                 .set_clkdiv = s3c24xx_i2s_set_clkdiv,
478                 .set_sysclk = s3c24xx_i2s_set_sysclk,
479         },
480 };
481 EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai);
482
483 /* Module information */
484 MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
485 MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
486 MODULE_LICENSE("GPL");