/**
* @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"