diff options
| author | Andrew Victor <linux@maxim.org.za> | 2008-04-16 15:43:49 -0400 |
|---|---|---|
| committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2008-04-17 10:55:54 -0400 |
| commit | ad48ce74f70a201c4c1cf3b4e8f6b6203a2e4a8d (patch) | |
| tree | 982ac3f881a9c09115ae9b9723c343ed00dcfc8d | |
| parent | 11aadac4f6adc032cfd1e41c348a7a568819ed94 (diff) | |
[ARM] 4989/1: [AT91] SAM9 ClockSource / ClockEvents
Update AT91SAM9/CAP9 PIT driver to use generic time and clockevent
infrastructure:
- Clocksource gives sub-microsecond timestamp precision, assuming
memory is clocked at over 16 MHz. It's less than a 32 bit counter,
unless it's is also generating IRQs.
- Clockevent device supports periodic mode only; no oneshot
support from this hardware. No IRQs generated unless it's the
active clocksource.
Later, another timer (probably from a TC module) can provide a oneshot
clockevent device to get NO_HZ and High-Res-Timer behavior.
This also updates the timekeeping to use the actual master clock rate
on the system, instead of compile-time <asm/arch/timex.h> constants
matching what Atmel's EK boards use. (Product boards may well differ!)
Plus cleanup: rename "*_timer*" symbols to "*_pit*" (there are other
timers, but only one PIT); shorter lines; remove needless CPP stuff;
make several symbols static; etc.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Andrew Victor <linux@maxim.org.za>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
| -rw-r--r-- | arch/arm/mach-at91/Kconfig | 10 | ||||
| -rw-r--r-- | arch/arm/mach-at91/at91sam926x_time.c | 171 |
2 files changed, 135 insertions, 46 deletions
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig index 8a9f67b62ec1..0fc07b6db749 100644 --- a/arch/arm/mach-at91/Kconfig +++ b/arch/arm/mach-at91/Kconfig | |||
| @@ -12,18 +12,28 @@ config ARCH_AT91RM9200 | |||
| 12 | 12 | ||
| 13 | config ARCH_AT91SAM9260 | 13 | config ARCH_AT91SAM9260 |
| 14 | bool "AT91SAM9260 or AT91SAM9XE" | 14 | bool "AT91SAM9260 or AT91SAM9XE" |
| 15 | select GENERIC_TIME | ||
| 16 | select GENERIC_CLOCKEVENTS | ||
| 15 | 17 | ||
| 16 | config ARCH_AT91SAM9261 | 18 | config ARCH_AT91SAM9261 |
| 17 | bool "AT91SAM9261" | 19 | bool "AT91SAM9261" |
| 20 | select GENERIC_TIME | ||
| 21 | select GENERIC_CLOCKEVENTS | ||
| 18 | 22 | ||
| 19 | config ARCH_AT91SAM9263 | 23 | config ARCH_AT91SAM9263 |
| 20 | bool "AT91SAM9263" | 24 | bool "AT91SAM9263" |
| 25 | select GENERIC_TIME | ||
| 26 | select GENERIC_CLOCKEVENTS | ||
| 21 | 27 | ||
| 22 | config ARCH_AT91SAM9RL | 28 | config ARCH_AT91SAM9RL |
| 23 | bool "AT91SAM9RL" | 29 | bool "AT91SAM9RL" |
| 30 | select GENERIC_TIME | ||
| 31 | select GENERIC_CLOCKEVENTS | ||
| 24 | 32 | ||
| 25 | config ARCH_AT91CAP9 | 33 | config ARCH_AT91CAP9 |
| 26 | bool "AT91CAP9" | 34 | bool "AT91CAP9" |
| 35 | select GENERIC_TIME | ||
| 36 | select GENERIC_CLOCKEVENTS | ||
| 27 | 37 | ||
| 28 | config ARCH_AT91X40 | 38 | config ARCH_AT91X40 |
| 29 | bool "AT91x40" | 39 | bool "AT91x40" |
diff --git a/arch/arm/mach-at91/at91sam926x_time.c b/arch/arm/mach-at91/at91sam926x_time.c index e38d23770992..5cecbd7de6a6 100644 --- a/arch/arm/mach-at91/at91sam926x_time.c +++ b/arch/arm/mach-at91/at91sam926x_time.c | |||
| @@ -1,23 +1,20 @@ | |||
| 1 | /* | 1 | /* |
| 2 | * linux/arch/arm/mach-at91/at91sam926x_time.c | 2 | * at91sam926x_time.c - Periodic Interval Timer (PIT) for at91sam926x |
| 3 | * | 3 | * |
| 4 | * Copyright (C) 2005-2006 M. Amine SAYA, ATMEL Rousset, France | 4 | * Copyright (C) 2005-2006 M. Amine SAYA, ATMEL Rousset, France |
| 5 | * Revision 2005 M. Nicolas Diremdjian, ATMEL Rousset, France | 5 | * Revision 2005 M. Nicolas Diremdjian, ATMEL Rousset, France |
| 6 | * Converted to ClockSource/ClockEvents by David Brownell. | ||
| 6 | * | 7 | * |
| 7 | * This program is free software; you can redistribute it and/or modify | 8 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License version 2 as | 9 | * it under the terms of the GNU General Public License version 2 as |
| 9 | * published by the Free Software Foundation. | 10 | * published by the Free Software Foundation. |
| 10 | */ | 11 | */ |
| 11 | |||
| 12 | #include <linux/init.h> | ||
| 13 | #include <linux/interrupt.h> | 12 | #include <linux/interrupt.h> |
| 14 | #include <linux/irq.h> | 13 | #include <linux/irq.h> |
| 15 | #include <linux/kernel.h> | 14 | #include <linux/kernel.h> |
| 16 | #include <linux/sched.h> | 15 | #include <linux/clk.h> |
| 17 | #include <linux/time.h> | 16 | #include <linux/clockchips.h> |
| 18 | 17 | ||
| 19 | #include <asm/hardware.h> | ||
| 20 | #include <asm/io.h> | ||
| 21 | #include <asm/mach/time.h> | 18 | #include <asm/mach/time.h> |
| 22 | 19 | ||
| 23 | #include <asm/arch/at91_pit.h> | 20 | #include <asm/arch/at91_pit.h> |
| @@ -26,85 +23,167 @@ | |||
| 26 | #define PIT_CPIV(x) ((x) & AT91_PIT_CPIV) | 23 | #define PIT_CPIV(x) ((x) & AT91_PIT_CPIV) |
| 27 | #define PIT_PICNT(x) (((x) & AT91_PIT_PICNT) >> 20) | 24 | #define PIT_PICNT(x) (((x) & AT91_PIT_PICNT) >> 20) |
| 28 | 25 | ||
| 26 | static u32 pit_cycle; /* write-once */ | ||
| 27 | static u32 pit_cnt; /* access only w/system irq blocked */ | ||
| 28 | |||
| 29 | |||
| 29 | /* | 30 | /* |
| 30 | * Returns number of microseconds since last timer interrupt. Note that interrupts | 31 | * Clocksource: just a monotonic counter of MCK/16 cycles. |
| 31 | * will have been disabled by do_gettimeofday() | 32 | * We don't care whether or not PIT irqs are enabled. |
| 32 | * 'LATCH' is hwclock ticks (see CLOCK_TICK_RATE in timex.h) per jiffy. | ||
| 33 | */ | 33 | */ |
| 34 | static unsigned long at91sam926x_gettimeoffset(void) | 34 | static cycle_t read_pit_clk(void) |
| 35 | { | 35 | { |
| 36 | unsigned long elapsed; | 36 | unsigned long flags; |
| 37 | unsigned long t = at91_sys_read(AT91_PIT_PIIR); | 37 | u32 elapsed; |
| 38 | u32 t; | ||
| 39 | |||
| 40 | raw_local_irq_save(flags); | ||
| 41 | elapsed = pit_cnt; | ||
| 42 | t = at91_sys_read(AT91_PIT_PIIR); | ||
| 43 | raw_local_irq_restore(flags); | ||
| 44 | |||
| 45 | elapsed += PIT_PICNT(t) * pit_cycle; | ||
| 46 | elapsed += PIT_CPIV(t); | ||
| 47 | return elapsed; | ||
| 48 | } | ||
| 49 | |||
| 50 | static struct clocksource pit_clk = { | ||
| 51 | .name = "pit", | ||
| 52 | .rating = 175, | ||
| 53 | .read = read_pit_clk, | ||
| 54 | .shift = 20, | ||
| 55 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
| 56 | }; | ||
| 38 | 57 | ||
| 39 | elapsed = (PIT_PICNT(t) * LATCH) + PIT_CPIV(t); /* hardware clock cycles */ | ||
| 40 | 58 | ||
| 41 | return (unsigned long)(elapsed * jiffies_to_usecs(1)) / LATCH; | 59 | /* |
| 60 | * Clockevent device: interrupts every 1/HZ (== pit_cycles * MCK/16) | ||
| 61 | */ | ||
| 62 | static void | ||
| 63 | pit_clkevt_mode(enum clock_event_mode mode, struct clock_event_device *dev) | ||
| 64 | { | ||
| 65 | unsigned long flags; | ||
| 66 | |||
| 67 | switch (mode) { | ||
| 68 | case CLOCK_EVT_MODE_PERIODIC: | ||
| 69 | /* update clocksource counter, then enable the IRQ */ | ||
| 70 | raw_local_irq_save(flags); | ||
| 71 | pit_cnt += pit_cycle * PIT_PICNT(at91_sys_read(AT91_PIT_PIVR)); | ||
| 72 | at91_sys_write(AT91_PIT_MR, (pit_cycle - 1) | AT91_PIT_PITEN | ||
| 73 | | AT91_PIT_PITIEN); | ||
| 74 | raw_local_irq_restore(flags); | ||
| 75 | break; | ||
| 76 | case CLOCK_EVT_MODE_ONESHOT: | ||
| 77 | BUG(); | ||
| 78 | /* FALLTHROUGH */ | ||
| 79 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
| 80 | case CLOCK_EVT_MODE_UNUSED: | ||
| 81 | /* disable irq, leaving the clocksource active */ | ||
| 82 | at91_sys_write(AT91_PIT_MR, (pit_cycle - 1) | AT91_PIT_PITEN); | ||
| 83 | break; | ||
| 84 | case CLOCK_EVT_MODE_RESUME: | ||
| 85 | break; | ||
| 86 | } | ||
| 42 | } | 87 | } |
| 43 | 88 | ||
| 89 | static struct clock_event_device pit_clkevt = { | ||
| 90 | .name = "pit", | ||
| 91 | .features = CLOCK_EVT_FEAT_PERIODIC, | ||
| 92 | .shift = 32, | ||
| 93 | .rating = 100, | ||
| 94 | .cpumask = CPU_MASK_CPU0, | ||
| 95 | .set_mode = pit_clkevt_mode, | ||
| 96 | }; | ||
| 97 | |||
| 98 | |||
| 44 | /* | 99 | /* |
| 45 | * IRQ handler for the timer. | 100 | * IRQ handler for the timer. |
| 46 | */ | 101 | */ |
| 47 | static irqreturn_t at91sam926x_timer_interrupt(int irq, void *dev_id) | 102 | static irqreturn_t at91sam926x_pit_interrupt(int irq, void *dev_id) |
| 48 | { | 103 | { |
| 49 | volatile long nr_ticks; | ||
| 50 | 104 | ||
| 51 | if (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS) { /* This is a shared interrupt */ | 105 | /* The PIT interrupt may be disabled, and is shared */ |
| 52 | /* Get number to ticks performed before interrupt and clear PIT interrupt */ | 106 | if ((pit_clkevt.mode == CLOCK_EVT_MODE_PERIODIC) |
| 107 | && (at91_sys_read(AT91_PIT_SR) & AT91_PIT_PITS)) { | ||
| 108 | unsigned nr_ticks; | ||
| 109 | |||
| 110 | /* Get number of ticks performed before irq, and ack it */ | ||
| 53 | nr_ticks = PIT_PICNT(at91_sys_read(AT91_PIT_PIVR)); | 111 | nr_ticks = PIT_PICNT(at91_sys_read(AT91_PIT_PIVR)); |
| 54 | do { | 112 | do { |
| 55 | timer_tick(); | 113 | pit_cnt += pit_cycle; |
| 114 | pit_clkevt.event_handler(&pit_clkevt); | ||
| 56 | nr_ticks--; | 115 | nr_ticks--; |
| 57 | } while (nr_ticks); | 116 | } while (nr_ticks); |
| 58 | 117 | ||
| 59 | return IRQ_HANDLED; | 118 | return IRQ_HANDLED; |
| 60 | } else | 119 | } |
| 61 | return IRQ_NONE; /* not handled */ | 120 | |
| 121 | return IRQ_NONE; | ||
| 62 | } | 122 | } |
| 63 | 123 | ||
| 64 | static struct irqaction at91sam926x_timer_irq = { | 124 | static struct irqaction at91sam926x_pit_irq = { |
| 65 | .name = "at91_tick", | 125 | .name = "at91_tick", |
| 66 | .flags = IRQF_SHARED | IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, | 126 | .flags = IRQF_SHARED | IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, |
| 67 | .handler = at91sam926x_timer_interrupt | 127 | .handler = at91sam926x_pit_interrupt |
| 68 | }; | 128 | }; |
| 69 | 129 | ||
| 70 | void at91sam926x_timer_reset(void) | 130 | static void at91sam926x_pit_reset(void) |
| 71 | { | 131 | { |
| 72 | /* Disable timer */ | 132 | /* Disable timer and irqs */ |
| 73 | at91_sys_write(AT91_PIT_MR, 0); | 133 | at91_sys_write(AT91_PIT_MR, 0); |
| 74 | 134 | ||
| 75 | /* Clear any pending interrupts */ | 135 | /* Clear any pending interrupts, wait for PIT to stop counting */ |
| 76 | (void) at91_sys_read(AT91_PIT_PIVR); | 136 | while (PIT_CPIV(at91_sys_read(AT91_PIT_PIVR)) != 0) |
| 137 | cpu_relax(); | ||
| 77 | 138 | ||
| 78 | /* Set Period Interval timer and enable its interrupt */ | 139 | /* Start PIT but don't enable IRQ */ |
| 79 | at91_sys_write(AT91_PIT_MR, (LATCH & AT91_PIT_PIV) | AT91_PIT_PITIEN | AT91_PIT_PITEN); | 140 | at91_sys_write(AT91_PIT_MR, (pit_cycle - 1) | AT91_PIT_PITEN); |
| 80 | } | 141 | } |
| 81 | 142 | ||
| 82 | /* | 143 | /* |
| 83 | * Set up timer interrupt. | 144 | * Set up both clocksource and clockevent support. |
| 84 | */ | 145 | */ |
| 85 | void __init at91sam926x_timer_init(void) | 146 | static void __init at91sam926x_pit_init(void) |
| 86 | { | 147 | { |
| 87 | /* Initialize and enable the timer */ | 148 | unsigned long pit_rate; |
| 88 | at91sam926x_timer_reset(); | 149 | unsigned bits; |
| 150 | |||
| 151 | /* | ||
| 152 | * Use our actual MCK to figure out how many MCK/16 ticks per | ||
| 153 | * 1/HZ period (instead of a compile-time constant LATCH). | ||
| 154 | */ | ||
| 155 | pit_rate = clk_get_rate(clk_get(NULL, "mck")) / 16; | ||
| 156 | pit_cycle = (pit_rate + HZ/2) / HZ; | ||
| 157 | WARN_ON(((pit_cycle - 1) & ~AT91_PIT_PIV) != 0); | ||
| 89 | 158 | ||
| 90 | /* Make IRQs happen for the system timer. */ | 159 | /* Initialize and enable the timer */ |
| 91 | setup_irq(AT91_ID_SYS, &at91sam926x_timer_irq); | 160 | at91sam926x_pit_reset(); |
| 161 | |||
| 162 | /* | ||
| 163 | * Register clocksource. The high order bits of PIV are unused, | ||
| 164 | * so this isn't a 32-bit counter unless we get clockevent irqs. | ||
| 165 | */ | ||
| 166 | pit_clk.mult = clocksource_hz2mult(pit_rate, pit_clk.shift); | ||
| 167 | bits = 12 /* PICNT */ + ilog2(pit_cycle) /* PIV */; | ||
| 168 | pit_clk.mask = CLOCKSOURCE_MASK(bits); | ||
| 169 | clocksource_register(&pit_clk); | ||
| 170 | |||
| 171 | /* Set up irq handler */ | ||
| 172 | setup_irq(AT91_ID_SYS, &at91sam926x_pit_irq); | ||
| 173 | |||
| 174 | /* Set up and register clockevents */ | ||
| 175 | pit_clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, pit_clkevt.shift); | ||
| 176 | clockevents_register_device(&pit_clkevt); | ||
| 92 | } | 177 | } |
| 93 | 178 | ||
| 94 | #ifdef CONFIG_PM | 179 | static void at91sam926x_pit_suspend(void) |
| 95 | static void at91sam926x_timer_suspend(void) | ||
| 96 | { | 180 | { |
| 97 | /* Disable timer */ | 181 | /* Disable timer */ |
| 98 | at91_sys_write(AT91_PIT_MR, 0); | 182 | at91_sys_write(AT91_PIT_MR, 0); |
| 99 | } | 183 | } |
| 100 | #else | ||
| 101 | #define at91sam926x_timer_suspend NULL | ||
| 102 | #endif | ||
| 103 | 184 | ||
| 104 | struct sys_timer at91sam926x_timer = { | 185 | struct sys_timer at91sam926x_timer = { |
| 105 | .init = at91sam926x_timer_init, | 186 | .init = at91sam926x_pit_init, |
| 106 | .offset = at91sam926x_gettimeoffset, | 187 | .suspend = at91sam926x_pit_suspend, |
| 107 | .suspend = at91sam926x_timer_suspend, | 188 | .resume = at91sam926x_pit_reset, |
| 108 | .resume = at91sam926x_timer_reset, | ||
| 109 | }; | 189 | }; |
| 110 | |||
