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