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