| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * ARM Generic Memory Mapped Timer support | |
| * | |
| * Split from drivers/clocksource/arm_arch_timer.c | |
| * | |
| * Copyright (C) 2011 ARM Ltd. | |
| * All Rights Reserved | |
| */ | |
| #define pr_fmt(fmt) "arch_timer_mmio: " fmt | |
| #include <linux/clockchips.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/io-64-nonatomic-lo-hi.h> | |
| #include <linux/of_irq.h> | |
| #include <linux/of_address.h> | |
| #include <linux/platform_device.h> | |
| #include <clocksource/arm_arch_timer.h> | |
| #define CNTTIDR 0x08 | |
| #define CNTTIDR_VIRT(n) (BIT(1) << ((n) * 4)) | |
| #define CNTACR(n) (0x40 + ((n) * 4)) | |
| #define CNTACR_RPCT BIT(0) | |
| #define CNTACR_RVCT BIT(1) | |
| #define CNTACR_RFRQ BIT(2) | |
| #define CNTACR_RVOFF BIT(3) | |
| #define CNTACR_RWVT BIT(4) | |
| #define CNTACR_RWPT BIT(5) | |
| #define CNTPCT_LO 0x00 | |
| #define CNTVCT_LO 0x08 | |
| #define CNTFRQ 0x10 | |
| #define CNTP_CVAL_LO 0x20 | |
| #define CNTP_CTL 0x2c | |
| #define CNTV_CVAL_LO 0x30 | |
| #define CNTV_CTL 0x3c | |
| enum arch_timer_access { | |
| PHYS_ACCESS, | |
| VIRT_ACCESS, | |
| }; | |
| struct arch_timer { | |
| struct clock_event_device evt; | |
| struct clocksource cs; | |
| struct arch_timer_mem *gt_block; | |
| void __iomem *base; | |
| enum arch_timer_access access; | |
| u32 rate; | |
| }; | |
| #define evt_to_arch_timer(e) container_of(e, struct arch_timer, evt) | |
| #define cs_to_arch_timer(c) container_of(c, struct arch_timer, cs) | |
| static void arch_timer_mmio_write(struct arch_timer *timer, | |
| enum arch_timer_reg reg, u64 val) | |
| { | |
| switch (timer->access) { | |
| case PHYS_ACCESS: | |
| switch (reg) { | |
| case ARCH_TIMER_REG_CTRL: | |
| writel_relaxed((u32)val, timer->base + CNTP_CTL); | |
| return; | |
| case ARCH_TIMER_REG_CVAL: | |
| /* | |
| * Not guaranteed to be atomic, so the timer | |
| * must be disabled at this point. | |
| */ | |
| writeq_relaxed(val, timer->base + CNTP_CVAL_LO); | |
| return; | |
| } | |
| break; | |
| case VIRT_ACCESS: | |
| switch (reg) { | |
| case ARCH_TIMER_REG_CTRL: | |
| writel_relaxed((u32)val, timer->base + CNTV_CTL); | |
| return; | |
| case ARCH_TIMER_REG_CVAL: | |
| /* Same restriction as above */ | |
| writeq_relaxed(val, timer->base + CNTV_CVAL_LO); | |
| return; | |
| } | |
| break; | |
| } | |
| /* Should never be here */ | |
| WARN_ON_ONCE(1); | |
| } | |
| static u32 arch_timer_mmio_read(struct arch_timer *timer, enum arch_timer_reg reg) | |
| { | |
| switch (timer->access) { | |
| case PHYS_ACCESS: | |
| switch (reg) { | |
| case ARCH_TIMER_REG_CTRL: | |
| return readl_relaxed(timer->base + CNTP_CTL); | |
| default: | |
| break; | |
| } | |
| break; | |
| case VIRT_ACCESS: | |
| switch (reg) { | |
| case ARCH_TIMER_REG_CTRL: | |
| return readl_relaxed(timer->base + CNTV_CTL); | |
| default: | |
| break; | |
| } | |
| break; | |
| } | |
| /* Should never be here */ | |
| WARN_ON_ONCE(1); | |
| return 0; | |
| } | |
| static noinstr u64 arch_counter_mmio_get_cnt(struct arch_timer *t) | |
| { | |
| int offset_lo = t->access == VIRT_ACCESS ? CNTVCT_LO : CNTPCT_LO; | |
| u32 cnt_lo, cnt_hi, tmp_hi; | |
| do { | |
| cnt_hi = __le32_to_cpu((__le32 __force)__raw_readl(t->base + offset_lo + 4)); | |
| cnt_lo = __le32_to_cpu((__le32 __force)__raw_readl(t->base + offset_lo)); | |
| tmp_hi = __le32_to_cpu((__le32 __force)__raw_readl(t->base + offset_lo + 4)); | |
| } while (cnt_hi != tmp_hi); | |
| return ((u64) cnt_hi << 32) | cnt_lo; | |
| } | |
| static u64 arch_mmio_counter_read(struct clocksource *cs) | |
| { | |
| struct arch_timer *at = cs_to_arch_timer(cs); | |
| return arch_counter_mmio_get_cnt(at); | |
| } | |
| static int arch_timer_mmio_shutdown(struct clock_event_device *clk) | |
| { | |
| struct arch_timer *at = evt_to_arch_timer(clk); | |
| unsigned long ctrl; | |
| ctrl = arch_timer_mmio_read(at, ARCH_TIMER_REG_CTRL); | |
| ctrl &= ~ARCH_TIMER_CTRL_ENABLE; | |
| arch_timer_mmio_write(at, ARCH_TIMER_REG_CTRL, ctrl); | |
| return 0; | |
| } | |
| static int arch_timer_mmio_set_next_event(unsigned long evt, | |
| struct clock_event_device *clk) | |
| { | |
| struct arch_timer *timer = evt_to_arch_timer(clk); | |
| unsigned long ctrl; | |
| u64 cnt; | |
| ctrl = arch_timer_mmio_read(timer, ARCH_TIMER_REG_CTRL); | |
| /* Timer must be disabled before programming CVAL */ | |
| if (ctrl & ARCH_TIMER_CTRL_ENABLE) { | |
| ctrl &= ~ARCH_TIMER_CTRL_ENABLE; | |
| arch_timer_mmio_write(timer, ARCH_TIMER_REG_CTRL, ctrl); | |
| } | |
| ctrl |= ARCH_TIMER_CTRL_ENABLE; | |
| ctrl &= ~ARCH_TIMER_CTRL_IT_MASK; | |
| cnt = arch_counter_mmio_get_cnt(timer); | |
| arch_timer_mmio_write(timer, ARCH_TIMER_REG_CVAL, evt + cnt); | |
| arch_timer_mmio_write(timer, ARCH_TIMER_REG_CTRL, ctrl); | |
| return 0; | |
| } | |
| static irqreturn_t arch_timer_mmio_handler(int irq, void *dev_id) | |
| { | |
| struct clock_event_device *evt = dev_id; | |
| struct arch_timer *at = evt_to_arch_timer(evt); | |
| unsigned long ctrl; | |
| ctrl = arch_timer_mmio_read(at, ARCH_TIMER_REG_CTRL); | |
| if (ctrl & ARCH_TIMER_CTRL_IT_STAT) { | |
| ctrl |= ARCH_TIMER_CTRL_IT_MASK; | |
| arch_timer_mmio_write(at, ARCH_TIMER_REG_CTRL, ctrl); | |
| evt->event_handler(evt); | |
| return IRQ_HANDLED; | |
| } | |
| return IRQ_NONE; | |
| } | |
| static struct arch_timer_mem_frame *find_best_frame(struct platform_device *pdev) | |
| { | |
| 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); | |
| return NULL; | |
| } | |
| cnttidr = readl_relaxed(cntctlbase + CNTTIDR); | |
| /* | |
| * Try to find a virtual capable frame. Otherwise fall back to a | |
| * physical capable frame. | |
| */ | |
| for (int i = 0; i < ARCH_TIMER_MEM_MAX_FRAMES; i++) { | |
| u32 cntacr = CNTACR_RFRQ | CNTACR_RWPT | CNTACR_RPCT | | |
| CNTACR_RWVT | CNTACR_RVOFF | CNTACR_RVCT; | |
| frame = &at->gt_block->frame[i]; | |
| if (!frame->valid) | |
| continue; | |
| /* Try enabling everything, and see what sticks */ | |
| writel_relaxed(cntacr, cntctlbase + CNTACR(i)); | |
| cntacr = readl_relaxed(cntctlbase + CNTACR(i)); | |
| /* Pick a suitable frame for which we have an IRQ */ | |
| if ((cnttidr & CNTTIDR_VIRT(i)) && | |
| !(~cntacr & (CNTACR_RWVT | CNTACR_RVCT)) && | |
| frame->virt_irq) { | |
| best_frame = frame; | |
| at->access = VIRT_ACCESS; | |
| break; | |
| } | |
| if ((~cntacr & (CNTACR_RWPT | CNTACR_RPCT)) || | |
| !frame->phys_irq) | |
| continue; | |
| at->access = PHYS_ACCESS; | |
| best_frame = frame; | |
| } | |
| iounmap(cntctlbase); | |
| return best_frame; | |
| } | |
| static void arch_timer_mmio_setup(struct arch_timer *at, int irq) | |
| { | |
| at->evt = (struct clock_event_device) { | |
| .features = (CLOCK_EVT_FEAT_ONESHOT | | |
| CLOCK_EVT_FEAT_DYNIRQ), | |
| .name = "arch_mem_timer", | |
| .rating = 400, | |
| .cpumask = cpu_possible_mask, | |
| .irq = irq, | |
| .set_next_event = arch_timer_mmio_set_next_event, | |
| .set_state_oneshot_stopped = arch_timer_mmio_shutdown, | |
| .set_state_shutdown = arch_timer_mmio_shutdown, | |
| }; | |
| at->evt.set_state_shutdown(&at->evt); | |
| clockevents_config_and_register(&at->evt, at->rate, 0xf, | |
| (unsigned long)CLOCKSOURCE_MASK(56)); | |
| enable_irq(at->evt.irq); | |
| at->cs = (struct clocksource) { | |
| .name = "arch_mmio_counter", | |
| .rating = 300, | |
| .read = arch_mmio_counter_read, | |
| .mask = CLOCKSOURCE_MASK(56), | |
| .flags = CLOCK_SOURCE_IS_CONTINUOUS, | |
| }; | |
| clocksource_register_hz(&at->cs, at->rate); | |
| } | |
| static int arch_timer_mmio_frame_register(struct platform_device *pdev, | |
| struct arch_timer_mem_frame *frame) | |
| { | |
| 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")) | |
| return -EBUSY; | |
| at->base = devm_ioremap(&pdev->dev, frame->cntbase, frame->size); | |
| if (!at->base) { | |
| dev_err(&pdev->dev, "Can't map frame's registers\n"); | |
| return -ENXIO; | |
| } | |
| /* | |
| * Allow "clock-frequency" to override the probed rate. If neither | |
| * lead to something useful, use the CPU timer frequency as the | |
| * fallback. The nice thing about that last point is that we woudn't | |
| * made it here if we didn't have a valid frequency. | |
| */ | |
| rate = readl_relaxed(at->base + CNTFRQ); | |
| if (!np || of_property_read_u32(np, "clock-frequency", &at->rate)) | |
| at->rate = rate; | |
| if (!at->rate) | |
| 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); | |
| if (ret) { | |
| dev_err(&pdev->dev, "Failed to request mem timer irq\n"); | |
| return ret; | |
| } | |
| /* Afer this point, we're not allowed to fail anymore */ | |
| arch_timer_mmio_setup(at, irq); | |
| return 0; | |
| } | |
| static int of_populate_gt_block(struct platform_device *pdev, | |
| struct arch_timer *at) | |
| { | |
| struct resource res; | |
| if (of_address_to_resource(pdev->dev.of_node, 0, &res)) | |
| return -EINVAL; | |
| at->gt_block->cntctlbase = res.start; | |
| at->gt_block->size = resource_size(&res); | |
| for_each_available_child_of_node_scoped(pdev->dev.of_node, 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"); | |
| 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); | |
| return -EINVAL; | |
| } | |
| frame = &at->gt_block->frame[n]; | |
| if (frame->valid) { | |
| dev_err(&pdev->dev, FW_BUG "Duplicated frame-number\n"); | |
| return -EINVAL; | |
| } | |
| if (of_address_to_resource(frame_node, 0, &res)) | |
| return -EINVAL; | |
| frame->cntbase = res.start; | |
| frame->size = resource_size(&res); | |
| frame->phys_irq = irq_of_parse_and_map(frame_node, 0); | |
| frame->virt_irq = irq_of_parse_and_map(frame_node, 1); | |
| frame->valid = true; | |
| } | |
| return 0; | |
| } | |
| static int arch_timer_mmio_probe(struct platform_device *pdev) | |
| { | |
| 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; | |
| if (np) { | |
| at->gt_block = devm_kmalloc(&pdev->dev, sizeof(*at->gt_block), | |
| GFP_KERNEL | __GFP_ZERO); | |
| if (!at->gt_block) | |
| return -ENOMEM; | |
| ret = of_populate_gt_block(pdev, at); | |
| 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; | |
| } | |
| 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"); | |
| return ret; | |
| } | |
| static const struct of_device_id arch_timer_mmio_of_table[] = { | |
| { .compatible = "arm,armv7-timer-mem", }, | |
| {} | |
| }; | |
| static struct platform_driver arch_timer_mmio_drv = { | |
| .driver = { | |
| .name = "arch-timer-mmio", | |
| .of_match_table = arch_timer_mmio_of_table, | |
| .suppress_bind_attrs = true, | |
| }, | |
| .probe = arch_timer_mmio_probe, | |
| }; | |
| builtin_platform_driver(arch_timer_mmio_drv); | |
| static struct platform_driver arch_timer_mmio_acpi_drv = { | |
| .driver = { | |
| .name = "gtdt-arm-mmio-timer", | |
| .suppress_bind_attrs = true, | |
| }, | |
| .probe = arch_timer_mmio_probe, | |
| }; | |
| builtin_platform_driver(arch_timer_mmio_acpi_drv); |