* [PATCH 1/2] clocksource/drivers/arm_arch_timer_mmio: Refactor for early init
2026-06-10 17:53 [PATCH 0/2] clocksource/drivers/arm_arch_timer_mmio: Restore support for early init Stephan Gerhold
@ 2026-06-10 17:53 ` Stephan Gerhold
2026-06-10 17:53 ` [PATCH 2/2] clocksource/drivers/arm_arch_timer_mmio: Restore support " Stephan Gerhold
2026-06-11 7:59 ` [PATCH 0/2] " Marc Zyngier
2 siblings, 0 replies; 5+ messages in thread
From: Stephan Gerhold @ 2026-06-10 17:53 UTC (permalink / raw)
To: Mark Rutland, Marc Zyngier, Daniel Lezcano, Thomas Gleixner,
Sudeep Holla
Cc: linux-arm-kernel, linux-kernel, linux-arm-msm, Jack Matthews
In preparation of restoring support for using arm,armv7-timer-mem as an
early timer, refactor the driver to allow early initialization without
a device pointer. Replace uses of dev_() logging with pr_(), replace devm
helpers with manual cleanup or scope-based cleanup helpers where possible.
Create a new arch_timer_mmio_init() function that performs the
initialization and registration without a device pointer.
This is not very pretty, although given that the driver cannot be removed
at runtime due to .suppress_bind_attrs = true, at least the overhead for
the manual resource management is limited.
Signed-off-by: Stephan Gerhold <stephan.gerhold@linaro.org>
---
drivers/clocksource/arm_arch_timer_mmio.c | 139 +++++++++++++++++-------------
1 file changed, 79 insertions(+), 60 deletions(-)
diff --git a/drivers/clocksource/arm_arch_timer_mmio.c b/drivers/clocksource/arm_arch_timer_mmio.c
index d10362692fdd..5cb94051c4be 100644
--- a/drivers/clocksource/arm_arch_timer_mmio.c
+++ b/drivers/clocksource/arm_arch_timer_mmio.c
@@ -10,7 +10,9 @@
#define pr_fmt(fmt) "arch_timer_mmio: " fmt
+#include <linux/cleanup.h>
#include <linux/clockchips.h>
+#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/of_irq.h>
@@ -191,17 +193,16 @@ static irqreturn_t arch_timer_mmio_handler(int irq, void *dev_id)
return IRQ_NONE;
}
-static struct arch_timer_mem_frame *find_best_frame(struct platform_device *pdev)
+static struct arch_timer_mem_frame *find_best_frame(struct arch_timer *at)
{
struct arch_timer_mem_frame *frame, *best_frame = NULL;
- struct arch_timer *at = platform_get_drvdata(pdev);
void __iomem *cntctlbase;
u32 cnttidr;
cntctlbase = ioremap(at->gt_block->cntctlbase, at->gt_block->size);
if (!cntctlbase) {
- dev_err(&pdev->dev, "Can't map CNTCTLBase @ %pa\n",
- &at->gt_block->cntctlbase);
+ pr_err("Can't map CNTCTLBase @ %pa\n",
+ &at->gt_block->cntctlbase);
return NULL;
}
@@ -277,22 +278,21 @@ static void arch_timer_mmio_setup(struct arch_timer *at, int irq)
clocksource_register_hz(&at->cs, at->rate);
}
-static int arch_timer_mmio_frame_register(struct platform_device *pdev,
- struct arch_timer_mem_frame *frame)
+static int arch_timer_mmio_frame_register(struct arch_timer *at,
+ struct arch_timer_mem_frame *frame,
+ struct device_node *np)
{
- struct arch_timer *at = platform_get_drvdata(pdev);
- struct device_node *np = pdev->dev.of_node;
int ret, irq;
u32 rate;
- if (!devm_request_mem_region(&pdev->dev, frame->cntbase, frame->size,
- "arch_mem_timer"))
+ if (!request_mem_region(frame->cntbase, frame->size, "arch_mem_timer"))
return -EBUSY;
- at->base = devm_ioremap(&pdev->dev, frame->cntbase, frame->size);
+ at->base = ioremap(frame->cntbase, frame->size);
if (!at->base) {
- dev_err(&pdev->dev, "Can't map frame's registers\n");
- return -ENXIO;
+ pr_err("Can't map frame's registers @ %pa\n", &frame->cntbase);
+ ret = -ENXIO;
+ goto err_release_region;
}
/*
@@ -310,49 +310,56 @@ static int arch_timer_mmio_frame_register(struct platform_device *pdev,
at->rate = arch_timer_get_rate();
irq = at->access == VIRT_ACCESS ? frame->virt_irq : frame->phys_irq;
- ret = devm_request_irq(&pdev->dev, irq, arch_timer_mmio_handler,
- IRQF_TIMER | IRQF_NO_AUTOEN, "arch_mem_timer",
- &at->evt);
+ ret = request_irq(irq, arch_timer_mmio_handler,
+ IRQF_TIMER | IRQF_NO_AUTOEN, "arch_mem_timer",
+ &at->evt);
if (ret) {
- dev_err(&pdev->dev, "Failed to request mem timer irq\n");
- return ret;
+ pr_err("Failed to request mem timer irq for frame @ %pa\n",
+ &frame->cntbase);
+ goto err_iounmap;
}
/* Afer this point, we're not allowed to fail anymore */
arch_timer_mmio_setup(at, irq);
return 0;
+
+err_iounmap:
+ iounmap(at->base);
+err_release_region:
+ release_mem_region(frame->cntbase, frame->size);
+ return ret;
}
-static int of_populate_gt_block(struct platform_device *pdev,
- struct arch_timer *at)
+static int of_populate_gt_block(struct device_node *np, struct arch_timer_mem *gt_block)
{
struct resource res;
- if (of_address_to_resource(pdev->dev.of_node, 0, &res))
+ if (of_address_to_resource(np, 0, &res))
return -EINVAL;
- at->gt_block->cntctlbase = res.start;
- at->gt_block->size = resource_size(&res);
+ gt_block->cntctlbase = res.start;
+ gt_block->size = resource_size(&res);
- for_each_available_child_of_node_scoped(pdev->dev.of_node, frame_node) {
+ for_each_available_child_of_node_scoped(np, frame_node) {
struct arch_timer_mem_frame *frame;
u32 n;
if (of_property_read_u32(frame_node, "frame-number", &n)) {
- dev_err(&pdev->dev, FW_BUG "Missing frame-number\n");
+ pr_err(FW_BUG "Missing frame-number for %pOF\n",
+ frame_node);
return -EINVAL;
}
if (n >= ARCH_TIMER_MEM_MAX_FRAMES) {
- dev_err(&pdev->dev,
- FW_BUG "Wrong frame-number, only 0-%u are permitted\n",
- ARCH_TIMER_MEM_MAX_FRAMES - 1);
+ pr_err(FW_BUG "Wrong frame-number %u for %pOF, only 0-%u are permitted\n",
+ n, frame_node, ARCH_TIMER_MEM_MAX_FRAMES - 1);
return -EINVAL;
}
- frame = &at->gt_block->frame[n];
+ frame = >_block->frame[n];
if (frame->valid) {
- dev_err(&pdev->dev, FW_BUG "Duplicated frame-number\n");
+ pr_err(FW_BUG "Duplicated frame-number %u for %pOF\n",
+ n, frame_node);
return -EINVAL;
}
@@ -371,50 +378,62 @@ static int of_populate_gt_block(struct platform_device *pdev,
return 0;
}
-static int arch_timer_mmio_probe(struct platform_device *pdev)
+static struct arch_timer *arch_timer_mmio_init(struct arch_timer_mem *gt_block,
+ struct device_node *np)
{
+ struct arch_timer *at __free(kfree) = kzalloc_obj(*at);
struct arch_timer_mem_frame *frame;
- struct arch_timer *at;
- struct device_node *np;
int ret;
- np = pdev->dev.of_node;
-
- at = devm_kmalloc(&pdev->dev, sizeof(*at), GFP_KERNEL | __GFP_ZERO);
if (!at)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
+
+ at->gt_block = gt_block;
+
+ frame = find_best_frame(at);
+ if (!frame) {
+ pr_err("Unable to find a suitable frame in timer @ %pa\n",
+ &at->gt_block->cntctlbase);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ret = arch_timer_mmio_frame_register(at, frame, np);
+ if (ret)
+ return ERR_PTR(ret);
+
+ pr_info("mmio timer running at %lu.%02luMHz (%s)\n",
+ (unsigned long)at->rate / 1000000,
+ (unsigned long)(at->rate / 10000) % 100,
+ at->access == VIRT_ACCESS ? "virt" : "phys");
+
+ return_ptr(at);
+}
+
+static int arch_timer_mmio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct arch_timer_mem *gt_block;
+ struct arch_timer *at;
+ int ret;
if (np) {
- at->gt_block = devm_kmalloc(&pdev->dev, sizeof(*at->gt_block),
- GFP_KERNEL | __GFP_ZERO);
- if (!at->gt_block)
+ gt_block = devm_kzalloc(&pdev->dev, sizeof(*gt_block),
+ GFP_KERNEL);
+ if (!gt_block)
return -ENOMEM;
- ret = of_populate_gt_block(pdev, at);
+ ret = of_populate_gt_block(np, gt_block);
if (ret)
return ret;
} else {
- at->gt_block = dev_get_platdata(&pdev->dev);
- }
-
- platform_set_drvdata(pdev, at);
-
- frame = find_best_frame(pdev);
- if (!frame) {
- dev_err(&pdev->dev,
- "Unable to find a suitable frame in timer @ %pa\n",
- &at->gt_block->cntctlbase);
- return -EINVAL;
+ gt_block = dev_get_platdata(&pdev->dev);
}
- ret = arch_timer_mmio_frame_register(pdev, frame);
- if (!ret)
- dev_info(&pdev->dev,
- "mmio timer running at %lu.%02luMHz (%s)\n",
- (unsigned long)at->rate / 1000000,
- (unsigned long)(at->rate / 10000) % 100,
- at->access == VIRT_ACCESS ? "virt" : "phys");
+ at = arch_timer_mmio_init(gt_block, np);
+ if (IS_ERR(at))
+ return PTR_ERR(at);
- return ret;
+ platform_set_drvdata(pdev, at);
+ return 0;
}
static const struct of_device_id arch_timer_mmio_of_table[] = {
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] clocksource/drivers/arm_arch_timer_mmio: Restore support for early init
2026-06-10 17:53 [PATCH 0/2] clocksource/drivers/arm_arch_timer_mmio: Restore support for early init Stephan Gerhold
2026-06-10 17:53 ` [PATCH 1/2] clocksource/drivers/arm_arch_timer_mmio: Refactor " Stephan Gerhold
@ 2026-06-10 17:53 ` Stephan Gerhold
2026-06-11 7:59 ` [PATCH 0/2] " Marc Zyngier
2 siblings, 0 replies; 5+ messages in thread
From: Stephan Gerhold @ 2026-06-10 17:53 UTC (permalink / raw)
To: Mark Rutland, Marc Zyngier, Daniel Lezcano, Thomas Gleixner,
Sudeep Holla
Cc: linux-arm-kernel, linux-kernel, linux-arm-msm, Jack Matthews
Some single-core Qualcomm modem platforms (e.g. MDM9625, MDM9607) have an
obscure timer setup where the global Arm MMIO timer (arm,armv7-timer-mem)
is used as the only available timer for the CPU. This setup used to work
fine until commit 0f67b56d84b4 ("clocksource/drivers/arm_arch_timer_mmio:
Switch over to standalone driver") when the early timer initialization
using TIMER_OF_DECLARE() was removed when moving to the standalone MMIO
driver.
We need some timer early to run properly, so without another timer in the
system the only choice is to make the MMIO timer available early again
using TIMER_OF_DECLARE(). Use the refactoring in the previous commit to
reuse most of the initialization code in the new standalone driver and
probe one timer early if required. ACPI-based systems and platforms with a
CPU-local CP15 timer continue to probe the timer late as before.
Reported-by: Jack Matthews <jack@jackmatthe.ws>
Closes: https://lore.kernel.org/r/46A20F89-E208-4091-8B6E-B5C38BF82B42@jackmatthe.ws/
Fixes: 0f67b56d84b4 ("clocksource/drivers/arm_arch_timer_mmio: Switch over to standalone driver")
Signed-off-by: Stephan Gerhold <stephan.gerhold@linaro.org>
---
I couldn't find any existing (fully-supported) platform upstream that
relies on this, so I omitted Cc stable. MDM9607 does have most of the
necessary drivers upstream, it's just missing the DT (I would like to
upstream that once ready).
---
drivers/clocksource/arm_arch_timer_mmio.c | 47 +++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/clocksource/arm_arch_timer_mmio.c b/drivers/clocksource/arm_arch_timer_mmio.c
index 5cb94051c4be..d128dff7067f 100644
--- a/drivers/clocksource/arm_arch_timer_mmio.c
+++ b/drivers/clocksource/arm_arch_timer_mmio.c
@@ -15,6 +15,7 @@
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
@@ -409,6 +410,8 @@ static struct arch_timer *arch_timer_mmio_init(struct arch_timer_mem *gt_block,
return_ptr(at);
}
+static struct device_node *arch_timer_mmio_early_np;
+
static int arch_timer_mmio_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
@@ -417,6 +420,10 @@ static int arch_timer_mmio_probe(struct platform_device *pdev)
int ret;
if (np) {
+ /* Check if timer was already probed early */
+ if (np == arch_timer_mmio_early_np)
+ return 0;
+
gt_block = devm_kzalloc(&pdev->dev, sizeof(*gt_block),
GFP_KERNEL);
if (!gt_block)
@@ -436,6 +443,46 @@ static int arch_timer_mmio_probe(struct platform_device *pdev)
return 0;
}
+static const struct of_device_id arch_timer_cp15_match[] __initconst = {
+ { .compatible = "arm,armv7-timer", },
+ { .compatible = "arm,armv8-timer", },
+ {}
+};
+
+static bool __init arch_timer_mmio_has_cp15(void)
+{
+ struct device_node *np __free(device_node) =
+ of_find_matching_node(NULL, arch_timer_cp15_match);
+
+ return np && of_device_is_available(np);
+}
+
+static int __init arch_timer_mmio_of_early_init(struct device_node *np)
+{
+ struct arch_timer *at;
+ int ret;
+
+ if (arch_timer_mmio_early_np || arch_timer_mmio_has_cp15())
+ return -EPROBE_DEFER;
+
+ struct arch_timer_mem *gt_block __free(kfree) = kzalloc_obj(*gt_block);
+ if (!gt_block)
+ return -ENOMEM;
+
+ ret = of_populate_gt_block(np, gt_block);
+ if (ret)
+ return ret;
+
+ at = arch_timer_mmio_init(gt_block, np);
+ if (IS_ERR(at))
+ return PTR_ERR(at);
+ retain_and_null_ptr(gt_block);
+
+ arch_timer_mmio_early_np = np;
+ return 0;
+}
+TIMER_OF_DECLARE(armv7_arch_timer_mem, "arm,armv7-timer-mem", arch_timer_mmio_of_early_init);
+
static const struct of_device_id arch_timer_mmio_of_table[] = {
{ .compatible = "arm,armv7-timer-mem", },
{}
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread