/*++
linux/arch/arm/mach-wmt/wmt_time.c
Copyright (c) 2008 WonderMedia Technologies, Inc.
This program is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software Foundation,
either version 2 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see .
WonderMedia Technologies, Inc.
10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
--*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define DEBUG
#ifdef DEBUG
#define fq_dbg(fmt, args...) printk(KERN_ERR "[%s]_%d: " fmt, __func__ , __LINE__, ## args)
#define fq_trace() printk(KERN_ERR "trace in %s %d\n", __func__, __LINE__)
#else
#define fq_dbg(fmt, args...)
#define fq_trace()
#endif
#define MIN_OSCR_DELTA 16
#define MIN_HRTMR_CYC_DELTA 64
#define WMT_CLOCK_TICK_RATE 3000000
#define WMT_CLOCK_TICK_RATE1 5000000
#define WMT_CLOCK_TICK_RATE2 6000000
#define WMT_CLOCK_TICK_RATE3 6000000
/* Clear OS Timer1 irq */
static inline void wmt_os_timer_clear_irq(void)
{
OSTS_VAL = OSTS_M1;
}
/* disable OS Timer1 irq */
static inline void wmt_os_timer_disable_irq(void)
{
OSTI_VAL &= ~OSTI_E1;
}
/* Enable OS timer1 irq */
static inline void wmt_os_timer_enable_irq(void)
{
OSTI_VAL |= OSTI_E1;
}
/* Stop ostimer, counter stop */
static inline void wmt_os_timer_freeze_counter(void)
{
OSTC_VAL = 0;
}
/* Let OS Timer free run, counter increase now */
static inline void wmt_os_timer_restart_counter(void)
{
OSTC_VAL = OSTC_ENABLE;
}
static inline void wmt_os_timer_set_counter(u32 new_cnt)
{
OSCR_VAL = new_cnt;
}
static inline u32 wmt_os_timer_read_counter(void)
{
OSTC_VAL |= OSTC_RDREQ;
while (OSTA_VAL & OSTA_RCA)
;
return (u32)OSCR_VAL;
}
static inline void wmt_os_timer_set_match(u32 new_match)
{
/* check if can write OS Timer1 match register nows */
while (OSTA_VAL & OSTA_MWA1)
;
OSM1_VAL = new_match;
}
/* OS timer hardware initializing routine */
/* TODO: Here we let os timer run, but disable interrupt,
when clcokevent registed, then enable interrupt
*/
static void __init wmt_os_timer_init(void)
{
wmt_os_timer_disable_irq();
wmt_os_timer_clear_irq();
wmt_os_timer_freeze_counter();
wmt_os_timer_set_match(0);
wmt_os_timer_restart_counter();
}
/* for clocksource */
static cycle_t wmt_timer_read_cycles(struct clocksource *cs)
{
return (cycle_t)wmt_os_timer_read_counter();
}
struct clocksource wmt_clocksource = {
.name = "wmt_clocksource",
.rating = 200,
.read = wmt_timer_read_cycles,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void __init wmt_clocksource_init(struct clocksource *cs)
{
clocksource_register_hz(cs, WMT_CLOCK_TICK_RATE);
fq_dbg("%s mult:%u, shift:%u, max_idle_ns:%llu\n\n", cs->name,
cs->mult, cs->shift, cs->max_idle_ns);
}
struct clocksource wmt_clocksource1 = {
.name = "wmt_clocksource1",
.rating = 150,
.read = wmt_timer_read_cycles,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void __init wmt_clocksource_init1(struct clocksource *cs)
{
clocksource_register_hz(cs, WMT_CLOCK_TICK_RATE1);
fq_dbg("%s mult:%u, shift:%u, max_idle_ns:%llu\n\n", cs->name,
cs->mult, cs->shift, cs->max_idle_ns);
}
struct clocksource wmt_clocksource2 = {
.name = "wmt_clocksource2",
.rating = 150,
.read = wmt_timer_read_cycles,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void __init wmt_clocksource_init2(struct clocksource *cs)
{
clocksource_register_hz(cs, WMT_CLOCK_TICK_RATE2);
fq_dbg("%s mult:%u, shift:%u, max_idle_ns:%llu\n\n", cs->name,
cs->mult, cs->shift, cs->max_idle_ns);
}
struct clocksource wmt_clocksource3 = {
.name = "wmt_clocksource3",
.rating = 150,
.read = wmt_timer_read_cycles,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void __init wmt_clocksource_init3(struct clocksource *cs)
{
clocksource_register_hz(cs, WMT_CLOCK_TICK_RATE3);
fq_dbg("%s mult:%u, shift:%u, max_idle_ns:%llu\n\n", cs->name,
cs->mult, cs->shift, cs->max_idle_ns);
}
static int
wmt_timer_set_next_event(unsigned long cycles, struct clock_event_device *evt)
{
unsigned long next = 0;
unsigned long oscr = 0;
oscr = wmt_os_timer_read_counter();
next = oscr + cycles;
/* set new value to os time1 match register */
wmt_os_timer_set_match(next);
/* Enable match on timer 1 to cause interrupts. */
wmt_os_timer_enable_irq();
/* check if overflow */
if ((signed long)(next - wmt_os_timer_read_counter()) <= MIN_OSCR_DELTA) {
fq_dbg("set os timer overflow!, set_cyc:%lu, now_cyc:%lu,"
" next_cyc:%lu\n\n\n", cycles, oscr, next);
return -ETIME;
}
return 0;
}
static void
wmt_timer_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
{
switch (mode) {
case CLOCK_EVT_MODE_UNUSED:
case CLOCK_EVT_MODE_SHUTDOWN:
case CLOCK_EVT_MODE_ONESHOT:
/* disable OS Timer irq here */
wmt_os_timer_disable_irq();
/* Clear match on OS Timer 1 */
wmt_os_timer_clear_irq();
break;
case CLOCK_EVT_MODE_RESUME:
case CLOCK_EVT_MODE_PERIODIC:
default:
break;
}
return ;
}
struct clock_event_device wmt_clockevent = {
.name = "wmt_clockevent",
.features = CLOCK_EVT_FEAT_ONESHOT,
.rating = 200,
.set_next_event = wmt_timer_set_next_event,
.set_mode = wmt_timer_set_mode,
.shift = 32,
};
static irqreturn_t wmt_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = dev_id;
/* Clear match on OS Timer 1 irq */
wmt_os_timer_clear_irq();
evt->event_handler(evt);
return IRQ_HANDLED;
}
struct irqaction wmt_timer_irq = {
.name = "wmt_timer",
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.handler = wmt_timer_interrupt,
.dev_id = &wmt_clockevent,
};
static void __init wmt_clockevent_init(struct clock_event_device *evt,
unsigned int irq, struct irqaction *act)
{
/* reset some elements for clockevent */
evt->cpumask = cpumask_of(smp_processor_id());
evt->mult = div_sc(WMT_CLOCK_TICK_RATE, NSEC_PER_SEC, evt->shift);
evt->max_delta_ns = clockevent_delta2ns(0x7fffffff, evt);
evt->min_delta_ns = clockevent_delta2ns(MIN_HRTMR_CYC_DELTA, evt);
fq_dbg("%s, mult:%u, shift:%d, max_delta:%llu, min_delta:%llu\n\n\n",
evt->name, evt->mult, evt->shift, evt->max_delta_ns, evt->min_delta_ns);
/* setup os timer1 irq */
if (setup_irq(irq, act))
printk(KERN_ERR "setup clockevent %s irq %u, failed!\n\n", evt->name, irq);
/* register clockevent */
clockevents_register_device(evt);
return ;
}
/* for sched_clock */
static u32 notrace wmt_read_sched_clock(void)
{
return wmt_os_timer_read_counter();
}
/* for MPcore local timer */
#ifdef CONFIG_LOCAL_TIMERS
extern void wmt3498_init_clocks(void);
#define WMT_LOCAL_TIMER_BASE (0xD8018000 + 0x600)
#define WMT_LOCAL_TIMER_PPI_NUM 29
static DEFINE_TWD_LOCAL_TIMER(wmt_local_timer,
WMT_LOCAL_TIMER_BASE, WMT_LOCAL_TIMER_PPI_NUM);
/* check if uboot env wmt.twd.disable = 1, if twd disable (=1), return 1,
* if twd enable, return 0
*/
static int wmt_twd_is_disabled(void)
{
int ret = 0;
int varlen = 128;
unsigned int drv_en = 0;
unsigned char buf[128] = {0};
/* uboot env name is: wmt.twd.disable */
ret = wmt_getsyspara("wmt.twd.disable", buf, &varlen);
if (ret) {
ret = 0;
goto out;
}
sscanf(buf, "%d", &drv_en);
if (drv_en) {
ret = -ENODEV;
goto out;
}
out:
return ret;
}
#endif
static void __init wmt_timer_init(void)
{
/* prepare OS timer hardware, irq disabled */
wmt_os_timer_init();
/* os timer1 as clocksourece */
wmt_clocksource_init(&wmt_clocksource);
wmt_clocksource_init1(&wmt_clocksource1);
wmt_clocksource_init2(&wmt_clocksource2);
wmt_clocksource_init3(&wmt_clocksource3);
/* sched_clock for timestamp, as printk.... */
setup_sched_clock(wmt_read_sched_clock, 32, WMT_CLOCK_TICK_RATE);
/* os timer1 as clockevent device */
wmt_clockevent_init(&wmt_clockevent, IRQ_OST1, &wmt_timer_irq);
#ifdef CONFIG_LOCAL_TIMERS
wmt3498_init_clocks();
if (wmt_twd_is_disabled()) {
printk(KERN_INFO "WMT local timer disabled\n");
goto out;
}
if (twd_local_timer_register(&wmt_local_timer))
printk(KERN_ERR "Register ARM local timer failed! Use broadcast mode!\n");
#endif
out:
/* this is a MUST operation */
wmt_os_timer_enable_irq();
return ;
}
struct sys_timer wmt_timer = {
.init = wmt_timer_init
};
/* below code is for old design compatibility */
inline unsigned int wmt_read_oscr(void)
{
return wmt_os_timer_read_counter();
}
EXPORT_SYMBOL(wmt_read_oscr);
int wmt_rtc_on = 1;
static int __init no_rtc(char *str)
{
wmt_rtc_on = 0;
return 1;
}
__setup("nortc", no_rtc);
static void wmt_rtc_init(void)
{
fq_dbg("Enter\n");
RTCC_VAL = (RTCC_ENA|RTCC_INTTYPE);
if (!(RTSR_VAL&RTSR_VAILD))
while (!(RTSR_VAL&RTSR_VAILD))
;
/* Reset RTC alarm settings */
//RTAS_VAL = 0;
/* Reset RTC test mode. */
RTTM_VAL = 0;
/* Patch 1, RTCD default value isn't 0x41 and it will not sync with RTDS. */
if (RTCD_VAL == 0) {
while (RTWS_VAL & RTWS_DATESET)
;
RTDS_VAL = RTDS_VAL;
}
/*
* Disable all RTC control functions.
* Set to 24-hr format and update type to each second.
* Disable sec/min update interrupt.
* Let RTC free run without interrupts.
*/
/* RTCC_VAL &= ~(RTCC_12HR | RTCC_INTENA); */
/* RTCC_VAL |= RTCC_ENA | RTCC_INTTYPE; */
RTCC_VAL = (RTCC_ENA|RTCC_INTTYPE);
if (RTWS_VAL & RTWS_CONTROL) {
while (RTWS_VAL & RTWS_CONTROL)
;
}
fq_dbg("Exit\n\n\n");
return ;
}
void wmt_read_rtc(unsigned int *date, unsigned int *time)
{
/* before read rtc, we should check if rtc already be initialized */
if (((RTCC_VAL & RTCC_ENA) == 0) || ((RTSR_VAL & RTSR_VAILD) == 0)) {
wmt_rtc_init();
}
if (!(RTSR_VAL & RTSR_VAILD))
while (!(RTSR_VAL & RTSR_VAILD))
;
if (RTWS_VAL & RTWS_DATESET)
while (RTWS_VAL & RTWS_DATESET)
;
*date = RTCD_VAL;
if (RTWS_VAL & RTWS_TIMESET)
while (RTWS_VAL & RTWS_TIMESET)
;
*time = RTCT_VAL;
}
EXPORT_SYMBOL(wmt_read_rtc);
/*
* get current Gregorian date from RTC,
* coverts to seconds since 1970-01-01 00:00:00
*/
static unsigned long wmt_get_rtc_time(void)
{
unsigned int date = 0;
unsigned int time = 0;
/* if no rtc or rtc disabled, return 0,
* means timekeeping is 1970-01-01 00:00:00 when system booting */
if (!wmt_rtc_on)
return 0;
wmt_read_rtc(&date, &time);
return mktime(RTCD_CENT(date)? 2100+RTCD_YEAR(date) : 2000+RTCD_YEAR(date),
RTCD_MON(date), RTCD_MDAY(date),RTCT_HOUR(time),
RTCT_MIN(time), RTCT_SEC(time));
}
void read_persistent_clock(struct timespec *ts)
{
ts->tv_nsec = 0;
ts->tv_sec = (__kernel_time_t)wmt_get_rtc_time();
fq_dbg("seconds read from RTC is %lu\n\n\n", ts->tv_sec);
return ;
}