Messages in this thread | | | Subject | Re: [PATCH v8 1/2] clocksource: loongson2_hpet: add hpet driver support | From | Yinbo Zhu <> | Date | Wed, 9 Nov 2022 17:40:11 +0800 |
| |
sorry, please ignore this ping in v8.
在 2022/11/9 下午5:18, Yinbo Zhu 写道: > Hi maintainer, > > please help me merge it to upstream. > in addition, this patch need rely on > "https://patchwork.kernel.org/project/linux-clk/list/?series=691497" > > thanks, > Yinbo. > > 在 2022/11/3 下午2:53, Yinbo Zhu 写道: >> HPET (High Precision Event Timer) defines a new set of timers, which >> are used by the operating system to schedule threads, interrupt the >> kernel and interrupt the multimedia timer server. The operating >> system can assign different timers to different applications. By >> configuration, each timer can generate interrupt independently. >> >> The Loongson-2 HPET module includes a main count and three comparators, >> all of which are 32 bits wide. Among the three comparators, only >> one comparator supports periodic interrupt, all three comparators >> support non periodic interrupts. >> >> Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn> >> --- >> Change in v8: >> 1. Add all history change log information. >> Change in v7: >> 1. Replace setup_irq with request_irq. >> Change in v6: >> 1. Move comma to the end of the previous line if that comma at >> the beginning of the line. >> Change in v5: >> 1. Replace string loongson2 with Loongson-2 in commit message >> and Kconfig file. >> 2. Replace string LOONGSON2 with LOONGSON-2 in MAINTAINERS. >> 3. Make include asm headers after all linux headers. >> 4. Add blank place before comma if comma when the comma is at >> the beginning of the line. >> Change in v4: >> 1. Use common clock framework ops to gain apb clock. >> 2. This patch need rely on clock patch, which patchwork >> link was >> "https://patchwork.kernel.org/project/linux-clk/list/?series=688892". >> Change in v3: >> 1. NO change, but other patch in this series of patches set >> has changes >> Change in v2: >> 1. NO change, but other patch in this series of patches set >> has changes >> >> MAINTAINERS | 6 + >> arch/loongarch/kernel/time.c | 4 +- >> drivers/clocksource/Kconfig | 9 + >> drivers/clocksource/Makefile | 1 + >> drivers/clocksource/loongson2_hpet.c | 335 +++++++++++++++++++++++++++ >> 5 files changed, 354 insertions(+), 1 deletion(-) >> create mode 100644 drivers/clocksource/loongson2_hpet.c >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 7afaf6d72800..52519695a458 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -12026,6 +12026,12 @@ F: >> Documentation/devicetree/bindings/clock/loongson,ls2k-clk.yaml >> F: drivers/clk/clk-loongson2.c >> F: include/dt-bindings/clock/loongson,ls2k-clk.h >> +LOONGSON-2 SOC SERIES HPET DRIVER >> +M: Yinbo Zhu <zhuyinbo@loongson.cn> >> +L: linux-kernel@vger.kernel.org >> +S: Maintained >> +F: drivers/clocksource/loongson2_hpet.c >> + >> LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI) >> M: Sathya Prakash <sathya.prakash@broadcom.com> >> M: Sreekanth Reddy <sreekanth.reddy@broadcom.com> >> diff --git a/arch/loongarch/kernel/time.c b/arch/loongarch/kernel/time.c >> index 09f20bc81798..0d8b37763086 100644 >> --- a/arch/loongarch/kernel/time.c >> +++ b/arch/loongarch/kernel/time.c >> @@ -216,7 +216,9 @@ int __init constant_clocksource_init(void) >> void __init time_init(void) >> { >> of_clk_init(NULL); >> - >> +#ifdef CONFIG_TIMER_PROBE >> + timer_probe(); >> +#endif >> if (!cpu_has_cpucfg) >> const_clock_freq = cpu_clock_freq; >> else >> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig >> index 4469e7f555e9..f114ee47e6f7 100644 >> --- a/drivers/clocksource/Kconfig >> +++ b/drivers/clocksource/Kconfig >> @@ -721,4 +721,13 @@ config GOLDFISH_TIMER >> help >> Support for the timer/counter of goldfish-rtc >> +config LOONGSON2_HPET >> + bool "Loongson-2 High Precision Event Timer (HPET)" >> + select TIMER_PROBE >> + select TIMER_OF >> + help >> + This option enables Loongson-2 High Precision Event Timer >> + (HPET) module driver. It supports the oneshot, the periodic >> + modes and high resolution. It is used as a clocksource and >> + a clockevent. >> endmenu >> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile >> index 64ab547de97b..1a3abb770f11 100644 >> --- a/drivers/clocksource/Makefile >> +++ b/drivers/clocksource/Makefile >> @@ -88,3 +88,4 @@ obj-$(CONFIG_MICROCHIP_PIT64B) += >> timer-microchip-pit64b.o >> obj-$(CONFIG_MSC313E_TIMER) += timer-msc313e.o >> obj-$(CONFIG_GOLDFISH_TIMER) += timer-goldfish.o >> obj-$(CONFIG_GXP_TIMER) += timer-gxp.o >> +obj-$(CONFIG_LOONGSON2_HPET) += loongson2_hpet.o >> diff --git a/drivers/clocksource/loongson2_hpet.c >> b/drivers/clocksource/loongson2_hpet.c >> new file mode 100644 >> index 000000000000..ed1451202bcd >> --- /dev/null >> +++ b/drivers/clocksource/loongson2_hpet.c >> @@ -0,0 +1,335 @@ >> +// SPDX-License-Identifier: GPL-2.0+ >> +/* >> + * Author: Yinbo Zhu <zhuyinbo@loongson.cn> >> + * Copyright (C) 2022-2023 Loongson Technology Corporation Limited >> + */ >> + >> +#include <linux/init.h> >> +#include <linux/percpu.h> >> +#include <linux/delay.h> >> +#include <linux/spinlock.h> >> +#include <linux/interrupt.h> >> +#include <linux/of_irq.h> >> +#include <linux/of_address.h> >> +#include <linux/clk.h> >> +#include <asm/time.h> >> + >> +/* HPET regs */ >> +#define HPET_CFG 0x010 >> +#define HPET_STATUS 0x020 >> +#define HPET_COUNTER 0x0f0 >> +#define HPET_T0_IRS 0x001 >> +#define HPET_T0_CFG 0x100 >> +#define HPET_T0_CMP 0x108 >> +#define HPET_CFG_ENABLE 0x001 >> +#define HPET_TN_LEVEL 0x0002 >> +#define HPET_TN_ENABLE 0x0004 >> +#define HPET_TN_PERIODIC 0x0008 >> +#define HPET_TN_SETVAL 0x0040 >> +#define HPET_TN_32BIT 0x0100 >> + >> +#define HPET_MIN_CYCLES 16 >> +#define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES * 12) >> +#define HPET_COMPARE_VAL ((hpet_freq + HZ / 2) / HZ) >> + >> +void __iomem *hpet_mmio_base; >> +unsigned int hpet_freq; >> +unsigned int hpet_t0_irq; >> +unsigned int hpet_irq_flags; >> +unsigned int hpet_t0_cfg; >> + >> +static DEFINE_SPINLOCK(hpet_lock); >> +DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device); >> + >> +static int hpet_read(int offset) >> +{ >> + return readl(hpet_mmio_base + offset); >> +} >> + >> +static void hpet_write(int offset, int data) >> +{ >> + writel(data, hpet_mmio_base + offset); >> +} >> + >> +static void hpet_start_counter(void) >> +{ >> + unsigned int cfg = hpet_read(HPET_CFG); >> + >> + cfg |= HPET_CFG_ENABLE; >> + hpet_write(HPET_CFG, cfg); >> +} >> + >> +static void hpet_stop_counter(void) >> +{ >> + unsigned int cfg = hpet_read(HPET_CFG); >> + >> + cfg &= ~HPET_CFG_ENABLE; >> + hpet_write(HPET_CFG, cfg); >> +} >> + >> +static void hpet_reset_counter(void) >> +{ >> + hpet_write(HPET_COUNTER, 0); >> + hpet_write(HPET_COUNTER + 4, 0); >> +} >> + >> +static void hpet_restart_counter(void) >> +{ >> + hpet_stop_counter(); >> + hpet_reset_counter(); >> + hpet_start_counter(); >> +} >> + >> +static void hpet_enable_legacy_int(void) >> +{ >> + /* Do nothing on Loongson2 */ >> +} >> + >> +static int hpet_set_state_periodic(struct clock_event_device *evt) >> +{ >> + int cfg; >> + >> + spin_lock(&hpet_lock); >> + >> + pr_info("set clock event to periodic mode!\n"); >> + /* stop counter */ >> + hpet_stop_counter(); >> + hpet_reset_counter(); >> + hpet_write(HPET_T0_CMP, 0); >> + >> + /* enables the timer0 to generate a periodic interrupt */ >> + cfg = hpet_read(HPET_T0_CFG); >> + cfg &= ~HPET_TN_LEVEL; >> + cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL | >> + HPET_TN_32BIT | hpet_irq_flags; >> + hpet_write(HPET_T0_CFG, cfg); >> + >> + /* set the comparator */ >> + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); >> + udelay(1); >> + hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); >> + >> + /* start counter */ >> + hpet_start_counter(); >> + >> + spin_unlock(&hpet_lock); >> + return 0; >> +} >> + >> +static int hpet_set_state_shutdown(struct clock_event_device *evt) >> +{ >> + int cfg; >> + >> + spin_lock(&hpet_lock); >> + >> + cfg = hpet_read(HPET_T0_CFG); >> + cfg &= ~HPET_TN_ENABLE; >> + hpet_write(HPET_T0_CFG, cfg); >> + >> + spin_unlock(&hpet_lock); >> + return 0; >> +} >> + >> +static int hpet_set_state_oneshot(struct clock_event_device *evt) >> +{ >> + int cfg; >> + >> + spin_lock(&hpet_lock); >> + >> + pr_info("set clock event to one shot mode!\n"); >> + cfg = hpet_read(HPET_T0_CFG); >> + /* >> + * set timer0 type >> + * 1 : periodic interrupt >> + * 0 : non-periodic(oneshot) interrupt >> + */ >> + cfg &= ~HPET_TN_PERIODIC; >> + cfg |= HPET_TN_ENABLE | HPET_TN_32BIT | >> + hpet_irq_flags; >> + hpet_write(HPET_T0_CFG, cfg); >> + >> + /* start counter */ >> + hpet_start_counter(); >> + >> + spin_unlock(&hpet_lock); >> + return 0; >> +} >> + >> +static int hpet_tick_resume(struct clock_event_device *evt) >> +{ >> + spin_lock(&hpet_lock); >> + hpet_enable_legacy_int(); >> + spin_unlock(&hpet_lock); >> + >> + return 0; >> +} >> + >> +static int hpet_next_event(unsigned long delta, >> + struct clock_event_device *evt) >> +{ >> + u32 cnt; >> + s32 res; >> + >> + cnt = hpet_read(HPET_COUNTER); >> + cnt += (u32) delta; >> + hpet_write(HPET_T0_CMP, cnt); >> + >> + res = (s32)(cnt - hpet_read(HPET_COUNTER)); >> + >> + return res < HPET_MIN_CYCLES ? -ETIME : 0; >> +} >> + >> +static irqreturn_t hpet_irq_handler(int irq, void *data) >> +{ >> + int is_irq; >> + struct clock_event_device *cd; >> + unsigned int cpu = smp_processor_id(); >> + >> + is_irq = hpet_read(HPET_STATUS); >> + if (is_irq & HPET_T0_IRS) { >> + /* clear the TIMER0 irq status register */ >> + hpet_write(HPET_STATUS, HPET_T0_IRS); >> + cd = &per_cpu(hpet_clockevent_device, cpu); >> + cd->event_handler(cd); >> + return IRQ_HANDLED; >> + } >> + return IRQ_NONE; >> +} >> + >> +/* >> + * HPET address assignation and irq setting should be done in bios. >> + * But, sometimes bios don't do this, we just setup here directly. >> + */ >> +static void hpet_setup(void) >> +{ >> + hpet_enable_legacy_int(); >> +} >> + >> +static int hpet_request_irq(struct clock_event_device *cd) >> +{ >> + unsigned long flags = IRQD_NO_BALANCING | IRQF_TIMER; >> + >> + if (request_irq(cd->irq, hpet_irq_handler, flags, "hpet", NULL)) { >> + pr_err("Failed to register hpet interrupt\n"); >> + return -1; >> + } >> + >> + disable_irq(cd->irq); >> + irq_set_affinity(cd->irq, cd->cpumask); >> + enable_irq(cd->irq); >> + >> + return 0; >> +} >> + >> +static int __init loongson2_hpet_clockevent_init(void) >> +{ >> + unsigned int cpu = smp_processor_id(); >> + struct clock_event_device *cd; >> + >> + hpet_setup(); >> + >> + cd = &per_cpu(hpet_clockevent_device, cpu); >> + cd->name = "hpet"; >> + cd->rating = 300; >> + cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; >> + cd->set_state_shutdown = hpet_set_state_shutdown; >> + cd->set_state_periodic = hpet_set_state_periodic; >> + cd->set_state_oneshot = hpet_set_state_oneshot; >> + cd->tick_resume = hpet_tick_resume; >> + cd->set_next_event = hpet_next_event; >> + cd->irq = hpet_t0_irq; >> + cd->cpumask = cpumask_of(cpu); >> + clockevent_set_clock(cd, hpet_freq); >> + cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); >> + cd->max_delta_ticks = 0x7fffffff; >> + cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd); >> + cd->min_delta_ticks = HPET_MIN_PROG_DELTA; >> + >> + clockevents_register_device(cd); >> + if (hpet_request_irq(cd)) >> + return -1; >> + >> + pr_info("hpet clock event device register\n"); >> + >> + return 0; >> +} >> + >> +static u64 hpet_read_counter(struct clocksource *cs) >> +{ >> + return (u64)hpet_read(HPET_COUNTER); >> +} >> + >> +static void hpet_suspend(struct clocksource *cs) >> +{ >> + hpet_t0_cfg = hpet_read(HPET_T0_CFG); >> +} >> + >> +static void hpet_resume(struct clocksource *cs) >> +{ >> + hpet_write(HPET_T0_CFG, hpet_t0_cfg); >> + hpet_setup(); >> + hpet_restart_counter(); >> +} >> + >> +struct clocksource csrc_hpet = { >> + .name = "hpet", >> + .rating = 300, >> + .read = hpet_read_counter, >> + .mask = CLOCKSOURCE_MASK(32), >> + /* oneshot mode work normal with this flag */ >> + .flags = CLOCK_SOURCE_IS_CONTINUOUS, >> + .suspend = hpet_suspend, >> + .resume = hpet_resume, >> + .mult = 0, >> + .shift = 10, >> +}; >> + >> +static int __init loongson2_hpet_clocksource_init(void) >> +{ >> + csrc_hpet.mult = clocksource_hz2mult(hpet_freq, csrc_hpet.shift); >> + >> + /* start counter */ >> + hpet_start_counter(); >> + >> + return clocksource_register_hz(&csrc_hpet, hpet_freq); >> +} >> + >> +static int __init loongson2_hpet_init(struct device_node *np) >> +{ >> + int ret; >> + struct clk *clk; >> + >> + hpet_mmio_base = of_iomap(np, 0); >> + if (!hpet_mmio_base) { >> + pr_err("hpet: unable to map loongson2 hpet registers\n"); >> + goto err; >> + } >> + >> + ret = -EINVAL; >> + hpet_t0_irq = irq_of_parse_and_map(np, 0); >> + if (hpet_t0_irq <= 0) { >> + pr_err("hpet: unable to get IRQ from DT, %d\n", hpet_t0_irq); >> + goto err; >> + } >> + >> + clk = of_clk_get(np, 0); >> + if (!IS_ERR(clk)) { >> + hpet_freq = clk_get_rate(clk); >> + clk_put(clk); >> + } else >> + goto err; >> + >> + hpet_irq_flags = HPET_TN_LEVEL; >> + >> + loongson2_hpet_clocksource_init(); >> + >> + loongson2_hpet_clockevent_init(); >> + >> + return 0; >> + >> +err: >> + iounmap(hpet_mmio_base); >> + return ret; >> +} >> + >> +TIMER_OF_DECLARE(loongson2_hpet, "loongson,ls2k-hpet", >> loongson2_hpet_init); >>
| |