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