aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/atmel
diff options
context:
space:
mode:
authorSedji Gaouaou <sedji.gaouaou@atmel.com>2008-10-03 10:57:50 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2008-10-31 09:12:26 -0400
commit6c7425095c9ee23d080dba3e27217a254cce4562 (patch)
treec6c27e1c986b2adeda4adbdcad934b816d654b75 /sound/soc/atmel
parentdc06102a0c8b5aa0dd7f9a40ce241e793c252a87 (diff)
ASoC: Merge AT91 and AVR32 support into a single atmel architecture
The Ateml AT91 and AVR32 SoC share common IP for audio and can share the same driver code using the atmel-ssc API provided for both architectures. Do this, creating a new unified atmel ASoC architecture to replace the previous at32 and at91 ones. [This was contributed as a patch series for reviewability but has been squashed down to a single commit to help preserve both the history and bisectability. A small bugfix from Jukka is included.] Tested-by: Jukka Hynninen <ext-jukka.hynninen@vaisala.com> Signed-off-by: Sedji Gaouaou <sedji.gaouaou@atmel.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/atmel')
-rw-r--r--sound/soc/atmel/Kconfig43
-rw-r--r--sound/soc/atmel/Makefile15
-rw-r--r--sound/soc/atmel/atmel-pcm.c484
-rw-r--r--sound/soc/atmel/atmel-pcm.h86
-rw-r--r--sound/soc/atmel/atmel_ssc_dai.c782
-rw-r--r--sound/soc/atmel/atmel_ssc_dai.h121
-rw-r--r--sound/soc/atmel/playpaq_wm8510.c513
7 files changed, 2044 insertions, 0 deletions
diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig
new file mode 100644
index 000000000000..a608d7009dbd
--- /dev/null
+++ b/sound/soc/atmel/Kconfig
@@ -0,0 +1,43 @@
1config SND_ATMEL_SOC
2 tristate "SoC Audio for the Atmel System-on-Chip"
3 depends on ARCH_AT91 || AVR32
4 help
5 Say Y or M if you want to add support for codecs attached to
6 the ATMEL SSC interface. You will also need
7 to select the audio interfaces to support below.
8
9config SND_ATMEL_SOC_SSC
10 tristate
11 depends on SND_ATMEL_SOC
12 help
13 Say Y or M if you want to add support for codecs the
14 ATMEL SSC interface. You will also needs to select the individual
15 machine drivers to support below.
16
17config SND_AT91_SOC_SAM9G20_WM8731
18 tristate "SoC Audio support for WM8731-based At91sam9g20 evaluation board"
19 depends on ATMEL_SSC && ARCH_AT91SAM9G20 && SND_ATMEL_SOC
20 select SND_ATMEL_SOC_SSC
21 select SND_SOC_WM8731
22 help
23 Say Y if you want to add support for SoC audio on WM8731-based
24 AT91sam9g20 evaluation board.
25
26config SND_AT32_SOC_PLAYPAQ
27 tristate "SoC Audio support for PlayPaq with WM8510"
28 depends on SND_ATMEL_SOC && BOARD_PLAYPAQ
29 select SND_ATMEL_SOC_SSC
30 select SND_SOC_WM8510
31 help
32 Say Y or M here if you want to add support for SoC audio
33 on the LRS PlayPaq.
34
35config SND_AT32_SOC_PLAYPAQ_SLAVE
36 bool "Run CODEC on PlayPaq in slave mode"
37 depends on SND_AT32_SOC_PLAYPAQ
38 default n
39 help
40 Say Y if you want to run with the AT32 SSC generating the BCLK
41 and FRAME signals on the PlayPaq. Unless you want to play
42 with the AT32 as the SSC master, you probably want to say N here,
43 as this will give you better sound quality.
diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile
new file mode 100644
index 000000000000..f54a7cc68e66
--- /dev/null
+++ b/sound/soc/atmel/Makefile
@@ -0,0 +1,15 @@
1# AT91 Platform Support
2snd-soc-atmel-pcm-objs := atmel-pcm.o
3snd-soc-atmel_ssc_dai-objs := atmel_ssc_dai.o
4
5obj-$(CONFIG_SND_ATMEL_SOC) += snd-soc-atmel-pcm.o
6obj-$(CONFIG_SND_ATMEL_SOC_SSC) += snd-soc-atmel_ssc_dai.o
7
8# AT91 Machine Support
9snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o
10
11# AT32 Machine Support
12snd-soc-playpaq-objs := playpaq_wm8510.o
13
14obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o
15obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o
diff --git a/sound/soc/atmel/atmel-pcm.c b/sound/soc/atmel/atmel-pcm.c
new file mode 100644
index 000000000000..394412fb396f
--- /dev/null
+++ b/sound/soc/atmel/atmel-pcm.c
@@ -0,0 +1,484 @@
1/*
2 * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC.
3 *
4 * Copyright (C) 2005 SAN People
5 * Copyright (C) 2008 Atmel
6 *
7 * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
8 *
9 * Based on at91-pcm. by:
10 * Frank Mandarino <fmandarino@endrelia.com>
11 * Copyright 2006 Endrelia Technologies Inc.
12 *
13 * Based on pxa2xx-pcm.c by:
14 *
15 * Author: Nicolas Pitre
16 * Created: Nov 30, 2004
17 * Copyright: (C) 2004 MontaVista Software, Inc.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 */
33
34#include <linux/module.h>
35#include <linux/init.h>
36#include <linux/platform_device.h>
37#include <linux/slab.h>
38#include <linux/dma-mapping.h>
39#include <linux/atmel_pdc.h>
40#include <linux/atmel-ssc.h>
41
42#include <sound/core.h>
43#include <sound/pcm.h>
44#include <sound/pcm_params.h>
45#include <sound/soc.h>
46
47#include <mach/hardware.h>
48
49#include "atmel-pcm.h"
50
51
52/*--------------------------------------------------------------------------*\
53 * Hardware definition
54\*--------------------------------------------------------------------------*/
55/* TODO: These values were taken from the AT91 platform driver, check
56 * them against real values for AT32
57 */
58static const struct snd_pcm_hardware atmel_pcm_hardware = {
59 .info = SNDRV_PCM_INFO_MMAP |
60 SNDRV_PCM_INFO_MMAP_VALID |
61 SNDRV_PCM_INFO_INTERLEAVED |
62 SNDRV_PCM_INFO_PAUSE,
63 .formats = SNDRV_PCM_FMTBIT_S16_LE,
64 .period_bytes_min = 32,
65 .period_bytes_max = 8192,
66 .periods_min = 2,
67 .periods_max = 1024,
68 .buffer_bytes_max = 32 * 1024,
69};
70
71
72/*--------------------------------------------------------------------------*\
73 * Data types
74\*--------------------------------------------------------------------------*/
75struct atmel_runtime_data {
76 struct atmel_pcm_dma_params *params;
77 dma_addr_t dma_buffer; /* physical address of dma buffer */
78 dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */
79 size_t period_size;
80
81 dma_addr_t period_ptr; /* physical address of next period */
82 int periods; /* period index of period_ptr */
83
84 /* PDC register save */
85 u32 pdc_xpr_save;
86 u32 pdc_xcr_save;
87 u32 pdc_xnpr_save;
88 u32 pdc_xncr_save;
89};
90
91
92/*--------------------------------------------------------------------------*\
93 * Helper functions
94\*--------------------------------------------------------------------------*/
95static int atmel_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
96 int stream)
97{
98 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
99 struct snd_dma_buffer *buf = &substream->dma_buffer;
100 size_t size = atmel_pcm_hardware.buffer_bytes_max;
101
102 buf->dev.type = SNDRV_DMA_TYPE_DEV;
103 buf->dev.dev = pcm->card->dev;
104 buf->private_data = NULL;
105 buf->area = dma_alloc_coherent(pcm->card->dev, size,
106 &buf->addr, GFP_KERNEL);
107 pr_debug("atmel-pcm:"
108 "preallocate_dma_buffer: area=%p, addr=%p, size=%d\n",
109 (void *) buf->area,
110 (void *) buf->addr,
111 size);
112
113 if (!buf->area)
114 return -ENOMEM;
115
116 buf->bytes = size;
117 return 0;
118}
119/*--------------------------------------------------------------------------*\
120 * ISR
121\*--------------------------------------------------------------------------*/
122static void atmel_pcm_dma_irq(u32 ssc_sr,
123 struct snd_pcm_substream *substream)
124{
125 struct atmel_runtime_data *prtd = substream->runtime->private_data;
126 struct atmel_pcm_dma_params *params = prtd->params;
127 static int count;
128
129 count++;
130
131 if (ssc_sr & params->mask->ssc_endbuf) {
132 pr_warning("atmel-pcm: buffer %s on %s"
133 " (SSC_SR=%#x, count=%d)\n",
134 substream->stream == SNDRV_PCM_STREAM_PLAYBACK
135 ? "underrun" : "overrun",
136 params->name, ssc_sr, count);
137
138 /* re-start the PDC */
139 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
140 params->mask->pdc_disable);
141 prtd->period_ptr += prtd->period_size;
142 if (prtd->period_ptr >= prtd->dma_buffer_end)
143 prtd->period_ptr = prtd->dma_buffer;
144
145 ssc_writex(params->ssc->regs, params->pdc->xpr,
146 prtd->period_ptr);
147 ssc_writex(params->ssc->regs, params->pdc->xcr,
148 prtd->period_size / params->pdc_xfer_size);
149 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
150 params->mask->pdc_enable);
151 }
152
153 if (ssc_sr & params->mask->ssc_endx) {
154 /* Load the PDC next pointer and counter registers */
155 prtd->period_ptr += prtd->period_size;
156 if (prtd->period_ptr >= prtd->dma_buffer_end)
157 prtd->period_ptr = prtd->dma_buffer;
158
159 ssc_writex(params->ssc->regs, params->pdc->xnpr,
160 prtd->period_ptr);
161 ssc_writex(params->ssc->regs, params->pdc->xncr,
162 prtd->period_size / params->pdc_xfer_size);
163 }
164
165 snd_pcm_period_elapsed(substream);
166}
167
168
169/*--------------------------------------------------------------------------*\
170 * PCM operations
171\*--------------------------------------------------------------------------*/
172static int atmel_pcm_hw_params(struct snd_pcm_substream *substream,
173 struct snd_pcm_hw_params *params)
174{
175 struct snd_pcm_runtime *runtime = substream->runtime;
176 struct atmel_runtime_data *prtd = runtime->private_data;
177 struct snd_soc_pcm_runtime *rtd = substream->private_data;
178
179 /* this may get called several times by oss emulation
180 * with different params */
181
182 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
183 runtime->dma_bytes = params_buffer_bytes(params);
184
185 prtd->params = rtd->dai->cpu_dai->dma_data;
186 prtd->params->dma_intr_handler = atmel_pcm_dma_irq;
187
188 prtd->dma_buffer = runtime->dma_addr;
189 prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes;
190 prtd->period_size = params_period_bytes(params);
191
192 pr_debug("atmel-pcm: "
193 "hw_params: DMA for %s initialized "
194 "(dma_bytes=%u, period_size=%u)\n",
195 prtd->params->name,
196 runtime->dma_bytes,
197 prtd->period_size);
198 return 0;
199}
200
201static int atmel_pcm_hw_free(struct snd_pcm_substream *substream)
202{
203 struct atmel_runtime_data *prtd = substream->runtime->private_data;
204 struct atmel_pcm_dma_params *params = prtd->params;
205
206 if (params != NULL) {
207 ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
208 params->mask->pdc_disable);
209 prtd->params->dma_intr_handler = NULL;
210 }
211
212 return 0;
213}
214
215static int atmel_pcm_prepare(struct snd_pcm_substream *substream)
216{
217 struct atmel_runtime_data *prtd = substream->runtime->private_data;
218 struct atmel_pcm_dma_params *params = prtd->params;
219
220 ssc_writex(params->ssc->regs, SSC_IDR,
221 params->mask->ssc_endx | params->mask->ssc_endbuf);
222 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
223 params->mask->pdc_disable);
224 return 0;
225}
226
227static int atmel_pcm_trigger(struct snd_pcm_substream *substream,
228 int cmd)
229{
230 struct snd_pcm_runtime *rtd = substream->runtime;
231 struct atmel_runtime_data *prtd = rtd->private_data;
232 struct atmel_pcm_dma_params *params = prtd->params;
233 int ret = 0;
234
235 pr_debug("atmel-pcm:buffer_size = %ld,"
236 "dma_area = %p, dma_bytes = %u\n",
237 rtd->buffer_size, rtd->dma_area, rtd->dma_bytes);
238
239 switch (cmd) {
240 case SNDRV_PCM_TRIGGER_START:
241 prtd->period_ptr = prtd->dma_buffer;
242
243 ssc_writex(params->ssc->regs, params->pdc->xpr,
244 prtd->period_ptr);
245 ssc_writex(params->ssc->regs, params->pdc->xcr,
246 prtd->period_size / params->pdc_xfer_size);
247
248 prtd->period_ptr += prtd->period_size;
249 ssc_writex(params->ssc->regs, params->pdc->xnpr,
250 prtd->period_ptr);
251 ssc_writex(params->ssc->regs, params->pdc->xncr,
252 prtd->period_size / params->pdc_xfer_size);
253
254 pr_debug("atmel-pcm: trigger: "
255 "period_ptr=%lx, xpr=%u, "
256 "xcr=%u, xnpr=%u, xncr=%u\n",
257 (unsigned long)prtd->period_ptr,
258 ssc_readx(params->ssc->regs, params->pdc->xpr),
259 ssc_readx(params->ssc->regs, params->pdc->xcr),
260 ssc_readx(params->ssc->regs, params->pdc->xnpr),
261 ssc_readx(params->ssc->regs, params->pdc->xncr));
262
263 ssc_writex(params->ssc->regs, SSC_IER,
264 params->mask->ssc_endx | params->mask->ssc_endbuf);
265 ssc_writex(params->ssc->regs, SSC_PDC_PTCR,
266 params->mask->pdc_enable);
267
268 pr_debug("sr=%u imr=%u\n",
269 ssc_readx(params->ssc->regs, SSC_SR),
270 ssc_readx(params->ssc->regs, SSC_IER));
271 break; /* SNDRV_PCM_TRIGGER_START */
272
273 case SNDRV_PCM_TRIGGER_STOP:
274 case SNDRV_PCM_TRIGGER_SUSPEND:
275 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
276 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
277 params->mask->pdc_disable);
278 break;
279
280 case SNDRV_PCM_TRIGGER_RESUME:
281 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
282 ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR,
283 params->mask->pdc_enable);
284 break;
285
286 default:
287 ret = -EINVAL;
288 }
289
290 return ret;
291}
292
293static snd_pcm_uframes_t atmel_pcm_pointer(
294 struct snd_pcm_substream *substream)
295{
296 struct snd_pcm_runtime *runtime = substream->runtime;
297 struct atmel_runtime_data *prtd = runtime->private_data;
298 struct atmel_pcm_dma_params *params = prtd->params;
299 dma_addr_t ptr;
300 snd_pcm_uframes_t x;
301
302 ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr);
303 x = bytes_to_frames(runtime, ptr - prtd->dma_buffer);
304
305 if (x == runtime->buffer_size)
306 x = 0;
307
308 return x;
309}
310
311static int atmel_pcm_open(struct snd_pcm_substream *substream)
312{
313 struct snd_pcm_runtime *runtime = substream->runtime;
314 struct atmel_runtime_data *prtd;
315 int ret = 0;
316
317 snd_soc_set_runtime_hwparams(substream, &atmel_pcm_hardware);
318
319 /* ensure that buffer size is a multiple of period size */
320 ret = snd_pcm_hw_constraint_integer(runtime,
321 SNDRV_PCM_HW_PARAM_PERIODS);
322 if (ret < 0)
323 goto out;
324
325 prtd = kzalloc(sizeof(struct atmel_runtime_data), GFP_KERNEL);
326 if (prtd == NULL) {
327 ret = -ENOMEM;
328 goto out;
329 }
330 runtime->private_data = prtd;
331
332 out:
333 return ret;
334}
335
336static int atmel_pcm_close(struct snd_pcm_substream *substream)
337{
338 struct atmel_runtime_data *prtd = substream->runtime->private_data;
339
340 kfree(prtd);
341 return 0;
342}
343
344static int atmel_pcm_mmap(struct snd_pcm_substream *substream,
345 struct vm_area_struct *vma)
346{
347 return remap_pfn_range(vma, vma->vm_start,
348 substream->dma_buffer.addr >> PAGE_SHIFT,
349 vma->vm_end - vma->vm_start, vma->vm_page_prot);
350}
351
352struct snd_pcm_ops atmel_pcm_ops = {
353 .open = atmel_pcm_open,
354 .close = atmel_pcm_close,
355 .ioctl = snd_pcm_lib_ioctl,
356 .hw_params = atmel_pcm_hw_params,
357 .hw_free = atmel_pcm_hw_free,
358 .prepare = atmel_pcm_prepare,
359 .trigger = atmel_pcm_trigger,
360 .pointer = atmel_pcm_pointer,
361 .mmap = atmel_pcm_mmap,
362};
363
364
365/*--------------------------------------------------------------------------*\
366 * ASoC platform driver
367\*--------------------------------------------------------------------------*/
368static u64 atmel_pcm_dmamask = 0xffffffff;
369
370static int atmel_pcm_new(struct snd_card *card,
371 struct snd_soc_dai *dai, struct snd_pcm *pcm)
372{
373 int ret = 0;
374
375 if (!card->dev->dma_mask)
376 card->dev->dma_mask = &atmel_pcm_dmamask;
377 if (!card->dev->coherent_dma_mask)
378 card->dev->coherent_dma_mask = 0xffffffff;
379
380 if (dai->playback.channels_min) {
381 ret = atmel_pcm_preallocate_dma_buffer(pcm,
382 SNDRV_PCM_STREAM_PLAYBACK);
383 if (ret)
384 goto out;
385 }
386
387 if (dai->capture.channels_min) {
388 pr_debug("at32-pcm:"
389 "Allocating PCM capture DMA buffer\n");
390 ret = atmel_pcm_preallocate_dma_buffer(pcm,
391 SNDRV_PCM_STREAM_CAPTURE);
392 if (ret)
393 goto out;
394 }
395 out:
396 return ret;
397}
398
399static void atmel_pcm_free_dma_buffers(struct snd_pcm *pcm)
400{
401 struct snd_pcm_substream *substream;
402 struct snd_dma_buffer *buf;
403 int stream;
404
405 for (stream = 0; stream < 2; stream++) {
406 substream = pcm->streams[stream].substream;
407 if (!substream)
408 continue;
409
410 buf = &substream->dma_buffer;
411 if (!buf->area)
412 continue;
413 dma_free_coherent(pcm->card->dev, buf->bytes,
414 buf->area, buf->addr);
415 buf->area = NULL;
416 }
417}
418
419#ifdef CONFIG_PM
420static int atmel_pcm_suspend(struct platform_device *pdev,
421 struct snd_soc_dai *dai)
422{
423 struct snd_pcm_runtime *runtime = dai->runtime;
424 struct atmel_runtime_data *prtd;
425 struct atmel_pcm_dma_params *params;
426
427 if (!runtime)
428 return 0;
429
430 prtd = runtime->private_data;
431 params = prtd->params;
432
433 /* disable the PDC and save the PDC registers */
434
435 ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_disable);
436
437 prtd->pdc_xpr_save = ssc_readx(params->ssc->regs, params->pdc->xpr);
438 prtd->pdc_xcr_save = ssc_readx(params->ssc->regs, params->pdc->xcr);
439 prtd->pdc_xnpr_save = ssc_readx(params->ssc->regs, params->pdc->xnpr);
440 prtd->pdc_xncr_save = ssc_readx(params->ssc->regs, params->pdc->xncr);
441
442 return 0;
443}
444
445static int atmel_pcm_resume(struct platform_device *pdev,
446 struct snd_soc_dai *dai)
447{
448 struct snd_pcm_runtime *runtime = dai->runtime;
449 struct atmel_runtime_data *prtd;
450 struct atmel_pcm_dma_params *params;
451
452 if (!runtime)
453 return 0;
454
455 prtd = runtime->private_data;
456 params = prtd->params;
457
458 /* restore the PDC registers and enable the PDC */
459 ssc_writex(params->ssc->regs, params->pdc->xpr, prtd->pdc_xpr_save);
460 ssc_writex(params->ssc->regs, params->pdc->xcr, prtd->pdc_xcr_save);
461 ssc_writex(params->ssc->regs, params->pdc->xnpr, prtd->pdc_xnpr_save);
462 ssc_writex(params->ssc->regs, params->pdc->xncr, prtd->pdc_xncr_save);
463
464 ssc_writel(params->ssc->regs, PDC_PTCR, params->mask->pdc_enable);
465 return 0;
466}
467#else
468#define atmel_pcm_suspend NULL
469#define atmel_pcm_resume NULL
470#endif
471
472struct snd_soc_platform atmel_soc_platform = {
473 .name = "atmel-audio",
474 .pcm_ops = &atmel_pcm_ops,
475 .pcm_new = atmel_pcm_new,
476 .pcm_free = atmel_pcm_free_dma_buffers,
477 .suspend = atmel_pcm_suspend,
478 .resume = atmel_pcm_resume,
479};
480EXPORT_SYMBOL_GPL(atmel_soc_platform);
481
482MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
483MODULE_DESCRIPTION("Atmel PCM module");
484MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/atmel-pcm.h b/sound/soc/atmel/atmel-pcm.h
new file mode 100644
index 000000000000..ec9b2824b663
--- /dev/null
+++ b/sound/soc/atmel/atmel-pcm.h
@@ -0,0 +1,86 @@
1/*
2 * at91-pcm.h - ALSA PCM interface for the Atmel AT91 SoC.
3 *
4 * Copyright (C) 2005 SAN People
5 * Copyright (C) 2008 Atmel
6 *
7 * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
8 *
9 * Based on at91-pcm. by:
10 * Frank Mandarino <fmandarino@endrelia.com>
11 * Copyright 2006 Endrelia Technologies Inc.
12 *
13 * Based on pxa2xx-pcm.c by:
14 *
15 * Author: Nicolas Pitre
16 * Created: Nov 30, 2004
17 * Copyright: (C) 2004 MontaVista Software, Inc.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 */
33
34#ifndef _ATMEL_PCM_H
35#define _ATMEL_PCM_H
36
37#include <linux/atmel-ssc.h>
38
39/*
40 * Registers and status bits that are required by the PCM driver.
41 */
42struct atmel_pdc_regs {
43 unsigned int xpr; /* PDC recv/trans pointer */
44 unsigned int xcr; /* PDC recv/trans counter */
45 unsigned int xnpr; /* PDC next recv/trans pointer */
46 unsigned int xncr; /* PDC next recv/trans counter */
47 unsigned int ptcr; /* PDC transfer control */
48};
49
50struct atmel_ssc_mask {
51 u32 ssc_enable; /* SSC recv/trans enable */
52 u32 ssc_disable; /* SSC recv/trans disable */
53 u32 ssc_endx; /* SSC ENDTX or ENDRX */
54 u32 ssc_endbuf; /* SSC TXBUFE or RXBUFF */
55 u32 pdc_enable; /* PDC recv/trans enable */
56 u32 pdc_disable; /* PDC recv/trans disable */
57};
58
59/*
60 * This structure, shared between the PCM driver and the interface,
61 * contains all information required by the PCM driver to perform the
62 * PDC DMA operation. All fields except dma_intr_handler() are initialized
63 * by the interface. The dms_intr_handler() pointer is set by the PCM
64 * driver and called by the interface SSC interrupt handler if it is
65 * non-NULL.
66 */
67struct atmel_pcm_dma_params {
68 char *name; /* stream identifier */
69 int pdc_xfer_size; /* PDC counter increment in bytes */
70 struct ssc_device *ssc; /* SSC device for stream */
71 struct atmel_pdc_regs *pdc; /* PDC receive or transmit registers */
72 struct atmel_ssc_mask *mask; /* SSC & PDC status bits */
73 struct snd_pcm_substream *substream;
74 void (*dma_intr_handler)(u32, struct snd_pcm_substream *);
75};
76
77extern struct snd_soc_platform atmel_soc_platform;
78
79
80/*
81 * SSC register access (since ssc_writel() / ssc_readl() require literal name)
82 */
83#define ssc_readx(base, reg) (__raw_readl((base) + (reg)))
84#define ssc_writex(base, reg, value) __raw_writel((value), (base) + (reg))
85
86#endif /* _ATMEL_PCM_H */
diff --git a/sound/soc/atmel/atmel_ssc_dai.c b/sound/soc/atmel/atmel_ssc_dai.c
new file mode 100644
index 000000000000..d290b7894917
--- /dev/null
+++ b/sound/soc/atmel/atmel_ssc_dai.c
@@ -0,0 +1,782 @@
1/*
2 * atmel_ssc_dai.c -- ALSA SoC ATMEL SSC Audio Layer Platform driver
3 *
4 * Copyright (C) 2005 SAN People
5 * Copyright (C) 2008 Atmel
6 *
7 * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
8 * ATMEL CORP.
9 *
10 * Based on at91-ssc.c by
11 * Frank Mandarino <fmandarino@endrelia.com>
12 * Based on pxa2xx Platform drivers by
13 * Liam Girdwood <liam.girdwood@wolfsonmicro.com>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 */
29
30#include <linux/init.h>
31#include <linux/module.h>
32#include <linux/interrupt.h>
33#include <linux/device.h>
34#include <linux/delay.h>
35#include <linux/clk.h>
36#include <linux/atmel_pdc.h>
37
38#include <linux/atmel-ssc.h>
39#include <sound/core.h>
40#include <sound/pcm.h>
41#include <sound/pcm_params.h>
42#include <sound/initval.h>
43#include <sound/soc.h>
44
45#include <mach/hardware.h>
46
47#include "atmel-pcm.h"
48#include "atmel_ssc_dai.h"
49
50
51#if defined(CONFIG_ARCH_AT91SAM9260) || defined(CONFIG_ARCH_AT91SAM9G20)
52#define NUM_SSC_DEVICES 1
53#else
54#define NUM_SSC_DEVICES 3
55#endif
56
57/*
58 * SSC PDC registers required by the PCM DMA engine.
59 */
60static struct atmel_pdc_regs pdc_tx_reg = {
61 .xpr = ATMEL_PDC_TPR,
62 .xcr = ATMEL_PDC_TCR,
63 .xnpr = ATMEL_PDC_TNPR,
64 .xncr = ATMEL_PDC_TNCR,
65};
66
67static struct atmel_pdc_regs pdc_rx_reg = {
68 .xpr = ATMEL_PDC_RPR,
69 .xcr = ATMEL_PDC_RCR,
70 .xnpr = ATMEL_PDC_RNPR,
71 .xncr = ATMEL_PDC_RNCR,
72};
73
74/*
75 * SSC & PDC status bits for transmit and receive.
76 */
77static struct atmel_ssc_mask ssc_tx_mask = {
78 .ssc_enable = SSC_BIT(CR_TXEN),
79 .ssc_disable = SSC_BIT(CR_TXDIS),
80 .ssc_endx = SSC_BIT(SR_ENDTX),
81 .ssc_endbuf = SSC_BIT(SR_TXBUFE),
82 .pdc_enable = ATMEL_PDC_TXTEN,
83 .pdc_disable = ATMEL_PDC_TXTDIS,
84};
85
86static struct atmel_ssc_mask ssc_rx_mask = {
87 .ssc_enable = SSC_BIT(CR_RXEN),
88 .ssc_disable = SSC_BIT(CR_RXDIS),
89 .ssc_endx = SSC_BIT(SR_ENDRX),
90 .ssc_endbuf = SSC_BIT(SR_RXBUFF),
91 .pdc_enable = ATMEL_PDC_RXTEN,
92 .pdc_disable = ATMEL_PDC_RXTDIS,
93};
94
95
96/*
97 * DMA parameters.
98 */
99static struct atmel_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = {
100 {{
101 .name = "SSC0 PCM out",
102 .pdc = &pdc_tx_reg,
103 .mask = &ssc_tx_mask,
104 },
105 {
106 .name = "SSC0 PCM in",
107 .pdc = &pdc_rx_reg,
108 .mask = &ssc_rx_mask,
109 } },
110#if NUM_SSC_DEVICES == 3
111 {{
112 .name = "SSC1 PCM out",
113 .pdc = &pdc_tx_reg,
114 .mask = &ssc_tx_mask,
115 },
116 {
117 .name = "SSC1 PCM in",
118 .pdc = &pdc_rx_reg,
119 .mask = &ssc_rx_mask,
120 } },
121 {{
122 .name = "SSC2 PCM out",
123 .pdc = &pdc_tx_reg,
124 .mask = &ssc_tx_mask,
125 },
126 {
127 .name = "SSC2 PCM in",
128 .pdc = &pdc_rx_reg,
129 .mask = &ssc_rx_mask,
130 } },
131#endif
132};
133
134
135static struct atmel_ssc_info ssc_info[NUM_SSC_DEVICES] = {
136 {
137 .name = "ssc0",
138 .lock = __SPIN_LOCK_UNLOCKED(ssc_info[0].lock),
139 .dir_mask = SSC_DIR_MASK_UNUSED,
140 .initialized = 0,
141 },
142#if NUM_SSC_DEVICES == 3
143 {
144 .name = "ssc1",
145 .lock = __SPIN_LOCK_UNLOCKED(ssc_info[1].lock),
146 .dir_mask = SSC_DIR_MASK_UNUSED,
147 .initialized = 0,
148 },
149 {
150 .name = "ssc2",
151 .lock = __SPIN_LOCK_UNLOCKED(ssc_info[2].lock),
152 .dir_mask = SSC_DIR_MASK_UNUSED,
153 .initialized = 0,
154 },
155#endif
156};
157
158
159/*
160 * SSC interrupt handler. Passes PDC interrupts to the DMA
161 * interrupt handler in the PCM driver.
162 */
163static irqreturn_t atmel_ssc_interrupt(int irq, void *dev_id)
164{
165 struct atmel_ssc_info *ssc_p = dev_id;
166 struct atmel_pcm_dma_params *dma_params;
167 u32 ssc_sr;
168 u32 ssc_substream_mask;
169 int i;
170
171 ssc_sr = (unsigned long)ssc_readl(ssc_p->ssc->regs, SR)
172 & (unsigned long)ssc_readl(ssc_p->ssc->regs, IMR);
173
174 /*
175 * Loop through the substreams attached to this SSC. If
176 * a DMA-related interrupt occurred on that substream, call
177 * the DMA interrupt handler function, if one has been
178 * registered in the dma_params structure by the PCM driver.
179 */
180 for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) {
181 dma_params = ssc_p->dma_params[i];
182
183 if ((dma_params != NULL) &&
184 (dma_params->dma_intr_handler != NULL)) {
185 ssc_substream_mask = (dma_params->mask->ssc_endx |
186 dma_params->mask->ssc_endbuf);
187 if (ssc_sr & ssc_substream_mask) {
188 dma_params->dma_intr_handler(ssc_sr,
189 dma_params->
190 substream);
191 }
192 }
193 }
194
195 return IRQ_HANDLED;
196}
197
198
199/*-------------------------------------------------------------------------*\
200 * DAI functions
201\*-------------------------------------------------------------------------*/
202/*
203 * Startup. Only that one substream allowed in each direction.
204 */
205static int atmel_ssc_startup(struct snd_pcm_substream *substream)
206{
207 struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
208 struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
209 int dir_mask;
210
211 pr_debug("atmel_ssc_startup: SSC_SR=0x%u\n",
212 ssc_readl(ssc_p->ssc->regs, SR));
213
214 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
215 dir_mask = SSC_DIR_MASK_PLAYBACK;
216 else
217 dir_mask = SSC_DIR_MASK_CAPTURE;
218
219 spin_lock_irq(&ssc_p->lock);
220 if (ssc_p->dir_mask & dir_mask) {
221 spin_unlock_irq(&ssc_p->lock);
222 return -EBUSY;
223 }
224 ssc_p->dir_mask |= dir_mask;
225 spin_unlock_irq(&ssc_p->lock);
226
227 return 0;
228}
229
230/*
231 * Shutdown. Clear DMA parameters and shutdown the SSC if there
232 * are no other substreams open.
233 */
234static void atmel_ssc_shutdown(struct snd_pcm_substream *substream)
235{
236 struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
237 struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
238 struct atmel_pcm_dma_params *dma_params;
239 int dir, dir_mask;
240
241 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
242 dir = 0;
243 else
244 dir = 1;
245
246 dma_params = ssc_p->dma_params[dir];
247
248 if (dma_params != NULL) {
249 ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_disable);
250 pr_debug("atmel_ssc_shutdown: %s disabled SSC_SR=0x%08x\n",
251 (dir ? "receive" : "transmit"),
252 ssc_readl(ssc_p->ssc->regs, SR));
253
254 dma_params->ssc = NULL;
255 dma_params->substream = NULL;
256 ssc_p->dma_params[dir] = NULL;
257 }
258
259 dir_mask = 1 << dir;
260
261 spin_lock_irq(&ssc_p->lock);
262 ssc_p->dir_mask &= ~dir_mask;
263 if (!ssc_p->dir_mask) {
264 if (ssc_p->initialized) {
265 /* Shutdown the SSC clock. */
266 pr_debug("atmel_ssc_dau: Stopping clock\n");
267 clk_disable(ssc_p->ssc->clk);
268
269 free_irq(ssc_p->ssc->irq, ssc_p);
270 ssc_p->initialized = 0;
271 }
272
273 /* Reset the SSC */
274 ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
275 /* Clear the SSC dividers */
276 ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0;
277 }
278 spin_unlock_irq(&ssc_p->lock);
279}
280
281
282/*
283 * Record the DAI format for use in hw_params().
284 */
285static int atmel_ssc_set_dai_fmt(struct snd_soc_dai *cpu_dai,
286 unsigned int fmt)
287{
288 struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
289
290 ssc_p->daifmt = fmt;
291 return 0;
292}
293
294/*
295 * Record SSC clock dividers for use in hw_params().
296 */
297static int atmel_ssc_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
298 int div_id, int div)
299{
300 struct atmel_ssc_info *ssc_p = &ssc_info[cpu_dai->id];
301
302 switch (div_id) {
303 case ATMEL_SSC_CMR_DIV:
304 /*
305 * The same master clock divider is used for both
306 * transmit and receive, so if a value has already
307 * been set, it must match this value.
308 */
309 if (ssc_p->cmr_div == 0)
310 ssc_p->cmr_div = div;
311 else
312 if (div != ssc_p->cmr_div)
313 return -EBUSY;
314 break;
315
316 case ATMEL_SSC_TCMR_PERIOD:
317 ssc_p->tcmr_period = div;
318 break;
319
320 case ATMEL_SSC_RCMR_PERIOD:
321 ssc_p->rcmr_period = div;
322 break;
323
324 default:
325 return -EINVAL;
326 }
327
328 return 0;
329}
330
331/*
332 * Configure the SSC.
333 */
334static int atmel_ssc_hw_params(struct snd_pcm_substream *substream,
335 struct snd_pcm_hw_params *params)
336{
337 struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
338 int id = rtd->dai->cpu_dai->id;
339 struct atmel_ssc_info *ssc_p = &ssc_info[id];
340 struct atmel_pcm_dma_params *dma_params;
341 int dir, channels, bits;
342 u32 tfmr, rfmr, tcmr, rcmr;
343 int start_event;
344 int ret;
345
346 /*
347 * Currently, there is only one set of dma params for
348 * each direction. If more are added, this code will
349 * have to be changed to select the proper set.
350 */
351 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
352 dir = 0;
353 else
354 dir = 1;
355
356 dma_params = &ssc_dma_params[id][dir];
357 dma_params->ssc = ssc_p->ssc;
358 dma_params->substream = substream;
359
360 ssc_p->dma_params[dir] = dma_params;
361
362 /*
363 * The cpu_dai->dma_data field is only used to communicate the
364 * appropriate DMA parameters to the pcm driver hw_params()
365 * function. It should not be used for other purposes
366 * as it is common to all substreams.
367 */
368 rtd->dai->cpu_dai->dma_data = dma_params;
369
370 channels = params_channels(params);
371
372 /*
373 * Determine sample size in bits and the PDC increment.
374 */
375 switch (params_format(params)) {
376 case SNDRV_PCM_FORMAT_S8:
377 bits = 8;
378 dma_params->pdc_xfer_size = 1;
379 break;
380 case SNDRV_PCM_FORMAT_S16_LE:
381 bits = 16;
382 dma_params->pdc_xfer_size = 2;
383 break;
384 case SNDRV_PCM_FORMAT_S24_LE:
385 bits = 24;
386 dma_params->pdc_xfer_size = 4;
387 break;
388 case SNDRV_PCM_FORMAT_S32_LE:
389 bits = 32;
390 dma_params->pdc_xfer_size = 4;
391 break;
392 default:
393 printk(KERN_WARNING "atmel_ssc_dai: unsupported PCM format");
394 return -EINVAL;
395 }
396
397 /*
398 * The SSC only supports up to 16-bit samples in I2S format, due
399 * to the size of the Frame Mode Register FSLEN field.
400 */
401 if ((ssc_p->daifmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S
402 && bits > 16) {
403 printk(KERN_WARNING
404 "atmel_ssc_dai: sample size %d"
405 "is too large for I2S\n", bits);
406 return -EINVAL;
407 }
408
409 /*
410 * Compute SSC register settings.
411 */
412 switch (ssc_p->daifmt
413 & (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_MASTER_MASK)) {
414
415 case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBS_CFS:
416 /*
417 * I2S format, SSC provides BCLK and LRC clocks.
418 *
419 * The SSC transmit and receive clocks are generated
420 * from the MCK divider, and the BCLK signal
421 * is output on the SSC TK line.
422 */
423 rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
424 | SSC_BF(RCMR_STTDLY, START_DELAY)
425 | SSC_BF(RCMR_START, SSC_START_FALLING_RF)
426 | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
427 | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
428 | SSC_BF(RCMR_CKS, SSC_CKS_DIV);
429
430 rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
431 | SSC_BF(RFMR_FSOS, SSC_FSOS_NEGATIVE)
432 | SSC_BF(RFMR_FSLEN, (bits - 1))
433 | SSC_BF(RFMR_DATNB, (channels - 1))
434 | SSC_BIT(RFMR_MSBF)
435 | SSC_BF(RFMR_LOOP, 0)
436 | SSC_BF(RFMR_DATLEN, (bits - 1));
437
438 tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
439 | SSC_BF(TCMR_STTDLY, START_DELAY)
440 | SSC_BF(TCMR_START, SSC_START_FALLING_RF)
441 | SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
442 | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
443 | SSC_BF(TCMR_CKS, SSC_CKS_DIV);
444
445 tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
446 | SSC_BF(TFMR_FSDEN, 0)
447 | SSC_BF(TFMR_FSOS, SSC_FSOS_NEGATIVE)
448 | SSC_BF(TFMR_FSLEN, (bits - 1))
449 | SSC_BF(TFMR_DATNB, (channels - 1))
450 | SSC_BIT(TFMR_MSBF)
451 | SSC_BF(TFMR_DATDEF, 0)
452 | SSC_BF(TFMR_DATLEN, (bits - 1));
453 break;
454
455 case SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_CBM_CFM:
456 /*
457 * I2S format, CODEC supplies BCLK and LRC clocks.
458 *
459 * The SSC transmit clock is obtained from the BCLK signal on
460 * on the TK line, and the SSC receive clock is
461 * generated from the transmit clock.
462 *
463 * For single channel data, one sample is transferred
464 * on the falling edge of the LRC clock.
465 * For two channel data, one sample is
466 * transferred on both edges of the LRC clock.
467 */
468 start_event = ((channels == 1)
469 ? SSC_START_FALLING_RF
470 : SSC_START_EDGE_RF);
471
472 rcmr = SSC_BF(RCMR_PERIOD, 0)
473 | SSC_BF(RCMR_STTDLY, START_DELAY)
474 | SSC_BF(RCMR_START, start_event)
475 | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
476 | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
477 | SSC_BF(RCMR_CKS, SSC_CKS_CLOCK);
478
479 rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
480 | SSC_BF(RFMR_FSOS, SSC_FSOS_NONE)
481 | SSC_BF(RFMR_FSLEN, 0)
482 | SSC_BF(RFMR_DATNB, 0)
483 | SSC_BIT(RFMR_MSBF)
484 | SSC_BF(RFMR_LOOP, 0)
485 | SSC_BF(RFMR_DATLEN, (bits - 1));
486
487 tcmr = SSC_BF(TCMR_PERIOD, 0)
488 | SSC_BF(TCMR_STTDLY, START_DELAY)
489 | SSC_BF(TCMR_START, start_event)
490 | SSC_BF(TCMR_CKI, SSC_CKI_FALLING)
491 | SSC_BF(TCMR_CKO, SSC_CKO_NONE)
492 | SSC_BF(TCMR_CKS, SSC_CKS_PIN);
493
494 tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
495 | SSC_BF(TFMR_FSDEN, 0)
496 | SSC_BF(TFMR_FSOS, SSC_FSOS_NONE)
497 | SSC_BF(TFMR_FSLEN, 0)
498 | SSC_BF(TFMR_DATNB, 0)
499 | SSC_BIT(TFMR_MSBF)
500 | SSC_BF(TFMR_DATDEF, 0)
501 | SSC_BF(TFMR_DATLEN, (bits - 1));
502 break;
503
504 case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBS_CFS:
505 /*
506 * DSP/PCM Mode A format, SSC provides BCLK and LRC clocks.
507 *
508 * The SSC transmit and receive clocks are generated from the
509 * MCK divider, and the BCLK signal is output
510 * on the SSC TK line.
511 */
512 rcmr = SSC_BF(RCMR_PERIOD, ssc_p->rcmr_period)
513 | SSC_BF(RCMR_STTDLY, 1)
514 | SSC_BF(RCMR_START, SSC_START_RISING_RF)
515 | SSC_BF(RCMR_CKI, SSC_CKI_RISING)
516 | SSC_BF(RCMR_CKO, SSC_CKO_NONE)
517 | SSC_BF(RCMR_CKS, SSC_CKS_DIV);
518
519 rfmr = SSC_BF(RFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
520 | SSC_BF(RFMR_FSOS, SSC_FSOS_POSITIVE)
521 | SSC_BF(RFMR_FSLEN, 0)
522 | SSC_BF(RFMR_DATNB, (channels - 1))
523 | SSC_BIT(RFMR_MSBF)
524 | SSC_BF(RFMR_LOOP, 0)
525 | SSC_BF(RFMR_DATLEN, (bits - 1));
526
527 tcmr = SSC_BF(TCMR_PERIOD, ssc_p->tcmr_period)
528 | SSC_BF(TCMR_STTDLY, 1)
529 | SSC_BF(TCMR_START, SSC_START_RISING_RF)
530 | SSC_BF(TCMR_CKI, SSC_CKI_RISING)
531 | SSC_BF(TCMR_CKO, SSC_CKO_CONTINUOUS)
532 | SSC_BF(TCMR_CKS, SSC_CKS_DIV);
533
534 tfmr = SSC_BF(TFMR_FSEDGE, SSC_FSEDGE_POSITIVE)
535 | SSC_BF(TFMR_FSDEN, 0)
536 | SSC_BF(TFMR_FSOS, SSC_FSOS_POSITIVE)
537 | SSC_BF(TFMR_FSLEN, 0)
538 | SSC_BF(TFMR_DATNB, (channels - 1))
539 | SSC_BIT(TFMR_MSBF)
540 | SSC_BF(TFMR_DATDEF, 0)
541 | SSC_BF(TFMR_DATLEN, (bits - 1));
542 break;
543
544 case SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_CBM_CFM:
545 default:
546 printk(KERN_WARNING "atmel_ssc_dai: unsupported DAI format 0x%x\n",
547 ssc_p->daifmt);
548 return -EINVAL;
549 break;
550 }
551 pr_debug("atmel_ssc_hw_params: "
552 "RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n",
553 rcmr, rfmr, tcmr, tfmr);
554
555 if (!ssc_p->initialized) {
556
557 /* Enable PMC peripheral clock for this SSC */
558 pr_debug("atmel_ssc_dai: Starting clock\n");
559 clk_enable(ssc_p->ssc->clk);
560
561 /* Reset the SSC and its PDC registers */
562 ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_SWRST));
563
564 ssc_writel(ssc_p->ssc->regs, PDC_RPR, 0);
565 ssc_writel(ssc_p->ssc->regs, PDC_RCR, 0);
566 ssc_writel(ssc_p->ssc->regs, PDC_RNPR, 0);
567 ssc_writel(ssc_p->ssc->regs, PDC_RNCR, 0);
568
569 ssc_writel(ssc_p->ssc->regs, PDC_TPR, 0);
570 ssc_writel(ssc_p->ssc->regs, PDC_TCR, 0);
571 ssc_writel(ssc_p->ssc->regs, PDC_TNPR, 0);
572 ssc_writel(ssc_p->ssc->regs, PDC_TNCR, 0);
573
574 ret = request_irq(ssc_p->ssc->irq, atmel_ssc_interrupt, 0,
575 ssc_p->name, ssc_p);
576 if (ret < 0) {
577 printk(KERN_WARNING
578 "atmel_ssc_dai: request_irq failure\n");
579 pr_debug("Atmel_ssc_dai: Stoping clock\n");
580 clk_disable(ssc_p->ssc->clk);
581 return ret;
582 }
583
584 ssc_p->initialized = 1;
585 }
586
587 /* set SSC clock mode register */
588 ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->cmr_div);
589
590 /* set receive clock mode and format */
591 ssc_writel(ssc_p->ssc->regs, RCMR, rcmr);
592 ssc_writel(ssc_p->ssc->regs, RFMR, rfmr);
593
594 /* set transmit clock mode and format */
595 ssc_writel(ssc_p->ssc->regs, TCMR, tcmr);
596 ssc_writel(ssc_p->ssc->regs, TFMR, tfmr);
597
598 pr_debug("atmel_ssc_dai,hw_params: SSC initialized\n");
599 return 0;
600}
601
602
603static int atmel_ssc_prepare(struct snd_pcm_substream *substream)
604{
605 struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream);
606 struct atmel_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id];
607 struct atmel_pcm_dma_params *dma_params;
608 int dir;
609
610 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
611 dir = 0;
612 else
613 dir = 1;
614
615 dma_params = ssc_p->dma_params[dir];
616
617 ssc_writel(ssc_p->ssc->regs, CR, dma_params->mask->ssc_enable);
618
619 pr_debug("%s enabled SSC_SR=0x%08x\n",
620 dir ? "receive" : "transmit",
621 ssc_readl(ssc_p->ssc->regs, SR));
622 return 0;
623}
624
625
626#ifdef CONFIG_PM
627static int atmel_ssc_suspend(struct platform_device *pdev,
628 struct snd_soc_dai *cpu_dai)
629{
630 struct atmel_ssc_info *ssc_p;
631
632 if (!cpu_dai->active)
633 return 0;
634
635 ssc_p = &ssc_info[cpu_dai->id];
636
637 /* Save the status register before disabling transmit and receive */
638 ssc_p->ssc_state.ssc_sr = ssc_readl(ssc_p->ssc->regs, SR);
639 ssc_writel(ssc_p->ssc->regs, CR, SSC_BIT(CR_TXDIS) | SSC_BIT(CR_RXDIS));
640
641 /* Save the current interrupt mask, then disable unmasked interrupts */
642 ssc_p->ssc_state.ssc_imr = ssc_readl(ssc_p->ssc->regs, IMR);
643 ssc_writel(ssc_p->ssc->regs, IDR, ssc_p->ssc_state.ssc_imr);
644
645 ssc_p->ssc_state.ssc_cmr = ssc_readl(ssc_p->ssc->regs, CMR);
646 ssc_p->ssc_state.ssc_rcmr = ssc_readl(ssc_p->ssc->regs, RCMR);
647 ssc_p->ssc_state.ssc_rfmr = ssc_readl(ssc_p->ssc->regs, RFMR);
648 ssc_p->ssc_state.ssc_tcmr = ssc_readl(ssc_p->ssc->regs, TCMR);
649 ssc_p->ssc_state.ssc_tfmr = ssc_readl(ssc_p->ssc->regs, TFMR);
650
651 return 0;
652}
653
654
655
656static int atmel_ssc_resume(struct platform_device *pdev,
657 struct snd_soc_dai *cpu_dai)
658{
659 struct atmel_ssc_info *ssc_p;
660 u32 cr;
661
662 if (!cpu_dai->active)
663 return 0;
664
665 ssc_p = &ssc_info[cpu_dai->id];
666
667 /* restore SSC register settings */
668 ssc_writel(ssc_p->ssc->regs, TFMR, ssc_p->ssc_state.ssc_tfmr);
669 ssc_writel(ssc_p->ssc->regs, TCMR, ssc_p->ssc_state.ssc_tcmr);
670 ssc_writel(ssc_p->ssc->regs, RFMR, ssc_p->ssc_state.ssc_rfmr);
671 ssc_writel(ssc_p->ssc->regs, RCMR, ssc_p->ssc_state.ssc_rcmr);
672 ssc_writel(ssc_p->ssc->regs, CMR, ssc_p->ssc_state.ssc_cmr);
673
674 /* re-enable interrupts */
675 ssc_writel(ssc_p->ssc->regs, IER, ssc_p->ssc_state.ssc_imr);
676
677 /* Re-enable recieve and transmit as appropriate */
678 cr = 0;
679 cr |=
680 (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_RXEN)) ? SSC_BIT(CR_RXEN) : 0;
681 cr |=
682 (ssc_p->ssc_state.ssc_sr & SSC_BIT(SR_TXEN)) ? SSC_BIT(CR_TXEN) : 0;
683 ssc_writel(ssc_p->ssc->regs, CR, cr);
684
685 return 0;
686}
687#else /* CONFIG_PM */
688# define atmel_ssc_suspend NULL
689# define atmel_ssc_resume NULL
690#endif /* CONFIG_PM */
691
692
693#define ATMEL_SSC_RATES (SNDRV_PCM_RATE_8000_96000)
694
695#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
696 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
697
698struct snd_soc_dai atmel_ssc_dai[NUM_SSC_DEVICES] = {
699 { .name = "atmel-ssc0",
700 .id = 0,
701 .type = SND_SOC_DAI_PCM,
702 .suspend = atmel_ssc_suspend,
703 .resume = atmel_ssc_resume,
704 .playback = {
705 .channels_min = 1,
706 .channels_max = 2,
707 .rates = ATMEL_SSC_RATES,
708 .formats = ATMEL_SSC_FORMATS,},
709 .capture = {
710 .channels_min = 1,
711 .channels_max = 2,
712 .rates = ATMEL_SSC_RATES,
713 .formats = ATMEL_SSC_FORMATS,},
714 .ops = {
715 .startup = atmel_ssc_startup,
716 .shutdown = atmel_ssc_shutdown,
717 .prepare = atmel_ssc_prepare,
718 .hw_params = atmel_ssc_hw_params,},
719 .dai_ops = {
720 .set_fmt = atmel_ssc_set_dai_fmt,
721 .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
722 .private_data = &ssc_info[0],
723 },
724#if NUM_SSC_DEVICES == 3
725 { .name = "atmel-ssc1",
726 .id = 1,
727 .type = SND_SOC_DAI_PCM,
728 .suspend = atmel_ssc_suspend,
729 .resume = atmel_ssc_resume,
730 .playback = {
731 .channels_min = 1,
732 .channels_max = 2,
733 .rates = ATMEL_SSC_RATES,
734 .formats = ATMEL_SSC_FORMATS,},
735 .capture = {
736 .channels_min = 1,
737 .channels_max = 2,
738 .rates = ATMEL_SSC_RATES,
739 .formats = ATMEL_SSC_FORMATS,},
740 .ops = {
741 .startup = atmel_ssc_startup,
742 .shutdown = atmel_ssc_shutdown,
743 .prepare = atmel_ssc_prepare,
744 .hw_params = atmel_ssc_hw_params,},
745 .dai_ops = {
746 .set_fmt = atmel_ssc_set_dai_fmt,
747 .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
748 .private_data = &ssc_info[1],
749 },
750 { .name = "atmel-ssc2",
751 .id = 2,
752 .type = SND_SOC_DAI_PCM,
753 .suspend = atmel_ssc_suspend,
754 .resume = atmel_ssc_resume,
755 .playback = {
756 .channels_min = 1,
757 .channels_max = 2,
758 .rates = ATMEL_SSC_RATES,
759 .formats = ATMEL_SSC_FORMATS,},
760 .capture = {
761 .channels_min = 1,
762 .channels_max = 2,
763 .rates = ATMEL_SSC_RATES,
764 .formats = ATMEL_SSC_FORMATS,},
765 .ops = {
766 .startup = atmel_ssc_startup,
767 .shutdown = atmel_ssc_shutdown,
768 .prepare = atmel_ssc_prepare,
769 .hw_params = atmel_ssc_hw_params,},
770 .dai_ops = {
771 .set_fmt = atmel_ssc_set_dai_fmt,
772 .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
773 .private_data = &ssc_info[2],
774 },
775#endif
776};
777EXPORT_SYMBOL_GPL(atmel_ssc_dai);
778
779/* Module information */
780MODULE_AUTHOR("Sedji Gaouaou, sedji.gaouaou@atmel.com, www.atmel.com");
781MODULE_DESCRIPTION("ATMEL SSC ASoC Interface");
782MODULE_LICENSE("GPL");
diff --git a/sound/soc/atmel/atmel_ssc_dai.h b/sound/soc/atmel/atmel_ssc_dai.h
new file mode 100644
index 000000000000..a828746e8a2f
--- /dev/null
+++ b/sound/soc/atmel/atmel_ssc_dai.h
@@ -0,0 +1,121 @@
1/*
2 * atmel_ssc_dai.h - ALSA SSC interface for the Atmel SoC
3 *
4 * Copyright (C) 2005 SAN People
5 * Copyright (C) 2008 Atmel
6 *
7 * Author: Sedji Gaouaou <sedji.gaouaou@atmel.com>
8 * ATMEL CORP.
9 *
10 * Based on at91-ssc.c by
11 * Frank Mandarino <fmandarino@endrelia.com>
12 * Based on pxa2xx Platform drivers by
13 * Liam Girdwood <liam.girdwood@wolfsonmicro.com>
14 *
15 * This program is free software; you can redistribute it and/or modify
16 * it under the terms of the GNU General Public License as published by
17 * the Free Software Foundation; either version 2 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License
26 * along with this program; if not, write to the Free Software
27 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 */
29
30#ifndef _ATMEL_SSC_DAI_H
31#define _ATMEL_SSC_DAI_H
32
33#include <linux/types.h>
34#include <linux/atmel-ssc.h>
35
36#include "atmel-pcm.h"
37
38/* SSC system clock ids */
39#define ATMEL_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */
40
41/* SSC divider ids */
42#define ATMEL_SSC_CMR_DIV 0 /* MCK divider for BCLK */
43#define ATMEL_SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */
44#define ATMEL_SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */
45/*
46 * SSC direction masks
47 */
48#define SSC_DIR_MASK_UNUSED 0
49#define SSC_DIR_MASK_PLAYBACK 1
50#define SSC_DIR_MASK_CAPTURE 2
51
52/*
53 * SSC register values that Atmel left out of <linux/atmel-ssc.h>. These
54 * are expected to be used with SSC_BF
55 */
56/* START bit field values */
57#define SSC_START_CONTINUOUS 0
58#define SSC_START_TX_RX 1
59#define SSC_START_LOW_RF 2
60#define SSC_START_HIGH_RF 3
61#define SSC_START_FALLING_RF 4
62#define SSC_START_RISING_RF 5
63#define SSC_START_LEVEL_RF 6
64#define SSC_START_EDGE_RF 7
65#define SSS_START_COMPARE_0 8
66
67/* CKI bit field values */
68#define SSC_CKI_FALLING 0
69#define SSC_CKI_RISING 1
70
71/* CKO bit field values */
72#define SSC_CKO_NONE 0
73#define SSC_CKO_CONTINUOUS 1
74#define SSC_CKO_TRANSFER 2
75
76/* CKS bit field values */
77#define SSC_CKS_DIV 0
78#define SSC_CKS_CLOCK 1
79#define SSC_CKS_PIN 2
80
81/* FSEDGE bit field values */
82#define SSC_FSEDGE_POSITIVE 0
83#define SSC_FSEDGE_NEGATIVE 1
84
85/* FSOS bit field values */
86#define SSC_FSOS_NONE 0
87#define SSC_FSOS_NEGATIVE 1
88#define SSC_FSOS_POSITIVE 2
89#define SSC_FSOS_LOW 3
90#define SSC_FSOS_HIGH 4
91#define SSC_FSOS_TOGGLE 5
92
93#define START_DELAY 1
94
95struct atmel_ssc_state {
96 u32 ssc_cmr;
97 u32 ssc_rcmr;
98 u32 ssc_rfmr;
99 u32 ssc_tcmr;
100 u32 ssc_tfmr;
101 u32 ssc_sr;
102 u32 ssc_imr;
103};
104
105
106struct atmel_ssc_info {
107 char *name;
108 struct ssc_device *ssc;
109 spinlock_t lock; /* lock for dir_mask */
110 unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */
111 unsigned short initialized; /* true if SSC has been initialized */
112 unsigned short daifmt;
113 unsigned short cmr_div;
114 unsigned short tcmr_period;
115 unsigned short rcmr_period;
116 struct atmel_pcm_dma_params *dma_params[2];
117 struct atmel_ssc_state ssc_state;
118};
119extern struct snd_soc_dai atmel_ssc_dai[];
120
121#endif /* _AT91_SSC_DAI_H */
diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c
new file mode 100644
index 000000000000..5b07cf7ea4e7
--- /dev/null
+++ b/sound/soc/atmel/playpaq_wm8510.c
@@ -0,0 +1,513 @@
1/* sound/soc/at32/playpaq_wm8510.c
2 * ASoC machine driver for PlayPaq using WM8510 codec
3 *
4 * Copyright (C) 2008 Long Range Systems
5 * Geoffrey Wossum <gwossum@acm.org>
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 * This code is largely inspired by sound/soc/at91/eti_b1_wm8731.c
12 *
13 * NOTE: If you don't have the AT32 enhanced portmux configured (which
14 * isn't currently in the mainline or Atmel patched kernel), you will
15 * need to set the MCLK pin (PA30) to peripheral A in your board initialization
16 * code. Something like:
17 * at32_select_periph(GPIO_PIN_PA(30), GPIO_PERIPH_A, 0);
18 *
19 */
20
21/* #define DEBUG */
22
23#include <linux/module.h>
24#include <linux/moduleparam.h>
25#include <linux/version.h>
26#include <linux/kernel.h>
27#include <linux/errno.h>
28#include <linux/clk.h>
29#include <linux/timer.h>
30#include <linux/interrupt.h>
31#include <linux/platform_device.h>
32
33#include <sound/core.h>
34#include <sound/pcm.h>
35#include <sound/pcm_params.h>
36#include <sound/soc.h>
37#include <sound/soc-dapm.h>
38
39#include <mach/at32ap700x.h>
40#include <mach/portmux.h>
41
42#include "../codecs/wm8510.h"
43#include "atmel-pcm.h"
44#include "atmel_ssc_dai.h"
45
46
47/*-------------------------------------------------------------------------*\
48 * constants
49\*-------------------------------------------------------------------------*/
50#define MCLK_PIN GPIO_PIN_PA(30)
51#define MCLK_PERIPH GPIO_PERIPH_A
52
53
54/*-------------------------------------------------------------------------*\
55 * data types
56\*-------------------------------------------------------------------------*/
57/* SSC clocking data */
58struct ssc_clock_data {
59 /* CMR div */
60 unsigned int cmr_div;
61
62 /* Frame period (as needed by xCMR.PERIOD) */
63 unsigned int period;
64
65 /* The SSC clock rate these settings where calculated for */
66 unsigned long ssc_rate;
67};
68
69
70/*-------------------------------------------------------------------------*\
71 * module data
72\*-------------------------------------------------------------------------*/
73static struct clk *_gclk0;
74static struct clk *_pll0;
75
76#define CODEC_CLK (_gclk0)
77
78
79/*-------------------------------------------------------------------------*\
80 * Sound SOC operations
81\*-------------------------------------------------------------------------*/
82#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
83static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock(
84 struct snd_pcm_hw_params *params,
85 struct snd_soc_dai *cpu_dai)
86{
87 struct at32_ssc_info *ssc_p = cpu_dai->private_data;
88 struct ssc_device *ssc = ssc_p->ssc;
89 struct ssc_clock_data cd;
90 unsigned int rate, width_bits, channels;
91 unsigned int bitrate, ssc_div;
92 unsigned actual_rate;
93
94
95 /*
96 * Figure out required bitrate
97 */
98 rate = params_rate(params);
99 channels = params_channels(params);
100 width_bits = snd_pcm_format_physical_width(params_format(params));
101 bitrate = rate * width_bits * channels;
102
103
104 /*
105 * Figure out required SSC divider and period for required bitrate
106 */
107 cd.ssc_rate = clk_get_rate(ssc->clk);
108 ssc_div = cd.ssc_rate / bitrate;
109 cd.cmr_div = ssc_div / 2;
110 if (ssc_div & 1) {
111 /* round cmr_div up */
112 cd.cmr_div++;
113 }
114 cd.period = width_bits - 1;
115
116
117 /*
118 * Find actual rate, compare to requested rate
119 */
120 actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1));
121 pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n",
122 rate, actual_rate);
123
124
125 return cd;
126}
127#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
128
129
130
131static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream,
132 struct snd_pcm_hw_params *params)
133{
134 struct snd_soc_pcm_runtime *rtd = substream->private_data;
135 struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
136 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
137 struct at32_ssc_info *ssc_p = cpu_dai->private_data;
138 struct ssc_device *ssc = ssc_p->ssc;
139 unsigned int pll_out = 0, bclk = 0, mclk_div = 0;
140 int ret;
141
142
143 /* Due to difficulties with getting the correct clocks from the AT32's
144 * PLL0, we're going to let the CODEC be in charge of all the clocks
145 */
146#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
147 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
148 SND_SOC_DAIFMT_NB_NF |
149 SND_SOC_DAIFMT_CBM_CFM);
150#else
151 struct ssc_clock_data cd;
152 const unsigned int fmt = (SND_SOC_DAIFMT_I2S |
153 SND_SOC_DAIFMT_NB_NF |
154 SND_SOC_DAIFMT_CBS_CFS);
155#endif
156
157 if (ssc == NULL) {
158 pr_warning("playpaq_wm8510_hw_params: ssc is NULL!\n");
159 return -EINVAL;
160 }
161
162
163 /*
164 * Figure out PLL and BCLK dividers for WM8510
165 */
166 switch (params_rate(params)) {
167 case 48000:
168 pll_out = 12288000;
169 mclk_div = WM8510_MCLKDIV_1;
170 bclk = WM8510_BCLKDIV_8;
171 break;
172
173 case 44100:
174 pll_out = 11289600;
175 mclk_div = WM8510_MCLKDIV_1;
176 bclk = WM8510_BCLKDIV_8;
177 break;
178
179 case 22050:
180 pll_out = 11289600;
181 mclk_div = WM8510_MCLKDIV_2;
182 bclk = WM8510_BCLKDIV_8;
183 break;
184
185 case 16000:
186 pll_out = 12288000;
187 mclk_div = WM8510_MCLKDIV_3;
188 bclk = WM8510_BCLKDIV_8;
189 break;
190
191 case 11025:
192 pll_out = 11289600;
193 mclk_div = WM8510_MCLKDIV_4;
194 bclk = WM8510_BCLKDIV_8;
195 break;
196
197 case 8000:
198 pll_out = 12288000;
199 mclk_div = WM8510_MCLKDIV_6;
200 bclk = WM8510_BCLKDIV_8;
201 break;
202
203 default:
204 pr_warning("playpaq_wm8510: Unsupported sample rate %d\n",
205 params_rate(params));
206 return -EINVAL;
207 }
208
209
210 /*
211 * set CPU and CODEC DAI configuration
212 */
213 ret = snd_soc_dai_set_fmt(codec_dai, fmt);
214 if (ret < 0) {
215 pr_warning("playpaq_wm8510: "
216 "Failed to set CODEC DAI format (%d)\n",
217 ret);
218 return ret;
219 }
220 ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
221 if (ret < 0) {
222 pr_warning("playpaq_wm8510: "
223 "Failed to set CPU DAI format (%d)\n",
224 ret);
225 return ret;
226 }
227
228
229 /*
230 * Set CPU clock configuration
231 */
232#if defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
233 cd = playpaq_wm8510_calc_ssc_clock(params, cpu_dai);
234 pr_debug("playpaq_wm8510: cmr_div = %d, period = %d\n",
235 cd.cmr_div, cd.period);
236 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_CMR_DIV, cd.cmr_div);
237 if (ret < 0) {
238 pr_warning("playpaq_wm8510: Failed to set CPU CMR_DIV (%d)\n",
239 ret);
240 return ret;
241 }
242 ret = snd_soc_dai_set_clkdiv(cpu_dai, AT32_SSC_TCMR_PERIOD,
243 cd.period);
244 if (ret < 0) {
245 pr_warning("playpaq_wm8510: "
246 "Failed to set CPU transmit period (%d)\n",
247 ret);
248 return ret;
249 }
250#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
251
252
253 /*
254 * Set CODEC clock configuration
255 */
256 pr_debug("playpaq_wm8510: "
257 "pll_in = %ld, pll_out = %u, bclk = %x, mclk = %x\n",
258 clk_get_rate(CODEC_CLK), pll_out, bclk, mclk_div);
259
260
261#if !defined CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE
262 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_BCLKDIV, bclk);
263 if (ret < 0) {
264 pr_warning
265 ("playpaq_wm8510: Failed to set CODEC DAI BCLKDIV (%d)\n",
266 ret);
267 return ret;
268 }
269#endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */
270
271
272 ret = snd_soc_dai_set_pll(codec_dai, 0,
273 clk_get_rate(CODEC_CLK), pll_out);
274 if (ret < 0) {
275 pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n",
276 ret);
277 return ret;
278 }
279
280
281 ret = snd_soc_dai_set_clkdiv(codec_dai, WM8510_MCLKDIV, mclk_div);
282 if (ret < 0) {
283 pr_warning("playpaq_wm8510: Failed to set CODEC MCLKDIV (%d)\n",
284 ret);
285 return ret;
286 }
287
288
289 return 0;
290}
291
292
293
294static struct snd_soc_ops playpaq_wm8510_ops = {
295 .hw_params = playpaq_wm8510_hw_params,
296};
297
298
299
300static const struct snd_soc_dapm_widget playpaq_dapm_widgets[] = {
301 SND_SOC_DAPM_MIC("Int Mic", NULL),
302 SND_SOC_DAPM_SPK("Ext Spk", NULL),
303};
304
305
306
307static const struct snd_soc_dapm_route intercon[] = {
308 /* speaker connected to SPKOUT */
309 {"Ext Spk", NULL, "SPKOUTP"},
310 {"Ext Spk", NULL, "SPKOUTN"},
311
312 {"Mic Bias", NULL, "Int Mic"},
313 {"MICN", NULL, "Mic Bias"},
314 {"MICP", NULL, "Mic Bias"},
315};
316
317
318
319static int playpaq_wm8510_init(struct snd_soc_codec *codec)
320{
321 int i;
322
323 /*
324 * Add DAPM widgets
325 */
326 for (i = 0; i < ARRAY_SIZE(playpaq_dapm_widgets); i++)
327 snd_soc_dapm_new_control(codec, &playpaq_dapm_widgets[i]);
328
329
330
331 /*
332 * Setup audio path interconnects
333 */
334 snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon));
335
336
337
338 /* always connected pins */
339 snd_soc_dapm_enable_pin(codec, "Int Mic");
340 snd_soc_dapm_enable_pin(codec, "Ext Spk");
341 snd_soc_dapm_sync(codec);
342
343
344
345 /* Make CSB show PLL rate */
346 snd_soc_dai_set_clkdiv(codec->dai, WM8510_OPCLKDIV,
347 WM8510_OPCLKDIV_1 | 4);
348
349 return 0;
350}
351
352
353
354static struct snd_soc_dai_link playpaq_wm8510_dai = {
355 .name = "WM8510",
356 .stream_name = "WM8510 PCM",
357 .cpu_dai = &at32_ssc_dai[0],
358 .codec_dai = &wm8510_dai,
359 .init = playpaq_wm8510_init,
360 .ops = &playpaq_wm8510_ops,
361};
362
363
364
365static struct snd_soc_machine snd_soc_machine_playpaq = {
366 .name = "LRS_PlayPaq_WM8510",
367 .dai_link = &playpaq_wm8510_dai,
368 .num_links = 1,
369};
370
371
372
373static struct wm8510_setup_data playpaq_wm8510_setup = {
374 .i2c_bus = 0,
375 .i2c_address = 0x1a,
376};
377
378
379
380static struct snd_soc_device playpaq_wm8510_snd_devdata = {
381 .machine = &snd_soc_machine_playpaq,
382 .platform = &at32_soc_platform,
383 .codec_dev = &soc_codec_dev_wm8510,
384 .codec_data = &playpaq_wm8510_setup,
385};
386
387static struct platform_device *playpaq_snd_device;
388
389
390static int __init playpaq_asoc_init(void)
391{
392 int ret = 0;
393 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
394 struct ssc_device *ssc = NULL;
395
396
397 /*
398 * Request SSC device
399 */
400 ssc = ssc_request(0);
401 if (IS_ERR(ssc)) {
402 ret = PTR_ERR(ssc);
403 goto err_ssc;
404 }
405 ssc_p->ssc = ssc;
406
407
408 /*
409 * Configure MCLK for WM8510
410 */
411 _gclk0 = clk_get(NULL, "gclk0");
412 if (IS_ERR(_gclk0)) {
413 _gclk0 = NULL;
414 goto err_gclk0;
415 }
416 _pll0 = clk_get(NULL, "pll0");
417 if (IS_ERR(_pll0)) {
418 _pll0 = NULL;
419 goto err_pll0;
420 }
421 if (clk_set_parent(_gclk0, _pll0)) {
422 pr_warning("snd-soc-playpaq: "
423 "Failed to set PLL0 as parent for DAC clock\n");
424 goto err_set_clk;
425 }
426 clk_set_rate(CODEC_CLK, 12000000);
427 clk_enable(CODEC_CLK);
428
429#if defined CONFIG_AT32_ENHANCED_PORTMUX
430 at32_select_periph(MCLK_PIN, MCLK_PERIPH, 0);
431#endif
432
433
434 /*
435 * Create and register platform device
436 */
437 playpaq_snd_device = platform_device_alloc("soc-audio", 0);
438 if (playpaq_snd_device == NULL) {
439 ret = -ENOMEM;
440 goto err_device_alloc;
441 }
442
443 platform_set_drvdata(playpaq_snd_device, &playpaq_wm8510_snd_devdata);
444 playpaq_wm8510_snd_devdata.dev = &playpaq_snd_device->dev;
445
446 ret = platform_device_add(playpaq_snd_device);
447 if (ret) {
448 pr_warning("playpaq_wm8510: platform_device_add failed (%d)\n",
449 ret);
450 goto err_device_add;
451 }
452
453 return 0;
454
455
456err_device_add:
457 if (playpaq_snd_device != NULL) {
458 platform_device_put(playpaq_snd_device);
459 playpaq_snd_device = NULL;
460 }
461err_device_alloc:
462err_set_clk:
463 if (_pll0 != NULL) {
464 clk_put(_pll0);
465 _pll0 = NULL;
466 }
467err_pll0:
468 if (_gclk0 != NULL) {
469 clk_put(_gclk0);
470 _gclk0 = NULL;
471 }
472err_gclk0:
473 ssc_free(ssc);
474err_ssc:
475 return ret;
476}
477
478
479static void __exit playpaq_asoc_exit(void)
480{
481 struct at32_ssc_info *ssc_p = playpaq_wm8510_dai.cpu_dai->private_data;
482 struct ssc_device *ssc;
483
484 if (ssc_p != NULL) {
485 ssc = ssc_p->ssc;
486 if (ssc != NULL)
487 ssc_free(ssc);
488 ssc_p->ssc = NULL;
489 }
490
491 if (_gclk0 != NULL) {
492 clk_put(_gclk0);
493 _gclk0 = NULL;
494 }
495 if (_pll0 != NULL) {
496 clk_put(_pll0);
497 _pll0 = NULL;
498 }
499
500#if defined CONFIG_AT32_ENHANCED_PORTMUX
501 at32_free_pin(MCLK_PIN);
502#endif
503
504 platform_device_unregister(playpaq_snd_device);
505 playpaq_snd_device = NULL;
506}
507
508module_init(playpaq_asoc_init);
509module_exit(playpaq_asoc_exit);
510
511MODULE_AUTHOR("Geoffrey Wossum <gwossum@acm.org>");
512MODULE_DESCRIPTION("ASoC machine driver for LRS PlayPaq");
513MODULE_LICENSE("GPL");