[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <aKdA030OhAlIXRxU@xhacker>
Date: Thu, 21 Aug 2025 23:52:51 +0800
From: Jisheng Zhang <jszhang@...nel.org>
To: Daniel Lezcano <daniel.lezcano@...aro.org>,
Thomas Gleixner <tglx@...utronix.de>, Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
linux-arm-kernel@...ts.infradead.org,
"linux-watchdog@...r.kernel.org Conor Dooley" <conor+dt@...nel.org>
Cc: linux-kernel@...r.kernel.org, devicetree@...r.kernel.org
Subject: Re: [PATCH 2/2] clocksource/drivers: Add ARM SSE(Subsystems for
Embedded) Timer driver
On Thu, Aug 21, 2025 at 11:24:29PM +0800, Jisheng Zhang wrote:
> Here is the ARM SSE(Subsystems for Embedded) timer doc URL:
> https://developer.arm.com/documentation/107610/0000/System-timer-components?lang=en
>
> Although the IP is mostly seen on MCU SoC platforms, but nothing
> prevent it from being integrated into linux capable SoC platforms.
>
> The IP core may have a system counter to generate timestamp value,
> a system timer to raise an interrupt when a period has elapsed, and
> a System Watchdog to detect errant system behaviour then reset the
> system if a period elapses without ping.
>
> The differences between this IP and arm mmio arch timer include not
> limit to:
> 1. The system can add the timer frames as many as it want, I.E no
> up to 8 timer frames limitation at all.
> 2. The IP supports watchdog.
> 3. physical timer only.
> 4. The system counter can be exposed to so can be under the control of
> linux.
>
> Introduce a clocksource+watchdog driver for the ARM SSE timer IP.
+ arm and wdt maillist
>
> Signed-off-by: Jisheng Zhang <jszhang@...nel.org>
> ---
> drivers/clocksource/Kconfig | 7 +
> drivers/clocksource/Makefile | 1 +
> drivers/clocksource/timer-sse.c | 540 ++++++++++++++++++++++++++++++++
> 3 files changed, 548 insertions(+)
> create mode 100644 drivers/clocksource/timer-sse.c
>
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index 645f517a1ac2..bfcbd7a1a498 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -152,6 +152,13 @@ config REALTEK_OTTO_TIMER
> RT8391, RTL8392, RTL8393 and RTL8396 and chips of the RTL930x series
> such as RTL9301, RTL9302 or RTL9303.
>
> +config SSE_TIMER
> + bool "ARM SSE(Subsystems for Embedded) system timer driver"
> + depends on COMMON_CLK
> + default n
> + help
> + Enables support for the ARM SSE(Subsystems for Embedded) system timer driver.
> +
> config SUN4I_TIMER
> bool "Sun4i timer driver" if COMPILE_TEST
> depends on HAS_IOMEM
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index 205bf3b0a8f3..dd688807694d 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -33,6 +33,7 @@ obj-$(CONFIG_BCM2835_TIMER) += bcm2835_timer.o
> obj-$(CONFIG_CLPS711X_TIMER) += clps711x-timer.o
> obj-$(CONFIG_MXS_TIMER) += mxs_timer.o
> obj-$(CONFIG_CLKSRC_PXA) += timer-pxa.o
> +obj-$(CONFIG_SSE_TIMER) += timer-sse.o
> obj-$(CONFIG_SUN4I_TIMER) += timer-sun4i.o
> obj-$(CONFIG_SUN5I_HSTIMER) += timer-sun5i.o
> obj-$(CONFIG_MESON6_TIMER) += timer-meson6.o
> diff --git a/drivers/clocksource/timer-sse.c b/drivers/clocksource/timer-sse.c
> new file mode 100644
> index 000000000000..cf7d0bf07c2e
> --- /dev/null
> +++ b/drivers/clocksource/timer-sse.c
> @@ -0,0 +1,540 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Synaptics Incorporated
> + *
> + * Author: Jisheng Zhang <jszhang@...nel.org>
> + *
> + * Some code are borrow from
> + * linux/drivers/clocksource/arm_arch_timer.c
> + * Copyright (C) 2011 ARM Ltd.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/clocksource.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/io-64-nonatomic-lo-hi.h>
> +#include <linux/irq.h>
> +#include <linux/irqreturn.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/slab.h>
> +#include <linux/watchdog.h>
> +
> +#define CNTCR 0x00
> +#define CNTCV 0x08
> +
> +#define CNTPCT 0x00
> +#define CNTP_CVAL 0x20
> +#define CNTP_CTL 0x2c
> +#define CNTP_CTL_ENABLE BIT(0)
> +#define CNTP_CTL_IMASK BIT(1)
> +#define CNTP_CTL_ISTATUS BIT(2)
> +#define CNTP_AIVAL_RELOAD 0x48
> +#define CNTP_AIVAL_CTL 0x4c
> +#define CNTP_AIVAL_CTL_EN BIT(0)
> +#define CNTP_AIVAL_CTL_CLR BIT(1)
> +#define CNTP_CFG 0x50
> +#define CNTP_CFG_AIVAL_MASK GENMASK(3, 0)
> +#define CNTP_CFG_AIVAL_IMPL 1
> +
> +#define WCS 0x00
> +#define WCS_EN BIT(0)
> +#define WCS_WS0 BIT(1)
> +#define WCS_WS1 BIT(2)
> +#define WOR 0x08
> +#define WCV 0x10
> +
> +#define WRR 0x00
> +
> +#define SSE_WDT_DEFAULT_SECONDS 30
> +
> +enum sse_timer_mode {
> + SSE_TIMER_NORMAL,
> + SSE_TIMER_AUTOINC,
> +};
> +
> +struct sse_timer_frame {
> + void __iomem *base;
> + struct clocksource cs;
> + struct clock_event_device ce;
> + u32 ticks_per_jiffy;
> + int irq;
> + enum sse_timer_mode mode;
> +};
> +
> +struct sse_wdt_frame {
> + struct watchdog_device wdd;
> + void __iomem *ctrl_base;
> + void __iomem *refr_base;
> + unsigned long freq;
> + int irq;
> +};
> +
> +struct sse_timer {
> + void __iomem *cntctrl_base;
> + struct clk *clk;
> + struct sse_timer_frame *timers;
> + struct sse_wdt_frame *wdts;
> + int timer_num;
> + int wdt_num;
> +};
> +
> +#define cs_to_sse_timer_frame(x) \
> + container_of(x, struct sse_timer_frame, cs)
> +#define ce_to_sse_timer_frame(x) \
> + container_of(x, struct sse_timer_frame, ce)
> +
> +static bool nowayout = WATCHDOG_NOWAYOUT;
> +module_param(nowayout, bool, 0444);
> +MODULE_PARM_DESC(nowayout,
> + "Watchdog cannot be stopped once started (default="
> + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
> +
> +static __always_inline u64 sse_get_cntpct(struct sse_timer_frame *f)
> +{
> +#ifndef CONFIG_64BIT
> + u32 cnt_lo, cnt_hi, tmp_hi;
> +
> + do {
> + cnt_hi = __le32_to_cpu((__le32 __force)__raw_readl(f->base + CNTPCT + 4));
> + cnt_lo = __le32_to_cpu((__le32 __force)__raw_readl(f->base + CNTPCT));
> + tmp_hi = __le32_to_cpu((__le32 __force)__raw_readl(f->base + CNTPCT + 4));
> + } while (cnt_hi != tmp_hi);
> +
> + return ((u64) cnt_hi << 32) | cnt_lo;
> +#else
> + return readq_relaxed(f->base + CNTPCT);
> +#endif
> +
> +}
> +
> +static int sse_ce_shutdown(struct clock_event_device *ce)
> +{
> + struct sse_timer_frame *f = ce_to_sse_timer_frame(ce);
> + u32 val;
> +
> + val = readl_relaxed(f->base + CNTP_CTL);
> + val &= ~CNTP_CTL_ENABLE;
> + writel_relaxed(val, f->base + CNTP_CTL);
> + return 0;
> +}
> +
> +static int sse_ce_set_oneshot(struct clock_event_device *ce)
> +{
> + struct sse_timer_frame *f = ce_to_sse_timer_frame(ce);
> + u32 val;
> +
> + f->mode = SSE_TIMER_NORMAL;
> +
> + val = readl_relaxed(f->base + CNTP_CTL);
> + if (val & CNTP_CTL_ENABLE) {
> + val &= ~CNTP_CTL_ENABLE;
> + writel_relaxed(val, f->base + CNTP_CTL);
> + }
> +
> + writel_relaxed(0, f->base + CNTP_AIVAL_CTL);
> +
> + return 0;
> +}
> +
> +static int sse_ce_set_periodic(struct clock_event_device *ce)
> +{
> + struct sse_timer_frame *f = ce_to_sse_timer_frame(ce);
> + u32 val;
> +
> + f->mode = SSE_TIMER_AUTOINC;
> +
> + val = readl_relaxed(f->base + CNTP_CTL);
> + if (val & CNTP_CTL_ENABLE) {
> + val &= ~CNTP_CTL_ENABLE;
> + writel_relaxed(val, f->base + CNTP_CTL);
> + }
> +
> + val |= CNTP_CTL_ENABLE;
> + val &= ~CNTP_CTL_IMASK;
> +
> + writel_relaxed(f->ticks_per_jiffy, f->base + CNTP_AIVAL_RELOAD);
> + writel_relaxed(CNTP_AIVAL_CTL_EN, f->base + CNTP_AIVAL_CTL);
> + writel_relaxed(val, f->base + CNTP_CTL);
> +
> + return 0;
> +}
> +
> +static int sse_ce_next_event(unsigned long evt, struct clock_event_device *ce)
> +{
> + struct sse_timer_frame *f = ce_to_sse_timer_frame(ce);
> + u32 val;
> + u64 cnt;
> +
> + val = readl_relaxed(f->base + CNTP_CTL);
> + if (val & CNTP_CTL_ENABLE) {
> + val &= ~CNTP_CTL_ENABLE;
> + writel_relaxed(val, f->base + CNTP_CTL);
> + }
> +
> + val |= CNTP_CTL_ENABLE;
> + val &= ~CNTP_CTL_IMASK;
> +
> + cnt = sse_get_cntpct(f);
> + writeq_relaxed(evt + cnt, f->base + CNTP_CVAL);
> + writel_relaxed(val, f->base + CNTP_CTL);
> +
> + return 0;
> +}
> +
> +static irqreturn_t sse_timer_interrupt(int irq, void *dev_id)
> +{
> + struct sse_timer_frame *f = dev_id;
> + u32 val;
> +
> + val = readl_relaxed(f->base + CNTP_CTL);
> + if (!(val & CNTP_CTL_ISTATUS))
> + return IRQ_NONE;
> +
> + if (f->mode == SSE_TIMER_AUTOINC) {
> + val = readl_relaxed(f->base + CNTP_AIVAL_CTL);
> + val &= ~CNTP_AIVAL_CTL_CLR;
> + writel_relaxed(val, f->base + CNTP_AIVAL_CTL);
> + } else {
> + val |= CNTP_CTL_IMASK;
> + writel_relaxed(val, f->base + CNTP_CTL);
> + }
> + f->ce.event_handler(&f->ce);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sse_setup_clockevent(struct device *dev, struct sse_timer_frame *f,
> + unsigned long rate)
> +{
> + int ret;
> + u32 val = readl_relaxed(f->base + CNTP_CFG);
> +
> + f->ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
> +
> + f->ce.name = "sse-timer";
> + f->ce.rating = 300;
> + f->ce.irq = f->irq;
> + f->ce.cpumask = cpu_possible_mask;
> + f->ce.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ;
> + f->ce.set_state_shutdown = sse_ce_shutdown;
> + f->ce.set_next_event = sse_ce_next_event;
> + f->ce.set_state_oneshot_stopped = sse_ce_shutdown;
> + if (FIELD_GET(CNTP_CFG_AIVAL_MASK, val) == CNTP_CFG_AIVAL_IMPL) {
> + f->ce.set_state_periodic = sse_ce_set_periodic;
> + f->ce.set_state_oneshot = sse_ce_set_oneshot;
> + }
> +
> + clockevents_config_and_register(&f->ce, rate, 0xf, ULONG_MAX);
> +
> + ret = devm_request_irq(dev, f->irq, sse_timer_interrupt,
> + IRQF_TIMER | IRQF_NOBALANCING,
> + f->ce.name, f);
> + if (ret) {
> + dev_err(dev, "Unable to register interrupt\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static noinstr u64 cs_sse_get_cntpct(struct clocksource *cs)
> +{
> + struct sse_timer_frame *f = cs_to_sse_timer_frame(cs);
> +
> + return sse_get_cntpct(f);
> +}
> +
> +static int sse_setup_clocksource(struct device *dev, struct sse_timer_frame *f,
> + unsigned long rate)
> +{
> + int ret;
> +
> + f->cs.name = "sse-timer";
> + f->cs.rating = 300;
> + f->cs.read = cs_sse_get_cntpct;
> + f->cs.mask = CLOCKSOURCE_MASK(64);
> + f->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS;
> +
> + ret = clocksource_register_hz(&f->cs, rate);
> + if (ret) {
> + dev_err(dev, "Couldn't register clock source.\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static irqreturn_t sse_wdt_interrupt(int irq, void *dev_id)
> +{
> + struct sse_wdt_frame *f = dev_id;
> + u32 val;
> +
> + val = readl_relaxed(f->ctrl_base + WCS);
> + if (val & WCS_WS0)
> + watchdog_notify_pretimeout(&f->wdd);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int sse_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout)
> +{
> + struct sse_wdt_frame *f = watchdog_get_drvdata(wdd);
> +
> + wdd->timeout = timeout;
> +
> + /* Pretimeout is 1/2 of timeout */
> + wdd->pretimeout = timeout / 2;
> +
> + writel_relaxed(((u64)f->freq / 2) * timeout, f->ctrl_base + WOR);
> +
> + return 0;
> +}
> +
> +static int sse_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int timeout)
> +{
> + struct sse_wdt_frame *f = watchdog_get_drvdata(wdd);
> +
> + if (!timeout)
> + return -EINVAL;
> +
> + /* pretimeout should not exceed 1/2 of max_timeout */
> + if (timeout * 2 <= f->wdd.max_timeout)
> + return sse_wdt_set_timeout(wdd, timeout * 2);
> +
> + return -EINVAL;
> +}
> +
> +static int sse_wdt_ping(struct watchdog_device *wdd)
> +{
> + struct sse_wdt_frame *f = watchdog_get_drvdata(wdd);
> +
> + writel_relaxed(0, f->refr_base + WRR);
> +
> + return 0;
> +}
> +
> +static int sse_wdt_start(struct watchdog_device *wdd)
> +{
> + struct sse_wdt_frame *f = watchdog_get_drvdata(wdd);
> +
> + writel_relaxed(WCS_EN, f->ctrl_base + WCS);
> +
> + return 0;
> +}
> +
> +static int sse_wdt_stop(struct watchdog_device *wdd)
> +{
> + struct sse_wdt_frame *f = watchdog_get_drvdata(wdd);
> +
> + writel_relaxed(0, f->ctrl_base + WCS);
> +
> + return 0;
> +}
> +
> +static const struct watchdog_info sse_wdt_info = {
> + .identity = "ARM SSE Watchdog",
> + .options = WDIOF_SETTIMEOUT |
> + WDIOF_PRETIMEOUT |
> + WDIOF_KEEPALIVEPING |
> + WDIOF_MAGICCLOSE |
> + WDIOF_CARDRESET,
> +};
> +
> +static const struct watchdog_ops sse_wdt_ops = {
> + .owner = THIS_MODULE,
> + .start = sse_wdt_start,
> + .stop = sse_wdt_stop,
> + .ping = sse_wdt_ping,
> + .set_timeout = sse_wdt_set_timeout,
> + .set_pretimeout = sse_wdt_set_pretimeout,
> +};
> +
> +static int sse_setup_wdt(struct device *dev, struct sse_wdt_frame *f, unsigned long rate)
> +{
> + u32 val;
> + int ret;
> + unsigned int max_pretimeout, timeout = SSE_WDT_DEFAULT_SECONDS;
> +
> + f->freq = rate;
> + f->wdd.info = &sse_wdt_info;
> + f->wdd.ops = &sse_wdt_ops;
> + f->wdd.parent = dev;
> +
> + max_pretimeout = U32_MAX / f->freq;
> + /* Maximum timeout is twice the pretimeout */
> + f->wdd.max_timeout = max_pretimeout * 2;
> + f->wdd.max_hw_heartbeat_ms = max_pretimeout * 1000;
> + /* Minimum first timeout (pretimeout) is 1, so min_timeout as 2 */
> + f->wdd.min_timeout = 2;
> +
> + val = readl_relaxed(f->ctrl_base + WCS);
> + if (val & WCS_WS1) {
> + dev_warn(dev, "System reset by WDT.\n");
> + f->wdd.bootstatus |= WDIOF_CARDRESET;
> + }
> +
> + if (val & WCS_EN) {
> + timeout = readl_relaxed(f->ctrl_base + WOR) / 2 / f->freq;
> + set_bit(WDOG_HW_RUNNING, &f->wdd.status);
> + }
> +
> + watchdog_set_drvdata(&f->wdd, f);
> + watchdog_set_nowayout(&f->wdd, nowayout);
> + watchdog_init_timeout(&f->wdd, timeout, dev);
> + watchdog_stop_on_reboot(&f->wdd);
> + watchdog_stop_on_unregister(&f->wdd);
> +
> + ret = devm_watchdog_register_device(dev, &f->wdd);
> + if (ret)
> + return ret;
> +
> + ret = devm_request_irq(dev, f->irq, sse_wdt_interrupt, 0, "sse-wdt", f);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to register interrupt handler\n");
> +
> + return 0;
> +}
> +
> +static int sse_timer_probe(struct platform_device *pdev)
> +{
> + struct device_node *np = pdev->dev.of_node;
> + struct sse_timer *sse;
> + unsigned long rate;
> + int i, j, ret;
> +
> + sse = devm_kzalloc(&pdev->dev, sizeof(*sse), GFP_KERNEL);
> + if (!sse)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, sse);
> +
> + sse->cntctrl_base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(sse->cntctrl_base)) {
> + dev_err(&pdev->dev, "Can't map registers\n");
> + return PTR_ERR(sse->cntctrl_base);
> + }
> +
> + sse->clk = devm_clk_get_enabled(&pdev->dev, NULL);
> + if (IS_ERR(sse->clk)) {
> + dev_err(&pdev->dev, "Can't get timer clock\n");
> + return PTR_ERR(sse->clk);
> + }
> +
> + rate = clk_get_rate(sse->clk);
> + if (!rate) {
> + dev_err(&pdev->dev, "Couldn't get parent clock rate\n");
> + return -EINVAL;
> + }
> +
> + for_each_available_child_of_node_scoped(np, frame_node) {
> + const __be32 *addr = of_get_address(frame_node, 1, NULL, NULL);
> +
> + if (!addr)
> + sse->timer_num++;
> + else
> + sse->wdt_num++;
> + }
> +
> + sse->timers = devm_kcalloc(&pdev->dev, sse->timer_num, sizeof(*sse->timers), GFP_KERNEL);
> + if (!sse->timers)
> + return -ENOMEM;
> + sse->wdts = devm_kcalloc(&pdev->dev, sse->wdt_num, sizeof(*sse->wdts), GFP_KERNEL);
> + if (!sse->wdts)
> + return -ENOMEM;
> +
> + writel_relaxed(0, sse->cntctrl_base + CNTCR);
> + writeq_relaxed(0, sse->cntctrl_base + CNTCV);
> + writel_relaxed(1, sse->cntctrl_base + CNTCR);
> +
> + i = j = 0;
> + for_each_available_child_of_node_scoped(np, frame_node) {
> + struct resource res;
> + void __iomem *base;
> + const __be32 *addr;
> +
> + ret = of_address_to_resource(frame_node, 0, &res);
> + if (ret < 0)
> + return ret;
> + base = devm_ioremap_resource(&pdev->dev, &res);
> + if (IS_ERR(base))
> + return PTR_ERR(base);
> +
> + addr = of_get_address(frame_node, 1, NULL, NULL);
> + if (!addr) {
> + sse->timers[i].base = base;
> +
> + ret = of_irq_get(frame_node, 0);
> + if (ret < 0)
> + return ret;
> + sse->timers[i].irq = ret;
> + i++;
> + } else {
> + sse->wdts[j].ctrl_base = base;
> +
> + ret = of_address_to_resource(frame_node, 1, &res);
> + if (ret < 0)
> + return ret;
> + sse->wdts[j].refr_base = devm_ioremap_resource(&pdev->dev, &res);
> + if (IS_ERR(sse->wdts[j].refr_base))
> + return PTR_ERR(sse->wdts[j].refr_base);
> +
> + ret = of_irq_get(frame_node, 0);
> + if (ret < 0)
> + return ret;
> + sse->wdts[j].irq = ret;
> + j++;
> + }
> + }
> +
> + if (IS_ENABLED(CONFIG_WATCHDOG)) {
> + for (i = 0; i < sse->wdt_num; i++) {
> + ret = sse_setup_wdt(&pdev->dev, &sse->wdts[i], rate);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + for (i = 0; i < sse->timer_num; i++) {
> + ret = sse_setup_clocksource(&pdev->dev, &sse->timers[i], rate);
> + if (ret)
> + goto err_unreg_clocksource;
> + }
> +
> + if (sse->timer_num > 0) {
> + ret = sse_setup_clockevent(&pdev->dev, &sse->timers[0], rate);
> + if (ret)
> + goto err_unreg_clocksource;
> + }
> +
> + return 0;
> +
> +err_unreg_clocksource:
> + for (j = 0; j < i; j++)
> + clocksource_unregister(&sse->timers[j].cs);
> + return ret;
> +}
> +
> +static const struct of_device_id sse_timer_of_match[] = {
> + { .compatible = "arm,sse-timer" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, sse_timer_of_match);
> +
> +static struct platform_driver sse_timer_driver = {
> + .probe = sse_timer_probe,
> + .driver = {
> + .name = "sse-timer",
> + .of_match_table = sse_timer_of_match,
> + },
> +};
> +module_platform_driver(sse_timer_driver);
> +
> +MODULE_AUTHOR("Jisheng Zhang <jszhang@...nel.org>");
> +MODULE_DESCRIPTION("ARM SSE system timer driver");
> +MODULE_LICENSE("GPL");
> --
> 2.50.1
>
Powered by blists - more mailing lists