| /* 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" |
| #include "hooks.h" |
| #include "system.h" |
| #include "math_util.h" |
| |
| #if !(DEBUG_FAN) |
| #define CPRINTS(...) |
| #else |
| #define CPRINTS(format, args...) cprints(CC_PWM, format, ## args) |
| #endif |
| |
| /* 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 |
| }; |
| |
| /* Tacho measurement state */ |
| enum tacho_measure_state { |
| /* Tacho normal state */ |
| TACHO_NORMAL = 0, |
| /* Tacho underflow state */ |
| TACHO_UNDERFLOW |
| }; |
| |
| /* Fan mode */ |
| enum tacho_fan_mode { |
| /* FAN rpm mode */ |
| TACHO_FAN_RPM = 0, |
| /* FAN duty mode */ |
| TACHO_FAN_DUTY = 0, |
| }; |
| |
| /* Fan status data structure */ |
| struct fan_status_t { |
| /* Current state of the measurement */ |
| enum tacho_measure_state cur_state; |
| /* Fan mode */ |
| enum tacho_fan_mode fan_mode; |
| /* MFT sampling freq*/ |
| uint32_t mft_freq; |
| /* Actual rpm */ |
| int rpm_actual; |
| /* Target rpm */ |
| int rpm_target; |
| /* Automatic fan status */ |
| enum fan_status auto_status; |
| }; |
| |
| /* Global variables */ |
| static volatile struct fan_status_t fan_status[FAN_CH_COUNT]; |
| |
| /* Fan encoder spec. */ |
| #define RPM_SCALE 1 /* Fan RPM is multiplier of actual RPM */ |
| #define RPM_EDGES 1 /* Fan number of edges - 1 */ |
| #define POLES 2 /* Pole number of fan */ |
| /* Rounds per second */ |
| #define ROUNDS ((60 / POLES) * RPM_EDGES * RPM_SCALE) |
| /* |
| * 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 TACH_TO_RPM(ch, tach) \ |
| ((fan_status[ch].mft_freq * ROUNDS) / MAX((tach), 1)) |
| |
| /* MFT TCNT default count */ |
| #define TACHO_MAX_CNT ((1<<16)-1) |
| |
| /* Smart fan control settings */ |
| #define RPM_MARGIN_PERCENT 3 |
| #define RPM_MARGIN_THRES(rpm_target) (((rpm_target)*RPM_MARGIN_PERCENT)/100) |
| /** |
| * MFT get fan rpm value |
| * |
| * @param ch operation channel |
| * @return actual rpm |
| */ |
| static int mft_fan_rpm(int ch) |
| { |
| volatile struct fan_status_t *p_status = fan_status + ch; |
| int mdl = mft_channels[ch].module; |
| int tacho; |
| |
| /* Check whether MFT underflow flag is occurred */ |
| if (IS_BIT_SET(NPCX_TECTRL(mdl), NPCX_TECTRL_TCPND)) { |
| /* Clear pending flags */ |
| SET_BIT(NPCX_TECLR(mdl), NPCX_TECLR_TCCLR); |
| |
| /* Need to avoid underflow state happen */ |
| p_status->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. |
| */ |
| p_status->cur_state = TACHO_UNDERFLOW; |
| p_status->auto_status = FAN_STATUS_STOPPED; |
| CPRINTS("Tacho is underflow !"); |
| |
| return 0; |
| } |
| |
| p_status->cur_state = TACHO_NORMAL; |
| /* |
| * Start of the last tacho cycle is detected - |
| * calculated tacho cycle duration |
| */ |
| tacho = TACHO_MAX_CNT - NPCX_TCRA(mdl); |
| /* Transfer tacho to actual rpm */ |
| return (tacho > 0) ? (TACH_TO_RPM(ch, tacho)) : 0; |
| } |
| |
| /** |
| * Set fan prescaler based on apb1 clock |
| * |
| * @param none |
| * @return none |
| * @notes changed when initial or HOOK_FREQ_CHANGE command |
| */ |
| void mft_set_apb1_prescaler(int ch) |
| { |
| int mdl = mft_channels[ch].module; |
| uint16_t prescaler_divider = 0; |
| |
| /* Set clock prescaler divider to MFT module*/ |
| prescaler_divider = (uint16_t)(clock_get_apb1_freq() |
| / fan_status[ch].mft_freq); |
| if (prescaler_divider >= 1) |
| prescaler_divider = prescaler_divider - 1; |
| if (prescaler_divider > 0xFF) |
| prescaler_divider = 0xFF; |
| |
| NPCX_TPRSC(mdl) = (uint8_t) prescaler_divider; |
| } |
| |
| /** |
| * 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 mdl = mft_channels[ch].module; |
| int pwm_id = mft_channels[ch].pwm_id; |
| enum npcx_mft_clk_src clk_src = mft_channels[ch].clk_src; |
| |
| volatile struct fan_status_t *p_status = fan_status + ch; |
| |
| /* Setup pwm with fan spec. */ |
| pwm_config(pwm_id); |
| |
| /* Need to initialize MFT or not */ |
| if (enable_mft_read_rpm) { |
| |
| /* Initialize tacho sampling rate */ |
| if (clk_src == TCKC_LFCLK) |
| p_status->mft_freq = INT_32K_CLOCK; |
| else if (clk_src == TCKC_PRESCALE_APB1_CLK) |
| p_status->mft_freq = clock_get_apb1_freq(); |
| else |
| p_status->mft_freq = 0; |
| |
| /* Set mode 5 to MFT module */ |
| SET_FIELD(NPCX_TMCTRL(mdl), NPCX_TMCTRL_MDSEL_FIELD, |
| NPCX_MFT_MDSEL_5); |
| |
| /* Set MFT operation frequency */ |
| if (clk_src == TCKC_PRESCALE_APB1_CLK) |
| mft_set_apb1_prescaler(ch); |
| |
| /* Set the low power mode or not. */ |
| UPDATE_BIT(NPCX_TCKC(mdl), NPCX_TCKC_LOW_PWR, |
| clk_src == TCKC_LFCLK); |
| |
| /* Set the default count-down timer. */ |
| NPCX_TCNT1(mdl) = TACHO_MAX_CNT; |
| NPCX_TCRA(mdl) = TACHO_MAX_CNT; |
| |
| /* Set the edge polarity to rising. */ |
| SET_BIT(NPCX_TMCTRL(mdl), NPCX_TMCTRL_TAEDG); |
| /* Enable capture TCNT1 into TCRA and preset TCNT1. */ |
| SET_BIT(NPCX_TMCTRL(mdl), NPCX_TMCTRL_TAEN); |
| /* Enable input debounce logic into TA. */ |
| SET_BIT(NPCX_TCFG(mdl), NPCX_TCFG_TADBEN); |
| |
| /* Set the clock source type and start capturing */ |
| SET_FIELD(NPCX_TCKC(mdl), NPCX_TCKC_C1CSEL_FIELD, clk_src); |
| } |
| |
| /* Set default fan states */ |
| p_status->cur_state = TACHO_NORMAL; |
| p_status->fan_mode = TACHO_FAN_DUTY; |
| p_status->auto_status = FAN_STATUS_STOPPED; |
| } |
| |
| /** |
| * Smart fan control function. |
| * |
| * @param ch operation channel |
| * @param rpm_actual actual operation rpm value |
| * @param rpm_target target operation rpm value |
| * @return current fan control status |
| */ |
| enum fan_status fan_smart_control(int ch, int rpm_actual, int rpm_target) |
| { |
| static int rpm_pre; |
| int duty, duty_diff, rpm_diff; |
| |
| /* wait rpm is stable */ |
| if (ABS(rpm_actual - rpm_pre) > RPM_MARGIN_THRES(rpm_target)/2) { |
| rpm_pre = rpm_actual; |
| return FAN_STATUS_CHANGING; |
| } |
| |
| /* Record previous rpm */ |
| rpm_pre = rpm_actual; |
| |
| /* Find suitable duty step */ |
| rpm_diff = rpm_target - rpm_actual; |
| if (ABS(rpm_diff) >= 2000) |
| duty_diff = 20; |
| else if (ABS(rpm_diff) >= 1000) |
| duty_diff = 10; |
| else if (ABS(rpm_diff) >= 500) |
| duty_diff = 5; |
| else if (ABS(rpm_diff) >= 250) |
| duty_diff = 3; |
| else |
| duty_diff = 1; |
| |
| /* Adjust PWM duty */ |
| duty = fan_get_duty(ch); |
| if (duty == 0 && rpm_target == 0) |
| return FAN_STATUS_STOPPED; |
| |
| /* Increase PWM duty */ |
| if (rpm_diff > RPM_MARGIN_THRES(rpm_target)) { |
| if (duty == 100) |
| return FAN_STATUS_FRUSTRATED; |
| |
| fan_set_duty(ch, duty + duty_diff); |
| CPRINTS("duty=%d, threshold %d, rpm target %d, actual %d", duty, |
| RPM_MARGIN_THRES(rpm_target), rpm_target, rpm_actual); |
| return FAN_STATUS_CHANGING; |
| /* Decrease PWM duty */ |
| } else if (rpm_diff < -RPM_MARGIN_THRES(rpm_target)) { |
| if (duty == 1 && rpm_target != 0) |
| return FAN_STATUS_FRUSTRATED; |
| |
| fan_set_duty(ch, duty - duty_diff); |
| CPRINTS("duty=%d, threshold %d, rpm target %d, actual %d", duty, |
| RPM_MARGIN_THRES(rpm_target), rpm_target, rpm_actual); |
| return FAN_STATUS_CHANGING; |
| } |
| |
| return FAN_STATUS_LOCKED; |
| } |
| |
| /** |
| * Tick function for fan control. |
| * |
| * @return none |
| */ |
| void fan_tick_func(void) |
| { |
| int ch; |
| |
| for (ch = 0; ch < FAN_CH_COUNT ; ch++) { |
| volatile struct fan_status_t *p_status = fan_status + ch; |
| /* Make sure rpm mode is enabled */ |
| if (p_status->fan_mode != TACHO_FAN_RPM) { |
| p_status->auto_status = FAN_STATUS_STOPPED; |
| return; |
| } |
| /* Get actual rpm */ |
| p_status->rpm_actual = mft_fan_rpm(ch); |
| /* Do smart fan stuff */ |
| p_status->auto_status = fan_smart_control(ch, |
| p_status->rpm_actual, p_status->rpm_target); |
| } |
| } |
| DECLARE_HOOK(HOOK_TICK, fan_tick_func, HOOK_PRIO_DEFAULT); |
| |
| /*****************************************************************************/ |
| /* IC specific low-level driver */ |
| |
| /** |
| * 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_id = mft_channels[ch].pwm_id; |
| |
| /* duty is zero */ |
| if (!percent) { |
| fan_status[ch].auto_status = FAN_STATUS_STOPPED; |
| enable_sleep(SLEEP_MASK_FAN); |
| } else |
| disable_sleep(SLEEP_MASK_FAN); |
| |
| /* Set the duty cycle of PWM */ |
| pwm_set_duty(pwm_id, percent); |
| } |
| /* ensure that only one fan used since ec enables sleep bit if duty is zero */ |
| BUILD_ASSERT(CONFIG_FANS <= 1); |
| |
| |
| /** |
| * Get fan duty cycle. |
| * |
| * @param ch operation channel |
| * @return duty cycle |
| */ |
| int fan_get_duty(int ch) |
| { |
| int pwm_id = mft_channels[ch].pwm_id; |
| |
| /* Return percent */ |
| return pwm_get_duty(pwm_id); |
| } |
| /** |
| * Check fan is rpm operation mode. |
| * |
| * @param ch operation channel |
| * @return rpm operation mode or not |
| */ |
| int fan_get_rpm_mode(int ch) |
| { |
| return fan_status[ch].fan_mode == TACHO_FAN_RPM ? 1 : 0; |
| } |
| |
| /** |
| * Set fan to rpm operation mode. |
| * |
| * @param ch operation channel |
| * @param rpm_mode rpm operation mode flag |
| * @return none |
| */ |
| void fan_set_rpm_mode(int ch, int rpm_mode) |
| { |
| if (rpm_mode) |
| fan_status[ch].fan_mode = TACHO_FAN_RPM; |
| else |
| fan_status[ch].fan_mode = TACHO_FAN_DUTY; |
| } |
| |
| /** |
| * Get fan actual operation rpm. |
| * |
| * @param ch operation channel |
| * @return actual operation rpm value |
| */ |
| int fan_get_rpm_actual(int ch) |
| { |
| /* Check PWM is enabled first */ |
| if (fan_get_duty(ch) == 0) |
| return 0; |
| |
| CPRINTS("fan %d: get actual rpm = %d", ch, fan_status[ch].rpm_actual); |
| return fan_status[ch].rpm_actual; |
| } |
| |
| /** |
| * Check fan enabled. |
| * |
| * @param ch operation channel |
| * @return enabled or not |
| */ |
| int fan_get_enabled(int ch) |
| { |
| int pwm_id = mft_channels[ch].pwm_id; |
| |
| return pwm_get_enabled(pwm_id); |
| } |
| /** |
| * Set fan enabled. |
| * |
| * @param ch operation channel |
| * @param enabled enabled flag |
| * @return none |
| */ |
| void fan_set_enabled(int ch, int enabled) |
| { |
| int pwm_id = mft_channels[ch].pwm_id; |
| |
| if (!enabled) |
| fan_status[ch].auto_status = FAN_STATUS_STOPPED; |
| pwm_enable(pwm_id, enabled); |
| } |
| |
| /** |
| * Get fan setting rpm. |
| * |
| * @param ch operation channel |
| * @return setting rpm value |
| */ |
| int fan_get_rpm_target(int ch) |
| { |
| return fan_status[ch].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) |
| { |
| /* If rpm = 0, disable PWM */ |
| if (rpm == 0) |
| fan_set_duty(ch, 0); |
| else if (rpm > fans[ch].rpm_max) |
| rpm = fans[ch].rpm_max; |
| else if (rpm < fans[ch].rpm_min) |
| rpm = fans[ch].rpm_min; |
| |
| /* Set target rpm */ |
| fan_status[ch].rpm_target = rpm; |
| CPRINTS("fan %d: set target rpm = %d", ch, fan_status[ch].rpm_target); |
| } |
| |
| /** |
| * Check fan operation status. |
| * |
| * @param ch operation channel |
| * @return fan_status fan operation status |
| */ |
| enum fan_status fan_get_status(int ch) |
| { |
| return fan_status[ch].auto_status; |
| } |
| |
| /** |
| * Check fan is stall condition. |
| * |
| * @param ch operation channel |
| * @return non-zero if fan is enabled but stalled |
| */ |
| int fan_is_stalled(int ch) |
| { |
| /* if fan is enabled but we didn't detect any tacho */ |
| if (fan_get_enabled(ch) && fan_status[ch].cur_state == TACHO_UNDERFLOW) |
| return 1; |
| else |
| return 0; |
| } |
| |
| /** |
| * 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) |
| { |
| /* Enable the fan module and delay a few clocks */ |
| clock_enable_peripheral(CGC_OFFSET_FAN, CGC_FAN_MASK, CGC_MODE_ALL); |
| } |
| DECLARE_HOOK(HOOK_INIT, fan_init, HOOK_PRIO_INIT_FAN); |