[PATCH] ARM: S5P: Add System Timer

Ben Dooks ben-linux at fluff.org
Thu May 6 02:05:52 EDT 2010


On Tue, Mar 30, 2010 at 11:29:45AM +0900, Kukjin Kim wrote:
> From: Jongpill Lee <boyko.lee at samsung.com>
> 
> This patch addes system timer for Samsung S5P series SoCs

You might be better off looking at doing a clocksource based implementation
of this to avoid the scaling, and allow better scheduling of interrupts.
 
> Signed-off-by: Jongpill Lee <boyko.lee at samsung.com>
> Signed-off-by: Kukjin Kim <kgene.kim at samsung.com>
> ---
>  arch/arm/mach-s5p6442/include/mach/tick.h      |    6 +
>  arch/arm/mach-s5p6442/mach-smdk6442.c          |    2 +-
>  arch/arm/mach-s5pv210/include/mach/tick.h      |    6 +
>  arch/arm/mach-s5pv210/mach-smdkv210.c          |    2 +-
>  arch/arm/plat-s5p/Kconfig                      |    7 +
>  arch/arm/plat-s5p/Makefile                     |    1 +
>  arch/arm/plat-s5p/include/plat/regs-systimer.h |   75 ++++++
>  arch/arm/plat-s5p/systimer-s5p.c               |  298 ++++++++++++++++++++++++
>  arch/arm/plat-samsung/include/plat/cpu.h       |    3 +
>  9 files changed, 398 insertions(+), 2 deletions(-)
>  create mode 100644 arch/arm/plat-s5p/include/plat/regs-systimer.h
>  create mode 100644 arch/arm/plat-s5p/systimer-s5p.c
> 
> diff --git a/arch/arm/mach-s5p6442/include/mach/tick.h b/arch/arm/mach-s5p6442/include/mach/tick.h
> index e1d4cab..1795b43 100644
> --- a/arch/arm/mach-s5p6442/include/mach/tick.h
> +++ b/arch/arm/mach-s5p6442/include/mach/tick.h
> @@ -21,6 +21,12 @@ static inline u32 s3c24xx_ostimer_pending(void)
>  	return pend & (1 << (IRQ_TIMER4_VIC - S5P_IRQ_VIC0(0)));
>  }
>  
> +static inline u32 s5p_ostimer_pending(void)
> +{
> +	u32 pend = __raw_readl(VA_VIC0 + VIC_RAW_STATUS);
> +	return pend & (1 << (IRQ_SYSTIMER - S5P_IRQ_VIC0(0)));
> +}
> +
>  #define TICK_MAX	(0xffffffff)
>  
>  #endif /* __ASM_ARCH_TICK_H */
> diff --git a/arch/arm/mach-s5p6442/mach-smdk6442.c b/arch/arm/mach-s5p6442/mach-smdk6442.c
> index 0d63371..825df9d 100644
> --- a/arch/arm/mach-s5p6442/mach-smdk6442.c
> +++ b/arch/arm/mach-s5p6442/mach-smdk6442.c
> @@ -87,5 +87,5 @@ MACHINE_START(SMDK6442, "SMDK6442")
>  	.init_irq	= s5p6442_init_irq,
>  	.map_io		= smdk6442_map_io,
>  	.init_machine	= smdk6442_machine_init,
> -	.timer		= &s3c24xx_timer,
> +	.timer		= &s5p_systimer,
>  MACHINE_END
> diff --git a/arch/arm/mach-s5pv210/include/mach/tick.h b/arch/arm/mach-s5pv210/include/mach/tick.h
> index 7993b36..9fc5a8d 100644
> --- a/arch/arm/mach-s5pv210/include/mach/tick.h
> +++ b/arch/arm/mach-s5pv210/include/mach/tick.h
> @@ -21,6 +21,12 @@ static inline u32 s3c24xx_ostimer_pending(void)
>  	return pend & (1 << (IRQ_TIMER4_VIC - S5P_IRQ_VIC0(0)));
>  }
>  
> +static inline u32 s5p_ostimer_pending(void)
> +{
> +	u32 pend = __raw_readl(VA_VIC0 + VIC_RAW_STATUS);
> +	return pend & (1 << (IRQ_SYSTIMER - S5P_IRQ_VIC0(0)));
> +}
> +
>  #define TICK_MAX	(0xffffffff)
>  
>  #endif /* __ASM_ARCH_TICK_H */
> diff --git a/arch/arm/mach-s5pv210/mach-smdkv210.c b/arch/arm/mach-s5pv210/mach-smdkv210.c
> index a278832..22ed209 100644
> --- a/arch/arm/mach-s5pv210/mach-smdkv210.c
> +++ b/arch/arm/mach-s5pv210/mach-smdkv210.c
> @@ -94,5 +94,5 @@ MACHINE_START(SMDKV210, "SMDKV210")
>  	.init_irq	= s5pv210_init_irq,
>  	.map_io		= smdkv210_map_io,
>  	.init_machine	= smdkv210_machine_init,
> -	.timer		= &s3c24xx_timer,
> +	.timer		= &s5p_systimer,
>  MACHINE_END
> diff --git a/arch/arm/plat-s5p/Kconfig b/arch/arm/plat-s5p/Kconfig
> index d400a6a..a73fc56 100644
> --- a/arch/arm/plat-s5p/Kconfig
> +++ b/arch/arm/plat-s5p/Kconfig
> @@ -23,3 +23,10 @@ config PLAT_S5P
>  	select SAMSUNG_IRQ_UART
>  	help
>  	  Base platform code for Samsung's S5P series SoC.
> +
> +config SYSTIMER_S5P
> +	bool
> +	depends on (ARCH_S5P6442 || ARCH_S5PV210)
> +	default y
> +	help
> +	  Support System Timer for S5P Series
> diff --git a/arch/arm/plat-s5p/Makefile b/arch/arm/plat-s5p/Makefile
> index a7c54b3..ec28f1b 100644
> --- a/arch/arm/plat-s5p/Makefile
> +++ b/arch/arm/plat-s5p/Makefile
> @@ -17,3 +17,4 @@ obj-y				+= cpu.o
>  obj-y				+= clock.o
>  obj-y				+= irq.o
>  obj-y				+= setup-i2c0.o
> +obj-$(CONFIG_SYSTIMER_S5P)	+= systimer-s5p.o
> diff --git a/arch/arm/plat-s5p/include/plat/regs-systimer.h b/arch/arm/plat-s5p/include/plat/regs-systimer.h
> new file mode 100644
> index 0000000..937ec44
> --- /dev/null
> +++ b/arch/arm/plat-s5p/include/plat/regs-systimer.h
> @@ -0,0 +1,75 @@
> +/* linux/arch/arm/plat-s5p/include/plat/regs-systimer.h
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *              http://www.samsung.com/
> + *
> + * S5P System Timer Driver Header information
> + *
> + * 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.
> +*/
> +
> +#ifndef __ASM_PLAT_REGS_SYSTIMER_H
> +#define __ASM_PLAT_REGS_SYSTIMER_H __FILE__
> +
> +#define S5P_SYSTIMERREG(x)		(S5P_VA_SYSTIMER + (x))
> +
> +#define S5P_SYSTIMER_TCFG		S5P_SYSTIMERREG(0x00)
> +#define S5P_SYSTIMER_TCON		S5P_SYSTIMERREG(0x04)
> +#define S5P_SYSTIMER_TICNTB		S5P_SYSTIMERREG(0x08)
> +#define S5P_SYSTIMER_TICNTO		S5P_SYSTIMERREG(0x0c)
> +#define S5P_SYSTIMER_TFCNTB		S5P_SYSTIMERREG(0x10)
> +#define S5P_SYSTIMER_ICNTB		S5P_SYSTIMERREG(0x18)
> +#define S5P_SYSTIMER_ICNTO		S5P_SYSTIMERREG(0x1c)
> +#define S5P_SYSTIMER_INT_CSTAT		S5P_SYSTIMERREG(0x20)
> +
> +/* Value for TCFG */
> +
> +#define S5P_SYSTIMER_SWRST		(1<<16)
> +
> +#define S5P_SYSTIMER_DIV_GEN		(0<<15)
> +#define S5P_SYSTIMER_DIV_RTC		(1<<15)

a space between the << and the integers around it would be useful to
make it easier to read.

> +
> +#define S5P_SYSTIMER_TICK_INT		(0<<14)
> +#define S5P_SYSTIMER_TICK_FRA		(1<<14)
> +
> +#define S5P_SYSTIMER_TCLK_MASK		(3<<12)
> +#define S5P_SYSTIMER_TCLK_XXTI		(0<<12)
> +#define S5P_SYSTIMER_TCLK_RTC		(1<<12)
> +#define S5P_SYSTIMER_TCLK_USB		(2<<12)
> +#define S5P_SYSTIMER_TCLK_PCLK		(3<<12)
> +
> +#define S5P_SYSTIMER_DIV_MASK		(7<<8)
> +#define S5P_SYSTIMER_DIV_1		(0<<8)
> +#define S5P_SYSTIMER_DIV_2		(1<<8)
> +#define S5P_SYSTIMER_DIV_4		(2<<8)
> +#define S5P_SYSTIMER_DIV_8		(3<<8)
> +#define S5P_SYSTIMER_DIV_16		(4<<8)
> +
> +#define S5P_SYSTIMER_TARGET_HZ		1000
> +#define S5P_SYSTIMER_PRESCALER		5
> +#define S5P_SYSTIMER_PRESCALER_MASK	(0x3f<<0)
> +
> +/* value for TCON */
> +
> +#define S5P_SYSTIMER_INT_AUTO		(1<<5)
> +#define S5P_SYSTIMER_INT_IMM		(1<<4)
> +#define S5P_SYSTIMER_INT_START		(1<<3)
> +#define S5P_SYSTIMER_START		(1<<0)
> +
> +/* Value for INT_CSTAT */
> +
> +#define S5P_SYSTIMER_INT_TWIE		(1<<10)
> +#define S5P_SYSTIMER_INT_IWIE		(1<<9)
> +#define S5P_SYSTIMER_INT_TFWIE		(1<<8)
> +#define S5P_SYSTIMER_INT_TIWIE		(1<<7)
> +#define S5P_SYSTIMER_INT_ICNTEIE	(1<<6)
> +#define S5P_SYSTIMER_INT_TCON		(1<<5)
> +#define S5P_SYSTIMER_INT_ICNTB		(1<<4)
> +#define S5P_SYSTIMER_INT_TFCNTB		(1<<3)
> +#define S5P_SYSTIMER_INT_TICNTB		(1<<2)
> +#define S5P_SYSTIMER_INT_INTCNT		(1<<1)
> +#define S5P_SYSTIMER_INT_INTENABLE	(1<<0)
> +
> +#endif /* __ASM_PLAT_REGS_SYSTIMER_H */
> diff --git a/arch/arm/plat-s5p/systimer-s5p.c b/arch/arm/plat-s5p/systimer-s5p.c
> new file mode 100644
> index 0000000..66951c8
> --- /dev/null
> +++ b/arch/arm/plat-s5p/systimer-s5p.c
> @@ -0,0 +1,298 @@
> +/* linux/arch/arm/plat-s5p/systimer-s5p.c
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *              http://www.samsung.com/
> + *
> + * S5P System Timer
> + *
> + * Based on linux/arch/arm/plat-samsung/time.c
> + *
> + * 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/kernel.h>
> +#include <linux/sched.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +
> +#include <asm/system.h>
> +#include <asm/mach-types.h>
> +
> +#include <asm/irq.h>
> +#include <asm/mach/time.h>
> +
> +#include <mach/map.h>
> +#include <mach/regs-irq.h>
> +#include <mach/tick.h>
> +
> +#include <plat/regs-systimer.h>
> +#include <plat/clock.h>
> +#include <plat/cpu.h>
> +
> +#include <mach/regs-clock.h>
> +
> +static unsigned long timer_startval;
> +static unsigned long timer_usec_ticks;
> +static unsigned long timer_icnt;
> +
> +#define TICK_MAX		(0xffffffff)
> +#define TIMER_USEC_SHIFT 	16
> +
> +static unsigned int systimer_write_done(unsigned int value)
> +{
> +	unsigned int cnt;
> +	unsigned int tmp_reg;
> +
> +	cnt = 1000;
> +	do {
> +		cnt--;
> +
> +		if (__raw_readl(S5P_SYSTIMER_INT_CSTAT) & value) {
> +			tmp_reg = __raw_readl(S5P_SYSTIMER_INT_CSTAT);
> +			tmp_reg |= value;
> +			__raw_writel(tmp_reg , S5P_SYSTIMER_INT_CSTAT);
> +
> +			return 0;
> +		}
> +
> +	} while (cnt > 0);

while (--cnt > 0) would remove the need to do it in the loop.

> +
> +	printk(KERN_ERR "%s : %d : Timer Expired\n", __func__, value);
> +
> +	return -ETIME;
> +}
> +
> +static unsigned int s5p_systimer_write(void __iomem *reg_offset,
> +				       unsigned int value)

reg_offset is badly named. it isn't an offset, it is the register.
suggest for clarity you call this reg.

> +{
> +	unsigned int int_cstat;
> +	unsigned int ret = 0;
> +
> +	int_cstat = __raw_readl(S5P_SYSTIMER_INT_CSTAT);
> +
> +	if (reg_offset == S5P_SYSTIMER_TCON) {
> +		__raw_writel(value, reg_offset);
> +
> +		if (int_cstat & S5P_SYSTIMER_INT_TWIE)
> +			ret = systimer_write_done(S5P_SYSTIMER_INT_TCON);
> +
> +	} else if (reg_offset == S5P_SYSTIMER_ICNTB) {
> +		__raw_writel(value, reg_offset);
> +
> +		if (int_cstat & S5P_SYSTIMER_INT_IWIE)
> +			ret = systimer_write_done(S5P_SYSTIMER_INT_ICNTB);
> +
> +	} else if (reg_offset == S5P_SYSTIMER_TFCNTB) {
> +		__raw_writel(value, reg_offset);
> +
> +		if (int_cstat & S5P_SYSTIMER_INT_TFWIE)
> +			ret = systimer_write_done(S5P_SYSTIMER_INT_TFCNTB);
> +
> +	} else if (reg_offset == S5P_SYSTIMER_TICNTB) {
> +		__raw_writel(value, reg_offset);
> +
> +		if (int_cstat & S5P_SYSTIMER_INT_TIWIE)
> +			ret = systimer_write_done(S5P_SYSTIMER_INT_TICNTB);
> +	} else {
> +		__raw_writel(value, reg_offset);

hmm, looks like __raw_writel() is always done, how about moving it out of
the if () block?

> +	}
> +
> +	return ret;
> +}
> +
> +/*
> + * S5P has system timer to use as OS tick Timer.
> + * System Timer provides two distincive feature. Accurate timer which provides
> + * exact 1ms time tick at any power mode except sleep mode.
> + * interrupt interval without stopping reference tick timer.
> + */
> +
> +/*
> + * timer_mask_usec_ticks
> + *
> + * given a clock and divisor, make the value to pass into timer_ticks_to_usec
> + * to scale the ticks into usecs
> + */
> +static inline unsigned long timer_mask_usec_ticks(unsigned long scaler,
> +						  unsigned long pclk)
> +{
> +	unsigned long den = pclk / 1000;
> +
> +	return ((1000 << TIMER_USEC_SHIFT) * scaler + (den >> 1)) / den;
> +}
> +
> +/*
> + * timer_ticks_to_usec
> + *
> + * convert timer ticks to usec.
> + */
> +static inline unsigned long timer_ticks_to_usec(unsigned long ticks)
> +{
> +	unsigned long res;
> +
> +	res = ticks * timer_usec_ticks;
> +	res += 1 << (TIMER_USEC_SHIFT - 4);	/* round up slightly */
> +
> +	return res >> TIMER_USEC_SHIFT;
> +}
> +
> +/*
> + * Returns microsecond  since last clock interrupt.  Note that interrupts
> + * will have been disabled by do_gettimeoffset()
> + * IRQs are disabled before entering here from do_gettimeofday()
> + */
> +static unsigned long s5p_gettimeoffset(void)
> +{
> +	unsigned long tdone;
> +	unsigned long tval;
> +	unsigned long clk_tick_totcnt;
> +
> +	clk_tick_totcnt = (timer_icnt + 1) * timer_startval;
> +
> +	/* work out how many ticks have gone since last timer interrupt */
> +	tval = __raw_readl(S5P_SYSTIMER_ICNTO) * timer_startval;
> +	tval += __raw_readl(S5P_SYSTIMER_TICNTO);
> +
> +	tdone = clk_tick_totcnt - tval;
> +
> +	/* check to see if there is an interrupt pending */
> +	if (s5p_ostimer_pending()) {
> +		/* re-read the timer, and try and fix up for the missed
> +		 * interrupt. Note, the interrupt may go off before the
> +		 * timer has re-loaded from wrapping.
> +		 */
> +
> +		tval = __raw_readl(S5P_SYSTIMER_ICNTO) * timer_startval;
> +		tval += __raw_readl(S5P_SYSTIMER_TICNTO);
> +
> +		tdone = clk_tick_totcnt - tval;
> +
> +		if (tval != 0)
> +			tdone += clk_tick_totcnt;
> +	}
> +
> +	return timer_ticks_to_usec(tdone);
> +}
> +
> +/*
> + * IRQ handler for the timer
> + */
> +static irqreturn_t s5p_systimer_interrupt(int irq, void *dev_id)
> +{
> +	unsigned int temp_cstat;
> +
> +	temp_cstat = __raw_readl(S5P_SYSTIMER_INT_CSTAT);
> +	temp_cstat |= S5P_SYSTIMER_INT_INTCNT;
> +	s5p_systimer_write(S5P_SYSTIMER_INT_CSTAT, temp_cstat);
> +
> +	timer_tick();
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct irqaction s5p_systimer_irq = {
> +	.name		= "S5P System Timer",
> +	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
> +	.handler	= s5p_systimer_interrupt,
> +};
> +
> +/*
> + * Set up timer interrupt, and return the current time in seconds.
> + */
> +static void s5p_systimer_setup(void)
> +{
> +	unsigned long tcon;
> +	unsigned long tcnt;
> +	unsigned long tcfg;
> +	unsigned long int_csata;
> +
> +	/* clock configuration setting and enable */
> +	unsigned long pclk;
> +	struct clk *clk;
> +
> +	clk = clk_get(NULL, "systimer");
> +	if (IS_ERR(clk))
> +		panic("failed to get clock for system timer");
> +
> +	clk_enable(clk);
> +
> +	pclk = clk_get_rate(clk);
> +
> +	tcfg = __raw_readl(S5P_SYSTIMER_TCFG);
> +	tcfg |= S5P_SYSTIMER_SWRST;
> +	s5p_systimer_write(S5P_SYSTIMER_TCFG, tcfg);
> +
> +	tcnt = TICK_MAX;  /* default value for tcnt */
> +
> +	/* initialize system timer clock */
> +	tcfg = __raw_readl(S5P_SYSTIMER_TCFG);
> +
> +	tcfg &= ~S5P_SYSTIMER_TCLK_MASK;
> +	tcfg |= S5P_SYSTIMER_TCLK_PCLK;
> +
> +	s5p_systimer_write(S5P_SYSTIMER_TCFG, tcfg);
> +
> +	/* TCFG must not be changed at run-time.
> +	 * If you want to change TCFG, stop timer(TCON[0] = 0)
> +	 */
> +
> +	s5p_systimer_write(S5P_SYSTIMER_TCON, 0);
> +
> +	/* read the current timer configuration bits */
> +	tcon = __raw_readl(S5P_SYSTIMER_TCON);
> +	tcfg = __raw_readl(S5P_SYSTIMER_TCFG);
> +
> +	/* configure clock tick */
> +	timer_usec_ticks = timer_mask_usec_ticks(S5P_SYSTIMER_PRESCALER, pclk);
> +
> +	tcfg &= ~S5P_SYSTIMER_TCLK_MASK;
> +	tcfg |= S5P_SYSTIMER_TCLK_PCLK;
> +	tcfg &= ~S5P_SYSTIMER_PRESCALER_MASK;
> +	tcfg |= S5P_SYSTIMER_PRESCALER - 1;
> +
> +	tcnt = ((pclk / S5P_SYSTIMER_PRESCALER) / S5P_SYSTIMER_TARGET_HZ) - 1;
> +
> +	/* check to see if timer is within 16bit range... */
> +	if (tcnt > TICK_MAX) {
> +		panic("setup_timer: HZ is too small, cannot configure timer!");
> +		return;
> +	}
> +
> +	s5p_systimer_write(S5P_SYSTIMER_TCFG, tcfg);
> +
> +	timer_startval = tcnt;
> +	s5p_systimer_write(S5P_SYSTIMER_TICNTB, tcnt);
> +
> +	/* set Interrupt tick value */
> +	timer_icnt = (S5P_SYSTIMER_TARGET_HZ / HZ) - 1;
> +	s5p_systimer_write(S5P_SYSTIMER_ICNTB, timer_icnt);
> +
> +	tcon = (S5P_SYSTIMER_INT_AUTO | S5P_SYSTIMER_START
> +		| S5P_SYSTIMER_INT_START);
> +	s5p_systimer_write(S5P_SYSTIMER_TCON, tcon);
> +
> +	/* Interrupt Start and Enable */
> +	int_csata = __raw_readl(S5P_SYSTIMER_INT_CSTAT);
> +	int_csata |= (S5P_SYSTIMER_INT_ICNTEIE | S5P_SYSTIMER_INT_INTENABLE);
> +	s5p_systimer_write(S5P_SYSTIMER_INT_CSTAT, int_csata);
> +}
> +
> +static void __init s5p_systimer_init(void)
> +{
> +	s5p_systimer_setup();
> +	setup_irq(IRQ_SYSTIMER, &s5p_systimer_irq);
> +}
> +
> +struct sys_timer s5p_systimer = {
> +	.init		= s5p_systimer_init,
> +	.offset		= s5p_gettimeoffset,
> +	.resume		= s5p_systimer_setup
> +};
> diff --git a/arch/arm/plat-samsung/include/plat/cpu.h b/arch/arm/plat-samsung/include/plat/cpu.h
> index d316b4a..6baa357 100644
> --- a/arch/arm/plat-samsung/include/plat/cpu.h
> +++ b/arch/arm/plat-samsung/include/plat/cpu.h
> @@ -68,6 +68,9 @@ extern void s3c24xx_init_uartdevs(char *name,
>  struct sys_timer;
>  extern struct sys_timer s3c24xx_timer;
>  
> +/* timer for s5p */
> +extern struct sys_timer s5p_systimer;
> +
>  /* system device classes */
>  
>  extern struct sysdev_class s3c2410_sysclass;
> -- 
> 1.6.2.5
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
-- 
Ben

Q:      What's a light-year?
A:      One-third less calories than a regular year.




More information about the linux-arm-kernel mailing list