0a3c630951bee11833fd349195f6a90e0cc8951b
[pandora-kernel.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 <sound/core.h>
29 #include <sound/pcm.h>
30 #include <sound/pcm_params.h>
31 #include <sound/initval.h>
32 #include <sound/soc.h>
33
34 #include <asm/hardware.h>
35 #include <asm/io.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 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", __FUNCTION__);
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", __FUNCTION__);
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", __FUNCTION__);
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", __FUNCTION__);
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", __FUNCTION__);
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                 break;
228         default:
229                 return -EINVAL;
230         }
231
232         switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
233         case SND_SOC_DAIFMT_LEFT_J:
234                 iismod |= S3C2410_IISMOD_MSB;
235                 break;
236         case SND_SOC_DAIFMT_I2S:
237                 break;
238         default:
239                 return -EINVAL;
240         }
241
242         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
243         DBG("hw_params w: IISMOD: %lx \n", iismod);
244         return 0;
245 }
246
247 static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
248                                 struct snd_pcm_hw_params *params)
249 {
250         struct snd_soc_pcm_runtime *rtd = substream->private_data;
251         u32 iismod;
252
253         DBG("Entered %s\n", __FUNCTION__);
254
255         if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
256                 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out;
257         else
258                 rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in;
259
260         /* Working copies of register */
261         iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
262         DBG("hw_params r: IISMOD: %lx\n", iismod);
263
264         switch (params_format(params)) {
265         case SNDRV_PCM_FORMAT_S8:
266                 break;
267         case SNDRV_PCM_FORMAT_S16_LE:
268                 iismod |= S3C2410_IISMOD_16BIT;
269                 break;
270         }
271
272         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
273         DBG("hw_params w: IISMOD: %lx\n", iismod);
274         return 0;
275 }
276
277 static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
278 {
279         int ret = 0;
280
281         DBG("Entered %s\n", __FUNCTION__);
282
283         switch (cmd) {
284         case SNDRV_PCM_TRIGGER_START:
285         case SNDRV_PCM_TRIGGER_RESUME:
286         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
287                 if (!s3c24xx_snd_is_clkmaster()) {
288                         ret = s3c24xx_snd_lrsync();
289                         if (ret)
290                                 goto exit_err;
291                 }
292
293                 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
294                         s3c24xx_snd_rxctrl(1);
295                 else
296                         s3c24xx_snd_txctrl(1);
297                 break;
298         case SNDRV_PCM_TRIGGER_STOP:
299         case SNDRV_PCM_TRIGGER_SUSPEND:
300         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
301                 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
302                         s3c24xx_snd_rxctrl(0);
303                 else
304                         s3c24xx_snd_txctrl(0);
305                 break;
306         default:
307                 ret = -EINVAL;
308                 break;
309         }
310
311 exit_err:
312         return ret;
313 }
314
315 /*
316  * Set S3C24xx Clock source
317  */
318 static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai,
319         int clk_id, unsigned int freq, int dir)
320 {
321         u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
322
323         DBG("Entered %s\n", __FUNCTION__);
324
325         iismod &= ~S3C2440_IISMOD_MPLL;
326
327         switch (clk_id) {
328         case S3C24XX_CLKSRC_PCLK:
329                 break;
330         case S3C24XX_CLKSRC_MPLL:
331                 iismod |= S3C2440_IISMOD_MPLL;
332                 break;
333         default:
334                 return -EINVAL;
335         }
336
337         writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
338         return 0;
339 }
340
341 /*
342  * Set S3C24xx Clock dividers
343  */
344 static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai,
345         int div_id, int div)
346 {
347         u32 reg;
348
349         DBG("Entered %s\n", __FUNCTION__);
350
351         switch (div_id) {
352         case S3C24XX_DIV_BCLK:
353                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
354                 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
355                 break;
356         case S3C24XX_DIV_MCLK:
357                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
358                 writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
359                 break;
360         case S3C24XX_DIV_PRESCALER:
361                 writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
362                 reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
363                 writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
364                 break;
365         default:
366                 return -EINVAL;
367         }
368
369         return 0;
370 }
371
372 /*
373  * To avoid duplicating clock code, allow machine driver to
374  * get the clockrate from here.
375  */
376 u32 s3c24xx_i2s_get_clockrate(void)
377 {
378         return clk_get_rate(s3c24xx_i2s.iis_clk);
379 }
380 EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate);
381
382 static int s3c24xx_i2s_probe(struct platform_device *pdev)
383 {
384         DBG("Entered %s\n", __FUNCTION__);
385
386         s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
387         if (s3c24xx_i2s.regs == NULL)
388                 return -ENXIO;
389
390         s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis");
391         if (s3c24xx_i2s.iis_clk == NULL) {
392                 DBG("failed to get iis_clock\n");
393                 iounmap(s3c24xx_i2s.regs);
394                 return -ENODEV;
395         }
396         clk_enable(s3c24xx_i2s.iis_clk);
397
398         /* Configure the I2S pins in correct mode */
399         s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
400         s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
401         s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
402         s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
403         s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
404
405         writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
406
407         s3c24xx_snd_txctrl(0);
408         s3c24xx_snd_rxctrl(0);
409
410         return 0;
411 }
412
413 #ifdef CONFIG_PM
414 int s3c24xx_i2s_suspend(struct platform_device *pdev,
415                 struct snd_soc_cpu_dai *cpu_dai)
416 {
417         s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
418         s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
419         s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
420         s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
421
422         clk_disable(s3c24xx_i2s.iis_clk);
423
424         return 0;
425 }
426
427 int s3c24xx_i2s_resume(struct platform_device *pdev,
428                 struct snd_soc_cpu_dai *cpu_dai)
429 {
430         clk_enable(s3c24xx_i2s.iis_clk);
431
432         writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
433         writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
434         writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
435         writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
436
437         return 0;
438 }
439 #else
440 #define s3c24xx_i2s_suspend NULL
441 #define s3c24xx_i2s_resume NULL
442 #endif
443
444
445 #define S3C24XX_I2S_RATES \
446         (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
447         SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
448         SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
449
450 struct snd_soc_cpu_dai s3c24xx_i2s_dai = {
451         .name = "s3c24xx-i2s",
452         .id = 0,
453         .type = SND_SOC_DAI_I2S,
454         .probe = s3c24xx_i2s_probe,
455         .suspend = s3c24xx_i2s_suspend,
456         .resume = s3c24xx_i2s_resume,
457         .playback = {
458                 .channels_min = 2,
459                 .channels_max = 2,
460                 .rates = S3C24XX_I2S_RATES,
461                 .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
462         .capture = {
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         .ops = {
468                 .trigger = s3c24xx_i2s_trigger,
469                 .hw_params = s3c24xx_i2s_hw_params,},
470         .dai_ops = {
471                 .set_fmt = s3c24xx_i2s_set_fmt,
472                 .set_clkdiv = s3c24xx_i2s_set_clkdiv,
473                 .set_sysclk = s3c24xx_i2s_set_sysclk,
474         },
475 };
476 EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai);
477
478 /* Module information */
479 MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
480 MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
481 MODULE_LICENSE("GPL");