| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| |
| #include <utils/util.h> |
| |
| #include <platsupport/timer.h> |
| #include <platsupport/mach/gpt.h> |
| #include <platsupport/io.h> |
| #include <platsupport/ltimer.h> |
| #include <platsupport/fdt.h> |
| |
| typedef enum { |
| |
| /* Start or stop the timer */ |
| ST = 0, |
| |
| /* Autoreload mode */ |
| AR = 1, |
| |
| /* Prescale value |
| * Timer is prescaled 2^(PTV+1). |
| * EG: PTV = 3. Timer increases every 16 clock periods. |
| */ |
| PTV = 2, |
| |
| /* Enable prescaler */ |
| PRE = 5, |
| |
| /* Compare enabled */ |
| CE = 6, |
| |
| /* Pulse-width-modulation output pin default setting when |
| * counter is stopped or trigger output mode is set to no trigger |
| * 0x0: Default value of PWM_out output: 0 |
| * 0x1: Default value of PWM_out output: 1 |
| */ |
| SCPWM = 7, |
| |
| /* Transition capture mode |
| * 0x0: No capture |
| * 0x1: Capture on rising edges of EVENT_CAPTURE pin. |
| * 0x2: Capture on falling edges of EVENT_CAPTURE pin. |
| * 0x3: Capture on both edges of EVENT_CAPTURE pin. |
| */ |
| TCM = 8, |
| |
| /* Trigger output mode |
| * 0x0: No trigger |
| * 0x1: Overflow trigger |
| * 0x2: Overflow and match trigger |
| * 0x3: Reserved |
| */ |
| TRG = 10, |
| |
| /* Pulse or toggle select bit. Pulse 0. Toggle 1. */ |
| PT = 12, |
| |
| /* Capture mode select bit (first/second) |
| * 0x0: Capture the first enabled capture event in TCAR1. |
| * 0x1: Capture the second enabled capture event in TCAR2. |
| */ |
| CAPT_MODE = 13, |
| |
| /* PWM output/event detection input pin direction control: |
| * 0x0: Configures the pin as an output (needed when PWM mode is required) |
| * 0x1: Configures the pin as an input (needed when capture mode is required) |
| */ |
| GPO_CFG = 14 |
| } gpt_control_reg; |
| |
| typedef enum { |
| |
| /* Enable match interrupt */ |
| MAT_IT_ENA = 0, |
| |
| /* Enable overflow interrupt */ |
| OVF_IT_ENA = 1, |
| |
| /* Enable capture interrupt */ |
| TCAR_IT_ENA = 2 |
| |
| } gpt_int_en_reg; |
| |
| typedef enum { |
| /* General guide for all three flags: |
| * Read 1: Interrupt pending |
| * Read 0: No Interrupt pending |
| * Write 1: Clear flag |
| * Write 0: No change |
| */ |
| |
| /* match interrupt */ |
| MAT_IT_FLAG = 0, |
| |
| /* overflow interrupt */ |
| OVF_IT_FLAG = 1, |
| |
| /* capture interrupt */ |
| TCAR_IT_FLAG = 2 |
| |
| } gpt_int_stat_reg; |
| |
| typedef enum { |
| |
| /* PWM output/event detection input pin direction control: |
| * 0x0: Configures the pin as an output (needed when PWMmode is required) |
| * 0x1: Configures the pin as an input (needed when capture mode is required) |
| */ |
| AUTOIDLE = 0, |
| |
| /* Software reset. This bit is automatically reset by the hardware. |
| * During reads, it always returns 0. |
| * 0x0: Normal mode |
| * 0x1: The module is reset. |
| */ |
| SOFTRESET = 1, |
| |
| /* Software reset. This bit is automatically reset by the RW 0 |
| * hardware. During reads, it always returns 0. |
| * 0x0: Normal mode |
| * 0x1: The module is reset. |
| */ |
| ENAWAKEUP = 3 |
| |
| } gpt_cfg_reg; |
| |
| /* Memory map for GPT */ |
| struct gpt_map { |
| uint32_t tidr; // GPTIMER_TIDR 0x00 |
| uint32_t padding1[3]; |
| uint32_t cfg; // GPTIMER_CFG 0x10 |
| uint32_t tistat; // GPTIMER_TISTAT 0x14 |
| uint32_t tisr; // GPTIMER_TISR 0x18 |
| uint32_t tier; // GPTIMER_TIER 0x1C |
| uint32_t twer; // GPTIMER_TWER 0x20 |
| uint32_t tclr; // GPTIMER_TCLR 0x24 |
| uint32_t tcrr; // GPTIMER_TCRR 0x28 |
| uint32_t tldr; // GPTIMER_TLDR 0x2C |
| uint32_t ttgr; // GPTIMER_TTGR 0x30 |
| uint32_t twps; // GPTIMER_TWPS 0x34 |
| uint32_t tmar; // GPTIMER_TMAR 0x38 |
| uint32_t tcar1; // GPTIMER_TCAR1 0x3C |
| uint32_t tsicr; // GPTIMER_TSICR 0x40 |
| uint32_t tcar2; // GPTIMER_TCAR2 0x44 |
| uint32_t tpir; // GPTIMER_TPIR 0x48 |
| uint32_t tnir; // GPTIMER_TNIR 0x4C |
| uint32_t tcvr; // GPTIMER_TCVR 0x50 |
| uint32_t tocr; // GPTIMER_TOCR 0x54 |
| uint32_t towr; // GPTIMER_TOWR 0x58 |
| }; |
| |
| void gpt_start(gpt_t *gpt) |
| { |
| assert(gpt != NULL && gpt->gpt_map != NULL); |
| gpt->gpt_map->tclr |= BIT(ST); |
| } |
| |
| void gpt_stop(gpt_t *gpt) |
| { |
| assert(gpt != NULL && gpt->gpt_map != NULL); |
| /* Disable timer. */ |
| gpt->gpt_map->tclr &= ~BIT(ST); |
| } |
| |
| static void gpt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) |
| { |
| gpt_t *gpt = data; |
| uint32_t tisr = gpt->gpt_map->tisr; |
| |
| /* track timekeeping overflow */ |
| if (tisr & BIT(OVF_IT_FLAG)) { |
| gpt->high_bits++; |
| } |
| |
| /* ack any possible irqs */ |
| gpt->gpt_map->tisr = (BIT(OVF_IT_FLAG) | BIT(MAT_IT_FLAG) | BIT(TCAR_IT_FLAG)); |
| |
| if (acknowledge_fn(ack_data)) { |
| ZF_LOGE("Failed to acknowledge ps_irq"); |
| } |
| |
| if (gpt->user_callback) { |
| if (tisr & BIT(OVF_IT_FLAG)) { |
| gpt->user_callback(gpt->user_callback_token, LTIMER_OVERFLOW_EVENT); |
| } else if (tisr & BIT(MAT_IT_FLAG)) { |
| gpt->user_callback(gpt->user_callback_token, LTIMER_TIMEOUT_EVENT); |
| } else { |
| ZF_LOGE("Unknown interrupt neither overflow or match"); |
| } |
| } |
| } |
| |
| static bool gpt_ok_prescaler(uint32_t prescaler) |
| { |
| if (prescaler > 7) { |
| ZF_LOGE("Prescaler value set too large for device, value: %d, max 7", prescaler); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static uint64_t gpt_ticks_to_ns(uint64_t ticks) |
| { |
| return (ticks / CLK_MHZ) * NS_IN_US; |
| } |
| |
| static uint64_t gpt_ns_to_ticks(uint64_t ns) |
| { |
| return ns / NS_IN_US * CLK_MHZ; |
| } |
| |
| uint64_t gpt_get_max(void) |
| { |
| return gpt_ticks_to_ns(UINT32_MAX - 1); |
| } |
| |
| static void gpt_init(gpt_t *gpt) |
| { |
| /* Disable GPT. */ |
| gpt->gpt_map->tclr = 0; |
| |
| /* Perform a soft reset */ |
| gpt->gpt_map->cfg = BIT(SOFTRESET); |
| |
| while (!gpt->gpt_map->tistat); /* Wait for timer to reset */ |
| |
| /* set prescaler */ |
| if (gpt->prescaler > 0) { |
| gpt->gpt_map->tclr = (gpt->prescaler << PTV); /* Set the prescaler */ |
| gpt->gpt_map->tclr |= BIT(PRE); /* Enable the prescaler */ |
| } |
| } |
| |
| /* Relative timeout driver for the gpt. |
| * |
| * This driver sets up the gpt for relative timeouts (oneshot or periodic) only. |
| * |
| * It works by setting the timer to interrupt on overflow and reloading the timer |
| * with (0xFFFFFFFF - relative timeout). |
| */ |
| |
| int rel_gpt_set_timeout(gpt_t *gpt, uint64_t ns, bool periodic) |
| { |
| uint32_t reload = periodic ? BIT(AR) : 0; |
| uint64_t ticks = gpt_ns_to_ticks(ns) / BIT(gpt->prescaler + 1); |
| |
| if (ticks >= UINT32_MAX) { |
| /* too big for this timer implementation */ |
| ZF_LOGE("Timeout too big for timer, max %u, got %llu\n", UINT32_MAX - 1, ticks); |
| return ETIME; |
| } |
| |
| /* invert ticks - it's an upcounter and will interrupt on overflow */ |
| ticks = UINT32_MAX - ticks; |
| |
| gpt_init(gpt); |
| |
| /* Clear pending overflows. */ |
| gpt->gpt_map->tisr |= BIT(OVF_IT_FLAG); |
| |
| /* Set the reload value. */ |
| gpt->gpt_map->tldr = (uint32_t) ticks; |
| |
| /* Reset the read register. */ |
| gpt->gpt_map->tcrr = (uint32_t) ticks; |
| |
| /* Enable interrupt on overflow. */ |
| gpt->gpt_map->tier |= BIT(OVF_IT_ENA); |
| |
| assert(!(gpt->gpt_map->tisr & BIT(OVF_IT_FLAG))); |
| /* Set autoreload and start the timer. */ |
| gpt->gpt_map->tclr |= (reload | BIT(ST)); |
| |
| /* success */ |
| return 0; |
| } |
| |
| int rel_gpt_init(gpt_t *gpt, gpt_config_t config) |
| { |
| if (!gpt_ok_prescaler(config.prescaler)) { |
| return EINVAL; |
| } |
| |
| gpt->prescaler = config.prescaler; |
| |
| gpt_init(gpt); |
| |
| return 0; |
| } |
| |
| uint64_t abs_gpt_get_time(gpt_t *gpt) |
| { |
| bool overflow; |
| uint64_t ticks; |
| |
| overflow = !!(gpt->gpt_map->tisr & OVF_IT_FLAG); |
| /* high bits */ |
| ticks = ((uint64_t)(gpt->high_bits + overflow)) << 32llu; |
| /* low bits */ |
| ticks += gpt->gpt_map->tcrr; |
| ticks = ticks * BIT(gpt->prescaler + 1); |
| |
| return gpt_ticks_to_ns(ticks); |
| } |
| |
| int abs_gpt_init(gpt_t *gpt, gpt_config_t config) |
| { |
| if (!gpt_ok_prescaler(config.prescaler)) { |
| return EINVAL; |
| } |
| |
| gpt->prescaler = config.prescaler; |
| |
| /* enable interrupt on overflow. */ |
| gpt->gpt_map->tier |= BIT(OVF_IT_ENA); |
| |
| /* set the reload value. */ |
| gpt->gpt_map->tldr = 0u; |
| |
| /* reset the read register. */ |
| gpt->gpt_map->tcrr = 0u; |
| |
| /* clear pending irqs. */ |
| gpt->gpt_map->tisr |= BIT(OVF_IT_FLAG | MAT_IT_FLAG | TCAR_IT_FLAG); |
| |
| gpt->gpt_map->tclr |= (BIT(CE) | BIT(AR)); |
| |
| gpt_init(gpt); |
| |
| return 0; |
| } |
| |
| static int allocate_register_callback(pmem_region_t pmem, unsigned curr_num, size_t num_regs, void *token) |
| { |
| gpt_t *gpt = token; |
| assert(num_regs == 1 && curr_num == 0); |
| void *vaddr = ps_pmem_map(&gpt->ops, pmem, false, PS_MEM_NORMAL); |
| if (vaddr == NULL) { |
| return EIO; |
| } |
| gpt->gpt_map = vaddr; |
| gpt->timer_pmem = pmem; |
| return 0; |
| } |
| |
| static int allocate_irq_callback(ps_irq_t irq, unsigned curr_num, size_t num_irqs, void *token) |
| { |
| gpt_t *gpt = token; |
| /* Device should only have one interrupt */ |
| if (num_irqs != 1) { |
| return ENODEV; |
| } |
| assert(curr_num == 0); |
| irq_id_t irq_id = ps_irq_register(&gpt->ops.irq_ops, irq, gpt_handle_irq, gpt); |
| if (irq_id < 0) { |
| return EIO; |
| } |
| gpt->irq_id = irq_id; |
| return 0; |
| } |
| |
| void gpt_destroy(gpt_t *gpt) |
| { |
| int error; |
| |
| /* pre-set INVALID_IRQ_ID before init and do not run if not initialised */ |
| if (gpt->irq_id != PS_INVALID_IRQ_ID) { |
| error = ps_irq_unregister(&gpt->ops.irq_ops, gpt->irq_id); |
| ZF_LOGE_IF(error, "Unable to un-register timer gpt irq") |
| } |
| |
| /* check if pwm_map is NULL and do not run if not initialised */ |
| if (gpt->gpt_map != NULL) { |
| gpt_stop(gpt); |
| ps_pmem_unmap(&gpt->ops, gpt->timer_pmem, (void *) gpt->gpt_map); |
| } |
| } |
| |
| int gpt_create(gpt_t *gpt, ps_io_ops_t ops, char *fdt_path, ltimer_callback_fn_t user_cb_fn, void *user_cb_token) |
| { |
| int error; |
| |
| if (gpt == NULL || fdt_path == NULL) { |
| return EINVAL; |
| } |
| |
| /* Set up gpt */ |
| gpt->ops = ops; |
| gpt->user_callback = user_cb_fn; |
| gpt->user_callback_token = user_cb_token; |
| |
| /* Set up variables that will be set by callback */ |
| gpt->gpt_map = NULL; |
| gpt->irq_id = PS_INVALID_IRQ_ID; |
| |
| /* Gather FDT info */ |
| ps_fdt_cookie_t *cookie = NULL; |
| error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, fdt_path, &cookie); |
| if (error) { |
| gpt_destroy(gpt); |
| return error; |
| } |
| |
| /* walk the registers and allocate them */ |
| error = ps_fdt_walk_registers(&ops.io_fdt, cookie, allocate_register_callback, gpt); |
| if (error) { |
| gpt_destroy(gpt); |
| return error; |
| } |
| |
| /* walk the interrupts and allocate the first */ |
| error = ps_fdt_walk_irqs(&ops.io_fdt, cookie, allocate_irq_callback, gpt); |
| if (error) { |
| gpt_destroy(gpt); |
| return error; |
| } |
| |
| error = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); |
| if (error) { |
| gpt_destroy(gpt); |
| return error; |
| } |
| |
| return 0; |
| } |