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