diff --git a/drivers/clock_control/clock_control_mcux_ccm_rev2.c b/drivers/clock_control/clock_control_mcux_ccm_rev2.c index f7ef8b80b594..8edb33e3798c 100644 --- a/drivers/clock_control/clock_control_mcux_ccm_rev2.c +++ b/drivers/clock_control/clock_control_mcux_ccm_rev2.c @@ -115,6 +115,24 @@ static int mcux_ccm_get_subsys_rate(const struct device *dev, break; #endif +#if defined(CONFIG_SOC_MIMX93_A55) && defined(CONFIG_DAI_NXP_SAI) + case IMX_CCM_SAI1_CLK: + case IMX_CCM_SAI2_CLK: + case IMX_CCM_SAI3_CLK: + clock_root = kCLOCK_Root_Sai1 + instance; + uint32_t mux = CLOCK_GetRootClockMux(clock_root); + uint32_t divider = CLOCK_GetRootClockDiv(clock_root); + + /* assumption: SAI's SRC is AUDIO_PLL */ + if (mux != 1) { + return -EINVAL; + } + + /* assumption: AUDIO_PLL's frequency is 393216000 Hz */ + *rate = 393216000 / divider; + + return 0; +#endif default: return -EINVAL; } diff --git a/drivers/dai/CMakeLists.txt b/drivers/dai/CMakeLists.txt index 90b5d8b931cc..b6bd04939d52 100644 --- a/drivers/dai/CMakeLists.txt +++ b/drivers/dai/CMakeLists.txt @@ -4,3 +4,4 @@ add_subdirectory_ifdef(CONFIG_DAI_INTEL_SSP intel/ssp) add_subdirectory_ifdef(CONFIG_DAI_INTEL_ALH intel/alh) add_subdirectory_ifdef(CONFIG_DAI_INTEL_DMIC intel/dmic) add_subdirectory_ifdef(CONFIG_DAI_INTEL_HDA intel/hda) +add_subdirectory_ifdef(CONFIG_DAI_NXP_SAI nxp/sai) diff --git a/drivers/dai/Kconfig b/drivers/dai/Kconfig index 770b528da6a3..da882a99fa00 100644 --- a/drivers/dai/Kconfig +++ b/drivers/dai/Kconfig @@ -29,5 +29,6 @@ source "drivers/dai/intel/ssp/Kconfig.ssp" source "drivers/dai/intel/alh/Kconfig.alh" source "drivers/dai/intel/dmic/Kconfig.dmic" source "drivers/dai/intel/hda/Kconfig.hda" +source "drivers/dai/nxp/sai/Kconfig.sai" endif # DAI diff --git a/drivers/dai/nxp/sai/CMakeLists.txt b/drivers/dai/nxp/sai/CMakeLists.txt new file mode 100644 index 000000000000..755261307a6a --- /dev/null +++ b/drivers/dai/nxp/sai/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(sai.c) diff --git a/drivers/dai/nxp/sai/Kconfig.sai b/drivers/dai/nxp/sai/Kconfig.sai new file mode 100644 index 000000000000..e123c955f6bc --- /dev/null +++ b/drivers/dai/nxp/sai/Kconfig.sai @@ -0,0 +1,30 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +config DAI_NXP_SAI + bool "NXP Synchronous Audio Interface (SAI) driver" + default y + depends on DT_HAS_NXP_DAI_SAI_ENABLED + help + Select this to enable NXP SAI driver. + +if DAI_NXP_SAI + +config SAI_HAS_MCLK_CONFIG_OPTION + bool "Set if SAI has MCLK configuration options" + default n + help + Select this if the SAI IP allows configuration + of the master clock. Master clock configuration + refers to enabling/disabling the master clock, + setting the signal as input or output or dividing + the master clock output. + +config SAI_FIFO_WORD_SIZE + int "Size (in bytes) of a FIFO word" + default 4 + help + Use this to set the size (in bytes) of a SAI + FIFO word. + +endif # DAI_NXP_SAI diff --git a/drivers/dai/nxp/sai/sai.c b/drivers/dai/nxp/sai/sai.c new file mode 100644 index 000000000000..000c754f8b68 --- /dev/null +++ b/drivers/dai/nxp/sai/sai.c @@ -0,0 +1,758 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "sai.h" + +/* used for binding the driver */ +#define DT_DRV_COMPAT nxp_dai_sai + +#define SAI_TX_RX_HW_DISABLE_TIMEOUT 50 + +/* TODO list: + * + * 1) No busy waiting should be performed in any of the operations. + * In the case of STOP(), the operation should be split into TRIGGER_STOP + * and TRIGGER_POST_STOP. (SOF) + * + * 2) The SAI ISR should stop the SAI whenever a FIFO error interrupt + * is raised. + * + * 3) Transmitter/receiver may remain enabled after sai_tx_rx_disable(). + * Fix this. + */ + +#ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION +/* note: i.MX8 boards don't seem to support the MICS field in the MCR + * register. As such, the MCLK source field of sai_master_clock_t is + * useless. I'm assuming the source is selected through xCR2's MSEL. + * + * TODO: for now, this function will set MCR's MSEL to the same value + * as xCR2's MSEL or, rather, to the same MCLK as the one used for + * generating BCLK. Is there a need to support different MCLKs in + * xCR2 and MCR? + */ +static int sai_mclk_config(const struct device *dev, + sai_bclk_source_t bclk_source, + const struct sai_bespoke_config *bespoke) +{ + const struct sai_config *cfg; + struct sai_data *data; + sai_master_clock_t mclk_config; + uint32_t msel, mclk_rate; + int ret; + + cfg = dev->config; + data = dev->data; + + mclk_config.mclkOutputEnable = cfg->mclk_is_output; + + ret = get_msel(bclk_source, &msel); + if (ret < 0) { + LOG_ERR("invalid MCLK source %d for MSEL", bclk_source); + return ret; + } + + /* get MCLK's rate */ + ret = get_mclk_rate(&cfg->clk_data, bclk_source, &mclk_rate); + if (ret < 0) { + LOG_ERR("failed to query MCLK's rate"); + return ret; + } + + LOG_DBG("source MCLK is %u", mclk_rate); + + LOG_DBG("target MCLK is %u", bespoke->mclk_rate); + + /* source MCLK rate */ + mclk_config.mclkSourceClkHz = mclk_rate; + + /* target MCLK rate */ + mclk_config.mclkHz = bespoke->mclk_rate; + + /* commit configuration */ + SAI_SetMasterClockConfig(UINT_TO_I2S(data->regmap), &mclk_config); + + set_msel(data->regmap, msel); + + return 0; +} +#endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */ + +void sai_isr(const void *parameter) +{ + const struct device *dev; + struct sai_data *data; + + dev = parameter; + data = dev->data; + + /* check for TX FIFO error */ + if (SAI_TX_RX_STATUS_IS_SET(DAI_DIR_TX, data->regmap, kSAI_FIFOErrorFlag)) { + LOG_ERR("FIFO underrun detected"); + /* TODO: this will crash the program and should be addressed as + * mentioned in TODO list's 2). + */ + z_irq_spurious(NULL); + } + + /* check for RX FIFO error */ + if (SAI_TX_RX_STATUS_IS_SET(DAI_DIR_RX, data->regmap, kSAI_FIFOErrorFlag)) { + LOG_ERR("FIFO overrun detected"); + /* TODO: this will crash the program and should be addressed as + * mentioned in TODO list's 2). + */ + z_irq_spurious(NULL); + } +} + +static int sai_config_get(const struct device *dev, + struct dai_config *cfg, + enum dai_dir dir) +{ + struct sai_data *data = dev->data; + + /* dump content of the DAI configuration */ + memcpy(cfg, &data->cfg, sizeof(*cfg)); + + return 0; +} + +static const struct dai_properties + *sai_get_properties(const struct device *dev, enum dai_dir dir, int stream_id) +{ + const struct sai_config *cfg = dev->config; + + switch (dir) { + case DAI_DIR_RX: + return cfg->rx_props; + case DAI_DIR_TX: + return cfg->tx_props; + default: + LOG_ERR("invalid direction: %d", dir); + return NULL; + } + + CODE_UNREACHABLE; +} + +static int sai_config_set(const struct device *dev, + const struct dai_config *cfg, + const void *bespoke_data) +{ + const struct sai_bespoke_config *bespoke; + sai_transceiver_t *rx_config, *tx_config; + struct sai_data *data; + const struct sai_config *sai_cfg; + int ret; + + if (cfg->type != DAI_IMX_SAI) { + LOG_ERR("wrong DAI type: %d", cfg->type); + return -EINVAL; + } + + bespoke = bespoke_data; + data = dev->data; + sai_cfg = dev->config; + rx_config = &data->rx_config; + tx_config = &data->tx_config; + + /* since this function configures the transmitter AND the receiver, that + * means both of them need to be stopped. As such, doing the state + * transition here will also result in a state check. + */ + ret = sai_update_state(DAI_DIR_TX, data, DAI_STATE_READY); + if (ret < 0) { + LOG_ERR("failed to update TX state. Reason: %d", ret); + return ret; + } + + ret = sai_update_state(DAI_DIR_RX, data, DAI_STATE_READY); + if (ret < 0) { + LOG_ERR("failed to update RX state. Reason: %d", ret); + return ret; + } + + /* condition: BCLK = FSYNC * TDM_SLOT_WIDTH * TDM_SLOTS */ + if (bespoke->bclk_rate != + (bespoke->fsync_rate * bespoke->tdm_slot_width * bespoke->tdm_slots)) { + LOG_ERR("bad BCLK value: %d", bespoke->bclk_rate); + return -EINVAL; + } + + /* TODO: this should be removed if we're to support sw channels != hw channels */ + if (count_leading_zeros(~bespoke->tx_slots) != bespoke->tdm_slots || + count_leading_zeros(~bespoke->rx_slots) != bespoke->tdm_slots) { + LOG_ERR("number of TX/RX slots doesn't match number of TDM slots"); + return -EINVAL; + } + + /* get default configurations */ + get_bclk_default_config(&tx_config->bitClock); + get_fsync_default_config(&tx_config->frameSync); + get_serial_default_config(&tx_config->serialData); + get_fifo_default_config(&tx_config->fifo); + + /* note1: this may be obvious but enabling multiple SAI + * channels (or data lines) may lead to FIFO starvation/ + * overflow if data is not written/read from the respective + * TDR/RDR registers. + * + * note2: the SAI data line should be enabled based on + * the direction (TX/RX) we're enabling. Enabling the + * data line for the opposite direction will lead to FIFO + * overrun/underrun when working with a SYNC direction. + * + * note3: the TX/RX data line shall be enabled/disabled + * via the sai_trigger_() suite to avoid scenarios in + * which one configures both direction but only starts + * the SYNC direction which would lead to a FIFO underrun. + */ + tx_config->channelMask = 0x0; + + /* TODO: for now, only MCLK1 is supported */ + tx_config->bitClock.bclkSource = kSAI_BclkSourceMclkOption1; + + /* FSYNC is asserted for tdm_slot_width BCLKs */ + tx_config->frameSync.frameSyncWidth = bespoke->tdm_slot_width; + + /* serial data common configuration */ + tx_config->serialData.dataWord0Length = bespoke->tdm_slot_width; + tx_config->serialData.dataWordNLength = bespoke->tdm_slot_width; + tx_config->serialData.dataFirstBitShifted = bespoke->tdm_slot_width; + tx_config->serialData.dataWordNum = bespoke->tdm_slots; + + /* clock provider configuration */ + switch (cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK) { + case DAI_CBP_CFP: + tx_config->masterSlave = kSAI_Slave; + break; + case DAI_CBC_CFC: + tx_config->masterSlave = kSAI_Master; + break; + case DAI_CBC_CFP: + case DAI_CBP_CFC: + LOG_ERR("unsupported provider configuration: %d", + cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK); + return -ENOTSUP; + default: + LOG_ERR("invalid provider configuration: %d", + cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK); + return -EINVAL; + } + + LOG_DBG("SAI is in %d mode", tx_config->masterSlave); + + /* protocol configuration */ + switch (cfg->format & DAI_FORMAT_PROTOCOL_MASK) { + case DAI_PROTO_I2S: + /* BCLK is active LOW */ + tx_config->bitClock.bclkPolarity = kSAI_PolarityActiveLow; + /* FSYNC is active LOW */ + tx_config->frameSync.frameSyncPolarity = kSAI_PolarityActiveLow; + break; + case DAI_PROTO_DSP_A: + /* FSYNC is asserted for a single BCLK */ + tx_config->frameSync.frameSyncWidth = 1; + /* BCLK is active LOW */ + tx_config->bitClock.bclkPolarity = kSAI_PolarityActiveLow; + break; + default: + LOG_ERR("unsupported DAI protocol: %d", + cfg->format & DAI_FORMAT_PROTOCOL_MASK); + return -EINVAL; + } + + LOG_DBG("SAI uses protocol: %d", + cfg->format & DAI_FORMAT_PROTOCOL_MASK); + + /* clock inversion configuration */ + switch (cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK) { + case DAI_INVERSION_IB_IF: + SAI_INVERT_POLARITY(tx_config->bitClock.bclkPolarity); + SAI_INVERT_POLARITY(tx_config->frameSync.frameSyncPolarity); + break; + case DAI_INVERSION_IB_NF: + SAI_INVERT_POLARITY(tx_config->bitClock.bclkPolarity); + break; + case DAI_INVERSION_NB_IF: + SAI_INVERT_POLARITY(tx_config->frameSync.frameSyncPolarity); + break; + case DAI_INVERSION_NB_NF: + /* nothing to do here */ + break; + default: + LOG_ERR("invalid clock inversion configuration: %d", + cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK); + return -EINVAL; + } + + LOG_DBG("FSYNC polarity: %d", tx_config->frameSync.frameSyncPolarity); + LOG_DBG("BCLK polarity: %d", tx_config->bitClock.bclkPolarity); + + /* duplicate TX configuration */ + memcpy(rx_config, tx_config, sizeof(sai_transceiver_t)); + + tx_config->serialData.dataMaskedWord = ~bespoke->tx_slots; + rx_config->serialData.dataMaskedWord = ~bespoke->rx_slots; + + tx_config->fifo.fifoWatermark = sai_cfg->tx_fifo_watermark - 1; + rx_config->fifo.fifoWatermark = sai_cfg->rx_fifo_watermark - 1; + + LOG_DBG("RX watermark: %d", sai_cfg->rx_fifo_watermark); + LOG_DBG("TX watermark: %d", sai_cfg->tx_fifo_watermark); + + /* TODO: for now, the only supported operation mode is RX sync with TX. + * Is there a need to support other modes? + */ + tx_config->syncMode = kSAI_ModeAsync; + rx_config->syncMode = kSAI_ModeSync; + + /* commit configuration */ + SAI_RxSetConfig(UINT_TO_I2S(data->regmap), rx_config); + SAI_TxSetConfig(UINT_TO_I2S(data->regmap), tx_config); + + /* a few notes here: + * 1) TX and RX operate in the same mode: master or slave. + * 2) Setting BCLK's rate needs to be performed explicitly + * since SetConfig() doesn't do it for us. + * 3) Setting BCLK's rate has to be performed after the + * SetConfig() call as that resets the SAI registers. + */ + if (tx_config->masterSlave == kSAI_Master) { + SAI_TxSetBitClockRate(UINT_TO_I2S(data->regmap), bespoke->mclk_rate, + bespoke->fsync_rate, bespoke->tdm_slot_width, + bespoke->tdm_slots); + + SAI_RxSetBitClockRate(UINT_TO_I2S(data->regmap), bespoke->mclk_rate, + bespoke->fsync_rate, bespoke->tdm_slot_width, + bespoke->tdm_slots); + } + +#ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION + ret = sai_mclk_config(dev, tx_config->bitClock.bclkSource, bespoke); + if (ret < 0) { + LOG_ERR("failed to set MCLK configuration"); + return ret; + } +#endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */ + + /* this is needed so that rates different from FSYNC_RATE + * will not be allowed. + * + * this is because the hardware is configured to match + * the topology rates so attempting to play a file using + * a different rate from the one configured in the hardware + * doesn't work properly. + * + * if != 0, SOF will raise an error if the PCM rate is + * different than the hardware rate (a.k.a this one). + */ + data->cfg.rate = bespoke->fsync_rate; + /* SOF note: we don't support a variable number of channels + * at the moment so leaving the number of channels as 0 is + * unnecessary and leads to issues (e.g: the mixer buffers + * use this value to set the number of channels so having + * a 0 as this value leads to mixer buffers having 0 channels, + * which, in turn, leads to the DAI ending up with 0 channels, + * thus resulting in an error) + */ + data->cfg.channels = bespoke->tdm_slots; + + sai_dump_register_data(data->regmap); + + return 0; +} + +/* SOF note: please be very careful with this function as it does + * busy waiting and may mess up your timing in time critial applications + * (especially with timer domain). If this becomes unusable, the busy + * waiting should be removed altogether and the HW state check should + * be performed in sai_trigger_start() or in sai_config_set(). + * + * TODO: seems like the transmitter still remains active (even if 1ms + * has passed after doing a sai_trigger_stop()!). Most likely this is + * because sai_trigger_stop() immediately stops the data line w/o + * checking the HW state of the transmitter/receiver. As such, to get + * rid of the busy waiting, the STOP operation may have to be split into + * 2 operations: TRIG_STOP and TRIG_POST_STOP. + */ +static int sai_tx_rx_disable(struct sai_data *data, enum dai_dir dir) +{ + bool ret; + + /* sai_disable() should never be called from ISR context + * as it does some busy waiting. + */ + if (k_is_in_isr()) { + LOG_ERR("sai_disable() should never be called from ISR context"); + return -EINVAL; + } + + if ((dir == DAI_DIR_TX && !data->rx_enabled) || dir == DAI_DIR_RX) { + /* VERY IMPORTANT: DO NOT use SAI_TxEnable/SAI_RxEnable + * here as they do not disable the ASYNC direction. + * Since the software logic assures that the ASYNC direction + * is not disabled before the SYNC direction, we can force + * the disablement of the given direction. + */ + sai_tx_rx_force_disable(dir, data->regmap); + + /* please note the difference between the transmitter/receiver's + * hardware states and their software states. The software + * states can be obtained by reading data->tx/rx_enabled, while + * the hardware states can be obtained by reading TCSR/RCSR. The + * hadrware state can actually differ from the software state. + * Here, we're interested in reading the hardware state which + * indicates if the transmitter/receiver was actually disabled + * or not. + */ + ret = WAIT_FOR(!SAI_TX_RX_IS_HW_ENABLED(dir, data->regmap), + SAI_TX_RX_HW_DISABLE_TIMEOUT, k_busy_wait(1)); + if (!ret) { + LOG_ERR("timed out while waiting for dir %d disable", dir); + return -ETIMEDOUT; + } + } + + /* if TX wasn't explicitly enabled (via sai_trigger_start(TX)) + * then that means it was enabled by a sai_trigger_start(RX). As + * such, data->tx_enabled will be false. + */ + if (dir == DAI_DIR_RX && !data->tx_enabled) { + sai_tx_rx_force_disable(DAI_DIR_TX, data->regmap); + + ret = WAIT_FOR(!SAI_TX_RX_IS_HW_ENABLED(DAI_DIR_TX, data->regmap), + SAI_TX_RX_HW_DISABLE_TIMEOUT, k_busy_wait(1)); + if (!ret) { + LOG_ERR("timed out while waiting for dir TX disable"); + return -ETIMEDOUT; + } + } + + return 0; +} + +static int sai_trigger_pause(const struct device *dev, + enum dai_dir dir) +{ + struct sai_data *data; + int ret; + + data = dev->data; + + if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) { + LOG_ERR("invalid direction: %d", dir); + return -EINVAL; + } + + /* attempt to change state */ + ret = sai_update_state(dir, data, DAI_STATE_PAUSED); + if (ret < 0) { + LOG_ERR("failed to transition to PAUSED from %d. Reason: %d", + sai_get_state(dir, data), ret); + return ret; + } + + LOG_DBG("pause on direction %d", dir); + + ret = sai_tx_rx_disable(data, dir); + if (ret < 0) { + return ret; + } + + /* update the software state of TX/RX */ + sai_tx_rx_sw_enable_disable(dir, data, false); + + return 0; +} + +static int sai_trigger_stop(const struct device *dev, + enum dai_dir dir) +{ + struct sai_data *data; + int ret; + uint32_t old_state; + + data = dev->data; + old_state = sai_get_state(dir, data); + + if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) { + LOG_ERR("invalid direction: %d", dir); + return -EINVAL; + } + + /* attempt to change state */ + ret = sai_update_state(dir, data, DAI_STATE_STOPPING); + if (ret < 0) { + LOG_ERR("failed to transition to STOPPING from %d. Reason: %d", + sai_get_state(dir, data), ret); + return ret; + } + + LOG_DBG("stop on direction %d", dir); + + if (old_state == DAI_STATE_PAUSED) { + /* if SAI was previously paused then all that's + * left to do is disable the DMA requests and + * the data line. + */ + goto out_dline_disable; + } + + ret = sai_tx_rx_disable(data, dir); + if (ret < 0) { + return ret; + } + + /* update the software state of TX/RX */ + sai_tx_rx_sw_enable_disable(dir, data, false); + +out_dline_disable: + /* disable TX/RX data line */ + sai_tx_rx_set_dline_mask(dir, data->regmap, 0x0); + + /* disable DMA requests */ + SAI_TX_RX_DMA_ENABLE_DISABLE(dir, data->regmap, false); + + /* disable error interrupt */ + SAI_TX_RX_ENABLE_DISABLE_IRQ(dir, data->regmap, + kSAI_FIFOErrorInterruptEnable, false); + + return 0; +} + +static int sai_trigger_start(const struct device *dev, + enum dai_dir dir) +{ + struct sai_data *data; + uint32_t old_state; + int ret; + + data = dev->data; + old_state = sai_get_state(dir, data); + + /* TX and RX should be triggered independently */ + if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) { + LOG_ERR("invalid direction: %d", dir); + return -EINVAL; + } + + /* attempt to change state */ + ret = sai_update_state(dir, data, DAI_STATE_RUNNING); + if (ret < 0) { + LOG_ERR("failed to transition to RUNNING from %d. Reason: %d", + sai_get_state(dir, data), ret); + return ret; + } + + if (old_state == DAI_STATE_PAUSED) { + /* if the SAI has been paused then there's no + * point in issuing a software reset. As such, + * skip this part and go directly to the TX/RX + * enablement. + */ + goto out_enable_tx_rx; + } + + LOG_DBG("start on direction %d", dir); + + if (dir == DAI_DIR_RX) { + /* this is fine because TX is async so it won't be + * affected by an RX software reset. + */ + SAI_TX_RX_SW_RESET(dir, data->regmap); + + /* do a TX software reset only if not already enabled */ + if (!data->tx_enabled) { + SAI_TX_RX_SW_RESET(DAI_DIR_TX, data->regmap); + } + } else { + /* a software reset should be issued for TX + * only if RX was not already enabled. + */ + if (!data->rx_enabled) { + SAI_TX_RX_SW_RESET(dir, data->regmap); + } + } + + /* enable error interrupt */ + SAI_TX_RX_ENABLE_DISABLE_IRQ(dir, data->regmap, + kSAI_FIFOErrorInterruptEnable, true); + + /* TODO: is there a need to write some words to the FIFO to avoid starvation? */ + + /* TODO: for now, only DMA mode is supported */ + SAI_TX_RX_DMA_ENABLE_DISABLE(dir, data->regmap, true); + + /* enable TX/RX data line. This translates to TX_DLINE0/RX_DLINE0 + * being enabled. + * + * TODO: for now we only support 1 data line per direction. + */ + sai_tx_rx_set_dline_mask(dir, data->regmap, 0x1); + +out_enable_tx_rx: + /* this will also enable the async side */ + SAI_TX_RX_ENABLE_DISABLE(dir, data->regmap, true); + + /* update the software state of TX/RX */ + sai_tx_rx_sw_enable_disable(dir, data, true); + + return 0; +} + +static int sai_trigger(const struct device *dev, + enum dai_dir dir, + enum dai_trigger_cmd cmd) +{ + switch (cmd) { + case DAI_TRIGGER_START: + return sai_trigger_start(dev, dir); + case DAI_TRIGGER_PAUSE: + return sai_trigger_pause(dev, dir); + case DAI_TRIGGER_STOP: + return sai_trigger_stop(dev, dir); + case DAI_TRIGGER_PRE_START: + case DAI_TRIGGER_COPY: + /* COPY and PRE_START don't require the SAI + * driver to do anything at the moment so + * mark them as successful via a NULL return + * + * note: although the rest of the unhandled + * trigger commands may be valid, return + * an error code for them as they aren't + * implemented ATM (since they're not + * mandatory for the SAI driver to work). + */ + return 0; + default: + LOG_ERR("invalid trigger command: %d", cmd); + return -EINVAL; + } + + CODE_UNREACHABLE; +} + +static int sai_probe(const struct device *dev) +{ + /* nothing to be done here but sadly mandatory to implement */ + return 0; +} + +static int sai_remove(const struct device *dev) +{ + /* nothing to be done here but sadly mandatory to implement */ + return 0; +} + +static const struct dai_driver_api sai_api = { + .config_set = sai_config_set, + .config_get = sai_config_get, + .trigger = sai_trigger, + .get_properties = sai_get_properties, + .probe = sai_probe, + .remove = sai_remove, +}; + +static int sai_init(const struct device *dev) +{ + const struct sai_config *cfg; + struct sai_data *data; + int i, ret; + + cfg = dev->config; + data = dev->data; + + device_map(&data->regmap, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE); + + /* enable clocks if any */ + for (i = 0; i < cfg->clk_data.clock_num; i++) { + ret = clock_control_on(cfg->clk_data.dev, + UINT_TO_POINTER(cfg->clk_data.clocks[i])); + if (ret < 0) { + return ret; + } + + LOG_DBG("clock %s has been ungated", cfg->clk_data.clock_names[i]); + } + + /* set TX/RX default states */ + data->tx_state = DAI_STATE_NOT_READY; + data->rx_state = DAI_STATE_NOT_READY; + + /* register ISR and enable IRQ */ + cfg->irq_config(); + + return 0; +} + +#define SAI_INIT(inst) \ + \ +BUILD_ASSERT(SAI_FIFO_DEPTH(inst) > 0 && \ + SAI_FIFO_DEPTH(inst) <= _SAI_FIFO_DEPTH(inst), \ + "invalid FIFO depth"); \ + \ +BUILD_ASSERT(SAI_RX_FIFO_WATERMARK(inst) > 0 && \ + SAI_RX_FIFO_WATERMARK(inst) <= _SAI_FIFO_DEPTH(inst), \ + "invalid RX FIFO watermark"); \ + \ +BUILD_ASSERT(SAI_TX_FIFO_WATERMARK(inst) > 0 && \ + SAI_TX_FIFO_WATERMARK(inst) <= _SAI_FIFO_DEPTH(inst), \ + "invalid TX FIFO watermark"); \ + \ +BUILD_ASSERT(IS_ENABLED(CONFIG_SAI_HAS_MCLK_CONFIG_OPTION) || \ + !DT_INST_PROP(inst, mclk_is_output), \ + "SAI doesn't support MCLK config but mclk_is_output is specified");\ + \ +static const struct dai_properties sai_tx_props_##inst = { \ + .fifo_address = SAI_TX_FIFO_BASE(inst), \ + .fifo_depth = SAI_FIFO_DEPTH(inst) * CONFIG_SAI_FIFO_WORD_SIZE, \ + .dma_hs_id = SAI_TX_DMA_MUX(inst), \ +}; \ + \ +static const struct dai_properties sai_rx_props_##inst = { \ + .fifo_address = SAI_RX_FIFO_BASE(inst), \ + .fifo_depth = SAI_FIFO_DEPTH(inst) * CONFIG_SAI_FIFO_WORD_SIZE, \ + .dma_hs_id = SAI_RX_DMA_MUX(inst), \ +}; \ + \ +void irq_config_##inst(void) \ +{ \ + IRQ_CONNECT(DT_INST_IRQN(inst), \ + 0, \ + sai_isr, \ + DEVICE_DT_INST_GET(inst), \ + 0); \ + irq_enable(DT_INST_IRQN(inst)); \ +} \ + \ +static struct sai_config sai_config_##inst = { \ + .regmap_phys = DT_INST_REG_ADDR(inst), \ + .regmap_size = DT_INST_REG_SIZE(inst), \ + .clk_data = SAI_CLOCK_DATA_DECLARE(inst), \ + .rx_fifo_watermark = SAI_RX_FIFO_WATERMARK(inst), \ + .tx_fifo_watermark = SAI_TX_FIFO_WATERMARK(inst), \ + .mclk_is_output = DT_INST_PROP_OR(inst, mclk_is_output, false), \ + .tx_props = &sai_tx_props_##inst, \ + .rx_props = &sai_rx_props_##inst, \ + .irq_config = irq_config_##inst, \ +}; \ + \ +static struct sai_data sai_data_##inst = { \ + .cfg.type = DAI_IMX_SAI, \ + .cfg.dai_index = DT_INST_PROP_OR(inst, dai_index, 0), \ +}; \ + \ +DEVICE_DT_INST_DEFINE(inst, &sai_init, NULL, \ + &sai_data_##inst, &sai_config_##inst, \ + POST_KERNEL, CONFIG_DAI_INIT_PRIORITY, \ + &sai_api); \ + +DT_INST_FOREACH_STATUS_OKAY(SAI_INIT); diff --git a/drivers/dai/nxp/sai/sai.h b/drivers/dai/nxp/sai/sai.h new file mode 100644 index 000000000000..33200d5a8fea --- /dev/null +++ b/drivers/dai/nxp/sai/sai.h @@ -0,0 +1,511 @@ +/* Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_DAI_NXP_SAI_H_ +#define ZEPHYR_DRIVERS_DAI_NXP_SAI_H_ + +#include +#include +#include + +LOG_MODULE_REGISTER(nxp_dai_sai); + +#ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION +#define SAI_MCLK_MCR_MSEL_SHIFT 24 +#define SAI_MCLK_MCR_MSEL_MASK GENMASK(24, 25) +#endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */ +/* workaround the fact that device_map() doesn't exist for SoCs with no MMU */ +#ifndef DEVICE_MMIO_IS_IN_RAM +#define device_map(virt, phys, size, flags) *(virt) = (phys) +#endif /* DEVICE_MMIO_IS_IN_RAM */ + +/* used to convert an uint to I2S_Type * */ +#define UINT_TO_I2S(x) ((I2S_Type *)(uintptr_t)(x)) + +/* macros used for parsing DTS data */ + +/* used instead of IDENTITY because LISTIFY expects the used macro function + * to also take a variable number of arguments. + */ +#define IDENTITY_VARGS(V, ...) IDENTITY(V) + +/* used to generate the list of clock indexes */ +#define _SAI_CLOCK_INDEX_ARRAY(inst)\ + LISTIFY(DT_INST_PROP_LEN_OR(inst, clocks, 0), IDENTITY_VARGS, (,)) + +/* used to retrieve a clock's ID using its index generated via _SAI_CLOCK_INDEX_ARRAY */ +#define _SAI_GET_CLOCK_ID(clock_idx, inst)\ + DT_INST_CLOCKS_CELL_BY_IDX(inst, clock_idx, name) + +/* used to retrieve a clock's name using its index generated via _SAI_CLOCK_INDEX_ARRAY */ +#define _SAI_GET_CLOCK_NAME(clock_idx, inst)\ + DT_INST_PROP_BY_IDX(inst, clock_names, clock_idx) + +/* used to convert the clocks property into an array of clock IDs */ +#define _SAI_CLOCK_ID_ARRAY(inst)\ + FOR_EACH_FIXED_ARG(_SAI_GET_CLOCK_ID, (,), inst, _SAI_CLOCK_INDEX_ARRAY(inst)) + +/* used to convert the clock-names property into an array of clock names */ +#define _SAI_CLOCK_NAME_ARRAY(inst)\ + FOR_EACH_FIXED_ARG(_SAI_GET_CLOCK_NAME, (,), inst, _SAI_CLOCK_INDEX_ARRAY(inst)) + +/* used to convert a clocks property into an array of clock IDs. If the property + * is not specified then this macro will return {}. + */ +#define _SAI_GET_CLOCK_ARRAY(inst)\ + COND_CODE_1(DT_NODE_HAS_PROP(DT_INST(inst, nxp_dai_sai), clocks),\ + ({ _SAI_CLOCK_ID_ARRAY(inst) }),\ + ({ })) + +/* used to retrieve a const struct device *dev pointing to the clock controller. + * It is assumed that all SAI clocks come from a single clock provider. + * This macro returns a NULL if the clocks property doesn't exist. + */ +#define _SAI_GET_CLOCK_CONTROLLER(inst)\ + COND_CODE_1(DT_NODE_HAS_PROP(DT_INST(inst, nxp_dai_sai), clocks),\ + (DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst))),\ + (NULL)) + +/* used to convert a clock-names property into an array of clock names. If the + * property is not specified then this macro will return {}. + */ +#define _SAI_GET_CLOCK_NAMES(inst)\ + COND_CODE_1(DT_NODE_HAS_PROP(DT_INST(inst, nxp_dai_sai), clocks),\ + ({ _SAI_CLOCK_NAME_ARRAY(inst) }),\ + ({ })) + +/* used to declare a struct clock_data */ +#define SAI_CLOCK_DATA_DECLARE(inst) \ +{ \ + .clocks = (uint32_t [])_SAI_GET_CLOCK_ARRAY(inst), \ + .clock_num = DT_INST_PROP_LEN_OR(inst, clocks, 0), \ + .dev = _SAI_GET_CLOCK_CONTROLLER(inst), \ + .clock_names = (const char *[])_SAI_GET_CLOCK_NAMES(inst), \ +} + +/* used to parse the tx-fifo-watermark property. If said property is not + * specified then this macro will return half of the number of words in the + * FIFO. + */ +#define SAI_TX_FIFO_WATERMARK(inst)\ + DT_INST_PROP_OR(inst, tx_fifo_watermark,\ + FSL_FEATURE_SAI_FIFO_COUNTn(UINT_TO_I2S(DT_INST_REG_ADDR(inst))) / 2) + +/* used to parse the rx-fifo-watermark property. If said property is not + * specified then this macro will return half of the number of words in the + * FIFO. + */ +#define SAI_RX_FIFO_WATERMARK(inst)\ + DT_INST_PROP_OR(inst, rx_fifo_watermark,\ + FSL_FEATURE_SAI_FIFO_COUNTn(UINT_TO_I2S(DT_INST_REG_ADDR(inst))) / 2) + +/* used to retrieve TFR0's address based on SAI's physical address */ +#define SAI_TX_FIFO_BASE(inst)\ + FSL_FEATURE_SAI_TX_FIFO_BASEn(UINT_TO_I2S(DT_INST_REG_ADDR(inst)), 0) + +/* used to retrieve RFR0's address based on SAI's physical address */ +#define SAI_RX_FIFO_BASE(inst)\ + FSL_FEATURE_SAI_RX_FIFO_BASEn(UINT_TO_I2S(DT_INST_REG_ADDR(inst)), 0) + +/* internal macro used to retrieve the default TX/RX FIFO's size (in FIFO words) */ +#define _SAI_FIFO_DEPTH(inst)\ + FSL_FEATURE_SAI_FIFO_COUNTn(UINT_TO_I2S(DT_INST_REG_ADDR(inst))) + +/* used to retrieve the TX/RX FIFO's size (in FIFO words) */ +#define SAI_FIFO_DEPTH(inst)\ + DT_INST_PROP_OR(inst, fifo_depth, _SAI_FIFO_DEPTH(inst)) + +/* used to retrieve the DMA MUX for transmitter */ +#define SAI_TX_DMA_MUX(inst)\ + FSL_FEATURE_SAI_TX_DMA_MUXn(UINT_TO_I2S(DT_INST_REG_ADDR(inst))) + +/* used to retrieve the DMA MUX for receiver */ +#define SAI_RX_DMA_MUX(inst)\ + FSL_FEATURE_SAI_RX_DMA_MUXn(UINT_TO_I2S(DT_INST_REG_ADDR(inst))) + +/* utility macros */ + +/* invert a clock's polarity. This works because a clock's polarity is expressed + * as a 0 or as a 1. + */ +#define SAI_INVERT_POLARITY(polarity) (polarity) = !(polarity) + +/* used to issue a software reset of the transmitter/receiver */ +#define SAI_TX_RX_SW_RESET(dir, regmap)\ + ((dir) == DAI_DIR_RX ? SAI_RxSoftwareReset(UINT_TO_I2S(regmap), kSAI_ResetTypeSoftware) :\ + SAI_TxSoftwareReset(UINT_TO_I2S(regmap), kSAI_ResetTypeSoftware)) + +/* used to enable/disable the transmitter/receiver. + * When enabling the SYNC component, the ASYNC component will also be enabled. + * Attempting to disable the SYNC component will fail unless the SYNC bit is + * cleared. It is recommended to use sai_tx_rx_force_disable() instead of this + * macro when disabling transmitter/receiver. + */ +#define SAI_TX_RX_ENABLE_DISABLE(dir, regmap, enable)\ + ((dir) == DAI_DIR_RX ? SAI_RxEnable(UINT_TO_I2S(regmap), enable) :\ + SAI_TxEnable(UINT_TO_I2S(regmap), enable)) + +/* used to enable/disable the DMA requests for transmitter/receiver */ +#define SAI_TX_RX_DMA_ENABLE_DISABLE(dir, regmap, enable)\ + ((dir) == DAI_DIR_RX ? SAI_RxEnableDMA(UINT_TO_I2S(regmap),\ + kSAI_FIFORequestDMAEnable, enable) :\ + SAI_TxEnableDMA(UINT_TO_I2S(regmap), kSAI_FIFORequestDMAEnable, enable)) + +/* used to check if the hardware transmitter/receiver is enabled */ +#define SAI_TX_RX_IS_HW_ENABLED(dir, regmap)\ + ((dir) == DAI_DIR_RX ? (UINT_TO_I2S(regmap)->RCSR & I2S_RCSR_RE_MASK) : \ + (UINT_TO_I2S(regmap)->TCSR & I2S_TCSR_TE_MASK)) + +/* used to enable various transmitter/receiver interrupts */ +#define _SAI_TX_RX_ENABLE_IRQ(dir, regmap, which)\ + ((dir) == DAI_DIR_RX ? SAI_RxEnableInterrupts(UINT_TO_I2S(regmap), which) : \ + SAI_TxEnableInterrupts(UINT_TO_I2S(regmap), which)) + +/* used to disable various transmitter/receiver interrupts */ +#define _SAI_TX_RX_DISABLE_IRQ(dir, regmap, which)\ + ((dir) == DAI_DIR_RX ? SAI_RxDisableInterrupts(UINT_TO_I2S(regmap), which) : \ + SAI_TxDisableInterrupts(UINT_TO_I2S(regmap), which)) + +/* used to enable/disable various transmitter/receiver interrupts */ +#define SAI_TX_RX_ENABLE_DISABLE_IRQ(dir, regmap, which, enable)\ + ((enable == true) ? _SAI_TX_RX_ENABLE_IRQ(dir, regmap, which) :\ + _SAI_TX_RX_DISABLE_IRQ(dir, regmap, which)) + +/* used to check if a status flag is set */ +#define SAI_TX_RX_STATUS_IS_SET(dir, regmap, which)\ + ((dir) == DAI_DIR_RX ? ((UINT_TO_I2S(regmap))->RCSR & (which)) : \ + ((UINT_TO_I2S(regmap))->TCSR & (which))) + +struct sai_clock_data { + uint32_t *clocks; + uint32_t clock_num; + /* assumption: all clocks belong to the same producer */ + const struct device *dev; + const char **clock_names; +}; + +struct sai_data { + mm_reg_t regmap; + sai_transceiver_t rx_config; + sai_transceiver_t tx_config; + bool tx_enabled; + bool rx_enabled; + enum dai_state tx_state; + enum dai_state rx_state; + struct dai_config cfg; +}; + +struct sai_config { + uint32_t regmap_phys; + uint32_t regmap_size; + struct sai_clock_data clk_data; + bool mclk_is_output; + /* if the tx/rx-fifo-watermark properties are not specified, it's going + * to be assumed that the watermark should be set to half of the FIFO + * size. + */ + uint32_t rx_fifo_watermark; + uint32_t tx_fifo_watermark; + const struct dai_properties *tx_props; + const struct dai_properties *rx_props; + uint32_t dai_index; + void (*irq_config)(void); +}; + +/* this needs to perfectly match SOF's struct sof_ipc_dai_sai_params */ +struct sai_bespoke_config { + uint32_t reserved0; + + uint16_t reserved1; + uint16_t mclk_id; + uint32_t mclk_direction; + + /* CLOCK-related data */ + uint32_t mclk_rate; + uint32_t fsync_rate; + uint32_t bclk_rate; + + /* TDM-related data */ + uint32_t tdm_slots; + uint32_t rx_slots; + uint32_t tx_slots; + uint16_t tdm_slot_width; + uint16_t reserved2; +}; + +#ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION +static int get_msel(sai_bclk_source_t bclk_source, uint32_t *msel) +{ + switch (bclk_source) { + case kSAI_BclkSourceMclkOption1: + *msel = 0; + break; + case kSAI_BclkSourceMclkOption2: + *msel = (0x2 << SAI_MCLK_MCR_MSEL_SHIFT); + break; + case kSAI_BclkSourceMclkOption3: + *msel = (0x3 << SAI_MCLK_MCR_MSEL_SHIFT); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void set_msel(uint32_t regmap, int msel) +{ + UINT_TO_I2S(regmap)->MCR &= ~SAI_MCLK_MCR_MSEL_MASK; + UINT_TO_I2S(regmap)->MCR |= msel; +} + +static int clk_lookup_by_name(const struct sai_clock_data *clk_data, char *name) +{ + int i; + + for (i = 0; i < clk_data->clock_num; i++) { + if (!strcmp(name, clk_data->clock_names[i])) { + return i; + } + } + + return -EINVAL; +} + +static int get_mclk_rate(const struct sai_clock_data *clk_data, + sai_bclk_source_t bclk_source, + uint32_t *rate) +{ + int clk_idx; + char *clk_name; + + switch (bclk_source) { + case kSAI_BclkSourceMclkOption1: + clk_name = "mclk1"; + break; + case kSAI_BclkSourceMclkOption2: + clk_name = "mclk2"; + break; + case kSAI_BclkSourceMclkOption3: + clk_name = "mclk3"; + break; + default: + LOG_ERR("invalid bitclock source: %d", bclk_source); + return -EINVAL; + } + + clk_idx = clk_lookup_by_name(clk_data, clk_name); + if (clk_idx < 0) { + LOG_ERR("failed to get clock index for %s", clk_name); + return clk_idx; + } + + return clock_control_get_rate(clk_data->dev, + UINT_TO_POINTER(clk_data->clocks[clk_idx]), + rate); +} +#endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */ + +static inline void get_bclk_default_config(sai_bit_clock_t *cfg) +{ + memset(cfg, 0, sizeof(sai_bit_clock_t)); + + /* by default, BCLK has the following properties: + * + * 1) BCLK is active HIGH. + * 2) BCLK uses MCLK1 source. (only applicable to master mode) + * 3) No source swap. + * 4) No input delay. + */ + cfg->bclkPolarity = kSAI_PolarityActiveHigh; + cfg->bclkSource = kSAI_BclkSourceMclkOption1; +} + +static inline void get_fsync_default_config(sai_frame_sync_t *cfg) +{ + memset(cfg, 0, sizeof(sai_frame_sync_t)); + + /* by default, FSYNC has the following properties: + * + * 1) FSYNC is asserted one bit early with respect to the next + * frame. + * 2) FSYNC is active HIGH. + */ + cfg->frameSyncEarly = true; + cfg->frameSyncPolarity = kSAI_PolarityActiveHigh; +} + +static inline void get_serial_default_config(sai_serial_data_t *cfg) +{ + memset(cfg, 0, sizeof(sai_serial_data_t)); + + /* by default, the serial configuration has the following quirks: + * + * 1) Data pin is not tri-stated. + * 2) MSB is first. + */ + /* note: this is equivalent to checking if the SAI has xCR4's CHMOD bit */ +#if FSL_FEATURE_SAI_HAS_CHANNEL_MODE + cfg->dataMode = kSAI_DataPinStateOutputZero; +#endif /* FSL_FEATURE_SAI_HAS_CHANNEL_MODE */ + cfg->dataOrder = kSAI_DataMSB; +} + +static inline void get_fifo_default_config(sai_fifo_t *cfg) +{ + memset(cfg, 0, sizeof(sai_fifo_t)); +} + +static inline uint32_t sai_get_state(enum dai_dir dir, + struct sai_data *data) +{ + if (dir == DAI_DIR_RX) { + return data->rx_state; + } else { + return data->tx_state; + } +} + +static int sai_update_state(enum dai_dir dir, + struct sai_data *data, + enum dai_state new_state) +{ + enum dai_state old_state = sai_get_state(dir, data); + + LOG_DBG("attempting to transition from %d to %d", old_state, new_state); + + /* check if transition is possible */ + switch (new_state) { + case DAI_STATE_NOT_READY: + /* this shouldn't be possible */ + return -EPERM; + case DAI_STATE_READY: + if (old_state != DAI_STATE_NOT_READY && + old_state != DAI_STATE_READY && + old_state != DAI_STATE_STOPPING) { + return -EPERM; + } + break; + case DAI_STATE_RUNNING: + if (old_state != DAI_STATE_PAUSED && + old_state != DAI_STATE_STOPPING && + old_state != DAI_STATE_READY) { + return -EPERM; + } + break; + case DAI_STATE_PAUSED: + if (old_state != DAI_STATE_RUNNING) { + return -EPERM; + } + break; + case DAI_STATE_STOPPING: + if (old_state != DAI_STATE_READY && + old_state != DAI_STATE_RUNNING && + old_state != DAI_STATE_PAUSED) { + return -EPERM; + } + break; + case DAI_STATE_ERROR: + case DAI_STATE_PRE_RUNNING: + /* these states are not used so transitioning to them + * is considered invalid. + */ + default: + return -EINVAL; + } + + if (dir == DAI_DIR_RX) { + data->rx_state = new_state; + } else { + data->tx_state = new_state; + } + + return 0; +} + +static inline void sai_tx_rx_force_disable(enum dai_dir dir, + uint32_t regmap) +{ + I2S_Type *base = UINT_TO_I2S(regmap); + + if (dir == DAI_DIR_RX) { + base->RCSR = ((base->RCSR & 0xFFE3FFFFU) & (~I2S_RCSR_RE_MASK)); + } else { + base->TCSR = ((base->TCSR & 0xFFE3FFFFU) & (~I2S_TCSR_TE_MASK)); + } +} + +static inline void sai_tx_rx_sw_enable_disable(enum dai_dir dir, + struct sai_data *data, + bool enable) +{ + if (dir == DAI_DIR_RX) { + data->rx_enabled = enable; + } else { + data->tx_enabled = enable; + } +} + +static inline int count_leading_zeros(uint32_t word) +{ + int num = 0; + + while (word) { + if (!(word & 0x1)) { + num++; + } else { + break; + } + + word = word >> 1; + } + + return num; +} + +static inline void sai_tx_rx_set_dline_mask(enum dai_dir dir, uint32_t regmap, uint32_t mask) +{ + I2S_Type *base = UINT_TO_I2S(regmap); + + if (dir == DAI_DIR_RX) { + base->RCR3 &= ~I2S_RCR3_RCE_MASK; + base->RCR3 |= I2S_RCR3_RCE(mask); + } else { + base->TCR3 &= ~I2S_TCR3_TCE_MASK; + base->TCR3 |= I2S_TCR3_TCE(mask); + } +} + +static inline void sai_dump_register_data(uint32_t regmap) +{ + I2S_Type *base = UINT_TO_I2S(regmap); + + LOG_DBG("TCSR: 0x%x", base->TCSR); + LOG_DBG("RCSR: 0x%x", base->RCSR); + + LOG_DBG("TCR1: 0x%x", base->TCR1); + LOG_DBG("RCR1: 0x%x", base->RCR1); + + LOG_DBG("TCR2: 0x%x", base->TCR2); + LOG_DBG("RCR2: 0x%x", base->RCR2); + + LOG_DBG("TCR3: 0x%x", base->TCR3); + LOG_DBG("RCR3: 0x%x", base->RCR3); + + LOG_DBG("TCR4: 0x%x", base->TCR4); + LOG_DBG("RCR4: 0x%x", base->RCR4); + + LOG_DBG("TCR5: 0x%x", base->TCR5); + LOG_DBG("RCR5: 0x%x", base->RCR5); + + LOG_DBG("TMR: 0x%x", base->TMR); + LOG_DBG("RMR: 0x%x", base->RMR); + +#ifdef CONFIG_SAI_HAS_MCLK_CONFIG_OPTION + LOG_DBG("MCR: 0x%x", base->MCR); +#endif /* CONFIG_SAI_HAS_MCLK_CONFIG_OPTION */ +} + +#endif /* ZEPHYR_DRIVERS_DAI_NXP_SAI_H_ */ diff --git a/dts/bindings/dai/nxp,dai-sai.yaml b/dts/bindings/dai/nxp,dai-sai.yaml new file mode 100644 index 000000000000..901382f90bb0 --- /dev/null +++ b/dts/bindings/dai/nxp,dai-sai.yaml @@ -0,0 +1,60 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP Synchronous Audio Interface (SAI) node + +compatible: "nxp,dai-sai" + +include: base.yaml + +properties: + reg: + required: true + mclk-is-output: + type: boolean + description: | + Use this property to set the SAI MCLK as output or as input. + By default, if this property is not specified, MCLK will be + set as input. Setting the MCLK as output for SAIs which don't + support MCLK configuration will result in a BUILD_ASSERT() + failure. + rx-fifo-watermark: + type: int + description: | + Use this property to specify the watermark value for the TX + FIFO. This value needs to be in FIFO words (NOT BYTES). This + value needs to be in the following interval: (0, DEFAULT_FIFO_DEPTH], + otherwise a BUILD_ASSERT() failure will be raised. + tx-fifo-watermark: + type: int + description: | + Use this property to specify the watermark value for the RX + FIFO. This value needs to be in FIFO words (NOT BYTES). This + value needs to be in the following interval: (0, DEFAULT_FIFO_DEPTH], + otherwise a BUILD_ASSERT() failure will be raised. + interrupts: + required: true + fifo-depth: + type: int + description: | + Use this property to set the FIFO depth that will be reported + to other applications calling dai_get_properties(). This value + should be in the following interval: (0, DEFAULT_FIFO_DEPTH], + otherwise a BUILD_ASSERT() failure will be raised. + By DEFAULT_FIFO_DEPTH we mean the actual (hardware) value of + the FIFO depth. This is needed because some applications (e.g: SOF) + use this value to compute the DMA burst size, in which case + DEFAULT_FIFO_DEPTH cannot be used. Generally, reporting a false + FIFO depth should be avoided. Please note that the sanity check + for tx/rx-fifo-watermark uses DEFAULT_FIFO_DEPTH instead of this + value so use with caution. If unsure, it's better to simply not + use this property, in which case the reported value will be + DEFAULT_FIFO_DEPTH. + dai-index: + type: int + description: | + Use this property to specify the index of the DAI. At the + moment, this is only used by SOF to fetch the "struct device" + associated with the DAI whose index Linux passes to SOF + through an IPC. If this property is not specified, the DAI + index will be considered 0. diff --git a/include/zephyr/drivers/dai.h b/include/zephyr/drivers/dai.h index 8e5fbcf553ee..e0969292fbbc 100644 --- a/include/zephyr/drivers/dai.h +++ b/include/zephyr/drivers/dai.h @@ -33,6 +33,61 @@ extern "C" { #endif +/** Used to extract the clock configuration from the format attribute of struct dai_config */ +#define DAI_FORMAT_CLOCK_PROVIDER_MASK 0xf000 +/** Used to extract the protocol from the format attribute of struct dai_config */ +#define DAI_FORMAT_PROTOCOL_MASK 0x000f +/** Used to extract the clock inversion from the format attribute of struct dai_config */ +#define DAI_FORMAT_CLOCK_INVERSION_MASK 0x0f00 + +/** @brief DAI clock configurations + * + * This is used to describe all of the possible + * clock-related configurations w.r.t the DAI + * and the codec. + */ +enum dai_clock_provider { + /**< codec BLCK provider, codec FSYNC provider */ + DAI_CBP_CFP = (0 << 12), + /**< codec BCLK consumer, codec FSYNC provider */ + DAI_CBC_CFP = (2 << 12), + /**< codec BCLK provider, codec FSYNC consumer */ + DAI_CBP_CFC = (3 << 12), + /**< codec BCLK consumer, codec FSYNC consumer */ + DAI_CBC_CFC = (4 << 12), +}; + +/** @brief DAI protocol + * + * The communication between the DAI and the CODEC + * may use different protocols depending on the scenario. + */ +enum dai_protocol { + DAI_PROTO_I2S = 1, /**< I2S */ + DAI_PROTO_RIGHT_J, /**< Right Justified */ + DAI_PROTO_LEFT_J, /**< Left Justified */ + DAI_PROTO_DSP_A, /**< TDM, FSYNC asserted 1 BCLK early */ + DAI_PROTO_DSP_B, /**< TDM, FSYNC asserted at the same time as MSB */ + DAI_PROTO_PDM, /**< Pulse Density Modulation */ +}; + +/** @brief DAI clock inversion + * + * Some applications may require a different + * clock polarity (FSYNC/BCLK) compared to + * the default one chosen based on the protocol. + */ +enum dai_clock_inversion { + /**< no BCLK inversion, no FSYNC inversion */ + DAI_INVERSION_NB_NF = 0, + /**< no BCLK inversion, FSYNC inversion */ + DAI_INVERSION_NB_IF = (2 << 8), + /**< BCLK inversion, no FSYNC inversion */ + DAI_INVERSION_IB_NF = (3 << 8), + /**< BCLK inversion, FSYNC inversion */ + DAI_INVERSION_IB_IF = (4 << 8), +}; + /** @brief Types of DAI * * The type of the DAI. This ID type is used to configure bespoke DAI HW diff --git a/west.yml b/west.yml index 261740c1d26b..eb3c7bafc557 100644 --- a/west.yml +++ b/west.yml @@ -193,7 +193,7 @@ manifest: groups: - hal - name: hal_nxp - revision: 2a294b540c09b36f7cddece44d25628bfde5970e + revision: ed3efff426ce56230be189d99ce985ceafece4a4 path: modules/hal/nxp groups: - hal