[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <b8229e20-92ce-5756-f72c-74f715e61b15@loongson.cn>
Date: Wed, 9 Nov 2022 17:40:11 +0800
From: Yinbo Zhu <zhuyinbo@...ngson.cn>
To: Daniel Lezcano <daniel.lezcano@...aro.org>,
Thomas Gleixner <tglx@...utronix.de>,
Rob Herring <robh+dt@...nel.org>,
Krzysztof Kozlowski <krzysztof.kozlowski+dt@...aro.org>,
Huacai Chen <chenhuacai@...nel.org>,
WANG Xuerui <kernel@...0n.name>,
Jiaxun Yang <jiaxun.yang@...goat.com>,
Jianmin Lv <lvjianmin@...ngson.cn>,
Yun Liu <liuyun@...ngson.cn>,
Yang Li <yang.lee@...ux.alibaba.com>,
linux-kernel@...r.kernel.org, devicetree@...r.kernel.org,
loongarch@...ts.linux.dev
Subject: Re: [PATCH v8 1/2] clocksource: loongson2_hpet: add hpet driver
support
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@...ngson.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@...ngson.cn>
>> +L: linux-kernel@...r.kernel.org
>> +S: Maintained
>> +F: drivers/clocksource/loongson2_hpet.c
>> +
>> LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI)
>> M: Sathya Prakash <sathya.prakash@...adcom.com>
>> M: Sreekanth Reddy <sreekanth.reddy@...adcom.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@...ngson.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);
>>
Powered by blists - more mailing lists