diff options
Diffstat (limited to 'sys/arm/econa/timer.c')
-rw-r--r-- | sys/arm/econa/timer.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/sys/arm/econa/timer.c b/sys/arm/econa/timer.c new file mode 100644 index 0000000..e7cca79 --- /dev/null +++ b/sys/arm/econa/timer.c @@ -0,0 +1,382 @@ +/*- + * Copyright (c) 2009 Yohanes Nugroho <yohanes@gmail.com>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/rman.h> +#include <sys/timetc.h> +#include <sys/watchdog.h> +#include <machine/bus.h> +#include <machine/cpu.h> +#include <machine/frame.h> +#include <machine/intr.h> + +#include "econa_reg.h" +#include "econa_var.h" + +#define INITIAL_TIMECOUNTER (0xffffffff) + +static int timers_initialized = 0; + +#define HZ 100 + +extern unsigned int CPU_clock; +extern unsigned int AHB_clock; +extern unsigned int APB_clock; + +static unsigned long timer_counter = 0; + +struct ec_timer_softc { + struct resource * timer_res[3]; + bus_space_tag_t timer_bst; + bus_space_handle_t timer_bsh; + struct mtx timer_mtx; +}; + +static struct resource_spec ec_timer_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 1, RF_ACTIVE }, + { -1, 0 } +}; + +static unsigned ec_timer_get_timecount(struct timecounter *); + +static struct timecounter ec_timecounter = { + .tc_get_timecount = ec_timer_get_timecount, + .tc_name = "CPU Timer", + /* This is assigned on the fly in the init sequence */ + .tc_frequency = 0, + .tc_counter_mask = ~0u, + .tc_quality = 1000, +}; + +static struct ec_timer_softc *timer_softc = NULL; + +static inline +void write_4(unsigned int val, unsigned int addr) +{ + bus_space_write_4(timer_softc->timer_bst, + timer_softc->timer_bsh, addr, val); + +} + +static inline +unsigned int read_4(unsigned int addr) +{ + + return bus_space_read_4(timer_softc->timer_bst, + timer_softc->timer_bsh, addr); +} + +#define uSECS_PER_TICK (1000000 / APB_clock) +#define TICKS2USECS(x) ((x) * uSECS_PER_TICK) + +static unsigned +read_timer_counter_noint(void) +{ + + arm_mask_irq(0); + unsigned int v = read_4(TIMER_TM1_COUNTER_REG); + arm_unmask_irq(0); + return v; +} + +void +DELAY(int usec) +{ + uint32_t val, val_temp; + int nticks; + + if (!timers_initialized) { + for (; usec > 0; usec--) + for (val = 100; val > 0; val--) + ; + return; + } + + val = read_timer_counter_noint(); + nticks = (((APB_clock / 1000) * usec) / 1000) + 100; + + while (nticks > 0) { + val_temp = read_timer_counter_noint(); + if (val > val_temp) + nticks -= (val - val_temp); + else + nticks -= (val + (timer_counter - val_temp)); + + val = val_temp; + } + +} + +/* + * Setup timer + */ +static inline void +setup_timer(unsigned int counter_value) +{ + unsigned int control_value; + unsigned int mask_value; + + control_value = read_4(TIMER_TM_CR_REG); + + mask_value = read_4(TIMER_TM_INTR_MASK_REG); + write_4(counter_value, TIMER_TM1_COUNTER_REG); + write_4(counter_value, TIMER_TM1_LOAD_REG); + write_4(0, TIMER_TM1_MATCH1_REG); + write_4(0,TIMER_TM1_MATCH2_REG); + + control_value &= ~(TIMER1_CLOCK_SOURCE); + control_value |= TIMER1_UP_DOWN_COUNT; + + write_4(0, TIMER_TM2_COUNTER_REG); + write_4(0, TIMER_TM2_LOAD_REG); + write_4(~0u, TIMER_TM2_MATCH1_REG); + write_4(~0u,TIMER_TM2_MATCH2_REG); + + control_value &= ~(TIMER2_CLOCK_SOURCE); + control_value &= ~(TIMER2_UP_DOWN_COUNT); + + mask_value &= ~(63); + + write_4(control_value, TIMER_TM_CR_REG); + write_4(mask_value, TIMER_TM_INTR_MASK_REG); +} + +/* + * Enable timer + */ +static inline void +timer_enable(void) +{ + unsigned int control_value; + + control_value = read_4(TIMER_TM_CR_REG); + + control_value |= TIMER1_OVERFLOW_ENABLE; + control_value |= TIMER1_ENABLE; + control_value |= TIMER2_OVERFLOW_ENABLE; + control_value |= TIMER2_ENABLE; + + write_4(control_value, TIMER_TM_CR_REG); +} + +static inline unsigned int +read_second_timer_counter(void) +{ + + return read_4(TIMER_TM2_COUNTER_REG); +} + +/* + * Get timer interrupt status + */ +static inline unsigned int +read_timer_interrupt_status(void) +{ + + return read_4(TIMER_TM_INTR_STATUS_REG); +} + +/* + * Clear timer interrupt status + */ +static inline void +clear_timer_interrupt_status(unsigned int irq) +{ + unsigned int interrupt_status; + + interrupt_status = read_4(TIMER_TM_INTR_STATUS_REG); + if (irq == 0) { + if (interrupt_status & (TIMER1_MATCH1_INTR)) + interrupt_status &= ~(TIMER1_MATCH1_INTR); + if (interrupt_status & (TIMER1_MATCH2_INTR)) + interrupt_status &= ~(TIMER1_MATCH2_INTR); + if (interrupt_status & (TIMER1_OVERFLOW_INTR)) + interrupt_status &= ~(TIMER1_OVERFLOW_INTR); + } + if (irq == 1) { + if (interrupt_status & (TIMER2_MATCH1_INTR)) + interrupt_status &= ~(TIMER2_MATCH1_INTR); + if (interrupt_status & (TIMER2_MATCH2_INTR)) + interrupt_status &= ~(TIMER2_MATCH2_INTR); + if (interrupt_status & (TIMER2_OVERFLOW_INTR)) + interrupt_status &= ~(TIMER2_OVERFLOW_INTR); + } + + write_4(interrupt_status, TIMER_TM_INTR_STATUS_REG); +} + +static unsigned +ec_timer_get_timecount(struct timecounter *a) +{ + unsigned int ticks1; + arm_mask_irq(1); + ticks1 = read_second_timer_counter(); + arm_unmask_irq(1); + return ticks1; +} + +/* + * Setup timer + */ +static inline void +do_setup_timer(void) +{ + + timer_counter = APB_clock/HZ; + /* + * setup timer-related values + */ + setup_timer(timer_counter); +} + +void +cpu_initclocks(void) +{ + + ec_timecounter.tc_frequency = APB_clock; + tc_init(&ec_timecounter); + timer_enable(); + timers_initialized = 1; +} + +void +cpu_startprofclock(void) +{ + +} + +void +cpu_stopprofclock(void) +{ + +} + +static int +ec_timer_probe(device_t dev) +{ + + device_set_desc(dev, "Econa CPU Timer"); + return (0); +} + +static int +ec_reset(void *arg) +{ + + arm_mask_irq(1); + clear_timer_interrupt_status(1); + arm_unmask_irq(1); + return (FILTER_HANDLED); +} + +static int +ec_hardclock(void *arg) +{ + struct trapframe *frame; + unsigned int val; + /*clear timer interrupt status*/ + + arm_mask_irq(0); + + val = read_4(TIMER_INTERRUPT_STATUS_REG); + val &= ~(TIMER1_OVERFLOW_INTERRUPT); + write_4(val, TIMER_INTERRUPT_STATUS_REG); + + frame = (struct trapframe *)arg; + hardclock(TRAPF_USERMODE(frame), TRAPF_PC(frame)); + + arm_unmask_irq(0); + + return (FILTER_HANDLED); +} + +static int +ec_timer_attach(device_t dev) +{ + struct ec_timer_softc *sc; + int error; + void *ihl; + + + if (timer_softc != NULL) + return (ENXIO); + + sc = (struct ec_timer_softc *)device_get_softc(dev); + + timer_softc = sc; + + error = bus_alloc_resources(dev, ec_timer_spec, sc->timer_res); + if (error) { + device_printf(dev, "could not allocate resources\n"); + return (ENXIO); + } + + sc->timer_bst = rman_get_bustag(sc->timer_res[0]); + sc->timer_bsh = rman_get_bushandle(sc->timer_res[0]); + + do_setup_timer(); + + if (bus_setup_intr(dev, sc->timer_res[1], INTR_TYPE_CLK, + ec_hardclock, NULL, NULL, &ihl) != 0) { + bus_release_resources(dev, ec_timer_spec, sc->timer_res); + device_printf(dev, "could not setup hardclock interrupt\n"); + return (ENXIO); + } + + if (bus_setup_intr(dev, sc->timer_res[2], INTR_TYPE_CLK, + ec_reset, NULL, NULL, &ihl) != 0) { + bus_release_resources(dev, ec_timer_spec, sc->timer_res); + device_printf(dev, "could not setup timer interrupt\n"); + return (ENXIO); + } + + return (0); +} + +static device_method_t ec_timer_methods[] = { + DEVMETHOD(device_probe, ec_timer_probe), + DEVMETHOD(device_attach, ec_timer_attach), + { 0, 0 } +}; + +static driver_t ec_timer_driver = { + "timer", + ec_timer_methods, + sizeof(struct ec_timer_softc), +}; + +static devclass_t ec_timer_devclass; + +DRIVER_MODULE(timer, econaarm, ec_timer_driver, ec_timer_devclass, 0, 0); |