aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/imx/mxc-ssi.c
diff options
context:
space:
mode:
authorjavier Martin <javier.martin@vista-silicon.com>2009-08-05 02:47:48 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-08-05 17:31:54 -0400
commit9d8bc2968cb0b50ffa76ed806591974e34fc296a (patch)
tree598964c6dccbfc7cdc5823f5f4e58ba2fe90ffbb /sound/soc/imx/mxc-ssi.c
parentfd6a6394d7d6142afb3d4c87beb3c2c1d25c69bd (diff)
ASoC: add DAI platform ssi driver for MXC
This adds support for DAI platform for the SSI present in MXC platforms. It currently does not support i.MX3, the only thing necessary to do this is to export DMA data for i.MX3 interface which I haven't done because I don't have a i.MX3 based board available. It has been tested on i.MX27 board. Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/imx/mxc-ssi.c')
-rw-r--r--sound/soc/imx/mxc-ssi.c868
1 files changed, 868 insertions, 0 deletions
diff --git a/sound/soc/imx/mxc-ssi.c b/sound/soc/imx/mxc-ssi.c
new file mode 100644
index 000000000000..3806ff2c0cd4
--- /dev/null
+++ b/sound/soc/imx/mxc-ssi.c
@@ -0,0 +1,868 @@
1/*
2 * mxc-ssi.c -- SSI driver for Freescale IMX
3 *
4 * Copyright 2006 Wolfson Microelectronics PLC.
5 * Author: Liam Girdwood
6 * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
7 *
8 * Based on mxc-alsa-mc13783 (C) 2006 Freescale.
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version.
14 *
15 * TODO:
16 * Need to rework SSI register defs when new defs go into mainline.
17 * Add support for TDM and FIFO 1.
18 * Add support for i.mx3x DMA interface.
19 *
20 */
21
22
23#include <linux/module.h>
24#include <linux/init.h>
25#include <linux/platform_device.h>
26#include <linux/slab.h>
27#include <linux/dma-mapping.h>
28#include <linux/clk.h>
29#include <sound/core.h>
30#include <sound/pcm.h>
31#include <sound/pcm_params.h>
32#include <sound/soc.h>
33#include <mach/dma-mx1-mx2.h>
34#include <asm/mach-types.h>
35
36#include "mxc-ssi.h"
37#include "mx1_mx2-pcm.h"
38
39#define SSI1_PORT 0
40#define SSI2_PORT 1
41
42static int ssi_active[2] = {0, 0};
43
44/* DMA information for mx1_mx2 platforms */
45static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out0 = {
46 .name = "SSI1 PCM Stereo out 0",
47 .transfer_type = DMA_MODE_WRITE,
48 .per_address = SSI1_BASE_ADDR + STX0,
49 .event_id = DMA_REQ_SSI1_TX0,
50 .watermark_level = TXFIFO_WATERMARK,
51 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
52 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
53};
54
55static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_out1 = {
56 .name = "SSI1 PCM Stereo out 1",
57 .transfer_type = DMA_MODE_WRITE,
58 .per_address = SSI1_BASE_ADDR + STX1,
59 .event_id = DMA_REQ_SSI1_TX1,
60 .watermark_level = TXFIFO_WATERMARK,
61 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
62 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
63};
64
65static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in0 = {
66 .name = "SSI1 PCM Stereo in 0",
67 .transfer_type = DMA_MODE_READ,
68 .per_address = SSI1_BASE_ADDR + SRX0,
69 .event_id = DMA_REQ_SSI1_RX0,
70 .watermark_level = RXFIFO_WATERMARK,
71 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
72 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
73};
74
75static struct mx1_mx2_pcm_dma_params imx_ssi1_pcm_stereo_in1 = {
76 .name = "SSI1 PCM Stereo in 1",
77 .transfer_type = DMA_MODE_READ,
78 .per_address = SSI1_BASE_ADDR + SRX1,
79 .event_id = DMA_REQ_SSI1_RX1,
80 .watermark_level = RXFIFO_WATERMARK,
81 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
82 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
83};
84
85static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out0 = {
86 .name = "SSI2 PCM Stereo out 0",
87 .transfer_type = DMA_MODE_WRITE,
88 .per_address = SSI2_BASE_ADDR + STX0,
89 .event_id = DMA_REQ_SSI2_TX0,
90 .watermark_level = TXFIFO_WATERMARK,
91 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
92 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
93};
94
95static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_out1 = {
96 .name = "SSI2 PCM Stereo out 1",
97 .transfer_type = DMA_MODE_WRITE,
98 .per_address = SSI2_BASE_ADDR + STX1,
99 .event_id = DMA_REQ_SSI2_TX1,
100 .watermark_level = TXFIFO_WATERMARK,
101 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
102 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
103};
104
105static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in0 = {
106 .name = "SSI2 PCM Stereo in 0",
107 .transfer_type = DMA_MODE_READ,
108 .per_address = SSI2_BASE_ADDR + SRX0,
109 .event_id = DMA_REQ_SSI2_RX0,
110 .watermark_level = RXFIFO_WATERMARK,
111 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
112 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
113};
114
115static struct mx1_mx2_pcm_dma_params imx_ssi2_pcm_stereo_in1 = {
116 .name = "SSI2 PCM Stereo in 1",
117 .transfer_type = DMA_MODE_READ,
118 .per_address = SSI2_BASE_ADDR + SRX1,
119 .event_id = DMA_REQ_SSI2_RX1,
120 .watermark_level = RXFIFO_WATERMARK,
121 .per_config = IMX_DMA_MEMSIZE_16 | IMX_DMA_TYPE_FIFO,
122 .mem_config = IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR,
123};
124
125static struct clk *ssi_clk0, *ssi_clk1;
126
127int get_ssi_clk(int ssi, struct device *dev)
128{
129 switch (ssi) {
130 case 0:
131 ssi_clk0 = clk_get(dev, "ssi1");
132 if (IS_ERR(ssi_clk0))
133 return PTR_ERR(ssi_clk0);
134 return 0;
135 case 1:
136 ssi_clk1 = clk_get(dev, "ssi2");
137 if (IS_ERR(ssi_clk1))
138 return PTR_ERR(ssi_clk1);
139 return 0;
140 default:
141 return -EINVAL;
142 }
143}
144EXPORT_SYMBOL(get_ssi_clk);
145
146void put_ssi_clk(int ssi)
147{
148 switch (ssi) {
149 case 0:
150 clk_put(ssi_clk0);
151 ssi_clk0 = NULL;
152 break;
153 case 1:
154 clk_put(ssi_clk1);
155 ssi_clk1 = NULL;
156 break;
157 }
158}
159EXPORT_SYMBOL(put_ssi_clk);
160
161/*
162 * SSI system clock configuration.
163 * Should only be called when port is inactive (i.e. SSIEN = 0).
164 */
165static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
166 int clk_id, unsigned int freq, int dir)
167{
168 u32 scr;
169
170 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
171 scr = SSI1_SCR;
172 pr_debug("%s: SCR for SSI1 is %x\n", __func__, scr);
173 } else {
174 scr = SSI2_SCR;
175 pr_debug("%s: SCR for SSI2 is %x\n", __func__, scr);
176 }
177
178 if (scr & SSI_SCR_SSIEN) {
179 printk(KERN_WARNING "Warning ssi already enabled\n");
180 return 0;
181 }
182
183 switch (clk_id) {
184 case IMX_SSP_SYS_CLK:
185 if (dir == SND_SOC_CLOCK_OUT) {
186 scr |= SSI_SCR_SYS_CLK_EN;
187 pr_debug("%s: clk of is output\n", __func__);
188 } else {
189 scr &= ~SSI_SCR_SYS_CLK_EN;
190 pr_debug("%s: clk of is input\n", __func__);
191 }
192 break;
193 default:
194 return -EINVAL;
195 }
196
197 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
198 pr_debug("%s: writeback of SSI1_SCR\n", __func__);
199 SSI1_SCR = scr;
200 } else {
201 pr_debug("%s: writeback of SSI2_SCR\n", __func__);
202 SSI2_SCR = scr;
203 }
204
205 return 0;
206}
207
208/*
209 * SSI Clock dividers
210 * Should only be called when port is inactive (i.e. SSIEN = 0).
211 */
212static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
213 int div_id, int div)
214{
215 u32 stccr, srccr;
216
217 pr_debug("%s\n", __func__);
218 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
219 if (SSI1_SCR & SSI_SCR_SSIEN)
220 return 0;
221 srccr = SSI1_STCCR;
222 stccr = SSI1_STCCR;
223 } else {
224 if (SSI2_SCR & SSI_SCR_SSIEN)
225 return 0;
226 srccr = SSI2_STCCR;
227 stccr = SSI2_STCCR;
228 }
229
230 switch (div_id) {
231 case IMX_SSI_TX_DIV_2:
232 stccr &= ~SSI_STCCR_DIV2;
233 stccr |= div;
234 break;
235 case IMX_SSI_TX_DIV_PSR:
236 stccr &= ~SSI_STCCR_PSR;
237 stccr |= div;
238 break;
239 case IMX_SSI_TX_DIV_PM:
240 stccr &= ~0xff;
241 stccr |= SSI_STCCR_PM(div);
242 break;
243 case IMX_SSI_RX_DIV_2:
244 stccr &= ~SSI_STCCR_DIV2;
245 stccr |= div;
246 break;
247 case IMX_SSI_RX_DIV_PSR:
248 stccr &= ~SSI_STCCR_PSR;
249 stccr |= div;
250 break;
251 case IMX_SSI_RX_DIV_PM:
252 stccr &= ~0xff;
253 stccr |= SSI_STCCR_PM(div);
254 break;
255 default:
256 return -EINVAL;
257 }
258
259 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
260 SSI1_STCCR = stccr;
261 SSI1_SRCCR = srccr;
262 } else {
263 SSI2_STCCR = stccr;
264 SSI2_SRCCR = srccr;
265 }
266 return 0;
267}
268
269/*
270 * SSI Network Mode or TDM slots configuration.
271 * Should only be called when port is inactive (i.e. SSIEN = 0).
272 */
273static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
274 unsigned int mask, int slots)
275{
276 u32 stmsk, srmsk, stccr;
277
278 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
279 if (SSI1_SCR & SSI_SCR_SSIEN) {
280 printk(KERN_WARNING "Warning ssi already enabled\n");
281 return 0;
282 }
283 stccr = SSI1_STCCR;
284 } else {
285 if (SSI2_SCR & SSI_SCR_SSIEN) {
286 printk(KERN_WARNING "Warning ssi already enabled\n");
287 return 0;
288 }
289 stccr = SSI2_STCCR;
290 }
291
292 stmsk = srmsk = mask;
293 stccr &= ~SSI_STCCR_DC_MASK;
294 stccr |= SSI_STCCR_DC(slots - 1);
295
296 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
297 SSI1_STMSK = stmsk;
298 SSI1_SRMSK = srmsk;
299 SSI1_SRCCR = SSI1_STCCR = stccr;
300 } else {
301 SSI2_STMSK = stmsk;
302 SSI2_SRMSK = srmsk;
303 SSI2_SRCCR = SSI2_STCCR = stccr;
304 }
305
306 return 0;
307}
308
309/*
310 * SSI DAI format configuration.
311 * Should only be called when port is inactive (i.e. SSIEN = 0).
312 * Note: We don't use the I2S modes but instead manually configure the
313 * SSI for I2S.
314 */
315static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai,
316 unsigned int fmt)
317{
318 u32 stcr = 0, srcr = 0, scr;
319
320 /*
321 * This is done to avoid this function to modify
322 * previous set values in stcr
323 */
324 stcr = SSI1_STCR;
325
326 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
327 scr = SSI1_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
328 else
329 scr = SSI2_SCR & ~(SSI_SCR_SYN | SSI_SCR_NET);
330
331 if (scr & SSI_SCR_SSIEN) {
332 printk(KERN_WARNING "Warning ssi already enabled\n");
333 return 0;
334 }
335
336 /* DAI mode */
337 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
338 case SND_SOC_DAIFMT_I2S:
339 /* data on rising edge of bclk, frame low 1clk before data */
340 stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
341 srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0;
342 break;
343 case SND_SOC_DAIFMT_LEFT_J:
344 /* data on rising edge of bclk, frame high with data */
345 stcr |= SSI_STCR_TXBIT0;
346 srcr |= SSI_SRCR_RXBIT0;
347 break;
348 case SND_SOC_DAIFMT_DSP_B:
349 /* data on rising edge of bclk, frame high with data */
350 stcr |= SSI_STCR_TFSL;
351 srcr |= SSI_SRCR_RFSL;
352 break;
353 case SND_SOC_DAIFMT_DSP_A:
354 /* data on rising edge of bclk, frame high 1clk before data */
355 stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
356 srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS;
357 break;
358 }
359
360 /* DAI clock inversion */
361 switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
362 case SND_SOC_DAIFMT_IB_IF:
363 stcr |= SSI_STCR_TFSI;
364 stcr &= ~SSI_STCR_TSCKP;
365 srcr |= SSI_SRCR_RFSI;
366 srcr &= ~SSI_SRCR_RSCKP;
367 break;
368 case SND_SOC_DAIFMT_IB_NF:
369 stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
370 srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI);
371 break;
372 case SND_SOC_DAIFMT_NB_IF:
373 stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
374 srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP;
375 break;
376 case SND_SOC_DAIFMT_NB_NF:
377 stcr &= ~SSI_STCR_TFSI;
378 stcr |= SSI_STCR_TSCKP;
379 srcr &= ~SSI_SRCR_RFSI;
380 srcr |= SSI_SRCR_RSCKP;
381 break;
382 }
383
384 /* DAI clock master masks */
385 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
386 case SND_SOC_DAIFMT_CBS_CFS:
387 stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
388 srcr |= SSI_SRCR_RFDIR | SSI_SRCR_RXDIR;
389 break;
390 case SND_SOC_DAIFMT_CBM_CFS:
391 stcr |= SSI_STCR_TFDIR;
392 srcr |= SSI_SRCR_RFDIR;
393 break;
394 case SND_SOC_DAIFMT_CBS_CFM:
395 stcr |= SSI_STCR_TXDIR;
396 srcr |= SSI_SRCR_RXDIR;
397 break;
398 }
399
400 /* sync */
401 if (!(fmt & SND_SOC_DAIFMT_ASYNC))
402 scr |= SSI_SCR_SYN;
403
404 /* tdm - only for stereo atm */
405 if (fmt & SND_SOC_DAIFMT_TDM)
406 scr |= SSI_SCR_NET;
407
408 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
409 SSI1_STCR = stcr;
410 SSI1_SRCR = srcr;
411 SSI1_SCR = scr;
412 } else {
413 SSI2_STCR = stcr;
414 SSI2_SRCR = srcr;
415 SSI2_SCR = scr;
416 }
417
418 return 0;
419}
420
421static int imx_ssi_startup(struct snd_pcm_substream *substream,
422 struct snd_soc_dai *dai)
423{
424 struct snd_soc_pcm_runtime *rtd = substream->private_data;
425 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
426
427 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
428 /* set up TX DMA params */
429 switch (cpu_dai->id) {
430 case IMX_DAI_SSI0:
431 cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out0;
432 break;
433 case IMX_DAI_SSI1:
434 cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out1;
435 break;
436 case IMX_DAI_SSI2:
437 cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out0;
438 break;
439 case IMX_DAI_SSI3:
440 cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out1;
441 }
442 pr_debug("%s: (playback)\n", __func__);
443 } else {
444 /* set up RX DMA params */
445 switch (cpu_dai->id) {
446 case IMX_DAI_SSI0:
447 cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in0;
448 break;
449 case IMX_DAI_SSI1:
450 cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in1;
451 break;
452 case IMX_DAI_SSI2:
453 cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in0;
454 break;
455 case IMX_DAI_SSI3:
456 cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in1;
457 }
458 pr_debug("%s: (capture)\n", __func__);
459 }
460
461 /*
462 * we cant really change any SSI values after SSI is enabled
463 * need to fix in software for max flexibility - lrg
464 */
465 if (cpu_dai->active) {
466 printk(KERN_WARNING "Warning ssi already enabled\n");
467 return 0;
468 }
469
470 /* reset the SSI port - Sect 45.4.4 */
471 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
472
473 if (!ssi_clk0)
474 return -EINVAL;
475
476 if (ssi_active[SSI1_PORT]++) {
477 pr_debug("%s: exit before reset\n", __func__);
478 return 0;
479 }
480
481 /* SSI1 Reset */
482 SSI1_SCR = 0;
483
484 SSI1_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
485 SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
486 SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
487 SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
488 } else {
489
490 if (!ssi_clk1)
491 return -EINVAL;
492
493 if (ssi_active[SSI2_PORT]++) {
494 pr_debug("%s: exit before reset\n", __func__);
495 return 0;
496 }
497
498 /* SSI2 Reset */
499 SSI2_SCR = 0;
500
501 SSI2_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) |
502 SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) |
503 SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) |
504 SSI_SFCSR_TFWM0(TXFIFO_WATERMARK);
505 }
506
507 return 0;
508}
509
510int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream,
511 struct snd_pcm_hw_params *params)
512{
513 struct snd_soc_pcm_runtime *rtd = substream->private_data;
514 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
515 u32 stccr, stcr, sier;
516
517 pr_debug("%s\n", __func__);
518
519 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
520 stccr = SSI1_STCCR & ~SSI_STCCR_WL_MASK;
521 stcr = SSI1_STCR;
522 sier = SSI1_SIER;
523 } else {
524 stccr = SSI2_STCCR & ~SSI_STCCR_WL_MASK;
525 stcr = SSI2_STCR;
526 sier = SSI2_SIER;
527 }
528
529 /* DAI data (word) size */
530 switch (params_format(params)) {
531 case SNDRV_PCM_FORMAT_S16_LE:
532 stccr |= SSI_STCCR_WL(16);
533 break;
534 case SNDRV_PCM_FORMAT_S20_3LE:
535 stccr |= SSI_STCCR_WL(20);
536 break;
537 case SNDRV_PCM_FORMAT_S24_LE:
538 stccr |= SSI_STCCR_WL(24);
539 break;
540 }
541
542 /* enable interrupts */
543 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
544 stcr |= SSI_STCR_TFEN0;
545 else
546 stcr |= SSI_STCR_TFEN1;
547 sier |= SSI_SIER_TDMAE;
548
549 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
550 SSI1_STCR = stcr;
551 SSI1_STCCR = stccr;
552 SSI1_SIER = sier;
553 } else {
554 SSI2_STCR = stcr;
555 SSI2_STCCR = stccr;
556 SSI2_SIER = sier;
557 }
558
559 return 0;
560}
561
562int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream,
563 struct snd_pcm_hw_params *params)
564{
565 struct snd_soc_pcm_runtime *rtd = substream->private_data;
566 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
567 u32 srccr, srcr, sier;
568
569 pr_debug("%s\n", __func__);
570
571 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
572 srccr = SSI1_SRCCR & ~SSI_SRCCR_WL_MASK;
573 srcr = SSI1_SRCR;
574 sier = SSI1_SIER;
575 } else {
576 srccr = SSI2_SRCCR & ~SSI_SRCCR_WL_MASK;
577 srcr = SSI2_SRCR;
578 sier = SSI2_SIER;
579 }
580
581 /* DAI data (word) size */
582 switch (params_format(params)) {
583 case SNDRV_PCM_FORMAT_S16_LE:
584 srccr |= SSI_SRCCR_WL(16);
585 break;
586 case SNDRV_PCM_FORMAT_S20_3LE:
587 srccr |= SSI_SRCCR_WL(20);
588 break;
589 case SNDRV_PCM_FORMAT_S24_LE:
590 srccr |= SSI_SRCCR_WL(24);
591 break;
592 }
593
594 /* enable interrupts */
595 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
596 srcr |= SSI_SRCR_RFEN0;
597 else
598 srcr |= SSI_SRCR_RFEN1;
599 sier |= SSI_SIER_RDMAE;
600
601 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
602 SSI1_SRCR = srcr;
603 SSI1_SRCCR = srccr;
604 SSI1_SIER = sier;
605 } else {
606 SSI2_SRCR = srcr;
607 SSI2_SRCCR = srccr;
608 SSI2_SIER = sier;
609 }
610
611 return 0;
612}
613
614/*
615 * Should only be called when port is inactive (i.e. SSIEN = 0),
616 * although can be called multiple times by upper layers.
617 */
618int imx_ssi_hw_params(struct snd_pcm_substream *substream,
619 struct snd_pcm_hw_params *params,
620 struct snd_soc_dai *dai)
621{
622 struct snd_soc_pcm_runtime *rtd = substream->private_data;
623 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
624
625 int ret;
626
627 /* cant change any parameters when SSI is running */
628 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
629 if (SSI1_SCR & SSI_SCR_SSIEN) {
630 printk(KERN_WARNING "Warning ssi already enabled\n");
631 return 0;
632 }
633 } else {
634 if (SSI2_SCR & SSI_SCR_SSIEN) {
635 printk(KERN_WARNING "Warning ssi already enabled\n");
636 return 0;
637 }
638 }
639
640 /*
641 * Configure both tx and rx params with the same settings. This is
642 * really a harware restriction because SSI must be disabled until
643 * we can change those values. If there is an active audio stream in
644 * one direction, enabling the other direction with different
645 * settings would mean disturbing the running one.
646 */
647 ret = imx_ssi_hw_tx_params(substream, params);
648 if (ret < 0)
649 return ret;
650 return imx_ssi_hw_rx_params(substream, params);
651}
652
653int imx_ssi_prepare(struct snd_pcm_substream *substream,
654 struct snd_soc_dai *dai)
655{
656 struct snd_soc_pcm_runtime *rtd = substream->private_data;
657 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
658 int ret;
659
660 pr_debug("%s\n", __func__);
661
662 /* Enable clks here to follow SSI recommended init sequence */
663 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) {
664 ret = clk_enable(ssi_clk0);
665 if (ret < 0)
666 printk(KERN_ERR "Unable to enable ssi_clk0\n");
667 } else {
668 ret = clk_enable(ssi_clk1);
669 if (ret < 0)
670 printk(KERN_ERR "Unable to enable ssi_clk1\n");
671 }
672
673 return 0;
674}
675
676static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
677 struct snd_soc_dai *dai)
678{
679 struct snd_soc_pcm_runtime *rtd = substream->private_data;
680 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
681 u32 scr;
682
683 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
684 scr = SSI1_SCR;
685 else
686 scr = SSI2_SCR;
687
688 switch (cmd) {
689 case SNDRV_PCM_TRIGGER_START:
690 case SNDRV_PCM_TRIGGER_RESUME:
691 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
692 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
693 scr |= SSI_SCR_TE | SSI_SCR_SSIEN;
694 else
695 scr |= SSI_SCR_RE | SSI_SCR_SSIEN;
696 break;
697 case SNDRV_PCM_TRIGGER_SUSPEND:
698 case SNDRV_PCM_TRIGGER_STOP:
699 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
700 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
701 scr &= ~SSI_SCR_TE;
702 else
703 scr &= ~SSI_SCR_RE;
704 break;
705 default:
706 return -EINVAL;
707 }
708
709 if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2)
710 SSI1_SCR = scr;
711 else
712 SSI2_SCR = scr;
713
714 return 0;
715}
716
717static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
718 struct snd_soc_dai *dai)
719{
720 struct snd_soc_pcm_runtime *rtd = substream->private_data;
721 struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
722
723 /* shutdown SSI if neither Tx or Rx is active */
724 if (!cpu_dai->active) {
725
726 if (cpu_dai->id == IMX_DAI_SSI0 ||
727 cpu_dai->id == IMX_DAI_SSI2) {
728
729 if (--ssi_active[SSI1_PORT] > 1)
730 return;
731
732 SSI1_SCR = 0;
733 clk_disable(ssi_clk0);
734 } else {
735 if (--ssi_active[SSI2_PORT])
736 return;
737 SSI2_SCR = 0;
738 clk_disable(ssi_clk1);
739 }
740 }
741}
742
743#ifdef CONFIG_PM
744static int imx_ssi_suspend(struct platform_device *dev,
745 struct snd_soc_dai *dai)
746{
747 return 0;
748}
749
750static int imx_ssi_resume(struct platform_device *pdev,
751 struct snd_soc_dai *dai)
752{
753 return 0;
754}
755
756#else
757#define imx_ssi_suspend NULL
758#define imx_ssi_resume NULL
759#endif
760
761#define IMX_SSI_RATES \
762 (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
763 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
764 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
765 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
766 SNDRV_PCM_RATE_96000)
767
768#define IMX_SSI_BITS \
769 (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
770 SNDRV_PCM_FMTBIT_S24_LE)
771
772static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
773 .startup = imx_ssi_startup,
774 .shutdown = imx_ssi_shutdown,
775 .trigger = imx_ssi_trigger,
776 .prepare = imx_ssi_prepare,
777 .hw_params = imx_ssi_hw_params,
778 .set_sysclk = imx_ssi_set_dai_sysclk,
779 .set_clkdiv = imx_ssi_set_dai_clkdiv,
780 .set_fmt = imx_ssi_set_dai_fmt,
781 .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
782};
783
784struct snd_soc_dai imx_ssi_pcm_dai[] = {
785{
786 .name = "imx-i2s-1-0",
787 .id = IMX_DAI_SSI0,
788 .suspend = imx_ssi_suspend,
789 .resume = imx_ssi_resume,
790 .playback = {
791 .channels_min = 1,
792 .channels_max = 2,
793 .formats = IMX_SSI_BITS,
794 .rates = IMX_SSI_RATES,},
795 .capture = {
796 .channels_min = 1,
797 .channels_max = 2,
798 .formats = IMX_SSI_BITS,
799 .rates = IMX_SSI_RATES,},
800 .ops = &imx_ssi_pcm_dai_ops,
801},
802{
803 .name = "imx-i2s-2-0",
804 .id = IMX_DAI_SSI1,
805 .playback = {
806 .channels_min = 1,
807 .channels_max = 2,
808 .formats = IMX_SSI_BITS,
809 .rates = IMX_SSI_RATES,},
810 .capture = {
811 .channels_min = 1,
812 .channels_max = 2,
813 .formats = IMX_SSI_BITS,
814 .rates = IMX_SSI_RATES,},
815 .ops = &imx_ssi_pcm_dai_ops,
816},
817{
818 .name = "imx-i2s-1-1",
819 .id = IMX_DAI_SSI2,
820 .suspend = imx_ssi_suspend,
821 .resume = imx_ssi_resume,
822 .playback = {
823 .channels_min = 1,
824 .channels_max = 2,
825 .formats = IMX_SSI_BITS,
826 .rates = IMX_SSI_RATES,},
827 .capture = {
828 .channels_min = 1,
829 .channels_max = 2,
830 .formats = IMX_SSI_BITS,
831 .rates = IMX_SSI_RATES,},
832 .ops = &imx_ssi_pcm_dai_ops,
833},
834{
835 .name = "imx-i2s-2-1",
836 .id = IMX_DAI_SSI3,
837 .playback = {
838 .channels_min = 1,
839 .channels_max = 2,
840 .formats = IMX_SSI_BITS,
841 .rates = IMX_SSI_RATES,},
842 .capture = {
843 .channels_min = 1,
844 .channels_max = 2,
845 .formats = IMX_SSI_BITS,
846 .rates = IMX_SSI_RATES,},
847 .ops = &imx_ssi_pcm_dai_ops,
848},
849};
850EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
851
852static int __init imx_ssi_init(void)
853{
854 return snd_soc_register_dais(imx_ssi_pcm_dai,
855 ARRAY_SIZE(imx_ssi_pcm_dai));
856}
857
858static void __exit imx_ssi_exit(void)
859{
860 snd_soc_unregister_dais(imx_ssi_pcm_dai,
861 ARRAY_SIZE(imx_ssi_pcm_dai));
862}
863
864module_init(imx_ssi_init);
865module_exit(imx_ssi_exit);
866MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com");
867MODULE_DESCRIPTION("i.MX ASoC I2S driver");
868MODULE_LICENSE("GPL");