mirror of https://github.com/torvalds/linux.git
151 lines
3.2 KiB
C
151 lines
3.2 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2025 Realtek Semiconductor Corp.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/irqflags.h>
|
|
#include <linux/interrupt.h>
|
|
#include "timer-of.h"
|
|
|
|
#define ENBL 1
|
|
#define DSBL 0
|
|
|
|
#define SYSTIMER_RATE 1000000
|
|
#define SYSTIMER_MIN_DELTA 0x64
|
|
#define SYSTIMER_MAX_DELTA ULONG_MAX
|
|
|
|
/* SYSTIMER Register Offset (RTK Internal Use) */
|
|
#define TS_LW_OFST 0x0
|
|
#define TS_HW_OFST 0x4
|
|
#define TS_CMP_VAL_LW_OFST 0x8
|
|
#define TS_CMP_VAL_HW_OFST 0xC
|
|
#define TS_CMP_CTRL_OFST 0x10
|
|
#define TS_CMP_STAT_OFST 0x14
|
|
|
|
/* SYSTIMER CMP CTRL REG Mask */
|
|
#define TS_CMP_EN_MASK 0x1
|
|
#define TS_WR_EN0_MASK 0x2
|
|
|
|
static void __iomem *systimer_base;
|
|
|
|
static u64 rtk_ts64_read(void)
|
|
{
|
|
u32 low, high;
|
|
u64 ts;
|
|
|
|
/* Caution: Read LSB word (TS_LW_OFST) first then MSB (TS_HW_OFST) */
|
|
low = readl(systimer_base + TS_LW_OFST);
|
|
high = readl(systimer_base + TS_HW_OFST);
|
|
ts = ((u64)high << 32) | low;
|
|
|
|
return ts;
|
|
}
|
|
|
|
static void rtk_cmp_value_write(u64 value)
|
|
{
|
|
u32 high, low;
|
|
|
|
low = value & 0xFFFFFFFF;
|
|
high = value >> 32;
|
|
|
|
writel(high, systimer_base + TS_CMP_VAL_HW_OFST);
|
|
writel(low, systimer_base + TS_CMP_VAL_LW_OFST);
|
|
}
|
|
|
|
static inline void rtk_cmp_en_write(bool cmp_en)
|
|
{
|
|
u32 val;
|
|
|
|
val = TS_WR_EN0_MASK;
|
|
if (cmp_en == ENBL)
|
|
val |= TS_CMP_EN_MASK;
|
|
|
|
writel(val, systimer_base + TS_CMP_CTRL_OFST);
|
|
}
|
|
|
|
static int rtk_syst_clkevt_next_event(unsigned long cycles, struct clock_event_device *clkevt)
|
|
{
|
|
u64 cmp_val;
|
|
|
|
rtk_cmp_en_write(DSBL);
|
|
cmp_val = rtk_ts64_read();
|
|
|
|
/* Set CMP value to current timestamp plus delta_us */
|
|
rtk_cmp_value_write(cmp_val + cycles);
|
|
rtk_cmp_en_write(ENBL);
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t rtk_ts_match_intr_handler(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *clkevt = dev_id;
|
|
void __iomem *reg_base;
|
|
u32 val;
|
|
|
|
/* Disable TS CMP Match */
|
|
rtk_cmp_en_write(DSBL);
|
|
|
|
/* Clear TS CMP INTR */
|
|
reg_base = systimer_base + TS_CMP_STAT_OFST;
|
|
val = readl(reg_base) & TS_CMP_EN_MASK;
|
|
writel(val | TS_CMP_EN_MASK, reg_base);
|
|
clkevt->event_handler(clkevt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int rtk_syst_shutdown(struct clock_event_device *clkevt)
|
|
{
|
|
void __iomem *reg_base;
|
|
u64 cmp_val = 0;
|
|
|
|
/* Disable TS CMP Match */
|
|
rtk_cmp_en_write(DSBL);
|
|
/* Set compare value to 0 */
|
|
rtk_cmp_value_write(cmp_val);
|
|
|
|
/* Clear TS CMP INTR */
|
|
reg_base = systimer_base + TS_CMP_STAT_OFST;
|
|
writel(TS_CMP_EN_MASK, reg_base);
|
|
return 0;
|
|
}
|
|
|
|
static struct timer_of rtk_timer_to = {
|
|
.flags = TIMER_OF_IRQ | TIMER_OF_BASE,
|
|
|
|
.clkevt = {
|
|
.name = "rtk-clkevt",
|
|
.rating = 300,
|
|
.cpumask = cpu_possible_mask,
|
|
.features = CLOCK_EVT_FEAT_DYNIRQ |
|
|
CLOCK_EVT_FEAT_ONESHOT,
|
|
.set_next_event = rtk_syst_clkevt_next_event,
|
|
.set_state_oneshot = rtk_syst_shutdown,
|
|
.set_state_shutdown = rtk_syst_shutdown,
|
|
},
|
|
|
|
.of_irq = {
|
|
.flags = IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = rtk_ts_match_intr_handler,
|
|
},
|
|
};
|
|
|
|
static int __init rtk_systimer_init(struct device_node *node)
|
|
{
|
|
int ret;
|
|
|
|
ret = timer_of_init(node, &rtk_timer_to);
|
|
if (ret)
|
|
return ret;
|
|
|
|
systimer_base = timer_of_base(&rtk_timer_to);
|
|
clockevents_config_and_register(&rtk_timer_to.clkevt, SYSTIMER_RATE,
|
|
SYSTIMER_MIN_DELTA, SYSTIMER_MAX_DELTA);
|
|
|
|
return 0;
|
|
}
|
|
|
|
TIMER_OF_DECLARE(rtk_systimer, "realtek,rtd1625-systimer", rtk_systimer_init);
|