/* * Copyright (c) 2020 Stephanos Ioannidis * Copyright (c) 2018 Xilinx, Inc. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT xlnx_ttcps #include #include #include #include #include #include #include "xlnx_psttc_timer_priv.h" #define TIMER_INDEX CONFIG_XLNX_PSTTC_TIMER_INDEX #define TIMER_IRQ DT_INST_IRQN(0) #define TIMER_BASE_ADDR DT_INST_REG_ADDR(0) #define TIMER_CLOCK_FREQUECY DT_INST_PROP(0, clock_frequency) #define TICKS_PER_SEC CONFIG_SYS_CLOCK_TICKS_PER_SEC #define CYCLES_PER_SEC TIMER_CLOCK_FREQUECY #define CYCLES_PER_TICK (CYCLES_PER_SEC / TICKS_PER_SEC) #if defined(CONFIG_TEST) const int32_t z_sys_timer_irq_for_test = DT_IRQN(DT_INST(0, xlnx_ttcps)); #endif /* * CYCLES_NEXT_MIN must be large enough to ensure that the timer does not miss * interrupts. This value was conservatively set using the trial and error * method, and there is room for improvement. */ #define CYCLES_NEXT_MIN (10000) #define CYCLES_NEXT_MAX (XTTC_MAX_INTERVAL_COUNT) BUILD_ASSERT(TIMER_CLOCK_FREQUECY == CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC, "Configured system timer frequency does not match the TTC " "clock frequency in the device tree"); BUILD_ASSERT(CYCLES_PER_SEC >= TICKS_PER_SEC, "Timer clock frequency must be greater than the system tick " "frequency"); BUILD_ASSERT((CYCLES_PER_SEC % TICKS_PER_SEC) == 0, "Timer clock frequency is not divisible by the system tick " "frequency"); #ifdef CONFIG_TICKLESS_KERNEL static uint32_t last_cycles; #endif static uint32_t read_count(void) { /* Read current counter value */ return sys_read32(TIMER_BASE_ADDR + XTTCPS_COUNT_VALUE_OFFSET); } static void update_match(uint32_t cycles, uint32_t match) { uint32_t delta = match - cycles; /* Ensure that the match value meets the minimum timing requirements */ if (delta < CYCLES_NEXT_MIN) { match += CYCLES_NEXT_MIN - delta; } /* Write counter match value for interrupt generation */ sys_write32(match, TIMER_BASE_ADDR + XTTCPS_MATCH_0_OFFSET); } static void ttc_isr(const void *arg) { uint32_t cycles; uint32_t ticks; ARG_UNUSED(arg); /* Acknowledge interrupt */ sys_read32(TIMER_BASE_ADDR + XTTCPS_ISR_OFFSET); /* Read counter value */ cycles = read_count(); #ifdef CONFIG_TICKLESS_KERNEL /* Calculate the number of ticks since last announcement */ ticks = (cycles - last_cycles) / CYCLES_PER_TICK; /* Update last cycles count */ last_cycles = cycles; #else /* Update counter match value for the next interrupt */ update_match(cycles, cycles + CYCLES_PER_TICK); /* Advance tick count by 1 */ ticks = 1; #endif /* Announce to the kernel*/ sys_clock_announce(ticks); } void sys_clock_set_timeout(int32_t ticks, bool idle) { #ifdef CONFIG_TICKLESS_KERNEL uint32_t cycles; uint32_t next_cycles; /* Read counter value */ cycles = read_count(); /* Calculate timeout counter value */ if (ticks == K_TICKS_FOREVER) { next_cycles = cycles + CYCLES_NEXT_MAX; } else { next_cycles = cycles + ((uint32_t)ticks * CYCLES_PER_TICK); } /* Set match value for the next interrupt */ update_match(cycles, next_cycles); #endif } uint32_t sys_clock_elapsed(void) { #ifdef CONFIG_TICKLESS_KERNEL uint32_t cycles; /* Read counter value */ cycles = read_count(); /* Return the number of ticks since last announcement */ return (cycles - last_cycles) / CYCLES_PER_TICK; #else /* Always return 0 for tickful operation */ return 0; #endif } uint32_t sys_clock_cycle_get_32(void) { /* Return the current counter value */ return read_count(); } static int sys_clock_driver_init(void) { uint32_t reg_val; /* Stop timer */ sys_write32(XTTCPS_CNT_CNTRL_DIS_MASK, TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); #ifdef CONFIG_TICKLESS_KERNEL /* Initialise internal states */ last_cycles = 0; #endif /* Initialise timer registers */ sys_write32(XTTCPS_CNT_CNTRL_RESET_VALUE, TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_CLK_CNTRL_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_INTERVAL_VAL_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_MATCH_0_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_MATCH_1_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_MATCH_2_OFFSET); sys_write32(0, TIMER_BASE_ADDR + XTTCPS_IER_OFFSET); sys_write32(XTTCPS_IXR_ALL_MASK, TIMER_BASE_ADDR + XTTCPS_ISR_OFFSET); /* Reset counter value */ reg_val = sys_read32(TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); reg_val |= XTTCPS_CNT_CNTRL_RST_MASK; sys_write32(reg_val, TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); /* Set match mode */ reg_val = sys_read32(TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); reg_val |= XTTCPS_CNT_CNTRL_MATCH_MASK; sys_write32(reg_val, TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); /* Set initial timeout */ reg_val = IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? CYCLES_NEXT_MAX : CYCLES_PER_TICK; sys_write32(reg_val, TIMER_BASE_ADDR + XTTCPS_MATCH_0_OFFSET); /* Connect timer interrupt */ IRQ_CONNECT(TIMER_IRQ, 0, ttc_isr, 0, 0); irq_enable(TIMER_IRQ); /* Enable timer interrupt */ reg_val = sys_read32(TIMER_BASE_ADDR + XTTCPS_IER_OFFSET); reg_val |= XTTCPS_IXR_MATCH_0_MASK; sys_write32(reg_val, TIMER_BASE_ADDR + XTTCPS_IER_OFFSET); /* Start timer */ reg_val = sys_read32(TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); reg_val &= (~XTTCPS_CNT_CNTRL_DIS_MASK); sys_write32(reg_val, TIMER_BASE_ADDR + XTTCPS_CNT_CNTRL_OFFSET); return 0; } SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);