| /* |
| * arch/arm/mach-orion/time.c |
| * |
| * Core time functions for Marvell Orion System On Chip |
| * |
| * Maintainer: Tzachi Perelstein <tzachi@marvell.com> |
| * |
| * This file is licensed under the terms of the GNU General Public |
| * License version 2. This program is licensed "as is" without any |
| * warranty of any kind, whether express or implied. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/clockchips.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <asm/mach/time.h> |
| #include <asm/arch/orion.h> |
| #include "common.h" |
| |
| /* |
| * Timer0: clock_event_device, Tick. |
| * Timer1: clocksource, Free running. |
| * WatchDog: Not used. |
| * |
| * Timers are counting down. |
| */ |
| #define CLOCKEVENT 0 |
| #define CLOCKSOURCE 1 |
| |
| /* |
| * Timers bits |
| */ |
| #define BRIDGE_INT_TIMER(x) (1 << ((x) + 1)) |
| #define TIMER_EN(x) (1 << ((x) * 2)) |
| #define TIMER_RELOAD_EN(x) (1 << (((x) * 2) + 1)) |
| #define BRIDGE_INT_TIMER_WD (1 << 3) |
| #define TIMER_WD_EN (1 << 4) |
| #define TIMER_WD_RELOAD_EN (1 << 5) |
| |
| static cycle_t orion_clksrc_read(void) |
| { |
| return (0xffffffff - orion_read(TIMER_VAL(CLOCKSOURCE))); |
| } |
| |
| static struct clocksource orion_clksrc = { |
| .name = "orion_clocksource", |
| .shift = 20, |
| .rating = 300, |
| .read = orion_clksrc_read, |
| .mask = CLOCKSOURCE_MASK(32), |
| .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
| }; |
| |
| static int |
| orion_clkevt_next_event(unsigned long delta, struct clock_event_device *dev) |
| { |
| unsigned long flags; |
| |
| if (delta == 0) |
| return -ETIME; |
| |
| local_irq_save(flags); |
| |
| /* |
| * Clear and enable timer interrupt bit |
| */ |
| orion_write(BRIDGE_CAUSE, ~BRIDGE_INT_TIMER(CLOCKEVENT)); |
| orion_setbits(BRIDGE_MASK, BRIDGE_INT_TIMER(CLOCKEVENT)); |
| |
| /* |
| * Setup new timer value |
| */ |
| orion_write(TIMER_VAL(CLOCKEVENT), delta); |
| |
| /* |
| * Disable auto reload and kickoff the timer |
| */ |
| orion_clrbits(TIMER_CTRL, TIMER_RELOAD_EN(CLOCKEVENT)); |
| orion_setbits(TIMER_CTRL, TIMER_EN(CLOCKEVENT)); |
| |
| local_irq_restore(flags); |
| |
| return 0; |
| } |
| |
| static void |
| orion_clkevt_mode(enum clock_event_mode mode, struct clock_event_device *dev) |
| { |
| unsigned long flags; |
| |
| local_irq_save(flags); |
| |
| if (mode == CLOCK_EVT_MODE_PERIODIC) { |
| /* |
| * Setup latch cycles in timer and enable reload interrupt. |
| */ |
| orion_write(TIMER_VAL_RELOAD(CLOCKEVENT), LATCH); |
| orion_write(TIMER_VAL(CLOCKEVENT), LATCH); |
| orion_setbits(BRIDGE_MASK, BRIDGE_INT_TIMER(CLOCKEVENT)); |
| orion_setbits(TIMER_CTRL, TIMER_RELOAD_EN(CLOCKEVENT) | |
| TIMER_EN(CLOCKEVENT)); |
| } else { |
| /* |
| * Disable timer and interrupt |
| */ |
| orion_clrbits(BRIDGE_MASK, BRIDGE_INT_TIMER(CLOCKEVENT)); |
| orion_write(BRIDGE_CAUSE, ~BRIDGE_INT_TIMER(CLOCKEVENT)); |
| orion_clrbits(TIMER_CTRL, TIMER_RELOAD_EN(CLOCKEVENT) | |
| TIMER_EN(CLOCKEVENT)); |
| } |
| |
| local_irq_restore(flags); |
| } |
| |
| static struct clock_event_device orion_clkevt = { |
| .name = "orion_tick", |
| .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, |
| .shift = 32, |
| .rating = 300, |
| .cpumask = CPU_MASK_CPU0, |
| .set_next_event = orion_clkevt_next_event, |
| .set_mode = orion_clkevt_mode, |
| }; |
| |
| static irqreturn_t orion_timer_interrupt(int irq, void *dev_id) |
| { |
| /* |
| * Clear cause bit and do event |
| */ |
| orion_write(BRIDGE_CAUSE, ~BRIDGE_INT_TIMER(CLOCKEVENT)); |
| orion_clkevt.event_handler(&orion_clkevt); |
| return IRQ_HANDLED; |
| } |
| |
| static struct irqaction orion_timer_irq = { |
| .name = "orion_tick", |
| .flags = IRQF_DISABLED | IRQF_TIMER, |
| .handler = orion_timer_interrupt |
| }; |
| |
| static void orion_timer_init(void) |
| { |
| /* |
| * Setup clocksource free running timer (no interrupt on reload) |
| */ |
| orion_write(TIMER_VAL(CLOCKSOURCE), 0xffffffff); |
| orion_write(TIMER_VAL_RELOAD(CLOCKSOURCE), 0xffffffff); |
| orion_clrbits(BRIDGE_MASK, BRIDGE_INT_TIMER(CLOCKSOURCE)); |
| orion_setbits(TIMER_CTRL, TIMER_RELOAD_EN(CLOCKSOURCE) | |
| TIMER_EN(CLOCKSOURCE)); |
| |
| /* |
| * Register clocksource |
| */ |
| orion_clksrc.mult = |
| clocksource_hz2mult(CLOCK_TICK_RATE, orion_clksrc.shift); |
| |
| clocksource_register(&orion_clksrc); |
| |
| /* |
| * Connect and enable tick handler |
| */ |
| setup_irq(IRQ_ORION_BRIDGE, &orion_timer_irq); |
| |
| /* |
| * Register clockevent |
| */ |
| orion_clkevt.mult = |
| div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, orion_clkevt.shift); |
| orion_clkevt.max_delta_ns = |
| clockevent_delta2ns(0xfffffffe, &orion_clkevt); |
| orion_clkevt.min_delta_ns = |
| clockevent_delta2ns(1, &orion_clkevt); |
| |
| clockevents_register_device(&orion_clkevt); |
| } |
| |
| struct sys_timer orion_timer = { |
| .init = orion_timer_init, |
| }; |