479 lines
10 KiB
C
479 lines
10 KiB
C
/* Copyright 2015 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.
|
|
*/
|
|
|
|
/* Fan control module. */
|
|
|
|
#include "clock.h"
|
|
#include "fan.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "hwtimer_chip.h"
|
|
#include "math_util.h"
|
|
#include "pwm.h"
|
|
#include "pwm_chip.h"
|
|
#include "registers.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "util.h"
|
|
|
|
#define TACH_EC_FREQ 8000000
|
|
#define FAN_CTRL_BASED_MS 10
|
|
#define FAN_CTRL_INTERVAL_MAX_MS 60
|
|
|
|
/* The sampling rate (fs) is FreqEC / 128 */
|
|
#define TACH_DATA_VALID_TIMEOUT_MS (0xFFFF * 128 / (TACH_EC_FREQ / 1000))
|
|
|
|
/*
|
|
* Fan Speed (RPM) = 60 / (1/fs sec * {FnTMRR, FnTLRR} * P)
|
|
* n denotes 1 or 2.
|
|
* P denotes the numbers of square pulses per revolution.
|
|
* And {FnTMRR, FnTLRR} = 0000h denotes Fan Speed is zero.
|
|
* The sampling rate (fs) is FreqEC / 128.
|
|
*/
|
|
/* pulse, the numbers of square pulses per revolution. */
|
|
#define TACH0_TO_RPM(pulse, raw) (60 * TACH_EC_FREQ / 128 / pulse / raw)
|
|
#define TACH1_TO_RPM(pulse, raw) (raw * 120 / (pulse * 2))
|
|
|
|
enum fan_output_s {
|
|
FAN_DUTY_I = 0x01,
|
|
FAN_DUTY_R = 0x02,
|
|
FAN_DUTY_OV = 0x03,
|
|
FAN_DUTY_DONE = 0x04,
|
|
};
|
|
|
|
struct fan_info {
|
|
unsigned int flags;
|
|
int fan_mode;
|
|
int fan_p;
|
|
int rpm_target;
|
|
int rpm_actual;
|
|
int tach_valid_ms;
|
|
int rpm_re;
|
|
int fan_ms;
|
|
int fan_ms_idx;
|
|
int startup_duty;
|
|
enum fan_status fan_sts;
|
|
int enabled;
|
|
};
|
|
static struct fan_info fan_info_data[TACH_CH_COUNT];
|
|
|
|
static enum tach_ch_sel tach_bind(int ch)
|
|
{
|
|
return fan_tach[pwm_channels[ch].channel].ch_tach;
|
|
}
|
|
|
|
static void fan_set_interval(int ch)
|
|
{
|
|
int diff, fan_ms;
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
diff = ABS(fan_info_data[tach_ch].rpm_target -
|
|
fan_info_data[tach_ch].rpm_actual) / 100;
|
|
|
|
fan_ms = FAN_CTRL_INTERVAL_MAX_MS;
|
|
|
|
fan_ms -= diff;
|
|
if (fan_ms < FAN_CTRL_BASED_MS)
|
|
fan_ms = FAN_CTRL_BASED_MS;
|
|
|
|
fan_info_data[tach_ch].fan_ms = fan_ms;
|
|
}
|
|
|
|
static void fan_init_start(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
fan_set_duty(ch, fan_info_data[tach_ch].startup_duty);
|
|
}
|
|
|
|
static int fan_all_disabled(void)
|
|
{
|
|
int fan, all_disabled = 0;
|
|
|
|
for (fan = 0; fan < fan_get_count(); fan++) {
|
|
if (!fan_get_enabled(FAN_CH(fan)))
|
|
all_disabled++;
|
|
}
|
|
|
|
if (all_disabled >= fan_get_count())
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void fan_set_enabled(int ch, int enabled)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
/* enable */
|
|
if (enabled) {
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_CHANGING;
|
|
|
|
disable_sleep(SLEEP_MASK_FAN);
|
|
/* enable timer interrupt for fan control */
|
|
ext_timer_start(FAN_CTRL_EXT_TIMER, 1);
|
|
/* disable */
|
|
} else {
|
|
fan_set_duty(ch, 0);
|
|
|
|
if (tach_ch < TACH_CH_COUNT) {
|
|
fan_info_data[tach_ch].rpm_actual = 0;
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_STOPPED;
|
|
}
|
|
}
|
|
|
|
/* on/off */
|
|
if (tach_ch < TACH_CH_COUNT) {
|
|
fan_info_data[tach_ch].enabled = enabled;
|
|
fan_info_data[tach_ch].tach_valid_ms = 0;
|
|
}
|
|
|
|
pwm_enable(ch, enabled);
|
|
|
|
if (!enabled) {
|
|
/* disable timer interrupt if all fan off. */
|
|
if (fan_all_disabled()) {
|
|
ext_timer_stop(FAN_CTRL_EXT_TIMER, 1);
|
|
enable_sleep(SLEEP_MASK_FAN);
|
|
}
|
|
}
|
|
}
|
|
|
|
int fan_get_enabled(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
return pwm_get_enabled(ch) && fan_info_data[tach_ch].enabled;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void fan_set_duty(int ch, int percent)
|
|
{
|
|
pwm_set_duty(ch, percent);
|
|
}
|
|
|
|
int fan_get_duty(int ch)
|
|
{
|
|
return pwm_get_duty(ch);
|
|
}
|
|
|
|
int fan_get_rpm_mode(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
return fan_info_data[tach_ch].fan_mode;
|
|
else
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
void fan_set_rpm_mode(int ch, int rpm_mode)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
fan_info_data[tach_ch].fan_mode = rpm_mode;
|
|
}
|
|
|
|
int fan_get_rpm_actual(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
return fan_info_data[tach_ch].rpm_actual;
|
|
else
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
int fan_get_rpm_target(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
return fan_info_data[tach_ch].rpm_target;
|
|
else
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
test_mockable void fan_set_rpm_target(int ch, int rpm)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
fan_info_data[tach_ch].rpm_target = rpm;
|
|
}
|
|
|
|
enum fan_status fan_get_status(int ch)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
return fan_info_data[tach_ch].fan_sts;
|
|
else
|
|
return FAN_STATUS_STOPPED;
|
|
}
|
|
|
|
/**
|
|
* Return non-zero if fan is enabled but stalled.
|
|
*/
|
|
int fan_is_stalled(int ch)
|
|
{
|
|
/* Must be enabled with non-zero target to stall */
|
|
if (!fan_get_enabled(ch) ||
|
|
fan_get_rpm_target(ch) == 0 ||
|
|
!fan_get_duty(ch))
|
|
return 0;
|
|
|
|
/* Check for stall condition */
|
|
return fan_get_status(ch) == FAN_STATUS_STOPPED;
|
|
}
|
|
|
|
void fan_channel_setup(int ch, unsigned int flags)
|
|
{
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
if (tach_ch < TACH_CH_COUNT)
|
|
fan_info_data[tach_ch].flags = flags;
|
|
}
|
|
|
|
static void fan_ctrl(int ch)
|
|
{
|
|
int status = -1, adjust = 0;
|
|
int rpm_actual, rpm_target, rpm_re, duty;
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
fan_info_data[tach_ch].fan_ms_idx += FAN_CTRL_BASED_MS;
|
|
if (fan_info_data[tach_ch].fan_ms_idx >
|
|
fan_info_data[tach_ch].fan_ms) {
|
|
fan_info_data[tach_ch].fan_ms_idx = 0x00;
|
|
adjust = 1;
|
|
}
|
|
|
|
if (adjust) {
|
|
/* get current pwm output duty */
|
|
duty = fan_get_duty(ch);
|
|
|
|
/* rpm mode */
|
|
if (fan_info_data[tach_ch].fan_mode) {
|
|
rpm_actual = fan_info_data[tach_ch].rpm_actual;
|
|
rpm_target = fan_info_data[tach_ch].rpm_target;
|
|
rpm_re = fan_info_data[tach_ch].rpm_re;
|
|
|
|
if (rpm_actual < (rpm_target - rpm_re)) {
|
|
if (duty == 100) {
|
|
status = FAN_DUTY_OV;
|
|
} else {
|
|
if (duty == 0)
|
|
fan_init_start(ch);
|
|
|
|
pwm_duty_inc(ch);
|
|
status = FAN_DUTY_I;
|
|
}
|
|
} else if (rpm_actual > (rpm_target + rpm_re)) {
|
|
if (duty == 0) {
|
|
status = FAN_DUTY_OV;
|
|
} else {
|
|
pwm_duty_reduce(ch);
|
|
status = FAN_DUTY_R;
|
|
}
|
|
} else {
|
|
status = FAN_DUTY_DONE;
|
|
}
|
|
} else {
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_LOCKED;
|
|
}
|
|
|
|
if (status == FAN_DUTY_DONE) {
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_LOCKED;
|
|
} else if ((status == FAN_DUTY_I) || (status == FAN_DUTY_R)) {
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_CHANGING;
|
|
} else if (status == FAN_DUTY_OV) {
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_FRUSTRATED;
|
|
|
|
if (!fan_info_data[tach_ch].rpm_actual && duty)
|
|
fan_info_data[tach_ch].fan_sts =
|
|
FAN_STATUS_STOPPED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int tach_ch_valid(enum tach_ch_sel tach_ch)
|
|
{
|
|
int valid = 0;
|
|
|
|
switch (tach_ch) {
|
|
case TACH_CH_TACH0A:
|
|
if ((IT83XX_PWM_TSWCTRL & 0x0C) == 0x08)
|
|
valid = 1;
|
|
break;
|
|
case TACH_CH_TACH1A:
|
|
if ((IT83XX_PWM_TSWCTRL & 0x03) == 0x02)
|
|
valid = 1;
|
|
break;
|
|
case TACH_CH_TACH0B:
|
|
if ((IT83XX_PWM_TSWCTRL & 0x0C) == 0x0C)
|
|
valid = 1;
|
|
break;
|
|
case TACH_CH_TACH1B:
|
|
if ((IT83XX_PWM_TSWCTRL & 0x03) == 0x03)
|
|
valid = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
static int get_tach0_rpm(int fan_p)
|
|
{
|
|
uint16_t rpm;
|
|
|
|
/* TACH0A / TACH0B data is valid */
|
|
if (IT83XX_PWM_TSWCTRL & 0x08) {
|
|
rpm = (IT83XX_PWM_F1TMRR << 8) | IT83XX_PWM_F1TLRR;
|
|
|
|
if (rpm)
|
|
rpm = TACH0_TO_RPM(fan_p, rpm);
|
|
|
|
/* W/C */
|
|
IT83XX_PWM_TSWCTRL |= 0x08;
|
|
return rpm;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int get_tach1_rpm(int fan_p)
|
|
{
|
|
uint16_t rpm;
|
|
|
|
/* TACH1A / TACH1B data is valid */
|
|
if (IT83XX_PWM_TSWCTRL & 0x02) {
|
|
rpm = (IT83XX_PWM_F2TMRR << 8) | IT83XX_PWM_F2TLRR;
|
|
|
|
if (rpm)
|
|
rpm = TACH1_TO_RPM(fan_p, rpm);
|
|
|
|
/* W/C */
|
|
IT83XX_PWM_TSWCTRL |= 0x02;
|
|
return rpm;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void proc_tach(int ch)
|
|
{
|
|
int t_rpm;
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
tach_ch = tach_bind(ch);
|
|
|
|
/* tachometer data valid */
|
|
if (tach_ch_valid(tach_ch)) {
|
|
if ((tach_ch == TACH_CH_TACH0A) || (tach_ch == TACH_CH_TACH0B))
|
|
t_rpm = get_tach0_rpm(fan_info_data[tach_ch].fan_p);
|
|
else
|
|
t_rpm = get_tach1_rpm(fan_info_data[tach_ch].fan_p);
|
|
|
|
fan_info_data[tach_ch].rpm_actual = t_rpm;
|
|
fan_set_interval(ch);
|
|
fan_info_data[tach_ch].tach_valid_ms = 0;
|
|
} else {
|
|
fan_info_data[tach_ch].tach_valid_ms += FAN_CTRL_BASED_MS;
|
|
if (fan_info_data[tach_ch].tach_valid_ms >
|
|
TACH_DATA_VALID_TIMEOUT_MS)
|
|
fan_info_data[tach_ch].rpm_actual = 0;
|
|
}
|
|
}
|
|
|
|
void fan_ext_timer_interrupt(void)
|
|
{
|
|
int fan;
|
|
|
|
task_clear_pending_irq(et_ctrl_regs[FAN_CTRL_EXT_TIMER].irq);
|
|
|
|
for (fan = 0; fan < fan_get_count(); fan++) {
|
|
if (fan_get_enabled(FAN_CH(fan))) {
|
|
proc_tach(FAN_CH(fan));
|
|
fan_ctrl(FAN_CH(fan));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fan_init(void)
|
|
{
|
|
int ch, rpm_re, fan_p, s_duty;
|
|
enum tach_ch_sel tach_ch;
|
|
|
|
for (ch = 0; ch < fan_get_count(); ch++) {
|
|
|
|
rpm_re = fan_tach[pwm_channels[FAN_CH(ch)].channel].rpm_re;
|
|
fan_p = fan_tach[pwm_channels[FAN_CH(ch)].channel].fan_p;
|
|
s_duty = fan_tach[pwm_channels[FAN_CH(ch)].channel].s_duty;
|
|
tach_ch = tach_bind(FAN_CH(ch));
|
|
|
|
if (tach_ch < TACH_CH_COUNT) {
|
|
|
|
if (tach_ch == TACH_CH_TACH0B) {
|
|
/* GPJ2 will select TACH0B as its alt. */
|
|
IT83XX_GPIO_GRC5 |= 0x01;
|
|
/* bit2, to select TACH0B */
|
|
IT83XX_PWM_TSWCTRL |= 0x04;
|
|
} else if (tach_ch == TACH_CH_TACH1B) {
|
|
/* GPJ3 will select TACH1B as its alt. */
|
|
IT83XX_GPIO_GRC5 |= 0x02;
|
|
/* bit0, to select TACH1B */
|
|
IT83XX_PWM_TSWCTRL |= 0x01;
|
|
}
|
|
|
|
fan_info_data[tach_ch].flags = 0;
|
|
fan_info_data[tach_ch].fan_mode = 0;
|
|
fan_info_data[tach_ch].rpm_target = 0;
|
|
fan_info_data[tach_ch].rpm_actual = 0;
|
|
fan_info_data[tach_ch].tach_valid_ms = 0;
|
|
fan_info_data[tach_ch].fan_ms_idx = 0;
|
|
fan_info_data[tach_ch].enabled = 0;
|
|
fan_info_data[tach_ch].fan_p = fan_p;
|
|
fan_info_data[tach_ch].rpm_re = rpm_re;
|
|
fan_info_data[tach_ch].fan_ms = FAN_CTRL_BASED_MS;
|
|
fan_info_data[tach_ch].fan_sts = FAN_STATUS_STOPPED;
|
|
fan_info_data[tach_ch].startup_duty = s_duty;
|
|
}
|
|
}
|
|
|
|
/* init external timer for fan control */
|
|
ext_timer_ms(FAN_CTRL_EXT_TIMER, EXT_PSR_32P768K_HZ, 0, 0,
|
|
FAN_CTRL_BASED_MS, 1, 0);
|
|
}
|
|
DECLARE_HOOK(HOOK_INIT, fan_init, HOOK_PRIO_INIT_FAN);
|