clocksource: dw_apb_timer: support for timer variant used in rk3188 SoCs
Subsystems (4)
Heiko Stübner
maintainercover clocksource: dw_apb_timer: support for timer variant used in rk3188 SoCs
The Rockchip rk3188 SoCs use a variant of the timer with some slight modifications. This series implements them as quirks for the dw_apb_timer.
Tested on a rk3188 for the quirk handling and on a rk3066a to check that nothing broke.
Heiko Stuebner (5):
clocksource: dw_apb_timer: infrastructure to handle quirks
clocksource: dw_apb_timer: flexible register addresses
clocksource: dw_apb_timer: quirk for variants with 64bit counter
clocksource: dw_apb_timer_of: add quirk handling
clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
Ulrich Prinz (4):
clocksource: dw_apb_timer: use the eoi callback to clear pending interrupts
clocksource: dw_apb_timer: quirk for variants without EOI register
clocksource: dw_apb_timer: quirk for inverted int mask
clocksource: dw_apb_timer: quirk for inverted timer mode setting
.../bindings/arm/rockchip/rk3188-timer.txt | 20 +++ arch/x86/kernel/apb_timer.c | 4 +- drivers/clocksource/dw_apb_timer.c | 166 +++++++++++++++----- drivers/clocksource/dw_apb_timer_of.c | 27 +++- include/linux/dw_apb_timer.h | 34 +++- 5 files changed, 198 insertions(+), 53 deletions(-) create mode 100644 Documentation/devicetree/bindings/arm/rockchip/rk3188-timer.txt
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerPATCH 1/9 clocksource: dw_apb_timer: infrastructure to handle quirks
There exist variants of the timer IP with some modified properties.
Therefore add infrastructure to handle hardware-quirks in the driver.
--- arch/x86/kernel/apb_timer.c | 4 ++-- drivers/clocksource/dw_apb_timer.c | 7 +++++-- drivers/clocksource/dw_apb_timer_of.c | 4 ++-- include/linux/dw_apb_timer.h | 6 ++++-- 4 files changed, 13 insertions(+), 8 deletions(-)
arch/x86/kernel/apb_timer.c +2 -2
@@ -121,7 +121,7 @@ static inline void apbt_set_mapping(void) clocksource_apbt =dw_apb_clocksource_init (APBT_CLOCKSOURCE_RATING ,"apbt0" , apbt_virt_address + phy_cs_timer_id * -APBTMRS_REG_SIZE , apbt_freq); +APBTMRS_REG_SIZE , apbt_freq,0 );return ; panic_noapbt:
@@ -159,7 +159,7 @@ static int __init apbt_clockevent_register(void) adev->timer =dw_apb_clockevent_init (smp_processor_id (),"apbt0" , mrst_timer_options ==MRST_TIMER_LAPIC_APBT ?APBT_CLOCKEVENT_RATING -100 :APBT_CLOCKEVENT_RATING , -adev_virt_addr (adev),0 , apbt_freq); +adev_virt_addr (adev),0 , apbt_freq,0 );/* Firmware does EOI handling for us. */ adev->timer->eoi =NULL ;
drivers/clocksource/dw_apb_timer.c +5 -2
@@ -213,7 +213,8 @@ static int apbt_next_event(unsigned long delta,*/ struct dw_apb_clock_event_device *dw_apb_clockevent_init (int cpu,const char *name,unsigned rating, -void __iomem *base,int irq,unsigned long freq) +void __iomem *base,int irq,unsigned long freq, +int quirks) {struct dw_apb_clock_event_device *dw_ced =kzalloc (sizeof (*dw_ced),GFP_KERNEL );
@@ -225,6 +226,7 @@ dw_apb_clockevent_init(int cpu, const char *name, unsigned rating, dw_ced->timer.base = base; dw_ced->timer.irq = irq; dw_ced->timer.freq = freq; + dw_ced->timer.quirks = quirks;clockevents_calc_mult_shift (&dw_ced->ced, freq,APBT_MIN_PERIOD ); dw_ced->ced.max_delta_ns = clockevent_delta2ns(0x7fffffff,
@@ -349,7 +351,7 @@ static void apbt_restart_clocksource(struct clocksource *cs)*/ struct dw_apb_clocksource *dw_apb_clocksource_init (unsigned rating,const char *name,void __iomem *base, -unsigned long freq) +unsigned long freq,int quirks) {struct dw_apb_clocksource *dw_cs =kzalloc (sizeof (*dw_cs),GFP_KERNEL );
@@ -358,6 +360,7 @@ dw_apb_clocksource_init(unsigned rating, const char *name, void __iomem *base, dw_cs->timer.base = base; dw_cs->timer.freq = freq; + dw_cs->timer.quirks = quirks; dw_cs->cs.name = name; dw_cs->cs.rating = rating; dw_cs->cs.read = __apbt_read_clocksource;
drivers/clocksource/dw_apb_timer_of.c +2 -2
@@ -74,7 +74,7 @@ static void add_clockevent(struct device_node *event_timer)timer_get_base_and_rate (event_timer, &iobase, &rate); ced =dw_apb_clockevent_init (0 , event_timer->name,300 , iobase, irq, - rate); + rate,0 );if (!ced)panic ("Unable to initialise clockevent device" );
@@ -92,7 +92,7 @@ static void add_clocksource(struct device_node *source_timer)timer_get_base_and_rate (source_timer, &iobase, &rate); - cs =dw_apb_clocksource_init (300 , source_timer->name, iobase, rate); + cs =dw_apb_clocksource_init (300 , source_timer->name, iobase, rate,0 );if (!cs)panic ("Unable to initialise clocksource device" );
include/linux/dw_apb_timer.h +4 -2
@@ -23,6 +23,7 @@ struct dw_apb_timer {void __iomem *base;unsigned long freq;int irq; +int quirks; };struct dw_apb_clock_event_device {
@@ -44,10 +45,11 @@ void dw_apb_clockevent_stop(struct dw_apb_clock_event_device *dw_ced);struct dw_apb_clock_event_device *dw_apb_clockevent_init (int cpu,const char *name,unsigned rating, -void __iomem *base,int irq,unsigned long freq); +void __iomem *base,int irq,unsigned long freq, +int quirks);struct dw_apb_clocksource *dw_apb_clocksource_init (unsigned rating,const char *name,void __iomem *base, -unsigned long freq); +unsigned long freq,int quirks);void dw_apb_clocksource_register (struct dw_apb_clocksource *dw_cs);void dw_apb_clocksource_start (struct dw_apb_clocksource *dw_cs);cycle_t dw_apb_clocksource_read (struct dw_apb_clocksource *dw_cs);
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerPATCH 2/9 clocksource: dw_apb_timer: flexible register addresses
In reply to Heiko StübnerThere exists variants of the apb-timer that use slightly different register positions. To accomodate this, add elements to the timer struct to hold the actual register offsets.
--- drivers/clocksource/dw_apb_timer.c | 83 ++++++++++++++++++++++-------------- include/linux/dw_apb_timer.h | 5 +++ 2 files changed, 56 insertions(+), 32 deletions(-)
drivers/clocksource/dw_apb_timer.c +51 -32
@@ -49,6 +49,15 @@ clocksource_to_dw_apb_clocksource(struct clocksource *cs) return container_of(cs, struct dw_apb_clocksource, cs); } +static void apbt_init_regs (struct dw_apb_timer *timer,int quirks) +{ + timer->reg_load_count =APBTMR_N_LOAD_COUNT ; + timer->reg_current_value =APBTMR_N_CURRENT_VALUE ; + timer->reg_control =APBTMR_N_CONTROL ; + timer->reg_eoi =APBTMR_N_EOI ; + timer->reg_int_status =APBTMR_N_INT_STATUS ; +} +static unsigned long apbt_readl (struct dw_apb_timer *timer,unsigned long offs) {return readl (timer->base + offs);
@@ -62,10 +71,10 @@ static void apbt_writel(struct dw_apb_timer *timer, unsigned long val,static void apbt_disable_int (struct dw_apb_timer *timer) { -unsigned long ctrl =apbt_readl (timer,APBTMR_N_CONTROL ); +unsigned long ctrl =apbt_readl (timer, timer->reg_control); ctrl |=APBTMR_CONTROL_INT ; -apbt_writel (timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control); }/**
@@ -81,7 +90,7 @@ void dw_apb_clockevent_pause(struct dw_apb_clock_event_device *dw_ced)static void apbt_eoi (struct dw_apb_timer *timer) { -apbt_readl (timer,APBTMR_N_EOI ); +apbt_readl (timer, timer->reg_eoi); }static irqreturn_t dw_apb_clockevent_irq (int irq,void *data)
@@ -103,11 +112,11 @@ static irqreturn_t dw_apb_clockevent_irq(int irq, void *data)static void apbt_enable_int (struct dw_apb_timer *timer) { -unsigned long ctrl =apbt_readl (timer,APBTMR_N_CONTROL ); +unsigned long ctrl =apbt_readl (timer, timer->reg_control);/* clear pending intr */ -apbt_readl (timer,APBTMR_N_EOI ); +apbt_readl (timer, timer->reg_eoi); ctrl &= ~APBTMR_CONTROL_INT ; -apbt_writel (timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control); } static void apbt_set_mode(enum clock_event_mode mode,
@@ -116,31 +125,32 @@ static void apbt_set_mode(enum clock_event_mode mode,unsigned long ctrl;unsigned long period;struct dw_apb_clock_event_device *dw_ced =ced_to_dw_apb_ced (evt); +struct dw_apb_timer *timer = &dw_ced->timer;pr_debug ("%s CPU %d mode=%d\n" , __func__,first_cpu (*evt->cpumask), mode);switch (mode) {case CLOCK_EVT_MODE_PERIODIC : - period =DIV_ROUND_UP (dw_ced->timer.freq,HZ ); - ctrl =apbt_readl (&dw_ced->timer,APBTMR_N_CONTROL ); + period =DIV_ROUND_UP (timer->freq,HZ ); + ctrl =apbt_readl (timer, timer->reg_control); ctrl |=APBTMR_CONTROL_MODE_PERIODIC ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);/* * DW APB p. 46, have to disable timer before load counter, * may cause sync problem. */ ctrl &= ~APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);udelay (1 );pr_debug ("Setting clock period %lu for HZ %d\n" , period,HZ ); -apbt_writel (&dw_ced->timer, period,APBTMR_N_LOAD_COUNT ); +apbt_writel (timer, period, timer->reg_load_count); ctrl |=APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);break ;case CLOCK_EVT_MODE_ONESHOT : - ctrl =apbt_readl (&dw_ced->timer,APBTMR_N_CONTROL ); + ctrl =apbt_readl (timer, timer->reg_control);/* * set free running mode, this mode will let timer reload max * timeout which will give time (3min on 25MHz clock) to rearm
@@ -149,29 +159,29 @@ static void apbt_set_mode(enum clock_event_mode mode, ctrl &= ~APBTMR_CONTROL_ENABLE ; ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);/* write again to set free running mode */ -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);/* * DW APB p. 46, load counter with all 1s before starting free * running mode. */ -apbt_writel (&dw_ced->timer, ~0 ,APBTMR_N_LOAD_COUNT ); +apbt_writel (timer, ~0 , timer->reg_load_count); ctrl &= ~APBTMR_CONTROL_INT ; ctrl |=APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);break ;case CLOCK_EVT_MODE_UNUSED :case CLOCK_EVT_MODE_SHUTDOWN : - ctrl =apbt_readl (&dw_ced->timer,APBTMR_N_CONTROL ); + ctrl =apbt_readl (timer, timer->reg_control); ctrl &= ~APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);break ;case CLOCK_EVT_MODE_RESUME : -apbt_enable_int (&dw_ced->timer); +apbt_enable_int (timer);break ; } }
@@ -181,15 +191,16 @@ static int apbt_next_event(unsigned long delta, {unsigned long ctrl;struct dw_apb_clock_event_device *dw_ced =ced_to_dw_apb_ced (evt); +struct dw_apb_timer *timer = &dw_ced->timer;/* Disable timer */ - ctrl =apbt_readl (&dw_ced->timer,APBTMR_N_CONTROL ); + ctrl =apbt_readl (timer, timer->reg_control); ctrl &= ~APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);/* write new count */ -apbt_writel (&dw_ced->timer, delta,APBTMR_N_LOAD_COUNT ); +apbt_writel (timer, delta, timer->reg_load_count); ctrl |=APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_ced->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);return 0 ; }
@@ -227,6 +238,7 @@ dw_apb_clockevent_init(int cpu, const char *name, unsigned rating, dw_ced->timer.irq = irq; dw_ced->timer.freq = freq; dw_ced->timer.quirks = quirks; +apbt_init_regs (&dw_ced->timer, quirks);clockevents_calc_mult_shift (&dw_ced->ced, freq,APBT_MIN_PERIOD ); dw_ced->ced.max_delta_ns = clockevent_delta2ns(0x7fffffff,
@@ -286,9 +298,11 @@ void dw_apb_clockevent_stop(struct dw_apb_clock_event_device *dw_ced)*/ void dw_apb_clockevent_register (struct dw_apb_clock_event_device *dw_ced) { -apbt_writel (&dw_ced->timer,0 ,APBTMR_N_CONTROL ); +struct dw_apb_timer *timer = &dw_ced->timer; + +apbt_writel (timer,0 , timer->reg_control);clockevents_register_device (&dw_ced->ced); -apbt_enable_int (&dw_ced->timer); +apbt_enable_int (timer); }/**
@@ -301,19 +315,20 @@ void dw_apb_clockevent_register(struct dw_apb_clock_event_device *dw_ced)*/ void dw_apb_clocksource_start (struct dw_apb_clocksource *dw_cs) { +struct dw_apb_timer *timer = &dw_cs->timer;/* * start count down from 0xffff_ffff. this is done by toggling the * enable bit then load initial load count to ~0. */ -unsigned long ctrl =apbt_readl (&dw_cs->timer,APBTMR_N_CONTROL ); +unsigned long ctrl =apbt_readl (timer, timer->reg_control); ctrl &= ~APBTMR_CONTROL_ENABLE ; -apbt_writel (&dw_cs->timer, ctrl,APBTMR_N_CONTROL ); -apbt_writel (&dw_cs->timer, ~0 ,APBTMR_N_LOAD_COUNT ); +apbt_writel (timer, ctrl, timer->reg_control); +apbt_writel (timer, ~0 , timer->reg_load_count);/* enable, mask interrupt */ ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; ctrl |= (APBTMR_CONTROL_ENABLE |APBTMR_CONTROL_INT ); -apbt_writel (&dw_cs->timer, ctrl,APBTMR_N_CONTROL ); +apbt_writel (timer, ctrl, timer->reg_control);/* read it once to get cached counter value initialized */ dw_apb_clocksource_read (dw_cs); }
@@ -323,8 +338,9 @@ static cycle_t __apbt_read_clocksource(struct clocksource *cs)unsigned long current_count;struct dw_apb_clocksource *dw_cs =clocksource_to_dw_apb_clocksource (cs); +struct dw_apb_timer *timer = &dw_cs->timer; - current_count =apbt_readl (&dw_cs->timer,APBTMR_N_CURRENT_VALUE ); + current_count =apbt_readl (timer, timer->reg_current_value);return (cycle_t )~current_count; }
@@ -361,6 +377,8 @@ dw_apb_clocksource_init(unsigned rating, const char *name, void __iomem *base, dw_cs->timer.base = base; dw_cs->timer.freq = freq; dw_cs->timer.quirks = quirks; +apbt_init_regs (&dw_cs->timer, quirks); + dw_cs->cs.name = name; dw_cs->cs.rating = rating; dw_cs->cs.read = __apbt_read_clocksource;
@@ -388,7 +406,8 @@ void dw_apb_clocksource_register(struct dw_apb_clocksource *dw_cs)*/ cycle_t dw_apb_clocksource_read (struct dw_apb_clocksource *dw_cs) { -return (cycle_t )~apbt_readl (&dw_cs->timer,APBTMR_N_CURRENT_VALUE ); +struct dw_apb_timer *timer = &dw_cs->timer; +return (cycle_t )~apbt_readl (timer, timer->reg_current_value); }/**
include/linux/dw_apb_timer.h +5 -0
@@ -24,6 +24,11 @@ struct dw_apb_timer {unsigned long freq;int irq;int quirks; +u8 reg_load_count; +u8 reg_current_value; +u8 reg_control; +u8 reg_eoi; +u8 reg_int_status; };struct dw_apb_clock_event_device {
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerPATCH 3/9 clocksource: dw_apb_timer: quirk for variants with 64bit counter
In reply to Heiko StübnerThis adds a quirk for IP variants containing two load_count and value registers that are used to provide 64bit accuracy on 32bit systems.
The added accuracy is currently not used, the driver is only adapted to handle the different register layout and make it work on affected devices.
--- drivers/clocksource/dw_apb_timer.c | 27 +++++++++++++++++++++++++++ include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 33 insertions(+)
drivers/clocksource/dw_apb_timer.c +27 -0
@@ -56,6 +56,17 @@ static void apbt_init_regs(struct dw_apb_timer *timer, int quirks) timer->reg_control =APBTMR_N_CONTROL ; timer->reg_eoi =APBTMR_N_EOI ; timer->reg_int_status =APBTMR_N_INT_STATUS ; + +/* +* On variants with 64bit counters some registers are +* moved further down. +*/ +if (quirks &APBTMR_QUIRK_64BIT_COUNTER ) { + timer->reg_current_value +=0x4 ; + timer->reg_control +=0x8 ; + timer->reg_eoi +=0x8 ; + timer->reg_int_status +=0x8 ; + } }static unsigned long apbt_readl (struct dw_apb_timer *timer,unsigned long offs)
@@ -145,6 +156,10 @@ static void apbt_set_mode(enum clock_event_mode mode,udelay (1 );pr_debug ("Setting clock period %lu for HZ %d\n" , period,HZ );apbt_writel (timer, period, timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); + ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);break ;
@@ -168,6 +183,10 @@ static void apbt_set_mode(enum clock_event_mode mode,* running mode. */ apbt_writel (timer, ~0 , timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); + ctrl &= ~APBTMR_CONTROL_INT ; ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);
@@ -199,6 +218,10 @@ static int apbt_next_event(unsigned long delta,apbt_writel (timer, ctrl, timer->reg_control);/* write new count */ apbt_writel (timer, delta, timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); + ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);
@@ -325,6 +348,10 @@ void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) ctrl &= ~APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);apbt_writel (timer, ~0 , timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +/* enable, mask interrupt */ ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; ctrl |= (APBTMR_CONTROL_ENABLE |APBTMR_CONTROL_INT );
include/linux/dw_apb_timer.h +6 -0
@@ -19,6 +19,12 @@#define APBTMRS_REG_SIZE 0x14 +/* The IP uses two registers for count and values, to provide 64bit accuracy +* on 32bit platforms. The additional registers move the following registers +* down by 0x8 byte, as both the count and value registers are duplicated. +*/ +#define APBTMR_QUIRK_64BIT_COUNTER BIT(0) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Signature · 1 line
1.7.10.4
Re: [PATCH 3/9] clocksource: dw_apb_timer: quirk for variants with 64bit counter
Quoted 22 lines · Heiko St?bner
This adds a quirk for IP variants containing two
load_countand value registers that are used to provide 64bit accuracy on 32bit systems.The added accuracy is currently not used, the driver is only adapted to handle the different register layout and make it work on affected devices.
Signed-off-by Heiko Stuebner <[email protected]>--- drivers/clocksource/dw_apb_timer.c | 27 +++++++++++++++++++++++++++ include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 33 insertions(+)drivers/clocksource/dw_apb_timer.c +2
-0@@ -56,6 +56,17 @@ static void apbt_init_regs(struct dw_apb_timer *timer, int quirks) timer->reg_control =APBTMR_N_CONTROL ; timer->reg_eoi =APBTMR_N_EOI ; timer->reg_int_status =APBTMR_N_INT_STATUS ; + +/*
drivers/clocksource/dw_apb_timer.c static void apbt_init_regs(struct dw_apb_timer *timer, int quirks) · line 70
Quoted 10 lines
drivers/clocksource/dw_apb_timer.c +9
-0+* On variants with 64bit counters some registers are +* moved further down. +*/ +if (quirks &APBTMR_QUIRK_64BIT_COUNTER ) { + timer->reg_current_value +=0x4 ; + timer->reg_control +=0x8 ; + timer->reg_eoi +=0x8 ; + timer->reg_int_status +=0x8 ; + } }
Oh, no. this is not how we handle these things.
1) We want proper constants for this 64bit IP block
2) This is not a quirk, it's a property of that particular IP block
You already made the register offsets a part of the timer structure, so why don't you supply a proper structure filled with that values to the init function?
That's what we do all over the place. Either we instantiate those structs at compile time or runtime fed by device tree or any other configuration mechanism.
drivers/clocksource/dw_apb_timer.c static void apbt_set_mode(enum clock_event_mode mode, · line 162
Quoted 9 lines
drivers/clocksource/dw_apb_timer.c +4
-0static unsigned long apbt_readl (struct dw_apb_timer *timer,unsigned long offs)@@ -145,6 +156,10 @@ static void apbt_set_mode(enum clock_event_mode mode,udelay (1 );pr_debug ("Setting clock period %lu for HZ %d\n" , period,HZ );apbt_writel (timer, period, timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +
No. We are not adding such conditional constructs when we can deal with them just by providing a proper set of function pointers. And definitely not with hardcoded magic 0x4 constants involved.
timer->load_count(timer, value);
Provide a 32 bit and a 64 bit version of that function and be done with it.
drivers/clocksource/dw_apb_timer.c static void apbt_set_mode(enum clock_event_mode mode, · line 189
Quoted 11 lines
drivers/clocksource/dw_apb_timer.c +4
-0ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);break ;@@ -168,6 +183,10 @@ static void apbt_set_mode(enum clock_event_mode mode,* running mode. */ apbt_writel (timer, ~0 , timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +
Makes this copy go away.
drivers/clocksource/dw_apb_timer.c static int apbt_next_event(unsigned long delta, · line 224
Quoted 11 lines
drivers/clocksource/dw_apb_timer.c +4
-0ctrl &= ~APBTMR_CONTROL_INT ; ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);@@ -199,6 +218,10 @@ static int apbt_next_event(unsigned long delta,apbt_writel (timer, ctrl, timer->reg_control);/* write new count */ apbt_writel (timer, delta, timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +
And this one as well.
drivers/clocksource/dw_apb_timer.c void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) · line 354
Quoted 11 lines
drivers/clocksource/dw_apb_timer.c +4
-0ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);@@ -325,6 +348,10 @@ void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) ctrl &= ~APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);apbt_writel (timer, ~0 , timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +
Copy and paste is a conveniant thing, right? It just should have a pop up window assigned which asks at the second instance of copying the same thing whether you really thought about it.
Thanks,
tglx
Re: [PATCH 3/9] clocksource: dw_apb_timer: quirk for variants with 64bit counter
Hi Thomas,
sorry but it is my first release to the kernel and I was trained to take as less as possible influence on existing code and reuse as much as possible existing code.
Sometimes just two rules are to simple...
Am 06.07.2013 01:45, schrieb Thomas Gleixner:
Quoted 37 lines · Heiko Stübner
Quoted 23 lines
This adds a quirk for IP variants containing two
load_countand value registers that are used to provide 64bit accuracy on 32bit systems.The added accuracy is currently not used, the driver is only adapted to handle the different register layout and make it work on affected devices.
Signed-off-by Heiko Stuebner <[email protected]>--- drivers/clocksource/dw_apb_timer.c | 27 +++++++++++++++++++++++++++ include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 33 insertions(+)drivers/clocksource/dw_apb_timer.c +2
-0@@ -56,6 +56,17 @@ static void apbt_init_regs(struct dw_apb_timer *timer, int quirks) timer->reg_control =APBTMR_N_CONTROL ; timer->reg_eoi =APBTMR_N_EOI ; timer->reg_int_status =APBTMR_N_INT_STATUS ; + +/* drivers/clocksource/dw_apb_timer.c static void apbt_init_regs(struct dw_apb_timer *timer, int quirks) · line 70
Quoted 10 lines
drivers/clocksource/dw_apb_timer.c +9
-0+* On variants with 64bit counters some registers are +* moved further down. +*/ +if (quirks &APBTMR_QUIRK_64BIT_COUNTER ) { + timer->reg_current_value +=0x4 ; + timer->reg_control +=0x8 ; + timer->reg_eoi +=0x8 ; + timer->reg_int_status +=0x8 ; + } }Oh, no. this is not how we handle these things.
Quoted 1 line
1) We want proper constants for this 64bit IP block
ACK
Quoted 2 lines
2) This is not a quirk, it's a property of that particular IP block
Ok, noted and will be reworked.
Quoted 9 lines
You already made the register offsets a part of the timer structure, so why don't you supply a proper structure filled with that values to the init function?
That's what we do all over the place. Either we instantiate those structs at compile time or runtime fed by device tree or any other configuration mechanism.
Lesson learned, will be reworked. But taking the other comments and remarks into account this leads to an integration into a single new patch inheriting this new driver path.
Quoted 14 lines
drivers/clocksource/dw_apb_timer.c static void apbt_set_mode(enum clock_event_mode mode, · line 162
Quoted 9 lines
drivers/clocksource/dw_apb_timer.c +4
-0static unsigned long apbt_readl (struct dw_apb_timer *timer,unsigned long offs)@@ -145,6 +156,10 @@ static void apbt_set_mode(enum clock_event_mode mode,udelay (1 );pr_debug ("Setting clock period %lu for HZ %d\n" , period,HZ );apbt_writel (timer, period, timer->reg_load_count); + +if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER ) +apbt_writel (timer,0 , timer->reg_load_count +0x4 ); +No. We are not adding such conditional constructs when we can deal with them just by providing a proper set of function pointers. And definitely not with hardcoded magic
0x4constants involved.
Quoted 4 lines
timer->load_count(timer, value);Provide a 32 bit and a 64 bit version of that function and be done with it.
For the first version, 64bit will not be supported cause of lack of information and testing. But is it an option to control the load of functions by dtsi according this way:
dw-apb-timer-osc: will load standard function to load timer
rk3188-dw-apb-timer-osc: will load 32bit variant of 64bit registers
rk3188-dw-apb-timer64-osc: will load 64bit variant of 64bit registers
Quoted 5 lines
Copy and paste is a conveniant thing, right? It just should have a pop up window assigned which asks at the second instance of copying the same thing whether you really thought about it.
We thought about and tested a lot. But the problem is that if you find a bug in one copy of it, you (or anyone else inspecting the code) need to find all the other copies. In such manor a c'n'p solution is always bad.
Regards,
Ulrich
Heiko Stübner
maintainerPATCH 4/9 clocksource: dw_apb_timer: use the eoi callback to clear pending interrupts
In reply to Heiko StübnerSome timer variants have different mechanisms to clear a pending timer interrupt. Therefore don't hardcode the reading of the eoi register to clear them, but instead use the already existing eoi callback for this.
--- drivers/clocksource/dw_apb_timer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)
drivers/clocksource/dw_apb_timer.c +7 -4
@@ -121,11 +121,14 @@ static irqreturn_t dw_apb_clockevent_irq(int irq, void *data)return IRQ_HANDLED ; } -static void apbt_enable_int (struct dw_apb_timer *timer) +static void apbt_enable_int (struct dw_apb_clock_event_device *dw_ced) { +struct dw_apb_timer *timer = &dw_ced->timer;unsigned long ctrl =apbt_readl (timer, timer->reg_control); +/* clear pending intr */ -apbt_readl (timer, timer->reg_eoi); +if (dw_ced->eoi) + dw_ced->eoi (timer); ctrl &= ~APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }
@@ -200,7 +203,7 @@ static void apbt_set_mode(enum clock_event_mode mode,break ;case CLOCK_EVT_MODE_RESUME : -apbt_enable_int (timer); +apbt_enable_int (dw_ced);break ; } }
@@ -325,7 +328,7 @@ void dw_apb_clockevent_register(struct dw_apb_clock_event_device *dw_ced)apbt_writel (timer,0 , timer->reg_control);clockevents_register_device (&dw_ced->ced); -apbt_enable_int (timer); +apbt_enable_int (dw_ced); }/**
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerRe: [PATCH 4/9] clocksource: dw_apb_timer: use the eoi callback to clear pending interrupts
this patch should have had a
From: Ulrich Prinz <[email protected]>
sorry for the mistake
Am Samstag, 6. Juli 2013, 00:53:36 schrieb Heiko St?bner:
Quoted 49 lines
Some timer variants have different mechanisms to clear a pending timer interrupt. Therefore don't hardcode the reading of the eoi register to clear them, but instead use the already existing eoi callback for this.
Signed-off-by Ulrich Prinz <[email protected]>--- drivers/clocksource/dw_apb_timer.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-)(quoted diff) +0
-0
b/drivers/clocksource/dw_apb_timer.cindex bd45351..5f80a30 100644drivers/clocksource/dw_apb_timer.c +7
-4@@ -121,11 +121,14 @@ static irqreturn_t dw_apb_clockevent_irq(int irq, void *data) return IRQ_HANDLED; } -static void apbt_enable_int (struct dw_apb_timer *timer) +static void apbt_enable_int (struct dw_apb_clock_event_device *dw_ced) { +struct dw_apb_timer *timer = &dw_ced->timer;unsigned long ctrl =apbt_readl (timer, timer->reg_control); +/* clear pending intr */ -apbt_readl (timer, timer->reg_eoi); +if (dw_ced->eoi) + dw_ced->eoi (timer); ctrl &= ~APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }@@ -200,7 +203,7 @@ static void apbt_set_mode(enum clock_event_mode mode,break ;case CLOCK_EVT_MODE_RESUME : -apbt_enable_int (timer); +apbt_enable_int (dw_ced);break ; } }@@ -325,7 +328,7 @@ void dw_apb_clockevent_register(struct dw_apb_clock_event_device *dw_ced) apbt_writel(timer, 0, timer->reg_control);clockevents_register_device (&dw_ced->ced); -apbt_enable_int (timer); +apbt_enable_int (dw_ced); }/**
Heiko Stübner
maintainerPATCH 5/9 clocksource: dw_apb_timer: quirk for variants without EOI register
In reply to Heiko StübnerSome variants of the dw_apb_timer don't have an eoi register but instead expect a one to be written to the int_status register at eoi time.
--- drivers/clocksource/dw_apb_timer.c | 10 +++++++++- include/linux/dw_apb_timer.h | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-)
drivers/clocksource/dw_apb_timer.c +9 -1
@@ -104,6 +104,11 @@ static void apbt_eoi(struct dw_apb_timer *timer)apbt_readl (timer, timer->reg_eoi); } +static void apbt_eoi_int_status (struct dw_apb_timer *timer) +{ +apbt_writel (timer,1 , timer->reg_int_status); +} +static irqreturn_t dw_apb_clockevent_irq (int irq,void *data) {struct clock_event_device *evt = data;
@@ -286,7 +291,10 @@ dw_apb_clockevent_init(int cpu, const char *name, unsigned rating,IRQF_NOBALANCING |IRQF_DISABLED ; - dw_ced->eoi = apbt_eoi; +if (quirks &APBTMR_QUIRK_NO_EOI ) + dw_ced->eoi = apbt_eoi_int_status; +else + dw_ced->eoi = apbt_eoi; err =setup_irq (irq, &dw_ced->irqaction);if (err) {pr_err ("failed to request timer irq\n" );
include/linux/dw_apb_timer.h +5 -0
@@ -25,6 +25,11 @@*/ #define APBTMR_QUIRK_64BIT_COUNTER BIT(0) +/* The IP does not provide a end-of-interrupt register to clear pending +* interrupts, but requires to write a 1 to the interrupt-status register. +*/ +#define APBTMR_QUIRK_NO_EOI BIT(1) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerRe: [PATCH 5/9] clocksource: dw_apb_timer: quirk for variants without EOI register
this patch should have had a
From: Ulrich Prinz <[email protected]>
sorry for the mistake
Am Samstag, 6. Juli 2013, 00:54:07 schrieb Heiko St?bner:
Quoted 53 lines
Some variants of the
dw_apb_timerdon't have an eoi register but instead expect a one to be written to theint_statusregister at eoi time.Signed-off-by Ulrich Prinz <[email protected]>--- drivers/clocksource/dw_apb_timer.c | 10 +++++++++- include/linux/dw_apb_timer.h | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-)(quoted diff) +0
-0
b/drivers/clocksource/dw_apb_timer.cindex 5f80a30..23cd7c6 100644drivers/clocksource/dw_apb_timer.c +5
-0@@ -104,6 +104,11 @@ static void apbt_eoi(struct dw_apb_timer *timer)apbt_readl (timer, timer->reg_eoi); } +static void apbt_eoi_int_status (struct dw_apb_timer *timer) +{ +apbt_writel (timer,1 , timer->reg_int_status); +} +static irqreturn_t dw_apb_clockevent_irq (int irq,void *data) {struct clock_event_device *evt = data;@@ -286,7 +291,10 @@ dw_apb_clockevent_init(int cpu, const char *name,unsigned rating,
IRQF_NOBALANCING|drivers/clocksource/dw_apb_timer.c +4
-1IRQF_DISABLED ; - dw_ced->eoi = apbt_eoi; +if (quirks &APBTMR_QUIRK_NO_EOI ) + dw_ced->eoi = apbt_eoi_int_status; +else + dw_ced->eoi = apbt_eoi; err =setup_irq (irq, &dw_ced->irqaction);if (err) {pr_err ("failed to request timer irq\n" );include/linux/dw_apb_timer.h +5
-0@@ -25,6 +25,11 @@*/ #define APBTMR_QUIRK_64BIT_COUNTER BIT(0) +/* The IP does not provide a end-of-interrupt register to clear pending +* interrupts, but requires to write a 1 to the interrupt-status register. +*/ +#define APBTMR_QUIRK_NO_EOI BIT(1) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Re: [PATCH 5/9] clocksource: dw_apb_timer: quirk for variants without EOI register
In reply to Heiko Stübnerdrivers/clocksource/dw_apb_timer.c dw_apb_clockevent_init(int cpu, const char *name, unsigned rating, · line 297
Quoted 5 lines · Heiko St?bner
drivers/clocksource/dw_apb_timer.c +4
-1- dw_ced->eoi = apbt_eoi; +if (quirks &APBTMR_QUIRK_NO_EOI ) + dw_ced->eoi = apbt_eoi_int_status; +else + dw_ced->eoi = apbt_eoi;
No again. This has nothing to do with quirks. We use quirks for workarounds and not for refactoring of code.
Thanks,
tglx
Heiko Stübner
maintainerPATCH 6/9 clocksource: dw_apb_timer: quirk for inverted int mask
In reply to Heiko StübnerSome timer variants use an inverted setting to mask the timer interrupt. Therefore add a quirk to handle these variants.
--- drivers/clocksource/dw_apb_timer.c | 23 ++++++++++++++++++----- include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 24 insertions(+), 5 deletions(-)
drivers/clocksource/dw_apb_timer.c +18 -5
@@ -84,7 +84,10 @@ static void apbt_disable_int(struct dw_apb_timer *timer) {unsigned long ctrl =apbt_readl (timer, timer->reg_control); - ctrl |=APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl &= ~APBTMR_CONTROL_INT ; +else + ctrl |=APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }
@@ -134,7 +137,10 @@ static void apbt_enable_int(struct dw_apb_clock_event_device *dw_ced)/* clear pending intr */ if (dw_ced->eoi) dw_ced->eoi (timer); - ctrl &= ~APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl |=APBTMR_CONTROL_INT ; +else + ctrl &= ~APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }
@@ -195,7 +201,10 @@ static void apbt_set_mode(enum clock_event_mode mode,if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER )apbt_writel (timer,0 , timer->reg_load_count +0x4 ); - ctrl &= ~APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl |=APBTMR_CONTROL_INT ; +else + ctrl &= ~APBTMR_CONTROL_INT ; ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);break ;
@@ -363,9 +372,13 @@ void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs)if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER )apbt_writel (timer,0 , timer->reg_load_count +0x4 ); -/* enable, mask interrupt */ +/* set periodic, mask interrupt, enable timer */ ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; - ctrl |= (APBTMR_CONTROL_ENABLE |APBTMR_CONTROL_INT ); +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl &= ~APBTMR_CONTROL_INT ; +else + ctrl |=APBTMR_CONTROL_INT ; + ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);/* read it once to get cached counter value initialized */ dw_apb_clocksource_read (dw_cs);
include/linux/dw_apb_timer.h +6 -0
@@ -30,6 +30,12 @@*/ #define APBTMR_QUIRK_NO_EOI BIT(1) +/* The IP uses an inverted interrupt-mask bit. +* Instead of activating interrupts clearing a maks bit, it needs an enable +* bit to be set 1. +*/ +#define APBTMR_QUIRK_INVERSE_INTMASK BIT(2) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerRe: [PATCH 6/9] clocksource: dw_apb_timer: quirk for inverted int mask
this patch should have had a
From: Ulrich Prinz <[email protected]>
sorry for the mistake
Am Samstag, 6. Juli 2013, 00:54:35 schrieb Heiko St?bner:
Quoted 82 lines
Some timer variants use an inverted setting to mask the timer interrupt. Therefore add a quirk to handle these variants.
Signed-off-by Ulrich Prinz <[email protected]>--- drivers/clocksource/dw_apb_timer.c | 23 ++++++++++++++++++----- include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 24 insertions(+), 5 deletions(-)(quoted diff) +0
-0
b/drivers/clocksource/dw_apb_timer.cindex 23cd7c6..7705d13 100644drivers/clocksource/dw_apb_timer.c +4
-1@@ -84,7 +84,10 @@ static void apbt_disable_int(struct dw_apb_timer *timer) {unsigned long ctrl =apbt_readl (timer, timer->reg_control); - ctrl |=APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl &= ~APBTMR_CONTROL_INT ; +else + ctrl |=APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }@@ -134,7 +137,10 @@ static void apbt_enable_int(struct
dw_apb_clock_event_device*dw_ced) /* clear pending intr */drivers/clocksource/dw_apb_timer.c +14
-4if (dw_ced->eoi) dw_ced->eoi (timer); - ctrl &= ~APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl |=APBTMR_CONTROL_INT ; +else + ctrl &= ~APBTMR_CONTROL_INT ;apbt_writel (timer, ctrl, timer->reg_control); }@@ -195,7 +201,10 @@ static void apbt_set_mode(enum clock_event_mode mode,if (timer->quirks &APBTMR_QUIRK_64BIT_COUNTER )apbt_writel (timer,0 , timer->reg_load_count +0x4 ); - ctrl &= ~APBTMR_CONTROL_INT ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl |=APBTMR_CONTROL_INT ; +else + ctrl &= ~APBTMR_CONTROL_INT ; ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);break ;@@ -363,9 +372,13 @@ void dw_apb_clocksource_start(struct dw_apb_clocksource *dw_cs) if (timer->quirks & APBTMR_QUIRK_64BIT_COUNTER)apbt_writel (timer,0 , timer->reg_load_count +0x4 ); -/* enable, mask interrupt */ +/* set periodic, mask interrupt, enable timer */ ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; - ctrl |= (APBTMR_CONTROL_ENABLE |APBTMR_CONTROL_INT ); +if (timer->quirks &APBTMR_QUIRK_INVERSE_INTMASK ) + ctrl &= ~APBTMR_CONTROL_INT ; +else + ctrl |=APBTMR_CONTROL_INT ; + ctrl |=APBTMR_CONTROL_ENABLE ;apbt_writel (timer, ctrl, timer->reg_control);/* read it once to get cached counter value initialized */ dw_apb_clocksource_read (dw_cs);include/linux/dw_apb_timer.h +2
-0@@ -30,6 +30,12 @@*/ #define APBTMR_QUIRK_NO_EOI BIT(1) +/* The IP uses an inverted interrupt-mask bit. +* Instead of activating interrupts clearing a maks bit, it needs an enable + * bit to be set 1.
+ */include/linux/dw_apb_timer.h +2
-0+#define APBTMR_QUIRK_INVERSE_INTMASK BIT(2) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Re: [PATCH 6/9] clocksource: dw_apb_timer: quirk for inverted int mask
In reply to Heiko StübnerQuoted 2 lines · Heiko St?bner
Some timer variants use an inverted setting to mask the timer interrupt. Therefore add a quirk to handle these variants.
And by that add even more pointless conditionals into critical code pathes.
Heiko Stübner
maintainerPATCH 7/9 clocksource: dw_apb_timer: quirk for inverted timer mode setting
In reply to Heiko StübnerFrom: Ulrich Prinz <[email protected]>
Some variants of SOCs using dw_apb_timer have inverted logic for the bit that sets one-shot / periodic mode or free running timer. This commit adds the new APBTMR_QUIRK_INVERSE_PERIODIC.
--- drivers/clocksource/dw_apb_timer.c | 11 +++++++++-- include/linux/dw_apb_timer.h | 6 ++++++ 2 files changed, 15 insertions(+), 2 deletions(-)
(quoted diff) +0 -0
b/drivers/clocksource/dw_apb_timer.c
index 7705d13..a2e8306 100644
drivers/clocksource/dw_apb_timer.c +9 -2
@@ -159,7 +159,11 @@ static void apbt_set_mode(enum clock_event_mode mode,case CLOCK_EVT_MODE_PERIODIC : period =DIV_ROUND_UP (timer->freq,HZ ); ctrl =apbt_readl (timer, timer->reg_control); - ctrl |=APBTMR_CONTROL_MODE_PERIODIC ; + +if (timer->quirks &APBTMR_QUIRK_INVERSE_PERIODIC ) + ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; +else + ctrl |=APBTMR_CONTROL_MODE_PERIODIC ;apbt_writel (timer, ctrl, timer->reg_control);/* * DW APB p. 46, have to disable timer before load counter,
@@ -186,7 +190,10 @@ static void apbt_set_mode(enum clock_event_mode mode,* the next event, therefore emulate the one-shot mode. */ ctrl &= ~APBTMR_CONTROL_ENABLE ; - ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ; +if (timer->quirks &APBTMR_QUIRK_INVERSE_PERIODIC ) + ctrl |=APBTMR_CONTROL_MODE_PERIODIC ; +else + ctrl &= ~APBTMR_CONTROL_MODE_PERIODIC ;apbt_writel (timer, ctrl, timer->reg_control);/* write again to set free running mode */
include/linux/dw_apb_timer.h +6 -0
@@ -36,6 +36,12 @@*/ #define APBTMR_QUIRK_INVERSE_INTMASK BIT(2) +/* The IP uses inverted logic for the bit setting periodic mode. +* Periodic means it times out after the period is over and is set to +* 1 in the original IP. This IP uses 1 for free running mode. +*/ +#define APBTMR_QUIRK_INVERSE_PERIODIC BIT(3) +struct dw_apb_timer {void __iomem *base;unsigned long freq;
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerPATCH 8/9 clocksource: dw_apb_timer_of: add quirk handling
In reply to Heiko Stübnertimer_get_base_and_rate now also can extract informations about present hardware-quirks from the devicetree node and transmit it to the clocksource / clockevent init function.
--- drivers/clocksource/dw_apb_timer_of.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-)
drivers/clocksource/dw_apb_timer_of.c +15 -6
@@ -26,7 +26,7 @@#include <asm/sched_clock.h> static void timer_get_base_and_rate (struct device_node *np, -void __iomem **base,u32 *rate) +void __iomem **base,u32 *rate,int *quirks) {struct clk *timer_clk;struct clk *pclk;
@@ -36,6 +36,8 @@ static void timer_get_base_and_rate(struct device_node *np,if (!*base)panic ("Unable to map regs for %s" , np->name); + *quirks =0 ; +/* * Not all implementations use a periphal clock, so don't panic * if it's not present
@@ -66,15 +68,16 @@ static void add_clockevent(struct device_node *event_timer)void __iomem *iobase;struct dw_apb_clock_event_device *ced;u32 irq, rate; +int quirks; irq =irq_of_parse_and_map (event_timer,0 );if (irq ==NO_IRQ )panic ("No IRQ for clock event timer" ); -timer_get_base_and_rate (event_timer, &iobase, &rate); +timer_get_base_and_rate (event_timer, &iobase, &rate, &quirks); ced =dw_apb_clockevent_init (0 , event_timer->name,300 , iobase, irq, - rate,0 ); + rate, quirks);if (!ced)panic ("Unable to initialise clockevent device" );
@@ -89,10 +92,12 @@ static void add_clocksource(struct device_node *source_timer)void __iomem *iobase;struct dw_apb_clocksource *cs;u32 rate; +int quirks; -timer_get_base_and_rate (source_timer, &iobase, &rate); +timer_get_base_and_rate (source_timer, &iobase, &rate, &quirks); - cs =dw_apb_clocksource_init (300 , source_timer->name, iobase, rate,0 ); + cs =dw_apb_clocksource_init (300 , source_timer->name, iobase, rate, + quirks);if (!cs)panic ("Unable to initialise clocksource device" );
@@ -106,6 +111,9 @@ static void add_clocksource(struct device_node *source_timer)*/ sched_io_base = iobase +0x04 ; sched_rate = rate; + +if (quirks &APBTMR_QUIRK_64BIT_COUNTER ) + sched_io_base +=0x04 ; }static u32 read_sched_clock (void )
@@ -122,11 +130,12 @@ static const struct of_device_id sptimer_ids[] __initconst = {static void init_sched_clock (void ) {struct device_node *sched_timer; +int quirks; sched_timer =of_find_matching_node (NULL , sptimer_ids);if (sched_timer) {timer_get_base_and_rate (sched_timer, &sched_io_base, - &sched_rate); + &sched_rate, &quirks);of_node_put (sched_timer); }
Signature · 1 line
1.7.10.4
Heiko Stübner
maintainerPATCH 9/9 clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
In reply to Heiko StübnerThe rk3188 uses a variant of the timer containing two registers for load_count and current_values.
--- .../bindings/arm/rockchip/rk3188-timer.txt | 20 ++++++++++++++++++++ drivers/clocksource/dw_apb_timer_of.c | 6 ++++++ 2 files changed, 26 insertions(+) create mode 100644 Documentation/devicetree/bindings/arm/rockchip/rk3188-timer.txt
Documentation/devicetree/bindings/arm/rockchip/rk3188-timer.txt new file +20 -0
@@ -0,0 +1,20 @@ +Rockchip rk3188 timer: +---------------------- + +The rk3188 SoCs contain a slightly modified dw-apb-timer. + +Required node properties: +- compatible value : = "rockchip,rk3188-dw-apb-timer-osc"; + +For the other properties see the generic documentation in +../../rtc/dw-apb.txt + +Example: + + timer3: timer at ffe00000 { + compatible = "rockchip,rk3188-dw-apb-timer-osc"; + interrupts = <0 170 4>; + reg = <0xffe00000 0x1000>; + clocks = <&timer_clk>, <&timer_pclk>; + clock-names = "timer", "pclk"; + };
drivers/clocksource/dw_apb_timer_of.c +6 -0
@@ -38,6 +38,11 @@ static void timer_get_base_and_rate(struct device_node *np, *quirks =0 ; +if (of_device_is_compatible (np,"rockchip,rk3188-dw-apb-timer-osc" )) + *quirks |=APBTMR_QUIRK_64BIT_COUNTER |APBTMR_QUIRK_NO_EOI | +APBTMR_QUIRK_INVERSE_INTMASK | +APBTMR_QUIRK_INVERSE_PERIODIC ; +/* * Not all implementations use a periphal clock, so don't panic * if it's not present
@@ -165,3 +170,4 @@ static void __init dw_apb_timer_init(struct device_node *timer) }CLOCKSOURCE_OF_DECLARE (pc3x2_timer,"picochip,pc3x2-timer" , dw_apb_timer_init);CLOCKSOURCE_OF_DECLARE (apb_timer,"snps,dw-apb-timer-osc" , dw_apb_timer_init); +CLOCKSOURCE_OF_DECLARE (rk3188_timer,"rockchip,rk3188-dw-apb-timer-osc" , dw_apb_timer_init);
Signature · 1 line
1.7.10.4
Re: [PATCH 9/9] clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
drivers/clocksource/dw_apb_timer_of.c static void timer_get_base_and_rate(struct device_node *np, · line 44
Quoted 4 lines · Heiko St?bner
drivers/clocksource/dw_apb_timer_of.c +4
-0+if (of_device_is_compatible (np,"rockchip,rk3188-dw-apb-timer-osc" )) + *quirks |=APBTMR_QUIRK_64BIT_COUNTER |APBTMR_QUIRK_NO_EOI | +APBTMR_QUIRK_INVERSE_INTMASK | +APBTMR_QUIRK_INVERSE_PERIODIC ;
Brilliant. Next time we add
drivers/clocksource/dw_apb_timer_of.c static void timer_get_base_and_rate(struct device_node *np,
Quoted 4 lines
drivers/clocksource/dw_apb_timer_of.c +4
-0+if (of_device_is_compatible (np,"rockchip,rk3188-dw-apb-timer-osc2" )) + *quirks |=APBTMR_QUIRK_64BIT_COUNTER |APBTMR_QUIRK_NO_EOI | +APBTMR_QUIRK_INVERSE_INTMASK | +APBTMR_QUIRK_INVERSE_PERIODIC |MORE_NONSENSE ;
Plus the extra conditionals all over the place
and a week later
Review
Quoted 2 lines
(quoted diff) +2
-0+ if (of_device_is_compatible(np, "rockchip,rk3188-dw-apb-timer-osc3")) + *quirks |= ALLNONSENSE;
This has nothing to do with QUIRKS at all. QUIRKS are our last resort when we cannot deal with the problem in a sane way.
In this case this is simply the wrong aproach.
We can deal with it in a sane way as I pointed out before and handle it as simple properties of the IP block. We have all mechanisms in place to handle such properties, device tree, platform data, static init structures. It's all there and we really do not need to plaster the code with random QUIRK conditionals.
Did you ever consider the runtime penalty of this? Probably not, otherwise you would have spent time on speeding up that code by caching frequently accessed registers instead of reading them back over and over for no reason.
Thanks,
tglx
Re: [PATCH 9/9] clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
Ok, ok...
I got the message. With modifying the existing driver to support more function pointers in its system struct and assigning them at the beginning, and using them on runtime, these quirks are obsolete.
Again, this is the first time I provide code to the kernel officially and I learned from others that I should try it by modifying not too much code if not needed.
Adding more function pointers to a system relevant structure, doubling the number of functions and such didn't look non-invasive to me.
But, I totally agree with your argumentation and I even wanted to do it in the way you explained in your replies. Just the courage was missing I guess :)
Regards,
Ulrich
Re: [PATCH 9/9] clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
Ulrich,
Quoted 3 lines · Ulrich Prinz
I got the message. With modifying the existing driver to support more function pointers in its system struct and assigning them at the beginning, and using them on runtime, these quirks are obsolete.
Correct.
Quoted 1 line
Again, this is the first time I provide code to the kernel officially
No problem. That's what code review is about. First post or not.
Quoted 5 lines
and I learned from others that I should try it by modifying not too much code if not needed.
Adding more function pointers to a system relevant structure, doubling the number of functions and such didn't look non-invasive to me.
Well, it always depends. If there is a single place to deal with some oddball hardware, a flag is often the simplest way to go.
If you have to add 10 conditionals in several functions then in a first step converting the existing implementation into function pointer calls and then in the next step providing new implementations for your hardware is most of the time simpler and cleaner.
Quoted 3 lines
But, I totally agree with your argumentation and I even wanted to do it in the way you explained in your replies. Just the courage was missing I guess :)
Gut feelings are often a better guidance than our self-doubting intellect. :)
But don't worry. I had to learn it the hard way as well and I still trip over from time to time.
Thanks,
tglx
Re: [PATCH 9/9] clocksource: dw_apb_timer: special variant for rockchip rk3188 timers
Hey guys,
according the dw_apb_timer I need your statement...
Am 06.07.2013 23:00, schrieb Thomas Gleixner:
Quoted 22 lines
Ulrich,
Quoted 3 lines · Ulrich Prinz
I got the message. With modifying the existing driver to support more function pointers in its system struct and assigning them at the beginning, and using them on runtime, these quirks are obsolete.
Correct.
Quoted 3 lines
Adding more function pointers to a system relevant structure, doubling the number of functions and such didn't look non-invasive to me.
Well, it always depends. If there is a single place to deal with some oddball hardware, a flag is often the simplest way to go.
If you have to add 10 conditionals in several functions then in a first step converting the existing implementation into function pointer calls and then in the next step providing new implementations for your hardware is most of the time simpler and cleaner.
Quoted 6 lines
Quoted 3 lines
But, I totally agree with your argumentation and I even wanted to do it in the way you explained in your replies. Just the courage was missing I guess :)
Gut feelings are often a better guidance than our self-doubting intellect. :)
You can look at my github repo for the new driver, rewriting the interface as discussed above:
https://github.com/Astralix/linux-rockchip/blob/wip/dw_timer64/drivers/clocksource/dw_apb_timer.c
Summary: It is a 90% rewrite of the complete dw_apb_timer.c and modifications of dw_apb_timer_of.c and dw_apb_timer.h and the related x86 drivers are needed.
And after that the 64bit functionality is still not given as the existing structures do not take it. But for now scheduler doesn't support 64bit either, I guess.
The drivers local struct stays, but instead of carrying pointers to registers it now consists of function pointers. This could be used to fit almost any kind of timer.
I would really appreciate your comments. Sure I need to clean up a bit, but we're still testing. But before investing more time into that code I need you comment if I should continue or split to a separate driver for these kind of timers.
Thanks for any comments
Regards
Ulrich