blob: 84cbd0e6f3293bf33fc7a4185495b6424e203c55 [file] [log] [blame]
/* 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);