| /* Copyright (c) 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_WAKE_ON_VOICE if npcx ec doesn't support WOV !" |
| #endif |
| |
| /* Console output macros */ |
| #if !(DEBUG_WOV) |
| #define CPUTS(...) |
| #define CPRINTS(...) |
| #else |
| #define CPUTS(outstr) cputs(CC_WOV, outstr) |
| #define CPRINTS(format, args...) cprints(CC_WOV, 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; |
| |
| const uint32_t voice_buffer[VOICE_BUF_SIZE] = {0}; |
| |
| #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 NPCX7m7w 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) |
| { |
| 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; |
| } |
| |
| /** |
| * 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); |
| 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); |
| 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); |
| 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); |
| apm_set_vad_input_channel(APM_IN_RESERVED); |
| break; |
| |
| default: |
| return EC_ERROR_INVAL; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * 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_CLEAR_VAD_INTERRUPT; |
| apm_vad_enable(0); |
| apm_enable_vad_interrupt(0); |
| 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_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); |
| |
| /* 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) { |
| /* 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); |
| } else |
| SET_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_FMUL2_DIS); |
| |
| udelay(WOV_FMUL2_CLK_TUNING_DELAY_TIME); |
| |
| /* Disable clock tuning. */ |
| SET_BIT(NPCX_FMUL2_FM2CTRL, NPCX_FMUL2_FM2CTRL_TUNE_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); |
| } |
| |
| /*************************************************************************** |
| * |
| * 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; |
| |
| if ((wov_mode == WOV_MODE_I2S) || (wov_mode == WOV_MODE_RAM_AND_I2S)) { |
| wov_pll_enable(1); |
| wov_set_clk_selection(WOV_PLL_CLK_SRC); |
| } else { |
| wov_pll_enable(0); |
| wov_set_clk_selection(WOV_FMUL2_CLK_SRC); |
| wov_stop_i2s_capture(); |
| } |
| |
| apm_set_mode(wov_mode); |
| |
| ret_code = wov_set_mic_source_l(); |
| if (ret_code != EC_SUCCESS) |
| return ret_code; |
| |
| switch (wov_mode) { |
| case WOV_MODE_OFF: |
| wov_dmic_clk_config(0, WOV_DMIC_DIV_DISABLE); |
| wov_mute(1); |
| wov_fmul2_enable(0); |
| break; |
| case WOV_MODE_VAD: |
| wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); |
| wov_fmul2_enable(1); |
| wov_mute(0); |
| break; |
| case WOV_MODE_RAM: |
| if ((wov_conf.bit_depth != 16) && (wov_conf.bit_depth != 24)) |
| return EC_ERROR_INVAL; |
| wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); |
| wov_fmul2_enable(1); |
| wov_mute(0); |
| break; |
| case WOV_MODE_I2S: |
| wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); |
| wov_fmul2_enable(0); |
| wov_set_i2s_config_l(); |
| wov_start_i2s_capture(); |
| wov_mute(0); |
| break; |
| case WOV_MODE_RAM_AND_I2S: |
| if ((wov_conf.bit_depth != 16) && (wov_conf.bit_depth != 24)) |
| return EC_ERROR_INVAL; |
| wov_dmic_clk_config(1, WOV_DMIC_DIV_DISABLE); |
| wov_fmul2_enable(0); |
| wov_set_i2s_config_l(); |
| wov_start_i2s_capture(); |
| wov_mute(0); |
| break; |
| default: |
| wov_dmic_clk_config(0, WOV_DMIC_DIV_DISABLE); |
| wov_fmul2_enable(0); |
| wov_mute(1); |
| return EC_ERROR_INVAL; |
| } |
| |
| 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.rigth_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; |
| |
| 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_1_0); |
| apm_set_adc_dmic_config(APM_DMIC_RATE_2_4); |
| } |
| |
| /** |
| * 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; |
| |
| 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(1); |
| |
| if (!is_apm_disable) |
| apm_enable(1); |
| } |
| |
| /** |
| * 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(1); |
| } |
| |
| /** |
| * 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(1); |
| |
| CLEAR_BIT(NPCX_WOV_PLL_CNTL1, NPCX_WOV_PLL_CNTL1_PLL_PWDEN); |
| |
| udelay(1); |
| |
| 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(10); |
| } |
| |
| /** |
| * 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(10); |
| |
| 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) |
| { |
| SET_BIT(NPCX_WOV_FIFO_CNT, NPCX_WOV_FIFO_CNT_I2S_FFRST); |
| |
| udelay(10); |
| |
| 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 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(10); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * wov_i2s_channel1_disable |
| * |
| * @param disable - disabled flag, 1 means disable |
| * |
| * @return None |
| */ |
| 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 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) |
| { |
| (void) apm_adc_gain_config(APM_ADC_CHAN_GAINS_INDEPENDENT, |
| left_chan_gain, 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. (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) |
| * @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_config(uint32_t i2s_clock, enum wov_dai_format format) |
| { |
| if (wov_conf.mode != WOV_MODE_OFF) |
| return; |
| |
| wov_conf.i2s_clock = i2s_clock; |
| wov_conf.dai_format = format; |
| } |
| |
| /** |
| * 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 = (1 << 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 = (1 << 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) |
| { |
| enum wov_modes mode; |
| |
| mode = wov_get_mode(); |
| if (event == WOV_EVENT_DATA_READY) { |
| CPRINTS("ram data ready"); |
| if (mode == WOV_MODE_RAM) { |
| wov_set_mode(WOV_MODE_OFF); |
| } else if (mode == WOV_MODE_RAM_AND_I2S) { |
| /* 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"); |
| } |
| /* 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; |
| } |
| } 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_config(bit_clk, i2s_fmt); |
| 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_config(bit_clk, i2s_fmt); |
| return EC_SUCCESS; |
| } |
| if (strcasecmp(argv[1], "cfgdck") == 0) { |
| if (strcasecmp(argv[2], "1.0") == 0) |
| apm_set_adc_dmic_config(APM_DMIC_RATE_1_0); |
| else if (strcasecmp(argv[2], "2.4") == 0) |
| apm_set_adc_dmic_config(APM_DMIC_RATE_2_4); |
| else if (strcasecmp(argv[2], "3.0") == 0) |
| apm_set_adc_dmic_config(APM_DMIC_RATE_3_0); |
| 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) |
| CPRINTS("Init fail: voice buf size"); |
| wov_set_mode(WOV_MODE_RAM); |
| wov_start_ram_capture(); |
| } else if (strcasecmp(argv[2], "i2s") == 0) { |
| wov_set_mode(WOV_MODE_I2S); |
| wov_stop_ram_capture(); |
| } else if (strcasecmp(argv[2], "rami2s") == 0) { |
| if (wov_set_buffer((uint32_t *)voice_buffer, |
| sizeof(voice_buffer) / sizeof(uint32_t)) |
| != EC_SUCCESS) |
| CPRINTS("Init fail: voice buf size"); |
| wov_set_mode(WOV_MODE_RAM_AND_I2S); |
| wov_start_ram_capture(); |
| } 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], "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 <enable|disable>\n" |
| "cfgsrc <mono|stereo|left|right>\n" |
| "cfgbit <16|18|20|24>\n" |
| "cfgsfs <8000|12000|16000|24000|32000|48000>\n" |
| "cfgbck <32fs|48fs|64fs|128fs|256fs>\n" |
| "cfgfmt <i2s|right|left|pcma|pcmb|tdm>\n" |
| "cfgmod <off|vad|ram|i2s|rami2s>\n" |
| "cfgtdm [0~496 0~496 0~3]>\n" |
| "cfgdck <1.0|2.4|3.0>\n" |
| "cfgget\n" |
| "vadsens <0~31>\n" |
| "gain <0~31>", |
| "wov configuration"); |