aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/s6000
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/s6000')
-rw-r--r--sound/soc/s6000/Kconfig19
-rw-r--r--sound/soc/s6000/Makefile11
-rw-r--r--sound/soc/s6000/s6000-i2s.c629
-rw-r--r--sound/soc/s6000/s6000-i2s.h25
-rw-r--r--sound/soc/s6000/s6000-pcm.c497
-rw-r--r--sound/soc/s6000/s6000-pcm.h35
-rw-r--r--sound/soc/s6000/s6105-ipcam.c244
7 files changed, 1460 insertions, 0 deletions
diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig
new file mode 100644
index 000000000000..c74eb3d4a47c
--- /dev/null
+++ b/sound/soc/s6000/Kconfig
@@ -0,0 +1,19 @@
1config SND_S6000_SOC
2 tristate "SoC Audio for the Stretch s6000 family"
3 depends on XTENSA_VARIANT_S6000
4 help
5 Say Y or M if you want to add support for codecs attached to
6 s6000 family chips. You will also need to select the platform
7 to support below.
8
9config SND_S6000_SOC_I2S
10 tristate
11
12config SND_S6000_SOC_S6IPCAM
13 tristate "SoC Audio support for Stretch 6105 IP Camera"
14 depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105
15 select SND_S6000_SOC_I2S
16 select SND_SOC_TLV320AIC3X
17 help
18 Say Y if you want to add support for SoC audio on the
19 Stretch s6105 IP Camera Reference Design.
diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile
new file mode 100644
index 000000000000..7a613612e010
--- /dev/null
+++ b/sound/soc/s6000/Makefile
@@ -0,0 +1,11 @@
1# s6000 Platform Support
2snd-soc-s6000-objs := s6000-pcm.o
3snd-soc-s6000-i2s-objs := s6000-i2s.o
4
5obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o
6obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o
7
8# s6105 Machine Support
9snd-soc-s6ipcam-objs := s6105-ipcam.o
10
11obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o
diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c
new file mode 100644
index 000000000000..c5cda187ecab
--- /dev/null
+++ b/sound/soc/s6000/s6000-i2s.c
@@ -0,0 +1,629 @@
1/*
2 * ALSA SoC I2S Audio Layer for the Stretch S6000 family
3 *
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/init.h>
13#include <linux/module.h>
14#include <linux/device.h>
15#include <linux/delay.h>
16#include <linux/clk.h>
17#include <linux/interrupt.h>
18#include <linux/io.h>
19
20#include <sound/core.h>
21#include <sound/pcm.h>
22#include <sound/pcm_params.h>
23#include <sound/initval.h>
24#include <sound/soc.h>
25
26#include "s6000-i2s.h"
27#include "s6000-pcm.h"
28
29struct s6000_i2s_dev {
30 dma_addr_t sifbase;
31 u8 __iomem *scbbase;
32 unsigned int wide;
33 unsigned int channel_in;
34 unsigned int channel_out;
35 unsigned int lines_in;
36 unsigned int lines_out;
37 struct s6000_pcm_dma_params dma_params;
38};
39
40#define S6_I2S_INTERRUPT_STATUS 0x00
41#define S6_I2S_INT_OVERRUN 1
42#define S6_I2S_INT_UNDERRUN 2
43#define S6_I2S_INT_ALIGNMENT 4
44#define S6_I2S_INTERRUPT_ENABLE 0x04
45#define S6_I2S_INTERRUPT_RAW 0x08
46#define S6_I2S_INTERRUPT_CLEAR 0x0C
47#define S6_I2S_INTERRUPT_SET 0x10
48#define S6_I2S_MODE 0x20
49#define S6_I2S_DUAL 0
50#define S6_I2S_WIDE 1
51#define S6_I2S_TX_DEFAULT 0x24
52#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c))
53#define S6_I2S_IN 0
54#define S6_I2S_OUT 1
55#define S6_I2S_UNUSED 2
56#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c))
57#define S6_I2S_DIV_MASK 0x001fff
58#define S6_I2S_16BIT 0x000000
59#define S6_I2S_20BIT 0x002000
60#define S6_I2S_24BIT 0x004000
61#define S6_I2S_32BIT 0x006000
62#define S6_I2S_BITS_MASK 0x006000
63#define S6_I2S_MEM_16BIT 0x000000
64#define S6_I2S_MEM_32BIT 0x008000
65#define S6_I2S_MEM_MASK 0x008000
66#define S6_I2S_CHANNELS_SHIFT 16
67#define S6_I2S_CHANNELS_MASK 0x030000
68#define S6_I2S_SCK_IN 0x000000
69#define S6_I2S_SCK_OUT 0x040000
70#define S6_I2S_SCK_DIR 0x040000
71#define S6_I2S_WS_IN 0x000000
72#define S6_I2S_WS_OUT 0x080000
73#define S6_I2S_WS_DIR 0x080000
74#define S6_I2S_LEFT_FIRST 0x000000
75#define S6_I2S_RIGHT_FIRST 0x100000
76#define S6_I2S_FIRST 0x100000
77#define S6_I2S_CUR_SCK 0x200000
78#define S6_I2S_CUR_WS 0x400000
79#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c))
80#define S6_I2S_DISABLE_IF 0x02
81#define S6_I2S_ENABLE_IF 0x03
82#define S6_I2S_IS_BUSY 0x04
83#define S6_I2S_DMA_ACTIVE 0x08
84#define S6_I2S_IS_ENABLED 0x10
85
86#define S6_I2S_NUM_LINES 4
87
88#define S6_I2S_SIF_PORT0 0x0000000
89#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */
90
91static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)
92{
93 writel(val, dev->scbbase + reg);
94}
95
96static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)
97{
98 return readl(dev->scbbase + reg);
99}
100
101static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,
102 u32 mask, u32 val)
103{
104 val ^= s6_i2s_read_reg(dev, reg) & ~mask;
105 s6_i2s_write_reg(dev, reg, val);
106}
107
108static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)
109{
110 int i, j, cur, prev;
111
112 /*
113 * Wait for WCLK to toggle 5 times before enabling the channel
114 * s6000 Family Datasheet 3.6.4:
115 * "At least two cycles of WS must occur between commands
116 * to disable or enable the interface"
117 */
118 j = 0;
119 prev = ~S6_I2S_CUR_WS;
120 for (i = 1000000; --i && j < 6; ) {
121 cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))
122 & S6_I2S_CUR_WS;
123 if (prev != cur) {
124 prev = cur;
125 j++;
126 }
127 }
128 if (j < 6)
129 printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");
130
131 s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);
132}
133
134static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)
135{
136 s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);
137}
138
139static void s6000_i2s_start(struct snd_pcm_substream *substream)
140{
141 struct snd_soc_pcm_runtime *rtd = substream->private_data;
142 struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;
143 int channel;
144
145 channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
146 dev->channel_out : dev->channel_in;
147
148 s6000_i2s_start_channel(dev, channel);
149}
150
151static void s6000_i2s_stop(struct snd_pcm_substream *substream)
152{
153 struct snd_soc_pcm_runtime *rtd = substream->private_data;
154 struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data;
155 int channel;
156
157 channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
158 dev->channel_out : dev->channel_in;
159
160 s6000_i2s_stop_channel(dev, channel);
161}
162
163static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
164 int after)
165{
166 switch (cmd) {
167 case SNDRV_PCM_TRIGGER_START:
168 case SNDRV_PCM_TRIGGER_RESUME:
169 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
170 if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)
171 s6000_i2s_start(substream);
172 break;
173 case SNDRV_PCM_TRIGGER_STOP:
174 case SNDRV_PCM_TRIGGER_SUSPEND:
175 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
176 if (!after)
177 s6000_i2s_stop(substream);
178 }
179 return 0;
180}
181
182static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)
183{
184 unsigned int pending;
185 pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);
186 pending &= S6_I2S_INT_ALIGNMENT |
187 S6_I2S_INT_UNDERRUN |
188 S6_I2S_INT_OVERRUN;
189 s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);
190
191 return pending;
192}
193
194static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)
195{
196 struct s6000_i2s_dev *dev = cpu_dai->private_data;
197 unsigned int errors;
198 unsigned int ret;
199
200 errors = s6000_i2s_int_sources(dev);
201 if (likely(!errors))
202 return 0;
203
204 ret = 0;
205 if (errors & S6_I2S_INT_ALIGNMENT)
206 printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");
207 if (errors & S6_I2S_INT_UNDERRUN)
208 ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;
209 if (errors & S6_I2S_INT_OVERRUN)
210 ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;
211 return ret;
212}
213
214static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)
215{
216 int channel;
217 int n = 50;
218 for (channel = 0; channel < 2; channel++) {
219 while (--n >= 0) {
220 int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));
221 if ((v & S6_I2S_IS_ENABLED)
222 || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))
223 break;
224 udelay(20);
225 }
226 }
227 if (n < 0)
228 printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");
229}
230
231static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
232 unsigned int fmt)
233{
234 struct s6000_i2s_dev *dev = cpu_dai->private_data;
235 u32 w;
236
237 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
238 case SND_SOC_DAIFMT_CBM_CFM:
239 w = S6_I2S_SCK_IN | S6_I2S_WS_IN;
240 break;
241 case SND_SOC_DAIFMT_CBS_CFM:
242 w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;
243 break;
244 case SND_SOC_DAIFMT_CBM_CFS:
245 w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;
246 break;
247 case SND_SOC_DAIFMT_CBS_CFS:
248 w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;
249 break;
250 default:
251 return -EINVAL;
252 }
253
254 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
255 case SND_SOC_DAIFMT_NB_NF:
256 w |= S6_I2S_LEFT_FIRST;
257 break;
258 case SND_SOC_DAIFMT_NB_IF:
259 w |= S6_I2S_RIGHT_FIRST;
260 break;
261 default:
262 return -EINVAL;
263 }
264
265 s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),
266 S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
267 s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),
268 S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
269
270 return 0;
271}
272
273static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
274{
275 struct s6000_i2s_dev *dev = dai->private_data;
276
277 if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)
278 return -EINVAL;
279
280 s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),
281 S6_I2S_DIV_MASK, div / 2 - 1);
282 return 0;
283}
284
285static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,
286 struct snd_pcm_hw_params *params,
287 struct snd_soc_dai *dai)
288{
289 struct s6000_i2s_dev *dev = dai->private_data;
290 int interf;
291 u32 w = 0;
292
293 if (dev->wide)
294 interf = 0;
295 else {
296 w |= (((params_channels(params) - 2) / 2)
297 << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;
298 interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
299 ? dev->channel_out : dev->channel_in;
300 }
301
302 switch (params_format(params)) {
303 case SNDRV_PCM_FORMAT_S16_LE:
304 w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;
305 break;
306 case SNDRV_PCM_FORMAT_S32_LE:
307 w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;
308 break;
309 default:
310 printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",
311 params_format(params));
312 return -EINVAL;
313 }
314
315 if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))
316 & S6_I2S_IS_ENABLED) {
317 printk(KERN_ERR "s6000-i2s: interface already enabled\n");
318 return -EBUSY;
319 }
320
321 s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),
322 S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,
323 w);
324
325 return 0;
326}
327
328static int s6000_i2s_dai_probe(struct platform_device *pdev,
329 struct snd_soc_dai *dai)
330{
331 struct s6000_i2s_dev *dev = dai->private_data;
332 struct s6000_snd_platform_data *pdata = pdev->dev.platform_data;
333
334 if (!pdata)
335 return -EINVAL;
336
337 dev->wide = pdata->wide;
338 dev->channel_in = pdata->channel_in;
339 dev->channel_out = pdata->channel_out;
340 dev->lines_in = pdata->lines_in;
341 dev->lines_out = pdata->lines_out;
342
343 s6_i2s_write_reg(dev, S6_I2S_MODE,
344 dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);
345
346 if (dev->wide) {
347 int i;
348
349 if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)
350 return -EINVAL;
351
352 dev->channel_in = 0;
353 dev->channel_out = 1;
354 dai->capture.channels_min = 2 * dev->lines_in;
355 dai->capture.channels_max = dai->capture.channels_min;
356 dai->playback.channels_min = 2 * dev->lines_out;
357 dai->playback.channels_max = dai->playback.channels_min;
358
359 for (i = 0; i < dev->lines_out; i++)
360 s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);
361
362 for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)
363 s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),
364 S6_I2S_UNUSED);
365
366 for (; i < S6_I2S_NUM_LINES; i++)
367 s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);
368 } else {
369 unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};
370
371 if (dev->lines_in > 1 || dev->lines_out > 1)
372 return -EINVAL;
373
374 dai->capture.channels_min = 2 * dev->lines_in;
375 dai->capture.channels_max = 8 * dev->lines_in;
376 dai->playback.channels_min = 2 * dev->lines_out;
377 dai->playback.channels_max = 8 * dev->lines_out;
378
379 if (dev->lines_in)
380 cfg[dev->channel_in] = S6_I2S_IN;
381 if (dev->lines_out)
382 cfg[dev->channel_out] = S6_I2S_OUT;
383
384 s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);
385 s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);
386 }
387
388 if (dev->lines_out) {
389 if (dev->lines_in) {
390 if (!dev->dma_params.dma_out)
391 return -ENODEV;
392 } else {
393 dev->dma_params.dma_out = dev->dma_params.dma_in;
394 dev->dma_params.dma_in = 0;
395 }
396 }
397 dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?
398 S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
399 dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?
400 S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
401 dev->dma_params.same_rate = pdata->same_rate | pdata->wide;
402 return 0;
403}
404
405#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
406 SNDRV_PCM_RATE_8000_192000)
407#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
408
409static struct snd_soc_dai_ops s6000_i2s_dai_ops = {
410 .set_fmt = s6000_i2s_set_dai_fmt,
411 .set_clkdiv = s6000_i2s_set_clkdiv,
412 .hw_params = s6000_i2s_hw_params,
413};
414
415struct snd_soc_dai s6000_i2s_dai = {
416 .name = "s6000-i2s",
417 .id = 0,
418 .probe = s6000_i2s_dai_probe,
419 .playback = {
420 .channels_min = 2,
421 .channels_max = 8,
422 .formats = S6000_I2S_FORMATS,
423 .rates = S6000_I2S_RATES,
424 .rate_min = 0,
425 .rate_max = 1562500,
426 },
427 .capture = {
428 .channels_min = 2,
429 .channels_max = 8,
430 .formats = S6000_I2S_FORMATS,
431 .rates = S6000_I2S_RATES,
432 .rate_min = 0,
433 .rate_max = 1562500,
434 },
435 .ops = &s6000_i2s_dai_ops,
436}
437EXPORT_SYMBOL_GPL(s6000_i2s_dai);
438
439static int __devinit s6000_i2s_probe(struct platform_device *pdev)
440{
441 struct s6000_i2s_dev *dev;
442 struct resource *scbmem, *sifmem, *region, *dma1, *dma2;
443 u8 __iomem *mmio;
444 int ret;
445
446 scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
447 if (!scbmem) {
448 dev_err(&pdev->dev, "no mem resource?\n");
449 ret = -ENODEV;
450 goto err_release_none;
451 }
452
453 region = request_mem_region(scbmem->start,
454 scbmem->end - scbmem->start + 1,
455 pdev->name);
456 if (!region) {
457 dev_err(&pdev->dev, "I2S SCB region already claimed\n");
458 ret = -EBUSY;
459 goto err_release_none;
460 }
461
462 mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1);
463 if (!mmio) {
464 dev_err(&pdev->dev, "can't ioremap SCB region\n");
465 ret = -ENOMEM;
466 goto err_release_scb;
467 }
468
469 sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
470 if (!sifmem) {
471 dev_err(&pdev->dev, "no second mem resource?\n");
472 ret = -ENODEV;
473 goto err_release_map;
474 }
475
476 region = request_mem_region(sifmem->start,
477 sifmem->end - sifmem->start + 1,
478 pdev->name);
479 if (!region) {
480 dev_err(&pdev->dev, "I2S SIF region already claimed\n");
481 ret = -EBUSY;
482 goto err_release_map;
483 }
484
485 dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);
486 if (!dma1) {
487 dev_err(&pdev->dev, "no dma resource?\n");
488 ret = -ENODEV;
489 goto err_release_sif;
490 }
491
492 region = request_mem_region(dma1->start, dma1->end - dma1->start + 1,
493 pdev->name);
494 if (!region) {
495 dev_err(&pdev->dev, "I2S DMA region already claimed\n");
496 ret = -EBUSY;
497 goto err_release_sif;
498 }
499
500 dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
501 if (dma2) {
502 region = request_mem_region(dma2->start,
503 dma2->end - dma2->start + 1,
504 pdev->name);
505 if (!region) {
506 dev_err(&pdev->dev,
507 "I2S DMA region already claimed\n");
508 ret = -EBUSY;
509 goto err_release_dma1;
510 }
511 }
512
513 dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);
514 if (!dev) {
515 ret = -ENOMEM;
516 goto err_release_dma2;
517 }
518
519 s6000_i2s_dai.dev = &pdev->dev;
520 s6000_i2s_dai.private_data = dev;
521 s6000_i2s_dai.dma_data = &dev->dma_params;
522
523 dev->sifbase = sifmem->start;
524 dev->scbbase = mmio;
525
526 s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
527 s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,
528 S6_I2S_INT_ALIGNMENT |
529 S6_I2S_INT_UNDERRUN |
530 S6_I2S_INT_OVERRUN);
531
532 s6000_i2s_stop_channel(dev, 0);
533 s6000_i2s_stop_channel(dev, 1);
534 s6000_i2s_wait_disabled(dev);
535
536 dev->dma_params.check_xrun = s6000_i2s_check_xrun;
537 dev->dma_params.trigger = s6000_i2s_trigger;
538 dev->dma_params.dma_in = dma1->start;
539 dev->dma_params.dma_out = dma2 ? dma2->start : 0;
540 dev->dma_params.irq = platform_get_irq(pdev, 0);
541 if (dev->dma_params.irq < 0) {
542 dev_err(&pdev->dev, "no irq resource?\n");
543 ret = -ENODEV;
544 goto err_release_dev;
545 }
546
547 s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,
548 S6_I2S_INT_ALIGNMENT |
549 S6_I2S_INT_UNDERRUN |
550 S6_I2S_INT_OVERRUN);
551
552 ret = snd_soc_register_dai(&s6000_i2s_dai);
553 if (ret)
554 goto err_release_dev;
555
556 return 0;
557
558err_release_dev:
559 kfree(dev);
560err_release_dma2:
561 if (dma2)
562 release_mem_region(dma2->start, dma2->end - dma2->start + 1);
563err_release_dma1:
564 release_mem_region(dma1->start, dma1->end - dma1->start + 1);
565err_release_sif:
566 release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1);
567err_release_map:
568 iounmap(mmio);
569err_release_scb:
570 release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1);
571err_release_none:
572 return ret;
573}
574
575static void __devexit s6000_i2s_remove(struct platform_device *pdev)
576{
577 struct s6000_i2s_dev *dev = s6000_i2s_dai.private_data;
578 struct resource *region;
579 void __iomem *mmio = dev->scbbase;
580
581 snd_soc_unregister_dai(&s6000_i2s_dai);
582
583 s6000_i2s_stop_channel(dev, 0);
584 s6000_i2s_stop_channel(dev, 1);
585
586 s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
587 s6000_i2s_dai.private_data = 0;
588 kfree(dev);
589
590 region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
591 release_mem_region(region->start, region->end - region->start + 1);
592
593 region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
594 if (region)
595 release_mem_region(region->start,
596 region->end - region->start + 1);
597
598 region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
599 release_mem_region(region->start, (region->end - region->start) + 1);
600
601 iounmap(mmio);
602 region = platform_get_resource(pdev, IORESOURCE_IO, 0);
603 release_mem_region(region->start, (region->end - region->start) + 1);
604}
605
606static struct platform_driver s6000_i2s_driver = {
607 .probe = s6000_i2s_probe,
608 .remove = __devexit_p(s6000_i2s_remove),
609 .driver = {
610 .name = "s6000-i2s",
611 .owner = THIS_MODULE,
612 },
613};
614
615static int __init s6000_i2s_init(void)
616{
617 return platform_driver_register(&s6000_i2s_driver);
618}
619module_init(s6000_i2s_init);
620
621static void __exit s6000_i2s_exit(void)
622{
623 platform_driver_unregister(&s6000_i2s_driver);
624}
625module_exit(s6000_i2s_exit);
626
627MODULE_AUTHOR("Daniel Gloeckner");
628MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");
629MODULE_LICENSE("GPL");
diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.h
new file mode 100644
index 000000000000..2375fdfe6dba
--- /dev/null
+++ b/sound/soc/s6000/s6000-i2s.h
@@ -0,0 +1,25 @@
1/*
2 * ALSA SoC I2S Audio Layer for the Stretch s6000 family
3 *
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#ifndef _S6000_I2S_H
13#define _S6000_I2S_H
14
15extern struct snd_soc_dai s6000_i2s_dai;
16
17struct s6000_snd_platform_data {
18 int lines_in;
19 int lines_out;
20 int channel_in;
21 int channel_out;
22 int wide;
23 int same_rate;
24};
25#endif
diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c
new file mode 100644
index 000000000000..83b8028e209d
--- /dev/null
+++ b/sound/soc/s6000/s6000-pcm.c
@@ -0,0 +1,497 @@
1/*
2 * ALSA PCM interface for the Stetch s6000 family
3 *
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/module.h>
13#include <linux/init.h>
14#include <linux/platform_device.h>
15#include <linux/slab.h>
16#include <linux/dma-mapping.h>
17#include <linux/interrupt.h>
18
19#include <sound/core.h>
20#include <sound/pcm.h>
21#include <sound/pcm_params.h>
22#include <sound/soc.h>
23
24#include <asm/dma.h>
25#include <variant/dmac.h>
26
27#include "s6000-pcm.h"
28
29#define S6_PCM_PREALLOCATE_SIZE (96 * 1024)
30#define S6_PCM_PREALLOCATE_MAX (2048 * 1024)
31
32static struct snd_pcm_hardware s6000_pcm_hardware = {
33 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
34 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
35 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX),
36 .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE),
37 .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
38 SNDRV_PCM_RATE_8000_192000),
39 .rate_min = 0,
40 .rate_max = 1562500,
41 .channels_min = 2,
42 .channels_max = 8,
43 .buffer_bytes_max = 0x7ffffff0,
44 .period_bytes_min = 16,
45 .period_bytes_max = 0xfffff0,
46 .periods_min = 2,
47 .periods_max = 1024, /* no limit */
48 .fifo_size = 0,
49};
50
51struct s6000_runtime_data {
52 spinlock_t lock;
53 int period; /* current DMA period */
54};
55
56static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream)
57{
58 struct snd_pcm_runtime *runtime = substream->runtime;
59 struct s6000_runtime_data *prtd = runtime->private_data;
60 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
61 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
62 int channel;
63 unsigned int period_size;
64 unsigned int dma_offset;
65 dma_addr_t dma_pos;
66 dma_addr_t src, dst;
67
68 period_size = snd_pcm_lib_period_bytes(substream);
69 dma_offset = prtd->period * period_size;
70 dma_pos = runtime->dma_addr + dma_offset;
71
72 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
73 src = dma_pos;
74 dst = par->sif_out;
75 channel = par->dma_out;
76 } else {
77 src = par->sif_in;
78 dst = dma_pos;
79 channel = par->dma_in;
80 }
81
82 if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel),
83 DMA_INDEX_CHNL(channel)))
84 return;
85
86 if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) {
87 printk(KERN_ERR "s6000-pcm: fifo full\n");
88 return;
89 }
90
91 BUG_ON(period_size & 15);
92 s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel),
93 src, dst, period_size);
94
95 prtd->period++;
96 if (unlikely(prtd->period >= runtime->periods))
97 prtd->period = 0;
98}
99
100static irqreturn_t s6000_pcm_irq(int irq, void *data)
101{
102 struct snd_pcm *pcm = data;
103 struct snd_soc_pcm_runtime *runtime = pcm->private_data;
104 struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
105 struct s6000_runtime_data *prtd;
106 unsigned int has_xrun;
107 int i, ret = IRQ_NONE;
108 u32 channel[2] = {
109 [SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out,
110 [SNDRV_PCM_STREAM_CAPTURE] = params->dma_in
111 };
112
113 has_xrun = params->check_xrun(runtime->dai->cpu_dai);
114
115 for (i = 0; i < ARRAY_SIZE(channel); ++i) {
116 struct snd_pcm_substream *substream = pcm->streams[i].substream;
117 unsigned int pending;
118
119 if (!channel[i])
120 continue;
121
122 if (unlikely(has_xrun & (1 << i)) &&
123 substream->runtime &&
124 snd_pcm_running(substream)) {
125 dev_dbg(pcm->dev, "xrun\n");
126 snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
127 ret = IRQ_HANDLED;
128 }
129
130 pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]),
131 DMA_INDEX_CHNL(channel[i]));
132
133 if (pending & 1) {
134 ret = IRQ_HANDLED;
135 if (likely(substream->runtime &&
136 snd_pcm_running(substream))) {
137 snd_pcm_period_elapsed(substream);
138 dev_dbg(pcm->dev, "period elapsed %x %x\n",
139 s6dmac_cur_src(DMA_MASK_DMAC(channel[i]),
140 DMA_INDEX_CHNL(channel[i])),
141 s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]),
142 DMA_INDEX_CHNL(channel[i])));
143 prtd = substream->runtime->private_data;
144 spin_lock(&prtd->lock);
145 s6000_pcm_enqueue_dma(substream);
146 spin_unlock(&prtd->lock);
147 }
148 }
149
150 if (unlikely(pending & ~7)) {
151 if (pending & (1 << 3))
152 printk(KERN_WARNING
153 "s6000-pcm: DMA %x Underflow\n",
154 channel[i]);
155 if (pending & (1 << 4))
156 printk(KERN_WARNING
157 "s6000-pcm: DMA %x Overflow\n",
158 channel[i]);
159 if (pending & 0x1e0)
160 printk(KERN_WARNING
161 "s6000-pcm: DMA %x Master Error "
162 "(mask %x)\n",
163 channel[i], pending >> 5);
164
165 }
166 }
167
168 return ret;
169}
170
171static int s6000_pcm_start(struct snd_pcm_substream *substream)
172{
173 struct s6000_runtime_data *prtd = substream->runtime->private_data;
174 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
175 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
176 unsigned long flags;
177 int srcinc;
178 u32 dma;
179
180 spin_lock_irqsave(&prtd->lock, flags);
181
182 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
183 srcinc = 1;
184 dma = par->dma_out;
185 } else {
186 srcinc = 0;
187 dma = par->dma_in;
188 }
189 s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma),
190 1 /* priority 1 (0 is max) */,
191 0 /* peripheral requests w/o xfer length mode */,
192 srcinc /* source address increment */,
193 srcinc^1 /* destination address increment */,
194 0 /* chunksize 0 (skip impossible on this dma) */,
195 0 /* source skip after chunk (impossible) */,
196 0 /* destination skip after chunk (impossible) */,
197 4 /* 16 byte burst size */,
198 -1 /* don't conserve bandwidth */,
199 0 /* low watermark irq descriptor theshold */,
200 0 /* disable hardware timestamps */,
201 1 /* enable channel */);
202
203 s6000_pcm_enqueue_dma(substream);
204 s6000_pcm_enqueue_dma(substream);
205
206 spin_unlock_irqrestore(&prtd->lock, flags);
207
208 return 0;
209}
210
211static int s6000_pcm_stop(struct snd_pcm_substream *substream)
212{
213 struct s6000_runtime_data *prtd = substream->runtime->private_data;
214 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
215 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
216 unsigned long flags;
217 u32 channel;
218
219 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
220 channel = par->dma_out;
221 else
222 channel = par->dma_in;
223
224 s6dmac_set_terminal_count(DMA_MASK_DMAC(channel),
225 DMA_INDEX_CHNL(channel), 0);
226
227 spin_lock_irqsave(&prtd->lock, flags);
228
229 s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel));
230
231 spin_unlock_irqrestore(&prtd->lock, flags);
232
233 return 0;
234}
235
236static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
237{
238 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
239 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
240 int ret;
241
242 ret = par->trigger(substream, cmd, 0);
243 if (ret < 0)
244 return ret;
245
246 switch (cmd) {
247 case SNDRV_PCM_TRIGGER_START:
248 case SNDRV_PCM_TRIGGER_RESUME:
249 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
250 ret = s6000_pcm_start(substream);
251 break;
252 case SNDRV_PCM_TRIGGER_STOP:
253 case SNDRV_PCM_TRIGGER_SUSPEND:
254 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
255 ret = s6000_pcm_stop(substream);
256 break;
257 default:
258 ret = -EINVAL;
259 }
260 if (ret < 0)
261 return ret;
262
263 return par->trigger(substream, cmd, 1);
264}
265
266static int s6000_pcm_prepare(struct snd_pcm_substream *substream)
267{
268 struct s6000_runtime_data *prtd = substream->runtime->private_data;
269
270 prtd->period = 0;
271
272 return 0;
273}
274
275static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream)
276{
277 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
278 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
279 struct snd_pcm_runtime *runtime = substream->runtime;
280 struct s6000_runtime_data *prtd = runtime->private_data;
281 unsigned long flags;
282 unsigned int offset;
283 dma_addr_t count;
284
285 spin_lock_irqsave(&prtd->lock, flags);
286
287 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
288 count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out),
289 DMA_INDEX_CHNL(par->dma_out));
290 else
291 count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in),
292 DMA_INDEX_CHNL(par->dma_in));
293
294 count -= runtime->dma_addr;
295
296 spin_unlock_irqrestore(&prtd->lock, flags);
297
298 offset = bytes_to_frames(runtime, count);
299 if (unlikely(offset >= runtime->buffer_size))
300 offset = 0;
301
302 return offset;
303}
304
305static int s6000_pcm_open(struct snd_pcm_substream *substream)
306{
307 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
308 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
309 struct snd_pcm_runtime *runtime = substream->runtime;
310 struct s6000_runtime_data *prtd;
311 int ret;
312
313 snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware);
314
315 ret = snd_pcm_hw_constraint_step(runtime, 0,
316 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16);
317 if (ret < 0)
318 return ret;
319 ret = snd_pcm_hw_constraint_step(runtime, 0,
320 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16);
321 if (ret < 0)
322 return ret;
323 ret = snd_pcm_hw_constraint_integer(runtime,
324 SNDRV_PCM_HW_PARAM_PERIODS);
325 if (ret < 0)
326 return ret;
327
328 if (par->same_rate) {
329 int rate;
330 spin_lock(&par->lock); /* needed? */
331 rate = par->rate;
332 spin_unlock(&par->lock);
333 if (rate != -1) {
334 ret = snd_pcm_hw_constraint_minmax(runtime,
335 SNDRV_PCM_HW_PARAM_RATE,
336 rate, rate);
337 if (ret < 0)
338 return ret;
339 }
340 }
341
342 prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL);
343 if (prtd == NULL)
344 return -ENOMEM;
345
346 spin_lock_init(&prtd->lock);
347
348 runtime->private_data = prtd;
349
350 return 0;
351}
352
353static int s6000_pcm_close(struct snd_pcm_substream *substream)
354{
355 struct snd_pcm_runtime *runtime = substream->runtime;
356 struct s6000_runtime_data *prtd = runtime->private_data;
357
358 kfree(prtd);
359
360 return 0;
361}
362
363static int s6000_pcm_hw_params(struct snd_pcm_substream *substream,
364 struct snd_pcm_hw_params *hw_params)
365{
366 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
367 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
368 int ret;
369 ret = snd_pcm_lib_malloc_pages(substream,
370 params_buffer_bytes(hw_params));
371 if (ret < 0) {
372 printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n");
373 return ret;
374 }
375
376 if (par->same_rate) {
377 spin_lock(&par->lock);
378 if (par->rate == -1 ||
379 !(par->in_use & ~(1 << substream->stream))) {
380 par->rate = params_rate(hw_params);
381 par->in_use |= 1 << substream->stream;
382 } else if (params_rate(hw_params) != par->rate) {
383 snd_pcm_lib_free_pages(substream);
384 par->in_use &= ~(1 << substream->stream);
385 ret = -EBUSY;
386 }
387 spin_unlock(&par->lock);
388 }
389 return ret;
390}
391
392static int s6000_pcm_hw_free(struct snd_pcm_substream *substream)
393{
394 struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
395 struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
396
397 spin_lock(&par->lock);
398 par->in_use &= ~(1 << substream->stream);
399 if (!par->in_use)
400 par->rate = -1;
401 spin_unlock(&par->lock);
402
403 return snd_pcm_lib_free_pages(substream);
404}
405
406static struct snd_pcm_ops s6000_pcm_ops = {
407 .open = s6000_pcm_open,
408 .close = s6000_pcm_close,
409 .ioctl = snd_pcm_lib_ioctl,
410 .hw_params = s6000_pcm_hw_params,
411 .hw_free = s6000_pcm_hw_free,
412 .trigger = s6000_pcm_trigger,
413 .prepare = s6000_pcm_prepare,
414 .pointer = s6000_pcm_pointer,
415};
416
417static void s6000_pcm_free(struct snd_pcm *pcm)
418{
419 struct snd_soc_pcm_runtime *runtime = pcm->private_data;
420 struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
421
422 free_irq(params->irq, pcm);
423 snd_pcm_lib_preallocate_free_for_all(pcm);
424}
425
426static u64 s6000_pcm_dmamask = DMA_32BIT_MASK;
427
428static int s6000_pcm_new(struct snd_card *card,
429 struct snd_soc_dai *dai, struct snd_pcm *pcm)
430{
431 struct snd_soc_pcm_runtime *runtime = pcm->private_data;
432 struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data;
433 int res;
434
435 if (!card->dev->dma_mask)
436 card->dev->dma_mask = &s6000_pcm_dmamask;
437 if (!card->dev->coherent_dma_mask)
438 card->dev->coherent_dma_mask = DMA_32BIT_MASK;
439
440 if (params->dma_in) {
441 s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in),
442 DMA_INDEX_CHNL(params->dma_in));
443 s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in),
444 DMA_INDEX_CHNL(params->dma_in));
445 }
446
447 if (params->dma_out) {
448 s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out),
449 DMA_INDEX_CHNL(params->dma_out));
450 s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out),
451 DMA_INDEX_CHNL(params->dma_out));
452 }
453
454 res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED,
455 s6000_soc_platform.name, pcm);
456 if (res) {
457 printk(KERN_ERR "s6000-pcm couldn't get IRQ\n");
458 return res;
459 }
460
461 res = snd_pcm_lib_preallocate_pages_for_all(pcm,
462 SNDRV_DMA_TYPE_DEV,
463 card->dev,
464 S6_PCM_PREALLOCATE_SIZE,
465 S6_PCM_PREALLOCATE_MAX);
466 if (res)
467 printk(KERN_WARNING "s6000-pcm: preallocation failed\n");
468
469 spin_lock_init(&params->lock);
470 params->in_use = 0;
471 params->rate = -1;
472 return 0;
473}
474
475struct snd_soc_platform s6000_soc_platform = {
476 .name = "s6000-audio",
477 .pcm_ops = &s6000_pcm_ops,
478 .pcm_new = s6000_pcm_new,
479 .pcm_free = s6000_pcm_free,
480};
481EXPORT_SYMBOL_GPL(s6000_soc_platform);
482
483static int __init s6000_pcm_init(void)
484{
485 return snd_soc_register_platform(&s6000_soc_platform);
486}
487module_init(s6000_pcm_init);
488
489static void __exit s6000_pcm_exit(void)
490{
491 snd_soc_unregister_platform(&s6000_soc_platform);
492}
493module_exit(s6000_pcm_exit);
494
495MODULE_AUTHOR("Daniel Gloeckner");
496MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module");
497MODULE_LICENSE("GPL");
diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.h
new file mode 100644
index 000000000000..96f23f6f52bf
--- /dev/null
+++ b/sound/soc/s6000/s6000-pcm.h
@@ -0,0 +1,35 @@
1/*
2 * ALSA PCM interface for the Stretch s6000 family
3 *
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#ifndef _S6000_PCM_H
13#define _S6000_PCM_H
14
15struct snd_soc_dai;
16struct snd_pcm_substream;
17
18struct s6000_pcm_dma_params {
19 unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai);
20 int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after);
21 dma_addr_t sif_in;
22 dma_addr_t sif_out;
23 u32 dma_in;
24 u32 dma_out;
25 int irq;
26 int same_rate;
27
28 spinlock_t lock;
29 int in_use;
30 int rate;
31};
32
33extern struct snd_soc_platform s6000_soc_platform;
34
35#endif
diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c
new file mode 100644
index 000000000000..b5f95f9781c1
--- /dev/null
+++ b/sound/soc/s6000/s6105-ipcam.c
@@ -0,0 +1,244 @@
1/*
2 * ASoC driver for Stretch s6105 IP camera platform
3 *
4 * Author: Daniel Gloeckner, <dg@emlix.com>
5 * Copyright: (C) 2009 emlix GmbH <info@emlix.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
12#include <linux/module.h>
13#include <linux/moduleparam.h>
14#include <linux/timer.h>
15#include <linux/interrupt.h>
16#include <linux/platform_device.h>
17#include <sound/core.h>
18#include <sound/pcm.h>
19#include <sound/soc.h>
20#include <sound/soc-dapm.h>
21
22#include <variant/dmac.h>
23
24#include "../codecs/tlv320aic3x.h"
25#include "s6000-pcm.h"
26#include "s6000-i2s.h"
27
28#define S6105_CAM_CODEC_CLOCK 12288000
29
30static int s6105_hw_params(struct snd_pcm_substream *substream,
31 struct snd_pcm_hw_params *params)
32{
33 struct snd_soc_pcm_runtime *rtd = substream->private_data;
34 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
35 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
36 int ret = 0;
37
38 /* set codec DAI configuration */
39 ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
40 SND_SOC_DAIFMT_CBM_CFM);
41 if (ret < 0)
42 return ret;
43
44 /* set cpu DAI configuration */
45 ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM |
46 SND_SOC_DAIFMT_NB_NF);
47 if (ret < 0)
48 return ret;
49
50 /* set the codec system clock */
51 ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CAM_CODEC_CLOCK,
52 SND_SOC_CLOCK_OUT);
53 if (ret < 0)
54 return ret;
55
56 return 0;
57}
58
59static struct snd_soc_ops s6105_ops = {
60 .hw_params = s6105_hw_params,
61};
62
63/* s6105 machine dapm widgets */
64static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = {
65 SND_SOC_DAPM_LINE("Audio Out Differential", NULL),
66 SND_SOC_DAPM_LINE("Audio Out Stereo", NULL),
67 SND_SOC_DAPM_LINE("Audio In", NULL),
68};
69
70/* s6105 machine audio_mapnections to the codec pins */
71static const struct snd_soc_dapm_route audio_map[] = {
72 /* Audio Out connected to HPLOUT, HPLCOM, HPROUT */
73 {"Audio Out Differential", NULL, "HPLOUT"},
74 {"Audio Out Differential", NULL, "HPLCOM"},
75 {"Audio Out Stereo", NULL, "HPLOUT"},
76 {"Audio Out Stereo", NULL, "HPROUT"},
77
78 /* Audio In connected to LINE1L, LINE1R */
79 {"LINE1L", NULL, "Audio In"},
80 {"LINE1R", NULL, "Audio In"},
81};
82
83static int output_type_info(struct snd_kcontrol *kcontrol,
84 struct snd_ctl_elem_info *uinfo)
85{
86 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
87 uinfo->count = 1;
88 uinfo->value.enumerated.items = 2;
89 if (uinfo->value.enumerated.item) {
90 uinfo->value.enumerated.item = 1;
91 strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT");
92 } else {
93 strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM");
94 }
95 return 0;
96}
97
98static int output_type_get(struct snd_kcontrol *kcontrol,
99 struct snd_ctl_elem_value *ucontrol)
100{
101 ucontrol->value.enumerated.item[0] = kcontrol->private_value;
102 return 0;
103}
104
105static int output_type_put(struct snd_kcontrol *kcontrol,
106 struct snd_ctl_elem_value *ucontrol)
107{
108 struct snd_soc_codec *codec = kcontrol->private_data;
109 unsigned int val = (ucontrol->value.enumerated.item[0] != 0);
110 char *differential = "Audio Out Differential";
111 char *stereo = "Audio Out Stereo";
112
113 if (kcontrol->private_value == val)
114 return 0;
115 kcontrol->private_value = val;
116 snd_soc_dapm_disable_pin(codec, val ? differential : stereo);
117 snd_soc_dapm_sync(codec);
118 snd_soc_dapm_enable_pin(codec, val ? stereo : differential);
119 snd_soc_dapm_sync(codec);
120
121 return 1;
122}
123
124static const struct snd_kcontrol_new audio_out_mux = {
125 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
126 .name = "Master Output Mux",
127 .index = 0,
128 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
129 .info = output_type_info,
130 .get = output_type_get,
131 .put = output_type_put,
132 .private_value = 1 /* default to stereo */
133};
134
135/* Logic for a aic3x as connected on the s6105 ip camera ref design */
136static int s6105_aic3x_init(struct snd_soc_codec *codec)
137{
138 /* Add s6105 specific widgets */
139 snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets,
140 ARRAY_SIZE(aic3x_dapm_widgets));
141
142 /* Set up s6105 specific audio path audio_map */
143 snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
144
145 /* not present */
146 snd_soc_dapm_nc_pin(codec, "MONO_LOUT");
147 snd_soc_dapm_nc_pin(codec, "LINE2L");
148 snd_soc_dapm_nc_pin(codec, "LINE2R");
149
150 /* not connected */
151 snd_soc_dapm_nc_pin(codec, "MIC3L"); /* LINE2L on this chip */
152 snd_soc_dapm_nc_pin(codec, "MIC3R"); /* LINE2R on this chip */
153 snd_soc_dapm_nc_pin(codec, "LLOUT");
154 snd_soc_dapm_nc_pin(codec, "RLOUT");
155 snd_soc_dapm_nc_pin(codec, "HPRCOM");
156
157 /* always connected */
158 snd_soc_dapm_enable_pin(codec, "Audio In");
159
160 /* must correspond to audio_out_mux.private_value initializer */
161 snd_soc_dapm_disable_pin(codec, "Audio Out Differential");
162 snd_soc_dapm_sync(codec);
163 snd_soc_dapm_enable_pin(codec, "Audio Out Stereo");
164
165 snd_soc_dapm_sync(codec);
166
167 snd_ctl_add(codec->card, snd_ctl_new1(&audio_out_mux, codec));
168
169 return 0;
170}
171
172/* s6105 digital audio interface glue - connects codec <--> CPU */
173static struct snd_soc_dai_link s6105_dai = {
174 .name = "TLV320AIC31",
175 .stream_name = "AIC31",
176 .cpu_dai = &s6000_i2s_dai,
177 .codec_dai = &aic3x_dai,
178 .init = s6105_aic3x_init,
179 .ops = &s6105_ops,
180};
181
182/* s6105 audio machine driver */
183static struct snd_soc_card snd_soc_card_s6105 = {
184 .name = "Stretch IP Camera",
185 .platform = &s6000_soc_platform,
186 .dai_link = &s6105_dai,
187 .num_links = 1,
188};
189
190/* s6105 audio private data */
191static struct aic3x_setup_data s6105_aic3x_setup = {
192 .i2c_bus = 0,
193 .i2c_address = 0x18,
194};
195
196/* s6105 audio subsystem */
197static struct snd_soc_device s6105_snd_devdata = {
198 .card = &snd_soc_card_s6105,
199 .codec_dev = &soc_codec_dev_aic3x,
200 .codec_data = &s6105_aic3x_setup,
201};
202
203static struct s6000_snd_platform_data __initdata s6105_snd_data = {
204 .wide = 0,
205 .channel_in = 0,
206 .channel_out = 1,
207 .lines_in = 1,
208 .lines_out = 1,
209 .same_rate = 1,
210};
211
212static struct platform_device *s6105_snd_device;
213
214static int __init s6105_init(void)
215{
216 int ret;
217
218 s6105_snd_device = platform_device_alloc("soc-audio", -1);
219 if (!s6105_snd_device)
220 return -ENOMEM;
221
222 platform_set_drvdata(s6105_snd_device, &s6105_snd_devdata);
223 s6105_snd_devdata.dev = &s6105_snd_device->dev;
224 platform_device_add_data(s6105_snd_device, &s6105_snd_data,
225 sizeof(s6105_snd_data));
226
227 ret = platform_device_add(s6105_snd_device);
228 if (ret)
229 platform_device_put(s6105_snd_device);
230
231 return ret;
232}
233
234static void __exit s6105_exit(void)
235{
236 platform_device_unregister(s6105_snd_device);
237}
238
239module_init(s6105_init);
240module_exit(s6105_exit);
241
242MODULE_AUTHOR("Daniel Gloeckner");
243MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver");
244MODULE_LICENSE("GPL");