[PATCH] clockevents: rockchip: Add rockchip timer for rk3288

Heiko Stübner heiko at sntech.de
Mon Jan 12 15:20:25 PST 2015


Hi Daniel,

sorry it took a bit longer to reply.

Generally it looks good to me, just some minor issues inline below.

It would also be nice to include the rockchip list (linux-
rockchip at lists.infradead.org) for future patches.


Am Dienstag, 6. Januar 2015, 12:03:53 schrieb Daniel Lezcano:
> The rk3288 board uses the architected timers and these ones are shutdown
> when the cpu is powered down. There is a need of a broadcast timer in this
> case to ensure proper wakeup when the cpus are in sleep mode and a timer
> expires.
> 
> This driver provides the basic timer functionnality as a backup for the
> local timers at sleep time.
> 
> The timer belongs to the alive subsystem. It includes two programmables 64
> bits timer channels but the driver only uses 32bits. It works with two
> operations mode: free running and user defined count.
> 
> Programing sequence:
> 
> 1. Timer initialization:
>  * Disable the timer by writing '0' to the CONTROLREG register
>  * Program the timer mode by writing the mode to the CONTROLREG register
>  * Set the interrupt mask
> 
> 2. Setting the count value:
>  * Load the count value to the registers COUNT0 and COUNT1 (not used).
> 
> 3. Enable the timer
>  * Write '1' to the CONTROLREG register with the mode (free running or user)
> 
> Signed-off-by: Daniel Lezcano <daniel.lezcano at linaro.org>
> ---
>  .../bindings/timer/rockchip,rk3288-timer.txt       |  15 ++
>  arch/arm/boot/dts/rk3288.dtsi                      |   7 +
>  arch/arm/mach-rockchip/Kconfig                     |   1 +
>  drivers/clocksource/Kconfig                        |   4 +
>  drivers/clocksource/Makefile                       |   1 +
>  drivers/clocksource/rockchip_timer.c               | 158
> +++++++++++++++++++++ 6 files changed, 186 insertions(+)
>  create mode 100644
> Documentation/devicetree/bindings/timer/rockchip,rk3288-timer.txt create
> mode 100644 drivers/clocksource/rockchip_timer.c
> 
> diff --git
> a/Documentation/devicetree/bindings/timer/rockchip,rk3288-timer.txt
> b/Documentation/devicetree/bindings/timer/rockchip,rk3288-timer.txt new
> file mode 100644
> index 0000000..54ef747
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/timer/rockchip,rk3288-timer.txt
> @@ -0,0 +1,15 @@
> +Rockchip rk3288 timer
> +
> +Required properties:
> +- compatible: shall be "rockchip,rk3288-timer"
> +- reg: base address of the timer register starting with TIMERS CONTROL
> register +- interrupts: should contain the interrupts for Timer0
> +- clock-frequency: the frequency the timer is running
> +
> +Example:
> +	timer: timer at ff810000 {
> +		compatible = "rockchip,rk3288-timer";
> +		reg = <0xff810000 0x20>;
> +		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
> +		clock-frequency = <24000000>;

wouldn't it make sense to use the actual supplying clock?

For the timer you want to use it is just the non-gateable &xin24m, but the 
timers in the other block (timer0-5) actually do have gateable clocks.

Similarly there is a pclk_timer supplying at least one of the two actual 
blocks. I'll try to inquire how the blocks are actually supplied.


> +	};
> diff --git a/arch/arm/boot/dts/rk3288.dtsi b/arch/arm/boot/dts/rk3288.dtsi
> index 2a878a3..7a7db48 100644
> --- a/arch/arm/boot/dts/rk3288.dtsi
> +++ b/arch/arm/boot/dts/rk3288.dtsi
> @@ -149,6 +149,13 @@
>  		clock-frequency = <24000000>;
>  	};
> 
> +	timer: timer at ff810000 {
> +		compatible = "rockchip,rk3288-timer";
> +		reg = <0xff810000 0x20>;
> +		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
> +		clock-frequency = <24000000>;
> +	};
> +
>  	sdmmc: dwmmc at ff0c0000 {
>  		compatible = "rockchip,rk3288-dw-mshc";
>  		clock-freq-min-max = <400000 150000000>;
> diff --git a/arch/arm/mach-rockchip/Kconfig b/arch/arm/mach-rockchip/Kconfig
> index ac5803c..4c3fa7e 100644
> --- a/arch/arm/mach-rockchip/Kconfig
> +++ b/arch/arm/mach-rockchip/Kconfig
> @@ -11,6 +11,7 @@ config ARCH_ROCKCHIP
>  	select HAVE_ARM_SCU if SMP
>  	select HAVE_ARM_TWD if SMP
>  	select DW_APB_TIMER_OF
> +	select RK3288_TIMER
>  	select ARM_GLOBAL_TIMER
>  	select CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
>  	help
> diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
> index fc01ec2..6ed97a6 100644
> --- a/drivers/clocksource/Kconfig
> +++ b/drivers/clocksource/Kconfig
> @@ -26,6 +26,10 @@ config DW_APB_TIMER_OF
>  	select DW_APB_TIMER
>  	select CLKSRC_OF
> 
> +config RK3288_TIMER
> +	bool
> +	select CLKSRC_OF
> +

the config symbol to compile rockchip_timer.c is RK3288_TIMER?
I'd think it could match a bit more ...
ROCKCHIP_TIMER -> rockchip_timer.c
	or
RK3288_TIMER -> rk3288_timer.c

This timer-block is actually also present on the rk3188, but not on the rk3066 
which uses an unmodified dw_apb_timer.


>  config ARMADA_370_XP_TIMER
>  	bool
>  	select CLKSRC_OF
> diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
> index 94d90b2..39e4f77 100644
> --- a/drivers/clocksource/Makefile
> +++ b/drivers/clocksource/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
>  obj-$(CONFIG_CLKSRC_MMIO)	+= mmio.o
>  obj-$(CONFIG_DW_APB_TIMER)	+= dw_apb_timer.o
>  obj-$(CONFIG_DW_APB_TIMER_OF)	+= dw_apb_timer_of.o
> +obj-$(CONFIG_RK3288_TIMER)      += rockchip_timer.o
>  obj-$(CONFIG_CLKSRC_NOMADIK_MTU)	+= nomadik-mtu.o
>  obj-$(CONFIG_CLKSRC_DBX500_PRCMU)	+= clksrc-dbx500-prcmu.o
>  obj-$(CONFIG_ARMADA_370_XP_TIMER)	+= time-armada-370-xp.o
> diff --git a/drivers/clocksource/rockchip_timer.c
> b/drivers/clocksource/rockchip_timer.c new file mode 100644
> index 0000000..00e24bd
> --- /dev/null
> +++ b/drivers/clocksource/rockchip_timer.c
> @@ -0,0 +1,158 @@
> +/*
> + * Rockchip timer support
> + *
> + * Copyright (C) Daniel Lezcano <daniel.lezcano at linaro.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/clockchips.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +
> +#define TIMER_NAME "rk_timer"
> +
> +#define TIMER_LOAD_COUNT0               0x00
> +#define TIMER_LOAD_COUNT1               0x04
> +#define TIMER_CONTROL_REG               0x10
> +#define TIMER_INT_STATUS                0x18
> +
> +#define TIMER_DISABLE                   0x0
> +#define TIMER_ENABLE                    0x1
> +#define TIMER_MODE_FREE_RUNNING         (0 << 1)
> +#define TIMER_MODE_USER_DEFINED_COUNT   (1 << 1)
> +#define TIMER_INT_UNMASK                (1 << 2)
> +
> +struct bc_timer {
> +	struct clock_event_device ce;
> +	void __iomem *base;
> +	u32 freq;
> +};
> +
> +static struct bc_timer bc_timer;
> +
> +static inline struct bc_timer *rk_timer(struct clock_event_device *ce)
> +{
> +	return container_of(ce, struct bc_timer, ce);
> +}
> +
> +static inline void __iomem *rk_base(struct clock_event_device *ce)
> +{
> +	return rk_timer(ce)->base;
> +}
> +
> +static inline void rk_timer_disable(struct clock_event_device *ce)
> +{
> +	writel_relaxed(TIMER_DISABLE, rk_base(ce) + TIMER_CONTROL_REG);
> +	dsb();
> +}
> +
> +static inline void rk_timer_enable(struct clock_event_device *ce, u32
> flags) +{
> +	writel_relaxed(TIMER_ENABLE | TIMER_INT_UNMASK | flags,
> +		       rk_base(ce) + TIMER_CONTROL_REG);
> +	dsb();
> +}
> +
> +static void rk_timer_update_counter(unsigned long cycles,
> +				    struct clock_event_device *ce)
> +{
> +	writel_relaxed(cycles, rk_base(ce) + TIMER_LOAD_COUNT0);
> +	writel_relaxed(0, rk_base(ce) + TIMER_LOAD_COUNT1);
> +	dsb();
> +}
> +
> +static void rk_timer_interrupt_clear(struct clock_event_device *ce)
> +{
> +	writel_relaxed(1, rk_base(ce) + TIMER_INT_STATUS);
> +	dsb();
> +}
> +
> +static inline int rk_timer_set_next_event(unsigned long cycles,
> +					  struct clock_event_device *ce)
> +{
> +	rk_timer_disable(ce);
> +	rk_timer_update_counter(cycles, ce);
> +	rk_timer_enable(ce, TIMER_MODE_USER_DEFINED_COUNT);
> +	return 0;
> +}
> +
> +static inline void rk_timer_set_mode(enum clock_event_mode mode,
> +				     struct clock_event_device *ce)
> +{
> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		rk_timer_disable(ce);
> +		rk_timer_update_counter(rk_timer(ce)->freq / HZ - 1, ce);
> +		rk_timer_enable(ce, TIMER_MODE_FREE_RUNNING);
> +	case CLOCK_EVT_MODE_ONESHOT:
> +	case CLOCK_EVT_MODE_RESUME:
> +		break;
> +	case CLOCK_EVT_MODE_UNUSED:
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +		rk_timer_disable(ce);
> +		break;
> +	}
> +}
> +
> +static irqreturn_t rk_timer_interrupt(int irq, void *dev_id)
> +{
> +	struct clock_event_device *ce = dev_id;
> +
> +	rk_timer_interrupt_clear(ce);
> +
> +	if (ce->mode == CLOCK_EVT_MODE_ONESHOT) {
> +		rk_timer_disable(ce);
> +	}
> +
> +	ce->event_handler(ce);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void __init rk_timer_init(struct device_node *np)
> +{
> +	struct clock_event_device *ce = &bc_timer.ce;
> +	int ret, irq;
> +
> +	bc_timer.base = of_iomap(np, 0);
> +	if (!bc_timer.base) {
> +		pr_err("Failed to get base address for '%s'\n", TIMER_NAME);
> +		return;
> +	}
> +
> +	irq = irq_of_parse_and_map(np, 0);
> +	if (irq == NO_IRQ) {
> +                pr_err("Failed to map interrupts for '%s'\n", TIMER_NAME);
> +                return;
> +        }
> +
> +        if (of_property_read_u32(np, "clock-frequency", &bc_timer.freq)) {

formatting issue with spaces instead of tabs in the 5 lines above


Cheers,
Heiko


> +		pr_err("Failed to read the frequency for '%s'\n", TIMER_NAME);
> +		return;
> +	}
> +
> +	ce->name = TIMER_NAME;
> +	ce->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
> +	ce->set_next_event = rk_timer_set_next_event;
> +	ce->set_mode = rk_timer_set_mode;
> +	ce->irq = irq;
> +	ce->cpumask = cpumask_of(0);
> +	ce->rating = 250;
> +
> +	rk_timer_interrupt_clear(ce);
> +	rk_timer_disable(ce);
> +
> +	ret = request_irq(irq, rk_timer_interrupt, IRQF_TIMER, TIMER_NAME, ce);
> +	if (ret) {
> +		pr_err("Failed to initialize '%s': %d\n", TIMER_NAME, ret);
> +		return;
> +	}
> +
> +	clockevents_config_and_register(ce, bc_timer.freq, 1, UINT_MAX);
> +}
> +CLOCKSOURCE_OF_DECLARE(rk_timer, "rockchip,rk3288-timer", rk_timer_init);



More information about the Linux-rockchip mailing list