diff options
Diffstat (limited to 'sound/soc/imx/mxc-ssi.c')
-rw-r--r-- | sound/soc/imx/mxc-ssi.c | 868 |
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 | |||
42 | static int ssi_active[2] = {0, 0}; | ||
43 | |||
44 | /* DMA information for mx1_mx2 platforms */ | ||
45 | static 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 | |||
55 | static 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 | |||
65 | static 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 | |||
75 | static 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 | |||
85 | static 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 | |||
95 | static 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 | |||
105 | static 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 | |||
115 | static 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 | |||
125 | static struct clk *ssi_clk0, *ssi_clk1; | ||
126 | |||
127 | int 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 | } | ||
144 | EXPORT_SYMBOL(get_ssi_clk); | ||
145 | |||
146 | void 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 | } | ||
159 | EXPORT_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 | */ | ||
165 | static 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 | */ | ||
212 | static 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 | */ | ||
273 | static 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 | */ | ||
315 | static 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 | |||
421 | static 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 | |||
510 | int 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 | |||
562 | int 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 | */ | ||
618 | int 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 | |||
653 | int 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 | |||
676 | static 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 | |||
717 | static 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 | ||
744 | static int imx_ssi_suspend(struct platform_device *dev, | ||
745 | struct snd_soc_dai *dai) | ||
746 | { | ||
747 | return 0; | ||
748 | } | ||
749 | |||
750 | static 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 | |||
772 | static 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 | |||
784 | struct 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 | }; | ||
850 | EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai); | ||
851 | |||
852 | static 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 | |||
858 | static 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 | |||
864 | module_init(imx_ssi_init); | ||
865 | module_exit(imx_ssi_exit); | ||
866 | MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com"); | ||
867 | MODULE_DESCRIPTION("i.MX ASoC I2S driver"); | ||
868 | MODULE_LICENSE("GPL"); | ||