mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-09 17:11:42 +00:00
To get better power consumption in S0, we add FW support for
CONFIG_LOW_POWER_S0.
Before entering deep idle in S0, we must enable Host interrupt to wake up
EC if it needs to service LPC bus.
This version also add a new bit of sleep_mask (SLEEP_MASK_FAN) in system.h
to prevent EC enter deep idle if fan's duty isn't zero. Normally, the freq of
PWM fan is 25 kHz. It means we must select apb2 clock as the source clock of
PWM fan. Or fan would stop when ec enters deep idle because of no PWM signal.
In hwtimer.c, we reset the preload counter to maximum value in ITEI32's ISR
since preload counter is changed by __hw_clock_source_set all the time.
We also found there're no event set if it's deadline is over 32 bits but
current source clock isn't. To prevent ec doesn't wake-up in deep-idle even if
ITIM32 expires, FW set an event for ITIM32 after process_timers().
Modified sources:
1. wheatley/board.h: Add CONFIG_LOW_POWER_S0 definition.
2. clock.c: Enable Host interrupt for LPC.
3. clock.c: Disable LP_WK_CTL for better power consumption.
4. gpio.c: Add ISR for Host interrupt.
5. uart.c: Introduce bit 6 of USTAT to make sure transmitting is completed.
6. register.h: Add uart_clear_pending_wakeup function.
7. hwtimer.c: Fixed watchdog issue when ITIM32 is closed to overflow.
8. fan.c: Enable deep sleep if duty cycle is zero.
9. include/system.h: Add SLEEP_MASK_FAN for fan control loop.
10. core/cortex-m/task.c: Add "isb" to flash the garbage data in the
instruction pipeline.
BUG=chrome-os-partner:34346
TEST=make buildall -j; test nuvoton IC specific drivers
BRANCH=none
Change-Id: Ibe3630d0d68cf3f32206adb2afa1b5958916a2be
Signed-off-by: Mulin Chao <mlchao@nuvoton.com>
Reviewed-on: https://chromium-review.googlesource.com/324651
Reviewed-by: Shawn N <shawnn@chromium.org>
292 lines
7.8 KiB
C
292 lines
7.8 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.
|
|
*/
|
|
|
|
/* Hardware timers driver */
|
|
|
|
#include "clock.h"
|
|
#include "clock_chip.h"
|
|
#include "common.h"
|
|
#include "hooks.h"
|
|
#include "hwtimer.h"
|
|
#include "hwtimer_chip.h"
|
|
#include "math_util.h"
|
|
#include "registers.h"
|
|
#include "console.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
|
|
/* Use ITIM32 as main hardware timer */
|
|
#define TICK_ITIM32_MAX_CNT 0xFFFFFFFF
|
|
/* Maximum deadline of event */
|
|
#define EVT_MAX_EXPIRED_US 0xFFFFFFFF
|
|
|
|
/* Depth of event timer */
|
|
#define TICK_EVT_DEPTH 16 /* Depth of event timer Unit: bits */
|
|
#define TICK_EVT_INTERVAL (1 << TICK_EVT_DEPTH) /* Unit: us */
|
|
#define TICK_EVT_INTERVAL_MASK (TICK_EVT_INTERVAL - 1) /* Mask of interval */
|
|
#define TICK_EVT_MAX_CNT (TICK_EVT_INTERVAL - 1) /* Maximum event counter */
|
|
|
|
/* Time when event will be expired unit:us */
|
|
static volatile uint32_t evt_expired_us;
|
|
/* 32-bits event counter */
|
|
static volatile uint32_t evt_cnt;
|
|
/* Debugger information */
|
|
#if DEBUG_TMR
|
|
static volatile uint32_t evt_cnt_us_dbg;
|
|
static volatile uint32_t cur_cnt_us_dbg;
|
|
#endif
|
|
|
|
#if !(DEBUG_TMR)
|
|
#define CPUTS(...)
|
|
#define CPRINTS(...)
|
|
#else
|
|
#define CPUTS(outstr) cputs(CC_CLOCK, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)
|
|
#endif
|
|
|
|
/*****************************************************************************/
|
|
/* Internal functions */
|
|
void init_hw_timer(int itim_no, enum ITIM_SOURCE_CLOCK_T source)
|
|
{
|
|
/* Use internal 32K clock/APB2 for ITIM16 */
|
|
UPDATE_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_CKSEL,
|
|
source != ITIM_SOURCE_CLOCK_APB2);
|
|
|
|
/* Clear timeout status */
|
|
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_STS);
|
|
|
|
/* ITIM timeout interrupt enable */
|
|
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_IE);
|
|
|
|
/* ITIM timeout wake-up enable */
|
|
SET_BIT(NPCX_ITCTS(itim_no), NPCX_ITCTS_TO_WUE);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* HWTimer event handlers */
|
|
void __hw_clock_event_set(uint32_t deadline)
|
|
{
|
|
fp_t inv_evt_tick = FLOAT_TO_FP(INT_32K_CLOCK/(float)SECOND);
|
|
int32_t evt_cnt_us;
|
|
/* Is deadline min value? */
|
|
if (evt_expired_us != 0 && evt_expired_us < deadline)
|
|
return;
|
|
|
|
/* mark min event value */
|
|
evt_expired_us = deadline;
|
|
evt_cnt_us = deadline - __hw_clock_source_read();
|
|
#if DEBUG_TMR
|
|
evt_cnt_us_dbg = deadline - __hw_clock_source_read();
|
|
#endif
|
|
/* Deadline is behind current timer */
|
|
if (evt_cnt_us < 0)
|
|
evt_cnt_us = 1;
|
|
|
|
/* Event module disable */
|
|
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
|
|
/*
|
|
* ITIM count down : event expired : Unit: 1/32768 sec
|
|
* It must exceed evt_expired_us for process_timers function
|
|
*/
|
|
evt_cnt = FP_TO_INT((fp_inter_t)(evt_cnt_us) * inv_evt_tick);
|
|
if (evt_cnt > TICK_EVT_MAX_CNT) {
|
|
CPRINTS("Event overflow! 0x%08x, us is %d\r\n",
|
|
evt_cnt, evt_cnt_us);
|
|
evt_cnt = TICK_EVT_MAX_CNT;
|
|
}
|
|
NPCX_ITCNT16(ITIM_EVENT_NO) = evt_cnt;
|
|
|
|
/* Event module enable */
|
|
SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
|
|
|
|
/* Enable interrupt of ITIM */
|
|
task_enable_irq(ITIM16_INT(ITIM_EVENT_NO));
|
|
}
|
|
|
|
/* Returns the time-stamp of the next programmed event */
|
|
uint32_t __hw_clock_event_get(void)
|
|
{
|
|
if (evt_expired_us)
|
|
return evt_expired_us;
|
|
else /* No events. Give maximum deadline */
|
|
return EVT_MAX_EXPIRED_US;
|
|
}
|
|
|
|
/* Get current counter value of event timer */
|
|
uint16_t __hw_clock_event_count(void)
|
|
{
|
|
return NPCX_ITCNT16(ITIM_EVENT_NO);
|
|
}
|
|
|
|
/* Returns time delay cause of deep idle */
|
|
uint32_t __hw_clock_get_sleep_time(uint16_t pre_evt_cnt)
|
|
{
|
|
fp_t evt_tick = FLOAT_TO_FP(SECOND/(float)INT_32K_CLOCK);
|
|
uint32_t sleep_time;
|
|
uint16_t cnt = NPCX_ITCNT16(ITIM_EVENT_NO);
|
|
|
|
/* Event has been triggered but timer ISR dosen't handle it */
|
|
if (IS_BIT_SET(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS))
|
|
sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1) * evt_tick);
|
|
/* Event hasn't been triggered */
|
|
else
|
|
sleep_time = FP_TO_INT((fp_inter_t)(pre_evt_cnt+1 - cnt) *
|
|
evt_tick);
|
|
|
|
return sleep_time;
|
|
}
|
|
|
|
/* Cancel the next event programmed by __hw_clock_event_set */
|
|
void __hw_clock_event_clear(void)
|
|
{
|
|
/* ITIM event module disable */
|
|
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
|
|
|
|
/* Disable interrupt of Event */
|
|
task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));
|
|
|
|
/* Clear event parameters */
|
|
evt_expired_us = 0;
|
|
evt_cnt = 0;
|
|
}
|
|
|
|
/* Irq for hwtimer event */
|
|
void __hw_clock_event_irq(void)
|
|
{
|
|
/* ITIM event module disable */
|
|
CLEAR_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_ITEN);
|
|
|
|
/* Disable interrupt of event */
|
|
task_disable_irq(ITIM16_INT(ITIM_EVENT_NO));
|
|
|
|
/* Clear timeout status for event */
|
|
SET_BIT(NPCX_ITCTS(ITIM_EVENT_NO), NPCX_ITCTS_TO_STS);
|
|
|
|
/* Clear event parameters */
|
|
evt_expired_us = 0;
|
|
evt_cnt = 0;
|
|
|
|
/* handle upper driver */
|
|
process_timers(0);
|
|
|
|
#ifdef CONFIG_LOW_POWER_IDLE
|
|
/*
|
|
* Set event for ITIM32 after process_timers() since no events set if
|
|
* event's deadline is over 32 bits but current source clock isn't.
|
|
* ITIM32 is based on apb2 and ec won't wake-up in deep-idle even if it
|
|
* expires.
|
|
*/
|
|
if (evt_expired_us == 0)
|
|
__hw_clock_event_set(EVT_MAX_EXPIRED_US);
|
|
#endif
|
|
|
|
}
|
|
DECLARE_IRQ(ITIM16_INT(ITIM_EVENT_NO) , __hw_clock_event_irq, 1);
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* HWTimer tick handlers */
|
|
|
|
/* Modify preload counter of source clock. */
|
|
void hw_clock_source_set_preload(uint32_t ts, uint8_t clear)
|
|
{
|
|
/* ITIM32 module disable */
|
|
CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
|
|
|
|
CLEAR_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_CKSEL);
|
|
|
|
/* Set preload counter to current time */
|
|
NPCX_ITCNT32 = TICK_ITIM32_MAX_CNT - ts;
|
|
/* Clear timeout status or not */
|
|
if (clear)
|
|
SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS);
|
|
/* ITIM32 module enable */
|
|
SET_BIT(NPCX_ITCTS(ITIM32), NPCX_ITCTS_ITEN);
|
|
}
|
|
|
|
/* Returns the value of the free-running counter used as clock. */
|
|
uint32_t __hw_clock_source_read(void)
|
|
{
|
|
uint32_t cnt = NPCX_ITCNT32;
|
|
#if DEBUG_TMR
|
|
cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - cnt;
|
|
#endif
|
|
return TICK_ITIM32_MAX_CNT - cnt;
|
|
}
|
|
|
|
/* Override the current value of the hardware counter */
|
|
void __hw_clock_source_set(uint32_t ts)
|
|
{
|
|
#if DEBUG_TMR
|
|
cur_cnt_us_dbg = TICK_ITIM32_MAX_CNT - ts;
|
|
#endif
|
|
hw_clock_source_set_preload(ts, 0);
|
|
}
|
|
|
|
/* Irq for hwtimer tick */
|
|
void __hw_clock_source_irq(void)
|
|
{
|
|
/* Is timeout trigger trigger? */
|
|
if (IS_BIT_SET(NPCX_ITCTS(ITIM32), NPCX_ITCTS_TO_STS)) {
|
|
/* Restore ITIM32 preload counter value to maximum value */
|
|
hw_clock_source_set_preload(0, 1);
|
|
/* 32-bits timer count overflow */
|
|
process_timers(1);
|
|
|
|
} else { /* Handle soft trigger */
|
|
process_timers(0);
|
|
#ifdef CONFIG_LOW_POWER_IDLE
|
|
/* Set event for ITIM32. Please see above for detail */
|
|
if (evt_expired_us == 0)
|
|
__hw_clock_event_set(EVT_MAX_EXPIRED_US);
|
|
#endif
|
|
}
|
|
}
|
|
DECLARE_IRQ(NPCX_IRQ_ITIM32, __hw_clock_source_irq, 0);
|
|
|
|
static void update_prescaler(void)
|
|
{
|
|
/*
|
|
* prescaler to time tick
|
|
* Ttick_unit = (PRE_8+1) * Tapb2_clk
|
|
* PRE_8 = (Ttick_unit/Tapb2_clk) -1
|
|
*/
|
|
NPCX_ITPRE(ITIM32) = (clock_get_apb2_freq() / SECOND) - 1;
|
|
/* Set event tick unit = 1/32768 sec */
|
|
NPCX_ITPRE(ITIM_EVENT_NO) = 0;
|
|
|
|
}
|
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT);
|
|
|
|
int __hw_clock_source_init(uint32_t start_t)
|
|
{
|
|
/*
|
|
* 1. Use ITIM16-1 as internal time reading
|
|
* 2. Use ITIM16-2 for event handling
|
|
*/
|
|
|
|
/* Enable clock for ITIM peripheral */
|
|
clock_enable_peripheral(CGC_OFFSET_TIMER, CGC_TIMER_MASK,
|
|
CGC_MODE_RUN | CGC_MODE_SLEEP);
|
|
|
|
/* init tick & event timer first */
|
|
init_hw_timer(ITIM32, ITIM_SOURCE_CLOCK_APB2);
|
|
init_hw_timer(ITIM_EVENT_NO, ITIM_SOURCE_CLOCK_32K);
|
|
|
|
/* Set initial prescaler */
|
|
update_prescaler();
|
|
|
|
/*
|
|
* Override the count with the start value now that counting has
|
|
* started.
|
|
*/
|
|
hw_clock_source_set_preload(start_t, 1);
|
|
|
|
/* Enable interrupt of ITIM */
|
|
task_enable_irq(NPCX_IRQ_ITIM32);
|
|
|
|
return NPCX_IRQ_ITIM32;
|
|
}
|