From de48bb3d4ab6b7aaaff908a7346776d640429d10 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 12 Feb 2024 17:05:02 +0200 Subject: [PATCH 1/3] west.yml: update hal_nxp revision Update hal_nxp reivison to pull in the following patches: * mcux: hal_nxp: include HAL ESAI driver if Zephyr ESAI driver is enabled Signed-off-by: Laurentiu Mihalcea --- west.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/west.yml b/west.yml index 1e82f4512c06..a05d5f35aeeb 100644 --- a/west.yml +++ b/west.yml @@ -193,7 +193,7 @@ manifest: groups: - hal - name: hal_nxp - revision: 874ed3b297ba1f21bc6422aed85c3fdba3219de2 + revision: 6b252c34eef7d0fdd2dee8261ab68086522c4435 path: modules/hal/nxp groups: - hal From 8082678a3bef04da61d0c4be31913a31059f5e96 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Thu, 8 Feb 2024 17:25:52 +0200 Subject: [PATCH 2/3] drivers: dai: add driver for NXP's ESAI This commit introduces a new DAI driver used for NXP's ESAI IP. Signed-off-by: Laurentiu Mihalcea --- drivers/dai/CMakeLists.txt | 1 + drivers/dai/Kconfig | 1 + drivers/dai/nxp/esai/CMakeLists.txt | 5 + drivers/dai/nxp/esai/Kconfig.esai | 9 + drivers/dai/nxp/esai/esai.c | 766 ++++++++++++++++++++++++++ drivers/dai/nxp/esai/esai.h | 533 ++++++++++++++++++ dts/bindings/dai/nxp,dai-esai.yaml | 75 +++ include/zephyr/dt-bindings/dai/esai.h | 54 ++ 8 files changed, 1444 insertions(+) create mode 100644 drivers/dai/nxp/esai/CMakeLists.txt create mode 100644 drivers/dai/nxp/esai/Kconfig.esai create mode 100644 drivers/dai/nxp/esai/esai.c create mode 100644 drivers/dai/nxp/esai/esai.h create mode 100644 dts/bindings/dai/nxp,dai-esai.yaml create mode 100644 include/zephyr/dt-bindings/dai/esai.h diff --git a/drivers/dai/CMakeLists.txt b/drivers/dai/CMakeLists.txt index b6bd04939d52..506c2a54335b 100644 --- a/drivers/dai/CMakeLists.txt +++ b/drivers/dai/CMakeLists.txt @@ -5,3 +5,4 @@ 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) +add_subdirectory_ifdef(CONFIG_DAI_NXP_ESAI nxp/esai) diff --git a/drivers/dai/Kconfig b/drivers/dai/Kconfig index da882a99fa00..05877efd88ed 100644 --- a/drivers/dai/Kconfig +++ b/drivers/dai/Kconfig @@ -30,5 +30,6 @@ 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" +source "drivers/dai/nxp/esai/Kconfig.esai" endif # DAI diff --git a/drivers/dai/nxp/esai/CMakeLists.txt b/drivers/dai/nxp/esai/CMakeLists.txt new file mode 100644 index 000000000000..826c8592c185 --- /dev/null +++ b/drivers/dai/nxp/esai/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(esai.c) diff --git a/drivers/dai/nxp/esai/Kconfig.esai b/drivers/dai/nxp/esai/Kconfig.esai new file mode 100644 index 000000000000..99651e8dc9a1 --- /dev/null +++ b/drivers/dai/nxp/esai/Kconfig.esai @@ -0,0 +1,9 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +config DAI_NXP_ESAI + bool "NXP Enhanced Serial Audio Interface (ESAI) driver" + default y + depends on DT_HAS_NXP_DAI_ESAI_ENABLED + help + Select this to enable NXP ESAI driver. diff --git a/drivers/dai/nxp/esai/esai.c b/drivers/dai/nxp/esai/esai.c new file mode 100644 index 000000000000..1d7bea6495ef --- /dev/null +++ b/drivers/dai/nxp/esai/esai.c @@ -0,0 +1,766 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esai.h" + +/* TODO: + * 1) Some pin functions can be inferred from software ctx. For instance, + * if you use more than 1 data line, it's obvious you're going + * to want to keep the pins of the data lines in ESAI mode. + * + * 2) Add function for handling underrun/overrun. Preferably + * we should do the same as we did for SAI to ease the testing + * process. This approach will do for now. In the future, this + * can be handled in a more sophisticated maner. + * + * notes: + * 1) EXTAL clock is divided as follows: + * a) Initial EXTAL signal is automatically divided by 2. + * b) If prescaler is enabled the resulting EXTAL from a) + * is divided by 8. + * c) The resulting EXTAL signal from b) can be divided + * by 1 up to 256 (configured via xPM0-xPM7). The resulting + * signal is referred to as HCLK. + * d) HCLK obtained from c) can be further divided by 1 + * up to 16 (configured via xFP0-xFP3). The resulting signal is + * referred to as BCLK. + */ + +static int 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) +{ + uint32_t hclk_div_ratio, bclk_div_ratio; + + /* sanity checks */ + if (!cfg) { + LOG_ERR("got NULL clock configuration"); + return -EINVAL; + } + + if (!extal_rate || !hclk_rate || !bclk_rate) { + LOG_ERR("got NULL clock rate"); + return -EINVAL; + } + + if (hclk_rate > extal_rate) { + LOG_ERR("HCLK rate cannot be higher than EXTAL rate"); + return -EINVAL; + } + + if (bclk_rate > extal_rate) { + LOG_ERR("BCLK rate cannot be higher than EXTAL rate"); + return -EINVAL; + } + + if (DIV_ROUND_UP(extal_rate, bclk_rate) > 2 * 8 * 256 * 16) { + LOG_ERR("BCLK rate %u cannot be obtained from EXTAL rate %u", + bclk_rate, extal_rate); + return -EINVAL; + } + + /* TODO: add explanation */ + if (DIV_ROUND_UP(extal_rate / 2, bclk_rate) == 1) { + LOG_ERR("HCLK prescaler bypass with divider bypass is not supported"); + return -EINVAL; + } + + hclk_div_ratio = 1; + bclk_div_ratio = 1; + + /* check if HCLK is in (EXTAL_RATE / 2, EXTAL_RATE). If so, + * return an error as any rates from this interval cannot be obtained. + */ + if (hclk_rate > extal_rate / 2 && hclk_rate < extal_rate) { + LOG_ERR("HCLK rate cannot be higher than EXTAL's rate divided by 2"); + return -EINVAL; + } + + /* compute HCLK configuration - only required if HCLK pad output is used */ + if (!variable_hclk) { + if (extal_rate == hclk_rate) { + /* HCLK rate from pad is the same as EXTAL rate */ + cfg->hclk_bypass = true; + } else { + /* EXTAL is automatically divided by 2 */ + extal_rate /= 2; + + /* compute prescaler divide ratio w/ prescaler bypass */ + hclk_div_ratio = DIV_ROUND_UP(extal_rate, hclk_rate); + + if (hclk_div_ratio > 256) { + /* can't obtain HCLK w/o prescaler */ + cfg->hclk_prescaler_en = true; + + extal_rate /= 8; + + /* recompute ratio w/ prescaler */ + hclk_div_ratio = DIV_ROUND_UP(extal_rate, hclk_rate); + + if (hclk_div_ratio > 256) { + LOG_ERR("cannot obtain HCLK rate %u from EXTAL rate %u", + hclk_rate, extal_rate); + return -EINVAL; + } + } + } + } + + cfg->hclk_div_ratio = hclk_div_ratio; + + if (!allow_bclk_configuration) { + return 0; + } + + extal_rate = DIV_ROUND_UP(extal_rate, hclk_div_ratio); + + /* compute BCLK configuration */ + if (variable_hclk || cfg->hclk_bypass) { + /* attempt to find a configuration that satisfies BCLK's rate */ + extal_rate /= 2; + + hclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate); + + /* check if prescaler is required */ + if (hclk_div_ratio > 256 * 16) { + extal_rate /= 8; + cfg->hclk_prescaler_en = true; + hclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate); + } + + /* check if we really need to loop through TPM div ratios */ + if (hclk_div_ratio < 256) { + cfg->bclk_div_ratio = 1; + cfg->hclk_div_ratio = hclk_div_ratio; + return 0; + } + + for (int i = 1; i < 256; i++) { + hclk_div_ratio = DIV_ROUND_UP(extal_rate / i, bclk_rate); + bclk_div_ratio = DIV_ROUND_UP(extal_rate / hclk_div_ratio, bclk_rate); + + if (bclk_div_ratio <= 16) { + /* found valid configuration, let caller know */ + cfg->bclk_div_ratio = bclk_div_ratio; + cfg->hclk_div_ratio = hclk_div_ratio; + + return 0; + } + } + + /* no valid configuration found */ + LOG_ERR("no valid configuration for BCLK rate %u and EXTAL rate %u", + bclk_rate, extal_rate); + return -EINVAL; + } + + /* can the BCLK rate be obtained w/o modifying divided EXTAL? */ + bclk_div_ratio = DIV_ROUND_UP(extal_rate, bclk_rate); + + if (bclk_div_ratio > 16) { + LOG_ERR("cannot obtain BCLK rate %d from EXTAL rate %d", + bclk_rate, extal_rate); + return -EINVAL; + } + + /* save ratios before returning */ + cfg->bclk_div_ratio = bclk_div_ratio; + cfg->hclk_div_ratio = hclk_div_ratio; + + return 0; +} + +static int esai_get_clk_provider_config(const struct dai_config *cfg, + struct esai_transceiver_config *xceiver_cfg) +{ + switch (cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK) { + case DAI_CBC_CFC: + /* default FSYNC and BCLK are OUTPUT */ + break; + case DAI_CBP_CFP: + xceiver_cfg->bclk_dir = kESAI_ClockInput; + xceiver_cfg->fsync_dir = kESAI_ClockInput; + break; + default: + LOG_ERR("invalid clock provider configuration: %d", + cfg->format & DAI_FORMAT_CLOCK_PROVIDER_MASK); + return -EINVAL; + } + + return 0; +} + +static int esai_get_clk_inversion_config(const struct dai_config *cfg, + struct esai_transceiver_config *xceiver_cfg) +{ + switch (cfg->format & DAI_FORMAT_CLOCK_INVERSION_MASK) { + case DAI_INVERSION_IB_IF: + ESAI_INVERT_POLARITY(xceiver_cfg->bclk_polarity); + ESAI_INVERT_POLARITY(xceiver_cfg->fsync_polarity); + break; + case DAI_INVERSION_IB_NF: + ESAI_INVERT_POLARITY(xceiver_cfg->bclk_polarity); + break; + case DAI_INVERSION_NB_IF: + ESAI_INVERT_POLARITY(xceiver_cfg->fsync_polarity); + 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; + } + + return 0; +} + +static int esai_get_proto_config(const struct dai_config *cfg, + struct esai_transceiver_config *xceiver_cfg) +{ + switch (cfg->format & DAI_FORMAT_PROTOCOL_MASK) { + case DAI_PROTO_I2S: + xceiver_cfg->bclk_polarity = kESAI_ClockActiveLow; + xceiver_cfg->fsync_polarity = kESAI_ClockActiveLow; + break; + case DAI_PROTO_DSP_A: + xceiver_cfg->bclk_polarity = kESAI_ClockActiveLow; + xceiver_cfg->fsync_is_bit_wide = true; + break; + default: + LOG_ERR("invalid DAI protocol: %d", + cfg->format & DAI_FORMAT_PROTOCOL_MASK); + return -EINVAL; + } + return 0; +} + +static int esai_get_slot_format(uint32_t slot_width, uint32_t word_width, + struct esai_transceiver_config *cfg) +{ + /* sanity check */ + if (!ESAI_SLOT_WORD_WIDTH_IS_VALID(slot_width, word_width)) { + LOG_ERR("invalid slot %d word %d width configuration", + slot_width, word_width); + return -EINVAL; + } + + cfg->slot_format = ESAI_SLOT_FORMAT(slot_width, word_width); + + return 0; +} + +static void esai_get_xceiver_default_config(struct esai_transceiver_config *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); + + cfg->hclk_prescaler_en = false; + cfg->hclk_div_ratio = 1; + cfg->bclk_div_ratio = 1; + cfg->hclk_bypass = false; + + cfg->hclk_src = kESAI_HckSourceExternal; + cfg->hclk_dir = kESAI_ClockOutput; + cfg->hclk_polarity = kESAI_ClockActiveHigh; + + cfg->bclk_dir = kESAI_ClockOutput; + cfg->bclk_polarity = kESAI_ClockActiveHigh; + + cfg->fsync_dir = kESAI_ClockOutput; + cfg->fsync_polarity = kESAI_ClockActiveHigh; + + cfg->fsync_is_bit_wide = false; + cfg->zero_pad_en = true; + cfg->fsync_early = true; + + cfg->mode = kESAI_NetworkMode; + cfg->data_order = kESAI_ShifterMSB; + cfg->data_left_aligned = true; +} + +static void esai_commit_config(ESAI_Type *base, + enum dai_dir dir, + struct esai_transceiver_config *cfg) +{ + if (dir == DAI_DIR_TX) { + base->TCCR &= ~(ESAI_TCCR_THCKD_MASK | ESAI_TCCR_TFSD_MASK | + ESAI_TCCR_TCKD_MASK | ESAI_TCCR_THCKP_MASK | + ESAI_TCCR_TFSP_MASK | ESAI_TCCR_TCKP_MASK | + ESAI_TCCR_TFP_MASK | ESAI_TCCR_TDC_MASK | + ESAI_TCCR_TPSR_MASK | ESAI_TCCR_TPM_MASK); + + base->TCCR |= ESAI_TCCR_THCKD(cfg->hclk_dir) | + ESAI_TCCR_TFSD(cfg->fsync_dir) | + ESAI_TCCR_TCKD(cfg->bclk_dir) | + ESAI_TCCR_THCKP(cfg->hclk_polarity) | + ESAI_TCCR_TFSP(cfg->fsync_polarity) | + ESAI_TCCR_TCKP(cfg->bclk_polarity) | + ESAI_TCCR_TFP(cfg->bclk_div_ratio - 1) | + ESAI_TCCR_TDC(cfg->fsync_div - 1) | + ESAI_TCCR_TPSR(!cfg->hclk_prescaler_en) | + ESAI_TCCR_TPM(cfg->hclk_div_ratio - 1); + + base->TCR &= ~(ESAI_TCR_PADC_MASK | ESAI_TCR_TFSR_MASK | + ESAI_TCR_TFSL_MASK | ESAI_TCR_TMOD_MASK | + ESAI_TCR_TWA_MASK | ESAI_TCR_TSHFD_MASK); + + base->TCR |= ESAI_TCR_PADC(cfg->zero_pad_en) | + ESAI_TCR_TFSR(cfg->fsync_early) | + ESAI_TCR_TFSL(cfg->fsync_is_bit_wide) | + ESAI_TCR_TSWS(cfg->slot_format) | + ESAI_TCR_TMOD(cfg->mode) | + ESAI_TCR_TWA(!cfg->data_left_aligned) | + ESAI_TCR_TSHFD(cfg->data_order); + + base->ECR &= ~(ESAI_ECR_ETI_MASK | + ESAI_ECR_ETO_MASK); + + base->ECR |= ESAI_ECR_ETI(cfg->hclk_src) | + ESAI_ECR_ETO(cfg->hclk_bypass); + + base->TFCR &= ~(ESAI_TFCR_TFWM_MASK | ESAI_TFCR_TWA_MASK); + base->TFCR |= ESAI_TFCR_TFWM(cfg->watermark) | + ESAI_TFCR_TWA(cfg->word_alignment); + + ESAI_TxSetSlotMask(base, cfg->slot_mask); + } else { + base->RCCR &= ~(ESAI_RCCR_RHCKD_MASK | ESAI_RCCR_RFSD_MASK | + ESAI_RCCR_RCKD_MASK | ESAI_RCCR_RHCKP_MASK | + ESAI_RCCR_RFSP_MASK | ESAI_RCCR_RCKP_MASK | + ESAI_RCCR_RFP_MASK | ESAI_RCCR_RDC_MASK | + ESAI_RCCR_RPSR_MASK | ESAI_RCCR_RPM_MASK); + + base->RCCR |= ESAI_RCCR_RHCKD(cfg->hclk_dir) | + ESAI_RCCR_RFSD(cfg->fsync_dir) | + ESAI_RCCR_RCKD(cfg->bclk_dir) | + ESAI_RCCR_RHCKP(cfg->hclk_polarity) | + ESAI_RCCR_RFSP(cfg->fsync_polarity) | + ESAI_RCCR_RCKP(cfg->bclk_polarity) | + ESAI_RCCR_RFP(cfg->bclk_div_ratio - 1) | + ESAI_RCCR_RDC(cfg->fsync_div - 1) | + ESAI_RCCR_RPSR(!cfg->hclk_prescaler_en) | + ESAI_RCCR_RPM(cfg->hclk_div_ratio - 1); + + base->RCR &= ~(ESAI_RCR_RFSR_MASK | ESAI_RCR_RFSL_MASK | + ESAI_RCR_RMOD_MASK | ESAI_RCR_RWA_MASK | + ESAI_RCR_RSHFD_MASK); + + base->RCR |= ESAI_RCR_RFSR(cfg->fsync_early) | + ESAI_RCR_RFSL(cfg->fsync_is_bit_wide) | + ESAI_RCR_RSWS(cfg->slot_format) | + ESAI_RCR_RMOD(cfg->mode) | + ESAI_RCR_RWA(!cfg->data_left_aligned) | + ESAI_RCR_RSHFD(cfg->data_order); + + base->ECR &= ~(ESAI_ECR_ERI_MASK | + ESAI_ECR_ERO_MASK); + + base->ECR |= ESAI_ECR_ERI(cfg->hclk_src) | + ESAI_ECR_ERO(cfg->hclk_bypass); + + base->RFCR &= ~(ESAI_RFCR_RFWM_MASK | ESAI_RFCR_RWA_MASK); + base->RFCR |= ESAI_RFCR_RFWM(cfg->watermark) | + ESAI_RFCR_RWA(cfg->word_alignment); + + EASI_RxSetSlotMask(base, cfg->slot_mask); + } +} + +static int esai_config_set(const struct device *dev, + const struct dai_config *cfg, + const void *bespoke_data) +{ + const struct esai_bespoke_config *bespoke; + struct esai_data *data; + const struct esai_config *esai_cfg; + struct esai_transceiver_config tx_config; + struct esai_transceiver_config rx_config; + ESAI_Type *base; + int ret; + + if (!cfg || !bespoke_data) { + return -EINVAL; + } + + if (cfg->type != DAI_IMX_ESAI) { + LOG_ERR("wrong DAI type: %d", cfg->type); + return -EINVAL; + } + + data = dev->data; + esai_cfg = dev->config; + bespoke = bespoke_data; + base = UINT_TO_ESAI(data->regmap); + + /* config_set() configures both the transmitter and the receiver. + * As such, the following state transitions make sure that the + * directions are stopped. This means that they can be safely + * reset and re-configured. + */ + ret = esai_update_state(data, DAI_DIR_TX, DAI_STATE_READY); + if (ret < 0) { + LOG_ERR("failed to update TX state"); + return ret; + } + + ret = esai_update_state(data, DAI_DIR_RX, DAI_STATE_READY); + if (ret < 0) { + LOG_ERR("failed to update RX state"); + return ret; + } + + ESAI_Enable(base, true); + + /* disconnect all ESAI pins */ + base->PCRC &= ~ESAI_PCRC_PC_MASK; + base->PRRC &= ~ESAI_PRRC_PDC_MASK; + + /* go back to known configuration through reset */ + ESAI_Reset(UINT_TO_ESAI(data->regmap)); + + /* get default configuration */ + esai_get_xceiver_default_config(&tx_config); + + /* TODO: for now, only network mode is supported */ + tx_config.fsync_div = bespoke->tdm_slots; + + /* clock provider configuration */ + ret = esai_get_clk_provider_config(cfg, &tx_config); + if (ret < 0) { + return ret; + } + + /* protocol configuration */ + ret = esai_get_proto_config(cfg, &tx_config); + if (ret < 0) { + return ret; + } + + /* clock inversion configuration */ + ret = esai_get_clk_inversion_config(cfg, &tx_config); + if (ret < 0) { + return ret; + } + + ret = esai_get_slot_format(bespoke->tdm_slot_width, + esai_cfg->word_width, &tx_config); + if (ret < 0) { + return ret; + } + + tx_config.word_alignment = ESAI_WORD_ALIGNMENT(esai_cfg->word_width); + + /* duplicate TX configuration */ + memcpy(&rx_config, &tx_config, sizeof(tx_config)); + + /* parse clock configuration from DTS. This will overwrite + * directions set in bespoke data. + */ + ret = esai_parse_clock_config(esai_cfg, &tx_config, &rx_config); + if (ret < 0) { + return ret; + } + + /* compute TX clock configuration */ + ret = esai_get_clock_rate_config(bespoke->mclk_rate, bespoke->mclk_rate, + bespoke->bclk_rate, + !ESAI_PIN_IS_USED(data, ESAI_PIN_HCKT), + tx_config.bclk_dir, + &tx_config); + if (ret < 0) { + return ret; + } + + /* compute RX clock configuration */ + ret = esai_get_clock_rate_config(bespoke->mclk_rate, bespoke->mclk_rate, + bespoke->bclk_rate, + !ESAI_PIN_IS_USED(data, ESAI_PIN_HCKR), + rx_config.bclk_dir, + &rx_config); + if (ret < 0) { + return ret; + } + + + tx_config.watermark = esai_cfg->tx_fifo_watermark; + rx_config.watermark = esai_cfg->rx_fifo_watermark; + + tx_config.slot_mask = bespoke->tx_slots; + rx_config.slot_mask = bespoke->rx_slots; + + LOG_DBG("dumping TX configuration"); + esai_dump_xceiver_config(&tx_config); + + LOG_DBG("dumping RX configuration"); + esai_dump_xceiver_config(&rx_config); + + /* enable ESAI to allow committing the configurations */ + ESAI_Enable(base, true); + + esai_dump_register_data(base); + + esai_commit_config(base, DAI_DIR_TX, &tx_config); + esai_commit_config(base, DAI_DIR_RX, &rx_config); + + /* allow each TX data register to be initialized from TX FIFO */ + base->TFCR |= ESAI_TFCR_TIEN_MASK; + + /* enable FIFO usage + * + * TODO: for now, only 1 data line per direction is supported. + */ + esai_tx_rx_enable_disable_fifo_usage(base, DAI_DIR_TX, BIT(0), true); + esai_tx_rx_enable_disable_fifo_usage(base, DAI_DIR_RX, BIT(0), true); + + /* re-connect pins based on DTS pin configuration */ + base->PCRC = data->pcrc; + base->PRRC = data->prrc; + + data->cfg.rate = bespoke->fsync_rate; + data->cfg.channels = bespoke->tdm_slots; + + esai_dump_register_data(base); + + return 0; +} + +static int esai_config_get(const struct device *dev, + struct dai_config *cfg, + enum dai_dir dir) +{ + struct esai_data *data = dev->data; + + if (!cfg) { + return -EINVAL; + } + + memcpy(cfg, &data->cfg, sizeof(*cfg)); + + return 0; +} + +static int esai_trigger_start(const struct device *dev, enum dai_dir dir) +{ + struct esai_data *data; + ESAI_Type *base; + int ret, i; + + data = dev->data; + base = UINT_TO_ESAI(data->regmap); + + ret = esai_update_state(data, dir, DAI_STATE_RUNNING); + if (ret < 0) { + LOG_ERR("failed to transition to RUNNING"); + return -EINVAL; + } + + LOG_DBG("starting direction %d", dir); + + /* enable the FIFO */ + esai_tx_rx_enable_disable_fifo(base, dir, true); + + /* TODO: without this, the ESAI won't enter underrun + * but playing a song while doing pause-resume very + * fast seems to result in a degraded sound quality? + * + * TODO: for multiple channels, this needs to be changed. + */ + if (dir == DAI_DIR_TX) { + for (i = 0; i < 1; i++) { + ESAI_WriteData(base, 0x0); + } + } + + /* enable the transmitter/receiver */ + esai_tx_rx_enable_disable(base, dir, BIT(0), true); + + return 0; +} + +static int esai_trigger_stop(const struct device *dev, enum dai_dir dir) +{ + struct esai_data *data; + int ret; + ESAI_Type *base; + + data = dev->data; + base = UINT_TO_ESAI(data->regmap); + + ret = esai_update_state(data, dir, DAI_STATE_STOPPING); + if (ret < 0) { + LOG_ERR("failed to transition to STOPPING"); + return -EINVAL; + } + + LOG_DBG("stopping direction %d", dir); + + /* disable transmitter/receiver */ + esai_tx_rx_enable_disable(base, dir, BIT(0), false); + + /* disable FIFO */ + esai_tx_rx_enable_disable_fifo(base, dir, false); + + return 0; +} + +static int esai_trigger(const struct device *dev, + enum dai_dir dir, + enum dai_trigger_cmd cmd) +{ + /* TX/RX should be triggered individually */ + if (dir != DAI_DIR_RX && dir != DAI_DIR_TX) { + LOG_ERR("invalid direction: %d", dir); + return -EINVAL; + } + + switch (cmd) { + case DAI_TRIGGER_START: + return esai_trigger_start(dev, dir); + case DAI_TRIGGER_PAUSE: + case DAI_TRIGGER_STOP: + return esai_trigger_stop(dev, dir); + case DAI_TRIGGER_PRE_START: + case DAI_TRIGGER_COPY: + /* nothing to do here, return success code */ + return 0; + default: + LOG_ERR("invalid trigger command: %d", cmd); + return -EINVAL; + } + + return 0; +} + +static const struct dai_properties + *esai_get_properties(const struct device *dev, enum dai_dir dir, int stream_id) +{ + const struct esai_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; + } +} + +static int esai_probe(const struct device *dev) +{ + /* nothing to be done here but mandatory to implement */ + return 0; +} + +static int esai_remove(const struct device *dev) +{ + /* nothing to be done here but mandatory to implement */ + return 0; +} + +static const struct dai_driver_api esai_api = { + .config_set = esai_config_set, + .config_get = esai_config_get, + .trigger = esai_trigger, + .get_properties = esai_get_properties, + .probe = esai_probe, + .remove = esai_remove, +}; + +static int esai_init(const struct device *dev) +{ + const struct esai_config *cfg; + struct esai_data *data; + int ret; + + cfg = dev->config; + data = dev->data; + + device_map(&data->regmap, cfg->regmap_phys, cfg->regmap_size, K_MEM_CACHE_NONE); + + ESAI_Reset(UINT_TO_ESAI(data->regmap)); + + ret = esai_parse_pinmodes(cfg, data); + if (ret < 0) { + return ret; + } + + return 0; +} + +#define ESAI_INIT(inst) \ + \ +BUILD_ASSERT(ESAI_TX_FIFO_WATERMARK(inst) >= 1 && \ + ESAI_TX_FIFO_WATERMARK(inst) <= _ESAI_FIFO_DEPTH(inst), \ + "invalid TX watermark value"); \ + \ +BUILD_ASSERT(ESAI_RX_FIFO_WATERMARK(inst) >= 1 && \ + ESAI_RX_FIFO_WATERMARK(inst) <= _ESAI_FIFO_DEPTH(inst), \ + "invalid RX watermark value"); \ + \ +BUILD_ASSERT(ESAI_FIFO_DEPTH(inst) >= 1 && \ + ESAI_FIFO_DEPTH(inst) <= _ESAI_FIFO_DEPTH(inst), \ + "invalid FIFO depth value"); \ + \ +BUILD_ASSERT(ESAI_WORD_WIDTH(inst) == 8 || \ + ESAI_WORD_WIDTH(inst) == 12 || \ + ESAI_WORD_WIDTH(inst) == 16 || \ + ESAI_WORD_WIDTH(inst) == 20 || \ + ESAI_WORD_WIDTH(inst) == 24, \ + "invalid word width value"); \ + \ +static const struct dai_properties esai_tx_props_##inst = { \ + .fifo_address = ESAI_TX_FIFO_BASE(inst), \ + .fifo_depth = ESAI_FIFO_DEPTH(inst) * 4, \ + .dma_hs_id = ESAI_TX_RX_DMA_HANDSHAKE(inst, tx), \ +}; \ + \ +static const struct dai_properties esai_rx_props_##inst = { \ + .fifo_address = ESAI_RX_FIFO_BASE(inst), \ + .fifo_depth = ESAI_FIFO_DEPTH(inst) * 4, \ + .dma_hs_id = ESAI_TX_RX_DMA_HANDSHAKE(inst, rx), \ +}; \ + \ +static uint32_t pinmodes_##inst[] = \ + DT_INST_PROP_OR(inst, esai_pin_modes, {}); \ + \ +BUILD_ASSERT(ARRAY_SIZE(pinmodes_##inst) % 2 == 0, \ + "bad pinmask array size"); \ + \ +static uint32_t clock_cfg_##inst[] = \ + DT_INST_PROP_OR(inst, esai_clock_configuration, {}); \ + \ +BUILD_ASSERT(ARRAY_SIZE(clock_cfg_##inst) % 2 == 0, \ + "bad clock configuration array size"); \ + \ +static struct esai_config esai_config_##inst = { \ + .regmap_phys = DT_INST_REG_ADDR(inst), \ + .regmap_size = DT_INST_REG_SIZE(inst), \ + .tx_props = &esai_tx_props_##inst, \ + .rx_props = &esai_rx_props_##inst, \ + .tx_fifo_watermark = ESAI_TX_FIFO_WATERMARK(inst), \ + .rx_fifo_watermark = ESAI_RX_FIFO_WATERMARK(inst), \ + .word_width = ESAI_WORD_WIDTH(inst), \ + .pinmodes = pinmodes_##inst, \ + .pinmodes_size = ARRAY_SIZE(pinmodes_##inst), \ + .clock_cfg = clock_cfg_##inst, \ + .clock_cfg_size = ARRAY_SIZE(clock_cfg_##inst), \ +}; \ + \ +static struct esai_data esai_data_##inst = { \ + .cfg.type = DAI_IMX_ESAI, \ + .cfg.dai_index = DT_INST_PROP_OR(inst, dai_index, 0), \ +}; \ + \ +DEVICE_DT_INST_DEFINE(inst, &esai_init, NULL, \ + &esai_data_##inst, &esai_config_##inst, \ + POST_KERNEL, CONFIG_DAI_INIT_PRIORITY, \ + &esai_api); \ + +DT_INST_FOREACH_STATUS_OKAY(ESAI_INIT); diff --git a/drivers/dai/nxp/esai/esai.h b/drivers/dai/nxp/esai/esai.h new file mode 100644 index 000000000000..9b008d8a03b9 --- /dev/null +++ b/drivers/dai/nxp/esai/esai.h @@ -0,0 +1,533 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_DAI_NXP_ESAI_H_ +#define ZEPHYR_DRIVERS_DAI_NXP_ESAI_H_ + +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(nxp_dai_esai); + +/* used for binding the driver */ +#define DT_DRV_COMPAT nxp_dai_esai + +/* 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 */ + +/* macros used for parsing DTS data */ + +#define _ESAI_FIFO_DEPTH(inst)\ + FSL_FEATURE_ESAI_FIFO_SIZEn(UINT_TO_ESAI(DT_INST_REG_ADDR(inst))) + +/* used to fetch the depth of the FIFO. If the "fifo-depth" property is + * not specified, the FIFO depth that will be reported to the upper layers + * will be 128 * 4 (which is the maximum value, or, well, the actual FIFO + * depth) + */ +#define ESAI_FIFO_DEPTH(inst)\ + DT_INST_PROP_OR(inst, fifo_depth, _ESAI_FIFO_DEPTH(inst)) + +/* used to fetch the TX FIFO watermark value. If the "tx-fifo-watermark" + * property is not specified, this will be set to half of the FIFO depth. + */ +#define ESAI_TX_FIFO_WATERMARK(inst)\ + DT_INST_PROP_OR(inst, tx_fifo_watermark, (_ESAI_FIFO_DEPTH(inst) / 2)) + +/* used to fetch the RX FIFO watermark value. If the "rx-fifo-watermark" + * property is not specified, this will be set to half of the FIFO depth. + */ +#define ESAI_RX_FIFO_WATERMARK(inst)\ + DT_INST_PROP_OR(inst, rx_fifo_watermark, (_ESAI_FIFO_DEPTH(inst) / 2)) + +/* use to fetch the handshake value for a given direction. The handshake + * is computed as follows: + * handshake = CHANNEL_ID | (MUX_VALUE << 8) + * The channel ID and MUX value are each encoded in 8 bits. + */ +#define ESAI_TX_RX_DMA_HANDSHAKE(inst, dir)\ + ((DT_INST_DMAS_CELL_BY_NAME(inst, dir, channel) & GENMASK(7, 0)) |\ + ((DT_INST_DMAS_CELL_BY_NAME(inst, dir, mux) << 8) & GENMASK(15, 8))) + +/* used to fetch the word width. If the "word-width" property is not specified, + * this will default to 24. + */ +#define ESAI_WORD_WIDTH(inst) DT_INST_PROP_OR(inst, word_width, 24) + +/* utility macros */ + +/* convert uint to ESAI_Type * */ +#define UINT_TO_ESAI(x) ((ESAI_Type *)(uintptr_t)(x)) + +/* invert a clock's polarity. This works because a clock's polarity + * is expressed as a 0 or as a 1. + */ +#define ESAI_INVERT_POLARITY(polarity) (polarity) = !(polarity) + +#define _ESAI_SLOT_WORD_WIDTH_IS_VALID(width) (!(((width) - 8) % 4)) + +/* used to check if a slot/word width combination is valid */ +#define ESAI_SLOT_WORD_WIDTH_IS_VALID(slot_width, word_width)\ + (_ESAI_SLOT_WORD_WIDTH_IS_VALID(slot_width) && \ + _ESAI_SLOT_WORD_WIDTH_IS_VALID(word_width) && \ + ((word_width) < 32) && ((word_width) <= (slot_width))) + +/* used to convert slot/word width combination to a value that can be written + * to TCR's TSWS or RCR's RSWS. + */ +#define ESAI_SLOT_FORMAT(s, w)\ + ((w) < 24 ? ((s) - (w) + (((w) - 8) / 4)) : ((s) < 32 ? 0x1e : 0x1f)) + +/* used to compute the word alignment based on the word width value. + * This returns a value that can be written to TFCR's TWA or RFCR's + * RWA. + */ +#define ESAI_WORD_ALIGNMENT(word_width) ((32 - (word_width)) / 4) + +#define _ESAI_RX_FIFO_USAGE_EN(mask)\ + (((mask) << ESAI_RFCR_RE0_SHIFT) &\ + (ESAI_RFCR_RE0_MASK | ESAI_RFCR_RE1_MASK |\ + ESAI_RFCR_RE2_MASK | ESAI_RFCR_RE3_MASK)) + +#define _ESAI_TX_FIFO_USAGE_EN(mask)\ + (((mask) << ESAI_TFCR_TE0_SHIFT) &\ + (ESAI_TFCR_TE0_MASK | ESAI_TFCR_TE1_MASK | ESAI_TFCR_TE2_MASK |\ + ESAI_TFCR_TE3_MASK | ESAI_TFCR_TE4_MASK | ESAI_TFCR_TE5_MASK)) + +/* used to fetch the mask for setting TX/RX FIFO usage. By FIFO usage + * we mean we allow receivers/transmitters to use the "global" TX/RX + * FIFO (i.e: the FIFO that's common to all transmitters/receivers). + * More specifically, this macro returns the mask required for setting + * TFCR's TEx fields or RFCR's REx fields. + */ +#define ESAI_TX_RX_FIFO_USAGE_EN(dir, mask)\ + ((dir) == DAI_DIR_TX ? _ESAI_TX_FIFO_USAGE_EN(mask) :\ + _ESAI_RX_FIFO_USAGE_EN(mask)) + +#define _ESAI_TX_EN(mask)\ + (((mask) << ESAI_TCR_TE0_SHIFT) &\ + (ESAI_TCR_TE0_MASK | ESAI_TCR_TE1_MASK | ESAI_TCR_TE2_MASK |\ + ESAI_TCR_TE3_MASK | ESAI_TCR_TE4_MASK | ESAI_TCR_TE5_MASK)) + +#define _ESAI_RX_EN(mask)\ + (((mask) << ESAI_RCR_RE0_SHIFT) &\ + (ESAI_RCR_RE0_MASK | ESAI_RCR_RE1_MASK | ESAI_RCR_RE2_MASK |\ + ESAI_RCR_RE3_MASK)) + +/* used to fetch the mask for enabling transmitters/receivers. + * More specifically, this refers to TCR's TEx bits or RCR's REx + * bits. + */ +#define ESAI_TX_RX_EN(dir, mask)\ + ((dir) == DAI_DIR_TX ? _ESAI_TX_EN(mask) : _ESAI_RX_EN(mask)) + +/* used to fetch the base address of the TX FIFO */ +#define ESAI_TX_FIFO_BASE(inst)\ + POINTER_TO_UINT(&(UINT_TO_ESAI(DT_INST_REG_ADDR(inst))->ETDR)) + +/* used to fetch the base address of the RX FIFO */ +#define ESAI_RX_FIFO_BASE(inst)\ + POINTER_TO_UINT(&(UINT_TO_ESAI(DT_INST_REG_ADDR(inst))->ERDR)) + +/* used to check if an ESAI pin is used. An ESAI pin is considered to + * be used if PDC and PC bits for that pin are set (i.e: pin is in ESAI + * mode). + * + * The ESAI pins support 4 functionalities which can be configured + * via PCRC and PRRC: + * 1) Disconnected + * 2) GPIO input + * 3) GPIO output + * 4) ESAI + */ +#define ESAI_PIN_IS_USED(data, which)\ + (((data)->pcrc & BIT(which)) && ((data->prrc) & BIT(which))) + +struct esai_data { + mm_reg_t regmap; + struct dai_config cfg; + /* transmitter state */ + enum dai_state tx_state; + /* receiver state */ + enum dai_state rx_state; + /* value to be committed to PRRC. This is computed + * during esai_init() and committed during config_set() + * stage. + */ + uint32_t prrc; + /* value to be committed to PCRC. Computed and committed + * during the same stages as PRRC. + */ + uint32_t pcrc; +}; + +struct esai_config { + uint32_t regmap_phys; + uint32_t regmap_size; + const struct dai_properties *tx_props; + const struct dai_properties *rx_props; + uint32_t rx_fifo_watermark; + uint32_t tx_fifo_watermark; + uint32_t word_width; + uint32_t *pinmodes; + uint32_t pinmodes_size; + uint32_t *clock_cfg; + uint32_t clock_cfg_size; +}; + +/* this needs to perfectly match SOF's struct sof_ipc_dai_esai_params */ +struct esai_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; +}; + +struct esai_transceiver_config { + /* enable/disable the HCLK prescaler */ + bool hclk_prescaler_en; + /* controls the divison value of HCLK (i.e: TPM0-TPM7) */ + uint32_t hclk_div_ratio; + /* controls the division value of HCLK before reaching + * BCLK consumers (i.e: TFP0-TFP3) + */ + uint32_t bclk_div_ratio; + /* should the HCLK divison be bypassed or not? + * If in bypass, HCLK pad will be the same as EXTAL + */ + bool hclk_bypass; + + /* HCLK direction - input or output */ + esai_clock_direction_t hclk_dir; + /* HCLK source - EXTAL or IPG clock */ + esai_hclk_source_t hclk_src; + /* HCLK polarity - LOW or HIGH */ + esai_clock_polarity_t hclk_polarity; + + /* BCLK direction - input or output */ + esai_clock_direction_t bclk_dir; + /* BCLK polarity - LOW or HIGH */ + esai_clock_polarity_t bclk_polarity; + + /* FSYNC direction - input or output */ + esai_clock_direction_t fsync_dir; + /* FSYNC polarity - LOW or HIGH */ + esai_clock_polarity_t fsync_polarity; + + /* should FSYNC be bit-wide or word-wide? */ + bool fsync_is_bit_wide; + /* enable/disable padding word with zeros. If + * disabled, pad will be done using last/first + * bit - see TCR's PADC bit for more info. + */ + bool zero_pad_en; + /* should FSYNC be asserted before MSB transmission + * or alongside it? + */ + bool fsync_early; + + /* FSYNC divison value - for network mode this is + * the same as the number of slots - 1. + */ + uint32_t fsync_div; + + /* slot format - see TCR's TSWS or RCR's RSWS */ + esai_slot_format_t slot_format; + /* mode - network or normal + * TODO: at the moment, only network mode is supported. + */ + esai_mode_t mode; + + /* controls whether MSB or LSB is transmitted first */ + esai_shift_direction_t data_order; + + /* controls the word alignment inside a slot. If enabled + * word is left-aligned, otherwise it will be right-aligned. + * For details, see TCR/RCR's TWA/RWA. + */ + bool data_left_aligned; + /* TX/RX watermark value */ + uint32_t watermark; + + /* concatenation of TSMA+TSMB/RSMA+RSMB. Controls which + * slots should be High-Z or data. + */ + uint32_t slot_mask; + /* controls the alignment of data written to FIFO. + * See TFCR's TWA or RFCR's RWA for more details. + */ + uint32_t word_alignment; +}; + +static int esai_parse_clock_config(const struct esai_config *cfg, + struct esai_transceiver_config *tx_cfg, + struct esai_transceiver_config *rx_cfg) +{ + int i; + uint32_t crt_clock, crt_dir; + + for (i = 0; i < cfg->clock_cfg_size; i += 2) { + crt_clock = cfg->clock_cfg[i]; + crt_dir = cfg->clock_cfg[i + 1]; + + /* sanity checks */ + if (crt_clock > ESAI_CLOCK_FST) { + LOG_ERR("invalid clock configuration ID: %d", crt_clock); + return -EINVAL; + } + + if (crt_dir > ESAI_CLOCK_OUTPUT) { + LOG_ERR("invalid clock configuration direction: %d", crt_dir); + return -EINVAL; + } + + switch (crt_clock) { + case ESAI_CLOCK_HCKT: + tx_cfg->hclk_dir = crt_dir; + break; + case ESAI_CLOCK_HCKR: + rx_cfg->hclk_dir = crt_dir; + break; + case ESAI_CLOCK_SCKT: + tx_cfg->bclk_dir = crt_dir; + break; + case ESAI_CLOCK_SCKR: + rx_cfg->bclk_dir = crt_dir; + break; + case ESAI_CLOCK_FST: + tx_cfg->fsync_dir = crt_dir; + break; + case ESAI_CLOCK_FSR: + rx_cfg->fsync_dir = crt_dir; + break; + } + } + + return 0; +} + +static int esai_parse_pinmodes(const struct esai_config *cfg, + struct esai_data *data) +{ + int i; + uint32_t pin, pin_mode; + + /* initially, the assumption is that all pins are in ESAI mode */ + data->pcrc = ESAI_PCRC_PC_MASK; + data->prrc = ESAI_PRRC_PDC_MASK; + + for (i = 0; i < cfg->pinmodes_size; i += 2) { + pin = cfg->pinmodes[i]; + pin_mode = cfg->pinmodes[i + 1]; + + if (pin > ESAI_PIN_SDO0 || pin_mode > ESAI_PIN_ESAI) { + return -EINVAL; + } + + switch (pin_mode) { + case ESAI_PIN_DISCONNECTED: + data->pcrc &= ~BIT(pin); + data->prrc &= ~BIT(pin); + break; + case ESAI_PIN_GPIO_INPUT: + data->pcrc &= ~BIT(pin); + break; + case ESAI_PIN_GPIO_OUTPUT: + data->prrc &= ~BIT(pin); + break; + case ESAI_PIN_ESAI: + /* nothing to be done here, this is the default */ + break; + } + } + + return 0; +} + +static inline uint32_t esai_get_state(struct esai_data *data, + enum dai_dir dir) +{ + if (dir == DAI_DIR_RX) { + return data->rx_state; + } else { + return data->tx_state; + } +} + +static inline int esai_update_state(struct esai_data *data, + enum dai_dir dir, enum dai_state new_state) +{ + enum dai_state old_state = esai_get_state(data, dir); + + LOG_DBG("attempting state transition from %d to %d", old_state, new_state); + + switch (new_state) { + case DAI_STATE_NOT_READY: + /* initial state, transition is not 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_STOPPING && + old_state != DAI_STATE_READY) { + return -EPERM; + } + break; + case DAI_STATE_STOPPING: + if (old_state != DAI_STATE_RUNNING) { + return -EPERM; + } + break; + default: + LOG_ERR("invalid new state: %d", new_state); + return -EINVAL; + } + + if (dir == DAI_DIR_RX) { + data->rx_state = new_state; + } else { + data->tx_state = new_state; + } + + return 0; +} + +static inline void esai_tx_rx_enable_disable_fifo(ESAI_Type *base, + enum dai_dir dir, + bool enable) +{ + if (enable) { + if (dir == DAI_DIR_RX) { + base->RFCR |= ESAI_RFCR_RFE_MASK; + } else { + base->TFCR |= ESAI_TFCR_TFE_MASK; + } + } else { + if (dir == DAI_DIR_RX) { + base->RFCR &= ~ESAI_RFCR_RFE_MASK; + } else { + base->TFCR &= ~ESAI_TFCR_TFE_MASK; + } + } +} + +static inline void esai_tx_rx_enable_disable(ESAI_Type *base, + enum dai_dir dir, + uint32_t which, bool enable) +{ + uint32_t val = ESAI_TX_RX_EN(dir, which); + + if (enable) { + if (dir == DAI_DIR_RX) { + base->RCR |= val; + } else { + base->TCR |= val; + } + } else { + if (dir == DAI_DIR_RX) { + base->RCR &= ~val; + } else { + base->TCR &= ~val; + } + } +} + +static inline void esai_tx_rx_enable_disable_fifo_usage(ESAI_Type *base, + enum dai_dir dir, + uint32_t which, bool enable) +{ + uint32_t val = ESAI_TX_RX_FIFO_USAGE_EN(dir, which); + + if (enable) { + if (dir == DAI_DIR_RX) { + base->RFCR |= val; + } else { + base->TFCR |= val; + } + } else { + if (dir == DAI_DIR_RX) { + base->RFCR &= ~val; + } else { + base->TFCR &= ~val; + } + } +} + +static inline void esai_dump_xceiver_config(struct esai_transceiver_config *cfg) +{ + LOG_DBG("HCLK prescaler enable: %d", cfg->hclk_prescaler_en); + LOG_DBG("HCLK divider ratio: %d", cfg->hclk_div_ratio); + LOG_DBG("BCLK divider ratio: %d", cfg->bclk_div_ratio); + LOG_DBG("HCLK bypass: %d", cfg->hclk_bypass); + + LOG_DBG("HCLK direction: %d", cfg->hclk_dir); + LOG_DBG("HCLK source: %d", cfg->hclk_src); + LOG_DBG("HCLK polarity: %d", cfg->hclk_polarity); + + LOG_DBG("BCLK direction: %d", cfg->bclk_dir); + LOG_DBG("BCLK polarity: %d", cfg->bclk_polarity); + + LOG_DBG("FSYNC direction: %d", cfg->fsync_dir); + LOG_DBG("FSYNC polarity: %d", cfg->fsync_polarity); + + LOG_DBG("FSYNC is bit wide: %d", cfg->fsync_is_bit_wide); + LOG_DBG("zero pad enable: %d", cfg->zero_pad_en); + LOG_DBG("FSYNC asserted early: %d", cfg->fsync_early); + + LOG_DBG("watermark: %d", cfg->watermark); + LOG_DBG("slot mask: 0x%x", cfg->slot_mask); + LOG_DBG("word alignment: 0x%x", cfg->word_alignment); +} + +static inline void esai_dump_register_data(ESAI_Type *base) +{ + LOG_DBG("ECR: 0x%x", base->ECR); + LOG_DBG("ESR: 0x%x", base->ESR); + LOG_DBG("TFCR: 0x%x", base->TFCR); + LOG_DBG("TFSR: 0x%x", base->TFSR); + LOG_DBG("RFCR: 0x%x", base->RFCR); + LOG_DBG("RFSR: 0x%x", base->RFSR); + LOG_DBG("TSR: 0x%x", base->TSR); + LOG_DBG("SAISR: 0x%x", base->SAISR); + LOG_DBG("SAICR: 0x%x", base->SAICR); + LOG_DBG("TCR: 0x%x", base->TCR); + LOG_DBG("TCCR: 0x%x", base->TCCR); + LOG_DBG("RCR: 0x%x", base->RCR); + LOG_DBG("RCCR: 0x%x", base->RCCR); + LOG_DBG("TSMA: 0x%x", base->TSMA); + LOG_DBG("TSMB: 0x%x", base->TSMB); + LOG_DBG("RSMA: 0x%x", base->RSMA); + LOG_DBG("RSMB: 0x%x", base->RSMB); + LOG_DBG("PRRC: 0x%x", base->PRRC); + LOG_DBG("PCRC: 0x%x", base->PCRC); +} + +#endif /* ZEPHYR_DRIVERS_DAI_NXP_ESAI_H_ */ diff --git a/dts/bindings/dai/nxp,dai-esai.yaml b/dts/bindings/dai/nxp,dai-esai.yaml new file mode 100644 index 000000000000..b179a8c6b9ba --- /dev/null +++ b/dts/bindings/dai/nxp,dai-esai.yaml @@ -0,0 +1,75 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP Enhanced Serial Audio Interface (ESAI) node + +compatible: "nxp,dai-esai" + +include: base.yaml + +properties: + reg: + required: true + 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. + tx-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. If unspecified, + the TX FIFO watermark will be set to DEFAULT_FIFO_DEPTH / 2. + rx-fifo-watermark: + type: int + description: | + Use this property to specify the watermark value for the RX + FIFO. This values 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. If unspecified, + the RX FIFO watermark will be set to DEFAULT_FIFO_DEPTH / 2. + fifo-depth: + type: int + description: | + Use this property to set the FIFO depth that will be reported + to upper layer 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 directly as 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_DETPH + instead of this value so use with caution. If unsure, it's better to + not use this property at all, in which case the reported value will be + DEFAULT_FIFO_DEPTH. + word-width: + type: int + description: | + This property is used to specify the width of a word. If unspecified, + the word width used will be 24. + esai-pin-modes: + type: array + description: | + This property is used to configure the ESAI pins. Each ESAI pin + supports 4 modes: + 1) DISCONNECTED (PDC[i] = 0, PC[i] = 0) + 2) GPIO input (PDC[i] = 0, PC[i] = 1) + 3) GPIO output (PDC[i] = 1, PC[i] = 0) + 4) ESAI (PDC[i] = 1, PC[i] = 1) + If pin is not used then DISCONNECTED mode should be used for said pin. + If unsure, don't specify this property at all. By default, all pins will + be set to ESAI mode. + esai-clock-configuration: + type: array + description: | + Use this property to configure the directions of the ESAI clocks (HCLK, BCLK, FSYNC). + This provides extra flexibility since the bespoke configuration is not direction-based. + The values from this array will overwrite the values set through the bespoke + configuration. If unspecified, the values from the bespoke configuration will be used. diff --git a/include/zephyr/dt-bindings/dai/esai.h b/include/zephyr/dt-bindings/dai/esai.h new file mode 100644 index 000000000000..8dd45a91bc82 --- /dev/null +++ b/include/zephyr/dt-bindings/dai/esai.h @@ -0,0 +1,54 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _INCLUDE_ZEPHYR_DT_BINDINGS_DAI_ESAI_H_ +#define _INCLUDE_ZEPHYR_DT_BINDINGS_DAI_ESAI_H_ + +/* ESAI pin IDs + * the values of these macros are meant to match + * the bit position from PCRC/PRRC's PC/PDC associated + * with each of these pins. + */ +#define ESAI_PIN_SCKR 0 +#define ESAI_PIN_FSR 1 +#define ESAI_PIN_HCKR 2 +#define ESAI_PIN_SCKT 3 +#define ESAI_PIN_FST 4 +#define ESAI_PIN_HCKT 5 +#define ESAI_PIN_SDO5_SDI0 6 +#define ESAI_PIN_SDO4_SDI1 7 +#define ESAI_PIN_SDO3_SDI2 8 +#define ESAI_PIN_SDO2_SDI3 9 +#define ESAI_PIN_SDO1 10 +#define ESAI_PIN_SDO0 11 + +/* ESAI pin modes + * the values of these macros are set according to + * the following table: + * + * PDC = 0, PC = 0 => DISCONNECTED (0) + * PDC = 0, PC = 1 => GPIO INPUT (1) + * PDC = 1, PC = 0 => GPIO OUTUT (2) + * PDC = 1, PC = 1 => ESAI (3) + */ +#define ESAI_PIN_DISCONNECTED 0 +#define ESAI_PIN_GPIO_INPUT 1 +#define ESAI_PIN_GPIO_OUTPUT 2 +#define ESAI_PIN_ESAI 3 + +/* ESAI clock IDs */ +#define ESAI_CLOCK_HCKT 0 +#define ESAI_CLOCK_HCKR 1 +#define ESAI_CLOCK_SCKR 2 +#define ESAI_CLOCK_SCKT 3 +#define ESAI_CLOCK_FSR 4 +#define ESAI_CLOCK_FST 5 + +/* ESAI clock directions */ +#define ESAI_CLOCK_INPUT 0 +#define ESAI_CLOCK_OUTPUT 1 + +#endif /* _INCLUDE_ZEPHYR_DT_BINDINGS_DAI_ESAI_H_ */ From 360e162cb585ca20da04bb792b51183b4e38e6c8 Mon Sep 17 00:00:00 2001 From: Laurentiu Mihalcea Date: Mon, 12 Feb 2024 14:28:08 +0200 Subject: [PATCH 3/3] dts: xtensa: nxp_imx8: add ESAI0 node Add node for NXP's i.MX8QM/i.MX8QXP AUDIO ESAI0 IP. Signed-off-by: Laurentiu Mihalcea --- dts/xtensa/nxp/nxp_imx8.dtsi | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dts/xtensa/nxp/nxp_imx8.dtsi b/dts/xtensa/nxp/nxp_imx8.dtsi index ec97e8d1816b..87713a71200a 100644 --- a/dts/xtensa/nxp/nxp_imx8.dtsi +++ b/dts/xtensa/nxp/nxp_imx8.dtsi @@ -7,6 +7,7 @@ #include #include #include +#include / { cpus { @@ -135,6 +136,20 @@ status = "disabled"; }; + esai0: dai@59010000 { + compatible = "nxp,dai-esai"; + reg = <0x59010000 DT_SIZE_K(64)>; + dmas = <&edma0 7 0>, <&edma0 6 0>; + dma-names = "tx", "rx"; + esai-pin-modes = , + , + , + , + , + ; + status = "disabled"; + }; + /* LSIO MU2, used to interact with the SCFW */ scu_mu: mailbox@5d1d0000 { reg = <0x5d1d0000 DT_SIZE_K(64)>;