1 /*
2 * Copyright 2024 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include "esai.h"
8
9 /* TODO:
10 * 1) Some pin functions can be inferred from software ctx. For instance,
11 * if you use more than 1 data line, it's obvious you're going
12 * to want to keep the pins of the data lines in ESAI mode.
13 *
14 * 2) Add function for handling underrun/overrun. Preferably
15 * we should do the same as we did for SAI to ease the testing
16 * process. This approach will do for now. In the future, this
17 * can be handled in a more sophisticated maner.
18 *
19 * notes:
20 * 1) EXTAL clock is divided as follows:
21 * a) Initial EXTAL signal is automatically divided by 2.
22 * b) If prescaler is enabled the resulting EXTAL from a)
23 * is divided by 8.
24 * c) The resulting EXTAL signal from b) can be divided
25 * by 1 up to 256 (configured via xPM0-xPM7). The resulting
26 * signal is referred to as HCLK.
27 * d) HCLK obtained from c) can be further divided by 1
28 * up to 16 (configured via xFP0-xFP3). The resulting signal is
29 * referred to as BCLK.
30 */
31
esai_get_clock_rate_config(uint32_t extal_rate,uint32_t hclk_rate,uint32_t bclk_rate,bool variable_hclk,bool allow_bclk_configuration,struct esai_transceiver_config * cfg)32 static int esai_get_clock_rate_config(uint32_t extal_rate, uint32_t hclk_rate,
33 uint32_t bclk_rate, bool variable_hclk,
34 bool allow_bclk_configuration,
35 struct esai_transceiver_config *cfg)
36 {
37 uint32_t hclk_div_ratio, bclk_div_ratio;
38
39 /* sanity checks */
40 if (!cfg) {
41 LOG_ERR("got NULL clock configuration");
42 return -EINVAL;
43 }
44
45 if (!extal_rate || !hclk_rate || !bclk_rate) {
46 LOG_ERR("got NULL clock rate");
47 return -EINVAL;
48 }
49
50 if (hclk_rate > extal_rate) {
51 LOG_ERR("HCLK rate cannot be higher than EXTAL rate");
52 return -EINVAL;
53 }
54
55 if (bclk_rate > extal_rate) {
56 LOG_ERR("BCLK rate cannot be higher than EXTAL rate");
57 return -EINVAL;
58 }
59
60 if (DIV_ROUND_UP(extal_rate, bclk_rate) > 2 * 8 * 256 * 16) {
61 LOG_ERR("BCLK rate %u cannot be obtained from EXTAL rate %u",
62 bclk_rate, extal_rate);
63 return -EINVAL;
64 }
65
66 /* TODO: add explanation */
67 if (DIV_ROUND_UP(extal_rate / 2, bclk_rate) == 1) {
68 LOG_ERR("HCLK prescaler bypass with divider bypass is not supported");
69 return -EINVAL;
70 }
71
72 hclk_div_ratio = 1;
73 bclk_div_ratio = 1;
74
75 /* check if HCLK is in (EXTAL_RATE / 2, EXTAL_RATE). If so,
76 * return an error as any rates from this interval cannot be obtained.
77 */
78 if (hclk_rate > extal_rate / 2 && hclk_rate < extal_rate) {
79 LOG_ERR("HCLK rate cannot be higher than EXTAL's rate divided by 2");
80 return -EINVAL;
81 }
82
83 /* compute HCLK configuration - only required if HCLK pad output is used */
84 if (!variable_hclk) {
85 if (extal_rate == hclk_rate) {
86 /* HCLK rate from pad is the same as EXTAL rate */
87 cfg->hclk_bypass = true;
88 } else {
89 /* EXTAL is automatically divided by 2 */
90 extal_rate /= 2;
91
92 /* compute prescaler divide ratio w/ prescaler bypass */
93 hclk_div_ratio = DIV_ROUND_UP(extal_rate, hclk_rate);
94
95 if (hclk_div_ratio > 256) {
96 /* can't obtain HCLK w/o prescaler */
97 cfg->hclk_prescaler_en = true;
98
99 extal_rate /= 8;
100
101 /* recompute ratio w/ prescaler */
102 hclk_div_ratio = DIV_ROUND_UP(extal_rate, hclk_rate);
103
104 if (hclk_div_ratio > 256) {
105 LOG_ERR("cannot obtain HCLK rate %u from EXTAL rate %u",
106 hclk_rate, extal_rate);
107 return -EINVAL;
108 }
109 }
110 }
111 }
112
113 cfg->hclk_div_ratio = hclk_div_ratio;
114
115 if (!allow_bclk_configuration) {
116 return 0;
117 }
118
119 extal_rate = DIV_ROUND_UP(extal_rate, hclk_div_ratio);
120
121 /* compute BCLK configuration */
122 if (variable_hclk || cfg->hclk_bypass) {
123 /* attempt to find a configuration that satisfies BCLK's rate */
124 extal_rate /= 2;
125
126 hclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate);
127
128 /* check if prescaler is required */
129 if (hclk_div_ratio > 256 * 16) {
130 extal_rate /= 8;
131 cfg->hclk_prescaler_en = true;
132 hclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate);
133 }
134
135 /* check if we really need to loop through TPM div ratios */
136 if (hclk_div_ratio < 256) {
137 cfg->bclk_div_ratio = 1;
138 cfg->hclk_div_ratio = hclk_div_ratio;
139 return 0;
140 }
141
142 for (int i = 1; i < 256; i++) {
143 hclk_div_ratio = DIV_ROUND_UP(extal_rate / i, bclk_rate);
144 bclk_div_ratio = DIV_ROUND_UP(extal_rate / hclk_div_ratio, bclk_rate);
145
146 if (bclk_div_ratio <= 16) {
147 /* found valid configuration, let caller know */
148 cfg->bclk_div_ratio = bclk_div_ratio;
149 cfg->hclk_div_ratio = hclk_div_ratio;
150
151 return 0;
152 }
153 }
154
155 /* no valid configuration found */
156 LOG_ERR("no valid configuration for BCLK rate %u and EXTAL rate %u",
157 bclk_rate, extal_rate);
158 return -EINVAL;
159 }
160
161 /* can the BCLK rate be obtained w/o modifying divided EXTAL? */
162 bclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate);
163
164 if (bclk_div_ratio > 16) {
165 LOG_ERR("cannot obtain BCLK rate %d from EXTAL rate %d",
166 bclk_rate, extal_rate);
167 return -EINVAL;
168 }
169
170 /* save ratios before returning */
171 cfg->bclk_div_ratio = bclk_div_ratio;
172 cfg->hclk_div_ratio = hclk_div_ratio;
173
174 return 0;
175 }
176
esai_get_clk_provider_config(const struct dai_config * cfg,struct esai_transceiver_config * xceiver_cfg)177 static int esai_get_clk_provider_config(const struct dai_config *cfg,
178 struct esai_transceiver_config *xceiver_cfg)
179 {
180 switch (cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK) {
181 case DAI_CBC_CFC:
182 /* default FSYNC and BCLK are OUTPUT */
183 break;
184 case DAI_CBP_CFP:
185 xceiver_cfg->bclk_dir = kESAI_ClockInput;
186 xceiver_cfg->fsync_dir = kESAI_ClockInput;
187 break;
188 default:
189 LOG_ERR("invalid clock provider configuration: %d",
190 cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK);
191 return -EINVAL;
192 }
193
194 return 0;
195 }
196
esai_get_clk_inversion_config(const struct dai_config * cfg,struct esai_transceiver_config * xceiver_cfg)197 static int esai_get_clk_inversion_config(const struct dai_config *cfg,
198 struct esai_transceiver_config *xceiver_cfg)
199 {
200 switch (cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK) {
201 case DAI_INVERSION_IB_IF:
202 ESAI_INVERT_POLARITY(xceiver_cfg->bclk_polarity);
203 ESAI_INVERT_POLARITY(xceiver_cfg->fsync_polarity);
204 break;
205 case DAI_INVERSION_IB_NF:
206 ESAI_INVERT_POLARITY(xceiver_cfg->bclk_polarity);
207 break;
208 case DAI_INVERSION_NB_IF:
209 ESAI_INVERT_POLARITY(xceiver_cfg->fsync_polarity);
210 break;
211 case DAI_INVERSION_NB_NF:
212 /* nothing to do here */
213 break;
214 default:
215 LOG_ERR("invalid clock inversion configuration: %d",
216 cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK);
217 return -EINVAL;
218 }
219
220 return 0;
221 }
222
esai_get_proto_config(const struct dai_config * cfg,struct esai_transceiver_config * xceiver_cfg)223 static int esai_get_proto_config(const struct dai_config *cfg,
224 struct esai_transceiver_config *xceiver_cfg)
225 {
226 switch (cfg->format & DAI_FORMAT_PROTOCOL_MASK) {
227 case DAI_PROTO_I2S:
228 xceiver_cfg->bclk_polarity = kESAI_ClockActiveLow;
229 xceiver_cfg->fsync_polarity = kESAI_ClockActiveLow;
230 break;
231 case DAI_PROTO_DSP_A:
232 xceiver_cfg->bclk_polarity = kESAI_ClockActiveLow;
233 xceiver_cfg->fsync_is_bit_wide = true;
234 break;
235 default:
236 LOG_ERR("invalid DAI protocol: %d",
237 cfg->format & DAI_FORMAT_PROTOCOL_MASK);
238 return -EINVAL;
239 }
240 return 0;
241 }
242
esai_get_slot_format(uint32_t slot_width,uint32_t word_width,struct esai_transceiver_config * cfg)243 static int esai_get_slot_format(uint32_t slot_width, uint32_t word_width,
244 struct esai_transceiver_config *cfg)
245 {
246 /* sanity check */
247 if (!ESAI_SLOT_WORD_WIDTH_IS_VALID(slot_width, word_width)) {
248 LOG_ERR("invalid slot %d word %d width configuration",
249 slot_width, word_width);
250 return -EINVAL;
251 }
252
253 cfg->slot_format = ESAI_SLOT_FORMAT(slot_width, word_width);
254
255 return 0;
256 }
257
esai_get_xceiver_default_config(struct esai_transceiver_config * cfg)258 static void esai_get_xceiver_default_config(struct esai_transceiver_config *cfg)
259 {
260 memset(cfg, 0, sizeof(*cfg));
261
262 cfg->hclk_prescaler_en = false;
263 cfg->hclk_div_ratio = 1;
264 cfg->bclk_div_ratio = 1;
265 cfg->hclk_bypass = false;
266
267 cfg->hclk_src = kESAI_HckSourceExternal;
268 cfg->hclk_dir = kESAI_ClockOutput;
269 cfg->hclk_polarity = kESAI_ClockActiveHigh;
270
271 cfg->bclk_dir = kESAI_ClockOutput;
272 cfg->bclk_polarity = kESAI_ClockActiveHigh;
273
274 cfg->fsync_dir = kESAI_ClockOutput;
275 cfg->fsync_polarity = kESAI_ClockActiveHigh;
276
277 cfg->fsync_is_bit_wide = false;
278 cfg->zero_pad_en = true;
279 cfg->fsync_early = true;
280
281 cfg->mode = kESAI_NetworkMode;
282 cfg->data_order = kESAI_ShifterMSB;
283 cfg->data_left_aligned = true;
284 }
285
esai_commit_config(ESAI_Type * base,enum dai_dir dir,struct esai_transceiver_config * cfg)286 static void esai_commit_config(ESAI_Type *base,
287 enum dai_dir dir,
288 struct esai_transceiver_config *cfg)
289 {
290 if (dir == DAI_DIR_TX) {
291 base->TCCR &= ~(ESAI_TCCR_THCKD_MASK | ESAI_TCCR_TFSD_MASK |
292 ESAI_TCCR_TCKD_MASK | ESAI_TCCR_THCKP_MASK |
293 ESAI_TCCR_TFSP_MASK | ESAI_TCCR_TCKP_MASK |
294 ESAI_TCCR_TFP_MASK | ESAI_TCCR_TDC_MASK |
295 ESAI_TCCR_TPSR_MASK | ESAI_TCCR_TPM_MASK);
296
297 base->TCCR |= ESAI_TCCR_THCKD(cfg->hclk_dir) |
298 ESAI_TCCR_TFSD(cfg->fsync_dir) |
299 ESAI_TCCR_TCKD(cfg->bclk_dir) |
300 ESAI_TCCR_THCKP(cfg->hclk_polarity) |
301 ESAI_TCCR_TFSP(cfg->fsync_polarity) |
302 ESAI_TCCR_TCKP(cfg->bclk_polarity) |
303 ESAI_TCCR_TFP(cfg->bclk_div_ratio - 1) |
304 ESAI_TCCR_TDC(cfg->fsync_div - 1) |
305 ESAI_TCCR_TPSR(!cfg->hclk_prescaler_en) |
306 ESAI_TCCR_TPM(cfg->hclk_div_ratio - 1);
307
308 base->TCR &= ~(ESAI_TCR_PADC_MASK | ESAI_TCR_TFSR_MASK |
309 ESAI_TCR_TFSL_MASK | ESAI_TCR_TMOD_MASK |
310 ESAI_TCR_TWA_MASK | ESAI_TCR_TSHFD_MASK);
311
312 base->TCR |= ESAI_TCR_PADC(cfg->zero_pad_en) |
313 ESAI_TCR_TFSR(cfg->fsync_early) |
314 ESAI_TCR_TFSL(cfg->fsync_is_bit_wide) |
315 ESAI_TCR_TSWS(cfg->slot_format) |
316 ESAI_TCR_TMOD(cfg->mode) |
317 ESAI_TCR_TWA(!cfg->data_left_aligned) |
318 ESAI_TCR_TSHFD(cfg->data_order);
319
320 base->ECR &= ~(ESAI_ECR_ETI_MASK |
321 ESAI_ECR_ETO_MASK);
322
323 base->ECR |= ESAI_ECR_ETI(cfg->hclk_src) |
324 ESAI_ECR_ETO(cfg->hclk_bypass);
325
326 base->TFCR &= ~(ESAI_TFCR_TFWM_MASK | ESAI_TFCR_TWA_MASK);
327 base->TFCR |= ESAI_TFCR_TFWM(cfg->watermark) |
328 ESAI_TFCR_TWA(cfg->word_alignment);
329
330 ESAI_TxSetSlotMask(base, cfg->slot_mask);
331 } else {
332 base->RCCR &= ~(ESAI_RCCR_RHCKD_MASK | ESAI_RCCR_RFSD_MASK |
333 ESAI_RCCR_RCKD_MASK | ESAI_RCCR_RHCKP_MASK |
334 ESAI_RCCR_RFSP_MASK | ESAI_RCCR_RCKP_MASK |
335 ESAI_RCCR_RFP_MASK | ESAI_RCCR_RDC_MASK |
336 ESAI_RCCR_RPSR_MASK | ESAI_RCCR_RPM_MASK);
337
338 base->RCCR |= ESAI_RCCR_RHCKD(cfg->hclk_dir) |
339 ESAI_RCCR_RFSD(cfg->fsync_dir) |
340 ESAI_RCCR_RCKD(cfg->bclk_dir) |
341 ESAI_RCCR_RHCKP(cfg->hclk_polarity) |
342 ESAI_RCCR_RFSP(cfg->fsync_polarity) |
343 ESAI_RCCR_RCKP(cfg->bclk_polarity) |
344 ESAI_RCCR_RFP(cfg->bclk_div_ratio - 1) |
345 ESAI_RCCR_RDC(cfg->fsync_div - 1) |
346 ESAI_RCCR_RPSR(!cfg->hclk_prescaler_en) |
347 ESAI_RCCR_RPM(cfg->hclk_div_ratio - 1);
348
349 base->RCR &= ~(ESAI_RCR_RFSR_MASK | ESAI_RCR_RFSL_MASK |
350 ESAI_RCR_RMOD_MASK | ESAI_RCR_RWA_MASK |
351 ESAI_RCR_RSHFD_MASK);
352
353 base->RCR |= ESAI_RCR_RFSR(cfg->fsync_early) |
354 ESAI_RCR_RFSL(cfg->fsync_is_bit_wide) |
355 ESAI_RCR_RSWS(cfg->slot_format) |
356 ESAI_RCR_RMOD(cfg->mode) |
357 ESAI_RCR_RWA(!cfg->data_left_aligned) |
358 ESAI_RCR_RSHFD(cfg->data_order);
359
360 base->ECR &= ~(ESAI_ECR_ERI_MASK |
361 ESAI_ECR_ERO_MASK);
362
363 base->ECR |= ESAI_ECR_ERI(cfg->hclk_src) |
364 ESAI_ECR_ERO(cfg->hclk_bypass);
365
366 base->RFCR &= ~(ESAI_RFCR_RFWM_MASK | ESAI_RFCR_RWA_MASK);
367 base->RFCR |= ESAI_RFCR_RFWM(cfg->watermark) |
368 ESAI_RFCR_RWA(cfg->word_alignment);
369
370 EASI_RxSetSlotMask(base, cfg->slot_mask);
371 }
372 }
373
esai_config_set(const struct device * dev,const struct dai_config * cfg,const void * bespoke_data)374 static int esai_config_set(const struct device *dev,
375 const struct dai_config *cfg,
376 const void *bespoke_data)
377 {
378 const struct esai_bespoke_config *bespoke;
379 struct esai_data *data;
380 const struct esai_config *esai_cfg;
381 struct esai_transceiver_config tx_config;
382 struct esai_transceiver_config rx_config;
383 ESAI_Type *base;
384 int ret;
385
386 if (!cfg || !bespoke_data) {
387 return -EINVAL;
388 }
389
390 if (cfg->type != DAI_IMX_ESAI) {
391 LOG_ERR("wrong DAI type: %d", cfg->type);
392 return -EINVAL;
393 }
394
395 data = dev->data;
396 esai_cfg = dev->config;
397 bespoke = bespoke_data;
398 base = UINT_TO_ESAI(data->regmap);
399
400 /* config_set() configures both the transmitter and the receiver.
401 * As such, the following state transitions make sure that the
402 * directions are stopped. This means that they can be safely
403 * reset and re-configured.
404 */
405 ret = esai_update_state(data, DAI_DIR_TX, DAI_STATE_READY);
406 if (ret < 0) {
407 LOG_ERR("failed to update TX state");
408 return ret;
409 }
410
411 ret = esai_update_state(data, DAI_DIR_RX, DAI_STATE_READY);
412 if (ret < 0) {
413 LOG_ERR("failed to update RX state");
414 return ret;
415 }
416
417 ESAI_Enable(base, true);
418
419 /* disconnect all ESAI pins */
420 base->PCRC &= ~ESAI_PCRC_PC_MASK;
421 base->PRRC &= ~ESAI_PRRC_PDC_MASK;
422
423 /* go back to known configuration through reset */
424 ESAI_Reset(UINT_TO_ESAI(data->regmap));
425
426 /* get default configuration */
427 esai_get_xceiver_default_config(&tx_config);
428
429 /* TODO: for now, only network mode is supported */
430 tx_config.fsync_div = bespoke->tdm_slots;
431
432 /* clock provider configuration */
433 ret = esai_get_clk_provider_config(cfg, &tx_config);
434 if (ret < 0) {
435 return ret;
436 }
437
438 /* protocol configuration */
439 ret = esai_get_proto_config(cfg, &tx_config);
440 if (ret < 0) {
441 return ret;
442 }
443
444 /* clock inversion configuration */
445 ret = esai_get_clk_inversion_config(cfg, &tx_config);
446 if (ret < 0) {
447 return ret;
448 }
449
450 ret = esai_get_slot_format(bespoke->tdm_slot_width,
451 esai_cfg->word_width, &tx_config);
452 if (ret < 0) {
453 return ret;
454 }
455
456 tx_config.word_alignment = ESAI_WORD_ALIGNMENT(esai_cfg->word_width);
457
458 /* duplicate TX configuration */
459 memcpy(&rx_config, &tx_config, sizeof(tx_config));
460
461 /* parse clock configuration from DTS. This will overwrite
462 * directions set in bespoke data.
463 */
464 ret = esai_parse_clock_config(esai_cfg, &tx_config, &rx_config);
465 if (ret < 0) {
466 return ret;
467 }
468
469 /* compute TX clock configuration */
470 ret = esai_get_clock_rate_config(bespoke->mclk_rate, bespoke->mclk_rate,
471 bespoke->bclk_rate,
472 !ESAI_PIN_IS_USED(data, ESAI_PIN_HCKT),
473 tx_config.bclk_dir,
474 &tx_config);
475 if (ret < 0) {
476 return ret;
477 }
478
479 /* compute RX clock configuration */
480 ret = esai_get_clock_rate_config(bespoke->mclk_rate, bespoke->mclk_rate,
481 bespoke->bclk_rate,
482 !ESAI_PIN_IS_USED(data, ESAI_PIN_HCKR),
483 rx_config.bclk_dir,
484 &rx_config);
485 if (ret < 0) {
486 return ret;
487 }
488
489
490 tx_config.watermark = esai_cfg->tx_fifo_watermark;
491 rx_config.watermark = esai_cfg->rx_fifo_watermark;
492
493 tx_config.slot_mask = bespoke->tx_slots;
494 rx_config.slot_mask = bespoke->rx_slots;
495
496 LOG_DBG("dumping TX configuration");
497 esai_dump_xceiver_config(&tx_config);
498
499 LOG_DBG("dumping RX configuration");
500 esai_dump_xceiver_config(&rx_config);
501
502 /* enable ESAI to allow committing the configurations */
503 ESAI_Enable(base, true);
504
505 esai_dump_register_data(base);
506
507 esai_commit_config(base, DAI_DIR_TX, &tx_config);
508 esai_commit_config(base, DAI_DIR_RX, &rx_config);
509
510 /* allow each TX data register to be initialized from TX FIFO */
511 base->TFCR |= ESAI_TFCR_TIEN_MASK;
512
513 /* enable FIFO usage
514 *
515 * TODO: for now, only 1 data line per direction is supported.
516 */
517 esai_tx_rx_enable_disable_fifo_usage(base, DAI_DIR_TX, BIT(0), true);
518 esai_tx_rx_enable_disable_fifo_usage(base, DAI_DIR_RX, BIT(0), true);
519
520 /* re-connect pins based on DTS pin configuration */
521 base->PCRC = data->pcrc;
522 base->PRRC = data->prrc;
523
524 data->cfg.rate = bespoke->fsync_rate;
525 data->cfg.channels = bespoke->tdm_slots;
526
527 esai_dump_register_data(base);
528
529 return 0;
530 }
531
esai_config_get(const struct device * dev,struct dai_config * cfg,enum dai_dir dir)532 static int esai_config_get(const struct device *dev,
533 struct dai_config *cfg,
534 enum dai_dir dir)
535 {
536 struct esai_data *data = dev->data;
537
538 if (!cfg) {
539 return -EINVAL;
540 }
541
542 memcpy(cfg, &data->cfg, sizeof(*cfg));
543
544 return 0;
545 }
546
esai_trigger_start(const struct device * dev,enum dai_dir dir)547 static int esai_trigger_start(const struct device *dev, enum dai_dir dir)
548 {
549 struct esai_data *data;
550 ESAI_Type *base;
551 int ret, i;
552
553 data = dev->data;
554 base = UINT_TO_ESAI(data->regmap);
555
556 ret = esai_update_state(data, dir, DAI_STATE_RUNNING);
557 if (ret < 0) {
558 LOG_ERR("failed to transition to RUNNING");
559 return -EINVAL;
560 }
561
562 LOG_DBG("starting direction %d", dir);
563
564 /* enable the FIFO */
565 esai_tx_rx_enable_disable_fifo(base, dir, true);
566
567 /* TODO: without this, the ESAI won't enter underrun
568 * but playing a song while doing pause-resume very
569 * fast seems to result in a degraded sound quality?
570 *
571 * TODO: for multiple channels, this needs to be changed.
572 */
573 if (dir == DAI_DIR_TX) {
574 for (i = 0; i < 1; i++) {
575 ESAI_WriteData(base, 0x0);
576 }
577 }
578
579 /* enable the transmitter/receiver */
580 esai_tx_rx_enable_disable(base, dir, BIT(0), true);
581
582 return 0;
583 }
584
esai_trigger_stop(const struct device * dev,enum dai_dir dir)585 static int esai_trigger_stop(const struct device *dev, enum dai_dir dir)
586 {
587 struct esai_data *data;
588 int ret;
589 ESAI_Type *base;
590
591 data = dev->data;
592 base = UINT_TO_ESAI(data->regmap);
593
594 ret = esai_update_state(data, dir, DAI_STATE_STOPPING);
595 if (ret < 0) {
596 LOG_ERR("failed to transition to STOPPING");
597 return -EINVAL;
598 }
599
600 LOG_DBG("stopping direction %d", dir);
601
602 /* disable transmitter/receiver */
603 esai_tx_rx_enable_disable(base, dir, BIT(0), false);
604
605 /* disable FIFO */
606 esai_tx_rx_enable_disable_fifo(base, dir, false);
607
608 return 0;
609 }
610
esai_trigger(const struct device * dev,enum dai_dir dir,enum dai_trigger_cmd cmd)611 static int esai_trigger(const struct device *dev,
612 enum dai_dir dir,
613 enum dai_trigger_cmd cmd)
614 {
615 /* TX/RX should be triggered individually */
616 if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) {
617 LOG_ERR("invalid direction: %d", dir);
618 return -EINVAL;
619 }
620
621 switch (cmd) {
622 case DAI_TRIGGER_START:
623 return esai_trigger_start(dev, dir);
624 case DAI_TRIGGER_PAUSE:
625 case DAI_TRIGGER_STOP:
626 return esai_trigger_stop(dev, dir);
627 case DAI_TRIGGER_PRE_START:
628 case DAI_TRIGGER_COPY:
629 /* nothing to do here, return success code */
630 return 0;
631 default:
632 LOG_ERR("invalid trigger command: %d", cmd);
633 return -EINVAL;
634 }
635
636 return 0;
637 }
638
639 static const struct dai_properties
esai_get_properties(const struct device * dev,enum dai_dir dir,int stream_id)640 *esai_get_properties(const struct device *dev, enum dai_dir dir, int stream_id)
641 {
642 const struct esai_config *cfg = dev->config;
643
644 switch (dir) {
645 case DAI_DIR_RX:
646 return cfg->rx_props;
647 case DAI_DIR_TX:
648 return cfg->tx_props;
649 default:
650 LOG_ERR("invalid direction: %d", dir);
651 return NULL;
652 }
653 }
654
esai_probe(const struct device * dev)655 static int esai_probe(const struct device *dev)
656 {
657 /* nothing to be done here but mandatory to implement */
658 return 0;
659 }
660
esai_remove(const struct device * dev)661 static int esai_remove(const struct device *dev)
662 {
663 /* nothing to be done here but mandatory to implement */
664 return 0;
665 }
666
667 static DEVICE_API(dai, esai_api) = {
668 .config_set = esai_config_set,
669 .config_get = esai_config_get,
670 .trigger = esai_trigger,
671 .get_properties = esai_get_properties,
672 .probe = esai_probe,
673 .remove = esai_remove,
674 };
675
esai_init(const struct device * dev)676 static int esai_init(const struct device *dev)
677 {
678 const struct esai_config *cfg;
679 struct esai_data *data;
680 int ret;
681
682 cfg = dev->config;
683 data = dev->data;
684
685 device_map(&data->regmap, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE);
686
687 ret = esai_parse_pinmodes(cfg, data);
688 if (ret < 0) {
689 return ret;
690 }
691
692 return 0;
693 }
694
695 #define ESAI_INIT(inst) \
696 \
697 BUILD_ASSERT(ESAI_TX_FIFO_WATERMARK(inst) >= 1 && \
698 ESAI_TX_FIFO_WATERMARK(inst) <= _ESAI_FIFO_DEPTH(inst), \
699 "invalid TX watermark value"); \
700 \
701 BUILD_ASSERT(ESAI_RX_FIFO_WATERMARK(inst) >= 1 && \
702 ESAI_RX_FIFO_WATERMARK(inst) <= _ESAI_FIFO_DEPTH(inst), \
703 "invalid RX watermark value"); \
704 \
705 BUILD_ASSERT(ESAI_FIFO_DEPTH(inst) >= 1 && \
706 ESAI_FIFO_DEPTH(inst) <= _ESAI_FIFO_DEPTH(inst), \
707 "invalid FIFO depth value"); \
708 \
709 BUILD_ASSERT(ESAI_WORD_WIDTH(inst) == 8 || \
710 ESAI_WORD_WIDTH(inst) == 12 || \
711 ESAI_WORD_WIDTH(inst) == 16 || \
712 ESAI_WORD_WIDTH(inst) == 20 || \
713 ESAI_WORD_WIDTH(inst) == 24, \
714 "invalid word width value"); \
715 \
716 static const struct dai_properties esai_tx_props_##inst = { \
717 .fifo_address = ESAI_TX_FIFO_BASE(inst), \
718 .fifo_depth = ESAI_FIFO_DEPTH(inst) * 4, \
719 .dma_hs_id = ESAI_TX_RX_DMA_HANDSHAKE(inst, tx), \
720 }; \
721 \
722 static const struct dai_properties esai_rx_props_##inst = { \
723 .fifo_address = ESAI_RX_FIFO_BASE(inst), \
724 .fifo_depth = ESAI_FIFO_DEPTH(inst) * 4, \
725 .dma_hs_id = ESAI_TX_RX_DMA_HANDSHAKE(inst, rx), \
726 }; \
727 \
728 static uint32_t pinmodes_##inst[] = \
729 DT_INST_PROP_OR(inst, esai_pin_modes, {}); \
730 \
731 BUILD_ASSERT(ARRAY_SIZE(pinmodes_##inst) % 2 == 0, \
732 "bad pinmask array size"); \
733 \
734 static uint32_t clock_cfg_##inst[] = \
735 DT_INST_PROP_OR(inst, esai_clock_configuration, {}); \
736 \
737 BUILD_ASSERT(ARRAY_SIZE(clock_cfg_##inst) % 2 == 0, \
738 "bad clock configuration array size"); \
739 \
740 static struct esai_config esai_config_##inst = { \
741 .regmap_phys = DT_INST_REG_ADDR(inst), \
742 .regmap_size = DT_INST_REG_SIZE(inst), \
743 .tx_props = &esai_tx_props_##inst, \
744 .rx_props = &esai_rx_props_##inst, \
745 .tx_fifo_watermark = ESAI_TX_FIFO_WATERMARK(inst), \
746 .rx_fifo_watermark = ESAI_RX_FIFO_WATERMARK(inst), \
747 .word_width = ESAI_WORD_WIDTH(inst), \
748 .pinmodes = pinmodes_##inst, \
749 .pinmodes_size = ARRAY_SIZE(pinmodes_##inst), \
750 .clock_cfg = clock_cfg_##inst, \
751 .clock_cfg_size = ARRAY_SIZE(clock_cfg_##inst), \
752 }; \
753 \
754 static struct esai_data esai_data_##inst = { \
755 .cfg.type = DAI_IMX_ESAI, \
756 .cfg.dai_index = DT_INST_PROP_OR(inst, dai_index, 0), \
757 }; \
758 \
759 DEVICE_DT_INST_DEFINE(inst, &esai_init, NULL, \
760 &esai_data_##inst, &esai_config_##inst, \
761 POST_KERNEL, CONFIG_DAI_INIT_PRIORITY, \
762 &esai_api); \
763
764 DT_INST_FOREACH_STATUS_OKAY(ESAI_INIT);
765