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