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