Commit | Line | Data |
---|---|---|
0dcd9f87 RV |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | #define pr_fmt(fmt) "irq-ls-extirq: " fmt | |
4 | ||
5 | #include <linux/irq.h> | |
6 | #include <linux/irqchip.h> | |
7 | #include <linux/irqdomain.h> | |
8 | #include <linux/of.h> | |
9 | #include <linux/mfd/syscon.h> | |
10 | #include <linux/regmap.h> | |
11 | #include <linux/slab.h> | |
12 | ||
13 | #include <dt-bindings/interrupt-controller/arm-gic.h> | |
14 | ||
15 | #define MAXIRQ 12 | |
16 | #define LS1021A_SCFGREVCR 0x200 | |
17 | ||
18 | struct ls_extirq_data { | |
19 | struct regmap *syscon; | |
20 | u32 intpcr; | |
b16a1caf | 21 | bool is_ls1021a_or_ls1043a; |
0dcd9f87 RV |
22 | u32 nirq; |
23 | struct irq_fwspec map[MAXIRQ]; | |
24 | }; | |
25 | ||
26 | static int | |
27 | ls_extirq_set_type(struct irq_data *data, unsigned int type) | |
28 | { | |
29 | struct ls_extirq_data *priv = data->chip_data; | |
30 | irq_hw_number_t hwirq = data->hwirq; | |
31 | u32 value, mask; | |
32 | ||
b16a1caf | 33 | if (priv->is_ls1021a_or_ls1043a) |
0dcd9f87 RV |
34 | mask = 1U << (31 - hwirq); |
35 | else | |
36 | mask = 1U << hwirq; | |
37 | ||
38 | switch (type) { | |
39 | case IRQ_TYPE_LEVEL_LOW: | |
40 | type = IRQ_TYPE_LEVEL_HIGH; | |
41 | value = mask; | |
42 | break; | |
43 | case IRQ_TYPE_EDGE_FALLING: | |
44 | type = IRQ_TYPE_EDGE_RISING; | |
45 | value = mask; | |
46 | break; | |
47 | case IRQ_TYPE_LEVEL_HIGH: | |
48 | case IRQ_TYPE_EDGE_RISING: | |
49 | value = 0; | |
50 | break; | |
51 | default: | |
52 | return -EINVAL; | |
53 | } | |
54 | regmap_update_bits(priv->syscon, priv->intpcr, mask, value); | |
55 | ||
56 | return irq_chip_set_type_parent(data, type); | |
57 | } | |
58 | ||
59 | static struct irq_chip ls_extirq_chip = { | |
60 | .name = "ls-extirq", | |
61 | .irq_mask = irq_chip_mask_parent, | |
62 | .irq_unmask = irq_chip_unmask_parent, | |
63 | .irq_eoi = irq_chip_eoi_parent, | |
64 | .irq_set_type = ls_extirq_set_type, | |
65 | .irq_retrigger = irq_chip_retrigger_hierarchy, | |
66 | .irq_set_affinity = irq_chip_set_affinity_parent, | |
c6076742 | 67 | .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE, |
0dcd9f87 RV |
68 | }; |
69 | ||
70 | static int | |
71 | ls_extirq_domain_alloc(struct irq_domain *domain, unsigned int virq, | |
72 | unsigned int nr_irqs, void *arg) | |
73 | { | |
74 | struct ls_extirq_data *priv = domain->host_data; | |
75 | struct irq_fwspec *fwspec = arg; | |
76 | irq_hw_number_t hwirq; | |
77 | ||
78 | if (fwspec->param_count != 2) | |
79 | return -EINVAL; | |
80 | ||
81 | hwirq = fwspec->param[0]; | |
82 | if (hwirq >= priv->nirq) | |
83 | return -EINVAL; | |
84 | ||
85 | irq_domain_set_hwirq_and_chip(domain, virq, hwirq, &ls_extirq_chip, | |
86 | priv); | |
87 | ||
88 | return irq_domain_alloc_irqs_parent(domain, virq, 1, &priv->map[hwirq]); | |
89 | } | |
90 | ||
91 | static const struct irq_domain_ops extirq_domain_ops = { | |
92 | .xlate = irq_domain_xlate_twocell, | |
93 | .alloc = ls_extirq_domain_alloc, | |
94 | .free = irq_domain_free_irqs_common, | |
95 | }; | |
96 | ||
97 | static int | |
98 | ls_extirq_parse_map(struct ls_extirq_data *priv, struct device_node *node) | |
99 | { | |
100 | const __be32 *map; | |
101 | u32 mapsize; | |
102 | int ret; | |
103 | ||
104 | map = of_get_property(node, "interrupt-map", &mapsize); | |
105 | if (!map) | |
106 | return -ENOENT; | |
107 | if (mapsize % sizeof(*map)) | |
108 | return -EINVAL; | |
109 | mapsize /= sizeof(*map); | |
110 | ||
111 | while (mapsize) { | |
112 | struct device_node *ipar; | |
113 | u32 hwirq, intsize, j; | |
114 | ||
115 | if (mapsize < 3) | |
116 | return -EINVAL; | |
117 | hwirq = be32_to_cpup(map); | |
118 | if (hwirq >= MAXIRQ) | |
119 | return -EINVAL; | |
120 | priv->nirq = max(priv->nirq, hwirq + 1); | |
121 | ||
122 | ipar = of_find_node_by_phandle(be32_to_cpup(map + 2)); | |
123 | map += 3; | |
124 | mapsize -= 3; | |
125 | if (!ipar) | |
126 | return -EINVAL; | |
127 | priv->map[hwirq].fwnode = &ipar->fwnode; | |
128 | ret = of_property_read_u32(ipar, "#interrupt-cells", &intsize); | |
129 | if (ret) | |
130 | return ret; | |
131 | ||
132 | if (intsize > mapsize) | |
133 | return -EINVAL; | |
134 | ||
135 | priv->map[hwirq].param_count = intsize; | |
136 | for (j = 0; j < intsize; ++j) | |
137 | priv->map[hwirq].param[j] = be32_to_cpup(map++); | |
138 | mapsize -= intsize; | |
139 | } | |
140 | return 0; | |
141 | } | |
142 | ||
143 | static int __init | |
144 | ls_extirq_of_init(struct device_node *node, struct device_node *parent) | |
145 | { | |
146 | ||
147 | struct irq_domain *domain, *parent_domain; | |
148 | struct ls_extirq_data *priv; | |
149 | int ret; | |
150 | ||
151 | parent_domain = irq_find_host(parent); | |
152 | if (!parent_domain) { | |
153 | pr_err("Cannot find parent domain\n"); | |
154 | return -ENODEV; | |
155 | } | |
156 | ||
157 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
158 | if (!priv) | |
159 | return -ENOMEM; | |
160 | ||
161 | priv->syscon = syscon_node_to_regmap(node->parent); | |
162 | if (IS_ERR(priv->syscon)) { | |
163 | ret = PTR_ERR(priv->syscon); | |
164 | pr_err("Failed to lookup parent regmap\n"); | |
165 | goto out; | |
166 | } | |
167 | ret = of_property_read_u32(node, "reg", &priv->intpcr); | |
168 | if (ret) { | |
169 | pr_err("Missing INTPCR offset value\n"); | |
170 | goto out; | |
171 | } | |
172 | ||
173 | ret = ls_extirq_parse_map(priv, node); | |
174 | if (ret) | |
175 | goto out; | |
176 | ||
b16a1caf HZ |
177 | priv->is_ls1021a_or_ls1043a = of_device_is_compatible(node, "fsl,ls1021a-extirq") || |
178 | of_device_is_compatible(node, "fsl,ls1043a-extirq"); | |
0dcd9f87 RV |
179 | |
180 | domain = irq_domain_add_hierarchy(parent_domain, 0, priv->nirq, node, | |
181 | &extirq_domain_ops, priv); | |
182 | if (!domain) | |
183 | ret = -ENOMEM; | |
184 | ||
185 | out: | |
186 | if (ret) | |
187 | kfree(priv); | |
188 | return ret; | |
189 | } | |
190 | ||
191 | IRQCHIP_DECLARE(ls1021a_extirq, "fsl,ls1021a-extirq", ls_extirq_of_init); | |
b16a1caf HZ |
192 | IRQCHIP_DECLARE(ls1043a_extirq, "fsl,ls1043a-extirq", ls_extirq_of_init); |
193 | IRQCHIP_DECLARE(ls1088a_extirq, "fsl,ls1088a-extirq", ls_extirq_of_init); |