]> err.no Git - linux-2.6/commitdiff
[AVR32] Change system timer from count-compare to Timer/Counter 0
authorHans-Christian Egtvedt <hcegtvedt@atmel.com>
Mon, 12 Mar 2007 17:15:16 +0000 (18:15 +0100)
committerHaavard Skinnemoen <hskinnemoen@atmel.com>
Fri, 27 Apr 2007 11:44:12 +0000 (13:44 +0200)
Due to limitation of the count-compare system timer (not able to
count when CPU is in sleep), the system timer had to be changed to
use a peripheral timer/counter.

The old COUNT-COMPARE code is still present in time.c as weak
functions. The new timer is added to the architecture directory.

This patch sets up TC0 as system timer The new timer has been tested
on AT32AP7000/ATSTK1000 at 100 Hz, 250 Hz, 300 Hz and 1000 Hz.

For more details about the timer/counter see the datasheet for
AT32AP700x available at

http://www.atmel.com/dyn/products/product_card.asp?part_id=3903

Signed-off-by: Hans-Christian Egtvedt <hcegtvedt@atmel.com>
Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
arch/avr32/kernel/time.c
arch/avr32/mach-at32ap/Makefile
arch/avr32/mach-at32ap/at32ap7000.c
arch/avr32/mach-at32ap/time-tc.c [new file with mode: 0644]
include/asm-avr32/arch-at32ap/time.h [new file with mode: 0644]

index c10833f2ee0ce0e1dea60d255e64e07f13f14c48..7014a3571ec024a5cdccc4161817ceb73401013b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2004-2006 Atmel Corporation
+ * Copyright (C) 2004-2007 Atmel Corporation
  *
  * Based on MIPS implementation arch/mips/kernel/time.c
  *   Copyright 2001 MontaVista Software Inc.
 #include <linux/init.h>
 #include <linux/profile.h>
 #include <linux/sysdev.h>
+#include <linux/err.h>
 
 #include <asm/div64.h>
 #include <asm/sysreg.h>
 #include <asm/io.h>
 #include <asm/sections.h>
 
-static cycle_t read_cycle_count(void)
+/* how many counter cycles in a jiffy? */
+static u32 cycles_per_jiffy;
+
+/* the count value for the next timer interrupt */
+static u32 expirelo;
+
+cycle_t __weak read_cycle_count(void)
 {
        return (cycle_t)sysreg_read(COUNT);
 }
 
-static struct clocksource clocksource_avr32 = {
+struct clocksource __weak clocksource_avr32 = {
        .name           = "avr32",
        .rating         = 350,
        .read           = read_cycle_count,
@@ -40,12 +47,20 @@ static struct clocksource clocksource_avr32 = {
        .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
 };
 
+irqreturn_t __weak timer_interrupt(int irq, void *dev_id);
+
+struct irqaction timer_irqaction = {
+       .handler        = timer_interrupt,
+       .flags          = IRQF_DISABLED,
+       .name           = "timer",
+};
+
 /*
  * By default we provide the null RTC ops
  */
 static unsigned long null_rtc_get_time(void)
 {
-       return mktime(2004, 1, 1, 0, 0, 0);
+       return mktime(2007, 1, 1, 0, 0, 0);
 }
 
 static int null_rtc_set_time(unsigned long sec)
@@ -56,23 +71,14 @@ static int null_rtc_set_time(unsigned long sec)
 static unsigned long (*rtc_get_time)(void) = null_rtc_get_time;
 static int (*rtc_set_time)(unsigned long) = null_rtc_set_time;
 
-/* how many counter cycles in a jiffy? */
-static unsigned long cycles_per_jiffy;
-
-/* cycle counter value at the previous timer interrupt */
-static unsigned int timerhi, timerlo;
-
-/* the count value for the next timer interrupt */
-static unsigned int expirelo;
-
 static void avr32_timer_ack(void)
 {
-       unsigned int count;
+       u32 count;
 
        /* Ack this timer interrupt and set the next one */
        expirelo += cycles_per_jiffy;
+       /* setting COMPARE to 0 stops the COUNT-COMPARE */
        if (expirelo == 0) {
-               printk(KERN_DEBUG "expirelo == 0\n");
                sysreg_write(COMPARE, expirelo + 1);
        } else {
                sysreg_write(COMPARE, expirelo);
@@ -86,27 +92,56 @@ static void avr32_timer_ack(void)
        }
 }
 
-static unsigned int avr32_hpt_read(void)
+int __weak avr32_hpt_init(void)
 {
-       return sysreg_read(COUNT);
+       int ret;
+       unsigned long mult, shift, count_hz;
+
+       count_hz = clk_get_rate(boot_cpu_data.clk);
+       shift = clocksource_avr32.shift;
+       mult = clocksource_hz2mult(count_hz, shift);
+       clocksource_avr32.mult = mult;
+
+       {
+               u64 tmp;
+
+               tmp = TICK_NSEC;
+               tmp <<= shift;
+               tmp += mult / 2;
+               do_div(tmp, mult);
+
+               cycles_per_jiffy = tmp;
+       }
+
+       ret = setup_irq(0, &timer_irqaction);
+       if (ret) {
+               pr_debug("timer: could not request IRQ 0: %d\n", ret);
+               return -ENODEV;
+       }
+
+       printk(KERN_INFO "timer: AT32AP COUNT-COMPARE at irq 0, "
+                       "%lu.%03lu MHz\n",
+                       ((count_hz + 500) / 1000) / 1000,
+                       ((count_hz + 500) / 1000) % 1000);
+
+       return 0;
 }
 
 /*
  * Taken from MIPS c0_hpt_timer_init().
  *
- * Why is it so complicated, and what is "count"?  My assumption is
- * that `count' specifies the "reference cycle", i.e. the cycle since
- * reset that should mean "zero". The reason COUNT is written twice is
- * probably to make sure we don't get any timer interrupts while we
- * are messing with the counter.
+ * The reason COUNT is written twice is probably to make sure we don't get any
+ * timer interrupts while we are messing with the counter.
  */
-static void avr32_hpt_init(unsigned int count)
+int __weak avr32_hpt_start(void)
 {
-       count = sysreg_read(COUNT) - count;
+       u32 count = sysreg_read(COUNT);
        expirelo = (count / cycles_per_jiffy + 1) * cycles_per_jiffy;
        sysreg_write(COUNT, expirelo - cycles_per_jiffy);
        sysreg_write(COMPARE, expirelo);
        sysreg_write(COUNT, count);
+
+       return 0;
 }
 
 /*
@@ -115,26 +150,18 @@ static void avr32_hpt_init(unsigned int count)
  *
  * In UP mode, it is invoked from the (global) timer_interrupt.
  */
-static void local_timer_interrupt(int irq, void *dev_id)
+void local_timer_interrupt(int irq, void *dev_id)
 {
        if (current->pid)
                profile_tick(CPU_PROFILING);
        update_process_times(user_mode(get_irq_regs()));
 }
 
-static irqreturn_t
-timer_interrupt(int irq, void *dev_id)
+irqreturn_t __weak timer_interrupt(int irq, void *dev_id)
 {
-       unsigned int count;
-
        /* ack timer interrupt and try to set next interrupt */
-       count = avr32_hpt_read();
        avr32_timer_ack();
 
-       /* Update timerhi/timerlo for intra-jiffy calibration */
-       timerhi += count < timerlo;     /* Wrap around */
-       timerlo = count;
-
        /*
         * Call the generic timer interrupt handler
         */
@@ -153,60 +180,37 @@ timer_interrupt(int irq, void *dev_id)
        return IRQ_HANDLED;
 }
 
-static struct irqaction timer_irqaction = {
-       .handler        = timer_interrupt,
-       .flags          = IRQF_DISABLED,
-       .name           = "timer",
-};
-
 void __init time_init(void)
 {
-       unsigned long mult, shift, count_hz;
        int ret;
 
+       /*
+        * Make sure we don't get any COMPARE interrupts before we can
+        * handle them.
+        */
+       sysreg_write(COMPARE, 0);
+
        xtime.tv_sec = rtc_get_time();
        xtime.tv_nsec = 0;
 
        set_normalized_timespec(&wall_to_monotonic,
                                -xtime.tv_sec, -xtime.tv_nsec);
 
-       printk("Before time_init: count=%08lx, compare=%08lx\n",
-              (unsigned long)sysreg_read(COUNT),
-              (unsigned long)sysreg_read(COMPARE));
-
-       count_hz = clk_get_rate(boot_cpu_data.clk);
-       shift = clocksource_avr32.shift;
-       mult = clocksource_hz2mult(count_hz, shift);
-       clocksource_avr32.mult = mult;
-
-       printk("Cycle counter: mult=%lu, shift=%lu\n", mult, shift);
-
-       {
-               u64 tmp;
-
-               tmp = TICK_NSEC;
-               tmp <<= shift;
-               tmp += mult / 2;
-               do_div(tmp, mult);
-
-               cycles_per_jiffy = tmp;
+       ret = avr32_hpt_init();
+       if (ret) {
+               pr_debug("timer: failed setup: %d\n", ret);
+               return;
        }
 
-       /* This sets up the high precision timer for the first interrupt. */
-       avr32_hpt_init(avr32_hpt_read());
-
-       printk("After time_init: count=%08lx, compare=%08lx\n",
-              (unsigned long)sysreg_read(COUNT),
-              (unsigned long)sysreg_read(COMPARE));
-
        ret = clocksource_register(&clocksource_avr32);
        if (ret)
-               printk(KERN_ERR
-                      "timer: could not register clocksource: %d\n", ret);
+               pr_debug("timer: could not register clocksource: %d\n", ret);
 
-       ret = setup_irq(0, &timer_irqaction);
-       if (ret)
-               printk("timer: could not request IRQ 0: %d\n", ret);
+       ret = avr32_hpt_start();
+       if (ret) {
+               pr_debug("timer: failed starting: %d\n", ret);
+               return;
+       }
 }
 
 static struct sysdev_class timer_class = {
index b21bea9af8b11000ddccb6a38355f75809984d6c..f1d395724ac63aed5ba6c6731c07e3aa8ea31e0f 100644 (file)
@@ -1,2 +1,3 @@
 obj-y                          += at32ap.o clock.o intc.o extint.o pio.o hsmc.o
 obj-$(CONFIG_CPU_AT32AP7000)   += at32ap7000.o
+obj-$(CONFIG_CPU_AT32AP7000)   += time-tc.o
index 32c7045141c2512ef5ae94d05d275b5cd0755f0b..6eeda60b8288662321ad6a78f784bbeb4846b7fa 100644 (file)
@@ -503,6 +503,21 @@ static inline void set_ebi_sfr_bits(u32 mask)
        clk_disable(&hmatrix_clk);
 }
 
+/* --------------------------------------------------------------------
+ *  System Timer/Counter (TC)
+ * -------------------------------------------------------------------- */
+static struct resource at32_systc0_resource[] = {
+       PBMEM(0xfff00c00),
+       IRQ(22),
+};
+struct platform_device at32_systc0_device = {
+       .name           = "systc",
+       .id             = 0,
+       .resource       = at32_systc0_resource,
+       .num_resources  = ARRAY_SIZE(at32_systc0_resource),
+};
+DEV_CLK(pclk, at32_systc0, pbb, 3);
+
 /* --------------------------------------------------------------------
  *  PIO
  * -------------------------------------------------------------------- */
@@ -551,6 +566,8 @@ void __init at32_add_system_devices(void)
        platform_device_register(&smc0_device);
        platform_device_register(&pdc_device);
 
+       platform_device_register(&at32_systc0_device);
+
        platform_device_register(&pio0_device);
        platform_device_register(&pio1_device);
        platform_device_register(&pio2_device);
@@ -1000,6 +1017,7 @@ struct clk *at32_clock_list[] = {
        &pio2_mck,
        &pio3_mck,
        &pio4_mck,
+       &at32_systc0_pclk,
        &atmel_usart0_usart,
        &atmel_usart1_usart,
        &atmel_usart2_usart,
diff --git a/arch/avr32/mach-at32ap/time-tc.c b/arch/avr32/mach-at32ap/time-tc.c
new file mode 100644 (file)
index 0000000..7ac0e94
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2004-2007 Atmel Corporation
+ *
+ * Based on MIPS implementation arch/mips/kernel/time.c
+ *   Copyright 2001 MontaVista Software Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel_stat.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/profile.h>
+#include <linux/sysdev.h>
+#include <linux/err.h>
+
+#include <asm/div64.h>
+#include <asm/sysreg.h>
+#include <asm/io.h>
+#include <asm/sections.h>
+
+#include <asm/arch/time.h>
+
+/* how many counter cycles in a jiffy? */
+static u32 cycles_per_jiffy;
+
+/* the count value for the next timer interrupt */
+static u32 expirelo;
+
+/* the I/O registers of the TC module */
+static void __iomem *ioregs;
+
+cycle_t read_cycle_count(void)
+{
+       return (cycle_t)timer_read(ioregs, 0, CV);
+}
+
+struct clocksource clocksource_avr32 = {
+       .name           = "avr32",
+       .rating         = 342,
+       .read           = read_cycle_count,
+       .mask           = CLOCKSOURCE_MASK(16),
+       .shift          = 16,
+       .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static void avr32_timer_ack(void)
+{
+       u16 count = expirelo;
+
+       /* Ack this timer interrupt and set the next one, use a u16
+        * variable so it will wrap around correctly */
+       count += cycles_per_jiffy;
+       expirelo = count;
+       timer_write(ioregs, 0, RC, expirelo);
+
+       /* Check to see if we have missed any timer interrupts */
+       count = timer_read(ioregs, 0, CV);
+       if ((count - expirelo) < 0x7fff) {
+               expirelo = count + cycles_per_jiffy;
+               timer_write(ioregs, 0, RC, expirelo);
+       }
+}
+
+u32 avr32_hpt_read(void)
+{
+       return timer_read(ioregs, 0, CV);
+}
+
+static int avr32_timer_calc_div_and_set_jiffies(struct clk *pclk)
+{
+       unsigned int cycles_max = (clocksource_avr32.mask + 1) / 2;
+       unsigned int divs[] = { 4, 8, 16, 32 };
+       int divs_size = sizeof(divs) / sizeof(*divs);
+       int i = 0;
+       unsigned long count_hz;
+       unsigned long shift;
+       unsigned long mult;
+       int clock_div = -1;
+       u64 tmp;
+
+       shift = clocksource_avr32.shift;
+
+       do {
+               count_hz = clk_get_rate(pclk) / divs[i];
+               mult = clocksource_hz2mult(count_hz, shift);
+               clocksource_avr32.mult = mult;
+
+               tmp = TICK_NSEC;
+               tmp <<= shift;
+               tmp += mult / 2;
+               do_div(tmp, mult);
+
+               cycles_per_jiffy = tmp;
+       } while (cycles_per_jiffy > cycles_max && ++i < divs_size);
+
+       clock_div = i + 1;
+
+       if (clock_div > divs_size) {
+               pr_debug("timer: could not calculate clock divider\n");
+               return -EFAULT;
+       }
+
+       /* Set the clock divider */
+       timer_write(ioregs, 0, CMR, TIMER_BF(CMR_TCCLKS, clock_div));
+
+       return 0;
+}
+
+int avr32_hpt_init(unsigned int count)
+{
+       struct resource *regs;
+       struct clk *pclk;
+       int irq = -1;
+       int ret = 0;
+
+       ret = -ENXIO;
+
+       irq = platform_get_irq(&at32_systc0_device, 0);
+       if (irq < 0) {
+               pr_debug("timer: could not get irq\n");
+               goto out_error;
+       }
+
+       pclk = clk_get(&at32_systc0_device.dev, "pclk");
+       if (IS_ERR(pclk)) {
+               pr_debug("timer: could not get clk: %ld\n", PTR_ERR(pclk));
+               goto out_error;
+       }
+
+       regs = platform_get_resource(&at32_systc0_device, IORESOURCE_MEM, 0);
+       if (!regs) {
+               pr_debug("timer: could not get resource\n");
+               goto out_error_clk;
+       }
+
+       ioregs = ioremap(regs->start, regs->end - regs->start + 1);
+       if (!ioregs) {
+               pr_debug("timer: could not get ioregs\n");
+               goto out_error_clk;
+       }
+
+       ret = avr32_timer_calc_div_and_set_jiffies(pclk);
+       if (ret)
+               goto out_error_io;
+
+       ret = setup_irq(irq, &timer_irqaction);
+       if (ret) {
+               pr_debug("timer: could not request irq %d: %d\n",
+                               irq, ret);
+               goto out_error_io;
+       }
+
+       expirelo = (timer_read(ioregs, 0, CV) / cycles_per_jiffy + 1)
+               * cycles_per_jiffy;
+
+       /* Enable clock and interrupts on RC compare */
+       timer_write(ioregs, 0, CCR, TIMER_BIT(CCR_CLKEN));
+       timer_write(ioregs, 0, IER, TIMER_BIT(IER_CPCS));
+       /* Set cycles to first interrupt */
+       timer_write(ioregs, 0,  RC, expirelo);
+
+       printk(KERN_INFO "timer: AT32AP system timer/counter at 0x%p irq %d\n",
+                       ioregs, irq);
+
+       return 0;
+
+out_error_io:
+       iounmap(ioregs);
+out_error_clk:
+       clk_put(pclk);
+out_error:
+       return ret;
+}
+
+int avr32_hpt_start(void)
+{
+       timer_write(ioregs, 0, CCR, TIMER_BIT(CCR_SWTRG));
+       return 0;
+}
+
+irqreturn_t timer_interrupt(int irq, void *dev_id)
+{
+       unsigned int sr = timer_read(ioregs, 0, SR);
+
+       if (sr & TIMER_BIT(SR_CPCS)) {
+               /* ack timer interrupt and try to set next interrupt */
+               avr32_timer_ack();
+
+               /*
+                * Call the generic timer interrupt handler
+                */
+               write_seqlock(&xtime_lock);
+               do_timer(1);
+               write_sequnlock(&xtime_lock);
+
+               /*
+                * In UP mode, we call local_timer_interrupt() to do profiling
+                * and process accounting.
+                *
+                * SMP is not supported yet.
+                */
+               local_timer_interrupt(irq, dev_id);
+
+               return IRQ_HANDLED;
+       }
+
+       return IRQ_NONE;
+}
diff --git a/include/asm-avr32/arch-at32ap/time.h b/include/asm-avr32/arch-at32ap/time.h
new file mode 100644 (file)
index 0000000..cc8a434
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _ASM_AVR32_ARCH_AT32AP_TIME_H
+#define _ASM_AVR32_ARCH_AT32AP_TIME_H
+
+#include <linux/platform_device.h>
+
+extern struct irqaction timer_irqaction;
+extern struct platform_device at32_systc0_device;
+extern void local_timer_interrupt(int irq, void *dev_id);
+
+#define TIMER_BCR                                      0x000000c0
+#define TIMER_BCR_SYNC                                          0
+#define TIMER_BMR                                      0x000000c4
+#define TIMER_BMR_TC0XC0S                                       0
+#define TIMER_BMR_TC1XC1S                                       2
+#define TIMER_BMR_TC2XC2S                                       4
+#define TIMER_CCR                                      0x00000000
+#define TIMER_CCR_CLKDIS                                        1
+#define TIMER_CCR_CLKEN                                                 0
+#define TIMER_CCR_SWTRG                                                 2
+#define TIMER_CMR                                      0x00000004
+#define TIMER_CMR_ABETRG                                       10
+#define TIMER_CMR_ACPA                                         16
+#define TIMER_CMR_ACPC                                         18
+#define TIMER_CMR_AEEVT                                                20
+#define TIMER_CMR_ASWTRG                                       22
+#define TIMER_CMR_BCPB                                         24
+#define TIMER_CMR_BCPC                                         26
+#define TIMER_CMR_BEEVT                                                28
+#define TIMER_CMR_BSWTRG                                       30
+#define TIMER_CMR_BURST                                                 4
+#define TIMER_CMR_CLKI                                          3
+#define TIMER_CMR_CPCDIS                                        7
+#define TIMER_CMR_CPCSTOP                                       6
+#define TIMER_CMR_CPCTRG                                       14
+#define TIMER_CMR_EEVT                                         10
+#define TIMER_CMR_EEVTEDG                                       8
+#define TIMER_CMR_ENETRG                                       12
+#define TIMER_CMR_ETRGEDG                                       8
+#define TIMER_CMR_LDBDIS                                        7
+#define TIMER_CMR_LDBSTOP                                       6
+#define TIMER_CMR_LDRA                                         16
+#define TIMER_CMR_LDRB                                         18
+#define TIMER_CMR_TCCLKS                                        0
+#define TIMER_CMR_WAVE                                         15
+#define TIMER_CMR_WAVSEL                                       13
+#define TIMER_CV                                       0x00000010
+#define TIMER_CV_CV                                             0
+#define TIMER_IDR                                      0x00000028
+#define TIMER_IDR_COVFS                                                 0
+#define TIMER_IDR_CPAS                                          2
+#define TIMER_IDR_CPBS                                          3
+#define TIMER_IDR_CPCS                                          4
+#define TIMER_IDR_ETRGS                                                 7
+#define TIMER_IDR_LDRAS                                                 5
+#define TIMER_IDR_LDRBS                                                 6
+#define TIMER_IDR_LOVRS                                                 1
+#define TIMER_IER                                      0x00000024
+#define TIMER_IER_COVFS                                                 0
+#define TIMER_IER_CPAS                                          2
+#define TIMER_IER_CPBS                                          3
+#define TIMER_IER_CPCS                                          4
+#define TIMER_IER_ETRGS                                                 7
+#define TIMER_IER_LDRAS                                                 5
+#define TIMER_IER_LDRBS                                                 6
+#define TIMER_IER_LOVRS                                                 1
+#define TIMER_IMR                                      0x0000002c
+#define TIMER_IMR_COVFS                                                 0
+#define TIMER_IMR_CPAS                                          2
+#define TIMER_IMR_CPBS                                          3
+#define TIMER_IMR_CPCS                                          4
+#define TIMER_IMR_ETRGS                                                 7
+#define TIMER_IMR_LDRAS                                                 5
+#define TIMER_IMR_LDRBS                                                 6
+#define TIMER_IMR_LOVRS                                                 1
+#define TIMER_RA                                       0x00000014
+#define TIMER_RA_RA                                             0
+#define TIMER_RB                                       0x00000018
+#define TIMER_RB_RB                                             0
+#define TIMER_RC                                       0x0000001c
+#define TIMER_RC_RC                                             0
+#define TIMER_SR                                       0x00000020
+#define TIMER_SR_CLKSTA                                                16
+#define TIMER_SR_COVFS                                          0
+#define TIMER_SR_CPAS                                           2
+#define TIMER_SR_CPBS                                           3
+#define TIMER_SR_CPCS                                           4
+#define TIMER_SR_ETRGS                                          7
+#define TIMER_SR_LDRAS                                          5
+#define TIMER_SR_LDRBS                                          6
+#define TIMER_SR_LOVRS                                          1
+#define TIMER_SR_MTIOA                                         17
+#define TIMER_SR_MTIOB                                         18
+
+/* Bit manipulation macros */
+#define TIMER_BIT(name)                (1 << TIMER_##name)
+#define TIMER_BF(name,value)   ((value) << TIMER_##name)
+
+/* Register access macros */
+#define timer_read(port,instance,reg) \
+       __raw_readl(port + (0x40 * instance) + TIMER_##reg)
+#define timer_write(port,instance,reg,value) \
+       __raw_writel((value), port + (0x40 * instance) + TIMER_##reg)
+
+#endif /* _ASM_AVR32_ARCH_AT32AP_TIME_H */