1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for MIPS Goldfish Programmable Interrupt Controller. |
4 | * |
5 | * Author: Miodrag Dinic <miodrag.dinic@mips.com> |
6 | */ |
7 | |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | |
10 | #include <linux/interrupt.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/irqchip.h> |
13 | #include <linux/irqchip/chained_irq.h> |
14 | #include <linux/irqdomain.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/of_irq.h> |
17 | |
18 | #define GFPIC_NR_IRQS 32 |
19 | |
20 | /* 8..39 Cascaded Goldfish PIC interrupts */ |
21 | #define GFPIC_IRQ_BASE 8 |
22 | |
23 | #define GFPIC_REG_IRQ_PENDING 0x04 |
24 | #define GFPIC_REG_IRQ_DISABLE_ALL 0x08 |
25 | #define GFPIC_REG_IRQ_DISABLE 0x0c |
26 | #define GFPIC_REG_IRQ_ENABLE 0x10 |
27 | |
28 | struct goldfish_pic_data { |
29 | void __iomem *base; |
30 | struct irq_domain *irq_domain; |
31 | }; |
32 | |
33 | static void goldfish_pic_cascade(struct irq_desc *desc) |
34 | { |
35 | struct goldfish_pic_data *gfpic = irq_desc_get_handler_data(desc); |
36 | struct irq_chip *host_chip = irq_desc_get_chip(desc); |
37 | u32 pending, hwirq; |
38 | |
39 | chained_irq_enter(chip: host_chip, desc); |
40 | |
41 | pending = readl(addr: gfpic->base + GFPIC_REG_IRQ_PENDING); |
42 | while (pending) { |
43 | hwirq = __fls(word: pending); |
44 | generic_handle_domain_irq(domain: gfpic->irq_domain, hwirq); |
45 | pending &= ~(1 << hwirq); |
46 | } |
47 | |
48 | chained_irq_exit(chip: host_chip, desc); |
49 | } |
50 | |
51 | static const struct irq_domain_ops goldfish_irq_domain_ops = { |
52 | .xlate = irq_domain_xlate_onecell, |
53 | }; |
54 | |
55 | static int __init goldfish_pic_of_init(struct device_node *of_node, |
56 | struct device_node *parent) |
57 | { |
58 | struct goldfish_pic_data *gfpic; |
59 | struct irq_chip_generic *gc; |
60 | struct irq_chip_type *ct; |
61 | unsigned int parent_irq; |
62 | int ret = 0; |
63 | |
64 | gfpic = kzalloc(size: sizeof(*gfpic), GFP_KERNEL); |
65 | if (!gfpic) { |
66 | ret = -ENOMEM; |
67 | goto out_err; |
68 | } |
69 | |
70 | parent_irq = irq_of_parse_and_map(node: of_node, index: 0); |
71 | if (!parent_irq) { |
72 | pr_err("Failed to map parent IRQ!\n" ); |
73 | ret = -EINVAL; |
74 | goto out_free; |
75 | } |
76 | |
77 | gfpic->base = of_iomap(node: of_node, index: 0); |
78 | if (!gfpic->base) { |
79 | pr_err("Failed to map base address!\n" ); |
80 | ret = -ENOMEM; |
81 | goto out_unmap_irq; |
82 | } |
83 | |
84 | /* Mask interrupts. */ |
85 | writel(val: 1, addr: gfpic->base + GFPIC_REG_IRQ_DISABLE_ALL); |
86 | |
87 | gc = irq_alloc_generic_chip(name: "GFPIC" , nr_ct: 1, GFPIC_IRQ_BASE, reg_base: gfpic->base, |
88 | handler: handle_level_irq); |
89 | if (!gc) { |
90 | pr_err("Failed to allocate chip structures!\n" ); |
91 | ret = -ENOMEM; |
92 | goto out_iounmap; |
93 | } |
94 | |
95 | ct = gc->chip_types; |
96 | ct->regs.enable = GFPIC_REG_IRQ_ENABLE; |
97 | ct->regs.disable = GFPIC_REG_IRQ_DISABLE; |
98 | ct->chip.irq_unmask = irq_gc_unmask_enable_reg; |
99 | ct->chip.irq_mask = irq_gc_mask_disable_reg; |
100 | |
101 | irq_setup_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS), flags: 0, |
102 | clr: IRQ_NOPROBE | IRQ_LEVEL, set: 0); |
103 | |
104 | gfpic->irq_domain = irq_domain_add_legacy(of_node, GFPIC_NR_IRQS, |
105 | GFPIC_IRQ_BASE, first_hwirq: 0, |
106 | ops: &goldfish_irq_domain_ops, |
107 | NULL); |
108 | if (!gfpic->irq_domain) { |
109 | pr_err("Failed to add irqdomain!\n" ); |
110 | ret = -ENOMEM; |
111 | goto out_destroy_generic_chip; |
112 | } |
113 | |
114 | irq_set_chained_handler_and_data(irq: parent_irq, |
115 | handle: goldfish_pic_cascade, data: gfpic); |
116 | |
117 | pr_info("Successfully registered.\n" ); |
118 | return 0; |
119 | |
120 | out_destroy_generic_chip: |
121 | irq_destroy_generic_chip(gc, IRQ_MSK(GFPIC_NR_IRQS), |
122 | clr: IRQ_NOPROBE | IRQ_LEVEL, set: 0); |
123 | out_iounmap: |
124 | iounmap(addr: gfpic->base); |
125 | out_unmap_irq: |
126 | irq_dispose_mapping(virq: parent_irq); |
127 | out_free: |
128 | kfree(objp: gfpic); |
129 | out_err: |
130 | pr_err("Failed to initialize! (errno = %d)\n" , ret); |
131 | return ret; |
132 | } |
133 | |
134 | IRQCHIP_DECLARE(google_gf_pic, "google,goldfish-pic" , goldfish_pic_of_init); |
135 | |