diff options
-rw-r--r-- | sound/soc/fsl/fsl_micfil.c | 1679 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_micfil.h | 53 |
2 files changed, 1710 insertions, 22 deletions
diff --git a/sound/soc/fsl/fsl_micfil.c b/sound/soc/fsl/fsl_micfil.c index f7f2d29f1bfe..4efd09f0a2c2 100644 --- a/sound/soc/fsl/fsl_micfil.c +++ b/sound/soc/fsl/fsl_micfil.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright 2018 NXP +#include <linux/atomic.h> #include <linux/clk.h> #include <linux/device.h> #include <linux/interrupt.h> @@ -32,7 +33,10 @@ struct fsl_micfil { struct regmap *regmap; const struct fsl_micfil_soc_data *soc; struct clk *mclk; + struct clk *clk_src[MICFIL_CLK_SRC_NUM]; struct snd_dmaengine_dai_dma_data dma_params_rx; + struct kobject *hwvad_kobject; + unsigned int vad_channel; unsigned int dataline; char name[32]; int irq[MICFIL_IRQ_LINES]; @@ -40,6 +44,25 @@ struct fsl_micfil { int quality; /*QUALITY 2-0 bits */ bool slave_mode; int channel_gain[8]; + int clk_src_id; + int dc_remover; + int vad_sound_gain; + int vad_noise_gain; + int vad_input_gain; + int vad_frame_time; + int vad_init_time; + int vad_init_mode; + int vad_nfil_adjust; + int vad_hpf; + int vad_zcd_th; + int vad_zcd_auto; + int vad_zcd_en; + int vad_zcd_adj; + int vad_rate_index; + atomic_t recording_state; + atomic_t hwvad_state; + /* spinlock to control HWVAD enable/disable */ + spinlock_t hwvad_lock; }; struct fsl_micfil_soc_data { @@ -49,6 +72,11 @@ struct fsl_micfil_soc_data { bool imx; }; +static char *envp[] = { + "EVENT=PDM_VOICE_DETECT", + NULL, +}; + static struct fsl_micfil_soc_data fsl_micfil_imx8mm = { .imx = true, .fifos = 8, @@ -77,11 +105,525 @@ static const char * const micfil_quality_select_texts[] = { "VLow0", "Low", }; +static const char * const micfil_hwvad_init_mode[] = { + "Envelope mode", "Energy mode", +}; + +static const char * const micfil_hwvad_hpf_texts[] = { + "Filter bypass", + "Cut-off @1750Hz", + "Cut-off @215Hz", + "Cut-off @102Hz", +}; + +static const char * const micfil_hwvad_zcd_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_zcdauto_enable[] = { + "OFF", "ON", +}; + +static const char * const micfil_hwvad_noise_decimation[] = { + "Disabled", "Enabled", +}; + +/* when adding new rate text, also add it to the + * micfil_hwvad_rate_ints + */ +static const char * const micfil_hwvad_rate[] = { + "48KHz", "44.1KHz", +}; + +static const int micfil_hwvad_rate_ints[] = { + 48000, 44100, +}; + +static const char * const micfil_clk_src_texts[] = { + "Auto", "AudioPLL1", "AudioPLL2", "ExtClk3", +}; + +/* DC Remover Control + * Filter Bypassed 1 1 + * Cut-off @21Hz 0 0 + * Cut-off @83Hz 0 1 + * Cut-off @152HZ 1 0 + */ +static const char * const micfil_dc_remover_texts[] = { + "Cut-off @21Hz", "Cut-off @83Hz", + "Cut-off @152Hz", "Bypass", +}; + static const struct soc_enum fsl_micfil_quality_enum = SOC_ENUM_SINGLE(REG_MICFIL_CTRL2, MICFIL_CTRL2_QSEL_SHIFT, ARRAY_SIZE(micfil_quality_select_texts), micfil_quality_select_texts); +static const struct soc_enum fsl_micfil_hwvad_init_mode_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_init_mode), + micfil_hwvad_init_mode); +static const struct soc_enum fsl_micfil_hwvad_hpf_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_hpf_texts), + micfil_hwvad_hpf_texts); +static const struct soc_enum fsl_micfil_hwvad_zcd_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcd_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_zcdauto_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_zcdauto_enable), + micfil_hwvad_zcd_enable); +static const struct soc_enum fsl_micfil_hwvad_ndec_enum = + SOC_ENUM_SINGLE(REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NOREN_SHIFT, + ARRAY_SIZE(micfil_hwvad_noise_decimation), + micfil_hwvad_noise_decimation); +static const struct soc_enum fsl_micfil_hwvad_rate_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_hwvad_rate), + micfil_hwvad_rate); +static const struct soc_enum fsl_micfil_clk_src_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_clk_src_texts), + micfil_clk_src_texts); +static const struct soc_enum fsl_micfil_dc_remover_enum = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(micfil_dc_remover_texts), + micfil_dc_remover_texts); + +static int micfil_put_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->clk_src_id = val; + + return 0; +} + +static int micfil_get_clk_src(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->clk_src_id; + + return 0; +} + +static int micfil_put_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + unsigned int *item = ucontrol->value.enumerated.item; + int val = snd_soc_enum_item_to_val(e, item[0]); + int i = 0, ret = 0; + u32 reg_val = 0; + + if (val < 0 || val > 3) + return -EINVAL; + + micfil->dc_remover = val; + + /* Calculate total value for all channels */ + for (i = 0; i < 8; i++) + reg_val |= MICFIL_DC_MODE(val, i); + + /* Update DC Remover mode for all channels */ + ret = snd_soc_component_update_bits(comp, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, reg_val); + if (ret < 0) + return ret; + + return 0; +} + +static int micfil_get_dc_remover_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->dc_remover; + + return 0; +} + +static int hwvad_put_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 0 - Envelope-based Mode + * 1 - Energy-based Mode + */ + micfil->vad_init_mode = val; + return 0; +} + +static int hwvad_get_init_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_mode; + + return 0; +} + +static int hwvad_put_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + /* 00 - HPF Bypass + * 01 - Cut-off frequency 1750Hz + * 10 - Cut-off frequency 215Hz + * 11 - Cut-off frequency 102Hz + */ + micfil->vad_hpf = val; + + return 0; +} + +static int hwvad_get_hpf(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_hpf; + + return 0; +} + +static int hwvad_put_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_en = val; + + return 0; +} + +static int hwvad_get_zcd_en(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_en; + + return 0; +} + +static int hwvad_put_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_rate_index = val; + + return 0; +} + +static int hwvad_get_rate(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_rate_index; + + return 0; +} + +static int hwvad_put_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + int val = snd_soc_enum_item_to_val(e, item[0]); + + micfil->vad_zcd_auto = val; + + return 0; +} + +static int hwvad_get_zcd_auto(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_auto; + + return 0; +} + +static int gain_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0xf; + + return 0; +} + +static int hwvad_put_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_input_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_input_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_input_gain; + + return 0; +} + +static int hwvad_put_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_sound_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_sound_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_sound_gain; + + return 0; +} + +static int hwvad_put_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_noise_gain = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_noise_gain(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_noise_gain; + + return 0; +} + +static int hwvad_framet_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 64; + + return 0; +} + +static int hwvad_put_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_frame_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_frame_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_frame_time; + + return 0; +} + +static int hwvad_initt_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_init_time = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_init_time(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_init_time; + + return 0; +} + +static int hwvad_nfiladj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 32; + + return 0; +} + +static int hwvad_put_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_nfil_adjust = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_nfil_adjust(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_nfil_adjust; + + return 0; +} + +static int hwvad_zcdth_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 1024; + + return 0; +} + +static int hwvad_put_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_th = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_th(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_th; + + return 0; +} + +static int hwvad_zcdadj_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 16; + + return 0; +} + +static int hwvad_put_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + micfil->vad_zcd_adj = ucontrol->value.integer.value[0]; + + return 0; +} + +static int hwvad_get_zcd_adj(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol); + struct fsl_micfil *micfil = snd_soc_component_get_drvdata(comp); + + ucontrol->value.enumerated.item[0] = micfil->vad_zcd_adj; + + return 0; +} static DECLARE_TLV_DB_SCALE(gain_tlv, 0, 100, 0); @@ -105,8 +647,107 @@ static const struct snd_kcontrol_new fsl_micfil_snd_controls[] = { SOC_ENUM_EXT("MICFIL Quality Select", fsl_micfil_quality_enum, snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Initialization Mode", + fsl_micfil_hwvad_init_mode_enum, + hwvad_get_init_mode, hwvad_put_init_mode), + SOC_ENUM_EXT("HWVAD High-Pass Filter", + fsl_micfil_hwvad_hpf_enum, + hwvad_get_hpf, hwvad_put_hpf), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Enable", + fsl_micfil_hwvad_zcd_enum, + hwvad_get_zcd_en, hwvad_put_zcd_en), + SOC_ENUM_EXT("HWVAD Zero-Crossing Detector Auto Threshold", + fsl_micfil_hwvad_zcdauto_enum, + hwvad_get_zcd_auto, hwvad_put_zcd_auto), + SOC_ENUM_EXT("HWVAD Noise OR Enable", + fsl_micfil_hwvad_ndec_enum, + snd_soc_get_enum_double, snd_soc_put_enum_double), + SOC_ENUM_EXT("HWVAD Sampling Rate", + fsl_micfil_hwvad_rate_enum, + hwvad_get_rate, hwvad_put_rate), + SOC_ENUM_EXT("Clock Source", + fsl_micfil_clk_src_enum, + micfil_get_clk_src, micfil_put_clk_src), + SOC_ENUM_EXT("MICFIL DC Remover Control", fsl_micfil_dc_remover_enum, + micfil_get_dc_remover_state, micfil_put_dc_remover_state), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Input Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_input_gain, + .put = hwvad_put_input_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Sound Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_sound_gain, + .put = hwvad_put_sound_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Gain", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = gain_info, + .get = hwvad_get_noise_gain, + .put = hwvad_put_noise_gain, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Frame Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_framet_info, + .get = hwvad_get_frame_time, + .put = hwvad_put_frame_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Detector Initialization Time", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_initt_info, + .get = hwvad_get_init_time, + .put = hwvad_put_init_time, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Noise Filter Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_nfiladj_info, + .get = hwvad_get_nfil_adjust, + .put = hwvad_put_nfil_adjust, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Threshold", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdth_info, + .get = hwvad_get_zcd_th, + .put = hwvad_put_zcd_th, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "HWVAD Zero-Crossing Detector Adjustment", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE, + .info = hwvad_zcdadj_info, + .get = hwvad_get_zcd_adj, + .put = hwvad_put_zcd_adj, + }, + }; +static int disable_hwvad(struct device *dev, bool sync); + + static inline int get_pdm_clk(struct fsl_micfil *micfil, unsigned int rate) { @@ -193,19 +834,587 @@ static int fsl_micfil_reset(struct device *dev) return 0; } -static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, +/* enable/disable hwvad interrupts */ +static int configure_hwvad_interrupts(struct device *dev, + int enable) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 vadie_reg = enable ? MICFIL_VAD0_CTRL1_IE : 0; + u32 vaderie_reg = enable ? MICFIL_VAD0_CTRL1_ERIE : 0; + + /* Voice Activity Detector Error Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ERIE_MASK, + vaderie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADERIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Interruption Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_IE_MASK, + vadie_reg); + if (ret) { + dev_err(dev, + "Failed to set/clear VADIE in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + return 0; +} + +static int init_hwvad_internal_filters(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* Voice Activity Detector Internal Filters Initialization*/ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + MICFIL_VAD0_CTRL1_ST10); + if (ret) { + dev_err(dev, + "Failed to set VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + + /* sleep for 100ms - it should be enough for bit to stay + * pulsed for more than 2 cycles + */ + mdelay(MICFIL_SLEEP); + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_ST10_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to clear VADST10 in CTRL1_VAD0 [%d]\n", + ret); + return ret; + } + return 0; +} + +/* Zero-Crossing Detector Initialization + * Optionally a Zero-Crossing Detection block (ZCD) could + * be enabled to avoid low energy voiced speech be missed, + * improving the voice detection performance. + * See Section 8.4.3 + */ +static int __maybe_unused init_zcd(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + + /* exit if zcd is not enabled from userspace */ + if (!micfil->vad_zcd_en) + return 0; + + if (micfil->vad_zcd_auto) { + /* Zero-Crossing Detector Adjustment */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDADJ_MASK, + micfil->vad_zcd_adj); + if (ret) { + dev_err(dev, + "Failed to set ZCDADJ in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + } + + /* Zero-Crossing Detector Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDTH_MASK, + MICFIL_VAD0_ZCD_ZCDTH(micfil->vad_zcd_th)); + if (ret) { + dev_err(dev, "Failed to set ZCDTH in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector AND Behavior */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAND_MASK, + MICFIL_HWVAD_ZCDAND); + if (ret) { + dev_err(dev, "Failed to set ZCDAND in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + /* Zero-Crossing Detector Automatic Threshold */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDAUT_MASK, + micfil->vad_zcd_auto); + if (ret) { + dev_err(dev, + "Failed to set/clear ZCDAUT in ZCD_VAD0 [%d]\n", + ret); + return ret; + } + + /* Zero-Crossing Detector Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_ZCD, + MICFIL_VAD0_ZCD_ZCDEN_MASK, + MICFIL_VAD0_ZCD_ZCDEN); + if (ret) { + dev_err(dev, "Failed to set ZCDEN in ZCD_VAD0 [%d]\n", ret); + return ret; + } + + return 0; +} + +/* Configuration done only in energy-based initialization mode */ +static int init_hwvad_energy_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + dev_info(dev, "Energy-based mode initialization\n"); + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(5 * MICFIL_SLEEP); + + /* Enable Interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + return ret; +} + +/* Configuration done only in envelope-based initialization mode */ +static int init_hwvad_envelope_mode(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret, i; + u32 stat; + u32 flag; + + /* Frame energy disable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRENDIS_MASK, + MICFIL_VAD0_CTRL2_FRENDIS); + if (ret) { + dev_err(dev, "Failed to set FRENDIS in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable pre-filter Noise & Signal */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + MICFIL_VAD0_CTRL2_PREFEN); + if (ret) { + dev_err(dev, "Failed to set PREFEN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable Signal Filter */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + MICFIL_VAD0_SCONFIG_SFILEN); + if (ret) { + dev_err(dev, + "Failed to set SFILEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Signal Maximum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + MICFIL_VAD0_SCONFIG_SMAXEN); + if (ret) { + dev_err(dev, + "Failed to set SMAXEN in SCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Allways enable noise filter, not based on voice activity + * information + */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILAUT_MASK, + 0); + if (ret) { + dev_err(dev, + "Failed to set NFILAUT in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Minimum Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NMINEN_MASK, + MICFIL_VAD0_NCONFIG_NMINEN); + if (ret) { + dev_err(dev, + "Failed to set NMINEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Noise Decimation Enable */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + MICFIL_VAD0_NCONFIG_NDECEN); + if (ret) { + dev_err(dev, + "Failed to set NDECEN in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* Voice Activity Detector Reset */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_MASK, + MICFIL_VAD0_CTRL1_RST); + if (ret) { + dev_err(dev, "Failed to set VADRST in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Initialize Zero Crossing Detector */ + ret = init_zcd(dev); + if (ret) + return ret; + + /* Voice Activity Detector Enabled */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + MICFIL_VAD0_CTRL1_EN); + if (ret) { + dev_err(dev, "Failed to set VADEN in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* Enable MICFIL module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + MICFIL_CTRL1_PDMIEN); + if (ret) { + dev_err(dev, "failed to enable the module\n"); + return ret; + } + + /* it would be a good idea to wait some time before VADEN + * is set + */ + mdelay(3 * MICFIL_SLEEP); + + /* Wait for INITF to be asserted */ + for (i = 0; i < MICFIL_MAX_RETRY; i++) { + ret = regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &stat); + if (ret) { + dev_err(dev, "failed to read register %d\n", + REG_MICFIL_VAD0_STAT); + return ret; + } + + flag = (stat & MICFIL_VAD0_STAT_INITF_MASK); + if (flag == 0) + break; + + mdelay(MICFIL_SLEEP); + } + + if (i == MICFIL_MAX_RETRY) { + dev_err(dev, "initf not asserted. Failed to init hwvad\n"); + return -EBUSY; + } + + /* Initialize Internal Filters */ + ret = init_hwvad_internal_filters(dev); + if (ret) + return ret; + + /* Enable interrupts */ + ret = configure_hwvad_interrupts(dev, 1); + if (ret) + return ret; + + return ret; +} + +/* Hardware Voice Active Detection: The HWVAD takes data from the input + * of a selected PDM microphone to detect if there is any + * voice activity. When a voice activity is detected, an interrupt could + * be delivered to the system. Initialization in section 8.4: + * Can work in two modes: + * -> Eneveope-based mode (section 8.4.1) + * -> Energy-based mode (section 8.4.2) + * + * It is important to remark that the HWVAD detector could be enabled + * or reset only when the MICFIL isn't running i.e. when the BSY_FIL + * bit in STAT register is cleared + */ +static int __maybe_unused init_hwvad(struct device *dev) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 reg_val; + + /* configure CIC OSR in VADCICOSR */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CICOSR_MASK, + MICFIL_CTRL2_OSR_DEFAULT); + if (ret) { + dev_err(dev, "Failed to set CICOSR in CTRL1_VAD0i [%d]\n", ret); + return ret; + } + + /* configure source channel in VADCHSEL */ + reg_val = MICFIL_VAD0_CTRL1_CHSEL(micfil->vad_channel); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_CHSEL_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set CHSEL in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure detector frame time VADFRAMET */ + reg_val = MICFIL_VAD0_CTRL2_FRAMET(micfil->vad_frame_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_FRAMET_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set FRAMET in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure initialization time in VADINITT */ + reg_val = MICFIL_VAD0_CTRL1_INITT(micfil->vad_init_time); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_INITT_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INITT in CTRL1_VAD0 [%d]\n", ret); + return ret; + } + + /* configure input gain in VADINPGAIN */ + reg_val = MICFIL_VAD0_CTRL2_INPGAIN(micfil->vad_input_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_INPGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set INPGAIN in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* configure sound gain in SGAIN */ + reg_val = MICFIL_VAD0_SCONFIG_SGAIN(micfil->vad_sound_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set SGAIN in SCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure noise gain in NGAIN */ + reg_val = MICFIL_VAD0_NCONFIG_NGAIN(micfil->vad_noise_gain); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NGAIN_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set NGAIN in NCONFIG_VAD0 [%d]\n", ret); + return ret; + } + + /* configure or clear the VADNFILADJ based on mode */ + reg_val = MICFIL_VAD0_NCONFIG_NFILADJ(micfil->vad_nfil_adjust); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NFILADJ_MASK, + reg_val); + if (ret) { + dev_err(dev, + "Failed to set VADNFILADJ in NCONFIG_VAD0 [%d]\n", + ret); + return ret; + } + + /* enable the high-pass filter in VADHPF */ + reg_val = MICFIL_VAD0_CTRL2_HPF(micfil->vad_hpf); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_HPF_MASK, + reg_val); + if (ret) { + dev_err(dev, "Failed to set HPF in CTRL2_VAD0 [%d]\n", ret); + return ret; + } + + /* envelope-based specific initialization */ + if (micfil->vad_init_mode == MICFIL_HWVAD_ENVELOPE_MODE) { + ret = init_hwvad_envelope_mode(dev); + if (ret) + return ret; + } else { + ret = init_hwvad_energy_mode(dev); + if (ret) + return ret; + } + + return 0; +} + +static inline bool clk_in_list(struct clk *p, struct clk *clk_src[]) +{ + int i; + + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) + if (clk_is_match(p, clk_src[i])) + return true; + + return false; +} + +static int fsl_micfil_set_mclk_rate(struct fsl_micfil *micfil, int clk_id, unsigned int freq) { + struct clk *p = micfil->mclk, *pll = 0, *npll = 0; struct device *dev = &micfil->pdev->dev; + u64 ratio = freq; + u64 clk_rate; int ret; + int i; + + /* Do not touch the clock if hwvad is already enabled + * since you can record only at hwvad rate and clock + * has already been set to the required frequency + */ + if (atomic_read(&micfil->hwvad_state) == MICFIL_HWVAD_ON) + return 0; + + /* check if all clock sources are valid */ + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + if (micfil->clk_src[i]) + continue; + + dev_err(dev, "Clock Source %d is not valid.\n", i); + return -EINVAL; + } + + while (p) { + struct clk *pp = clk_get_parent(p); + + if (clk_in_list(pp, micfil->clk_src)) { + pll = pp; + break; + } + p = pp; + } + + if (!pll) { + dev_err(dev, "reached a null clock\n"); + return -EINVAL; + } + + if (micfil->clk_src_id == MICFIL_CLK_AUTO) { + for (i = 0; i < MICFIL_CLK_SRC_NUM; i++) { + clk_rate = clk_get_rate(micfil->clk_src[i]); + /* This is an workaround since audio_pll2 clock + * has 722534399 rate and this will never divide + * to any known frequency ??? + */ + clk_rate = round_up(clk_rate, 10); + if (do_div(clk_rate, ratio) == 0) + npll = micfil->clk_src[i]; + } + } else { + /* clock id is offseted by 1 since ID=0 means + * auto clock selection + */ + npll = micfil->clk_src[micfil->clk_src_id - 1]; + } + + if (!npll) { + dev_err(dev, + "failed to find a suitable clock source\n"); + return -EINVAL; + } clk_disable_unprepare(micfil->mclk); + if (!clk_is_match(pll, npll)) { + ret = clk_set_parent(p, npll); + if (ret < 0) + dev_warn(dev, + "failed to set parrent %d\n", ret); + } ret = clk_set_rate(micfil->mclk, freq * 1024); if (ret) dev_warn(dev, "failed to set rate (%u): %d\n", freq * 1024, ret); - clk_prepare_enable(micfil->mclk); return ret; @@ -298,7 +1507,7 @@ static int fsl_set_clock_params(struct device *dev, unsigned int rate) int clk_div; int ret = 0; - ret = fsl_micfil_set_mclk_rate(micfil, rate); + ret = fsl_micfil_set_mclk_rate(micfil, 0, rate); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), rate); @@ -333,14 +1542,32 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, unsigned int channels = params_channels(params); unsigned int rate = params_rate(params); struct device *dev = &micfil->pdev->dev; + unsigned int hwvad_rate; int ret; + u32 hwvad_state; - /* 1. Disable the module */ - ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, - MICFIL_CTRL1_PDMIEN_MASK, 0); - if (ret) { - dev_err(dev, "failed to disable the module\n"); - return ret; + hwvad_rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + hwvad_state = atomic_read(&micfil->hwvad_state); + + /* if hwvad is enabled, make sure you are recording at + * the same rate the hwvad is on or reject it to avoid + * changing the clock rate. + */ + if (hwvad_state == MICFIL_HWVAD_ON && rate != hwvad_rate) { + dev_err(dev, "Record at hwvad rate %u\n", hwvad_rate); + return -EINVAL; + } + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_ON); + + if (hwvad_state == MICFIL_HWVAD_OFF) { + /* 1. Disable the module */ + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, 0); + if (ret) { + dev_err(dev, "failed to disable the module\n"); + return ret; + } } /* enable channels */ @@ -358,11 +1585,22 @@ static int fsl_micfil_hw_params(struct snd_pcm_substream *substream, return ret; } + micfil->dma_params_rx.fifo_num = channels; micfil->dma_params_rx.maxburst = channels * MICFIL_DMA_MAXBURST_RX; return 0; } +static int fsl_micfil_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + atomic_set(&micfil->recording_state, MICFIL_RECORDING_OFF); + + return 0; +} + static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { @@ -374,7 +1612,7 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, if (!freq) return 0; - ret = fsl_micfil_set_mclk_rate(micfil, freq); + ret = fsl_micfil_set_mclk_rate(micfil, clk_id, freq); if (ret < 0) dev_err(dev, "failed to set mclk[%lu] to rate %u\n", clk_get_rate(micfil->mclk), freq); @@ -382,11 +1620,38 @@ static int fsl_micfil_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, return ret; } +static int fsl_micfil_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct fsl_micfil *micfil = snd_soc_dai_get_drvdata(dai); + + /* DAI MODE */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + return -EINVAL; + } + + /* DAI CLK INVERSION */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + micfil->slave_mode = false; + + return 0; +} + static struct snd_soc_dai_ops fsl_micfil_dai_ops = { .startup = fsl_micfil_startup, .trigger = fsl_micfil_trigger, .hw_params = fsl_micfil_hw_params, + .hw_free = fsl_micfil_hw_free, .set_sysclk = fsl_micfil_set_dai_sysclk, + .set_fmt = fsl_micfil_set_dai_fmt, }; static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) @@ -399,17 +1664,30 @@ static int fsl_micfil_dai_probe(struct snd_soc_dai *cpu_dai) /* set qsel to medium */ ret = regmap_update_bits(micfil->regmap, REG_MICFIL_CTRL2, - MICFIL_CTRL2_QSEL_MASK, MICFIL_MEDIUM_QUALITY); + MICFIL_CTRL2_QSEL_MASK, MICFIL_VLOW0_QUALITY); if (ret) { dev_err(dev, "failed to set quality mode bits, reg 0x%X\n", REG_MICFIL_CTRL2); return ret; } - /* set default gain to max_gain */ - regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x77777777); - for (i = 0; i < 8; i++) - micfil->channel_gain[i] = 0xF; + /* set default gain to 2 */ + regmap_write(micfil->regmap, REG_MICFIL_OUT_CTRL, 0x22222222); + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + micfil->channel_gain[i] = 0xA; + + /* set DC Remover in bypass mode*/ + val = 0; + for (i = 0; i < MICFIL_OUTPUT_CHANNELS; i++) + val |= MICFIL_DC_MODE(MICFIL_DC_BYPASS, i); + ret = regmap_update_bits(micfil->regmap, REG_MICFIL_DC_CTRL, + MICFIL_DC_CTRL_MASK, val); + if (ret) { + dev_err(dev, "failed to set DC Remover mode bits, reg 0x%X\n", + REG_MICFIL_DC_CTRL); + return ret; + } + micfil->dc_remover = MICFIL_DC_BYPASS; snd_soc_dai_init_dma_data(cpu_dai, NULL, &micfil->dma_params_rx); @@ -566,6 +1844,72 @@ static const struct regmap_config fsl_micfil_regmap_config = { /* END OF REGMAP */ +static irqreturn_t voice_detected_fn(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + + /* disable hwvad */ + spin_lock(&micfil->hwvad_lock); + ret = disable_hwvad(dev, true); + spin_unlock(&micfil->hwvad_lock); + + if (ret) + dev_err(dev, "Failed to disable HWVAD module: %d\n", ret); + + /* notify userspace that voice was detected */ + kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp); + + return IRQ_HANDLED; +} + +static irqreturn_t hwvad_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + int ret; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + /* The only difference between MICFIL_VAD0_STAT_EF and + * MICFIL_VAD0_STAT_IF is that the former requires Write + * 1 to Clear. Since both flags are set, it is enough + * to only read one of them + */ + if (vad0_reg & MICFIL_VAD0_STAT_IF_MASK) { + /* Write 1 to clear */ + regmap_write_bits(micfil->regmap, REG_MICFIL_VAD0_STAT, + MICFIL_VAD0_STAT_IF_MASK, + MICFIL_VAD0_STAT_IF); + + /* disable hwvad interrupts */ + ret = configure_hwvad_interrupts(dev, 0); + if (ret) + dev_err(dev, "Failed to disable interrupts\n"); + } + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hwvad_err_isr(int irq, void *devid) +{ + struct fsl_micfil *micfil = (struct fsl_micfil *)devid; + struct device *dev = &micfil->pdev->dev; + u32 vad0_reg; + + regmap_read(micfil->regmap, REG_MICFIL_VAD0_STAT, &vad0_reg); + + if (vad0_reg & MICFIL_VAD0_STAT_INSATF_MASK) + dev_dbg(dev, "voice activity input overflow/underflow detected\n"); + + if (vad0_reg & MICFIL_VAD0_STAT_INITF_MASK) + dev_dbg(dev, "voice activity dectector is initializing\n"); + + return IRQ_HANDLED; +} + static irqreturn_t micfil_isr(int irq, void *devid) { struct fsl_micfil *micfil = (struct fsl_micfil *)devid; @@ -635,6 +1979,197 @@ static irqreturn_t micfil_err_isr(int irq, void *devid) return IRQ_HANDLED; } +static int fsl_set_clock_params(struct device *, unsigned int); + +static int enable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + int rate; + u32 state; + + if (sync) + pm_runtime_get_sync(dev); + + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_OFF, + MICFIL_HWVAD_ON); + + /* we should not reenable when sync = true because + * this means enable was called for second time by + * user. However state = ON and sync = false can only + * occur when enable is called from system_resume. In + * this case we should enable the hwvad + */ + if (sync && state == MICFIL_HWVAD_ON) { + dev_err(dev, "hwvad already on\n"); + ret = -EBUSY; + goto enable_error; + } + + if (micfil->vad_rate_index >= ARRAY_SIZE(micfil_hwvad_rate_ints)) { + dev_err(dev, "There are more select texts than rates\n"); + ret = -EINVAL; + goto enable_error; + } + + rate = micfil_hwvad_rate_ints[micfil->vad_rate_index]; + + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + ret = fsl_set_clock_params(dev, rate); + if (ret) + goto enable_error; + + ret = fsl_micfil_reset(dev); + if (ret) + goto enable_error; + + /* Initialize Hardware Voice Activity */ + ret = init_hwvad(dev); + if (ret == 0) + return 0; + +enable_error: + if (state == MICFIL_HWVAD_OFF) + atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, MICFIL_HWVAD_OFF); + if (sync) + pm_runtime_put_sync(dev); + return ret; +} + +static int disable_hwvad(struct device *dev, bool sync) +{ + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret = 0; + u32 state; + + /* This is required because if an arecord was done, + * suspend function will mark regmap as cache only + * and reads/writes in volatile regs will fail + */ + regcache_cache_only(micfil->regmap, false); + regcache_mark_dirty(micfil->regmap); + regcache_sync(micfil->regmap); + + /* disable is called with sync = false only from + * system suspend and in this case, you should not + * change the hwvad_state so we know at system_resume + * to reenable hwvad + */ + if (sync) + state = atomic_cmpxchg(&micfil->hwvad_state, + MICFIL_HWVAD_ON, + MICFIL_HWVAD_OFF); + else + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + /* Voice Activity Detector Reset */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_RST_SHIFT, + MICFIL_VAD0_CTRL1_RST); + + /* Disable HWVAD */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL1, + MICFIL_VAD0_CTRL1_EN_MASK, + 0); + + /* Disable Signal Filter */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SFILEN_MASK, + 0); + + /* Signal Maximum Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_SCONFIG, + MICFIL_VAD0_SCONFIG_SMAXEN_MASK, + 0); + + /* Enable pre-filter Noise & Signal */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_CTRL2, + MICFIL_VAD0_CTRL2_PREFEN_MASK, + 0); + + /* Noise Decimation Enable */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_VAD0_NCONFIG, + MICFIL_VAD0_NCONFIG_NDECEN_MASK, + 0); + + /* disable the module and clock only if recording + * is not done in parallel + */ + state = atomic_read(&micfil->recording_state); + if (state == MICFIL_RECORDING_OFF) { + /* Disable MICFIL module */ + ret |= regmap_update_bits(micfil->regmap, + REG_MICFIL_CTRL1, + MICFIL_CTRL1_PDMIEN_MASK, + 0); + } + + } else { + ret = -EPERM; + dev_err(dev, "HWVAD is not enabled %d\n", ret); + } + + if (sync) + pm_runtime_put_sync(dev); + + return ret; +} + +static ssize_t micfil_hwvad_handler(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + struct kobject *nand_kobj = kobj->parent; + struct device *dev = container_of(nand_kobj, struct device, kobj); + struct fsl_micfil *micfil = dev_get_drvdata(dev); + unsigned long vad_channel, flags; + int ret; + + ret = kstrtoul(buf, 16, &vad_channel); + if (ret < 0) + return -EINVAL; + + spin_lock_irqsave(&micfil->hwvad_lock, flags); + if (vad_channel <= 7) { + micfil->vad_channel = vad_channel; + ret = enable_hwvad(dev, true); + } else { + micfil->vad_channel = -1; + ret = disable_hwvad(dev, true); + } + spin_unlock_irqrestore(&micfil->hwvad_lock, flags); + + if (ret) { + dev_err(dev, "Failed to %s hwvad: %d\n", + vad_channel <= 7 ? "enable" : "disable", ret); + return ret; + } + + return count; +} + +static struct kobj_attribute hwvad_en_attr = __ATTR(enable, + 0660, + NULL, + micfil_hwvad_handler); + static int fsl_micfil_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -668,6 +2203,19 @@ static int fsl_micfil_probe(struct platform_device *pdev) return PTR_ERR(micfil->mclk); } + /* get audio pll1 and pll2 */ + micfil->clk_src[MICFIL_AUDIO_PLL1] = devm_clk_get(&pdev->dev, "pll8k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL1])) + micfil->clk_src[MICFIL_AUDIO_PLL1] = NULL; + + micfil->clk_src[MICFIL_AUDIO_PLL2] = devm_clk_get(&pdev->dev, "pll11k"); + if (IS_ERR(micfil->clk_src[MICFIL_AUDIO_PLL2])) + micfil->clk_src[MICFIL_AUDIO_PLL2] = NULL; + + micfil->clk_src[MICFIL_CLK_EXT3] = devm_clk_get(&pdev->dev, "clkext3"); + if (IS_ERR(micfil->clk_src[MICFIL_CLK_EXT3])) + micfil->clk_src[MICFIL_CLK_EXT3] = NULL; + /* init regmap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); @@ -711,6 +2259,30 @@ static int fsl_micfil_probe(struct platform_device *pdev) if (of_property_read_bool(np, "fsl,shared-interrupt")) irqflag = IRQF_SHARED; + /* Digital Microphone interface voice activity detector event + * interrupt - IRQ 44 + */ + ret = devm_request_threaded_irq(&pdev->dev, micfil->irq[2], + hwvad_isr, voice_detected_fn, + irqflag, micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad event irq %u\n", + micfil->irq[0]); + return ret; + } + + /* Digital Microphone interface voice activity detector error + * interrupt - IRQ 45 + */ + ret = devm_request_irq(&pdev->dev, micfil->irq[3], + hwvad_err_isr, irqflag, + micfil->name, micfil); + if (ret) { + dev_err(&pdev->dev, "failed to claim hwvad error irq %u\n", + micfil->irq[1]); + return ret; + } + /* Digital Microphone interface interrupt - IRQ 109 */ ret = devm_request_irq(&pdev->dev, micfil->irq[0], micfil_isr, irqflag, @@ -731,10 +2303,16 @@ static int fsl_micfil_probe(struct platform_device *pdev) return ret; } + micfil->slave_mode = false; + micfil->dma_params_rx.chan_name = "rx"; micfil->dma_params_rx.addr = res->start + REG_MICFIL_DATACH0; micfil->dma_params_rx.maxburst = MICFIL_DMA_MAXBURST_RX; + /* set default rate to first value in available vad rates */ + micfil->vad_rate_index = 0; + /* init HWVAD enable/disable spinlock */ + spin_lock_init(&micfil->hwvad_lock); platform_set_drvdata(pdev, micfil); @@ -748,21 +2326,48 @@ static int fsl_micfil_probe(struct platform_device *pdev) return ret; } - ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); - if (ret) + if (micfil->soc->imx) + ret = imx_pcm_platform_register(&pdev->dev); + else + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + + if (ret) { dev_err(&pdev->dev, "failed to pcm register\n"); + return ret; + } - return ret; + /* create sysfs entry used to enable hwvad from userspace */ + micfil->hwvad_kobject = kobject_create_and_add("hwvad", + &pdev->dev.kobj); + if (!micfil->hwvad_kobject) + return -ENOMEM; + + ret = sysfs_create_file(micfil->hwvad_kobject, + &hwvad_en_attr.attr); + if (ret) { + dev_err(&pdev->dev, "failed to create file for hwvad_enable\n"); + kobject_put(micfil->hwvad_kobject); + return -ENOMEM; + } + + return 0; } #ifdef CONFIG_PM static int __maybe_unused fsl_micfil_runtime_suspend(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); + u32 state; + + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) + return 0; regcache_cache_only(micfil->regmap, true); - clk_disable_unprepare(micfil->mclk); + /* Disable the clock only if the hwvad is not enabled */ + if (state == MICFIL_HWVAD_OFF) + clk_disable_unprepare(micfil->mclk); return 0; } @@ -771,6 +2376,17 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) { struct fsl_micfil *micfil = dev_get_drvdata(dev); int ret; + u32 state; + + state = atomic_read(&micfil->hwvad_state); + + /* enable mclk only if the hwvad is not enabled + * When hwvad is enabled, clock won't be disabled + * in suspend since hwvad and recording share the + * same clock + */ + if (state == MICFIL_HWVAD_ON) + return 0; ret = clk_prepare_enable(micfil->mclk); if (ret < 0) @@ -787,6 +2403,19 @@ static int __maybe_unused fsl_micfil_runtime_resume(struct device *dev) #ifdef CONFIG_PM_SLEEP static int __maybe_unused fsl_micfil_suspend(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + + state = atomic_read(&micfil->hwvad_state); + + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Disabling hwvad on suspend"); + ret = disable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to disable hwvad"); + } + pm_runtime_force_suspend(dev); return 0; @@ -794,8 +2423,20 @@ static int __maybe_unused fsl_micfil_suspend(struct device *dev) static int __maybe_unused fsl_micfil_resume(struct device *dev) { + struct fsl_micfil *micfil = dev_get_drvdata(dev); + int ret; + u32 state; + pm_runtime_force_resume(dev); + state = atomic_read(&micfil->hwvad_state); + if (state == MICFIL_HWVAD_ON) { + dev_err(dev, "Enabling hwvad on resume"); + ret = enable_hwvad(dev, false); + if (ret) + dev_warn(dev, "Failed to re-enable hwvad"); + } + return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/sound/soc/fsl/fsl_micfil.h b/sound/soc/fsl/fsl_micfil.h index bac825c3135a..14ad08b6b150 100644 --- a/sound/soc/fsl/fsl_micfil.h +++ b/sound/soc/fsl/fsl_micfil.h @@ -258,6 +258,40 @@ #define MICFIL_VAD0_STAT_IF_MASK BIT(MICFIL_VAD0_STAT_IF_SHIFT) #define MICFIL_VAD0_STAT_IF BIT(MICFIL_VAD0_STAT_IF_SHIFT) +/* HWVAD Constants */ +#define MICFIL_HWVAD_ENVELOPE_MODE 0 +#define MICFIL_HWVAD_ENERGY_MODE 1 +#define MICFIL_HWVAD_INIT_FRAMES 10 +#define MICFIL_HWVAD_INPGAIN 0 +#define MICFIL_HWVAD_SGAIN 6 +#define MICFIL_HWVAD_NGAIN 3 +#define MICFIL_HWVAD_NFILADJ 0 +#define MICFIL_HWVAD_ZCDADJ (1 << (MICFIL_VAD0_ZCD_ZCDADJ_WIDTH - 2)) +#define MICFIL_HWVAD_ZCDTH 10 /* initial threshold value */ +#define MICFIL_HWVAD_ZCDOR 0 +#define MICFIL_HWVAD_ZCDAND 1 +#define MICFIL_HWVAD_ZCD_MANUAL 0 +#define MICFIL_HWVAD_ZCD_AUTO 1 +#define MICFIL_HWVAD_HPF_BYPASS 0 +#define MICFIL_HWVAD_HPF_1750HZ 1 +#define MICFIL_HWVAD_HPF_215HZ 2 +#define MICFIL_HWVAD_HPF_102HZ 3 +#define MICFIL_HWVAD_FRAMET_DEFAULT 10 + +/* MICFIL DC Remover Control Register -- REG_MICFIL_DC_CTRL */ +#define MICFIL_DC_CTRL_SHIFT 0 +#define MICFIL_DC_CTRL_MASK 0xFFFF +#define MICFIL_DC_CTRL_WIDTH 2 +#define MICFIL_DC_CHX_SHIFT(v) (2 * (v)) +#define MICFIL_DC_CHX_MASK(v) ((BIT(MICFIL_DC_CTRL_WIDTH) - 1) \ + << MICFIL_DC_CHX_SHIFT(v)) +#define MICFIL_DC_MODE(v1, v2) (((v1) << MICFIL_DC_CHX_SHIFT(v2)) \ + & MICFIL_DC_CHX_MASK(v2)) +#define MICFIL_DC_CUTOFF_21HZ 0 +#define MICFIL_DC_CUTOFF_83HZ 1 +#define MICFIL_DC_CUTOFF_152Hz 2 +#define MICFIL_DC_BYPASS 3 + /* MICFIL Output Control Register */ #define MICFIL_OUTGAIN_CHX_SHIFT(v) (4 * (v)) @@ -273,11 +307,24 @@ #define FIFO_PTRWID 3 #define FIFO_LEN BIT(FIFO_PTRWID) -#define MICFIL_IRQ_LINES 2 +#define MICFIL_IRQ_LINES 4 #define MICFIL_MAX_RETRY 25 -#define MICFIL_SLEEP_MIN 90000 /* in us */ -#define MICFIL_SLEEP_MAX 100000 /* in us */ +#define MICFIL_SLEEP 100 /* in ms */ #define MICFIL_DMA_MAXBURST_RX 6 #define MICFIL_CTRL2_OSR_DEFAULT (0 << MICFIL_CTRL2_CICOSR_SHIFT) +#define MICFIL_DEFAULT_RATE 48000 +#define MICFIL_CLK_SRC_NUM 3 +#define MICFIL_CLK_AUTO 0 + +/* clock source ids */ +#define MICFIL_AUDIO_PLL1 0 +#define MICFIL_AUDIO_PLL2 1 +#define MICFIL_CLK_EXT3 2 + +/* States of micfil */ +#define MICFIL_HWVAD_OFF 0 +#define MICFIL_HWVAD_ON 1 +#define MICFIL_RECORDING_OFF 0 +#define MICFIL_RECORDING_ON 1 #endif /* _FSL_MICFIL_H */ |