summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/samsung/clk-exynos4.c93
-rw-r--r--drivers/clk/samsung/clk-exynos5250.c1
-rw-r--r--drivers/clk/samsung/clk-exynos5440.c1
-rw-r--r--drivers/clk/samsung/clk.h2
-rw-r--r--drivers/clocksource/Kconfig9
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/exynos_mct.c21
-rw-r--r--drivers/clocksource/samsung_pwm_timer.c494
-rw-r--r--drivers/irqchip/exynos-combiner.c125
9 files changed, 616 insertions, 131 deletions
diff --git a/drivers/clk/samsung/clk-exynos4.c b/drivers/clk/samsung/clk-exynos4.c
index 7104669..d0940e6 100644
--- a/drivers/clk/samsung/clk-exynos4.c
+++ b/drivers/clk/samsung/clk-exynos4.c
@@ -16,7 +16,6 @@
#include <linux/of.h>
#include <linux/of_address.h>
-#include <plat/cpu.h>
#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 bb54606..5c97e75 100644
--- a/drivers/clk/samsung/clk-exynos5250.c
+++ b/drivers/clk/samsung/clk-exynos5250.c
@@ -16,7 +16,6 @@
#include <linux/of.h>
#include <linux/of_address.h>
-#include <plat/cpu.h>
#include "clk.h"
#include "clk-pll.h"
diff --git a/drivers/clk/samsung/clk-exynos5440.c b/drivers/clk/samsung/clk-exynos5440.c
index a0a094c..7d54341 100644
--- a/drivers/clk/samsung/clk-exynos5440.c
+++ b/drivers/clk/samsung/clk-exynos5440.c
@@ -15,7 +15,6 @@
#include <linux/of.h>
#include <linux/of_address.h>
-#include <plat/cpu.h>
#include "clk.h"
#include "clk-pll.h"
diff --git a/drivers/clk/samsung/clk.h b/drivers/clk/samsung/clk.h
index 10b2111..e4ad6ea 100644
--- a/drivers/clk/samsung/clk.h
+++ b/drivers/clk/samsung/clk.h
@@ -20,8 +20,6 @@
#include <linux/of.h>
#include <linux/of_address.h>
-#include <mach/map.h>
-
/**
* struct samsung_clock_alias: information about mux clock
* @id: platform specific id of the clock.
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index c20de4a..f151c6c 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -76,3 +76,12 @@ config CLKSRC_EXYNOS_MCT
def_bool y if ARCH_EXYNOS
help
Support for Multi Core Timer controller on Exynos SoCs.
+
+config CLKSRC_SAMSUNG_PWM
+ bool
+ 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 caacdb6..8d979c7 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_VT8500_TIMER) += vt8500_timer.o
obj-$(CONFIG_ARCH_BCM) += bcm_kona_timer.o
obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o
obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.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/exynos_mct.c b/drivers/clocksource/exynos_mct.c
index 13a9e49..662fcc0 100644
--- a/drivers/clocksource/exynos_mct.c
+++ b/drivers/clocksource/exynos_mct.c
@@ -25,11 +25,6 @@
#include <linux/clocksource.h>
#include <asm/localtimer.h>
-
-#include <plat/cpu.h>
-
-#include <mach/map.h>
-#include <mach/irqs.h>
#include <asm/mach/time.h>
#define EXYNOS4_MCTREG(x) (x)
@@ -510,18 +505,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();
}
diff --git a/drivers/clocksource/samsung_pwm_timer.c b/drivers/clocksource/samsung_pwm_timer.c
new file mode 100644
index 0000000..0234c8d
--- /dev/null
+++ b/drivers/clocksource/samsung_pwm_timer.c
@@ -0,0 +1,494 @@
+/*
+ * 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 <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <clocksource/samsung_pwm.h>
+
+#include <asm/sched_clock.h>
+
+/*
+ * 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))
+
+DEFINE_SPINLOCK(samsung_pwm_lock);
+EXPORT_SYMBOL(samsung_pwm_lock);
+
+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_clocksource pwm;
+
+static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)
+{
+ unsigned long flags;
+ u8 shift = 0;
+ u32 reg;
+
+ if (channel >= 2)
+ shift = TCFG0_PRESCALER1_SHIFT;
+
+ 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(&samsung_pwm_lock, flags);
+}
+
+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;
+
+ 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(&samsung_pwm_lock, flags);
+}
+
+static void samsung_time_stop(unsigned int channel)
+{
+ unsigned long tcon;
+ unsigned long flags;
+
+ if (channel > 0)
+ ++channel;
+
+ 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(&samsung_pwm_lock, 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(&samsung_pwm_lock, flags);
+
+ tcon = __raw_readl(pwm.base + REG_TCON);
+
+ 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(&samsung_pwm_lock, flags);
+}
+
+static void samsung_time_start(unsigned int channel, bool periodic)
+{
+ unsigned long tcon;
+ unsigned long flags;
+
+ if (channel > 0)
+ ++channel;
+
+ spin_lock_irqsave(&samsung_pwm_lock, 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(&samsung_pwm_lock, flags);
+}
+
+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);
+
+ return 0;
+}
+
+static void samsung_timer_resume(void)
+{
+ /* event timer restart */
+ samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
+ samsung_time_start(pwm.event_id, true);
+
+ /* source timer restart */
+ 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(pwm.event_id);
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
+ samsung_time_start(pwm.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 << pwm.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(pwm.timerclk);
+
+ samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);
+ samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);
+
+ 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, pwm.tcnt_max);
+
+ irq_number = pwm.irq[pwm.event_id];
+ setup_irq(irq_number, &samsung_clock_event_irq);
+
+ 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 (pwm.source_id) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return pwm.base + pwm.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(pwm.timerclk);
+
+ samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);
+ samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);
+
+ clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
+
+ 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);
+
+ 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)
+{
+ pwm.timerclk = clk_get(NULL, "timers");
+ if (IS_ERR(pwm.timerclk))
+ panic("failed to get timers clock for timer");
+
+ clk_prepare_enable(pwm.timerclk);
+
+ pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;
+ if (pwm.variant.bits == 16) {
+ pwm.tscaler_div = 25;
+ pwm.tdiv = 2;
+ } else {
+ pwm.tscaler_div = 2;
+ pwm.tdiv = 1;
+ }
+}
+
+/*
+ * PWM master driver
+ */
+static void __init _samsung_pwm_clocksource_init(void)
+{
+ u8 mask;
+ int channel;
+
+ 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");
+ pwm.source_id = channel;
+
+ mask &= ~(1 << channel);
+ channel = fls(mask) - 1;
+ if (channel < 0)
+ panic("failed to find PWM channel for clock event");
+ pwm.event_id = channel;
+
+ samsung_timer_resources();
+ samsung_clockevent_init();
+ 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)
+{
+ struct resource res;
+ struct property *prop;
+ const __be32 *cur;
+ u32 val;
+ int i;
+
+ 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);
+
+ 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);
+#endif
diff --git a/drivers/irqchip/exynos-combiner.c b/drivers/irqchip/exynos-combiner.c
index 02492ab..a9d2b2f 100644
--- a/drivers/irqchip/exynos-combiner.c
+++ b/drivers/irqchip/exynos-combiner.c
@@ -12,13 +12,16 @@
#include <linux/export.h>
#include <linux/init.h>
#include <linux/io.h>
+#include <linux/slab.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/irq.h>
+#ifdef CONFIG_EXYNOS_ATAGS
#include <plat/cpu.h>
+#endif
#include "irqchip.h"
@@ -26,17 +29,18 @@
#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 {
- unsigned int irq_offset;
+ unsigned int hwirq_offset;
unsigned int irq_mask;
void __iomem *base;
unsigned int parent_irq;
};
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)
{
@@ -77,11 +81,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);
@@ -113,40 +117,25 @@ 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,
+static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data,
unsigned int irq)
{
- if (combiner_nr >= max_combiner_nr())
- BUG();
- 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_irq_domain, combiner_nr * MAX_IRQ_IN_COMBINER);
- combiner_data[combiner_nr].irq_mask = 0xff << ((combiner_nr % 4) << 3);
- combiner_data[combiner_nr].parent_irq = irq;
+ combiner_data->base = base;
+ combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER;
+ 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
@@ -162,7 +151,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;
@@ -181,6 +170,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);
@@ -193,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);
@@ -204,53 +199,46 @@ 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,
- struct device_node *np)
+ struct device_node *np,
+ unsigned int max_nr,
+ int irq_base)
{
- int i, irq, irq_base;
- unsigned int max_nr, nr_irq;
+ int i, irq;
+ unsigned int nr_irq;
+ struct combiner_chip_data *combiner_data;
- max_nr = max_combiner_nr();
+ nr_irq = max_nr * IRQ_IN_COMBINER;
- 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;
-
- 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_ops, &combiner_data);
+ 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__);
return;
}
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
- combiner_init_one(i, combiner_base + (i >> 2) * 0x10, irq);
- combiner_cascade_irq(i, irq);
+ 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);
}
}
@@ -259,6 +247,8 @@ static int __init combiner_of_init(struct device_node *np,
struct device_node *parent)
{
void __iomem *combiner_base;
+ unsigned int max_nr = 20;
+ int irq_base = -1;
combiner_base = of_iomap(np, 0);
if (!combiner_base) {
@@ -266,7 +256,20 @@ 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);
+ }
+
+ /*
+ * 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;
}