/* 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)<= 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)< 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);