/*++ 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 ; }