blob: f79ff6544984c669e84809daf44a632e11d37843 [file] [log] [blame]
/*
* 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 <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_DOMAIN);
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/spinlock.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/dai.h>
#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;
}
/* 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, base[i] + CIC_CONTROL,
CIC_CONTROL_MIC_MUTE_BIT, 0);
if (dmic->startcount == DMIC_UNMUTE_FIR) {
switch (dmic->dai_config_params.dai_index) {
case 0:
dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_A,
FIR_CONTROL_A_MUTE_BIT, 0);
break;
case 1:
dai_dmic_update_bits(dmic, base[i] + FIR_CONTROL_B,
FIR_CONTROL_B_MUTE_BIT, 0);
break;
}
}
switch (dmic->dai_config_params.dai_index) {
case 0:
val = OUT_GAIN_LEFT_A_GAIN(gval);
dai_dmic_write(dmic, base[i] + OUT_GAIN_LEFT_A, val);
dai_dmic_write(dmic, base[i] + OUT_GAIN_RIGHT_A, val);
break;
case 1:
val = OUT_GAIN_LEFT_B_GAIN(gval);
dai_dmic_write(dmic, base[i] + OUT_GAIN_LEFT_B, val);
dai_dmic_write(dmic, base[i] + OUT_GAIN_RIGHT_B, val);
break;
}
}
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 fir_a;
int fir_b;
/* enable port */
key = k_spin_lock(&dmic->lock);
LOG_DBG("dmic_start()");
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);
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;
case DAI_TRIGGER_COPY:
dai_dmic_gain_ramp(dmic);
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);
/* 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;
}
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);