/* Copyright 2018 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* NPCX-specific WOV module for Chrome EC */ #include "apm_chip.h" #include "clock.h" #include "console.h" #include "gpio.h" #include "hooks.h" #include "registers.h" #include "task.h" #include "timer.h" #include "util.h" #include "wov_chip.h" #ifndef NPCX_WOV_SUPPORT #error "Do not enable CONFIG_AUDIO_CODEC_* if npcx ec doesn't support WOV !" #endif /* Console output macros */ #ifndef DEBUG_AUDIO_CODEC #define CPUTS(...) #define CPRINTS(...) #else #define CPUTS(outstr) cputs(CC_AUDIO_CODEC, outstr) #define CPRINTS(format, args...) cprints(CC_AUDIO_CODEC, format, ## args) #endif /* WOV FIFO status. */ #define WOV_STATUS_OFFSET NPCX_WOV_STATUS_CFIFO_OIT #define WOV_IS_CFIFO_INT_THRESHOLD(sts) \ IS_BIT_SET(sts, (NPCX_WOV_STATUS_CFIFO_OIT - WOV_STATUS_OFFSET)) #define WOV_IS_CFIFO_WAKE_THRESHOLD(sts) \ IS_BIT_SET(sts, (NPCX_WOV_STATUS_CFIFO_OWT - WOV_STATUS_OFFSET)) #define WOV_IS_CFIFO_OVERRUN(sts) \ IS_BIT_SET(sts, (NPCX_WOV_STATUS_CFIFO_OVRN - WOV_STATUS_OFFSET)) #define WOV_IS_I2S_FIFO_OVERRUN(sts) \ IS_BIT_SET(sts, (NPCX_WOV_STATUS_I2S_FIFO_OVRN - WOV_STATUS_OFFSET)) #define WOV_IS_I2S_FIFO_UNDERRUN(sts) \ IS_BIT_SET(sts, (NPCX_WOV_STATUS_I2S_FIFO_UNDRN - WOV_STATUS_OFFSET)) /* Core FIFO threshold. */ #define WOV_SET_FIFO_WAKE_THRESHOLD(n) \ SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_FIFO_WTHRSH, n) #define WOV_SET_FIFO_INT_THRESHOLD(n) \ SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_FIFO_ITHRSH, n) #define WOV_GET_FIFO_INT_THRESHOLD \ GET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_FIFO_ITHRSH) #define WOV_PLL_IS_NOT_LOCK \ (!IS_BIT_SET(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_LOCKI)) /* mask definitions that clear reserved fields for WOV registers.*/ #define WOV_CLK_CTRL_REG_RESERVED_MASK 0x037F7FFF #define WOV_GET_FIFO_WAKE_THRESHOLD \ GET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_FIFO_WTHRSH) /* Wait time 4ms for FMUL2 enabled and for configuration tuning sequence. */ #define WOV_FMUL2_CLK_TUNING_DELAY_TIME (4 * 1000) /* The size of RAM buffer to store the voice data */ #define VOICE_BUF_SIZE 16000 /* PLL setting options. */ struct wov_pll_set_options_val { uint8_t pll_indv; /* Input Divider */ uint16_t pll_fbdv; /* Feedback Divider */ uint8_t pll_otdv1; /* Output devide 1. */ uint8_t pll_otdv2; /* Output devide 2. */ uint32_t pll_ext_div; /* Index for the table pll_ext_div */ }; /* PLL External Divider Load Values. */ struct wov_pll_ext_div_val { uint8_t pll_ediv; /* Required PLL external divider */ uint8_t pll_ediv_dc; /* Required PLL external divider DC */ }; static const struct wov_pll_ext_div_val pll_ext_div[] = { {0x2F, 0x78}, /* 12 */ {0x57, 0x7C}, /* 13 */ {0x2B, 0x7C}, /* 14 */ {0x55, 0x7E}, /* 15 */ {0x2A, 0x7E}, /* 16 */ {0x15, 0x7F}, /* 17 */ {0x4A, 0x7F}, /* 18 */ {0x65, 0x3F}, /* 19 */ {0x32, 0x3F}, /* 20 */ {0x19, 0x5F}, /* 21 */ {0x4C, 0x5F}, /* 22 */ {0x66, 0x2F}, /* 23 */ {0x73, 0x2F}, /* 24 */ {0x39, 0x57}, /* 25 */ {0x5C, 0x57}, /* 26 */ {0x6E, 0x2B}, /* 27 */ {0x77, 0x2B}, /* 28 */ {0x3B, 0x55}, /* 29 */ {0x5D, 0x55}, /* 30 */ {0x2E, 0x2A}, /* 31 */ {0x17, 0x2A}, /* 32 */ {0x4B, 0x15}, /* 33 */ {0x25, 0x15}, /* 34 */ {0x52, 0x4A}, /* 35 */ {0x69, 0x4A}, /* 36 */ {0x34, 0x65}, /* 37 */ {0x1A, 0x65}, /* 38 */ {0x0D, 0x32}, /* 39 */ {0x46, 0x32}, /* 40 */ {0x63, 0x19}, /* 41 */ {0x31, 0x19}, /* 42 */ {0x58, 0x4C}, /* 43 */ {0x6C, 0x4C}, /* 44 */ {0x76, 0x66}, /* 45 */ {0x7B, 0x66}, /* 46 */ {0x3D, 0x73}, /* 47 */ {0x5E, 0x73}, /* 48 */ {0x6F, 0x39}, /* 49 */ {0x37, 0x39}, /* 50 */ {0x5B, 0x5C}, /* 51 */ {0x2D, 0x5C}, /* 52 */ {0x56, 0x6E}, /* 53 */ {0x6B, 0x6E}, /* 54 */ {0x35, 0x77}, /* 55 */ {0x5A, 0x77}, /* 56 */ {0x6D, 0x3B}, /* 57 */ {0x36, 0x3B}, /* 58 */ {0x1B, 0x5D}, /* 59 */ {0x4D, 0x5D}, /* 60 */ {0x26, 0x2E}, /* 61 */ {0x13, 0x2E}, /* 62 */ {0x49, 0x17}, /* 63 */ {0x24, 0x17}, /* 64 */ {0x12, 0x4B}, /* 65 */ {0x09, 0x4B}, /* 66 */ {0x44, 0x25} /* 67 */ }; /* WOV interrupts */ static const uint8_t wov_interupts[] = { 0, /* VAD_INTEN */ 1, /* VAD_WKEN */ 8, /* CFIFO_NE_IE */ 9, /* CFIFO_OIT_IE */ 10, /* CFIFO_OWT_WE */ 11, /* CFIFO_OVRN_IE */ 12, /* I2S_FIFO_OVRN_IE */ 13 /* I2S_FIFO_UNDRN_IE */ }; struct wov_ppl_divider { uint16_t pll_frame_len; /* PLL frame length. */ uint16_t pll_fbdv; /* PLL feedback divider. */ uint8_t pll_indv; /* PLL Input Divider. */ uint8_t pll_otdv1; /* PLL Output Divider 1. */ uint8_t pll_otdv2; /* PLL Output Divider 2. */ uint8_t pll_ediv; /* PLL External Divide Factor. */ }; struct wov_cfifo_buf { uint32_t *buf; /* Pointer to a buffer. */ int size; /* Buffer size in words. */ }; struct wov_config wov_conf; static struct wov_cfifo_buf cfifo_buf; static wov_call_back_t callback_fun; #define WOV_RATE_ERROR_THRESH_MSEC 10 #define WOV_RATE_ERROR_THRESH 5 static int irq_underrun_count; static int irq_overrun_count; static uint32_t wov_i2s_underrun_tstamp; static uint32_t wov_i2s_overrun_tstamp; #define WOV_CALLBACK(event) \ { \ if (callback_fun != NULL) \ callback_fun(event); \ } #define CONFIG_WOV_FIFO_THRESH_WORDS WOV_FIFO_THRESHOLD_80_DATA_WORDS /** * Reads data from the core fifo. * * @param num_elements - Number of elements (Dword) to read. * * @return None */ void wov_cfifo_read_handler_l(uint32_t num_elements) { uint32_t index; for (index = 0; index < num_elements; index++) cfifo_buf.buf[index] = NPCX_WOV_FIFO_OUT; cfifo_buf.buf = &cfifo_buf.buf[index]; cfifo_buf.size -= num_elements; } static enum ec_error_list wov_calc_pll_div_s(int32_t d_in, int32_t total_div, int32_t vco_freq, struct wov_ppl_divider *pll_div) { int32_t d_1, d_2, d_e; /* * Please see comments in wov_calc_pll_div_l function below. */ for (d_e = 4; d_e < 75; d_e++) { for (d_2 = 1; d_2 < 7; d_2++) { for (d_1 = 1; d_1 < 7; d_1++) { if ((vco_freq / (d_1 * d_2)) > 900) continue; if (total_div == (d_in * d_e * d_1 * d_2)) { pll_div->pll_indv = d_in; pll_div->pll_otdv1 = d_1; pll_div->pll_otdv2 = d_2; pll_div->pll_ediv = d_e; return EC_SUCCESS; } } } } return EC_ERROR_INVAL; } /** * Gets the PLL divider value accordingly to the i2S clock frequency. * * @param i2s_clk_freq - i2S clock frequency * @param sample_rate - Sample rate in KHz (16KHz or 48KHz) * @param pll_div - PLL dividers. * * @return None */ static enum ec_error_list wov_calc_pll_div_l(uint32_t i2s_clk_freq, uint32_t sample_rate, struct wov_ppl_divider *pll_div) { int32_t d_f; int32_t total_div; int32_t d_in; int32_t n; int32_t vco_freq; int32_t i2s_clk_freq_khz; n = i2s_clk_freq / sample_rate; if (i2s_clk_freq != (sample_rate * n)) return EC_ERROR_INVAL; if ((n < 32) || (n >= 257)) return EC_ERROR_INVAL; pll_div->pll_frame_len = n; i2s_clk_freq_khz = i2s_clk_freq / 1000; /* * The code below implemented the “PLL setting option” table as * describe in the NPCX7m7wb specification document. * - Total_div is VCO frequency in MHz / 12 MHz * - d_f is the Feedback Divider * - d_in is the Input Divider (PLL_INDV) * - d_e is the PLL Ext Divider * - d_2 is the Output Divide 2 (PLL_OTDV2) * - d_1 is the Output Divide 1 (PLL_OTDV1) * It is preferred that d_f will be as smaller as possible, after that * the d_in will be as smaller as possible and so on, this is the * reason that d_f (calculated from total_div) is in the external loop * and d-1 is in the internal loop (as it may contain the bigger value). * The “PLL setting option” code divided to 2 function in order to * fulfil the coding style indentation rule. */ /* total div is min_vco/12 400/12=33. */ for (total_div = 33; total_div < 1500; total_div++) { d_f = (total_div * 12000) / i2s_clk_freq_khz; if ((total_div * 12000) == (d_f * i2s_clk_freq_khz)) { for (d_in = 1; d_in < 10; d_in++) { if (((i2s_clk_freq / 1000) / d_in) <= 500) continue; vco_freq = total_div * 12 / d_in; if ((vco_freq < 500) || (vco_freq > 1600)) continue; if (wov_calc_pll_div_s(d_in, total_div, vco_freq, pll_div) == EC_SUCCESS) { pll_div->pll_fbdv = d_f; return EC_SUCCESS; } } } } return EC_ERROR_INVAL; } /** * Check if PLL is locked. * * @param None * * @return EC_SUCCESS if PLL is locked, EC_ERROR_UNKNOWN otherwise . */ enum ec_error_list wov_wait_for_pll_lock_l(void) { volatile uint32_t index; for (index = 0; WOV_PLL_IS_NOT_LOCK; index++) { /* PLL doesn't reach to lock state. */ if (index > 0xFFFF) return EC_ERROR_UNKNOWN; } return EC_SUCCESS; } /** * Configure I2S bus. (Parameters determined via common config functions.) * * @param None. * * @return none. */ static enum ec_error_list wov_set_i2s_config_l(void) { struct wov_ppl_divider pll_div; enum ec_error_list ret_code; enum wov_i2s_chan_trigger trigger_0, trigger_1; int32_t start_delay_0, start_delay_1; ret_code = wov_calc_pll_div_l(wov_conf.i2s_clock, wov_conf.sample_per_sec, &pll_div); if (ret_code == EC_SUCCESS) { /* Configure the PLL. */ ret_code = wov_pll_clk_div_config( pll_div.pll_otdv1, pll_div.pll_otdv2, pll_div.pll_fbdv, pll_div.pll_indv); if (ret_code != EC_SUCCESS) return ret_code; ret_code = wov_pll_clk_ext_div_config( (enum wov_pll_ext_div_sel)(pll_div.pll_ediv > 15), pll_div.pll_ediv); if (ret_code != EC_SUCCESS) return ret_code; wov_i2s_global_config( (enum wov_floating_mode)(wov_conf.dai_format == WOV_DAI_FMT_PCM_TDM), WOV_FLOATING_DRIVEN, WOV_CLK_NORMAL, 0, WOV_PULL_DOWN, 0, WOV_PULL_DOWN, WOV_NORMAL_MODE); /* Configure DAI format. */ switch (wov_conf.dai_format) { case WOV_DAI_FMT_I2S: trigger_0 = WOV_I2S_SAMPLED_0_AFTER_1; trigger_1 = WOV_I2S_SAMPLED_1_AFTER_0; start_delay_0 = 1; start_delay_1 = 1; break; case WOV_DAI_FMT_RIGHT_J: trigger_0 = WOV_I2S_SAMPLED_1_AFTER_0; trigger_1 = WOV_I2S_SAMPLED_0_AFTER_1; start_delay_0 = (pll_div.pll_frame_len / 2) - wov_conf.bit_depth; start_delay_1 = (pll_div.pll_frame_len / 2) - wov_conf.bit_depth; break; case WOV_DAI_FMT_LEFT_J: trigger_0 = WOV_I2S_SAMPLED_1_AFTER_0; trigger_1 = WOV_I2S_SAMPLED_0_AFTER_1; start_delay_0 = 0; start_delay_1 = 0; break; case WOV_DAI_FMT_PCM_A: trigger_0 = WOV_I2S_SAMPLED_1_AFTER_0; trigger_1 = WOV_I2S_SAMPLED_1_AFTER_0; start_delay_0 = 1; start_delay_1 = wov_conf.bit_depth + 1; break; case WOV_DAI_FMT_PCM_B: trigger_0 = WOV_I2S_SAMPLED_1_AFTER_0; trigger_1 = WOV_I2S_SAMPLED_1_AFTER_0; start_delay_0 = 0; start_delay_1 = wov_conf.bit_depth; break; case WOV_DAI_FMT_PCM_TDM: trigger_0 = WOV_I2S_SAMPLED_1_AFTER_0; trigger_1 = WOV_I2S_SAMPLED_1_AFTER_0; start_delay_0 = wov_conf.i2s_start_delay_0; start_delay_1 = wov_conf.i2s_start_delay_1; break; default: return EC_ERROR_INVALID_CONFIG; } udelay(100); ret_code = wov_i2s_channel_config(0, wov_conf.bit_depth, trigger_0, start_delay_0); ret_code = wov_i2s_channel_config(1, wov_conf.bit_depth, trigger_1, start_delay_1); } return EC_SUCCESS; } /** * wov_i2s_channel1_disable * * @param disable - disabled flag, 1 means disable * * @return None */ static void wov_i2s_channel1_disable(int disable) { if (disable) SET_BIT(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL1_I2S_CHN1_DIS); else CLEAR_BIT(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL1_I2S_CHN1_DIS); } /** * Sets microphone source. * * | Left | Right | Mono | Stereo *------------------|-----------|---------------|---------------|-------------- *FIFO_CNT. |0x0 or 0x2 | 0x0 or 0x2 | 0x1 or 0x3 |0x1 or 0x3 *CFIFI_ISEL | (left) |(left) |(left & Right) |(left & right) *------------------|-----------|---------------|---------------|-------------- *CR_DMIC. | 0x1 | 0x1 | 0x2 | 0x1 *ADC_DMIC_SEL_LEFT | (left) | (left) | (average) | (left) *------------------|-----------|---------------|---------------|-------------- *CR_DMIC. | 0x1 | 0x1 | 0x2 | 0x1 *ADC_DMIC_SEL_RIGHT| (right) | (right) | (average) | (right) *------------------|-----------|---------------|---------------|-------------- *MIX_2. | 0x0 | 0x1 | 0x0 | 0x0 *AIADCL_SEL | (normal) |(cross inputs) | (normal) | (normal) *------------------|-----------|---------------|---------------|-------------- *MIX_2. | 0x3 | 0x3 | 0x0 | 0x0 *AIADCR_SEL |(no input) | (no input) | (normal) | (normal) *------------------|-----------|---------------|---------------|-------------- *VAD_0. | 0x0 | 0x1 | 0x2 | Not *VAD_INSEL | (left) | (right) | (average) | applicable * * @param None. * @return return EC_SUCCESS if mic source valid othewise return error code. */ static enum ec_error_list wov_set_mic_source_l(void) { switch (wov_conf.mic_src) { case WOV_SRC_LEFT: if (wov_conf.bit_depth == 16) SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x00); else SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x02); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_LEFT, 0x01); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_RIGHT, 0x01); apm_digital_mixer_config(APM_OUT_MIX_NORMAL_INPUT, APM_OUT_MIX_NO_INPUT); apm_set_vad_input_channel(APM_IN_LEFT); wov_i2s_channel1_disable(1); break; case WOV_SRC_RIGHT: if (wov_conf.bit_depth == 16) SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x00); else SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x02); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_LEFT, 0x01); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_RIGHT, 0x01); apm_digital_mixer_config(APM_OUT_MIX_CROSS_INPUT, APM_OUT_MIX_NO_INPUT); apm_set_vad_input_channel(APM_IN_RIGHT); wov_i2s_channel1_disable(1); break; case WOV_SRC_MONO: if (wov_conf.bit_depth == 16) SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x01); else SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x03); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_LEFT, 0x02); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_RIGHT, 0x02); apm_digital_mixer_config(APM_OUT_MIX_NORMAL_INPUT, APM_OUT_MIX_NORMAL_INPUT); apm_set_vad_input_channel(APM_IN_AVERAGE_LEFT_RIGHT); wov_i2s_channel1_disable(0); break; case WOV_SRC_STEREO: if (wov_conf.bit_depth == 16) SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x01); else SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, 0x03); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_LEFT, 0x01); SET_FIELD(NPCX_APM_CR_DMIC, NPCX_APM_CR_DMIC_ADC_DMIC_SEL_RIGHT, 0x01); apm_digital_mixer_config(APM_OUT_MIX_NORMAL_INPUT, APM_OUT_MIX_NORMAL_INPUT); wov_i2s_channel1_disable(0); break; default: return EC_ERROR_INVAL; } return EC_SUCCESS; } static void wov_over_under_deferred(void) { CPRINTS("wov: Under/Over run error: under = %d, over = %d", irq_underrun_count, irq_overrun_count); } DECLARE_DEFERRED(wov_over_under_deferred); static void wov_under_over_error_handler(int *count, uint32_t *last_time) { uint32_t time_delta_msec; uint32_t current_time = get_time().le.lo; if (!(*count)) { *last_time = current_time; (*count)++; } else { time_delta_msec = (current_time - *last_time) / MSEC; *last_time = current_time; if (time_delta_msec < WOV_RATE_ERROR_THRESH_MSEC) (*count)++; else *count = 0; if (*count >= WOV_RATE_ERROR_THRESH) { wov_stop_i2s_capture(); hook_call_deferred(&wov_over_under_deferred_data, 0); } } } /** * WoV interrupt handler. * * @param None * * @return None */ void wov_interrupt_handler(void) { uint32_t wov_status; uint32_t wov_inten; wov_inten = GET_FIELD(NPCX_WOV_WOV_INTEN, NPCX_WOV_STATUS_BITS); wov_status = wov_inten & GET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS); /* * Voice activity detected. */ if (APM_IS_VOICE_ACTIVITY_DETECTED) { apm_enable_vad_interrupt(0); APM_CLEAR_VAD_INTERRUPT; WOV_CALLBACK(WOV_EVENT_VAD); } /* Core FIFO is overrun. Reset the Core FIFO and inform the FW */ if (WOV_IS_CFIFO_OVERRUN(wov_status)) { WOV_CALLBACK(WOV_EVENT_ERROR_CORE_FIFO_OVERRUN); wov_core_fifo_reset(); } else if (WOV_IS_CFIFO_INT_THRESHOLD(wov_status) && (cfifo_buf.buf != NULL)) { /* * Core FIFO threshold or FIFO not empty event occurred. * - Read data from core FIFO to the buffer. * - In case data ready or no space for data, inform the FW. */ /* Copy data from CFIFO to RAM. */ wov_cfifo_read_handler_l((WOV_GET_CORE_FIFO_THRESHOLD * 2)); if (cfifo_buf.size < (WOV_GET_CORE_FIFO_THRESHOLD * 2)) { cfifo_buf.buf = NULL; cfifo_buf.size = 0; WOV_CALLBACK(WOV_EVENT_DATA_READY); } } /* I2S FIFO is overrun. Reset the I2S FIFO and inform the FW. */ if (WOV_IS_I2S_FIFO_OVERRUN(wov_status)) { WOV_CALLBACK(WOV_EVENT_ERROR_I2S_FIFO_OVERRUN); wov_under_over_error_handler(&irq_overrun_count, &wov_i2s_overrun_tstamp); wov_i2s_fifo_reset(); } /* I2S FIFO is underrun. Reset the I2S FIFO and inform the FW. */ if (WOV_IS_I2S_FIFO_UNDERRUN(wov_status)) { WOV_CALLBACK(WOV_EVENT_ERROR_I2S_FIFO_UNDERRUN); wov_under_over_error_handler(&irq_underrun_count, &wov_i2s_underrun_tstamp); wov_i2s_fifo_reset(); } /* Clear the WoV status register. */ SET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS, wov_status); } DECLARE_IRQ(NPCX_IRQ_WOV, wov_interrupt_handler, 4); /** * Enable FMUL2. * * @param enable - enabled flag, true for enable * @return None */ static void wov_fmul2_enable(int enable) { if (enable) { /* If clock disabled, then enable it. */ if (IS_BIT_SET(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS)) { /* Enable clock tuning. */ CLEAR_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_DIS); /* Enable clock. */ CLEAR_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS); udelay(WOV_FMUL2_CLK_TUNING_DELAY_TIME); } } else SET_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS); } #define WOV_FMUL2_MAX_RETRIES 0x000FFFFF /* FMUL2 clock multipliers values. */ struct wov_fmul2_multiplier_setting_val { uint8_t fm2mh; uint8_t fm2ml; uint8_t fm2n; }; /** * Configure FMUL2 clock tunning. * * @param None * @return None */ void wov_fmul2_conf_tuning(void) { /* Check if FMUL2 is enabled, then do nothing. */ if (IS_BIT_SET(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS) == 0x00) return; /* Enable clock tuning. */ CLEAR_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_DIS); udelay(WOV_FMUL2_CLK_TUNING_DELAY_TIME); /* Disable clock tuning. */ SET_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_DIS); } static int wov_get_cfifo_threshold_l(void) { int fifo_threshold; fifo_threshold = WOV_GET_FIFO_INT_THRESHOLD; if (fifo_threshold == 0) return 1; else return (fifo_threshold * 2); } /** * Gets clock source FMUL2 or PLL. * * @param None. * * NOTE: * * @return The clock source FMUL2 (WOV_FMUL2_CLK_SRC) and * PLL (WOV_PLL_CLK_SRC) */ static enum wov_clk_src_sel wov_get_clk_selection(void) { if (IS_BIT_SET(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_CLK_SEL)) return WOV_PLL_CLK_SRC; else return WOV_FMUL2_CLK_SRC; } /*************************************************************************** * * Exported function. * **************************************************************************/ /** * Set FMUL2 clock divider. * * @param None * @return None */ void wov_fmul2_set_clk_divider(enum fmul2_clk_divider clk_div) { SET_FIELD(NPCX_FMUL2_FM2P, NPCX_FMUL2_FM2P_WFPRED, clk_div); } /** * Configure DMIC clock. * * @param enable - DMIC enabled , 1 means enable * @param clk_div - DMIC clock division factor (disable, divide by 2 * divide by 4) * @return None */ void wov_dmic_clk_config(int enable, enum wov_dmic_clk_div_sel clk_div) { /* If DMIC enabled then configured its clock.*/ if (enable) { if (clk_div != WOV_DMIC_DIV_DISABLE) { SET_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_CKDIV_EN); if (clk_div == WOV_DMIC_DIV_BY_2) CLEAR_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_CKDIV_SEL); else SET_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_CKDIV_SEL); } else CLEAR_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_CKDIV_EN); SET_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_EN); } else CLEAR_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_DMIC_EN); } /** * Sets WoV mode * * @param wov_mode - WoV mode * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_set_mode(enum wov_modes wov_mode) { enum ec_error_list ret_code; enum wov_clk_src_sel prev_clock; /* If mode is OFF, then power down and exit. */ if (wov_mode == WOV_MODE_OFF) { wov_stop_i2s_capture(); wov_stop_ram_capture(); wov_set_clk_selection(WOV_FMUL2_CLK_SRC); wov_dmic_clk_config(0, WOV_DMIC_DIV_DISABLE); wov_mute(1); apm_set_mode(WOV_MODE_OFF); wov_fmul2_enable(0); wov_conf.mode = WOV_MODE_OFF; return EC_SUCCESS; } switch (wov_mode) { case WOV_MODE_VAD: if (apm_get_vad_dmic_rate() == APM_DMIC_RATE_0_75) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_4); else if (apm_get_vad_dmic_rate() == APM_DMIC_RATE_1_2) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_2); else wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); wov_stop_i2s_capture(); wov_stop_ram_capture(); wov_set_clk_selection(WOV_FMUL2_CLK_SRC); apm_set_mode(wov_mode); ret_code = wov_set_mic_source_l(); if (ret_code != EC_SUCCESS) return ret_code; break; case WOV_MODE_RAM: if ((wov_conf.bit_depth != 16) && (wov_conf.bit_depth != 24)) return EC_ERROR_INVAL; if (apm_get_adc_ram_dmic_rate() == APM_DMIC_RATE_0_75) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_4); else if (apm_get_adc_ram_dmic_rate() == APM_DMIC_RATE_1_2) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_2); else wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); wov_stop_i2s_capture(); wov_set_clk_selection(WOV_FMUL2_CLK_SRC); apm_set_mode(wov_mode); ret_code = wov_set_mic_source_l(); if (ret_code != EC_SUCCESS) return ret_code; wov_start_ram_capture(); break; case WOV_MODE_RAM_AND_I2S: if ((wov_conf.bit_depth != 16) && (wov_conf.bit_depth != 24)) return EC_ERROR_INVAL; case WOV_MODE_I2S: if (apm_get_adc_i2s_dmic_rate() == APM_DMIC_RATE_0_75) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_4); else if (apm_get_adc_i2s_dmic_rate() == APM_DMIC_RATE_1_2) wov_dmic_clk_config(1, WOV_DMIC_DIV_BY_2); else wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); prev_clock = wov_get_clk_selection(); if (prev_clock != WOV_PLL_CLK_SRC) { wov_set_i2s_config_l(); wov_set_clk_selection(WOV_PLL_CLK_SRC); } apm_set_mode(wov_mode); ret_code = wov_set_mic_source_l(); if (ret_code != EC_SUCCESS) return ret_code; wov_start_i2s_capture(); if (wov_mode == WOV_MODE_RAM_AND_I2S) wov_start_ram_capture(); else wov_stop_ram_capture(); break; default: wov_dmic_clk_config(0, WOV_DMIC_DIV_DISABLE); wov_fmul2_enable(0); wov_mute(1); return EC_ERROR_INVAL; } wov_mute(0); wov_conf.mode = wov_mode; return EC_SUCCESS; } /** * Gets WoV mode * * @param None * @return WoV mode */ enum wov_modes wov_get_mode(void) { return wov_conf.mode; } /** * Initiates WoV. * * @param callback - Pointer to callback function. * * @return None */ void wov_init(void) { apm_init(); wov_apm_active(1); wov_mute(1); wov_conf.mode = WOV_MODE_OFF; wov_conf.sample_per_sec = 16000; wov_conf.bit_depth = 16; wov_conf.mic_src = WOV_SRC_LEFT; wov_conf.left_chan_gain = 0; wov_conf.right_chan_gain = 0; wov_conf.i2s_start_delay_0 = 0; wov_conf.i2s_start_delay_1 = 0; wov_conf.i2s_clock = 0; wov_conf.dai_format = WOV_DAI_FMT_I2S; wov_conf.sensitivity_db = 5; /* Set DMIC clock signal output to use fast transitions. */ SET_BIT(NPCX_DEVALT(0xE), NPCX_DEVALTE_DMCLK_FAST); callback_fun = wov_handle_event; wov_cfifo_config(WOV_CFIFO_IN_LEFT_CHAN_2_CONS_16_BITS, WOV_FIFO_THRESHOLD_80_DATA_WORDS); apm_set_vad_dmic_rate(APM_DMIC_RATE_0_75); apm_set_adc_ram_dmic_config(APM_DMIC_RATE_0_75); apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_3_0); } /** * Select clock source FMUL2 or PLL. * * @param clk_src - select between FMUL2 (WOV_FMUL2_CLK_SRC) and * PLL (WOV_PLL_CLK_SRC) * * NOTE: THIS FUNCTION RESETS THE APM and RETURN ITS REGISSTERS TO THEIR * DEFAULT VALUES !!!!!!! * * @return None */ void wov_set_clk_selection(enum wov_clk_src_sel clk_src) { int is_apm_disable; /* * Be sure that both clocks are active, as both of them need to * be active when modify the CLK_SEL bit. */ if (IS_BIT_SET(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN)) wov_pll_enable(1); if (IS_BIT_SET(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS)) wov_fmul2_enable(1); is_apm_disable = IS_BIT_SET(NPCX_APM_CR_APM, NPCX_APM_CR_APM_PD); apm_enable(0); if (clk_src == WOV_FMUL2_CLK_SRC) CLEAR_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_CLK_SEL); else if (wov_wait_for_pll_lock_l() == EC_SUCCESS) SET_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_CLK_SEL); udelay(100); if (!is_apm_disable) apm_enable(1); /* Disable the unneeded clock. */ if (clk_src == WOV_PLL_CLK_SRC) wov_fmul2_enable(0); else wov_pll_enable(0); } /** * Configure PLL clock. * * @param ext_div_sel - PLL external divider selector. * @param div_factor - When ext_div_sel is WOV_PLL_EXT_DIV_BIN_CNT * then it is the 4 LSBits of this field, * otherwise this field is an index to * PLL External Divider Load Values table. * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_pll_clk_ext_div_config( enum wov_pll_ext_div_sel ext_div_sel, uint32_t div_factor) { /* Sets the clock division factor for the PLL external divider. * The divide factor should be in the range of 2 to 67. * When ext_div_sel is WOV_PLL_EXT_DIV_BIN_CNT, then the 4 least * significant bits of div_factor are used to set the divide * ratio. * In this case the divide ration legal values are from 2 to 15 * For WOV_PLL_EXT_DIV_LFSR, this parameter is used as index for * pll_ext_div table. */ if (ext_div_sel == WOV_PLL_EXT_DIV_BIN_CNT) CLEAR_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_PLL_EDIV_SEL); else SET_BIT(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_PLL_EDIV_SEL); if (ext_div_sel == WOV_PLL_EXT_DIV_BIN_CNT) { if ((div_factor > 15) || (div_factor < 2)) return EC_ERROR_INVAL; SET_FIELD(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_PLL_EDIV, (div_factor)); } else { if ((div_factor > 67) || (div_factor < 12)) return EC_ERROR_INVAL; SET_FIELD(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_PLL_EDIV, pll_ext_div[div_factor - 12].pll_ediv); SET_FIELD(NPCX_WOV_CLOCK_CNTL, NPCX_WOV_CLOCK_CNT_PLL_EDIV_DC, pll_ext_div[div_factor - 12].pll_ediv_dc); } return EC_SUCCESS; } /** * PLL power down. * * @param enable - 1 enable the PLL or 0 PLL disable * @return None */ void wov_pll_enable(int enable) { if (enable) CLEAR_BIT(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN); else SET_BIT(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN); udelay(100); } /** * Configures PLL clock dividers.. * * @param out_div_1 - PLL output divider #1, valid values 1-7 * @param out_div_2 - PLL output divider #2, valid values 1-7 * @param feedback_div - PLL feadback divider (Default is 375 decimal) * @param in_div - PLL input divider * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_pll_clk_div_config(uint32_t out_div_1, uint32_t out_div_2, uint32_t feedback_div, uint32_t in_div) { /* Parameter check. */ if ((out_div_1 < 1) || (out_div_1 > 7) || (out_div_2 < 1) || (out_div_2 > 7)) return EC_ERROR_INVAL; /* * PLL configuration sequence: * 1. Set PLL_PWDEN bit to 1. * 2. Set PLL divider values. * 3. Wait 1usec. * 4. Clear PLL_PWDEN bit to 0 while not changing other PLL parameters. */ SET_BIT(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN); SET_FIELD(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_OTDV1, out_div_1); SET_FIELD(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_OTDV2, out_div_2); SET_FIELD(NPCX_WOV_PLL_CNTL2, NPCX_WOV_PLL_CNTL2_PLL_FBDV, feedback_div); SET_FIELD(NPCX_WOV_PLL_CNTL2, NPCX_WOV_PLL_CNTL2_PLL_INDV, in_div); udelay(100); CLEAR_BIT(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN); udelay(100); return EC_SUCCESS; } /** * Enables/Disables WoV interrupt. * * @param int_index - Interrupt ID. * @param enable - enabled flag, 1 means enable * * @return None. */ void wov_interrupt_enable(enum wov_interrupt_index int_index, int enable) { if (enable) SET_BIT(NPCX_WOV_WOV_INTEN, wov_interupts[int_index]); else CLEAR_BIT(NPCX_WOV_WOV_INTEN, wov_interupts[int_index]); } /** * Sets core FIFO threshold. * * @param in_sel - Core FIFO input select * @param threshold - Core FIFO threshold * * @return None */ void wov_cfifo_config(enum wov_core_fifo_in_sel in_sel, enum wov_fifo_threshold threshold) { /* Set core FIFO input selection. */ SET_FIELD(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CFIFO_ISEL, in_sel); /* Set wake & interrupt core FIFO threshold. */ WOV_SET_FIFO_WAKE_THRESHOLD(threshold); WOV_SET_FIFO_INT_THRESHOLD(threshold); } /** * Start the actual capturing of the Voice data to the RAM. * Note that the pointer to the RAM buffer must be precisely * set by calling wov_set_buffer(); * * @param None * * @return None */ void wov_start_ram_capture(void) { /* Clear the CFIFO status bits in WoV status register. */ SET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS, 0x27); CLEAR_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CORE_FFRST); wov_interrupt_enable(WOV_CFIFO_OVERRUN_INT_INDX, 1); wov_interrupt_enable(WOV_CFIFO_THRESHOLD_INT_INDX, 1); wov_interrupt_enable(WOV_CFIFO_THRESHOLD_WAKE_INDX, 1); } /** * Stop the capturing of the Voice data to the RAM. * * @param none * * @return None */ void wov_stop_ram_capture(void) { SET_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CORE_FFRST); wov_interrupt_enable(WOV_CFIFO_OVERRUN_INT_INDX, 0); wov_interrupt_enable(WOV_CFIFO_THRESHOLD_INT_INDX, 0); wov_interrupt_enable(WOV_CFIFO_THRESHOLD_WAKE_INDX, 0); udelay(100); } /** * Rests the Core FIFO. * * @param None * * @return None */ void wov_core_fifo_reset(void) { SET_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CORE_FFRST); udelay(1000); /* Clear the CFIFO status bits in WoV status register. */ SET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS, 0x27); CLEAR_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_CORE_FFRST); } /** * Rests the I2S FIFO. * * @param None * * @return None */ void wov_i2s_fifo_reset(void) { int disable; disable = IS_BIT_SET(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); SET_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); udelay(1000); /* Clear the I2S status bits in WoV status register. */ SET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS, 0x18); if (!disable) CLEAR_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); } /** * Start the capturing of the Voice data via I2S. * * @param None * * @return None */ void wov_start_i2s_capture(void) { /* Clear counters used to track for underrun/overrun errors */ irq_underrun_count = 0; irq_overrun_count = 0; /* Clear the I2S status bits in WoV status register. */ SET_FIELD(NPCX_WOV_STATUS, NPCX_WOV_STATUS_BITS, 0x18); CLEAR_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); wov_interrupt_enable(WOV_I2SFIFO_OVERRUN_INT_INDX, 1); wov_interrupt_enable(WOV_I2SFIFO_UNDERRUN_INT_INDX, 1); } /** * Stop the capturing of the Voice data via I2S. * * @param none * * @return None */ void wov_stop_i2s_capture(void) { SET_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); wov_interrupt_enable(WOV_I2SFIFO_OVERRUN_INT_INDX, 0); wov_interrupt_enable(WOV_I2SFIFO_UNDERRUN_INT_INDX, 0); udelay(100); } /** * Sets data buffer for reading from core FIFO * * @param buff - Pointer to the read buffer, buffer must be 32 bits * aligned. * @param size_in_words - Size must be a multiple of CONFIG_WOV_THRESHOLD_WORDS * (defaulte = 80 words) * * @return None * * Note - When the data buffer will be full the FW will be notifyed * about it, and the FW will need to recall to this function. */ int wov_set_buffer(uint32_t *buf, int size_in_words) { int cfifo_threshold; cfifo_threshold = wov_get_cfifo_threshold_l(); if (size_in_words != ((size_in_words / cfifo_threshold) * cfifo_threshold)) return EC_ERROR_INVAL; cfifo_buf.buf = buf; cfifo_buf.size = size_in_words; return EC_SUCCESS; } /** * Resets the APM. * * @param enable - enabled flag, 1 means enable * @return None */ void wov_apm_active(int enable) { /* For APM it is negativ logic. */ if (enable) CLEAR_BIT(NPCX_WOV_APM_CTRL, NPCX_WOV_APM_CTRL_APM_RST); else SET_BIT(NPCX_WOV_APM_CTRL, NPCX_WOV_APM_CTRL_APM_RST); } /** * I2S golobal configuration * * @param i2s_hiz_data - Defines when the I2S data output is floating. * @param i2s_hiz - Defines if the I2S data output is always floating. * @param clk_invert - Defines the I2S bit clock edge sensitivity * @param out_pull_en - Enable a pull-up or a pull-down resistor on * I2S output * @param out_pull_mode - Select a pull-up or a pull-down resistor on * I2S output * @param in_pull_en - Enable a pull-up or a pull-down resistor on * I2S input * @param in_pull_mode - Select a pull-up or a pull-down resistor on * I2S intput * @param test_mode - Selects I2S test mode * * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_i2s_global_config( enum wov_floating_mode i2s_hiz_data, enum wov_floating_mode i2s_hiz, enum wov_clk_inverted_mode clk_invert, int out_pull_en, enum wov_pull_upd_down_sel out_pull_mode, int in_pull_en, enum wov_pull_upd_down_sel in_pull_mode, enum wov_test_mode test_mode) { /* Check the parameters correctness. */ if ((i2s_hiz_data == WOV_FLOATING) && ((GET_FIELD(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL_I2S_ST_DEL) == 0) || (GET_FIELD(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL_I2S_ST_DEL) == 0))) return EC_ERROR_INVAL; /* Set the parameters. */ if (i2s_hiz_data == WOV_FLOATING_DRIVEN) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_HIZD); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_HIZD); if (i2s_hiz == WOV_FLOATING_DRIVEN) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_HIZ); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_HIZ); if (clk_invert == WOV_CLK_NORMAL) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_SCLK_INV); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_SCLK_INV); if (out_pull_en) SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_OPE); else CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_OPE); if (out_pull_mode == WOV_PULL_DOWN) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_OPS); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_OPS); if (in_pull_en) SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_IPE); else CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_IPE); if (in_pull_mode == WOV_PULL_DOWN) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_IPS); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_IPS); if (test_mode == WOV_NORMAL_MODE) CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_TST); else SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL0_I2S_TST); /* I2S should be reset in order I2S interface to function correctly. */ wov_i2s_fifo_reset(); return EC_SUCCESS; } /** * I2S channel configuration * * @param channel_num - I2S channel number, 0 or 1. * @param bit_count - I2S channel bit count. * @param trigger - Define the I2S chanel trigger 1->0 or 0->1 * @param start_delay - Defines the delay from the trigger defined for * the channel till the first bit (MSB) of the data. * * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_i2s_channel_config(uint32_t channel_num, uint32_t bit_count, enum wov_i2s_chan_trigger trigger, int32_t start_delay) { /* Check the parameters correctnes. */ if ((channel_num != 0) && (channel_num != 1)) return EC_ERROR_INVAL; if ((start_delay < 0) || (start_delay > 496)) return EC_ERROR_INVAL; if ((bit_count != 16) && (bit_count != 18) && (bit_count != 20) && (bit_count != 24)) return EC_ERROR_INVAL; /* Set the parameters. */ SET_FIELD(NPCX_WOV_I2S_CNTL(channel_num), NPCX_WOV_I2S_CNTL_I2S_BCNT, (bit_count - 1)); if (trigger == WOV_I2S_SAMPLED_1_AFTER_0) CLEAR_BIT(NPCX_WOV_I2S_CNTL(channel_num), NPCX_WOV_I2S_CNTL_I2S_TRIG); else SET_BIT(NPCX_WOV_I2S_CNTL(channel_num), NPCX_WOV_I2S_CNTL_I2S_TRIG); SET_FIELD(NPCX_WOV_I2S_CNTL(channel_num), NPCX_WOV_I2S_CNTL_I2S_ST_DEL, start_delay); /* I2S should be reset in order I2S interface to function correctly. */ wov_i2s_fifo_reset(); return EC_SUCCESS; } /** * Sets sampling rate. * * @param samples_per_second - Valid sample rate. * @return In case sample rate is valid return EC_SUCCESS othewise return * error code. */ int wov_set_sample_rate(uint32_t samples_per_second) { if (wov_conf.mode != WOV_MODE_OFF) return EC_ERROR_INVALID_CONFIG; switch (samples_per_second) { case 8000: case 12000: case 16000: case 24000: case 32000: case 48000: wov_conf.sample_per_sec = samples_per_second; return EC_SUCCESS; default: return EC_ERROR_INVAL; } } /** * Gets sampling rate. * * @param None * @return the current sampling rate. */ uint32_t wov_get_sample_rate(void) { return wov_conf.sample_per_sec; } /** * Sets sampling depth. * * @param bits_num - Valid sample depth in bits. * @return In case sample depth is valid return EC_SUCCESS othewise return * error code. */ int wov_set_sample_depth(int bits_num) { if (wov_conf.mode != WOV_MODE_OFF) return EC_ERROR_INVALID_CONFIG; if ((bits_num != 16) && (bits_num != 18) && (bits_num != 20) && (bits_num != 24)) return EC_ERROR_INVAL; wov_conf.bit_depth = bits_num; return EC_SUCCESS; } /** * Gets sampling depth. * * @param None. * @return sample depth in bits. */ int wov_get_sample_depth(void) { return wov_conf.bit_depth; } /** * Sets microphone source. * * @param mic_src - Valid microphone source * @return return EC_SUCCESS if mic source valid othewise return error code. */ int wov_set_mic_source(enum wov_mic_source mic_src) { wov_conf.mic_src = mic_src; return wov_set_mic_source_l(); } /** * Gets microphone source. * * @param None. * @return sample depth in bits. */ enum wov_mic_source wov_get_mic_source(void) { return wov_conf.mic_src; } /** * Mutes the WoV. * * @param enable - enabled flag, 1 means enable * @return None */ void wov_mute(int enable) { if (enable) SET_BIT(NPCX_APM_CR_ADC, NPCX_APM_CR_ADC_ADC_SOFT_MUTE); else CLEAR_BIT(NPCX_APM_CR_ADC, NPCX_APM_CR_ADC_ADC_SOFT_MUTE); } /** * Sets gain * * @param left_chan_gain - Left channel gain. * @param right_chan_gain - Right channel gain * @return None */ void wov_set_gain(int left_chan_gain, int right_chan_gain) { wov_conf.left_chan_gain = left_chan_gain; wov_conf.right_chan_gain = right_chan_gain; (void) apm_adc_gain_config(APM_ADC_CHAN_GAINS_INDEPENDENT, left_chan_gain, right_chan_gain); } /** * Gets gain values * * @param left_chan_gain - address of left channel gain response. * @param right_chan_gain - address of right channel gain response. * @return None */ void wov_get_gain(int *left_chan_gain, int *right_chan_gain) { *left_chan_gain = wov_conf.left_chan_gain; *right_chan_gain = wov_conf.right_chan_gain; } /** * Enables/Disables ADC. * * @param enable - enabled flag, 1 means enable * @return None */ void wov_enable_agc(int enable) { apm_auto_gain_cntrl_enable(enable); } /** * Enables/Disables the automatic gain. * * @param stereo - Stereo enabled flag, 1 means enable. * @param target - Target output level of the ADC. * @param noise_gate_threshold - Noise Gate system select. 1 means enable. * @param hold_time - Hold time before starting AGC adjustment to * the TARGET value. * @param attack_time - Attack time - gain ramp down. * @param decay_time - Decay time - gain ramp up. * @param max_applied_gain - Maximum Gain Value to apply to the ADC path. * @param min_applied_gain - Minimum Gain Value to apply to the ADC path. * @return EC_ERROR_INVAL or EC_SUCCESS */ enum ec_error_list wov_set_agc_config(int stereo, float target, int noise_gate_threshold, uint8_t hold_time, uint16_t attack_time, uint16_t decay_time, float max_applied_gain, float min_applied_gain) { int target_code; int ngth_code; int attack_time_code; int decay_time_code; int max_applied_gain_code; int min_applied_gain_code; enum ec_error_list ret_code; struct apm_auto_gain_config gain_cfg; for (target_code = 0; target_code < 16; target_code++) { if (((float)target_code * (-1.5)) == target) break; } if (target_code == 16) return EC_ERROR_INVAL; if (noise_gate_threshold == 0) ngth_code = 0; else { for (ngth_code = 0; ngth_code <= 0x07; ngth_code++) { if ((-68 + ngth_code * 6) == noise_gate_threshold) break; } if (ngth_code * 6 > 42) return EC_ERROR_INVAL; } if (hold_time > 15) return EC_ERROR_INVAL; for (attack_time_code = 0; attack_time_code <= 0x0F; attack_time_code++) { if (((attack_time_code + 1) * 32) == attack_time) break; } if (attack_time_code > 0x0F) return EC_ERROR_INVAL; for (decay_time_code = 0; decay_time_code <= 0x0F; decay_time_code++) { if (((decay_time_code + 1) * 32) == decay_time) break; } if (decay_time_code > 0x0F) return EC_ERROR_INVAL; for (max_applied_gain_code = 0; max_applied_gain_code < 16; max_applied_gain_code++) { if ((max_applied_gain_code * 1.5) == max_applied_gain) break; } if (max_applied_gain_code == 16) { for (max_applied_gain_code = 18; max_applied_gain_code < 32; max_applied_gain_code++) { if (((max_applied_gain_code * 1.5) - 4) == max_applied_gain) break; } } if (max_applied_gain_code >= 32) return EC_ERROR_INVAL; for (min_applied_gain_code = 0; min_applied_gain_code < 16; min_applied_gain_code++) { if ((min_applied_gain_code * 1.5) == min_applied_gain) break; } if (min_applied_gain_code == 16) { for (min_applied_gain_code = 18; min_applied_gain_code < 32; min_applied_gain_code++) { if (((min_applied_gain_code * 1.5) - 4) == min_applied_gain) break; } } if (min_applied_gain_code > 32) return EC_ERROR_INVAL; gain_cfg.stereo_enable = stereo, gain_cfg.agc_target = (enum apm_adc_target_out_level) target_code; gain_cfg.nois_gate_en = (noise_gate_threshold != 0); gain_cfg.nois_gate_thold = (enum apm_noise_gate_threshold) ngth_code; gain_cfg.hold_time = (enum apm_agc_adj_hold_time) hold_time; gain_cfg.attack_time = (enum apm_gain_ramp_time) attack_time_code; gain_cfg.decay_time = (enum apm_gain_ramp_time) decay_time_code; gain_cfg.gain_max = (enum apm_gain_values) max_applied_gain_code; gain_cfg.gain_min = (enum apm_gain_values) min_applied_gain_code; ret_code = apm_adc_auto_gain_config(&gain_cfg); return ret_code; } /** * Sets VAD sensitivity. * * @param sensitivity_db - VAD sensitivity in db. * @return None */ int wov_set_vad_sensitivity(int sensitivity_db) { if ((sensitivity_db < 0) || (sensitivity_db > 31)) return EC_ERROR_INVAL; wov_conf.sensitivity_db = sensitivity_db; apm_set_vad_sensitivity(sensitivity_db); return EC_SUCCESS; } /** * Gets VAD sensitivity. * * @param None. * @return VAD sensitivity in db */ int wov_get_vad_sensitivity(void) { return wov_conf.sensitivity_db; } /** * Configure I2S bus format. (Sample rate and size are determined via common * config functions.) * * @param format - one of the following: I2S mode, Right Justified mode, * Left Justified mode, PCM A Audio, PCM B Audio and * Time Division Multiplexing * @return EC error code. */ void wov_set_i2s_fmt(enum wov_dai_format format) { if (wov_conf.mode != WOV_MODE_OFF) return; wov_conf.dai_format = format; } /** * Configure I2S bus clock. (Sample rate and size are determined via common * config functions.) * * @param i2s_clock - I2S clock frequency in Hz (needed in order to * configure the internal PLL for 12MHz) * @return EC error code. */ void wov_set_i2s_bclk(uint32_t i2s_clock) { if (wov_conf.mode != WOV_MODE_OFF) return; wov_conf.i2s_clock = i2s_clock; } /** * Configure I2S bus. (Sample rate and size are determined via common * config functions.) * * @param ch0_delay - 0 to 496. Defines the delay from the SYNC till the * first bit (MSB) of channel 0 (left channel) * @param ch1_delay - -1 to 496. Defines the delay from the SYNC till the * first bit (MSB) of channel 1 (right channel). * If channel 1 is not used set this field to -1. * * @param flags - WOV_TDM_ADJACENT_TO_CH0 = BIT(0). There is a * channel adjacent to channel 0, so float SDAT when * driving the last bit (LSB) of the channel during the * second half of the clock cycle to avoid bus contention. * * WOV_TDM_ADJACENT_TO_CH1 = BIT(1). There is a channel * adjacent to channel 1. * * @return EC error code. */ enum ec_error_list wov_set_i2s_tdm_config(int ch0_delay, int ch1_delay, uint32_t flags) { if (wov_conf.mode != WOV_MODE_OFF) return EC_ERROR_INVALID_CONFIG; if ((ch0_delay < 0) || (ch0_delay > 496) || (ch1_delay < -1) || (ch1_delay > 496)) return EC_ERROR_INVAL; wov_conf.i2s_start_delay_0 = ch0_delay; wov_conf.i2s_start_delay_1 = ch1_delay; SET_FIELD(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL_I2S_ST_DEL, ch0_delay); if (ch1_delay == -1) wov_i2s_channel1_disable(1); else { wov_i2s_channel1_disable(0); SET_FIELD(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL_I2S_ST_DEL, ch1_delay); } if (flags & 0x0001) SET_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL_I2S_LBHIZ); else CLEAR_BIT(NPCX_WOV_I2S_CNTL(0), NPCX_WOV_I2S_CNTL_I2S_LBHIZ); if (flags & 0x0002) SET_BIT(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL_I2S_LBHIZ); else CLEAR_BIT(NPCX_WOV_I2S_CNTL(1), NPCX_WOV_I2S_CNTL_I2S_LBHIZ); /* I2S should be reset in order I2S interface to function correctly. */ wov_i2s_fifo_reset(); return EC_SUCCESS; } static void wov_system_init(void) { /* Set WoV module to be operational. */ clock_enable_peripheral(CGC_OFFSET_WOV, CGC_WOV_MASK, CGC_MODE_RUN | CGC_MODE_SLEEP); /* Configure pins from GPIOs to WOV */ gpio_config_module(MODULE_WOV, 1); wov_init(); task_enable_irq(NPCX_IRQ_WOV); CPRINTS("WoV init done"); } DECLARE_HOOK(HOOK_INIT, wov_system_init, HOOK_PRIO_DEFAULT); void wov_handle_event(enum wov_events event) { if (event == WOV_EVENT_DATA_READY) { CPRINTS("ram data ready and stop ram capture"); /* just capture one times on RAM*/ wov_stop_ram_capture(); } if (event == WOV_EVENT_VAD) CPRINTS("got vad"); if (event == WOV_EVENT_ERROR_CORE_FIFO_OVERRUN) CPRINTS("error: cfifo overrun"); } #ifdef DEBUG_AUDIO_CODEC static uint32_t voice_buffer[VOICE_BUF_SIZE] = {0}; /* voice data 16Khz 2ch 16bit 1s */ static int command_wov(int argc, char **argv) { static int bit_clk; static enum wov_dai_format i2s_fmt; if (argc == 2) { if (strcasecmp(argv[1], "init") == 0) { wov_system_init(); return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgget") == 0) { CPRINTS("mode:%d", wov_get_mode()); CPRINTS("sample rate:%d", wov_get_sample_rate()); CPRINTS("sample bits:%d", wov_get_sample_depth()); CPRINTS("mic source:%d", wov_get_mic_source()); CPRINTS("vad sensitivity :%d", wov_get_vad_sensitivity()); return EC_SUCCESS; } /* Start to capature voice data and store in RAM buffer */ if (strcasecmp(argv[1], "capram") == 0) { if (wov_set_buffer((uint32_t *)voice_buffer, sizeof(voice_buffer) / sizeof(uint32_t)) == EC_SUCCESS) { CPRINTS("Start RAM Catpure..."); wov_start_ram_capture(); return EC_SUCCESS; } CPRINTS("Init fail: voice buffer size"); return EC_ERROR_INVAL; } } else if (argc == 3) { if (strcasecmp(argv[1], "cfgsrc") == 0) { if (strcasecmp(argv[2], "mono") == 0) wov_set_mic_source(WOV_SRC_MONO); else if (strcasecmp(argv[2], "stereo") == 0) wov_set_mic_source(WOV_SRC_STEREO); else if (strcasecmp(argv[2], "left") == 0) wov_set_mic_source(WOV_SRC_LEFT); else if (strcasecmp(argv[2], "right") == 0) wov_set_mic_source(WOV_SRC_RIGHT); else return EC_ERROR_INVAL; wov_i2s_fifo_reset(); return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgbit") == 0) { int bits; bits = atoi(argv[2]); if ((bits == 16) || (bits == 18) || (bits == 20) || (bits == 24)) { return wov_set_sample_depth(bits); } } if (strcasecmp(argv[1], "cfgsfs") == 0) { int fs; fs = atoi(argv[2]); return wov_set_sample_rate(fs); } if (strcasecmp(argv[1], "cfgbck") == 0) { int fs; fs = wov_get_sample_rate(); if (strcasecmp(argv[2], "32fs") == 0) bit_clk = fs * 32; else if (strcasecmp(argv[2], "48fs") == 0) bit_clk = fs * 48; else if (strcasecmp(argv[2], "64fs") == 0) bit_clk = fs * 64; else if (strcasecmp(argv[2], "128fs") == 0) bit_clk = fs * 128; else if (strcasecmp(argv[2], "256fs") == 0) bit_clk = fs * 256; else return EC_ERROR_INVAL; wov_set_i2s_fmt(i2s_fmt); wov_set_i2s_bclk(bit_clk); return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgfmt") == 0) { if (strcasecmp(argv[2], "i2s") == 0) i2s_fmt = WOV_DAI_FMT_I2S; else if (strcasecmp(argv[2], "right") == 0) i2s_fmt = WOV_DAI_FMT_RIGHT_J; else if (strcasecmp(argv[2], "left") == 0) i2s_fmt = WOV_DAI_FMT_LEFT_J; else if (strcasecmp(argv[2], "pcma") == 0) i2s_fmt = WOV_DAI_FMT_PCM_A; else if (strcasecmp(argv[2], "pcmb") == 0) i2s_fmt = WOV_DAI_FMT_PCM_B; else if (strcasecmp(argv[2], "tdm") == 0) i2s_fmt = WOV_DAI_FMT_PCM_TDM; else return EC_ERROR_INVAL; wov_set_i2s_fmt(i2s_fmt); wov_set_i2s_bclk(bit_clk); return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgdckV") == 0) { if (strcasecmp(argv[2], "1.0") == 0) apm_set_vad_dmic_rate(APM_DMIC_RATE_1_0); else if (strcasecmp(argv[2], "1.2") == 0) apm_set_vad_dmic_rate(APM_DMIC_RATE_1_2); else if (strcasecmp(argv[2], "2.4") == 0) apm_set_vad_dmic_rate(APM_DMIC_RATE_2_4); else if (strcasecmp(argv[2], "3.0") == 0) apm_set_vad_dmic_rate(APM_DMIC_RATE_3_0); else if (strcasecmp(argv[2], "0.75") == 0) apm_set_vad_dmic_rate(APM_DMIC_RATE_0_75); else return EC_ERROR_INVAL; return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgdckR") == 0) { if (strcasecmp(argv[2], "1.0") == 0) apm_set_adc_ram_dmic_config(APM_DMIC_RATE_1_0); else if (strcasecmp(argv[2], "1.2") == 0) apm_set_adc_ram_dmic_config(APM_DMIC_RATE_1_2); else if (strcasecmp(argv[2], "2.4") == 0) apm_set_adc_ram_dmic_config(APM_DMIC_RATE_2_4); else if (strcasecmp(argv[2], "3.0") == 0) apm_set_adc_ram_dmic_config(APM_DMIC_RATE_3_0); else if (strcasecmp(argv[2], "0.75") == 0) apm_set_adc_ram_dmic_config(APM_DMIC_RATE_0_75); else return EC_ERROR_INVAL; return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgdckI") == 0) { if (strcasecmp(argv[2], "1.0") == 0) apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_1_0); else if (strcasecmp(argv[2], "1.2") == 0) apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_1_2); else if (strcasecmp(argv[2], "2.4") == 0) apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_2_4); else if (strcasecmp(argv[2], "3.0") == 0) apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_3_0); else if (strcasecmp(argv[2], "0.75") == 0) apm_set_adc_i2s_dmic_config(APM_DMIC_RATE_0_75); else return EC_ERROR_INVAL; return EC_SUCCESS; } if (strcasecmp(argv[1], "cfgmod") == 0) { if (strcasecmp(argv[2], "off") == 0) { wov_set_mode(WOV_MODE_OFF); wov_stop_ram_capture(); } else if (strcasecmp(argv[2], "vad") == 0) { wov_set_mode(WOV_MODE_VAD); } else if (strcasecmp(argv[2], "ram") == 0) { if (wov_set_buffer((uint32_t *)voice_buffer, sizeof(voice_buffer) / sizeof(uint32_t)) == EC_SUCCESS) wov_set_mode(WOV_MODE_RAM); else return EC_ERROR_INVAL; } else if (strcasecmp(argv[2], "i2s") == 0) { wov_set_mode(WOV_MODE_I2S); } else if (strcasecmp(argv[2], "rami2s") == 0) { if (wov_set_buffer((uint32_t *)voice_buffer, sizeof(voice_buffer) / sizeof(uint32_t)) == EC_SUCCESS) wov_set_mode(WOV_MODE_RAM_AND_I2S); else return EC_ERROR_INVAL; } else { return EC_ERROR_INVAL; } wov_i2s_fifo_reset(); return EC_SUCCESS; } if (strcasecmp(argv[1], "mute") == 0) { if (strcasecmp(argv[2], "enable") == 0) { wov_mute(1); return EC_SUCCESS; } if (strcasecmp(argv[2], "disable") == 0) { wov_mute(0); return EC_SUCCESS; } } if (strcasecmp(argv[1], "fmul2") == 0) { if (strcasecmp(argv[2], "enable") == 0) { CLEAR_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_DIS); return EC_SUCCESS; } if (strcasecmp(argv[2], "disable") == 0) { SET_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_DIS); return EC_SUCCESS; } } if (strcasecmp(argv[1], "vadsens") == 0) return wov_set_vad_sensitivity(atoi(argv[2])); if (strcasecmp(argv[1], "gain") == 0) { wov_set_gain(atoi(argv[2]), atoi(argv[2])); return EC_SUCCESS; } } else if (argc == 5) { if (strcasecmp(argv[1], "cfgtdm") == 0) { int delay0, delay1; uint32_t flags; delay0 = atoi(argv[2]); delay1 = atoi(argv[3]); flags = atoi(argv[4]); if ((delay0 > 496) || (delay1 > 496) || (flags > 3) || (delay0 < 0) || (delay1 < 0)) { return EC_ERROR_INVAL; } wov_set_i2s_tdm_config(delay0, delay1, flags); return EC_SUCCESS; } } return EC_ERROR_INVAL; } DECLARE_CONSOLE_COMMAND(wov, command_wov, "init\n" "mute \n" "capram\n" "cfgsrc \n" "cfgbit <16|18|20|24>\n" "cfgsfs <8000|12000|16000|24000|32000|48000>\n" "cfgbck <32fs|48fs|64fs|128fs|256fs>\n" "cfgfmt \n" "cfgmod \n" "cfgtdm [0~496 0~496 0~3]>\n" "cfgdckV <0.75|1.0|1.2|2.4|3.0>\n" "cfgdckR <0.75|1.0|1.2|2.4|3.0>\n" "cfgdckI <0.75|1.0|1.2|2.4|3.0>\n" "cfgget\n" "fmul2 \n" "vadsens <0~31>\n" "gain <0~31>", "wov configuration"); #endif