From 25e56eba0ae783fc5b66d50c68826f276e8bd8c6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 10 Apr 2013 11:31:44 +0200 Subject: clk: exynos: prepare for multiplatform The new common clock drivers for exynos are using compile time constants and soc_is_exynos* macros to provide backwards compatibility for pre-DT systems, which is not possible with multiplatform kernels. This moves all the necessary information back into platform code and removes the mach/* header inclusions. Signed-off-by: Arnd Bergmann Cc: Mike Turquette --- drivers/clk/samsung/clk-exynos4.c | 93 ++++++++++++++++-------------------- drivers/clk/samsung/clk-exynos5250.c | 1 - drivers/clk/samsung/clk-exynos5440.c | 1 - drivers/clk/samsung/clk.h | 2 - 4 files changed, 42 insertions(+), 55 deletions(-) (limited to 'drivers') diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c index 71046694d9dd..d0940e69d034 100644 --- a/drivers/clk/samsung/clk-exynos4.c +++ b/drivers/clk/samsung/clk-exynos4.c @@ -16,7 +16,6 @@ #include #include -#include #include "clk.h" #include "clk-pll.h" @@ -910,16 +909,6 @@ struct samsung_gate_clock exynos4x12_gate_clks[] __initdata = { CLK_IGNORE_UNUSED, 0), }; -#ifdef CONFIG_OF -static struct of_device_id exynos4_clk_ids[] __initdata = { - { .compatible = "samsung,exynos4210-clock", - .data = (void *)EXYNOS4210, }, - { .compatible = "samsung,exynos4412-clock", - .data = (void *)EXYNOS4X12, }, - { }, -}; -#endif - /* * The parent of the fin_pll clock is selected by the XOM[0] bit. This bit * resides in chipid register space, outside of the clock controller memory @@ -927,33 +916,40 @@ static struct of_device_id exynos4_clk_ids[] __initdata = { * controller is first remapped and the value of XOM[0] bit is read to * determine the parent clock. */ -static void __init exynos4_clk_register_finpll(void) +static unsigned long exynos4_get_xom(void) { - struct samsung_fixed_rate_clock fclk; + unsigned long xom = 0; + void __iomem *chipid_base; struct device_node *np; - struct clk *clk; - void __iomem *chipid_base = S5P_VA_CHIPID; - unsigned long xom, finpll_f = 24000000; - char *parent_name; np = of_find_compatible_node(NULL, NULL, "samsung,exynos4210-chipid"); - if (np) + if (np) { chipid_base = of_iomap(np, 0); - if (chipid_base) { - xom = readl(chipid_base + 8); - parent_name = xom & 1 ? "xusbxti" : "xxti"; - clk = clk_get(NULL, parent_name); - if (IS_ERR(clk)) { - pr_err("%s: failed to lookup parent clock %s, assuming " - "fin_pll clock frequency is 24MHz\n", __func__, - parent_name); - } else { - finpll_f = clk_get_rate(clk); - } + if (chipid_base) + xom = readl(chipid_base + 8); + + iounmap(chipid_base); + } + + return xom; +} + +static void __init exynos4_clk_register_finpll(unsigned long xom) +{ + struct samsung_fixed_rate_clock fclk; + struct clk *clk; + unsigned long finpll_f = 24000000; + char *parent_name; + + parent_name = xom & 1 ? "xusbxti" : "xxti"; + clk = clk_get(NULL, parent_name); + if (IS_ERR(clk)) { + pr_err("%s: failed to lookup parent clock %s, assuming " + "fin_pll clock frequency is 24MHz\n", __func__, + parent_name); } else { - pr_err("%s: failed to map chipid registers, assuming " - "fin_pll clock frequency is 24MHz\n", __func__); + finpll_f = clk_get_rate(clk); } fclk.id = fin_pll; @@ -963,8 +959,6 @@ static void __init exynos4_clk_register_finpll(void) fclk.fixed_rate = finpll_f; samsung_clk_register_fixed_rate(&fclk, 1); - if (np) - iounmap(chipid_base); } /* @@ -988,28 +982,14 @@ static __initdata struct of_device_id ext_clk_match[] = { }; /* register exynos4 clocks */ -void __init exynos4_clk_init(struct device_node *np) +void __init exynos4_clk_init(struct device_node *np, enum exynos4_soc exynos4_soc, void __iomem *reg_base, unsigned long xom) { - void __iomem *reg_base; struct clk *apll, *mpll, *epll, *vpll; - u32 exynos4_soc; if (np) { - const struct of_device_id *match; - match = of_match_node(exynos4_clk_ids, np); - exynos4_soc = (u32)match->data; - reg_base = of_iomap(np, 0); if (!reg_base) panic("%s: failed to map registers\n", __func__); - } else { - reg_base = S5P_VA_CMU; - if (soc_is_exynos4210()) - exynos4_soc = EXYNOS4210; - else if (soc_is_exynos4212() || soc_is_exynos4412()) - exynos4_soc = EXYNOS4X12; - else - panic("%s: unable to determine soc\n", __func__); } if (exynos4_soc == EXYNOS4210) @@ -1026,7 +1006,7 @@ void __init exynos4_clk_init(struct device_node *np) ARRAY_SIZE(exynos4_fixed_rate_ext_clks), ext_clk_match); - exynos4_clk_register_finpll(); + exynos4_clk_register_finpll(xom); if (exynos4_soc == EXYNOS4210) { apll = samsung_clk_register_pll45xx("fout_apll", "fin_pll", @@ -1087,5 +1067,16 @@ void __init exynos4_clk_init(struct device_node *np) _get_rate("sclk_epll"), _get_rate("sclk_vpll"), _get_rate("arm_clk")); } -CLK_OF_DECLARE(exynos4210_clk, "samsung,exynos4210-clock", exynos4_clk_init); -CLK_OF_DECLARE(exynos4412_clk, "samsung,exynos4412-clock", exynos4_clk_init); + + +static void __init exynos4210_clk_init(struct device_node *np) +{ + exynos4_clk_init(np, EXYNOS4210, NULL, exynos4_get_xom()); +} +CLK_OF_DECLARE(exynos4210_clk, "samsung,exynos4210-clock", exynos4210_clk_init); + +static void __init exynos4412_clk_init(struct device_node *np) +{ + exynos4_clk_init(np, EXYNOS4X12, NULL, exynos4_get_xom()); +} +CLK_OF_DECLARE(exynos4412_clk, "samsung,exynos4412-clock", exynos4412_clk_init); diff --git a/drivers/clk/samsung/clk-exynos5250.c b/drivers/clk/samsung/clk-exynos5250.c index 7290faa518d2..61068cda2ab3 100644 --- a/drivers/clk/samsung/clk-exynos5250.c +++ b/drivers/clk/samsung/clk-exynos5250.c @@ -16,7 +16,6 @@ #include #include -#include #include "clk.h" #include "clk-pll.h" diff --git a/drivers/clk/samsung/clk-exynos5440.c b/drivers/clk/samsung/clk-exynos5440.c index a0a094c06f19..7d5434167a96 100644 --- a/drivers/clk/samsung/clk-exynos5440.c +++ b/drivers/clk/samsung/clk-exynos5440.c @@ -15,7 +15,6 @@ #include #include -#include #include "clk.h" #include "clk-pll.h" diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h index 10b2111f0c0f..e4ad6ea9aa76 100644 --- a/drivers/clk/samsung/clk.h +++ b/drivers/clk/samsung/clk.h @@ -20,8 +20,6 @@ #include #include -#include - /** * struct samsung_clock_alias: information about mux clock * @id: platform specific id of the clock. -- cgit v1.2.3 From 034c097ca27fb163754ee4f4e26f85559bece69b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 10 Apr 2013 11:35:29 +0200 Subject: clocksource: exynos_mct: remove platform header dependency For the non-DT case, the mct_init() function requires access to a couple of platform specific constants, but cannot include the header files in case we are building for multiplatform. This changes the interface to the platform so we pass all the necessary data as arguments to mct_init. Signed-off-by: Arnd Bergmann Cc: Thomas Gleixner Cc: John Stultz --- arch/arm/mach-exynos/common.c | 2 +- arch/arm/mach-exynos/common.h | 2 +- drivers/clocksource/exynos_mct.c | 21 ++++++--------------- 3 files changed, 8 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index c3167b741180..a453991ce90b 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -416,7 +416,7 @@ void __init exynos_init_time(void) exynos4_clk_init(NULL, !soc_is_exynos4210(), S5P_VA_CMU, readl(S5P_VA_CHIPID + 8) & 1); exynos4_clk_register_fixed_ext(xxti_f, xusbxti_f); #endif - mct_init(); + mct_init(S5P_VA_SYSTIMER, EXYNOS4_IRQ_MCT_G0, EXYNOS4_IRQ_MCT_L0, EXYNOS4_IRQ_MCT_L1); } } diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 4ed8eef2b096..9717d0f6088f 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -14,7 +14,7 @@ #include -extern void mct_init(void); +void mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1); void exynos_init_time(void); extern unsigned long xxti_f, xusbxti_f; diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c index 661026834b23..a6ca0fb06939 100644 --- a/drivers/clocksource/exynos_mct.c +++ b/drivers/clocksource/exynos_mct.c @@ -26,11 +26,6 @@ #include #include - -#include - -#include -#include #include #define EXYNOS4_MCTREG(x) (x) @@ -511,18 +506,14 @@ static void __init exynos4_timer_resources(struct device_node *np, void __iomem #endif /* CONFIG_LOCAL_TIMERS */ } -void __init mct_init(void) +void __init mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1) { - if (soc_is_exynos4210()) { - mct_irqs[MCT_G0_IRQ] = EXYNOS4_IRQ_MCT_G0; - mct_irqs[MCT_L0_IRQ] = EXYNOS4_IRQ_MCT_L0; - mct_irqs[MCT_L1_IRQ] = EXYNOS4_IRQ_MCT_L1; - mct_int_type = MCT_INT_SPI; - } else { - panic("unable to determine mct controller type\n"); - } + mct_irqs[MCT_G0_IRQ] = irq_g0; + mct_irqs[MCT_L0_IRQ] = irq_l0; + mct_irqs[MCT_L1_IRQ] = irq_l1; + mct_int_type = MCT_INT_SPI; - exynos4_timer_resources(NULL, S5P_VA_SYSTIMER); + exynos4_timer_resources(NULL, base); exynos4_clocksource_init(); exynos4_clockevent_init(); } -- cgit v1.2.3 From 6761dcfe8c42b55076753bc8bea7b5dcbfb445c0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 10 Apr 2013 15:17:47 +0200 Subject: irqchip: exynos: pass max combiner number to combiner_init We can find out the number of combined IRQs from the device tree, but in case of ATAGS boot, the driver currently uses hardcoded values based on the SoC type. We can't do that in general for a multiplatform kernel, so let's instead pass this information from platform code directly in case of ATAGS boot. Signed-off-by: Arnd Bergmann Cc: Thomas Gleixner --- arch/arm/mach-exynos/common.c | 15 ++++++++++++- arch/arm/mach-exynos/common.h | 3 ++- drivers/irqchip/exynos-combiner.c | 46 ++++++++++++++------------------------- 3 files changed, 32 insertions(+), 32 deletions(-) (limited to 'drivers') diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index a453991ce90b..368fa4b01e7c 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -420,6 +420,19 @@ void __init exynos_init_time(void) } } +static unsigned int max_combiner_nr(void) +{ + if (soc_is_exynos5250()) + return EXYNOS5_MAX_COMBINER_NR; + else if (soc_is_exynos4412()) + return EXYNOS4412_MAX_COMBINER_NR; + else if (soc_is_exynos4212()) + return EXYNOS4212_MAX_COMBINER_NR; + else + return EXYNOS4210_MAX_COMBINER_NR; +} + + void __init exynos4_init_irq(void) { unsigned int gic_bank_offset; @@ -434,7 +447,7 @@ void __init exynos4_init_irq(void) #endif if (!of_have_populated_dt()) - combiner_init(S5P_VA_COMBINER_BASE, NULL); + combiner_init(S5P_VA_COMBINER_BASE, NULL, max_combiner_nr()); /* * The parameters of s5p_init_irq() are for VIC init. diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 9717d0f6088f..4ba8cbecc144 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -69,7 +69,8 @@ void exynos4212_register_clocks(void); #endif struct device_node; -void combiner_init(void __iomem *combiner_base, struct device_node *np); +void combiner_init(void __iomem *combiner_base, struct device_node *np, + unsigned int max_nr); extern struct smp_operations exynos_smp_ops; diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index e8501dbaa0b7..d8683836ee1e 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -25,6 +25,8 @@ #define COMBINER_ENABLE_CLEAR 0x4 #define COMBINER_INT_STATUS 0xC +#define IRQ_IN_COMBINER 8 + static DEFINE_SPINLOCK(irq_controller_lock); struct combiner_chip_data { @@ -112,23 +114,9 @@ static struct irq_chip combiner_chip = { #endif }; -static unsigned int max_combiner_nr(void) -{ - if (soc_is_exynos5250()) - return EXYNOS5_MAX_COMBINER_NR; - else if (soc_is_exynos4412()) - return EXYNOS4412_MAX_COMBINER_NR; - else if (soc_is_exynos4212()) - return EXYNOS4212_MAX_COMBINER_NR; - else - return EXYNOS4210_MAX_COMBINER_NR; -} - static void __init combiner_cascade_irq(unsigned int combiner_nr, unsigned int irq) { - if (combiner_nr >= max_combiner_nr()) - BUG(); if (irq_set_handler_data(irq, &combiner_data[combiner_nr]) != 0) BUG(); irq_set_chained_handler(irq, combiner_handle_cascade_irq); @@ -139,7 +127,7 @@ static void __init combiner_init_one(unsigned int combiner_nr, { combiner_data[combiner_nr].base = base; combiner_data[combiner_nr].irq_offset = irq_find_mapping( - combiner_irq_domain, combiner_nr * MAX_IRQ_IN_COMBINER); + combiner_irq_domain, combiner_nr * IRQ_IN_COMBINER); combiner_data[combiner_nr].irq_mask = 0xff << ((combiner_nr % 4) << 3); combiner_data[combiner_nr].parent_irq = irq; @@ -161,7 +149,7 @@ static int combiner_irq_domain_xlate(struct irq_domain *d, if (intsize < 2) return -EINVAL; - *out_hwirq = intspec[0] * MAX_IRQ_IN_COMBINER + intspec[1]; + *out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1]; *out_type = 0; return 0; @@ -209,22 +197,13 @@ static unsigned int exynos4x12_combiner_extra_irq(int group) } void __init combiner_init(void __iomem *combiner_base, - struct device_node *np) + struct device_node *np, + unsigned int max_nr) { int i, irq, irq_base; - unsigned int max_nr, nr_irq; + unsigned int nr_irq; - max_nr = max_combiner_nr(); - - if (np) { - if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { - pr_info("%s: number of combiners not specified, " - "setting default as %d.\n", - __func__, max_nr); - } - } - - nr_irq = max_nr * MAX_IRQ_IN_COMBINER; + nr_irq = max_nr * IRQ_IN_COMBINER; irq_base = irq_alloc_descs(COMBINER_IRQ(0, 0), 1, nr_irq, 0); if (IS_ERR_VALUE(irq_base)) { @@ -258,6 +237,7 @@ static int __init combiner_of_init(struct device_node *np, struct device_node *parent) { void __iomem *combiner_base; + unsigned int max_nr = 20; combiner_base = of_iomap(np, 0); if (!combiner_base) { @@ -265,7 +245,13 @@ static int __init combiner_of_init(struct device_node *np, return -ENXIO; } - combiner_init(combiner_base, np); + if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) { + pr_info("%s: number of combiners not specified, " + "setting default as %d.\n", + __func__, max_nr); + } + + combiner_init(combiner_base, np, max_nr); return 0; } -- cgit v1.2.3 From d34f03d4a1e4e56f5944186c2e74cbed58b27090 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 10 Apr 2013 15:31:11 +0200 Subject: irqchip: exynos: allocate combiner_data dynamically The number of combiners on a given SoC is a platform specific constant, and we cannot encode this number on a multiplatform kernel since the header file defining it is not available. Allocating the structure dynamically ends up cleaner anyway since we keep all the data local. Signed-off-by: Arnd Bergmann Cc: Thomas Gleixner --- drivers/irqchip/exynos-combiner.c | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index d8683836ee1e..7fcdeee869ce 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -37,7 +38,6 @@ struct combiner_chip_data { }; static struct irq_domain *combiner_irq_domain; -static struct combiner_chip_data combiner_data[MAX_COMBINER_NR]; static inline void __iomem *combiner_base(struct irq_data *data) { @@ -114,26 +114,26 @@ static struct irq_chip combiner_chip = { #endif }; -static void __init combiner_cascade_irq(unsigned int combiner_nr, +static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data, unsigned int irq) { - if (irq_set_handler_data(irq, &combiner_data[combiner_nr]) != 0) + if (irq_set_handler_data(irq, combiner_data) != 0) BUG(); irq_set_chained_handler(irq, combiner_handle_cascade_irq); } -static void __init combiner_init_one(unsigned int combiner_nr, +static void __init combiner_init_one(struct combiner_chip_data *combiner_data, + unsigned int combiner_nr, void __iomem *base, unsigned int irq) { - combiner_data[combiner_nr].base = base; - combiner_data[combiner_nr].irq_offset = irq_find_mapping( + combiner_data->base = base; + combiner_data->irq_offset = irq_find_mapping( combiner_irq_domain, combiner_nr * IRQ_IN_COMBINER); - combiner_data[combiner_nr].irq_mask = 0xff << ((combiner_nr % 4) << 3); - combiner_data[combiner_nr].parent_irq = irq; + combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3); + combiner_data->parent_irq = irq; /* Disable all interrupts */ - __raw_writel(combiner_data[combiner_nr].irq_mask, - base + COMBINER_ENABLE_CLEAR); + __raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR); } #ifdef CONFIG_OF @@ -168,6 +168,8 @@ static int combiner_irq_domain_xlate(struct irq_domain *d, static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) { + struct combiner_chip_data *combiner_data = d->host_data; + irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq); irq_set_chip_data(irq, &combiner_data[hw >> 3]); set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); @@ -202,6 +204,7 @@ void __init combiner_init(void __iomem *combiner_base, { int i, irq, irq_base; unsigned int nr_irq; + struct combiner_chip_data *combiner_data; nr_irq = max_nr * IRQ_IN_COMBINER; @@ -211,8 +214,14 @@ void __init combiner_init(void __iomem *combiner_base, pr_warning("%s: irq desc alloc failed. Continuing with %d as linux irq base\n", __func__, irq_base); } + combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL); + if (!combiner_data) { + pr_warning("%s: could not allocate combiner data\n", __func__); + return; + } + combiner_irq_domain = irq_domain_add_legacy(np, nr_irq, irq_base, 0, - &combiner_irq_domain_ops, &combiner_data); + &combiner_irq_domain_ops, combiner_data); if (WARN_ON(!combiner_irq_domain)) { pr_warning("%s: irq domain init failed\n", __func__); return; @@ -227,8 +236,9 @@ void __init combiner_init(void __iomem *combiner_base, if (np) irq = irq_of_parse_and_map(np, i); #endif - combiner_init_one(i, combiner_base + (i >> 2) * 0x10, irq); - combiner_cascade_irq(i, irq); + combiner_init_one(&combiner_data[i], i, + combiner_base + (i >> 2) * 0x10, irq); + combiner_cascade_irq(&combiner_data[i], irq); } } -- cgit v1.2.3 From 92c8e4962054a6cf5171b3d7a3a77b799ca62c10 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 10 Apr 2013 15:59:58 +0200 Subject: irqchip: exynos: localize irq lookup for ATAGS The IRQ_SPI() macro is not available in the driver when building with sparse IRQs or multiplatform, so let's move all users of this into one function that we can leave out when building DT-only. Signed-off-by: Arnd Bergmann Cc: Thomas Gleixner --- drivers/irqchip/exynos-combiner.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index 7fcdeee869ce..acb9c74b070a 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -18,7 +18,9 @@ #include #include +#ifdef CONFIG_EXYNOS_ATAGS #include +#endif #include "irqchip.h" @@ -182,8 +184,12 @@ static struct irq_domain_ops combiner_irq_domain_ops = { .map = combiner_irq_domain_map, }; -static unsigned int exynos4x12_combiner_extra_irq(int group) +static unsigned int combiner_lookup_irq(int group) { +#ifdef CONFIG_EXYNOS_ATAGS + if (group < EXYNOS4210_MAX_COMBINER_NR || soc_is_exynos5250()) + return IRQ_SPI(group); + switch (group) { case 16: return IRQ_SPI(107); @@ -193,9 +199,9 @@ static unsigned int exynos4x12_combiner_extra_irq(int group) return IRQ_SPI(48); case 19: return IRQ_SPI(42); - default: - return 0; } +#endif + return 0; } void __init combiner_init(void __iomem *combiner_base, @@ -228,14 +234,13 @@ void __init combiner_init(void __iomem *combiner_base, } for (i = 0; i < max_nr; i++) { - if (i < EXYNOS4210_MAX_COMBINER_NR || soc_is_exynos5250()) - irq = IRQ_SPI(i); - else - irq = exynos4x12_combiner_extra_irq(i); #ifdef CONFIG_OF if (np) irq = irq_of_parse_and_map(np, i); + else #endif + irq = combiner_lookup_irq(i); + combiner_init_one(&combiner_data[i], i, combiner_base + (i >> 2) * 0x10, irq); combiner_cascade_irq(&combiner_data[i], irq); -- cgit v1.2.3 From 863a08dc8bc7ce32ecc9136671610a93a0dd68b1 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 12 Apr 2013 15:27:09 +0200 Subject: irqchip: exynos: pass irq_base from platform The platform code knows the IRQ base, while the irqchip driver should really not. This is a littly hacky because we still hardwire the IRQ base to 160 for the combiner in the DT case, when we should really use -1. Removing that line will cause a linear IRQ domain to be use, as we should. Signed-off-by: Arnd Bergmann Cc: Thomas Gleixner --- arch/arm/mach-exynos/common.c | 3 ++- arch/arm/mach-exynos/common.h | 2 +- drivers/irqchip/exynos-combiner.c | 23 +++++++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) (limited to 'drivers') diff --git a/arch/arm/mach-exynos/common.c b/arch/arm/mach-exynos/common.c index 368fa4b01e7c..9208079d5d52 100644 --- a/arch/arm/mach-exynos/common.c +++ b/arch/arm/mach-exynos/common.c @@ -447,7 +447,8 @@ void __init exynos4_init_irq(void) #endif if (!of_have_populated_dt()) - combiner_init(S5P_VA_COMBINER_BASE, NULL, max_combiner_nr()); + combiner_init(S5P_VA_COMBINER_BASE, NULL, + max_combiner_nr(), COMBINER_IRQ(0, 0)); /* * The parameters of s5p_init_irq() are for VIC init. diff --git a/arch/arm/mach-exynos/common.h b/arch/arm/mach-exynos/common.h index 4ba8cbecc144..3e72d03a385e 100644 --- a/arch/arm/mach-exynos/common.h +++ b/arch/arm/mach-exynos/common.h @@ -70,7 +70,7 @@ void exynos4212_register_clocks(void); struct device_node; void combiner_init(void __iomem *combiner_base, struct device_node *np, - unsigned int max_nr); + unsigned int max_nr, int irq_base); extern struct smp_operations exynos_smp_ops; diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index acb9c74b070a..6855c92c2262 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -206,27 +206,22 @@ static unsigned int combiner_lookup_irq(int group) void __init combiner_init(void __iomem *combiner_base, struct device_node *np, - unsigned int max_nr) + unsigned int max_nr, + int irq_base) { - int i, irq, irq_base; + int i, irq; unsigned int nr_irq; struct combiner_chip_data *combiner_data; nr_irq = max_nr * IRQ_IN_COMBINER; - irq_base = irq_alloc_descs(COMBINER_IRQ(0, 0), 1, nr_irq, 0); - if (IS_ERR_VALUE(irq_base)) { - irq_base = COMBINER_IRQ(0, 0); - pr_warning("%s: irq desc alloc failed. Continuing with %d as linux irq base\n", __func__, irq_base); - } - combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL); if (!combiner_data) { pr_warning("%s: could not allocate combiner data\n", __func__); return; } - combiner_irq_domain = irq_domain_add_legacy(np, nr_irq, irq_base, 0, + combiner_irq_domain = irq_domain_add_simple(np, nr_irq, irq_base, &combiner_irq_domain_ops, combiner_data); if (WARN_ON(!combiner_irq_domain)) { pr_warning("%s: irq domain init failed\n", __func__); @@ -253,6 +248,7 @@ static int __init combiner_of_init(struct device_node *np, { void __iomem *combiner_base; unsigned int max_nr = 20; + int irq_base = -1; combiner_base = of_iomap(np, 0); if (!combiner_base) { @@ -266,7 +262,14 @@ static int __init combiner_of_init(struct device_node *np, __func__, max_nr); } - combiner_init(combiner_base, np, max_nr); + /* + * FIXME: This is a hardwired COMBINER_IRQ(0,0). Once all devices + * get their IRQ from DT, remove this in order to get dynamic + * allocation. + */ + irq_base = 160; + + combiner_init(combiner_base, np, max_nr, irq_base); return 0; } -- cgit v1.2.3 From 20adee8fa06ef69012bc277739e9e3762c78b7b7 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 18 Apr 2013 23:57:26 +0200 Subject: irqchip: exynos: look up irq using irq_find_mapping Since we want to move to using the linear IRQ domain in the future, we cannot rely on the irq numbers to be contiguous and need to look up the irq from the hwirq using the domain. This also turns the bogus comparison with NR_IRQ into a more meaningful check to see if the number has a valid mapping. Signed-off-by: Arnd Bergmann --- drivers/irqchip/exynos-combiner.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c index 6855c92c2262..494c2e21b538 100644 --- a/drivers/irqchip/exynos-combiner.c +++ b/drivers/irqchip/exynos-combiner.c @@ -33,7 +33,7 @@ static DEFINE_SPINLOCK(irq_controller_lock); struct combiner_chip_data { - unsigned int irq_offset; + unsigned int hwirq_offset; unsigned int irq_mask; void __iomem *base; unsigned int parent_irq; @@ -80,11 +80,11 @@ static void combiner_handle_cascade_irq(unsigned int irq, struct irq_desc *desc) if (status == 0) goto out; - combiner_irq = __ffs(status); + combiner_irq = chip_data->hwirq_offset + __ffs(status); + cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq); - cascade_irq = combiner_irq + (chip_data->irq_offset & ~31); - if (unlikely(cascade_irq >= NR_IRQS)) - do_bad_IRQ(cascade_irq, desc); + if (unlikely(!cascade_irq)) + do_bad_IRQ(irq, desc); else generic_handle_irq(cascade_irq); @@ -129,8 +129,7 @@ static void __init combiner_init_one(struct combiner_chip_data *combiner_data, void __iomem *base, unsigned int irq) { combiner_data->base = base; - combiner_data->irq_offset = irq_find_mapping( - combiner_irq_domain, combiner_nr * IRQ_IN_COMBINER); + combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER; combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3); combiner_data->parent_irq = irq; -- cgit v1.2.3 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 --- drivers/clocksource/Kconfig | 10 + drivers/clocksource/Makefile | 1 + drivers/clocksource/samsung_pwm_timer.c | 477 ++++++++++++++++++++++++++++++++ include/clocksource/samsung_pwm.h | 41 +++ 4 files changed, 529 insertions(+) create mode 100644 drivers/clocksource/samsung_pwm_timer.c create mode 100644 include/clocksource/samsung_pwm.h (limited to 'drivers') diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index e507ab7df60b..03caba2540e2 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 4d8283aec5b5..891c9f2af021 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 000000000000..1752457a4f76 --- /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 000000000000..eff8668da252 --- /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 v1.2.3 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 --- drivers/clocksource/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 03caba2540e2..ff5b6d87d65a 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 v1.2.3 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 --- drivers/clocksource/samsung_pwm_timer.c | 24 +++++++++++++----------- include/clocksource/samsung_pwm.h | 2 ++ 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 1752457a4f76..d9048b843546 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 eff8668da252..399d2414734f 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 v1.2.3 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 --- drivers/clocksource/samsung_pwm_timer.c | 157 +++++++++++++++----------------- 1 file changed, 74 insertions(+), 83 deletions(-) (limited to 'drivers') diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index d9048b843546..e3257fae04e6 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 v1.2.3 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 --- drivers/clocksource/Kconfig | 1 - drivers/clocksource/samsung_pwm_timer.c | 16 ++++++++++++++-- include/clocksource/samsung_pwm.h | 3 +++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index ff5b6d87d65a..6d6174978f95 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 e3257fae04e6..9f4bd6aa2343 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 b1d8fe706f1a..5c449c8199e9 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 v1.2.3 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 --- drivers/clocksource/samsung_pwm_timer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 9f4bd6aa2343..cb866156b8b0 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 v1.2.3 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 --- drivers/clocksource/samsung_pwm_timer.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index cb866156b8b0..92b2f130ae9b 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 v1.2.3 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 --- drivers/clocksource/samsung_pwm_timer.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'drivers') diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c index 92b2f130ae9b..0234c8d2c8f2 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 v1.2.3