diff options
Diffstat (limited to 'sound/soc/imx/mxc-ssi.c')
-rw-r--r-- | sound/soc/imx/mxc-ssi.c | 860 |
1 files changed, 0 insertions, 860 deletions
diff --git a/sound/soc/imx/mxc-ssi.c b/sound/soc/imx/mxc-ssi.c deleted file mode 100644 index ccdefe60e752..000000000000 --- a/sound/soc/imx/mxc-ssi.c +++ /dev/null | |||
@@ -1,860 +0,0 @@ | |||
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 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
401 | SSI1_STCR = stcr; | ||
402 | SSI1_SRCR = srcr; | ||
403 | SSI1_SCR = scr; | ||
404 | } else { | ||
405 | SSI2_STCR = stcr; | ||
406 | SSI2_SRCR = srcr; | ||
407 | SSI2_SCR = scr; | ||
408 | } | ||
409 | |||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | static int imx_ssi_startup(struct snd_pcm_substream *substream, | ||
414 | struct snd_soc_dai *dai) | ||
415 | { | ||
416 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
417 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
418 | |||
419 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
420 | /* set up TX DMA params */ | ||
421 | switch (cpu_dai->id) { | ||
422 | case IMX_DAI_SSI0: | ||
423 | cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out0; | ||
424 | break; | ||
425 | case IMX_DAI_SSI1: | ||
426 | cpu_dai->dma_data = &imx_ssi1_pcm_stereo_out1; | ||
427 | break; | ||
428 | case IMX_DAI_SSI2: | ||
429 | cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out0; | ||
430 | break; | ||
431 | case IMX_DAI_SSI3: | ||
432 | cpu_dai->dma_data = &imx_ssi2_pcm_stereo_out1; | ||
433 | } | ||
434 | pr_debug("%s: (playback)\n", __func__); | ||
435 | } else { | ||
436 | /* set up RX DMA params */ | ||
437 | switch (cpu_dai->id) { | ||
438 | case IMX_DAI_SSI0: | ||
439 | cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in0; | ||
440 | break; | ||
441 | case IMX_DAI_SSI1: | ||
442 | cpu_dai->dma_data = &imx_ssi1_pcm_stereo_in1; | ||
443 | break; | ||
444 | case IMX_DAI_SSI2: | ||
445 | cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in0; | ||
446 | break; | ||
447 | case IMX_DAI_SSI3: | ||
448 | cpu_dai->dma_data = &imx_ssi2_pcm_stereo_in1; | ||
449 | } | ||
450 | pr_debug("%s: (capture)\n", __func__); | ||
451 | } | ||
452 | |||
453 | /* | ||
454 | * we cant really change any SSI values after SSI is enabled | ||
455 | * need to fix in software for max flexibility - lrg | ||
456 | */ | ||
457 | if (cpu_dai->active) { | ||
458 | printk(KERN_WARNING "Warning ssi already enabled\n"); | ||
459 | return 0; | ||
460 | } | ||
461 | |||
462 | /* reset the SSI port - Sect 45.4.4 */ | ||
463 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
464 | |||
465 | if (!ssi_clk0) | ||
466 | return -EINVAL; | ||
467 | |||
468 | if (ssi_active[SSI1_PORT]++) { | ||
469 | pr_debug("%s: exit before reset\n", __func__); | ||
470 | return 0; | ||
471 | } | ||
472 | |||
473 | /* SSI1 Reset */ | ||
474 | SSI1_SCR = 0; | ||
475 | |||
476 | SSI1_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) | | ||
477 | SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) | | ||
478 | SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) | | ||
479 | SSI_SFCSR_TFWM0(TXFIFO_WATERMARK); | ||
480 | } else { | ||
481 | |||
482 | if (!ssi_clk1) | ||
483 | return -EINVAL; | ||
484 | |||
485 | if (ssi_active[SSI2_PORT]++) { | ||
486 | pr_debug("%s: exit before reset\n", __func__); | ||
487 | return 0; | ||
488 | } | ||
489 | |||
490 | /* SSI2 Reset */ | ||
491 | SSI2_SCR = 0; | ||
492 | |||
493 | SSI2_SFCSR = SSI_SFCSR_RFWM1(RXFIFO_WATERMARK) | | ||
494 | SSI_SFCSR_RFWM0(RXFIFO_WATERMARK) | | ||
495 | SSI_SFCSR_TFWM1(TXFIFO_WATERMARK) | | ||
496 | SSI_SFCSR_TFWM0(TXFIFO_WATERMARK); | ||
497 | } | ||
498 | |||
499 | return 0; | ||
500 | } | ||
501 | |||
502 | int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream, | ||
503 | struct snd_pcm_hw_params *params) | ||
504 | { | ||
505 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
506 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
507 | u32 stccr, stcr, sier; | ||
508 | |||
509 | pr_debug("%s\n", __func__); | ||
510 | |||
511 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
512 | stccr = SSI1_STCCR & ~SSI_STCCR_WL_MASK; | ||
513 | stcr = SSI1_STCR; | ||
514 | sier = SSI1_SIER; | ||
515 | } else { | ||
516 | stccr = SSI2_STCCR & ~SSI_STCCR_WL_MASK; | ||
517 | stcr = SSI2_STCR; | ||
518 | sier = SSI2_SIER; | ||
519 | } | ||
520 | |||
521 | /* DAI data (word) size */ | ||
522 | switch (params_format(params)) { | ||
523 | case SNDRV_PCM_FORMAT_S16_LE: | ||
524 | stccr |= SSI_STCCR_WL(16); | ||
525 | break; | ||
526 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
527 | stccr |= SSI_STCCR_WL(20); | ||
528 | break; | ||
529 | case SNDRV_PCM_FORMAT_S24_LE: | ||
530 | stccr |= SSI_STCCR_WL(24); | ||
531 | break; | ||
532 | } | ||
533 | |||
534 | /* enable interrupts */ | ||
535 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) | ||
536 | stcr |= SSI_STCR_TFEN0; | ||
537 | else | ||
538 | stcr |= SSI_STCR_TFEN1; | ||
539 | sier |= SSI_SIER_TDMAE; | ||
540 | |||
541 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
542 | SSI1_STCR = stcr; | ||
543 | SSI1_STCCR = stccr; | ||
544 | SSI1_SIER = sier; | ||
545 | } else { | ||
546 | SSI2_STCR = stcr; | ||
547 | SSI2_STCCR = stccr; | ||
548 | SSI2_SIER = sier; | ||
549 | } | ||
550 | |||
551 | return 0; | ||
552 | } | ||
553 | |||
554 | int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream, | ||
555 | struct snd_pcm_hw_params *params) | ||
556 | { | ||
557 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
558 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
559 | u32 srccr, srcr, sier; | ||
560 | |||
561 | pr_debug("%s\n", __func__); | ||
562 | |||
563 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
564 | srccr = SSI1_SRCCR & ~SSI_SRCCR_WL_MASK; | ||
565 | srcr = SSI1_SRCR; | ||
566 | sier = SSI1_SIER; | ||
567 | } else { | ||
568 | srccr = SSI2_SRCCR & ~SSI_SRCCR_WL_MASK; | ||
569 | srcr = SSI2_SRCR; | ||
570 | sier = SSI2_SIER; | ||
571 | } | ||
572 | |||
573 | /* DAI data (word) size */ | ||
574 | switch (params_format(params)) { | ||
575 | case SNDRV_PCM_FORMAT_S16_LE: | ||
576 | srccr |= SSI_SRCCR_WL(16); | ||
577 | break; | ||
578 | case SNDRV_PCM_FORMAT_S20_3LE: | ||
579 | srccr |= SSI_SRCCR_WL(20); | ||
580 | break; | ||
581 | case SNDRV_PCM_FORMAT_S24_LE: | ||
582 | srccr |= SSI_SRCCR_WL(24); | ||
583 | break; | ||
584 | } | ||
585 | |||
586 | /* enable interrupts */ | ||
587 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) | ||
588 | srcr |= SSI_SRCR_RFEN0; | ||
589 | else | ||
590 | srcr |= SSI_SRCR_RFEN1; | ||
591 | sier |= SSI_SIER_RDMAE; | ||
592 | |||
593 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
594 | SSI1_SRCR = srcr; | ||
595 | SSI1_SRCCR = srccr; | ||
596 | SSI1_SIER = sier; | ||
597 | } else { | ||
598 | SSI2_SRCR = srcr; | ||
599 | SSI2_SRCCR = srccr; | ||
600 | SSI2_SIER = sier; | ||
601 | } | ||
602 | |||
603 | return 0; | ||
604 | } | ||
605 | |||
606 | /* | ||
607 | * Should only be called when port is inactive (i.e. SSIEN = 0), | ||
608 | * although can be called multiple times by upper layers. | ||
609 | */ | ||
610 | int imx_ssi_hw_params(struct snd_pcm_substream *substream, | ||
611 | struct snd_pcm_hw_params *params, | ||
612 | struct snd_soc_dai *dai) | ||
613 | { | ||
614 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
615 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
616 | |||
617 | int ret; | ||
618 | |||
619 | /* cant change any parameters when SSI is running */ | ||
620 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
621 | if (SSI1_SCR & SSI_SCR_SSIEN) { | ||
622 | printk(KERN_WARNING "Warning ssi already enabled\n"); | ||
623 | return 0; | ||
624 | } | ||
625 | } else { | ||
626 | if (SSI2_SCR & SSI_SCR_SSIEN) { | ||
627 | printk(KERN_WARNING "Warning ssi already enabled\n"); | ||
628 | return 0; | ||
629 | } | ||
630 | } | ||
631 | |||
632 | /* | ||
633 | * Configure both tx and rx params with the same settings. This is | ||
634 | * really a harware restriction because SSI must be disabled until | ||
635 | * we can change those values. If there is an active audio stream in | ||
636 | * one direction, enabling the other direction with different | ||
637 | * settings would mean disturbing the running one. | ||
638 | */ | ||
639 | ret = imx_ssi_hw_tx_params(substream, params); | ||
640 | if (ret < 0) | ||
641 | return ret; | ||
642 | return imx_ssi_hw_rx_params(substream, params); | ||
643 | } | ||
644 | |||
645 | int imx_ssi_prepare(struct snd_pcm_substream *substream, | ||
646 | struct snd_soc_dai *dai) | ||
647 | { | ||
648 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
649 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
650 | int ret; | ||
651 | |||
652 | pr_debug("%s\n", __func__); | ||
653 | |||
654 | /* Enable clks here to follow SSI recommended init sequence */ | ||
655 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) { | ||
656 | ret = clk_enable(ssi_clk0); | ||
657 | if (ret < 0) | ||
658 | printk(KERN_ERR "Unable to enable ssi_clk0\n"); | ||
659 | } else { | ||
660 | ret = clk_enable(ssi_clk1); | ||
661 | if (ret < 0) | ||
662 | printk(KERN_ERR "Unable to enable ssi_clk1\n"); | ||
663 | } | ||
664 | |||
665 | return 0; | ||
666 | } | ||
667 | |||
668 | static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, | ||
669 | struct snd_soc_dai *dai) | ||
670 | { | ||
671 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
672 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
673 | u32 scr; | ||
674 | |||
675 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) | ||
676 | scr = SSI1_SCR; | ||
677 | else | ||
678 | scr = SSI2_SCR; | ||
679 | |||
680 | switch (cmd) { | ||
681 | case SNDRV_PCM_TRIGGER_START: | ||
682 | case SNDRV_PCM_TRIGGER_RESUME: | ||
683 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
684 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
685 | scr |= SSI_SCR_TE | SSI_SCR_SSIEN; | ||
686 | else | ||
687 | scr |= SSI_SCR_RE | SSI_SCR_SSIEN; | ||
688 | break; | ||
689 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
690 | case SNDRV_PCM_TRIGGER_STOP: | ||
691 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
692 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
693 | scr &= ~SSI_SCR_TE; | ||
694 | else | ||
695 | scr &= ~SSI_SCR_RE; | ||
696 | break; | ||
697 | default: | ||
698 | return -EINVAL; | ||
699 | } | ||
700 | |||
701 | if (cpu_dai->id == IMX_DAI_SSI0 || cpu_dai->id == IMX_DAI_SSI2) | ||
702 | SSI1_SCR = scr; | ||
703 | else | ||
704 | SSI2_SCR = scr; | ||
705 | |||
706 | return 0; | ||
707 | } | ||
708 | |||
709 | static void imx_ssi_shutdown(struct snd_pcm_substream *substream, | ||
710 | struct snd_soc_dai *dai) | ||
711 | { | ||
712 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
713 | struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; | ||
714 | |||
715 | /* shutdown SSI if neither Tx or Rx is active */ | ||
716 | if (!cpu_dai->active) { | ||
717 | |||
718 | if (cpu_dai->id == IMX_DAI_SSI0 || | ||
719 | cpu_dai->id == IMX_DAI_SSI2) { | ||
720 | |||
721 | if (--ssi_active[SSI1_PORT] > 1) | ||
722 | return; | ||
723 | |||
724 | SSI1_SCR = 0; | ||
725 | clk_disable(ssi_clk0); | ||
726 | } else { | ||
727 | if (--ssi_active[SSI2_PORT]) | ||
728 | return; | ||
729 | SSI2_SCR = 0; | ||
730 | clk_disable(ssi_clk1); | ||
731 | } | ||
732 | } | ||
733 | } | ||
734 | |||
735 | #ifdef CONFIG_PM | ||
736 | static int imx_ssi_suspend(struct platform_device *dev, | ||
737 | struct snd_soc_dai *dai) | ||
738 | { | ||
739 | return 0; | ||
740 | } | ||
741 | |||
742 | static int imx_ssi_resume(struct platform_device *pdev, | ||
743 | struct snd_soc_dai *dai) | ||
744 | { | ||
745 | return 0; | ||
746 | } | ||
747 | |||
748 | #else | ||
749 | #define imx_ssi_suspend NULL | ||
750 | #define imx_ssi_resume NULL | ||
751 | #endif | ||
752 | |||
753 | #define IMX_SSI_RATES \ | ||
754 | (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \ | ||
755 | SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ | ||
756 | SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ | ||
757 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ | ||
758 | SNDRV_PCM_RATE_96000) | ||
759 | |||
760 | #define IMX_SSI_BITS \ | ||
761 | (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ | ||
762 | SNDRV_PCM_FMTBIT_S24_LE) | ||
763 | |||
764 | static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { | ||
765 | .startup = imx_ssi_startup, | ||
766 | .shutdown = imx_ssi_shutdown, | ||
767 | .trigger = imx_ssi_trigger, | ||
768 | .prepare = imx_ssi_prepare, | ||
769 | .hw_params = imx_ssi_hw_params, | ||
770 | .set_sysclk = imx_ssi_set_dai_sysclk, | ||
771 | .set_clkdiv = imx_ssi_set_dai_clkdiv, | ||
772 | .set_fmt = imx_ssi_set_dai_fmt, | ||
773 | .set_tdm_slot = imx_ssi_set_dai_tdm_slot, | ||
774 | }; | ||
775 | |||
776 | struct snd_soc_dai imx_ssi_pcm_dai[] = { | ||
777 | { | ||
778 | .name = "imx-i2s-1-0", | ||
779 | .id = IMX_DAI_SSI0, | ||
780 | .suspend = imx_ssi_suspend, | ||
781 | .resume = imx_ssi_resume, | ||
782 | .playback = { | ||
783 | .channels_min = 1, | ||
784 | .channels_max = 2, | ||
785 | .formats = IMX_SSI_BITS, | ||
786 | .rates = IMX_SSI_RATES,}, | ||
787 | .capture = { | ||
788 | .channels_min = 1, | ||
789 | .channels_max = 2, | ||
790 | .formats = IMX_SSI_BITS, | ||
791 | .rates = IMX_SSI_RATES,}, | ||
792 | .ops = &imx_ssi_pcm_dai_ops, | ||
793 | }, | ||
794 | { | ||
795 | .name = "imx-i2s-2-0", | ||
796 | .id = IMX_DAI_SSI1, | ||
797 | .playback = { | ||
798 | .channels_min = 1, | ||
799 | .channels_max = 2, | ||
800 | .formats = IMX_SSI_BITS, | ||
801 | .rates = IMX_SSI_RATES,}, | ||
802 | .capture = { | ||
803 | .channels_min = 1, | ||
804 | .channels_max = 2, | ||
805 | .formats = IMX_SSI_BITS, | ||
806 | .rates = IMX_SSI_RATES,}, | ||
807 | .ops = &imx_ssi_pcm_dai_ops, | ||
808 | }, | ||
809 | { | ||
810 | .name = "imx-i2s-1-1", | ||
811 | .id = IMX_DAI_SSI2, | ||
812 | .suspend = imx_ssi_suspend, | ||
813 | .resume = imx_ssi_resume, | ||
814 | .playback = { | ||
815 | .channels_min = 1, | ||
816 | .channels_max = 2, | ||
817 | .formats = IMX_SSI_BITS, | ||
818 | .rates = IMX_SSI_RATES,}, | ||
819 | .capture = { | ||
820 | .channels_min = 1, | ||
821 | .channels_max = 2, | ||
822 | .formats = IMX_SSI_BITS, | ||
823 | .rates = IMX_SSI_RATES,}, | ||
824 | .ops = &imx_ssi_pcm_dai_ops, | ||
825 | }, | ||
826 | { | ||
827 | .name = "imx-i2s-2-1", | ||
828 | .id = IMX_DAI_SSI3, | ||
829 | .playback = { | ||
830 | .channels_min = 1, | ||
831 | .channels_max = 2, | ||
832 | .formats = IMX_SSI_BITS, | ||
833 | .rates = IMX_SSI_RATES,}, | ||
834 | .capture = { | ||
835 | .channels_min = 1, | ||
836 | .channels_max = 2, | ||
837 | .formats = IMX_SSI_BITS, | ||
838 | .rates = IMX_SSI_RATES,}, | ||
839 | .ops = &imx_ssi_pcm_dai_ops, | ||
840 | }, | ||
841 | }; | ||
842 | EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai); | ||
843 | |||
844 | static int __init imx_ssi_init(void) | ||
845 | { | ||
846 | return snd_soc_register_dais(imx_ssi_pcm_dai, | ||
847 | ARRAY_SIZE(imx_ssi_pcm_dai)); | ||
848 | } | ||
849 | |||
850 | static void __exit imx_ssi_exit(void) | ||
851 | { | ||
852 | snd_soc_unregister_dais(imx_ssi_pcm_dai, | ||
853 | ARRAY_SIZE(imx_ssi_pcm_dai)); | ||
854 | } | ||
855 | |||
856 | module_init(imx_ssi_init); | ||
857 | module_exit(imx_ssi_exit); | ||
858 | MODULE_AUTHOR("Liam Girdwood, liam.girdwood@wolfsonmicro.com"); | ||
859 | MODULE_DESCRIPTION("i.MX ASoC I2S driver"); | ||
860 | MODULE_LICENSE("GPL"); | ||