Skip to content

Commit

Permalink
driver: imx: Add PDM MICFIL driver
Browse files Browse the repository at this point in the history
The Pulse Density Modulated Microphone Interface (MICFIL) is a popular
way to deliver audio from microphones to the processor in several
applications, such as mobile telephones. However, current digital-audio
systems use multibit audio signal (also known as multibit PCM) to
represent the signal. This block implements the required digital
interface to provide a 24-bits audio signal from a PDM microphone
bitstream in a configurable output sampling rate.

This patch adds initial support for PDM MICFIL IP found on i.MX8MP
board.

Signed-off-by: Daniel Baluta <[email protected]>
  • Loading branch information
dbaluta committed Sep 12, 2023
1 parent b474e57 commit efa2cb7
Show file tree
Hide file tree
Showing 12 changed files with 571 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/drivers/imx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ if(CONFIG_IMX_SDMA)
endif()
endif()

if(CONFIG_IMX_MICFIL)
add_local_sources(sof micfil.c)
endif()

if(CONFIG_IMX_INTERRUPT_IRQSTEER)
add_local_sources(sof interrupt-irqsteer.c)
elseif(CONFIG_IMX_INTERRUPT_GENERIC)
Expand Down
9 changes: 9 additions & 0 deletions src/drivers/imx/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ config IMX_ESAI
help
Select this to enable support for i.MX ESAI IP.

config IMX_MICFIL
bool "i.MX MICFIL PDM driver"
default n
depends on IMX
help
Select this to enable support for i.MX MICFIL PDM IP. This block implements
the required digital interface to provide a 24-bits audio signal from a PDM
microphone bitstream in a configurable output sampling rate

config IMX_INTERRUPT_IRQSTEER
bool
default n
Expand Down
335 changes: 335 additions & 0 deletions src/drivers/imx/micfil.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
// SPDX-License-Identifier: BSD-3-Clause
//
// Copyright 2023 NXP
//
// Author: Daniel Baluta <[email protected]>

#include <sof/audio/component.h>
#include <sof/audio/format.h>
#include <rtos/alloc.h>
#include <sof/drivers/micfil.h>
#include <sof/lib/dai.h>
#include <sof/lib/dma.h>
#include <sof/lib/memory.h>
#include <sof/lib/pm_runtime.h>
#include <sof/lib/uuid.h>
#include <ipc/dai.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>

LOG_MODULE_REGISTER(micfil_dai, CONFIG_SOF_LOG_LEVEL);

/* dd400475-35d7-4045-ab03-0c34957d7a08 */
DECLARE_SOF_UUID("micfil-dai", micfil_uuid, 0xdd400475, 0x35d7, 0x4045,
0xab, 0x03, 0x0c, 0x34, 0x95, 0x7d, 0x7a, 0x08);

DECLARE_TR_CTX(micfil_tr, SOF_UUID(micfil_uuid), LOG_LEVEL_INFO);

#define MICFIL_OSR_DEFAULT 16
/* set default gain to 2 */
#define MICFIL_DEFAULT_ADJ_RANGE 0x22222222
#define MICFIL_CLK_ROOT 24576000

enum micfil_quality {
QUALITY_HIGH,
QUALITY_MEDIUM,
QUALITY_LOW,
QUALITY_VLOW0,
QUALITY_VLOW1,
QUALITY_VLOW2,
};

static int micfil_reset(struct dai *dai)
{
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_MDIS, 0);
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_SRES, MICFIL_CTRL1_SRES);
dai_update_bits(dai, REG_MICFIL_STAT, 0xff, 0xff);

return 0;
}

static int micfil_get_hw_params(struct dai *dai,
struct sof_ipc_stream_params *params, int dir)
{
struct micfil_pdata *micfil = dai_get_drvdata(dai);

dai_err(dai, "micfil_get_hw_params()");

params->rate = micfil->params.pdm_rate;
params->channels = micfil->params.pdm_ch;
params->buffer_fmt = SOF_IPC_BUFFER_INTERLEAVED;
params->frame_fmt = SOF_IPC_FRAME_S32_LE;

return 0;
}

static int micfil_set_quality(struct dai *dai)
{
struct micfil_pdata *micfil = dai_get_drvdata(dai);
int qsel;

switch (micfil->quality) {
case QUALITY_HIGH:
qsel = MICFIL_QSEL_HIGH_QUALITY;
break;
case QUALITY_MEDIUM:
qsel = MICFIL_QSEL_MEDIUM_QUALITY;
break;
case QUALITY_LOW:
qsel = MICFIL_QSEL_LOW_QUALITY;
break;
case QUALITY_VLOW0:
qsel = MICFIL_QSEL_VLOW0_QUALITY;
break;
case QUALITY_VLOW1:
qsel = MICFIL_QSEL_VLOW1_QUALITY;
break;
case QUALITY_VLOW2:
qsel = MICFIL_QSEL_VLOW2_QUALITY;
break;
default:
return -EINVAL;
}

dai_update_bits(dai, REG_MICFIL_CTRL2, MICFIL_CTRL2_QSEL,
MICFIL_CTRL2_QSEL_BITS(qsel));

return 0;
}

/* get_pdm_clk - computes the product between k-factor and PDM_CLK rate
* @param dai, SOF DAI struct
* @param rate, output sampling rate
*
* PDM_CLK depends on Quality Mode, output sampling rate and quality
* mode
*/
static int micfil_get_pdm_clk(struct dai *dai, int rate)
{
unsigned int osr;
unsigned int qsel;
unsigned int ctrl2_reg;
unsigned int pdm_clk = 0;

ctrl2_reg = dai_read(dai, REG_MICFIL_CTRL2);
osr = 16 - ((ctrl2_reg & MICFIL_CTRL2_CICOSR) >> MICFIL_CTRL2_CICOSR_SHIFT);
qsel = (ctrl2_reg & MICFIL_CTRL2_QSEL) >> MICFIL_CTRL2_QSEL_SHIFT;

switch (qsel) {
case MICFIL_QSEL_HIGH_QUALITY:
pdm_clk = rate * 8 * osr / 2; /* kfactor = 0.5 */
break;
case MICFIL_QSEL_MEDIUM_QUALITY:
case MICFIL_QSEL_VLOW0_QUALITY:
pdm_clk = rate * 4 * osr; /* kfactor = 1 */
break;
case MICFIL_QSEL_LOW_QUALITY:
case MICFIL_QSEL_VLOW1_QUALITY:
pdm_clk = rate * 2 * osr * 2; /* kfactor = 2 */
break;
case MICFIL_QSEL_VLOW2_QUALITY:
pdm_clk = rate * osr; /* kfactor = 4 */
break;
default:
break;
}

return pdm_clk;
}

static int micfil_get_clk_div(struct dai *dai, int rate)
{
int pdm_clk;

pdm_clk = micfil_get_pdm_clk(dai, rate);
if (pdm_clk <= 0)
return -EINVAL;

return MICFIL_CLK_ROOT / (pdm_clk * 2);
}

static int micfil_set_clock_params(struct dai *dai, int rate)
{
int clk_div;

dai_update_bits(dai, REG_MICFIL_CTRL2, MICFIL_CTRL2_CICOSR,
MICFIL_CTRL2_CICOSR_BITS(MICFIL_OSR_DEFAULT));

clk_div = micfil_get_clk_div(dai, rate);
if (clk_div < 0)
return -EINVAL;

dai_update_bits(dai, REG_MICFIL_CTRL2, MICFIL_CTRL2_CLKDIV,
MICFIL_CTRL2_CLKDIV_BITS(clk_div));

return 0;
}

static int micfil_set_config(struct dai *dai, struct ipc_config_dai *common_config,
const void *spec_config)
{
int i, ret;
unsigned int val = 0;
struct micfil_pdata *micfil = dai_get_drvdata(dai);
const struct sof_ipc_dai_config *config = spec_config;

micfil->params = config->micfil;

dai_info(dai, "micfil_set_config() dai_idx %d channels %d sampling_freq %d",
common_config->dai_index, micfil->params.pdm_ch, micfil->params.pdm_rate);

/* disable the module */
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_PDMIEN, 0);

micfil_set_quality(dai);

/* set default gain to 2 */
dai_write(dai, REG_MICFIL_OUT_CTRL, MICFIL_DEFAULT_ADJ_RANGE);

/* set DC Remover in bypass mode */
for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++)
val |= MICFIL_DC_BYPASS << MICFIL_DC_CHX_SHIFT(i);
dai_update_bits(dai, REG_MICFIL_DC_CTRL, MICFIL_DC_CTRL_CONFIG, val);

/* FIFO WMK */
dai_update_bits(dai, REG_MICFIL_FIFO_CTRL, MICFIL_FIFO_CTRL_FIFOWMK,
MICFIL_FIFO_CTRL_FIFOWMK_BITS(31));

/* enable channels */
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_CHNEN,
((1 << micfil->params.pdm_ch) - 1));

ret = micfil_set_clock_params(dai, micfil->params.pdm_rate);
if (ret < 0)
return -EINVAL;

return 0;
}

static int micfil_get_handshake(struct dai *dai, int direction, int stream_id)
{
return dai->plat_data.fifo[SOF_IPC_STREAM_CAPTURE].handshake;
}

static int micfil_get_fifo(struct dai *dai, int direction, int stream_id)
{
return dai->plat_data.fifo[SOF_IPC_STREAM_CAPTURE].offset;
}

static int micfil_get_fifo_depth(struct dai *dai, int direction)
{
return dai->plat_data.fifo[SOF_IPC_STREAM_CAPTURE].depth;
}

static int micfil_start(struct dai *dai)
{
int ret;

dai_info(dai, "micfil_start()");

ret = micfil_reset(dai);
if (ret < 0) {
dai_err(dai, "micfil_start(): failed to reset micfil");
return ret;
}

/* DMA Interrupt Selection - DISEL bits
* 00 - DMA and IRQ disabled
* 01 - DMA req enabled
* 10 - IRQ enabled
* 11 - reserved
*/
dai_update_bits(dai, REG_MICFIL_CTRL1,
MICFIL_CTRL1_DISEL,
MICFIL_CTRL1_DISEL_BITS(MICFIL_CTRL1_DISEL_DMA));

/* Enable the module */
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_PDMIEN, MICFIL_CTRL1_PDMIEN);

return 0;
}

static int micfil_stop(struct dai *dai)
{
dai_info(dai, "micfil_stop()");

/* Disable the module */
dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_PDMIEN, 0);

dai_update_bits(dai, REG_MICFIL_CTRL1, MICFIL_CTRL1_DISEL,
MICFIL_CTRL1_DISEL_BITS(MICFIL_CTRL1_DISEL_DISABLE));

return 0;
}

static int micfil_trigger(struct dai *dai, int cmd, int direction)
{
dai_info(dai, "micfil_trigger() cmd %d dir %d", cmd, direction);

switch (cmd) {
case COMP_TRIGGER_START:
case COMP_TRIGGER_RELEASE:
micfil_start(dai);
break;
case COMP_TRIGGER_STOP:
case COMP_TRIGGER_PAUSE:
micfil_stop(dai);
case COMP_TRIGGER_PRE_START:
case COMP_TRIGGER_PRE_RELEASE:
break;
default:
dai_err(dai, "MICFIL: invalid trigger cmd %d", cmd);
return -EINVAL;
}

return 0;
}

static int micfil_probe(struct dai *dai)
{
struct micfil_pdata *micfil;

dai_info(dai, "micfil_probe()");

micfil = rzalloc(SOF_MEM_ZONE_RUNTIME_SHARED, 0, SOF_MEM_CAPS_RAM, sizeof(*micfil));
if (!micfil) {
dai_err(dai, "micfil probe failed");
return -ENOMEM;
}

micfil->quality = QUALITY_VLOW0;

dai_set_drvdata(dai, micfil);

return 0;
}

static int micfil_remove(struct dai *dai)
{
struct micfil_pdata *micfil = dai_get_drvdata(dai);

dai_info(dai, "micfil_remove()");

rfree(micfil);
dai_set_drvdata(dai, NULL);

return 0;
}

const struct dai_driver micfil_driver = {
.type = SOF_DAI_IMX_MICFIL,
.uid = SOF_UUID(micfil_uuid),
.tctx = &micfil_tr,
.dma_dev = DMA_DEV_MICFIL,
.ops = {
.trigger = micfil_trigger,
.set_config = micfil_set_config,
.get_hw_params = micfil_get_hw_params,
.get_handshake = micfil_get_handshake,
.get_fifo = micfil_get_fifo,
.get_fifo_depth = micfil_get_fifo_depth,
.probe = micfil_probe,
.remove = micfil_remove,
},
};
7 changes: 7 additions & 0 deletions src/include/ipc/dai-imx.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ struct sof_ipc_dai_sai_params {
uint16_t reserved2; /* alignment */

} __attribute__((packed, aligned(4)));

/* MICFIL Configuration Request - SOF_IPC_DAI_MICFIL_CONFIG */
struct sof_ipc_dai_micfil_params {
uint32_t pdm_rate;
uint32_t pdm_ch;
} __attribute__((packed, aligned(4)));

#endif /* __IPC_DAI_IMX_H__ */
4 changes: 3 additions & 1 deletion src/include/ipc/dai.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ enum sof_ipc_dai_type {
SOF_DAI_MEDIATEK_AFE, /**< Mtk AFE */
SOF_DAI_AMD_HS, /**< Amd HS */
SOF_DAI_AMD_SP_VIRTUAL, /**<Amd SP VIRTUAL */
SOF_DAI_AMD_HS_VIRTUAL /**<Amd HS VIRTUAL */
SOF_DAI_AMD_HS_VIRTUAL, /**<Amd HS VIRTUAL */
SOF_DAI_IMX_MICFIL, /**< i.MX MICFIL */
};

/* general purpose DAI configuration */
Expand Down Expand Up @@ -122,6 +123,7 @@ struct sof_ipc_dai_config {
struct sof_ipc_dai_acpdmic_params acpdmic;
struct sof_ipc_dai_acp_params acphs;
struct sof_ipc_dai_afe_params afe;
struct sof_ipc_dai_micfil_params micfil;
};
} __attribute__((packed, aligned(4)));

Expand Down
Loading

0 comments on commit efa2cb7

Please sign in to comment.