/* This file contains the driver for the GRLIB GPTIMER timers port. The driver * is implemented by using the tlib.c simple timer layer and the Driver * Manager. * * The Driver can be configured using driver resources: * * - timerStart Timer Index if first Timer, this parameters is typically used * in AMP systems for resource allocation. The Timers before * timerStart will not be accessed. * - timerCnt Number of timers that the driver will use, this parameters is * typically used in AMP systems for resource allocation between * OS instances. * - prescaler Base prescaler, normally set by bootloader but can be * overridden. The default scaler reload value set by bootloader * is so that Timers operate in 1MHz. Setting the prescaler to a * lower value increase the accuracy of the timers but shortens * the time until underflow happens. * - clockTimer Used to select a particular timer to be the system clock * timer. This is useful when multiple GPTIMERs cores are * available, or in AMP systems. By default the TLIB selects the * first timer registered as system clock timer. * * The BSP define APBUART_INFO_AVAIL in order to add the info routine * used for debugging. * * COPYRIGHT (c) 2010. * Cobham Gaisler AB. * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.com/license/LICENSE. */ #include #include #include #include #include #include #include #include "tlib.h" #if defined(LEON3) && defined(RTEMS_DRVMGR_STARTUP) #include volatile struct gptimer_regs *LEON3_Timer_Regs = 0; #endif #ifdef GPTIMER_INFO_AVAIL #include #endif /* GPTIMER Core Configuration Register (READ-ONLY) */ #define GPTIMER_CFG_TIMERS_BIT 0 #define GPTIMER_CFG_IRQ_BIT 3 #define GPTIMER_CFG_SI_BIT 8 #define GPTIMER_CFG_DF_BIT 9 #define GPTIMER_CFG_TIMERS (0x7<businfo; if ( ambadev == NULL ) { return -1; } pnpinfo = &ambadev->info; regs = (struct gptimer_regs *)pnpinfo->apb_slv->start; DBG("GPTIMER[%d] on bus %s\n", dev->minor_drv, dev->parent->dev->name); /* Get number of Timers */ timer_hw_cnt = regs->cfg & GPTIMER_CFG_TIMERS; /* Let user spelect a range of timers to be used. In AMP systems * it is sometimes neccessary to leave timers for other CPU instances. * * The default operation in AMP is to shared the timers within the * first GPTIMER core as below. This can of course be overrided by * driver resources. */ timer_cnt = timer_hw_cnt; timer_start = 0; #if defined(RTEMS_MULTIPROCESSING) && defined(LEON3) if ((dev->minor_drv == 0) && drvmgr_on_rootbus(dev)) { timer_cnt = 1; timer_start = LEON3_Cpu_Index; } #endif value = drvmgr_dev_key_get(dev, "timerStart", KEY_TYPE_INT); if ( value) { timer_start = value->i; timer_cnt = timer_hw_cnt - timer_start; } value = drvmgr_dev_key_get(dev, "timerCnt", KEY_TYPE_INT); if ( value && (value->i < timer_cnt) ) { timer_cnt = value->i; } /* Allocate Common Timer Description, size depends on how many timers * are present. */ size = sizeof(struct gptimer_priv) + timer_cnt*sizeof(struct gptimer_timer); priv = dev->priv = (struct gptimer_priv *)malloc(size); if ( !priv ) return DRVMGR_NOMEM; memset(priv, 0, size); priv->dev = dev; priv->regs = regs; #if defined(LEON3) && defined(RTEMS_DRVMGR_STARTUP) if ( drvmgr_on_rootbus(priv->dev) && !LEON3_Timer_Regs) { /* Bootloader has initialized the Timer prescaler to 1MHz, * this means that the AMBA Frequency is 1MHz * PRESCALER. */ priv->base_clk = (regs->scaler_reload + 1) * 1000000; ambapp_bus_freq_register(priv->dev,DEV_APB_SLV,priv->base_clk); LEON3_Timer_Regs = (void *)regs; } else #endif { /* The Base Frequency of the GPTIMER core is the same as the * frequency of the AMBA bus it is situated on. */ drvmgr_freq_get(dev, DEV_APB_SLV, &priv->base_clk); } /* This core will may provide important Timer functionality * to other drivers and the RTEMS kernel, the Clock driver * may for example use this device. So the Timer driver must be * initialized in the first iiitialization stage. */ /*** Initialize Hardware ***/ /* If user request to set prescaler, we will do that. However, note * that doing so for the Root-Bus GPTIMER may affect the RTEMS Clock * so that Clock frequency is wrong. */ value = drvmgr_dev_key_get(priv->dev, "prescaler", KEY_TYPE_INT); if ( value ) regs->scaler_reload = value->i; /* Get Frequency that the timers are operating in (after prescaler) */ priv->base_freq = priv->base_clk / (priv->regs->scaler_reload + 1); /* Stop Timer and probe Pending bit. In newer hardware the * timer has pending bit is cleared by writing a one to it, * whereas older versions it is cleared with a zero. */ priv->regs->timer[timer_start].ctrl = GPTIMER_CTRL_IP; if ((priv->regs->timer[timer_start].ctrl & GPTIMER_CTRL_IP) != 0) irq_ack_mask = ~GPTIMER_CTRL_IP; else irq_ack_mask = ~0; priv->timer_cnt = timer_cnt; for (i=0; itimers[i]; timer->index = i; timer->tindex = i + timer_start; timer->tregs = ®s->timer[(int)timer->tindex]; timer->tdev.drv = &gptimer_tlib_drv; timer->irq_ack_mask = irq_ack_mask; /* Register Timer at Timer Library */ #if defined(LEON3) && defined(RTEMS_DRVMGR_STARTUP) timer_index[i] = #endif tlib_dev_reg(&timer->tdev); } /* Check Interrupt support implementation, two cases: * A. All Timers share one IRQ * B. Each Timer have an individual IRQ. The number is: * BASE_IRQ + timer_index */ priv->separate_interrupt = regs->cfg & GPTIMER_CFG_SI; if ( priv->separate_interrupt == 0 ) { /* Shared IRQ handler */ drvmgr_interrupt_register( priv->dev, 0, "gptimer_shared", gptimer_isr, priv); } /* Older HW */ /* If the user request a certain Timer to be the RTEMS Clock Timer, * the timer must be registered at the Clock Driver. */ #if defined(LEON3) && defined(RTEMS_DRVMGR_STARTUP) value = drvmgr_dev_key_get(priv->dev, "clockTimer", KEY_TYPE_INT); if ( value && (value->i < timer_cnt) ) { LEON3_Timer_Regs = (void *)regs; Clock_timer_register(timer_index[value->i]); } #endif return DRVMGR_OK; } #ifdef GPTIMER_INFO_AVAIL static int gptimer_info( struct drvmgr_dev *dev, void (*print_line)(void *p, char *str), void *p, int argc, char *argv[]) { struct gptimer_priv *priv = dev->priv; struct gptimer_timer *timer; char buf[64]; int i; if (priv == NULL || argc != 0) return -DRVMGR_EINVAL; sprintf(buf, "Timer Count: %d", priv->timer_cnt); print_line(p, buf); sprintf(buf, "REGS: 0x%08x", (unsigned int)priv->regs); print_line(p, buf); sprintf(buf, "BASE SCALER: %d", priv->regs->scaler_reload); print_line(p, buf); sprintf(buf, "BASE FREQ: %dkHz", priv->base_freq / 1000); print_line(p, buf); sprintf(buf, "SeparateIRQ: %s", priv->separate_interrupt ? "YES":"NO"); print_line(p, buf); for (i=0; itimer_cnt; i++) { timer = &priv->timers[i]; sprintf(buf, " - TIMER HW Index %d -", timer->tindex); print_line(p, buf); sprintf(buf, " TLIB Index: %d", timer->index); print_line(p, buf); sprintf(buf, " RELOAD REG: %d", timer->tregs->reload); print_line(p, buf); sprintf(buf, " CTRL REG: %d", timer->tregs->ctrl); print_line(p, buf); } return DRVMGR_OK; } #endif static inline struct gptimer_priv *priv_from_timer(struct gptimer_timer *t) { return (struct gptimer_priv *) ((unsigned int)t - sizeof(struct gptimer_priv) - t->index * sizeof(struct gptimer_timer)); } static int gptimer_tlib_int_pend(struct tlib_dev *hand, int ack) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; unsigned int ctrl = timer->tregs->ctrl; if ((ctrl & (GPTIMER_CTRL_IP | GPTIMER_CTRL_IE)) == (GPTIMER_CTRL_IP | GPTIMER_CTRL_IE)) { /* clear Pending IRQ ? */ if (ack) timer->tregs->ctrl = ctrl & timer->irq_ack_mask; return 1; /* timer generated IRQ */ } else return 0; /* was not timer causing IRQ */ } void gptimer_isr(void *data) { struct gptimer_priv *priv = data; int i; /* Check all timers for IRQ */ for (i=0;itimer_cnt; i++) { if (gptimer_tlib_int_pend((void *)&priv->timers[i], 0)) { /* IRQ Was generated by Timer and Pending flag has *not* * yet been cleared, this is to allow ISR to look at * pending bit. Call ISR registered. Clear pending bit. */ if (priv->timers[i].tdev.isr_func) { priv->timers[i].tdev.isr_func( priv->timers[i].tdev.isr_data); } gptimer_tlib_int_pend((void *)&priv->timers[i], 1); } } } static void gptimer_tlib_reset(struct tlib_dev *hand) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; timer->tregs->ctrl = 0; timer->tregs->reload = 0xffffffff; timer->tregs->ctrl = GPTIMER_CTRL_LD; } static void gptimer_tlib_get_freq( struct tlib_dev *hand, unsigned int *basefreq, unsigned int *tickrate) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; struct gptimer_priv *priv = priv_from_timer(timer); /* Calculate base frequency from Timer Clock and Prescaler */ if ( basefreq ) *basefreq = priv->base_freq; if ( tickrate ) *tickrate = timer->tregs->reload + 1; } static int gptimer_tlib_set_freq(struct tlib_dev *hand, unsigned int tickrate) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; timer->tregs->reload = tickrate - 1; /*Check that value was allowed (Timer may not be as wide as expected)*/ if ( timer->tregs->reload != (tickrate - 1) ) return -1; else return 0; } static void gptimer_tlib_irq_reg(struct tlib_dev *hand, tlib_isr_t func, void *data) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; struct gptimer_priv *priv = priv_from_timer(timer); if ( priv->separate_interrupt ) { drvmgr_interrupt_register(priv->dev, timer->tindex, "gptimer", func, data); } timer->tregs->ctrl |= GPTIMER_CTRL_IE; } static void gptimer_tlib_irq_unreg(struct tlib_dev *hand, tlib_isr_t func, void *data) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; struct gptimer_priv *priv = priv_from_timer(timer); /* Turn off IRQ at source, unregister IRQ handler */ timer->tregs->ctrl &= ~GPTIMER_CTRL_IE; if ( priv->separate_interrupt ) { drvmgr_interrupt_unregister(priv->dev, timer->tindex, func, data); } else { timer->tdev.isr_func = NULL; } } static void gptimer_tlib_start(struct tlib_dev *hand, int once) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; unsigned int ctrl; /* Load the selected frequency before starting Frequency */ ctrl = GPTIMER_CTRL_LD | GPTIMER_CTRL_EN; if ( once == 0 ) ctrl |= GPTIMER_CTRL_RS; /* Restart Timer */ timer->tregs->ctrl |= ctrl; } static void gptimer_tlib_stop(struct tlib_dev *hand) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; /* Load the selected Frequency */ timer->tregs->ctrl &= ~(GPTIMER_CTRL_EN|GPTIMER_CTRL_IP); } static void gptimer_tlib_restart(struct tlib_dev *hand) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; timer->tregs->ctrl |= GPTIMER_CTRL_LD | GPTIMER_CTRL_EN; } static void gptimer_tlib_get_counter( struct tlib_dev *hand, unsigned int *counter) { struct gptimer_timer *timer = (struct gptimer_timer *)hand; *counter = timer->tregs->value; } static struct tlib_drv gptimer_tlib_drv = { .reset = gptimer_tlib_reset, .get_freq = gptimer_tlib_get_freq, .set_freq = gptimer_tlib_set_freq, .irq_reg = gptimer_tlib_irq_reg, .irq_unreg = gptimer_tlib_irq_unreg, .start = gptimer_tlib_start, .stop = gptimer_tlib_stop, .restart = gptimer_tlib_restart, .get_counter = gptimer_tlib_get_counter, .custom = NULL, .int_pend = gptimer_tlib_int_pend, };