/* * 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 #include #include #include #include "dmic.h" #include /* Base addresses (in PDM scope) of 2ch PDM controllers and coefficient RAM. */ static const uint32_t dmic_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); /* Exponent function for small values of x. This function calculates * fairly accurately exponent for x in range -2.0 .. +2.0. The iteration * uses first 11 terms of Taylor series approximation for exponent * function. With the current scaling the numerator just remains under * 64 bits with the 11 terms. * * See https://en.wikipedia.org/wiki/Exponential_function#Computation * * The input is Q3.29 * The output is Q9.23 */ static int32_t exp_small_fixed(int32_t x) { int64_t p; int64_t num = Q_SHIFT_RND(x, 29, 23); int32_t y = (int32_t)num; int32_t den = 1; int32_t inc; int k; /* Numerator is x^k, denominator is k! */ for (k = 2; k < 12; k++) { p = num * x; /* Q9.23 x Q3.29 -> Q12.52 */ num = Q_SHIFT_RND(p, 52, 23); den = den * k; inc = (int32_t)(num / den); y += inc; } return y + ONE_Q23; } static int32_t exp_fixed(int32_t x) { int32_t xs; int32_t y; int32_t z; int i; int n = 0; if (x < Q_CONVERT_FLOAT(-11.5, 27)) return 0; if (x > Q_CONVERT_FLOAT(7.6245, 27)) return INT32_MAX; /* x is Q5.27 */ xs = x; while (xs >= TWO_Q27 || xs <= MINUS_TWO_Q27) { xs >>= 1; n++; } /* exp_small_fixed() input is Q3.29, while x1 is Q5.27 * exp_small_fixed() output is Q9.23, while z is Q12.20 */ z = Q_SHIFT_RND(exp_small_fixed(Q_SHIFT_LEFT(xs, 27, 29)), 23, 20); y = ONE_Q20; for (i = 0; i < (1 << n); i++) y = (int32_t)Q_MULTSR_32X32((int64_t)y, z, 20, 20, 20); return y; } static int32_t db2lin_fixed(int32_t db) { int32_t arg; if (db < Q_CONVERT_FLOAT(-100.0, 24)) return 0; /* Q8.24 x Q5.27, result needs to be Q5.27 */ arg = (int32_t)Q_MULTSR_32X32((int64_t)db, LOG10_DIV20_Q27, 24, 27, 27); return exp_fixed(arg); } 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) | FIELD_PREP(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, 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 */ static inline uint32_t dai_dmic_base(const struct dai_intel_dmic *dmic) { #ifdef CONFIG_SOC_INTEL_ACE20_LNL return dmic->hdamldmic_base; #else return dmic->shim_base; #endif } #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; uint32_t base = dai_dmic_base(dmic); /* DMIC Change sync period */ #ifdef CONFIG_SOC_INTEL_ACE20_LNL sys_write32(sys_read32(base + DMICSYNC_OFFSET) | FIELD_PREP(DMICSYNC_SYNCPRD, val), base + DMICSYNC_OFFSET); sys_write32(sys_read32(base + DMICSYNC_OFFSET) | DMICSYNC_SYNCPU, base + DMICSYNC_OFFSET); if (!WAIT_FOR((sys_read32(base + DMICSYNC_OFFSET) & DMICSYNC_SYNCPU) == 0, 1000, k_sleep(K_USEC(100)))) { LOG_ERR("poll timeout"); } sys_write32(sys_read32(base + DMICSYNC_OFFSET) | DMICSYNC_CMDSYNC, base + DMICSYNC_OFFSET); #else /* All other CAVS and ACE platforms */ sys_write32(sys_read32(base + DMICSYNC_OFFSET) | FIELD_PREP(DMICSYNC_SYNCPRD, val), base + DMICSYNC_OFFSET); sys_write32(sys_read32(base + DMICSYNC_OFFSET) | DMICSYNC_CMDSYNC, base + DMICSYNC_OFFSET); #endif } static inline void dai_dmic_clear_sync_period(const struct dai_intel_dmic *dmic) { uint32_t base = dai_dmic_base(dmic); /* DMIC Clean sync period */ sys_write32(sys_read32(base + DMICSYNC_OFFSET) & ~DMICSYNC_SYNCPRD, base + DMICSYNC_OFFSET); sys_write32(sys_read32(base + DMICSYNC_OFFSET) & ~DMICSYNC_CMDSYNC, base + DMICSYNC_OFFSET); } /* Preparing for command synchronization on multiple link segments */ static inline void dai_dmic_sync_prepare(const struct dai_intel_dmic *dmic) { uint32_t base = dai_dmic_base(dmic); sys_write32(sys_read32(base + DMICSYNC_OFFSET) | DMICSYNC_CMDSYNC, base + DMICSYNC_OFFSET); } /* Trigering synchronization of command execution */ static void dmic_sync_trigger(const struct dai_intel_dmic *dmic) { uint32_t base = dai_dmic_base(dmic); __ASSERT_NO_MSG((sys_read32(base + DMICSYNC_OFFSET) & DMICSYNC_CMDSYNC) != 0); sys_write32(sys_read32(base + DMICSYNC_OFFSET) | DMICSYNC_SYNCGO, base + DMICSYNC_OFFSET); /* waiting for CMDSYNC bit clearing */ if (!WAIT_FOR((sys_read32(base + DMICSYNC_OFFSET) & DMICSYNC_CMDSYNC) == 0, 1000, k_sleep(K_USEC(100)))) { LOG_ERR("poll timeout"); } } #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_start_fifo_packers(struct dai_intel_dmic *dmic, int fifo_index) { /* Start FIFO packers and clear FIFO initialize bits */ dai_dmic_update_bits(dmic, fifo_index * PDM_CHANNEL_REGS_SIZE + OUTCONTROL, OUTCONTROL_SIP | OUTCONTROL_FINIT, OUTCONTROL_SIP); } static void dai_dmic_stop_fifo_packers(struct dai_intel_dmic *dmic, int fifo_index) { /* Stop FIFO packers and set FIFO initialize bits */ dai_dmic_update_bits(dmic, fifo_index * PDM_CHANNEL_REGS_SIZE + OUTCONTROL, OUTCONTROL_SIP | OUTCONTROL_FINIT, OUTCONTROL_FINIT); } /* 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 device *)data)->data; uint32_t val0; uint32_t val1; /* Trace OUTSTAT0 register */ val0 = dai_dmic_read(dmic, OUTSTAT); val1 = dai_dmic_read(dmic, OUTSTAT + PDM_CHANNEL_REGS_SIZE); LOG_DBG("dmic_irq_handler(), OUTSTAT0 = 0x%x, OUTSTAT1 = 0x%x", val0, val1); if (val0 & OUTSTAT_ROR) { LOG_ERR("dmic_irq_handler(): full fifo A or PDM overrun"); dai_dmic_write(dmic, OUTSTAT, val0); dai_dmic_stop_fifo_packers(dmic, 0); } if (val1 & OUTSTAT_ROR) { LOG_ERR("dmic_irq_handler(): full fifo B or PDM overrun"); dai_dmic_write(dmic, OUTSTAT + PDM_CHANNEL_REGS_SIZE, 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 */ #ifdef CONFIG_SOC_INTEL_ACE20_LNL /* Ace 2.0 */ sys_write32((sys_read32(dmic->vshim_base + DMICLVSCTL_OFFSET) | DMICLVSCTL_DCGD), dmic->vshim_base + DMICLVSCTL_OFFSET); #else sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) | DMICLCTL_DCGD), dmic->shim_base + DMICLCTL_OFFSET); #endif } static inline void dai_dmic_en_clk_gating(const struct dai_intel_dmic *dmic) { /* Enable DMIC clock gating */ #ifdef CONFIG_SOC_INTEL_ACE20_LNL /* Ace 2.0 */ sys_write32((sys_read32(dmic->vshim_base + DMICLVSCTL_OFFSET) & ~DMICLVSCTL_DCGD), dmic->vshim_base + DMICLVSCTL_OFFSET); #else sys_write32((sys_read32(dmic->shim_base + DMICLCTL_OFFSET) & ~DMICLCTL_DCGD), dmic->shim_base + DMICLCTL_OFFSET); #endif } static inline void dai_dmic_program_channel_map(const struct dai_intel_dmic *dmic, const struct dai_config *cfg, uint32_t index) { #ifdef CONFIG_SOC_INTEL_ACE20_LNL uint16_t pcmsycm = cfg->link_config; uint32_t reg_add = dmic->shim_base + DMICXPCMSyCM_OFFSET + 0x0004*index; sys_write16(pcmsycm, reg_add); #else ARG_UNUSED(dmic); ARG_UNUSED(cfg); ARG_UNUSED(index); #endif /* defined(CONFIG_SOC_INTEL_ACE20_LNL) */ } static inline void dai_dmic_en_power(const struct dai_intel_dmic *dmic) { uint32_t base = dai_dmic_base(dmic); /* Enable DMIC power */ sys_write32((sys_read32(base + DMICLCTL_OFFSET) | DMICLCTL_SPA), base + DMICLCTL_OFFSET); #ifdef CONFIG_SOC_INTEL_ACE20_LNL /* Ace 2.0 */ while (!(sys_read32(base + DMICLCTL_OFFSET) & DMICLCTL_CPA)) { k_sleep(K_USEC(100)); } #endif } static inline void dai_dmic_dis_power(const struct dai_intel_dmic *dmic) { uint32_t base = dai_dmic_base(dmic); /* Disable DMIC power */ sys_write32((sys_read32(base + DMICLCTL_OFFSET) & (~DMICLCTL_SPA)), 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 = FIELD_PREP(TS_LOCAL_TSCTRL_CDMAS, cfg->dma_chan_index + cfg->dma_chan_count * cfg->dma_id); sys_write32(TS_LOCAL_TSCTRL_NTK | cdmas, addr); /* Request on demand timestamp */ sys_write32(TS_LOCAL_TSCTRL_ODTS | 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, 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; 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, tsctrl); out: tsd->walclk_rate = cfg->walclk_rate; if (!ntk) return -ENODATA; return 0; } /* this ramps volume changes over time */ static void dai_dmic_gain_ramp(struct dai_intel_dmic *dmic) { k_spinlock_key_t key; int32_t gval; uint32_t val; int i; /* Currently there's no DMIC HW internal mutings and wait times * applied into this start sequence. It can be implemented here if * start of audio capture would contain clicks and/or noise and it * is not suppressed by gain ramp somewhere in the capture pipe. */ LOG_DBG("DMIC gain ramp"); /* * At run-time dmic->gain is only changed in this function, and this * function runs in the pipeline task context, so it cannot run * concurrently on multiple cores, since there's always only one * task associated with each DAI, so we don't need to hold the lock to * read the value here. */ if (dmic->gain == DMIC_HW_FIR_GAIN_MAX << 11) return; key = k_spin_lock(&dmic->lock); /* Increment gain with logarithmic step. * Gain is Q2.30 and gain modifier is Q12.20. */ dmic->startcount++; dmic->gain = q_multsr_sat_32x32(dmic->gain, dmic->gain_coef, Q_SHIFT_GAIN_X_GAIN_COEF); /* Gain is stored as Q2.30, while HW register is Q1.19 so shift * the value right by 11. */ gval = dmic->gain >> 11; /* Note that DMIC gain value zero has a special purpose. Value zero * sets gain bypass mode in HW. Zero value will be applied after ramp * is complete. It is because exact 1.0 gain is not possible with Q1.19. */ if (gval > DMIC_HW_FIR_GAIN_MAX) { gval = 0; dmic->gain = DMIC_HW_FIR_GAIN_MAX << 11; } /* Write gain to registers */ for (i = 0; i < CONFIG_DAI_DMIC_HW_CONTROLLERS; i++) { if (!dmic->enable[i]) continue; if (dmic->startcount == DMIC_UNMUTE_CIC) dai_dmic_update_bits(dmic, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_MIC_MUTE, 0); if (dmic->startcount == DMIC_UNMUTE_FIR) { dai_dmic_update_bits(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + FIR_CONTROL, FIR_CONTROL_MUTE, 0); } if (gval != 0) { val = FIELD_PREP(OUT_GAIN, gval); dai_dmic_write(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + OUT_GAIN_LEFT, val); dai_dmic_write(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + OUT_GAIN_RIGHT, val); } else { dai_dmic_write(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + OUT_GAIN_LEFT, dmic->gain_left); dai_dmic_write(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + OUT_GAIN_RIGHT, dmic->gain_right); } } k_spin_unlock(&dmic->lock, key); } static void dai_dmic_start(struct dai_intel_dmic *dmic) { k_spinlock_key_t key; int i; int mic_a; int mic_b; int start_fir; /* enable port */ key = k_spin_lock(&dmic->lock); LOG_DBG("dmic_start(), dai_index = %d", dmic->dai_config_params.dai_index); dmic->startcount = 0; /* Compute unmute ramp gain update coefficient. */ dmic->gain_coef = db2lin_fixed(LOGRAMP_CONST_TERM / dmic->unmute_time_ms); /* Initial gain value, convert Q12.20 to Q2.30 */ dmic->gain = Q_SHIFT_LEFT(db2lin_fixed(LOGRAMP_START_DB), 20, 30); dai_dmic_sync_prepare(dmic); dai_dmic_start_fifo_packers(dmic, dmic->dai_config_params.dai_index); for (i = 0; i < CONFIG_DAI_DMIC_HW_CONTROLLERS; i++) { #ifdef CONFIG_SOC_SERIES_INTEL_ACE dai_dmic_update_bits(dmic, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_SOFT_RESET, 0); LOG_INF("dmic_start(), cic 0x%08x", dai_dmic_read(dmic, dmic_base[i] + CIC_CONTROL)); #endif mic_a = dmic->enable[i] & 1; mic_b = (dmic->enable[i] & 2) >> 1; start_fir = dmic->enable[i] > 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, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_CIC_START_A | CIC_CONTROL_CIC_START_B, FIELD_PREP(CIC_CONTROL_CIC_START_A, 1) | FIELD_PREP(CIC_CONTROL_CIC_START_B, 1)); dai_dmic_update_bits(dmic, dmic_base[i] + MIC_CONTROL, MIC_CONTROL_PDM_EN_A | MIC_CONTROL_PDM_EN_B, FIELD_PREP(MIC_CONTROL_PDM_EN_A, 1) | FIELD_PREP(MIC_CONTROL_PDM_EN_B, 1)); } else if (mic_a) { dai_dmic_update_bits(dmic, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_CIC_START_A, FIELD_PREP(CIC_CONTROL_CIC_START_A, 1)); dai_dmic_update_bits(dmic, dmic_base[i] + MIC_CONTROL, MIC_CONTROL_PDM_EN_A, FIELD_PREP(MIC_CONTROL_PDM_EN_A, 1)); } else if (mic_b) { dai_dmic_update_bits(dmic, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_CIC_START_B, FIELD_PREP(CIC_CONTROL_CIC_START_B, 1)); dai_dmic_update_bits(dmic, dmic_base[i] + MIC_CONTROL, MIC_CONTROL_PDM_EN_B, FIELD_PREP(MIC_CONTROL_PDM_EN_B, 1)); } dai_dmic_update_bits(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + FIR_CONTROL, FIR_CONTROL_START, FIELD_PREP(FIR_CONTROL_START, start_fir)); } #ifndef CONFIG_SOC_SERIES_INTEL_ACE /* 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, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_SOFT_RESET, 0); LOG_INF("dmic_start(), cic 0x%08x", dai_dmic_read(dmic, dmic_base[i] + CIC_CONTROL)); } #endif /* 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, dmic_base[i] + CIC_CONTROL, CIC_CONTROL_SOFT_RESET | CIC_CONTROL_MIC_MUTE, CIC_CONTROL_SOFT_RESET | CIC_CONTROL_MIC_MUTE); } dai_dmic_update_bits(dmic, dmic_base[i] + FIR_CHANNEL_REGS_SIZE * dmic->dai_config_params.dai_index + FIR_CONTROL, FIR_CONTROL_MUTE, FIR_CONTROL_MUTE); } 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->fifo_depth = dmic->fifo.depth; 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; case DAI_TRIGGER_COPY: dai_dmic_gain_ramp(dmic); break; default: break; } return 0; } static int dai_dmic_get_config(const struct device *dev, struct dai_config *cfg, enum dai_dir dir) { struct dai_intel_dmic *dmic = (struct dai_intel_dmic *)dev->data; if (dir != DAI_DIR_RX) { return -EINVAL; } if (!cfg) { return -EINVAL; } *cfg = dmic->dai_config_params; return 0; } 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; } dai_dmic_program_channel_map(dmic, cfg, di); 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); /* There's no unmute ramp duration in blob, so the default rate dependent is used. */ dmic->unmute_time_ms = dmic_get_unmute_ramp_from_samplerate(dmic->dai_config_params.rate); #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; } static int dmic_pm_action(const struct device *dev, enum pm_device_action action) { switch (action) { case PM_DEVICE_ACTION_SUSPEND: dai_dmic_remove_wrapper(dev); break; case PM_DEVICE_ACTION_RESUME: dai_dmic_probe_wrapper(dev); break; case PM_DEVICE_ACTION_TURN_OFF: case PM_DEVICE_ACTION_TURN_ON: /* All device pm is handled during resume and suspend */ break; default: return -ENOTSUP; } return 0; } const struct dai_driver_api dai_dmic_ops = { .probe = pm_device_runtime_get, .remove = pm_device_runtime_put, .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); if (pm_device_on_power_domain(dev)) { pm_device_init_off(dev); } else { pm_device_init_suspended(dev); } return pm_device_runtime_enable(dev); }; #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(n, shim), \ IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(hdamlddmic)), \ (.hdamldmic_base = DT_REG_ADDR(DT_NODELABEL(hdamlddmic)),)) \ IF_ENABLED(DT_NODE_EXISTS(DT_NODELABEL(dmicvss)), \ (.vshim_base = DT_REG_ADDR(DT_NODELABEL(dmicvss)),)) \ .irq = DT_INST_IRQN(n), \ .fifo = \ { \ .offset = DT_INST_REG_ADDR_BY_IDX(n, 0) \ + DT_INST_PROP(n, fifo), \ .handshake = DMA_HANDSHAKE_DMIC_CH##n \ }, \ }; \ \ PM_DEVICE_DT_INST_DEFINE(n, dmic_pm_action); \ \ DEVICE_DT_INST_DEFINE(n, \ dai_dmic_initialize_device, \ PM_DEVICE_DT_INST_GET(n), \ &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)