diff options
author | Timur Tabi <timur@freescale.com> | 2008-01-11 12:15:26 -0500 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2008-01-31 11:29:55 -0500 |
commit | 17467f23395f05ba7b361f7b504fe0f1095d5bb7 (patch) | |
tree | 8afcd6fa89cfd6e152635719fd935f5cb3cb2532 /sound/soc/fsl/fsl_ssi.c | |
parent | ce22e03e62fd37fb2612abb7af1c66cc17038606 (diff) |
[ALSA] Add ASoC drivers for the Freescale MPC8610 SoC
Add the ASoC drivers for the Freescale MPC8610 SoC and the MPC8610 HPCD
reference board.
Signed-off-by: Timur Tabi <timur@freescale.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/soc/fsl/fsl_ssi.c')
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c new file mode 100644 index 000000000000..145ad13d52d1 --- /dev/null +++ b/sound/soc/fsl/fsl_ssi.c | |||
@@ -0,0 +1,644 @@ | |||
1 | /* | ||
2 | * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver | ||
3 | * | ||
4 | * Author: Timur Tabi <timur@freescale.com> | ||
5 | * | ||
6 | * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed | ||
7 | * under the terms of the GNU General Public License version 2. This | ||
8 | * program is licensed "as is" without any warranty of any kind, whether | ||
9 | * express or implied. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/device.h> | ||
16 | #include <linux/delay.h> | ||
17 | |||
18 | #include <sound/driver.h> | ||
19 | #include <sound/core.h> | ||
20 | #include <sound/pcm.h> | ||
21 | #include <sound/pcm_params.h> | ||
22 | #include <sound/initval.h> | ||
23 | #include <sound/soc.h> | ||
24 | |||
25 | #include <asm/immap_86xx.h> | ||
26 | |||
27 | #include "fsl_ssi.h" | ||
28 | |||
29 | /** | ||
30 | * FSLSSI_I2S_RATES: sample rates supported by the I2S | ||
31 | * | ||
32 | * This driver currently only supports the SSI running in I2S slave mode, | ||
33 | * which means the codec determines the sample rate. Therefore, we tell | ||
34 | * ALSA that we support all rates and let the codec driver decide what rates | ||
35 | * are really supported. | ||
36 | */ | ||
37 | #define FSLSSI_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \ | ||
38 | SNDRV_PCM_RATE_CONTINUOUS) | ||
39 | |||
40 | /** | ||
41 | * FSLSSI_I2S_FORMATS: audio formats supported by the SSI | ||
42 | * | ||
43 | * This driver currently only supports the SSI running in I2S slave mode. | ||
44 | * | ||
45 | * The SSI has a limitation in that the samples must be in the same byte | ||
46 | * order as the host CPU. This is because when multiple bytes are written | ||
47 | * to the STX register, the bytes and bits must be written in the same | ||
48 | * order. The STX is a shift register, so all the bits need to be aligned | ||
49 | * (bit-endianness must match byte-endianness). Processors typically write | ||
50 | * the bits within a byte in the same order that the bytes of a word are | ||
51 | * written in. So if the host CPU is big-endian, then only big-endian | ||
52 | * samples will be written to STX properly. | ||
53 | */ | ||
54 | #ifdef __BIG_ENDIAN | ||
55 | #define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ | ||
56 | SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_S20_3BE | \ | ||
57 | SNDRV_PCM_FMTBIT_S24_3BE | SNDRV_PCM_FMTBIT_S24_BE) | ||
58 | #else | ||
59 | #define FSLSSI_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \ | ||
60 | SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | \ | ||
61 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE) | ||
62 | #endif | ||
63 | |||
64 | /** | ||
65 | * fsl_ssi_private: per-SSI private data | ||
66 | * | ||
67 | * @name: short name for this device ("SSI0", "SSI1", etc) | ||
68 | * @ssi: pointer to the SSI's registers | ||
69 | * @ssi_phys: physical address of the SSI registers | ||
70 | * @irq: IRQ of this SSI | ||
71 | * @dev: struct device pointer | ||
72 | * @playback: the number of playback streams opened | ||
73 | * @capture: the number of capture streams opened | ||
74 | * @cpu_dai: the CPU DAI for this device | ||
75 | * @dev_attr: the sysfs device attribute structure | ||
76 | * @stats: SSI statistics | ||
77 | */ | ||
78 | struct fsl_ssi_private { | ||
79 | char name[8]; | ||
80 | struct ccsr_ssi __iomem *ssi; | ||
81 | dma_addr_t ssi_phys; | ||
82 | unsigned int irq; | ||
83 | struct device *dev; | ||
84 | unsigned int playback; | ||
85 | unsigned int capture; | ||
86 | struct snd_soc_cpu_dai cpu_dai; | ||
87 | struct device_attribute dev_attr; | ||
88 | |||
89 | struct { | ||
90 | unsigned int rfrc; | ||
91 | unsigned int tfrc; | ||
92 | unsigned int cmdau; | ||
93 | unsigned int cmddu; | ||
94 | unsigned int rxt; | ||
95 | unsigned int rdr1; | ||
96 | unsigned int rdr0; | ||
97 | unsigned int tde1; | ||
98 | unsigned int tde0; | ||
99 | unsigned int roe1; | ||
100 | unsigned int roe0; | ||
101 | unsigned int tue1; | ||
102 | unsigned int tue0; | ||
103 | unsigned int tfs; | ||
104 | unsigned int rfs; | ||
105 | unsigned int tls; | ||
106 | unsigned int rls; | ||
107 | unsigned int rff1; | ||
108 | unsigned int rff0; | ||
109 | unsigned int tfe1; | ||
110 | unsigned int tfe0; | ||
111 | } stats; | ||
112 | }; | ||
113 | |||
114 | /** | ||
115 | * fsl_ssi_isr: SSI interrupt handler | ||
116 | * | ||
117 | * Although it's possible to use the interrupt handler to send and receive | ||
118 | * data to/from the SSI, we use the DMA instead. Programming is more | ||
119 | * complicated, but the performance is much better. | ||
120 | * | ||
121 | * This interrupt handler is used only to gather statistics. | ||
122 | * | ||
123 | * @irq: IRQ of the SSI device | ||
124 | * @dev_id: pointer to the ssi_private structure for this SSI device | ||
125 | */ | ||
126 | static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) | ||
127 | { | ||
128 | struct fsl_ssi_private *ssi_private = dev_id; | ||
129 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
130 | irqreturn_t ret = IRQ_NONE; | ||
131 | __be32 sisr; | ||
132 | __be32 sisr2 = 0; | ||
133 | |||
134 | /* We got an interrupt, so read the status register to see what we | ||
135 | were interrupted for. We mask it with the Interrupt Enable register | ||
136 | so that we only check for events that we're interested in. | ||
137 | */ | ||
138 | sisr = in_be32(&ssi->sisr) & in_be32(&ssi->sier); | ||
139 | |||
140 | if (sisr & CCSR_SSI_SISR_RFRC) { | ||
141 | ssi_private->stats.rfrc++; | ||
142 | sisr2 |= CCSR_SSI_SISR_RFRC; | ||
143 | ret = IRQ_HANDLED; | ||
144 | } | ||
145 | |||
146 | if (sisr & CCSR_SSI_SISR_TFRC) { | ||
147 | ssi_private->stats.tfrc++; | ||
148 | sisr2 |= CCSR_SSI_SISR_TFRC; | ||
149 | ret = IRQ_HANDLED; | ||
150 | } | ||
151 | |||
152 | if (sisr & CCSR_SSI_SISR_CMDAU) { | ||
153 | ssi_private->stats.cmdau++; | ||
154 | ret = IRQ_HANDLED; | ||
155 | } | ||
156 | |||
157 | if (sisr & CCSR_SSI_SISR_CMDDU) { | ||
158 | ssi_private->stats.cmddu++; | ||
159 | ret = IRQ_HANDLED; | ||
160 | } | ||
161 | |||
162 | if (sisr & CCSR_SSI_SISR_RXT) { | ||
163 | ssi_private->stats.rxt++; | ||
164 | ret = IRQ_HANDLED; | ||
165 | } | ||
166 | |||
167 | if (sisr & CCSR_SSI_SISR_RDR1) { | ||
168 | ssi_private->stats.rdr1++; | ||
169 | ret = IRQ_HANDLED; | ||
170 | } | ||
171 | |||
172 | if (sisr & CCSR_SSI_SISR_RDR0) { | ||
173 | ssi_private->stats.rdr0++; | ||
174 | ret = IRQ_HANDLED; | ||
175 | } | ||
176 | |||
177 | if (sisr & CCSR_SSI_SISR_TDE1) { | ||
178 | ssi_private->stats.tde1++; | ||
179 | ret = IRQ_HANDLED; | ||
180 | } | ||
181 | |||
182 | if (sisr & CCSR_SSI_SISR_TDE0) { | ||
183 | ssi_private->stats.tde0++; | ||
184 | ret = IRQ_HANDLED; | ||
185 | } | ||
186 | |||
187 | if (sisr & CCSR_SSI_SISR_ROE1) { | ||
188 | ssi_private->stats.roe1++; | ||
189 | sisr2 |= CCSR_SSI_SISR_ROE1; | ||
190 | ret = IRQ_HANDLED; | ||
191 | } | ||
192 | |||
193 | if (sisr & CCSR_SSI_SISR_ROE0) { | ||
194 | ssi_private->stats.roe0++; | ||
195 | sisr2 |= CCSR_SSI_SISR_ROE0; | ||
196 | ret = IRQ_HANDLED; | ||
197 | } | ||
198 | |||
199 | if (sisr & CCSR_SSI_SISR_TUE1) { | ||
200 | ssi_private->stats.tue1++; | ||
201 | sisr2 |= CCSR_SSI_SISR_TUE1; | ||
202 | ret = IRQ_HANDLED; | ||
203 | } | ||
204 | |||
205 | if (sisr & CCSR_SSI_SISR_TUE0) { | ||
206 | ssi_private->stats.tue0++; | ||
207 | sisr2 |= CCSR_SSI_SISR_TUE0; | ||
208 | ret = IRQ_HANDLED; | ||
209 | } | ||
210 | |||
211 | if (sisr & CCSR_SSI_SISR_TFS) { | ||
212 | ssi_private->stats.tfs++; | ||
213 | ret = IRQ_HANDLED; | ||
214 | } | ||
215 | |||
216 | if (sisr & CCSR_SSI_SISR_RFS) { | ||
217 | ssi_private->stats.rfs++; | ||
218 | ret = IRQ_HANDLED; | ||
219 | } | ||
220 | |||
221 | if (sisr & CCSR_SSI_SISR_TLS) { | ||
222 | ssi_private->stats.tls++; | ||
223 | ret = IRQ_HANDLED; | ||
224 | } | ||
225 | |||
226 | if (sisr & CCSR_SSI_SISR_RLS) { | ||
227 | ssi_private->stats.rls++; | ||
228 | ret = IRQ_HANDLED; | ||
229 | } | ||
230 | |||
231 | if (sisr & CCSR_SSI_SISR_RFF1) { | ||
232 | ssi_private->stats.rff1++; | ||
233 | ret = IRQ_HANDLED; | ||
234 | } | ||
235 | |||
236 | if (sisr & CCSR_SSI_SISR_RFF0) { | ||
237 | ssi_private->stats.rff0++; | ||
238 | ret = IRQ_HANDLED; | ||
239 | } | ||
240 | |||
241 | if (sisr & CCSR_SSI_SISR_TFE1) { | ||
242 | ssi_private->stats.tfe1++; | ||
243 | ret = IRQ_HANDLED; | ||
244 | } | ||
245 | |||
246 | if (sisr & CCSR_SSI_SISR_TFE0) { | ||
247 | ssi_private->stats.tfe0++; | ||
248 | ret = IRQ_HANDLED; | ||
249 | } | ||
250 | |||
251 | /* Clear the bits that we set */ | ||
252 | if (sisr2) | ||
253 | out_be32(&ssi->sisr, sisr2); | ||
254 | |||
255 | return ret; | ||
256 | } | ||
257 | |||
258 | /** | ||
259 | * fsl_ssi_startup: create a new substream | ||
260 | * | ||
261 | * This is the first function called when a stream is opened. | ||
262 | * | ||
263 | * If this is the first stream open, then grab the IRQ and program most of | ||
264 | * the SSI registers. | ||
265 | */ | ||
266 | static int fsl_ssi_startup(struct snd_pcm_substream *substream) | ||
267 | { | ||
268 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
269 | struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; | ||
270 | |||
271 | /* | ||
272 | * If this is the first stream opened, then request the IRQ | ||
273 | * and initialize the SSI registers. | ||
274 | */ | ||
275 | if (!ssi_private->playback && !ssi_private->capture) { | ||
276 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
277 | int ret; | ||
278 | |||
279 | ret = request_irq(ssi_private->irq, fsl_ssi_isr, 0, | ||
280 | ssi_private->name, ssi_private); | ||
281 | if (ret < 0) { | ||
282 | dev_err(substream->pcm->card->dev, | ||
283 | "could not claim irq %u\n", ssi_private->irq); | ||
284 | return ret; | ||
285 | } | ||
286 | |||
287 | /* | ||
288 | * Section 16.5 of the MPC8610 reference manual says that the | ||
289 | * SSI needs to be disabled before updating the registers we set | ||
290 | * here. | ||
291 | */ | ||
292 | clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); | ||
293 | |||
294 | /* | ||
295 | * Program the SSI into I2S Slave Non-Network Synchronous mode. | ||
296 | * Also enable the transmit and receive FIFO. | ||
297 | * | ||
298 | * FIXME: Little-endian samples require a different shift dir | ||
299 | */ | ||
300 | clrsetbits_be32(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK, | ||
301 | CCSR_SSI_SCR_TFR_CLK_DIS | | ||
302 | CCSR_SSI_SCR_I2S_MODE_SLAVE | CCSR_SSI_SCR_SYN); | ||
303 | |||
304 | out_be32(&ssi->stcr, | ||
305 | CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | | ||
306 | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TEFS | | ||
307 | CCSR_SSI_STCR_TSCKP); | ||
308 | |||
309 | out_be32(&ssi->srcr, | ||
310 | CCSR_SSI_SRCR_RXBIT0 | CCSR_SSI_SRCR_RFEN0 | | ||
311 | CCSR_SSI_SRCR_RFSI | CCSR_SSI_SRCR_REFS | | ||
312 | CCSR_SSI_SRCR_RSCKP); | ||
313 | |||
314 | /* | ||
315 | * The DC and PM bits are only used if the SSI is the clock | ||
316 | * master. | ||
317 | */ | ||
318 | |||
319 | /* 4. Enable the interrupts and DMA requests */ | ||
320 | out_be32(&ssi->sier, | ||
321 | CCSR_SSI_SIER_TFRC_EN | CCSR_SSI_SIER_TDMAE | | ||
322 | CCSR_SSI_SIER_TIE | CCSR_SSI_SIER_TUE0_EN | | ||
323 | CCSR_SSI_SIER_TUE1_EN | CCSR_SSI_SIER_RFRC_EN | | ||
324 | CCSR_SSI_SIER_RDMAE | CCSR_SSI_SIER_RIE | | ||
325 | CCSR_SSI_SIER_ROE0_EN | CCSR_SSI_SIER_ROE1_EN); | ||
326 | |||
327 | /* | ||
328 | * Set the watermark for transmit FIFI 0 and receive FIFO 0. We | ||
329 | * don't use FIFO 1. Since the SSI only supports stereo, the | ||
330 | * watermark should never be an odd number. | ||
331 | */ | ||
332 | out_be32(&ssi->sfcsr, | ||
333 | CCSR_SSI_SFCSR_TFWM0(6) | CCSR_SSI_SFCSR_RFWM0(2)); | ||
334 | |||
335 | /* | ||
336 | * We keep the SSI disabled because if we enable it, then the | ||
337 | * DMA controller will start. It's not supposed to start until | ||
338 | * the SCR.TE (or SCR.RE) bit is set, but it does anyway. The | ||
339 | * DMA controller will transfer one "BWC" of data (i.e. the | ||
340 | * amount of data that the MR.BWC bits are set to). The reason | ||
341 | * this is bad is because at this point, the PCM driver has not | ||
342 | * finished initializing the DMA controller. | ||
343 | */ | ||
344 | } | ||
345 | |||
346 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
347 | ssi_private->playback++; | ||
348 | |||
349 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | ||
350 | ssi_private->capture++; | ||
351 | |||
352 | return 0; | ||
353 | } | ||
354 | |||
355 | /** | ||
356 | * fsl_ssi_prepare: prepare the SSI. | ||
357 | * | ||
358 | * Most of the SSI registers have been programmed in the startup function, | ||
359 | * but the word length must be programmed here. Unfortunately, programming | ||
360 | * the SxCCR.WL bits requires the SSI to be temporarily disabled. This can | ||
361 | * cause a problem with supporting simultaneous playback and capture. If | ||
362 | * the SSI is already playing a stream, then that stream may be temporarily | ||
363 | * stopped when you start capture. | ||
364 | * | ||
365 | * Note: The SxCCR.DC and SxCCR.PM bits are only used if the SSI is the | ||
366 | * clock master. | ||
367 | */ | ||
368 | static int fsl_ssi_prepare(struct snd_pcm_substream *substream) | ||
369 | { | ||
370 | struct snd_pcm_runtime *runtime = substream->runtime; | ||
371 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
372 | struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; | ||
373 | |||
374 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
375 | u32 wl; | ||
376 | |||
377 | wl = CCSR_SSI_SxCCR_WL(snd_pcm_format_width(runtime->format)); | ||
378 | |||
379 | clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); | ||
380 | |||
381 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
382 | clrsetbits_be32(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); | ||
383 | else | ||
384 | clrsetbits_be32(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); | ||
385 | |||
386 | setbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); | ||
387 | |||
388 | return 0; | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * fsl_ssi_trigger: start and stop the DMA transfer. | ||
393 | * | ||
394 | * This function is called by ALSA to start, stop, pause, and resume the DMA | ||
395 | * transfer of data. | ||
396 | * | ||
397 | * The DMA channel is in external master start and pause mode, which | ||
398 | * means the SSI completely controls the flow of data. | ||
399 | */ | ||
400 | static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd) | ||
401 | { | ||
402 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
403 | struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; | ||
404 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
405 | |||
406 | switch (cmd) { | ||
407 | case SNDRV_PCM_TRIGGER_START: | ||
408 | case SNDRV_PCM_TRIGGER_RESUME: | ||
409 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | ||
410 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | ||
411 | setbits32(&ssi->scr, CCSR_SSI_SCR_TE); | ||
412 | } else { | ||
413 | setbits32(&ssi->scr, CCSR_SSI_SCR_RE); | ||
414 | |||
415 | /* | ||
416 | * I think we need this delay to allow time for the SSI | ||
417 | * to put data into its FIFO. Without it, ALSA starts | ||
418 | * to complain about overruns. | ||
419 | */ | ||
420 | msleep(1); | ||
421 | } | ||
422 | break; | ||
423 | |||
424 | case SNDRV_PCM_TRIGGER_STOP: | ||
425 | case SNDRV_PCM_TRIGGER_SUSPEND: | ||
426 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | ||
427 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
428 | clrbits32(&ssi->scr, CCSR_SSI_SCR_TE); | ||
429 | else | ||
430 | clrbits32(&ssi->scr, CCSR_SSI_SCR_RE); | ||
431 | break; | ||
432 | |||
433 | default: | ||
434 | return -EINVAL; | ||
435 | } | ||
436 | |||
437 | return 0; | ||
438 | } | ||
439 | |||
440 | /** | ||
441 | * fsl_ssi_shutdown: shutdown the SSI | ||
442 | * | ||
443 | * Shutdown the SSI if there are no other substreams open. | ||
444 | */ | ||
445 | static void fsl_ssi_shutdown(struct snd_pcm_substream *substream) | ||
446 | { | ||
447 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
448 | struct fsl_ssi_private *ssi_private = rtd->dai->cpu_dai->private_data; | ||
449 | |||
450 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | ||
451 | ssi_private->playback--; | ||
452 | |||
453 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) | ||
454 | ssi_private->capture--; | ||
455 | |||
456 | /* | ||
457 | * If this is the last active substream, disable the SSI and release | ||
458 | * the IRQ. | ||
459 | */ | ||
460 | if (!ssi_private->playback && !ssi_private->capture) { | ||
461 | struct ccsr_ssi __iomem *ssi = ssi_private->ssi; | ||
462 | |||
463 | clrbits32(&ssi->scr, CCSR_SSI_SCR_SSIEN); | ||
464 | |||
465 | free_irq(ssi_private->irq, ssi_private); | ||
466 | } | ||
467 | } | ||
468 | |||
469 | /** | ||
470 | * fsl_ssi_set_sysclk: set the clock frequency and direction | ||
471 | * | ||
472 | * This function is called by the machine driver to tell us what the clock | ||
473 | * frequency and direction are. | ||
474 | * | ||
475 | * Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN), | ||
476 | * and we don't care about the frequency. Return an error if the direction | ||
477 | * is not SND_SOC_CLOCK_IN. | ||
478 | * | ||
479 | * @clk_id: reserved, should be zero | ||
480 | * @freq: the frequency of the given clock ID, currently ignored | ||
481 | * @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master) | ||
482 | */ | ||
483 | static int fsl_ssi_set_sysclk(struct snd_soc_cpu_dai *cpu_dai, | ||
484 | int clk_id, unsigned int freq, int dir) | ||
485 | { | ||
486 | |||
487 | return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; | ||
488 | } | ||
489 | |||
490 | /** | ||
491 | * fsl_ssi_set_fmt: set the serial format. | ||
492 | * | ||
493 | * This function is called by the machine driver to tell us what serial | ||
494 | * format to use. | ||
495 | * | ||
496 | * Currently, we only support I2S mode. Return an error if the format is | ||
497 | * not SND_SOC_DAIFMT_I2S. | ||
498 | * | ||
499 | * @format: one of SND_SOC_DAIFMT_xxx | ||
500 | */ | ||
501 | static int fsl_ssi_set_fmt(struct snd_soc_cpu_dai *cpu_dai, unsigned int format) | ||
502 | { | ||
503 | return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; | ||
504 | } | ||
505 | |||
506 | /** | ||
507 | * fsl_ssi_dai_template: template CPU DAI for the SSI | ||
508 | */ | ||
509 | static struct snd_soc_cpu_dai fsl_ssi_dai_template = { | ||
510 | .playback = { | ||
511 | /* The SSI does not support monaural audio. */ | ||
512 | .channels_min = 2, | ||
513 | .channels_max = 2, | ||
514 | .rates = FSLSSI_I2S_RATES, | ||
515 | .formats = FSLSSI_I2S_FORMATS, | ||
516 | }, | ||
517 | .capture = { | ||
518 | .channels_min = 2, | ||
519 | .channels_max = 2, | ||
520 | .rates = FSLSSI_I2S_RATES, | ||
521 | .formats = FSLSSI_I2S_FORMATS, | ||
522 | }, | ||
523 | .ops = { | ||
524 | .startup = fsl_ssi_startup, | ||
525 | .prepare = fsl_ssi_prepare, | ||
526 | .shutdown = fsl_ssi_shutdown, | ||
527 | .trigger = fsl_ssi_trigger, | ||
528 | }, | ||
529 | .dai_ops = { | ||
530 | .set_sysclk = fsl_ssi_set_sysclk, | ||
531 | .set_fmt = fsl_ssi_set_fmt, | ||
532 | }, | ||
533 | }; | ||
534 | |||
535 | /** | ||
536 | * fsl_sysfs_ssi_show: display SSI statistics | ||
537 | * | ||
538 | * Display the statistics for the current SSI device. | ||
539 | */ | ||
540 | static ssize_t fsl_sysfs_ssi_show(struct device *dev, | ||
541 | struct device_attribute *attr, char *buf) | ||
542 | { | ||
543 | struct fsl_ssi_private *ssi_private = | ||
544 | container_of(attr, struct fsl_ssi_private, dev_attr); | ||
545 | ssize_t length; | ||
546 | |||
547 | length = sprintf(buf, "rfrc=%u", ssi_private->stats.rfrc); | ||
548 | length += sprintf(buf + length, "\ttfrc=%u", ssi_private->stats.tfrc); | ||
549 | length += sprintf(buf + length, "\tcmdau=%u", ssi_private->stats.cmdau); | ||
550 | length += sprintf(buf + length, "\tcmddu=%u", ssi_private->stats.cmddu); | ||
551 | length += sprintf(buf + length, "\trxt=%u", ssi_private->stats.rxt); | ||
552 | length += sprintf(buf + length, "\trdr1=%u", ssi_private->stats.rdr1); | ||
553 | length += sprintf(buf + length, "\trdr0=%u", ssi_private->stats.rdr0); | ||
554 | length += sprintf(buf + length, "\ttde1=%u", ssi_private->stats.tde1); | ||
555 | length += sprintf(buf + length, "\ttde0=%u", ssi_private->stats.tde0); | ||
556 | length += sprintf(buf + length, "\troe1=%u", ssi_private->stats.roe1); | ||
557 | length += sprintf(buf + length, "\troe0=%u", ssi_private->stats.roe0); | ||
558 | length += sprintf(buf + length, "\ttue1=%u", ssi_private->stats.tue1); | ||
559 | length += sprintf(buf + length, "\ttue0=%u", ssi_private->stats.tue0); | ||
560 | length += sprintf(buf + length, "\ttfs=%u", ssi_private->stats.tfs); | ||
561 | length += sprintf(buf + length, "\trfs=%u", ssi_private->stats.rfs); | ||
562 | length += sprintf(buf + length, "\ttls=%u", ssi_private->stats.tls); | ||
563 | length += sprintf(buf + length, "\trls=%u", ssi_private->stats.rls); | ||
564 | length += sprintf(buf + length, "\trff1=%u", ssi_private->stats.rff1); | ||
565 | length += sprintf(buf + length, "\trff0=%u", ssi_private->stats.rff0); | ||
566 | length += sprintf(buf + length, "\ttfe1=%u", ssi_private->stats.tfe1); | ||
567 | length += sprintf(buf + length, "\ttfe0=%u\n", ssi_private->stats.tfe0); | ||
568 | |||
569 | return length; | ||
570 | } | ||
571 | |||
572 | /** | ||
573 | * fsl_ssi_create_dai: create a snd_soc_cpu_dai structure | ||
574 | * | ||
575 | * This function is called by the machine driver to create a snd_soc_cpu_dai | ||
576 | * structure. The function creates an ssi_private object, which contains | ||
577 | * the snd_soc_cpu_dai. It also creates the sysfs statistics device. | ||
578 | */ | ||
579 | struct snd_soc_cpu_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) | ||
580 | { | ||
581 | struct snd_soc_cpu_dai *fsl_ssi_dai; | ||
582 | struct fsl_ssi_private *ssi_private; | ||
583 | int ret = 0; | ||
584 | struct device_attribute *dev_attr; | ||
585 | |||
586 | ssi_private = kzalloc(sizeof(struct fsl_ssi_private), GFP_KERNEL); | ||
587 | if (!ssi_private) { | ||
588 | dev_err(ssi_info->dev, "could not allocate DAI object\n"); | ||
589 | return NULL; | ||
590 | } | ||
591 | memcpy(&ssi_private->cpu_dai, &fsl_ssi_dai_template, | ||
592 | sizeof(struct snd_soc_cpu_dai)); | ||
593 | |||
594 | fsl_ssi_dai = &ssi_private->cpu_dai; | ||
595 | dev_attr = &ssi_private->dev_attr; | ||
596 | |||
597 | sprintf(ssi_private->name, "ssi%u", (u8) ssi_info->id); | ||
598 | ssi_private->ssi = ssi_info->ssi; | ||
599 | ssi_private->ssi_phys = ssi_info->ssi_phys; | ||
600 | ssi_private->irq = ssi_info->irq; | ||
601 | ssi_private->dev = ssi_info->dev; | ||
602 | |||
603 | ssi_private->dev->driver_data = fsl_ssi_dai; | ||
604 | |||
605 | /* Initialize the the device_attribute structure */ | ||
606 | dev_attr->attr.name = "ssi-stats"; | ||
607 | dev_attr->attr.mode = S_IRUGO; | ||
608 | dev_attr->show = fsl_sysfs_ssi_show; | ||
609 | |||
610 | ret = device_create_file(ssi_private->dev, dev_attr); | ||
611 | if (ret) { | ||
612 | dev_err(ssi_info->dev, "could not create sysfs %s file\n", | ||
613 | ssi_private->dev_attr.attr.name); | ||
614 | kfree(fsl_ssi_dai); | ||
615 | return NULL; | ||
616 | } | ||
617 | |||
618 | fsl_ssi_dai->private_data = ssi_private; | ||
619 | fsl_ssi_dai->name = ssi_private->name; | ||
620 | fsl_ssi_dai->id = ssi_info->id; | ||
621 | |||
622 | return fsl_ssi_dai; | ||
623 | } | ||
624 | EXPORT_SYMBOL_GPL(fsl_ssi_create_dai); | ||
625 | |||
626 | /** | ||
627 | * fsl_ssi_destroy_dai: destroy the snd_soc_cpu_dai object | ||
628 | * | ||
629 | * This function undoes the operations of fsl_ssi_create_dai() | ||
630 | */ | ||
631 | void fsl_ssi_destroy_dai(struct snd_soc_cpu_dai *fsl_ssi_dai) | ||
632 | { | ||
633 | struct fsl_ssi_private *ssi_private = | ||
634 | container_of(fsl_ssi_dai, struct fsl_ssi_private, cpu_dai); | ||
635 | |||
636 | device_remove_file(ssi_private->dev, &ssi_private->dev_attr); | ||
637 | |||
638 | kfree(ssi_private); | ||
639 | } | ||
640 | EXPORT_SYMBOL_GPL(fsl_ssi_destroy_dai); | ||
641 | |||
642 | MODULE_AUTHOR("Timur Tabi <timur@freescale.com>"); | ||
643 | MODULE_DESCRIPTION("Freescale Synchronous Serial Interface (SSI) ASoC Driver"); | ||
644 | MODULE_LICENSE("GPL"); | ||