diff options
Diffstat (limited to 'sound/soc/samsung/s3c-i2s-v2.c')
-rw-r--r-- | sound/soc/samsung/s3c-i2s-v2.c | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c new file mode 100644 index 000000000000..094f36e41e83 --- /dev/null +++ b/sound/soc/samsung/s3c-i2s-v2.c | |||
@@ -0,0 +1,757 @@ | |||
1 | /* sound/soc/samsung/s3c-i2c-v2.c | ||
2 | * | ||
3 | * ALSA Soc Audio Layer - I2S core for newer Samsung SoCs. | ||
4 | * | ||
5 | * Copyright (c) 2006 Wolfson Microelectronics PLC. | ||
6 | * Graeme Gregory graeme.gregory@wolfsonmicro.com | ||
7 | * linux@wolfsonmicro.com | ||
8 | * | ||
9 | * Copyright (c) 2008, 2007, 2004-2005 Simtec Electronics | ||
10 | * http://armlinux.simtec.co.uk/ | ||
11 | * Ben Dooks <ben@simtec.co.uk> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify it | ||
14 | * under the terms of the GNU General Public License as published by the | ||
15 | * Free Software Foundation; either version 2 of the License, or (at your | ||
16 | * option) any later version. | ||
17 | */ | ||
18 | |||
19 | #include <linux/delay.h> | ||
20 | #include <linux/clk.h> | ||
21 | #include <linux/io.h> | ||
22 | |||
23 | #include <sound/pcm.h> | ||
24 | #include <sound/pcm_params.h> | ||
25 | #include <sound/soc.h> | ||
26 | |||
27 | #include <mach/dma.h> | ||
28 | |||
29 | #include "regs-i2s-v2.h" | ||
30 | #include "s3c-i2s-v2.h" | ||
31 | #include "dma.h" | ||
32 | |||
33 | #undef S3C_IIS_V2_SUPPORTED | ||
34 | |||
35 | #if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) \ | ||
36 | || defined(CONFIG_CPU_S5PV210) | ||
37 | #define S3C_IIS_V2_SUPPORTED | ||
38 | #endif | ||
39 | |||
40 | #ifdef CONFIG_PLAT_S3C64XX | ||
41 | #define S3C_IIS_V2_SUPPORTED | ||
42 | #endif | ||
43 | |||
44 | #ifndef S3C_IIS_V2_SUPPORTED | ||
45 | #error Unsupported CPU model | ||
46 | #endif | ||
47 | |||
48 | #define S3C2412_I2S_DEBUG_CON 0 | ||
49 | |||
50 | static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) | ||
51 | { | ||
52 | return snd_soc_dai_get_drvdata(cpu_dai); | ||
53 | } | ||
54 | |||
55 | #define bit_set(v, b) (((v) & (b)) ? 1 : 0) | ||
56 | |||
57 | #if S3C2412_I2S_DEBUG_CON | ||
58 | static void dbg_showcon(const char *fn, u32 con) | ||
59 | { | ||
60 | printk(KERN_DEBUG "%s: LRI=%d, TXFEMPT=%d, RXFEMPT=%d, TXFFULL=%d, RXFFULL=%d\n", fn, | ||
61 | bit_set(con, S3C2412_IISCON_LRINDEX), | ||
62 | bit_set(con, S3C2412_IISCON_TXFIFO_EMPTY), | ||
63 | bit_set(con, S3C2412_IISCON_RXFIFO_EMPTY), | ||
64 | bit_set(con, S3C2412_IISCON_TXFIFO_FULL), | ||
65 | bit_set(con, S3C2412_IISCON_RXFIFO_FULL)); | ||
66 | |||
67 | printk(KERN_DEBUG "%s: PAUSE: TXDMA=%d, RXDMA=%d, TXCH=%d, RXCH=%d\n", | ||
68 | fn, | ||
69 | bit_set(con, S3C2412_IISCON_TXDMA_PAUSE), | ||
70 | bit_set(con, S3C2412_IISCON_RXDMA_PAUSE), | ||
71 | bit_set(con, S3C2412_IISCON_TXCH_PAUSE), | ||
72 | bit_set(con, S3C2412_IISCON_RXCH_PAUSE)); | ||
73 | printk(KERN_DEBUG "%s: ACTIVE: TXDMA=%d, RXDMA=%d, IIS=%d\n", fn, | ||
74 | bit_set(con, S3C2412_IISCON_TXDMA_ACTIVE), | ||
75 | bit_set(con, S3C2412_IISCON_RXDMA_ACTIVE), | ||
76 | bit_set(con, S3C2412_IISCON_IIS_ACTIVE)); | ||
77 | } | ||
78 | #else | ||
79 | static inline void dbg_showcon(const char *fn, u32 con) | ||
80 | { | ||
81 | } | ||
82 | #endif | ||
83 | |||
84 | |||
85 | /* Turn on or off the transmission path. */ | ||
86 | static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) | ||
87 | { | ||
88 | void __iomem *regs = i2s->regs; | ||
89 | u32 fic, con, mod; | ||
90 | |||
91 | pr_debug("%s(%d)\n", __func__, on); | ||
92 | |||
93 | fic = readl(regs + S3C2412_IISFIC); | ||
94 | con = readl(regs + S3C2412_IISCON); | ||
95 | mod = readl(regs + S3C2412_IISMOD); | ||
96 | |||
97 | pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); | ||
98 | |||
99 | if (on) { | ||
100 | con |= S3C2412_IISCON_TXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; | ||
101 | con &= ~S3C2412_IISCON_TXDMA_PAUSE; | ||
102 | con &= ~S3C2412_IISCON_TXCH_PAUSE; | ||
103 | |||
104 | switch (mod & S3C2412_IISMOD_MODE_MASK) { | ||
105 | case S3C2412_IISMOD_MODE_TXONLY: | ||
106 | case S3C2412_IISMOD_MODE_TXRX: | ||
107 | /* do nothing, we are in the right mode */ | ||
108 | break; | ||
109 | |||
110 | case S3C2412_IISMOD_MODE_RXONLY: | ||
111 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
112 | mod |= S3C2412_IISMOD_MODE_TXRX; | ||
113 | break; | ||
114 | |||
115 | default: | ||
116 | dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", | ||
117 | mod & S3C2412_IISMOD_MODE_MASK); | ||
118 | break; | ||
119 | } | ||
120 | |||
121 | writel(con, regs + S3C2412_IISCON); | ||
122 | writel(mod, regs + S3C2412_IISMOD); | ||
123 | } else { | ||
124 | /* Note, we do not have any indication that the FIFO problems | ||
125 | * tha the S3C2410/2440 had apply here, so we should be able | ||
126 | * to disable the DMA and TX without resetting the FIFOS. | ||
127 | */ | ||
128 | |||
129 | con |= S3C2412_IISCON_TXDMA_PAUSE; | ||
130 | con |= S3C2412_IISCON_TXCH_PAUSE; | ||
131 | con &= ~S3C2412_IISCON_TXDMA_ACTIVE; | ||
132 | |||
133 | switch (mod & S3C2412_IISMOD_MODE_MASK) { | ||
134 | case S3C2412_IISMOD_MODE_TXRX: | ||
135 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
136 | mod |= S3C2412_IISMOD_MODE_RXONLY; | ||
137 | break; | ||
138 | |||
139 | case S3C2412_IISMOD_MODE_TXONLY: | ||
140 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
141 | con &= ~S3C2412_IISCON_IIS_ACTIVE; | ||
142 | break; | ||
143 | |||
144 | default: | ||
145 | dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n", | ||
146 | mod & S3C2412_IISMOD_MODE_MASK); | ||
147 | break; | ||
148 | } | ||
149 | |||
150 | writel(mod, regs + S3C2412_IISMOD); | ||
151 | writel(con, regs + S3C2412_IISCON); | ||
152 | } | ||
153 | |||
154 | fic = readl(regs + S3C2412_IISFIC); | ||
155 | dbg_showcon(__func__, con); | ||
156 | pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); | ||
157 | } | ||
158 | |||
159 | static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) | ||
160 | { | ||
161 | void __iomem *regs = i2s->regs; | ||
162 | u32 fic, con, mod; | ||
163 | |||
164 | pr_debug("%s(%d)\n", __func__, on); | ||
165 | |||
166 | fic = readl(regs + S3C2412_IISFIC); | ||
167 | con = readl(regs + S3C2412_IISCON); | ||
168 | mod = readl(regs + S3C2412_IISMOD); | ||
169 | |||
170 | pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); | ||
171 | |||
172 | if (on) { | ||
173 | con |= S3C2412_IISCON_RXDMA_ACTIVE | S3C2412_IISCON_IIS_ACTIVE; | ||
174 | con &= ~S3C2412_IISCON_RXDMA_PAUSE; | ||
175 | con &= ~S3C2412_IISCON_RXCH_PAUSE; | ||
176 | |||
177 | switch (mod & S3C2412_IISMOD_MODE_MASK) { | ||
178 | case S3C2412_IISMOD_MODE_TXRX: | ||
179 | case S3C2412_IISMOD_MODE_RXONLY: | ||
180 | /* do nothing, we are in the right mode */ | ||
181 | break; | ||
182 | |||
183 | case S3C2412_IISMOD_MODE_TXONLY: | ||
184 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
185 | mod |= S3C2412_IISMOD_MODE_TXRX; | ||
186 | break; | ||
187 | |||
188 | default: | ||
189 | dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", | ||
190 | mod & S3C2412_IISMOD_MODE_MASK); | ||
191 | } | ||
192 | |||
193 | writel(mod, regs + S3C2412_IISMOD); | ||
194 | writel(con, regs + S3C2412_IISCON); | ||
195 | } else { | ||
196 | /* See txctrl notes on FIFOs. */ | ||
197 | |||
198 | con &= ~S3C2412_IISCON_RXDMA_ACTIVE; | ||
199 | con |= S3C2412_IISCON_RXDMA_PAUSE; | ||
200 | con |= S3C2412_IISCON_RXCH_PAUSE; | ||
201 | |||
202 | switch (mod & S3C2412_IISMOD_MODE_MASK) { | ||
203 | case S3C2412_IISMOD_MODE_RXONLY: | ||
204 | con &= ~S3C2412_IISCON_IIS_ACTIVE; | ||
205 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
206 | break; | ||
207 | |||
208 | case S3C2412_IISMOD_MODE_TXRX: | ||
209 | mod &= ~S3C2412_IISMOD_MODE_MASK; | ||
210 | mod |= S3C2412_IISMOD_MODE_TXONLY; | ||
211 | break; | ||
212 | |||
213 | default: | ||
214 | dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n", | ||
215 | mod & S3C2412_IISMOD_MODE_MASK); | ||
216 | } | ||
217 | |||
218 | writel(con, regs + S3C2412_IISCON); | ||
219 | writel(mod, regs + S3C2412_IISMOD); | ||
220 | } | ||
221 | |||
222 | fic = readl(regs + S3C2412_IISFIC); | ||
223 | pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); | ||
224 | } | ||
225 | |||
226 | #define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t) | ||
227 | |||
228 | /* | ||
229 | * Wait for the LR signal to allow synchronisation to the L/R clock | ||
230 | * from the codec. May only be needed for slave mode. | ||
231 | */ | ||
232 | static int s3c2412_snd_lrsync(struct s3c_i2sv2_info *i2s) | ||
233 | { | ||
234 | u32 iiscon; | ||
235 | unsigned long loops = msecs_to_loops(5); | ||
236 | |||
237 | pr_debug("Entered %s\n", __func__); | ||
238 | |||
239 | while (--loops) { | ||
240 | iiscon = readl(i2s->regs + S3C2412_IISCON); | ||
241 | if (iiscon & S3C2412_IISCON_LRINDEX) | ||
242 | break; | ||
243 | |||
244 | cpu_relax(); | ||
245 | } | ||
246 | |||
247 | if (!loops) { | ||
248 | printk(KERN_ERR "%s: timeout\n", __func__); | ||
249 | return -ETIMEDOUT; | ||
250 | } | ||
251 | |||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | /* | ||
256 | * Set S3C2412 I2S DAI format | ||
257 | */ | ||
258 | static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, | ||
259 | unsigned int fmt) | ||
260 | { | ||
261 | struct s3c_i2sv2_info *i2s = to_info(cpu_dai); | ||
262 | u32 iismod; | ||
263 | |||
264 | pr_debug("Entered %s\n", __func__); | ||
265 | |||
266 | iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
267 | pr_debug("hw_params r: IISMOD: %x \n", iismod); | ||
268 | |||
269 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | ||
270 | case SND_SOC_DAIFMT_CBM_CFM: | ||
271 | i2s->master = 0; | ||
272 | iismod |= S3C2412_IISMOD_SLAVE; | ||
273 | break; | ||
274 | case SND_SOC_DAIFMT_CBS_CFS: | ||
275 | i2s->master = 1; | ||
276 | iismod &= ~S3C2412_IISMOD_SLAVE; | ||
277 | break; | ||
278 | default: | ||
279 | pr_err("unknwon master/slave format\n"); | ||
280 | return -EINVAL; | ||
281 | } | ||
282 | |||
283 | iismod &= ~S3C2412_IISMOD_SDF_MASK; | ||
284 | |||
285 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | ||
286 | case SND_SOC_DAIFMT_RIGHT_J: | ||
287 | iismod |= S3C2412_IISMOD_LR_RLOW; | ||
288 | iismod |= S3C2412_IISMOD_SDF_MSB; | ||
289 | break; | ||
290 | case SND_SOC_DAIFMT_LEFT_J: | ||
291 | iismod |= S3C2412_IISMOD_LR_RLOW; | ||
292 | iismod |= S3C2412_IISMOD_SDF_LSB; | ||
293 | break; | ||
294 | case SND_SOC_DAIFMT_I2S: | ||
295 | iismod &= ~S3C2412_IISMOD_LR_RLOW; | ||
296 | iismod |= S3C2412_IISMOD_SDF_IIS; | ||
297 | break; | ||
298 | default: | ||
299 | pr_err("Unknown data format\n"); | ||
300 | return -EINVAL; | ||
301 | } | ||
302 | |||
303 | writel(iismod, i2s->regs + S3C2412_IISMOD); | ||
304 | pr_debug("hw_params w: IISMOD: %x \n", iismod); | ||
305 | return 0; | ||
306 | } | ||
307 | |||
308 | static int s3c_i2sv2_hw_params(struct snd_pcm_substream *substream, | ||
309 | struct snd_pcm_hw_params *params, | ||
310 | struct snd_soc_dai *dai) | ||
311 | { | ||
312 | struct s3c_i2sv2_info *i2s = to_info(dai); | ||
313 | struct s3c_dma_params *dma_data; | ||
314 | u32 iismod; | ||
315 | |||
316 | pr_debug("Entered %s\n", __func__); | ||
317 | |||
318 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
319 | dma_data = i2s->dma_playback; | ||
320 | else | ||
321 | dma_data = i2s->dma_capture; | ||
322 | |||
323 | snd_soc_dai_set_dma_data(dai, substream, dma_data); | ||
324 | |||
325 | /* Working copies of register */ | ||
326 | iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
327 | pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); | ||
328 | |||
329 | iismod &= ~S3C64XX_IISMOD_BLC_MASK; | ||
330 | /* Sample size */ | ||
331 | switch (params_format(params)) { | ||
332 | case SNDRV_PCM_FORMAT_S8: | ||
333 | iismod |= S3C64XX_IISMOD_BLC_8BIT; | ||
334 | break; | ||
335 | case SNDRV_PCM_FORMAT_S16_LE: | ||
336 | break; | ||
337 | case SNDRV_PCM_FORMAT_S24_LE: | ||
338 | iismod |= S3C64XX_IISMOD_BLC_24BIT; | ||
339 | break; | ||
340 | } | ||
341 | |||
342 | writel(iismod, i2s->regs + S3C2412_IISMOD); | ||
343 | pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); | ||
344 | |||
345 | return 0; | ||
346 | } | ||
347 | |||
348 | static int s3c_i2sv2_set_sysclk(struct snd_soc_dai *cpu_dai, | ||
349 | int clk_id, unsigned int freq, int dir) | ||
350 | { | ||
351 | struct s3c_i2sv2_info *i2s = to_info(cpu_dai); | ||
352 | u32 iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
353 | |||
354 | pr_debug("Entered %s\n", __func__); | ||
355 | pr_debug("%s r: IISMOD: %x\n", __func__, iismod); | ||
356 | |||
357 | switch (clk_id) { | ||
358 | case S3C_I2SV2_CLKSRC_PCLK: | ||
359 | iismod &= ~S3C2412_IISMOD_IMS_SYSMUX; | ||
360 | break; | ||
361 | |||
362 | case S3C_I2SV2_CLKSRC_AUDIOBUS: | ||
363 | iismod |= S3C2412_IISMOD_IMS_SYSMUX; | ||
364 | break; | ||
365 | |||
366 | case S3C_I2SV2_CLKSRC_CDCLK: | ||
367 | /* Error if controller doesn't have the CDCLKCON bit */ | ||
368 | if (!(i2s->feature & S3C_FEATURE_CDCLKCON)) | ||
369 | return -EINVAL; | ||
370 | |||
371 | switch (dir) { | ||
372 | case SND_SOC_CLOCK_IN: | ||
373 | iismod |= S3C64XX_IISMOD_CDCLKCON; | ||
374 | break; | ||
375 | case SND_SOC_CLOCK_OUT: | ||
376 | iismod &= ~S3C64XX_IISMOD_CDCLKCON; | ||
377 | break; | ||
378 | default: | ||
379 | return -EINVAL; | ||
380 | } | ||
381 | break; | ||
382 | |||
383 | default: | ||
384 | return -EINVAL; | ||
385 | } | ||
386 | |||
387 | writel(iismod, i2s->regs + S3C2412_IISMOD); | ||
388 | pr_debug("%s w: IISMOD: %x\n", __func__, iismod); | ||
389 | |||
390 | return 0; | ||
391 | } | ||
392 | |||
393 | static int s3c2412_i2s_trigger(struct snd_pcm_substream *substream, int cmd, | ||
394 | struct snd_soc_dai *dai) | ||
395 | { | ||
396 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
397 | struct s3c_i2sv2_info *i2s = to_info(rtd->cpu_dai); | ||
398 | int capture = (substream->stream == SNDRV_PCM_STREAM_CAPTURE); | ||
399 | unsigned long irqs; | ||
400 | int ret = 0; | ||
401 | struct s3c_dma_params *dma_data = | ||
402 | snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); | ||
403 | |||
404 | pr_debug("Entered %s\n", __func__); | ||
405 | |||
406 | switch (cmd) { | ||
407 | case SNDRV_PCM_TRIGGER_START: | ||
408 | /* On start, ensure that the FIFOs are cleared and reset. */ | ||
409 | |||
410 | writel(capture ? S3C2412_IISFIC_RXFLUSH : S3C2412_IISFIC_TXFLUSH, | ||
411 | i2s->regs + S3C2412_IISFIC); | ||
412 | |||
413 | /* clear again, just in case */ | ||
414 | writel(0x0, i2s->regs + S3C2412_IISFIC); | ||
415 | |||
416 | case SNDRV_PCM_TRIGGER_RESUME: | ||
417 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
418 | if (!i2s->master) { | ||
419 | ret = s3c2412_snd_lrsync(i2s); | ||
420 | if (ret) | ||
421 | goto exit_err; | ||
422 | } | ||
423 | |||
424 | local_irq_save(irqs); | ||
425 | |||
426 | if (capture) | ||
427 | s3c2412_snd_rxctrl(i2s, 1); | ||
428 | else | ||
429 | s3c2412_snd_txctrl(i2s, 1); | ||
430 | |||
431 | local_irq_restore(irqs); | ||
432 | |||
433 | /* | ||
434 | * Load the next buffer to DMA to meet the reqirement | ||
435 | * of the auto reload mechanism of S3C24XX. | ||
436 | * This call won't bother S3C64XX. | ||
437 | */ | ||
438 | s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); | ||
439 | |||
440 | break; | ||
441 | |||
442 | case SNDRV_PCM_TRIGGER_STOP: | ||
443 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
444 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
445 | local_irq_save(irqs); | ||
446 | |||
447 | if (capture) | ||
448 | s3c2412_snd_rxctrl(i2s, 0); | ||
449 | else | ||
450 | s3c2412_snd_txctrl(i2s, 0); | ||
451 | |||
452 | local_irq_restore(irqs); | ||
453 | break; | ||
454 | default: | ||
455 | ret = -EINVAL; | ||
456 | break; | ||
457 | } | ||
458 | |||
459 | exit_err: | ||
460 | return ret; | ||
461 | } | ||
462 | |||
463 | /* | ||
464 | * Set S3C2412 Clock dividers | ||
465 | */ | ||
466 | static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, | ||
467 | int div_id, int div) | ||
468 | { | ||
469 | struct s3c_i2sv2_info *i2s = to_info(cpu_dai); | ||
470 | u32 reg; | ||
471 | |||
472 | pr_debug("%s(%p, %d, %d)\n", __func__, cpu_dai, div_id, div); | ||
473 | |||
474 | switch (div_id) { | ||
475 | case S3C_I2SV2_DIV_BCLK: | ||
476 | switch (div) { | ||
477 | case 16: | ||
478 | div = S3C2412_IISMOD_BCLK_16FS; | ||
479 | break; | ||
480 | |||
481 | case 32: | ||
482 | div = S3C2412_IISMOD_BCLK_32FS; | ||
483 | break; | ||
484 | |||
485 | case 24: | ||
486 | div = S3C2412_IISMOD_BCLK_24FS; | ||
487 | break; | ||
488 | |||
489 | case 48: | ||
490 | div = S3C2412_IISMOD_BCLK_48FS; | ||
491 | break; | ||
492 | |||
493 | default: | ||
494 | return -EINVAL; | ||
495 | } | ||
496 | |||
497 | reg = readl(i2s->regs + S3C2412_IISMOD); | ||
498 | reg &= ~S3C2412_IISMOD_BCLK_MASK; | ||
499 | writel(reg | div, i2s->regs + S3C2412_IISMOD); | ||
500 | |||
501 | pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); | ||
502 | break; | ||
503 | |||
504 | case S3C_I2SV2_DIV_RCLK: | ||
505 | switch (div) { | ||
506 | case 256: | ||
507 | div = S3C2412_IISMOD_RCLK_256FS; | ||
508 | break; | ||
509 | |||
510 | case 384: | ||
511 | div = S3C2412_IISMOD_RCLK_384FS; | ||
512 | break; | ||
513 | |||
514 | case 512: | ||
515 | div = S3C2412_IISMOD_RCLK_512FS; | ||
516 | break; | ||
517 | |||
518 | case 768: | ||
519 | div = S3C2412_IISMOD_RCLK_768FS; | ||
520 | break; | ||
521 | |||
522 | default: | ||
523 | return -EINVAL; | ||
524 | } | ||
525 | |||
526 | reg = readl(i2s->regs + S3C2412_IISMOD); | ||
527 | reg &= ~S3C2412_IISMOD_RCLK_MASK; | ||
528 | writel(reg | div, i2s->regs + S3C2412_IISMOD); | ||
529 | pr_debug("%s: MOD=%08x\n", __func__, readl(i2s->regs + S3C2412_IISMOD)); | ||
530 | break; | ||
531 | |||
532 | case S3C_I2SV2_DIV_PRESCALER: | ||
533 | if (div >= 0) { | ||
534 | writel((div << 8) | S3C2412_IISPSR_PSREN, | ||
535 | i2s->regs + S3C2412_IISPSR); | ||
536 | } else { | ||
537 | writel(0x0, i2s->regs + S3C2412_IISPSR); | ||
538 | } | ||
539 | pr_debug("%s: PSR=%08x\n", __func__, readl(i2s->regs + S3C2412_IISPSR)); | ||
540 | break; | ||
541 | |||
542 | default: | ||
543 | return -EINVAL; | ||
544 | } | ||
545 | |||
546 | return 0; | ||
547 | } | ||
548 | |||
549 | static snd_pcm_sframes_t s3c2412_i2s_delay(struct snd_pcm_substream *substream, | ||
550 | struct snd_soc_dai *dai) | ||
551 | { | ||
552 | struct s3c_i2sv2_info *i2s = to_info(dai); | ||
553 | u32 reg = readl(i2s->regs + S3C2412_IISFIC); | ||
554 | snd_pcm_sframes_t delay; | ||
555 | |||
556 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
557 | delay = S3C2412_IISFIC_TXCOUNT(reg); | ||
558 | else | ||
559 | delay = S3C2412_IISFIC_RXCOUNT(reg); | ||
560 | |||
561 | return delay; | ||
562 | } | ||
563 | |||
564 | struct clk *s3c_i2sv2_get_clock(struct snd_soc_dai *cpu_dai) | ||
565 | { | ||
566 | struct s3c_i2sv2_info *i2s = to_info(cpu_dai); | ||
567 | u32 iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
568 | |||
569 | if (iismod & S3C2412_IISMOD_IMS_SYSMUX) | ||
570 | return i2s->iis_cclk; | ||
571 | else | ||
572 | return i2s->iis_pclk; | ||
573 | } | ||
574 | EXPORT_SYMBOL_GPL(s3c_i2sv2_get_clock); | ||
575 | |||
576 | /* default table of all avaialable root fs divisors */ | ||
577 | static unsigned int iis_fs_tab[] = { 256, 512, 384, 768 }; | ||
578 | |||
579 | int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, | ||
580 | unsigned int *fstab, | ||
581 | unsigned int rate, struct clk *clk) | ||
582 | { | ||
583 | unsigned long clkrate = clk_get_rate(clk); | ||
584 | unsigned int div; | ||
585 | unsigned int fsclk; | ||
586 | unsigned int actual; | ||
587 | unsigned int fs; | ||
588 | unsigned int fsdiv; | ||
589 | signed int deviation = 0; | ||
590 | unsigned int best_fs = 0; | ||
591 | unsigned int best_div = 0; | ||
592 | unsigned int best_rate = 0; | ||
593 | unsigned int best_deviation = INT_MAX; | ||
594 | |||
595 | pr_debug("Input clock rate %ldHz\n", clkrate); | ||
596 | |||
597 | if (fstab == NULL) | ||
598 | fstab = iis_fs_tab; | ||
599 | |||
600 | for (fs = 0; fs < ARRAY_SIZE(iis_fs_tab); fs++) { | ||
601 | fsdiv = iis_fs_tab[fs]; | ||
602 | |||
603 | fsclk = clkrate / fsdiv; | ||
604 | div = fsclk / rate; | ||
605 | |||
606 | if ((fsclk % rate) > (rate / 2)) | ||
607 | div++; | ||
608 | |||
609 | if (div <= 1) | ||
610 | continue; | ||
611 | |||
612 | actual = clkrate / (fsdiv * div); | ||
613 | deviation = actual - rate; | ||
614 | |||
615 | printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n", | ||
616 | fsdiv, div, actual, deviation); | ||
617 | |||
618 | deviation = abs(deviation); | ||
619 | |||
620 | if (deviation < best_deviation) { | ||
621 | best_fs = fsdiv; | ||
622 | best_div = div; | ||
623 | best_rate = actual; | ||
624 | best_deviation = deviation; | ||
625 | } | ||
626 | |||
627 | if (deviation == 0) | ||
628 | break; | ||
629 | } | ||
630 | |||
631 | printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n", | ||
632 | best_fs, best_div, best_rate); | ||
633 | |||
634 | info->fs_div = best_fs; | ||
635 | info->clk_div = best_div; | ||
636 | |||
637 | return 0; | ||
638 | } | ||
639 | EXPORT_SYMBOL_GPL(s3c_i2sv2_iis_calc_rate); | ||
640 | |||
641 | int s3c_i2sv2_probe(struct snd_soc_dai *dai, | ||
642 | struct s3c_i2sv2_info *i2s, | ||
643 | unsigned long base) | ||
644 | { | ||
645 | struct device *dev = dai->dev; | ||
646 | unsigned int iismod; | ||
647 | |||
648 | i2s->dev = dev; | ||
649 | |||
650 | /* record our i2s structure for later use in the callbacks */ | ||
651 | snd_soc_dai_set_drvdata(dai, i2s); | ||
652 | |||
653 | i2s->regs = ioremap(base, 0x100); | ||
654 | if (i2s->regs == NULL) { | ||
655 | dev_err(dev, "cannot ioremap registers\n"); | ||
656 | return -ENXIO; | ||
657 | } | ||
658 | |||
659 | i2s->iis_pclk = clk_get(dev, "iis"); | ||
660 | if (IS_ERR(i2s->iis_pclk)) { | ||
661 | dev_err(dev, "failed to get iis_clock\n"); | ||
662 | iounmap(i2s->regs); | ||
663 | return -ENOENT; | ||
664 | } | ||
665 | |||
666 | clk_enable(i2s->iis_pclk); | ||
667 | |||
668 | /* Mark ourselves as in TXRX mode so we can run through our cleanup | ||
669 | * process without warnings. */ | ||
670 | iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
671 | iismod |= S3C2412_IISMOD_MODE_TXRX; | ||
672 | writel(iismod, i2s->regs + S3C2412_IISMOD); | ||
673 | s3c2412_snd_txctrl(i2s, 0); | ||
674 | s3c2412_snd_rxctrl(i2s, 0); | ||
675 | |||
676 | return 0; | ||
677 | } | ||
678 | EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); | ||
679 | |||
680 | #ifdef CONFIG_PM | ||
681 | static int s3c2412_i2s_suspend(struct snd_soc_dai *dai) | ||
682 | { | ||
683 | struct s3c_i2sv2_info *i2s = to_info(dai); | ||
684 | u32 iismod; | ||
685 | |||
686 | if (dai->active) { | ||
687 | i2s->suspend_iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
688 | i2s->suspend_iiscon = readl(i2s->regs + S3C2412_IISCON); | ||
689 | i2s->suspend_iispsr = readl(i2s->regs + S3C2412_IISPSR); | ||
690 | |||
691 | /* some basic suspend checks */ | ||
692 | |||
693 | iismod = readl(i2s->regs + S3C2412_IISMOD); | ||
694 | |||
695 | if (iismod & S3C2412_IISCON_RXDMA_ACTIVE) | ||
696 | pr_warning("%s: RXDMA active?\n", __func__); | ||
697 | |||
698 | if (iismod & S3C2412_IISCON_TXDMA_ACTIVE) | ||
699 | pr_warning("%s: TXDMA active?\n", __func__); | ||
700 | |||
701 | if (iismod & S3C2412_IISCON_IIS_ACTIVE) | ||
702 | pr_warning("%s: IIS active\n", __func__); | ||
703 | } | ||
704 | |||
705 | return 0; | ||
706 | } | ||
707 | |||
708 | static int s3c2412_i2s_resume(struct snd_soc_dai *dai) | ||
709 | { | ||
710 | struct s3c_i2sv2_info *i2s = to_info(dai); | ||
711 | |||
712 | pr_info("dai_active %d, IISMOD %08x, IISCON %08x\n", | ||
713 | dai->active, i2s->suspend_iismod, i2s->suspend_iiscon); | ||
714 | |||
715 | if (dai->active) { | ||
716 | writel(i2s->suspend_iiscon, i2s->regs + S3C2412_IISCON); | ||
717 | writel(i2s->suspend_iismod, i2s->regs + S3C2412_IISMOD); | ||
718 | writel(i2s->suspend_iispsr, i2s->regs + S3C2412_IISPSR); | ||
719 | |||
720 | writel(S3C2412_IISFIC_RXFLUSH | S3C2412_IISFIC_TXFLUSH, | ||
721 | i2s->regs + S3C2412_IISFIC); | ||
722 | |||
723 | ndelay(250); | ||
724 | writel(0x0, i2s->regs + S3C2412_IISFIC); | ||
725 | } | ||
726 | |||
727 | return 0; | ||
728 | } | ||
729 | #else | ||
730 | #define s3c2412_i2s_suspend NULL | ||
731 | #define s3c2412_i2s_resume NULL | ||
732 | #endif | ||
733 | |||
734 | int s3c_i2sv2_register_dai(struct device *dev, int id, | ||
735 | struct snd_soc_dai_driver *drv) | ||
736 | { | ||
737 | struct snd_soc_dai_ops *ops = drv->ops; | ||
738 | |||
739 | ops->trigger = s3c2412_i2s_trigger; | ||
740 | if (!ops->hw_params) | ||
741 | ops->hw_params = s3c_i2sv2_hw_params; | ||
742 | ops->set_fmt = s3c2412_i2s_set_fmt; | ||
743 | ops->set_clkdiv = s3c2412_i2s_set_clkdiv; | ||
744 | ops->set_sysclk = s3c_i2sv2_set_sysclk; | ||
745 | |||
746 | /* Allow overriding by (for example) IISv4 */ | ||
747 | if (!ops->delay) | ||
748 | ops->delay = s3c2412_i2s_delay; | ||
749 | |||
750 | drv->suspend = s3c2412_i2s_suspend; | ||
751 | drv->resume = s3c2412_i2s_resume; | ||
752 | |||
753 | return snd_soc_register_dai(dev, drv); | ||
754 | } | ||
755 | EXPORT_SYMBOL_GPL(s3c_i2sv2_register_dai); | ||
756 | |||
757 | MODULE_LICENSE("GPL"); | ||