1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2019, Jiaxun Yang <jiaxun.yang@flygoat.com> |
4 | * Loongson-1 platform IRQ support |
5 | */ |
6 | |
7 | #include <linux/errno.h> |
8 | #include <linux/init.h> |
9 | #include <linux/types.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/ioport.h> |
12 | #include <linux/irqchip.h> |
13 | #include <linux/of_address.h> |
14 | #include <linux/of_irq.h> |
15 | #include <linux/io.h> |
16 | #include <linux/irqchip/chained_irq.h> |
17 | |
18 | #define LS_REG_INTC_STATUS 0x00 |
19 | #define LS_REG_INTC_EN 0x04 |
20 | #define LS_REG_INTC_SET 0x08 |
21 | #define LS_REG_INTC_CLR 0x0c |
22 | #define LS_REG_INTC_POL 0x10 |
23 | #define LS_REG_INTC_EDGE 0x14 |
24 | |
25 | /** |
26 | * struct ls1x_intc_priv - private ls1x-intc data. |
27 | * @domain: IRQ domain. |
28 | * @intc_base: IO Base of intc registers. |
29 | */ |
30 | |
31 | struct ls1x_intc_priv { |
32 | struct irq_domain *domain; |
33 | void __iomem *intc_base; |
34 | }; |
35 | |
36 | |
37 | static void ls1x_chained_handle_irq(struct irq_desc *desc) |
38 | { |
39 | struct ls1x_intc_priv *priv = irq_desc_get_handler_data(desc); |
40 | struct irq_chip *chip = irq_desc_get_chip(desc); |
41 | u32 pending; |
42 | |
43 | chained_irq_enter(chip, desc); |
44 | pending = readl(addr: priv->intc_base + LS_REG_INTC_STATUS) & |
45 | readl(addr: priv->intc_base + LS_REG_INTC_EN); |
46 | |
47 | if (!pending) |
48 | spurious_interrupt(); |
49 | |
50 | while (pending) { |
51 | int bit = __ffs(pending); |
52 | |
53 | generic_handle_domain_irq(domain: priv->domain, hwirq: bit); |
54 | pending &= ~BIT(bit); |
55 | } |
56 | |
57 | chained_irq_exit(chip, desc); |
58 | } |
59 | |
60 | static void ls_intc_set_bit(struct irq_chip_generic *gc, |
61 | unsigned int offset, |
62 | u32 mask, bool set) |
63 | { |
64 | if (set) |
65 | writel(readl(addr: gc->reg_base + offset) | mask, |
66 | addr: gc->reg_base + offset); |
67 | else |
68 | writel(readl(addr: gc->reg_base + offset) & ~mask, |
69 | addr: gc->reg_base + offset); |
70 | } |
71 | |
72 | static int ls_intc_set_type(struct irq_data *data, unsigned int type) |
73 | { |
74 | struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d: data); |
75 | u32 mask = data->mask; |
76 | |
77 | switch (type) { |
78 | case IRQ_TYPE_LEVEL_HIGH: |
79 | ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, set: false); |
80 | ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, set: true); |
81 | break; |
82 | case IRQ_TYPE_LEVEL_LOW: |
83 | ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, set: false); |
84 | ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, set: false); |
85 | break; |
86 | case IRQ_TYPE_EDGE_RISING: |
87 | ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, set: true); |
88 | ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, set: true); |
89 | break; |
90 | case IRQ_TYPE_EDGE_FALLING: |
91 | ls_intc_set_bit(gc, LS_REG_INTC_EDGE, mask, set: true); |
92 | ls_intc_set_bit(gc, LS_REG_INTC_POL, mask, set: false); |
93 | break; |
94 | default: |
95 | return -EINVAL; |
96 | } |
97 | |
98 | irqd_set_trigger_type(d: data, type); |
99 | return irq_setup_alt_chip(d: data, type); |
100 | } |
101 | |
102 | |
103 | static int __init ls1x_intc_of_init(struct device_node *node, |
104 | struct device_node *parent) |
105 | { |
106 | struct irq_chip_generic *gc; |
107 | struct irq_chip_type *ct; |
108 | struct ls1x_intc_priv *priv; |
109 | int parent_irq, err = 0; |
110 | |
111 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
112 | if (!priv) |
113 | return -ENOMEM; |
114 | |
115 | priv->intc_base = of_iomap(node, index: 0); |
116 | if (!priv->intc_base) { |
117 | err = -ENODEV; |
118 | goto out_free_priv; |
119 | } |
120 | |
121 | parent_irq = irq_of_parse_and_map(node, index: 0); |
122 | if (!parent_irq) { |
123 | pr_err("ls1x-irq: unable to get parent irq\n" ); |
124 | err = -ENODEV; |
125 | goto out_iounmap; |
126 | } |
127 | |
128 | /* Set up an IRQ domain */ |
129 | priv->domain = irq_domain_add_linear(of_node: node, size: 32, ops: &irq_generic_chip_ops, |
130 | NULL); |
131 | if (!priv->domain) { |
132 | pr_err("ls1x-irq: cannot add IRQ domain\n" ); |
133 | err = -ENOMEM; |
134 | goto out_iounmap; |
135 | } |
136 | |
137 | err = irq_alloc_domain_generic_chips(priv->domain, 32, 2, |
138 | node->full_name, handle_level_irq, |
139 | IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN, 0, |
140 | IRQ_GC_INIT_MASK_CACHE); |
141 | if (err) { |
142 | pr_err("ls1x-irq: unable to register IRQ domain\n" ); |
143 | goto out_free_domain; |
144 | } |
145 | |
146 | /* Mask all irqs */ |
147 | writel(val: 0x0, addr: priv->intc_base + LS_REG_INTC_EN); |
148 | |
149 | /* Ack all irqs */ |
150 | writel(val: 0xffffffff, addr: priv->intc_base + LS_REG_INTC_CLR); |
151 | |
152 | /* Set all irqs to high level triggered */ |
153 | writel(val: 0xffffffff, addr: priv->intc_base + LS_REG_INTC_POL); |
154 | |
155 | gc = irq_get_domain_generic_chip(d: priv->domain, hw_irq: 0); |
156 | |
157 | gc->reg_base = priv->intc_base; |
158 | |
159 | ct = gc->chip_types; |
160 | ct[0].type = IRQ_TYPE_LEVEL_MASK; |
161 | ct[0].regs.mask = LS_REG_INTC_EN; |
162 | ct[0].regs.ack = LS_REG_INTC_CLR; |
163 | ct[0].chip.irq_unmask = irq_gc_mask_set_bit; |
164 | ct[0].chip.irq_mask = irq_gc_mask_clr_bit; |
165 | ct[0].chip.irq_ack = irq_gc_ack_set_bit; |
166 | ct[0].chip.irq_set_type = ls_intc_set_type; |
167 | ct[0].handler = handle_level_irq; |
168 | |
169 | ct[1].type = IRQ_TYPE_EDGE_BOTH; |
170 | ct[1].regs.mask = LS_REG_INTC_EN; |
171 | ct[1].regs.ack = LS_REG_INTC_CLR; |
172 | ct[1].chip.irq_unmask = irq_gc_mask_set_bit; |
173 | ct[1].chip.irq_mask = irq_gc_mask_clr_bit; |
174 | ct[1].chip.irq_ack = irq_gc_ack_set_bit; |
175 | ct[1].chip.irq_set_type = ls_intc_set_type; |
176 | ct[1].handler = handle_edge_irq; |
177 | |
178 | irq_set_chained_handler_and_data(irq: parent_irq, |
179 | handle: ls1x_chained_handle_irq, data: priv); |
180 | |
181 | return 0; |
182 | |
183 | out_free_domain: |
184 | irq_domain_remove(host: priv->domain); |
185 | out_iounmap: |
186 | iounmap(addr: priv->intc_base); |
187 | out_free_priv: |
188 | kfree(objp: priv); |
189 | |
190 | return err; |
191 | } |
192 | |
193 | IRQCHIP_DECLARE(ls1x_intc, "loongson,ls1x-intc" , ls1x_intc_of_init); |
194 | |