| /* Copyright (c) 2014 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 fan control module. */ |
| |
| #include "clock.h" |
| #include "clock_chip.h" |
| #include "fan.h" |
| #include "fan_chip.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "registers.h" |
| #include "util.h" |
| #include "pwm.h" |
| #include "pwm_chip.h" |
| #include "console.h" |
| #include "timer.h" |
| #include "task.h" |
| |
| #if !(DEBUG_FAN) |
| #define CPRINTS(...) |
| #else |
| #define CPRINTS(format, args...) cprints(CC_PWM, format, ## args) |
| #endif |
| |
| /* Fan operation module */ |
| enum npcx_fan_op_module { |
| NPCX_FAN_OP_PWM, |
| NPCX_FAN_OP_MFT, |
| /* Number of FAN module operations */ |
| NPCX_FAN_OP_COUNT |
| }; |
| |
| /* MFT model select */ |
| enum npcx_mft_mdsel { |
| NPCX_MFT_MDSEL_1, |
| NPCX_MFT_MDSEL_2, |
| NPCX_MFT_MDSEL_3, |
| NPCX_MFT_MDSEL_4, |
| NPCX_MFT_MDSEL_5, |
| /* Number of MFT modes */ |
| NPCX_MFT_MDSEL_COUNT |
| }; |
| |
| /* MFT clock source */ |
| enum npcx_mft_clk_src { |
| TCKC_NOCLK = 0, |
| TCKC_PRESCALE_APB1_CLK, |
| TCKC_EXTERNAL, |
| TCKC_PULSE_ACC, |
| TCKC_LFCLK |
| }; |
| |
| #define RPM_SCALE 1 /* Fan RPM is multiplier of actual RPM */ |
| #define RPM_EDGES 1 /* Fan number of edges - 1 */ |
| /* |
| * RPM = (n - 1) * m * f * 60 / poles / TACH |
| * n = Fan number of edges = (RPM_EDGES + 1) |
| * m = Fan multiplier defined by RANGE |
| * f = PWM and MFT operation freq |
| * poles = 2 |
| */ |
| #define RPM_TO_TACH(pwm_channel, rpm) \ |
| MIN(((uint32_t)(pwm_channels[pwm_channel].freq) \ |
| *(pwm_channels[pwm_channel].cycle_pulses)*30*RPM_EDGES*RPM_SCALE \ |
| /MAX((rpm), 1)), (pwm_channels[pwm_channel].cycle_pulses)) |
| |
| #define TACH_TO_RPM(mft_channel, tach) \ |
| ((mft_channels[mft_channel].freq)*30*RPM_EDGES*RPM_SCALE \ |
| /MAX((tach), 1)) |
| |
| /* Global variables */ |
| static volatile struct tacho_status_t tacho_status; |
| static int rpm_target; |
| static int pre_duty; |
| static int rpm_actual = -1; |
| static int fan_init_ch; |
| /** |
| * Select fan operation channel by module. |
| * |
| * @param none |
| * @param op_module npcx operation module |
| * @return npcx operation channel by module |
| * @notes Fan is controlled by PWM/MFT module in npcx chip |
| */ |
| static int fan_op_ch(int ch, enum npcx_fan_op_module op_module) |
| { |
| uint8_t op_ch; |
| |
| switch (ch) { |
| case 0: |
| if (op_module == NPCX_FAN_OP_PWM) |
| op_ch = PWM_CH_FAN; |
| else |
| op_ch = MFT_CH_0; |
| break; |
| default: |
| op_ch = 0; |
| break; |
| } |
| return op_ch; |
| } |
| |
| /** |
| * MFT start measure. |
| * |
| * @param ch operation channel |
| * @return none |
| */ |
| static void mft_startmeasure(int ch) |
| { |
| int mft_ch = fan_op_ch(ch, NPCX_FAN_OP_MFT); |
| |
| /* Start measurement */ |
| #ifdef CONFIG_MFT_INPUT_LFCLK |
| /* Set the LFCLK clock. */ |
| if (NPCX_MFT_MODULE_PORT_TB == mft_channels[mft_ch].port) |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C2CSEL))) |
| |(TCKC_LFCLK<<NPCX_TCKC_C2CSEL); |
| else |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C1CSEL))) |
| |(TCKC_LFCLK<<NPCX_TCKC_C1CSEL); |
| #else |
| /* Set the core clock. */ |
| if (NPCX_MFT_MODULE_PORT_TB == mft_channels[mft_ch].port) |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C2CSEL))) |
| |(TCKC_PRESCALE_APB1_CLK<<NPCX_TCKC_C2CSEL); |
| else |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C1CSEL))) |
| |(TCKC_PRESCALE_APB1_CLK<<NPCX_TCKC_C1CSEL); |
| #endif |
| } |
| |
| /** |
| * MFT stop measure. |
| * |
| * @param ch operation channel |
| * @return none |
| */ |
| static void mft_stopmeasure(int ch) |
| { |
| int mft_ch = fan_op_ch(ch, NPCX_FAN_OP_MFT); |
| |
| /* Clear all pending flag */ |
| NPCX_TECLR(mft_channels[mft_ch].module) = |
| NPCX_TECTRL(mft_channels[mft_ch].module); |
| |
| /* Stop the timer */ |
| if (NPCX_MFT_MODULE_PORT_TB == mft_channels[mft_ch].port) |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C2CSEL))) |
| |(TCKC_NOCLK<<NPCX_TCKC_C2CSEL); |
| else |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| &(~(((1<<3)-1)<<NPCX_TCKC_C1CSEL))) |
| |(TCKC_NOCLK<<NPCX_TCKC_C1CSEL); |
| } |
| |
| /** |
| * MFT final measure. |
| * |
| * @param ch operation channel |
| * @return none |
| */ |
| static void mft_finalmeasure(int ch) |
| { |
| int mft_ch = fan_op_ch(ch, NPCX_FAN_OP_MFT); |
| |
| /* |
| * Start of the last tacho cycle is detected - |
| * calculated tacho cycle duration |
| */ |
| if (NPCX_MFT_MODULE_PORT_TB == mft_channels[mft_ch].port) |
| tacho_status.edge_interval = |
| (uint32_t)(mft_channels[mft_ch].default_count |
| - NPCX_TCRB(mft_channels[mft_ch].module)); |
| else |
| tacho_status.edge_interval = |
| (uint32_t)(mft_channels[mft_ch].default_count |
| - NPCX_TCRA(mft_channels[mft_ch].module)); |
| } |
| |
| /** |
| * Preset fan operation clock. |
| * |
| * @param none |
| * @return none |
| * @notes changed when initial or HOOK_FREQ_CHANGE command |
| */ |
| #ifndef CONFIG_MFT_INPUT_LFCLK |
| void mft_freq_changed(void) |
| { |
| uint16_t prescaler_divider = 0; |
| int mft_ch = fan_op_ch(fan_init_ch, NPCX_FAN_OP_MFT); |
| |
| /* Set clock prescaler divider to MFT module*/ |
| prescaler_divider = (uint16_t)(clock_get_apb1_freq() |
| /mft_channels[mft_ch].freq); |
| if (prescaler_divider >= 1) |
| prescaler_divider = prescaler_divider - 1; |
| if (prescaler_divider > 0xFF) |
| prescaler_divider = 0xFF; |
| |
| NPCX_TPRSC(mft_channels[mft_ch].module) = (uint8_t)prescaler_divider; |
| } |
| DECLARE_HOOK(HOOK_FREQ_CHANGE, mft_freq_changed, HOOK_PRIO_DEFAULT); |
| #endif |
| |
| /** |
| * Fan configuration. |
| * |
| * @param ch operation channel |
| * @param enable_mft_read_rpm FAN_USE_RPM_MODE enable flag |
| * @return none |
| */ |
| static void fan_config(int ch, int enable_mft_read_rpm) |
| { |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| int mft_ch = fan_op_ch(ch, NPCX_FAN_OP_MFT); |
| |
| fan_init_ch = ch; |
| pwm_config(pwm_ch); |
| |
| /* Configure pins from GPIOs to FAN */ |
| gpio_config_module(MODULE_PWM_FAN, 1); |
| |
| if (enable_mft_read_rpm) { |
| /* Set mode 5 to MFT module*/ |
| NPCX_TMCTRL(mft_channels[mft_ch].module) = |
| (NPCX_TMCTRL(mft_channels[mft_ch].module) |
| & (~(((1<<3)-1)<<NPCX_TMCTRL_MDSEL))) |
| | (NPCX_MFT_MDSEL_5<<NPCX_TMCTRL_MDSEL); |
| |
| #ifndef CONFIG_MFT_INPUT_LFCLK |
| /* Set MFT operation frequence */ |
| mft_freq_changed(); |
| /* Set the active power mode. */ |
| CLEAR_BIT(NPCX_TCKC(mft_channels[mft_ch].module), |
| NPCX_TCKC_LOW_PWR); |
| #else |
| /* Set the low power mode. */ |
| SET_BIT(NPCX_TCKC(mft_channels[mft_ch].module), |
| NPCX_TCKC_LOW_PWR); |
| #endif |
| if (NPCX_MFT_MODULE_PORT_TB == mft_channels[mft_ch].port) { |
| /* Set the default count-down timer. */ |
| NPCX_TCNT2(mft_channels[mft_ch].module) = |
| mft_channels[mft_ch].default_count; |
| NPCX_TCRB(mft_channels[mft_ch].module) = |
| mft_channels[mft_ch].default_count; |
| /* Set the edge polarity to rising. */ |
| SET_BIT(NPCX_TMCTRL(mft_channels[mft_ch].module), |
| NPCX_TMCTRL_TBEDG); |
| /* Enable capture TCNT2 into TCRB and preset TCNT2. */ |
| SET_BIT(NPCX_TMCTRL(mft_channels[mft_ch].module), |
| NPCX_TMCTRL_TBEN); |
| /* Enable input debounce logic into TB. */ |
| SET_BIT(NPCX_TCFG(mft_channels[mft_ch].module), |
| NPCX_TCFG_TBDBEN); |
| /* Set the no clock to TCNT2. */ |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| & (~(((1<<3)-1)<<NPCX_TCKC_C2CSEL))) |
| | (TCKC_NOCLK<<NPCX_TCKC_C2CSEL); |
| /* Set timer wake-up enable */ |
| SET_BIT(NPCX_TWUEN(mft_channels[mft_ch].module), |
| NPCX_TWUEN_TBWEN); |
| SET_BIT(NPCX_TWUEN(mft_channels[mft_ch].module), |
| NPCX_TWUEN_TDWEN); |
| } else { |
| /* Set the default count-down timer. */ |
| NPCX_TCNT1(mft_channels[mft_ch].module) = |
| mft_channels[mft_ch].default_count; |
| NPCX_TCRA(mft_channels[mft_ch].module) = |
| mft_channels[mft_ch].default_count; |
| /* Set the edge polarity to rising. */ |
| SET_BIT(NPCX_TMCTRL(mft_channels[mft_ch].module), |
| NPCX_TMCTRL_TAEDG); |
| /* Enable capture TCNT1 into TCRA and preset TCNT1. */ |
| SET_BIT(NPCX_TMCTRL(mft_channels[mft_ch].module), |
| NPCX_TMCTRL_TAEN); |
| /* Enable input debounce logic into TA. */ |
| SET_BIT(NPCX_TCFG(mft_channels[mft_ch].module), |
| NPCX_TCFG_TADBEN); |
| /* Set the no clock to TCNT1. */ |
| NPCX_TCKC(mft_channels[mft_ch].module) = |
| (NPCX_TCKC(mft_channels[mft_ch].module) |
| & (~(((1<<3)-1)<<NPCX_TCKC_C1CSEL))) |
| | (TCKC_NOCLK<<NPCX_TCKC_C1CSEL); |
| /* Set timer wake-up enable */ |
| SET_BIT(NPCX_TWUEN(mft_channels[mft_ch].module), |
| NPCX_TWUEN_TAWEN); |
| SET_BIT(NPCX_TWUEN(mft_channels[mft_ch].module), |
| NPCX_TWUEN_TCWEN); |
| } |
| } |
| |
| /* Back to Idle mode*/ |
| tacho_status.cur_state = TACHO_IN_IDLE; |
| } |
| |
| /** |
| * Set fan enabled. |
| * |
| * @param ch operation channel |
| * @param enabled enabled flag |
| * @return none |
| */ |
| void fan_set_enabled(int ch, int enabled) |
| { |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| |
| pwm_enable(pwm_ch, enabled); |
| } |
| |
| /** |
| * Check fan enabled. |
| * |
| * @param ch operation channel |
| * @return enabled or not |
| */ |
| int fan_get_enabled(int ch) |
| { |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| |
| return pwm_get_enabled(pwm_ch); |
| } |
| |
| /** |
| * Set fan duty cycle. |
| * |
| * @param ch operation channel |
| * @param percent duty cycle percent |
| * @return none |
| */ |
| void fan_set_duty(int ch, int percent) |
| { |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| |
| CPRINTS("set duty percent=%d", percent); |
| |
| /* Set the duty cycle */ |
| pwm_set_duty(pwm_ch, percent); |
| } |
| |
| /** |
| * Get fan duty cycle. |
| * |
| * @param ch operation channel |
| * @return duty cycle |
| */ |
| int fan_get_duty(int ch) |
| { |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| |
| /* Return percent */ |
| return pwm_get_duty(pwm_ch); |
| } |
| |
| /** |
| * Check fan is rpm operation mode. |
| * |
| * @param ch operation channel |
| * @return rpm operation mode or not |
| * @notes not support in npcx chip |
| */ |
| int fan_get_rpm_mode(int ch) |
| { |
| /* TODO: (Benson_TBD_4) not support rpm mode, return 0 always */ |
| return 0; |
| } |
| |
| /** |
| * Set fan to rpm operation mode. |
| * |
| * @param ch operation channel |
| * @param rpm_mode rpm operation mode flag |
| * @return none |
| * @notes not support in npcx chip |
| */ |
| void fan_set_rpm_mode(int ch, int rpm_mode) |
| { |
| /* TODO: (Benson_TBD_4) not support rpm mode */ |
| } |
| |
| /** |
| * Get fan actual operation rpm. |
| * |
| * @param ch operation channel |
| * @return actual operation rpm value |
| */ |
| int fan_get_rpm_actual(int ch) |
| { |
| int mft_ch = fan_op_ch(ch, NPCX_FAN_OP_MFT); |
| uint8_t capture_pnd = NPCX_TECTRL_TBPND, |
| underflow_pnd = NPCX_TECTRL_TDPND; |
| uint8_t capture_clr = NPCX_TECLR_TBCLR, |
| underflow_clr = NPCX_TECLR_TDCLR; |
| |
| /* Init pending/clear flag bit */ |
| if (NPCX_MFT_MODULE_PORT_TA == mft_channels[mft_ch].port) { |
| capture_pnd = NPCX_TECTRL_TAPND; |
| underflow_pnd = NPCX_TECTRL_TCPND; |
| capture_clr = NPCX_TECLR_TACLR; |
| underflow_clr = NPCX_TECLR_TCCLR; |
| } |
| /* Start measure and return previous value when fan is working*/ |
| if ((fan_get_enabled(ch)) && (fan_get_duty(ch))) { |
| if ((tacho_status.cur_state == TACHO_IN_IDLE) |
| || (pre_duty != fan_get_duty(ch))) { |
| CPRINTS("mft_startmeasure"); |
| if ((0 == rpm_actual) || (-1 == rpm_actual)) |
| rpm_actual = fans[ch].rpm_min; |
| /* Clear all pending flags */ |
| NPCX_TECLR(mft_channels[mft_ch].module) = |
| NPCX_TECTRL(mft_channels[mft_ch].module); |
| /* Start from first edge state */ |
| tacho_status.cur_state = TACHO_WAIT_FOR_1_EDGE; |
| /* Start measure */ |
| mft_startmeasure(ch); |
| } |
| /* Check whether MFT underflow flag is occurred */ |
| else if (IS_BIT_SET(NPCX_TECTRL(mft_channels[mft_ch].module), |
| underflow_pnd)) { |
| /* Measurement is active - stop the measurement */ |
| mft_stopmeasure(fan_init_ch); |
| /* Need to avoid underflow state happen */ |
| rpm_actual = 0; |
| /* |
| * Flag TDPND means mft underflow happen, |
| * but let MFT still can re-measure actual rpm |
| * when user change pwm/fan duty during |
| * TACHO_UNDERFLOW state. |
| */ |
| tacho_status.cur_state = TACHO_UNDERFLOW; |
| CPRINTS("TACHO_UNDERFLOW"); |
| |
| /* Clear pending flags */ |
| SET_BIT(NPCX_TECLR(mft_channels[mft_ch].module), |
| underflow_clr); |
| } |
| /* Check whether MFT signal detection flag is occurred */ |
| else if (IS_BIT_SET(NPCX_TECTRL(mft_channels[mft_ch].module), |
| capture_pnd)) { |
| /* Start of tacho cycle is detected */ |
| switch (tacho_status.cur_state) { |
| case TACHO_WAIT_FOR_1_EDGE: |
| CPRINTS("TACHO_WAIT_FOR_1_EDGE"); |
| /* |
| * Start of the first tacho cycle is detected |
| * and wait for the second tacho cycle |
| * (second edge) |
| */ |
| tacho_status.cur_state = TACHO_WAIT_FOR_2_EDGE; |
| /* Send previous rpm before complete measure */ |
| break; |
| case TACHO_WAIT_FOR_2_EDGE: |
| /* Complete measure tach and get actual tach */ |
| mft_finalmeasure(fan_init_ch); |
| /* Stop the measurement */ |
| mft_stopmeasure(ch); |
| /* Transfer actual tach to actual rpm */ |
| rpm_actual = (tacho_status.edge_interval > 0) ? |
| (TACH_TO_RPM(mft_ch, |
| tacho_status.edge_interval)) : 0; |
| /* Back to Idle mode*/ |
| tacho_status.cur_state = TACHO_IN_IDLE; |
| CPRINTS("TACHO_WAIT_FOR_2_EDGE"); |
| CPRINTS("edge_interval=%x", |
| tacho_status.edge_interval); |
| CPRINTS("rpm_actual=%d", rpm_actual); |
| break; |
| default: |
| break; |
| } |
| /* Clear pending flags */ |
| SET_BIT(NPCX_TECLR(mft_channels[mft_ch].module), |
| capture_clr); |
| } |
| } else { |
| CPRINTS("preset rpm"); |
| /* Send preset rpm before fan is working */ |
| if (fan_get_enabled(ch)) |
| rpm_actual = fans[ch].rpm_min; |
| else |
| rpm_actual = 0; |
| |
| tacho_status.cur_state = TACHO_IN_IDLE; |
| } |
| pre_duty = fan_get_duty(ch); |
| return rpm_actual; |
| } |
| |
| /** |
| * Get fan setting rpm. |
| * |
| * @param ch operation channel |
| * @return setting rpm value |
| */ |
| int fan_get_rpm_target(int ch) |
| { |
| return rpm_target; |
| } |
| |
| /** |
| * Set fan setting rpm. |
| * |
| * @param ch operation channel |
| * @param rpm setting rpm value |
| * @return none |
| */ |
| void fan_set_rpm_target(int ch, int rpm) |
| { |
| uint32_t percent = 0; |
| int pwm_ch = fan_op_ch(ch, NPCX_FAN_OP_PWM); |
| |
| rpm_target = rpm; |
| /* Transfer rpm to tach then calculate percentage */ |
| percent = (RPM_TO_TACH(pwm_ch, rpm_target)*100) |
| /(pwm_channels[pwm_ch].cycle_pulses); |
| |
| if (percent < 0) |
| percent = 0; |
| else if (percent > 100) |
| percent = 100; |
| |
| /* RPM is inverse ratio to tach and percentage */ |
| percent = 100 - percent; |
| |
| pwm_set_duty(pwm_ch, percent); |
| } |
| |
| /** |
| * Check fan operation status. |
| * |
| * @param ch operation channel |
| * @return fan_status fan operation status |
| */ |
| enum fan_status fan_get_status(int ch) |
| { |
| int rpm_actual; |
| |
| rpm_actual = fan_get_rpm_actual(ch); |
| |
| if (((fan_get_duty(ch)) && (rpm_actual < fans[ch].rpm_min)) |
| || ((!fan_get_duty(ch)) && (rpm_actual))) |
| return FAN_STATUS_FRUSTRATED; |
| else if ((rpm_actual == 0) && (!fan_get_duty(ch))) |
| return FAN_STATUS_STOPPED; |
| else |
| return FAN_STATUS_LOCKED; |
| } |
| |
| /** |
| * Check fan is stall condition. |
| * |
| * @param ch operation channel |
| * @return non-zero if fan is enabled but stalled |
| */ |
| int fan_is_stalled(int ch) |
| { |
| int rpm_actual; |
| |
| rpm_actual = fan_get_rpm_actual(ch); |
| |
| /* Check for normal condition, others are stall condition */ |
| if ((!fan_get_enabled(ch)) || ((fan_get_duty(ch)) |
| && (rpm_actual >= fans[ch].rpm_min)) |
| || ((!fan_get_duty(ch)) && (!rpm_actual))) |
| return 0; |
| |
| return 1; |
| } |
| |
| /** |
| * Fan channel setup. |
| * |
| * @param ch operation channel |
| * @param flags input flags |
| * @return none |
| */ |
| void fan_channel_setup(int ch, unsigned int flags) |
| { |
| fan_config(ch, (flags & FAN_USE_RPM_MODE)); |
| } |
| |
| /** |
| * Fan initial. |
| * |
| * @param none |
| * @return none |
| */ |
| static void fan_init(void) |
| { |
| #ifdef CONFIG_PWM_DSLEEP |
| /* Enable the fan module and delay a few clocks */ |
| clock_enable_peripheral(CGC_OFFSET_FAN, CGC_FAN_MASK, CGC_MODE_ALL); |
| #else |
| /* Enable the fan module and delay a few clocks */ |
| clock_enable_peripheral(CGC_OFFSET_FAN, CGC_FAN_MASK, |
| CGC_MODE_RUN | CGC_MODE_SLEEP); |
| #endif |
| } |
| DECLARE_HOOK(HOOK_INIT, fan_init, HOOK_PRIO_INIT_PWM); |