Commit | Line | Data |
---|---|---|
43d24e76 NC |
1 | /* |
2 | * Freescale ESAI ALSA SoC Digital Audio Interface (DAI) driver | |
3 | * | |
4 | * Copyright (C) 2014 Freescale Semiconductor, Inc. | |
5 | * | |
6 | * This file is licensed under the terms of the GNU General Public License | |
7 | * version 2. This program is licensed "as is" without any warranty of any | |
8 | * kind, whether express or implied. | |
9 | */ | |
10 | ||
11 | #include <linux/clk.h> | |
12 | #include <linux/dmaengine.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/of_irq.h> | |
15 | #include <linux/of_platform.h> | |
16 | #include <sound/dmaengine_pcm.h> | |
17 | #include <sound/pcm_params.h> | |
18 | ||
19 | #include "fsl_esai.h" | |
20 | #include "imx-pcm.h" | |
21 | ||
22 | #define FSL_ESAI_RATES SNDRV_PCM_RATE_8000_192000 | |
23 | #define FSL_ESAI_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ | |
24 | SNDRV_PCM_FMTBIT_S16_LE | \ | |
25 | SNDRV_PCM_FMTBIT_S20_3LE | \ | |
26 | SNDRV_PCM_FMTBIT_S24_LE) | |
27 | ||
28 | /** | |
29 | * fsl_esai: ESAI private data | |
30 | * | |
31 | * @dma_params_rx: DMA parameters for receive channel | |
32 | * @dma_params_tx: DMA parameters for transmit channel | |
33 | * @pdev: platform device pointer | |
34 | * @regmap: regmap handler | |
35 | * @coreclk: clock source to access register | |
36 | * @extalclk: esai clock source to derive HCK, SCK and FS | |
37 | * @fsysclk: system clock source to derive HCK, SCK and FS | |
38 | * @fifo_depth: depth of tx/rx FIFO | |
39 | * @slot_width: width of each DAI slot | |
40 | * @hck_rate: clock rate of desired HCKx clock | |
41 | * @sck_div: if using PSR/PM dividers for SCKx clock | |
42 | * @slave_mode: if fully using DAI slave mode | |
43 | * @synchronous: if using tx/rx synchronous mode | |
44 | * @name: driver name | |
45 | */ | |
46 | struct fsl_esai { | |
47 | struct snd_dmaengine_dai_dma_data dma_params_rx; | |
48 | struct snd_dmaengine_dai_dma_data dma_params_tx; | |
49 | struct platform_device *pdev; | |
50 | struct regmap *regmap; | |
51 | struct clk *coreclk; | |
52 | struct clk *extalclk; | |
53 | struct clk *fsysclk; | |
54 | u32 fifo_depth; | |
55 | u32 slot_width; | |
56 | u32 hck_rate[2]; | |
57 | bool sck_div[2]; | |
58 | bool slave_mode; | |
59 | bool synchronous; | |
60 | char name[32]; | |
61 | }; | |
62 | ||
63 | static irqreturn_t esai_isr(int irq, void *devid) | |
64 | { | |
65 | struct fsl_esai *esai_priv = (struct fsl_esai *)devid; | |
66 | struct platform_device *pdev = esai_priv->pdev; | |
67 | u32 esr; | |
68 | ||
69 | regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr); | |
70 | ||
71 | if (esr & ESAI_ESR_TINIT_MASK) | |
72 | dev_dbg(&pdev->dev, "isr: Transmition Initialized\n"); | |
73 | ||
74 | if (esr & ESAI_ESR_RFF_MASK) | |
75 | dev_warn(&pdev->dev, "isr: Receiving overrun\n"); | |
76 | ||
77 | if (esr & ESAI_ESR_TFE_MASK) | |
78 | dev_warn(&pdev->dev, "isr: Transmition underrun\n"); | |
79 | ||
80 | if (esr & ESAI_ESR_TLS_MASK) | |
81 | dev_dbg(&pdev->dev, "isr: Just transmitted the last slot\n"); | |
82 | ||
83 | if (esr & ESAI_ESR_TDE_MASK) | |
84 | dev_dbg(&pdev->dev, "isr: Transmition data exception\n"); | |
85 | ||
86 | if (esr & ESAI_ESR_TED_MASK) | |
87 | dev_dbg(&pdev->dev, "isr: Transmitting even slots\n"); | |
88 | ||
89 | if (esr & ESAI_ESR_TD_MASK) | |
90 | dev_dbg(&pdev->dev, "isr: Transmitting data\n"); | |
91 | ||
92 | if (esr & ESAI_ESR_RLS_MASK) | |
93 | dev_dbg(&pdev->dev, "isr: Just received the last slot\n"); | |
94 | ||
95 | if (esr & ESAI_ESR_RDE_MASK) | |
96 | dev_dbg(&pdev->dev, "isr: Receiving data exception\n"); | |
97 | ||
98 | if (esr & ESAI_ESR_RED_MASK) | |
99 | dev_dbg(&pdev->dev, "isr: Receiving even slots\n"); | |
100 | ||
101 | if (esr & ESAI_ESR_RD_MASK) | |
102 | dev_dbg(&pdev->dev, "isr: Receiving data\n"); | |
103 | ||
104 | return IRQ_HANDLED; | |
105 | } | |
106 | ||
107 | /** | |
108 | * This function is used to calculate the divisors of psr, pm, fp and it is | |
109 | * supposed to be called in set_dai_sysclk() and set_bclk(). | |
110 | * | |
111 | * @ratio: desired overall ratio for the paticipating dividers | |
112 | * @usefp: for HCK setting, there is no need to set fp divider | |
113 | * @fp: bypass other dividers by setting fp directly if fp != 0 | |
114 | * @tx: current setting is for playback or capture | |
115 | */ | |
116 | static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio, | |
117 | bool usefp, u32 fp) | |
118 | { | |
119 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
120 | u32 psr, pm = 999, maxfp, prod, sub, savesub, i, j; | |
121 | ||
122 | maxfp = usefp ? 16 : 1; | |
123 | ||
124 | if (usefp && fp) | |
125 | goto out_fp; | |
126 | ||
127 | if (ratio > 2 * 8 * 256 * maxfp || ratio < 2) { | |
128 | dev_err(dai->dev, "the ratio is out of range (2 ~ %d)\n", | |
129 | 2 * 8 * 256 * maxfp); | |
130 | return -EINVAL; | |
131 | } else if (ratio % 2) { | |
132 | dev_err(dai->dev, "the raio must be even if using upper divider\n"); | |
133 | return -EINVAL; | |
134 | } | |
135 | ||
136 | ratio /= 2; | |
137 | ||
138 | psr = ratio <= 256 * maxfp ? ESAI_xCCR_xPSR_BYPASS : ESAI_xCCR_xPSR_DIV8; | |
139 | ||
140 | /* Set the max fluctuation -- 0.1% of the max devisor */ | |
141 | savesub = (psr ? 1 : 8) * 256 * maxfp / 1000; | |
142 | ||
143 | /* Find the best value for PM */ | |
144 | for (i = 1; i <= 256; i++) { | |
145 | for (j = 1; j <= maxfp; j++) { | |
146 | /* PSR (1 or 8) * PM (1 ~ 256) * FP (1 ~ 16) */ | |
147 | prod = (psr ? 1 : 8) * i * j; | |
148 | ||
149 | if (prod == ratio) | |
150 | sub = 0; | |
151 | else if (prod / ratio == 1) | |
152 | sub = prod - ratio; | |
153 | else if (ratio / prod == 1) | |
154 | sub = ratio - prod; | |
155 | else | |
156 | continue; | |
157 | ||
158 | /* Calculate the fraction */ | |
159 | sub = sub * 1000 / ratio; | |
160 | if (sub < savesub) { | |
161 | savesub = sub; | |
162 | pm = i; | |
163 | fp = j; | |
164 | } | |
165 | ||
166 | /* We are lucky */ | |
167 | if (savesub == 0) | |
168 | goto out; | |
169 | } | |
170 | } | |
171 | ||
172 | if (pm == 999) { | |
173 | dev_err(dai->dev, "failed to calculate proper divisors\n"); | |
174 | return -EINVAL; | |
175 | } | |
176 | ||
177 | out: | |
178 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), | |
179 | ESAI_xCCR_xPSR_MASK | ESAI_xCCR_xPM_MASK, | |
180 | psr | ESAI_xCCR_xPM(pm)); | |
181 | ||
182 | out_fp: | |
183 | /* Bypass fp if not being required */ | |
184 | if (maxfp <= 1) | |
185 | return 0; | |
186 | ||
187 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), | |
188 | ESAI_xCCR_xFP_MASK, ESAI_xCCR_xFP(fp)); | |
189 | ||
190 | return 0; | |
191 | } | |
192 | ||
193 | /** | |
194 | * This function mainly configures the clock frequency of MCLK (HCKT/HCKR) | |
195 | * | |
196 | * @Parameters: | |
197 | * clk_id: The clock source of HCKT/HCKR | |
198 | * (Input from outside; output from inside, FSYS or EXTAL) | |
199 | * freq: The required clock rate of HCKT/HCKR | |
200 | * dir: The clock direction of HCKT/HCKR | |
201 | * | |
202 | * Note: If the direction is input, we do not care about clk_id. | |
203 | */ | |
204 | static int fsl_esai_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, | |
205 | unsigned int freq, int dir) | |
206 | { | |
207 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
208 | struct clk *clksrc = esai_priv->extalclk; | |
209 | bool tx = clk_id <= ESAI_HCKT_EXTAL; | |
210 | bool in = dir == SND_SOC_CLOCK_IN; | |
211 | u32 ret, ratio, ecr = 0; | |
212 | unsigned long clk_rate; | |
213 | ||
214 | /* sck_div can be only bypassed if ETO/ERO=0 and SNC_SOC_CLOCK_OUT */ | |
215 | esai_priv->sck_div[tx] = true; | |
216 | ||
217 | /* Set the direction of HCKT/HCKR pins */ | |
218 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCCR(tx), | |
219 | ESAI_xCCR_xHCKD, in ? 0 : ESAI_xCCR_xHCKD); | |
220 | ||
221 | if (in) | |
222 | goto out; | |
223 | ||
224 | switch (clk_id) { | |
225 | case ESAI_HCKT_FSYS: | |
226 | case ESAI_HCKR_FSYS: | |
227 | clksrc = esai_priv->fsysclk; | |
228 | break; | |
229 | case ESAI_HCKT_EXTAL: | |
230 | ecr |= ESAI_ECR_ETI; | |
231 | case ESAI_HCKR_EXTAL: | |
232 | ecr |= ESAI_ECR_ERI; | |
233 | break; | |
234 | default: | |
235 | return -EINVAL; | |
236 | } | |
237 | ||
238 | if (IS_ERR(clksrc)) { | |
239 | dev_err(dai->dev, "no assigned %s clock\n", | |
240 | clk_id % 2 ? "extal" : "fsys"); | |
241 | return PTR_ERR(clksrc); | |
242 | } | |
243 | clk_rate = clk_get_rate(clksrc); | |
244 | ||
245 | ratio = clk_rate / freq; | |
246 | if (ratio * freq > clk_rate) | |
247 | ret = ratio * freq - clk_rate; | |
248 | else if (ratio * freq < clk_rate) | |
249 | ret = clk_rate - ratio * freq; | |
250 | else | |
251 | ret = 0; | |
252 | ||
253 | /* Block if clock source can not be divided into the required rate */ | |
254 | if (ret != 0 && clk_rate / ret < 1000) { | |
255 | dev_err(dai->dev, "failed to derive required HCK%c rate\n", | |
256 | tx ? 'T' : 'R'); | |
257 | return -EINVAL; | |
258 | } | |
259 | ||
260 | if (ratio == 1) { | |
261 | /* Bypass all the dividers if not being needed */ | |
262 | ecr |= tx ? ESAI_ECR_ETO : ESAI_ECR_ERO; | |
263 | goto out; | |
264 | } | |
265 | ||
266 | ret = fsl_esai_divisor_cal(dai, tx, ratio, false, 0); | |
267 | if (ret) | |
268 | return ret; | |
269 | ||
270 | esai_priv->sck_div[tx] = false; | |
271 | ||
272 | out: | |
273 | esai_priv->hck_rate[tx] = freq; | |
274 | ||
275 | regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, | |
276 | tx ? ESAI_ECR_ETI | ESAI_ECR_ETO : | |
277 | ESAI_ECR_ERI | ESAI_ECR_ERO, ecr); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | /** | |
283 | * This function configures the related dividers according to the bclk rate | |
284 | */ | |
285 | static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) | |
286 | { | |
287 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
288 | u32 hck_rate = esai_priv->hck_rate[tx]; | |
289 | u32 sub, ratio = hck_rate / freq; | |
290 | ||
291 | /* Don't apply for fully slave mode*/ | |
292 | if (esai_priv->slave_mode) | |
293 | return 0; | |
294 | ||
295 | if (ratio * freq > hck_rate) | |
296 | sub = ratio * freq - hck_rate; | |
297 | else if (ratio * freq < hck_rate) | |
298 | sub = hck_rate - ratio * freq; | |
299 | else | |
300 | sub = 0; | |
301 | ||
302 | /* Block if clock source can not be divided into the required rate */ | |
303 | if (sub != 0 && hck_rate / sub < 1000) { | |
304 | dev_err(dai->dev, "failed to derive required SCK%c rate\n", | |
305 | tx ? 'T' : 'R'); | |
306 | return -EINVAL; | |
307 | } | |
308 | ||
309 | if (esai_priv->sck_div[tx] && (ratio > 16 || ratio == 0)) { | |
310 | dev_err(dai->dev, "the ratio is out of range (1 ~ 16)\n"); | |
311 | return -EINVAL; | |
312 | } | |
313 | ||
314 | return fsl_esai_divisor_cal(dai, tx, ratio, true, | |
315 | esai_priv->sck_div[tx] ? 0 : ratio); | |
316 | } | |
317 | ||
318 | static int fsl_esai_set_dai_tdm_slot(struct snd_soc_dai *dai, u32 tx_mask, | |
319 | u32 rx_mask, int slots, int slot_width) | |
320 | { | |
321 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
322 | ||
323 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, | |
324 | ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); | |
325 | ||
326 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMA, | |
327 | ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(tx_mask)); | |
328 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TSMB, | |
329 | ESAI_xSMA_xS_MASK, ESAI_xSMB_xS(tx_mask)); | |
330 | ||
331 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, | |
332 | ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(slots)); | |
333 | ||
334 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMA, | |
335 | ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(rx_mask)); | |
336 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RSMB, | |
337 | ESAI_xSMA_xS_MASK, ESAI_xSMB_xS(rx_mask)); | |
338 | ||
339 | esai_priv->slot_width = slot_width; | |
340 | ||
341 | return 0; | |
342 | } | |
343 | ||
344 | static int fsl_esai_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) | |
345 | { | |
346 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
347 | u32 xcr = 0, xccr = 0, mask; | |
348 | ||
349 | /* DAI mode */ | |
350 | switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { | |
351 | case SND_SOC_DAIFMT_I2S: | |
352 | /* Data on rising edge of bclk, frame low, 1clk before data */ | |
353 | xcr |= ESAI_xCR_xFSR; | |
354 | xccr |= ESAI_xCCR_xFSP | ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; | |
355 | break; | |
356 | case SND_SOC_DAIFMT_LEFT_J: | |
357 | /* Data on rising edge of bclk, frame high */ | |
358 | xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; | |
359 | break; | |
360 | case SND_SOC_DAIFMT_RIGHT_J: | |
361 | /* Data on rising edge of bclk, frame high, right aligned */ | |
362 | xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCR_xWA; | |
363 | break; | |
364 | case SND_SOC_DAIFMT_DSP_A: | |
365 | /* Data on rising edge of bclk, frame high, 1clk before data */ | |
366 | xcr |= ESAI_xCR_xFSL | ESAI_xCR_xFSR; | |
367 | xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; | |
368 | break; | |
369 | case SND_SOC_DAIFMT_DSP_B: | |
370 | /* Data on rising edge of bclk, frame high */ | |
371 | xcr |= ESAI_xCR_xFSL; | |
372 | xccr |= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; | |
373 | break; | |
374 | default: | |
375 | return -EINVAL; | |
376 | } | |
377 | ||
378 | /* DAI clock inversion */ | |
379 | switch (fmt & SND_SOC_DAIFMT_INV_MASK) { | |
380 | case SND_SOC_DAIFMT_NB_NF: | |
381 | /* Nothing to do for both normal cases */ | |
382 | break; | |
383 | case SND_SOC_DAIFMT_IB_NF: | |
384 | /* Invert bit clock */ | |
385 | xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP; | |
386 | break; | |
387 | case SND_SOC_DAIFMT_NB_IF: | |
388 | /* Invert frame clock */ | |
389 | xccr ^= ESAI_xCCR_xFSP; | |
390 | break; | |
391 | case SND_SOC_DAIFMT_IB_IF: | |
392 | /* Invert both clocks */ | |
393 | xccr ^= ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP; | |
394 | break; | |
395 | default: | |
396 | return -EINVAL; | |
397 | } | |
398 | ||
399 | esai_priv->slave_mode = false; | |
400 | ||
401 | /* DAI clock master masks */ | |
402 | switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { | |
403 | case SND_SOC_DAIFMT_CBM_CFM: | |
404 | esai_priv->slave_mode = true; | |
405 | break; | |
406 | case SND_SOC_DAIFMT_CBS_CFM: | |
407 | xccr |= ESAI_xCCR_xCKD; | |
408 | break; | |
409 | case SND_SOC_DAIFMT_CBM_CFS: | |
410 | xccr |= ESAI_xCCR_xFSD; | |
411 | break; | |
412 | case SND_SOC_DAIFMT_CBS_CFS: | |
413 | xccr |= ESAI_xCCR_xFSD | ESAI_xCCR_xCKD; | |
414 | break; | |
415 | default: | |
416 | return -EINVAL; | |
417 | } | |
418 | ||
419 | mask = ESAI_xCR_xFSL | ESAI_xCR_xFSR; | |
420 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, xcr); | |
421 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, mask, xcr); | |
422 | ||
423 | mask = ESAI_xCCR_xCKP | ESAI_xCCR_xHCKP | ESAI_xCCR_xFSP | | |
424 | ESAI_xCCR_xFSD | ESAI_xCCR_xCKD | ESAI_xCR_xWA; | |
425 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, mask, xccr); | |
426 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, mask, xccr); | |
427 | ||
428 | return 0; | |
429 | } | |
430 | ||
431 | static int fsl_esai_startup(struct snd_pcm_substream *substream, | |
432 | struct snd_soc_dai *dai) | |
433 | { | |
434 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
435 | ||
436 | /* | |
437 | * Some platforms might use the same bit to gate all three or two of | |
438 | * clocks, so keep all clocks open/close at the same time for safety | |
439 | */ | |
440 | clk_prepare_enable(esai_priv->coreclk); | |
441 | if (!IS_ERR(esai_priv->extalclk)) | |
442 | clk_prepare_enable(esai_priv->extalclk); | |
443 | if (!IS_ERR(esai_priv->fsysclk)) | |
444 | clk_prepare_enable(esai_priv->fsysclk); | |
445 | ||
446 | if (!dai->active) { | |
447 | /* Reset Port C */ | |
448 | regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, | |
449 | ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); | |
450 | regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, | |
451 | ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); | |
452 | ||
453 | /* Set synchronous mode */ | |
454 | regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR, | |
455 | ESAI_SAICR_SYNC, esai_priv->synchronous ? | |
456 | ESAI_SAICR_SYNC : 0); | |
457 | ||
458 | /* Set a default slot number -- 2 */ | |
459 | regmap_update_bits(esai_priv->regmap, REG_ESAI_TCCR, | |
460 | ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); | |
461 | regmap_update_bits(esai_priv->regmap, REG_ESAI_RCCR, | |
462 | ESAI_xCCR_xDC_MASK, ESAI_xCCR_xDC(2)); | |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
468 | static int fsl_esai_hw_params(struct snd_pcm_substream *substream, | |
469 | struct snd_pcm_hw_params *params, | |
470 | struct snd_soc_dai *dai) | |
471 | { | |
472 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
473 | bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; | |
474 | u32 width = snd_pcm_format_width(params_format(params)); | |
475 | u32 channels = params_channels(params); | |
476 | u32 bclk, mask, val, ret; | |
477 | ||
478 | bclk = params_rate(params) * esai_priv->slot_width * 2; | |
479 | ||
480 | ret = fsl_esai_set_bclk(dai, tx, bclk); | |
481 | if (ret) | |
482 | return ret; | |
483 | ||
484 | /* Use Normal mode to support monaural audio */ | |
485 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), | |
486 | ESAI_xCR_xMOD_MASK, params_channels(params) > 1 ? | |
487 | ESAI_xCR_xMOD_NETWORK : 0); | |
488 | ||
489 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), | |
490 | ESAI_xFCR_xFR_MASK, ESAI_xFCR_xFR); | |
491 | ||
492 | mask = ESAI_xFCR_xFR_MASK | ESAI_xFCR_xWA_MASK | ESAI_xFCR_xFWM_MASK | | |
493 | (tx ? ESAI_xFCR_TE_MASK | ESAI_xFCR_TIEN : ESAI_xFCR_RE_MASK); | |
494 | val = ESAI_xFCR_xWA(width) | ESAI_xFCR_xFWM(esai_priv->fifo_depth) | | |
495 | (tx ? ESAI_xFCR_TE(channels) | ESAI_xFCR_TIEN : ESAI_xFCR_RE(channels)); | |
496 | ||
497 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); | |
498 | ||
499 | mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); | |
500 | val = ESAI_xCR_xSWS(esai_priv->slot_width, width) | (tx ? ESAI_xCR_PADC : 0); | |
501 | ||
502 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); | |
503 | ||
504 | return 0; | |
505 | } | |
506 | ||
507 | static void fsl_esai_shutdown(struct snd_pcm_substream *substream, | |
508 | struct snd_soc_dai *dai) | |
509 | { | |
510 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
511 | ||
512 | if (!IS_ERR(esai_priv->fsysclk)) | |
513 | clk_disable_unprepare(esai_priv->fsysclk); | |
514 | if (!IS_ERR(esai_priv->extalclk)) | |
515 | clk_disable_unprepare(esai_priv->extalclk); | |
516 | clk_disable_unprepare(esai_priv->coreclk); | |
517 | } | |
518 | ||
519 | static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, | |
520 | struct snd_soc_dai *dai) | |
521 | { | |
522 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
523 | bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; | |
524 | u8 i, channels = substream->runtime->channels; | |
525 | ||
526 | switch (cmd) { | |
527 | case SNDRV_PCM_TRIGGER_START: | |
528 | case SNDRV_PCM_TRIGGER_RESUME: | |
529 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
530 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), | |
531 | ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN); | |
532 | ||
533 | /* Write initial words reqiured by ESAI as normal procedure */ | |
534 | for (i = 0; tx && i < channels; i++) | |
535 | regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0); | |
536 | ||
537 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), | |
538 | tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, | |
539 | tx ? ESAI_xCR_TE(channels) : ESAI_xCR_RE(channels)); | |
540 | break; | |
541 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
542 | case SNDRV_PCM_TRIGGER_STOP: | |
543 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
544 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), | |
545 | tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0); | |
546 | ||
547 | /* Disable and reset FIFO */ | |
548 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), | |
549 | ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR); | |
550 | regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), | |
551 | ESAI_xFCR_xFR, 0); | |
552 | break; | |
553 | default: | |
554 | return -EINVAL; | |
555 | } | |
556 | ||
557 | return 0; | |
558 | } | |
559 | ||
560 | static struct snd_soc_dai_ops fsl_esai_dai_ops = { | |
561 | .startup = fsl_esai_startup, | |
562 | .shutdown = fsl_esai_shutdown, | |
563 | .trigger = fsl_esai_trigger, | |
564 | .hw_params = fsl_esai_hw_params, | |
565 | .set_sysclk = fsl_esai_set_dai_sysclk, | |
566 | .set_fmt = fsl_esai_set_dai_fmt, | |
567 | .set_tdm_slot = fsl_esai_set_dai_tdm_slot, | |
568 | }; | |
569 | ||
570 | static int fsl_esai_dai_probe(struct snd_soc_dai *dai) | |
571 | { | |
572 | struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); | |
573 | ||
574 | snd_soc_dai_init_dma_data(dai, &esai_priv->dma_params_tx, | |
575 | &esai_priv->dma_params_rx); | |
576 | ||
577 | return 0; | |
578 | } | |
579 | ||
580 | static struct snd_soc_dai_driver fsl_esai_dai = { | |
581 | .probe = fsl_esai_dai_probe, | |
582 | .playback = { | |
583 | .channels_min = 1, | |
584 | .channels_max = 12, | |
585 | .rates = FSL_ESAI_RATES, | |
586 | .formats = FSL_ESAI_FORMATS, | |
587 | }, | |
588 | .capture = { | |
589 | .channels_min = 1, | |
590 | .channels_max = 8, | |
591 | .rates = FSL_ESAI_RATES, | |
592 | .formats = FSL_ESAI_FORMATS, | |
593 | }, | |
594 | .ops = &fsl_esai_dai_ops, | |
595 | }; | |
596 | ||
597 | static const struct snd_soc_component_driver fsl_esai_component = { | |
598 | .name = "fsl-esai", | |
599 | }; | |
600 | ||
601 | static bool fsl_esai_readable_reg(struct device *dev, unsigned int reg) | |
602 | { | |
603 | switch (reg) { | |
604 | case REG_ESAI_ERDR: | |
605 | case REG_ESAI_ECR: | |
606 | case REG_ESAI_ESR: | |
607 | case REG_ESAI_TFCR: | |
608 | case REG_ESAI_TFSR: | |
609 | case REG_ESAI_RFCR: | |
610 | case REG_ESAI_RFSR: | |
611 | case REG_ESAI_RX0: | |
612 | case REG_ESAI_RX1: | |
613 | case REG_ESAI_RX2: | |
614 | case REG_ESAI_RX3: | |
615 | case REG_ESAI_SAISR: | |
616 | case REG_ESAI_SAICR: | |
617 | case REG_ESAI_TCR: | |
618 | case REG_ESAI_TCCR: | |
619 | case REG_ESAI_RCR: | |
620 | case REG_ESAI_RCCR: | |
621 | case REG_ESAI_TSMA: | |
622 | case REG_ESAI_TSMB: | |
623 | case REG_ESAI_RSMA: | |
624 | case REG_ESAI_RSMB: | |
625 | case REG_ESAI_PRRC: | |
626 | case REG_ESAI_PCRC: | |
627 | return true; | |
628 | default: | |
629 | return false; | |
630 | } | |
631 | } | |
632 | ||
633 | static bool fsl_esai_writeable_reg(struct device *dev, unsigned int reg) | |
634 | { | |
635 | switch (reg) { | |
636 | case REG_ESAI_ETDR: | |
637 | case REG_ESAI_ECR: | |
638 | case REG_ESAI_TFCR: | |
639 | case REG_ESAI_RFCR: | |
640 | case REG_ESAI_TX0: | |
641 | case REG_ESAI_TX1: | |
642 | case REG_ESAI_TX2: | |
643 | case REG_ESAI_TX3: | |
644 | case REG_ESAI_TX4: | |
645 | case REG_ESAI_TX5: | |
646 | case REG_ESAI_TSR: | |
647 | case REG_ESAI_SAICR: | |
648 | case REG_ESAI_TCR: | |
649 | case REG_ESAI_TCCR: | |
650 | case REG_ESAI_RCR: | |
651 | case REG_ESAI_RCCR: | |
652 | case REG_ESAI_TSMA: | |
653 | case REG_ESAI_TSMB: | |
654 | case REG_ESAI_RSMA: | |
655 | case REG_ESAI_RSMB: | |
656 | case REG_ESAI_PRRC: | |
657 | case REG_ESAI_PCRC: | |
658 | return true; | |
659 | default: | |
660 | return false; | |
661 | } | |
662 | } | |
663 | ||
664 | static const struct regmap_config fsl_esai_regmap_config = { | |
665 | .reg_bits = 32, | |
666 | .reg_stride = 4, | |
667 | .val_bits = 32, | |
668 | ||
669 | .max_register = REG_ESAI_PCRC, | |
670 | .readable_reg = fsl_esai_readable_reg, | |
671 | .writeable_reg = fsl_esai_writeable_reg, | |
672 | }; | |
673 | ||
674 | static int fsl_esai_probe(struct platform_device *pdev) | |
675 | { | |
676 | struct device_node *np = pdev->dev.of_node; | |
677 | struct fsl_esai *esai_priv; | |
678 | struct resource *res; | |
679 | const uint32_t *iprop; | |
680 | void __iomem *regs; | |
681 | int irq, ret; | |
682 | ||
683 | esai_priv = devm_kzalloc(&pdev->dev, sizeof(*esai_priv), GFP_KERNEL); | |
684 | if (!esai_priv) | |
685 | return -ENOMEM; | |
686 | ||
687 | esai_priv->pdev = pdev; | |
688 | strcpy(esai_priv->name, np->name); | |
689 | ||
690 | /* Get the addresses and IRQ */ | |
691 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
692 | regs = devm_ioremap_resource(&pdev->dev, res); | |
693 | if (IS_ERR(regs)) | |
694 | return PTR_ERR(regs); | |
695 | ||
696 | esai_priv->regmap = devm_regmap_init_mmio_clk(&pdev->dev, | |
697 | "core", regs, &fsl_esai_regmap_config); | |
698 | if (IS_ERR(esai_priv->regmap)) { | |
699 | dev_err(&pdev->dev, "failed to init regmap: %ld\n", | |
700 | PTR_ERR(esai_priv->regmap)); | |
701 | return PTR_ERR(esai_priv->regmap); | |
702 | } | |
703 | ||
704 | esai_priv->coreclk = devm_clk_get(&pdev->dev, "core"); | |
705 | if (IS_ERR(esai_priv->coreclk)) { | |
706 | dev_err(&pdev->dev, "failed to get core clock: %ld\n", | |
707 | PTR_ERR(esai_priv->coreclk)); | |
708 | return PTR_ERR(esai_priv->coreclk); | |
709 | } | |
710 | ||
711 | esai_priv->extalclk = devm_clk_get(&pdev->dev, "extal"); | |
712 | if (IS_ERR(esai_priv->extalclk)) | |
713 | dev_warn(&pdev->dev, "failed to get extal clock: %ld\n", | |
714 | PTR_ERR(esai_priv->extalclk)); | |
715 | ||
716 | esai_priv->fsysclk = devm_clk_get(&pdev->dev, "fsys"); | |
717 | if (IS_ERR(esai_priv->fsysclk)) | |
718 | dev_warn(&pdev->dev, "failed to get fsys clock: %ld\n", | |
719 | PTR_ERR(esai_priv->fsysclk)); | |
720 | ||
721 | irq = platform_get_irq(pdev, 0); | |
722 | if (irq < 0) { | |
723 | dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); | |
724 | return irq; | |
725 | } | |
726 | ||
727 | ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, | |
728 | esai_priv->name, esai_priv); | |
729 | if (ret) { | |
730 | dev_err(&pdev->dev, "failed to claim irq %u\n", irq); | |
731 | return ret; | |
732 | } | |
733 | ||
734 | /* Set a default slot size */ | |
735 | esai_priv->slot_width = 32; | |
736 | ||
737 | /* Set a default master/slave state */ | |
738 | esai_priv->slave_mode = true; | |
739 | ||
740 | /* Determine the FIFO depth */ | |
741 | iprop = of_get_property(np, "fsl,fifo-depth", NULL); | |
742 | if (iprop) | |
743 | esai_priv->fifo_depth = be32_to_cpup(iprop); | |
744 | else | |
745 | esai_priv->fifo_depth = 64; | |
746 | ||
747 | esai_priv->dma_params_tx.maxburst = 16; | |
748 | esai_priv->dma_params_rx.maxburst = 16; | |
749 | esai_priv->dma_params_tx.addr = res->start + REG_ESAI_ETDR; | |
750 | esai_priv->dma_params_rx.addr = res->start + REG_ESAI_ERDR; | |
751 | ||
752 | esai_priv->synchronous = | |
753 | of_property_read_bool(np, "fsl,esai-synchronous"); | |
754 | ||
755 | /* Implement full symmetry for synchronous mode */ | |
756 | if (esai_priv->synchronous) { | |
757 | fsl_esai_dai.symmetric_rates = 1; | |
758 | fsl_esai_dai.symmetric_channels = 1; | |
759 | fsl_esai_dai.symmetric_samplebits = 1; | |
760 | } | |
761 | ||
762 | dev_set_drvdata(&pdev->dev, esai_priv); | |
763 | ||
764 | /* Reset ESAI unit */ | |
765 | ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST); | |
766 | if (ret) { | |
767 | dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret); | |
768 | return ret; | |
769 | } | |
770 | ||
771 | /* | |
772 | * We need to enable ESAI so as to access some of its registers. | |
773 | * Otherwise, we would fail to dump regmap from user space. | |
774 | */ | |
775 | ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN); | |
776 | if (ret) { | |
777 | dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret); | |
778 | return ret; | |
779 | } | |
780 | ||
781 | ret = devm_snd_soc_register_component(&pdev->dev, &fsl_esai_component, | |
782 | &fsl_esai_dai, 1); | |
783 | if (ret) { | |
784 | dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); | |
785 | return ret; | |
786 | } | |
787 | ||
788 | ret = imx_pcm_dma_init(pdev); | |
789 | if (ret) | |
790 | dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); | |
791 | ||
792 | return ret; | |
793 | } | |
794 | ||
795 | static const struct of_device_id fsl_esai_dt_ids[] = { | |
796 | { .compatible = "fsl,imx35-esai", }, | |
797 | {} | |
798 | }; | |
799 | MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); | |
800 | ||
801 | static struct platform_driver fsl_esai_driver = { | |
802 | .probe = fsl_esai_probe, | |
803 | .driver = { | |
804 | .name = "fsl-esai-dai", | |
805 | .owner = THIS_MODULE, | |
806 | .of_match_table = fsl_esai_dt_ids, | |
807 | }, | |
808 | }; | |
809 | ||
810 | module_platform_driver(fsl_esai_driver); | |
811 | ||
812 | MODULE_AUTHOR("Freescale Semiconductor, Inc."); | |
813 | MODULE_DESCRIPTION("Freescale ESAI CPU DAI driver"); | |
814 | MODULE_LICENSE("GPL v2"); | |
815 | MODULE_ALIAS("platform:fsl-esai-dai"); |