[v4,1/2] clocksource: loongson2_hpet: add hpet driver support
Commit Message
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
, 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 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
Comments
Hi, Yinbo,
On Wed, Oct 26, 2022 at 11:58 AM Yinbo Zhu <zhuyinbo@loongson.cn> 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@loongson.cn>
> ---
> 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@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 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@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 <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
>
On 25/10/2022 23:57, Yinbo Zhu 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
> , all of which are 32 bits wide. Among the three comparators, only
No blank line before coma.
> one comparator supports periodic interrupt, all three comparators
> support non periodic interrupts.
>
> Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
> ---
> 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".
Link to lore.
The point 2 should go to cover letter. It's easy to miss it.
Best regards,
Krzysztof
On 25/10/2022 23:57, Yinbo Zhu 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
> , 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 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".
Your dependency does not apply. You based it on some other tree, so we
cannot even test this.
Fix your clock patchset.
Best regards,
Krzysztof
在 2022/10/26 下午10:20, Krzysztof Kozlowski 写道:
> On 25/10/2022 23:57, Yinbo Zhu 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
>> , all of which are 32 bits wide. Among the three comparators, only
>
> No blank line before coma.
>
>> one comparator supports periodic interrupt, all three comparators
>> support non periodic interrupts.
>>
>> Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
>> ---
>> 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".
>
> Link to lore.
>
> The point 2 should go to cover letter. It's easy to miss it.
okay, after fix the depend patch, then I will add a mail for the point2
for the hpet patch to upstream.
Thanks.
>
> Best regards,
> Krzysztof
>
在 2022/10/26 下午10:20, Krzysztof Kozlowski 写道:
> On 25/10/2022 23:57, Yinbo Zhu 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
>> , all of which are 32 bits wide. Among the three comparators, only
>
> No blank line before coma.
You said that need blank line is need a blank space before comma? I
will add it a blank space before comma.
>
>> one comparator supports periodic interrupt, all three comparators
>> support non periodic interrupts.
>>
>> Signed-off-by: Yinbo Zhu <zhuyinbo@loongson.cn>
>> ---
>> 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".
>
> Link to lore.
>
> The point 2 should go to cover letter. It's easy to miss it.
>
> Best regards,
> Krzysztof
>
On 27/10/2022 23:54, Yinbo Zhu wrote:
>
>
> 在 2022/10/26 下午10:20, Krzysztof Kozlowski 写道:
>> On 25/10/2022 23:57, Yinbo Zhu 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
>>> , all of which are 32 bits wide. Among the three comparators, only
>>
>> No blank line before coma.
> You said that need blank line is need a blank space before comma?
No, I did not say that.
Best regards,
Krzysztof
在 2022/10/28 下午7:33, Krzysztof Kozlowski 写道:
> On 27/10/2022 23:54, Yinbo Zhu wrote:
>>
>>
>> 在 2022/10/26 下午10:20, Krzysztof Kozlowski 写道:
>>> On 25/10/2022 23:57, Yinbo Zhu 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
>>>> , all of which are 32 bits wide. Among the three comparators, only
>>>
>>> No blank line before coma.
>> You said that need blank line is need a blank space before comma?
>
> No, I did not say that.
>
Your meaning is no new lines before commas ?
I will do it.
> Best regards,
> Krzysztof
>
@@ -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@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>
@@ -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
@@ -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
@@ -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
new file mode 100644
@@ -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 <asm/time.h>
+#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);