[U-Boot] [PATCH v2] timer: add High Precision Event Timers (HPET) support

Bin Meng bmeng.cn at gmail.com
Fri Mar 30 09:52:38 UTC 2018


Hi Ivan,

On Fri, Mar 30, 2018 at 6:29 AM, Ivan Gorinov <ivan.gorinov at intel.com> wrote:
> Adding HPET as an alternative timer for x86 (default is TSC).
> HPET counter has constant frequency and does not need calibration.
> This change also makes TSC timer driver optional on x86.
> If X86_TSC is disabled, early timer functions are provided by HPET.
>
> Signed-off-by: Ivan Gorinov <ivan.gorinov at intel.com>
> ---
>  arch/Kconfig               |   2 +-
>  arch/x86/dts/hpet.dtsi     |   7 ++
>  drivers/timer/Kconfig      |   6 ++
>  drivers/timer/Makefile     |   1 +
>  drivers/timer/hpet_timer.c | 191 +++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 206 insertions(+), 1 deletion(-)
>  create mode 100644 arch/x86/dts/hpet.dtsi
>  create mode 100644 drivers/timer/hpet_timer.c
>
> diff --git a/arch/Kconfig b/arch/Kconfig
> index e599e7a..37dabae 100644
> --- a/arch/Kconfig
> +++ b/arch/Kconfig
> @@ -104,7 +104,7 @@ config X86
>         select DM_PCI
>         select PCI
>         select TIMER
> -       select X86_TSC_TIMER
> +       imply X86_TSC_TIMER
>         imply BLK
>         imply DM_ETH
>         imply DM_GPIO
> diff --git a/arch/x86/dts/hpet.dtsi b/arch/x86/dts/hpet.dtsi
> new file mode 100644
> index 0000000..037dc7e
> --- /dev/null
> +++ b/arch/x86/dts/hpet.dtsi
> @@ -0,0 +1,7 @@
> +/ {
> +       hpet0: hpet at fed00000 {

nits: use 'hpet' instead of 'hpet0'? since there is only one hpet
timer in the system.

> +               compatible = "hpet-x86";
> +               u-boot,dm-pre-reloc;
> +               reg = <0xfed00000 0x1000>;
> +       };
> +};
> diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
> index 2c96896..72ae6c5 100644
> --- a/drivers/timer/Kconfig
> +++ b/drivers/timer/Kconfig
> @@ -65,6 +65,12 @@ config X86_TSC_TIMER
>         help
>           Select this to enable Time-Stamp Counter (TSC) timer for x86.
>
> +config HPET_TIMER
> +       bool "High Precision Event Timers (HPET) support"
> +       depends on TIMER && X86
> +       help
> +         Select this to enable High Precision Event Timers (HPET) for x86.
> +
>  config OMAP_TIMER
>         bool "Omap timer support"
>         depends on TIMER
> diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
> index a6e7832..557fecc 100644
> --- a/drivers/timer/Makefile
> +++ b/drivers/timer/Makefile
> @@ -8,6 +8,7 @@ obj-y += timer-uclass.o
>  obj-$(CONFIG_ALTERA_TIMER)     += altera_timer.o
>  obj-$(CONFIG_SANDBOX_TIMER)    += sandbox_timer.o
>  obj-$(CONFIG_X86_TSC_TIMER)    += tsc_timer.o
> +obj-$(CONFIG_HPET_TIMER)       += hpet_timer.o
>  obj-$(CONFIG_OMAP_TIMER)       += omap-timer.o
>  obj-$(CONFIG_AST_TIMER)        += ast_timer.o
>  obj-$(CONFIG_STI_TIMER)                += sti-timer.o
> diff --git a/drivers/timer/hpet_timer.c b/drivers/timer/hpet_timer.c
> new file mode 100644
> index 0000000..0bef859
> --- /dev/null
> +++ b/drivers/timer/hpet_timer.c
> @@ -0,0 +1,191 @@
> +/*
> + * Copyright (c) 2017 Intel Corporation
> + *
> + * SPDX-License-Identifier:    GPL-2.0+
> + */
> +
> +#include <common.h>
> +#include <dm.h>
> +#include <malloc.h>
> +#include <timer.h>
> +#include <asm/cpu.h>
> +#include <asm/io.h>
> +#include <asm/u-boot-x86.h>
> +
> +#define HPET_PERIOD_REG 0x004
> +#define HPET_CONFIG_REG 0x010
> +#define HPET_MAIN_COUNT_L 0x0f0
> +#define HPET_MAIN_COUNT_H 0x0f4
> +
> +#define HPET_MAX_PERIOD 100000000
> +
> +DECLARE_GLOBAL_DATA_PTR;

This is not needed.

> +
> +struct hpet_timer_priv {
> +       void *regs;
> +};
> +
> +/*
> + * Returns HPET clock frequency in HZ
> + * (rounding to the nearest integer),
> + * or 0 if HPET is not available.
> + */
> +static inline u32 get_clock_frequency(void *regs)
> +{
> +       u64 d = 1000000000000000ull;
> +       u32 period;
> +
> +       period = readl(regs + HPET_PERIOD_REG);
> +       if (period == 0)
> +               return 0;
> +       if (period > HPET_MAX_PERIOD)
> +               return 0;
> +
> +       d += period / 2;
> +
> +       return d / period;
> +}
> +
> +/*
> + * Reset and start the main counter.
> + */

nits: use one line comment format

> +static void start_main_counter(void *regs)
> +{
> +       u32 config;
> +
> +       config = readl(regs + HPET_CONFIG_REG);
> +       config &= ~1;
> +       writel(config, regs + HPET_CONFIG_REG);
> +       writel(0, regs + HPET_MAIN_COUNT_L);
> +       writel(0, regs + HPET_MAIN_COUNT_H);
> +       config |= 1;
> +       writel(config, regs + HPET_CONFIG_REG);
> +}
> +
> +/*
> + * Read the main counter as two 32-bit registers,
> + * repeat if rollover happens.
> + */
> +static u64 read_main_counter(void *regs)
> +{
> +       u64 now_tick;
> +       u32 tl, th, th0;
> +
> +       th = readl(regs + HPET_MAIN_COUNT_H);
> +       do {
> +               th0 = th;
> +               tl = readl(regs + HPET_MAIN_COUNT_L);
> +               th = readl(regs + HPET_MAIN_COUNT_H);
> +       } while (th != th0);
> +
> +       now_tick = th;
> +       now_tick <<= 32;
> +       now_tick |= tl;
> +
> +       return now_tick;
> +}
> +
> +static int hpet_timer_get_count(struct udevice *dev, u64 *count)
> +{
> +       struct hpet_timer_priv *priv = dev_get_priv(dev);
> +
> +       *count = read_main_counter(priv->regs);
> +
> +       return 0;
> +}
> +
> +static int hpet_timer_probe(struct udevice *dev)
> +{
> +       struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
> +       struct hpet_timer_priv *priv = dev_get_priv(dev);
> +       u32 rate;
> +
> +       rate = get_clock_frequency(priv->regs);
> +       if (rate == 0)
> +               return -ENODEV;
> +
> +       start_main_counter(priv->regs);
> +
> +       uc_priv->clock_rate = rate;
> +
> +       return 0;
> +}
> +
> +#ifndef CONFIG_X86_TSC_TIMER

This looks not good. If we support more timers on x86, the #ifdef
logic will become un-maintainable. Can we support such from the timer
library instead?

> +
> +static void *early_regs = (void *) CONFIG_HPET_ADDRESS;
> +
> +unsigned long notrace timer_early_get_rate(void)
> +{
> +       return get_clock_frequency(early_regs);
> +}
> +
> +u64 notrace timer_early_get_count(void)
> +{
> +       return read_main_counter(early_regs);
> +}
> +
> +int timer_init(void)
> +{
> +       if (get_clock_frequency(early_regs) == 0)
> +               return -ENODEV;
> +
> +       start_main_counter(early_regs);
> +
> +       return 0;
> +}
> +
> +ulong timer_get_boot_us(void)
> +{
> +       u32 period;
> +       u64 d;
> +
> +       period = readl(early_regs + HPET_PERIOD_REG);
> +       if (period == 0)
> +               return 0;
> +       if (period > HPET_MAX_PERIOD)
> +               return 0;
> +
> +       d = read_main_counter(early_regs);
> +
> +       /*
> +        * Multiplication overflow
> +        * at 2^64 femtoseconds
> +        * (more than 5 hours)
> +        */
> +
> +       d *= period;
> +
> +       return d / 1000000000;
> +}
> +
> +#endif /* CONFIG_X86_TSC_TIMER */
> +
> +static int hpet_timer_ofdata_to_platdata(struct udevice *dev)
> +{
> +       struct hpet_timer_priv *priv = dev_get_priv(dev);
> +       priv->regs = map_physmem(devfdt_get_addr(dev), 0x1000, MAP_NOCACHE);
> +
> +       return 0;
> +}
> +
> +static const struct timer_ops hpet_timer_ops = {
> +       .get_count = hpet_timer_get_count,
> +};
> +
> +static const struct udevice_id hpet_timer_ids[] = {
> +       { .compatible = "hpet-x86", },
> +       { .compatible = "intel,ce4100-hpet", },
> +       { }
> +};
> +
> +U_BOOT_DRIVER(hpet_timer) = {
> +       .name   = "hpet_timer",
> +       .id     = UCLASS_TIMER,
> +       .of_match = hpet_timer_ids,
> +       .ofdata_to_platdata = hpet_timer_ofdata_to_platdata,
> +       .priv_auto_alloc_size = sizeof(struct hpet_timer_priv),
> +       .probe = hpet_timer_probe,
> +       .ops    = &hpet_timer_ops,
> +       .flags = DM_FLAG_PRE_RELOC,
> +};
> --

I will try to test this patch on several x86 boards.

Regards,
Bin


More information about the U-Boot mailing list