blob: 9dbcd5f78581502e5ecbfdb809d5fb1676380ccd [file]
/*
* Copyright (C) 2026 Microchip Technology Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_pit_g1_timer
#include <soc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/mchp_sam_pmc.h>
#include <zephyr/drivers/timer/system_timer.h>
#include <zephyr/init.h>
#include <zephyr/irq.h>
#include <zephyr/spinlock.h>
#include <zephyr/sys/clock.h>
#if defined(CONFIG_TICKLESS_KERNEL)
BUILD_ASSERT(0, "PIT driver for tickless kernel support DOES NOT implemented yet!");
#endif
BUILD_ASSERT(DT_HAS_CHOSEN(zephyr_system_timer),
"zephyr,system-timer must be set to a microchip,pit-g1-timer node");
BUILD_ASSERT(DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_system_timer), microchip_pit_g1_timer),
"zephyr,system-timer must point to a microchip,pit-g1-timer compatible node");
#define NODE_SYSTICK DT_CHOSEN(zephyr_system_timer)
#define TIMER_IRQ_NUM DT_IRQN(NODE_SYSTICK)
#define TIMER_IRQ_PRIO DT_IRQ(NODE_SYSTICK, priority)
#define TIMER_IRQ_FLAGS DT_IRQ(NODE_SYSTICK, flags)
#define CYCLES_PER_TICK (sys_clock_hw_cycles_per_sec() / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
#define MAX_PERIOD_CYCLES ((PIT_PIVR_CPIV_Msk >> PIT_PIVR_CPIV_Pos) + 1)
#define MAX_TICKS ((k_ticks_t)(MAX_PERIOD_CYCLES / CYCLES_PER_TICK))
BUILD_ASSERT(CYCLES_PER_TICK > 0, "PIT CYCLES_PER_TICK must be greater than 0");
BUILD_ASSERT(CYCLES_PER_TICK <= MAX_PERIOD_CYCLES,
"system tick period exceeds the maximum Periodic Interval Value");
BUILD_ASSERT(MAX_TICKS > 0, "system tick MAX_TICKS must be greater than 0");
/* Device constant configuration parameters */
struct mchp_pit_timer_config {
DEVICE_MMIO_NAMED_ROM(reg_base);
struct sam_clk_cfg clock_cfg;
};
struct mchp_pit_timer_data {
DEVICE_MMIO_NAMED_RAM(reg_base);
uint64_t accumulated_cycles;
uint64_t last_cycle;
uint32_t piv;
};
#define DEV_CFG(_dev) ((const struct mchp_pit_timer_config *)(_dev)->config)
#define DEV_DATA(_dev) ((struct mchp_pit_timer_data *)(_dev)->data)
static const struct device *systick_timer_dev;
#if defined(CONFIG_TEST)
const int32_t z_sys_timer_irq_for_test = TIMER_IRQ_NUM;
#endif
static inline uint32_t mchp_pit_reg_read(uint32_t reg)
{
return sys_read32(DEVICE_MMIO_NAMED_GET(systick_timer_dev, reg_base) + reg);
}
static inline void mchp_pit_reg_write(uint32_t data, uint32_t reg, uint32_t mask)
{
sys_write32((mchp_pit_reg_read(reg) & ~mask) | data,
DEVICE_MMIO_NAMED_GET(systick_timer_dev, reg_base) + reg);
}
static uint64_t mchp_pit_get_cycles(uint32_t reg, bool commit)
{
struct mchp_pit_timer_data *data = systick_timer_dev->data;
uint64_t cycles;
uint32_t piir;
uint32_t cpiv;
uint32_t picnt;
piir = mchp_pit_reg_read(reg);
cpiv = FIELD_GET(PIT_PIIR_CPIV_Msk, piir);
picnt = FIELD_GET(PIT_PIIR_PICNT_Msk, piir);
cycles = data->accumulated_cycles;
cycles += (uint64_t)picnt * (data->piv + 1);
if (commit) {
data->accumulated_cycles = cycles;
}
cycles += cpiv;
return cycles;
}
static void mchp_pit_isr(const void *arg)
{
ARG_UNUSED(arg);
struct mchp_pit_timer_data *data = systick_timer_dev->data;
k_spinlock_key_t key;
uint32_t elapsed_ticks;
uint64_t curr_cycle;
/* If no pending event */
if (FIELD_GET(PIT_SR_PITS_Msk, mchp_pit_reg_read(PIT_SR_REG_OFST)) == 0) {
return;
}
key = sys_clock_lock();
curr_cycle = mchp_pit_get_cycles(PIT_PIVR_REG_OFST, true);
elapsed_ticks = (curr_cycle - data->last_cycle) / CYCLES_PER_TICK;
data->last_cycle += (uint64_t)elapsed_ticks * CYCLES_PER_TICK;
sys_clock_announce_locked(elapsed_ticks, key);
}
void sys_clock_set_timeout(uint32_t ticks, bool idle)
{
ARG_UNUSED(ticks);
ARG_UNUSED(idle);
/* do nothing for tickful kernel system */
}
uint32_t sys_clock_elapsed(void)
{
/* Always return 0 for tickful kernel system */
return 0;
}
uint32_t sys_clock_cycle_get_32(void)
{
k_spinlock_key_t key = sys_clock_lock();
uint32_t cycles = (uint32_t)mchp_pit_get_cycles(PIT_PIIR_REG_OFST, false);
sys_clock_unlock(key);
return cycles;
}
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
uint64_t sys_clock_cycle_get_64(void)
{
k_spinlock_key_t key = sys_clock_lock();
uint64_t cycles = mchp_pit_get_cycles(PIT_PIIR_REG_OFST, false);
sys_clock_unlock(key);
return cycles;
}
#endif
static int sys_clock_driver_init(void)
{
const struct mchp_pit_timer_config *cfg;
struct mchp_pit_timer_data *data;
systick_timer_dev = DEVICE_DT_GET(NODE_SYSTICK);
cfg = systick_timer_dev->config;
data = systick_timer_dev->data;
/* Enable the PIT peripheral clock through the PMC before any register access. */
(void)clock_control_on(DEVICE_DT_GET(DT_NODELABEL(pmc)),
(clock_control_subsys_t)&cfg->clock_cfg);
data->accumulated_cycles = 0;
data->last_cycle = 0;
data->piv = CYCLES_PER_TICK - 1;
DEVICE_MMIO_NAMED_MAP(systick_timer_dev, reg_base, K_MEM_CACHE_NONE);
/* Read PIT_PIVR and clear PITS in PIT_SR */
(void)mchp_pit_reg_read(PIT_PIVR_REG_OFST);
/* Disable PIT */
mchp_pit_reg_write(0, PIT_MR_REG_OFST, PIT_MR_PITIEN_Msk | PIT_MR_PITEN_Msk);
/* IRQ initialize */
IRQ_CONNECT(TIMER_IRQ_NUM, TIMER_IRQ_PRIO, mchp_pit_isr, NULL, TIMER_IRQ_FLAGS);
irq_enable(TIMER_IRQ_NUM);
/* Set Periodic Interval Value */
mchp_pit_reg_write(PIT_MR_PIV(data->piv), PIT_MR_REG_OFST, PIT_MR_PIV_Msk);
/* Enable Period Interval Timer Interrupt */
mchp_pit_reg_write(PIT_MR_PITIEN_Msk, PIT_MR_REG_OFST, PIT_MR_PITIEN_Msk);
/* Enable Period Interval Timer */
mchp_pit_reg_write(PIT_MR_PITEN_Msk, PIT_MR_REG_OFST, PIT_MR_PITEN_Msk);
return 0;
}
#define MCHP_PIT_TIMER(n) \
static struct mchp_pit_timer_data mchp_pit_timer_data_##n; \
static const struct mchp_pit_timer_config mchp_pit_timer_config_##n = { \
DEVICE_MMIO_NAMED_ROM_INIT(reg_base, DT_DRV_INST(n)), \
.clock_cfg = SAM_DT_INST_CLOCK_PMC_CFG(n), \
}; \
\
DEVICE_DT_INST_DEFINE(n, NULL, NULL, \
&mchp_pit_timer_data_##n, \
&mchp_pit_timer_config_##n, \
PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY, NULL);
DT_INST_FOREACH_STATUS_OKAY(MCHP_PIT_TIMER);
SYS_INIT(sys_clock_driver_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);