diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/Kconfig | 2 | ||||
-rw-r--r-- | arch/arm/mach-orion/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-orion/common.h | 5 | ||||
-rw-r--r-- | arch/arm/mach-orion/time.c | 181 |
4 files changed, 189 insertions, 1 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 71e905ebcc05..20084f273d8e 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -338,6 +338,8 @@ config ARCH_ORION depends on MMU select PCI select GENERIC_GPIO + select GENERIC_TIME + select GENERIC_CLOCKEVENTS help Support for Marvell Orion System on Chip family. diff --git a/arch/arm/mach-orion/Makefile b/arch/arm/mach-orion/Makefile index 57d459a2090e..3b2a3eef39dd 100644 --- a/arch/arm/mach-orion/Makefile +++ b/arch/arm/mach-orion/Makefile @@ -1 +1 @@ -obj-y += common.o addr-map.o pci.o gpio.o irq.o +obj-y += common.o addr-map.o pci.o gpio.o irq.o time.o diff --git a/arch/arm/mach-orion/common.h b/arch/arm/mach-orion/common.h index 155371e1d672..2b452facadd1 100644 --- a/arch/arm/mach-orion/common.h +++ b/arch/arm/mach-orion/common.h @@ -62,4 +62,9 @@ int orion_pci_hw_wr_conf(u32 bus, u32 dev, u32 func, u32 where, u32 size, u32 va void __init orion_gpio_set_valid_pins(u32 pins); void gpio_display(void); /* debug */ +/* + * Orion system timer (clocksource + clockevnt, /mach-orion/time.c) + */ +extern struct sys_timer orion_timer; + #endif /* __ARCH_ORION_COMMON_H__ */ diff --git a/arch/arm/mach-orion/time.c b/arch/arm/mach-orion/time.c new file mode 100644 index 000000000000..bd4262da4f40 --- /dev/null +++ b/arch/arm/mach-orion/time.c @@ -0,0 +1,181 @@ +/* + * 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, +}; |