summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorDino Li <dino.li@ite.com.tw>2015-06-24 11:43:29 +0800
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-06-25 05:32:30 +0000
commitd5c43a880c7dadf76df152422f8b0f5e68455f61 (patch)
treec34a9357877ac5ae4b625b3a8c96824d68d5697f /chip
parent52ee6aa1312b71472196015a4bf70f384f9f27d4 (diff)
downloadchrome-ec-d5c43a880c7dadf76df152422f8b0f5e68455f61.tar.gz
it8380dev: add fan control module
1. pwm, add frequency select function for pwm channels. 2. timer, add external timer 3~8 apis. 3. add fan control module for emulation board. Signed-off-by: Dino Li <dino.li@ite.com.tw> BRANCH=none BUG=none TEST=console command "faninfo, fanset, fanduty, and fanauto" fanset 3333 Setting fan 0 rpm target to 3333 faninfo Actual: 3390 rpm Target: 3333 rpm Duty: 35% Status: 1 (changing) Mode: rpm Auto: no Enable: yes faninfo Actual: 3301 rpm Target: 3333 rpm Duty: 34% Status: 2 (locked) Mode: rpm Auto: no Enable: yes fanduty 80 Setting fan 0 duty cycle to 80% faninfo Actual: 5952 rpm Target: 3333 rpm Duty: 80% Status: 2 (locked) Mode: duty Auto: no Enable: yes faninfo Actual: 5971 rpm Target: 3333 rpm Duty: 80% Status: 2 (locked) Mode: duty Auto: no Enable: yes fanauto faninfo Actual: 3330 rpm Target: 3333 rpm Duty: 36% Status: 2 (locked) Mode: rpm Auto: yes Enable: yes fanset 8000 Setting fan 0 rpm target to 8000 faninfo Actual: 6793 rpm Target: 8000 rpm Duty: 100% Status: 3 (frustrated) Mode: rpm Auto: no Enable: yes fanset 3456 Setting fan 0 rpm target to 3456 faninfo Actual: 5053 rpm Target: 3456 rpm Duty: 56% Status: 1 (changing) Mode: rpm Auto: no Enable: yes faninfo Actual: 3440 rpm Target: 3456 rpm Duty: 34% Status: 2 (locked) Mode: rpm Auto: no Enable: yes /* force stop the fan */ [87.035136 Fan 0 stalled!] [87.035520 event set 0x00000400] [88.035712 Fan 0 stalled!] [89.036288 Fan 0 stalled!] [90.036864 Fan 0 stalled!] [91.037440 Fan 0 stalled!] [92.038016 Fan 0 stalled!] [93.038592 Fan 0 stalled!] [94.039168 Fan 0 stalled!] /* release */ faninfo Actual: 3427 rpm Target: 3456 rpm Duty: 35% Status: 2 (locked) Mode: rpm Auto: no Enable: yes Change-Id: Icbe1917902d033a8be42b8d834ffc6045d08b985 Reviewed-on: https://chromium-review.googlesource.com/266625 Reviewed-by: Randall Spangler <rspangler@chromium.org> Commit-Queue: Dino Li <dino.li@ite.com.tw> Tested-by: Dino Li <dino.li@ite.com.tw>
Diffstat (limited to 'chip')
-rw-r--r--chip/it83xx/build.mk1
-rw-r--r--chip/it83xx/fan.c475
-rw-r--r--chip/it83xx/hwtimer.c128
-rw-r--r--chip/it83xx/hwtimer_chip.h54
-rw-r--r--chip/it83xx/pwm.c134
-rw-r--r--chip/it83xx/pwm_chip.h52
-rw-r--r--chip/it83xx/registers.h36
7 files changed, 855 insertions, 25 deletions
diff --git a/chip/it83xx/build.mk b/chip/it83xx/build.mk
index 9cd0b5ff4a..280c6c5de8 100644
--- a/chip/it83xx/build.mk
+++ b/chip/it83xx/build.mk
@@ -16,6 +16,7 @@ chip-y=hwtimer.o uart.o gpio.o system.o jtag.o clock.o irq.o intc.o
# Optional chip modules
chip-$(CONFIG_WATCHDOG)+=watchdog.o
+chip-$(CONFIG_FANS)+=fan.o pwm.o
chip-$(CONFIG_PWM)+=pwm.o
chip-$(CONFIG_ADC)+=adc.o
chip-$(CONFIG_EC2I)+=ec2i.o
diff --git a/chip/it83xx/fan.c b/chip/it83xx/fan.c
new file mode 100644
index 0000000000..4e0e7ab6bd
--- /dev/null
+++ b/chip/it83xx/fan.c
@@ -0,0 +1,475 @@
+/* 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 "registers.h"
+#include "util.h"
+#include "pwm.h"
+#include "task.h"
+#include "math_util.h"
+#include "hwtimer_chip.h"
+#include "pwm_chip.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)
+#ifdef CHIP_FAMILY_IT839X
+#define TACH1_TO_RPM(pulse, raw) (raw * 120 / (pulse * 2))
+#else
+#define TACH1_TO_RPM(pulse, raw) (60 * TACH_EC_FREQ / 128 / pulse / raw)
+#endif
+
+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 < CONFIG_FANS; fan++) {
+ if (!fan_get_enabled(fans[fan].ch))
+ all_disabled++;
+ }
+
+ if (all_disabled >= CONFIG_FANS)
+ 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;
+
+ /* 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;
+ }
+ /* disable timer interrupt if all fan off. */
+ if (fan_all_disabled())
+ ext_timer_stop(FAN_CTRL_EXT_TIMER, 1);
+ }
+
+ /* 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);
+}
+
+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 < CONFIG_FANS; fan++) {
+ if (fan_get_enabled(fans[fan].ch)) {
+ proc_tach(fans[fan].ch);
+ fan_ctrl(fans[fan].ch);
+ }
+ }
+}
+
+static void fan_init(void)
+{
+ int ch, rpm_re, fan_p, s_duty;
+ enum tach_ch_sel tach_ch;
+
+ for (ch = 0; ch < CONFIG_FANS; ch++) {
+
+ rpm_re = fan_tach[pwm_channels[fans[ch].ch].channel].rpm_re;
+ fan_p = fan_tach[pwm_channels[fans[ch].ch].channel].fan_p;
+ s_duty = fan_tach[pwm_channels[fans[ch].ch].channel].s_duty;
+ tach_ch = tach_bind(fans[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);
+}
+DECLARE_HOOK(HOOK_INIT, fan_init, HOOK_PRIO_INIT_PWM);
diff --git a/chip/it83xx/hwtimer.c b/chip/it83xx/hwtimer.c
index 3493843adc..1a5643ea2e 100644
--- a/chip/it83xx/hwtimer.c
+++ b/chip/it83xx/hwtimer.c
@@ -14,6 +14,7 @@
#include "timer.h"
#include "util.h"
#include "watchdog.h"
+#include "hwtimer_chip.h"
/* 128us (2^7 us) between 2 ticks */
#define TICK_INTERVAL_LOG2 7
@@ -21,6 +22,8 @@
#define TICK_INTERVAL (1 << TICK_INTERVAL_LOG2)
#define TICK_INTERVAL_MASK (TICK_INTERVAL - 1)
+#define MS_TO_COUNT(hz, ms) ((hz) * (ms) / 1000)
+
/*
* Tick interval must fit in one byte, and must be greater than two
* so that the duty cycle does not equal the cycle time (IT83XX_TMR_DCR_B0 must
@@ -37,6 +40,22 @@ static volatile uint32_t time_us;
*/
static uint32_t next_event_time;
+const struct ext_timer_ctrl_t et_ctrl_regs[] = {
+ {&IT83XX_INTC_IELMR19, &IT83XX_INTC_IPOLR19, 0x08,
+ IT83XX_IRQ_EXT_TIMER3},
+ {&IT83XX_INTC_IELMR19, &IT83XX_INTC_IPOLR19, 0x10,
+ IT83XX_IRQ_EXT_TIMER4},
+ {&IT83XX_INTC_IELMR19, &IT83XX_INTC_IPOLR19, 0x20,
+ IT83XX_IRQ_EXT_TIMER5},
+ {&IT83XX_INTC_IELMR19, &IT83XX_INTC_IPOLR19, 0x40,
+ IT83XX_IRQ_EXT_TIMER6},
+ {&IT83XX_INTC_IELMR19, &IT83XX_INTC_IPOLR19, 0x80,
+ IT83XX_IRQ_EXT_TIMER7},
+ {&IT83XX_INTC_IELMR10, &IT83XX_INTC_IPOLR10, 0x01,
+ IT83XX_IRQ_EXT_TMR8},
+};
+BUILD_ASSERT(ARRAY_SIZE(et_ctrl_regs) == EXT_TIMER_COUNT);
+
void __hw_clock_event_set(uint32_t deadline)
{
next_event_time = deadline;
@@ -65,7 +84,7 @@ void __hw_clock_source_set(uint32_t ts)
static void __hw_clock_source_irq(void)
{
-#ifdef CONFIG_WATCHDOG
+#if defined(CONFIG_WATCHDOG) || defined(CONFIG_FANS)
/* Determine interrupt number. */
int irq = IT83XX_INTC_IVCT3 - 16;
#endif
@@ -91,7 +110,17 @@ static void __hw_clock_source_irq(void)
}
#endif
- /* clear interrupt status */
+#ifdef CONFIG_FANS
+ if (irq == et_ctrl_regs[FAN_CTRL_EXT_TIMER].irq) {
+ fan_ext_timer_interrupt();
+ return;
+ }
+#endif
+
+ /*
+ * If we're still here, this is actually a hardware interrupt for the
+ * clock source. Clear its interrupt status and update time_us.
+ */
task_clear_pending_irq(IT83XX_IRQ_TMR_B0);
time_us += TICK_INTERVAL;
@@ -186,3 +215,98 @@ void udelay(unsigned us)
while (waits-- >= 0)
IT83XX_GCTRL_WNCKR = 0;
}
+
+void ext_timer_start(enum ext_timer_sel ext_timer, int en_irq)
+{
+ /* enable external timer n */
+ IT83XX_ETWD_ETXCTRL(ext_timer) |= 0x01;
+
+ if (en_irq) {
+ task_clear_pending_irq(et_ctrl_regs[ext_timer].irq);
+ task_enable_irq(et_ctrl_regs[ext_timer].irq);
+ }
+}
+
+void ext_timer_stop(enum ext_timer_sel ext_timer, int dis_irq)
+{
+ /* disable external timer n */
+ IT83XX_ETWD_ETXCTRL(ext_timer) &= ~0x01;
+
+ if (dis_irq)
+ task_disable_irq(et_ctrl_regs[ext_timer].irq);
+}
+
+static void ext_timer_ctrl(enum ext_timer_sel ext_timer,
+ enum ext_timer_clock_source ext_timer_clock,
+ int start,
+ int with_int,
+ int32_t count)
+{
+ uint8_t intc_mask;
+
+ /* rising-edge-triggered */
+ intc_mask = et_ctrl_regs[ext_timer].mask;
+ *et_ctrl_regs[ext_timer].mode |= intc_mask;
+ *et_ctrl_regs[ext_timer].polarity &= ~intc_mask;
+
+ /* clear interrupt status */
+ task_clear_pending_irq(et_ctrl_regs[ext_timer].irq);
+
+ /* These bits control the clock input source to the exttimer 3 - 8 */
+ IT83XX_ETWD_ETXPSR(ext_timer) = ext_timer_clock;
+
+ /* The count number of external timer n. */
+ IT83XX_ETWD_ETXCNTLH2R(ext_timer) = (count >> 16) & 0xFF;
+ IT83XX_ETWD_ETXCNTLHR(ext_timer) = (count >> 8) & 0xFF;
+ IT83XX_ETWD_ETXCNTLLR(ext_timer) = count & 0xFF;
+
+ ext_timer_stop(ext_timer, 0);
+ if (start)
+ ext_timer_start(ext_timer, 0);
+
+ if (with_int)
+ task_enable_irq(et_ctrl_regs[ext_timer].irq);
+ else
+ task_disable_irq(et_ctrl_regs[ext_timer].irq);
+}
+
+int ext_timer_ms(enum ext_timer_sel ext_timer,
+ enum ext_timer_clock_source ext_timer_clock,
+ int start,
+ int with_int,
+ int32_t ms,
+ int first_time_enable)
+{
+ uint32_t count;
+
+ if (ext_timer_clock == EXT_PSR_32P768K_HZ)
+ count = MS_TO_COUNT(32768, ms);
+ else if (ext_timer_clock == EXT_PSR_1P024K_HZ)
+ count = MS_TO_COUNT(1024, ms);
+ else if (ext_timer_clock == EXT_PSR_32_HZ)
+ count = MS_TO_COUNT(32, ms);
+ else if (ext_timer_clock == EXT_PSR_8M_HZ)
+ count = 8000 * ms;
+ else
+ return -1;
+
+ /*
+ * IT838X support 24-bits external timer only,
+ * IT839X support three(4, 6, and 8) 32-bit external timers,
+ * implemented later.
+ */
+ if (count >> 24)
+ return -2;
+
+ if (count == 0)
+ return -3;
+
+ if (first_time_enable) {
+ ext_timer_start(ext_timer, 0);
+ ext_timer_stop(ext_timer, 0);
+ }
+
+ ext_timer_ctrl(ext_timer, ext_timer_clock, start, with_int, count);
+
+ return 0;
+}
diff --git a/chip/it83xx/hwtimer_chip.h b/chip/it83xx/hwtimer_chip.h
new file mode 100644
index 0000000000..9217545c5b
--- /dev/null
+++ b/chip/it83xx/hwtimer_chip.h
@@ -0,0 +1,54 @@
+/* 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.
+ */
+
+/* External timers control module for IT83xx. */
+
+#ifndef __CROS_EC_HWTIMER_CHIP_H
+#define __CROS_EC_HWTIMER_CHIP_H
+
+#define FAN_CTRL_EXT_TIMER EXT_TIMER_5
+
+enum ext_timer_clock_source {
+ EXT_PSR_32P768K_HZ = 0,
+ EXT_PSR_1P024K_HZ = 1,
+ EXT_PSR_32_HZ = 2,
+ EXT_PSR_8M_HZ = 3
+};
+
+enum ext_timer_sel {
+ /* For WDT capture important state information before being reset */
+ EXT_TIMER_3 = 0,
+ /* reserved */
+ EXT_TIMER_4,
+ /* For fan control */
+ EXT_TIMER_5,
+ /* reserved */
+ EXT_TIMER_6,
+ /* reserved */
+ EXT_TIMER_7,
+ /* reserved */
+ EXT_TIMER_8,
+ EXT_TIMER_COUNT,
+};
+
+struct ext_timer_ctrl_t {
+ volatile uint8_t *mode;
+ volatile uint8_t *polarity;
+ uint8_t mask;
+ uint8_t irq;
+};
+
+extern const struct ext_timer_ctrl_t et_ctrl_regs[];
+void ext_timer_start(enum ext_timer_sel ext_timer, int en_irq);
+void ext_timer_stop(enum ext_timer_sel ext_timer, int dis_irq);
+void fan_ext_timer_interrupt(void);
+int ext_timer_ms(enum ext_timer_sel ext_timer,
+ enum ext_timer_clock_source ext_timer_clock,
+ int start,
+ int et_int,
+ int32_t ms,
+ int first_time_enable);
+
+#endif /* __CROS_EC_HWTIMER_CHIP_H */
diff --git a/chip/it83xx/pwm.c b/chip/it83xx/pwm.c
index 6d705f5c46..d027a366cc 100644
--- a/chip/it83xx/pwm.c
+++ b/chip/it83xx/pwm.c
@@ -13,6 +13,10 @@
#include "pwm_chip.h"
#include "registers.h"
#include "util.h"
+#include "math_util.h"
+
+#define PWM_CTRX_MIN 120
+#define PWM_EC_FREQ 8000000
const struct pwm_ctrl_t pwm_ctrl_regs[] = {
{ &IT83XX_PWM_DCR0, &IT83XX_PWM_PCSSGL, &IT83XX_GPIO_GPCRA0},
@@ -36,6 +40,31 @@ const struct pwm_ctrl_t2 pwm_clock_ctrl_regs[] = {
&IT83XX_PWM_PCFSR, 0x08},
};
+static int pwm_get_cycle_time(enum pwm_channel ch)
+{
+ int pcs_shift;
+ int pcs_mask;
+ int pcs_reg;
+ int cycle_time_setting;
+
+ /* pwm channel mapping */
+ ch = pwm_channels[ch].channel;
+
+ /* bit shift for "Prescaler Clock Source Select Group" register. */
+ pcs_shift = (ch % 4) * 2;
+
+ /* setting of "Prescaler Clock Source Select Group" register. */
+ pcs_reg = *pwm_ctrl_regs[ch].pwm_clock_source;
+
+ /* only bit0 bit1 information. */
+ pcs_mask = (pcs_reg >> pcs_shift) & 0x03;
+
+ /* get cycle time setting of PWM channel x. */
+ cycle_time_setting = *pwm_clock_ctrl_regs[pcs_mask].pwm_cycle_time;
+
+ return cycle_time_setting;
+}
+
void pwm_enable(enum pwm_channel ch, int enabled)
{
/* pwm channel mapping */
@@ -73,6 +102,9 @@ void pwm_set_duty(enum pwm_channel ch, int percent)
else if (percent > 100)
percent = 100;
+ if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW)
+ percent = 100 - percent;
+
/* pwm channel mapping */
ch = pwm_channels[ch].channel;
@@ -88,9 +120,6 @@ void pwm_set_duty(enum pwm_channel ch, int percent)
/* get cycle time setting of PWM channel x. */
cycle_time_setting = *pwm_clock_ctrl_regs[pcs_mask].pwm_cycle_time;
- if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW)
- percent = 100 - percent;
-
/* to update PWM DCRx depend on CTRx setting. */
if (percent == 100) {
*pwm_ctrl_regs[ch].pwm_duty = cycle_time_setting;
@@ -106,6 +135,9 @@ int pwm_get_duty(enum pwm_channel ch)
int pcs_reg;
int cycle_time_setting;
int percent;
+ int ch_idx;
+
+ ch_idx = ch;
/* pwm channel mapping */
ch = pwm_channels[ch].channel;
@@ -121,23 +153,105 @@ int pwm_get_duty(enum pwm_channel ch)
percent = *pwm_ctrl_regs[ch].pwm_duty * 100 / cycle_time_setting;
- if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW)
+ if (pwm_channels[ch_idx].flags & PWM_CONFIG_ACTIVE_LOW)
percent = 100 - percent;
/* output signal duty cycle. */
return percent;
}
+void pwm_duty_inc(enum pwm_channel ch)
+{
+ int cycle_time, pwm_ch;
+
+ /* pwm channel mapping */
+ pwm_ch = pwm_channels[ch].channel;
+
+ cycle_time = pwm_get_cycle_time(ch);
+
+ if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW) {
+ if (*pwm_ctrl_regs[pwm_ch].pwm_duty > 0)
+ *pwm_ctrl_regs[pwm_ch].pwm_duty -= 1;
+ } else {
+ if (*pwm_ctrl_regs[pwm_ch].pwm_duty < cycle_time)
+ *pwm_ctrl_regs[pwm_ch].pwm_duty += 1;
+ }
+}
+
+void pwm_duty_reduce(enum pwm_channel ch)
+{
+ int cycle_time, pwm_ch;
+
+ /* pwm channel mapping */
+ pwm_ch = pwm_channels[ch].channel;
+
+ cycle_time = pwm_get_cycle_time(ch);
+
+ if (pwm_channels[ch].flags & PWM_CONFIG_ACTIVE_LOW) {
+ if (*pwm_ctrl_regs[pwm_ch].pwm_duty < cycle_time)
+ *pwm_ctrl_regs[pwm_ch].pwm_duty += 1;
+ } else {
+ if (*pwm_ctrl_regs[pwm_ch].pwm_duty > 0)
+ *pwm_ctrl_regs[pwm_ch].pwm_duty -= 1;
+ }
+}
+
+static int pwm_ch_freq(enum pwm_channel ch)
+{
+ int actual_freq = -1, targe_freq, deviation;
+ int pcfsr, ctr, pcfsr_sel, pcs_shift, pcs_mask;
+
+ targe_freq = pwm_channels[ch].freq_hz;
+ deviation = (targe_freq / 100) + 1;
+
+ for (ctr = 0xFF; ctr > PWM_CTRX_MIN; ctr--) {
+ pcfsr = (PWM_EC_FREQ / (ctr + 1) / targe_freq) - 1;
+ if (pcfsr >= 0) {
+ actual_freq = PWM_EC_FREQ / (ctr + 1) / (pcfsr + 1);
+ if (ABS(actual_freq - targe_freq) < deviation)
+ break;
+ }
+ }
+
+ if (ctr < PWM_CTRX_MIN) {
+ actual_freq = -1;
+ } else {
+ pcfsr_sel = pwm_channels[ch].pcfsr_sel;
+ *pwm_clock_ctrl_regs[pcfsr_sel].pwm_cycle_time = ctr;
+ /* ec clock 8MHz */
+ *pwm_clock_ctrl_regs[pcfsr_sel].pwm_pcfsr_reg |=
+ pwm_clock_ctrl_regs[pcfsr_sel].pwm_pcfsr_ctrl;
+
+ /* pwm channel mapping */
+ ch = pwm_channels[ch].channel;
+
+ /*
+ * bit shift for "Prescaler Clock Source Select Group"
+ * register.
+ */
+ pcs_shift = (ch % 4) * 2;
+ pcs_mask = pcfsr_sel << pcs_shift;
+
+ *pwm_ctrl_regs[ch].pwm_clock_source &= ~(0x3 << pcs_shift);
+ *pwm_ctrl_regs[ch].pwm_clock_source |= pcs_mask;
+
+ *pwm_clock_ctrl_regs[pcfsr_sel].pwm_cpr_lsb = pcfsr & 0xFF;
+ *pwm_clock_ctrl_regs[pcfsr_sel].pwm_cpr_msb =
+ (pcfsr >> 8) & 0xFF;
+ }
+
+ return actual_freq;
+}
+
static void pwm_init(void)
{
+ int ch;
+
+ for (ch = 0; ch < PWM_CH_COUNT; ch++)
+ pwm_ch_freq(ch);
+
/* enable PWMs clock counter. */
IT83XX_PWM_ZTIER |= 0x02;
-
- /* 0.5 resolution */
- IT83XX_PWM_CTR = 200;
- IT83XX_PWM_CTR1 = 200;
- IT83XX_PWM_CTR2 = 200;
- IT83XX_PWM_CTR3 = 200;
}
/* The chip PWM module initialization. */
diff --git a/chip/it83xx/pwm_chip.h b/chip/it83xx/pwm_chip.h
index 3c17b00125..22f2076432 100644
--- a/chip/it83xx/pwm_chip.h
+++ b/chip/it83xx/pwm_chip.h
@@ -8,6 +8,40 @@
#ifndef __CROS_EC_PWM_CHIP_H
#define __CROS_EC_PWM_CHIP_H
+enum pwm_pcfsr_sel {
+ PWM_PRESCALER_C4 = 1,
+ PWM_PRESCALER_C6 = 2,
+ PWM_PRESCALER_C7 = 3,
+};
+
+enum pwm_hw_channel {
+ PWM_HW_CH_DCR0 = 0,
+ PWM_HW_CH_DCR1,
+ PWM_HW_CH_DCR2,
+ PWM_HW_CH_DCR3,
+ PWM_HW_CH_DCR4,
+ PWM_HW_CH_DCR5,
+ PWM_HW_CH_DCR6,
+ PWM_HW_CH_DCR7,
+
+ PWM_HW_CH_TOTAL,
+};
+
+enum tach_ch_sel {
+ /* Pin GPIOD.6 */
+ TACH_CH_TACH0A = 0,
+ /* Pin GPIOD.7 */
+ TACH_CH_TACH1A,
+ /* Pin GPIOJ.2 */
+ TACH_CH_TACH0B,
+ /* Pin GPIOJ.3 */
+ TACH_CH_TACH1B,
+ /* Number of TACH channels */
+ TACH_CH_COUNT,
+
+ TACH_CH_NULL = 0xFF,
+};
+
/* Data structure to define PWM channel control registers. */
struct pwm_ctrl_t {
/* PWM channel output duty register. */
@@ -38,8 +72,26 @@ struct pwm_t {
int channel;
/* PWM channel flags. See include/pwm.h */
uint32_t flags;
+ int freq_hz;
+ enum pwm_pcfsr_sel pcfsr_sel;
+};
+
+/* Tachometer channel of each physical fan */
+struct fan_tach_t {
+ enum tach_ch_sel ch_tach;
+ /* the numbers of square pulses per revolution of fan. */
+ int fan_p;
+ /* allow actual rpm ~= targe rpm +- rpm_re */
+ int rpm_re;
+ /* startup duty of fan */
+ int s_duty;
};
extern const struct pwm_t pwm_channels[];
+/* The list of tachometer channel of fans is instantiated in board.c. */
+extern const struct fan_tach_t fan_tach[];
+
+void pwm_duty_inc(enum pwm_channel ch);
+void pwm_duty_reduce(enum pwm_channel ch);
#endif /* __CROS_EC_PWM_CHIP_H */
diff --git a/chip/it83xx/registers.h b/chip/it83xx/registers.h
index c7d4e58c0b..bc7ddeee44 100644
--- a/chip/it83xx/registers.h
+++ b/chip/it83xx/registers.h
@@ -358,6 +358,11 @@
#define IT83XX_INTC_ISR19 REG8(IT83XX_INTC_BASE+0x50)
#define IT83XX_INTC_ISR20 REG8(IT83XX_INTC_BASE+0x54)
+#define IT83XX_INTC_IELMR10 REG8(IT83XX_INTC_BASE+0x2E)
+#define IT83XX_INTC_IPOLR10 REG8(IT83XX_INTC_BASE+0x2F)
+#define IT83XX_INTC_IELMR19 REG8(IT83XX_INTC_BASE+0x52)
+#define IT83XX_INTC_IPOLR19 REG8(IT83XX_INTC_BASE+0x53)
+
#define IT83XX_INTC_EXT_IER0 REG8(IT83XX_INTC_BASE+0x60)
#define IT83XX_INTC_EXT_IER1 REG8(IT83XX_INTC_BASE+0x61)
#define IT83XX_INTC_EXT_IER2 REG8(IT83XX_INTC_BASE+0x62)
@@ -571,19 +576,24 @@ enum clock_gate_offsets {
/* --- External Timer and Watchdog (ETWD) --- */
#define IT83XX_ETWD_BASE 0x00F01F00
-#define IT83XX_ETWD_ETWCFG REG8(IT83XX_ETWD_BASE+0x01)
-#define IT83XX_ETWD_ET1PSR REG8(IT83XX_ETWD_BASE+0x02)
-#define IT83XX_ETWD_ET1CNTLHR REG8(IT83XX_ETWD_BASE+0x03)
-#define IT83XX_ETWD_ET1CNTLLR REG8(IT83XX_ETWD_BASE+0x04)
-#define IT83XX_ETWD_ETWCTRL REG8(IT83XX_ETWD_BASE+0x05)
-#define IT83XX_ETWD_EWDCNTLLR REG8(IT83XX_ETWD_BASE+0x06)
-#define IT83XX_ETWD_EWDKEYR REG8(IT83XX_ETWD_BASE+0x07)
-#define IT83XX_ETWD_EWDCNTLHR REG8(IT83XX_ETWD_BASE+0x09)
-#define IT83XX_ETWD_ET3CTRL REG8(IT83XX_ETWD_BASE+0x10)
-#define IT83XX_ETWD_ET3PSR REG8(IT83XX_ETWD_BASE+0x11)
-#define IT83XX_ETWD_ET3CNTLLR REG8(IT83XX_ETWD_BASE+0x14)
-#define IT83XX_ETWD_ET3CNTLHR REG8(IT83XX_ETWD_BASE+0x15)
-#define IT83XX_ETWD_ET3CNTLH2R REG8(IT83XX_ETWD_BASE+0x16)
+#define IT83XX_ETWD_ETWCFG REG8(IT83XX_ETWD_BASE+0x01)
+#define IT83XX_ETWD_ET1PSR REG8(IT83XX_ETWD_BASE+0x02)
+#define IT83XX_ETWD_ET1CNTLHR REG8(IT83XX_ETWD_BASE+0x03)
+#define IT83XX_ETWD_ET1CNTLLR REG8(IT83XX_ETWD_BASE+0x04)
+#define IT83XX_ETWD_ETWCTRL REG8(IT83XX_ETWD_BASE+0x05)
+#define IT83XX_ETWD_EWDCNTLLR REG8(IT83XX_ETWD_BASE+0x06)
+#define IT83XX_ETWD_EWDKEYR REG8(IT83XX_ETWD_BASE+0x07)
+#define IT83XX_ETWD_EWDCNTLHR REG8(IT83XX_ETWD_BASE+0x09)
+#define IT83XX_ETWD_ET3CTRL REG8(IT83XX_ETWD_BASE+0x10)
+#define IT83XX_ETWD_ET3PSR REG8(IT83XX_ETWD_BASE+0x11)
+#define IT83XX_ETWD_ET3CNTLLR REG8(IT83XX_ETWD_BASE+0x14)
+#define IT83XX_ETWD_ET3CNTLHR REG8(IT83XX_ETWD_BASE+0x15)
+#define IT83XX_ETWD_ET3CNTLH2R REG8(IT83XX_ETWD_BASE+0x16)
+#define IT83XX_ETWD_ETXCTRL(n) REG8(IT83XX_ETWD_BASE + 0x10 + (n << 3))
+#define IT83XX_ETWD_ETXPSR(n) REG8(IT83XX_ETWD_BASE + 0x11 + (n << 3))
+#define IT83XX_ETWD_ETXCNTLLR(n) REG8(IT83XX_ETWD_BASE + 0x14 + (n << 3))
+#define IT83XX_ETWD_ETXCNTLHR(n) REG8(IT83XX_ETWD_BASE + 0x15 + (n << 3))
+#define IT83XX_ETWD_ETXCNTLH2R(n) REG8(IT83XX_ETWD_BASE + 0x16 + (n << 3))
/* --- General Control (GCTRL) --- */
#define IT83XX_GCTRL_BASE 0x00F02000