Re: [PATCH v4 1/2] clocksource: loongson2_hpet: add hpet driver support

From: Huacai Chen
Date: Wed Oct 26 2022 - 08:11:23 EST


Hi, Yinbo,

On Wed, Oct 26, 2022 at 11:58 AM Yinbo Zhu <zhuyinbo@xxxxxxxxxxx> wrote:
>
> 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 loongson2 HPET module includes a main count and three comparators
The naming issue, which has been pointed out in another thread.

> , 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@xxxxxxxxxxx>
> ---
> 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";.
>
> 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 f61a431ad8ca..db29c1dc2d89 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -11915,6 +11915,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
>
> +LOONGSON2 SOC SERIES HPET DRIVER
> +M: Yinbo Zhu <zhuyinbo@xxxxxxxxxxx>
> +L: linux-kernel@xxxxxxxxxxxxxxx
> +S: Maintained
> +F: drivers/clocksource/loongson2_hpet.c
> +
> LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
> M: Sathya Prakash <sathya.prakash@xxxxxxxxxxxx>
> M: Sreekanth Reddy <sreekanth.reddy@xxxxxxxxxxxx>
> 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 4f2bb7315b67..1c7ab3541d81 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 "Loongson2 High Precision Event Timer (HPET)"
> + select TIMER_PROBE
> + select TIMER_OF
> + help
> + This option enables Loongson2 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..fbcf69f0204f
> --- /dev/null
> +++ b/drivers/clocksource/loongson2_hpet.c
> @@ -0,0 +1,335 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Author: Yinbo Zhu <zhuyinbo@xxxxxxxxxxx>
> + * 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 <asm/time.h>
Please include asm headers after all linux headers.

Huacai
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +#include <linux/clk.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;
> +}
> +
> +static struct irqaction hpet_irq_str = {
> + .handler = hpet_irq_handler,
> + .flags = IRQD_NO_BALANCING | IRQF_TIMER,
> + .name = "hpet",
> +};
> +
> +/*
> + * 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_setup_irq(struct clock_event_device *cd)
> +{
> + setup_irq(cd->irq, &hpet_irq_str);
> +
> + 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);
> + hpet_setup_irq(cd);
> +
> + 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);
> --
> 2.31.1
>