162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2020 Western Digital Corporation or its affiliates. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Most of the M-mode (i.e. NoMMU) RISC-V systems usually have a 662306a36Sopenharmony_ci * CLINT MMIO timer device. 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) "clint: " fmt 1062306a36Sopenharmony_ci#include <linux/bitops.h> 1162306a36Sopenharmony_ci#include <linux/clocksource.h> 1262306a36Sopenharmony_ci#include <linux/clockchips.h> 1362306a36Sopenharmony_ci#include <linux/cpu.h> 1462306a36Sopenharmony_ci#include <linux/delay.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/of_address.h> 1762306a36Sopenharmony_ci#include <linux/sched_clock.h> 1862306a36Sopenharmony_ci#include <linux/io-64-nonatomic-lo-hi.h> 1962306a36Sopenharmony_ci#include <linux/interrupt.h> 2062306a36Sopenharmony_ci#include <linux/irq.h> 2162306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 2262306a36Sopenharmony_ci#include <linux/irqdomain.h> 2362306a36Sopenharmony_ci#include <linux/of_irq.h> 2462306a36Sopenharmony_ci#include <linux/smp.h> 2562306a36Sopenharmony_ci#include <linux/timex.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#ifndef CONFIG_RISCV_M_MODE 2862306a36Sopenharmony_ci#include <asm/clint.h> 2962306a36Sopenharmony_ci#endif 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#define CLINT_IPI_OFF 0 3262306a36Sopenharmony_ci#define CLINT_TIMER_CMP_OFF 0x4000 3362306a36Sopenharmony_ci#define CLINT_TIMER_VAL_OFF 0xbff8 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/* CLINT manages IPI and Timer for RISC-V M-mode */ 3662306a36Sopenharmony_cistatic u32 __iomem *clint_ipi_base; 3762306a36Sopenharmony_cistatic unsigned int clint_ipi_irq; 3862306a36Sopenharmony_cistatic u64 __iomem *clint_timer_cmp; 3962306a36Sopenharmony_cistatic u64 __iomem *clint_timer_val; 4062306a36Sopenharmony_cistatic unsigned long clint_timer_freq; 4162306a36Sopenharmony_cistatic unsigned int clint_timer_irq; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#ifdef CONFIG_RISCV_M_MODE 4462306a36Sopenharmony_ciu64 __iomem *clint_time_val; 4562306a36Sopenharmony_ciEXPORT_SYMBOL(clint_time_val); 4662306a36Sopenharmony_ci#endif 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#ifdef CONFIG_SMP 4962306a36Sopenharmony_cistatic void clint_send_ipi(unsigned int cpu) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci writel(1, clint_ipi_base + cpuid_to_hartid_map(cpu)); 5262306a36Sopenharmony_ci} 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic void clint_clear_ipi(void) 5562306a36Sopenharmony_ci{ 5662306a36Sopenharmony_ci writel(0, clint_ipi_base + cpuid_to_hartid_map(smp_processor_id())); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic void clint_ipi_interrupt(struct irq_desc *desc) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci chained_irq_enter(chip, desc); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci clint_clear_ipi(); 6662306a36Sopenharmony_ci ipi_mux_process(); 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci chained_irq_exit(chip, desc); 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci#endif 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci#ifdef CONFIG_64BIT 7362306a36Sopenharmony_ci#define clint_get_cycles() readq_relaxed(clint_timer_val) 7462306a36Sopenharmony_ci#else 7562306a36Sopenharmony_ci#define clint_get_cycles() readl_relaxed(clint_timer_val) 7662306a36Sopenharmony_ci#define clint_get_cycles_hi() readl_relaxed(((u32 *)clint_timer_val) + 1) 7762306a36Sopenharmony_ci#endif 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci#ifdef CONFIG_64BIT 8062306a36Sopenharmony_cistatic u64 notrace clint_get_cycles64(void) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci return clint_get_cycles(); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci#else /* CONFIG_64BIT */ 8562306a36Sopenharmony_cistatic u64 notrace clint_get_cycles64(void) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci u32 hi, lo; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci do { 9062306a36Sopenharmony_ci hi = clint_get_cycles_hi(); 9162306a36Sopenharmony_ci lo = clint_get_cycles(); 9262306a36Sopenharmony_ci } while (hi != clint_get_cycles_hi()); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return ((u64)hi << 32) | lo; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci#endif /* CONFIG_64BIT */ 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic u64 clint_rdtime(struct clocksource *cs) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci return clint_get_cycles64(); 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic struct clocksource clint_clocksource = { 10462306a36Sopenharmony_ci .name = "clint_clocksource", 10562306a36Sopenharmony_ci .rating = 300, 10662306a36Sopenharmony_ci .mask = CLOCKSOURCE_MASK(64), 10762306a36Sopenharmony_ci .flags = CLOCK_SOURCE_IS_CONTINUOUS, 10862306a36Sopenharmony_ci .read = clint_rdtime, 10962306a36Sopenharmony_ci}; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int clint_clock_next_event(unsigned long delta, 11262306a36Sopenharmony_ci struct clock_event_device *ce) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci void __iomem *r = clint_timer_cmp + 11562306a36Sopenharmony_ci cpuid_to_hartid_map(smp_processor_id()); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci csr_set(CSR_IE, IE_TIE); 11862306a36Sopenharmony_ci writeq_relaxed(clint_get_cycles64() + delta, r); 11962306a36Sopenharmony_ci return 0; 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct clock_event_device, clint_clock_event) = { 12362306a36Sopenharmony_ci .name = "clint_clockevent", 12462306a36Sopenharmony_ci .features = CLOCK_EVT_FEAT_ONESHOT, 12562306a36Sopenharmony_ci .rating = 100, 12662306a36Sopenharmony_ci .set_next_event = clint_clock_next_event, 12762306a36Sopenharmony_ci}; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic int clint_timer_starting_cpu(unsigned int cpu) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct clock_event_device *ce = per_cpu_ptr(&clint_clock_event, cpu); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci ce->cpumask = cpumask_of(cpu); 13462306a36Sopenharmony_ci clockevents_config_and_register(ce, clint_timer_freq, 100, 0x7fffffff); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci enable_percpu_irq(clint_timer_irq, 13762306a36Sopenharmony_ci irq_get_trigger_type(clint_timer_irq)); 13862306a36Sopenharmony_ci enable_percpu_irq(clint_ipi_irq, 13962306a36Sopenharmony_ci irq_get_trigger_type(clint_ipi_irq)); 14062306a36Sopenharmony_ci return 0; 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int clint_timer_dying_cpu(unsigned int cpu) 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci disable_percpu_irq(clint_timer_irq); 14662306a36Sopenharmony_ci /* 14762306a36Sopenharmony_ci * Don't disable IPI when CPU goes offline because 14862306a36Sopenharmony_ci * the masking/unmasking of virtual IPIs is done 14962306a36Sopenharmony_ci * via generic IPI-Mux 15062306a36Sopenharmony_ci */ 15162306a36Sopenharmony_ci return 0; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic irqreturn_t clint_timer_interrupt(int irq, void *dev_id) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci struct clock_event_device *evdev = this_cpu_ptr(&clint_clock_event); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci csr_clear(CSR_IE, IE_TIE); 15962306a36Sopenharmony_ci evdev->event_handler(evdev); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci return IRQ_HANDLED; 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic int __init clint_timer_init_dt(struct device_node *np) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci int rc; 16762306a36Sopenharmony_ci u32 i, nr_irqs; 16862306a36Sopenharmony_ci void __iomem *base; 16962306a36Sopenharmony_ci struct of_phandle_args oirq; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci /* 17262306a36Sopenharmony_ci * Ensure that CLINT device interrupts are either RV_IRQ_TIMER or 17362306a36Sopenharmony_ci * RV_IRQ_SOFT. If it's anything else then we ignore the device. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci nr_irqs = of_irq_count(np); 17662306a36Sopenharmony_ci for (i = 0; i < nr_irqs; i++) { 17762306a36Sopenharmony_ci if (of_irq_parse_one(np, i, &oirq)) { 17862306a36Sopenharmony_ci pr_err("%pOFP: failed to parse irq %d.\n", np, i); 17962306a36Sopenharmony_ci continue; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if ((oirq.args_count != 1) || 18362306a36Sopenharmony_ci (oirq.args[0] != RV_IRQ_TIMER && 18462306a36Sopenharmony_ci oirq.args[0] != RV_IRQ_SOFT)) { 18562306a36Sopenharmony_ci pr_err("%pOFP: invalid irq %d (hwirq %d)\n", 18662306a36Sopenharmony_ci np, i, oirq.args[0]); 18762306a36Sopenharmony_ci return -ENODEV; 18862306a36Sopenharmony_ci } 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci /* Find parent irq domain and map ipi irq */ 19162306a36Sopenharmony_ci if (!clint_ipi_irq && 19262306a36Sopenharmony_ci oirq.args[0] == RV_IRQ_SOFT && 19362306a36Sopenharmony_ci irq_find_host(oirq.np)) 19462306a36Sopenharmony_ci clint_ipi_irq = irq_of_parse_and_map(np, i); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci /* Find parent irq domain and map timer irq */ 19762306a36Sopenharmony_ci if (!clint_timer_irq && 19862306a36Sopenharmony_ci oirq.args[0] == RV_IRQ_TIMER && 19962306a36Sopenharmony_ci irq_find_host(oirq.np)) 20062306a36Sopenharmony_ci clint_timer_irq = irq_of_parse_and_map(np, i); 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* If CLINT ipi or timer irq not found then fail */ 20462306a36Sopenharmony_ci if (!clint_ipi_irq || !clint_timer_irq) { 20562306a36Sopenharmony_ci pr_err("%pOFP: ipi/timer irq not found\n", np); 20662306a36Sopenharmony_ci return -ENODEV; 20762306a36Sopenharmony_ci } 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci base = of_iomap(np, 0); 21062306a36Sopenharmony_ci if (!base) { 21162306a36Sopenharmony_ci pr_err("%pOFP: could not map registers\n", np); 21262306a36Sopenharmony_ci return -ENODEV; 21362306a36Sopenharmony_ci } 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci clint_ipi_base = base + CLINT_IPI_OFF; 21662306a36Sopenharmony_ci clint_timer_cmp = base + CLINT_TIMER_CMP_OFF; 21762306a36Sopenharmony_ci clint_timer_val = base + CLINT_TIMER_VAL_OFF; 21862306a36Sopenharmony_ci clint_timer_freq = riscv_timebase; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci#ifdef CONFIG_RISCV_M_MODE 22162306a36Sopenharmony_ci /* 22262306a36Sopenharmony_ci * Yes, that's an odd naming scheme. time_val is public, but hopefully 22362306a36Sopenharmony_ci * will die in favor of something cleaner. 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ci clint_time_val = clint_timer_val; 22662306a36Sopenharmony_ci#endif 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci pr_info("%pOFP: timer running at %ld Hz\n", np, clint_timer_freq); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci rc = clocksource_register_hz(&clint_clocksource, clint_timer_freq); 23162306a36Sopenharmony_ci if (rc) { 23262306a36Sopenharmony_ci pr_err("%pOFP: clocksource register failed [%d]\n", np, rc); 23362306a36Sopenharmony_ci goto fail_iounmap; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci sched_clock_register(clint_get_cycles64, 64, clint_timer_freq); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci rc = request_percpu_irq(clint_timer_irq, clint_timer_interrupt, 23962306a36Sopenharmony_ci "clint-timer", &clint_clock_event); 24062306a36Sopenharmony_ci if (rc) { 24162306a36Sopenharmony_ci pr_err("registering percpu irq failed [%d]\n", rc); 24262306a36Sopenharmony_ci goto fail_iounmap; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci#ifdef CONFIG_SMP 24662306a36Sopenharmony_ci rc = ipi_mux_create(BITS_PER_BYTE, clint_send_ipi); 24762306a36Sopenharmony_ci if (rc <= 0) { 24862306a36Sopenharmony_ci pr_err("unable to create muxed IPIs\n"); 24962306a36Sopenharmony_ci rc = (rc < 0) ? rc : -ENODEV; 25062306a36Sopenharmony_ci goto fail_free_irq; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci irq_set_chained_handler(clint_ipi_irq, clint_ipi_interrupt); 25462306a36Sopenharmony_ci riscv_ipi_set_virq_range(rc, BITS_PER_BYTE, true); 25562306a36Sopenharmony_ci clint_clear_ipi(); 25662306a36Sopenharmony_ci#endif 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci rc = cpuhp_setup_state(CPUHP_AP_CLINT_TIMER_STARTING, 25962306a36Sopenharmony_ci "clockevents/clint/timer:starting", 26062306a36Sopenharmony_ci clint_timer_starting_cpu, 26162306a36Sopenharmony_ci clint_timer_dying_cpu); 26262306a36Sopenharmony_ci if (rc) { 26362306a36Sopenharmony_ci pr_err("%pOFP: cpuhp setup state failed [%d]\n", np, rc); 26462306a36Sopenharmony_ci goto fail_free_irq; 26562306a36Sopenharmony_ci } 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cifail_free_irq: 27062306a36Sopenharmony_ci free_percpu_irq(clint_timer_irq, &clint_clock_event); 27162306a36Sopenharmony_cifail_iounmap: 27262306a36Sopenharmony_ci iounmap(base); 27362306a36Sopenharmony_ci return rc; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ciTIMER_OF_DECLARE(clint_timer, "riscv,clint0", clint_timer_init_dt); 27762306a36Sopenharmony_ciTIMER_OF_DECLARE(clint_timer1, "sifive,clint0", clint_timer_init_dt); 278