Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
s3c24xx-i2s.c
Go to the documentation of this file.
1 /*
2  * s3c24xx-i2s.c -- ALSA Soc Audio Layer
3  *
4  * (c) 2006 Wolfson Microelectronics PLC.
5  * Graeme Gregory [email protected] or [email protected]
6  *
7  * Copyright 2004-2005 Simtec Electronics
8  * http://armlinux.simtec.co.uk/
9  * Ben Dooks <[email protected]>
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 #include <linux/delay.h>
18 #include <linux/clk.h>
19 #include <linux/io.h>
20 #include <linux/gpio.h>
21 #include <linux/module.h>
22 
23 #include <sound/soc.h>
24 #include <sound/pcm_params.h>
25 
26 #include <mach/dma.h>
27 #include <plat/regs-iis.h>
28 
29 #include "dma.h"
30 #include "s3c24xx-i2s.h"
31 
32 static struct s3c2410_dma_client s3c24xx_dma_client_out = {
33  .name = "I2S PCM Stereo out"
34 };
35 
36 static struct s3c2410_dma_client s3c24xx_dma_client_in = {
37  .name = "I2S PCM Stereo in"
38 };
39 
40 static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_out = {
41  .client = &s3c24xx_dma_client_out,
42  .channel = DMACH_I2S_OUT,
43  .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
44  .dma_size = 2,
45 };
46 
47 static struct s3c_dma_params s3c24xx_i2s_pcm_stereo_in = {
48  .client = &s3c24xx_dma_client_in,
49  .channel = DMACH_I2S_IN,
50  .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
51  .dma_size = 2,
52 };
53 
55  void __iomem *regs;
56  struct clk *iis_clk;
61 };
62 static struct s3c24xx_i2s_info s3c24xx_i2s;
63 
64 static void s3c24xx_snd_txctrl(int on)
65 {
66  u32 iisfcon;
67  u32 iiscon;
68  u32 iismod;
69 
70  pr_debug("Entered %s\n", __func__);
71 
72  iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
73  iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
74  iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
75 
76  pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
77 
78  if (on) {
81  iiscon &= ~S3C2410_IISCON_TXIDLE;
82  iismod |= S3C2410_IISMOD_TXMODE;
83 
84  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
85  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
86  writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
87  } else {
88  /* note, we have to disable the FIFOs otherwise bad things
89  * seem to happen when the DMA stops. According to the
90  * Samsung supplied kernel, this should allow the DMA
91  * engine and FIFOs to reset. If this isn't allowed, the
92  * DMA engine will simply freeze randomly.
93  */
94 
95  iisfcon &= ~S3C2410_IISFCON_TXENABLE;
96  iisfcon &= ~S3C2410_IISFCON_TXDMA;
97  iiscon |= S3C2410_IISCON_TXIDLE;
98  iiscon &= ~S3C2410_IISCON_TXDMAEN;
99  iismod &= ~S3C2410_IISMOD_TXMODE;
100 
101  writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
102  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
103  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
104  }
105 
106  pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
107 }
108 
109 static void s3c24xx_snd_rxctrl(int on)
110 {
111  u32 iisfcon;
112  u32 iiscon;
113  u32 iismod;
114 
115  pr_debug("Entered %s\n", __func__);
116 
117  iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
118  iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
119  iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
120 
121  pr_debug("r: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
122 
123  if (on) {
126  iiscon &= ~S3C2410_IISCON_RXIDLE;
127  iismod |= S3C2410_IISMOD_RXMODE;
128 
129  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
130  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
131  writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
132  } else {
133  /* note, we have to disable the FIFOs otherwise bad things
134  * seem to happen when the DMA stops. According to the
135  * Samsung supplied kernel, this should allow the DMA
136  * engine and FIFOs to reset. If this isn't allowed, the
137  * DMA engine will simply freeze randomly.
138  */
139 
140  iisfcon &= ~S3C2410_IISFCON_RXENABLE;
141  iisfcon &= ~S3C2410_IISFCON_RXDMA;
142  iiscon |= S3C2410_IISCON_RXIDLE;
143  iiscon &= ~S3C2410_IISCON_RXDMAEN;
144  iismod &= ~S3C2410_IISMOD_RXMODE;
145 
146  writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
147  writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
148  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
149  }
150 
151  pr_debug("w: IISCON: %x IISMOD: %x IISFCON: %x\n", iiscon, iismod, iisfcon);
152 }
153 
154 /*
155  * Wait for the LR signal to allow synchronisation to the L/R clock
156  * from the codec. May only be needed for slave mode.
157  */
158 static int s3c24xx_snd_lrsync(void)
159 {
160  u32 iiscon;
161  int timeout = 50; /* 5ms */
162 
163  pr_debug("Entered %s\n", __func__);
164 
165  while (1) {
166  iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
167  if (iiscon & S3C2410_IISCON_LRINDEX)
168  break;
169 
170  if (!timeout--)
171  return -ETIMEDOUT;
172  udelay(100);
173  }
174 
175  return 0;
176 }
177 
178 /*
179  * Check whether CPU is the master or slave
180  */
181 static inline int s3c24xx_snd_is_clkmaster(void)
182 {
183  pr_debug("Entered %s\n", __func__);
184 
185  return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1;
186 }
187 
188 /*
189  * Set S3C24xx I2S DAI format
190  */
191 static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
192  unsigned int fmt)
193 {
194  u32 iismod;
195 
196  pr_debug("Entered %s\n", __func__);
197 
198  iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
199  pr_debug("hw_params r: IISMOD: %x \n", iismod);
200 
201  switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
203  iismod |= S3C2410_IISMOD_SLAVE;
204  break;
206  iismod &= ~S3C2410_IISMOD_SLAVE;
207  break;
208  default:
209  return -EINVAL;
210  }
211 
212  switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
214  iismod |= S3C2410_IISMOD_MSB;
215  break;
216  case SND_SOC_DAIFMT_I2S:
217  iismod &= ~S3C2410_IISMOD_MSB;
218  break;
219  default:
220  return -EINVAL;
221  }
222 
223  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
224  pr_debug("hw_params w: IISMOD: %x \n", iismod);
225  return 0;
226 }
227 
228 static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,
229  struct snd_pcm_hw_params *params,
230  struct snd_soc_dai *dai)
231 {
232  struct snd_soc_pcm_runtime *rtd = substream->private_data;
233  struct s3c_dma_params *dma_data;
234  u32 iismod;
235 
236  pr_debug("Entered %s\n", __func__);
237 
238  if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
239  dma_data = &s3c24xx_i2s_pcm_stereo_out;
240  else
241  dma_data = &s3c24xx_i2s_pcm_stereo_in;
242 
243  snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);
244 
245  /* Working copies of register */
246  iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
247  pr_debug("hw_params r: IISMOD: %x\n", iismod);
248 
249  switch (params_format(params)) {
250  case SNDRV_PCM_FORMAT_S8:
251  iismod &= ~S3C2410_IISMOD_16BIT;
252  dma_data->dma_size = 1;
253  break;
255  iismod |= S3C2410_IISMOD_16BIT;
256  dma_data->dma_size = 2;
257  break;
258  default:
259  return -EINVAL;
260  }
261 
262  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
263  pr_debug("hw_params w: IISMOD: %x\n", iismod);
264  return 0;
265 }
266 
267 static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
268  struct snd_soc_dai *dai)
269 {
270  int ret = 0;
271  struct s3c_dma_params *dma_data =
272  snd_soc_dai_get_dma_data(dai, substream);
273 
274  pr_debug("Entered %s\n", __func__);
275 
276  switch (cmd) {
280  if (!s3c24xx_snd_is_clkmaster()) {
281  ret = s3c24xx_snd_lrsync();
282  if (ret)
283  goto exit_err;
284  }
285 
286  if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
287  s3c24xx_snd_rxctrl(1);
288  else
289  s3c24xx_snd_txctrl(1);
290 
292  break;
296  if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
297  s3c24xx_snd_rxctrl(0);
298  else
299  s3c24xx_snd_txctrl(0);
300  break;
301  default:
302  ret = -EINVAL;
303  break;
304  }
305 
306 exit_err:
307  return ret;
308 }
309 
310 /*
311  * Set S3C24xx Clock source
312  */
313 static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
314  int clk_id, unsigned int freq, int dir)
315 {
316  u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
317 
318  pr_debug("Entered %s\n", __func__);
319 
320  iismod &= ~S3C2440_IISMOD_MPLL;
321 
322  switch (clk_id) {
323  case S3C24XX_CLKSRC_PCLK:
324  break;
325  case S3C24XX_CLKSRC_MPLL:
326  iismod |= S3C2440_IISMOD_MPLL;
327  break;
328  default:
329  return -EINVAL;
330  }
331 
332  writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
333  return 0;
334 }
335 
336 /*
337  * Set S3C24xx Clock dividers
338  */
339 static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
340  int div_id, int div)
341 {
342  u32 reg;
343 
344  pr_debug("Entered %s\n", __func__);
345 
346  switch (div_id) {
347  case S3C24XX_DIV_BCLK:
348  reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
349  writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
350  break;
351  case S3C24XX_DIV_MCLK:
352  reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
353  writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
354  break;
356  writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
357  reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
358  writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
359  break;
360  default:
361  return -EINVAL;
362  }
363 
364  return 0;
365 }
366 
367 /*
368  * To avoid duplicating clock code, allow machine driver to
369  * get the clockrate from here.
370  */
372 {
373  return clk_get_rate(s3c24xx_i2s.iis_clk);
374 }
376 
377 static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
378 {
379  pr_debug("Entered %s\n", __func__);
380 
381  s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
382  if (s3c24xx_i2s.regs == NULL)
383  return -ENXIO;
384 
385  s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis");
386  if (IS_ERR(s3c24xx_i2s.iis_clk)) {
387  pr_err("failed to get iis_clock\n");
388  iounmap(s3c24xx_i2s.regs);
389  return PTR_ERR(s3c24xx_i2s.iis_clk);
390  }
391  clk_enable(s3c24xx_i2s.iis_clk);
392 
393  /* Configure the I2S pins (GPE0...GPE4) in correct mode */
396 
397  writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
398 
399  s3c24xx_snd_txctrl(0);
400  s3c24xx_snd_rxctrl(0);
401 
402  return 0;
403 }
404 
405 #ifdef CONFIG_PM
406 static int s3c24xx_i2s_suspend(struct snd_soc_dai *cpu_dai)
407 {
408  pr_debug("Entered %s\n", __func__);
409 
410  s3c24xx_i2s.iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
411  s3c24xx_i2s.iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
412  s3c24xx_i2s.iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON);
413  s3c24xx_i2s.iispsr = readl(s3c24xx_i2s.regs + S3C2410_IISPSR);
414 
415  clk_disable(s3c24xx_i2s.iis_clk);
416 
417  return 0;
418 }
419 
420 static int s3c24xx_i2s_resume(struct snd_soc_dai *cpu_dai)
421 {
422  pr_debug("Entered %s\n", __func__);
423  clk_enable(s3c24xx_i2s.iis_clk);
424 
425  writel(s3c24xx_i2s.iiscon, s3c24xx_i2s.regs + S3C2410_IISCON);
426  writel(s3c24xx_i2s.iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
427  writel(s3c24xx_i2s.iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON);
428  writel(s3c24xx_i2s.iispsr, s3c24xx_i2s.regs + S3C2410_IISPSR);
429 
430  return 0;
431 }
432 #else
433 #define s3c24xx_i2s_suspend NULL
434 #define s3c24xx_i2s_resume NULL
435 #endif
436 
437 
438 #define S3C24XX_I2S_RATES \
439  (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \
440  SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
441  SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000)
442 
443 static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
444  .trigger = s3c24xx_i2s_trigger,
445  .hw_params = s3c24xx_i2s_hw_params,
446  .set_fmt = s3c24xx_i2s_set_fmt,
447  .set_clkdiv = s3c24xx_i2s_set_clkdiv,
448  .set_sysclk = s3c24xx_i2s_set_sysclk,
449 };
450 
451 static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
452  .probe = s3c24xx_i2s_probe,
453  .suspend = s3c24xx_i2s_suspend,
454  .resume = s3c24xx_i2s_resume,
455  .playback = {
456  .channels_min = 2,
457  .channels_max = 2,
458  .rates = S3C24XX_I2S_RATES,
460  .capture = {
461  .channels_min = 2,
462  .channels_max = 2,
463  .rates = S3C24XX_I2S_RATES,
465  .ops = &s3c24xx_i2s_dai_ops,
466 };
467 
468 static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
469 {
470  return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
471 }
472 
473 static __devexit int s3c24xx_iis_dev_remove(struct platform_device *pdev)
474 {
475  snd_soc_unregister_dai(&pdev->dev);
476  return 0;
477 }
478 
479 static struct platform_driver s3c24xx_iis_driver = {
480  .probe = s3c24xx_iis_dev_probe,
481  .remove = __devexit_p(s3c24xx_iis_dev_remove),
482  .driver = {
483  .name = "s3c24xx-iis",
484  .owner = THIS_MODULE,
485  },
486 };
487 
488 module_platform_driver(s3c24xx_iis_driver);
489 
490 /* Module information */
491 MODULE_AUTHOR("Ben Dooks, <[email protected]>");
492 MODULE_DESCRIPTION("s3c24xx I2S SoC Interface");
493 MODULE_LICENSE("GPL");
494 MODULE_ALIAS("platform:s3c24xx-iis");