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:
parent
0c138b7d28
commit
bd9b3c67b2
8 changed files with 1444 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
5
drivers/dai/nxp/esai/CMakeLists.txt
Normal file
5
drivers/dai/nxp/esai/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Copyright 2024 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
zephyr_library_sources(esai.c)
|
9
drivers/dai/nxp/esai/Kconfig.esai
Normal file
9
drivers/dai/nxp/esai/Kconfig.esai
Normal 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
766
drivers/dai/nxp/esai/esai.c
Normal 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
533
drivers/dai/nxp/esai/esai.h
Normal 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_ */
|
75
dts/bindings/dai/nxp,dai-esai.yaml
Normal file
75
dts/bindings/dai/nxp,dai-esai.yaml
Normal 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.
|
54
include/zephyr/dt-bindings/dai/esai.h
Normal file
54
include/zephyr/dt-bindings/dai/esai.h
Normal 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_ */
|
Loading…
Add table
Add a link
Reference in a new issue