diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2018-12-22 18:31:04 +0100 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2019-01-22 12:46:33 +0100 |
commit | 7eb606d393306da25fd6e6aa7f8595ffb2e924fc (patch) | |
tree | 085befd6fe5e29d229fec9683735516d48e9d41e /bsps/shared/grlib/btimer | |
parent | grlib: Move header files (diff) | |
download | rtems-7eb606d393306da25fd6e6aa7f8595ffb2e924fc.tar.bz2 |
grlib: Move source files
Update #3678.
Diffstat (limited to 'bsps/shared/grlib/btimer')
-rw-r--r-- | bsps/shared/grlib/btimer/gptimer.c | 545 | ||||
-rw-r--r-- | bsps/shared/grlib/btimer/tlib.c | 77 | ||||
-rw-r--r-- | bsps/shared/grlib/btimer/tlib_ckinit.c | 442 |
3 files changed, 1064 insertions, 0 deletions
diff --git a/bsps/shared/grlib/btimer/gptimer.c b/bsps/shared/grlib/btimer/gptimer.c new file mode 100644 index 0000000000..4b3ec8c4b8 --- /dev/null +++ b/bsps/shared/grlib/btimer/gptimer.c @@ -0,0 +1,545 @@ +/* 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.org/license/LICENSE. + */ + +#include <rtems.h> +#include <bsp.h> +#include <stdlib.h> +#include <string.h> +#include <drvmgr/drvmgr.h> +#include <grlib/ambapp_bus.h> +#include <grlib/grlib.h> +#include <grlib/gptimer.h> +#include <grlib/tlib.h> + +#if defined(LEON3) +#include <leon.h> +#endif + +#ifdef GPTIMER_INFO_AVAIL +#include <stdio.h> +#endif + +#ifdef RTEMS_SMP +#include <rtems/score/processormask.h> +#include <rtems/score/smpimpl.h> +#endif + +#include <grlib/grlib_impl.h> + +/* 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<<GPTIMER_CFG_TIMERS_BIT) +#define GPTIMER_CFG_IRQ (0x1f<<GPTIMER_CFG_IRQ_BIT) +#define GPTIMER_CFG_SI (1<<GPTIMER_CFG_SI_BIT) +#define GPTIMER_CFG_DF (1<<GPTIMER_CFG_DF_BIT) + +/* GPTIMER Timer Control Register */ +#define GPTIMER_CTRL_EN_BIT 0 +#define GPTIMER_CTRL_RS_BIT 1 +#define GPTIMER_CTRL_LD_BIT 2 +#define GPTIMER_CTRL_IE_BIT 3 +#define GPTIMER_CTRL_IP_BIT 4 +#define GPTIMER_CTRL_CH_BIT 5 +#define GPTIMER_CTRL_DH_BIT 6 + +#define GPTIMER_CTRL_EN (1<<GPTIMER_CTRL_EN_BIT) +#define GPTIMER_CTRL_RS (1<<GPTIMER_CTRL_RS_BIT) +#define GPTIMER_CTRL_LD (1<<GPTIMER_CTRL_LD_BIT) +#define GPTIMER_CTRL_IE (1<<GPTIMER_CTRL_IE_BIT) +#define GPTIMER_CTRL_IP (1<<GPTIMER_CTRL_IP_BIT) +#define GPTIMER_CTRL_CH (1<<GPTIMER_CTRL_CH_BIT) +#define GPTIMER_CTRL_DH (1<<GPTIMER_CTRL_DH_BIT) + +#define DBG(x...) + +/* GPTIMER timer private */ +struct gptimer_timer { + struct tlib_dev tdev; /* Must be first in struct */ + struct gptimer_timer_regs *tregs; + char index; /* Timer Index in this driver */ + char tindex; /* Timer Index In Hardware */ + unsigned char irq_ack_mask; +}; + +/* GPTIMER Core private */ +struct gptimer_priv { + struct drvmgr_dev *dev; + struct gptimer_regs *regs; + unsigned int base_clk; + unsigned int base_freq; + unsigned int widthmask; + char separate_interrupt; + char isr_installed; + + /* Structure per Timer unit, the core supports up to 8 timers */ + int timer_cnt; + struct gptimer_timer timers[0]; +}; + +void gptimer_isr(void *data); + +#if 0 +void gptimer_tlib_irq_register(struct tlib_drv *tdrv, tlib_isr_t func, void *data) +{ + struct gptimer_priv *priv = (struct gptimer_priv *)tdrv; + + if ( SHARED ...) + + + drvmgr_interrupt_register(); +} +#endif + +/******************* Driver manager interface ***********************/ + +/* Driver prototypes */ +static struct tlib_drv gptimer_tlib_drv; +int gptimer_device_init(struct gptimer_priv *priv); + +int gptimer_init1(struct drvmgr_dev *dev); +#ifdef GPTIMER_INFO_AVAIL +static int gptimer_info( + struct drvmgr_dev *dev, + void (*print_line)(void *p, char *str), + void *p, int, char *argv[]); +#define GTIMER_INFO_FUNC gptimer_info +#else +#define GTIMER_INFO_FUNC NULL +#endif + +struct drvmgr_drv_ops gptimer_ops = +{ + .init = {gptimer_init1, NULL, NULL, NULL}, + .remove = NULL, + .info = GTIMER_INFO_FUNC, +}; + +struct amba_dev_id gptimer_ids[] = +{ + {VENDOR_GAISLER, GAISLER_GPTIMER}, + {VENDOR_GAISLER, GAISLER_GRTIMER}, + {0, 0} /* Mark end of table */ +}; + +struct amba_drv_info gptimer_drv_info = +{ + { + DRVMGR_OBJ_DRV, /* Driver */ + NULL, /* Next driver */ + NULL, /* Device list */ + DRIVER_AMBAPP_GAISLER_GPTIMER_ID,/* Driver ID */ + "GPTIMER_DRV", /* Driver Name */ + DRVMGR_BUS_TYPE_AMBAPP, /* Bus Type */ + &gptimer_ops, + NULL, /* Funcs */ + 0, /* No devices yet */ + 0, + }, + &gptimer_ids[0] +}; + +void gptimer_register_drv (void) +{ + DBG("Registering GPTIMER driver\n"); + drvmgr_drv_register(&gptimer_drv_info.general); +} + +int gptimer_init1(struct drvmgr_dev *dev) +{ + struct gptimer_priv *priv; + struct gptimer_regs *regs; + struct amba_dev_info *ambadev; + struct ambapp_core *pnpinfo; + int timer_hw_cnt, timer_cnt, timer_start; + int i, size; + struct gptimer_timer *timer; + union drvmgr_key_value *value; + unsigned char irq_ack_mask; + + /* Get device information from AMBA PnP information */ + ambadev = (struct amba_dev_info *)dev->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", DRVMGR_KT_INT); + if ( value) { + timer_start = value->i; + timer_cnt = timer_hw_cnt - timer_start; + } + value = drvmgr_dev_key_get(dev, "timerCnt", DRVMGR_KT_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 = grlib_calloc(1, size); + if ( !priv ) + return DRVMGR_NOMEM; + priv->dev = dev; + priv->regs = regs; + + /* 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", DRVMGR_KT_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; + + /* Probe timer register width mask */ + priv->regs->timer[timer_start].value = 0xffffffff; + priv->widthmask = priv->regs->timer[timer_start].value; + + priv->timer_cnt = timer_cnt; + for (i=0; i<timer_cnt; i++) { + timer = &priv->timers[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 */ + 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) != 0; + + 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; i<priv->timer_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;i<priv->timer_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 = (timer->tregs->ctrl & timer->irq_ack_mask) & + GPTIMER_CTRL_IP; + 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, int flags) +{ + 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); + } else { + if (priv->isr_installed == 0) { + /* Shared IRQ handler */ + drvmgr_interrupt_register( + priv->dev, + 0, + "gptimer_shared", + gptimer_isr, + priv); + } + priv->isr_installed++; + } + +#if RTEMS_SMP + if (flags & TLIB_FLAGS_BROADCAST) { + int tindex = 0; + + if (priv->separate_interrupt) { + /* Offset interrupt number with HW subtimer index */ + tindex = timer->tindex; + } + drvmgr_interrupt_set_affinity(priv->dev, tindex, + _SMP_Get_online_processors()); + } +#endif + + 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; + priv->isr_installed--; + if (priv->isr_installed == 0) { + drvmgr_interrupt_unregister(priv->dev, 0, + gptimer_isr, priv); + } + } +} + +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 | (timer->tregs->ctrl & timer->irq_ack_mask & + ~GPTIMER_CTRL_RS); +} + +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 void gptimer_tlib_get_widthmask( + struct tlib_dev *hand, + unsigned int *widthmask) +{ + struct gptimer_timer *timer = (struct gptimer_timer *)hand; + struct gptimer_priv *priv = priv_from_timer(timer); + + *widthmask = priv->widthmask; +} + +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, + .get_widthmask = gptimer_tlib_get_widthmask, +}; diff --git a/bsps/shared/grlib/btimer/tlib.c b/bsps/shared/grlib/btimer/tlib.c new file mode 100644 index 0000000000..d66a472fe9 --- /dev/null +++ b/bsps/shared/grlib/btimer/tlib.c @@ -0,0 +1,77 @@ +/* + * Timer Library (TLIB) + * + * COPYRIGHT (c) 2011. + * 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.org/license/LICENSE. + */ + +#include <rtems.h> +#include <grlib/tlib.h> + +struct tlib_dev *tlib_dev_head = NULL; +struct tlib_dev *tlib_dev_tail = NULL; +static int tlib_dev_cnt = 0; + +/* Register Timer device to Timer Library */ +int tlib_dev_reg(struct tlib_dev *newdev) +{ + /* Reset device */ + newdev->status = 0; + newdev->isr_func = NULL; + newdev->index = tlib_dev_cnt; + + /* Insert last in queue */ + newdev->next = NULL; + if ( tlib_dev_tail == NULL ) { + tlib_dev_head = newdev; + } else { + tlib_dev_tail->next = newdev; + } + tlib_dev_tail = newdev; + + /* Return Index of Registered Timer */ + return tlib_dev_cnt++; +} + +void *tlib_open(int timer_no) +{ + struct tlib_dev *dev; + + if ( timer_no < 0 ) + return NULL; + + dev = tlib_dev_head; + while ( (timer_no > 0) && dev ) { + timer_no--; + dev = dev->next; + } + if ( dev ) { + if ( dev->status ) + return NULL; + dev->status = 1; + /* Reset Timer to initial state */ + tlib_reset(dev); + } + return dev; +} + +void tlib_close(void *hand) +{ + struct tlib_dev *dev = hand; + + /* Stop any ongoing timer operation and unregister IRQ if registered */ + tlib_stop(dev); + tlib_irq_unregister(dev); + + /* Mark not open */ + dev->status = 0; +} + +int tlib_ntimer(void) +{ + return tlib_dev_cnt; +} diff --git a/bsps/shared/grlib/btimer/tlib_ckinit.c b/bsps/shared/grlib/btimer/tlib_ckinit.c new file mode 100644 index 0000000000..e43c8fdd8e --- /dev/null +++ b/bsps/shared/grlib/btimer/tlib_ckinit.c @@ -0,0 +1,442 @@ +/* + * Clock Tick Device Driver using Timer Library implemented + * by the GRLIB GPTIMER / LEON2 Timer drivers. + * + * COPYRIGHT (c) 2010 - 2017. + * 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.org/license/LICENSE. + * + */ + +/* + * This is an implementation of the RTEMS "clockdrv_shell" interface for + * LEON2/3/4 systems using the Driver Manager. It is clock hardware agnostic + * and compatible with SMP and UP. Availability of free running counters is + * probed and selected as needed. + */ +#include <rtems.h> +#include <rtems/timecounter.h> +#include <rtems/clockdrv.h> +#include <stdlib.h> +#include <bsp.h> +#include <grlib/tlib.h> + +#ifdef RTEMS_DRVMGR_STARTUP + +#if defined(LEON3) +#include <leon.h> +#endif + +struct ops { + /* + * Set up the free running counter using the Timecounter or Simple + * Timecounter interface. + */ + rtems_device_driver (*initialize_counter)(void); + + /* + * Hardware-specific support at tick interrupt which runs early in Clock_isr. + * It can for example be used to check if interrupt was actually caused by + * the timer hardware. If return value is not RTEMS_SUCCESSFUL then Clock_isr + * returns immediately. at_tick can be initialized with NULL. + */ + rtems_device_driver (*at_tick)(void); + + /* + * Typically calls rtems_timecounter_tick(). A specialized clock driver may + * use for example rtems_timecounter_tick_simple() instead. + */ + void (*timecounter_tick)(void); + + /* + * Called when the clock driver exits. It can be used to stop functionality + * started by initialize_counter. The tick timer is stopped by default. + * shutdown_hardware can be initialized with NULL + */ + void (*shutdown_hardware)(void); +}; + +/* + * Different implementation depending on available free running counter for the + * timecounter. + * + * NOTE: The clock interface is not compatible with shared interrupts on the + * clock (tick) timer in SMP configuration. + */ + +#ifndef RTEMS_SMP +/* "simple timecounter" interface. Only for non-SMP. */ +static const struct ops ops_simple; +#else +/* Hardware support up-counter using LEON3 %asr23. */ +static const struct ops ops_timetag; +/* Timestamp counter available in some IRQ(A)MP instantiations. */ +static const struct ops ops_irqamp; +/* Separate GPTIMER subtimer as timecounter */ +static const struct ops ops_subtimer; +#endif + +struct clock_priv { + const struct ops *ops; + /* + * Timer number in Timer Library for tick timer used by this interface. + * Defaults to the first Timer in the System. + */ + int tlib_tick_index; + /* Timer number for timecounter timer if separate GPTIMER subtimer is used */ + int tlib_counter_index; + void *tlib_tick; + void *tlib_counter; + rtems_timecounter_simple tc_simple; + struct timecounter tc; +}; +static struct clock_priv priv; + +/** Common interface **/ + +/* Set system clock timer instance */ +void Clock_timer_register(int timer_number) +{ + priv.tlib_tick_index = timer_number; + priv.tlib_counter_index = timer_number + 1; +} + +static rtems_device_driver tlib_clock_find_timer(void) +{ + /* Take Timer that should be used as system timer. */ + priv.tlib_tick = tlib_open(priv.tlib_tick_index); + if (priv.tlib_tick == NULL) { + /* System Clock Timer not found */ + return RTEMS_NOT_DEFINED; + } + + /* Select which operation set to use */ +#ifndef RTEMS_SMP + priv.ops = &ops_simple; +#else + /* When on LEON3 try to use dedicated hardware free running counter. */ + leon3_up_counter_enable(); + if (leon3_up_counter_is_available()) { + priv.ops = &ops_timetag; + return RTEMS_SUCCESSFUL; + } else { + volatile struct irqmp_timestamp_regs *irqmp_ts; + + irqmp_ts = &LEON3_IrqCtrl_Regs->timestamp[0]; + if (leon3_irqmp_has_timestamp(irqmp_ts)) { + priv.ops = &ops_irqamp; + return RTEMS_SUCCESSFUL; + } + } + + /* Take another subtimer as the final option. */ + priv.ops = &ops_subtimer; +#endif + + return RTEMS_SUCCESSFUL; +} + +static rtems_device_driver tlib_clock_initialize_hardware(void) +{ + /* Set tick rate in number of "Base-Frequency ticks" */ + tlib_set_freq(priv.tlib_tick, rtems_configuration_get_microseconds_per_tick()); + priv.ops->initialize_counter(); + tlib_start(priv.tlib_tick, 0); + + return RTEMS_SUCCESSFUL; +} + +static rtems_device_driver tlib_clock_at_tick(void) +{ + if (priv.ops->at_tick) { + return priv.ops->at_tick(); + } + + return RTEMS_SUCCESSFUL; +} + +static void tlib_clock_timecounter_tick(void) +{ + priv.ops->timecounter_tick(); +} + +/* Return a value not equal to RTEMS_SUCCESFUL to make Clock_initialize fail. */ +static rtems_device_driver tlib_clock_install_isr(rtems_isr *isr) +{ + int flags = 0; + +#ifdef RTEMS_SMP + /* We shall broadcast the clock interrupt to all processors. */ + flags = TLIB_FLAGS_BROADCAST; +#endif + tlib_irq_register(priv.tlib_tick, isr, NULL, flags); + + return RTEMS_SUCCESSFUL; +} + +#ifndef RTEMS_SMP +/** Simple counter **/ +static uint32_t simple_tlib_tc_get(rtems_timecounter_simple *tc) +{ + unsigned int clicks = 0; + + if (priv.tlib_tick != NULL) { + tlib_get_counter(priv.tlib_tick, &clicks); + } + + return clicks; +} + +static bool simple_tlib_tc_is_pending(rtems_timecounter_simple *tc) +{ + bool pending = false; + + if (priv.tlib_tick != NULL) { + pending = tlib_interrupt_pending(priv.tlib_tick, 0) != 0; + } + + return pending; +} + +static uint32_t simple_tlib_tc_get_timecount(struct timecounter *tc) +{ + return rtems_timecounter_simple_downcounter_get( + tc, + simple_tlib_tc_get, + simple_tlib_tc_is_pending + ); +} + +static rtems_device_driver simple_initialize_counter(void) +{ + uint64_t frequency; + unsigned int tick_hz; + + frequency = 1000000; + tick_hz = rtems_configuration_get_microseconds_per_tick(); + + rtems_timecounter_simple_install( + &priv.tc_simple, + frequency, + tick_hz, + simple_tlib_tc_get_timecount + ); + + return RTEMS_NOT_DEFINED; +} + +static void simple_tlib_tc_at_tick(rtems_timecounter_simple *tc) +{ + /* Nothing to do */ +} + +/* + * Support for shared interrupts. Ack IRQ at source, only handle interrupts + * generated from the tick-timer. This is called early in Clock_isr. + */ +static rtems_device_driver simple_at_tick(void) +{ + if (tlib_interrupt_pending(priv.tlib_tick, 1) == 0) { + return RTEMS_NOT_DEFINED; + } + return RTEMS_SUCCESSFUL; +} + +static void simple_timecounter_tick(void) +{ + rtems_timecounter_simple_downcounter_tick( + &priv.tc_simple, + simple_tlib_tc_get, + simple_tlib_tc_at_tick + ); +} + +static const struct ops ops_simple = { + .initialize_counter = simple_initialize_counter, + .at_tick = simple_at_tick, + .timecounter_tick = simple_timecounter_tick, + .shutdown_hardware = NULL, +}; + +#else + +/** Subtimer as counter **/ +static uint32_t subtimer_get_timecount(struct timecounter *tc) +{ + unsigned int counter; + + tlib_get_counter(priv.tlib_counter, &counter); + + return 0xffffffff - counter; +} + +static rtems_device_driver subtimer_initialize_counter(void) +{ + unsigned int mask; + unsigned int basefreq; + + if (priv.tlib_counter_index == priv.tlib_tick_index) { + priv.tlib_counter_index = priv.tlib_tick_index + 1; + } + /* Take Timer that should be used as timecounter upcounter timer. */ + priv.tlib_counter = tlib_open(priv.tlib_counter_index); + if (priv.tlib_counter == NULL) { + /* Timecounter timer not found */ + return RTEMS_NOT_DEFINED; + } + + /* Configure free running counter: GPTIMER */ + tlib_get_freq(priv.tlib_counter, &basefreq, NULL); + tlib_get_widthmask(priv.tlib_counter, &mask); + + priv.tc.tc_get_timecount = subtimer_get_timecount; + priv.tc.tc_counter_mask = mask; + priv.tc.tc_frequency = basefreq; + priv.tc.tc_quality = RTEMS_TIMECOUNTER_QUALITY_CLOCK_DRIVER; + rtems_timecounter_install(&priv.tc); + /* Start free running counter */ + tlib_start(priv.tlib_counter, 0); + + return RTEMS_SUCCESSFUL; +} + +static void subtimer_timecounter_tick(void) +{ + rtems_timecounter_tick(); +} + +static void subtimer_shutdown_hardware(void) +{ + if (priv.tlib_counter) { + tlib_stop(priv.tlib_counter); + priv.tlib_counter = NULL; + } +} + +static const struct ops ops_subtimer = { + .initialize_counter = subtimer_initialize_counter, + .timecounter_tick = subtimer_timecounter_tick, + .shutdown_hardware = subtimer_shutdown_hardware, +}; + +/** DSU timetag as counter **/ +static uint32_t timetag_get_timecount(struct timecounter *tc) +{ + return leon3_up_counter_low(); +} + +static rtems_device_driver timetag_initialize_counter(void) +{ + /* Configure free running counter: timetag */ + priv.tc.tc_get_timecount = timetag_get_timecount; + priv.tc.tc_counter_mask = 0xffffffff; + priv.tc.tc_frequency = leon3_up_counter_frequency(); + priv.tc.tc_quality = RTEMS_TIMECOUNTER_QUALITY_CLOCK_DRIVER; + rtems_timecounter_install(&priv.tc); + + return RTEMS_SUCCESSFUL; +} + +static void timetag_timecounter_tick(void) +{ + rtems_timecounter_tick(); +} + +static const struct ops ops_timetag = { + .initialize_counter = timetag_initialize_counter, + .at_tick = NULL, + .timecounter_tick = timetag_timecounter_tick, + .shutdown_hardware = NULL, +}; + +/** IRQ(A)MP timestamp as counter **/ +static uint32_t irqamp_get_timecount(struct timecounter *tc) +{ + return LEON3_IrqCtrl_Regs->timestamp[0].counter; +} + +static rtems_device_driver irqamp_initialize_counter(void) +{ + volatile struct irqmp_timestamp_regs *irqmp_ts; + static const uint32_t A_TSISEL_FIELD = 0xf; + + /* Configure free running counter: timetag */ + priv.tc.tc_get_timecount = irqamp_get_timecount; + priv.tc.tc_counter_mask = 0xffffffff; + priv.tc.tc_frequency = leon3_up_counter_frequency(); + priv.tc.tc_quality = RTEMS_TIMECOUNTER_QUALITY_CLOCK_DRIVER; + rtems_timecounter_install(&priv.tc); + + /* + * The counter increments whenever a TSISEL field in a Timestamp Control + * Register is non-zero. + */ + irqmp_ts = &LEON3_IrqCtrl_Regs->timestamp[0]; + irqmp_ts->control = A_TSISEL_FIELD; + + return RTEMS_SUCCESSFUL; +} + +static void irqamp_timecounter_tick(void) +{ + rtems_timecounter_tick(); +} + +static const struct ops ops_irqamp = { + .initialize_counter = irqamp_initialize_counter, + .at_tick = NULL, + .timecounter_tick = irqamp_timecounter_tick, + .shutdown_hardware = NULL, +}; +#endif + +/** Interface to the Clock Driver Shell (dev/clock/clockimpl.h) **/ +#define Clock_driver_support_find_timer() \ + do { \ + rtems_device_driver ret; \ + ret = tlib_clock_find_timer(); \ + if (RTEMS_SUCCESSFUL != ret) { \ + return ret; \ + } \ + } while (0) + +#define Clock_driver_support_install_isr( isr ) \ + do { \ + rtems_device_driver ret; \ + ret = tlib_clock_install_isr( isr ); \ + if (RTEMS_SUCCESSFUL != ret) { \ + return ret; \ + } \ + } while (0) + +#define Clock_driver_support_set_interrupt_affinity(online_processors) \ + /* Done by tlib_clock_install_isr() */ + +#define Clock_driver_support_initialize_hardware() \ + do { \ + rtems_device_driver ret; \ + ret = tlib_clock_initialize_hardware(); \ + if (RTEMS_SUCCESSFUL != ret) { \ + return ret; \ + } \ + } while (0) + +#define Clock_driver_timecounter_tick() \ + tlib_clock_timecounter_tick() + +#define Clock_driver_support_at_tick() \ + do { \ + rtems_device_driver ret; \ + ret = tlib_clock_at_tick(); \ + if (RTEMS_SUCCESSFUL != ret) { \ + return; \ + } \ + } while (0) + +#include "../../../shared/dev/clock/clockimpl.h" + +#endif /* RTEMS_DRVMGR_STARTUP */ + |