12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29c21025cSAlbert Herranz /*
39c21025cSAlbert Herranz * arch/powerpc/platforms/embedded6xx/hlwd-pic.c
49c21025cSAlbert Herranz *
59c21025cSAlbert Herranz * Nintendo Wii "Hollywood" interrupt controller support.
69c21025cSAlbert Herranz * Copyright (C) 2009 The GameCube Linux Team
79c21025cSAlbert Herranz * Copyright (C) 2009 Albert Herranz
89c21025cSAlbert Herranz */
99c21025cSAlbert Herranz #define DRV_MODULE_NAME "hlwd-pic"
109c21025cSAlbert Herranz #define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt
119c21025cSAlbert Herranz
129c21025cSAlbert Herranz #include <linux/kernel.h>
139c21025cSAlbert Herranz #include <linux/irq.h>
149c21025cSAlbert Herranz #include <linux/of.h>
1526a2056eSRob Herring #include <linux/of_address.h>
1626a2056eSRob Herring #include <linux/of_irq.h>
179c21025cSAlbert Herranz #include <asm/io.h>
189c21025cSAlbert Herranz
199c21025cSAlbert Herranz #include "hlwd-pic.h"
209c21025cSAlbert Herranz
219c21025cSAlbert Herranz #define HLWD_NR_IRQS 32
229c21025cSAlbert Herranz
239c21025cSAlbert Herranz /*
249c21025cSAlbert Herranz * Each interrupt has a corresponding bit in both
259c21025cSAlbert Herranz * the Interrupt Cause (ICR) and Interrupt Mask (IMR) registers.
269c21025cSAlbert Herranz *
279c21025cSAlbert Herranz * Enabling/disabling an interrupt line involves asserting/clearing
289c21025cSAlbert Herranz * the corresponding bit in IMR. ACK'ing a request simply involves
299c21025cSAlbert Herranz * asserting the corresponding bit in ICR.
309c21025cSAlbert Herranz */
319c21025cSAlbert Herranz #define HW_BROADWAY_ICR 0x00
329c21025cSAlbert Herranz #define HW_BROADWAY_IMR 0x04
339dcb3df4SJonathan Neuschäfer #define HW_STARLET_ICR 0x08
349dcb3df4SJonathan Neuschäfer #define HW_STARLET_IMR 0x0c
359c21025cSAlbert Herranz
369c21025cSAlbert Herranz
379c21025cSAlbert Herranz /*
389c21025cSAlbert Herranz * IRQ chip hooks.
399c21025cSAlbert Herranz *
409c21025cSAlbert Herranz */
419c21025cSAlbert Herranz
hlwd_pic_mask_and_ack(struct irq_data * d)420bf8878eSLennert Buytenhek static void hlwd_pic_mask_and_ack(struct irq_data *d)
439c21025cSAlbert Herranz {
44476eb491SGrant Likely int irq = irqd_to_hwirq(d);
450bf8878eSLennert Buytenhek void __iomem *io_base = irq_data_get_irq_chip_data(d);
469c21025cSAlbert Herranz u32 mask = 1 << irq;
479c21025cSAlbert Herranz
489c21025cSAlbert Herranz clrbits32(io_base + HW_BROADWAY_IMR, mask);
499c21025cSAlbert Herranz out_be32(io_base + HW_BROADWAY_ICR, mask);
509c21025cSAlbert Herranz }
519c21025cSAlbert Herranz
hlwd_pic_ack(struct irq_data * d)520bf8878eSLennert Buytenhek static void hlwd_pic_ack(struct irq_data *d)
539c21025cSAlbert Herranz {
54476eb491SGrant Likely int irq = irqd_to_hwirq(d);
550bf8878eSLennert Buytenhek void __iomem *io_base = irq_data_get_irq_chip_data(d);
569c21025cSAlbert Herranz
579c21025cSAlbert Herranz out_be32(io_base + HW_BROADWAY_ICR, 1 << irq);
589c21025cSAlbert Herranz }
599c21025cSAlbert Herranz
hlwd_pic_mask(struct irq_data * d)600bf8878eSLennert Buytenhek static void hlwd_pic_mask(struct irq_data *d)
619c21025cSAlbert Herranz {
62476eb491SGrant Likely int irq = irqd_to_hwirq(d);
630bf8878eSLennert Buytenhek void __iomem *io_base = irq_data_get_irq_chip_data(d);
649c21025cSAlbert Herranz
659c21025cSAlbert Herranz clrbits32(io_base + HW_BROADWAY_IMR, 1 << irq);
669c21025cSAlbert Herranz }
679c21025cSAlbert Herranz
hlwd_pic_unmask(struct irq_data * d)680bf8878eSLennert Buytenhek static void hlwd_pic_unmask(struct irq_data *d)
699c21025cSAlbert Herranz {
70476eb491SGrant Likely int irq = irqd_to_hwirq(d);
710bf8878eSLennert Buytenhek void __iomem *io_base = irq_data_get_irq_chip_data(d);
729c21025cSAlbert Herranz
739c21025cSAlbert Herranz setbits32(io_base + HW_BROADWAY_IMR, 1 << irq);
749dcb3df4SJonathan Neuschäfer
759dcb3df4SJonathan Neuschäfer /* Make sure the ARM (aka. Starlet) doesn't handle this interrupt. */
769dcb3df4SJonathan Neuschäfer clrbits32(io_base + HW_STARLET_IMR, 1 << irq);
779c21025cSAlbert Herranz }
789c21025cSAlbert Herranz
799c21025cSAlbert Herranz
809c21025cSAlbert Herranz static struct irq_chip hlwd_pic = {
819c21025cSAlbert Herranz .name = "hlwd-pic",
820bf8878eSLennert Buytenhek .irq_ack = hlwd_pic_ack,
830bf8878eSLennert Buytenhek .irq_mask_ack = hlwd_pic_mask_and_ack,
840bf8878eSLennert Buytenhek .irq_mask = hlwd_pic_mask,
850bf8878eSLennert Buytenhek .irq_unmask = hlwd_pic_unmask,
869c21025cSAlbert Herranz };
879c21025cSAlbert Herranz
889c21025cSAlbert Herranz /*
899c21025cSAlbert Herranz * IRQ host hooks.
909c21025cSAlbert Herranz *
919c21025cSAlbert Herranz */
929c21025cSAlbert Herranz
93bae1d8f1SGrant Likely static struct irq_domain *hlwd_irq_host;
949c21025cSAlbert Herranz
hlwd_pic_map(struct irq_domain * h,unsigned int virq,irq_hw_number_t hwirq)95bae1d8f1SGrant Likely static int hlwd_pic_map(struct irq_domain *h, unsigned int virq,
969c21025cSAlbert Herranz irq_hw_number_t hwirq)
979c21025cSAlbert Herranz {
98ec775d0eSThomas Gleixner irq_set_chip_data(virq, h->host_data);
9998488db9SThomas Gleixner irq_set_status_flags(virq, IRQ_LEVEL);
100ec775d0eSThomas Gleixner irq_set_chip_and_handler(virq, &hlwd_pic, handle_level_irq);
1019c21025cSAlbert Herranz return 0;
1029c21025cSAlbert Herranz }
1039c21025cSAlbert Herranz
1049f70b8ebSGrant Likely static const struct irq_domain_ops hlwd_irq_domain_ops = {
1059c21025cSAlbert Herranz .map = hlwd_pic_map,
1069c21025cSAlbert Herranz };
1079c21025cSAlbert Herranz
__hlwd_pic_get_irq(struct irq_domain * h)108bae1d8f1SGrant Likely static unsigned int __hlwd_pic_get_irq(struct irq_domain *h)
1099c21025cSAlbert Herranz {
1109c21025cSAlbert Herranz void __iomem *io_base = h->host_data;
1119c21025cSAlbert Herranz u32 irq_status;
1129c21025cSAlbert Herranz
1139c21025cSAlbert Herranz irq_status = in_be32(io_base + HW_BROADWAY_ICR) &
1149c21025cSAlbert Herranz in_be32(io_base + HW_BROADWAY_IMR);
1159c21025cSAlbert Herranz if (irq_status == 0)
116ef24ba70SMichael Ellerman return 0; /* no more IRQs pending */
1179c21025cSAlbert Herranz
1182c899658SMarc Zyngier return __ffs(irq_status);
1199c21025cSAlbert Herranz }
1209c21025cSAlbert Herranz
hlwd_pic_irq_cascade(struct irq_desc * desc)121bd0b9ac4SThomas Gleixner static void hlwd_pic_irq_cascade(struct irq_desc *desc)
1229c21025cSAlbert Herranz {
123ec775d0eSThomas Gleixner struct irq_chip *chip = irq_desc_get_chip(desc);
124c1231a78SJiang Liu struct irq_domain *irq_domain = irq_desc_get_handler_data(desc);
1252c899658SMarc Zyngier unsigned int hwirq;
1269c21025cSAlbert Herranz
1277ccec3e7SAlbert Herranz raw_spin_lock(&desc->lock);
1280bf8878eSLennert Buytenhek chip->irq_mask(&desc->irq_data); /* IRQ_LEVEL */
1297ccec3e7SAlbert Herranz raw_spin_unlock(&desc->lock);
1309c21025cSAlbert Herranz
1312c899658SMarc Zyngier hwirq = __hlwd_pic_get_irq(irq_domain);
1322c899658SMarc Zyngier if (hwirq)
1332c899658SMarc Zyngier generic_handle_domain_irq(irq_domain, hwirq);
1349c21025cSAlbert Herranz else
1359c21025cSAlbert Herranz pr_err("spurious interrupt!\n");
1369c21025cSAlbert Herranz
1377ccec3e7SAlbert Herranz raw_spin_lock(&desc->lock);
1380bf8878eSLennert Buytenhek chip->irq_ack(&desc->irq_data); /* IRQ_LEVEL */
13998488db9SThomas Gleixner if (!irqd_irq_disabled(&desc->irq_data) && chip->irq_unmask)
1400bf8878eSLennert Buytenhek chip->irq_unmask(&desc->irq_data);
1417ccec3e7SAlbert Herranz raw_spin_unlock(&desc->lock);
1429c21025cSAlbert Herranz }
1439c21025cSAlbert Herranz
1449c21025cSAlbert Herranz /*
1459c21025cSAlbert Herranz * Platform hooks.
1469c21025cSAlbert Herranz *
1479c21025cSAlbert Herranz */
1489c21025cSAlbert Herranz
__hlwd_quiesce(void __iomem * io_base)1499c21025cSAlbert Herranz static void __hlwd_quiesce(void __iomem *io_base)
1509c21025cSAlbert Herranz {
1519c21025cSAlbert Herranz /* mask and ack all IRQs */
1529c21025cSAlbert Herranz out_be32(io_base + HW_BROADWAY_IMR, 0);
1539c21025cSAlbert Herranz out_be32(io_base + HW_BROADWAY_ICR, 0xffffffff);
1549c21025cSAlbert Herranz }
1559c21025cSAlbert Herranz
hlwd_pic_init(struct device_node * np)156c0dc225aSNick Child static struct irq_domain *__init hlwd_pic_init(struct device_node *np)
1579c21025cSAlbert Herranz {
158bae1d8f1SGrant Likely struct irq_domain *irq_domain;
1599c21025cSAlbert Herranz struct resource res;
1609c21025cSAlbert Herranz void __iomem *io_base;
1619c21025cSAlbert Herranz int retval;
1629c21025cSAlbert Herranz
1639c21025cSAlbert Herranz retval = of_address_to_resource(np, 0, &res);
1649c21025cSAlbert Herranz if (retval) {
1659c21025cSAlbert Herranz pr_err("no io memory range found\n");
1669c21025cSAlbert Herranz return NULL;
1679c21025cSAlbert Herranz }
1689c21025cSAlbert Herranz io_base = ioremap(res.start, resource_size(&res));
1699c21025cSAlbert Herranz if (!io_base) {
1709c21025cSAlbert Herranz pr_err("ioremap failed\n");
1719c21025cSAlbert Herranz return NULL;
1729c21025cSAlbert Herranz }
1739c21025cSAlbert Herranz
174*7b69600dSRandy Dunlap pr_info("controller at 0x%pa mapped to 0x%p\n", &res.start, io_base);
1759c21025cSAlbert Herranz
1769c21025cSAlbert Herranz __hlwd_quiesce(io_base);
1779c21025cSAlbert Herranz
178a8db8cf0SGrant Likely irq_domain = irq_domain_add_linear(np, HLWD_NR_IRQS,
179a8db8cf0SGrant Likely &hlwd_irq_domain_ops, io_base);
180bae1d8f1SGrant Likely if (!irq_domain) {
181bae1d8f1SGrant Likely pr_err("failed to allocate irq_domain\n");
1828d7c0b52SWei Yongjun iounmap(io_base);
1839c21025cSAlbert Herranz return NULL;
1849c21025cSAlbert Herranz }
1859c21025cSAlbert Herranz
186bae1d8f1SGrant Likely return irq_domain;
1879c21025cSAlbert Herranz }
1889c21025cSAlbert Herranz
hlwd_pic_get_irq(void)1899c21025cSAlbert Herranz unsigned int hlwd_pic_get_irq(void)
1909c21025cSAlbert Herranz {
1912c899658SMarc Zyngier unsigned int hwirq = __hlwd_pic_get_irq(hlwd_irq_host);
1922c899658SMarc Zyngier return hwirq ? irq_linear_revmap(hlwd_irq_host, hwirq) : 0;
1939c21025cSAlbert Herranz }
1949c21025cSAlbert Herranz
1959c21025cSAlbert Herranz /*
1969c21025cSAlbert Herranz * Probe function.
1979c21025cSAlbert Herranz *
1989c21025cSAlbert Herranz */
1999c21025cSAlbert Herranz
hlwd_pic_probe(void)200c0dc225aSNick Child void __init hlwd_pic_probe(void)
2019c21025cSAlbert Herranz {
202bae1d8f1SGrant Likely struct irq_domain *host;
2039c21025cSAlbert Herranz struct device_node *np;
2049c21025cSAlbert Herranz const u32 *interrupts;
2059c21025cSAlbert Herranz int cascade_virq;
2069c21025cSAlbert Herranz
2079c21025cSAlbert Herranz for_each_compatible_node(np, NULL, "nintendo,hollywood-pic") {
2089c21025cSAlbert Herranz interrupts = of_get_property(np, "interrupts", NULL);
2099c21025cSAlbert Herranz if (interrupts) {
2109c21025cSAlbert Herranz host = hlwd_pic_init(np);
2119c21025cSAlbert Herranz BUG_ON(!host);
2129c21025cSAlbert Herranz cascade_virq = irq_of_parse_and_map(np, 0);
213ec775d0eSThomas Gleixner irq_set_handler_data(cascade_virq, host);
214ec775d0eSThomas Gleixner irq_set_chained_handler(cascade_virq,
2159c21025cSAlbert Herranz hlwd_pic_irq_cascade);
2166d166fecSPaul Gortmaker hlwd_irq_host = host;
217f6e82647SJulia Lawall of_node_put(np);
2189c21025cSAlbert Herranz break;
2199c21025cSAlbert Herranz }
2209c21025cSAlbert Herranz }
2219c21025cSAlbert Herranz }
2229c21025cSAlbert Herranz
2239c21025cSAlbert Herranz /**
2249c21025cSAlbert Herranz * hlwd_quiesce() - quiesce hollywood irq controller
2259c21025cSAlbert Herranz *
2269c21025cSAlbert Herranz * Mask and ack all interrupt sources.
2279c21025cSAlbert Herranz *
2289c21025cSAlbert Herranz */
hlwd_quiesce(void)2299c21025cSAlbert Herranz void hlwd_quiesce(void)
2309c21025cSAlbert Herranz {
2316d166fecSPaul Gortmaker void __iomem *io_base = hlwd_irq_host->host_data;
2329c21025cSAlbert Herranz
2339c21025cSAlbert Herranz __hlwd_quiesce(io_base);
2349c21025cSAlbert Herranz }
2359c21025cSAlbert Herranz
236