| /* |
| * Copyright 2020, 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 <stdio.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <utils/util.h> |
| #include <inttypes.h> |
| #include <utils/fence.h> |
| #include <utils/arch/io.h> |
| |
| #include <platsupport/timer.h> |
| #include <platsupport/plat/timer.h> |
| |
| #define CLEANUP_FAIL_TEXT "Failed to cleanup the TTC after failing to initialise it" |
| |
| #define CLKCTRL_EXT_NEDGE BIT(6) |
| #define CLKCTRL_EXT_SRC_EN BIT(5) |
| #define CLKCTRL_PRESCALE_VAL(N) (((N) & 0xf) << 1) /* rate = clk_src/[2^(N+1)] */ |
| #define CLKCTRL_GET_PRESCALE_VAL(v) (((v) >> 1) & 0xf) |
| #define CLKCTRL_PRESCALE_EN BIT(0) |
| #define CLKCTRL_PRESCALE_MASK (CLKCTRL_PRESCALE_VAL(0xf) | CLKCTRL_PRESCALE_EN) |
| |
| /* Waveform polarity: When this bit is high, the |
| * waveform output goes from high to low on |
| * Match_1 interrupt and returns high on overflow |
| * or interval interrupt; when low, the waveform |
| * goes from low to high on Match_1 interrupt and |
| * returns low on overflow or interval interrupt */ |
| #define CNTCTRL_WAVE_POL BIT(6) |
| /* Output waveform enable, active low. */ |
| #define CNTCTRL_WAVE_EN BIT(5) |
| /* Setting this bit high resets the counter value and |
| * restarts counting; the RST bit is automatically |
| * cleared on restart. */ |
| #define CNTCTRL_RST BIT(4) |
| /* Register Match mode: when Match is set, an |
| * interrupt is generated when the count value |
| * matches one of the three match registers and the |
| * corresponding bit is set in the Interrupt Enable |
| * register. |
| */ |
| #define CNTCTRL_MATCH BIT(3) |
| /* Register Match mode: when Match is set, an |
| * interrupt is generated when the count value |
| * matches one of the three match registers and the |
| * corresponding bit is set in the Interrupt Enable |
| * register. */ |
| #define CNTCTRL_DECR BIT(2) |
| /* When this bit is high, the timer is in Interval |
| * Mode, and the counter generates interrupts at |
| * regular intervals; when low, the timer is in |
| * overflow mode. */ |
| #define CNTCTRL_INT BIT(1) |
| /* Disable counter: when this bit is high, the counter |
| * is stopped, holding its last value until reset, |
| * restarted or enabled again. */ |
| #define CNTCTRL_STOP BIT(0) |
| |
| /* Event timer overflow interrupt */ |
| #define INT_EVENT_OVR BIT(5) |
| /* Counter overflow */ |
| #define INT_CNT_OVR BIT(4) |
| /* Match 3 interrupt */ |
| #define INT_MATCH2 BIT(3) |
| /* Match 2 interrupt */ |
| #define INT_MATCH1 BIT(2) |
| /* Match 1 interrupt */ |
| #define INT_MATCH0 BIT(1) |
| /* Interval interrupt */ |
| #define INT_INTERVAL BIT(0) |
| |
| /* Event Control Timer register: controls the behavior of the internal counter */ |
| |
| /* Specifies how to handle overflow at the internal counter (during the counting phase |
| * of the external pulse) |
| * |
| * - When 0: Overflow causes E_En to be 0 (see E_En bit description) |
| * - When 1: Overflow causes the internal counter to wrap around and continues incrementing |
| */ |
| #define EVCTRL_OVR BIT(2) |
| /* Specifies the counting phase of the external pulse */ |
| #define EVCTRL_LO BIT(1) |
| /* When 0, immediately resets the internal counter to 0, and stops incrementing*/ |
| #define EVCTRL_EN BIT(0) |
| |
| #define PRESCALE_MAX 0xf |
| #define PCLK_FREQ 111110000U |
| |
| #ifdef CONFIG_PLAT_ZYNQMP |
| #define CNT_WIDTH 32 |
| #define CNT_MAX ((1ULL << CNT_WIDTH) - 1) |
| #else |
| #define CNT_WIDTH 16 |
| #define CNT_MAX (BIT(CNT_WIDTH) - 1) |
| #endif |
| |
| |
| /* Byte offsets into a field of ttc_tmr_regs_t for each ttc */ |
| #define TTCX_TIMER1_OFFSET 0x0 |
| #define TTCX_TIMER2_OFFSET 0x4 |
| #define TTCX_TIMER3_OFFSET 0x8 |
| |
| #define TTCX_TIMER1_IRQ_POS 0 |
| #define TTCX_TIMER2_IRQ_POS 1 |
| #define TTCX_TIMER3_IRQ_POS 2 |
| |
| struct ttc_tmr_regs { |
| /* Controls prescaler, selects clock input, edge */ |
| uint32_t clk_ctrl[3]; /* +0x00 */ |
| /* Enables counter, sets mode of operation, sets up/down |
| * counting, enables matching, enables waveform output */ |
| uint32_t cnt_ctrl[3]; /* +0x0C */ |
| /* Returns current counter value */ |
| uint32_t cnt_val[3]; /* +0x18 */ |
| /* Sets interval value - If interval is enabled, this is the maximum value |
| * that the counter will count up to or down from */ |
| uint32_t interval[3]; /* +0x24 */ |
| /* Sets match values, total 3 */ |
| uint32_t match[3][3]; /* +0x30 */ |
| /* Shows current interrupt status */ |
| uint32_t int_sts[3]; /* +0x54 */ |
| /* Enable interrupts */ |
| uint32_t int_en[3]; /* +0x60 */ |
| /* Enable event timer, stop timer, sets phrase */ |
| uint32_t event_ctrl[3]; /* +0x6C */ |
| /* Shows width of external pulse */ |
| uint32_t event[3]; /* +0x78 */ |
| }; |
| typedef volatile struct ttc_tmr_regs ttc_tmr_regs_t; |
| |
| static freq_t _ttc_clk_get_freq(clk_t *clk); |
| static freq_t _ttc_clk_set_freq(clk_t *clk, freq_t hz); |
| static void _ttc_clk_recal(clk_t *clk); |
| static clk_t *_ttc_clk_init(clk_t *clk); |
| |
| static inline ttc_tmr_regs_t *ttc_get_regs(ttc_t *ttc) |
| { |
| return ttc->regs; |
| } |
| |
| static inline size_t ttc_get_timer_shift(ttc_id_t id) |
| { |
| switch (id) { |
| case TTC0_TIMER1: |
| case TTC1_TIMER1: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER1: |
| case TTC3_TIMER1: |
| #endif |
| return TTCX_TIMER1_OFFSET; |
| case TTC0_TIMER2: |
| case TTC1_TIMER2: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER2: |
| case TTC3_TIMER2: |
| #endif |
| return TTCX_TIMER2_OFFSET; |
| case TTC0_TIMER3: |
| case TTC1_TIMER3: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER3: |
| case TTC3_TIMER3: |
| #endif |
| return TTCX_TIMER3_OFFSET; |
| default: |
| ZF_LOGF("Invalid ttc_id_t!"); |
| return 0; |
| } |
| } |
| |
| static inline unsigned ttc_get_irq_pos(ttc_id_t id) |
| { |
| switch (id) { |
| case TTC0_TIMER1: |
| case TTC1_TIMER1: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER1: |
| case TTC3_TIMER1: |
| #endif |
| return TTCX_TIMER1_IRQ_POS; |
| case TTC0_TIMER2: |
| case TTC1_TIMER2: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER2: |
| case TTC3_TIMER2: |
| #endif |
| return TTCX_TIMER2_IRQ_POS; |
| case TTC0_TIMER3: |
| case TTC1_TIMER3: |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER3: |
| case TTC3_TIMER3: |
| #endif |
| return TTCX_TIMER3_IRQ_POS; |
| default: |
| ZF_LOGF("Invalid ttc_id_t!"); |
| return 0; |
| } |
| } |
| |
| static inline char *ttc_get_device_path(ttc_id_t id) |
| { |
| switch (id) { |
| case TTC0_TIMER1: |
| case TTC0_TIMER2: |
| case TTC0_TIMER3: |
| return TTC0_PATH; |
| case TTC1_TIMER1: |
| case TTC1_TIMER2: |
| case TTC1_TIMER3: |
| return TTC1_PATH; |
| #ifdef CONFIG_PLAT_ZYNQMP |
| case TTC2_TIMER1: |
| case TTC2_TIMER2: |
| case TTC2_TIMER3: |
| return TTC2_PATH; |
| case TTC3_TIMER1: |
| case TTC3_TIMER2: |
| case TTC3_TIMER3: |
| return TTC3_PATH; |
| #endif /* CONFIG_PLAT_ZYNQMP */ |
| default: |
| ZF_LOGF("Invalid ttc_id_t!"); |
| return NULL; |
| } |
| } |
| |
| /****************** Clocks ******************/ |
| |
| static ttc_t *ttc_clk_get_priv(clk_t *clk) |
| { |
| return (ttc_t *)clk->priv; |
| } |
| |
| /* FPGA PL Clocks */ |
| static freq_t _ttc_clk_get_freq(clk_t *clk) |
| { |
| ttc_t *ttc = ttc_clk_get_priv(clk); |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| uint32_t clk_ctrl; |
| freq_t fin, fout; |
| /* Get the parent frequency */ |
| if (clk->parent) { |
| fin = clk_get_freq(clk->parent); |
| } else { |
| fin = PCLK_FREQ; |
| } |
| /* Calculate fout */ |
| clk_ctrl = *regs->clk_ctrl; |
| if (clk_ctrl & CLKCTRL_PRESCALE_EN) { |
| fout = fin >> (CLKCTRL_GET_PRESCALE_VAL(clk_ctrl) + 1); |
| } else { |
| fout = fin; |
| } |
| /* Return */ |
| return fout; |
| } |
| |
| static freq_t _ttc_clk_set_freq(clk_t *clk, freq_t hz) |
| { |
| ttc_t *ttc = ttc_clk_get_priv(clk); |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| uint32_t v; |
| freq_t fin; |
| int ps; |
| /* Determine input clock frequency */ |
| if (clk->parent) { |
| fin = clk_get_freq(clk->parent); |
| } else { |
| fin = PCLK_FREQ; |
| } |
| /* Find a prescale value */ |
| for (ps = 0; fin > hz; ps++, fin >>= 1); |
| if (ps > PRESCALE_MAX) { |
| return 0; |
| } |
| /* Configure the timer */ |
| v = regs->clk_ctrl[0] & ~CLKCTRL_PRESCALE_MASK; |
| if (ps > 0) { |
| v |= CLKCTRL_PRESCALE_EN | CLKCTRL_PRESCALE_VAL(ps - 1); |
| } else { |
| v &= ~CLKCTRL_PRESCALE_EN; |
| } |
| *regs->clk_ctrl = v; |
| return clk_get_freq(clk); |
| } |
| |
| static void _ttc_clk_recal(clk_t *clk UNUSED) |
| { |
| assert(0); |
| } |
| |
| static clk_t *_ttc_clk_init(clk_t *clk) |
| { |
| return clk; |
| } |
| |
| static inline freq_t _ttc_get_freq(ttc_t *ttc) |
| { |
| return ttc->freq; |
| } |
| |
| static inline freq_t _ttc_set_freq(ttc_t *ttc, freq_t hz) |
| { |
| ttc->freq = clk_set_freq(&ttc->clk, hz); |
| return ttc->freq; |
| } |
| |
| static inline bool _ttc_check_interrupt(ttc_t *ttc) |
| { |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| /* The int_sts register is being accessed through typedef ttc_tmr_regs_t |
| * which is marked volatile, so the compiler will not elide this read. |
| */ |
| uint32_t res = *regs->int_sts; |
| /* There are no data dependencies within this function that imply that the |
| * CPU cannot reorder this read in the pipeline. Use a CPU read barrier to |
| * inform the CPU that it should stall reads until this read has completed. |
| */ |
| THREAD_MEMORY_RELEASE(); |
| |
| return !!res; |
| } |
| |
| /********************************************/ |
| |
| /* Computes the optimal clock frequency for interrupting after |
| * a given period of time. This will be the highest frequency |
| * such that starting from 0, the timer counter will reach its |
| * maximum value in AT MOST the specified time. |
| * |
| * If no such frequency is supported by the clock (ie. the |
| * requested time is too high) this returns ETIME. Returns 0 |
| * on success. |
| * |
| * If a frequency is found, the clock is reprogrammed to run |
| * at that frequency. The number of ticks it will take for the |
| * requested time to pass (ie. the interval) is computed and |
| * returned via an argument (interval). */ |
| static inline int _ttc_set_freq_for_ns(ttc_t *ttc, uint64_t ns, uint64_t *interval) |
| { |
| freq_t fin, f; |
| uint64_t interval_value; |
| |
| /* Set the clock source frequency |
| * 1 / (fin / max_cnt) > interval |
| * fin < max_cnt / interval */ |
| f = freq_cycles_and_ns_to_hz(CNT_MAX, ns); |
| fin = _ttc_set_freq(ttc, f); |
| if (fin > f) { |
| /* This happens when the requested time is so long that the clock can't |
| * run slow enough. In this case, the clock driver reported the minimum |
| * rate it can run at, and we can use that to calculate a maximum time. |
| */ |
| ZF_LOGE("Timeout too big for timer, max %"PRIu64", got %"PRIu64"\n", |
| freq_cycles_and_hz_to_ns(CNT_MAX, fin), ns); |
| |
| return ETIME; |
| } |
| |
| interval_value = freq_ns_and_hz_to_cycles(ns, fin); |
| |
| assert(interval_value <= CNT_MAX); |
| |
| if (interval) { |
| *interval = interval_value; |
| } |
| |
| return 0; |
| } |
| |
| int ttc_start(ttc_t *ttc) |
| { |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| *regs->cnt_ctrl &= ~CNTCTRL_STOP; |
| return 0; |
| } |
| |
| int ttc_stop(ttc_t *ttc) |
| { |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| *regs->cnt_ctrl |= CNTCTRL_STOP; |
| return 0; |
| } |
| |
| void ttc_freerun(ttc_t *ttc) |
| { |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| *regs->cnt_ctrl = CNTCTRL_RST; |
| *regs->int_en = INT_EVENT_OVR | INT_CNT_OVR; |
| } |
| |
| /* Set up the ttc to fire an interrupt every ns nanoseconds. |
| * The first such interrupt may arrive before ns nanoseconds |
| * have passed since calling. */ |
| static int _ttc_periodic(ttc_tmr_regs_t *regs, uint64_t interval) |
| { |
| *regs->interval = interval; |
| |
| /* Interval mode: Continuously count from 0 to value in interval register, |
| * triggering an interval interrupt and resetting the counter to 0 |
| * whenever the counter passes through 0. */ |
| *regs->cnt_ctrl |= CNTCTRL_INT; |
| |
| /* The INTERVAL interrupt is used in periodic mode. The only source of |
| * interrupts will be when the counter passes through 0 after reaching |
| * the value in the interval register. */ |
| *regs->int_en = INT_INTERVAL; |
| |
| return 0; |
| } |
| |
| static void ttc_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) |
| { |
| assert(data != NULL); |
| ttc_t *ttc = data; |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| |
| bool interrupt_pending = _ttc_check_interrupt(ttc); |
| |
| if (ttc->is_timestamp) { |
| /* Check if we already updated the timestamp when reading the time, |
| * the interrupt status register should be empty if we did */ |
| if (interrupt_pending) { |
| ttc->hi_time += ttc_ticks_to_ns(ttc, CNT_MAX); |
| } |
| } else { |
| /* The MATCH0 interrupt is used in oneshot mode. It is enabled when a |
| * oneshot function is called, and disabled here so only one interrupt |
| * is triggered per call. */ |
| *regs->int_en &= ~INT_MATCH0; |
| } |
| |
| /* Acknowledge the interrupt and call the user callback if any */ |
| ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the interrupt from the TTC"); |
| if (ttc->user_callback) { |
| if (ttc->is_timestamp) { |
| ttc->user_callback(ttc->user_callback_token, LTIMER_OVERFLOW_EVENT); |
| } else { |
| ttc->user_callback(ttc->user_callback_token, LTIMER_TIMEOUT_EVENT); |
| } |
| } |
| } |
| |
| uint64_t ttc_ticks_to_ns(ttc_t *ttc, uint32_t ticks) |
| { |
| if (!ttc) { |
| return 0; |
| } |
| uint32_t fin = _ttc_get_freq(ttc); |
| return freq_cycles_and_hz_to_ns(ticks, fin); |
| } |
| |
| uint64_t ttc_get_time(ttc_t *ttc) |
| { |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| uint32_t cnt = *regs->cnt_val; |
| bool interrupt_pending = _ttc_check_interrupt(ttc); |
| /* Check if there is an interrupt pending, i.e. counter overflowed */ |
| if (interrupt_pending) { |
| /* Re-read the counter again */ |
| cnt = *regs->cnt_val; |
| /* Bump the hi_time counter now, as there may be latency in serving the interrupt */ |
| ttc->hi_time += ttc_ticks_to_ns(ttc, CNT_MAX); |
| } |
| uint32_t fin = _ttc_get_freq(ttc); |
| return ttc->hi_time + freq_cycles_and_hz_to_ns(cnt, fin); |
| } |
| |
| /* Set up the ttc to fire an interrupt ns nanoseconds after this |
| * function is called. */ |
| static int _ttc_oneshot_relative(ttc_tmr_regs_t *regs, uint64_t interval) |
| { |
| |
| /* In overflow mode the ttc will continuously count up to 0xffff and reset to 0. |
| * The ttc will be programmed to interrupt when the counter reaches |
| * current_time + interval, allowing the addition to wrap around (16 bits). |
| */ |
| |
| #ifdef CONFIG_PLAT_ZYNQMP |
| *regs->match[0] = (interval + *regs->cnt_val); |
| #else |
| *regs->match[0] = (interval + *regs->cnt_val) % BIT(CNT_WIDTH); |
| #endif |
| |
| /* Overflow mode: Continuously count from 0 to 0xffff (this is a 16 bit ttc). |
| * In this mode no interrval interrupts. A match interrupt (MATCH0) will be used |
| * in this mode. */ |
| *regs->cnt_ctrl &= ~CNTCTRL_INT; |
| |
| /* The MATCH0 interrupt is used in oneshot mode. The only source of interrupts |
| * will be when the counter passes through the value in the match[0] register. |
| * This interrupt is disabled in the irq handler so it is only triggered once. |
| */ |
| *regs->int_en = INT_MATCH0; |
| |
| return 0; |
| } |
| |
| int ttc_set_timeout(ttc_t *ttc, uint64_t ns, bool periodic) |
| { |
| if (ttc == NULL) { |
| return EINVAL; |
| } |
| |
| /* Program the clock and compute the interval value */ |
| uint64_t interval; |
| int error = _ttc_set_freq_for_ns(ttc, ns, &interval); |
| if (error) { |
| return error; |
| } |
| |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| if (periodic) { |
| return _ttc_periodic(regs, interval); |
| } else { |
| return _ttc_oneshot_relative(regs, interval); |
| } |
| } |
| |
| static int allocate_register_callback(pmem_region_t pmem, unsigned curr_num, size_t num_regs, void *token) |
| { |
| assert(token != NULL); |
| ttc_t *ttc = token; |
| ttc->regs = ps_pmem_map(&ttc->io_ops, pmem, false, PS_MEM_NORMAL); |
| if (ttc->regs == NULL) { |
| ZF_LOGE("Failed to map in registers for the TTC"); |
| return EIO; |
| } |
| /* This sets the base of the ttc_tmr_regs_t pointer to |
| * an offset into the ttc's mmio region such that |
| * ((ttc_tmr_regs_t*)vaddr)->clk_ctrl |
| * (and all other registers) refers to the address of the |
| * register relevant for the specified ttc device. */ |
| ttc->regs += ttc_get_timer_shift(ttc->id); |
| ttc->timer_pmem = pmem; |
| return 0; |
| } |
| |
| static int allocate_irq_callback(ps_irq_t irq, unsigned curr_num, size_t num_irqs, void *token) |
| { |
| assert(token != NULL); |
| ttc_t *ttc = token; |
| assert(num_irqs == IRQS_PER_TTC); |
| /* Get the corresponding IRQ position and register the IRQ if it matches */ |
| unsigned irq_pos = ttc_get_irq_pos(ttc->id); |
| if (irq_pos == curr_num) { |
| ttc->irq_id = ps_irq_register(&ttc->io_ops.irq_ops, irq, ttc_handle_irq, ttc); |
| if (ttc->irq_id < 0) { |
| ZF_LOGE("Failed to register the IRQ for TTC timer %d", ttc->id); |
| return EIO; |
| } |
| } |
| return 0; |
| } |
| |
| int ttc_init(ttc_t *ttc, ttc_config_t config) |
| { |
| if (ttc == NULL) { |
| ZF_LOGE("ttc is NULL"); |
| return EINVAL; |
| } |
| |
| /* Initialise all the struct members */ |
| ttc->io_ops = config.io_ops; |
| ttc->user_callback = config.user_callback; |
| ttc->user_callback_token = config.user_callback_token; |
| ttc->irq_id = PS_INVALID_IRQ_ID; |
| ttc->is_timestamp = config.is_timestamp; |
| ttc->id = config.id; |
| |
| char *device_path = ttc_get_device_path(config.id); |
| |
| /* Read the timer's path in the DTB */ |
| ps_fdt_cookie_t *cookie = NULL; |
| int error = ps_fdt_read_path(&ttc->io_ops.io_fdt, &ttc->io_ops.malloc_ops, device_path, &cookie); |
| if (error) { |
| ZF_LOGF_IF(ps_fdt_cleanup_cookie(&ttc->io_ops.malloc_ops, cookie), CLEANUP_FAIL_TEXT); |
| ZF_LOGF_IF(ttc_destroy(ttc), CLEANUP_FAIL_TEXT); |
| return ENODEV; |
| } |
| |
| /* Walk the registers and allocate them */ |
| error = ps_fdt_walk_registers(&ttc->io_ops.io_fdt, cookie, allocate_register_callback, ttc); |
| if (error) { |
| ZF_LOGF_IF(ps_fdt_cleanup_cookie(&ttc->io_ops.malloc_ops, cookie), CLEANUP_FAIL_TEXT); |
| ZF_LOGF_IF(ttc_destroy(ttc), CLEANUP_FAIL_TEXT); |
| return ENODEV; |
| } |
| |
| /* Walk the interrupts and allocate the corresponding interrupt for this timer */ |
| error = ps_fdt_walk_irqs(&ttc->io_ops.io_fdt, cookie, allocate_irq_callback, ttc); |
| if (error) { |
| ZF_LOGF_IF(ps_fdt_cleanup_cookie(&ttc->io_ops.malloc_ops, cookie), CLEANUP_FAIL_TEXT); |
| ZF_LOGF_IF(ttc_destroy(ttc), CLEANUP_FAIL_TEXT); |
| return ENODEV; |
| } |
| |
| /* Configure clock source */ |
| memset(&ttc->clk, 0, sizeof(ttc->clk)); |
| ttc->clk.name = STRINGIFY(config.id); |
| ttc->clk.get_freq = _ttc_clk_get_freq; |
| ttc->clk.set_freq = _ttc_clk_set_freq; |
| ttc->clk.recal = _ttc_clk_recal; |
| ttc->clk.init = _ttc_clk_init; |
| ttc->clk.priv = ttc; |
| |
| if (config.clk_src) { |
| clk_register_child(config.clk_src, &ttc->clk); |
| } |
| ttc->freq = clk_get_freq(&ttc->clk); |
| |
| ttc_tmr_regs_t *regs = ttc_get_regs(ttc); |
| *regs->int_en = 0; |
| FORCE_READ(regs->int_sts); /* Clear on read */ |
| *regs->cnt_ctrl = CNTCTRL_RST | CNTCTRL_STOP | CNTCTRL_INT | CNTCTRL_MATCH; |
| *regs->clk_ctrl = 0; |
| *regs->int_en = INT_INTERVAL; |
| *regs->interval = CNT_MAX; |
| |
| return 0; |
| } |
| |
| int ttc_destroy(ttc_t *ttc) |
| { |
| assert(ttc != NULL); |
| |
| if (ttc->regs) { |
| ZF_LOGF_IF(ttc_stop(ttc), "Failed to stop the TTC before de-allocating it"); |
| ps_io_unmap(&ttc->io_ops.io_mapper, ttc->regs, (size_t) ttc->timer_pmem.length); |
| } |
| |
| if (ttc->irq_id != PS_INVALID_IRQ_ID) { |
| ZF_LOGF_IF(ps_irq_unregister(&ttc->io_ops.irq_ops, ttc->irq_id), "Failed to unregister IRQ"); |
| } |
| |
| return 0; |
| } |