From ae8b17d2bb85dfdc8cb170eb0efb395f19aed496 Mon Sep 17 00:00:00 2001 From: Marcin Szkudlinski Date: Wed, 11 May 2022 16:42:40 +0200 Subject: [PATCH] Drivers: dmic-dai: Driver for Intel DMIC This is a driver for Intel Digital Microphone TODO: - volume rampup - TPLG config Signed-off-by: Marcin Szkudlinski --- drivers/dai/CMakeLists.txt | 1 + drivers/dai/Kconfig | 1 + drivers/dai/intel/dmic/CMakeLists.txt | 5 + drivers/dai/intel/dmic/Kconfig.dmic | 76 +++ drivers/dai/intel/dmic/dmic.c | 682 ++++++++++++++++++++++++++ drivers/dai/intel/dmic/dmic.h | 486 ++++++++++++++++++ drivers/dai/intel/dmic/dmic_nhlt.c | 503 +++++++++++++++++++ dts/bindings/dai/dai,intel,dmic.yaml | 19 + 8 files changed, 1773 insertions(+) create mode 100644 drivers/dai/intel/dmic/CMakeLists.txt create mode 100644 drivers/dai/intel/dmic/Kconfig.dmic create mode 100644 drivers/dai/intel/dmic/dmic.c create mode 100644 drivers/dai/intel/dmic/dmic.h create mode 100644 drivers/dai/intel/dmic/dmic_nhlt.c create mode 100644 dts/bindings/dai/dai,intel,dmic.yaml diff --git a/drivers/dai/CMakeLists.txt b/drivers/dai/CMakeLists.txt index dd622d69c0d..1cf95ee2de0 100644 --- a/drivers/dai/CMakeLists.txt +++ b/drivers/dai/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory_ifdef(CONFIG_DAI_INTEL_SSP intel/ssp) add_subdirectory_ifdef(CONFIG_DAI_INTEL_ALH intel/alh) +add_subdirectory_ifdef(CONFIG_DAI_INTEL_DMIC intel/dmic) diff --git a/drivers/dai/Kconfig b/drivers/dai/Kconfig index 24489662d93..84c79511f27 100644 --- a/drivers/dai/Kconfig +++ b/drivers/dai/Kconfig @@ -27,5 +27,6 @@ comment "Device Drivers" source "drivers/dai/intel/ssp/Kconfig.ssp" source "drivers/dai/intel/alh/Kconfig.alh" +source "drivers/dai/intel/dmic/Kconfig.dmic" endif # DAI diff --git a/drivers/dai/intel/dmic/CMakeLists.txt b/drivers/dai/intel/dmic/CMakeLists.txt new file mode 100644 index 00000000000..a3bd63f5d00 --- /dev/null +++ b/drivers/dai/intel/dmic/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_DAI_INTEL_DMIC dmic.c) +zephyr_library_sources_ifdef(CONFIG_DAI_INTEL_DMIC_NHLT dmic_nhlt.c) diff --git a/drivers/dai/intel/dmic/Kconfig.dmic b/drivers/dai/intel/dmic/Kconfig.dmic new file mode 100644 index 00000000000..63c1a02e7aa --- /dev/null +++ b/drivers/dai/intel/dmic/Kconfig.dmic @@ -0,0 +1,76 @@ +# SOF DMIC configuration options + +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +config DAI_INTEL_DMIC + bool "Intel digital PDM microphone driver support for DAI interface" + help + Enable Intel digital PDM microphone driver for DAI interface + +if DAI_INTEL_DMIC + +choice + prompt "Driver operation mode" + default DAI_INTEL_DMIC_NHLT + help + The driver can support two operation modes. + 1. A HW registers dump blob that is passed via IPC + 2. DAI tokens those describe the use case PCM format + and PDM bus and microphone parameters + +config DAI_INTEL_DMIC_NHLT + bool "Use NHLT DMIC blob" + help + All registers configuration is retrieved from blob. The + number of channels, sample rate, and PCM format are + defined in the blob and there are no runtime made + configuration choices. + +config DAI_INTEL_DMIC_TPLG_PARAMS + bool "Use parameters from topology - WIP" + help + All registers configuration is computed on the fly + based on use case and microphone datasheet parameters + and topology defined PCM format. The parameters are + easy to to customize in the topology. + WORK IN PROGRESS, not enabled in the driver yet + +endchoice + +config DAI_DMIC_HAS_OWNERSHIP + bool "Use DMIC ownership claim/release" + default n + help + a feature introduced in ACE1.5 hardware + dmic ownership must be claimed before use of dmic + +config DAI_DMIC_HAS_MULTIPLE_LINE_SYNC + bool "Use DMIC sync for multiple lines" + default n + help + a feature introduced in ACE1.5 hardware + dmic sync registers must be set before use of dmic + +config DAI_DMIC_HW_CONTROLLERS + int "Number of hardware controllers in the system" + default 2 + +config DAI_DMIC_HW_FIFOS + int "Number of stream FIFOs in DMIC controller" + default 2 + +config DAI_DMIC_HW_IOCLK + int "IO Clock value for DMIC" + default 19200000 + +config DAI_DMIC_PLATFORM_SYNC_PERIOD + int "Sync period per platform" + default 4000 + help + DMIC sync period used for: + CONFIG_DAI_DMIC_HW_IOCLK / CONFIG_DAI_DMIC_PLATFORM_SYNC_PERIOD + From spec: E.g. for 19.2 MHz XTAL oscillator clock, 4 KHz sync period, + the value to be programmed is 4799 (12BFh) + +endif diff --git a/drivers/dai/intel/dmic/dmic.c b/drivers/dai/intel/dmic/dmic.c new file mode 100644 index 00000000000..8125e946e2f --- /dev/null +++ b/drivers/dai/intel/dmic/dmic.c @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT intel_dai_dmic +#define LOG_DOMAIN dai_intel_dmic +#include +LOG_MODULE_REGISTER(LOG_DOMAIN); + +#include +#include +#include +#include +#include + +#include + +#include "dmic.h" + +/* Base addresses (in PDM scope) of 2ch PDM controllers and coefficient RAM. */ +static const uint32_t base[4] = {PDM0, PDM1, PDM2, PDM3}; + +/* global data shared between all dmic instances */ +struct dai_dmic_global_shared dai_dmic_global; + +/* Helper macro to read 64-bit data using two 32-bit data read */ +#define sys_read64(addr) (((uint64_t)(sys_read32(addr + 4)) << 32) | \ + sys_read32(addr)) + +int dai_dmic_set_config_nhlt(struct dai_intel_dmic *dmic, const void *spec_config); + +static void dai_dmic_update_bits(const struct dai_intel_dmic *dmic, + uint32_t reg, uint32_t mask, uint32_t val) +{ + uint32_t dest = dmic->reg_base + reg; + + LOG_INF("%s base %x, reg %x, mask %x, value %x", __func__, + dmic->reg_base, reg, mask, val); + + sys_write32((sys_read32(dest) & (~mask)) | (val & mask), dest); +} + +static inline void dai_dmic_write(const struct dai_intel_dmic *dmic, + uint32_t reg, uint32_t val) +{ + sys_write32(val, dmic->reg_base + reg); +} + +static inline uint32_t dai_dmic_read(const struct dai_intel_dmic *dmic, + uint32_t reg) +{ + return sys_read32(dmic->reg_base + reg); +} + +#if CONFIG_DAI_DMIC_HAS_OWNERSHIP +static inline void dai_dmic_claim_ownership(const struct dai_intel_dmic *dmic) +{ + /* DMIC Owner Select to DSP */ + sys_write32(sys_read32(dmic->shim_base + DMICLCTL_OFFSET) | + DMICLCTL_OSEL(0x3), dmic->shim_base + DMICLCTL_OFFSET); +} + +static inline void dai_dmic_release_ownership(const struct dai_intel_dmic *dmic) +{ + /* DMIC Owner Select back to Host CPU + DSP */ + sys_write32(sys_read32(dmic->shim_base + DMICLCTL_OFFSET) & + ~DMICLCTL_OSEL(0x0), dmic->shim_base + DMICLCTL_OFFSET); +} + +#else /* CONFIG_DAI_DMIC_HAS_OWNERSHIP */ + +static inline void dai_dmic_claim_ownership(const struct dai_intel_dmic *dmic) {} +static inline void dai_dmic_release_ownership(const struct dai_intel_dmic *dmic) {} + +#endif /* CONFIG_DAI_DMIC_HAS_OWNERSHIP */ + +#if CONFIG_DAI_DMIC_HAS_MULTIPLE_LINE_SYNC +static inline void dai_dmic_set_sync_period(uint32_t period, const struct dai_intel_dmic *dmic) +{ + uint32_t val = CONFIG_DAI_DMIC_HW_IOCLK / period - 1; + + /* DMIC Change sync period */ + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) | DMICSYNC_SYNCPRD(val), + dmic->shim_base + DMICSYNC_OFFSET); + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) | DMICSYNC_CMDSYNC, + dmic->shim_base + DMICSYNC_OFFSET); +} + +static inline void dai_dmic_clear_sync_period(const struct dai_intel_dmic *dmic) +{ + /* DMIC Clean sync period */ + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) & ~DMICSYNC_SYNCPRD(0x0000), + dmic->shim_base + DMICSYNC_OFFSET); + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) & ~DMICSYNC_CMDSYNC, + dmic->shim_base + DMICSYNC_OFFSET); + +} + +/* Preparing for command synchronization on multiple link segments */ +static inline void dai_dmic_sync_prepare(const struct dai_intel_dmic *dmic) +{ + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) | DMICSYNC_CMDSYNC, + dmic->shim_base + DMICSYNC_OFFSET); +} + +/* Trigering synchronization of command execution */ +static void dmic_sync_trigger(const struct dai_intel_dmic *dmic) +{ + __ASSERT_NO_MSG((sys_read32(dmic->shim_base + DMICSYNC_OFFSET) & DMICSYNC_CMDSYNC) != 0); + + sys_write32(sys_read32(dmic->shim_base + DMICSYNC_OFFSET) | + DMICSYNC_SYNCGO, dmic->shim_base + DMICSYNC_OFFSET); + /* waiting for CMDSYNC bit clearing */ + while (sys_read32(dmic->shim_base + DMICSYNC_OFFSET) & DMICSYNC_CMDSYNC) { + k_sleep(K_USEC(100)); + } +} + +#else /* CONFIG_DAI_DMIC_HAS_MULTIPLE_LINE_SYNC */ + +static inline void dai_dmic_set_sync_period(uint32_t period, const struct dai_intel_dmic *dmic) {} +static inline void dai_dmic_clear_sync_period(const struct dai_intel_dmic *dmic) {} +static inline void dai_dmic_sync_prepare(const struct dai_intel_dmic *dmic) {} +static void dmic_sync_trigger(const struct dai_intel_dmic *dmic) {} + +#endif /* CONFIG_DAI_DMIC_HAS_MULTIPLE_LINE_SYNC */ + +static void dai_dmic_stop_fifo_packers(struct dai_intel_dmic *dmic, + int fifo_index) +{ + /* Stop FIFO packers and set FIFO initialize bits */ + switch (fifo_index) { + case 0: + dai_dmic_update_bits(dmic, OUTCONTROL0, + OUTCONTROL0_SIP_BIT | OUTCONTROL0_FINIT_BIT, + OUTCONTROL0_FINIT_BIT); + break; + case 1: + dai_dmic_update_bits(dmic, OUTCONTROL1, + OUTCONTROL1_SIP_BIT | OUTCONTROL1_FINIT_BIT, + OUTCONTROL1_FINIT_BIT); + break; + } +} + +/* On DMIC IRQ event trace the status register that contains the status and + * error bit fields. + */ +static void dai_dmic_irq_handler(const void *data) +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *) data; + uint32_t val0; + uint32_t val1; + + /* Trace OUTSTAT0 register */ + val0 = dai_dmic_read(dmic, OUTSTAT0); + val1 = dai_dmic_read(dmic, OUTSTAT1); + LOG_INF("dmic_irq_handler(), OUTSTAT0 = 0x%x, OUTSTAT1 = 0x%x", val0, val1); + + if (val0 & OUTSTAT0_ROR_BIT) { + LOG_ERR("dmic_irq_handler(): full fifo A or PDM overrun"); + dai_dmic_write(dmic, OUTSTAT0, val0); + dai_dmic_stop_fifo_packers(dmic, 0); + } + + if (val1 & OUTSTAT1_ROR_BIT) { + LOG_ERR("dmic_irq_handler(): full fifo B or PDM overrun"); + dai_dmic_write(dmic, OUTSTAT1, val1); + dai_dmic_stop_fifo_packers(dmic, 1); + } +} + +static inline void dai_dmic_dis_clk_gating(const struct dai_intel_dmic *dmic) +{ + /* Disable DMIC clock gating */ + sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) | DMIC_DCGD), + dmic->shim_base + DMICLCTL_OFFSET); +} + +static inline void dai_dmic_en_clk_gating(const struct dai_intel_dmic *dmic) +{ + /* Enable DMIC clock gating */ + sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) & ~DMIC_DCGD), + dmic->shim_base + DMICLCTL_OFFSET); +} + +static inline void dai_dmic_en_power(const struct dai_intel_dmic *dmic) +{ + /* Enable DMIC power */ + sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) | DMICLCTL_SPA), + dmic->shim_base + DMICLCTL_OFFSET); + +} +static inline void dai_dmic_dis_power(const struct dai_intel_dmic *dmic) +{ + /* Disable DMIC power */ + sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) & (~DMICLCTL_SPA)), + dmic->shim_base + DMICLCTL_OFFSET); +} + +static int dai_dmic_probe(struct dai_intel_dmic *dmic) +{ + LOG_INF("dmic_probe()"); + + /* Set state, note there is no playback direction support */ + dmic->state = DAI_STATE_NOT_READY; + + /* Enable DMIC power */ + dai_dmic_en_power(dmic); + + /* Disable dynamic clock gating for dmic before touching any reg */ + dai_dmic_dis_clk_gating(dmic); + + /* DMIC Change sync period */ + dai_dmic_set_sync_period(CONFIG_DAI_DMIC_PLATFORM_SYNC_PERIOD, dmic); + + /* DMIC Owner Select to DSP */ + dai_dmic_claim_ownership(dmic); + + irq_enable(dmic->irq); + return 0; +} + +static int dai_dmic_remove(struct dai_intel_dmic *dmic) +{ + uint32_t active_fifos_mask = dai_dmic_global.active_fifos_mask; + uint32_t pause_mask = dai_dmic_global.pause_mask; + + LOG_INF("dmic_remove()"); + + irq_disable(dmic->irq); + + LOG_INF("dmic_remove(), dmic_active_fifos_mask = 0x%x, dmic_pause_mask = 0x%x", + active_fifos_mask, pause_mask); + + /* The next end tasks must be passed if another DAI FIFO still runs. + * Note: dai_put() function that calls remove() applies the spinlock + * so it is not needed here to protect access to mask bits. + */ + if (active_fifos_mask || pause_mask) + return 0; + + /* Disable DMIC clock and power */ + dai_dmic_en_clk_gating(dmic); + dai_dmic_dis_power(dmic); + + /* DMIC Clean sync period */ + dai_dmic_clear_sync_period(dmic); + + /* DMIC Owner Select back to Host CPU + DSP */ + dai_dmic_release_ownership(dmic); + + return 0; +} + +static int dai_dmic_timestamp_config(const struct device *dev, struct dai_ts_cfg *cfg) +{ + cfg->walclk_rate = CONFIG_DAI_DMIC_HW_IOCLK; + + return 0; +} + +static int dai_timestamp_dmic_start(const struct device *dev, struct dai_ts_cfg *cfg) +{ + uint32_t addr = TS_DMIC_LOCAL_TSCTRL; + uint32_t cdmas; + + /* Set DMIC timestamp registers */ + + /* First point CDMAS to GPDMA channel that is used by DMIC + * also clear NTK to be sure there is no old timestamp. + */ + cdmas = TS_LOCAL_TSCTRL_CDMAS(cfg->dma_chan_index + + cfg->dma_chan_count * cfg->dma_id); + sys_write32(TS_LOCAL_TSCTRL_NTK_BIT | cdmas, addr); + + /* Request on demand timestamp */ + sys_write32(TS_LOCAL_TSCTRL_ODTS_BIT | cdmas, addr); + + return 0; +} + +static int dai_timestamp_dmic_stop(const struct device *dev, struct dai_ts_cfg *cfg) +{ + /* Clear NTK and write zero to CDMAS */ + sys_write32(TS_LOCAL_TSCTRL_NTK_BIT, + TS_DMIC_LOCAL_TSCTRL); + return 0; +} + +static int dai_timestamp_dmic_get(const struct device *dev, struct dai_ts_cfg *cfg, + struct dai_ts_data *tsd) +{ + /* Read DMIC timestamp registers */ + uint32_t tsctrl = TS_DMIC_LOCAL_TSCTRL; + uint32_t ntk; + + /* Read SSP timestamp registers */ + ntk = sys_read32(tsctrl) & TS_LOCAL_TSCTRL_NTK_BIT; + if (!ntk) + goto out; + + /* NTK was set, get wall clock */ + tsd->walclk = sys_read64(TS_DMIC_LOCAL_WALCLK); + + /* Sample */ + tsd->sample = sys_read64(TS_DMIC_LOCAL_SAMPLE); + + /* Clear NTK to enable successive timestamps */ + sys_write32(TS_LOCAL_TSCTRL_NTK_BIT, tsctrl); + +out: + tsd->walclk_rate = cfg->walclk_rate; + if (!ntk) + return -ENODATA; + + return 0; +} + +static void dai_dmic_start(struct dai_intel_dmic *dmic) +{ + k_spinlock_key_t key; + int i; + int mic_a; + int mic_b; + int fir_a; + int fir_b; + + /* enable port */ + key = k_spin_lock(&dmic->lock); + LOG_DBG("dmic_start()"); + + dai_dmic_sync_prepare(dmic); + + switch (dmic->dai_config_params.dai_index) { + case 0: + LOG_INF("dmic_start(), dmic->fifo_a"); + /* Clear FIFO A initialize, Enable interrupts to DSP, + * Start FIFO A packer. + */ + dai_dmic_update_bits( + dmic, + OUTCONTROL0, + OUTCONTROL0_FINIT_BIT | OUTCONTROL0_SIP_BIT, + OUTCONTROL0_SIP_BIT); + break; + case 1: + LOG_INF("dmic_start(), dmic->fifo_b"); + /* Clear FIFO B initialize, Enable interrupts to DSP, + * Start FIFO B packer. + */ + dai_dmic_update_bits(dmic, OUTCONTROL1, + OUTCONTROL1_FINIT_BIT | OUTCONTROL1_SIP_BIT, + OUTCONTROL1_SIP_BIT); + } + + for (i = 0; i < CONFIG_DAI_DMIC_HW_CONTROLLERS; i++) { + mic_a = dmic->enable[i] & 1; + mic_b = (dmic->enable[i] & 2) >> 1; + fir_a = (dmic->enable[i] > 0) ? 1 : 0; + fir_b = (dmic->enable[i] > 0) ? 1 : 0; + LOG_INF("dmic_start(), pdm%d mic_a = %u, mic_b = %u", i, mic_a, mic_b); + + /* If both microphones are needed start them simultaneously + * to start them in sync. The reset may be cleared for another + * FIFO already. If only one mic, start them independently. + * This makes sure we do not clear start/en for another DAI. + */ + if (mic_a && mic_b) { + dai_dmic_update_bits(dmic, base[i] + CIC_CONTROL, + CIC_CONTROL_CIC_START_A_BIT | + CIC_CONTROL_CIC_START_B_BIT, + CIC_CONTROL_CIC_START_A(1) | + CIC_CONTROL_CIC_START_B(1)); + dai_dmic_update_bits(dmic, base[i] + MIC_CONTROL, + MIC_CONTROL_PDM_EN_A_BIT | + MIC_CONTROL_PDM_EN_B_BIT, + MIC_CONTROL_PDM_EN_A(1) | + MIC_CONTROL_PDM_EN_B(1)); + } else if (mic_a) { + dai_dmic_update_bits(dmic, base[i] + CIC_CONTROL, + CIC_CONTROL_CIC_START_A_BIT, + CIC_CONTROL_CIC_START_A(1)); + dai_dmic_update_bits(dmic, base[i] + MIC_CONTROL, + MIC_CONTROL_PDM_EN_A_BIT, + MIC_CONTROL_PDM_EN_A(1)); + } else if (mic_b) { + dai_dmic_update_bits(dmic, base[i] + CIC_CONTROL, + CIC_CONTROL_CIC_START_B_BIT, + CIC_CONTROL_CIC_START_B(1)); + dai_dmic_update_bits(dmic, base[i] + MIC_CONTROL, + MIC_CONTROL_PDM_EN_B_BIT, + MIC_CONTROL_PDM_EN_B(1)); + } + + switch (dmic->dai_config_params.dai_index) { + case 0: + dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_A, + FIR_CONTROL_A_START_BIT, + FIR_CONTROL_A_START(fir_a)); + break; + case 1: + dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_B, + FIR_CONTROL_B_START_BIT, + FIR_CONTROL_B_START(fir_b)); + break; + } + } + + /* Clear soft reset for all/used PDM controllers. This should + * start capture in sync. + */ + for (i = 0; i < CONFIG_DAI_DMIC_HW_CONTROLLERS; i++) { + dai_dmic_update_bits(dmic, base[i] + CIC_CONTROL, + CIC_CONTROL_SOFT_RESET_BIT, 0); + } + + /* Set bit dai->index */ + dai_dmic_global.active_fifos_mask |= BIT(dmic->dai_config_params.dai_index); + dai_dmic_global.pause_mask &= ~BIT(dmic->dai_config_params.dai_index); + + dmic->state = DAI_STATE_RUNNING; + k_spin_unlock(&dmic->lock, key); + + dmic_sync_trigger(dmic); + + LOG_INF("dmic_start(), dmic_active_fifos_mask = 0x%x", + dai_dmic_global.active_fifos_mask); +} + +static void dai_dmic_stop(struct dai_intel_dmic *dmic, bool stop_is_pause) +{ + k_spinlock_key_t key; + int i; + + LOG_DBG("dmic_stop()"); + key = k_spin_lock(&dmic->lock); + + dai_dmic_stop_fifo_packers(dmic, dmic->dai_config_params.dai_index); + + /* Set soft reset and mute on for all PDM controllers. */ + LOG_INF("dmic_stop(), dmic_active_fifos_mask = 0x%x", + dai_dmic_global.active_fifos_mask); + + /* Clear bit dmic->dai_config_params.dai_index for active FIFO. + * If stop for pause, set pause mask bit. + * If stop is not for pausing, it is safe to clear the pause bit. + */ + dai_dmic_global.active_fifos_mask &= ~BIT(dmic->dai_config_params.dai_index); + if (stop_is_pause) + dai_dmic_global.pause_mask |= BIT(dmic->dai_config_params.dai_index); + else + dai_dmic_global.pause_mask &= ~BIT(dmic->dai_config_params.dai_index); + + for (i = 0; i < CONFIG_DAI_DMIC_HW_CONTROLLERS; i++) { + /* Don't stop CIC yet if one FIFO remains active */ + if (dai_dmic_global.active_fifos_mask == 0) { + dai_dmic_update_bits(dmic, base[i] + CIC_CONTROL, + CIC_CONTROL_SOFT_RESET_BIT | + CIC_CONTROL_MIC_MUTE_BIT, + CIC_CONTROL_SOFT_RESET_BIT | + CIC_CONTROL_MIC_MUTE_BIT); + } + switch (dmic->dai_config_params.dai_index) { + case 0: + dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_A, + FIR_CONTROL_A_MUTE_BIT, + FIR_CONTROL_A_MUTE_BIT); + break; + case 1: + dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_B, + FIR_CONTROL_B_MUTE_BIT, + FIR_CONTROL_B_MUTE_BIT); + break; + } + } + + k_spin_unlock(&dmic->lock, key); +} + +const struct dai_properties *dai_dmic_get_properties(const struct device *dev, + enum dai_dir dir, + int stream_id) +{ + const struct dai_intel_dmic *dmic = (const struct dai_intel_dmic *)dev->data; + struct dai_properties *prop = (struct dai_properties *)dev->config; + + prop->fifo_address = dmic->fifo.offset; + prop->dma_hs_id = dmic->fifo.handshake; + prop->reg_init_delay = 0; + + return prop; +} + +static int dai_dmic_trigger(const struct device *dev, enum dai_dir dir, + enum dai_trigger_cmd cmd) +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; + + LOG_DBG("dmic_trigger()"); + + if (dir != DAI_DIR_RX) { + LOG_ERR("dmic_trigger(): direction != DAI_DIR_RX"); + return -EINVAL; + } + + switch (cmd) { + case DAI_TRIGGER_START: + if (dmic->state == DAI_STATE_PAUSED || + dmic->state == DAI_STATE_PRE_RUNNING) { + dai_dmic_start(dmic); + dmic->state = DAI_STATE_RUNNING; + } else { + LOG_ERR("dmic_trigger(): state is not prepare or paused, dmic->state = %u", + dmic->state); + } + break; + case DAI_TRIGGER_STOP: + dai_dmic_stop(dmic, false); + dmic->state = DAI_STATE_PRE_RUNNING; + break; + case DAI_TRIGGER_PAUSE: + dai_dmic_stop(dmic, true); + dmic->state = DAI_STATE_PAUSED; + break; + default: + break; + } + + return 0; +} + +static const struct dai_config *dai_dmic_get_config(const struct device *dev, enum dai_dir dir) +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; + + __ASSERT_NO_MSG(dir == DAI_DIR_CAPTURE); + return &dmic->dai_config_params; +} + +static int dai_dmic_set_config(const struct device *dev, + const struct dai_config *cfg, const void *bespoke_cfg) + +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; + int ret = 0; + int di = dmic->dai_config_params.dai_index; + k_spinlock_key_t key; + + LOG_INF("dmic_set_config()"); + + if (di >= CONFIG_DAI_DMIC_HW_FIFOS) { + LOG_ERR("dmic_set_config(): DAI index exceeds number of FIFOs"); + return -EINVAL; + } + + if (!bespoke_cfg) { + LOG_ERR("dmic_set_config(): NULL config"); + return -EINVAL; + } + + __ASSERT_NO_MSG(dmic->created); + key = k_spin_lock(&dmic->lock); + +#if CONFIG_DAI_INTEL_DMIC_TPLG_PARAMS +#error DMIC TPLG is not yet implemented + +#elif CONFIG_DAI_INTEL_DMIC_NHLT + ret = dai_dmic_set_config_nhlt(dmic, bespoke_cfg); + +#else +#error No DMIC config selected +#endif + + if (ret < 0) { + LOG_ERR("dmic_set_config(): Failed to set the requested configuration."); + goto out; + } + + dmic->state = DAI_STATE_PRE_RUNNING; + +out: + k_spin_unlock(&dmic->lock, key); + return ret; +} + + +static int dai_dmic_probe_wrapper(const struct device *dev) +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; + k_spinlock_key_t key; + int ret = 0; + + key = k_spin_lock(&dmic->lock); + + if (dmic->sref == 0) { + ret = dai_dmic_probe(dmic); + } + + if (!ret) { + dmic->sref++; + } + + k_spin_unlock(&dmic->lock, key); + + return ret; +} + +static int dai_dmic_remove_wrapper(const struct device *dev) +{ + struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; + k_spinlock_key_t key; + int ret = 0; + + key = k_spin_lock(&dmic->lock); + + if (--dmic->sref == 0) { + ret = dai_dmic_remove(dmic); + } + + k_spin_unlock(&dmic->lock, key); + + return ret; +} + +const struct dai_driver_api dai_dmic_ops = { + .probe = dai_dmic_probe_wrapper, + .remove = dai_dmic_remove_wrapper, + .config_set = dai_dmic_set_config, + .config_get = dai_dmic_get_config, + .get_properties = dai_dmic_get_properties, + .trigger = dai_dmic_trigger, + .ts_config = dai_dmic_timestamp_config, + .ts_start = dai_timestamp_dmic_start, + .ts_stop = dai_timestamp_dmic_stop, + .ts_get = dai_timestamp_dmic_get +}; + +static int dai_dmic_initialize_device(const struct device *dev) +{ + IRQ_CONNECT( + DT_INST_IRQN(0), + IRQ_DEFAULT_PRIORITY, + dai_dmic_irq_handler, + DEVICE_DT_INST_GET(0), + 0); + return 0; +}; + + +#define DAI_INTEL_DMIC_DEVICE_INIT(n) \ + static struct dai_properties dai_intel_dmic_properties_##n; \ + \ + static struct dai_intel_dmic dai_intel_dmic_data_##n = \ + { .dai_config_params = \ + { \ + .type = DAI_INTEL_DMIC, \ + .dai_index = n \ + }, \ + .reg_base = DT_INST_REG_ADDR_BY_IDX(n, 0), \ + .shim_base = DT_INST_PROP_BY_IDX(n, shim, 0), \ + .irq = DT_INST_IRQN(n), \ + .fifo = \ + { \ + .offset = DT_INST_REG_ADDR_BY_IDX(n, 0) \ + + OUTDATA##n, \ + .handshake = DMA_HANDSHAKE_DMIC_CH##n \ + }, \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, \ + dai_dmic_initialize_device, \ + NULL, \ + &dai_intel_dmic_data_##n, \ + &dai_intel_dmic_properties_##n, \ + POST_KERNEL, \ + CONFIG_DAI_INIT_PRIORITY, \ + &dai_dmic_ops); + +DT_INST_FOREACH_STATUS_OKAY(DAI_INTEL_DMIC_DEVICE_INIT); diff --git a/drivers/dai/intel/dmic/dmic.h b/drivers/dai/intel/dmic/dmic.h new file mode 100644 index 00000000000..aafa5ee288e --- /dev/null +++ b/drivers/dai/intel/dmic/dmic.h @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INTEL_DAI_DRIVER_DMIC_H__ +#define __INTEL_DAI_DRIVER_DMIC_H__ + +#include + +/* bit operations macros */ +#define MASK(b_hi, b_lo) \ + (((1ULL << ((b_hi) - (b_lo) + 1ULL)) - 1ULL) << (b_lo)) + +#define SET_BIT(b, x) (((x) & 1) << (b)) + +#define SET_BITS(b_hi, b_lo, x) \ + (((x) & ((1ULL << ((b_hi) - (b_lo) + 1ULL)) - 1ULL)) << (b_lo)) + +#define GET_BIT(b, x) \ + (((x) & (1ULL << (b))) >> (b)) + +#define GET_BITS(b_hi, b_lo, x) \ + (((x) & MASK(b_hi, b_lo)) >> (b_lo)) + +/* DMIC timestamping registers */ + + +#define TS_DMIC_LOCAL_TSCTRL_OFFSET 0x000 +#define TS_DMIC_LOCAL_OFFS_OFFSET 0x004 +#define TS_DMIC_LOCAL_SAMPLE_OFFSET 0x008 +#define TS_DMIC_LOCAL_WALCLK_OFFSET 0x010 +#define TS_DMIC_TSCC_OFFSET 0x018 + +/* Timestamping */ +#define TIMESTAMP_BASE 0x00071800 + +#define TS_DMIC_LOCAL_TSCTRL (TIMESTAMP_BASE + TS_DMIC_LOCAL_TSCTRL_OFFSET) +#define TS_DMIC_LOCAL_OFFS (TIMESTAMP_BASE + TS_DMIC_LOCAL_OFFS_OFFSET) +#define TS_DMIC_LOCAL_SAMPLE (TIMESTAMP_BASE + TS_DMIC_LOCAL_SAMPLE_OFFSET) +#define TS_DMIC_LOCAL_WALCLK (TIMESTAMP_BASE + TS_DMIC_LOCAL_WALCLK_OFFSET) +#define TS_DMIC_TSCC (TIMESTAMP_BASE + TS_DMIC_TSCC_OFFSET) + +#define TS_LOCAL_TSCTRL_NTK_BIT BIT(31) +#define TS_LOCAL_TSCTRL_IONTE_BIT BIT(30) +#define TS_LOCAL_TSCTRL_SIP_BIT BIT(8) +#define TS_LOCAL_TSCTRL_HHTSE_BIT BIT(7) +#define TS_LOCAL_TSCTRL_ODTS_BIT BIT(5) +#define TS_LOCAL_TSCTRL_CDMAS(x) SET_BITS(4, 0, x) +#define TS_LOCAL_OFFS_FRM GET_BITS(15, 12) +#define TS_LOCAL_OFFS_CLK GET_BITS(11, 0) + +/* Digital Mic Shim Registers */ +#define DMICLCTL_OFFSET 0x04 +#define DMICIPPTR_OFFSET 0x08 +#define DMICSYNC_OFFSET 0x0C + +/* DMIC power ON bit */ +#define DMICLCTL_SPA BIT(0) +/* DMIC Owner Select */ +#define DMICLCTL_OSEL(x) SET_BITS(25, 24, x) +/* DMIC disable clock gating */ +#define DMIC_DCGD BIT(30) + +/* DMIC Command Sync */ +#define DMICSYNC_CMDSYNC BIT(16) +/* DMIC Sync Go */ +#define DMICSYNC_SYNCGO BIT(24) +/* DMIC Sync Period */ +#define DMICSYNC_SYNCPRD(x) SET_BITS(14, 0, x) + +/* Parameters used in modes computation */ +#define DMIC_HW_BITS_CIC 26 +#define DMIC_HW_BITS_FIR_COEF 20 +#define DMIC_HW_BITS_FIR_GAIN 20 +#define DMIC_HW_BITS_FIR_INPUT 22 +#define DMIC_HW_BITS_FIR_OUTPUT 24 +#define DMIC_HW_BITS_FIR_INTERNAL 26 +#define DMIC_HW_BITS_GAIN_OUTPUT 22 +#define DMIC_HW_FIR_LENGTH_MAX 250 +#define DMIC_HW_CIC_SHIFT_MIN -8 +#define DMIC_HW_CIC_SHIFT_MAX 4 +#define DMIC_HW_FIR_SHIFT_MIN 0 +#define DMIC_HW_FIR_SHIFT_MAX 8 +#define DMIC_HW_CIC_DECIM_MIN 5 +#define DMIC_HW_CIC_DECIM_MAX 31 /* Note: Limited by BITS_CIC */ +#define DMIC_HW_FIR_DECIM_MIN 2 +#define DMIC_HW_FIR_DECIM_MAX 20 /* Note: Practical upper limit */ +#define DMIC_HW_SENS_Q28 Q_CONVERT_FLOAT(1.0, 28) /* Q1.28 */ +#define DMIC_HW_PDM_CLK_MIN 100000 /* Note: Practical min value */ +#define DMIC_HW_DUTY_MIN 20 /* Note: Practical min value */ +#define DMIC_HW_DUTY_MAX 80 /* Note: Practical max value */ + +/* DMIC register offsets */ + +/* Global registers */ +#define OUTCONTROL0 0x0000 +#define OUTSTAT0 0x0004 +#define OUTDATA0 0x0008 +#define OUTCONTROL1 0x0100 +#define OUTSTAT1 0x0104 +#define OUTDATA1 0x0108 +#define PDM0 0x1000 +#define PDM0_COEFFICIENT_A 0x1400 +#define PDM0_COEFFICIENT_B 0x1800 +#define PDM1 0x2000 +#define PDM1_COEFFICIENT_A 0x2400 +#define PDM1_COEFFICIENT_B 0x2800 +#define PDM2 0x3000 +#define PDM2_COEFFICIENT_A 0x3400 +#define PDM2_COEFFICIENT_B 0x3800 +#define PDM3 0x4000 +#define PDM3_COEFFICIENT_A 0x4400 +#define PDM3_COEFFICIENT_B 0x4800 +#define PDM_COEF_RAM_A_LENGTH 0x0400 +#define PDM_COEF_RAM_B_LENGTH 0x0400 + +/* Local registers in each PDMx */ +#define CIC_CONTROL 0x000 +#define CIC_CONFIG 0x004 +#define MIC_CONTROL 0x00c +#define FIR_CONTROL_A 0x020 +#define FIR_CONFIG_A 0x024 +#define DC_OFFSET_LEFT_A 0x028 +#define DC_OFFSET_RIGHT_A 0x02c +#define OUT_GAIN_LEFT_A 0x030 +#define OUT_GAIN_RIGHT_A 0x034 +#define FIR_CONTROL_B 0x040 +#define FIR_CONFIG_B 0x044 +#define DC_OFFSET_LEFT_B 0x048 +#define DC_OFFSET_RIGHT_B 0x04c +#define OUT_GAIN_LEFT_B 0x050 +#define OUT_GAIN_RIGHT_B 0x054 + +/* Register bits */ + +/* OUTCONTROLx IPM bit fields style */ +#define OUTCONTROL0_BFTH_MAX 4 /* Max depth 16 */ + +/* OUTCONTROL0 bits */ +#define OUTCONTROL0_TIE_BIT BIT(27) +#define OUTCONTROL0_SIP_BIT BIT(26) +#define OUTCONTROL0_FINIT_BIT BIT(25) +#define OUTCONTROL0_FCI_BIT BIT(24) +#define OUTCONTROL0_TIE(x) SET_BIT(27, x) +#define OUTCONTROL0_SIP(x) SET_BIT(26, x) +#define OUTCONTROL0_FINIT(x) SET_BIT(25, x) +#define OUTCONTROL0_FCI(x) SET_BIT(24, x) +#define OUTCONTROL0_BFTH(x) SET_BITS(23, 20, x) +#define OUTCONTROL0_OF(x) SET_BITS(19, 18, x) +#define OUTCONTROL0_IPM(x) SET_BITS(17, 15, x) +#define OUTCONTROL0_IPM_SOURCE_1(x) SET_BITS(14, 13, x) +#define OUTCONTROL0_IPM_SOURCE_2(x) SET_BITS(12, 11, x) +#define OUTCONTROL0_IPM_SOURCE_3(x) SET_BITS(10, 9, x) +#define OUTCONTROL0_IPM_SOURCE_4(x) SET_BITS(8, 7, x) +#define OUTCONTROL0_IPM_SOURCE_MODE(x) SET_BIT(6, x) +#define OUTCONTROL0_TH(x) SET_BITS(5, 0, x) +#define OUTCONTROL0_TIE_GET(x) GET_BIT(27, x) +#define OUTCONTROL0_SIP_GET(x) GET_BIT(26, x) +#define OUTCONTROL0_FINIT_GET(x) GET_BIT(25, x) +#define OUTCONTROL0_FCI_GET(x) GET_BIT(24, x) +#define OUTCONTROL0_BFTH_GET(x) GET_BITS(23, 20, x) +#define OUTCONTROL0_OF_GET(x) GET_BITS(19, 18, x) +#define OUTCONTROL0_IPM_GET(x) GET_BITS(17, 15, x) +#define OUTCONTROL0_IPM_SOURCE_1_GET(x) GET_BITS(14, 13, x) +#define OUTCONTROL0_IPM_SOURCE_2_GET(x) GET_BITS(12, 11, x) +#define OUTCONTROL0_IPM_SOURCE_3_GET(x) GET_BITS(10, 9, x) +#define OUTCONTROL0_IPM_SOURCE_4_GET(x) GET_BITS(8, 7, x) +#define OUTCONTROL0_IPM_SOURCE_MODE_GET(x) GET_BIT(6, x) +#define OUTCONTROL0_TH_GET(x) GET_BITS(5, 0, x) + +/* OUTCONTROL1 bits */ +#define OUTCONTROL1_TIE_BIT BIT(27) +#define OUTCONTROL1_SIP_BIT BIT(26) +#define OUTCONTROL1_FINIT_BIT BIT(25) +#define OUTCONTROL1_FCI_BIT BIT(24) +#define OUTCONTROL1_TIE(x) SET_BIT(27, x) +#define OUTCONTROL1_SIP(x) SET_BIT(26, x) +#define OUTCONTROL1_FINIT(x) SET_BIT(25, x) +#define OUTCONTROL1_FCI(x) SET_BIT(24, x) +#define OUTCONTROL1_BFTH(x) SET_BITS(23, 20, x) +#define OUTCONTROL1_OF(x) SET_BITS(19, 18, x) +#define OUTCONTROL1_IPM(x) SET_BITS(17, 15, x) +#define OUTCONTROL1_IPM_SOURCE_1(x) SET_BITS(14, 13, x) +#define OUTCONTROL1_IPM_SOURCE_2(x) SET_BITS(12, 11, x) +#define OUTCONTROL1_IPM_SOURCE_3(x) SET_BITS(10, 9, x) +#define OUTCONTROL1_IPM_SOURCE_4(x) SET_BITS(8, 7, x) +#define OUTCONTROL1_IPM_SOURCE_MODE(x) SET_BIT(6, x) +#define OUTCONTROL1_TH(x) SET_BITS(5, 0, x) +#define OUTCONTROL1_TIE_GET(x) GET_BIT(27, x) +#define OUTCONTROL1_SIP_GET(x) GET_BIT(26, x) +#define OUTCONTROL1_FINIT_GET(x) GET_BIT(25, x) +#define OUTCONTROL1_FCI_GET(x) GET_BIT(24, x) +#define OUTCONTROL1_BFTH_GET(x) GET_BITS(23, 20, x) +#define OUTCONTROL1_OF_GET(x) GET_BITS(19, 18, x) +#define OUTCONTROL1_IPM_GET(x) GET_BITS(17, 15, x) +#define OUTCONTROL1_IPM_SOURCE_1_GET(x) GET_BITS(14, 13, x) +#define OUTCONTROL1_IPM_SOURCE_2_GET(x) GET_BITS(12, 11, x) +#define OUTCONTROL1_IPM_SOURCE_3_GET(x) GET_BITS(10, 9, x) +#define OUTCONTROL1_IPM_SOURCE_4_GET(x) GET_BITS(8, 7, x) +#define OUTCONTROL1_IPM_SOURCE_MODE_GET(x) GET_BIT(6, x) +#define OUTCONTROL1_TH_GET(x) GET_BITS(5, 0, x) + +#define OUTCONTROLX_IPM_NUMSOURCES 4 + + +/* OUTSTAT0 bits */ +#define OUTSTAT0_AFE_BIT BIT(31) +#define OUTSTAT0_ASNE_BIT BIT(29) +#define OUTSTAT0_RFS_BIT BIT(28) +#define OUTSTAT0_ROR_BIT BIT(27) +#define OUTSTAT0_FL_MASK MASK(6, 0) + +/* OUTSTAT1 bits */ +#define OUTSTAT1_AFE_BIT BIT(31) +#define OUTSTAT1_ASNE_BIT BIT(29) +#define OUTSTAT1_RFS_BIT BIT(28) +#define OUTSTAT1_ROR_BIT BIT(27) +#define OUTSTAT1_FL_MASK MASK(6, 0) + +/* CIC_CONTROL bits */ +#define CIC_CONTROL_SOFT_RESET_BIT BIT(16) +#define CIC_CONTROL_CIC_START_B_BIT BIT(15) +#define CIC_CONTROL_CIC_START_A_BIT BIT(14) +#define CIC_CONTROL_MIC_B_POLARITY_BIT BIT(3) +#define CIC_CONTROL_MIC_A_POLARITY_BIT BIT(2) +#define CIC_CONTROL_MIC_MUTE_BIT BIT(1) +#define CIC_CONTROL_STEREO_MODE_BIT BIT(0) + +#define CIC_CONTROL_SOFT_RESET(x) SET_BIT(16, x) +#define CIC_CONTROL_CIC_START_B(x) SET_BIT(15, x) +#define CIC_CONTROL_CIC_START_A(x) SET_BIT(14, x) +#define CIC_CONTROL_MIC_B_POLARITY(x) SET_BIT(3, x) +#define CIC_CONTROL_MIC_A_POLARITY(x) SET_BIT(2, x) +#define CIC_CONTROL_MIC_MUTE(x) SET_BIT(1, x) +#define CIC_CONTROL_STEREO_MODE(x) SET_BIT(0, x) + +#define CIC_CONTROL_SOFT_RESET_GET(x) GET_BIT(16, x) +#define CIC_CONTROL_CIC_START_B_GET(x) GET_BIT(15, x) +#define CIC_CONTROL_CIC_START_A_GET(x) GET_BIT(14, x) +#define CIC_CONTROL_MIC_B_POLARITY_GET(x) GET_BIT(3, x) +#define CIC_CONTROL_MIC_A_POLARITY_GET(x) GET_BIT(2, x) +#define CIC_CONTROL_MIC_MUTE_GET(x) GET_BIT(1, x) +#define CIC_CONTROL_STEREO_MODE_GET(x) GET_BIT(0, x) + +/* CIC_CONFIG bits */ +#define CIC_CONFIG_CIC_SHIFT(x) SET_BITS(27, 24, x) +#define CIC_CONFIG_COMB_COUNT(x) SET_BITS(15, 8, x) + +/* CIC_CONFIG masks */ +#define CIC_CONFIG_CIC_SHIFT_MASK MASK(27, 24) +#define CIC_CONFIG_COMB_COUNT_MASK MASK(15, 8) + +#define CIC_CONFIG_CIC_SHIFT_GET(x) GET_BITS(27, 24, x) +#define CIC_CONFIG_COMB_COUNT_GET(x) GET_BITS(15, 8, x) + +/* MIC_CONTROL bits */ +#define MIC_CONTROL_PDM_EN_B_BIT BIT(1) +#define MIC_CONTROL_PDM_EN_A_BIT BIT(0) +#define MIC_CONTROL_PDM_CLKDIV(x) SET_BITS(15, 8, x) +#define MIC_CONTROL_PDM_SKEW(x) SET_BITS(7, 4, x) +#define MIC_CONTROL_CLK_EDGE(x) SET_BIT(3, x) +#define MIC_CONTROL_PDM_EN_B(x) SET_BIT(1, x) +#define MIC_CONTROL_PDM_EN_A(x) SET_BIT(0, x) + +/* MIC_CONTROL masks */ +#define MIC_CONTROL_PDM_CLKDIV_MASK MASK(15, 8) + +#define MIC_CONTROL_PDM_CLKDIV_GET(x) GET_BITS(15, 8, x) +#define MIC_CONTROL_PDM_SKEW_GET(x) GET_BITS(7, 4, x) +#define MIC_CONTROL_PDM_CLK_EDGE_GET(x) GET_BIT(3, x) +#define MIC_CONTROL_PDM_EN_B_GET(x) GET_BIT(1, x) +#define MIC_CONTROL_PDM_EN_A_GET(x) GET_BIT(0, x) + +/* FIR_CONTROL_A bits */ +#define FIR_CONTROL_A_START_BIT BIT(7) +#define FIR_CONTROL_A_ARRAY_START_EN_BIT BIT(6) +#define FIR_CONTROL_A_MUTE_BIT BIT(1) +#define FIR_CONTROL_A_START(x) SET_BIT(7, x) +#define FIR_CONTROL_A_ARRAY_START_EN(x) SET_BIT(6, x) +#define FIR_CONTROL_A_DCCOMP(x) SET_BIT(4, x) +#define FIR_CONTROL_A_MUTE(x) SET_BIT(1, x) +#define FIR_CONTROL_A_STEREO(x) SET_BIT(0, x) + +#define FIR_CONTROL_A_START_GET(x) GET_BIT(7, x) +#define FIR_CONTROL_A_ARRAY_START_EN_GET(x) GET_BIT(6, x) +#define FIR_CONTROL_A_DCCOMP_GET(x) GET_BIT(4, x) +#define FIR_CONTROL_A_MUTE_GET(x) GET_BIT(1, x) +#define FIR_CONTROL_A_STEREO_GET(x) GET_BIT(0, x) + +/* FIR_CONFIG_A bits */ +#define FIR_CONFIG_A_FIR_DECIMATION(x) SET_BITS(20, 16, x) +#define FIR_CONFIG_A_FIR_SHIFT(x) SET_BITS(11, 8, x) +#define FIR_CONFIG_A_FIR_LENGTH(x) SET_BITS(7, 0, x) + +#define FIR_CONFIG_A_FIR_DECIMATION_GET(x) GET_BITS(20, 16, x) +#define FIR_CONFIG_A_FIR_SHIFT_GET(x) GET_BITS(11, 8, x) +#define FIR_CONFIG_A_FIR_LENGTH_GET(x) GET_BITS(7, 0, x) + +/* DC offset compensation time constants */ +#define DCCOMP_TC0 0 +#define DCCOMP_TC1 1 +#define DCCOMP_TC2 2 +#define DCCOMP_TC3 3 +#define DCCOMP_TC4 4 +#define DCCOMP_TC5 5 +#define DCCOMP_TC6 6 +#define DCCOMP_TC7 7 + +/* DC_OFFSET_LEFT_A bits */ +#define DC_OFFSET_LEFT_A_DC_OFFS(x) SET_BITS(21, 0, x) + +/* DC_OFFSET_RIGHT_A bits */ +#define DC_OFFSET_RIGHT_A_DC_OFFS(x) SET_BITS(21, 0, x) + +/* OUT_GAIN_LEFT_A bits */ +#define OUT_GAIN_LEFT_A_GAIN(x) SET_BITS(19, 0, x) + +/* OUT_GAIN_RIGHT_A bits */ +#define OUT_GAIN_RIGHT_A_GAIN(x) SET_BITS(19, 0, x) + +/* FIR_CONTROL_B bits */ +#define FIR_CONTROL_B_START_BIT BIT(7) +#define FIR_CONTROL_B_ARRAY_START_EN_BIT BIT(6) +#define FIR_CONTROL_B_MUTE_BIT BIT(1) +#define FIR_CONTROL_B_START(x) SET_BIT(7, x) +#define FIR_CONTROL_B_ARRAY_START_EN(x) SET_BIT(6, x) +#define FIR_CONTROL_B_DCCOMP(x) SET_BIT(4, x) +#define FIR_CONTROL_B_MUTE(x) SET_BIT(1, x) +#define FIR_CONTROL_B_STEREO(x) SET_BIT(0, x) + +#define FIR_CONTROL_B_START_GET(x) GET_BIT(7, x) +#define FIR_CONTROL_B_ARRAY_START_EN_GET(x) GET_BIT(6, x) +#define FIR_CONTROL_B_DCCOMP_GET(x) GET_BIT(4, x) +#define FIR_CONTROL_B_MUTE_GET(x) GET_BIT(1, x) +#define FIR_CONTROL_B_STEREO_GET(x) GET_BIT(0, x) + +/* FIR_CONFIG_B bits */ +#define FIR_CONFIG_B_FIR_DECIMATION(x) SET_BITS(20, 16, x) +#define FIR_CONFIG_B_FIR_SHIFT(x) SET_BITS(11, 8, x) +#define FIR_CONFIG_B_FIR_LENGTH(x) SET_BITS(7, 0, x) + +#define FIR_CONFIG_B_FIR_DECIMATION_GET(x) GET_BITS(20, 16, x) +#define FIR_CONFIG_B_FIR_SHIFT_GET(x) GET_BITS(11, 8, x) +#define FIR_CONFIG_B_FIR_LENGTH_GET(x) GET_BITS(7, 0, x) + +/* DC_OFFSET_LEFT_B bits */ +#define DC_OFFSET_LEFT_B_DC_OFFS(x) SET_BITS(21, 0, x) + +/* DC_OFFSET_RIGHT_B bits */ +#define DC_OFFSET_RIGHT_B_DC_OFFS(x) SET_BITS(21, 0, x) + +/* OUT_GAIN_LEFT_B bits */ +#define OUT_GAIN_LEFT_B_GAIN(x) SET_BITS(19, 0, x) + +/* OUT_GAIN_RIGHT_B bits */ +#define OUT_GAIN_RIGHT_B_GAIN(x) SET_BITS(19, 0, x) + +/* FIR coefficients */ +#define FIR_COEF_A(x) SET_BITS(19, 0, x) +#define FIR_COEF_B(x) SET_BITS(19, 0, x) + +/* Used for scaling FIR coefficients for HW */ +#define DMIC_HW_FIR_COEF_MAX ((1 << (DMIC_HW_BITS_FIR_COEF - 1)) - 1) +#define DMIC_HW_FIR_COEF_Q (DMIC_HW_BITS_FIR_COEF - 1) + +/* Internal precision in gains computation, e.g. Q4.28 in int32_t */ +#define DMIC_FIR_SCALE_Q 28 + +/* Used in unmute ramp values calculation */ +#define DMIC_HW_FIR_GAIN_MAX ((1 << (DMIC_HW_BITS_FIR_GAIN - 1)) - 1) + +/* Hardwired log ramp parameters. The first value is the initial gain in + * decibels. The default ramp time is provided by 1st order equation + * ramp time = coef * samplerate + offset. The default ramp is 200 ms for + * 48 kHz and 400 ms for 16 kHz. + */ +#define LOGRAMP_START_DB Q_CONVERT_FLOAT(-90, DB2LIN_FIXED_INPUT_QY) +#define LOGRAMP_TIME_COEF_Q15 -205 /* dy/dx (16000,400) (48000,200) */ +#define LOGRAMP_TIME_OFFS_Q0 500 /* Offset for line slope */ + +/* Limits for ramp time from topology */ +#define LOGRAMP_TIME_MIN_MS 10 /* Min. 10 ms */ +#define LOGRAMP_TIME_MAX_MS 1000 /* Max. 1s */ + +/* Simplify log ramp step calculation equation with this constant term */ +#define LOGRAMP_CONST_TERM ((int32_t) \ + ((int64_t)-LOGRAMP_START_DB * DMIC_UNMUTE_RAMP_US / 1000)) + +/* Fractional shift for gain update. Gain format is Q2.30. */ +#define Q_SHIFT_GAIN_X_GAIN_COEF \ + (Q_SHIFT_BITS_32(30, DB2LIN_FIXED_OUTPUT_QY, 30)) + +#define DMA_HANDSHAKE_DMIC_CH0 0 +#define DMA_HANDSHAKE_DMIC_CH1 1 + +/* For NHLT DMIC configuration parsing */ +#define DMIC_HW_CONTROLLERS_MAX 4 +#define DMIC_HW_FIFOS_MAX 2 + +struct nhlt_dmic_gateway_attributes { + uint32_t dw; +}; + +struct nhlt_dmic_ts_group { + uint32_t ts_group[4]; +}; + +struct nhlt_dmic_clock_on_delay { + uint32_t clock_on_delay; +}; + +struct nhlt_dmic_channel_ctrl_mask { + uint32_t channel_ctrl_mask; +}; + +struct nhlt_pdm_ctrl_mask { + uint32_t pdm_ctrl_mask; +}; + +struct nhlt_pdm_ctrl_cfg { + uint32_t cic_control; + uint32_t cic_config; + uint32_t reserved0; + uint32_t mic_control; + uint32_t pdm_sdw_map; + uint32_t reuse_fir_from_pdm; + uint32_t reserved1[2]; +}; + +struct nhlt_pdm_ctrl_fir_cfg { + uint32_t fir_control; + uint32_t fir_config; + int32_t dc_offset_left; + int32_t dc_offset_right; + int32_t out_gain_left; + int32_t out_gain_right; + uint32_t reserved[2]; +}; + +struct nhlt_pdm_fir_coeffs { + int32_t fir_coeffs[0]; +}; + +enum dai_dmic_frame_format { + DAI_DMIC_FRAME_S16_LE = 0, + DAI_DMIC_FRAME_S24_4LE, + DAI_DMIC_FRAME_S32_LE, + DAI_DMIC_FRAME_FLOAT, + /* other formats here */ + DAI_DMIC_FRAME_S24_3LE, +}; + +/* Common data for all DMIC DAI instances */ +struct dai_dmic_global_shared { + uint32_t active_fifos_mask; /* Bits (dai->index) are set to indicate active FIFO */ + uint32_t pause_mask; /* Bits (dai->index) are set to indicate driver pause */ +}; + +struct dai_dmic_plat_fifo_data { + uint32_t offset; + uint32_t width; + uint32_t depth; + uint32_t watermark; + uint32_t handshake; +}; + + +struct dai_intel_dmic { + struct dai_config dai_config_params; + + struct k_spinlock lock; /**< locking mechanism */ + int sref; /**< simple ref counter, guarded by lock */ + enum dai_state state; /* Driver component state */ + uint16_t enable[CONFIG_DAI_DMIC_HW_CONTROLLERS];/* Mic 0 and 1 enable bits array for PDMx */ + struct dai_dmic_plat_fifo_data fifo; /* dmic capture fifo stream */ + + /* hardware parameters */ + uint32_t reg_base; + uint32_t shim_base; + int irq; + uint32_t flags; +}; + +#endif /* __INTEL_DAI_DRIVER_DMIC_H__ */ diff --git a/drivers/dai/intel/dmic/dmic_nhlt.c b/drivers/dai/intel/dmic/dmic_nhlt.c new file mode 100644 index 00000000000..1234f39a596 --- /dev/null +++ b/drivers/dai/intel/dmic/dmic_nhlt.c @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define LOG_DOMAIN dai_intel_dmic_nhlt +#include +LOG_MODULE_REGISTER(LOG_DOMAIN); + +#include +#include "dmic.h" + +extern struct dai_dmic_global_shared dai_dmic_global; + +/* Base addresses (in PDM scope) of 2ch PDM controllers and coefficient RAM. */ +static const uint32_t base[4] = {PDM0, PDM1, PDM2, PDM3}; +static const uint32_t coef_base_a[4] = {PDM0_COEFFICIENT_A, PDM1_COEFFICIENT_A, + PDM2_COEFFICIENT_A, PDM3_COEFFICIENT_A}; +static const uint32_t coef_base_b[4] = {PDM0_COEFFICIENT_B, PDM1_COEFFICIENT_B, + PDM2_COEFFICIENT_B, PDM3_COEFFICIENT_B}; + +static inline void dai_dmic_write(const struct dai_intel_dmic *dmic, + uint32_t reg, uint32_t val) +{ + sys_write32(val, dmic->reg_base + reg); +} + +static int dai_ipm_source_to_enable(struct dai_intel_dmic *dmic, + struct nhlt_pdm_ctrl_cfg **pdm_cfg, + int *count, int pdm_count, int stereo, + int source_pdm) +{ + int mic_swap; + + if (source_pdm >= CONFIG_DAI_DMIC_HW_CONTROLLERS) + return -EINVAL; + + if (*count < pdm_count) { + (*count)++; + mic_swap = MIC_CONTROL_PDM_CLK_EDGE_GET(pdm_cfg[source_pdm]->mic_control); + if (stereo) + dmic->enable[source_pdm] = 0x3; /* PDMi MIC A and B */ + else + dmic->enable[source_pdm] = mic_swap ? 0x2 : 0x1; /* PDMi MIC B or MIC A */ + } + + return 0; +} + +static int dai_nhlt_dmic_dai_params_get(struct dai_intel_dmic *dmic, + int32_t *outcontrol, + struct nhlt_pdm_ctrl_cfg **pdm_cfg, + struct nhlt_pdm_ctrl_fir_cfg **fir_cfg) +{ + uint32_t outcontrol_val = outcontrol[dmic->dai_config_params.dai_index]; + int num_pdm; + int source_pdm; + int ret; + int n; + bool stereo_pdm; + + switch (OUTCONTROL0_OF_GET(outcontrol_val)) { + case 0: + case 1: + dmic->dai_config_params.format = DAI_DMIC_FRAME_S16_LE; + dmic->dai_config_params.word_size = 16; + break; + case 2: + dmic->dai_config_params.format = DAI_DMIC_FRAME_S32_LE; + dmic->dai_config_params.word_size = 32; + break; + default: + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal OF bit field"); + return -EINVAL; + } + + num_pdm = OUTCONTROL0_IPM_GET(outcontrol_val); + if (num_pdm > CONFIG_DAI_DMIC_HW_CONTROLLERS) { + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal IPM PDM controllers count"); + return -EINVAL; + } + + stereo_pdm = 1; + + dmic->dai_config_params.channels = (stereo_pdm + 1) * num_pdm; + for (n = 0; n < CONFIG_DAI_DMIC_HW_CONTROLLERS; n++) + dmic->enable[n] = 0; + + n = 0; + source_pdm = OUTCONTROL0_IPM_SOURCE_1_GET(outcontrol_val); + ret = dai_ipm_source_to_enable(dmic, pdm_cfg, &n, num_pdm, stereo_pdm, source_pdm); + if (ret) { + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal IPM_SOURCE_1"); + return -EINVAL; + } + + source_pdm = OUTCONTROL0_IPM_SOURCE_2_GET(outcontrol_val); + ret = dai_ipm_source_to_enable(dmic, pdm_cfg, &n, num_pdm, stereo_pdm, source_pdm); + if (ret) { + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal IPM_SOURCE_2"); + return -EINVAL; + } + + source_pdm = OUTCONTROL0_IPM_SOURCE_3_GET(outcontrol_val); + ret = dai_ipm_source_to_enable(dmic, pdm_cfg, &n, num_pdm, stereo_pdm, source_pdm); + if (ret) { + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal IPM_SOURCE_3"); + return -EINVAL; + } + + source_pdm = OUTCONTROL0_IPM_SOURCE_4_GET(outcontrol_val); + ret = dai_ipm_source_to_enable(dmic, pdm_cfg, &n, num_pdm, stereo_pdm, source_pdm); + if (ret) { + LOG_ERR("nhlt_dmic_dai_params_get(): Illegal IPM_SOURCE_4"); + return -EINVAL; + } + + return 0; +} + +int dai_dmic_set_config_nhlt(struct dai_intel_dmic *dmic, const void *bespoke_cfg) +{ + struct nhlt_pdm_ctrl_cfg *pdm_cfg[DMIC_HW_CONTROLLERS_MAX]; + struct nhlt_pdm_ctrl_fir_cfg *fir_cfg_a[DMIC_HW_CONTROLLERS_MAX]; + struct nhlt_pdm_ctrl_fir_cfg *fir_cfg_b[DMIC_HW_CONTROLLERS_MAX]; + struct nhlt_pdm_fir_coeffs *fir_a[DMIC_HW_CONTROLLERS_MAX] = {NULL}; + struct nhlt_pdm_fir_coeffs *fir_b[DMIC_HW_CONTROLLERS_MAX]; + uint32_t out_control[DMIC_HW_FIFOS_MAX] = {0}; + uint32_t channel_ctrl_mask; + uint32_t fir_control; + uint32_t pdm_ctrl_mask; + uint32_t ref = 0; + uint32_t val; + const uint8_t *p = bespoke_cfg; + int num_fifos; + int num_pdm; + int fir_length_a; + int fir_length_b; + int n; + int i; + int rate_div; + int clk_div; + int comb_count; + int fir_decimation, fir_shift, fir_length; + int bf1, bf2, bf3, bf4, bf5, bf6, bf7, bf8; + int bf9, bf10, bf11, bf12, bf13; + int bfth; + int ret; + int p_mcic = 0; + int p_mfira = 0; + int p_mfirb = 0; + int p_clkdiv = 0; + + if (dmic->dai_config_params.dai_index >= DMIC_HW_FIFOS_MAX) { + LOG_ERR("dmic_set_config_nhlt(): illegal DAI index %d", + dmic->dai_config_params.dai_index); + return -EINVAL; + } + + /* Skip not used headers */ + p += sizeof(struct nhlt_dmic_gateway_attributes); + p += sizeof(struct nhlt_dmic_ts_group); + p += sizeof(struct nhlt_dmic_clock_on_delay); + + /* Channel_ctlr_mask bits indicate the FIFOs enabled*/ + channel_ctrl_mask = ((struct nhlt_dmic_channel_ctrl_mask *)p)->channel_ctrl_mask; + num_fifos = popcount(channel_ctrl_mask); /* Count set bits */ + p += sizeof(struct nhlt_dmic_channel_ctrl_mask); + LOG_DBG("dmic_set_config_nhlt(): channel_ctrl_mask = %d", channel_ctrl_mask); + + /* Get OUTCONTROLx configuration */ + if (num_fifos < 1 || num_fifos > DMIC_HW_FIFOS_MAX) { + LOG_ERR("dmic_set_config_nhlt(): illegal number of FIFOs %d", num_fifos); + return -EINVAL; + } + + for (n = 0; n < DMIC_HW_FIFOS_MAX; n++) { + if (!(channel_ctrl_mask & (1 << n))) + continue; + + val = *(uint32_t *)p; + out_control[n] = val; + bf1 = OUTCONTROL0_TIE_GET(val); + bf2 = OUTCONTROL0_SIP_GET(val); + bf3 = OUTCONTROL0_FINIT_GET(val); + bf4 = OUTCONTROL0_FCI_GET(val); + bf5 = OUTCONTROL0_BFTH_GET(val); + bf6 = OUTCONTROL0_OF_GET(val); + bf7 = OUTCONTROL0_IPM_GET(val); + bf8 = OUTCONTROL0_TH_GET(val); + LOG_INF("dmic_set_config_nhlt(): OUTCONTROL%d = %08x", n, out_control[n]); + LOG_INF(" tie=%d, sip=%d, finit=%d, fci=%d", bf1, bf2, bf3, bf4); + LOG_INF(" bfth=%d, of=%d, ipm=%d, th=%d", bf5, bf6, bf7, bf8); + if (bf5 > OUTCONTROL0_BFTH_MAX) { + LOG_ERR("dmic_set_config_nhlt(): illegal BFTH value"); + return -EINVAL; + } + + bf9 = OUTCONTROL0_IPM_SOURCE_1_GET(val); + bf10 = OUTCONTROL0_IPM_SOURCE_2_GET(val); + bf11 = OUTCONTROL0_IPM_SOURCE_3_GET(val); + bf12 = OUTCONTROL0_IPM_SOURCE_4_GET(val); + bf13 = OUTCONTROL0_IPM_SOURCE_MODE_GET(val); + LOG_INF(" ipms1=%d, ipms2=%d, ipms3=%d, ipms4=%d", bf9, bf10, bf11, bf12); + LOG_INF(" ipms_mode=%d", bf13); + ref = OUTCONTROL0_TIE(bf1) | OUTCONTROL0_SIP(bf2) | OUTCONTROL0_FINIT(bf3) | + OUTCONTROL0_FCI(bf4) | OUTCONTROL0_BFTH(bf5) | OUTCONTROL0_OF(bf6) | + OUTCONTROL0_IPM(bf7) | OUTCONTROL0_IPM_SOURCE_1(bf9) | + OUTCONTROL0_IPM_SOURCE_2(bf10) | OUTCONTROL0_IPM_SOURCE_3(bf11) | + OUTCONTROL0_IPM_SOURCE_4(bf12) | OUTCONTROL0_TH(bf8) | + OUTCONTROL0_IPM_SOURCE_MODE(bf13); + if (ref != val) { + LOG_ERR("dmic_set_config_nhlt(): illegal OUTCONTROL%d = 0x%08x", + n, val); + return -EINVAL; + } + + p += sizeof(uint32_t); + } + + /* Write the FIFO control registers. The clear/set of bits is the same for + * all DMIC_HW_VERSION + */ + /* Clear TIE, SIP, FCI, set FINIT, the rest of bits as such */ + val = (out_control[dmic->dai_config_params.dai_index] & + ~(OUTCONTROL0_TIE_BIT | OUTCONTROL0_SIP_BIT | OUTCONTROL0_FCI_BIT)) | + OUTCONTROL0_FINIT_BIT; + if (dmic->dai_config_params.dai_index == 0) + dai_dmic_write(dmic, OUTCONTROL0, val); + else + dai_dmic_write(dmic, OUTCONTROL1, val); + + LOG_INF("dmic_set_config_nhlt(): OUTCONTROL%d = %08x", + dmic->dai_config_params.dai_index, val); + + /* Pass 2^BFTH to plat_data fifo depth. It will be used later in DMA + * configuration + */ + bfth = OUTCONTROL0_BFTH_GET(val); + dmic->fifo.depth = 1 << bfth; + + /* Get PDMx registers */ + pdm_ctrl_mask = ((struct nhlt_pdm_ctrl_mask *)p)->pdm_ctrl_mask; + num_pdm = popcount(pdm_ctrl_mask); /* Count set bits */ + p += sizeof(struct nhlt_pdm_ctrl_mask); + LOG_DBG("dmic_set_config_nhlt(): pdm_ctrl_mask = %d", pdm_ctrl_mask); + if (num_pdm < 1 || num_pdm > CONFIG_DAI_DMIC_HW_CONTROLLERS) { + LOG_ERR("dmic_set_config_nhlt(): illegal number of PDMs %d", num_pdm); + return -EINVAL; + } + + for (n = 0; n < CONFIG_DAI_DMIC_HW_CONTROLLERS; n++) { + fir_cfg_a[n] = NULL; + fir_cfg_b[n] = NULL; + if (!(pdm_ctrl_mask & (1 << n))) + continue; + + LOG_DBG("dmic_set_config_nhlt(): PDM%d", n); + + /* Get CIC configuration */ + pdm_cfg[n] = (struct nhlt_pdm_ctrl_cfg *)p; + p += sizeof(struct nhlt_pdm_ctrl_cfg); + + comb_count = CIC_CONFIG_COMB_COUNT_GET(pdm_cfg[n]->cic_config); + p_mcic = comb_count + 1; + clk_div = MIC_CONTROL_PDM_CLKDIV_GET(pdm_cfg[n]->mic_control); + p_clkdiv = clk_div + 2; + if (dai_dmic_global.active_fifos_mask == 0) { + val = pdm_cfg[n]->cic_control; + bf1 = CIC_CONTROL_SOFT_RESET_GET(val); + bf2 = CIC_CONTROL_CIC_START_B_GET(val); + bf3 = CIC_CONTROL_CIC_START_A_GET(val); + bf4 = CIC_CONTROL_MIC_B_POLARITY_GET(val); + bf5 = CIC_CONTROL_MIC_A_POLARITY_GET(val); + bf6 = CIC_CONTROL_MIC_MUTE_GET(val); + bf7 = CIC_CONTROL_STEREO_MODE_GET(val); + LOG_DBG("dmic_set_config_nhlt(): CIC_CONTROL = %08x", val); + LOG_DBG(" soft_reset=%d, cic_start_b=%d, cic_start_a=%d", + bf1, bf2, bf3); + LOG_DBG(" mic_b_polarity=%d, mic_a_polarity=%d, mic_mute=%d", + bf4, bf5, bf6); + ref = CIC_CONTROL_SOFT_RESET(bf1) | CIC_CONTROL_CIC_START_B(bf2) | + CIC_CONTROL_CIC_START_A(bf3) | CIC_CONTROL_MIC_B_POLARITY(bf4) | + CIC_CONTROL_MIC_A_POLARITY(bf5) | CIC_CONTROL_MIC_MUTE(bf6) | + CIC_CONTROL_STEREO_MODE(bf7); + LOG_DBG(" stereo_mode=%d", bf7); + if (ref != val) { + LOG_ERR("dmic_set_config_nhlt(): illegal CIC_CONTROL = 0x%08x", + val); + return -EINVAL; + } + + /* Clear CIC_START_A and CIC_START_B, set SOF_RESET and MIC_MUTE*/ + val = (val & ~(CIC_CONTROL_CIC_START_A_BIT | CIC_CONTROL_CIC_START_A_BIT)) | + CIC_CONTROL_SOFT_RESET_BIT | CIC_CONTROL_MIC_MUTE_BIT; + dai_dmic_write(dmic, base[n] + CIC_CONTROL, val); + LOG_DBG("dmic_set_config_nhlt(): CIC_CONTROL = %08x", val); + + val = pdm_cfg[n]->cic_config; + bf1 = CIC_CONFIG_CIC_SHIFT_GET(val); + LOG_DBG("dmic_set_config_nhlt(): CIC_CONFIG = %08x", val); + LOG_DBG(" cic_shift=%d, comb_count=%d", bf1, comb_count); + + /* Use CIC_CONFIG as such */ + dai_dmic_write(dmic, base[n] + CIC_CONFIG, val); + LOG_DBG("dmic_set_config_nhlt(): CIC_CONFIG = %08x", val); + + val = pdm_cfg[n]->mic_control; + bf1 = MIC_CONTROL_PDM_SKEW_GET(val); + bf2 = MIC_CONTROL_PDM_CLK_EDGE_GET(val); + bf3 = MIC_CONTROL_PDM_EN_B_GET(val); + bf4 = MIC_CONTROL_PDM_EN_A_GET(val); + LOG_DBG("dmic_set_config_nhlt(): MIC_CONTROL = %08x", val); + LOG_DBG(" clkdiv=%d, skew=%d, clk_edge=%d", clk_div, bf1, bf2); + LOG_DBG(" en_b=%d, en_a=%d", bf3, bf4); + + /* Clear PDM_EN_A and PDM_EN_B */ + val &= ~(MIC_CONTROL_PDM_EN_A_BIT | MIC_CONTROL_PDM_EN_B_BIT); + dai_dmic_write(dmic, base[n] + MIC_CONTROL, val); + LOG_DBG("dmic_set_config_nhlt(): MIC_CONTROL = %08x", val); + } + + /* FIR A */ + fir_cfg_a[n] = (struct nhlt_pdm_ctrl_fir_cfg *)p; + p += sizeof(struct nhlt_pdm_ctrl_fir_cfg); + val = fir_cfg_a[n]->fir_config; + fir_length = FIR_CONFIG_A_FIR_LENGTH_GET(val); + fir_length_a = fir_length + 1; /* Need for parsing */ + fir_decimation = FIR_CONFIG_A_FIR_DECIMATION_GET(val); + p_mfira = fir_decimation + 1; + if (dmic->dai_config_params.dai_index == 0) { + fir_shift = FIR_CONFIG_A_FIR_SHIFT_GET(val); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONFIG_A = %08x", val); + LOG_DBG(" fir_decimation=%d, fir_shift=%d, fir_length=%d", + fir_decimation, fir_shift, fir_length); + + /* Use FIR_CONFIG_A as such */ + dai_dmic_write(dmic, base[n] + FIR_CONFIG_A, val); + LOG_DBG("configure_registers(), FIR_CONFIG_A = %08x", val); + + val = fir_cfg_a[n]->fir_control; + bf1 = FIR_CONTROL_A_START_GET(val); + bf2 = FIR_CONTROL_A_ARRAY_START_EN_GET(val); + bf3 = FIR_CONTROL_A_DCCOMP_GET(val); + bf4 = FIR_CONTROL_A_MUTE_GET(val); + bf5 = FIR_CONTROL_A_STEREO_GET(val); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONTROL_A = %08x", val); + LOG_DBG(" start=%d, array_start_en=%d, dccomp=%d", bf1, bf2, bf3); + LOG_DBG(" mute=%d, stereo=%d", bf4, bf5); + ref = FIR_CONTROL_A_START(bf1) | FIR_CONTROL_A_ARRAY_START_EN(bf2) | + FIR_CONTROL_A_DCCOMP(bf3) | FIR_CONTROL_A_MUTE(bf4) | + FIR_CONTROL_A_STEREO(bf5); + + if (ref != val) { + LOG_ERR("dmic_set_config_nhlt(): illegal FIR_CONTROL = 0x%08x", + val); + return -EINVAL; + } + + /* Clear START, set MUTE */ + fir_control = (val & ~FIR_CONTROL_A_START_BIT) | FIR_CONTROL_A_MUTE_BIT; + dai_dmic_write(dmic, base[n] + FIR_CONTROL_A, fir_control); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONTROL_A = %08x", fir_control); + + /* Use DC_OFFSET and GAIN as such */ + val = fir_cfg_a[n]->dc_offset_left; + dai_dmic_write(dmic, base[n] + DC_OFFSET_LEFT_A, val); + LOG_DBG("dmic_set_config_nhlt(): DC_OFFSET_LEFT_A = %08x", val); + + val = fir_cfg_a[n]->dc_offset_right; + dai_dmic_write(dmic, base[n] + DC_OFFSET_RIGHT_A, val); + LOG_DBG("dmic_set_config_nhlt(): DC_OFFSET_RIGHT_A = %08x", val); + + val = fir_cfg_a[n]->out_gain_left; + dai_dmic_write(dmic, base[n] + OUT_GAIN_LEFT_A, val); + LOG_DBG("dmic_set_config_nhlt(): OUT_GAIN_LEFT_A = %08x", val); + + val = fir_cfg_a[n]->out_gain_right; + dai_dmic_write(dmic, base[n] + OUT_GAIN_RIGHT_A, val); + LOG_DBG("dmic_set_config_nhlt(): OUT_GAIN_RIGHT_A = %08x", val); + } + + /* FIR B */ + fir_cfg_b[n] = (struct nhlt_pdm_ctrl_fir_cfg *)p; + p += sizeof(struct nhlt_pdm_ctrl_fir_cfg); + val = fir_cfg_b[n]->fir_config; + fir_length = FIR_CONFIG_B_FIR_LENGTH_GET(val); + fir_length_b = fir_length + 1; /* Need for parsing */ + fir_decimation = FIR_CONFIG_B_FIR_DECIMATION_GET(val); + p_mfirb = fir_decimation + 1; + if (dmic->dai_config_params.dai_index == 1) { + fir_shift = FIR_CONFIG_B_FIR_SHIFT_GET(val); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONFIG_B = %08x", val); + LOG_DBG(" fir_decimation=%d, fir_shift=%d, fir_length=%d", + fir_decimation, fir_shift, fir_length); + + /* Use FIR_CONFIG_B as such */ + dai_dmic_write(dmic, base[n] + FIR_CONFIG_B, val); + LOG_DBG("configure_registers(), FIR_CONFIG_B = %08x", val); + + val = fir_cfg_b[n]->fir_control; + bf1 = FIR_CONTROL_B_START_GET(val); + bf2 = FIR_CONTROL_B_ARRAY_START_EN_GET(val); + bf3 = FIR_CONTROL_B_DCCOMP_GET(val); + bf5 = FIR_CONTROL_B_MUTE_GET(val); + bf6 = FIR_CONTROL_B_STEREO_GET(val); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONTROL_B = %08x", val); + LOG_DBG(" start=%d, array_start_en=%d, dccomp=%d", bf1, bf2, bf3); + LOG_DBG(" mute=%d, stereo=%d", bf5, bf6); + + /* Clear START, set MUTE */ + fir_control = (val & ~FIR_CONTROL_B_START_BIT) | FIR_CONTROL_B_MUTE_BIT; + dai_dmic_write(dmic, base[n] + FIR_CONTROL_B, fir_control); + LOG_DBG("dmic_set_config_nhlt(): FIR_CONTROL_B = %08x", fir_control); + + /* Use DC_OFFSET and GAIN as such */ + val = fir_cfg_b[n]->dc_offset_left; + dai_dmic_write(dmic, base[n] + DC_OFFSET_LEFT_B, val); + LOG_DBG("dmic_set_config_nhlt(): DC_OFFSET_LEFT_B = %08x", val); + + val = fir_cfg_b[n]->dc_offset_right; + dai_dmic_write(dmic, base[n] + DC_OFFSET_RIGHT_B, val); + LOG_DBG("dmic_set_config_nhlt(): DC_OFFSET_RIGHT_B = %08x", val); + + val = fir_cfg_b[n]->out_gain_left; + dai_dmic_write(dmic, base[n] + OUT_GAIN_LEFT_B, val); + LOG_DBG("dmic_set_config_nhlt(): OUT_GAIN_LEFT_B = %08x", val); + + val = fir_cfg_b[n]->out_gain_right; + dai_dmic_write(dmic, base[n] + OUT_GAIN_RIGHT_B, val); + LOG_DBG("dmic_set_config_nhlt(): OUT_GAIN_RIGHT_B = %08x", val); + } + + /* Set up FIR coefficients RAM */ + val = pdm_cfg[n]->reuse_fir_from_pdm; + if (val == 0) { + fir_a[n] = (struct nhlt_pdm_fir_coeffs *)p; + p += sizeof(int32_t) * fir_length_a; + fir_b[n] = (struct nhlt_pdm_fir_coeffs *)p; + p += sizeof(int32_t) * fir_length_b; + } else { + val--; + if (val >= n) { + LOG_ERR("dmic_set_config_nhlt(): Illegal FIR reuse 0x%x", val); + return -EINVAL; + } + + if (!fir_a[val]) { + LOG_ERR("dmic_set_config_nhlt(): PDM%d FIR reuse from %d fail", + n, val); + return -EINVAL; + } + + fir_a[n] = fir_a[val]; + fir_b[n] = fir_b[val]; + } + + if (dmic->dai_config_params.dai_index == 0) { + LOG_INF( + "dmic_set_config_nhlt(): clkdiv = %d, mcic = %d, mfir_a = %d, len = %d", + p_clkdiv, p_mcic, p_mfira, fir_length_a); + for (i = 0; i < fir_length_a; i++) + dai_dmic_write(dmic, + coef_base_a[n] + (i << 2), fir_a[n]->fir_coeffs[i]); + } else { + LOG_INF( + "dmic_set_config_nhlt(): clkdiv = %d, mcic = %d, mfir_b = %d, len = %d", + p_clkdiv, p_mcic, p_mfirb, fir_length_b); + for (i = 0; i < fir_length_b; i++) + dai_dmic_write(dmic, + coef_base_b[n] + (i << 2), fir_b[n]->fir_coeffs[i]); + } + } + + if (dmic->dai_config_params.dai_index == 0) + ret = dai_nhlt_dmic_dai_params_get(dmic, out_control, pdm_cfg, fir_cfg_a); + else + ret = dai_nhlt_dmic_dai_params_get(dmic, out_control, pdm_cfg, fir_cfg_b); + + if (ret) + return ret; + + if (dmic->dai_config_params.dai_index == 0) + rate_div = p_clkdiv * p_mcic * p_mfira; + else + rate_div = p_clkdiv * p_mcic * p_mfirb; + + if (!rate_div) { + LOG_ERR("dmic_set_config_nhlt(): zero clock divide or decimation factor"); + return -EINVAL; + } + + dmic->dai_config_params.rate = CONFIG_DAI_DMIC_HW_IOCLK / rate_div; + LOG_INF("dmic_set_config_nhlt(): rate = %d, channels = %d, format = %d", + dmic->dai_config_params.rate, dmic->dai_config_params.channels, + dmic->dai_config_params.format); + return 0; +} diff --git a/dts/bindings/dai/dai,intel,dmic.yaml b/dts/bindings/dai/dai,intel,dmic.yaml new file mode 100644 index 00000000000..6294facf93d --- /dev/null +++ b/dts/bindings/dai/dai,intel,dmic.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Intel Digital PDM Microphone (DMIC) node + +compatible: "intel,dai,dmic" + +include: base.yaml + +properties: + reg: + required: true + + label: + required: true + + shim: + type: array + required: true