Commit | Line | Data |
---|---|---|
c1422a66 BD |
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> | |
f11b7992 | 27 | #include <linux/jiffies.h> |
c1422a66 BD |
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> | |
c1422a66 BD |
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 | ||
aa9673cf HW |
42 | #include <asm/plat-s3c24xx/regs-iis.h> |
43 | ||
c1422a66 BD |
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, | |
e81208fe GG |
65 | .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, |
66 | .dma_size = 2, | |
c1422a66 BD |
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, | |
e81208fe GG |
72 | .dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO, |
73 | .dma_size = 2, | |
c1422a66 BD |
74 | }; |
75 | ||
76 | struct s3c24xx_i2s_info { | |
77 | void __iomem *regs; | |
78 | struct clk *iis_clk; | |
79 | }; | |
80 | static struct s3c24xx_i2s_info s3c24xx_i2s; | |
81 | ||
82 | static void s3c24xx_snd_txctrl(int on) | |
83 | { | |
84 | u32 iisfcon; | |
85 | u32 iiscon; | |
86 | u32 iismod; | |
87 | ||
88 | DBG("Entered %s\n", __FUNCTION__); | |
89 | ||
90 | iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); | |
91 | iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | |
92 | iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | |
93 | ||
94 | DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | |
95 | ||
96 | if (on) { | |
97 | iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE; | |
98 | iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN; | |
99 | iiscon &= ~S3C2410_IISCON_TXIDLE; | |
100 | iismod |= S3C2410_IISMOD_TXMODE; | |
101 | ||
102 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
103 | writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | |
104 | writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); | |
105 | } else { | |
106 | /* note, we have to disable the FIFOs otherwise bad things | |
107 | * seem to happen when the DMA stops. According to the | |
108 | * Samsung supplied kernel, this should allow the DMA | |
109 | * engine and FIFOs to reset. If this isn't allowed, the | |
110 | * DMA engine will simply freeze randomly. | |
111 | */ | |
112 | ||
113 | iisfcon &= ~S3C2410_IISFCON_TXENABLE; | |
114 | iisfcon &= ~S3C2410_IISFCON_TXDMA; | |
115 | iiscon |= S3C2410_IISCON_TXIDLE; | |
116 | iiscon &= ~S3C2410_IISCON_TXDMAEN; | |
117 | iismod &= ~S3C2410_IISMOD_TXMODE; | |
118 | ||
119 | writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); | |
120 | writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | |
121 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
122 | } | |
123 | ||
124 | DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | |
125 | } | |
126 | ||
127 | static void s3c24xx_snd_rxctrl(int on) | |
128 | { | |
129 | u32 iisfcon; | |
130 | u32 iiscon; | |
131 | u32 iismod; | |
132 | ||
133 | DBG("Entered %s\n", __FUNCTION__); | |
134 | ||
135 | iisfcon = readl(s3c24xx_i2s.regs + S3C2410_IISFCON); | |
136 | iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | |
137 | iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | |
138 | ||
139 | DBG("r: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | |
140 | ||
141 | if (on) { | |
142 | iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE; | |
143 | iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN; | |
144 | iiscon &= ~S3C2410_IISCON_RXIDLE; | |
145 | iismod |= S3C2410_IISMOD_RXMODE; | |
146 | ||
147 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
148 | writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | |
149 | writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); | |
150 | } else { | |
151 | /* note, we have to disable the FIFOs otherwise bad things | |
152 | * seem to happen when the DMA stops. According to the | |
153 | * Samsung supplied kernel, this should allow the DMA | |
154 | * engine and FIFOs to reset. If this isn't allowed, the | |
155 | * DMA engine will simply freeze randomly. | |
156 | */ | |
157 | ||
158 | iisfcon &= ~S3C2410_IISFCON_RXENABLE; | |
159 | iisfcon &= ~S3C2410_IISFCON_RXDMA; | |
160 | iiscon |= S3C2410_IISCON_RXIDLE; | |
161 | iiscon &= ~S3C2410_IISCON_RXDMAEN; | |
162 | iismod &= ~S3C2410_IISMOD_RXMODE; | |
163 | ||
164 | writel(iisfcon, s3c24xx_i2s.regs + S3C2410_IISFCON); | |
165 | writel(iiscon, s3c24xx_i2s.regs + S3C2410_IISCON); | |
166 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
167 | } | |
168 | ||
169 | DBG("w: IISCON: %lx IISMOD: %lx IISFCON: %lx\n", iiscon, iismod, iisfcon); | |
170 | } | |
171 | ||
172 | /* | |
173 | * Wait for the LR signal to allow synchronisation to the L/R clock | |
174 | * from the codec. May only be needed for slave mode. | |
175 | */ | |
176 | static int s3c24xx_snd_lrsync(void) | |
177 | { | |
178 | u32 iiscon; | |
179 | unsigned long timeout = jiffies + msecs_to_jiffies(5); | |
180 | ||
181 | DBG("Entered %s\n", __FUNCTION__); | |
182 | ||
183 | while (1) { | |
184 | iiscon = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | |
185 | if (iiscon & S3C2410_IISCON_LRINDEX) | |
186 | break; | |
187 | ||
f11b7992 | 188 | if (time_after(jiffies, timeout)) |
c1422a66 BD |
189 | return -ETIMEDOUT; |
190 | } | |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
195 | /* | |
196 | * Check whether CPU is the master or slave | |
197 | */ | |
198 | static inline int s3c24xx_snd_is_clkmaster(void) | |
199 | { | |
200 | DBG("Entered %s\n", __FUNCTION__); | |
201 | ||
202 | return (readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & S3C2410_IISMOD_SLAVE) ? 0:1; | |
203 | } | |
204 | ||
205 | /* | |
206 | * Set S3C24xx I2S DAI format | |
207 | */ | |
208 | static int s3c24xx_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai, | |
209 | unsigned int fmt) | |
210 | { | |
211 | u32 iismod; | |
212 | ||
213 | DBG("Entered %s\n", __FUNCTION__); | |
214 | ||
215 | iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | |
216 | DBG("hw_params r: IISMOD: %lx \n", iismod); | |
217 | ||
218 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | |
219 | case SND_SOC_DAIFMT_CBM_CFM: | |
220 | iismod |= S3C2410_IISMOD_SLAVE; | |
221 | break; | |
222 | case SND_SOC_DAIFMT_CBS_CFS: | |
223 | break; | |
224 | default: | |
225 | return -EINVAL; | |
226 | } | |
227 | ||
228 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
229 | case SND_SOC_DAIFMT_LEFT_J: | |
230 | iismod |= S3C2410_IISMOD_MSB; | |
231 | break; | |
232 | case SND_SOC_DAIFMT_I2S: | |
233 | break; | |
234 | default: | |
235 | return -EINVAL; | |
236 | } | |
237 | ||
238 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
239 | DBG("hw_params w: IISMOD: %lx \n", iismod); | |
240 | return 0; | |
241 | } | |
242 | ||
243 | static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream, | |
244 | struct snd_pcm_hw_params *params) | |
245 | { | |
246 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
247 | u32 iismod; | |
248 | ||
249 | DBG("Entered %s\n", __FUNCTION__); | |
250 | ||
251 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | |
252 | rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_out; | |
253 | else | |
254 | rtd->dai->cpu_dai->dma_data = &s3c24xx_i2s_pcm_stereo_in; | |
255 | ||
256 | /* Working copies of register */ | |
257 | iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | |
258 | DBG("hw_params r: IISMOD: %lx\n", iismod); | |
259 | ||
260 | switch (params_format(params)) { | |
261 | case SNDRV_PCM_FORMAT_S8: | |
262 | break; | |
263 | case SNDRV_PCM_FORMAT_S16_LE: | |
264 | iismod |= S3C2410_IISMOD_16BIT; | |
265 | break; | |
266 | } | |
267 | ||
268 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
269 | DBG("hw_params w: IISMOD: %lx\n", iismod); | |
270 | return 0; | |
271 | } | |
272 | ||
273 | static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd) | |
274 | { | |
275 | int ret = 0; | |
276 | ||
277 | DBG("Entered %s\n", __FUNCTION__); | |
278 | ||
279 | switch (cmd) { | |
280 | case SNDRV_PCM_TRIGGER_START: | |
281 | case SNDRV_PCM_TRIGGER_RESUME: | |
282 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
283 | if (!s3c24xx_snd_is_clkmaster()) { | |
284 | ret = s3c24xx_snd_lrsync(); | |
285 | if (ret) | |
286 | goto exit_err; | |
287 | } | |
288 | ||
289 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | |
290 | s3c24xx_snd_rxctrl(1); | |
291 | else | |
292 | s3c24xx_snd_txctrl(1); | |
293 | break; | |
294 | case SNDRV_PCM_TRIGGER_STOP: | |
295 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
296 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
297 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | |
298 | s3c24xx_snd_rxctrl(0); | |
299 | else | |
300 | s3c24xx_snd_txctrl(0); | |
301 | break; | |
302 | default: | |
303 | ret = -EINVAL; | |
304 | break; | |
305 | } | |
306 | ||
307 | exit_err: | |
308 | return ret; | |
309 | } | |
310 | ||
311 | /* | |
312 | * Set S3C24xx Clock source | |
313 | */ | |
314 | static int s3c24xx_i2s_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, | |
315 | int clk_id, unsigned int freq, int dir) | |
316 | { | |
317 | u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD); | |
318 | ||
319 | DBG("Entered %s\n", __FUNCTION__); | |
320 | ||
321 | iismod &= ~S3C2440_IISMOD_MPLL; | |
322 | ||
323 | switch (clk_id) { | |
324 | case S3C24XX_CLKSRC_PCLK: | |
325 | break; | |
326 | case S3C24XX_CLKSRC_MPLL: | |
327 | iismod |= S3C2440_IISMOD_MPLL; | |
328 | break; | |
329 | default: | |
330 | return -EINVAL; | |
331 | } | |
332 | ||
333 | writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
334 | return 0; | |
335 | } | |
336 | ||
337 | /* | |
338 | * Set S3C24xx Clock dividers | |
339 | */ | |
340 | static int s3c24xx_i2s_set_clkdiv(struct snd_soc_cpu_dai *cpu_dai, | |
341 | int div_id, int div) | |
342 | { | |
343 | u32 reg; | |
344 | ||
345 | DBG("Entered %s\n", __FUNCTION__); | |
346 | ||
347 | switch (div_id) { | |
82fb159a | 348 | case S3C24XX_DIV_BCLK: |
c1422a66 BD |
349 | reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK; |
350 | writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
351 | break; | |
82fb159a | 352 | case S3C24XX_DIV_MCLK: |
c1422a66 BD |
353 | reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS); |
354 | writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD); | |
355 | break; | |
356 | case S3C24XX_DIV_PRESCALER: | |
357 | writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR); | |
358 | reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON); | |
359 | writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON); | |
360 | break; | |
361 | default: | |
362 | return -EINVAL; | |
363 | } | |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
368 | /* | |
369 | * To avoid duplicating clock code, allow machine driver to | |
370 | * get the clockrate from here. | |
371 | */ | |
372 | u32 s3c24xx_i2s_get_clockrate(void) | |
373 | { | |
374 | return clk_get_rate(s3c24xx_i2s.iis_clk); | |
375 | } | |
376 | EXPORT_SYMBOL_GPL(s3c24xx_i2s_get_clockrate); | |
377 | ||
378 | static int s3c24xx_i2s_probe(struct platform_device *pdev) | |
379 | { | |
380 | DBG("Entered %s\n", __FUNCTION__); | |
381 | ||
382 | s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100); | |
383 | if (s3c24xx_i2s.regs == NULL) | |
384 | return -ENXIO; | |
385 | ||
386 | s3c24xx_i2s.iis_clk=clk_get(&pdev->dev, "iis"); | |
387 | if (s3c24xx_i2s.iis_clk == NULL) { | |
388 | DBG("failed to get iis_clock\n"); | |
8642a4ba | 389 | iounmap(s3c24xx_i2s.regs); |
c1422a66 BD |
390 | return -ENODEV; |
391 | } | |
392 | clk_enable(s3c24xx_i2s.iis_clk); | |
393 | ||
394 | /* Configure the I2S pins in correct mode */ | |
395 | s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK); | |
396 | s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK); | |
397 | s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK); | |
398 | s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI); | |
399 | s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO); | |
400 | ||
401 | writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON); | |
402 | ||
403 | s3c24xx_snd_txctrl(0); | |
404 | s3c24xx_snd_rxctrl(0); | |
405 | ||
406 | return 0; | |
407 | } | |
408 | ||
409 | #define S3C24XX_I2S_RATES \ | |
410 | (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_16000 | \ | |
411 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ | |
412 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) | |
413 | ||
414 | struct snd_soc_cpu_dai s3c24xx_i2s_dai = { | |
415 | .name = "s3c24xx-i2s", | |
416 | .id = 0, | |
417 | .type = SND_SOC_DAI_I2S, | |
418 | .probe = s3c24xx_i2s_probe, | |
419 | .playback = { | |
420 | .channels_min = 2, | |
421 | .channels_max = 2, | |
422 | .rates = S3C24XX_I2S_RATES, | |
423 | .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, | |
424 | .capture = { | |
425 | .channels_min = 2, | |
426 | .channels_max = 2, | |
427 | .rates = S3C24XX_I2S_RATES, | |
428 | .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,}, | |
429 | .ops = { | |
430 | .trigger = s3c24xx_i2s_trigger, | |
431 | .hw_params = s3c24xx_i2s_hw_params,}, | |
432 | .dai_ops = { | |
433 | .set_fmt = s3c24xx_i2s_set_fmt, | |
434 | .set_clkdiv = s3c24xx_i2s_set_clkdiv, | |
435 | .set_sysclk = s3c24xx_i2s_set_sysclk, | |
436 | }, | |
437 | }; | |
438 | EXPORT_SYMBOL_GPL(s3c24xx_i2s_dai); | |
439 | ||
440 | /* Module information */ | |
441 | MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>"); | |
442 | MODULE_DESCRIPTION("s3c24xx I2S SoC Interface"); | |
443 | MODULE_LICENSE("GPL"); |