diff options
Diffstat (limited to 'arch/arm/mach-sa1100')
-rw-r--r-- | arch/arm/mach-sa1100/clock.c | 100 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/collie.c | 29 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/collie_pm.c | 22 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/cpu-sa1100.c | 20 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/cpu-sa1110.c | 18 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/dma.c | 10 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/include/mach/h3600.h | 12 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/include/mach/io.h | 8 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/include/mach/memory.h | 13 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/include/mach/mtd-xip.h | 2 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/pleb.c | 10 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/shannon.c | 2 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/sleep.S | 52 | ||||
-rw-r--r-- | arch/arm/mach-sa1100/time.c | 4 |
14 files changed, 121 insertions, 181 deletions
diff --git a/arch/arm/mach-sa1100/clock.c b/arch/arm/mach-sa1100/clock.c index 43c30f8..dab3c63 100644 --- a/arch/arm/mach-sa1100/clock.c +++ b/arch/arm/mach-sa1100/clock.c @@ -3,6 +3,7 @@ */ #include <linux/module.h> #include <linux/kernel.h> +#include <linux/device.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/err.h> @@ -14,36 +15,39 @@ #include <mach/hardware.h> /* - * Very simple clock implementation - we only have one clock to - * deal with at the moment, so we only match using the "name". + * Very simple clock implementation - we only have one clock to deal with. */ struct clk { - struct list_head node; - unsigned long rate; - const char *name; unsigned int enabled; - void (*enable)(void); - void (*disable)(void); }; -static LIST_HEAD(clocks); -static DEFINE_MUTEX(clocks_mutex); +static void clk_gpio27_enable(void) +{ + /* + * First, set up the 3.6864MHz clock on GPIO 27 for the SA-1111: + * (SA-1110 Developer's Manual, section 9.1.2.1) + */ + GAFR |= GPIO_32_768kHz; + GPDR |= GPIO_32_768kHz; + TUCR = TUCR_3_6864MHz; +} + +static void clk_gpio27_disable(void) +{ + TUCR = 0; + GPDR &= ~GPIO_32_768kHz; + GAFR &= ~GPIO_32_768kHz; +} + +static struct clk clk_gpio27; + static DEFINE_SPINLOCK(clocks_lock); struct clk *clk_get(struct device *dev, const char *id) { - struct clk *p, *clk = ERR_PTR(-ENOENT); - - mutex_lock(&clocks_mutex); - list_for_each_entry(p, &clocks, node) { - if (strcmp(id, p->name) == 0) { - clk = p; - break; - } - } - mutex_unlock(&clocks_mutex); + const char *devname = dev_name(dev); - return clk; + return strcmp(devname, "sa1111.0") ? ERR_PTR(-ENOENT) : &clk_gpio27; } EXPORT_SYMBOL(clk_get); @@ -58,7 +62,7 @@ int clk_enable(struct clk *clk) spin_lock_irqsave(&clocks_lock, flags); if (clk->enabled++ == 0) - clk->enable(); + clk_gpio27_enable(); spin_unlock_irqrestore(&clocks_lock, flags); return 0; } @@ -72,63 +76,13 @@ void clk_disable(struct clk *clk) spin_lock_irqsave(&clocks_lock, flags); if (--clk->enabled == 0) - clk->disable(); + clk_gpio27_disable(); spin_unlock_irqrestore(&clocks_lock, flags); } EXPORT_SYMBOL(clk_disable); unsigned long clk_get_rate(struct clk *clk) { - return clk->rate; + return 3686400; } EXPORT_SYMBOL(clk_get_rate); - - -static void clk_gpio27_enable(void) -{ - /* - * First, set up the 3.6864MHz clock on GPIO 27 for the SA-1111: - * (SA-1110 Developer's Manual, section 9.1.2.1) - */ - GAFR |= GPIO_32_768kHz; - GPDR |= GPIO_32_768kHz; - TUCR = TUCR_3_6864MHz; -} - -static void clk_gpio27_disable(void) -{ - TUCR = 0; - GPDR &= ~GPIO_32_768kHz; - GAFR &= ~GPIO_32_768kHz; -} - -static struct clk clk_gpio27 = { - .name = "SA1111_CLK", - .rate = 3686400, - .enable = clk_gpio27_enable, - .disable = clk_gpio27_disable, -}; - -int clk_register(struct clk *clk) -{ - mutex_lock(&clocks_mutex); - list_add(&clk->node, &clocks); - mutex_unlock(&clocks_mutex); - return 0; -} -EXPORT_SYMBOL(clk_register); - -void clk_unregister(struct clk *clk) -{ - mutex_lock(&clocks_mutex); - list_del(&clk->node); - mutex_unlock(&clocks_mutex); -} -EXPORT_SYMBOL(clk_unregister); - -static int __init clk_init(void) -{ - clk_register(&clk_gpio27); - return 0; -} -arch_initcall(clk_init); diff --git a/arch/arm/mach-sa1100/collie.c b/arch/arm/mach-sa1100/collie.c index fe28999..2052eb8 100644 --- a/arch/arm/mach-sa1100/collie.c +++ b/arch/arm/mach-sa1100/collie.c @@ -68,23 +68,22 @@ struct platform_device colliescoop_device = { }; static struct scoop_pcmcia_dev collie_pcmcia_scoop[] = { -{ - .dev = &colliescoop_device.dev, - .irq = COLLIE_IRQ_GPIO_CF_IRQ, - .cd_irq = COLLIE_IRQ_GPIO_CF_CD, - .cd_irq_str = "PCMCIA0 CD", -}, + { + .dev = &colliescoop_device.dev, + .irq = COLLIE_IRQ_GPIO_CF_IRQ, + .cd_irq = COLLIE_IRQ_GPIO_CF_CD, + .cd_irq_str = "PCMCIA0 CD", + }, }; static struct scoop_pcmcia_config collie_pcmcia_config = { - .devs = &collie_pcmcia_scoop[0], - .num_devs = 1, + .devs = &collie_pcmcia_scoop[0], + .num_devs = 1, }; - static struct mcp_plat_data collie_mcp_data = { - .mccr0 = MCCR0_ADM | MCCR0_ExtClk, - .sclk_rate = 9216000, + .mccr0 = MCCR0_ADM | MCCR0_ExtClk, + .sclk_rate = 9216000, }; #ifdef CONFIG_SHARP_LOCOMO @@ -95,14 +94,14 @@ struct platform_device collie_locomo_device; static void collie_uart_set_mctrl(struct uart_port *port, u_int mctrl) { - if (mctrl & TIOCM_RTS) + if (mctrl & TIOCM_RTS) locomo_gpio_write(&collie_locomo_device.dev, LOCOMO_GPIO_RTS, 0); - else + else locomo_gpio_write(&collie_locomo_device.dev, LOCOMO_GPIO_RTS, 1); - if (mctrl & TIOCM_DTR) + if (mctrl & TIOCM_DTR) locomo_gpio_write(&collie_locomo_device.dev, LOCOMO_GPIO_DTR, 0); - else + else locomo_gpio_write(&collie_locomo_device.dev, LOCOMO_GPIO_DTR, 1); } diff --git a/arch/arm/mach-sa1100/collie_pm.c b/arch/arm/mach-sa1100/collie_pm.c index b1161fc..b39307f 100644 --- a/arch/arm/mach-sa1100/collie_pm.c +++ b/arch/arm/mach-sa1100/collie_pm.c @@ -26,7 +26,7 @@ #include <asm/irq.h> #include <mach/hardware.h> #include <asm/hardware/scoop.h> -#include <asm/dma.h> +#include <mach/dma.h> #include <mach/collie.h> #include <asm/mach/sharpsl_param.h> #include <asm/hardware/sharpsl_pm.h> @@ -263,24 +263,24 @@ static int __init collie_pm_ucb_add(struct ucb1x00_dev *pdev) } static struct ucb1x00_driver collie_pm_ucb_driver = { - .add = collie_pm_ucb_add, + .add = collie_pm_ucb_add, }; static struct platform_device *collie_pm_device; static int __init collie_pm_init(void) { - int ret; + int ret; - collie_pm_device = platform_device_alloc("sharpsl-pm", -1); - if (!collie_pm_device) - return -ENOMEM; + collie_pm_device = platform_device_alloc("sharpsl-pm", -1); + if (!collie_pm_device) + return -ENOMEM; - collie_pm_device->dev.platform_data = &collie_pm_machinfo; - ret = platform_device_add(collie_pm_device); + collie_pm_device->dev.platform_data = &collie_pm_machinfo; + ret = platform_device_add(collie_pm_device); - if (ret) - platform_device_put(collie_pm_device); + if (ret) + platform_device_put(collie_pm_device); if (!ret) ret = ucb1x00_register_driver(&collie_pm_ucb_driver); @@ -291,7 +291,7 @@ static int __init collie_pm_init(void) static void __exit collie_pm_exit(void) { ucb1x00_unregister_driver(&collie_pm_ucb_driver); - platform_device_unregister(collie_pm_device); + platform_device_unregister(collie_pm_device); } module_init(collie_pm_init); diff --git a/arch/arm/mach-sa1100/cpu-sa1100.c b/arch/arm/mach-sa1100/cpu-sa1100.c index 244d595..ef81787 100644 --- a/arch/arm/mach-sa1100/cpu-sa1100.c +++ b/arch/arm/mach-sa1100/cpu-sa1100.c @@ -3,17 +3,17 @@ * * Copyright (C) 2000 2001, The Delft University of Technology * - * Authors: + * Authors: * - Johan Pouwelse (J.A.Pouwelse@its.tudelft.nl): initial version * - Erik Mouw (J.A.K.Mouw@its.tudelft.nl): * - major rewrite for linux-2.3.99 - * - rewritten for the more generic power management scheme in + * - rewritten for the more generic power management scheme in * linux-2.4.5-rmk1 * * This software has been developed while working on the LART * computing board (http://www.lartmaker.nl/), which is * sponsored by the Mobile Multi-media Communications - * (http://www.mmc.tudelft.nl/) and Ubiquitous Communications + * (http://www.mmc.tudelft.nl/) and Ubiquitous Communications * (http://www.ubicom.tudelft.nl/) projects. * * The authors can be reached at: @@ -36,7 +36,7 @@ * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -44,7 +44,7 @@ * * Theory of operations * ==================== - * + * * Clock scaling can be used to lower the power consumption of the CPU * core. This will give you a somewhat longer running time. * @@ -58,11 +58,11 @@ * MDCNFG 0xA0000000 DRAM config * MDCAS0 0xA0000004 Access waveform * MDCAS1 0xA0000008 Access waveform - * MDCAS2 0xA000000C Access waveform + * MDCAS2 0xA000000C Access waveform * * Care must be taken to change the DRAM parameters the correct way, * because otherwise the DRAM becomes unusable and the kernel will - * crash. + * crash. * * The simple solution to avoid a kernel crash is to put the actual * clock change in ROM and jump to that code from the kernel. The main @@ -75,7 +75,7 @@ * as long as all re-configuration steps yield a valid DRAM * configuration. The advantages are clear: it will run on all SA-1100 * platforms, and the code is very simple. - * + * * If you really want to understand what is going on in * sa1100_update_dram_timings(), you'll have to read sections 8.2, * 9.5.7.3, and 10.2 from the "Intel StrongARM SA-1100 Microprocessor @@ -97,7 +97,7 @@ typedef struct { int speed; u32 mdcnfg; - u32 mdcas0; + u32 mdcas0; u32 mdcas1; u32 mdcas2; } sa1100_dram_regs_t; @@ -147,7 +147,7 @@ static void sa1100_update_dram_timings(int current_speed, int new_speed) /* No risk, no fun: run with interrupts on! */ if (new_speed > current_speed) { /* We're going FASTER, so first relax the memory - * timings before changing the core frequency + * timings before changing the core frequency */ /* Half the memory access clock */ diff --git a/arch/arm/mach-sa1100/cpu-sa1110.c b/arch/arm/mach-sa1100/cpu-sa1110.c index 3e4fb21..63b32b6 100644 --- a/arch/arm/mach-sa1100/cpu-sa1110.c +++ b/arch/arm/mach-sa1100/cpu-sa1110.c @@ -81,14 +81,14 @@ static struct sdram_params sdram_tbl[] __initdata = { .twr = 9, .refresh = 64000, .cas_latency = 3, - }, { /* Samsung K4S281632B-1H */ - .name = "K4S281632B-1H", - .rows = 12, - .tck = 10, - .trp = 20, - .twr = 10, - .refresh = 64000, - .cas_latency = 3, + }, { /* Samsung K4S281632B-1H */ + .name = "K4S281632B-1H", + .rows = 12, + .tck = 10, + .trp = 20, + .twr = 10, + .refresh = 64000, + .cas_latency = 3, }, { /* Samsung KM416S4030CT */ .name = "KM416S4030CT", .rows = 13, @@ -220,7 +220,7 @@ sdram_update_refresh(u_int cpu_khz, struct sdram_params *sdram) } /* - * Ok, set the CPU frequency. + * Ok, set the CPU frequency. */ static int sa1110_target(struct cpufreq_policy *policy, unsigned int target_freq, diff --git a/arch/arm/mach-sa1100/dma.c b/arch/arm/mach-sa1100/dma.c index f990a3e..95f9c5a6 100644 --- a/arch/arm/mach-sa1100/dma.c +++ b/arch/arm/mach-sa1100/dma.c @@ -19,7 +19,7 @@ #include <asm/system.h> #include <asm/irq.h> #include <mach/hardware.h> -#include <asm/dma.h> +#include <mach/dma.h> #undef DEBUG @@ -113,10 +113,10 @@ int sa1100_request_dma (dma_device_t device, const char *device_id, } } if (!err) { - if (dma) - dma->device = device; - else - err = -ENOSR; + if (dma) + dma->device = device; + else + err = -ENOSR; } spin_unlock(&dma_list_lock); if (err) diff --git a/arch/arm/mach-sa1100/include/mach/h3600.h b/arch/arm/mach-sa1100/include/mach/h3600.h index 3ca0ecf..9cc47fd 100644 --- a/arch/arm/mach-sa1100/include/mach/h3600.h +++ b/arch/arm/mach-sa1100/include/mach/h3600.h @@ -32,14 +32,14 @@ typedef int __bitwise pm_request_t; #define machine_is_h3xxx() (machine_is_h3100() || machine_is_h3600() || machine_is_h3800()) /* Physical memory regions corresponding to chip selects */ -#define H3600_EGPIO_PHYS (SA1100_CS5_PHYS + 0x01000000) -#define H3600_BANK_2_PHYS SA1100_CS2_PHYS -#define H3600_BANK_4_PHYS SA1100_CS4_PHYS +#define H3600_EGPIO_PHYS (SA1100_CS5_PHYS + 0x01000000) +#define H3600_BANK_2_PHYS SA1100_CS2_PHYS +#define H3600_BANK_4_PHYS SA1100_CS4_PHYS /* Virtual memory regions corresponding to chip selects 2 & 4 (used on sleeves) */ -#define H3600_EGPIO_VIRT 0xf0000000 -#define H3600_BANK_2_VIRT 0xf1000000 -#define H3600_BANK_4_VIRT 0xf3800000 +#define H3600_EGPIO_VIRT 0xf0000000 +#define H3600_BANK_2_VIRT 0xf1000000 +#define H3600_BANK_4_VIRT 0xf3800000 /* Machine-independent GPIO definitions diff --git a/arch/arm/mach-sa1100/include/mach/io.h b/arch/arm/mach-sa1100/include/mach/io.h index 0c070a6..d8b43f3 100644 --- a/arch/arm/mach-sa1100/include/mach/io.h +++ b/arch/arm/mach-sa1100/include/mach/io.h @@ -16,11 +16,7 @@ * We don't actually have real ISA nor PCI buses, but there is so many * drivers out there that might just work if we fake them... */ -static inline void __iomem *__io(unsigned long addr) -{ - return (void __iomem *)addr; -} -#define __io(a) __io(a) -#define __mem_pci(a) (a) +#define __io(a) __typesafe_io(a) +#define __mem_pci(a) (a) #endif diff --git a/arch/arm/mach-sa1100/include/mach/memory.h b/arch/arm/mach-sa1100/include/mach/memory.h index 1c127b6..e9f8eed 100644 --- a/arch/arm/mach-sa1100/include/mach/memory.h +++ b/arch/arm/mach-sa1100/include/mach/memory.h @@ -23,23 +23,12 @@ void sa1111_adjust_zones(int node, unsigned long *size, unsigned long *holes); sa1111_adjust_zones(node, size, holes) #define ISA_DMA_THRESHOLD (PHYS_OFFSET + SZ_1M - 1) +#define MAX_DMA_ADDRESS (PAGE_OFFSET + SZ_1M) #endif #endif /* - * Virtual view <-> DMA view memory address translations - * virt_to_bus: Used to translate the virtual address to an - * address suitable to be passed to set_dma_addr - * bus_to_virt: Used to convert an address for DMA operations - * to an address that the kernel can use. - * - * On the SA1100, bus addresses are equivalent to physical addresses. - */ -#define __virt_to_bus(x) __virt_to_phys(x) -#define __bus_to_virt(x) __phys_to_virt(x) - -/* * Because of the wide memory address space between physical RAM banks on the * SA1100, it's much convenient to use Linux's SparseMEM support to implement * our memory map representation. Assuming all memory nodes have equal access diff --git a/arch/arm/mach-sa1100/include/mach/mtd-xip.h b/arch/arm/mach-sa1100/include/mach/mtd-xip.h index eaa09e8..b3d6840 100644 --- a/arch/arm/mach-sa1100/include/mach/mtd-xip.h +++ b/arch/arm/mach-sa1100/include/mach/mtd-xip.h @@ -15,6 +15,8 @@ #ifndef __ARCH_SA1100_MTD_XIP_H__ #define __ARCH_SA1100_MTD_XIP_H__ +#include <mach/hardware.h> + #define xip_irqpending() (ICIP & ICMR) /* we sample OSCR and convert desired delta to usec (1/4 ~= 1000000/3686400) */ diff --git a/arch/arm/mach-sa1100/pleb.c b/arch/arm/mach-sa1100/pleb.c index e45d3a1..e1458bc 100644 --- a/arch/arm/mach-sa1100/pleb.c +++ b/arch/arm/mach-sa1100/pleb.c @@ -122,12 +122,12 @@ static void __init pleb_map_io(void) sa1100_map_io(); sa1100_register_uart(0, 3); - sa1100_register_uart(1, 1); + sa1100_register_uart(1, 1); - GAFR |= (GPIO_UART_TXD | GPIO_UART_RXD); - GPDR |= GPIO_UART_TXD; - GPDR &= ~GPIO_UART_RXD; - PPAR |= PPAR_UPR; + GAFR |= (GPIO_UART_TXD | GPIO_UART_RXD); + GPDR |= GPIO_UART_TXD; + GPDR &= ~GPIO_UART_RXD; + PPAR |= PPAR_UPR; /* * Fix expansion memory timing for network card diff --git a/arch/arm/mach-sa1100/shannon.c b/arch/arm/mach-sa1100/shannon.c index 9ccdd09..ddd917d 100644 --- a/arch/arm/mach-sa1100/shannon.c +++ b/arch/arm/mach-sa1100/shannon.c @@ -33,7 +33,7 @@ static struct mtd_partition shannon_partitions[] = { .offset = MTDPART_OFS_APPEND, .size = 0xe0000 }, - { + { .name = "initrd", .offset = MTDPART_OFS_APPEND, .size = MTDPART_SIZ_FULL diff --git a/arch/arm/mach-sa1100/sleep.S b/arch/arm/mach-sa1100/sleep.S index 171441f..80f31ba 100644 --- a/arch/arm/mach-sa1100/sleep.S +++ b/arch/arm/mach-sa1100/sleep.S @@ -100,36 +100,36 @@ ENTRY(sa1100_cpu_suspend) ldr r1, =MSC1 ldr r2, =MSC2 - ldr r3, [r0] - bic r3, r3, #FMsk(MSC_RT) - bic r3, r3, #FMsk(MSC_RT)<<16 + ldr r3, [r0] + bic r3, r3, #FMsk(MSC_RT) + bic r3, r3, #FMsk(MSC_RT)<<16 - ldr r4, [r1] - bic r4, r4, #FMsk(MSC_RT) - bic r4, r4, #FMsk(MSC_RT)<<16 + ldr r4, [r1] + bic r4, r4, #FMsk(MSC_RT) + bic r4, r4, #FMsk(MSC_RT)<<16 - ldr r5, [r2] - bic r5, r5, #FMsk(MSC_RT) - bic r5, r5, #FMsk(MSC_RT)<<16 + ldr r5, [r2] + bic r5, r5, #FMsk(MSC_RT) + bic r5, r5, #FMsk(MSC_RT)<<16 - ldr r6, =MDREFR + ldr r6, =MDREFR - ldr r7, [r6] - bic r7, r7, #0x0000FF00 - bic r7, r7, #0x000000F0 - orr r8, r7, #MDREFR_SLFRSH + ldr r7, [r6] +bic r7, r7, #0x0000FF00 +bic r7, r7, #0x000000F0 +orr r8, r7, #MDREFR_SLFRSH - ldr r9, =MDCNFG - ldr r10, [r9] - bic r10, r10, #(MDCNFG_DE0+MDCNFG_DE1) - bic r10, r10, #(MDCNFG_DE2+MDCNFG_DE3) + ldr r9, =MDCNFG + ldr r10, [r9] + bic r10, r10, #(MDCNFG_DE0+MDCNFG_DE1) + bic r10, r10, #(MDCNFG_DE2+MDCNFG_DE3) - bic r11, r8, #MDREFR_SLFRSH - bic r11, r11, #MDREFR_E1PIN + bic r11, r8, #MDREFR_SLFRSH + bic r11, r11, #MDREFR_E1PIN - ldr r12, =PMCR + ldr r12, =PMCR - mov r13, #PMCR_SF + mov r13, #PMCR_SF b sa1110_sdram_controller_fix @@ -188,10 +188,10 @@ ENTRY(sa1100_cpu_resume) mcr p15, 0, r1, c8, c7, 0 @ flush I+D TLBs mcr p15, 0, r1, c7, c7, 0 @ flush I&D cache mcr p15, 0, r1, c9, c0, 0 @ invalidate RB - mcr p15, 0, r1, c9, c0, 5 @ allow user space to use RB + mcr p15, 0, r1, c9, c0, 5 @ allow user space to use RB - mcr p15, 0, r4, c3, c0, 0 @ domain ID - mcr p15, 0, r5, c2, c0, 0 @ translation table base addr + mcr p15, 0, r4, c3, c0, 0 @ domain ID + mcr p15, 0, r5, c2, c0, 0 @ translation table base addr mcr p15, 0, r6, c13, c0, 0 @ PID b resume_turn_on_mmu @ cache align execution @@ -209,7 +209,7 @@ sleep_save_sp: .text resume_after_mmu: - mcr p15, 0, r1, c15, c1, 2 @ enable clock switching + mcr p15, 0, r1, c15, c1, 2 @ enable clock switching ldmfd sp!, {r4 - r12, pc} @ return to caller diff --git a/arch/arm/mach-sa1100/time.c b/arch/arm/mach-sa1100/time.c index 24c0a4b..8c5e727 100644 --- a/arch/arm/mach-sa1100/time.c +++ b/arch/arm/mach-sa1100/time.c @@ -2,8 +2,8 @@ * linux/arch/arm/mach-sa1100/time.c * * Copyright (C) 1998 Deborah Wallach. - * Twiddles (C) 1999 Hugo Fiennes <hugo@empeg.com> - * + * Twiddles (C) 1999 Hugo Fiennes <hugo@empeg.com> + * * 2000/03/29 (C) Nicolas Pitre <nico@cam.org> * Rewritten: big cleanup, much simpler, better HZ accuracy. * |