From f11899894c0a683c754ca71b45f7c9c3d35a3a1c Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Sat, 20 Apr 2013 23:22:13 +0200 Subject: clocksource: add samsung pwm timer driver This adds a new clocksource driver for the PWM timer that is present in most Samsung SoCs, based on the existing driver in arch/arm/plat-samsung/samsung-time.c and many changes implemented by Tomasz Figa. Originally, the conversion of all Samsung machines to the new driver was planned for 3.10, but that work ended up being too late and too invasive just before the merge window. Unfortunately, other changes in the Exynos platform resulted in some Exynos4 setups, particularly the Universal C210 board to be broken. In order to fix that with minimum risk, so we now leave the existing pwm clocksource driver in place for all older platforms and use the new driver only for device tree enabled boards. This way, we can get the broken machines running again using DT descriptions. All clocksource changes were implemented by Tomasz, while the DT registration was rewritten by Arnd. Signed-off-by: Arnd Bergmann Cc: Tomasz Figa Cc: Kyungmin Park Cc: Kukjin Kim Cc: Ben Dooks Cc: John Stultz Cc: Thomas Gleixner diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index e507ab7..03caba2 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -67,3 +67,13 @@ config CLKSRC_METAG_GENERIC def_bool y if METAG help This option enables support for the Meta per-thread timers. + +config CLKSRC_SAMSUNG_PWM + def_bool ARCH_EXYNOS4 + depends on OF + select CLKSRC_MMIO + help + This is a new clocksource driver for the PWM timer found in + Samsung S3C, S5P and Exynos SoCs, replacing an earlier driver + for all devicetree enabled platforms. This driver will be + needed only on systems that do not have the Exynos MCT available. diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 4d8283a..891c9f2 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o obj-$(CONFIG_SUNXI_TIMER) += sunxi_timer.o obj-$(CONFIG_ARCH_TEGRA) += tegra20_timer.o obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o +obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c new file mode 100644 index 0000000..1752457 --- /dev/null +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * samsung - Common hr-timer support (s3c and s5p) + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* + * Clocksource driver + */ + +#define REG_TCFG0 0x00 +#define REG_TCFG1 0x04 +#define REG_TCON 0x08 +#define REG_TINT_CSTAT 0x44 + +#define REG_TCNTB(chan) (0x0c + 12 * (chan)) +#define REG_TCMPB(chan) (0x10 + 12 * (chan)) + +#define TCFG0_PRESCALER_MASK 0xff +#define TCFG0_PRESCALER1_SHIFT 8 + +#define TCFG1_SHIFT(x) ((x) * 4) +#define TCFG1_MUX_MASK 0xf + +#define TCON_START(chan) (1 << (4 * (chan) + 0)) +#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1)) +#define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) +#define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) + +struct samsung_timer_source { + unsigned int event_id; + unsigned int source_id; + unsigned int tcnt_max; + unsigned int tscaler_div; + unsigned int tdiv; +}; + +static struct samsung_pwm *pwm; +static struct clk *timerclk; +static struct samsung_timer_source timer_source; +static unsigned long clock_count_per_tick; + +static void samsung_timer_set_prescale(struct samsung_pwm *pwm, + unsigned int channel, u16 prescale) +{ + unsigned long flags; + u8 shift = 0; + u32 reg; + + if (channel >= 2) + shift = TCFG0_PRESCALER1_SHIFT; + + spin_lock_irqsave(&pwm->slock, flags); + + reg = readl(pwm->base + REG_TCFG0); + reg &= ~(TCFG0_PRESCALER_MASK << shift); + reg |= (prescale - 1) << shift; + writel(reg, pwm->base + REG_TCFG0); + + spin_unlock_irqrestore(&pwm->slock, flags); +} + +static void samsung_timer_set_divisor(struct samsung_pwm *pwm, + unsigned int channel, u8 divisor) +{ + u8 shift = TCFG1_SHIFT(channel); + unsigned long flags; + u32 reg; + u8 bits; + + bits = (fls(divisor) - 1) - pwm->variant.div_base; + + spin_lock_irqsave(&pwm->slock, flags); + + reg = readl(pwm->base + REG_TCFG1); + reg &= ~(TCFG1_MUX_MASK << shift); + reg |= bits << shift; + writel(reg, pwm->base + REG_TCFG1); + + spin_unlock_irqrestore(&pwm->slock, flags); +} + +static void samsung_time_stop(unsigned int channel) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&pwm->slock, flags); + + tcon = __raw_readl(pwm->base + REG_TCON); + tcon &= ~TCON_START(channel); + __raw_writel(tcon, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&pwm->slock, flags); +} + +static void samsung_time_setup(unsigned int channel, unsigned long tcnt) +{ + unsigned long tcon; + unsigned long flags; + unsigned int tcon_chan = channel; + + if (tcon_chan > 0) + ++tcon_chan; + + spin_lock_irqsave(&pwm->slock, flags); + + tcon = __raw_readl(pwm->base + REG_TCON); + + tcnt--; + + tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); + tcon |= TCON_MANUALUPDATE(tcon_chan); + + __raw_writel(tcnt, pwm->base + REG_TCNTB(channel)); + __raw_writel(tcnt, pwm->base + REG_TCMPB(channel)); + __raw_writel(tcon, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&pwm->slock, flags); +} + +static void samsung_time_start(unsigned int channel, bool periodic) +{ + unsigned long tcon; + unsigned long flags; + + if (channel > 0) + ++channel; + + spin_lock_irqsave(&pwm->slock, flags); + + tcon = __raw_readl(pwm->base + REG_TCON); + + tcon &= ~TCON_MANUALUPDATE(channel); + tcon |= TCON_START(channel); + + if (periodic) + tcon |= TCON_AUTORELOAD(channel); + else + tcon &= ~TCON_AUTORELOAD(channel); + + __raw_writel(tcon, pwm->base + REG_TCON); + + spin_unlock_irqrestore(&pwm->slock, flags); +} + +static int samsung_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + samsung_time_setup(timer_source.event_id, cycles); + samsung_time_start(timer_source.event_id, false); + + return 0; +} + +static void samsung_timer_resume(void) +{ + /* event timer restart */ + samsung_time_setup(timer_source.event_id, clock_count_per_tick); + samsung_time_start(timer_source.event_id, true); + + /* source timer restart */ + samsung_time_setup(timer_source.source_id, timer_source.tcnt_max); + samsung_time_start(timer_source.source_id, true); +} + +static void samsung_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + samsung_time_stop(timer_source.event_id); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + samsung_time_setup(timer_source.event_id, clock_count_per_tick); + samsung_time_start(timer_source.event_id, true); + break; + + case CLOCK_EVT_MODE_ONESHOT: + break; + + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + break; + + case CLOCK_EVT_MODE_RESUME: + samsung_timer_resume(); + break; + } +} + +static struct clock_event_device time_event_device = { + .name = "samsung_event_timer", + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, + .rating = 200, + .set_next_event = samsung_set_next_event, + .set_mode = samsung_set_mode, +}; + +static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + if (pwm->variant.has_tint_cstat) { + u32 mask = (1 << timer_source.event_id); + writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT); + } + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction samsung_clock_event_irq = { + .name = "samsung_time_irq", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = samsung_clock_event_isr, + .dev_id = &time_event_device, +}; + +static void __init samsung_clockevent_init(void) +{ + unsigned long pclk; + unsigned long clock_rate; + unsigned int irq_number; + + pclk = clk_get_rate(timerclk); + + samsung_timer_set_prescale(pwm, timer_source.event_id, + timer_source.tscaler_div); + samsung_timer_set_divisor(pwm, timer_source.event_id, + timer_source.tdiv); + + clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv); + clock_count_per_tick = clock_rate / HZ; + + time_event_device.cpumask = cpumask_of(0); + clockevents_config_and_register(&time_event_device, clock_rate, 1, -1); + + irq_number = pwm->irq[timer_source.event_id]; + setup_irq(irq_number, &samsung_clock_event_irq); + + if (pwm->variant.has_tint_cstat) { + u32 mask = (1 << timer_source.event_id); + writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT); + } +} + +static void __iomem *samsung_timer_reg(void) +{ + switch (timer_source.source_id) { + case 0: + case 1: + case 2: + case 3: + return pwm->base + timer_source.source_id * 0x0c + 0x14; + + case 4: + return pwm->base + 0x40; + + default: + BUG(); + } +} + +/* + * Override the global weak sched_clock symbol with this + * local implementation which uses the clocksource to get some + * better resolution when scheduling the kernel. We accept that + * this wraps around for now, since it is just a relative time + * stamp. (Inspired by U300 implementation.) + */ +static u32 notrace samsung_read_sched_clock(void) +{ + void __iomem *reg = samsung_timer_reg(); + + if (!reg) + return 0; + + return ~__raw_readl(reg); +} + +static void __init samsung_clocksource_init(void) +{ + void __iomem *reg = samsung_timer_reg(); + unsigned long pclk; + unsigned long clock_rate; + int ret; + + pclk = clk_get_rate(timerclk); + + samsung_timer_set_prescale(pwm, timer_source.source_id, + timer_source.tscaler_div); + samsung_timer_set_divisor(pwm, timer_source.source_id, + timer_source.tdiv); + + clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv); + + samsung_time_setup(timer_source.source_id, timer_source.tcnt_max); + samsung_time_start(timer_source.source_id, true); + + setup_sched_clock(samsung_read_sched_clock, + pwm->variant.bits, clock_rate); + + ret = clocksource_mmio_init(reg, "samsung_clocksource_timer", + clock_rate, 250, pwm->variant.bits, + clocksource_mmio_readl_down); + if (ret) + panic("samsung_clocksource_timer: can't register clocksource\n"); +} + +static void __init samsung_timer_resources(void) +{ + timerclk = clk_get(NULL, "timers"); + if (IS_ERR(timerclk)) + panic("failed to get timers clock for timer"); + + clk_prepare_enable(timerclk); + + timer_source.tcnt_max = (1UL << pwm->variant.bits) - 1; + if (pwm->variant.bits == 16) { + timer_source.tscaler_div = 25; + timer_source.tdiv = 2; + } else { + timer_source.tscaler_div = 2; + timer_source.tdiv = 1; + } +} + +/* + * PWM master driver + */ +static void __init samsung_pwm_clocksource_init(void) +{ + u8 mask; + int channel; + + if (!pwm) + panic("no pwm clocksource device found"); + + mask = ~pwm->variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clocksource"); + timer_source.source_id = channel; + + mask &= ~(1 << channel); + channel = fls(mask) - 1; + if (channel < 0) + panic("failed to find PWM channel for clock event"); + timer_source.event_id = channel; + + samsung_timer_resources(); + samsung_clockevent_init(); + samsung_clocksource_init(); +} + +static void __init samsung_pwm_alloc(struct device_node *np, + const struct samsung_pwm_variant *variant) +{ + struct resource res; + struct property *prop; + const __be32 *cur; + u32 val; + int i; + + pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); + if (!pwm) { + pr_err("%s: could not allocate PWM device struct\n", __func__); + return; + } + memcpy(&pwm->variant, variant, sizeof(pwm->variant)); + spin_lock_init(&pwm->slock); + for (i = 0; i < SAMSUNG_PWM_NUM; ++i) + pwm->irq[i] = irq_of_parse_and_map(np, i); + + of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { + if (val >= SAMSUNG_PWM_NUM) { + pr_warning("%s: invalid channel index in samsung,pwm-outputs property\n", + __func__); + continue; + } + pwm->variant.output_mask |= 1 << val; + } + + of_address_to_resource(np, 0, &res); + if (!request_mem_region(res.start, + resource_size(&res), "samsung-pwm")) { + pr_err("%s: failed to request IO mem region\n", __func__); + return; + } + + pwm->base = ioremap(res.start, resource_size(&res)); + if (!pwm->base) { + pr_err("%s: failed to map PWM registers\n", __func__); + release_mem_region(res.start, resource_size(&res)); + return; + } + + samsung_pwm_clocksource_init(); +} + +static const struct samsung_pwm_variant s3c24xx_variant = { + .bits = 16, + .div_base = 1, + .has_tint_cstat = false, + .tclk_mask = (1 << 4), +}; + +static void __init s3c2410_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c24xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init); + +static const struct samsung_pwm_variant s3c64xx_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5), +}; + +static void __init s3c64xx_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s3c64xx_variant); +} +CLOCKSOURCE_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p64x0_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = 0, +}; + +static void __init s5p64x0_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p64x0_variant); +} +CLOCKSOURCE_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init); + +static const struct samsung_pwm_variant s5p_variant = { + .bits = 32, + .div_base = 0, + .has_tint_cstat = true, + .tclk_mask = (1 << 5), +}; + +static void __init s5p_pwm_clocksource_init(struct device_node *np) +{ + samsung_pwm_alloc(np, &s5p_variant); +} +CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init); diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h new file mode 100644 index 0000000..eff8668 --- /dev/null +++ b/include/clocksource/samsung_pwm.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __CLOCKSOURCE_SAMSUNG_PWM_H +#define __CLOCKSOURCE_SAMSUNG_PWM_H + +#include + +#define SAMSUNG_PWM_NUM 5 + +struct platform_device; +struct device_node; + +struct samsung_pwm_variant { + u8 bits; + u8 div_base; + u8 tclk_mask; + u8 output_mask; + bool has_tint_cstat; +}; + +struct samsung_pwm { + struct samsung_pwm_variant variant; + spinlock_t slock; + void __iomem *base; + int irq[SAMSUNG_PWM_NUM]; +}; + +#endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */ -- cgit v0.10.2 From 5b5ff97d2c46c52d2a86208509633230c9a486ec Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:22 +0200 Subject: Documentation: Add device tree bindings for Samsung PWM timers This patch adds missing documentation describing Device Tree bindings for Samsung PWM timers. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/Documentation/devicetree/bindings/pwm/pwm-samsung.txt b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt new file mode 100644 index 0000000..ac67c68 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-samsung.txt @@ -0,0 +1,43 @@ +* Samsung PWM timers + +Samsung SoCs contain PWM timer blocks which can be used for system clock source +and clock event timers, as well as to drive SoC outputs with PWM signal. Each +PWM timer block provides 5 PWM channels (not all of them can drive physical +outputs - see SoC and board manual). + +Be aware that the clocksource driver supports only uniprocessor systems. + +Required properties: +- compatible : should be one of following: + samsung,s3c2410-pwm - for 16-bit timers present on S3C24xx SoCs + samsung,s3c6400-pwm - for 32-bit timers present on S3C64xx SoCs + samsung,s5p6440-pwm - for 32-bit timers present on S5P64x0 SoCs + samsung,s5pc100-pwm - for 32-bit timers present on S5PC100, S5PV210, + Exynos4210 rev0 SoCs + samsung,exynos4210-pwm - for 32-bit timers present on Exynos4210, + Exynos4x12 and Exynos5250 SoCs +- reg: base address and size of register area +- interrupts: list of timer interrupts (one interrupt per timer, starting at + timer 0) +- #pwm-cells: number of cells used for PWM specifier - must be 3 + the specifier format is as follows: + - phandle to PWM controller node + - index of PWM channel (from 0 to 4) + - PWM signal period in nanoseconds + - bitmask of optional PWM flags: + 0x1 - invert PWM signal + +Optional properties: +- samsung,pwm-outputs: list of PWM channels used as PWM outputs on particular + platform - an array of up to 5 elements being indices of PWM channels + (from 0 to 4), the order does not matter. + +Example: + pwm@7f006000 { + compatible = "samsung,s3c6400-pwm"; + reg = <0x7f006000 0x1000>; + interrupt-parent = <&vic0>; + interrupts = <23>, <24>, <25>, <27>, <28>; + samsung,pwm-outputs = <0>, <1>; + #pwm-cells = <3>; + } -- cgit v0.10.2 From 77d844344952934baf2902cf6f1ac870dc1b4d7a Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:23 +0200 Subject: clocksource: samsung_pwm_timer: Let platforms select the driver This patch modifies the way of enabling the driver to let the platforms select it in their Kconfig instead of specifying particular platforms in Kconfig entry of the driver. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 03caba2..ff5b6d8 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -69,7 +69,7 @@ config CLKSRC_METAG_GENERIC This option enables support for the Meta per-thread timers. config CLKSRC_SAMSUNG_PWM - def_bool ARCH_EXYNOS4 + bool depends on OF select CLKSRC_MMIO help -- cgit v0.10.2 From 7aac482e6290ab7ad21809e0c7327be959a2203e Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:24 +0200 Subject: clocksource: samsung_pwm_timer: Make PWM spinlock global This patch makes the PWM spinlock global and exports it to allow using it in Samsung PWM driver (will be reworked to use proper synchronization in further patches). Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 1752457..d9048b8 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -49,6 +49,9 @@ #define TCON_INVERT(chan) (1 << (4 * (chan) + 2)) #define TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3)) +DEFINE_SPINLOCK(samsung_pwm_lock); +EXPORT_SYMBOL(samsung_pwm_lock); + struct samsung_timer_source { unsigned int event_id; unsigned int source_id; @@ -72,14 +75,14 @@ static void samsung_timer_set_prescale(struct samsung_pwm *pwm, if (channel >= 2) shift = TCFG0_PRESCALER1_SHIFT; - spin_lock_irqsave(&pwm->slock, flags); + spin_lock_irqsave(&samsung_pwm_lock, flags); reg = readl(pwm->base + REG_TCFG0); reg &= ~(TCFG0_PRESCALER_MASK << shift); reg |= (prescale - 1) << shift; writel(reg, pwm->base + REG_TCFG0); - spin_unlock_irqrestore(&pwm->slock, flags); + spin_unlock_irqrestore(&samsung_pwm_lock, flags); } static void samsung_timer_set_divisor(struct samsung_pwm *pwm, @@ -92,14 +95,14 @@ static void samsung_timer_set_divisor(struct samsung_pwm *pwm, bits = (fls(divisor) - 1) - pwm->variant.div_base; - spin_lock_irqsave(&pwm->slock, flags); + spin_lock_irqsave(&samsung_pwm_lock, flags); reg = readl(pwm->base + REG_TCFG1); reg &= ~(TCFG1_MUX_MASK << shift); reg |= bits << shift; writel(reg, pwm->base + REG_TCFG1); - spin_unlock_irqrestore(&pwm->slock, flags); + spin_unlock_irqrestore(&samsung_pwm_lock, flags); } static void samsung_time_stop(unsigned int channel) @@ -110,13 +113,13 @@ static void samsung_time_stop(unsigned int channel) if (channel > 0) ++channel; - spin_lock_irqsave(&pwm->slock, flags); + spin_lock_irqsave(&samsung_pwm_lock, flags); tcon = __raw_readl(pwm->base + REG_TCON); tcon &= ~TCON_START(channel); __raw_writel(tcon, pwm->base + REG_TCON); - spin_unlock_irqrestore(&pwm->slock, flags); + spin_unlock_irqrestore(&samsung_pwm_lock, flags); } static void samsung_time_setup(unsigned int channel, unsigned long tcnt) @@ -128,7 +131,7 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt) if (tcon_chan > 0) ++tcon_chan; - spin_lock_irqsave(&pwm->slock, flags); + spin_lock_irqsave(&samsung_pwm_lock, flags); tcon = __raw_readl(pwm->base + REG_TCON); @@ -141,7 +144,7 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt) __raw_writel(tcnt, pwm->base + REG_TCMPB(channel)); __raw_writel(tcon, pwm->base + REG_TCON); - spin_unlock_irqrestore(&pwm->slock, flags); + spin_unlock_irqrestore(&samsung_pwm_lock, flags); } static void samsung_time_start(unsigned int channel, bool periodic) @@ -152,7 +155,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) if (channel > 0) ++channel; - spin_lock_irqsave(&pwm->slock, flags); + spin_lock_irqsave(&samsung_pwm_lock, flags); tcon = __raw_readl(pwm->base + REG_TCON); @@ -166,7 +169,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) __raw_writel(tcon, pwm->base + REG_TCON); - spin_unlock_irqrestore(&pwm->slock, flags); + spin_unlock_irqrestore(&samsung_pwm_lock, flags); } static int samsung_set_next_event(unsigned long cycles, @@ -394,7 +397,6 @@ static void __init samsung_pwm_alloc(struct device_node *np, return; } memcpy(&pwm->variant, variant, sizeof(pwm->variant)); - spin_lock_init(&pwm->slock); for (i = 0; i < SAMSUNG_PWM_NUM; ++i) pwm->irq[i] = irq_of_parse_and_map(np, i); diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h index eff8668..399d241 100644 --- a/include/clocksource/samsung_pwm.h +++ b/include/clocksource/samsung_pwm.h @@ -23,6 +23,8 @@ struct platform_device; struct device_node; +extern spinlock_t samsung_pwm_lock; + struct samsung_pwm_variant { u8 bits; u8 div_base; -- cgit v0.10.2 From 030c2a1e515077dbfabe4e43e32d4a7c549d5543 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:25 +0200 Subject: clocksource: samsung_pwm_timer: Keep all driver data in a structure This patch modifies the driver to keep all its private data consistently in a single struct, instead of keeping part as separate variables. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index d9048b8..e3257fa 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -52,21 +52,25 @@ DEFINE_SPINLOCK(samsung_pwm_lock); EXPORT_SYMBOL(samsung_pwm_lock); -struct samsung_timer_source { +struct samsung_pwm_clocksource { + void __iomem *base; + unsigned int irq[SAMSUNG_PWM_NUM]; + struct samsung_pwm_variant variant; + + struct clk *timerclk; + unsigned int event_id; unsigned int source_id; unsigned int tcnt_max; unsigned int tscaler_div; unsigned int tdiv; + + unsigned long clock_count_per_tick; }; -static struct samsung_pwm *pwm; -static struct clk *timerclk; -static struct samsung_timer_source timer_source; -static unsigned long clock_count_per_tick; +static struct samsung_pwm_clocksource pwm; -static void samsung_timer_set_prescale(struct samsung_pwm *pwm, - unsigned int channel, u16 prescale) +static void samsung_timer_set_prescale(unsigned int channel, u16 prescale) { unsigned long flags; u8 shift = 0; @@ -77,30 +81,29 @@ static void samsung_timer_set_prescale(struct samsung_pwm *pwm, spin_lock_irqsave(&samsung_pwm_lock, flags); - reg = readl(pwm->base + REG_TCFG0); + reg = readl(pwm.base + REG_TCFG0); reg &= ~(TCFG0_PRESCALER_MASK << shift); reg |= (prescale - 1) << shift; - writel(reg, pwm->base + REG_TCFG0); + writel(reg, pwm.base + REG_TCFG0); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } -static void samsung_timer_set_divisor(struct samsung_pwm *pwm, - unsigned int channel, u8 divisor) +static void samsung_timer_set_divisor(unsigned int channel, u8 divisor) { u8 shift = TCFG1_SHIFT(channel); unsigned long flags; u32 reg; u8 bits; - bits = (fls(divisor) - 1) - pwm->variant.div_base; + bits = (fls(divisor) - 1) - pwm.variant.div_base; spin_lock_irqsave(&samsung_pwm_lock, flags); - reg = readl(pwm->base + REG_TCFG1); + reg = readl(pwm.base + REG_TCFG1); reg &= ~(TCFG1_MUX_MASK << shift); reg |= bits << shift; - writel(reg, pwm->base + REG_TCFG1); + writel(reg, pwm.base + REG_TCFG1); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -115,9 +118,9 @@ static void samsung_time_stop(unsigned int channel) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm->base + REG_TCON); + tcon = __raw_readl(pwm.base + REG_TCON); tcon &= ~TCON_START(channel); - __raw_writel(tcon, pwm->base + REG_TCON); + __raw_writel(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -133,16 +136,16 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm->base + REG_TCON); + tcon = __raw_readl(pwm.base + REG_TCON); tcnt--; tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); tcon |= TCON_MANUALUPDATE(tcon_chan); - __raw_writel(tcnt, pwm->base + REG_TCNTB(channel)); - __raw_writel(tcnt, pwm->base + REG_TCMPB(channel)); - __raw_writel(tcon, pwm->base + REG_TCON); + __raw_writel(tcnt, pwm.base + REG_TCNTB(channel)); + __raw_writel(tcnt, pwm.base + REG_TCMPB(channel)); + __raw_writel(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -157,7 +160,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) spin_lock_irqsave(&samsung_pwm_lock, flags); - tcon = __raw_readl(pwm->base + REG_TCON); + tcon = __raw_readl(pwm.base + REG_TCON); tcon &= ~TCON_MANUALUPDATE(channel); tcon |= TCON_START(channel); @@ -167,7 +170,7 @@ static void samsung_time_start(unsigned int channel, bool periodic) else tcon &= ~TCON_AUTORELOAD(channel); - __raw_writel(tcon, pwm->base + REG_TCON); + __raw_writel(tcon, pwm.base + REG_TCON); spin_unlock_irqrestore(&samsung_pwm_lock, flags); } @@ -175,8 +178,8 @@ static void samsung_time_start(unsigned int channel, bool periodic) static int samsung_set_next_event(unsigned long cycles, struct clock_event_device *evt) { - samsung_time_setup(timer_source.event_id, cycles); - samsung_time_start(timer_source.event_id, false); + samsung_time_setup(pwm.event_id, cycles); + samsung_time_start(pwm.event_id, false); return 0; } @@ -184,23 +187,23 @@ static int samsung_set_next_event(unsigned long cycles, static void samsung_timer_resume(void) { /* event timer restart */ - samsung_time_setup(timer_source.event_id, clock_count_per_tick); - samsung_time_start(timer_source.event_id, true); + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick); + samsung_time_start(pwm.event_id, true); /* source timer restart */ - samsung_time_setup(timer_source.source_id, timer_source.tcnt_max); - samsung_time_start(timer_source.source_id, true); + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); } static void samsung_set_mode(enum clock_event_mode mode, struct clock_event_device *evt) { - samsung_time_stop(timer_source.event_id); + samsung_time_stop(pwm.event_id); switch (mode) { case CLOCK_EVT_MODE_PERIODIC: - samsung_time_setup(timer_source.event_id, clock_count_per_tick); - samsung_time_start(timer_source.event_id, true); + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick); + samsung_time_start(pwm.event_id, true); break; case CLOCK_EVT_MODE_ONESHOT: @@ -228,9 +231,9 @@ static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id) { struct clock_event_device *evt = dev_id; - if (pwm->variant.has_tint_cstat) { - u32 mask = (1 << timer_source.event_id); - writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT); + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); } evt->event_handler(evt); @@ -251,39 +254,37 @@ static void __init samsung_clockevent_init(void) unsigned long clock_rate; unsigned int irq_number; - pclk = clk_get_rate(timerclk); + pclk = clk_get_rate(pwm.timerclk); - samsung_timer_set_prescale(pwm, timer_source.event_id, - timer_source.tscaler_div); - samsung_timer_set_divisor(pwm, timer_source.event_id, - timer_source.tdiv); + samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.event_id, pwm.tdiv); - clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv); - clock_count_per_tick = clock_rate / HZ; + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); + pwm.clock_count_per_tick = clock_rate / HZ; time_event_device.cpumask = cpumask_of(0); clockevents_config_and_register(&time_event_device, clock_rate, 1, -1); - irq_number = pwm->irq[timer_source.event_id]; + irq_number = pwm.irq[pwm.event_id]; setup_irq(irq_number, &samsung_clock_event_irq); - if (pwm->variant.has_tint_cstat) { - u32 mask = (1 << timer_source.event_id); - writel(mask | (mask << 5), pwm->base + REG_TINT_CSTAT); + if (pwm.variant.has_tint_cstat) { + u32 mask = (1 << pwm.event_id); + writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT); } } static void __iomem *samsung_timer_reg(void) { - switch (timer_source.source_id) { + switch (pwm.source_id) { case 0: case 1: case 2: case 3: - return pwm->base + timer_source.source_id * 0x0c + 0x14; + return pwm.base + pwm.source_id * 0x0c + 0x14; case 4: - return pwm->base + 0x40; + return pwm.base + 0x40; default: BUG(); @@ -314,23 +315,21 @@ static void __init samsung_clocksource_init(void) unsigned long clock_rate; int ret; - pclk = clk_get_rate(timerclk); + pclk = clk_get_rate(pwm.timerclk); - samsung_timer_set_prescale(pwm, timer_source.source_id, - timer_source.tscaler_div); - samsung_timer_set_divisor(pwm, timer_source.source_id, - timer_source.tdiv); + samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div); + samsung_timer_set_divisor(pwm.source_id, pwm.tdiv); - clock_rate = pclk / (timer_source.tscaler_div * timer_source.tdiv); + clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv); - samsung_time_setup(timer_source.source_id, timer_source.tcnt_max); - samsung_time_start(timer_source.source_id, true); + samsung_time_setup(pwm.source_id, pwm.tcnt_max); + samsung_time_start(pwm.source_id, true); setup_sched_clock(samsung_read_sched_clock, - pwm->variant.bits, clock_rate); + pwm.variant.bits, clock_rate); ret = clocksource_mmio_init(reg, "samsung_clocksource_timer", - clock_rate, 250, pwm->variant.bits, + clock_rate, 250, pwm.variant.bits, clocksource_mmio_readl_down); if (ret) panic("samsung_clocksource_timer: can't register clocksource\n"); @@ -338,19 +337,19 @@ static void __init samsung_clocksource_init(void) static void __init samsung_timer_resources(void) { - timerclk = clk_get(NULL, "timers"); - if (IS_ERR(timerclk)) + pwm.timerclk = clk_get(NULL, "timers"); + if (IS_ERR(pwm.timerclk)) panic("failed to get timers clock for timer"); - clk_prepare_enable(timerclk); + clk_prepare_enable(pwm.timerclk); - timer_source.tcnt_max = (1UL << pwm->variant.bits) - 1; - if (pwm->variant.bits == 16) { - timer_source.tscaler_div = 25; - timer_source.tdiv = 2; + pwm.tcnt_max = (1UL << pwm.variant.bits) - 1; + if (pwm.variant.bits == 16) { + pwm.tscaler_div = 25; + pwm.tdiv = 2; } else { - timer_source.tscaler_div = 2; - timer_source.tdiv = 1; + pwm.tscaler_div = 2; + pwm.tdiv = 1; } } @@ -362,20 +361,17 @@ static void __init samsung_pwm_clocksource_init(void) u8 mask; int channel; - if (!pwm) - panic("no pwm clocksource device found"); - - mask = ~pwm->variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); + mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1); channel = fls(mask) - 1; if (channel < 0) panic("failed to find PWM channel for clocksource"); - timer_source.source_id = channel; + pwm.source_id = channel; mask &= ~(1 << channel); channel = fls(mask) - 1; if (channel < 0) panic("failed to find PWM channel for clock event"); - timer_source.event_id = channel; + pwm.event_id = channel; samsung_timer_resources(); samsung_clockevent_init(); @@ -391,14 +387,9 @@ static void __init samsung_pwm_alloc(struct device_node *np, u32 val; int i; - pwm = kzalloc(sizeof(*pwm), GFP_KERNEL); - if (!pwm) { - pr_err("%s: could not allocate PWM device struct\n", __func__); - return; - } - memcpy(&pwm->variant, variant, sizeof(pwm->variant)); + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); for (i = 0; i < SAMSUNG_PWM_NUM; ++i) - pwm->irq[i] = irq_of_parse_and_map(np, i); + pwm.irq[i] = irq_of_parse_and_map(np, i); of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) { if (val >= SAMSUNG_PWM_NUM) { @@ -406,7 +397,7 @@ static void __init samsung_pwm_alloc(struct device_node *np, __func__); continue; } - pwm->variant.output_mask |= 1 << val; + pwm.variant.output_mask |= 1 << val; } of_address_to_resource(np, 0, &res); @@ -416,8 +407,8 @@ static void __init samsung_pwm_alloc(struct device_node *np, return; } - pwm->base = ioremap(res.start, resource_size(&res)); - if (!pwm->base) { + pwm.base = ioremap(res.start, resource_size(&res)); + if (!pwm.base) { pr_err("%s: failed to map PWM registers\n", __func__); release_mem_region(res.start, resource_size(&res)); return; -- cgit v0.10.2 From 09d718a78c79ddc270dccbfa1b1070b20b9a9ae1 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:26 +0200 Subject: clocksource: samsung_pwm_timer: Drop unused samsung_pwm struct This patch removes the unused samsung_pwm struct from public header. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h index 399d241..b1d8fe7 100644 --- a/include/clocksource/samsung_pwm.h +++ b/include/clocksource/samsung_pwm.h @@ -20,9 +20,6 @@ #define SAMSUNG_PWM_NUM 5 -struct platform_device; -struct device_node; - extern spinlock_t samsung_pwm_lock; struct samsung_pwm_variant { @@ -33,11 +30,4 @@ struct samsung_pwm_variant { bool has_tint_cstat; }; -struct samsung_pwm { - struct samsung_pwm_variant variant; - spinlock_t slock; - void __iomem *base; - int irq[SAMSUNG_PWM_NUM]; -}; - #endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */ -- cgit v0.10.2 From f9bb48a2c25a96757d13795ba7cc52687f94446f Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:27 +0200 Subject: clocksource: samsung_pwm_timer: Add support for non-DT platforms This patch extends the driver to support platforms that still use legacy ATAGS-based boot, without device tree, by providing an exported function that can be used from platform code to initialize the clocksource. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index ff5b6d8..6d61749 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -70,7 +70,6 @@ config CLKSRC_METAG_GENERIC config CLKSRC_SAMSUNG_PWM bool - depends on OF select CLKSRC_MMIO help This is a new clocksource driver for the PWM timer found in diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index e3257fa..9f4bd6a 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -356,7 +356,7 @@ static void __init samsung_timer_resources(void) /* * PWM master driver */ -static void __init samsung_pwm_clocksource_init(void) +static void __init _samsung_pwm_clocksource_init(void) { u8 mask; int channel; @@ -378,6 +378,17 @@ static void __init samsung_pwm_clocksource_init(void) samsung_clocksource_init(); } +void __init samsung_pwm_clocksource_init(void __iomem *base, + unsigned int *irqs, struct samsung_pwm_variant *variant) +{ + pwm.base = base; + memcpy(&pwm.variant, variant, sizeof(pwm.variant)); + memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs)); + + _samsung_pwm_clocksource_init(); +} + +#ifdef CONFIG_CLKSRC_OF static void __init samsung_pwm_alloc(struct device_node *np, const struct samsung_pwm_variant *variant) { @@ -414,7 +425,7 @@ static void __init samsung_pwm_alloc(struct device_node *np, return; } - samsung_pwm_clocksource_init(); + _samsung_pwm_clocksource_init(); } static const struct samsung_pwm_variant s3c24xx_variant = { @@ -468,3 +479,4 @@ static void __init s5p_pwm_clocksource_init(struct device_node *np) samsung_pwm_alloc(np, &s5p_variant); } CLOCKSOURCE_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init); +#endif diff --git a/include/clocksource/samsung_pwm.h b/include/clocksource/samsung_pwm.h index b1d8fe7..5c449c8 100644 --- a/include/clocksource/samsung_pwm.h +++ b/include/clocksource/samsung_pwm.h @@ -30,4 +30,7 @@ struct samsung_pwm_variant { bool has_tint_cstat; }; +void samsung_pwm_clocksource_init(void __iomem *base, + unsigned int *irqs, struct samsung_pwm_variant *variant); + #endif /* __CLOCKSOURCE_SAMSUNG_PWM_H */ -- cgit v0.10.2 From e9b852b8a7bc0217a03afff07fad34093e087542 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:28 +0200 Subject: clocksource: samsung_pwm_timer: Use proper clockevents max_delta This patch replaces hardcoded -1 argument passed to clockevents_config_and_register() with tcnt_max calculated based on variant data. This fixes invalid max delta configuration for 16-bit timers of s3c24xx. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 9f4bd6a..cb86615 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -263,7 +263,8 @@ static void __init samsung_clockevent_init(void) pwm.clock_count_per_tick = clock_rate / HZ; time_event_device.cpumask = cpumask_of(0); - clockevents_config_and_register(&time_event_device, clock_rate, 1, -1); + clockevents_config_and_register(&time_event_device, + clock_rate, 1, pwm.tcnt_max); irq_number = pwm.irq[pwm.event_id]; setup_irq(irq_number, &samsung_clock_event_irq); -- cgit v0.10.2 From 6fe4dfd041dadbc1cc2460ed8680f2734dc3dc95 Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:29 +0200 Subject: clocksource: samsung_pwm_timer: Correct programming of clock events In current code, the tick count value programmed to the hardware is always decremented by one. This is reasonable for periodic mode, since there is one extra tick between 0 and COUNT (after reloading), but it makes oneshot events happen 1 tick earlier than requested, because the interrupt is triggered on transition from 1 to 0. This patch removes the decrementation from PWM channel setup code and moves it instead to periodic timer setup, to make both periodic and oneshot modes work correctly. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index cb86615..92b2f13 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -138,8 +138,6 @@ static void samsung_time_setup(unsigned int channel, unsigned long tcnt) tcon = __raw_readl(pwm.base + REG_TCON); - tcnt--; - tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan)); tcon |= TCON_MANUALUPDATE(tcon_chan); @@ -187,7 +185,7 @@ static int samsung_set_next_event(unsigned long cycles, static void samsung_timer_resume(void) { /* event timer restart */ - samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick); + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); samsung_time_start(pwm.event_id, true); /* source timer restart */ @@ -202,7 +200,7 @@ static void samsung_set_mode(enum clock_event_mode mode, switch (mode) { case CLOCK_EVT_MODE_PERIODIC: - samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick); + samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1); samsung_time_start(pwm.event_id, true); break; -- cgit v0.10.2 From 81d4f7bfdc9417e7d8fc1133c762daa4458eec5e Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:30 +0200 Subject: clocksource: samsung_pwm_timer: Work around rounding errors in clockevents core Due to rounding errors in clockevents core (in conversions between ticks and nsecs), it might happen that the set_next_event callback gets called with cycles = 0, causing the code to incorrectly program the PWM timer. This patch modifies the callback to program the timer for 1 tick, if received tick count value is 0. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 92b2f13..0234c8d 100644 --- a/drivers/clocksource/samsung_pwm_timer.c +++ b/drivers/clocksource/samsung_pwm_timer.c @@ -176,6 +176,19 @@ static void samsung_time_start(unsigned int channel, bool periodic) static int samsung_set_next_event(unsigned long cycles, struct clock_event_device *evt) { + /* + * This check is needed to account for internal rounding + * errors inside clockevents core, which might result in + * passing cycles = 0, which in turn would not generate any + * timer interrupt and hang the system. + * + * Another solution would be to set up the clockevent device + * with min_delta = 2, but this would unnecessarily increase + * the minimum sleep period. + */ + if (!cycles) + cycles = 1; + samsung_time_setup(pwm.event_id, cycles); samsung_time_start(pwm.event_id, false); -- cgit v0.10.2 From 278c800ec4bfcf26b76077e96f155c5b956bd1ad Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:31 +0200 Subject: ARM: SAMSUNG: Do not register legacy timer interrupts on Exynos This patch removes legacy PWM timer interrupt initialization from exynos{4,5}_init_irq() functions, since it conflicts with internal interrupt handling of the new PWM clocksource driver. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index d63d399..8abf2b6 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -456,13 +456,6 @@ void __init exynos4_init_irq(void) if (!of_have_populated_dt()) combiner_init(S5P_VA_COMBINER_BASE, NULL); - - /* - * The parameters of s5p_init_irq() are for VIC init. - * Theses parameters should be NULL and 0 because EXYNOS4 - * uses GIC instead of VIC. - */ - s5p_init_irq(NULL, 0); } void __init exynos5_init_irq(void) @@ -470,14 +463,6 @@ void __init exynos5_init_irq(void) #ifdef CONFIG_OF irqchip_init(); #endif - /* - * The parameters of s5p_init_irq() are for VIC init. - * Theses parameters should be NULL and 0 because EXYNOS4 - * uses GIC instead of VIC. - */ - if (!of_machine_is_compatible("samsung,exynos5440")) - s5p_init_irq(NULL, 0); - gic_arch_extn.irq_set_wake = s3c_irq_wake; } diff --git a/arch/arm/plat-samsung/Kconfig b/arch/arm/plat-samsung/Kconfig index a9d52167..2e1b190 100644 --- a/arch/arm/plat-samsung/Kconfig +++ b/arch/arm/plat-samsung/Kconfig @@ -101,9 +101,9 @@ config SAMSUNG_IRQ_VIC_TIMER Internal configuration to build the VIC timer interrupt code. config S5P_IRQ - def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS) + def_bool (ARCH_S5P64X0 || ARCH_S5PC100 || ARCH_S5PV210) help - Support common interrup part for ARCH_S5P and ARCH_EXYNOS SoCs + Support common interrupt part for ARCH_S5P SoCs config S5P_EXT_INT bool -- cgit v0.10.2 From cc4193eaca1b3bed1da5bed18742386b21295b9a Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:33 +0200 Subject: ARM: dts: exynos4: Add node for PWM device This patch adds device tree node for PWM block present on Exynos 4 SoCs. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/arch/arm/boot/dts/exynos4.dtsi b/arch/arm/boot/dts/exynos4.dtsi index e1347fc..4b86665 100644 --- a/arch/arm/boot/dts/exynos4.dtsi +++ b/arch/arm/boot/dts/exynos4.dtsi @@ -264,6 +264,14 @@ status = "disabled"; }; + pwm@139D0000 { + compatible = "samsung,exynos4210-pwm"; + reg = <0x139D0000 0x1000>; + interrupts = <0 37 0>, <0 38 0>, <0 39 0>, <0 40 0>, <0 41 0>; + #pwm-cells = <2>; + status = "disabled"; + }; + amba { #address-cells = <1>; #size-cells = <1>; -- cgit v0.10.2 From 765d012c2389cc0481f04dcccd3949f1ba0969ba Mon Sep 17 00:00:00 2001 From: Tomasz Figa Date: Tue, 23 Apr 2013 17:46:34 +0200 Subject: ARM: dts: exynos4210: Add basic dts file for universal_c210 board This patch adds basic device tree sources for Universal C210 board. Currently support includes: - eMMC - serial - max8952 and max8998 voltage regulators. - gpio-keys More support will be added in further patches. Signed-off-by: Tomasz Figa Signed-off-by: Kyungmin Park Reviewed-by: Arnd Bergmann Acked-by: Kukjin Kim Signed-off-by: Olof Johansson diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile index 9c62558..ff48893 100644 --- a/arch/arm/boot/dts/Makefile +++ b/arch/arm/boot/dts/Makefile @@ -42,6 +42,7 @@ dtb-$(CONFIG_ARCH_DOVE) += dove-cm-a510.dtb \ dtb-$(CONFIG_ARCH_EXYNOS) += exynos4210-origen.dtb \ exynos4210-smdkv310.dtb \ exynos4210-trats.dtb \ + exynos4210-universal_c210.dtb \ exynos4412-smdk4412.dtb \ exynos5250-smdk5250.dtb \ exynos5250-snow.dtb \ -- cgit v0.10.2 From 0682edaaa32c778ad2efac73fe3c8d9309e35991 Mon Sep 17 00:00:00 2001 From: Olof Johansson Date: Mon, 29 Apr 2013 10:28:57 -0700 Subject: ARM: dts: exynops4210: really add universal_c210 dts I fumbled when resolving a merge conflict on application of commit 765d012c2389 (ARM: dts: exynos4210: Add basic dts file for universal_c210 board), and left out the dts source file. Here it is. Signed-off-by: Olof Johansson diff --git a/arch/arm/boot/dts/exynos4210-universal_c210.dts b/arch/arm/boot/dts/exynos4210-universal_c210.dts new file mode 100644 index 0000000..345cdb5 --- /dev/null +++ b/arch/arm/boot/dts/exynos4210-universal_c210.dts @@ -0,0 +1,352 @@ +/* + * Samsung's Exynos4210 based Universal C210 board device tree source + * + * Copyright (c) 2012-2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Device tree source file for Samsung's Universal C210 board which is based on + * Samsung's Exynos4210 rev0 SoC. + * + * 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. +*/ + +/dts-v1/; +/include/ "exynos4210.dtsi" + +/ { + model = "Samsung Universal C210 based on Exynos4210 rev0"; + compatible = "samsung,universal_c210", "samsung,exynos4210"; + + memory { + reg = <0x40000000 0x10000000 + 0x50000000 0x10000000>; + }; + + chosen { + bootargs = "console=ttySAC2,115200N8 root=/dev/mmcblk0p5 rw rootwait earlyprintk panic=5 maxcpus=1"; + }; + + mct@10050000 { + compatible = "none"; + }; + + fixed-rate-clocks { + xxti { + compatible = "samsung,clock-xxti"; + clock-frequency = <0>; + }; + + xusbxti { + compatible = "samsung,clock-xusbxti"; + clock-frequency = <24000000>; + }; + }; + + vemmc_reg: voltage-regulator { + compatible = "regulator-fixed"; + regulator-name = "VMEM_VDD_2_8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + gpio = <&gpe1 3 0>; + enable-active-high; + }; + + sdhci_emmc: sdhci@12510000 { + bus-width = <8>; + non-removable; + pinctrl-0 = <&sd0_clk &sd0_cmd &sd0_bus8>; + pinctrl-names = "default"; + vmmc-supply = <&vemmc_reg>; + status = "okay"; + }; + + serial@13800000 { + status = "okay"; + }; + + serial@13810000 { + status = "okay"; + }; + + serial@13820000 { + status = "okay"; + }; + + serial@13830000 { + status = "okay"; + }; + + gpio-keys { + compatible = "gpio-keys"; + + vol-up-key { + gpios = <&gpx2 0 1>; + linux,code = <115>; + label = "volume up"; + debounce-interval = <1>; + }; + + vol-down-key { + gpios = <&gpx2 1 1>; + linux,code = <114>; + label = "volume down"; + debounce-interval = <1>; + }; + + config-key { + gpios = <&gpx2 2 1>; + linux,code = <171>; + label = "config"; + debounce-interval = <1>; + gpio-key,wakeup; + }; + + camera-key { + gpios = <&gpx2 3 1>; + linux,code = <212>; + label = "camera"; + debounce-interval = <1>; + }; + + power-key { + gpios = <&gpx2 7 1>; + linux,code = <116>; + label = "power"; + debounce-interval = <1>; + gpio-key,wakeup; + }; + + ok-key { + gpios = <&gpx3 5 1>; + linux,code = <352>; + label = "ok"; + debounce-interval = <1>; + }; + }; + + tsp_reg: voltage-regulator { + compatible = "regulator-fixed"; + regulator-name = "TSP_2_8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + gpio = <&gpe2 3 0>; + enable-active-high; + }; + + i2c@13890000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-slave-addr = <0x10>; + samsung,i2c-max-bus-freq = <100000>; + pinctrl-0 = <&i2c3_bus>; + pinctrl-names = "default"; + status = "okay"; + + tsp@4a { + /* TBD: Atmel maXtouch touchscreen */ + reg = <0x4a>; + }; + }; + + i2c@138B0000 { + samsung,i2c-sda-delay = <100>; + samsung,i2c-slave-addr = <0x10>; + samsung,i2c-max-bus-freq = <100000>; + pinctrl-0 = <&i2c5_bus>; + pinctrl-names = "default"; + status = "okay"; + + vdd_arm_reg: pmic@60 { + compatible = "maxim,max8952"; + reg = <0x60>; + + max8952,vid-gpios = <&gpx0 3 0>, <&gpx0 4 0>; + max8952,default-mode = <0>; + max8952,dvs-mode-microvolt = <1250000>, <1200000>, + <1050000>, <950000>; + max8952,sync-freq = <0>; + max8952,ramp-speed = <0>; + + regulator-name = "vdd_arm"; + regulator-min-microvolt = <770000>; + regulator-max-microvolt = <1400000>; + regulator-always-on; + regulator-boot-on; + }; + + pmic@66 { + compatible = "national,lp3974"; + reg = <0x66>; + + max8998,pmic-buck1-default-dvs-idx = <0>; + max8998,pmic-buck1-dvs-gpios = <&gpx0 5 0>, + <&gpx0 6 0>; + max8998,pmic-buck1-dvs-voltage = <1100000>, <1000000>, + <1100000>, <1000000>; + + max8998,pmic-buck2-default-dvs-idx = <0>; + max8998,pmic-buck2-dvs-gpio = <&gpe2 0 0>; + max8998,pmic-buck2-dvs-voltage = <1200000>, <1100000>; + + regulators { + ldo2_reg: LDO2 { + regulator-name = "VALIVE_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ldo3_reg: LDO3 { + regulator-name = "VUSB+MIPI_1.1V"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + }; + + ldo4_reg: LDO4 { + regulator-name = "VADC_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo5_reg: LDO5 { + regulator-name = "VTF_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo6_reg: LDO6 { + regulator-name = "LDO6"; + regulator-min-microvolt = <2000000>; + regulator-max-microvolt = <2000000>; + }; + + ldo7_reg: LDO7 { + regulator-name = "VLCD+VMIPI_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + ldo8_reg: LDO8 { + regulator-name = "VUSB+VDAC_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo9_reg: LDO9 { + regulator-name = "VCC_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + regulator-always-on; + }; + + ldo10_reg: LDO10 { + regulator-name = "VPLL_1.1V"; + regulator-min-microvolt = <1100000>; + regulator-max-microvolt = <1100000>; + regulator-boot-on; + regulator-always-on; + }; + + ldo11_reg: LDO11 { + regulator-name = "CAM_AF_3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + }; + + ldo12_reg: LDO12 { + regulator-name = "PS_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo13_reg: LDO13 { + regulator-name = "VHIC_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + ldo14_reg: LDO14 { + regulator-name = "CAM_I_HOST_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + }; + + ldo15_reg: LDO15 { + regulator-name = "CAM_S_DIG+FM33_CORE_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + }; + + ldo16_reg: LDO16 { + regulator-name = "CAM_S_ANA_2.8V"; + regulator-min-microvolt = <2800000>; + regulator-max-microvolt = <2800000>; + }; + + ldo17_reg: LDO17 { + regulator-name = "VCC_3.0V_LCD"; + regulator-min-microvolt = <3000000>; + regulator-max-microvolt = <3000000>; + }; + + buck1_reg: BUCK1 { + regulator-name = "VINT_1.1V"; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1500000>; + regulator-boot-on; + regulator-always-on; + }; + + buck2_reg: BUCK2 { + regulator-name = "VG3D_1.1V"; + regulator-min-microvolt = <750000>; + regulator-max-microvolt = <1500000>; + regulator-boot-on; + }; + + buck3_reg: BUCK3 { + regulator-name = "VCC_1.8V"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + regulator-always-on; + }; + + buck4_reg: BUCK4 { + regulator-name = "VMEM_1.2V"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1200000>; + regulator-always-on; + }; + + ap32khz_reg: EN32KHz-AP { + regulator-name = "32KHz AP"; + regulator-always-on; + }; + + cp32khz_reg: EN32KHz-CP { + regulator-name = "32KHz CP"; + }; + + vichg_reg: ENVICHG { + regulator-name = "VICHG"; + }; + + safeout1_reg: ESAFEOUT1 { + regulator-name = "SAFEOUT1"; + regulator-always-on; + }; + + safeout2_reg: ESAFEOUT2 { + regulator-name = "SAFEOUT2"; + regulator-boot-on; + }; + }; + }; + }; + + pwm@139D0000 { + compatible = "samsung,s5p6440-pwm"; + status = "okay"; + }; +}; -- cgit v0.10.2