Linux kernel & device driver programming

Cross-Referenced Linux and Device Driver Code

[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ]
Version: [ 2.6.11.8 ] [ 2.6.25 ] [ 2.6.25.8 ] [ 2.6.31.13 ] Architecture: [ i386 ]
  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");
482 
  This page was automatically generated by the LXR engine.