Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

driver: imx: Add PDM MICFIL driver #8184

Merged
merged 2 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
11 changes: 10 additions & 1 deletion 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 All @@ -36,7 +45,7 @@ config IMX_INTERRUPT_GENERIC
It enables NXP platforms-specific features.

config HAVE_SDMA_FIRMWARE
bool
bool "Enable SDMA firmware load"
default n
help
Select this to load SDMA firmware to enable additional functionality
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 void 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);
}
dbaluta marked this conversation as resolved.
Show resolved Hide resolved

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:
dai_err(dai, "MICFIL: invalid quality mode %d", micfil->quality);
return -EINVAL;
iuliana-prodan marked this conversation as resolved.
Show resolved Hide resolved
}

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 unsigned 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;

/* See Quality modes chapter in MICFIL documentation */
switch (qsel) {
case MICFIL_QSEL_HIGH_QUALITY:
pdm_clk = rate * 8 * osr / 2; /* kfactor = 0.5 */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is identical to the case below (8 / 2 = 4).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this has the same final value! But, I prefer to keep it that way to keep this in line with the documentation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... This is very... strange. It's your area so it's up to you to decide, but... This looks very confusing to me. You have multiple switch-case entries for different quality values, you seem to have different formulae, ant comments specify different "kfactor" values, but the result is the same. At the very least I'd put a comment there, preferably I'd unify all the cases and provide just one formula, and just explain in the comment how that matches the documentation

Copy link
Collaborator Author

@dbaluta dbaluta Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this comes from this table:

image

and this formula

image

I will add more comments to better explain the code.

What we want is to compute the CLK_DIV value so that we proper setup the MICFIL registers.

since

CLKDIV = MICFIL_CLK_ROOT  / ( 2 * K * PDM_CLK_RATE)

and since

K and PDM_CLK_RATE have different values for different clk rates we end up with equal values sometimes, but as I said I prefer to write the complete formulas for documentation purposes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, an explanation would be good, because even with that table I don't understand: e.g. with high quality the formula in the table seems to be

pdm_clk = rate * 8 * osr;

whereas you use

pdm_clk = rate * 8 * osr / 2;

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 */
dbaluta marked this conversation as resolved.
Show resolved Hide resolved
break;
case MICFIL_QSEL_VLOW2_QUALITY:
pdm_clk = rate * osr; /* kfactor = 4 */
dbaluta marked this conversation as resolved.
Show resolved Hide resolved
break;
default:
break;
}

return pdm_clk;
}

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

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

/*
*
* See: Clock divider chapter from micfil documentation
* PDM_CLK rate = MICFIL_CLK_ROOT rate / (2 * K * CLKDIV)
*
* this means that if we want to compute CLKDIV then:
* CLKDIV = MICFIL_CLK_ROOT rate / (PDM_CLK rate * K * 2)
*
* micfil_get_pdm_clk function returns K * PDM_CLK rate
*/
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 clk_div;

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 ret;

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 void micfil_start(struct dai *dai)
{
dai_info(dai, "micfil_start()");

micfil_reset(dai);

/* 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);
}

static void 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));
}

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);
dbaluta marked this conversation as resolved.
Show resolved Hide resolved
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__ */
Loading