/** * @file * * @ingroup RTEMSBSPsARMZynqMP * * @brief Triple Timer Counter clock functions definitions. */ /* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 2023 Reflex Aerospace GmbH * * Written by Philip Kirkpatrick * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include typedef struct { struct timecounter ttc_tc; uint32_t irq_match_interval; uint32_t tick_miss; } ttc_clock_context; static ttc_clock_context ttc_clock_instance = {0, }; #define TTC_REFERENCE_CLOCK 100000000 uint32_t _CPU_Counter_frequency( void ) { return ttc_clock_instance.ttc_tc.tc_frequency; } static uint32_t zynqmp_ttc_get_timecount(struct timecounter *tc) { uint32_t time; time = XTtcPs_ReadReg(BSP_SELECTED_TTC_ADDR, XTTCPS_COUNT_VALUE_OFFSET); return time; } CPU_Counter_ticks _CPU_Counter_read(void) { return zynqmp_ttc_get_timecount(&ttc_clock_instance.ttc_tc); } /** * @brief Initialize the HW peripheral for clock driver * * Clock driver is implemented by RTI module * * @retval Void */ static void zynqmp_ttc_clock_driver_support_initialize_hardware(void) { uint32_t microsec_per_tick; uint16_t clock_ratio; uint8_t index; uint32_t frequency; uint32_t prescaler; uint32_t tmp_reg_val; microsec_per_tick = rtems_configuration_get_microseconds_per_tick(); /* Check the TTC is OFF before reconfiguring */ XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_CNT_CNTRL_OFFSET, /* Don't enable waveform output */ XTTCPS_CNT_CNTRL_DIS_MASK | XTTCPS_CNT_CNTRL_EN_WAVE_MASK); /* Prescaler value is 2^(N + 1) * Divide down the clock as much as possible while still retaining a * frequency that is an integer multiple of 1MHz. This maximizes time to * overflow while minimizing rounding errors in 1us periods */ clock_ratio = TTC_REFERENCE_CLOCK / 1000000; /* Search for the highest set bit. This is effectively min(log2(ratio)) */ for(index = sizeof(clock_ratio) * 8 - 1; index > 0; index--) { if((clock_ratio >> (index)) & 0x01) { break; } } if(index == 0 && (clock_ratio & 0x01) == 0) { /* No prescaler */ frequency = TTC_REFERENCE_CLOCK; XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_CLK_CNTRL_OFFSET, 0); } else { prescaler = index - 1; frequency = TTC_REFERENCE_CLOCK / (1 << (prescaler + 1)); XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_CLK_CNTRL_OFFSET, prescaler << XTTCPS_CLK_CNTRL_PS_VAL_SHIFT | XTTCPS_CLK_CNTRL_PS_EN_MASK); } /* Max out the counter interval */ tmp_reg_val = XTTCPS_INTERVAL_VAL_MASK; XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_INTERVAL_VAL_OFFSET, tmp_reg_val); /* Setup match register to generate tick IRQ */ ttc_clock_instance.irq_match_interval = (uint32_t) (((uint64_t)frequency * microsec_per_tick) / 1000000); XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_MATCH_0_OFFSET, ttc_clock_instance.irq_match_interval); /* Clear interupts (clear on read) */ XTtcPs_ReadReg(BSP_SELECTED_TTC_ADDR, XTTCPS_ISR_OFFSET); /* Enable interupt for match register */ XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_IER_OFFSET, XTTCPS_IXR_MATCH_0_MASK); /* Configure, reset, and enable counter */ XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_CNT_CNTRL_OFFSET, XTTCPS_CNT_CNTRL_EN_WAVE_MASK | /* Don't enable waveform output */ XTTCPS_CNT_CNTRL_RST_MASK | /* Reset count and start counter */ XTTCPS_CNT_CNTRL_MATCH_MASK /* Enable match mode */ /* Increment mode */ /* Overflow mode */ /* Not disabled */ ); /* set timecounter */ ttc_clock_instance.ttc_tc.tc_get_timecount = zynqmp_ttc_get_timecount; ttc_clock_instance.ttc_tc.tc_counter_mask = XTTCPS_COUNT_VALUE_MASK; ttc_clock_instance.ttc_tc.tc_frequency = frequency; ttc_clock_instance.ttc_tc.tc_quality = RTEMS_TIMECOUNTER_QUALITY_CLOCK_DRIVER; rtems_timecounter_install(&ttc_clock_instance.ttc_tc); } /** * @brief Clears interrupt source * * @retval Void */ static void zynqmp_ttc_clock_driver_support_at_tick(ttc_clock_context *tc) { uint32_t irq_flags; uint32_t cval; uint32_t now; uint32_t delta; /* Get and clear interupts (clear on read) */ irq_flags = XTtcPs_ReadReg(BSP_SELECTED_TTC_ADDR, XTTCPS_ISR_OFFSET); if(irq_flags & XTTCPS_IXR_MATCH_0_MASK) { /* Update match */ cval = XTtcPs_ReadReg(BSP_SELECTED_TTC_ADDR, XTTCPS_MATCH_0_OFFSET); /* Check that the match for the next tick is in the future * If no, then set the match for one irq interval from now * This will have the effect that your timebase will slip but * won't hang waiting for the counter to wrap around. * If this happens during normal operation, there is a problem * causing this interrupt to not be serviced quickly enough * If this happens during debugging, that is normal and expected * because the TTC does NOT pause when the CPU is halted on a breakpoint */ now = XTtcPs_ReadReg(BSP_SELECTED_TTC_ADDR, XTTCPS_COUNT_VALUE_OFFSET); delta = now - cval; if(delta > tc->irq_match_interval) { cval = now; tc->tick_miss++; } cval += tc->irq_match_interval; XTtcPs_WriteReg(BSP_SELECTED_TTC_ADDR, XTTCPS_MATCH_0_OFFSET, cval); } /* Else, something is set up wrong, only match should be enabled */ } static rtems_interrupt_entry zynqmp_ttc_interrupt_entry; static void zynqmp_ttc_clock_driver_support_install_isr( rtems_interrupt_handler handler ) { rtems_status_code sc; rtems_interrupt_entry_initialize( &zynqmp_ttc_interrupt_entry, handler, &ttc_clock_instance, "Clock" ); sc = rtems_interrupt_entry_install( BSP_SELECTED_TTC_IRQ, RTEMS_INTERRUPT_UNIQUE, &zynqmp_ttc_interrupt_entry ); if ( sc != RTEMS_SUCCESSFUL ) { rtems_fatal_error_occurred(0xdeadbeef); } } #define Clock_driver_support_at_tick(arg) \ zynqmp_ttc_clock_driver_support_at_tick(arg) #define Clock_driver_support_initialize_hardware \ zynqmp_ttc_clock_driver_support_initialize_hardware #define Clock_driver_support_install_isr(isr) \ zynqmp_ttc_clock_driver_support_install_isr(isr) #include "../../../shared/dev/clock/clockimpl.h"