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