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