mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-01 21:02:27 +00:00
This CL simplified TACH_TO_RPM formula and abstracted two definitions (PULSES_ROUND and RPM_DEVIATION) for the pwm-type fan used on boards. The developers can modify them in board-level driver if fan doesn't meet the default spec. In this CL, it also fixed: 1. Declare rpm_pre as array if FAN_CH_COUNT > 1. 2. Add checking for the value of next duty of pwm. 3. Use TAPND pending bit to make sure TCRA is valid. BRANCH=none BUG=none TEST=test fan used in kahlee and Sunon fan with fanset command on npcx_evb and use faninfo for verifying. Measure the actual rpm by scope. Change-Id: Ieb07482eb359912286414ccb9738341d98ea99e4 Signed-off-by: Mulin Chao <mlchao@nuvoton.com> Reviewed-on: https://chromium-review.googlesource.com/472289 Reviewed-by: Randall Spangler <rspangler@chromium.org>
552 lines
12 KiB
C
552 lines
12 KiB
C
/* 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];
|
|
static int rpm_pre[FAN_CH_COUNT];
|
|
|
|
/*
|
|
* Fan specifications. If they (PULSES_ROUND and RPM_DEVIATION) cannot meet
|
|
* the followings, please replace them with correct one in board-level driver.
|
|
*/
|
|
|
|
/* Pulses per round */
|
|
#ifndef PULSES_ROUND
|
|
#define PULSES_ROUND 2 /* 4-phases pwm-type fan. (2-phases should be 1) */
|
|
#endif
|
|
|
|
/* Rpm deviation (Unit:percent) */
|
|
#ifndef RPM_DEVIATION
|
|
#define RPM_DEVIATION 7
|
|
#endif
|
|
|
|
/*
|
|
* RPM = 60 * f / (n * TACH)
|
|
* n = Pulses per round
|
|
* f = Tachometer (MFT) operation freq
|
|
* TACH = Counts of tachometer
|
|
*/
|
|
#define TACH_TO_RPM(ch, tach) \
|
|
((fan_status[ch].mft_freq * 60 / PULSES_ROUND) / MAX((tach), 1))
|
|
|
|
/* MFT TCNT default count */
|
|
#define TACHO_MAX_CNT ((1 << 16) - 1)
|
|
|
|
/* Margin of target rpm */
|
|
#define RPM_MARGIN(rpm_target) (((rpm_target) * RPM_DEVIATION) / 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);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/* Check whether MFT capture flag is set, else return previous rpm */
|
|
if (IS_BIT_SET(NPCX_TECTRL(mdl), NPCX_TECTRL_TAPND))
|
|
/* Clear pending flags */
|
|
SET_BIT(NPCX_TECLR(mdl), NPCX_TECLR_TACLR);
|
|
else
|
|
return p_status->rpm_actual;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Check all fans are stopped
|
|
*
|
|
* @return 1: all fans are stopped. 0: else.
|
|
*/
|
|
static int fan_all_disabled(void)
|
|
{
|
|
int ch;
|
|
|
|
for (ch = 0; ch < CONFIG_FANS; ch++)
|
|
if (fan_status[ch].auto_status != FAN_STATUS_STOPPED)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Adjust fan duty by difference between target and actual rpm
|
|
*
|
|
* @param ch operation channel
|
|
* @param rpm_diff difference between target and actual rpm
|
|
* @param duty current fan duty
|
|
*/
|
|
static void fan_adjust_duty(int ch, int rpm_diff, int duty)
|
|
{
|
|
int duty_step = 0;
|
|
|
|
/* Find suitable duty step */
|
|
if (ABS(rpm_diff) >= 2000)
|
|
duty_step = 20;
|
|
else if (ABS(rpm_diff) >= 1000)
|
|
duty_step = 10;
|
|
else if (ABS(rpm_diff) >= 500)
|
|
duty_step = 5;
|
|
else if (ABS(rpm_diff) >= 250)
|
|
duty_step = 3;
|
|
else
|
|
duty_step = 1;
|
|
|
|
/* Adjust fan duty step by step */
|
|
if (rpm_diff > 0)
|
|
duty = MIN(duty + duty_step, 100);
|
|
else
|
|
duty = MAX(duty - duty_step, 1);
|
|
|
|
fan_set_duty(ch, duty);
|
|
|
|
CPRINTS("fan%d: duty %d, rpm_diff %d", ch, duty, rpm_diff);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
int duty, rpm_diff;
|
|
|
|
/* wait rpm is stable */
|
|
if (ABS(rpm_actual - rpm_pre[ch]) > RPM_MARGIN(rpm_actual)) {
|
|
rpm_pre[ch] = rpm_actual;
|
|
return FAN_STATUS_CHANGING;
|
|
}
|
|
|
|
/* Record previous rpm */
|
|
rpm_pre[ch] = rpm_actual;
|
|
|
|
/* Adjust PWM duty */
|
|
rpm_diff = rpm_target - rpm_actual;
|
|
duty = fan_get_duty(ch);
|
|
if (duty == 0 && rpm_target == 0)
|
|
return FAN_STATUS_STOPPED;
|
|
|
|
/* Increase PWM duty */
|
|
if (rpm_diff > RPM_MARGIN(rpm_target)) {
|
|
if (duty == 100)
|
|
return FAN_STATUS_FRUSTRATED;
|
|
|
|
fan_adjust_duty(ch, rpm_diff, duty);
|
|
return FAN_STATUS_CHANGING;
|
|
/* Decrease PWM duty */
|
|
} else if (rpm_diff < -RPM_MARGIN(rpm_target)) {
|
|
if (duty == 1 && rpm_target != 0)
|
|
return FAN_STATUS_FRUSTRATED;
|
|
|
|
fan_adjust_duty(ch, rpm_diff, duty);
|
|
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;
|
|
if (fan_all_disabled())
|
|
enable_sleep(SLEEP_MASK_FAN);
|
|
} else
|
|
disable_sleep(SLEEP_MASK_FAN);
|
|
|
|
/* Set the duty cycle of PWM */
|
|
pwm_set_duty(pwm_id, percent);
|
|
}
|
|
|
|
/**
|
|
* 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);
|