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 <laurentiu.mihalcea@nxp.com>
This commit is contained in:
Laurentiu Mihalcea 2024-02-08 17:25:52 +02:00 committed by Fabio Baltieri
commit bd9b3c67b2
8 changed files with 1444 additions and 0 deletions

View file

@ -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)

View file

@ -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

View file

@ -0,0 +1,5 @@
# Copyright 2024 NXP
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(esai.c)

View file

@ -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.

766
drivers/dai/nxp/esai/esai.c Normal file
View file

@ -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);

533
drivers/dai/nxp/esai/esai.h Normal file
View file

@ -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 <zephyr/logging/log.h>
#include <zephyr/drivers/dai.h>
#include <zephyr/device.h>
#include <zephyr/dt-bindings/dai/esai.h>
#include <fsl_esai.h>
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_ */

View file

@ -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.

View file

@ -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_ */