| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * arch/powerpc/platforms/embedded6xx/hlwd-pic.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Nintendo Wii "Hollywood" interrupt controller support. | 
					
						
							|  |  |  |  * Copyright (C) 2009 The GameCube Linux Team | 
					
						
							|  |  |  |  * Copyright (C) 2009 Albert Herranz | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  |  * as published by the Free Software Foundation; either version 2 | 
					
						
							|  |  |  |  * of the License, or (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #define DRV_MODULE_NAME "hlwd-pic"
 | 
					
						
							|  |  |  | #define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/irq.h>
 | 
					
						
							|  |  |  | #include <linux/of.h>
 | 
					
						
							| 
									
										
										
										
											2013-09-26 07:40:04 -05:00
										 |  |  | #include <linux/of_address.h>
 | 
					
						
							|  |  |  | #include <linux/of_irq.h>
 | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | #include <asm/io.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "hlwd-pic.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define HLWD_NR_IRQS	32
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Each interrupt has a corresponding bit in both | 
					
						
							|  |  |  |  * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Enabling/disabling an interrupt line involves asserting/clearing | 
					
						
							|  |  |  |  * the corresponding bit in IMR. ACK'ing a request simply involves | 
					
						
							|  |  |  |  * asserting the corresponding bit in ICR. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #define HW_BROADWAY_ICR		0x00
 | 
					
						
							|  |  |  | #define HW_BROADWAY_IMR		0x04
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * IRQ chip hooks. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | static void hlwd_pic_mask_and_ack(struct irq_data *d) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-05-04 15:02:15 +10:00
										 |  |  | 	int irq = irqd_to_hwirq(d); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	void __iomem *io_base = irq_data_get_irq_chip_data(d); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	u32 mask = 1 << irq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clrbits32(io_base + HW_BROADWAY_IMR, mask); | 
					
						
							|  |  |  | 	out_be32(io_base + HW_BROADWAY_ICR, mask); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | static void hlwd_pic_ack(struct irq_data *d) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-05-04 15:02:15 +10:00
										 |  |  | 	int irq = irqd_to_hwirq(d); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	void __iomem *io_base = irq_data_get_irq_chip_data(d); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	out_be32(io_base + HW_BROADWAY_ICR, 1 << irq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | static void hlwd_pic_mask(struct irq_data *d) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-05-04 15:02:15 +10:00
										 |  |  | 	int irq = irqd_to_hwirq(d); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	void __iomem *io_base = irq_data_get_irq_chip_data(d); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	clrbits32(io_base + HW_BROADWAY_IMR, 1 << irq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | static void hlwd_pic_unmask(struct irq_data *d) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-05-04 15:02:15 +10:00
										 |  |  | 	int irq = irqd_to_hwirq(d); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	void __iomem *io_base = irq_data_get_irq_chip_data(d); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	setbits32(io_base + HW_BROADWAY_IMR, 1 << irq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct irq_chip hlwd_pic = { | 
					
						
							|  |  |  | 	.name		= "hlwd-pic", | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	.irq_ack	= hlwd_pic_ack, | 
					
						
							|  |  |  | 	.irq_mask_ack	= hlwd_pic_mask_and_ack, | 
					
						
							|  |  |  | 	.irq_mask	= hlwd_pic_mask, | 
					
						
							|  |  |  | 	.irq_unmask	= hlwd_pic_unmask, | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * IRQ host hooks. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | static struct irq_domain *hlwd_irq_host; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | static int hlwd_pic_map(struct irq_domain *h, unsigned int virq, | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 			   irq_hw_number_t hwirq) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-03-25 16:45:20 +01:00
										 |  |  | 	irq_set_chip_data(virq, h->host_data); | 
					
						
							| 
									
										
										
										
											2011-03-25 15:43:57 +01:00
										 |  |  | 	irq_set_status_flags(virq, IRQ_LEVEL); | 
					
						
							| 
									
										
										
										
											2011-03-25 16:45:20 +01:00
										 |  |  | 	irq_set_chip_and_handler(virq, &hlwd_pic, handle_level_irq); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-01-26 12:24:34 -07:00
										 |  |  | static const struct irq_domain_ops hlwd_irq_domain_ops = { | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	.map = hlwd_pic_map, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | static unsigned int __hlwd_pic_get_irq(struct irq_domain *h) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	void __iomem *io_base = h->host_data; | 
					
						
							|  |  |  | 	int irq; | 
					
						
							|  |  |  | 	u32 irq_status; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	irq_status = in_be32(io_base + HW_BROADWAY_ICR) & | 
					
						
							|  |  |  | 		     in_be32(io_base + HW_BROADWAY_IMR); | 
					
						
							|  |  |  | 	if (irq_status == 0) | 
					
						
							|  |  |  | 		return NO_IRQ;	/* no more IRQs pending */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	irq = __ffs(irq_status); | 
					
						
							|  |  |  | 	return irq_linear_revmap(h, irq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void hlwd_pic_irq_cascade(unsigned int cascade_virq, | 
					
						
							|  |  |  | 				      struct irq_desc *desc) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-03-25 16:45:20 +01:00
										 |  |  | 	struct irq_chip *chip = irq_desc_get_chip(desc); | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	struct irq_domain *irq_domain = irq_get_handler_data(cascade_virq); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	unsigned int virq; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:04:42 +00:00
										 |  |  | 	raw_spin_lock(&desc->lock); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	chip->irq_mask(&desc->irq_data); /* IRQ_LEVEL */ | 
					
						
							| 
									
										
										
										
											2009-12-18 10:04:42 +00:00
										 |  |  | 	raw_spin_unlock(&desc->lock); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	virq = __hlwd_pic_get_irq(irq_domain); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	if (virq != NO_IRQ) | 
					
						
							|  |  |  | 		generic_handle_irq(virq); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		pr_err("spurious interrupt!\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-12-18 10:04:42 +00:00
										 |  |  | 	raw_spin_lock(&desc->lock); | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 	chip->irq_ack(&desc->irq_data); /* IRQ_LEVEL */ | 
					
						
							| 
									
										
										
										
											2011-03-25 15:43:57 +01:00
										 |  |  | 	if (!irqd_irq_disabled(&desc->irq_data) && chip->irq_unmask) | 
					
						
							| 
									
										
										
										
											2011-03-08 22:26:53 +00:00
										 |  |  | 		chip->irq_unmask(&desc->irq_data); | 
					
						
							| 
									
										
										
										
											2009-12-18 10:04:42 +00:00
										 |  |  | 	raw_spin_unlock(&desc->lock); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Platform hooks. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __hlwd_quiesce(void __iomem *io_base) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* mask and ack all IRQs */ | 
					
						
							|  |  |  | 	out_be32(io_base + HW_BROADWAY_IMR, 0); | 
					
						
							|  |  |  | 	out_be32(io_base + HW_BROADWAY_ICR, 0xffffffff); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | struct irq_domain *hlwd_pic_init(struct device_node *np) | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	struct irq_domain *irq_domain; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	struct resource res; | 
					
						
							|  |  |  | 	void __iomem *io_base; | 
					
						
							|  |  |  | 	int retval; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	retval = of_address_to_resource(np, 0, &res); | 
					
						
							|  |  |  | 	if (retval) { | 
					
						
							|  |  |  | 		pr_err("no io memory range found\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	io_base = ioremap(res.start, resource_size(&res)); | 
					
						
							|  |  |  | 	if (!io_base) { | 
					
						
							|  |  |  | 		pr_err("ioremap failed\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_info("controller at 0x%08x mapped to 0x%p\n", res.start, io_base); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__hlwd_quiesce(io_base); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:54 -07:00
										 |  |  | 	irq_domain = irq_domain_add_linear(np, HLWD_NR_IRQS, | 
					
						
							|  |  |  | 					   &hlwd_irq_domain_ops, io_base); | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	if (!irq_domain) { | 
					
						
							|  |  |  | 		pr_err("failed to allocate irq_domain\n"); | 
					
						
							| 
									
										
										
										
											2013-10-12 15:13:19 +08:00
										 |  |  | 		iounmap(io_base); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	return irq_domain; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | unsigned int hlwd_pic_get_irq(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-02-22 18:35:03 -05:00
										 |  |  | 	return __hlwd_pic_get_irq(hlwd_irq_host); | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Probe function. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void hlwd_pic_probe(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-02-14 14:06:50 -07:00
										 |  |  | 	struct irq_domain *host; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 	struct device_node *np; | 
					
						
							|  |  |  | 	const u32 *interrupts; | 
					
						
							|  |  |  | 	int cascade_virq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") { | 
					
						
							|  |  |  | 		interrupts = of_get_property(np, "interrupts", NULL); | 
					
						
							|  |  |  | 		if (interrupts) { | 
					
						
							|  |  |  | 			host = hlwd_pic_init(np); | 
					
						
							|  |  |  | 			BUG_ON(!host); | 
					
						
							|  |  |  | 			cascade_virq = irq_of_parse_and_map(np, 0); | 
					
						
							| 
									
										
										
										
											2011-03-25 16:45:20 +01:00
										 |  |  | 			irq_set_handler_data(cascade_virq, host); | 
					
						
							|  |  |  | 			irq_set_chained_handler(cascade_virq, | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 						hlwd_pic_irq_cascade); | 
					
						
							| 
									
										
										
										
											2012-02-22 18:35:03 -05:00
										 |  |  | 			hlwd_irq_host = host; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * hlwd_quiesce() - quiesce hollywood irq controller | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Mask and ack all interrupt sources. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void hlwd_quiesce(void) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-02-22 18:35:03 -05:00
										 |  |  | 	void __iomem *io_base = hlwd_irq_host->host_data; | 
					
						
							| 
									
										
										
										
											2009-12-12 06:31:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	__hlwd_quiesce(io_base); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 |