1 /*
2  * Copyright 2023 NXP
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/dai.h>
8 #include <zephyr/device.h>
9 #include <zephyr/kernel.h>
10 #include <zephyr/pm/device_runtime.h>
11 #include <zephyr/pm/device.h>
12 
13 #include "sai.h"
14 
15 /* used for binding the driver */
16 #define DT_DRV_COMPAT nxp_dai_sai
17 
18 #define SAI_TX_RX_HW_DISABLE_TIMEOUT 50
19 
20 /* TODO list:
21  *
22  * 1) No busy waiting should be performed in any of the operations.
23  * In the case of STOP(), the operation should be split into TRIGGER_STOP
24  * and TRIGGER_POST_STOP. (SOF)
25  *
26  * 2) The SAI ISR should stop the SAI whenever a FIFO error interrupt
27  * is raised.
28  *
29  * 3) Transmitter/receiver may remain enabled after sai_tx_rx_disable().
30  * Fix this.
31  */
32 
33 #ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION
34 /* note: i.MX8 boards don't seem to support the MICS field in the MCR
35  * register. As such, the MCLK source field of sai_master_clock_t is
36  * useless. I'm assuming the source is selected through xCR2's MSEL.
37  *
38  * TODO: for now, this function will set MCR's MSEL to the same value
39  * as xCR2's MSEL or, rather, to the same MCLK as the one used for
40  * generating BCLK. Is there a need to support different MCLKs in
41  * xCR2 and MCR?
42  */
sai_mclk_config(const struct device * dev,sai_bclk_source_t bclk_source,const struct sai_bespoke_config * bespoke)43 static int sai_mclk_config(const struct device *dev,
44 			   sai_bclk_source_t bclk_source,
45 			   const struct sai_bespoke_config *bespoke)
46 {
47 	const struct sai_config *cfg;
48 	struct sai_data *data;
49 	sai_master_clock_t mclk_config;
50 	uint32_t msel, mclk_rate;
51 	int ret;
52 
53 	cfg = dev->config;
54 	data = dev->data;
55 
56 	mclk_config.mclkOutputEnable = cfg->mclk_is_output;
57 
58 	ret = get_msel(bclk_source, &msel);
59 	if (ret < 0) {
60 		LOG_ERR("invalid MCLK source %d for MSEL", bclk_source);
61 		return ret;
62 	}
63 
64 	/* get MCLK's rate */
65 	ret = get_mclk_rate(&cfg->clk_data, bclk_source, &mclk_rate);
66 	if (ret < 0) {
67 		LOG_ERR("failed to query MCLK's rate");
68 		return ret;
69 	}
70 
71 	LOG_DBG("source MCLK is %u", mclk_rate);
72 
73 	LOG_DBG("target MCLK is %u", bespoke->mclk_rate);
74 
75 	/* source MCLK rate */
76 	mclk_config.mclkSourceClkHz = mclk_rate;
77 
78 	/* target MCLK rate */
79 	mclk_config.mclkHz = bespoke->mclk_rate;
80 
81 	/* commit configuration */
82 	SAI_SetMasterClockConfig(UINT_TO_I2S(data->regmap), &mclk_config);
83 
84 	set_msel(data->regmap, msel);
85 
86 	return 0;
87 }
88 #endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */
89 
sai_isr(const void * parameter)90 void sai_isr(const void *parameter)
91 {
92 	const struct device *dev;
93 	struct sai_data *data;
94 
95 	dev = parameter;
96 	data = dev->data;
97 
98 	/* check for TX FIFO error */
99 	if (SAI_TX_RX_STATUS_IS_SET(DAI_DIR_TX, data->regmap, kSAI_FIFOErrorFlag)) {
100 		LOG_WRN("FIFO underrun detected");
101 		SAI_TX_RX_STATUS_CLEAR(DAI_DIR_TX, data->regmap, kSAI_FIFOErrorFlag);
102 	}
103 
104 	/* check for RX FIFO error */
105 	if (SAI_TX_RX_STATUS_IS_SET(DAI_DIR_RX, data->regmap, kSAI_FIFOErrorFlag)) {
106 		LOG_WRN("FIFO overrun detected");
107 		SAI_TX_RX_STATUS_CLEAR(DAI_DIR_RX, data->regmap, kSAI_FIFOErrorFlag);
108 	}
109 }
110 
sai_config_get(const struct device * dev,struct dai_config * cfg,enum dai_dir dir)111 static int sai_config_get(const struct device *dev,
112 			  struct dai_config *cfg,
113 			  enum dai_dir dir)
114 {
115 	struct sai_data *data = dev->data;
116 
117 	/* dump content of the DAI configuration */
118 	memcpy(cfg, &data->cfg, sizeof(*cfg));
119 
120 	return 0;
121 }
122 
123 static const struct dai_properties
sai_get_properties(const struct device * dev,enum dai_dir dir,int stream_id)124 	*sai_get_properties(const struct device *dev, enum dai_dir dir, int stream_id)
125 {
126 	const struct sai_config *cfg = dev->config;
127 
128 	switch (dir) {
129 	case DAI_DIR_RX:
130 		return cfg->rx_props;
131 	case DAI_DIR_TX:
132 		return cfg->tx_props;
133 	default:
134 		LOG_ERR("invalid direction: %d", dir);
135 		return NULL;
136 	}
137 
138 	CODE_UNREACHABLE;
139 }
140 
141 #ifdef CONFIG_SAI_IMX93_ERRATA_051421
142 /* notes:
143  *	1) TX and RX operate in the same mode: master/slave. As such,
144  *	there's no need to check the mode for both directions.
145  *
146  *	2) Only one of the directions can operate in SYNC mode at a
147  *	time.
148  *
149  *	3) What this piece of code does is it makes the SYNC direction
150  *	use the ASYNC direction's BCLK that comes from its input pad.
151  *	Logically speaking, this would look like:
152  *
153  *                      +--------+     +--------+
154  *                      |   TX   |     |   RX   |
155  *                      | module |     | module |
156  *                      +--------+     +--------+
157  *                         |   ^            |
158  *                         |   |            |
159  *                 TX_BCLK |   |____________| RX_BCLK
160  *                         |                |
161  *                         V                V
162  *                     +---------+    +---------+
163  *                     | TX BCLK |    | RX BCLK |
164  *                     |   pad   |    |   pad   |
165  *                     +---------+    +---------+
166  *                          |              |
167  *                          | TX_BCLK      | RX_BCLK
168  *                          V              V
169  *
170  *	Without BCI enabled, the TX module would use an RX_BCLK
171  *	that's divided instead of the one that's obtained from
172  *	bypassing the MCLK (i.e: TX_BCLK would have the value of
173  *	MCLK / ((RX_DIV + 1) * 2)). If BCI is 1, then TX_BCLK will
174  *	be the same as the RX_BCLK that's obtained from bypassing
175  *	the MCLK on RX's side.
176  *
177  *	4) The check for BCLK == MCLK is there to see if the ASYNC
178  *	direction will have the BYP bit toggled.
179  *
180  *	IMPORTANT1: in the above diagram and information, RX is SYNC
181  *	with TX. The same applies if RX is SYNC with TX. Also, this
182  *	applies to i.MX93. For other SoCs, things may be different
183  *	so use this information with caution.
184  *
185  *	IMPORTANT2: for this to work, you also need to enable the
186  *	pad's input path. For i.MX93, this can be achieved by setting
187  *	the pad's SION bit.
188  */
sai_config_set_err_051421(I2S_Type * base,const struct sai_config * cfg,const struct sai_bespoke_config * bespoke,sai_transceiver_t * rx_config,sai_transceiver_t * tx_config)189 static void sai_config_set_err_051421(I2S_Type *base,
190 				      const struct sai_config *cfg,
191 				      const struct sai_bespoke_config *bespoke,
192 				      sai_transceiver_t *rx_config,
193 				      sai_transceiver_t *tx_config)
194 {
195 	if (tx_config->masterSlave == kSAI_Master &&
196 	    bespoke->mclk_rate == bespoke->bclk_rate) {
197 		if (cfg->tx_sync_mode == kSAI_ModeSync) {
198 			base->TCR2 |= I2S_TCR2_BCI(1);
199 		}
200 
201 		if (cfg->rx_sync_mode == kSAI_ModeSync) {
202 			base->RCR2 |= I2S_RCR2_BCI(1);
203 		}
204 	}
205 }
206 #endif /* CONFIG_SAI_IMX93_ERRATA_051421 */
207 
sai_config_set(const struct device * dev,const struct dai_config * cfg,const void * bespoke_data)208 static int sai_config_set(const struct device *dev,
209 			  const struct dai_config *cfg,
210 			  const void *bespoke_data)
211 {
212 	const struct sai_bespoke_config *bespoke;
213 	sai_transceiver_t *rx_config, *tx_config;
214 	struct sai_data *data;
215 	const struct sai_config *sai_cfg;
216 	int ret;
217 
218 	if (cfg->type != DAI_IMX_SAI) {
219 		LOG_ERR("wrong DAI type: %d", cfg->type);
220 		return -EINVAL;
221 	}
222 
223 	bespoke = bespoke_data;
224 	data = dev->data;
225 	sai_cfg = dev->config;
226 	rx_config = &data->rx_config;
227 	tx_config = &data->tx_config;
228 
229 	/* since this function configures the transmitter AND the receiver, that
230 	 * means both of them need to be stopped. As such, doing the state
231 	 * transition here will also result in a state check.
232 	 */
233 	ret = sai_update_state(DAI_DIR_TX, data, DAI_STATE_READY);
234 	if (ret < 0) {
235 		LOG_ERR("failed to update TX state. Reason: %d", ret);
236 		return ret;
237 	}
238 
239 	ret = sai_update_state(DAI_DIR_RX, data, DAI_STATE_READY);
240 	if (ret < 0) {
241 		LOG_ERR("failed to update RX state. Reason: %d", ret);
242 		return ret;
243 	}
244 
245 	/* condition: BCLK = FSYNC * TDM_SLOT_WIDTH * TDM_SLOTS */
246 	if (bespoke->bclk_rate !=
247 	    (bespoke->fsync_rate * bespoke->tdm_slot_width * bespoke->tdm_slots)) {
248 		LOG_ERR("bad BCLK value: %d", bespoke->bclk_rate);
249 		return -EINVAL;
250 	}
251 
252 	/* TODO: this should be removed if we're to support sw channels != hw channels */
253 	if (count_leading_zeros(~bespoke->tx_slots) != bespoke->tdm_slots ||
254 	    count_leading_zeros(~bespoke->rx_slots) != bespoke->tdm_slots) {
255 		LOG_ERR("number of TX/RX slots doesn't match number of TDM slots");
256 		return -EINVAL;
257 	}
258 
259 	/* get default configurations */
260 	get_bclk_default_config(&tx_config->bitClock);
261 	get_fsync_default_config(&tx_config->frameSync);
262 	get_serial_default_config(&tx_config->serialData);
263 	get_fifo_default_config(&tx_config->fifo);
264 
265 	/* note1: this may be obvious but enabling multiple SAI
266 	 * channels (or data lines) may lead to FIFO starvation/
267 	 * overflow if data is not written/read from the respective
268 	 * TDR/RDR registers.
269 	 *
270 	 * note2: the SAI data line should be enabled based on
271 	 * the direction (TX/RX) we're enabling. Enabling the
272 	 * data line for the opposite direction will lead to FIFO
273 	 * overrun/underrun when working with a SYNC direction.
274 	 *
275 	 * note3: the TX/RX data line shall be enabled/disabled
276 	 * via the sai_trigger_() suite to avoid scenarios in
277 	 * which one configures both direction but only starts
278 	 * the SYNC direction which would lead to a FIFO underrun.
279 	 */
280 	tx_config->channelMask = 0x0;
281 
282 	/* TODO: for now, only MCLK1 is supported */
283 	tx_config->bitClock.bclkSource = kSAI_BclkSourceMclkOption1;
284 
285 	/* FSYNC is asserted for tdm_slot_width BCLKs */
286 	tx_config->frameSync.frameSyncWidth = bespoke->tdm_slot_width;
287 
288 	/* serial data common configuration */
289 	tx_config->serialData.dataWord0Length = bespoke->tdm_slot_width;
290 	tx_config->serialData.dataWordNLength = bespoke->tdm_slot_width;
291 	tx_config->serialData.dataFirstBitShifted = bespoke->tdm_slot_width;
292 	tx_config->serialData.dataWordNum = bespoke->tdm_slots;
293 
294 	/* clock provider configuration */
295 	switch (cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK) {
296 	case DAI_CBP_CFP:
297 		tx_config->masterSlave = kSAI_Slave;
298 		break;
299 	case DAI_CBC_CFC:
300 		tx_config->masterSlave = kSAI_Master;
301 		break;
302 	case DAI_CBC_CFP:
303 	case DAI_CBP_CFC:
304 		LOG_ERR("unsupported provider configuration: %d",
305 			cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK);
306 		return -ENOTSUP;
307 	default:
308 		LOG_ERR("invalid provider configuration: %d",
309 			cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK);
310 		return -EINVAL;
311 	}
312 
313 	LOG_DBG("SAI is in %d mode", tx_config->masterSlave);
314 
315 	/* protocol configuration */
316 	switch (cfg->format & DAI_FORMAT_PROTOCOL_MASK) {
317 	case DAI_PROTO_I2S:
318 		/* BCLK is active LOW */
319 		tx_config->bitClock.bclkPolarity = kSAI_PolarityActiveLow;
320 		/* FSYNC is active LOW */
321 		tx_config->frameSync.frameSyncPolarity = kSAI_PolarityActiveLow;
322 		break;
323 	case DAI_PROTO_DSP_A:
324 		/* FSYNC is asserted for a single BCLK */
325 		tx_config->frameSync.frameSyncWidth = 1;
326 		/* BCLK is active LOW */
327 		tx_config->bitClock.bclkPolarity = kSAI_PolarityActiveLow;
328 		break;
329 	default:
330 		LOG_ERR("unsupported DAI protocol: %d",
331 			cfg->format & DAI_FORMAT_PROTOCOL_MASK);
332 		return -EINVAL;
333 	}
334 
335 	LOG_DBG("SAI uses protocol: %d",
336 		cfg->format & DAI_FORMAT_PROTOCOL_MASK);
337 
338 	/* clock inversion configuration */
339 	switch (cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK) {
340 	case DAI_INVERSION_IB_IF:
341 		SAI_INVERT_POLARITY(tx_config->bitClock.bclkPolarity);
342 		SAI_INVERT_POLARITY(tx_config->frameSync.frameSyncPolarity);
343 		break;
344 	case DAI_INVERSION_IB_NF:
345 		SAI_INVERT_POLARITY(tx_config->bitClock.bclkPolarity);
346 		break;
347 	case DAI_INVERSION_NB_IF:
348 		SAI_INVERT_POLARITY(tx_config->frameSync.frameSyncPolarity);
349 		break;
350 	case DAI_INVERSION_NB_NF:
351 		/* nothing to do here */
352 		break;
353 	default:
354 		LOG_ERR("invalid clock inversion configuration: %d",
355 			cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK);
356 		return -EINVAL;
357 	}
358 
359 	LOG_DBG("FSYNC polarity: %d", tx_config->frameSync.frameSyncPolarity);
360 	LOG_DBG("BCLK polarity: %d", tx_config->bitClock.bclkPolarity);
361 
362 	/* duplicate TX configuration */
363 	memcpy(rx_config, tx_config, sizeof(sai_transceiver_t));
364 
365 	tx_config->serialData.dataMaskedWord = ~bespoke->tx_slots;
366 	rx_config->serialData.dataMaskedWord = ~bespoke->rx_slots;
367 
368 	tx_config->fifo.fifoWatermark = sai_cfg->tx_fifo_watermark - 1;
369 	rx_config->fifo.fifoWatermark = sai_cfg->rx_fifo_watermark - 1;
370 
371 	LOG_DBG("RX watermark: %d", sai_cfg->rx_fifo_watermark);
372 	LOG_DBG("TX watermark: %d", sai_cfg->tx_fifo_watermark);
373 
374 	/* set the synchronization mode based on data passed from the DTS */
375 	tx_config->syncMode = sai_cfg->tx_sync_mode;
376 	rx_config->syncMode = sai_cfg->rx_sync_mode;
377 
378 	ret = pm_device_runtime_get(dev);
379 	if (ret < 0) {
380 		LOG_ERR("failed to get() SAI device: %d", ret);
381 		return ret;
382 	}
383 
384 	/* commit configuration */
385 	SAI_RxSetConfig(UINT_TO_I2S(data->regmap), rx_config);
386 	SAI_TxSetConfig(UINT_TO_I2S(data->regmap), tx_config);
387 
388 	/* a few notes here:
389 	 *	1) TX and RX operate in the same mode: master or slave.
390 	 *	2) Setting BCLK's rate needs to be performed explicitly
391 	 *	since SetConfig() doesn't do it for us.
392 	 *	3) Setting BCLK's rate has to be performed after the
393 	 *	SetConfig() call as that resets the SAI registers.
394 	 */
395 	if (tx_config->masterSlave == kSAI_Master) {
396 		SAI_TxSetBitClockRate(UINT_TO_I2S(data->regmap), bespoke->mclk_rate,
397 				      bespoke->fsync_rate, bespoke->tdm_slot_width,
398 				      bespoke->tdm_slots);
399 
400 		SAI_RxSetBitClockRate(UINT_TO_I2S(data->regmap), bespoke->mclk_rate,
401 				      bespoke->fsync_rate, bespoke->tdm_slot_width,
402 				      bespoke->tdm_slots);
403 	}
404 
405 #ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION
406 	ret = sai_mclk_config(dev, tx_config->bitClock.bclkSource, bespoke);
407 	if (ret < 0) {
408 		LOG_ERR("failed to set MCLK configuration");
409 		pm_device_runtime_put(dev);
410 		return ret;
411 	}
412 #endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */
413 
414 #ifdef CONFIG_SAI_IMX93_ERRATA_051421
415 	sai_config_set_err_051421(UINT_TO_I2S(data->regmap),
416 				  sai_cfg, bespoke,
417 				  rx_config, tx_config);
418 #endif /* CONFIG_SAI_IMX93_ERRATA_051421 */
419 
420 	/* this is needed so that rates different from FSYNC_RATE
421 	 * will not be allowed.
422 	 *
423 	 * this is because the hardware is configured to match
424 	 * the topology rates so attempting to play a file using
425 	 * a different rate from the one configured in the hardware
426 	 * doesn't work properly.
427 	 *
428 	 * if != 0, SOF will raise an error if the PCM rate is
429 	 * different than the hardware rate (a.k.a this one).
430 	 */
431 	data->cfg.rate = bespoke->fsync_rate;
432 	/* SOF note: we don't support a variable number of channels
433 	 * at the moment so leaving the number of channels as 0 is
434 	 * unnecessary and leads to issues (e.g: the mixer buffers
435 	 * use this value to set the number of channels so having
436 	 * a 0 as this value leads to mixer buffers having 0 channels,
437 	 * which, in turn, leads to the DAI ending up with 0 channels,
438 	 * thus resulting in an error)
439 	 */
440 	data->cfg.channels = bespoke->tdm_slots;
441 
442 	sai_dump_register_data(data->regmap);
443 
444 	return pm_device_runtime_put(dev);
445 }
446 
447 /* SOF note: please be very careful with this function as it does
448  * busy waiting and may mess up your timing in time critial applications
449  * (especially with timer domain). If this becomes unusable, the busy
450  * waiting should be removed altogether and the HW state check should
451  * be performed in sai_trigger_start() or in sai_config_set().
452  *
453  * TODO: seems like the transmitter still remains active (even if 1ms
454  * has passed after doing a sai_trigger_stop()!). Most likely this is
455  * because sai_trigger_stop() immediately stops the data line w/o
456  * checking the HW state of the transmitter/receiver. As such, to get
457  * rid of the busy waiting, the STOP operation may have to be split into
458  * 2 operations: TRIG_STOP and TRIG_POST_STOP.
459  */
sai_dir_disable(struct sai_data * data,enum dai_dir dir)460 static bool sai_dir_disable(struct sai_data *data, enum dai_dir dir)
461 {
462 	/* VERY IMPORTANT: DO NOT use SAI_TxEnable/SAI_RxEnable
463 	 * here as they do not disable the ASYNC direction.
464 	 * Since the software logic assures that the ASYNC direction
465 	 * is not disabled before the SYNC direction, we can force
466 	 * the disablement of the given direction.
467 	 */
468 	sai_tx_rx_force_disable(dir, data->regmap);
469 
470 	/* please note the difference between the transmitter/receiver's
471 	 * hardware states and their software states. The software
472 	 * states can be obtained by reading data->tx/rx_enabled, while
473 	 * the hardware states can be obtained by reading TCSR/RCSR. The
474 	 * hardware state can actually differ from the software state.
475 	 * Here, we're interested in reading the hardware state which
476 	 * indicates if the transmitter/receiver was actually disabled
477 	 * or not.
478 	 */
479 	return WAIT_FOR(!SAI_TX_RX_IS_HW_ENABLED(dir, data->regmap),
480 			SAI_TX_RX_HW_DISABLE_TIMEOUT, k_busy_wait(1));
481 }
482 
sai_tx_rx_disable(struct sai_data * data,const struct sai_config * cfg,enum dai_dir dir)483 static int sai_tx_rx_disable(struct sai_data *data,
484 			     const struct sai_config *cfg, enum dai_dir dir)
485 {
486 	enum dai_dir sync_dir, async_dir;
487 	bool ret;
488 
489 	/* sai_disable() should never be called from ISR context
490 	 * as it does some busy waiting.
491 	 */
492 	if (k_is_in_isr()) {
493 		LOG_ERR("sai_disable() should never be called from ISR context");
494 		return -EINVAL;
495 	}
496 
497 	if (cfg->tx_sync_mode == kSAI_ModeAsync &&
498 	    cfg->rx_sync_mode == kSAI_ModeAsync) {
499 		ret = sai_dir_disable(data, dir);
500 		if (!ret) {
501 			LOG_ERR("timed out while waiting for dir %d disable", dir);
502 			return -ETIMEDOUT;
503 		}
504 	} else {
505 		sync_dir = SAI_TX_RX_GET_SYNC_DIR(cfg);
506 		async_dir = SAI_TX_RX_GET_ASYNC_DIR(cfg);
507 
508 		if (dir == sync_dir) {
509 			ret = sai_dir_disable(data, sync_dir);
510 			if (!ret) {
511 				LOG_ERR("timed out while waiting for dir %d disable",
512 					sync_dir);
513 				return -ETIMEDOUT;
514 			}
515 
516 			if (!SAI_TX_RX_DIR_IS_SW_ENABLED(async_dir, data)) {
517 				ret = sai_dir_disable(data, async_dir);
518 				if (!ret) {
519 					LOG_ERR("timed out while waiting for dir %d disable",
520 						async_dir);
521 					return -ETIMEDOUT;
522 				}
523 			}
524 		} else {
525 			if (!SAI_TX_RX_DIR_IS_SW_ENABLED(sync_dir, data)) {
526 				ret = sai_dir_disable(data, async_dir);
527 				if (!ret) {
528 					LOG_ERR("timed out while waiting for dir %d disable",
529 						async_dir);
530 					return -ETIMEDOUT;
531 				}
532 			}
533 		}
534 	}
535 
536 	return 0;
537 }
538 
sai_trigger_pause(const struct device * dev,enum dai_dir dir)539 static int sai_trigger_pause(const struct device *dev,
540 			     enum dai_dir dir)
541 {
542 	struct sai_data *data;
543 	const struct sai_config *cfg;
544 	int ret;
545 
546 	data = dev->data;
547 	cfg = dev->config;
548 
549 	if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) {
550 		LOG_ERR("invalid direction: %d", dir);
551 		return -EINVAL;
552 	}
553 
554 	/* attempt to change state */
555 	ret = sai_update_state(dir, data, DAI_STATE_PAUSED);
556 	if (ret < 0) {
557 		LOG_ERR("failed to transition to PAUSED from %d. Reason: %d",
558 			sai_get_state(dir, data), ret);
559 		return ret;
560 	}
561 
562 	LOG_DBG("pause on direction %d", dir);
563 
564 	ret = sai_tx_rx_disable(data, cfg, dir);
565 	if (ret < 0) {
566 		return ret;
567 	}
568 
569 	/* disable TX/RX data line */
570 	sai_tx_rx_set_dline_mask(dir, data->regmap, 0x0);
571 
572 	/* update the software state of TX/RX */
573 	sai_tx_rx_sw_enable_disable(dir, data, false);
574 
575 	return 0;
576 }
577 
sai_trigger_stop(const struct device * dev,enum dai_dir dir)578 static int sai_trigger_stop(const struct device *dev,
579 			    enum dai_dir dir)
580 {
581 	struct sai_data *data;
582 	const struct sai_config *cfg;
583 	int ret;
584 	uint32_t old_state;
585 
586 	data = dev->data;
587 	cfg = dev->config;
588 	old_state = sai_get_state(dir, data);
589 
590 	if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) {
591 		LOG_ERR("invalid direction: %d", dir);
592 		return -EINVAL;
593 	}
594 
595 	/* attempt to change state */
596 	ret = sai_update_state(dir, data, DAI_STATE_STOPPING);
597 	if (ret < 0) {
598 		LOG_ERR("failed to transition to STOPPING from %d. Reason: %d",
599 			sai_get_state(dir, data), ret);
600 		return ret;
601 	}
602 
603 	LOG_DBG("stop on direction %d", dir);
604 
605 	if (old_state == DAI_STATE_PAUSED) {
606 		/* if SAI was previously paused then all that's
607 		 * left to do is disable the DMA requests and
608 		 * the data line.
609 		 */
610 		goto out_dmareq_disable;
611 	}
612 
613 	ret = sai_tx_rx_disable(data, cfg, dir);
614 	if (ret < 0) {
615 		return ret;
616 	}
617 
618 	/* update the software state of TX/RX */
619 	sai_tx_rx_sw_enable_disable(dir, data, false);
620 
621 	/* disable TX/RX data line */
622 	sai_tx_rx_set_dline_mask(dir, data->regmap, 0x0);
623 
624 out_dmareq_disable:
625 	/* disable DMA requests */
626 	SAI_TX_RX_DMA_ENABLE_DISABLE(dir, data->regmap, false);
627 
628 	/* disable error interrupt */
629 	SAI_TX_RX_ENABLE_DISABLE_IRQ(dir, data->regmap,
630 				     kSAI_FIFOErrorInterruptEnable, false);
631 
632 	irq_disable(cfg->irq);
633 
634 	return pm_device_runtime_put(dev);
635 }
636 
637 /* notes:
638  *	1) The "rx_sync_mode" and "tx_sync_mode" properties force the user to pick from
639  *	SYNC and ASYNC for each direction. As such, there are 4 possible combinations
640  *	that need to be covered here:
641  *		a) TX ASYNC, RX ASYNC
642  *		b) TX SYNC, RX ASYNC
643  *		c) TX ASYNC, RX SYNC
644  *		d) TX SYNC, RX SYNC
645  *
646  *	Combination d) is not valid and is covered by a BUILD_ASSERT(). As such, there are 3 valid
647  *	combinations that need to be supported. Since the main branch of the IF statement covers
648  *	combination a), there's only combinations b) and c) to be covered here.
649  *
650  *	2) We can distinguish between 3 types of directions:
651  *		a) The target direction. This is the direction on which we want to perform the
652  *		software reset.
653  *		b) The SYNC direction. This is, well, the direction that's in SYNC with the other
654  *		direction.
655  *		c) The ASYNC direction.
656  *
657  *	Of course, the target direction may differ from the SYNC or ASYNC directions, but it
658  *	can't differ from both of them at the same time (i.e: TARGET != SYNC AND TARGET != ASYNC).
659  *
660  *	If the target direction is the same as the SYNC direction then we can safely perform the
661  *	software reset on the target direction as there's nothing depending on it. We also want
662  *	to do a software reset on the ASYNC direction. We can only do this if the ASYNC direction
663  *	wasn't software enabled (i.e: through an explicit trigger_start() call).
664  *
665  *	If the target direction is the same as the ASYNC direction then we can only perform a
666  *	software reset on it only if the SYNC direction wasn't software enabled (i.e: through an
667  *	explicit trigger_start() call).
668  */
sai_tx_rx_sw_reset(struct sai_data * data,const struct sai_config * cfg,enum dai_dir dir)669 static void sai_tx_rx_sw_reset(struct sai_data *data,
670 			       const struct sai_config *cfg, enum dai_dir dir)
671 {
672 	enum dai_dir sync_dir, async_dir;
673 
674 	if (cfg->tx_sync_mode == kSAI_ModeAsync &&
675 	    cfg->rx_sync_mode == kSAI_ModeAsync) {
676 		/* both directions are ASYNC w.r.t each other. As such, do
677 		 * software reset only on the targeted direction.
678 		 */
679 		SAI_TX_RX_SW_RESET(dir, data->regmap);
680 	} else {
681 		sync_dir = SAI_TX_RX_GET_SYNC_DIR(cfg);
682 		async_dir = SAI_TX_RX_GET_ASYNC_DIR(cfg);
683 
684 		if (dir == sync_dir) {
685 			SAI_TX_RX_SW_RESET(sync_dir, data->regmap);
686 
687 			if (!SAI_TX_RX_DIR_IS_SW_ENABLED(async_dir, data)) {
688 				SAI_TX_RX_SW_RESET(async_dir, data->regmap);
689 			}
690 		} else {
691 			if (!SAI_TX_RX_DIR_IS_SW_ENABLED(sync_dir, data)) {
692 				SAI_TX_RX_SW_RESET(async_dir, data->regmap);
693 			}
694 		}
695 	}
696 }
697 
sai_trigger_start(const struct device * dev,enum dai_dir dir)698 static int sai_trigger_start(const struct device *dev,
699 			     enum dai_dir dir)
700 {
701 	struct sai_data *data;
702 	const struct sai_config *cfg;
703 	uint32_t old_state;
704 	int ret, i;
705 
706 	data = dev->data;
707 	cfg = dev->config;
708 	old_state = sai_get_state(dir, data);
709 
710 	/* TX and RX should be triggered independently */
711 	if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) {
712 		LOG_ERR("invalid direction: %d", dir);
713 		return -EINVAL;
714 	}
715 
716 	/* attempt to change state */
717 	ret = sai_update_state(dir, data, DAI_STATE_RUNNING);
718 	if (ret < 0) {
719 		LOG_ERR("failed to transition to RUNNING from %d. Reason: %d",
720 			sai_get_state(dir, data), ret);
721 		return ret;
722 	}
723 
724 	if (old_state == DAI_STATE_PAUSED) {
725 		/* if the SAI has been paused then there's no
726 		 * point in issuing a software reset. As such,
727 		 * skip this part and go directly to the TX/RX
728 		 * enablement.
729 		 */
730 		goto out_enable_dline;
731 	}
732 
733 	LOG_DBG("start on direction %d", dir);
734 
735 	ret = pm_device_runtime_get(dev);
736 	if (ret < 0) {
737 		LOG_ERR("failed to get() SAI device: %d", ret);
738 		return ret;
739 	}
740 
741 	sai_tx_rx_sw_reset(data, cfg, dir);
742 
743 	irq_enable(cfg->irq);
744 
745 	/* enable error interrupt */
746 	SAI_TX_RX_ENABLE_DISABLE_IRQ(dir, data->regmap,
747 				     kSAI_FIFOErrorInterruptEnable, true);
748 
749 	/* avoid initial underrun by writing a frame's worth of 0s */
750 	if (dir == DAI_DIR_TX) {
751 		for (i = 0; i < data->cfg.channels; i++) {
752 			SAI_WriteData(UINT_TO_I2S(data->regmap), cfg->tx_dline, 0x0);
753 		}
754 	}
755 
756 	/* TODO: for now, only DMA mode is supported */
757 	SAI_TX_RX_DMA_ENABLE_DISABLE(dir, data->regmap, true);
758 
759 out_enable_dline:
760 	/* enable TX/RX data line. This translates to TX_DLINE0/RX_DLINE0
761 	 * being enabled.
762 	 *
763 	 * TODO: for now we only support 1 data line per direction.
764 	 */
765 	sai_tx_rx_set_dline_mask(dir, data->regmap,
766 				 SAI_TX_RX_DLINE_MASK(dir, cfg));
767 
768 	/* this will also enable the async side */
769 	SAI_TX_RX_ENABLE_DISABLE(dir, data->regmap, true);
770 
771 	/* update the software state of TX/RX */
772 	sai_tx_rx_sw_enable_disable(dir, data, true);
773 
774 	return 0;
775 }
776 
sai_trigger(const struct device * dev,enum dai_dir dir,enum dai_trigger_cmd cmd)777 static int sai_trigger(const struct device *dev,
778 		       enum dai_dir dir,
779 		       enum dai_trigger_cmd cmd)
780 {
781 	switch (cmd) {
782 	case DAI_TRIGGER_START:
783 		return sai_trigger_start(dev, dir);
784 	case DAI_TRIGGER_PAUSE:
785 		return sai_trigger_pause(dev, dir);
786 	case DAI_TRIGGER_STOP:
787 		return sai_trigger_stop(dev, dir);
788 	case DAI_TRIGGER_PRE_START:
789 	case DAI_TRIGGER_COPY:
790 		/* COPY and PRE_START don't require the SAI
791 		 * driver to do anything at the moment so
792 		 * mark them as successful via a NULL return
793 		 *
794 		 * note: although the rest of the unhandled
795 		 * trigger commands may be valid, return
796 		 * an error code for them as they aren't
797 		 * implemented ATM (since they're not
798 		 * mandatory for the SAI driver to work).
799 		 */
800 		return 0;
801 	default:
802 		LOG_ERR("invalid trigger command: %d", cmd);
803 		return -EINVAL;
804 	}
805 
806 	CODE_UNREACHABLE;
807 }
808 
sai_probe(const struct device * dev)809 static int sai_probe(const struct device *dev)
810 {
811 	/* nothing to be done here but sadly mandatory to implement */
812 	return 0;
813 }
814 
sai_remove(const struct device * dev)815 static int sai_remove(const struct device *dev)
816 {
817 	/* nothing to be done here but sadly mandatory to implement */
818 	return 0;
819 }
820 
821 static DEVICE_API(dai, sai_api) = {
822 	.config_set = sai_config_set,
823 	.config_get = sai_config_get,
824 	.trigger = sai_trigger,
825 	.get_properties = sai_get_properties,
826 	.probe = sai_probe,
827 	.remove = sai_remove,
828 };
829 
sai_clks_enable_disable(const struct device * dev,bool enable)830 static int sai_clks_enable_disable(const struct device *dev, bool enable)
831 {
832 	int i, ret;
833 	const struct sai_config *cfg;
834 	void *clk_id;
835 
836 	cfg = dev->config;
837 
838 	for (i = 0; i < cfg->clk_data.clock_num; i++) {
839 		clk_id = UINT_TO_POINTER(cfg->clk_data.clocks[i]);
840 
841 		if (enable) {
842 			ret = clock_control_on(cfg->clk_data.dev, clk_id);
843 		} else {
844 			ret = clock_control_off(cfg->clk_data.dev, clk_id);
845 		}
846 
847 		if (ret < 0) {
848 			LOG_ERR("failed to gate/ungate clock %u: %d",
849 				cfg->clk_data.clocks[i], ret);
850 			return ret;
851 		}
852 	}
853 
854 	return 0;
855 }
856 
sai_pm_action(const struct device * dev,enum pm_device_action action)857 __maybe_unused static int sai_pm_action(const struct device *dev,
858 					enum pm_device_action action)
859 {
860 	bool enable = true;
861 
862 	switch (action) {
863 	case PM_DEVICE_ACTION_RESUME:
864 		break;
865 	case PM_DEVICE_ACTION_SUSPEND:
866 		enable = false;
867 		break;
868 	case PM_DEVICE_ACTION_TURN_ON:
869 	case PM_DEVICE_ACTION_TURN_OFF:
870 		return 0;
871 	default:
872 		return -ENOTSUP;
873 	}
874 
875 	return sai_clks_enable_disable(dev, enable);
876 }
877 
sai_init(const struct device * dev)878 static int sai_init(const struct device *dev)
879 {
880 	const struct sai_config *cfg;
881 	struct sai_data *data;
882 	int ret;
883 
884 	cfg = dev->config;
885 	data = dev->data;
886 
887 	device_map(&data->regmap, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE);
888 
889 #ifndef CONFIG_PM_DEVICE_RUNTIME
890 	ret = sai_clks_enable_disable(dev, true);
891 	if (ret < 0) {
892 		return ret;
893 	}
894 #endif /* CONFIG_PM_DEVICE_RUNTIME */
895 
896 	/* note: optional operation so -ENOENT is allowed (i.e: we
897 	 * allow the default state to not be defined)
898 	 */
899 	ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
900 	if (ret < 0 && ret != -ENOENT) {
901 		return ret;
902 	}
903 
904 	/* set TX/RX default states */
905 	data->tx_state = DAI_STATE_NOT_READY;
906 	data->rx_state = DAI_STATE_NOT_READY;
907 
908 	/* register ISR */
909 	cfg->irq_config();
910 
911 	return pm_device_runtime_enable(dev);
912 }
913 
914 #define SAI_INIT(inst)								\
915 										\
916 PINCTRL_DT_INST_DEFINE(inst);							\
917 										\
918 BUILD_ASSERT(SAI_FIFO_DEPTH(inst) > 0 &&					\
919 	     SAI_FIFO_DEPTH(inst) <= _SAI_FIFO_DEPTH(inst),			\
920 	     "invalid FIFO depth");						\
921 										\
922 BUILD_ASSERT(SAI_RX_FIFO_WATERMARK(inst) > 0 &&					\
923 	     SAI_RX_FIFO_WATERMARK(inst) <= _SAI_FIFO_DEPTH(inst),		\
924 	     "invalid RX FIFO watermark");					\
925 										\
926 BUILD_ASSERT(SAI_TX_FIFO_WATERMARK(inst) > 0 &&					\
927 	     SAI_TX_FIFO_WATERMARK(inst) <= _SAI_FIFO_DEPTH(inst),		\
928 	     "invalid TX FIFO watermark");					\
929 										\
930 BUILD_ASSERT(IS_ENABLED(CONFIG_SAI_HAS_MCLK_CONFIG_OPTION) ||			\
931 	     !DT_INST_PROP(inst, mclk_is_output),				\
932 	     "SAI doesn't support MCLK config but mclk_is_output is specified");\
933 										\
934 BUILD_ASSERT(SAI_TX_SYNC_MODE(inst) != SAI_RX_SYNC_MODE(inst) ||		\
935 	     SAI_TX_SYNC_MODE(inst) != kSAI_ModeSync,				\
936 	     "transmitter and receiver can't be both SYNC with each other");	\
937 										\
938 BUILD_ASSERT(SAI_DLINE_COUNT(inst) != -1,					\
939 	     "bad or unsupported SAI instance. Is the base address correct?");	\
940 										\
941 BUILD_ASSERT(SAI_TX_DLINE_INDEX(inst) >= 0 &&					\
942 	     (SAI_TX_DLINE_INDEX(inst) < SAI_DLINE_COUNT(inst)),		\
943 	     "invalid TX data line index");					\
944 										\
945 BUILD_ASSERT(SAI_RX_DLINE_INDEX(inst) >= 0 &&					\
946 	     (SAI_RX_DLINE_INDEX(inst) < SAI_DLINE_COUNT(inst)),		\
947 	     "invalid RX data line index");					\
948 										\
949 static const struct dai_properties sai_tx_props_##inst = {			\
950 	.fifo_address = SAI_TX_FIFO_BASE(inst, SAI_TX_DLINE_INDEX(inst)),	\
951 	.fifo_depth = SAI_FIFO_DEPTH(inst) * CONFIG_SAI_FIFO_WORD_SIZE,		\
952 	.dma_hs_id = SAI_TX_RX_DMA_HANDSHAKE(inst, tx),				\
953 };										\
954 										\
955 static const struct dai_properties sai_rx_props_##inst = {			\
956 	.fifo_address = SAI_RX_FIFO_BASE(inst, SAI_RX_DLINE_INDEX(inst)),	\
957 	.fifo_depth = SAI_FIFO_DEPTH(inst) * CONFIG_SAI_FIFO_WORD_SIZE,		\
958 	.dma_hs_id = SAI_TX_RX_DMA_HANDSHAKE(inst, rx),				\
959 };										\
960 										\
961 void irq_config_##inst(void)							\
962 {										\
963 	IRQ_CONNECT(DT_INST_IRQN(inst),						\
964 		    0,								\
965 		    sai_isr,							\
966 		    DEVICE_DT_INST_GET(inst),					\
967 		    0);								\
968 }										\
969 										\
970 static struct sai_config sai_config_##inst = {					\
971 	.regmap_phys = DT_INST_REG_ADDR(inst),					\
972 	.regmap_size = DT_INST_REG_SIZE(inst),					\
973 	.irq = DT_INST_IRQN(inst),						\
974 	.clk_data = SAI_CLOCK_DATA_DECLARE(inst),				\
975 	.rx_fifo_watermark = SAI_RX_FIFO_WATERMARK(inst),			\
976 	.tx_fifo_watermark = SAI_TX_FIFO_WATERMARK(inst),			\
977 	.mclk_is_output = DT_INST_PROP(inst, mclk_is_output),			\
978 	.tx_props = &sai_tx_props_##inst,					\
979 	.rx_props = &sai_rx_props_##inst,					\
980 	.irq_config = irq_config_##inst,					\
981 	.tx_sync_mode = SAI_TX_SYNC_MODE(inst),					\
982 	.rx_sync_mode = SAI_RX_SYNC_MODE(inst),					\
983 	.tx_dline = SAI_TX_DLINE_INDEX(inst),					\
984 	.rx_dline = SAI_RX_DLINE_INDEX(inst),					\
985 	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),				\
986 };										\
987 										\
988 static struct sai_data sai_data_##inst = {					\
989 	.cfg.type = DAI_IMX_SAI,						\
990 	.cfg.dai_index = DT_INST_PROP_OR(inst, dai_index, 0),			\
991 };										\
992 										\
993 PM_DEVICE_DT_INST_DEFINE(inst, sai_pm_action);					\
994 										\
995 DEVICE_DT_INST_DEFINE(inst, &sai_init, PM_DEVICE_DT_INST_GET(inst),		\
996 		      &sai_data_##inst, &sai_config_##inst,			\
997 		      POST_KERNEL, CONFIG_DAI_INIT_PRIORITY,			\
998 		      &sai_api);						\
999 
1000 DT_INST_FOREACH_STATUS_OKAY(SAI_INIT);
1001