From 3a598264436e94c410c413088a7873fcad33616c Mon Sep 17 00:00:00 2001 From: Hitoshi Mitake Date: Mon, 21 Jun 2010 15:10:51 +0900 Subject: sh: SH-2007 board support. This patch series adds support for ITO Co., Ltd.'s SH-2007 reference platform (A PC-104 based SH7780 platform). This is a direct port of the out-of-tree board support from the vendor's kernel, originally located at: http://ms-n.org/sh-linux/kernel/ More information on the board and the vendor can be obtained from the vendor's site at: http://www.itonet.co.jp/ Presently supported peripherals are CF and ethernet, with support for the on-board IDE still pending further testing. Reviewed-by: Nobuhiro Iwamatsu Reviewed-by: Magnus Damm Signed-off-by: Hitoshi Mitake Signed-off-by: Paul Mundt --- drivers/serial/sh-sci.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/serial/sh-sci.h b/drivers/serial/sh-sci.h index f70c49f915fa..eb7ef4951e07 100644 --- a/drivers/serial/sh-sci.h +++ b/drivers/serial/sh-sci.h @@ -140,7 +140,15 @@ # define SCSPTR0 0xffe00024 /* 16 bit SCIF */ # define SCSPTR1 0xffe10024 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* Overrun error bit */ -# define SCSCR_INIT(port) 0x3a /* TIE=0,RIE=0,TE=1,RE=1,REIE=1 */ + +#if defined(CONFIG_SH_SH2007) +/* TIE=0,RIE=0,TE=1,RE=1,REIE=1,CKE1=0 */ +# define SCSCR_INIT(port) 0x38 +#else +/* TIE=0,RIE=0,TE=1,RE=1,REIE=1,CKE1=1 */ +# define SCSCR_INIT(port) 0x3a +#endif + #elif defined(CONFIG_CPU_SUBTYPE_SH7785) || \ defined(CONFIG_CPU_SUBTYPE_SH7786) # define SCSPTR0 0xffea0024 /* 16 bit SCIF */ @@ -599,9 +607,10 @@ static inline int sci_rxd_in(struct uart_port *port) * -- Mitch Davis - 15 Jul 2000 */ -#if defined(CONFIG_CPU_SUBTYPE_SH7780) || \ - defined(CONFIG_CPU_SUBTYPE_SH7785) || \ - defined(CONFIG_CPU_SUBTYPE_SH7786) +#if (defined(CONFIG_CPU_SUBTYPE_SH7780) || \ + defined(CONFIG_CPU_SUBTYPE_SH7785) || \ + defined(CONFIG_CPU_SUBTYPE_SH7786)) && \ + !defined(CONFIG_SH_SH2007) #define SCBRR_VALUE(bps, clk) ((clk+16*bps)/(16*bps)-1) #elif defined(CONFIG_CPU_SUBTYPE_SH7705) || \ defined(CONFIG_CPU_SUBTYPE_SH7720) || \ -- cgit v1.2.3 From ae9b12c72f831a2f956a5b30fd999f3018c43a89 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Mon, 7 Jun 2010 11:16:36 +0000 Subject: dmaengine: initialise DMA engine before MMC To use DMA engine based DMA with MMC in a non-modular build, the DMA engine has to initialise before MMC. Signed-off-by: Guennadi Liakhovetski Acked-by: Dan Williams Signed-off-by: Paul Mundt --- drivers/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/Makefile b/drivers/Makefile index 91874e048552..b713e75cee71 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_EISA) += eisa/ obj-y += lguest/ obj-$(CONFIG_CPU_FREQ) += cpufreq/ obj-$(CONFIG_CPU_IDLE) += cpuidle/ +obj-$(CONFIG_DMA_ENGINE) += dma/ obj-$(CONFIG_MMC) += mmc/ obj-$(CONFIG_MEMSTICK) += memstick/ obj-$(CONFIG_NEW_LEDS) += leds/ @@ -102,7 +103,6 @@ obj-$(CONFIG_CRYPTO) += crypto/ obj-$(CONFIG_SUPERH) += sh/ obj-$(CONFIG_ARCH_SHMOBILE) += sh/ obj-$(CONFIG_GENERIC_TIME) += clocksource/ -obj-$(CONFIG_DMA_ENGINE) += dma/ obj-$(CONFIG_DCA) += dca/ obj-$(CONFIG_HID) += hid/ obj-$(CONFIG_PPC_PS3) += ps3/ -- cgit v1.2.3 From c45f6f10263ab53574f54de0caf0299b7d7c06a8 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 25 Jun 2010 07:20:29 +0000 Subject: sh: move a debug printk() to a more meaningful location in the clock driver To actually output the _new_ clock rate it first has to be set. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 5d84adac9ec4..936c93aaef11 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -354,10 +354,10 @@ int clk_set_parent(struct clk *clk, struct clk *parent) ret = clk_reparent(clk, parent); if (ret == 0) { - pr_debug("clock: set parent of %s to %s (new rate %ld)\n", - clk->name, clk->parent->name, clk->rate); if (clk->ops->recalc) clk->rate = clk->ops->recalc(clk); + pr_debug("clock: set parent of %s to %s (new rate %ld)\n", + clk->name, clk->parent->name, clk->rate); propagate_rate(clk); } } else -- cgit v1.2.3 From 088bcc2aff25f31f1497f8196fa02c8cd2a43f68 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Fri, 25 Jun 2010 07:19:39 +0000 Subject: sh: remove bogus highest / lowest logic from clock rate rounding The use of highest and lowest in clk_rate_table_round() is completely bogus and superfluous. Remove it. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 936c93aaef11..cede14e34507 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -73,22 +73,14 @@ long clk_rate_table_round(struct clk *clk, { unsigned long rate_error, rate_error_prev = ~0UL; unsigned long rate_best_fit = rate; - unsigned long highest, lowest; int i; - highest = lowest = 0; - for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { unsigned long freq = freq_table[i].frequency; if (freq == CPUFREQ_ENTRY_INVALID) continue; - if (freq > highest) - highest = freq; - if (freq < lowest) - lowest = freq; - rate_error = abs(freq - rate); if (rate_error < rate_error_prev) { rate_best_fit = freq; @@ -99,11 +91,6 @@ long clk_rate_table_round(struct clk *clk, break; } - if (rate >= highest) - rate_best_fit = highest; - if (rate <= lowest) - rate_best_fit = lowest; - return rate_best_fit; } -- cgit v1.2.3 From 43809473b9d5fa9f82bf64ddeb3c21fe1140ef0e Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 4 Aug 2010 04:31:38 +0000 Subject: clocksource: sh_cmt: One-off clockevent fix V2 Fix a one-off error in the CMT driver V2. The match register should be programmed with the period minus one. Many thanks to Eiraku-san for tracking down this issue. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- drivers/clocksource/sh_cmt.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index 717305d30444..ed8e07b97e27 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -308,7 +308,7 @@ static irqreturn_t sh_cmt_interrupt(int irq, void *dev_id) * isr before we end up here. */ if (p->flags & FLAG_CLOCKSOURCE) - p->total_cycles += p->match_value; + p->total_cycles += p->match_value + 1; if (!(p->flags & FLAG_REPROGRAM)) p->next_match_value = p->max_match_value; @@ -403,7 +403,7 @@ static cycle_t sh_cmt_clocksource_read(struct clocksource *cs) raw = sh_cmt_get_counter(p, &has_wrapped); if (unlikely(has_wrapped)) - raw += p->match_value; + raw += p->match_value + 1; spin_unlock_irqrestore(&p->lock, flags); return value + raw; @@ -478,7 +478,7 @@ static void sh_cmt_clock_event_start(struct sh_cmt_priv *p, int periodic) ced->min_delta_ns = clockevent_delta2ns(0x1f, ced); if (periodic) - sh_cmt_set_next(p, (p->rate + HZ/2) / HZ); + sh_cmt_set_next(p, ((p->rate + HZ/2) / HZ) - 1); else sh_cmt_set_next(p, p->max_match_value); } @@ -523,9 +523,9 @@ static int sh_cmt_clock_event_next(unsigned long delta, BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT); if (likely(p->flags & FLAG_IRQCONTEXT)) - p->next_match_value = delta; + p->next_match_value = delta - 1; else - sh_cmt_set_next(p, delta); + sh_cmt_set_next(p, delta - 1); return 0; } -- cgit v1.2.3 From 38409d726bc6413d39f082f60bc803f52d8b5849 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 2 Aug 2010 09:24:05 +0000 Subject: clocksource: sh_cmt: Rate calculation fix Fix the rate calculation in the CMT driver. Without this fix the clocksource runs way too fast and we get a divide-by-zero error. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- drivers/clocksource/sh_cmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/clocksource/sh_cmt.c b/drivers/clocksource/sh_cmt.c index ed8e07b97e27..a44611652282 100644 --- a/drivers/clocksource/sh_cmt.c +++ b/drivers/clocksource/sh_cmt.c @@ -445,7 +445,7 @@ static int sh_cmt_register_clocksource(struct sh_cmt_priv *p, /* clk_get_rate() needs an enabled clock */ clk_enable(p->clk); - p->rate = clk_get_rate(p->clk) / (p->width == 16) ? 512 : 8; + p->rate = clk_get_rate(p->clk) / ((p->width == 16) ? 512 : 8); clk_disable(p->clk); /* TODO: calculate good shift from rate and counter bit width */ -- cgit v1.2.3 From 82b20d8bae03406e954d8b7d0f6f734967088b17 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Mon, 2 Aug 2010 07:16:37 +0000 Subject: i2c: i2c-sh_mobile irq rollback fix Update the i2c-sh_mobile driver to properly free interrupts. The function sh_mobile_i2c_hook_irqs() is fixed so module both unload and load are working as expected. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- drivers/i2c/busses/i2c-sh_mobile.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c index ffb405d7c6f2..97b84b80d0ff 100644 --- a/drivers/i2c/busses/i2c-sh_mobile.c +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -497,15 +497,17 @@ static int sh_mobile_i2c_hook_irqs(struct platform_device *dev, int hook) { struct resource *res; int ret = -ENXIO; - int q, m; - int k = 0; - int n = 0; + int n, k = 0; while ((res = platform_get_resource(dev, IORESOURCE_IRQ, k))) { for (n = res->start; hook && n <= res->end; n++) { if (request_irq(n, sh_mobile_i2c_isr, IRQF_DISABLED, - dev_name(&dev->dev), dev)) + dev_name(&dev->dev), dev)) { + for (n--; n >= res->start; n--) + free_irq(n, dev); + goto rollback; + } } k++; } @@ -513,16 +515,17 @@ static int sh_mobile_i2c_hook_irqs(struct platform_device *dev, int hook) if (hook) return k > 0 ? 0 : -ENOENT; - k--; ret = 0; rollback: - for (q = k; k >= 0; k--) { - for (m = n; m >= res->start; m--) - free_irq(m, dev); + k--; + + while (k >= 0) { + res = platform_get_resource(dev, IORESOURCE_IRQ, k); + for (n = res->start; n <= res->end; n++) + free_irq(n, dev); - res = platform_get_resource(dev, IORESOURCE_IRQ, k - 1); - m = res->end; + k--; } return ret; -- cgit v1.2.3 From 960bc368e7561791b94b749087d2e0d7b36d231c Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 20 Aug 2010 19:10:38 +0900 Subject: sh: reinstate clock framework rate rounding. This was killed off by a simplification patch previously that failed to take the cpufreq use case in to account, so reinstate the old bounding logic. The lowest rate bounding on the other hand was broken in that it never actually got assigned a rate and the best fit rate was instead just getting lucky based on the ordering of the rate table, fix this up so the code actually does what it was intended to do originally. Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index cede14e34507..b9c57a640c24 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -1,7 +1,7 @@ /* * drivers/sh/clk.c - SuperH clock framework * - * Copyright (C) 2005 - 2009 Paul Mundt + * Copyright (C) 2005 - 2010 Paul Mundt * * This clock framework is derived from the OMAP version by: * @@ -73,14 +73,23 @@ long clk_rate_table_round(struct clk *clk, { unsigned long rate_error, rate_error_prev = ~0UL; unsigned long rate_best_fit = rate; + unsigned long highest, lowest; int i; + highest = 0; + lowest = ~0UL; + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { unsigned long freq = freq_table[i].frequency; if (freq == CPUFREQ_ENTRY_INVALID) continue; + if (freq > highest) + highest = freq; + if (freq < lowest) + lowest = freq; + rate_error = abs(freq - rate); if (rate_error < rate_error_prev) { rate_best_fit = freq; @@ -91,6 +100,11 @@ long clk_rate_table_round(struct clk *clk, break; } + if (rate >= highest) + rate_best_fit = highest; + if (rate <= lowest) + rate_best_fit = lowest; + return rate_best_fit; } -- cgit v1.2.3 From 25ab998e2eff46d2e6ede80c9fc02a349fa005a3 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Thu, 16 Sep 2010 17:14:40 +0900 Subject: mfd: Fix up section mismatches in SH SDHI. The current probe/remove definitions are split between __init and __devexit, make them consistent by switching to __devinit. Signed-off-by: Paul Mundt --- drivers/mfd/sh_mobile_sdhi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/mfd/sh_mobile_sdhi.c b/drivers/mfd/sh_mobile_sdhi.c index cd164595f08a..49b4d069cbf9 100644 --- a/drivers/mfd/sh_mobile_sdhi.c +++ b/drivers/mfd/sh_mobile_sdhi.c @@ -65,7 +65,7 @@ static void sh_mobile_sdhi_set_pwr(struct platform_device *tmio, int state) p->set_pwr(pdev, state); } -static int __init sh_mobile_sdhi_probe(struct platform_device *pdev) +static int __devinit sh_mobile_sdhi_probe(struct platform_device *pdev) { struct sh_mobile_sdhi *priv; struct tmio_mmc_data *mmc_data; -- cgit v1.2.3 From 0238a0d8c3846f5c1f9db0dabe504481751e58e8 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Thu, 16 Sep 2010 17:16:31 +0900 Subject: usb: Fix up r8a66597-hcd section mismatches. The _remove() routine is flagged __init_or_module, despite only being used in a __devexit context. As the rest of the driver is already balanced out with __devinit, switch to __devexit and __devexit_p() wrapping. Signed-off-by: Paul Mundt --- drivers/usb/host/r8a66597-hcd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 77be3c24a427..3076b1cc05df 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2397,7 +2397,7 @@ static const struct dev_pm_ops r8a66597_dev_pm_ops = { #define R8A66597_DEV_PM_OPS NULL #endif -static int __init_or_module r8a66597_remove(struct platform_device *pdev) +static int __devexit r8a66597_remove(struct platform_device *pdev) { struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); @@ -2542,7 +2542,7 @@ clean_up: static struct platform_driver r8a66597_driver = { .probe = r8a66597_probe, - .remove = r8a66597_remove, + .remove = __devexit_p(r8a66597_remove), .driver = { .name = (char *) hcd_name, .owner = THIS_MODULE, -- cgit v1.2.3 From f9d885c3e59e50863565bddd2672656b57b15035 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Sat, 2 Oct 2010 01:04:30 +0900 Subject: sh: Support IRQ balancing for SH-X3 proto cores, too. This adds in hardware IRQ auto-distribution support for SH-X3 proto CPUs, following the SH7786 support. Signed-off-by: Paul Mundt --- arch/sh/kernel/cpu/sh4a/setup-shx3.c | 15 +++++++++++---- drivers/sh/Kconfig | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/arch/sh/kernel/cpu/sh4a/setup-shx3.c b/arch/sh/kernel/cpu/sh4a/setup-shx3.c index 9158bc5ea38b..04a487445aa6 100644 --- a/arch/sh/kernel/cpu/sh4a/setup-shx3.c +++ b/arch/sh/kernel/cpu/sh4a/setup-shx3.c @@ -1,7 +1,7 @@ /* * SH-X3 Prototype Setup * - * Copyright (C) 2007 - 2009 Paul Mundt + * Copyright (C) 2007 - 2010 Paul Mundt * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -354,6 +354,10 @@ static struct intc_group groups[] __initdata = { DMAC1_DMINT9, DMAC1_DMINT10, DMAC1_DMINT11), }; +#define INT2DISTCR0 0xfe4108a0 +#define INT2DISTCR1 0xfe4108a4 +#define INT2DISTCR2 0xfe4108a8 + static struct intc_mask_reg mask_registers[] __initdata = { { 0xfe410030, 0xfe410050, 32, /* CnINTMSK0 / CnINTMSKCLR0 */ { IRQ0, IRQ1, IRQ2, IRQ3 } }, @@ -363,20 +367,23 @@ static struct intc_mask_reg mask_registers[] __initdata = { { FE1, FE0, 0, ATAPI, VCORE0, VIN1, VIN0, IIC, DU, GPIO3, GPIO2, GPIO1, GPIO0, PAM, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* HUDI bits ignored */ - 0, TMU5, TMU4, TMU3, TMU2, TMU1, TMU0, 0, } }, + 0, TMU5, TMU4, TMU3, TMU2, TMU1, TMU0, 0, }, + INTC_SMP_BALANCING(INT2DISTCR0) }, { 0xfe410830, 0xfe410860, 32, /* CnINT2MSK1 / CnINT2MSKCLR1 */ { 0, 0, 0, 0, DTU3, DTU2, DTU1, DTU0, /* IRM bits ignored */ PCII9, PCII8, PCII7, PCII6, PCII5, PCII4, PCII3, PCII2, PCII1, PCII0, DMAC1_DMAE, DMAC1_DMINT11, DMAC1_DMINT10, DMAC1_DMINT9, DMAC1_DMINT8, DMAC1_DMINT7, DMAC1_DMINT6, DMAC0_DMAE, DMAC0_DMINT5, DMAC0_DMINT4, - DMAC0_DMINT3, DMAC0_DMINT2, DMAC0_DMINT1, DMAC0_DMINT0 } }, + DMAC0_DMINT3, DMAC0_DMINT2, DMAC0_DMINT1, DMAC0_DMINT0 }, + INTC_SMP_BALANCING(INT2DISTCR1) }, { 0xfe410840, 0xfe410870, 32, /* CnINT2MSK2 / CnINT2MSKCLR2 */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, SCIF3_TXI, SCIF3_BRI, SCIF3_RXI, SCIF3_ERI, SCIF2_TXI, SCIF2_BRI, SCIF2_RXI, SCIF2_ERI, SCIF1_TXI, SCIF1_BRI, SCIF1_RXI, SCIF1_ERI, - SCIF0_TXI, SCIF0_BRI, SCIF0_RXI, SCIF0_ERI } }, + SCIF0_TXI, SCIF0_BRI, SCIF0_RXI, SCIF0_ERI }, + INTC_SMP_BALANCING(INT2DISTCR2) }, }; static struct intc_prio_reg prio_registers[] __initdata = { diff --git a/drivers/sh/Kconfig b/drivers/sh/Kconfig index a54de0b9b3df..1dc8b7ae35fb 100644 --- a/drivers/sh/Kconfig +++ b/drivers/sh/Kconfig @@ -14,7 +14,7 @@ config INTC_USERIMASK config INTC_BALANCING bool "Hardware IRQ balancing support" - depends on SMP && SUPERH && CPU_SUBTYPE_SH7786 + depends on SMP && SUPERH && CPU_SHX3 help This enables support for IRQ auto-distribution mode on SH-X3 SMP parts. All of the balancing and CPU wakeup decisions are -- cgit v1.2.3 From 4bacd796ccd6976b03dd490708a1abc291d5521e Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Sat, 2 Oct 2010 22:02:07 +0900 Subject: sh: Support early IRQ vector map reservation for delayed controllers. Some controllers will need to be initialized lazily due to pinmux constraints, while others may simply have no need to be brought online if there are no backing devices for them attached. In this case it's still necessary to be able to reserve their hardware vector map before dynamic IRQs get a hold of them. Signed-off-by: Paul Mundt --- arch/sh/boards/mach-x3proto/setup.c | 23 ++++++++++++++--------- arch/sh/kernel/cpu/sh4a/setup-shx3.c | 3 +++ drivers/sh/intc.c | 11 +++++++++++ include/linux/sh_intc.h | 1 + 4 files changed, 29 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/arch/sh/boards/mach-x3proto/setup.c b/arch/sh/boards/mach-x3proto/setup.c index 102bf56befb4..21f1bb67248e 100644 --- a/arch/sh/boards/mach-x3proto/setup.c +++ b/arch/sh/boards/mach-x3proto/setup.c @@ -129,8 +129,22 @@ static struct platform_device *x3proto_devices[] __initdata = { &m66592_usb_peripheral_device, }; +static void __init x3proto_init_irq(void) +{ + plat_irq_setup_pins(IRQ_MODE_IRL3210); + + /* Set ICR0.LVLMODE */ + __raw_writel(__raw_readl(0xfe410000) | (1 << 21), 0xfe410000); +} + static int __init x3proto_devices_setup(void) { + /* + * IRLs are only needed for ILSEL mappings, so flip over the INTC + * pins at a later point to enable the GPIOs to settle. + */ + x3proto_init_irq(); + r8a66597_usb_host_resources[1].start = r8a66597_usb_host_resources[1].end = ilsel_enable(ILSEL_USBH_I); @@ -145,14 +159,6 @@ static int __init x3proto_devices_setup(void) } device_initcall(x3proto_devices_setup); -static void __init x3proto_init_irq(void) -{ - plat_irq_setup_pins(IRQ_MODE_IRL3210); - - /* Set ICR0.LVLMODE */ - __raw_writel(__raw_readl(0xfe410000) | (1 << 21), 0xfe410000); -} - static void __init x3proto_setup(char **cmdline_p) { register_smp_ops(&shx3_smp_ops); @@ -161,5 +167,4 @@ static void __init x3proto_setup(char **cmdline_p) static struct sh_machine_vector mv_x3proto __initmv = { .mv_name = "x3proto", .mv_setup = x3proto_setup, - .mv_init_irq = x3proto_init_irq, }; diff --git a/arch/sh/kernel/cpu/sh4a/setup-shx3.c b/arch/sh/kernel/cpu/sh4a/setup-shx3.c index f159ea2cebc7..013f0b144489 100644 --- a/arch/sh/kernel/cpu/sh4a/setup-shx3.c +++ b/arch/sh/kernel/cpu/sh4a/setup-shx3.c @@ -478,6 +478,9 @@ void __init plat_irq_setup_pins(int mode) void __init plat_irq_setup(void) { + reserve_intc_vectors(vectors_irq, ARRAY_SIZE(vectors_irq)); + reserve_intc_vectors(vectors_irl, ARRAY_SIZE(vectors_irl)); + register_intc_controller(&intc_desc); } diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index e91a23e5ffd8..4e01d65e5edb 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -1377,6 +1377,17 @@ int reserve_irq_vector(unsigned int irq) return ret; } +void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&vector_lock, flags); + for (i = 0; i < nr_vecs; i++) + __set_bit(evt2irq(vectors[i].vect), intc_irq_map); + spin_unlock_irqrestore(&vector_lock, flags); +} + void reserve_irq_legacy(void) { unsigned long flags; diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index 0d6cd38e673d..bff2f286ca61 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -106,6 +106,7 @@ struct intc_desc symbol __initdata = { \ } int __init register_intc_controller(struct intc_desc *desc); +void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs); int intc_set_priority(unsigned int irq, unsigned int prio); #ifdef CONFIG_INTC_USERIMASK -- cgit v1.2.3 From b72421d8aa39724474ec2bfb91e182001f1f25a7 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Mon, 4 Oct 2010 03:54:56 +0900 Subject: sh: pfc: support pinmux deregistration. Presently the pinmux code is a one-way thing, but there's nothing preventing an unregistration if no one has grabbed any of the pins. This will permit us to save a bit of memory on systems that require pin demux for certain peripherals in the case where registration of those peripherals fails, or they are otherwise not attached to the system. Signed-off-by: Paul Mundt --- drivers/sh/pfc.c | 11 ++++++++++- include/linux/sh_pfc.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/pfc.c b/drivers/sh/pfc.c index cf0303acab8e..dee581f6382c 100644 --- a/drivers/sh/pfc.c +++ b/drivers/sh/pfc.c @@ -7,6 +7,8 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include #include #include @@ -581,7 +583,7 @@ int register_pinmux(struct pinmux_info *pip) { struct gpio_chip *chip = &pip->chip; - pr_info("sh pinmux: %s handling gpio %d -> %d\n", + pr_info("%s handling gpio %d -> %d\n", pip->name, pip->first_gpio, pip->last_gpio); setup_data_regs(pip); @@ -602,3 +604,10 @@ int register_pinmux(struct pinmux_info *pip) return gpiochip_add(chip); } + +int unregister_pinmux(struct pinmux_info *pip) +{ + pr_info("%s deregistering\n", pip->name); + + return gpiochip_remove(&pip->chip); +} diff --git a/include/linux/sh_pfc.h b/include/linux/sh_pfc.h index 07c08af9f8f6..30cae70874f4 100644 --- a/include/linux/sh_pfc.h +++ b/include/linux/sh_pfc.h @@ -92,5 +92,6 @@ struct pinmux_info { }; int register_pinmux(struct pinmux_info *pip); +int unregister_pinmux(struct pinmux_info *pip); #endif /* __SH_PFC_H */ -- cgit v1.2.3 From e8184a47c9cc04380553114815356d1042a27788 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Mon, 4 Oct 2010 05:15:20 +0900 Subject: sh: pfc: Fix up BUG() triggered by gpiolib debugfs lookups. The gpiolib debugfs entry takes a hammer approach and iterates over all of the potential GPIOs, regardless of their type. The SH PFC code on the other hand contains a variable mismash of input/output/function types spread out sparsely, leading to situations where the debug code can trigger an out of range enum for the type. Since we already have an error path for out of range enums, we can just hand that up to the higher level instead of the current BUG() behaviour. Signed-off-by: Paul Mundt --- drivers/sh/pfc.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/pfc.c b/drivers/sh/pfc.c index dee581f6382c..75934e3ea34e 100644 --- a/drivers/sh/pfc.c +++ b/drivers/sh/pfc.c @@ -561,10 +561,8 @@ static int sh_gpio_get_value(struct pinmux_info *gpioc, unsigned gpio) struct pinmux_data_reg *dr = NULL; int bit = 0; - if (!gpioc || get_data_reg(gpioc, gpio, &dr, &bit) != 0) { - BUG(); - return 0; - } + if (!gpioc || get_data_reg(gpioc, gpio, &dr, &bit) != 0) + return -EINVAL; return gpio_read_reg(dr->reg, dr->reg_width, 1, bit); } -- cgit v1.2.3 From 44629f57accccbb8e6d443246fe6f51b42f7f781 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Thu, 23 Sep 2010 20:09:38 +0900 Subject: sh: intc: Implement reverse mapping for IRQs to per-controller IDs. This implements a scheme roughly analogous to the PowerPC virtual to hardware IRQ mapping, which we use for IRQ to per-controller ID mapping. This makes it possible for drivers to use the IDs directly for lookup instead of hardcoding the vector. The main motivation for this work is as a building block for dynamically allocating virtual IRQs for demuxing INTC events sharing a single INTEVT in addition to a common masking source. Signed-off-by: Paul Mundt --- drivers/sh/Kconfig | 9 +++++ drivers/sh/intc.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/sh_intc.h | 1 + 3 files changed, 100 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/Kconfig b/drivers/sh/Kconfig index 1dc8b7ae35fb..e01ae42774af 100644 --- a/drivers/sh/Kconfig +++ b/drivers/sh/Kconfig @@ -22,3 +22,12 @@ config INTC_BALANCING vectors. If in doubt, say N. + +config INTC_MAPPING_DEBUG + bool "Expose IRQ to per-controller id mapping via debugfs" + depends on DEBUG_FS + help + This will create a debugfs entry for showing the relationship + between system IRQs and the per-controller id tables. + + If in doubt, say N. diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index 4e01d65e5edb..a27dcb4254c7 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -30,6 +30,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ @@ -54,9 +59,15 @@ struct intc_window { unsigned long size; }; +struct intc_map_entry { + intc_enum enum_id; + struct intc_desc_int *desc; +}; + struct intc_desc_int { struct list_head list; struct sys_device sysdev; + struct radix_tree_root tree; pm_message_t state; unsigned long *reg; #ifdef CONFIG_SMP @@ -86,7 +97,9 @@ static LIST_HEAD(intc_list); * unused irq_desc positions in the sparse array. */ static DECLARE_BITMAP(intc_irq_map, NR_IRQS); +static struct intc_map_entry intc_irq_xlate[NR_IRQS]; static DEFINE_SPINLOCK(vector_lock); +static DEFINE_MUTEX(irq_xlate_mutex); #ifdef CONFIG_SMP #define IS_SMP(x) x.smp @@ -828,6 +841,26 @@ static unsigned int __init intc_sense_data(struct intc_desc *desc, return 0; } +unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id) +{ + struct intc_map_entry *ptr; + struct intc_desc_int *d; + unsigned int irq = 0; + + list_for_each_entry(d, &intc_list, list) { + if (strcmp(d->chip.name, chipname) == 0) { + ptr = radix_tree_lookup(&d->tree, enum_id); + if (ptr) { + irq = ptr - intc_irq_xlate; + break; + } + } + } + + return irq; +} +EXPORT_SYMBOL_GPL(intc_irq_lookup); + static void __init intc_register_irq(struct intc_desc *desc, struct intc_desc_int *d, intc_enum enum_id, @@ -837,10 +870,15 @@ static void __init intc_register_irq(struct intc_desc *desc, unsigned int data[2], primary; /* - * Register the IRQ position with the global IRQ map + * Register the IRQ position with the global IRQ map, then insert + * it in to the radix tree. */ set_bit(irq, intc_irq_map); + mutex_lock(&irq_xlate_mutex); + radix_tree_insert(&d->tree, enum_id, &intc_irq_xlate[irq]); + mutex_unlock(&irq_xlate_mutex); + /* * Prefer single interrupt source bitmap over other combinations: * @@ -1082,6 +1120,9 @@ int __init register_intc_controller(struct intc_desc *desc) continue; } + intc_irq_xlate[irq].enum_id = vect->enum_id; + intc_irq_xlate[irq].desc = d; + intc_register_irq(desc, d, vect->enum_id, irq); for (k = i + 1; k < hw->nr_vectors; k++) { @@ -1196,6 +1237,54 @@ static SYSDEV_CLASS_ATTR(userimask, S_IRUSR | S_IWUSR, show_intc_userimask, store_intc_userimask); #endif +#ifdef CONFIG_INTC_MAPPING_DEBUG +static int intc_irq_xlate_debug(struct seq_file *m, void *priv) +{ + int i; + + seq_printf(m, "%-5s %-7s %-15s\n", "irq", "enum", "chip name"); + + for (i = 1; i < nr_irqs; i++) { + struct intc_desc_int *desc = intc_irq_xlate[i].desc; + + if (!desc) + continue; + + seq_printf(m, "%5d ", i); + seq_printf(m, "0x%05x ", intc_irq_xlate[i].enum_id); + seq_printf(m, "%-15s\n", desc->chip.name); + } + + return 0; +} + +static int intc_irq_xlate_open(struct inode *inode, struct file *file) +{ + return single_open(file, intc_irq_xlate_debug, inode->i_private); +} + +static const struct file_operations intc_irq_xlate_fops = { + .open = intc_irq_xlate_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init intc_irq_xlate_init(void) +{ + /* + * XXX.. use arch_debugfs_dir here when all of the intc users are + * converted. + */ + if (debugfs_create_file("intc_irq_xlate", S_IRUGO, NULL, NULL, + &intc_irq_xlate_fops) == NULL) + return -ENOMEM; + + return 0; +} +fs_initcall(intc_irq_xlate_init); +#endif + static ssize_t show_intc_name(struct sys_device *dev, struct sysdev_attribute *attr, char *buf) { diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index bff2f286ca61..d40fd77fa75c 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -108,6 +108,7 @@ struct intc_desc symbol __initdata = { \ int __init register_intc_controller(struct intc_desc *desc); void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs); int intc_set_priority(unsigned int irq, unsigned int prio); +unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id); #ifdef CONFIG_INTC_USERIMASK int register_intc_userimask(unsigned long addr); -- cgit v1.2.3 From c1e30ad98fe210688edca872686db4a715c2fb23 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Tue, 5 Oct 2010 04:47:03 +0900 Subject: sh: intc: Support virtual mappings for IRQ subgroups. Many interrupts that share a single mask source but are on different hardware vectors will have an associated register tied to an INTEVT that denotes the precise cause for the interrupt exception being triggered. This introduces the concept of IRQ subgroups in the intc core, where a virtual IRQ map is constructed for each of the pre-defined cause bits, and a higher level chained handler takes control of the parent INTEVT. This enables CPUs with heavily muxed IRQ vectors (especially across disjoint blocks) to break things out in to a series of managed chained handlers while being able to dynamically lookup and adopt the IRQs created for them. This is largely an opt-in interface, requiring CPUs to manually submit IRQs for subgroup splitting, in addition to providing identifiers in their enum maps that can be used for lazy lookup via the radix tree. Signed-off-by: Paul Mundt --- arch/sh/kernel/irq.c | 2 + drivers/sh/intc.c | 404 ++++++++++++++++++++++++++++++++++++++++-------- include/linux/sh_intc.h | 12 +- 3 files changed, 351 insertions(+), 67 deletions(-) (limited to 'drivers') diff --git a/arch/sh/kernel/irq.c b/arch/sh/kernel/irq.c index 257de1f0692b..c8e1409f20a2 100644 --- a/arch/sh/kernel/irq.c +++ b/arch/sh/kernel/irq.c @@ -283,6 +283,8 @@ void __init init_IRQ(void) if (sh_mv.mv_init_irq) sh_mv.mv_init_irq(); + intc_finalize(); + irq_ctx_init(smp_processor_id()); } diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index a27dcb4254c7..c81fe23db7f7 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ @@ -64,11 +65,19 @@ struct intc_map_entry { struct intc_desc_int *desc; }; +struct intc_subgroup_entry { + unsigned int pirq; + intc_enum enum_id; + unsigned long handle; +}; + struct intc_desc_int { struct list_head list; struct sys_device sysdev; struct radix_tree_root tree; pm_message_t state; + spinlock_t lock; + unsigned int index; unsigned long *reg; #ifdef CONFIG_SMP unsigned long *smp; @@ -84,6 +93,7 @@ struct intc_desc_int { }; static LIST_HEAD(intc_list); +static unsigned int nr_intc_controllers; /* * The intc_irq_map provides a global map of bound IRQ vectors for a @@ -99,7 +109,7 @@ static LIST_HEAD(intc_list); static DECLARE_BITMAP(intc_irq_map, NR_IRQS); static struct intc_map_entry intc_irq_xlate[NR_IRQS]; static DEFINE_SPINLOCK(vector_lock); -static DEFINE_MUTEX(irq_xlate_mutex); +static DEFINE_SPINLOCK(xlate_lock); #ifdef CONFIG_SMP #define IS_SMP(x) x.smp @@ -118,12 +128,39 @@ static unsigned long ack_handle[NR_IRQS]; static unsigned long dist_handle[NR_IRQS]; #endif +struct intc_virq_list { + unsigned int irq; + struct intc_virq_list *next; +}; + +#define for_each_virq(entry, head) \ + for (entry = head; entry; entry = entry->next) + static inline struct intc_desc_int *get_intc_desc(unsigned int irq) { struct irq_chip *chip = get_irq_chip(irq); + return container_of(chip, struct intc_desc_int, chip); } +static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) +{ + generic_handle_irq((unsigned int)get_irq_data(irq)); +} + +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + set_irq_noprobe(irq); +#endif +} + static unsigned long intc_phys_to_virt(struct intc_desc_int *d, unsigned long address) { @@ -177,56 +214,103 @@ static inline unsigned int set_field(unsigned int value, return value; } -static void write_8(unsigned long addr, unsigned long h, unsigned long data) +static inline unsigned long get_field(unsigned int value, unsigned int handle) +{ + unsigned int width = _INTC_WIDTH(handle); + unsigned int shift = _INTC_SHIFT(handle); + unsigned int mask = ((1 << width) - 1) << shift; + + return (value & mask) >> shift; +} + +static unsigned long test_8(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readb(addr), h); +} + +static unsigned long test_16(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readw(addr), h); +} + +static unsigned long test_32(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return get_field(__raw_readl(addr), h); +} + +static unsigned long write_8(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writeb(set_field(0, data, h), addr); (void)__raw_readb(addr); /* Defeat write posting */ + return 0; } -static void write_16(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long write_16(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writew(set_field(0, data, h), addr); (void)__raw_readw(addr); /* Defeat write posting */ + return 0; } -static void write_32(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long write_32(unsigned long addr, unsigned long h, + unsigned long data) { __raw_writel(set_field(0, data, h), addr); (void)__raw_readl(addr); /* Defeat write posting */ + return 0; } -static void modify_8(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_8(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writeb(set_field(__raw_readb(addr), data, h), addr); (void)__raw_readb(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -static void modify_16(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_16(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writew(set_field(__raw_readw(addr), data, h), addr); (void)__raw_readw(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -static void modify_32(unsigned long addr, unsigned long h, unsigned long data) +static unsigned long modify_32(unsigned long addr, unsigned long h, + unsigned long data) { unsigned long flags; local_irq_save(flags); __raw_writel(set_field(__raw_readl(addr), data, h), addr); (void)__raw_readl(addr); /* Defeat write posting */ local_irq_restore(flags); + return 0; } -enum { REG_FN_ERR = 0, REG_FN_WRITE_BASE = 1, REG_FN_MODIFY_BASE = 5 }; +enum { + REG_FN_ERR = 0, + REG_FN_TEST_BASE = 1, + REG_FN_WRITE_BASE = 5, + REG_FN_MODIFY_BASE = 9 +}; -static void (*intc_reg_fns[])(unsigned long addr, - unsigned long h, - unsigned long data) = { +static unsigned long (*intc_reg_fns[])(unsigned long addr, + unsigned long h, + unsigned long data) = { + [REG_FN_TEST_BASE + 0] = test_8, + [REG_FN_TEST_BASE + 1] = test_16, + [REG_FN_TEST_BASE + 3] = test_32, [REG_FN_WRITE_BASE + 0] = write_8, [REG_FN_WRITE_BASE + 1] = write_16, [REG_FN_WRITE_BASE + 3] = write_32, @@ -242,42 +326,42 @@ enum { MODE_ENABLE_REG = 0, /* Bit(s) set -> interrupt enabled */ MODE_PCLR_REG, /* Above plus all bits set to disable interrupt */ }; -static void intc_mode_field(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_field(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); + return fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); } -static void intc_mode_zero(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_zero(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, 0); + return fn(addr, handle, 0); } -static void intc_mode_prio(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) +static unsigned long intc_mode_prio(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) { - fn(addr, handle, intc_prio_level[irq]); + return fn(addr, handle, intc_prio_level[irq]); } -static void (*intc_enable_fns[])(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { +static unsigned long (*intc_enable_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { [MODE_ENABLE_REG] = intc_mode_field, [MODE_MASK_REG] = intc_mode_zero, [MODE_DUAL_REG] = intc_mode_field, @@ -285,9 +369,9 @@ static void (*intc_enable_fns[])(unsigned long addr, [MODE_PCLR_REG] = intc_mode_prio, }; -static void (*intc_disable_fns[])(unsigned long addr, +static unsigned long (*intc_disable_fns[])(unsigned long addr, unsigned long handle, - void (*fn)(unsigned long, + unsigned long (*fn)(unsigned long, unsigned long, unsigned long), unsigned int irq) = { @@ -421,12 +505,13 @@ static void intc_disable(unsigned int irq) } } -static void (*intc_enable_noprio_fns[])(unsigned long addr, - unsigned long handle, - void (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { +static unsigned long +(*intc_enable_noprio_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { [MODE_ENABLE_REG] = intc_mode_field, [MODE_MASK_REG] = intc_mode_zero, [MODE_DUAL_REG] = intc_mode_field, @@ -439,8 +524,9 @@ static void intc_enable_disable(struct intc_desc_int *d, { unsigned long addr; unsigned int cpu; - void (*fn)(unsigned long, unsigned long, - void (*)(unsigned long, unsigned long, unsigned long), + unsigned long (*fn)(unsigned long, unsigned long, + unsigned long (*)(unsigned long, unsigned long, + unsigned long), unsigned int); if (do_enable) { @@ -861,6 +947,186 @@ unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id) } EXPORT_SYMBOL_GPL(intc_irq_lookup); +static int add_virq_to_pirq(unsigned int irq, unsigned int virq) +{ + struct intc_virq_list **last, *entry; + struct irq_desc *desc = irq_to_desc(irq); + + /* scan for duplicates */ + last = (struct intc_virq_list **)&desc->handler_data; + for_each_virq(entry, desc->handler_data) { + if (entry->irq == virq) + return 0; + last = &entry->next; + } + + entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC); + if (!entry) { + pr_err("can't allocate VIRQ mapping for %d\n", virq); + return -ENOMEM; + } + + entry->irq = virq; + + *last = entry; + + return 0; +} + +static void intc_virq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct intc_virq_list *entry, *vlist = get_irq_data(irq); + struct intc_desc_int *d = get_intc_desc(irq); + + desc->chip->mask_ack(irq); + + for_each_virq(entry, vlist) { + unsigned long addr, handle; + + handle = (unsigned long)get_irq_data(entry->irq); + addr = INTC_REG(d, _INTC_ADDR_E(handle), 0); + + if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0)) + generic_handle_irq(entry->irq); + } + + desc->chip->unmask(irq); +} + +static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup, + struct intc_desc_int *d, + unsigned int index) +{ + unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1; + + return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg), + 0, 1, (subgroup->reg_width - 1) - index); +} + +#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 + +static void __init intc_subgroup_init_one(struct intc_desc *desc, + struct intc_desc_int *d, + struct intc_subgroup *subgroup) +{ + struct intc_map_entry *mapped; + unsigned int pirq; + unsigned long flags; + int i; + + mapped = radix_tree_lookup(&d->tree, subgroup->parent_id); + if (!mapped) { + WARN_ON(1); + return; + } + + pirq = mapped - intc_irq_xlate; + + spin_lock_irqsave(&d->lock, flags); + + for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) { + struct intc_subgroup_entry *entry; + int err; + + if (!subgroup->enum_ids[i]) + continue; + + entry = kmalloc(sizeof(*entry), GFP_NOWAIT); + if (!entry) + break; + + entry->pirq = pirq; + entry->enum_id = subgroup->enum_ids[i]; + entry->handle = intc_subgroup_data(subgroup, d, i); + + err = radix_tree_insert(&d->tree, entry->enum_id, entry); + if (unlikely(err < 0)) + break; + + radix_tree_tag_set(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + } + + spin_unlock_irqrestore(&d->lock, flags); +} + +static void __init intc_subgroup_init(struct intc_desc *desc, + struct intc_desc_int *d) +{ + int i; + + if (!desc->hw.subgroups) + return; + + for (i = 0; i < desc->hw.nr_subgroups; i++) + intc_subgroup_init_one(desc, d, desc->hw.subgroups + i); +} + +static void __init intc_subgroup_map(struct intc_desc_int *d) +{ + struct intc_subgroup_entry *entries[32]; + unsigned long flags; + unsigned int nr_found; + int i; + + spin_lock_irqsave(&d->lock, flags); + +restart: + nr_found = radix_tree_gang_lookup_tag_slot(&d->tree, + (void ***)entries, 0, ARRAY_SIZE(entries), + INTC_TAG_VIRQ_NEEDS_ALLOC); + + for (i = 0; i < nr_found; i++) { + struct intc_subgroup_entry *entry; + int irq; + + entry = radix_tree_deref_slot((void **)entries[i]); + if (unlikely(!entry)) + continue; + if (unlikely(entry == RADIX_TREE_RETRY)) + goto restart; + + irq = create_irq(); + if (unlikely(irq < 0)) { + pr_err("no more free IRQs, bailing..\n"); + break; + } + + pr_info("Setting up a chained VIRQ from %d -> %d\n", + irq, entry->pirq); + + spin_lock(&xlate_lock); + intc_irq_xlate[irq].desc = d; + intc_irq_xlate[irq].enum_id = entry->enum_id; + spin_unlock(&xlate_lock); + + set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq), + handle_simple_irq, "virq"); + set_irq_chip_data(irq, get_irq_chip_data(entry->pirq)); + + set_irq_data(irq, (void *)entry->handle); + + set_irq_chained_handler(entry->pirq, intc_virq_handler); + add_virq_to_pirq(entry->pirq, irq); + + radix_tree_tag_clear(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + radix_tree_replace_slot((void **)entries[i], + &intc_irq_xlate[irq]); + } + + spin_unlock_irqrestore(&d->lock, flags); +} + +void __init intc_finalize(void) +{ + struct intc_desc_int *d; + + list_for_each_entry(d, &intc_list, list) + if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC)) + intc_subgroup_map(d); +} + static void __init intc_register_irq(struct intc_desc *desc, struct intc_desc_int *d, intc_enum enum_id, @@ -868,6 +1134,7 @@ static void __init intc_register_irq(struct intc_desc *desc, { struct intc_handle_int *hp; unsigned int data[2], primary; + unsigned long flags; /* * Register the IRQ position with the global IRQ map, then insert @@ -875,9 +1142,9 @@ static void __init intc_register_irq(struct intc_desc *desc, */ set_bit(irq, intc_irq_map); - mutex_lock(&irq_xlate_mutex); + spin_lock_irqsave(&xlate_lock, flags); radix_tree_insert(&d->tree, enum_id, &intc_irq_xlate[irq]); - mutex_unlock(&irq_xlate_mutex); + spin_unlock_irqrestore(&xlate_lock, flags); /* * Prefer single interrupt source bitmap over other combinations: @@ -957,9 +1224,7 @@ static void __init intc_register_irq(struct intc_desc *desc, dist_handle[irq] = intc_dist_data(desc, d, enum_id); #endif -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */ -#endif + activate_irq(irq); } static unsigned int __init save_reg(struct intc_desc_int *d, @@ -980,11 +1245,6 @@ static unsigned int __init save_reg(struct intc_desc_int *d, return 0; } -static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) -{ - generic_handle_irq((unsigned int)get_irq_data(irq)); -} - int __init register_intc_controller(struct intc_desc *desc) { unsigned int i, k, smp; @@ -1000,7 +1260,11 @@ int __init register_intc_controller(struct intc_desc *desc) goto err0; INIT_LIST_HEAD(&d->list); - list_add(&d->list, &intc_list); + list_add_tail(&d->list, &intc_list); + + spin_lock_init(&d->lock); + + d->index = nr_intc_controllers; if (desc->num_resources) { d->nr_windows = desc->num_resources; @@ -1029,6 +1293,7 @@ int __init register_intc_controller(struct intc_desc *desc) d->nr_reg += hw->prio_regs ? hw->nr_prio_regs * 2 : 0; d->nr_reg += hw->sense_regs ? hw->nr_sense_regs : 0; d->nr_reg += hw->ack_regs ? hw->nr_ack_regs : 0; + d->nr_reg += hw->subgroups ? hw->nr_subgroups : 0; d->reg = kzalloc(d->nr_reg * sizeof(*d->reg), GFP_NOWAIT); if (!d->reg) @@ -1075,6 +1340,11 @@ int __init register_intc_controller(struct intc_desc *desc) k += save_reg(d, k, hw->sense_regs[i].reg, 0); } + if (hw->subgroups) + for (i = 0; i < hw->nr_subgroups; i++) + if (hw->subgroups[i].reg) + k+= save_reg(d, k, hw->subgroups[i].reg, 0); + d->chip.name = desc->name; d->chip.mask = intc_disable; d->chip.unmask = intc_enable; @@ -1109,6 +1379,7 @@ int __init register_intc_controller(struct intc_desc *desc) for (i = 0; i < hw->nr_vectors; i++) { struct intc_vect *vect = hw->vectors + i; unsigned int irq = evt2irq(vect->vect); + unsigned long flags; struct irq_desc *irq_desc; if (!vect->enum_id) @@ -1120,8 +1391,10 @@ int __init register_intc_controller(struct intc_desc *desc) continue; } + spin_lock_irqsave(&xlate_lock, flags); intc_irq_xlate[irq].enum_id = vect->enum_id; intc_irq_xlate[irq].desc = d; + spin_unlock_irqrestore(&xlate_lock, flags); intc_register_irq(desc, d, vect->enum_id, irq); @@ -1152,10 +1425,14 @@ int __init register_intc_controller(struct intc_desc *desc) } } + intc_subgroup_init(desc, d); + /* enable bits matching force_enable after registering irqs */ if (desc->force_enable) intc_enable_disable_enum(desc, d, desc->force_enable, 1); + nr_intc_controllers++; + return 0; err5: kfree(d->prio); @@ -1353,7 +1630,6 @@ static int __init register_intc_sysdevs(void) { struct intc_desc_int *d; int error; - int id = 0; error = sysdev_class_register(&intc_sysdev_class); #ifdef CONFIG_INTC_USERIMASK @@ -1363,7 +1639,7 @@ static int __init register_intc_sysdevs(void) #endif if (!error) { list_for_each_entry(d, &intc_list, list) { - d->sysdev.id = id; + d->sysdev.id = d->index; d->sysdev.cls = &intc_sysdev_class; error = sysdev_register(&d->sysdev); if (error == 0) @@ -1371,8 +1647,6 @@ static int __init register_intc_sysdevs(void) &attr_name); if (error) break; - - id++; } } @@ -1422,9 +1696,7 @@ out_unlock: if (irq > 0) { dynamic_irq_init(irq); -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */ -#endif + activate_irq(irq); } return irq; diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index d40fd77fa75c..04134a6c7b52 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -20,6 +20,12 @@ struct intc_group { #define INTC_GROUP(enum_id, ids...) { enum_id, { ids } } +struct intc_subgroup { + unsigned long reg, reg_width; + intc_enum parent_id; + intc_enum enum_ids[32]; +}; + struct intc_mask_reg { unsigned long set_reg, clr_reg, reg_width; intc_enum enum_ids[32]; @@ -69,9 +75,12 @@ struct intc_hw_desc { unsigned int nr_sense_regs; struct intc_mask_reg *ack_regs; unsigned int nr_ack_regs; + struct intc_subgroup *subgroups; + unsigned int nr_subgroups; }; -#define _INTC_ARRAY(a) a, sizeof(a)/sizeof(*a) +#define _INTC_ARRAY(a) a, a == NULL ? 0 : sizeof(a)/sizeof(*a) + #define INTC_HW_DESC(vectors, groups, mask_regs, \ prio_regs, sense_regs, ack_regs) \ { \ @@ -109,6 +118,7 @@ int __init register_intc_controller(struct intc_desc *desc); void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs); int intc_set_priority(unsigned int irq, unsigned int prio); unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id); +void intc_finalize(void); #ifdef CONFIG_INTC_USERIMASK int register_intc_userimask(unsigned long addr); -- cgit v1.2.3 From d74310d3b18aabbb7d0549ea9e3fd3259c1dce00 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Tue, 5 Oct 2010 18:13:23 +0900 Subject: sh: intc: Handle early lookups of subgroup IRQs. If lookups happen while the radix node still points to a subgroup mapping, an IRQ hasn't yet been made available for the specified id, so error out accordingly. Once the slot is replaced with an IRQ mapping and the tag is discarded, lookup can commence as normal. Signed-off-by: Paul Mundt --- drivers/sh/intc.c | 34 ++++++++++++++++++++++++---------- include/linux/sh_intc.h | 2 +- 2 files changed, 25 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c index c81fe23db7f7..d4325c70cf61 100644 --- a/drivers/sh/intc.c +++ b/drivers/sh/intc.c @@ -927,19 +927,35 @@ static unsigned int __init intc_sense_data(struct intc_desc *desc, return 0; } -unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id) +#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 + +int intc_irq_lookup(const char *chipname, intc_enum enum_id) { struct intc_map_entry *ptr; struct intc_desc_int *d; - unsigned int irq = 0; + int irq = -1; list_for_each_entry(d, &intc_list, list) { - if (strcmp(d->chip.name, chipname) == 0) { - ptr = radix_tree_lookup(&d->tree, enum_id); - if (ptr) { - irq = ptr - intc_irq_xlate; - break; - } + int tagged; + + if (strcmp(d->chip.name, chipname) != 0) + continue; + + /* + * Catch early lookups for subgroup VIRQs that have not + * yet been allocated an IRQ. This already includes a + * fast-path out if the tree is untagged, so there is no + * need to explicitly test the root tree. + */ + tagged = radix_tree_tag_get(&d->tree, enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + if (unlikely(tagged)) + break; + + ptr = radix_tree_lookup(&d->tree, enum_id); + if (ptr) { + irq = ptr - intc_irq_xlate; + break; } } @@ -1003,8 +1019,6 @@ static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup, 0, 1, (subgroup->reg_width - 1) - index); } -#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 - static void __init intc_subgroup_init_one(struct intc_desc *desc, struct intc_desc_int *d, struct intc_subgroup *subgroup) diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index 04134a6c7b52..1fc69701e0f8 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -117,7 +117,7 @@ struct intc_desc symbol __initdata = { \ int __init register_intc_controller(struct intc_desc *desc); void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs); int intc_set_priority(unsigned int irq, unsigned int prio); -unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id); +int intc_irq_lookup(const char *chipname, intc_enum enum_id); void intc_finalize(void); #ifdef CONFIG_INTC_USERIMASK -- cgit v1.2.3 From 2be6bb0c79c7fbda3425b65ee51c558bbaf4cf91 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Tue, 5 Oct 2010 22:10:30 +0900 Subject: sh: intc: Split up the INTC code. This splits up the sh intc core in to something more vaguely resembling a subsystem. Most of the functionality was alread fairly well compartmentalized, and there were only a handful of interdependencies that needed to be resolved in the process. This also serves as future-proofing for the genirq and sparseirq rework, which will make some of the split out functionality wholly generic, allowing things to be killed off in place with minimal migration pain. Signed-off-by: Paul Mundt --- drivers/sh/Kconfig | 34 +- drivers/sh/Makefile | 2 +- drivers/sh/intc.c | 1776 ---------------------------------------- drivers/sh/intc/access.c | 237 ++++++ drivers/sh/intc/balancing.c | 97 +++ drivers/sh/intc/chip.c | 215 +++++ drivers/sh/intc/core.c | 469 +++++++++++ drivers/sh/intc/dynamic.c | 135 +++ drivers/sh/intc/handle.c | 307 +++++++ drivers/sh/intc/internals.h | 185 +++++ drivers/sh/intc/userimask.c | 83 ++ drivers/sh/intc/virq-debugfs.c | 64 ++ drivers/sh/intc/virq.c | 255 ++++++ include/linux/sh_intc.h | 2 +- 14 files changed, 2052 insertions(+), 1809 deletions(-) delete mode 100644 drivers/sh/intc.c create mode 100644 drivers/sh/intc/access.c create mode 100644 drivers/sh/intc/balancing.c create mode 100644 drivers/sh/intc/chip.c create mode 100644 drivers/sh/intc/core.c create mode 100644 drivers/sh/intc/dynamic.c create mode 100644 drivers/sh/intc/handle.c create mode 100644 drivers/sh/intc/internals.h create mode 100644 drivers/sh/intc/userimask.c create mode 100644 drivers/sh/intc/virq-debugfs.c create mode 100644 drivers/sh/intc/virq.c (limited to 'drivers') diff --git a/drivers/sh/Kconfig b/drivers/sh/Kconfig index e01ae42774af..f168a6159961 100644 --- a/drivers/sh/Kconfig +++ b/drivers/sh/Kconfig @@ -1,33 +1,5 @@ -config INTC_USERIMASK - bool "Userspace interrupt masking support" - depends on ARCH_SHMOBILE || (SUPERH && CPU_SH4A) - help - This enables support for hardware-assisted userspace hardirq - masking. +menu "SuperH / SH-Mobile Driver Options" - SH-4A and newer interrupt blocks all support a special shadowed - page with all non-masking registers obscured when mapped in to - userspace. This is primarily for use by userspace device - drivers that are using special priority levels. +source "drivers/sh/intc/Kconfig" - If in doubt, say N. - -config INTC_BALANCING - bool "Hardware IRQ balancing support" - depends on SMP && SUPERH && CPU_SHX3 - help - This enables support for IRQ auto-distribution mode on SH-X3 - SMP parts. All of the balancing and CPU wakeup decisions are - taken care of automatically by hardware for distributed - vectors. - - If in doubt, say N. - -config INTC_MAPPING_DEBUG - bool "Expose IRQ to per-controller id mapping via debugfs" - depends on DEBUG_FS - help - This will create a debugfs entry for showing the relationship - between system IRQs and the per-controller id tables. - - If in doubt, say N. +endmenu diff --git a/drivers/sh/Makefile b/drivers/sh/Makefile index 08fc653a825c..50dd5a65f72e 100644 --- a/drivers/sh/Makefile +++ b/drivers/sh/Makefile @@ -1,7 +1,7 @@ # # Makefile for the SuperH specific drivers. # -obj-y := clk.o intc.o +obj-y := clk.o intc/ obj-$(CONFIG_SUPERHYWAY) += superhyway/ obj-$(CONFIG_MAPLE) += maple/ diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c deleted file mode 100644 index d4325c70cf61..000000000000 --- a/drivers/sh/intc.c +++ /dev/null @@ -1,1776 +0,0 @@ -/* - * Shared interrupt handling code for IPR and INTC2 types of IRQs. - * - * Copyright (C) 2007, 2008 Magnus Damm - * Copyright (C) 2009, 2010 Paul Mundt - * - * Based on intc2.c and ipr.c - * - * Copyright (C) 1999 Niibe Yutaka & Takeshi Yaegashi - * Copyright (C) 2000 Kazumoto Kojima - * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) - * Copyright (C) 2003 Takashi Kusuda - * Copyright (C) 2005, 2006 Paul Mundt - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ - ((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \ - ((addr_e) << 16) | ((addr_d << 24))) - -#define _INTC_SHIFT(h) (h & 0x1f) -#define _INTC_WIDTH(h) ((h >> 5) & 0xf) -#define _INTC_FN(h) ((h >> 9) & 0xf) -#define _INTC_MODE(h) ((h >> 13) & 0x7) -#define _INTC_ADDR_E(h) ((h >> 16) & 0xff) -#define _INTC_ADDR_D(h) ((h >> 24) & 0xff) - -struct intc_handle_int { - unsigned int irq; - unsigned long handle; -}; - -struct intc_window { - phys_addr_t phys; - void __iomem *virt; - unsigned long size; -}; - -struct intc_map_entry { - intc_enum enum_id; - struct intc_desc_int *desc; -}; - -struct intc_subgroup_entry { - unsigned int pirq; - intc_enum enum_id; - unsigned long handle; -}; - -struct intc_desc_int { - struct list_head list; - struct sys_device sysdev; - struct radix_tree_root tree; - pm_message_t state; - spinlock_t lock; - unsigned int index; - unsigned long *reg; -#ifdef CONFIG_SMP - unsigned long *smp; -#endif - unsigned int nr_reg; - struct intc_handle_int *prio; - unsigned int nr_prio; - struct intc_handle_int *sense; - unsigned int nr_sense; - struct intc_window *window; - unsigned int nr_windows; - struct irq_chip chip; -}; - -static LIST_HEAD(intc_list); -static unsigned int nr_intc_controllers; - -/* - * The intc_irq_map provides a global map of bound IRQ vectors for a - * given platform. Allocation of IRQs are either static through the CPU - * vector map, or dynamic in the case of board mux vectors or MSI. - * - * As this is a central point for all IRQ controllers on the system, - * each of the available sources are mapped out here. This combined with - * sparseirq makes it quite trivial to keep the vector map tightly packed - * when dynamically creating IRQs, as well as tying in to otherwise - * unused irq_desc positions in the sparse array. - */ -static DECLARE_BITMAP(intc_irq_map, NR_IRQS); -static struct intc_map_entry intc_irq_xlate[NR_IRQS]; -static DEFINE_SPINLOCK(vector_lock); -static DEFINE_SPINLOCK(xlate_lock); - -#ifdef CONFIG_SMP -#define IS_SMP(x) x.smp -#define INTC_REG(d, x, c) (d->reg[(x)] + ((d->smp[(x)] & 0xff) * c)) -#define SMP_NR(d, x) ((d->smp[(x)] >> 8) ? (d->smp[(x)] >> 8) : 1) -#else -#define IS_SMP(x) 0 -#define INTC_REG(d, x, c) (d->reg[(x)]) -#define SMP_NR(d, x) 1 -#endif - -static unsigned int intc_prio_level[NR_IRQS]; /* for now */ -static unsigned int default_prio_level = 2; /* 2 - 16 */ -static unsigned long ack_handle[NR_IRQS]; -#ifdef CONFIG_INTC_BALANCING -static unsigned long dist_handle[NR_IRQS]; -#endif - -struct intc_virq_list { - unsigned int irq; - struct intc_virq_list *next; -}; - -#define for_each_virq(entry, head) \ - for (entry = head; entry; entry = entry->next) - -static inline struct intc_desc_int *get_intc_desc(unsigned int irq) -{ - struct irq_chip *chip = get_irq_chip(irq); - - return container_of(chip, struct intc_desc_int, chip); -} - -static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) -{ - generic_handle_irq((unsigned int)get_irq_data(irq)); -} - -static inline void activate_irq(int irq) -{ -#ifdef CONFIG_ARM - /* ARM requires an extra step to clear IRQ_NOREQUEST, which it - * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. - */ - set_irq_flags(irq, IRQF_VALID); -#else - /* same effect on other architectures */ - set_irq_noprobe(irq); -#endif -} - -static unsigned long intc_phys_to_virt(struct intc_desc_int *d, - unsigned long address) -{ - struct intc_window *window; - int k; - - /* scan through physical windows and convert address */ - for (k = 0; k < d->nr_windows; k++) { - window = d->window + k; - - if (address < window->phys) - continue; - - if (address >= (window->phys + window->size)) - continue; - - address -= window->phys; - address += (unsigned long)window->virt; - - return address; - } - - /* no windows defined, register must be 1:1 mapped virt:phys */ - return address; -} - -static unsigned int intc_get_reg(struct intc_desc_int *d, unsigned long address) -{ - unsigned int k; - - address = intc_phys_to_virt(d, address); - - for (k = 0; k < d->nr_reg; k++) { - if (d->reg[k] == address) - return k; - } - - BUG(); - return 0; -} - -static inline unsigned int set_field(unsigned int value, - unsigned int field_value, - unsigned int handle) -{ - unsigned int width = _INTC_WIDTH(handle); - unsigned int shift = _INTC_SHIFT(handle); - - value &= ~(((1 << width) - 1) << shift); - value |= field_value << shift; - return value; -} - -static inline unsigned long get_field(unsigned int value, unsigned int handle) -{ - unsigned int width = _INTC_WIDTH(handle); - unsigned int shift = _INTC_SHIFT(handle); - unsigned int mask = ((1 << width) - 1) << shift; - - return (value & mask) >> shift; -} - -static unsigned long test_8(unsigned long addr, unsigned long h, - unsigned long ignore) -{ - return get_field(__raw_readb(addr), h); -} - -static unsigned long test_16(unsigned long addr, unsigned long h, - unsigned long ignore) -{ - return get_field(__raw_readw(addr), h); -} - -static unsigned long test_32(unsigned long addr, unsigned long h, - unsigned long ignore) -{ - return get_field(__raw_readl(addr), h); -} - -static unsigned long write_8(unsigned long addr, unsigned long h, - unsigned long data) -{ - __raw_writeb(set_field(0, data, h), addr); - (void)__raw_readb(addr); /* Defeat write posting */ - return 0; -} - -static unsigned long write_16(unsigned long addr, unsigned long h, - unsigned long data) -{ - __raw_writew(set_field(0, data, h), addr); - (void)__raw_readw(addr); /* Defeat write posting */ - return 0; -} - -static unsigned long write_32(unsigned long addr, unsigned long h, - unsigned long data) -{ - __raw_writel(set_field(0, data, h), addr); - (void)__raw_readl(addr); /* Defeat write posting */ - return 0; -} - -static unsigned long modify_8(unsigned long addr, unsigned long h, - unsigned long data) -{ - unsigned long flags; - local_irq_save(flags); - __raw_writeb(set_field(__raw_readb(addr), data, h), addr); - (void)__raw_readb(addr); /* Defeat write posting */ - local_irq_restore(flags); - return 0; -} - -static unsigned long modify_16(unsigned long addr, unsigned long h, - unsigned long data) -{ - unsigned long flags; - local_irq_save(flags); - __raw_writew(set_field(__raw_readw(addr), data, h), addr); - (void)__raw_readw(addr); /* Defeat write posting */ - local_irq_restore(flags); - return 0; -} - -static unsigned long modify_32(unsigned long addr, unsigned long h, - unsigned long data) -{ - unsigned long flags; - local_irq_save(flags); - __raw_writel(set_field(__raw_readl(addr), data, h), addr); - (void)__raw_readl(addr); /* Defeat write posting */ - local_irq_restore(flags); - return 0; -} - -enum { - REG_FN_ERR = 0, - REG_FN_TEST_BASE = 1, - REG_FN_WRITE_BASE = 5, - REG_FN_MODIFY_BASE = 9 -}; - -static unsigned long (*intc_reg_fns[])(unsigned long addr, - unsigned long h, - unsigned long data) = { - [REG_FN_TEST_BASE + 0] = test_8, - [REG_FN_TEST_BASE + 1] = test_16, - [REG_FN_TEST_BASE + 3] = test_32, - [REG_FN_WRITE_BASE + 0] = write_8, - [REG_FN_WRITE_BASE + 1] = write_16, - [REG_FN_WRITE_BASE + 3] = write_32, - [REG_FN_MODIFY_BASE + 0] = modify_8, - [REG_FN_MODIFY_BASE + 1] = modify_16, - [REG_FN_MODIFY_BASE + 3] = modify_32, -}; - -enum { MODE_ENABLE_REG = 0, /* Bit(s) set -> interrupt enabled */ - MODE_MASK_REG, /* Bit(s) set -> interrupt disabled */ - MODE_DUAL_REG, /* Two registers, set bit to enable / disable */ - MODE_PRIO_REG, /* Priority value written to enable interrupt */ - MODE_PCLR_REG, /* Above plus all bits set to disable interrupt */ -}; - -static unsigned long intc_mode_field(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) -{ - return fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); -} - -static unsigned long intc_mode_zero(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) -{ - return fn(addr, handle, 0); -} - -static unsigned long intc_mode_prio(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) -{ - return fn(addr, handle, intc_prio_level[irq]); -} - -static unsigned long (*intc_enable_fns[])(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { - [MODE_ENABLE_REG] = intc_mode_field, - [MODE_MASK_REG] = intc_mode_zero, - [MODE_DUAL_REG] = intc_mode_field, - [MODE_PRIO_REG] = intc_mode_prio, - [MODE_PCLR_REG] = intc_mode_prio, -}; - -static unsigned long (*intc_disable_fns[])(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { - [MODE_ENABLE_REG] = intc_mode_zero, - [MODE_MASK_REG] = intc_mode_field, - [MODE_DUAL_REG] = intc_mode_field, - [MODE_PRIO_REG] = intc_mode_zero, - [MODE_PCLR_REG] = intc_mode_field, -}; - -#ifdef CONFIG_INTC_BALANCING -static inline void intc_balancing_enable(unsigned int irq) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned long handle = dist_handle[irq]; - unsigned long addr; - - if (irq_balancing_disabled(irq) || !handle) - return; - - addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); - intc_reg_fns[_INTC_FN(handle)](addr, handle, 1); -} - -static inline void intc_balancing_disable(unsigned int irq) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned long handle = dist_handle[irq]; - unsigned long addr; - - if (irq_balancing_disabled(irq) || !handle) - return; - - addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); - intc_reg_fns[_INTC_FN(handle)](addr, handle, 0); -} - -static unsigned int intc_dist_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id) -{ - struct intc_mask_reg *mr = desc->hw.mask_regs; - unsigned int i, j, fn, mode; - unsigned long reg_e, reg_d; - - for (i = 0; mr && enum_id && i < desc->hw.nr_mask_regs; i++) { - mr = desc->hw.mask_regs + i; - - /* - * Skip this entry if there's no auto-distribution - * register associated with it. - */ - if (!mr->dist_reg) - continue; - - for (j = 0; j < ARRAY_SIZE(mr->enum_ids); j++) { - if (mr->enum_ids[j] != enum_id) - continue; - - fn = REG_FN_MODIFY_BASE; - mode = MODE_ENABLE_REG; - reg_e = mr->dist_reg; - reg_d = mr->dist_reg; - - fn += (mr->reg_width >> 3) - 1; - return _INTC_MK(fn, mode, - intc_get_reg(d, reg_e), - intc_get_reg(d, reg_d), - 1, - (mr->reg_width - 1) - j); - } - } - - /* - * It's possible we've gotten here with no distribution options - * available for the IRQ in question, so we just skip over those. - */ - return 0; -} -#else -static inline void intc_balancing_enable(unsigned int irq) -{ -} - -static inline void intc_balancing_disable(unsigned int irq) -{ -} -#endif - -static inline void _intc_enable(unsigned int irq, unsigned long handle) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned long addr; - unsigned int cpu; - - for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_E(handle)); cpu++) { -#ifdef CONFIG_SMP - if (!cpumask_test_cpu(cpu, irq_to_desc(irq)->affinity)) - continue; -#endif - addr = INTC_REG(d, _INTC_ADDR_E(handle), cpu); - intc_enable_fns[_INTC_MODE(handle)](addr, handle, intc_reg_fns\ - [_INTC_FN(handle)], irq); - } - - intc_balancing_enable(irq); -} - -static void intc_enable(unsigned int irq) -{ - _intc_enable(irq, (unsigned long)get_irq_chip_data(irq)); -} - -static void intc_disable(unsigned int irq) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned long handle = (unsigned long)get_irq_chip_data(irq); - unsigned long addr; - unsigned int cpu; - - intc_balancing_disable(irq); - - for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_D(handle)); cpu++) { -#ifdef CONFIG_SMP - if (!cpumask_test_cpu(cpu, irq_to_desc(irq)->affinity)) - continue; -#endif - addr = INTC_REG(d, _INTC_ADDR_D(handle), cpu); - intc_disable_fns[_INTC_MODE(handle)](addr, handle,intc_reg_fns\ - [_INTC_FN(handle)], irq); - } -} - -static unsigned long -(*intc_enable_noprio_fns[])(unsigned long addr, - unsigned long handle, - unsigned long (*fn)(unsigned long, - unsigned long, - unsigned long), - unsigned int irq) = { - [MODE_ENABLE_REG] = intc_mode_field, - [MODE_MASK_REG] = intc_mode_zero, - [MODE_DUAL_REG] = intc_mode_field, - [MODE_PRIO_REG] = intc_mode_field, - [MODE_PCLR_REG] = intc_mode_field, -}; - -static void intc_enable_disable(struct intc_desc_int *d, - unsigned long handle, int do_enable) -{ - unsigned long addr; - unsigned int cpu; - unsigned long (*fn)(unsigned long, unsigned long, - unsigned long (*)(unsigned long, unsigned long, - unsigned long), - unsigned int); - - if (do_enable) { - for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_E(handle)); cpu++) { - addr = INTC_REG(d, _INTC_ADDR_E(handle), cpu); - fn = intc_enable_noprio_fns[_INTC_MODE(handle)]; - fn(addr, handle, intc_reg_fns[_INTC_FN(handle)], 0); - } - } else { - for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_D(handle)); cpu++) { - addr = INTC_REG(d, _INTC_ADDR_D(handle), cpu); - fn = intc_disable_fns[_INTC_MODE(handle)]; - fn(addr, handle, intc_reg_fns[_INTC_FN(handle)], 0); - } - } -} - -static int intc_set_wake(unsigned int irq, unsigned int on) -{ - return 0; /* allow wakeup, but setup hardware in intc_suspend() */ -} - -#ifdef CONFIG_SMP -/* - * This is held with the irq desc lock held, so we don't require any - * additional locking here at the intc desc level. The affinity mask is - * later tested in the enable/disable paths. - */ -static int intc_set_affinity(unsigned int irq, const struct cpumask *cpumask) -{ - if (!cpumask_intersects(cpumask, cpu_online_mask)) - return -1; - - cpumask_copy(irq_to_desc(irq)->affinity, cpumask); - - return 0; -} -#endif - -static void intc_mask_ack(unsigned int irq) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned long handle = ack_handle[irq]; - unsigned long addr; - - intc_disable(irq); - - /* read register and write zero only to the associated bit */ - if (handle) { - addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); - switch (_INTC_FN(handle)) { - case REG_FN_MODIFY_BASE + 0: /* 8bit */ - __raw_readb(addr); - __raw_writeb(0xff ^ set_field(0, 1, handle), addr); - break; - case REG_FN_MODIFY_BASE + 1: /* 16bit */ - __raw_readw(addr); - __raw_writew(0xffff ^ set_field(0, 1, handle), addr); - break; - case REG_FN_MODIFY_BASE + 3: /* 32bit */ - __raw_readl(addr); - __raw_writel(0xffffffff ^ set_field(0, 1, handle), addr); - break; - default: - BUG(); - break; - } - } -} - -static struct intc_handle_int *intc_find_irq(struct intc_handle_int *hp, - unsigned int nr_hp, - unsigned int irq) -{ - int i; - - /* - * this doesn't scale well, but... - * - * this function should only be used for cerain uncommon - * operations such as intc_set_priority() and intc_set_sense() - * and in those rare cases performance doesn't matter that much. - * keeping the memory footprint low is more important. - * - * one rather simple way to speed this up and still keep the - * memory footprint down is to make sure the array is sorted - * and then perform a bisect to lookup the irq. - */ - for (i = 0; i < nr_hp; i++) { - if ((hp + i)->irq != irq) - continue; - - return hp + i; - } - - return NULL; -} - -int intc_set_priority(unsigned int irq, unsigned int prio) -{ - struct intc_desc_int *d = get_intc_desc(irq); - struct intc_handle_int *ihp; - - if (!intc_prio_level[irq] || prio <= 1) - return -EINVAL; - - ihp = intc_find_irq(d->prio, d->nr_prio, irq); - if (ihp) { - if (prio >= (1 << _INTC_WIDTH(ihp->handle))) - return -EINVAL; - - intc_prio_level[irq] = prio; - - /* - * only set secondary masking method directly - * primary masking method is using intc_prio_level[irq] - * priority level will be set during next enable() - */ - if (_INTC_FN(ihp->handle) != REG_FN_ERR) - _intc_enable(irq, ihp->handle); - } - return 0; -} - -#define VALID(x) (x | 0x80) - -static unsigned char intc_irq_sense_table[IRQ_TYPE_SENSE_MASK + 1] = { - [IRQ_TYPE_EDGE_FALLING] = VALID(0), - [IRQ_TYPE_EDGE_RISING] = VALID(1), - [IRQ_TYPE_LEVEL_LOW] = VALID(2), - /* SH7706, SH7707 and SH7709 do not support high level triggered */ -#if !defined(CONFIG_CPU_SUBTYPE_SH7706) && \ - !defined(CONFIG_CPU_SUBTYPE_SH7707) && \ - !defined(CONFIG_CPU_SUBTYPE_SH7709) - [IRQ_TYPE_LEVEL_HIGH] = VALID(3), -#endif -}; - -static int intc_set_sense(unsigned int irq, unsigned int type) -{ - struct intc_desc_int *d = get_intc_desc(irq); - unsigned char value = intc_irq_sense_table[type & IRQ_TYPE_SENSE_MASK]; - struct intc_handle_int *ihp; - unsigned long addr; - - if (!value) - return -EINVAL; - - ihp = intc_find_irq(d->sense, d->nr_sense, irq); - if (ihp) { - addr = INTC_REG(d, _INTC_ADDR_E(ihp->handle), 0); - intc_reg_fns[_INTC_FN(ihp->handle)](addr, ihp->handle, value); - } - return 0; -} - -static intc_enum __init intc_grp_id(struct intc_desc *desc, - intc_enum enum_id) -{ - struct intc_group *g = desc->hw.groups; - unsigned int i, j; - - for (i = 0; g && enum_id && i < desc->hw.nr_groups; i++) { - g = desc->hw.groups + i; - - for (j = 0; g->enum_ids[j]; j++) { - if (g->enum_ids[j] != enum_id) - continue; - - return g->enum_id; - } - } - - return 0; -} - -static unsigned int __init _intc_mask_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, - unsigned int *reg_idx, - unsigned int *fld_idx) -{ - struct intc_mask_reg *mr = desc->hw.mask_regs; - unsigned int fn, mode; - unsigned long reg_e, reg_d; - - while (mr && enum_id && *reg_idx < desc->hw.nr_mask_regs) { - mr = desc->hw.mask_regs + *reg_idx; - - for (; *fld_idx < ARRAY_SIZE(mr->enum_ids); (*fld_idx)++) { - if (mr->enum_ids[*fld_idx] != enum_id) - continue; - - if (mr->set_reg && mr->clr_reg) { - fn = REG_FN_WRITE_BASE; - mode = MODE_DUAL_REG; - reg_e = mr->clr_reg; - reg_d = mr->set_reg; - } else { - fn = REG_FN_MODIFY_BASE; - if (mr->set_reg) { - mode = MODE_ENABLE_REG; - reg_e = mr->set_reg; - reg_d = mr->set_reg; - } else { - mode = MODE_MASK_REG; - reg_e = mr->clr_reg; - reg_d = mr->clr_reg; - } - } - - fn += (mr->reg_width >> 3) - 1; - return _INTC_MK(fn, mode, - intc_get_reg(d, reg_e), - intc_get_reg(d, reg_d), - 1, - (mr->reg_width - 1) - *fld_idx); - } - - *fld_idx = 0; - (*reg_idx)++; - } - - return 0; -} - -static unsigned int __init intc_mask_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, int do_grps) -{ - unsigned int i = 0; - unsigned int j = 0; - unsigned int ret; - - ret = _intc_mask_data(desc, d, enum_id, &i, &j); - if (ret) - return ret; - - if (do_grps) - return intc_mask_data(desc, d, intc_grp_id(desc, enum_id), 0); - - return 0; -} - -static unsigned int __init _intc_prio_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, - unsigned int *reg_idx, - unsigned int *fld_idx) -{ - struct intc_prio_reg *pr = desc->hw.prio_regs; - unsigned int fn, n, mode, bit; - unsigned long reg_e, reg_d; - - while (pr && enum_id && *reg_idx < desc->hw.nr_prio_regs) { - pr = desc->hw.prio_regs + *reg_idx; - - for (; *fld_idx < ARRAY_SIZE(pr->enum_ids); (*fld_idx)++) { - if (pr->enum_ids[*fld_idx] != enum_id) - continue; - - if (pr->set_reg && pr->clr_reg) { - fn = REG_FN_WRITE_BASE; - mode = MODE_PCLR_REG; - reg_e = pr->set_reg; - reg_d = pr->clr_reg; - } else { - fn = REG_FN_MODIFY_BASE; - mode = MODE_PRIO_REG; - if (!pr->set_reg) - BUG(); - reg_e = pr->set_reg; - reg_d = pr->set_reg; - } - - fn += (pr->reg_width >> 3) - 1; - n = *fld_idx + 1; - - BUG_ON(n * pr->field_width > pr->reg_width); - - bit = pr->reg_width - (n * pr->field_width); - - return _INTC_MK(fn, mode, - intc_get_reg(d, reg_e), - intc_get_reg(d, reg_d), - pr->field_width, bit); - } - - *fld_idx = 0; - (*reg_idx)++; - } - - return 0; -} - -static unsigned int __init intc_prio_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, int do_grps) -{ - unsigned int i = 0; - unsigned int j = 0; - unsigned int ret; - - ret = _intc_prio_data(desc, d, enum_id, &i, &j); - if (ret) - return ret; - - if (do_grps) - return intc_prio_data(desc, d, intc_grp_id(desc, enum_id), 0); - - return 0; -} - -static void __init intc_enable_disable_enum(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, int enable) -{ - unsigned int i, j, data; - - /* go through and enable/disable all mask bits */ - i = j = 0; - do { - data = _intc_mask_data(desc, d, enum_id, &i, &j); - if (data) - intc_enable_disable(d, data, enable); - j++; - } while (data); - - /* go through and enable/disable all priority fields */ - i = j = 0; - do { - data = _intc_prio_data(desc, d, enum_id, &i, &j); - if (data) - intc_enable_disable(d, data, enable); - - j++; - } while (data); -} - -static unsigned int __init intc_ack_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id) -{ - struct intc_mask_reg *mr = desc->hw.ack_regs; - unsigned int i, j, fn, mode; - unsigned long reg_e, reg_d; - - for (i = 0; mr && enum_id && i < desc->hw.nr_ack_regs; i++) { - mr = desc->hw.ack_regs + i; - - for (j = 0; j < ARRAY_SIZE(mr->enum_ids); j++) { - if (mr->enum_ids[j] != enum_id) - continue; - - fn = REG_FN_MODIFY_BASE; - mode = MODE_ENABLE_REG; - reg_e = mr->set_reg; - reg_d = mr->set_reg; - - fn += (mr->reg_width >> 3) - 1; - return _INTC_MK(fn, mode, - intc_get_reg(d, reg_e), - intc_get_reg(d, reg_d), - 1, - (mr->reg_width - 1) - j); - } - } - - return 0; -} - -static unsigned int __init intc_sense_data(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id) -{ - struct intc_sense_reg *sr = desc->hw.sense_regs; - unsigned int i, j, fn, bit; - - for (i = 0; sr && enum_id && i < desc->hw.nr_sense_regs; i++) { - sr = desc->hw.sense_regs + i; - - for (j = 0; j < ARRAY_SIZE(sr->enum_ids); j++) { - if (sr->enum_ids[j] != enum_id) - continue; - - fn = REG_FN_MODIFY_BASE; - fn += (sr->reg_width >> 3) - 1; - - BUG_ON((j + 1) * sr->field_width > sr->reg_width); - - bit = sr->reg_width - ((j + 1) * sr->field_width); - - return _INTC_MK(fn, 0, intc_get_reg(d, sr->reg), - 0, sr->field_width, bit); - } - } - - return 0; -} - -#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 - -int intc_irq_lookup(const char *chipname, intc_enum enum_id) -{ - struct intc_map_entry *ptr; - struct intc_desc_int *d; - int irq = -1; - - list_for_each_entry(d, &intc_list, list) { - int tagged; - - if (strcmp(d->chip.name, chipname) != 0) - continue; - - /* - * Catch early lookups for subgroup VIRQs that have not - * yet been allocated an IRQ. This already includes a - * fast-path out if the tree is untagged, so there is no - * need to explicitly test the root tree. - */ - tagged = radix_tree_tag_get(&d->tree, enum_id, - INTC_TAG_VIRQ_NEEDS_ALLOC); - if (unlikely(tagged)) - break; - - ptr = radix_tree_lookup(&d->tree, enum_id); - if (ptr) { - irq = ptr - intc_irq_xlate; - break; - } - } - - return irq; -} -EXPORT_SYMBOL_GPL(intc_irq_lookup); - -static int add_virq_to_pirq(unsigned int irq, unsigned int virq) -{ - struct intc_virq_list **last, *entry; - struct irq_desc *desc = irq_to_desc(irq); - - /* scan for duplicates */ - last = (struct intc_virq_list **)&desc->handler_data; - for_each_virq(entry, desc->handler_data) { - if (entry->irq == virq) - return 0; - last = &entry->next; - } - - entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC); - if (!entry) { - pr_err("can't allocate VIRQ mapping for %d\n", virq); - return -ENOMEM; - } - - entry->irq = virq; - - *last = entry; - - return 0; -} - -static void intc_virq_handler(unsigned int irq, struct irq_desc *desc) -{ - struct intc_virq_list *entry, *vlist = get_irq_data(irq); - struct intc_desc_int *d = get_intc_desc(irq); - - desc->chip->mask_ack(irq); - - for_each_virq(entry, vlist) { - unsigned long addr, handle; - - handle = (unsigned long)get_irq_data(entry->irq); - addr = INTC_REG(d, _INTC_ADDR_E(handle), 0); - - if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0)) - generic_handle_irq(entry->irq); - } - - desc->chip->unmask(irq); -} - -static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup, - struct intc_desc_int *d, - unsigned int index) -{ - unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1; - - return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg), - 0, 1, (subgroup->reg_width - 1) - index); -} - -static void __init intc_subgroup_init_one(struct intc_desc *desc, - struct intc_desc_int *d, - struct intc_subgroup *subgroup) -{ - struct intc_map_entry *mapped; - unsigned int pirq; - unsigned long flags; - int i; - - mapped = radix_tree_lookup(&d->tree, subgroup->parent_id); - if (!mapped) { - WARN_ON(1); - return; - } - - pirq = mapped - intc_irq_xlate; - - spin_lock_irqsave(&d->lock, flags); - - for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) { - struct intc_subgroup_entry *entry; - int err; - - if (!subgroup->enum_ids[i]) - continue; - - entry = kmalloc(sizeof(*entry), GFP_NOWAIT); - if (!entry) - break; - - entry->pirq = pirq; - entry->enum_id = subgroup->enum_ids[i]; - entry->handle = intc_subgroup_data(subgroup, d, i); - - err = radix_tree_insert(&d->tree, entry->enum_id, entry); - if (unlikely(err < 0)) - break; - - radix_tree_tag_set(&d->tree, entry->enum_id, - INTC_TAG_VIRQ_NEEDS_ALLOC); - } - - spin_unlock_irqrestore(&d->lock, flags); -} - -static void __init intc_subgroup_init(struct intc_desc *desc, - struct intc_desc_int *d) -{ - int i; - - if (!desc->hw.subgroups) - return; - - for (i = 0; i < desc->hw.nr_subgroups; i++) - intc_subgroup_init_one(desc, d, desc->hw.subgroups + i); -} - -static void __init intc_subgroup_map(struct intc_desc_int *d) -{ - struct intc_subgroup_entry *entries[32]; - unsigned long flags; - unsigned int nr_found; - int i; - - spin_lock_irqsave(&d->lock, flags); - -restart: - nr_found = radix_tree_gang_lookup_tag_slot(&d->tree, - (void ***)entries, 0, ARRAY_SIZE(entries), - INTC_TAG_VIRQ_NEEDS_ALLOC); - - for (i = 0; i < nr_found; i++) { - struct intc_subgroup_entry *entry; - int irq; - - entry = radix_tree_deref_slot((void **)entries[i]); - if (unlikely(!entry)) - continue; - if (unlikely(entry == RADIX_TREE_RETRY)) - goto restart; - - irq = create_irq(); - if (unlikely(irq < 0)) { - pr_err("no more free IRQs, bailing..\n"); - break; - } - - pr_info("Setting up a chained VIRQ from %d -> %d\n", - irq, entry->pirq); - - spin_lock(&xlate_lock); - intc_irq_xlate[irq].desc = d; - intc_irq_xlate[irq].enum_id = entry->enum_id; - spin_unlock(&xlate_lock); - - set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq), - handle_simple_irq, "virq"); - set_irq_chip_data(irq, get_irq_chip_data(entry->pirq)); - - set_irq_data(irq, (void *)entry->handle); - - set_irq_chained_handler(entry->pirq, intc_virq_handler); - add_virq_to_pirq(entry->pirq, irq); - - radix_tree_tag_clear(&d->tree, entry->enum_id, - INTC_TAG_VIRQ_NEEDS_ALLOC); - radix_tree_replace_slot((void **)entries[i], - &intc_irq_xlate[irq]); - } - - spin_unlock_irqrestore(&d->lock, flags); -} - -void __init intc_finalize(void) -{ - struct intc_desc_int *d; - - list_for_each_entry(d, &intc_list, list) - if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC)) - intc_subgroup_map(d); -} - -static void __init intc_register_irq(struct intc_desc *desc, - struct intc_desc_int *d, - intc_enum enum_id, - unsigned int irq) -{ - struct intc_handle_int *hp; - unsigned int data[2], primary; - unsigned long flags; - - /* - * Register the IRQ position with the global IRQ map, then insert - * it in to the radix tree. - */ - set_bit(irq, intc_irq_map); - - spin_lock_irqsave(&xlate_lock, flags); - radix_tree_insert(&d->tree, enum_id, &intc_irq_xlate[irq]); - spin_unlock_irqrestore(&xlate_lock, flags); - - /* - * Prefer single interrupt source bitmap over other combinations: - * - * 1. bitmap, single interrupt source - * 2. priority, single interrupt source - * 3. bitmap, multiple interrupt sources (groups) - * 4. priority, multiple interrupt sources (groups) - */ - data[0] = intc_mask_data(desc, d, enum_id, 0); - data[1] = intc_prio_data(desc, d, enum_id, 0); - - primary = 0; - if (!data[0] && data[1]) - primary = 1; - - if (!data[0] && !data[1]) - pr_warning("missing unique irq mask for irq %d (vect 0x%04x)\n", - irq, irq2evt(irq)); - - data[0] = data[0] ? data[0] : intc_mask_data(desc, d, enum_id, 1); - data[1] = data[1] ? data[1] : intc_prio_data(desc, d, enum_id, 1); - - if (!data[primary]) - primary ^= 1; - - BUG_ON(!data[primary]); /* must have primary masking method */ - - disable_irq_nosync(irq); - set_irq_chip_and_handler_name(irq, &d->chip, - handle_level_irq, "level"); - set_irq_chip_data(irq, (void *)data[primary]); - - /* - * set priority level - * - this needs to be at least 2 for 5-bit priorities on 7780 - */ - intc_prio_level[irq] = default_prio_level; - - /* enable secondary masking method if present */ - if (data[!primary]) - _intc_enable(irq, data[!primary]); - - /* add irq to d->prio list if priority is available */ - if (data[1]) { - hp = d->prio + d->nr_prio; - hp->irq = irq; - hp->handle = data[1]; - - if (primary) { - /* - * only secondary priority should access registers, so - * set _INTC_FN(h) = REG_FN_ERR for intc_set_priority() - */ - hp->handle &= ~_INTC_MK(0x0f, 0, 0, 0, 0, 0); - hp->handle |= _INTC_MK(REG_FN_ERR, 0, 0, 0, 0, 0); - } - d->nr_prio++; - } - - /* add irq to d->sense list if sense is available */ - data[0] = intc_sense_data(desc, d, enum_id); - if (data[0]) { - (d->sense + d->nr_sense)->irq = irq; - (d->sense + d->nr_sense)->handle = data[0]; - d->nr_sense++; - } - - /* irq should be disabled by default */ - d->chip.mask(irq); - - if (desc->hw.ack_regs) - ack_handle[irq] = intc_ack_data(desc, d, enum_id); - -#ifdef CONFIG_INTC_BALANCING - if (desc->hw.mask_regs) - dist_handle[irq] = intc_dist_data(desc, d, enum_id); -#endif - - activate_irq(irq); -} - -static unsigned int __init save_reg(struct intc_desc_int *d, - unsigned int cnt, - unsigned long value, - unsigned int smp) -{ - if (value) { - value = intc_phys_to_virt(d, value); - - d->reg[cnt] = value; -#ifdef CONFIG_SMP - d->smp[cnt] = smp; -#endif - return 1; - } - - return 0; -} - -int __init register_intc_controller(struct intc_desc *desc) -{ - unsigned int i, k, smp; - struct intc_hw_desc *hw = &desc->hw; - struct intc_desc_int *d; - struct resource *res; - - pr_info("Registered controller '%s' with %u IRQs\n", - desc->name, hw->nr_vectors); - - d = kzalloc(sizeof(*d), GFP_NOWAIT); - if (!d) - goto err0; - - INIT_LIST_HEAD(&d->list); - list_add_tail(&d->list, &intc_list); - - spin_lock_init(&d->lock); - - d->index = nr_intc_controllers; - - if (desc->num_resources) { - d->nr_windows = desc->num_resources; - d->window = kzalloc(d->nr_windows * sizeof(*d->window), - GFP_NOWAIT); - if (!d->window) - goto err1; - - for (k = 0; k < d->nr_windows; k++) { - res = desc->resource + k; - WARN_ON(resource_type(res) != IORESOURCE_MEM); - d->window[k].phys = res->start; - d->window[k].size = resource_size(res); - d->window[k].virt = ioremap_nocache(res->start, - resource_size(res)); - if (!d->window[k].virt) - goto err2; - } - } - - d->nr_reg = hw->mask_regs ? hw->nr_mask_regs * 2 : 0; -#ifdef CONFIG_INTC_BALANCING - if (d->nr_reg) - d->nr_reg += hw->nr_mask_regs; -#endif - d->nr_reg += hw->prio_regs ? hw->nr_prio_regs * 2 : 0; - d->nr_reg += hw->sense_regs ? hw->nr_sense_regs : 0; - d->nr_reg += hw->ack_regs ? hw->nr_ack_regs : 0; - d->nr_reg += hw->subgroups ? hw->nr_subgroups : 0; - - d->reg = kzalloc(d->nr_reg * sizeof(*d->reg), GFP_NOWAIT); - if (!d->reg) - goto err2; - -#ifdef CONFIG_SMP - d->smp = kzalloc(d->nr_reg * sizeof(*d->smp), GFP_NOWAIT); - if (!d->smp) - goto err3; -#endif - k = 0; - - if (hw->mask_regs) { - for (i = 0; i < hw->nr_mask_regs; i++) { - smp = IS_SMP(hw->mask_regs[i]); - k += save_reg(d, k, hw->mask_regs[i].set_reg, smp); - k += save_reg(d, k, hw->mask_regs[i].clr_reg, smp); -#ifdef CONFIG_INTC_BALANCING - k += save_reg(d, k, hw->mask_regs[i].dist_reg, 0); -#endif - } - } - - if (hw->prio_regs) { - d->prio = kzalloc(hw->nr_vectors * sizeof(*d->prio), - GFP_NOWAIT); - if (!d->prio) - goto err4; - - for (i = 0; i < hw->nr_prio_regs; i++) { - smp = IS_SMP(hw->prio_regs[i]); - k += save_reg(d, k, hw->prio_regs[i].set_reg, smp); - k += save_reg(d, k, hw->prio_regs[i].clr_reg, smp); - } - } - - if (hw->sense_regs) { - d->sense = kzalloc(hw->nr_vectors * sizeof(*d->sense), - GFP_NOWAIT); - if (!d->sense) - goto err5; - - for (i = 0; i < hw->nr_sense_regs; i++) - k += save_reg(d, k, hw->sense_regs[i].reg, 0); - } - - if (hw->subgroups) - for (i = 0; i < hw->nr_subgroups; i++) - if (hw->subgroups[i].reg) - k+= save_reg(d, k, hw->subgroups[i].reg, 0); - - d->chip.name = desc->name; - d->chip.mask = intc_disable; - d->chip.unmask = intc_enable; - d->chip.mask_ack = intc_disable; - d->chip.enable = intc_enable; - d->chip.disable = intc_disable; - d->chip.shutdown = intc_disable; - d->chip.set_type = intc_set_sense; - d->chip.set_wake = intc_set_wake; -#ifdef CONFIG_SMP - d->chip.set_affinity = intc_set_affinity; -#endif - - if (hw->ack_regs) { - for (i = 0; i < hw->nr_ack_regs; i++) - k += save_reg(d, k, hw->ack_regs[i].set_reg, 0); - - d->chip.mask_ack = intc_mask_ack; - } - - /* disable bits matching force_disable before registering irqs */ - if (desc->force_disable) - intc_enable_disable_enum(desc, d, desc->force_disable, 0); - - /* disable bits matching force_enable before registering irqs */ - if (desc->force_enable) - intc_enable_disable_enum(desc, d, desc->force_enable, 0); - - BUG_ON(k > 256); /* _INTC_ADDR_E() and _INTC_ADDR_D() are 8 bits */ - - /* register the vectors one by one */ - for (i = 0; i < hw->nr_vectors; i++) { - struct intc_vect *vect = hw->vectors + i; - unsigned int irq = evt2irq(vect->vect); - unsigned long flags; - struct irq_desc *irq_desc; - - if (!vect->enum_id) - continue; - - irq_desc = irq_to_desc_alloc_node(irq, numa_node_id()); - if (unlikely(!irq_desc)) { - pr_err("can't get irq_desc for %d\n", irq); - continue; - } - - spin_lock_irqsave(&xlate_lock, flags); - intc_irq_xlate[irq].enum_id = vect->enum_id; - intc_irq_xlate[irq].desc = d; - spin_unlock_irqrestore(&xlate_lock, flags); - - intc_register_irq(desc, d, vect->enum_id, irq); - - for (k = i + 1; k < hw->nr_vectors; k++) { - struct intc_vect *vect2 = hw->vectors + k; - unsigned int irq2 = evt2irq(vect2->vect); - - if (vect->enum_id != vect2->enum_id) - continue; - - /* - * In the case of multi-evt handling and sparse - * IRQ support, each vector still needs to have - * its own backing irq_desc. - */ - irq_desc = irq_to_desc_alloc_node(irq2, numa_node_id()); - if (unlikely(!irq_desc)) { - pr_err("can't get irq_desc for %d\n", irq2); - continue; - } - - vect2->enum_id = 0; - - /* redirect this interrupts to the first one */ - set_irq_chip(irq2, &dummy_irq_chip); - set_irq_chained_handler(irq2, intc_redirect_irq); - set_irq_data(irq2, (void *)irq); - } - } - - intc_subgroup_init(desc, d); - - /* enable bits matching force_enable after registering irqs */ - if (desc->force_enable) - intc_enable_disable_enum(desc, d, desc->force_enable, 1); - - nr_intc_controllers++; - - return 0; -err5: - kfree(d->prio); -err4: -#ifdef CONFIG_SMP - kfree(d->smp); -err3: -#endif - kfree(d->reg); -err2: - for (k = 0; k < d->nr_windows; k++) - if (d->window[k].virt) - iounmap(d->window[k].virt); - - kfree(d->window); -err1: - kfree(d); -err0: - pr_err("unable to allocate INTC memory\n"); - - return -ENOMEM; -} - -#ifdef CONFIG_INTC_USERIMASK -static void __iomem *uimask; - -int register_intc_userimask(unsigned long addr) -{ - if (unlikely(uimask)) - return -EBUSY; - - uimask = ioremap_nocache(addr, SZ_4K); - if (unlikely(!uimask)) - return -ENOMEM; - - pr_info("userimask support registered for levels 0 -> %d\n", - default_prio_level - 1); - - return 0; -} - -static ssize_t -show_intc_userimask(struct sysdev_class *cls, - struct sysdev_class_attribute *attr, char *buf) -{ - return sprintf(buf, "%d\n", (__raw_readl(uimask) >> 4) & 0xf); -} - -static ssize_t -store_intc_userimask(struct sysdev_class *cls, - struct sysdev_class_attribute *attr, - const char *buf, size_t count) -{ - unsigned long level; - - level = simple_strtoul(buf, NULL, 10); - - /* - * Minimal acceptable IRQ levels are in the 2 - 16 range, but - * these are chomped so as to not interfere with normal IRQs. - * - * Level 1 is a special case on some CPUs in that it's not - * directly settable, but given that USERIMASK cuts off below a - * certain level, we don't care about this limitation here. - * Level 0 on the other hand equates to user masking disabled. - * - * We use default_prio_level as a cut off so that only special - * case opt-in IRQs can be mangled. - */ - if (level >= default_prio_level) - return -EINVAL; - - __raw_writel(0xa5 << 24 | level << 4, uimask); - - return count; -} - -static SYSDEV_CLASS_ATTR(userimask, S_IRUSR | S_IWUSR, - show_intc_userimask, store_intc_userimask); -#endif - -#ifdef CONFIG_INTC_MAPPING_DEBUG -static int intc_irq_xlate_debug(struct seq_file *m, void *priv) -{ - int i; - - seq_printf(m, "%-5s %-7s %-15s\n", "irq", "enum", "chip name"); - - for (i = 1; i < nr_irqs; i++) { - struct intc_desc_int *desc = intc_irq_xlate[i].desc; - - if (!desc) - continue; - - seq_printf(m, "%5d ", i); - seq_printf(m, "0x%05x ", intc_irq_xlate[i].enum_id); - seq_printf(m, "%-15s\n", desc->chip.name); - } - - return 0; -} - -static int intc_irq_xlate_open(struct inode *inode, struct file *file) -{ - return single_open(file, intc_irq_xlate_debug, inode->i_private); -} - -static const struct file_operations intc_irq_xlate_fops = { - .open = intc_irq_xlate_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int __init intc_irq_xlate_init(void) -{ - /* - * XXX.. use arch_debugfs_dir here when all of the intc users are - * converted. - */ - if (debugfs_create_file("intc_irq_xlate", S_IRUGO, NULL, NULL, - &intc_irq_xlate_fops) == NULL) - return -ENOMEM; - - return 0; -} -fs_initcall(intc_irq_xlate_init); -#endif - -static ssize_t -show_intc_name(struct sys_device *dev, struct sysdev_attribute *attr, char *buf) -{ - struct intc_desc_int *d; - - d = container_of(dev, struct intc_desc_int, sysdev); - - return sprintf(buf, "%s\n", d->chip.name); -} - -static SYSDEV_ATTR(name, S_IRUGO, show_intc_name, NULL); - -static int intc_suspend(struct sys_device *dev, pm_message_t state) -{ - struct intc_desc_int *d; - struct irq_desc *desc; - int irq; - - /* get intc controller associated with this sysdev */ - d = container_of(dev, struct intc_desc_int, sysdev); - - switch (state.event) { - case PM_EVENT_ON: - if (d->state.event != PM_EVENT_FREEZE) - break; - for_each_irq_desc(irq, desc) { - if (desc->handle_irq == intc_redirect_irq) - continue; - if (desc->chip != &d->chip) - continue; - if (desc->status & IRQ_DISABLED) - intc_disable(irq); - else - intc_enable(irq); - } - break; - case PM_EVENT_FREEZE: - /* nothing has to be done */ - break; - case PM_EVENT_SUSPEND: - /* enable wakeup irqs belonging to this intc controller */ - for_each_irq_desc(irq, desc) { - if ((desc->status & IRQ_WAKEUP) && (desc->chip == &d->chip)) - intc_enable(irq); - } - break; - } - d->state = state; - - return 0; -} - -static int intc_resume(struct sys_device *dev) -{ - return intc_suspend(dev, PMSG_ON); -} - -static struct sysdev_class intc_sysdev_class = { - .name = "intc", - .suspend = intc_suspend, - .resume = intc_resume, -}; - -/* register this intc as sysdev to allow suspend/resume */ -static int __init register_intc_sysdevs(void) -{ - struct intc_desc_int *d; - int error; - - error = sysdev_class_register(&intc_sysdev_class); -#ifdef CONFIG_INTC_USERIMASK - if (!error && uimask) - error = sysdev_class_create_file(&intc_sysdev_class, - &attr_userimask); -#endif - if (!error) { - list_for_each_entry(d, &intc_list, list) { - d->sysdev.id = d->index; - d->sysdev.cls = &intc_sysdev_class; - error = sysdev_register(&d->sysdev); - if (error == 0) - error = sysdev_create_file(&d->sysdev, - &attr_name); - if (error) - break; - } - } - - if (error) - pr_err("sysdev registration error\n"); - - return error; -} -device_initcall(register_intc_sysdevs); - -/* - * Dynamic IRQ allocation and deallocation - */ -unsigned int create_irq_nr(unsigned int irq_want, int node) -{ - unsigned int irq = 0, new; - unsigned long flags; - struct irq_desc *desc; - - spin_lock_irqsave(&vector_lock, flags); - - /* - * First try the wanted IRQ - */ - if (test_and_set_bit(irq_want, intc_irq_map) == 0) { - new = irq_want; - } else { - /* .. then fall back to scanning. */ - new = find_first_zero_bit(intc_irq_map, nr_irqs); - if (unlikely(new == nr_irqs)) - goto out_unlock; - - __set_bit(new, intc_irq_map); - } - - desc = irq_to_desc_alloc_node(new, node); - if (unlikely(!desc)) { - pr_err("can't get irq_desc for %d\n", new); - goto out_unlock; - } - - desc = move_irq_desc(desc, node); - irq = new; - -out_unlock: - spin_unlock_irqrestore(&vector_lock, flags); - - if (irq > 0) { - dynamic_irq_init(irq); - activate_irq(irq); - } - - return irq; -} - -int create_irq(void) -{ - int nid = cpu_to_node(smp_processor_id()); - int irq; - - irq = create_irq_nr(NR_IRQS_LEGACY, nid); - if (irq == 0) - irq = -1; - - return irq; -} - -void destroy_irq(unsigned int irq) -{ - unsigned long flags; - - dynamic_irq_cleanup(irq); - - spin_lock_irqsave(&vector_lock, flags); - __clear_bit(irq, intc_irq_map); - spin_unlock_irqrestore(&vector_lock, flags); -} - -int reserve_irq_vector(unsigned int irq) -{ - unsigned long flags; - int ret = 0; - - spin_lock_irqsave(&vector_lock, flags); - if (test_and_set_bit(irq, intc_irq_map)) - ret = -EBUSY; - spin_unlock_irqrestore(&vector_lock, flags); - - return ret; -} - -void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs) -{ - unsigned long flags; - int i; - - spin_lock_irqsave(&vector_lock, flags); - for (i = 0; i < nr_vecs; i++) - __set_bit(evt2irq(vectors[i].vect), intc_irq_map); - spin_unlock_irqrestore(&vector_lock, flags); -} - -void reserve_irq_legacy(void) -{ - unsigned long flags; - int i, j; - - spin_lock_irqsave(&vector_lock, flags); - j = find_first_bit(intc_irq_map, nr_irqs); - for (i = 0; i < j; i++) - __set_bit(i, intc_irq_map); - spin_unlock_irqrestore(&vector_lock, flags); -} diff --git a/drivers/sh/intc/access.c b/drivers/sh/intc/access.c new file mode 100644 index 000000000000..f892ae1d212a --- /dev/null +++ b/drivers/sh/intc/access.c @@ -0,0 +1,237 @@ +/* + * Common INTC2 register accessors + * + * Copyright (C) 2007, 2008 Magnus Damm + * Copyright (C) 2009, 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include "internals.h" + +unsigned long intc_phys_to_virt(struct intc_desc_int *d, unsigned long address) +{ + struct intc_window *window; + int k; + + /* scan through physical windows and convert address */ + for (k = 0; k < d->nr_windows; k++) { + window = d->window + k; + + if (address < window->phys) + continue; + + if (address >= (window->phys + window->size)) + continue; + + address -= window->phys; + address += (unsigned long)window->virt; + + return address; + } + + /* no windows defined, register must be 1:1 mapped virt:phys */ + return address; +} + +unsigned int intc_get_reg(struct intc_desc_int *d, unsigned long address) +{ + unsigned int k; + + address = intc_phys_to_virt(d, address); + + for (k = 0; k < d->nr_reg; k++) { + if (d->reg[k] == address) + return k; + } + + BUG(); + return 0; +} + +unsigned int intc_set_field_from_handle(unsigned int value, + unsigned int field_value, + unsigned int handle) +{ + unsigned int width = _INTC_WIDTH(handle); + unsigned int shift = _INTC_SHIFT(handle); + + value &= ~(((1 << width) - 1) << shift); + value |= field_value << shift; + return value; +} + +unsigned long intc_get_field_from_handle(unsigned int value, unsigned int handle) +{ + unsigned int width = _INTC_WIDTH(handle); + unsigned int shift = _INTC_SHIFT(handle); + unsigned int mask = ((1 << width) - 1) << shift; + + return (value & mask) >> shift; +} + +static unsigned long test_8(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return intc_get_field_from_handle(__raw_readb(addr), h); +} + +static unsigned long test_16(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return intc_get_field_from_handle(__raw_readw(addr), h); +} + +static unsigned long test_32(unsigned long addr, unsigned long h, + unsigned long ignore) +{ + return intc_get_field_from_handle(__raw_readl(addr), h); +} + +static unsigned long write_8(unsigned long addr, unsigned long h, + unsigned long data) +{ + __raw_writeb(intc_set_field_from_handle(0, data, h), addr); + (void)__raw_readb(addr); /* Defeat write posting */ + return 0; +} + +static unsigned long write_16(unsigned long addr, unsigned long h, + unsigned long data) +{ + __raw_writew(intc_set_field_from_handle(0, data, h), addr); + (void)__raw_readw(addr); /* Defeat write posting */ + return 0; +} + +static unsigned long write_32(unsigned long addr, unsigned long h, + unsigned long data) +{ + __raw_writel(intc_set_field_from_handle(0, data, h), addr); + (void)__raw_readl(addr); /* Defeat write posting */ + return 0; +} + +static unsigned long modify_8(unsigned long addr, unsigned long h, + unsigned long data) +{ + unsigned long flags; + unsigned int value; + local_irq_save(flags); + value = intc_set_field_from_handle(__raw_readb(addr), data, h); + __raw_writeb(value, addr); + (void)__raw_readb(addr); /* Defeat write posting */ + local_irq_restore(flags); + return 0; +} + +static unsigned long modify_16(unsigned long addr, unsigned long h, + unsigned long data) +{ + unsigned long flags; + unsigned int value; + local_irq_save(flags); + value = intc_set_field_from_handle(__raw_readw(addr), data, h); + __raw_writew(value, addr); + (void)__raw_readw(addr); /* Defeat write posting */ + local_irq_restore(flags); + return 0; +} + +static unsigned long modify_32(unsigned long addr, unsigned long h, + unsigned long data) +{ + unsigned long flags; + unsigned int value; + local_irq_save(flags); + value = intc_set_field_from_handle(__raw_readl(addr), data, h); + __raw_writel(value, addr); + (void)__raw_readl(addr); /* Defeat write posting */ + local_irq_restore(flags); + return 0; +} + +static unsigned long intc_mode_field(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) +{ + return fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1)); +} + +static unsigned long intc_mode_zero(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) +{ + return fn(addr, handle, 0); +} + +static unsigned long intc_mode_prio(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) +{ + return fn(addr, handle, intc_get_prio_level(irq)); +} + +unsigned long (*intc_reg_fns[])(unsigned long addr, + unsigned long h, + unsigned long data) = { + [REG_FN_TEST_BASE + 0] = test_8, + [REG_FN_TEST_BASE + 1] = test_16, + [REG_FN_TEST_BASE + 3] = test_32, + [REG_FN_WRITE_BASE + 0] = write_8, + [REG_FN_WRITE_BASE + 1] = write_16, + [REG_FN_WRITE_BASE + 3] = write_32, + [REG_FN_MODIFY_BASE + 0] = modify_8, + [REG_FN_MODIFY_BASE + 1] = modify_16, + [REG_FN_MODIFY_BASE + 3] = modify_32, +}; + +unsigned long (*intc_enable_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { + [MODE_ENABLE_REG] = intc_mode_field, + [MODE_MASK_REG] = intc_mode_zero, + [MODE_DUAL_REG] = intc_mode_field, + [MODE_PRIO_REG] = intc_mode_prio, + [MODE_PCLR_REG] = intc_mode_prio, +}; + +unsigned long (*intc_disable_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { + [MODE_ENABLE_REG] = intc_mode_zero, + [MODE_MASK_REG] = intc_mode_field, + [MODE_DUAL_REG] = intc_mode_field, + [MODE_PRIO_REG] = intc_mode_zero, + [MODE_PCLR_REG] = intc_mode_field, +}; + +unsigned long (*intc_enable_noprio_fns[])(unsigned long addr, + unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, + unsigned long), + unsigned int irq) = { + [MODE_ENABLE_REG] = intc_mode_field, + [MODE_MASK_REG] = intc_mode_zero, + [MODE_DUAL_REG] = intc_mode_field, + [MODE_PRIO_REG] = intc_mode_field, + [MODE_PCLR_REG] = intc_mode_field, +}; diff --git a/drivers/sh/intc/balancing.c b/drivers/sh/intc/balancing.c new file mode 100644 index 000000000000..cec7a96f2c09 --- /dev/null +++ b/drivers/sh/intc/balancing.c @@ -0,0 +1,97 @@ +/* + * Support for hardware-managed IRQ auto-distribution. + * + * Copyright (C) 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include "internals.h" + +static unsigned long dist_handle[NR_IRQS]; + +void intc_balancing_enable(unsigned int irq) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned long handle = dist_handle[irq]; + unsigned long addr; + + if (irq_balancing_disabled(irq) || !handle) + return; + + addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); + intc_reg_fns[_INTC_FN(handle)](addr, handle, 1); +} + +void intc_balancing_disable(unsigned int irq) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned long handle = dist_handle[irq]; + unsigned long addr; + + if (irq_balancing_disabled(irq) || !handle) + return; + + addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); + intc_reg_fns[_INTC_FN(handle)](addr, handle, 0); +} + +static unsigned int intc_dist_data(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id) +{ + struct intc_mask_reg *mr = desc->hw.mask_regs; + unsigned int i, j, fn, mode; + unsigned long reg_e, reg_d; + + for (i = 0; mr && enum_id && i < desc->hw.nr_mask_regs; i++) { + mr = desc->hw.mask_regs + i; + + /* + * Skip this entry if there's no auto-distribution + * register associated with it. + */ + if (!mr->dist_reg) + continue; + + for (j = 0; j < ARRAY_SIZE(mr->enum_ids); j++) { + if (mr->enum_ids[j] != enum_id) + continue; + + fn = REG_FN_MODIFY_BASE; + mode = MODE_ENABLE_REG; + reg_e = mr->dist_reg; + reg_d = mr->dist_reg; + + fn += (mr->reg_width >> 3) - 1; + return _INTC_MK(fn, mode, + intc_get_reg(d, reg_e), + intc_get_reg(d, reg_d), + 1, + (mr->reg_width - 1) - j); + } + } + + /* + * It's possible we've gotten here with no distribution options + * available for the IRQ in question, so we just skip over those. + */ + return 0; +} + +void intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id) +{ + unsigned long flags; + + /* + * Nothing to do for this IRQ. + */ + if (!desc->hw.mask_regs) + return; + + raw_spin_lock_irqsave(&intc_big_lock, flags); + dist_handle[irq] = intc_dist_data(desc, d, id); + raw_spin_unlock_irqrestore(&intc_big_lock, flags); +} diff --git a/drivers/sh/intc/chip.c b/drivers/sh/intc/chip.c new file mode 100644 index 000000000000..35c03706cc21 --- /dev/null +++ b/drivers/sh/intc/chip.c @@ -0,0 +1,215 @@ +/* + * IRQ chip definitions for INTC IRQs. + * + * Copyright (C) 2007, 2008 Magnus Damm + * Copyright (C) 2009, 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include "internals.h" + +void _intc_enable(unsigned int irq, unsigned long handle) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned long addr; + unsigned int cpu; + + for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_E(handle)); cpu++) { +#ifdef CONFIG_SMP + if (!cpumask_test_cpu(cpu, irq_to_desc(irq)->affinity)) + continue; +#endif + addr = INTC_REG(d, _INTC_ADDR_E(handle), cpu); + intc_enable_fns[_INTC_MODE(handle)](addr, handle, intc_reg_fns\ + [_INTC_FN(handle)], irq); + } + + intc_balancing_enable(irq); +} + +static void intc_enable(unsigned int irq) +{ + _intc_enable(irq, (unsigned long)get_irq_chip_data(irq)); +} + +static void intc_disable(unsigned int irq) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned long handle = (unsigned long)get_irq_chip_data(irq); + unsigned long addr; + unsigned int cpu; + + intc_balancing_disable(irq); + + for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_D(handle)); cpu++) { +#ifdef CONFIG_SMP + if (!cpumask_test_cpu(cpu, irq_to_desc(irq)->affinity)) + continue; +#endif + addr = INTC_REG(d, _INTC_ADDR_D(handle), cpu); + intc_disable_fns[_INTC_MODE(handle)](addr, handle,intc_reg_fns\ + [_INTC_FN(handle)], irq); + } +} + +static int intc_set_wake(unsigned int irq, unsigned int on) +{ + return 0; /* allow wakeup, but setup hardware in intc_suspend() */ +} + +#ifdef CONFIG_SMP +/* + * This is held with the irq desc lock held, so we don't require any + * additional locking here at the intc desc level. The affinity mask is + * later tested in the enable/disable paths. + */ +static int intc_set_affinity(unsigned int irq, const struct cpumask *cpumask) +{ + if (!cpumask_intersects(cpumask, cpu_online_mask)) + return -1; + + cpumask_copy(irq_to_desc(irq)->affinity, cpumask); + + return 0; +} +#endif + +static void intc_mask_ack(unsigned int irq) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned long handle = intc_get_ack_handle(irq); + unsigned long addr; + + intc_disable(irq); + + /* read register and write zero only to the associated bit */ + if (handle) { + unsigned int value; + + addr = INTC_REG(d, _INTC_ADDR_D(handle), 0); + value = intc_set_field_from_handle(0, 1, handle); + + switch (_INTC_FN(handle)) { + case REG_FN_MODIFY_BASE + 0: /* 8bit */ + __raw_readb(addr); + __raw_writeb(0xff ^ value, addr); + break; + case REG_FN_MODIFY_BASE + 1: /* 16bit */ + __raw_readw(addr); + __raw_writew(0xffff ^ value, addr); + break; + case REG_FN_MODIFY_BASE + 3: /* 32bit */ + __raw_readl(addr); + __raw_writel(0xffffffff ^ value, addr); + break; + default: + BUG(); + break; + } + } +} + +static struct intc_handle_int *intc_find_irq(struct intc_handle_int *hp, + unsigned int nr_hp, + unsigned int irq) +{ + int i; + + /* + * this doesn't scale well, but... + * + * this function should only be used for cerain uncommon + * operations such as intc_set_priority() and intc_set_type() + * and in those rare cases performance doesn't matter that much. + * keeping the memory footprint low is more important. + * + * one rather simple way to speed this up and still keep the + * memory footprint down is to make sure the array is sorted + * and then perform a bisect to lookup the irq. + */ + for (i = 0; i < nr_hp; i++) { + if ((hp + i)->irq != irq) + continue; + + return hp + i; + } + + return NULL; +} + +int intc_set_priority(unsigned int irq, unsigned int prio) +{ + struct intc_desc_int *d = get_intc_desc(irq); + struct intc_handle_int *ihp; + + if (!intc_get_prio_level(irq) || prio <= 1) + return -EINVAL; + + ihp = intc_find_irq(d->prio, d->nr_prio, irq); + if (ihp) { + if (prio >= (1 << _INTC_WIDTH(ihp->handle))) + return -EINVAL; + + intc_set_prio_level(irq, prio); + + /* + * only set secondary masking method directly + * primary masking method is using intc_prio_level[irq] + * priority level will be set during next enable() + */ + if (_INTC_FN(ihp->handle) != REG_FN_ERR) + _intc_enable(irq, ihp->handle); + } + return 0; +} + +#define VALID(x) (x | 0x80) + +static unsigned char intc_irq_sense_table[IRQ_TYPE_SENSE_MASK + 1] = { + [IRQ_TYPE_EDGE_FALLING] = VALID(0), + [IRQ_TYPE_EDGE_RISING] = VALID(1), + [IRQ_TYPE_LEVEL_LOW] = VALID(2), + /* SH7706, SH7707 and SH7709 do not support high level triggered */ +#if !defined(CONFIG_CPU_SUBTYPE_SH7706) && \ + !defined(CONFIG_CPU_SUBTYPE_SH7707) && \ + !defined(CONFIG_CPU_SUBTYPE_SH7709) + [IRQ_TYPE_LEVEL_HIGH] = VALID(3), +#endif +}; + +static int intc_set_type(unsigned int irq, unsigned int type) +{ + struct intc_desc_int *d = get_intc_desc(irq); + unsigned char value = intc_irq_sense_table[type & IRQ_TYPE_SENSE_MASK]; + struct intc_handle_int *ihp; + unsigned long addr; + + if (!value) + return -EINVAL; + + ihp = intc_find_irq(d->sense, d->nr_sense, irq); + if (ihp) { + addr = INTC_REG(d, _INTC_ADDR_E(ihp->handle), 0); + intc_reg_fns[_INTC_FN(ihp->handle)](addr, ihp->handle, value); + } + + return 0; +} + +struct irq_chip intc_irq_chip = { + .mask = intc_disable, + .unmask = intc_enable, + .mask_ack = intc_mask_ack, + .enable = intc_enable, + .disable = intc_disable, + .shutdown = intc_disable, + .set_type = intc_set_type, + .set_wake = intc_set_wake, +#ifdef CONFIG_SMP + .set_affinity = intc_set_affinity, +#endif +}; diff --git a/drivers/sh/intc/core.c b/drivers/sh/intc/core.c new file mode 100644 index 000000000000..306ed287077a --- /dev/null +++ b/drivers/sh/intc/core.c @@ -0,0 +1,469 @@ +/* + * Shared interrupt handling code for IPR and INTC2 types of IRQs. + * + * Copyright (C) 2007, 2008 Magnus Damm + * Copyright (C) 2009, 2010 Paul Mundt + * + * Based on intc2.c and ipr.c + * + * Copyright (C) 1999 Niibe Yutaka & Takeshi Yaegashi + * Copyright (C) 2000 Kazumoto Kojima + * Copyright (C) 2001 David J. Mckay (david.mckay@st.com) + * Copyright (C) 2003 Takashi Kusuda + * Copyright (C) 2005, 2006 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "intc: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internals.h" + +LIST_HEAD(intc_list); +DEFINE_RAW_SPINLOCK(intc_big_lock); +unsigned int nr_intc_controllers; + +/* + * Default priority level + * - this needs to be at least 2 for 5-bit priorities on 7780 + */ +static unsigned int default_prio_level = 2; /* 2 - 16 */ +static unsigned int intc_prio_level[NR_IRQS]; /* for now */ + +unsigned int intc_get_dfl_prio_level(void) +{ + return default_prio_level; +} + +unsigned int intc_get_prio_level(unsigned int irq) +{ + return intc_prio_level[irq]; +} + +void intc_set_prio_level(unsigned int irq, unsigned int level) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&intc_big_lock, flags); + intc_prio_level[irq] = level; + raw_spin_unlock_irqrestore(&intc_big_lock, flags); +} + +static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc) +{ + generic_handle_irq((unsigned int)get_irq_data(irq)); +} + +static void __init intc_register_irq(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, + unsigned int irq) +{ + struct intc_handle_int *hp; + unsigned int data[2], primary; + unsigned long flags; + + /* + * Register the IRQ position with the global IRQ map, then insert + * it in to the radix tree. + */ + reserve_irq_vector(irq); + + raw_spin_lock_irqsave(&intc_big_lock, flags); + radix_tree_insert(&d->tree, enum_id, intc_irq_xlate_get(irq)); + raw_spin_unlock_irqrestore(&intc_big_lock, flags); + + /* + * Prefer single interrupt source bitmap over other combinations: + * + * 1. bitmap, single interrupt source + * 2. priority, single interrupt source + * 3. bitmap, multiple interrupt sources (groups) + * 4. priority, multiple interrupt sources (groups) + */ + data[0] = intc_get_mask_handle(desc, d, enum_id, 0); + data[1] = intc_get_prio_handle(desc, d, enum_id, 0); + + primary = 0; + if (!data[0] && data[1]) + primary = 1; + + if (!data[0] && !data[1]) + pr_warning("missing unique irq mask for irq %d (vect 0x%04x)\n", + irq, irq2evt(irq)); + + data[0] = data[0] ? data[0] : intc_get_mask_handle(desc, d, enum_id, 1); + data[1] = data[1] ? data[1] : intc_get_prio_handle(desc, d, enum_id, 1); + + if (!data[primary]) + primary ^= 1; + + BUG_ON(!data[primary]); /* must have primary masking method */ + + disable_irq_nosync(irq); + set_irq_chip_and_handler_name(irq, &d->chip, + handle_level_irq, "level"); + set_irq_chip_data(irq, (void *)data[primary]); + + /* + * set priority level + */ + intc_set_prio_level(irq, intc_get_dfl_prio_level()); + + /* enable secondary masking method if present */ + if (data[!primary]) + _intc_enable(irq, data[!primary]); + + /* add irq to d->prio list if priority is available */ + if (data[1]) { + hp = d->prio + d->nr_prio; + hp->irq = irq; + hp->handle = data[1]; + + if (primary) { + /* + * only secondary priority should access registers, so + * set _INTC_FN(h) = REG_FN_ERR for intc_set_priority() + */ + hp->handle &= ~_INTC_MK(0x0f, 0, 0, 0, 0, 0); + hp->handle |= _INTC_MK(REG_FN_ERR, 0, 0, 0, 0, 0); + } + d->nr_prio++; + } + + /* add irq to d->sense list if sense is available */ + data[0] = intc_get_sense_handle(desc, d, enum_id); + if (data[0]) { + (d->sense + d->nr_sense)->irq = irq; + (d->sense + d->nr_sense)->handle = data[0]; + d->nr_sense++; + } + + /* irq should be disabled by default */ + d->chip.mask(irq); + + intc_set_ack_handle(irq, desc, d, enum_id); + intc_set_dist_handle(irq, desc, d, enum_id); + + activate_irq(irq); +} + +static unsigned int __init save_reg(struct intc_desc_int *d, + unsigned int cnt, + unsigned long value, + unsigned int smp) +{ + if (value) { + value = intc_phys_to_virt(d, value); + + d->reg[cnt] = value; +#ifdef CONFIG_SMP + d->smp[cnt] = smp; +#endif + return 1; + } + + return 0; +} + +int __init register_intc_controller(struct intc_desc *desc) +{ + unsigned int i, k, smp; + struct intc_hw_desc *hw = &desc->hw; + struct intc_desc_int *d; + struct resource *res; + + pr_info("Registered controller '%s' with %u IRQs\n", + desc->name, hw->nr_vectors); + + d = kzalloc(sizeof(*d), GFP_NOWAIT); + if (!d) + goto err0; + + INIT_LIST_HEAD(&d->list); + list_add_tail(&d->list, &intc_list); + + raw_spin_lock_init(&d->lock); + + d->index = nr_intc_controllers; + + if (desc->num_resources) { + d->nr_windows = desc->num_resources; + d->window = kzalloc(d->nr_windows * sizeof(*d->window), + GFP_NOWAIT); + if (!d->window) + goto err1; + + for (k = 0; k < d->nr_windows; k++) { + res = desc->resource + k; + WARN_ON(resource_type(res) != IORESOURCE_MEM); + d->window[k].phys = res->start; + d->window[k].size = resource_size(res); + d->window[k].virt = ioremap_nocache(res->start, + resource_size(res)); + if (!d->window[k].virt) + goto err2; + } + } + + d->nr_reg = hw->mask_regs ? hw->nr_mask_regs * 2 : 0; +#ifdef CONFIG_INTC_BALANCING + if (d->nr_reg) + d->nr_reg += hw->nr_mask_regs; +#endif + d->nr_reg += hw->prio_regs ? hw->nr_prio_regs * 2 : 0; + d->nr_reg += hw->sense_regs ? hw->nr_sense_regs : 0; + d->nr_reg += hw->ack_regs ? hw->nr_ack_regs : 0; + d->nr_reg += hw->subgroups ? hw->nr_subgroups : 0; + + d->reg = kzalloc(d->nr_reg * sizeof(*d->reg), GFP_NOWAIT); + if (!d->reg) + goto err2; + +#ifdef CONFIG_SMP + d->smp = kzalloc(d->nr_reg * sizeof(*d->smp), GFP_NOWAIT); + if (!d->smp) + goto err3; +#endif + k = 0; + + if (hw->mask_regs) { + for (i = 0; i < hw->nr_mask_regs; i++) { + smp = IS_SMP(hw->mask_regs[i]); + k += save_reg(d, k, hw->mask_regs[i].set_reg, smp); + k += save_reg(d, k, hw->mask_regs[i].clr_reg, smp); +#ifdef CONFIG_INTC_BALANCING + k += save_reg(d, k, hw->mask_regs[i].dist_reg, 0); +#endif + } + } + + if (hw->prio_regs) { + d->prio = kzalloc(hw->nr_vectors * sizeof(*d->prio), + GFP_NOWAIT); + if (!d->prio) + goto err4; + + for (i = 0; i < hw->nr_prio_regs; i++) { + smp = IS_SMP(hw->prio_regs[i]); + k += save_reg(d, k, hw->prio_regs[i].set_reg, smp); + k += save_reg(d, k, hw->prio_regs[i].clr_reg, smp); + } + } + + if (hw->sense_regs) { + d->sense = kzalloc(hw->nr_vectors * sizeof(*d->sense), + GFP_NOWAIT); + if (!d->sense) + goto err5; + + for (i = 0; i < hw->nr_sense_regs; i++) + k += save_reg(d, k, hw->sense_regs[i].reg, 0); + } + + if (hw->subgroups) + for (i = 0; i < hw->nr_subgroups; i++) + if (hw->subgroups[i].reg) + k+= save_reg(d, k, hw->subgroups[i].reg, 0); + + memcpy(&d->chip, &intc_irq_chip, sizeof(struct irq_chip)); + d->chip.name = desc->name; + + if (hw->ack_regs) + for (i = 0; i < hw->nr_ack_regs; i++) + k += save_reg(d, k, hw->ack_regs[i].set_reg, 0); + else + d->chip.mask_ack = d->chip.disable; + + /* disable bits matching force_disable before registering irqs */ + if (desc->force_disable) + intc_enable_disable_enum(desc, d, desc->force_disable, 0); + + /* disable bits matching force_enable before registering irqs */ + if (desc->force_enable) + intc_enable_disable_enum(desc, d, desc->force_enable, 0); + + BUG_ON(k > 256); /* _INTC_ADDR_E() and _INTC_ADDR_D() are 8 bits */ + + /* register the vectors one by one */ + for (i = 0; i < hw->nr_vectors; i++) { + struct intc_vect *vect = hw->vectors + i; + unsigned int irq = evt2irq(vect->vect); + struct irq_desc *irq_desc; + + if (!vect->enum_id) + continue; + + irq_desc = irq_to_desc_alloc_node(irq, numa_node_id()); + if (unlikely(!irq_desc)) { + pr_err("can't get irq_desc for %d\n", irq); + continue; + } + + intc_irq_xlate_set(irq, vect->enum_id, d); + intc_register_irq(desc, d, vect->enum_id, irq); + + for (k = i + 1; k < hw->nr_vectors; k++) { + struct intc_vect *vect2 = hw->vectors + k; + unsigned int irq2 = evt2irq(vect2->vect); + + if (vect->enum_id != vect2->enum_id) + continue; + + /* + * In the case of multi-evt handling and sparse + * IRQ support, each vector still needs to have + * its own backing irq_desc. + */ + irq_desc = irq_to_desc_alloc_node(irq2, numa_node_id()); + if (unlikely(!irq_desc)) { + pr_err("can't get irq_desc for %d\n", irq2); + continue; + } + + vect2->enum_id = 0; + + /* redirect this interrupts to the first one */ + set_irq_chip(irq2, &dummy_irq_chip); + set_irq_chained_handler(irq2, intc_redirect_irq); + set_irq_data(irq2, (void *)irq); + } + } + + intc_subgroup_init(desc, d); + + /* enable bits matching force_enable after registering irqs */ + if (desc->force_enable) + intc_enable_disable_enum(desc, d, desc->force_enable, 1); + + nr_intc_controllers++; + + return 0; +err5: + kfree(d->prio); +err4: +#ifdef CONFIG_SMP + kfree(d->smp); +err3: +#endif + kfree(d->reg); +err2: + for (k = 0; k < d->nr_windows; k++) + if (d->window[k].virt) + iounmap(d->window[k].virt); + + kfree(d->window); +err1: + kfree(d); +err0: + pr_err("unable to allocate INTC memory\n"); + + return -ENOMEM; +} + +static ssize_t +show_intc_name(struct sys_device *dev, struct sysdev_attribute *attr, char *buf) +{ + struct intc_desc_int *d; + + d = container_of(dev, struct intc_desc_int, sysdev); + + return sprintf(buf, "%s\n", d->chip.name); +} + +static SYSDEV_ATTR(name, S_IRUGO, show_intc_name, NULL); + +static int intc_suspend(struct sys_device *dev, pm_message_t state) +{ + struct intc_desc_int *d; + struct irq_desc *desc; + int irq; + + /* get intc controller associated with this sysdev */ + d = container_of(dev, struct intc_desc_int, sysdev); + + switch (state.event) { + case PM_EVENT_ON: + if (d->state.event != PM_EVENT_FREEZE) + break; + + for_each_irq_desc(irq, desc) { + /* + * This will catch the redirect and VIRQ cases + * due to the dummy_irq_chip being inserted. + */ + if (desc->chip != &d->chip) + continue; + if (desc->status & IRQ_DISABLED) + desc->chip->disable(irq); + else + desc->chip->enable(irq); + } + break; + case PM_EVENT_FREEZE: + /* nothing has to be done */ + break; + case PM_EVENT_SUSPEND: + /* enable wakeup irqs belonging to this intc controller */ + for_each_irq_desc(irq, desc) { + if (desc->chip != &d->chip) + continue; + if ((desc->status & IRQ_WAKEUP)) + desc->chip->enable(irq); + } + break; + } + + d->state = state; + + return 0; +} + +static int intc_resume(struct sys_device *dev) +{ + return intc_suspend(dev, PMSG_ON); +} + +struct sysdev_class intc_sysdev_class = { + .name = "intc", + .suspend = intc_suspend, + .resume = intc_resume, +}; + +/* register this intc as sysdev to allow suspend/resume */ +static int __init register_intc_sysdevs(void) +{ + struct intc_desc_int *d; + int error; + + error = sysdev_class_register(&intc_sysdev_class); + if (!error) { + list_for_each_entry(d, &intc_list, list) { + d->sysdev.id = d->index; + d->sysdev.cls = &intc_sysdev_class; + error = sysdev_register(&d->sysdev); + if (error == 0) + error = sysdev_create_file(&d->sysdev, + &attr_name); + if (error) + break; + } + } + + if (error) + pr_err("sysdev registration error\n"); + + return error; +} +device_initcall(register_intc_sysdevs); diff --git a/drivers/sh/intc/dynamic.c b/drivers/sh/intc/dynamic.c new file mode 100644 index 000000000000..6caecdffe201 --- /dev/null +++ b/drivers/sh/intc/dynamic.c @@ -0,0 +1,135 @@ +/* + * Dynamic IRQ management + * + * Copyright (C) 2010 Paul Mundt + * + * Modelled after arch/x86/kernel/apic/io_apic.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "intc: " fmt + +#include +#include +#include +#include "internals.h" /* only for activate_irq() damage.. */ + +/* + * The intc_irq_map provides a global map of bound IRQ vectors for a + * given platform. Allocation of IRQs are either static through the CPU + * vector map, or dynamic in the case of board mux vectors or MSI. + * + * As this is a central point for all IRQ controllers on the system, + * each of the available sources are mapped out here. This combined with + * sparseirq makes it quite trivial to keep the vector map tightly packed + * when dynamically creating IRQs, as well as tying in to otherwise + * unused irq_desc positions in the sparse array. + */ +static DECLARE_BITMAP(intc_irq_map, NR_IRQS); +static DEFINE_RAW_SPINLOCK(vector_lock); + +/* + * Dynamic IRQ allocation and deallocation + */ +unsigned int create_irq_nr(unsigned int irq_want, int node) +{ + unsigned int irq = 0, new; + unsigned long flags; + struct irq_desc *desc; + + raw_spin_lock_irqsave(&vector_lock, flags); + + /* + * First try the wanted IRQ + */ + if (test_and_set_bit(irq_want, intc_irq_map) == 0) { + new = irq_want; + } else { + /* .. then fall back to scanning. */ + new = find_first_zero_bit(intc_irq_map, nr_irqs); + if (unlikely(new == nr_irqs)) + goto out_unlock; + + __set_bit(new, intc_irq_map); + } + + desc = irq_to_desc_alloc_node(new, node); + if (unlikely(!desc)) { + pr_err("can't get irq_desc for %d\n", new); + goto out_unlock; + } + + desc = move_irq_desc(desc, node); + irq = new; + +out_unlock: + raw_spin_unlock_irqrestore(&vector_lock, flags); + + if (irq > 0) { + dynamic_irq_init(irq); + activate_irq(irq); + } + + return irq; +} + +int create_irq(void) +{ + int nid = cpu_to_node(smp_processor_id()); + int irq; + + irq = create_irq_nr(NR_IRQS_LEGACY, nid); + if (irq == 0) + irq = -1; + + return irq; +} + +void destroy_irq(unsigned int irq) +{ + unsigned long flags; + + dynamic_irq_cleanup(irq); + + raw_spin_lock_irqsave(&vector_lock, flags); + __clear_bit(irq, intc_irq_map); + raw_spin_unlock_irqrestore(&vector_lock, flags); +} + +int reserve_irq_vector(unsigned int irq) +{ + unsigned long flags; + int ret = 0; + + raw_spin_lock_irqsave(&vector_lock, flags); + if (test_and_set_bit(irq, intc_irq_map)) + ret = -EBUSY; + raw_spin_unlock_irqrestore(&vector_lock, flags); + + return ret; +} + +void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs) +{ + unsigned long flags; + int i; + + raw_spin_lock_irqsave(&vector_lock, flags); + for (i = 0; i < nr_vecs; i++) + __set_bit(evt2irq(vectors[i].vect), intc_irq_map); + raw_spin_unlock_irqrestore(&vector_lock, flags); +} + +void reserve_irq_legacy(void) +{ + unsigned long flags; + int i, j; + + raw_spin_lock_irqsave(&vector_lock, flags); + j = find_first_bit(intc_irq_map, nr_irqs); + for (i = 0; i < j; i++) + __set_bit(i, intc_irq_map); + raw_spin_unlock_irqrestore(&vector_lock, flags); +} diff --git a/drivers/sh/intc/handle.c b/drivers/sh/intc/handle.c new file mode 100644 index 000000000000..057ce56829bf --- /dev/null +++ b/drivers/sh/intc/handle.c @@ -0,0 +1,307 @@ +/* + * Shared interrupt handling code for IPR and INTC2 types of IRQs. + * + * Copyright (C) 2007, 2008 Magnus Damm + * Copyright (C) 2009, 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include "internals.h" + +static unsigned long ack_handle[NR_IRQS]; + +static intc_enum __init intc_grp_id(struct intc_desc *desc, + intc_enum enum_id) +{ + struct intc_group *g = desc->hw.groups; + unsigned int i, j; + + for (i = 0; g && enum_id && i < desc->hw.nr_groups; i++) { + g = desc->hw.groups + i; + + for (j = 0; g->enum_ids[j]; j++) { + if (g->enum_ids[j] != enum_id) + continue; + + return g->enum_id; + } + } + + return 0; +} + +static unsigned int __init _intc_mask_data(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, + unsigned int *reg_idx, + unsigned int *fld_idx) +{ + struct intc_mask_reg *mr = desc->hw.mask_regs; + unsigned int fn, mode; + unsigned long reg_e, reg_d; + + while (mr && enum_id && *reg_idx < desc->hw.nr_mask_regs) { + mr = desc->hw.mask_regs + *reg_idx; + + for (; *fld_idx < ARRAY_SIZE(mr->enum_ids); (*fld_idx)++) { + if (mr->enum_ids[*fld_idx] != enum_id) + continue; + + if (mr->set_reg && mr->clr_reg) { + fn = REG_FN_WRITE_BASE; + mode = MODE_DUAL_REG; + reg_e = mr->clr_reg; + reg_d = mr->set_reg; + } else { + fn = REG_FN_MODIFY_BASE; + if (mr->set_reg) { + mode = MODE_ENABLE_REG; + reg_e = mr->set_reg; + reg_d = mr->set_reg; + } else { + mode = MODE_MASK_REG; + reg_e = mr->clr_reg; + reg_d = mr->clr_reg; + } + } + + fn += (mr->reg_width >> 3) - 1; + return _INTC_MK(fn, mode, + intc_get_reg(d, reg_e), + intc_get_reg(d, reg_d), + 1, + (mr->reg_width - 1) - *fld_idx); + } + + *fld_idx = 0; + (*reg_idx)++; + } + + return 0; +} + +unsigned int __init +intc_get_mask_handle(struct intc_desc *desc, struct intc_desc_int *d, + intc_enum enum_id, int do_grps) +{ + unsigned int i = 0; + unsigned int j = 0; + unsigned int ret; + + ret = _intc_mask_data(desc, d, enum_id, &i, &j); + if (ret) + return ret; + + if (do_grps) + return intc_get_mask_handle(desc, d, intc_grp_id(desc, enum_id), 0); + + return 0; +} + +static unsigned int __init _intc_prio_data(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, + unsigned int *reg_idx, + unsigned int *fld_idx) +{ + struct intc_prio_reg *pr = desc->hw.prio_regs; + unsigned int fn, n, mode, bit; + unsigned long reg_e, reg_d; + + while (pr && enum_id && *reg_idx < desc->hw.nr_prio_regs) { + pr = desc->hw.prio_regs + *reg_idx; + + for (; *fld_idx < ARRAY_SIZE(pr->enum_ids); (*fld_idx)++) { + if (pr->enum_ids[*fld_idx] != enum_id) + continue; + + if (pr->set_reg && pr->clr_reg) { + fn = REG_FN_WRITE_BASE; + mode = MODE_PCLR_REG; + reg_e = pr->set_reg; + reg_d = pr->clr_reg; + } else { + fn = REG_FN_MODIFY_BASE; + mode = MODE_PRIO_REG; + if (!pr->set_reg) + BUG(); + reg_e = pr->set_reg; + reg_d = pr->set_reg; + } + + fn += (pr->reg_width >> 3) - 1; + n = *fld_idx + 1; + + BUG_ON(n * pr->field_width > pr->reg_width); + + bit = pr->reg_width - (n * pr->field_width); + + return _INTC_MK(fn, mode, + intc_get_reg(d, reg_e), + intc_get_reg(d, reg_d), + pr->field_width, bit); + } + + *fld_idx = 0; + (*reg_idx)++; + } + + return 0; +} + +unsigned int __init +intc_get_prio_handle(struct intc_desc *desc, struct intc_desc_int *d, + intc_enum enum_id, int do_grps) +{ + unsigned int i = 0; + unsigned int j = 0; + unsigned int ret; + + ret = _intc_prio_data(desc, d, enum_id, &i, &j); + if (ret) + return ret; + + if (do_grps) + return intc_get_prio_handle(desc, d, intc_grp_id(desc, enum_id), 0); + + return 0; +} + +static unsigned int __init intc_ack_data(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id) +{ + struct intc_mask_reg *mr = desc->hw.ack_regs; + unsigned int i, j, fn, mode; + unsigned long reg_e, reg_d; + + for (i = 0; mr && enum_id && i < desc->hw.nr_ack_regs; i++) { + mr = desc->hw.ack_regs + i; + + for (j = 0; j < ARRAY_SIZE(mr->enum_ids); j++) { + if (mr->enum_ids[j] != enum_id) + continue; + + fn = REG_FN_MODIFY_BASE; + mode = MODE_ENABLE_REG; + reg_e = mr->set_reg; + reg_d = mr->set_reg; + + fn += (mr->reg_width >> 3) - 1; + return _INTC_MK(fn, mode, + intc_get_reg(d, reg_e), + intc_get_reg(d, reg_d), + 1, + (mr->reg_width - 1) - j); + } + } + + return 0; +} + +static void intc_enable_disable(struct intc_desc_int *d, + unsigned long handle, int do_enable) +{ + unsigned long addr; + unsigned int cpu; + unsigned long (*fn)(unsigned long, unsigned long, + unsigned long (*)(unsigned long, unsigned long, + unsigned long), + unsigned int); + + if (do_enable) { + for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_E(handle)); cpu++) { + addr = INTC_REG(d, _INTC_ADDR_E(handle), cpu); + fn = intc_enable_noprio_fns[_INTC_MODE(handle)]; + fn(addr, handle, intc_reg_fns[_INTC_FN(handle)], 0); + } + } else { + for (cpu = 0; cpu < SMP_NR(d, _INTC_ADDR_D(handle)); cpu++) { + addr = INTC_REG(d, _INTC_ADDR_D(handle), cpu); + fn = intc_disable_fns[_INTC_MODE(handle)]; + fn(addr, handle, intc_reg_fns[_INTC_FN(handle)], 0); + } + } +} + +void __init intc_enable_disable_enum(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, int enable) +{ + unsigned int i, j, data; + + /* go through and enable/disable all mask bits */ + i = j = 0; + do { + data = _intc_mask_data(desc, d, enum_id, &i, &j); + if (data) + intc_enable_disable(d, data, enable); + j++; + } while (data); + + /* go through and enable/disable all priority fields */ + i = j = 0; + do { + data = _intc_prio_data(desc, d, enum_id, &i, &j); + if (data) + intc_enable_disable(d, data, enable); + + j++; + } while (data); +} + +unsigned int __init +intc_get_sense_handle(struct intc_desc *desc, struct intc_desc_int *d, + intc_enum enum_id) +{ + struct intc_sense_reg *sr = desc->hw.sense_regs; + unsigned int i, j, fn, bit; + + for (i = 0; sr && enum_id && i < desc->hw.nr_sense_regs; i++) { + sr = desc->hw.sense_regs + i; + + for (j = 0; j < ARRAY_SIZE(sr->enum_ids); j++) { + if (sr->enum_ids[j] != enum_id) + continue; + + fn = REG_FN_MODIFY_BASE; + fn += (sr->reg_width >> 3) - 1; + + BUG_ON((j + 1) * sr->field_width > sr->reg_width); + + bit = sr->reg_width - ((j + 1) * sr->field_width); + + return _INTC_MK(fn, 0, intc_get_reg(d, sr->reg), + 0, sr->field_width, bit); + } + } + + return 0; +} + + +void intc_set_ack_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id) +{ + unsigned long flags; + + /* + * Nothing to do for this IRQ. + */ + if (!desc->hw.ack_regs) + return; + + raw_spin_lock_irqsave(&intc_big_lock, flags); + ack_handle[irq] = intc_ack_data(desc, d, id); + raw_spin_unlock_irqrestore(&intc_big_lock, flags); +} + +unsigned long intc_get_ack_handle(unsigned int irq) +{ + return ack_handle[irq]; +} diff --git a/drivers/sh/intc/internals.h b/drivers/sh/intc/internals.h new file mode 100644 index 000000000000..f02a47f74930 --- /dev/null +++ b/drivers/sh/intc/internals.h @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include + +#define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \ + ((shift) | ((width) << 5) | ((fn) << 9) | ((mode) << 13) | \ + ((addr_e) << 16) | ((addr_d << 24))) + +#define _INTC_SHIFT(h) (h & 0x1f) +#define _INTC_WIDTH(h) ((h >> 5) & 0xf) +#define _INTC_FN(h) ((h >> 9) & 0xf) +#define _INTC_MODE(h) ((h >> 13) & 0x7) +#define _INTC_ADDR_E(h) ((h >> 16) & 0xff) +#define _INTC_ADDR_D(h) ((h >> 24) & 0xff) + +#ifdef CONFIG_SMP +#define IS_SMP(x) (x.smp) +#define INTC_REG(d, x, c) (d->reg[(x)] + ((d->smp[(x)] & 0xff) * c)) +#define SMP_NR(d, x) ((d->smp[(x)] >> 8) ? (d->smp[(x)] >> 8) : 1) +#else +#define IS_SMP(x) 0 +#define INTC_REG(d, x, c) (d->reg[(x)]) +#define SMP_NR(d, x) 1 +#endif + +struct intc_handle_int { + unsigned int irq; + unsigned long handle; +}; + +struct intc_window { + phys_addr_t phys; + void __iomem *virt; + unsigned long size; +}; + +struct intc_map_entry { + intc_enum enum_id; + struct intc_desc_int *desc; +}; + +struct intc_subgroup_entry { + unsigned int pirq; + intc_enum enum_id; + unsigned long handle; +}; + +struct intc_desc_int { + struct list_head list; + struct sys_device sysdev; + struct radix_tree_root tree; + pm_message_t state; + raw_spinlock_t lock; + unsigned int index; + unsigned long *reg; +#ifdef CONFIG_SMP + unsigned long *smp; +#endif + unsigned int nr_reg; + struct intc_handle_int *prio; + unsigned int nr_prio; + struct intc_handle_int *sense; + unsigned int nr_sense; + struct intc_window *window; + unsigned int nr_windows; + struct irq_chip chip; +}; + + +enum { + REG_FN_ERR = 0, + REG_FN_TEST_BASE = 1, + REG_FN_WRITE_BASE = 5, + REG_FN_MODIFY_BASE = 9 +}; + +enum { MODE_ENABLE_REG = 0, /* Bit(s) set -> interrupt enabled */ + MODE_MASK_REG, /* Bit(s) set -> interrupt disabled */ + MODE_DUAL_REG, /* Two registers, set bit to enable / disable */ + MODE_PRIO_REG, /* Priority value written to enable interrupt */ + MODE_PCLR_REG, /* Above plus all bits set to disable interrupt */ +}; + +static inline struct intc_desc_int *get_intc_desc(unsigned int irq) +{ + struct irq_chip *chip = get_irq_chip(irq); + + return container_of(chip, struct intc_desc_int, chip); +} + +/* + * Grumble. + */ +static inline void activate_irq(int irq) +{ +#ifdef CONFIG_ARM + /* ARM requires an extra step to clear IRQ_NOREQUEST, which it + * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE. + */ + set_irq_flags(irq, IRQF_VALID); +#else + /* same effect on other architectures */ + set_irq_noprobe(irq); +#endif +} + +/* access.c */ +extern unsigned long +(*intc_reg_fns[])(unsigned long addr, unsigned long h, unsigned long data); + +extern unsigned long +(*intc_enable_fns[])(unsigned long addr, unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, unsigned long), + unsigned int irq); +extern unsigned long +(*intc_disable_fns[])(unsigned long addr, unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, unsigned long), + unsigned int irq); +extern unsigned long +(*intc_enable_noprio_fns[])(unsigned long addr, unsigned long handle, + unsigned long (*fn)(unsigned long, + unsigned long, unsigned long), + unsigned int irq); + +unsigned long intc_phys_to_virt(struct intc_desc_int *d, unsigned long address); +unsigned int intc_get_reg(struct intc_desc_int *d, unsigned long address); +unsigned int intc_set_field_from_handle(unsigned int value, + unsigned int field_value, + unsigned int handle); +unsigned long intc_get_field_from_handle(unsigned int value, + unsigned int handle); + +/* balancing.c */ +#ifdef CONFIG_INTC_BALANCING +void intc_balancing_enable(unsigned int irq); +void intc_balancing_disable(unsigned int irq); +void intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id); +#else +void intc_balancing_enable(unsigned int irq) { } +void intc_balancing_disable(unsigned int irq) { } +void intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id) { } +#endif + +/* chip.c */ +extern struct irq_chip intc_irq_chip; +void _intc_enable(unsigned int irq, unsigned long handle); + +/* core.c */ +extern struct list_head intc_list; +extern raw_spinlock_t intc_big_lock; +extern unsigned int nr_intc_controllers; +extern struct sysdev_class intc_sysdev_class; + +unsigned int intc_get_dfl_prio_level(void); +unsigned int intc_get_prio_level(unsigned int irq); +void intc_set_prio_level(unsigned int irq, unsigned int level); + +/* handle.c */ +unsigned int intc_get_mask_handle(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, int do_grps); +unsigned int intc_get_prio_handle(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id, int do_grps); +unsigned int intc_get_sense_handle(struct intc_desc *desc, + struct intc_desc_int *d, + intc_enum enum_id); +void intc_set_ack_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id); +unsigned long intc_get_ack_handle(unsigned int irq); +void intc_enable_disable_enum(struct intc_desc *desc, struct intc_desc_int *d, + intc_enum enum_id, int enable); + +/* virq.c */ +void intc_subgroup_init(struct intc_desc *desc, struct intc_desc_int *d); +void intc_irq_xlate_set(unsigned int irq, intc_enum id, struct intc_desc_int *d); +struct intc_map_entry *intc_irq_xlate_get(unsigned int irq); diff --git a/drivers/sh/intc/userimask.c b/drivers/sh/intc/userimask.c new file mode 100644 index 000000000000..e32304b66cf1 --- /dev/null +++ b/drivers/sh/intc/userimask.c @@ -0,0 +1,83 @@ +/* + * Support for hardware-assisted userspace interrupt masking. + * + * Copyright (C) 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "intc: " fmt + +#include +#include +#include +#include +#include +#include "internals.h" + +static void __iomem *uimask; + +static ssize_t +show_intc_userimask(struct sysdev_class *cls, + struct sysdev_class_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", (__raw_readl(uimask) >> 4) & 0xf); +} + +static ssize_t +store_intc_userimask(struct sysdev_class *cls, + struct sysdev_class_attribute *attr, + const char *buf, size_t count) +{ + unsigned long level; + + level = simple_strtoul(buf, NULL, 10); + + /* + * Minimal acceptable IRQ levels are in the 2 - 16 range, but + * these are chomped so as to not interfere with normal IRQs. + * + * Level 1 is a special case on some CPUs in that it's not + * directly settable, but given that USERIMASK cuts off below a + * certain level, we don't care about this limitation here. + * Level 0 on the other hand equates to user masking disabled. + * + * We use the default priority level as a cut off so that only + * special case opt-in IRQs can be mangled. + */ + if (level >= intc_get_dfl_prio_level()) + return -EINVAL; + + __raw_writel(0xa5 << 24 | level << 4, uimask); + + return count; +} + +static SYSDEV_CLASS_ATTR(userimask, S_IRUSR | S_IWUSR, + show_intc_userimask, store_intc_userimask); + + +static int __init userimask_sysdev_init(void) +{ + if (unlikely(!uimask)) + return -ENXIO; + + return sysdev_class_create_file(&intc_sysdev_class, &attr_userimask); +} +late_initcall(userimask_sysdev_init); + +int register_intc_userimask(unsigned long addr) +{ + if (unlikely(uimask)) + return -EBUSY; + + uimask = ioremap_nocache(addr, SZ_4K); + if (unlikely(!uimask)) + return -ENOMEM; + + pr_info("userimask support registered for levels 0 -> %d\n", + intc_get_dfl_prio_level() - 1); + + return 0; +} diff --git a/drivers/sh/intc/virq-debugfs.c b/drivers/sh/intc/virq-debugfs.c new file mode 100644 index 000000000000..9e62ba9311f0 --- /dev/null +++ b/drivers/sh/intc/virq-debugfs.c @@ -0,0 +1,64 @@ +/* + * Support for virtual IRQ subgroups debugfs mapping. + * + * Copyright (C) 2010 Paul Mundt + * + * Modelled after arch/powerpc/kernel/irq.c. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include +#include "internals.h" + +static int intc_irq_xlate_debug(struct seq_file *m, void *priv) +{ + int i; + + seq_printf(m, "%-5s %-7s %-15s\n", "irq", "enum", "chip name"); + + for (i = 1; i < nr_irqs; i++) { + struct intc_map_entry *entry = intc_irq_xlate_get(i); + struct intc_desc_int *desc = entry->desc; + + if (!desc) + continue; + + seq_printf(m, "%5d ", i); + seq_printf(m, "0x%05x ", entry->enum_id); + seq_printf(m, "%-15s\n", desc->chip.name); + } + + return 0; +} + +static int intc_irq_xlate_open(struct inode *inode, struct file *file) +{ + return single_open(file, intc_irq_xlate_debug, inode->i_private); +} + +static const struct file_operations intc_irq_xlate_fops = { + .open = intc_irq_xlate_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init intc_irq_xlate_init(void) +{ + /* + * XXX.. use arch_debugfs_dir here when all of the intc users are + * converted. + */ + if (debugfs_create_file("intc_irq_xlate", S_IRUGO, NULL, NULL, + &intc_irq_xlate_fops) == NULL) + return -ENOMEM; + + return 0; +} +fs_initcall(intc_irq_xlate_init); diff --git a/drivers/sh/intc/virq.c b/drivers/sh/intc/virq.c new file mode 100644 index 000000000000..643dfd4d2057 --- /dev/null +++ b/drivers/sh/intc/virq.c @@ -0,0 +1,255 @@ +/* + * Support for virtual IRQ subgroups. + * + * Copyright (C) 2010 Paul Mundt + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "intc: " fmt + +#include +#include +#include +#include +#include +#include "internals.h" + +static struct intc_map_entry intc_irq_xlate[NR_IRQS]; + +struct intc_virq_list { + unsigned int irq; + struct intc_virq_list *next; +}; + +#define for_each_virq(entry, head) \ + for (entry = head; entry; entry = entry->next) + +/* + * Tags for the radix tree + */ +#define INTC_TAG_VIRQ_NEEDS_ALLOC 0 + +void intc_irq_xlate_set(unsigned int irq, intc_enum id, struct intc_desc_int *d) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&intc_big_lock, flags); + intc_irq_xlate[irq].enum_id = id; + intc_irq_xlate[irq].desc = d; + raw_spin_unlock_irqrestore(&intc_big_lock, flags); +} + +struct intc_map_entry *intc_irq_xlate_get(unsigned int irq) +{ + return intc_irq_xlate + irq; +} + +int intc_irq_lookup(const char *chipname, intc_enum enum_id) +{ + struct intc_map_entry *ptr; + struct intc_desc_int *d; + int irq = -1; + + list_for_each_entry(d, &intc_list, list) { + int tagged; + + if (strcmp(d->chip.name, chipname) != 0) + continue; + + /* + * Catch early lookups for subgroup VIRQs that have not + * yet been allocated an IRQ. This already includes a + * fast-path out if the tree is untagged, so there is no + * need to explicitly test the root tree. + */ + tagged = radix_tree_tag_get(&d->tree, enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + if (unlikely(tagged)) + break; + + ptr = radix_tree_lookup(&d->tree, enum_id); + if (ptr) { + irq = ptr - intc_irq_xlate; + break; + } + } + + return irq; +} +EXPORT_SYMBOL_GPL(intc_irq_lookup); + +static int add_virq_to_pirq(unsigned int irq, unsigned int virq) +{ + struct intc_virq_list **last, *entry; + struct irq_desc *desc = irq_to_desc(irq); + + /* scan for duplicates */ + last = (struct intc_virq_list **)&desc->handler_data; + for_each_virq(entry, desc->handler_data) { + if (entry->irq == virq) + return 0; + last = &entry->next; + } + + entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC); + if (!entry) { + pr_err("can't allocate VIRQ mapping for %d\n", virq); + return -ENOMEM; + } + + entry->irq = virq; + + *last = entry; + + return 0; +} + +static void intc_virq_handler(unsigned int irq, struct irq_desc *desc) +{ + struct intc_virq_list *entry, *vlist = get_irq_data(irq); + struct intc_desc_int *d = get_intc_desc(irq); + + desc->chip->mask_ack(irq); + + for_each_virq(entry, vlist) { + unsigned long addr, handle; + + handle = (unsigned long)get_irq_data(entry->irq); + addr = INTC_REG(d, _INTC_ADDR_E(handle), 0); + + if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0)) + generic_handle_irq(entry->irq); + } + + desc->chip->unmask(irq); +} + +static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup, + struct intc_desc_int *d, + unsigned int index) +{ + unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1; + + return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg), + 0, 1, (subgroup->reg_width - 1) - index); +} + +static void __init intc_subgroup_init_one(struct intc_desc *desc, + struct intc_desc_int *d, + struct intc_subgroup *subgroup) +{ + struct intc_map_entry *mapped; + unsigned int pirq; + unsigned long flags; + int i; + + mapped = radix_tree_lookup(&d->tree, subgroup->parent_id); + if (!mapped) { + WARN_ON(1); + return; + } + + pirq = mapped - intc_irq_xlate; + + raw_spin_lock_irqsave(&d->lock, flags); + + for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) { + struct intc_subgroup_entry *entry; + int err; + + if (!subgroup->enum_ids[i]) + continue; + + entry = kmalloc(sizeof(*entry), GFP_NOWAIT); + if (!entry) + break; + + entry->pirq = pirq; + entry->enum_id = subgroup->enum_ids[i]; + entry->handle = intc_subgroup_data(subgroup, d, i); + + err = radix_tree_insert(&d->tree, entry->enum_id, entry); + if (unlikely(err < 0)) + break; + + radix_tree_tag_set(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + } + + raw_spin_unlock_irqrestore(&d->lock, flags); +} + +void __init intc_subgroup_init(struct intc_desc *desc, struct intc_desc_int *d) +{ + int i; + + if (!desc->hw.subgroups) + return; + + for (i = 0; i < desc->hw.nr_subgroups; i++) + intc_subgroup_init_one(desc, d, desc->hw.subgroups + i); +} + +static void __init intc_subgroup_map(struct intc_desc_int *d) +{ + struct intc_subgroup_entry *entries[32]; + unsigned long flags; + unsigned int nr_found; + int i; + + raw_spin_lock_irqsave(&d->lock, flags); + +restart: + nr_found = radix_tree_gang_lookup_tag_slot(&d->tree, + (void ***)entries, 0, ARRAY_SIZE(entries), + INTC_TAG_VIRQ_NEEDS_ALLOC); + + for (i = 0; i < nr_found; i++) { + struct intc_subgroup_entry *entry; + int irq; + + entry = radix_tree_deref_slot((void **)entries[i]); + if (unlikely(!entry)) + continue; + if (unlikely(entry == RADIX_TREE_RETRY)) + goto restart; + + irq = create_irq(); + if (unlikely(irq < 0)) { + pr_err("no more free IRQs, bailing..\n"); + break; + } + + pr_info("Setting up a chained VIRQ from %d -> %d\n", + irq, entry->pirq); + + intc_irq_xlate_set(irq, entry->enum_id, d); + + set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq), + handle_simple_irq, "virq"); + set_irq_chip_data(irq, get_irq_chip_data(entry->pirq)); + + set_irq_data(irq, (void *)entry->handle); + + set_irq_chained_handler(entry->pirq, intc_virq_handler); + add_virq_to_pirq(entry->pirq, irq); + + radix_tree_tag_clear(&d->tree, entry->enum_id, + INTC_TAG_VIRQ_NEEDS_ALLOC); + radix_tree_replace_slot((void **)entries[i], + &intc_irq_xlate[irq]); + } + + raw_spin_unlock_irqrestore(&d->lock, flags); +} + +void __init intc_finalize(void) +{ + struct intc_desc_int *d; + + list_for_each_entry(d, &intc_list, list) + if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC)) + intc_subgroup_map(d); +} diff --git a/include/linux/sh_intc.h b/include/linux/sh_intc.h index 1fc69701e0f8..b4f183a31f13 100644 --- a/include/linux/sh_intc.h +++ b/include/linux/sh_intc.h @@ -114,7 +114,7 @@ struct intc_desc symbol __initdata = { \ prio_regs, sense_regs, ack_regs), \ } -int __init register_intc_controller(struct intc_desc *desc); +int register_intc_controller(struct intc_desc *desc); void reserve_intc_vectors(struct intc_vect *vectors, unsigned int nr_vecs); int intc_set_priority(unsigned int irq, unsigned int prio); int intc_irq_lookup(const char *chipname, intc_enum enum_id); -- cgit v1.2.3 From 33fc1a211c400049f1b5276d5a925a84d3d2b6d2 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Wed, 6 Oct 2010 15:38:16 +0900 Subject: sh: intc: Add missing files. The Kconfig and Makefile were overlooked, add those in now to improve odds of building. Signed-off-by: Paul Mundt --- drivers/sh/intc/Kconfig | 35 +++++++++++++++++++++++++++++++++++ drivers/sh/intc/Makefile | 5 +++++ 2 files changed, 40 insertions(+) create mode 100644 drivers/sh/intc/Kconfig create mode 100644 drivers/sh/intc/Makefile (limited to 'drivers') diff --git a/drivers/sh/intc/Kconfig b/drivers/sh/intc/Kconfig new file mode 100644 index 000000000000..c88cbccc62b0 --- /dev/null +++ b/drivers/sh/intc/Kconfig @@ -0,0 +1,35 @@ +comment "Interrupt controller options" + +config INTC_USERIMASK + bool "Userspace interrupt masking support" + depends on ARCH_SHMOBILE || (SUPERH && CPU_SH4A) + help + This enables support for hardware-assisted userspace hardirq + masking. + + SH-4A and newer interrupt blocks all support a special shadowed + page with all non-masking registers obscured when mapped in to + userspace. This is primarily for use by userspace device + drivers that are using special priority levels. + + If in doubt, say N. + +config INTC_BALANCING + bool "Hardware IRQ balancing support" + depends on SMP && SUPERH && CPU_SHX3 + help + This enables support for IRQ auto-distribution mode on SH-X3 + SMP parts. All of the balancing and CPU wakeup decisions are + taken care of automatically by hardware for distributed + vectors. + + If in doubt, say N. + +config INTC_MAPPING_DEBUG + bool "Expose IRQ to per-controller id mapping via debugfs" + depends on DEBUG_FS + help + This will create a debugfs entry for showing the relationship + between system IRQs and the per-controller id tables. + + If in doubt, say N. diff --git a/drivers/sh/intc/Makefile b/drivers/sh/intc/Makefile new file mode 100644 index 000000000000..bb5df868d77a --- /dev/null +++ b/drivers/sh/intc/Makefile @@ -0,0 +1,5 @@ +obj-y := access.o chip.o core.o dynamic.o handle.o virq.o + +obj-$(CONFIG_INTC_BALANCING) += balancing.o +obj-$(CONFIG_INTC_USERIMASK) += userimask.o +obj-$(CONFIG_INTC_MAPPING_DEBUG) += virq-debugfs.o -- cgit v1.2.3 From 6966fed9d8a74f178fc0dabdcc687cb1fe5b75fe Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Wed, 6 Oct 2010 16:44:10 +0900 Subject: sh: intc: Fix build with IRQ balancing disabled. The balancing stubs obviously need to be static inline.. Signed-off-by: Paul Mundt --- drivers/sh/intc/internals.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/intc/internals.h b/drivers/sh/intc/internals.h index f02a47f74930..d49482c623fa 100644 --- a/drivers/sh/intc/internals.h +++ b/drivers/sh/intc/internals.h @@ -143,10 +143,11 @@ void intc_balancing_disable(unsigned int irq); void intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, struct intc_desc_int *d, intc_enum id); #else -void intc_balancing_enable(unsigned int irq) { } -void intc_balancing_disable(unsigned int irq) { } -void intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, - struct intc_desc_int *d, intc_enum id) { } +static inline void intc_balancing_enable(unsigned int irq) { } +static inline void intc_balancing_disable(unsigned int irq) { } +static inline void +intc_set_dist_handle(unsigned int irq, struct intc_desc *desc, + struct intc_desc_int *d, intc_enum id) { } #endif /* chip.c */ -- cgit v1.2.3 From 1ffbb037d8e81ba4f09901451b39c8f178b05559 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 13 Oct 2010 07:17:45 +0000 Subject: sh: free-without-alloc fix for sh_mobile_lcdcfb Without this fix the LCDC driver will try to free framebuffer memory even though the allocation failed. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_lcdcfb.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index d72075a9f01c..7a1419279c8f 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -1243,8 +1243,10 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) if (priv->ch[i].sglist) vfree(priv->ch[i].sglist); - dma_free_coherent(&pdev->dev, info->fix.smem_len, - info->screen_base, priv->ch[i].dma_handle); + if (info->screen_base) + dma_free_coherent(&pdev->dev, info->fix.smem_len, + info->screen_base, + priv->ch[i].dma_handle); fb_dealloc_cmap(&info->cmap); framebuffer_release(info); } -- cgit v1.2.3 From 69395396a0a8866f30d59c66b7be1912ccb5d160 Mon Sep 17 00:00:00 2001 From: Magnus Damm Date: Wed, 13 Oct 2010 07:44:36 +0000 Subject: sh: remove name and id from struct clk Remove "name" and "id" from drivers/sh/ struct clk. The struct clk members "name" and "id" are not used now when matching is done through clkdev. Signed-off-by: Magnus Damm Signed-off-by: Paul Mundt --- drivers/sh/clk-cpg.c | 2 -- drivers/sh/clk.c | 14 ++++++-------- include/linux/sh_clk.h | 3 --- 3 files changed, 6 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk-cpg.c b/drivers/sh/clk-cpg.c index 8c024b984ed8..392627be4544 100644 --- a/drivers/sh/clk-cpg.c +++ b/drivers/sh/clk-cpg.c @@ -180,7 +180,6 @@ static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, clkp = clks + k; clkp->ops = ops; - clkp->id = -1; clkp->freq_table = freq_table + (k * freq_table_size); clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; @@ -319,7 +318,6 @@ static int __init sh_clk_div4_register_ops(struct clk *clks, int nr, clkp = clks + k; clkp->ops = ops; - clkp->id = -1; clkp->priv = table; clkp->freq_table = freq_table + (k * freq_table_size); diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index b9c57a640c24..30ad6f04afb0 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -161,8 +161,8 @@ void propagate_rate(struct clk *tclk) static void __clk_disable(struct clk *clk) { - if (WARN(!clk->usecount, "Trying to disable clock %s with 0 usecount\n", - clk->name)) + if (WARN(!clk->usecount, "Trying to disable clock %p with 0 usecount\n", + clk)) return; if (!(--clk->usecount)) { @@ -357,8 +357,8 @@ int clk_set_parent(struct clk *clk, struct clk *parent) if (ret == 0) { if (clk->ops->recalc) clk->rate = clk->ops->recalc(clk); - pr_debug("clock: set parent of %s to %s (new rate %ld)\n", - clk->name, clk->parent->name, clk->rate); + pr_debug("clock: set parent of %p to %p (new rate %ld)\n", + clk, clk->parent, clk->rate); propagate_rate(clk); } } else @@ -470,9 +470,7 @@ static int clk_debugfs_register_one(struct clk *c) char s[255]; char *p = s; - p += sprintf(p, "%s", c->name); - if (c->id >= 0) - sprintf(p, ":%d", c->id); + p += sprintf(p, "%p", c); d = debugfs_create_dir(s, pa ? pa->dentry : clk_debugfs_root); if (!d) return -ENOMEM; @@ -514,7 +512,7 @@ static int clk_debugfs_register(struct clk *c) return err; } - if (!c->dentry && c->name) { + if (!c->dentry) { err = clk_debugfs_register_one(c); if (err) return err; diff --git a/include/linux/sh_clk.h b/include/linux/sh_clk.h index 875ce50719a9..ecdfea54a49e 100644 --- a/include/linux/sh_clk.h +++ b/include/linux/sh_clk.h @@ -21,9 +21,6 @@ struct clk_ops { struct clk { struct list_head node; - const char *name; - int id; - struct clk *parent; struct clk **parent_table; /* list of parents to */ unsigned short parent_num; /* choose between */ -- cgit v1.2.3 From 550a1ef18a53cae3d908edceca9daaafbedcf12f Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Wed, 13 Oct 2010 19:24:55 +0900 Subject: sh: use pr_fmt for clock framework, too. Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 30ad6f04afb0..661a801126ad 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -14,6 +14,8 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. */ +#define pr_fmt(fmt) "clock: " fmt + #include #include #include @@ -357,7 +359,7 @@ int clk_set_parent(struct clk *clk, struct clk *parent) if (ret == 0) { if (clk->ops->recalc) clk->rate = clk->ops->recalc(clk); - pr_debug("clock: set parent of %p to %p (new rate %ld)\n", + pr_debug("set parent of %p to %p (new rate %ld)\n", clk, clk->parent, clk->rate); propagate_rate(clk); } -- cgit v1.2.3 From 28085bc5de19cad365bcff98e9c8785c397c7c36 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 15 Oct 2010 16:46:37 +0900 Subject: sh: clkfwk: support clock remapping. This implements support for ioremapping of register windows that encapsulate clock control registers used by a struct clk, with transparent sibling inheritance. Root clocks at the top of a given topology often encapsulate the entire register space of all of their sibling clocks, so this mapping can be done once and handed down. A given clock enable/disable case maps out to a single bit in a shared register, so this prevents creating multiple overlapping mappings. The mapping case breaks down in to a couple of different situations: - Sibling clocks without a specific mapping. - Root clocks without a specific mapping. - Any of sibling/root clocks with a specific mapping. Sibling clocks with no specified mapping will grovel up the clock chain and install the root clock mapping unconditionally at registration time. Root clocks without their own mappings have a dummy BSS-initialized mapping inserted that is handed down the chain just like any other mapping. This permits all of the sibling clock ops to read/write using the mapping offsets without any special configuration, enabling them to not care whether access ultimately goes through translatable or untranslatable memory. Any clock with its own mapping will have the window initialized at registration time and be ready for use by its clock ops. Failure to establish the mapping will prevent registration, so no additional sanity checks are needed. Sibling clocks that double as parents for the moment will not propagate their mapping down, but this is easily tunable if the need arises. All clock mappings are kref refcounted, with each instance of mapping inheritance incrementing the refcount. Tested-by: Kuninori Morimoto Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/sh_clk.h | 10 ++++++ 2 files changed, 99 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 661a801126ad..813d97cdad49 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -251,8 +251,88 @@ void recalculate_root_clocks(void) } } +static struct clk_mapping dummy_mapping; + +static struct clk *lookup_root_clock(struct clk *clk) +{ + while (clk->parent) + clk = clk->parent; + + return clk; +} + +static int clk_establish_mapping(struct clk *clk) +{ + struct clk_mapping *mapping = clk->mapping; + + /* + * Propagate mappings. + */ + if (!mapping) { + struct clk *clkp; + + /* + * dummy mapping for root clocks with no specified ranges + */ + if (!clk->parent) { + clk->mapping = &dummy_mapping; + return 0; + } + + /* + * If we're on a child clock and it provides no mapping of its + * own, inherit the mapping from its root clock. + */ + clkp = lookup_root_clock(clk); + mapping = clkp->mapping; + BUG_ON(!mapping); + } + + /* + * Establish initial mapping. + */ + if (!mapping->base && mapping->phys) { + kref_init(&mapping->ref); + + mapping->base = ioremap_nocache(mapping->phys, mapping->len); + if (unlikely(!mapping->base)) + return -ENXIO; + } else if (mapping->base) { + /* + * Bump the refcount for an existing mapping + */ + kref_get(&mapping->ref); + } + + clk->mapping = mapping; + return 0; +} + +static void clk_destroy_mapping(struct kref *kref) +{ + struct clk_mapping *mapping; + + mapping = container_of(kref, struct clk_mapping, ref); + + iounmap(mapping->base); +} + +static void clk_teardown_mapping(struct clk *clk) +{ + struct clk_mapping *mapping = clk->mapping; + + /* Nothing to do */ + if (mapping == &dummy_mapping) + return; + + kref_put(&mapping->ref, clk_destroy_mapping); + clk->mapping = NULL; +} + int clk_register(struct clk *clk) { + int ret; + if (clk == NULL || IS_ERR(clk)) return -EINVAL; @@ -267,6 +347,10 @@ int clk_register(struct clk *clk) INIT_LIST_HEAD(&clk->children); clk->usecount = 0; + ret = clk_establish_mapping(clk); + if (unlikely(ret)) + goto out_unlock; + if (clk->parent) list_add(&clk->sibling, &clk->parent->children); else @@ -275,9 +359,11 @@ int clk_register(struct clk *clk) list_add(&clk->node, &clock_list); if (clk->ops && clk->ops->init) clk->ops->init(clk); + +out_unlock: mutex_unlock(&clock_list_sem); - return 0; + return ret; } EXPORT_SYMBOL_GPL(clk_register); @@ -286,6 +372,7 @@ void clk_unregister(struct clk *clk) mutex_lock(&clock_list_sem); list_del(&clk->sibling); list_del(&clk->node); + clk_teardown_mapping(clk); mutex_unlock(&clock_list_sem); } EXPORT_SYMBOL_GPL(clk_unregister); diff --git a/include/linux/sh_clk.h b/include/linux/sh_clk.h index ecdfea54a49e..8ae37707a4a4 100644 --- a/include/linux/sh_clk.h +++ b/include/linux/sh_clk.h @@ -4,11 +4,20 @@ #include #include #include +#include +#include #include #include struct clk; +struct clk_mapping { + phys_addr_t phys; + void __iomem *base; + unsigned long len; + struct kref ref; +}; + struct clk_ops { void (*init)(struct clk *clk); int (*enable)(struct clk *clk); @@ -42,6 +51,7 @@ struct clk { unsigned long arch_flags; void *priv; struct dentry *dentry; + struct clk_mapping *mapping; struct cpufreq_frequency_table *freq_table; }; -- cgit v1.2.3 From f586903d27e2503a3e7d427b3d665bbaf1b7f4d4 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 15 Oct 2010 18:17:35 +0900 Subject: sh: clkfwk: Abstract rate rounding helper. Presently the only assisted rate rounding is frequency table backed, but there are cases where it's impractical to use a frequency table for certain clocks (such as the FSIDIV case, which supports 65535 divisors), and we wish to reuse the same rate rounding algorithm. This breaks out the core of the rate rounding logic in to its own helper routine and shuffles the frequency table logic around, switching to using an iterator for the generic helper routine. Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 66 +++++++++++++++++++++++++++++++++++++++----------- include/linux/sh_clk.h | 1 + 2 files changed, 53 insertions(+), 14 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 813d97cdad49..3ac6fa0005b9 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -45,6 +45,8 @@ void clk_rate_table_build(struct clk *clk, unsigned long freq; int i; + clk->nr_freqs = nr_freqs; + for (i = 0; i < nr_freqs; i++) { div = 1; mult = 1; @@ -69,30 +71,39 @@ void clk_rate_table_build(struct clk *clk, freq_table[i].frequency = CPUFREQ_TABLE_END; } -long clk_rate_table_round(struct clk *clk, - struct cpufreq_frequency_table *freq_table, - unsigned long rate) +struct clk_rate_round_data; + +struct clk_rate_round_data { + unsigned long rate; + unsigned int min, max; + long (*func)(unsigned int pos, struct clk_rate_round_data *arg); + void *arg; +}; + +#define for_each_frequency(pos, r, freq) \ + for (pos = r->min, freq = r->func(pos, r->arg); \ + pos < r->max; pos++, freq = r->func(pos, r)) \ + if (unlikely(freq == 0)) \ + ; \ + else + +static long clk_rate_round_helper(struct clk_rate_round_data *rounder) { unsigned long rate_error, rate_error_prev = ~0UL; - unsigned long rate_best_fit = rate; - unsigned long highest, lowest; + unsigned long rate_best_fit = rounder->rate; + unsigned long highest, lowest, freq; int i; highest = 0; lowest = ~0UL; - for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { - unsigned long freq = freq_table[i].frequency; - - if (freq == CPUFREQ_ENTRY_INVALID) - continue; - + for_each_frequency(i, rounder, freq) { if (freq > highest) highest = freq; if (freq < lowest) lowest = freq; - rate_error = abs(freq - rate); + rate_error = abs(freq - rounder->rate); if (rate_error < rate_error_prev) { rate_best_fit = freq; rate_error_prev = rate_error; @@ -102,14 +113,41 @@ long clk_rate_table_round(struct clk *clk, break; } - if (rate >= highest) + if (rounder->rate >= highest) rate_best_fit = highest; - if (rate <= lowest) + if (rounder->rate <= lowest) rate_best_fit = lowest; return rate_best_fit; } +static long clk_rate_table_iter(unsigned int pos, + struct clk_rate_round_data *rounder) +{ + struct cpufreq_frequency_table *freq_table = rounder->arg; + unsigned long freq = freq_table[pos].frequency; + + if (freq == CPUFREQ_ENTRY_INVALID) + freq = 0; + + return freq; +} + +long clk_rate_table_round(struct clk *clk, + struct cpufreq_frequency_table *freq_table, + unsigned long rate) +{ + struct clk_rate_round_data table_round = { + .min = 0, + .max = clk->nr_freqs, + .func = clk_rate_table_iter, + .arg = freq_table, + .rate = rate, + }; + + return clk_rate_round_helper(&table_round); +} + int clk_rate_table_find(struct clk *clk, struct cpufreq_frequency_table *freq_table, unsigned long rate) diff --git a/include/linux/sh_clk.h b/include/linux/sh_clk.h index 8ae37707a4a4..49f6e9b6eda2 100644 --- a/include/linux/sh_clk.h +++ b/include/linux/sh_clk.h @@ -53,6 +53,7 @@ struct clk { struct dentry *dentry; struct clk_mapping *mapping; struct cpufreq_frequency_table *freq_table; + unsigned int nr_freqs; }; #define CLK_ENABLE_ON_INIT (1 << 0) -- cgit v1.2.3 From 8e122db61c98debbc35e26dd29504958cbcf2cbb Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Fri, 15 Oct 2010 18:33:24 +0900 Subject: sh: clkfwk: Add a helper for rate rounding by divisor ranges. This adds a new clk_rate_div_range_round() for implementing rate rounding by divisor ranges. This can be used trivially by clocks that support arbitrary ranged divisors without the need for rate table construction. This should only be used by clocks that both have large divisor ranges in addition to clocks that will never be arbitrarily scaled, as the lack of a backing frequency table will prevent cpufreq from being able to do much of anything with them. Primarily intended for use as a ->recalc helper. Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 22 +++++++++++++++++++++- include/linux/sh_clk.h | 3 +++ 2 files changed, 24 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 3ac6fa0005b9..018be37ef339 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -76,7 +76,7 @@ struct clk_rate_round_data; struct clk_rate_round_data { unsigned long rate; unsigned int min, max; - long (*func)(unsigned int pos, struct clk_rate_round_data *arg); + long (*func)(unsigned int, struct clk_rate_round_data *); void *arg; }; @@ -148,6 +148,26 @@ long clk_rate_table_round(struct clk *clk, return clk_rate_round_helper(&table_round); } +static long clk_rate_div_range_iter(unsigned int pos, + struct clk_rate_round_data *rounder) +{ + return clk_get_rate(rounder->arg) / pos; +} + +long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, + unsigned int div_max, unsigned long rate) +{ + struct clk_rate_round_data div_range_round = { + .min = div_min, + .max = div_max, + .func = clk_rate_div_range_iter, + .arg = clk_get_parent(clk), + .rate = rate, + }; + + return clk_rate_round_helper(&div_range_round); +} + int clk_rate_table_find(struct clk *clk, struct cpufreq_frequency_table *freq_table, unsigned long rate) diff --git a/include/linux/sh_clk.h b/include/linux/sh_clk.h index 49f6e9b6eda2..4dca992f3093 100644 --- a/include/linux/sh_clk.h +++ b/include/linux/sh_clk.h @@ -119,6 +119,9 @@ int clk_rate_table_find(struct clk *clk, struct cpufreq_frequency_table *freq_table, unsigned long rate); +long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, + unsigned int div_max, unsigned long rate); + #define SH_CLK_MSTP32(_parent, _enable_reg, _enable_bit, _flags) \ { \ .parent = _parent, \ -- cgit v1.2.3 From e5690e0dcffe38fa7b4b6336169a0c518d8a688e Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Sat, 16 Oct 2010 00:51:05 +0900 Subject: sh: clkfwk: Fix fault in frequency iterator. When updating the iterator macro an old argument assignment was used on the initial assignment causing a fault on the table rounding. Fix it up. Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index 018be37ef339..c76f972db69a 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -81,7 +81,7 @@ struct clk_rate_round_data { }; #define for_each_frequency(pos, r, freq) \ - for (pos = r->min, freq = r->func(pos, r->arg); \ + for (pos = r->min, freq = r->func(pos, r); \ pos < r->max; pos++, freq = r->func(pos, r)) \ if (unlikely(freq == 0)) \ ; \ -- cgit v1.2.3 From c2590f4a8ddf461d33ac2085d966432b2a6a09f2 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 18 Oct 2010 03:50:29 +0000 Subject: sh: clkfwk: modify for_each_frequency end condition The end condition of for_each_frequency should care about both clk_rate_table_round and clk_rate_div_range_round, and using "correct max size" is a natural idea in later function. To avoid data over flow, this patch didn't modify clk_rate_div_range_round side as .max = div_max + 1. Signed-off-by: Kuninori Morimoto Signed-off-by: Paul Mundt --- drivers/sh/clk.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c index c76f972db69a..dae64ee9ecc5 100644 --- a/drivers/sh/clk.c +++ b/drivers/sh/clk.c @@ -82,7 +82,7 @@ struct clk_rate_round_data { #define for_each_frequency(pos, r, freq) \ for (pos = r->min, freq = r->func(pos, r); \ - pos < r->max; pos++, freq = r->func(pos, r)) \ + pos <= r->max; pos++, freq = r->func(pos, r)) \ if (unlikely(freq == 0)) \ ; \ else @@ -139,12 +139,15 @@ long clk_rate_table_round(struct clk *clk, { struct clk_rate_round_data table_round = { .min = 0, - .max = clk->nr_freqs, + .max = clk->nr_freqs - 1, .func = clk_rate_table_iter, .arg = freq_table, .rate = rate, }; + if (clk->nr_freqs < 1) + return 0; + return clk_rate_round_helper(&table_round); } -- cgit v1.2.3 From de9186c257acb06ca8187cff1c94412b5f80a3bd Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Mon, 18 Oct 2010 21:32:58 +0900 Subject: sh: clkfwk: Shuffle around to match the intc split up. This shuffles the clock framework code around to a drivers/sh/clk subdir, to follow the intc split up. This will make it easier to subsequently break things out as well as plug in different helpers for non-CPG users. Signed-off-by: Paul Mundt --- drivers/sh/Makefile | 7 +- drivers/sh/clk-cpg.c | 350 ------------------------ drivers/sh/clk.c | 694 ------------------------------------------------ drivers/sh/clk/Makefile | 3 + drivers/sh/clk/core.c | 694 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/sh/clk/cpg.c | 359 +++++++++++++++++++++++++ 6 files changed, 1059 insertions(+), 1048 deletions(-) delete mode 100644 drivers/sh/clk-cpg.c delete mode 100644 drivers/sh/clk.c create mode 100644 drivers/sh/clk/Makefile create mode 100644 drivers/sh/clk/core.c create mode 100644 drivers/sh/clk/cpg.c (limited to 'drivers') diff --git a/drivers/sh/Makefile b/drivers/sh/Makefile index 50dd5a65f72e..24e6cec0ae8d 100644 --- a/drivers/sh/Makefile +++ b/drivers/sh/Makefile @@ -1,10 +1,9 @@ # # Makefile for the SuperH specific drivers. # -obj-y := clk.o intc/ +obj-y := intc/ -obj-$(CONFIG_SUPERHYWAY) += superhyway/ +obj-$(CONFIG_HAVE_CLK) += clk/ obj-$(CONFIG_MAPLE) += maple/ - +obj-$(CONFIG_SUPERHYWAY) += superhyway/ obj-$(CONFIG_GENERIC_GPIO) += pfc.o -obj-$(CONFIG_SH_CLK_CPG) += clk-cpg.o diff --git a/drivers/sh/clk-cpg.c b/drivers/sh/clk-cpg.c deleted file mode 100644 index 392627be4544..000000000000 --- a/drivers/sh/clk-cpg.c +++ /dev/null @@ -1,350 +0,0 @@ -#include -#include -#include -#include -#include - -static int sh_clk_mstp32_enable(struct clk *clk) -{ - __raw_writel(__raw_readl(clk->enable_reg) & ~(1 << clk->enable_bit), - clk->enable_reg); - return 0; -} - -static void sh_clk_mstp32_disable(struct clk *clk) -{ - __raw_writel(__raw_readl(clk->enable_reg) | (1 << clk->enable_bit), - clk->enable_reg); -} - -static struct clk_ops sh_clk_mstp32_clk_ops = { - .enable = sh_clk_mstp32_enable, - .disable = sh_clk_mstp32_disable, - .recalc = followparent_recalc, -}; - -int __init sh_clk_mstp32_register(struct clk *clks, int nr) -{ - struct clk *clkp; - int ret = 0; - int k; - - for (k = 0; !ret && (k < nr); k++) { - clkp = clks + k; - clkp->ops = &sh_clk_mstp32_clk_ops; - ret |= clk_register(clkp); - } - - return ret; -} - -static long sh_clk_div_round_rate(struct clk *clk, unsigned long rate) -{ - return clk_rate_table_round(clk, clk->freq_table, rate); -} - -static int sh_clk_div6_divisors[64] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 -}; - -static struct clk_div_mult_table sh_clk_div6_table = { - .divisors = sh_clk_div6_divisors, - .nr_divisors = ARRAY_SIZE(sh_clk_div6_divisors), -}; - -static unsigned long sh_clk_div6_recalc(struct clk *clk) -{ - struct clk_div_mult_table *table = &sh_clk_div6_table; - unsigned int idx; - - clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, - table, NULL); - - idx = __raw_readl(clk->enable_reg) & 0x003f; - - return clk->freq_table[idx].frequency; -} - -static int sh_clk_div6_set_parent(struct clk *clk, struct clk *parent) -{ - struct clk_div_mult_table *table = &sh_clk_div6_table; - u32 value; - int ret, i; - - if (!clk->parent_table || !clk->parent_num) - return -EINVAL; - - /* Search the parent */ - for (i = 0; i < clk->parent_num; i++) - if (clk->parent_table[i] == parent) - break; - - if (i == clk->parent_num) - return -ENODEV; - - ret = clk_reparent(clk, parent); - if (ret < 0) - return ret; - - value = __raw_readl(clk->enable_reg) & - ~(((1 << clk->src_width) - 1) << clk->src_shift); - - __raw_writel(value | (i << clk->src_shift), clk->enable_reg); - - /* Rebuild the frequency table */ - clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, - table, &clk->arch_flags); - - return 0; -} - -static int sh_clk_div6_set_rate(struct clk *clk, - unsigned long rate, int algo_id) -{ - unsigned long value; - int idx; - - idx = clk_rate_table_find(clk, clk->freq_table, rate); - if (idx < 0) - return idx; - - value = __raw_readl(clk->enable_reg); - value &= ~0x3f; - value |= idx; - __raw_writel(value, clk->enable_reg); - return 0; -} - -static int sh_clk_div6_enable(struct clk *clk) -{ - unsigned long value; - int ret; - - ret = sh_clk_div6_set_rate(clk, clk->rate, 0); - if (ret == 0) { - value = __raw_readl(clk->enable_reg); - value &= ~0x100; /* clear stop bit to enable clock */ - __raw_writel(value, clk->enable_reg); - } - return ret; -} - -static void sh_clk_div6_disable(struct clk *clk) -{ - unsigned long value; - - value = __raw_readl(clk->enable_reg); - value |= 0x100; /* stop clock */ - value |= 0x3f; /* VDIV bits must be non-zero, overwrite divider */ - __raw_writel(value, clk->enable_reg); -} - -static struct clk_ops sh_clk_div6_clk_ops = { - .recalc = sh_clk_div6_recalc, - .round_rate = sh_clk_div_round_rate, - .set_rate = sh_clk_div6_set_rate, - .enable = sh_clk_div6_enable, - .disable = sh_clk_div6_disable, -}; - -static struct clk_ops sh_clk_div6_reparent_clk_ops = { - .recalc = sh_clk_div6_recalc, - .round_rate = sh_clk_div_round_rate, - .set_rate = sh_clk_div6_set_rate, - .enable = sh_clk_div6_enable, - .disable = sh_clk_div6_disable, - .set_parent = sh_clk_div6_set_parent, -}; - -static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, - struct clk_ops *ops) -{ - struct clk *clkp; - void *freq_table; - int nr_divs = sh_clk_div6_table.nr_divisors; - int freq_table_size = sizeof(struct cpufreq_frequency_table); - int ret = 0; - int k; - - freq_table_size *= (nr_divs + 1); - freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); - if (!freq_table) { - pr_err("sh_clk_div6_register: unable to alloc memory\n"); - return -ENOMEM; - } - - for (k = 0; !ret && (k < nr); k++) { - clkp = clks + k; - - clkp->ops = ops; - clkp->freq_table = freq_table + (k * freq_table_size); - clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; - - ret = clk_register(clkp); - } - - return ret; -} - -int __init sh_clk_div6_register(struct clk *clks, int nr) -{ - return sh_clk_div6_register_ops(clks, nr, &sh_clk_div6_clk_ops); -} - -int __init sh_clk_div6_reparent_register(struct clk *clks, int nr) -{ - return sh_clk_div6_register_ops(clks, nr, - &sh_clk_div6_reparent_clk_ops); -} - -static unsigned long sh_clk_div4_recalc(struct clk *clk) -{ - struct clk_div4_table *d4t = clk->priv; - struct clk_div_mult_table *table = d4t->div_mult_table; - unsigned int idx; - - clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, - table, &clk->arch_flags); - - idx = (__raw_readl(clk->enable_reg) >> clk->enable_bit) & 0x000f; - - return clk->freq_table[idx].frequency; -} - -static int sh_clk_div4_set_parent(struct clk *clk, struct clk *parent) -{ - struct clk_div4_table *d4t = clk->priv; - struct clk_div_mult_table *table = d4t->div_mult_table; - u32 value; - int ret; - - /* we really need a better way to determine parent index, but for - * now assume internal parent comes with CLK_ENABLE_ON_INIT set, - * no CLK_ENABLE_ON_INIT means external clock... - */ - - if (parent->flags & CLK_ENABLE_ON_INIT) - value = __raw_readl(clk->enable_reg) & ~(1 << 7); - else - value = __raw_readl(clk->enable_reg) | (1 << 7); - - ret = clk_reparent(clk, parent); - if (ret < 0) - return ret; - - __raw_writel(value, clk->enable_reg); - - /* Rebiuld the frequency table */ - clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, - table, &clk->arch_flags); - - return 0; -} - -static int sh_clk_div4_set_rate(struct clk *clk, unsigned long rate, int algo_id) -{ - struct clk_div4_table *d4t = clk->priv; - unsigned long value; - int idx = clk_rate_table_find(clk, clk->freq_table, rate); - if (idx < 0) - return idx; - - value = __raw_readl(clk->enable_reg); - value &= ~(0xf << clk->enable_bit); - value |= (idx << clk->enable_bit); - __raw_writel(value, clk->enable_reg); - - if (d4t->kick) - d4t->kick(clk); - - return 0; -} - -static int sh_clk_div4_enable(struct clk *clk) -{ - __raw_writel(__raw_readl(clk->enable_reg) & ~(1 << 8), clk->enable_reg); - return 0; -} - -static void sh_clk_div4_disable(struct clk *clk) -{ - __raw_writel(__raw_readl(clk->enable_reg) | (1 << 8), clk->enable_reg); -} - -static struct clk_ops sh_clk_div4_clk_ops = { - .recalc = sh_clk_div4_recalc, - .set_rate = sh_clk_div4_set_rate, - .round_rate = sh_clk_div_round_rate, -}; - -static struct clk_ops sh_clk_div4_enable_clk_ops = { - .recalc = sh_clk_div4_recalc, - .set_rate = sh_clk_div4_set_rate, - .round_rate = sh_clk_div_round_rate, - .enable = sh_clk_div4_enable, - .disable = sh_clk_div4_disable, -}; - -static struct clk_ops sh_clk_div4_reparent_clk_ops = { - .recalc = sh_clk_div4_recalc, - .set_rate = sh_clk_div4_set_rate, - .round_rate = sh_clk_div_round_rate, - .enable = sh_clk_div4_enable, - .disable = sh_clk_div4_disable, - .set_parent = sh_clk_div4_set_parent, -}; - -static int __init sh_clk_div4_register_ops(struct clk *clks, int nr, - struct clk_div4_table *table, struct clk_ops *ops) -{ - struct clk *clkp; - void *freq_table; - int nr_divs = table->div_mult_table->nr_divisors; - int freq_table_size = sizeof(struct cpufreq_frequency_table); - int ret = 0; - int k; - - freq_table_size *= (nr_divs + 1); - freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); - if (!freq_table) { - pr_err("sh_clk_div4_register: unable to alloc memory\n"); - return -ENOMEM; - } - - for (k = 0; !ret && (k < nr); k++) { - clkp = clks + k; - - clkp->ops = ops; - clkp->priv = table; - - clkp->freq_table = freq_table + (k * freq_table_size); - clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; - - ret = clk_register(clkp); - } - - return ret; -} - -int __init sh_clk_div4_register(struct clk *clks, int nr, - struct clk_div4_table *table) -{ - return sh_clk_div4_register_ops(clks, nr, table, &sh_clk_div4_clk_ops); -} - -int __init sh_clk_div4_enable_register(struct clk *clks, int nr, - struct clk_div4_table *table) -{ - return sh_clk_div4_register_ops(clks, nr, table, - &sh_clk_div4_enable_clk_ops); -} - -int __init sh_clk_div4_reparent_register(struct clk *clks, int nr, - struct clk_div4_table *table) -{ - return sh_clk_div4_register_ops(clks, nr, table, - &sh_clk_div4_reparent_clk_ops); -} diff --git a/drivers/sh/clk.c b/drivers/sh/clk.c deleted file mode 100644 index dae64ee9ecc5..000000000000 --- a/drivers/sh/clk.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * drivers/sh/clk.c - SuperH clock framework - * - * Copyright (C) 2005 - 2010 Paul Mundt - * - * This clock framework is derived from the OMAP version by: - * - * Copyright (C) 2004 - 2008 Nokia Corporation - * Written by Tuukka Tikkanen - * - * Modified for omap shared clock framework by Tony Lindgren - * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - */ -#define pr_fmt(fmt) "clock: " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static LIST_HEAD(clock_list); -static DEFINE_SPINLOCK(clock_lock); -static DEFINE_MUTEX(clock_list_sem); - -void clk_rate_table_build(struct clk *clk, - struct cpufreq_frequency_table *freq_table, - int nr_freqs, - struct clk_div_mult_table *src_table, - unsigned long *bitmap) -{ - unsigned long mult, div; - unsigned long freq; - int i; - - clk->nr_freqs = nr_freqs; - - for (i = 0; i < nr_freqs; i++) { - div = 1; - mult = 1; - - if (src_table->divisors && i < src_table->nr_divisors) - div = src_table->divisors[i]; - - if (src_table->multipliers && i < src_table->nr_multipliers) - mult = src_table->multipliers[i]; - - if (!div || !mult || (bitmap && !test_bit(i, bitmap))) - freq = CPUFREQ_ENTRY_INVALID; - else - freq = clk->parent->rate * mult / div; - - freq_table[i].index = i; - freq_table[i].frequency = freq; - } - - /* Termination entry */ - freq_table[i].index = i; - freq_table[i].frequency = CPUFREQ_TABLE_END; -} - -struct clk_rate_round_data; - -struct clk_rate_round_data { - unsigned long rate; - unsigned int min, max; - long (*func)(unsigned int, struct clk_rate_round_data *); - void *arg; -}; - -#define for_each_frequency(pos, r, freq) \ - for (pos = r->min, freq = r->func(pos, r); \ - pos <= r->max; pos++, freq = r->func(pos, r)) \ - if (unlikely(freq == 0)) \ - ; \ - else - -static long clk_rate_round_helper(struct clk_rate_round_data *rounder) -{ - unsigned long rate_error, rate_error_prev = ~0UL; - unsigned long rate_best_fit = rounder->rate; - unsigned long highest, lowest, freq; - int i; - - highest = 0; - lowest = ~0UL; - - for_each_frequency(i, rounder, freq) { - if (freq > highest) - highest = freq; - if (freq < lowest) - lowest = freq; - - rate_error = abs(freq - rounder->rate); - if (rate_error < rate_error_prev) { - rate_best_fit = freq; - rate_error_prev = rate_error; - } - - if (rate_error == 0) - break; - } - - if (rounder->rate >= highest) - rate_best_fit = highest; - if (rounder->rate <= lowest) - rate_best_fit = lowest; - - return rate_best_fit; -} - -static long clk_rate_table_iter(unsigned int pos, - struct clk_rate_round_data *rounder) -{ - struct cpufreq_frequency_table *freq_table = rounder->arg; - unsigned long freq = freq_table[pos].frequency; - - if (freq == CPUFREQ_ENTRY_INVALID) - freq = 0; - - return freq; -} - -long clk_rate_table_round(struct clk *clk, - struct cpufreq_frequency_table *freq_table, - unsigned long rate) -{ - struct clk_rate_round_data table_round = { - .min = 0, - .max = clk->nr_freqs - 1, - .func = clk_rate_table_iter, - .arg = freq_table, - .rate = rate, - }; - - if (clk->nr_freqs < 1) - return 0; - - return clk_rate_round_helper(&table_round); -} - -static long clk_rate_div_range_iter(unsigned int pos, - struct clk_rate_round_data *rounder) -{ - return clk_get_rate(rounder->arg) / pos; -} - -long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, - unsigned int div_max, unsigned long rate) -{ - struct clk_rate_round_data div_range_round = { - .min = div_min, - .max = div_max, - .func = clk_rate_div_range_iter, - .arg = clk_get_parent(clk), - .rate = rate, - }; - - return clk_rate_round_helper(&div_range_round); -} - -int clk_rate_table_find(struct clk *clk, - struct cpufreq_frequency_table *freq_table, - unsigned long rate) -{ - int i; - - for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { - unsigned long freq = freq_table[i].frequency; - - if (freq == CPUFREQ_ENTRY_INVALID) - continue; - - if (freq == rate) - return i; - } - - return -ENOENT; -} - -/* Used for clocks that always have same value as the parent clock */ -unsigned long followparent_recalc(struct clk *clk) -{ - return clk->parent ? clk->parent->rate : 0; -} - -int clk_reparent(struct clk *child, struct clk *parent) -{ - list_del_init(&child->sibling); - if (parent) - list_add(&child->sibling, &parent->children); - child->parent = parent; - - /* now do the debugfs renaming to reattach the child - to the proper parent */ - - return 0; -} - -/* Propagate rate to children */ -void propagate_rate(struct clk *tclk) -{ - struct clk *clkp; - - list_for_each_entry(clkp, &tclk->children, sibling) { - if (clkp->ops && clkp->ops->recalc) - clkp->rate = clkp->ops->recalc(clkp); - - propagate_rate(clkp); - } -} - -static void __clk_disable(struct clk *clk) -{ - if (WARN(!clk->usecount, "Trying to disable clock %p with 0 usecount\n", - clk)) - return; - - if (!(--clk->usecount)) { - if (likely(clk->ops && clk->ops->disable)) - clk->ops->disable(clk); - if (likely(clk->parent)) - __clk_disable(clk->parent); - } -} - -void clk_disable(struct clk *clk) -{ - unsigned long flags; - - if (!clk) - return; - - spin_lock_irqsave(&clock_lock, flags); - __clk_disable(clk); - spin_unlock_irqrestore(&clock_lock, flags); -} -EXPORT_SYMBOL_GPL(clk_disable); - -static int __clk_enable(struct clk *clk) -{ - int ret = 0; - - if (clk->usecount++ == 0) { - if (clk->parent) { - ret = __clk_enable(clk->parent); - if (unlikely(ret)) - goto err; - } - - if (clk->ops && clk->ops->enable) { - ret = clk->ops->enable(clk); - if (ret) { - if (clk->parent) - __clk_disable(clk->parent); - goto err; - } - } - } - - return ret; -err: - clk->usecount--; - return ret; -} - -int clk_enable(struct clk *clk) -{ - unsigned long flags; - int ret; - - if (!clk) - return -EINVAL; - - spin_lock_irqsave(&clock_lock, flags); - ret = __clk_enable(clk); - spin_unlock_irqrestore(&clock_lock, flags); - - return ret; -} -EXPORT_SYMBOL_GPL(clk_enable); - -static LIST_HEAD(root_clks); - -/** - * recalculate_root_clocks - recalculate and propagate all root clocks - * - * Recalculates all root clocks (clocks with no parent), which if the - * clock's .recalc is set correctly, should also propagate their rates. - * Called at init. - */ -void recalculate_root_clocks(void) -{ - struct clk *clkp; - - list_for_each_entry(clkp, &root_clks, sibling) { - if (clkp->ops && clkp->ops->recalc) - clkp->rate = clkp->ops->recalc(clkp); - propagate_rate(clkp); - } -} - -static struct clk_mapping dummy_mapping; - -static struct clk *lookup_root_clock(struct clk *clk) -{ - while (clk->parent) - clk = clk->parent; - - return clk; -} - -static int clk_establish_mapping(struct clk *clk) -{ - struct clk_mapping *mapping = clk->mapping; - - /* - * Propagate mappings. - */ - if (!mapping) { - struct clk *clkp; - - /* - * dummy mapping for root clocks with no specified ranges - */ - if (!clk->parent) { - clk->mapping = &dummy_mapping; - return 0; - } - - /* - * If we're on a child clock and it provides no mapping of its - * own, inherit the mapping from its root clock. - */ - clkp = lookup_root_clock(clk); - mapping = clkp->mapping; - BUG_ON(!mapping); - } - - /* - * Establish initial mapping. - */ - if (!mapping->base && mapping->phys) { - kref_init(&mapping->ref); - - mapping->base = ioremap_nocache(mapping->phys, mapping->len); - if (unlikely(!mapping->base)) - return -ENXIO; - } else if (mapping->base) { - /* - * Bump the refcount for an existing mapping - */ - kref_get(&mapping->ref); - } - - clk->mapping = mapping; - return 0; -} - -static void clk_destroy_mapping(struct kref *kref) -{ - struct clk_mapping *mapping; - - mapping = container_of(kref, struct clk_mapping, ref); - - iounmap(mapping->base); -} - -static void clk_teardown_mapping(struct clk *clk) -{ - struct clk_mapping *mapping = clk->mapping; - - /* Nothing to do */ - if (mapping == &dummy_mapping) - return; - - kref_put(&mapping->ref, clk_destroy_mapping); - clk->mapping = NULL; -} - -int clk_register(struct clk *clk) -{ - int ret; - - if (clk == NULL || IS_ERR(clk)) - return -EINVAL; - - /* - * trap out already registered clocks - */ - if (clk->node.next || clk->node.prev) - return 0; - - mutex_lock(&clock_list_sem); - - INIT_LIST_HEAD(&clk->children); - clk->usecount = 0; - - ret = clk_establish_mapping(clk); - if (unlikely(ret)) - goto out_unlock; - - if (clk->parent) - list_add(&clk->sibling, &clk->parent->children); - else - list_add(&clk->sibling, &root_clks); - - list_add(&clk->node, &clock_list); - if (clk->ops && clk->ops->init) - clk->ops->init(clk); - -out_unlock: - mutex_unlock(&clock_list_sem); - - return ret; -} -EXPORT_SYMBOL_GPL(clk_register); - -void clk_unregister(struct clk *clk) -{ - mutex_lock(&clock_list_sem); - list_del(&clk->sibling); - list_del(&clk->node); - clk_teardown_mapping(clk); - mutex_unlock(&clock_list_sem); -} -EXPORT_SYMBOL_GPL(clk_unregister); - -void clk_enable_init_clocks(void) -{ - struct clk *clkp; - - list_for_each_entry(clkp, &clock_list, node) - if (clkp->flags & CLK_ENABLE_ON_INIT) - clk_enable(clkp); -} - -unsigned long clk_get_rate(struct clk *clk) -{ - return clk->rate; -} -EXPORT_SYMBOL_GPL(clk_get_rate); - -int clk_set_rate(struct clk *clk, unsigned long rate) -{ - return clk_set_rate_ex(clk, rate, 0); -} -EXPORT_SYMBOL_GPL(clk_set_rate); - -int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id) -{ - int ret = -EOPNOTSUPP; - unsigned long flags; - - spin_lock_irqsave(&clock_lock, flags); - - if (likely(clk->ops && clk->ops->set_rate)) { - ret = clk->ops->set_rate(clk, rate, algo_id); - if (ret != 0) - goto out_unlock; - } else { - clk->rate = rate; - ret = 0; - } - - if (clk->ops && clk->ops->recalc) - clk->rate = clk->ops->recalc(clk); - - propagate_rate(clk); - -out_unlock: - spin_unlock_irqrestore(&clock_lock, flags); - - return ret; -} -EXPORT_SYMBOL_GPL(clk_set_rate_ex); - -int clk_set_parent(struct clk *clk, struct clk *parent) -{ - unsigned long flags; - int ret = -EINVAL; - - if (!parent || !clk) - return ret; - if (clk->parent == parent) - return 0; - - spin_lock_irqsave(&clock_lock, flags); - if (clk->usecount == 0) { - if (clk->ops->set_parent) - ret = clk->ops->set_parent(clk, parent); - else - ret = clk_reparent(clk, parent); - - if (ret == 0) { - if (clk->ops->recalc) - clk->rate = clk->ops->recalc(clk); - pr_debug("set parent of %p to %p (new rate %ld)\n", - clk, clk->parent, clk->rate); - propagate_rate(clk); - } - } else - ret = -EBUSY; - spin_unlock_irqrestore(&clock_lock, flags); - - return ret; -} -EXPORT_SYMBOL_GPL(clk_set_parent); - -struct clk *clk_get_parent(struct clk *clk) -{ - return clk->parent; -} -EXPORT_SYMBOL_GPL(clk_get_parent); - -long clk_round_rate(struct clk *clk, unsigned long rate) -{ - if (likely(clk->ops && clk->ops->round_rate)) { - unsigned long flags, rounded; - - spin_lock_irqsave(&clock_lock, flags); - rounded = clk->ops->round_rate(clk, rate); - spin_unlock_irqrestore(&clock_lock, flags); - - return rounded; - } - - return clk_get_rate(clk); -} -EXPORT_SYMBOL_GPL(clk_round_rate); - -#ifdef CONFIG_PM -static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state) -{ - static pm_message_t prev_state; - struct clk *clkp; - - switch (state.event) { - case PM_EVENT_ON: - /* Resumeing from hibernation */ - if (prev_state.event != PM_EVENT_FREEZE) - break; - - list_for_each_entry(clkp, &clock_list, node) { - if (likely(clkp->ops)) { - unsigned long rate = clkp->rate; - - if (likely(clkp->ops->set_parent)) - clkp->ops->set_parent(clkp, - clkp->parent); - if (likely(clkp->ops->set_rate)) - clkp->ops->set_rate(clkp, - rate, NO_CHANGE); - else if (likely(clkp->ops->recalc)) - clkp->rate = clkp->ops->recalc(clkp); - } - } - break; - case PM_EVENT_FREEZE: - break; - case PM_EVENT_SUSPEND: - break; - } - - prev_state = state; - return 0; -} - -static int clks_sysdev_resume(struct sys_device *dev) -{ - return clks_sysdev_suspend(dev, PMSG_ON); -} - -static struct sysdev_class clks_sysdev_class = { - .name = "clks", -}; - -static struct sysdev_driver clks_sysdev_driver = { - .suspend = clks_sysdev_suspend, - .resume = clks_sysdev_resume, -}; - -static struct sys_device clks_sysdev_dev = { - .cls = &clks_sysdev_class, -}; - -static int __init clk_sysdev_init(void) -{ - sysdev_class_register(&clks_sysdev_class); - sysdev_driver_register(&clks_sysdev_class, &clks_sysdev_driver); - sysdev_register(&clks_sysdev_dev); - - return 0; -} -subsys_initcall(clk_sysdev_init); -#endif - -/* - * debugfs support to trace clock tree hierarchy and attributes - */ -static struct dentry *clk_debugfs_root; - -static int clk_debugfs_register_one(struct clk *c) -{ - int err; - struct dentry *d, *child, *child_tmp; - struct clk *pa = c->parent; - char s[255]; - char *p = s; - - p += sprintf(p, "%p", c); - d = debugfs_create_dir(s, pa ? pa->dentry : clk_debugfs_root); - if (!d) - return -ENOMEM; - c->dentry = d; - - d = debugfs_create_u8("usecount", S_IRUGO, c->dentry, (u8 *)&c->usecount); - if (!d) { - err = -ENOMEM; - goto err_out; - } - d = debugfs_create_u32("rate", S_IRUGO, c->dentry, (u32 *)&c->rate); - if (!d) { - err = -ENOMEM; - goto err_out; - } - d = debugfs_create_x32("flags", S_IRUGO, c->dentry, (u32 *)&c->flags); - if (!d) { - err = -ENOMEM; - goto err_out; - } - return 0; - -err_out: - d = c->dentry; - list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) - debugfs_remove(child); - debugfs_remove(c->dentry); - return err; -} - -static int clk_debugfs_register(struct clk *c) -{ - int err; - struct clk *pa = c->parent; - - if (pa && !pa->dentry) { - err = clk_debugfs_register(pa); - if (err) - return err; - } - - if (!c->dentry) { - err = clk_debugfs_register_one(c); - if (err) - return err; - } - return 0; -} - -static int __init clk_debugfs_init(void) -{ - struct clk *c; - struct dentry *d; - int err; - - d = debugfs_create_dir("clock", NULL); - if (!d) - return -ENOMEM; - clk_debugfs_root = d; - - list_for_each_entry(c, &clock_list, node) { - err = clk_debugfs_register(c); - if (err) - goto err_out; - } - return 0; -err_out: - debugfs_remove_recursive(clk_debugfs_root); - return err; -} -late_initcall(clk_debugfs_init); diff --git a/drivers/sh/clk/Makefile b/drivers/sh/clk/Makefile new file mode 100644 index 000000000000..5d15ebfaa074 --- /dev/null +++ b/drivers/sh/clk/Makefile @@ -0,0 +1,3 @@ +obj-y := core.o + +obj-$(CONFIG_SH_CLK_CPG) += cpg.o diff --git a/drivers/sh/clk/core.c b/drivers/sh/clk/core.c new file mode 100644 index 000000000000..fd0d1b98901c --- /dev/null +++ b/drivers/sh/clk/core.c @@ -0,0 +1,694 @@ +/* + * SuperH clock framework + * + * Copyright (C) 2005 - 2010 Paul Mundt + * + * This clock framework is derived from the OMAP version by: + * + * Copyright (C) 2004 - 2008 Nokia Corporation + * Written by Tuukka Tikkanen + * + * Modified for omap shared clock framework by Tony Lindgren + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "clock: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(clock_list); +static DEFINE_SPINLOCK(clock_lock); +static DEFINE_MUTEX(clock_list_sem); + +void clk_rate_table_build(struct clk *clk, + struct cpufreq_frequency_table *freq_table, + int nr_freqs, + struct clk_div_mult_table *src_table, + unsigned long *bitmap) +{ + unsigned long mult, div; + unsigned long freq; + int i; + + clk->nr_freqs = nr_freqs; + + for (i = 0; i < nr_freqs; i++) { + div = 1; + mult = 1; + + if (src_table->divisors && i < src_table->nr_divisors) + div = src_table->divisors[i]; + + if (src_table->multipliers && i < src_table->nr_multipliers) + mult = src_table->multipliers[i]; + + if (!div || !mult || (bitmap && !test_bit(i, bitmap))) + freq = CPUFREQ_ENTRY_INVALID; + else + freq = clk->parent->rate * mult / div; + + freq_table[i].index = i; + freq_table[i].frequency = freq; + } + + /* Termination entry */ + freq_table[i].index = i; + freq_table[i].frequency = CPUFREQ_TABLE_END; +} + +struct clk_rate_round_data; + +struct clk_rate_round_data { + unsigned long rate; + unsigned int min, max; + long (*func)(unsigned int, struct clk_rate_round_data *); + void *arg; +}; + +#define for_each_frequency(pos, r, freq) \ + for (pos = r->min, freq = r->func(pos, r); \ + pos <= r->max; pos++, freq = r->func(pos, r)) \ + if (unlikely(freq == 0)) \ + ; \ + else + +static long clk_rate_round_helper(struct clk_rate_round_data *rounder) +{ + unsigned long rate_error, rate_error_prev = ~0UL; + unsigned long rate_best_fit = rounder->rate; + unsigned long highest, lowest, freq; + int i; + + highest = 0; + lowest = ~0UL; + + for_each_frequency(i, rounder, freq) { + if (freq > highest) + highest = freq; + if (freq < lowest) + lowest = freq; + + rate_error = abs(freq - rounder->rate); + if (rate_error < rate_error_prev) { + rate_best_fit = freq; + rate_error_prev = rate_error; + } + + if (rate_error == 0) + break; + } + + if (rounder->rate >= highest) + rate_best_fit = highest; + if (rounder->rate <= lowest) + rate_best_fit = lowest; + + return rate_best_fit; +} + +static long clk_rate_table_iter(unsigned int pos, + struct clk_rate_round_data *rounder) +{ + struct cpufreq_frequency_table *freq_table = rounder->arg; + unsigned long freq = freq_table[pos].frequency; + + if (freq == CPUFREQ_ENTRY_INVALID) + freq = 0; + + return freq; +} + +long clk_rate_table_round(struct clk *clk, + struct cpufreq_frequency_table *freq_table, + unsigned long rate) +{ + struct clk_rate_round_data table_round = { + .min = 0, + .max = clk->nr_freqs - 1, + .func = clk_rate_table_iter, + .arg = freq_table, + .rate = rate, + }; + + if (clk->nr_freqs < 1) + return 0; + + return clk_rate_round_helper(&table_round); +} + +static long clk_rate_div_range_iter(unsigned int pos, + struct clk_rate_round_data *rounder) +{ + return clk_get_rate(rounder->arg) / pos; +} + +long clk_rate_div_range_round(struct clk *clk, unsigned int div_min, + unsigned int div_max, unsigned long rate) +{ + struct clk_rate_round_data div_range_round = { + .min = div_min, + .max = div_max, + .func = clk_rate_div_range_iter, + .arg = clk_get_parent(clk), + .rate = rate, + }; + + return clk_rate_round_helper(&div_range_round); +} + +int clk_rate_table_find(struct clk *clk, + struct cpufreq_frequency_table *freq_table, + unsigned long rate) +{ + int i; + + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) { + unsigned long freq = freq_table[i].frequency; + + if (freq == CPUFREQ_ENTRY_INVALID) + continue; + + if (freq == rate) + return i; + } + + return -ENOENT; +} + +/* Used for clocks that always have same value as the parent clock */ +unsigned long followparent_recalc(struct clk *clk) +{ + return clk->parent ? clk->parent->rate : 0; +} + +int clk_reparent(struct clk *child, struct clk *parent) +{ + list_del_init(&child->sibling); + if (parent) + list_add(&child->sibling, &parent->children); + child->parent = parent; + + /* now do the debugfs renaming to reattach the child + to the proper parent */ + + return 0; +} + +/* Propagate rate to children */ +void propagate_rate(struct clk *tclk) +{ + struct clk *clkp; + + list_for_each_entry(clkp, &tclk->children, sibling) { + if (clkp->ops && clkp->ops->recalc) + clkp->rate = clkp->ops->recalc(clkp); + + propagate_rate(clkp); + } +} + +static void __clk_disable(struct clk *clk) +{ + if (WARN(!clk->usecount, "Trying to disable clock %p with 0 usecount\n", + clk)) + return; + + if (!(--clk->usecount)) { + if (likely(clk->ops && clk->ops->disable)) + clk->ops->disable(clk); + if (likely(clk->parent)) + __clk_disable(clk->parent); + } +} + +void clk_disable(struct clk *clk) +{ + unsigned long flags; + + if (!clk) + return; + + spin_lock_irqsave(&clock_lock, flags); + __clk_disable(clk); + spin_unlock_irqrestore(&clock_lock, flags); +} +EXPORT_SYMBOL_GPL(clk_disable); + +static int __clk_enable(struct clk *clk) +{ + int ret = 0; + + if (clk->usecount++ == 0) { + if (clk->parent) { + ret = __clk_enable(clk->parent); + if (unlikely(ret)) + goto err; + } + + if (clk->ops && clk->ops->enable) { + ret = clk->ops->enable(clk); + if (ret) { + if (clk->parent) + __clk_disable(clk->parent); + goto err; + } + } + } + + return ret; +err: + clk->usecount--; + return ret; +} + +int clk_enable(struct clk *clk) +{ + unsigned long flags; + int ret; + + if (!clk) + return -EINVAL; + + spin_lock_irqsave(&clock_lock, flags); + ret = __clk_enable(clk); + spin_unlock_irqrestore(&clock_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_enable); + +static LIST_HEAD(root_clks); + +/** + * recalculate_root_clocks - recalculate and propagate all root clocks + * + * Recalculates all root clocks (clocks with no parent), which if the + * clock's .recalc is set correctly, should also propagate their rates. + * Called at init. + */ +void recalculate_root_clocks(void) +{ + struct clk *clkp; + + list_for_each_entry(clkp, &root_clks, sibling) { + if (clkp->ops && clkp->ops->recalc) + clkp->rate = clkp->ops->recalc(clkp); + propagate_rate(clkp); + } +} + +static struct clk_mapping dummy_mapping; + +static struct clk *lookup_root_clock(struct clk *clk) +{ + while (clk->parent) + clk = clk->parent; + + return clk; +} + +static int clk_establish_mapping(struct clk *clk) +{ + struct clk_mapping *mapping = clk->mapping; + + /* + * Propagate mappings. + */ + if (!mapping) { + struct clk *clkp; + + /* + * dummy mapping for root clocks with no specified ranges + */ + if (!clk->parent) { + clk->mapping = &dummy_mapping; + return 0; + } + + /* + * If we're on a child clock and it provides no mapping of its + * own, inherit the mapping from its root clock. + */ + clkp = lookup_root_clock(clk); + mapping = clkp->mapping; + BUG_ON(!mapping); + } + + /* + * Establish initial mapping. + */ + if (!mapping->base && mapping->phys) { + kref_init(&mapping->ref); + + mapping->base = ioremap_nocache(mapping->phys, mapping->len); + if (unlikely(!mapping->base)) + return -ENXIO; + } else if (mapping->base) { + /* + * Bump the refcount for an existing mapping + */ + kref_get(&mapping->ref); + } + + clk->mapping = mapping; + return 0; +} + +static void clk_destroy_mapping(struct kref *kref) +{ + struct clk_mapping *mapping; + + mapping = container_of(kref, struct clk_mapping, ref); + + iounmap(mapping->base); +} + +static void clk_teardown_mapping(struct clk *clk) +{ + struct clk_mapping *mapping = clk->mapping; + + /* Nothing to do */ + if (mapping == &dummy_mapping) + return; + + kref_put(&mapping->ref, clk_destroy_mapping); + clk->mapping = NULL; +} + +int clk_register(struct clk *clk) +{ + int ret; + + if (clk == NULL || IS_ERR(clk)) + return -EINVAL; + + /* + * trap out already registered clocks + */ + if (clk->node.next || clk->node.prev) + return 0; + + mutex_lock(&clock_list_sem); + + INIT_LIST_HEAD(&clk->children); + clk->usecount = 0; + + ret = clk_establish_mapping(clk); + if (unlikely(ret)) + goto out_unlock; + + if (clk->parent) + list_add(&clk->sibling, &clk->parent->children); + else + list_add(&clk->sibling, &root_clks); + + list_add(&clk->node, &clock_list); + if (clk->ops && clk->ops->init) + clk->ops->init(clk); + +out_unlock: + mutex_unlock(&clock_list_sem); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_register); + +void clk_unregister(struct clk *clk) +{ + mutex_lock(&clock_list_sem); + list_del(&clk->sibling); + list_del(&clk->node); + clk_teardown_mapping(clk); + mutex_unlock(&clock_list_sem); +} +EXPORT_SYMBOL_GPL(clk_unregister); + +void clk_enable_init_clocks(void) +{ + struct clk *clkp; + + list_for_each_entry(clkp, &clock_list, node) + if (clkp->flags & CLK_ENABLE_ON_INIT) + clk_enable(clkp); +} + +unsigned long clk_get_rate(struct clk *clk) +{ + return clk->rate; +} +EXPORT_SYMBOL_GPL(clk_get_rate); + +int clk_set_rate(struct clk *clk, unsigned long rate) +{ + return clk_set_rate_ex(clk, rate, 0); +} +EXPORT_SYMBOL_GPL(clk_set_rate); + +int clk_set_rate_ex(struct clk *clk, unsigned long rate, int algo_id) +{ + int ret = -EOPNOTSUPP; + unsigned long flags; + + spin_lock_irqsave(&clock_lock, flags); + + if (likely(clk->ops && clk->ops->set_rate)) { + ret = clk->ops->set_rate(clk, rate, algo_id); + if (ret != 0) + goto out_unlock; + } else { + clk->rate = rate; + ret = 0; + } + + if (clk->ops && clk->ops->recalc) + clk->rate = clk->ops->recalc(clk); + + propagate_rate(clk); + +out_unlock: + spin_unlock_irqrestore(&clock_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_rate_ex); + +int clk_set_parent(struct clk *clk, struct clk *parent) +{ + unsigned long flags; + int ret = -EINVAL; + + if (!parent || !clk) + return ret; + if (clk->parent == parent) + return 0; + + spin_lock_irqsave(&clock_lock, flags); + if (clk->usecount == 0) { + if (clk->ops->set_parent) + ret = clk->ops->set_parent(clk, parent); + else + ret = clk_reparent(clk, parent); + + if (ret == 0) { + if (clk->ops->recalc) + clk->rate = clk->ops->recalc(clk); + pr_debug("set parent of %p to %p (new rate %ld)\n", + clk, clk->parent, clk->rate); + propagate_rate(clk); + } + } else + ret = -EBUSY; + spin_unlock_irqrestore(&clock_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(clk_set_parent); + +struct clk *clk_get_parent(struct clk *clk) +{ + return clk->parent; +} +EXPORT_SYMBOL_GPL(clk_get_parent); + +long clk_round_rate(struct clk *clk, unsigned long rate) +{ + if (likely(clk->ops && clk->ops->round_rate)) { + unsigned long flags, rounded; + + spin_lock_irqsave(&clock_lock, flags); + rounded = clk->ops->round_rate(clk, rate); + spin_unlock_irqrestore(&clock_lock, flags); + + return rounded; + } + + return clk_get_rate(clk); +} +EXPORT_SYMBOL_GPL(clk_round_rate); + +#ifdef CONFIG_PM +static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state) +{ + static pm_message_t prev_state; + struct clk *clkp; + + switch (state.event) { + case PM_EVENT_ON: + /* Resumeing from hibernation */ + if (prev_state.event != PM_EVENT_FREEZE) + break; + + list_for_each_entry(clkp, &clock_list, node) { + if (likely(clkp->ops)) { + unsigned long rate = clkp->rate; + + if (likely(clkp->ops->set_parent)) + clkp->ops->set_parent(clkp, + clkp->parent); + if (likely(clkp->ops->set_rate)) + clkp->ops->set_rate(clkp, + rate, NO_CHANGE); + else if (likely(clkp->ops->recalc)) + clkp->rate = clkp->ops->recalc(clkp); + } + } + break; + case PM_EVENT_FREEZE: + break; + case PM_EVENT_SUSPEND: + break; + } + + prev_state = state; + return 0; +} + +static int clks_sysdev_resume(struct sys_device *dev) +{ + return clks_sysdev_suspend(dev, PMSG_ON); +} + +static struct sysdev_class clks_sysdev_class = { + .name = "clks", +}; + +static struct sysdev_driver clks_sysdev_driver = { + .suspend = clks_sysdev_suspend, + .resume = clks_sysdev_resume, +}; + +static struct sys_device clks_sysdev_dev = { + .cls = &clks_sysdev_class, +}; + +static int __init clk_sysdev_init(void) +{ + sysdev_class_register(&clks_sysdev_class); + sysdev_driver_register(&clks_sysdev_class, &clks_sysdev_driver); + sysdev_register(&clks_sysdev_dev); + + return 0; +} +subsys_initcall(clk_sysdev_init); +#endif + +/* + * debugfs support to trace clock tree hierarchy and attributes + */ +static struct dentry *clk_debugfs_root; + +static int clk_debugfs_register_one(struct clk *c) +{ + int err; + struct dentry *d, *child, *child_tmp; + struct clk *pa = c->parent; + char s[255]; + char *p = s; + + p += sprintf(p, "%p", c); + d = debugfs_create_dir(s, pa ? pa->dentry : clk_debugfs_root); + if (!d) + return -ENOMEM; + c->dentry = d; + + d = debugfs_create_u8("usecount", S_IRUGO, c->dentry, (u8 *)&c->usecount); + if (!d) { + err = -ENOMEM; + goto err_out; + } + d = debugfs_create_u32("rate", S_IRUGO, c->dentry, (u32 *)&c->rate); + if (!d) { + err = -ENOMEM; + goto err_out; + } + d = debugfs_create_x32("flags", S_IRUGO, c->dentry, (u32 *)&c->flags); + if (!d) { + err = -ENOMEM; + goto err_out; + } + return 0; + +err_out: + d = c->dentry; + list_for_each_entry_safe(child, child_tmp, &d->d_subdirs, d_u.d_child) + debugfs_remove(child); + debugfs_remove(c->dentry); + return err; +} + +static int clk_debugfs_register(struct clk *c) +{ + int err; + struct clk *pa = c->parent; + + if (pa && !pa->dentry) { + err = clk_debugfs_register(pa); + if (err) + return err; + } + + if (!c->dentry) { + err = clk_debugfs_register_one(c); + if (err) + return err; + } + return 0; +} + +static int __init clk_debugfs_init(void) +{ + struct clk *c; + struct dentry *d; + int err; + + d = debugfs_create_dir("clock", NULL); + if (!d) + return -ENOMEM; + clk_debugfs_root = d; + + list_for_each_entry(c, &clock_list, node) { + err = clk_debugfs_register(c); + if (err) + goto err_out; + } + return 0; +err_out: + debugfs_remove_recursive(clk_debugfs_root); + return err; +} +late_initcall(clk_debugfs_init); diff --git a/drivers/sh/clk/cpg.c b/drivers/sh/clk/cpg.c new file mode 100644 index 000000000000..3aea5f0ceb09 --- /dev/null +++ b/drivers/sh/clk/cpg.c @@ -0,0 +1,359 @@ +/* + * Helper routines for SuperH Clock Pulse Generator blocks (CPG). + * + * Copyright (C) 2010 Magnus Damm + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include +#include +#include +#include +#include + +static int sh_clk_mstp32_enable(struct clk *clk) +{ + __raw_writel(__raw_readl(clk->enable_reg) & ~(1 << clk->enable_bit), + clk->enable_reg); + return 0; +} + +static void sh_clk_mstp32_disable(struct clk *clk) +{ + __raw_writel(__raw_readl(clk->enable_reg) | (1 << clk->enable_bit), + clk->enable_reg); +} + +static struct clk_ops sh_clk_mstp32_clk_ops = { + .enable = sh_clk_mstp32_enable, + .disable = sh_clk_mstp32_disable, + .recalc = followparent_recalc, +}; + +int __init sh_clk_mstp32_register(struct clk *clks, int nr) +{ + struct clk *clkp; + int ret = 0; + int k; + + for (k = 0; !ret && (k < nr); k++) { + clkp = clks + k; + clkp->ops = &sh_clk_mstp32_clk_ops; + ret |= clk_register(clkp); + } + + return ret; +} + +static long sh_clk_div_round_rate(struct clk *clk, unsigned long rate) +{ + return clk_rate_table_round(clk, clk->freq_table, rate); +} + +static int sh_clk_div6_divisors[64] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 +}; + +static struct clk_div_mult_table sh_clk_div6_table = { + .divisors = sh_clk_div6_divisors, + .nr_divisors = ARRAY_SIZE(sh_clk_div6_divisors), +}; + +static unsigned long sh_clk_div6_recalc(struct clk *clk) +{ + struct clk_div_mult_table *table = &sh_clk_div6_table; + unsigned int idx; + + clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, + table, NULL); + + idx = __raw_readl(clk->enable_reg) & 0x003f; + + return clk->freq_table[idx].frequency; +} + +static int sh_clk_div6_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk_div_mult_table *table = &sh_clk_div6_table; + u32 value; + int ret, i; + + if (!clk->parent_table || !clk->parent_num) + return -EINVAL; + + /* Search the parent */ + for (i = 0; i < clk->parent_num; i++) + if (clk->parent_table[i] == parent) + break; + + if (i == clk->parent_num) + return -ENODEV; + + ret = clk_reparent(clk, parent); + if (ret < 0) + return ret; + + value = __raw_readl(clk->enable_reg) & + ~(((1 << clk->src_width) - 1) << clk->src_shift); + + __raw_writel(value | (i << clk->src_shift), clk->enable_reg); + + /* Rebuild the frequency table */ + clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, + table, &clk->arch_flags); + + return 0; +} + +static int sh_clk_div6_set_rate(struct clk *clk, + unsigned long rate, int algo_id) +{ + unsigned long value; + int idx; + + idx = clk_rate_table_find(clk, clk->freq_table, rate); + if (idx < 0) + return idx; + + value = __raw_readl(clk->enable_reg); + value &= ~0x3f; + value |= idx; + __raw_writel(value, clk->enable_reg); + return 0; +} + +static int sh_clk_div6_enable(struct clk *clk) +{ + unsigned long value; + int ret; + + ret = sh_clk_div6_set_rate(clk, clk->rate, 0); + if (ret == 0) { + value = __raw_readl(clk->enable_reg); + value &= ~0x100; /* clear stop bit to enable clock */ + __raw_writel(value, clk->enable_reg); + } + return ret; +} + +static void sh_clk_div6_disable(struct clk *clk) +{ + unsigned long value; + + value = __raw_readl(clk->enable_reg); + value |= 0x100; /* stop clock */ + value |= 0x3f; /* VDIV bits must be non-zero, overwrite divider */ + __raw_writel(value, clk->enable_reg); +} + +static struct clk_ops sh_clk_div6_clk_ops = { + .recalc = sh_clk_div6_recalc, + .round_rate = sh_clk_div_round_rate, + .set_rate = sh_clk_div6_set_rate, + .enable = sh_clk_div6_enable, + .disable = sh_clk_div6_disable, +}; + +static struct clk_ops sh_clk_div6_reparent_clk_ops = { + .recalc = sh_clk_div6_recalc, + .round_rate = sh_clk_div_round_rate, + .set_rate = sh_clk_div6_set_rate, + .enable = sh_clk_div6_enable, + .disable = sh_clk_div6_disable, + .set_parent = sh_clk_div6_set_parent, +}; + +static int __init sh_clk_div6_register_ops(struct clk *clks, int nr, + struct clk_ops *ops) +{ + struct clk *clkp; + void *freq_table; + int nr_divs = sh_clk_div6_table.nr_divisors; + int freq_table_size = sizeof(struct cpufreq_frequency_table); + int ret = 0; + int k; + + freq_table_size *= (nr_divs + 1); + freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); + if (!freq_table) { + pr_err("sh_clk_div6_register: unable to alloc memory\n"); + return -ENOMEM; + } + + for (k = 0; !ret && (k < nr); k++) { + clkp = clks + k; + + clkp->ops = ops; + clkp->freq_table = freq_table + (k * freq_table_size); + clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; + + ret = clk_register(clkp); + } + + return ret; +} + +int __init sh_clk_div6_register(struct clk *clks, int nr) +{ + return sh_clk_div6_register_ops(clks, nr, &sh_clk_div6_clk_ops); +} + +int __init sh_clk_div6_reparent_register(struct clk *clks, int nr) +{ + return sh_clk_div6_register_ops(clks, nr, + &sh_clk_div6_reparent_clk_ops); +} + +static unsigned long sh_clk_div4_recalc(struct clk *clk) +{ + struct clk_div4_table *d4t = clk->priv; + struct clk_div_mult_table *table = d4t->div_mult_table; + unsigned int idx; + + clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, + table, &clk->arch_flags); + + idx = (__raw_readl(clk->enable_reg) >> clk->enable_bit) & 0x000f; + + return clk->freq_table[idx].frequency; +} + +static int sh_clk_div4_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk_div4_table *d4t = clk->priv; + struct clk_div_mult_table *table = d4t->div_mult_table; + u32 value; + int ret; + + /* we really need a better way to determine parent index, but for + * now assume internal parent comes with CLK_ENABLE_ON_INIT set, + * no CLK_ENABLE_ON_INIT means external clock... + */ + + if (parent->flags & CLK_ENABLE_ON_INIT) + value = __raw_readl(clk->enable_reg) & ~(1 << 7); + else + value = __raw_readl(clk->enable_reg) | (1 << 7); + + ret = clk_reparent(clk, parent); + if (ret < 0) + return ret; + + __raw_writel(value, clk->enable_reg); + + /* Rebiuld the frequency table */ + clk_rate_table_build(clk, clk->freq_table, table->nr_divisors, + table, &clk->arch_flags); + + return 0; +} + +static int sh_clk_div4_set_rate(struct clk *clk, unsigned long rate, int algo_id) +{ + struct clk_div4_table *d4t = clk->priv; + unsigned long value; + int idx = clk_rate_table_find(clk, clk->freq_table, rate); + if (idx < 0) + return idx; + + value = __raw_readl(clk->enable_reg); + value &= ~(0xf << clk->enable_bit); + value |= (idx << clk->enable_bit); + __raw_writel(value, clk->enable_reg); + + if (d4t->kick) + d4t->kick(clk); + + return 0; +} + +static int sh_clk_div4_enable(struct clk *clk) +{ + __raw_writel(__raw_readl(clk->enable_reg) & ~(1 << 8), clk->enable_reg); + return 0; +} + +static void sh_clk_div4_disable(struct clk *clk) +{ + __raw_writel(__raw_readl(clk->enable_reg) | (1 << 8), clk->enable_reg); +} + +static struct clk_ops sh_clk_div4_clk_ops = { + .recalc = sh_clk_div4_recalc, + .set_rate = sh_clk_div4_set_rate, + .round_rate = sh_clk_div_round_rate, +}; + +static struct clk_ops sh_clk_div4_enable_clk_ops = { + .recalc = sh_clk_div4_recalc, + .set_rate = sh_clk_div4_set_rate, + .round_rate = sh_clk_div_round_rate, + .enable = sh_clk_div4_enable, + .disable = sh_clk_div4_disable, +}; + +static struct clk_ops sh_clk_div4_reparent_clk_ops = { + .recalc = sh_clk_div4_recalc, + .set_rate = sh_clk_div4_set_rate, + .round_rate = sh_clk_div_round_rate, + .enable = sh_clk_div4_enable, + .disable = sh_clk_div4_disable, + .set_parent = sh_clk_div4_set_parent, +}; + +static int __init sh_clk_div4_register_ops(struct clk *clks, int nr, + struct clk_div4_table *table, struct clk_ops *ops) +{ + struct clk *clkp; + void *freq_table; + int nr_divs = table->div_mult_table->nr_divisors; + int freq_table_size = sizeof(struct cpufreq_frequency_table); + int ret = 0; + int k; + + freq_table_size *= (nr_divs + 1); + freq_table = kzalloc(freq_table_size * nr, GFP_KERNEL); + if (!freq_table) { + pr_err("sh_clk_div4_register: unable to alloc memory\n"); + return -ENOMEM; + } + + for (k = 0; !ret && (k < nr); k++) { + clkp = clks + k; + + clkp->ops = ops; + clkp->priv = table; + + clkp->freq_table = freq_table + (k * freq_table_size); + clkp->freq_table[nr_divs].frequency = CPUFREQ_TABLE_END; + + ret = clk_register(clkp); + } + + return ret; +} + +int __init sh_clk_div4_register(struct clk *clks, int nr, + struct clk_div4_table *table) +{ + return sh_clk_div4_register_ops(clks, nr, table, &sh_clk_div4_clk_ops); +} + +int __init sh_clk_div4_enable_register(struct clk *clks, int nr, + struct clk_div4_table *table) +{ + return sh_clk_div4_register_ops(clks, nr, table, + &sh_clk_div4_enable_clk_ops); +} + +int __init sh_clk_div4_reparent_register(struct clk *clks, int nr, + struct clk_div4_table *table) +{ + return sh_clk_div4_register_ops(clks, nr, table, + &sh_clk_div4_reparent_clk_ops); +} -- cgit v1.2.3 From 8df399018df120d28f89fda6f2515cc6e096e43d Mon Sep 17 00:00:00 2001 From: Nobuhiro Iwamatsu Date: Sat, 23 Oct 2010 07:07:00 +0900 Subject: sh: i2c-sh7760: Replase from ctrl_* to __raw_* ctrl_* is deprecated. We should to use __raw_*. Signed-off-by: Nobuhiro Iwamatsu Signed-off-by: Paul Mundt --- drivers/i2c/busses/i2c-sh7760.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/i2c/busses/i2c-sh7760.c b/drivers/i2c/busses/i2c-sh7760.c index 4f93da31d3ad..3cad8fecc3d3 100644 --- a/drivers/i2c/busses/i2c-sh7760.c +++ b/drivers/i2c/busses/i2c-sh7760.c @@ -101,12 +101,12 @@ struct cami2c { static inline void OUT32(struct cami2c *cam, int reg, unsigned long val) { - ctrl_outl(val, (unsigned long)cam->iobase + reg); + __raw_writel(val, (unsigned long)cam->iobase + reg); } static inline unsigned long IN32(struct cami2c *cam, int reg) { - return ctrl_inl((unsigned long)cam->iobase + reg); + return __raw_readl((unsigned long)cam->iobase + reg); } static irqreturn_t sh7760_i2c_irq(int irq, void *ptr) -- cgit v1.2.3