blob: 923552ffffdf501da7aa616f8d77fd648a742cf1 [file] [log] [blame]
Archana Sathyakumar741a37a2017-05-10 11:03:39 -06001/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 */
13
14#include <linux/delay.h>
15#include <linux/err.h>
16#include <linux/init.h>
17#include <linux/irq.h>
18#include <linux/irqchip.h>
19#include <linux/irqdomain.h>
20#include <linux/io.h>
21#include <linux/kernel.h>
22#include <linux/of.h>
23#include <linux/of_address.h>
24#include <linux/of_device.h>
25#include <linux/spinlock.h>
26#include <linux/platform_device.h>
27#include <linux/slab.h>
28#include <linux/types.h>
29#include "pdc.h"
30#define CREATE_TRACE_POINTS
31#include "trace/events/pdc.h"
32
33#define MAX_IRQS 126
34#define CLEAR_INTR(reg, intr) (reg & ~(1 << intr))
35#define ENABLE_INTR(reg, intr) (reg | (1 << intr))
36
37enum pdc_register_offsets {
38 IRQ_ENABLE_BANK = 0x10,
39 IRQ_i_CFG = 0x110,
40};
41
42static DEFINE_SPINLOCK(pdc_lock);
43static void __iomem *pdc_base;
44
45static int get_pdc_pin(irq_hw_number_t hwirq, void *data)
46{
47 int i;
48 struct pdc_pin *pdc_data = (struct pdc_pin *) data;
49
50 for (i = 0; pdc_data[i].pin >= 0; i++) {
51 if (pdc_data[i].hwirq == hwirq)
52 return pdc_data[i].pin;
53 }
54
55 return -EINVAL;
56}
57
58static inline int pdc_enable_intr(struct irq_data *d, bool on)
59{
60 int pin_out = get_pdc_pin(d->hwirq, d->chip_data);
61 unsigned int index, mask;
62 u32 enable, r_enable;
63 unsigned long flags;
64
65 if (pin_out < 0)
66 return 0;
67
68 index = pin_out / 32;
69 mask = pin_out % 32;
70 spin_lock_irqsave(&pdc_lock, flags);
71
72 enable = readl_relaxed(pdc_base + IRQ_ENABLE_BANK + (index *
73 sizeof(uint32_t)));
74 if (on)
75 enable = ENABLE_INTR(enable, mask);
76 else
77 enable = CLEAR_INTR(enable, mask);
78
79 writel_relaxed(enable, pdc_base + IRQ_ENABLE_BANK + (index *
80 sizeof(uint32_t)));
81
82 do {
83 r_enable = readl_relaxed(pdc_base + IRQ_ENABLE_BANK +
84 (index * sizeof(uint32_t)));
85 if (r_enable == enable)
86 break;
87 udelay(5);
88 } while (1);
89
90 spin_unlock_irqrestore(&pdc_lock, flags);
91
92 trace_irq_pin_config("enable", (u32)pin_out, (u32)d->hwirq,
93 0, on);
94
95 return 0;
96}
97
98static void qcom_pdc_gic_mask(struct irq_data *d)
99{
100 pdc_enable_intr(d, false);
101 irq_chip_mask_parent(d);
102}
103
104static void qcom_pdc_gic_unmask(struct irq_data *d)
105{
106 pdc_enable_intr(d, true);
107 irq_chip_unmask_parent(d);
108}
109
110static void qcom_pdc_gic_enable(struct irq_data *d)
111{
112 pdc_enable_intr(d, true);
113 irq_chip_enable_parent(d);
114}
115
116static void qcom_pdc_gic_disable(struct irq_data *d)
117{
118 pdc_enable_intr(d, false);
119 irq_chip_disable_parent(d);
120}
121
122/*
123 * GIC does not handle falling edge or active low. To allow falling edge and
124 * active low interrupts to be handled at GIC, PDC has an inverter that inverts
125 * falling edge into a rising edge and active low into an active high.
126 * For the inverter to work, the polarity bit in the IRQ_CONFIG register has to
127 * set as per the table below.
128 * (polarity, falling edge, rising edge ) ORIG POL CONV POLARITY
129 * 3'b0 00 Level sensitive active low (~~~|_____) (___|~~~~~) LOW
130 * 3'b0 01 Rising edge sensitive (___|~~|__) (~~~|__|~~) NOT USED
131 * 3'b0 10 Falling edge sensitive (~~~|__|~~) (___|~~|__) LOW
132 * 3'b0 11 Dual Edge sensitive NOT USED
133 * 3'b1 00 Level senstive active High (___|~~~~~) (___|~~~~~) HIGH
134 * 3'b1 01 Falling Edge sensitive (~~~|__|~~) (~~~|__|~~) NOT USED
135 * 3'b1 10 Rising edge sensitive (___|~~|__) (___|~~|__) HIGH
136 * 3'b1 11 Dual Edge sensitive HIGH
137 */
138enum pdc_irq_config_bits {
139 POLARITY_LOW = 0, //0 00
140 FALLING_EDGE = 2, //0 10
141 POLARITY_HIGH = 4,//1 00
142 RISING_EDGE = 6, //1 10
143 DUAL_EDGE = 7, //1 11
144};
145
146static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type)
147{
148 int pin_out = get_pdc_pin(d->hwirq, d->chip_data);
149 u32 pdc_type = 0, config;
150
151 if (pin_out < 0)
152 goto fwd_to_parent;
153
154 switch (type) {
155 case IRQ_TYPE_EDGE_RISING:
156 pdc_type = RISING_EDGE;
157 break;
158 case IRQ_TYPE_EDGE_FALLING:
159 pdc_type = FALLING_EDGE;
160 break;
161 case IRQ_TYPE_EDGE_BOTH:
162 pdc_type = DUAL_EDGE;
163 break;
164 case IRQ_TYPE_LEVEL_HIGH:
165 pdc_type = POLARITY_HIGH;
166 break;
167 case IRQ_TYPE_LEVEL_LOW:
168 pdc_type = POLARITY_LOW;
169 break;
170 default:
171 pdc_type = POLARITY_HIGH;
172 break;
173 }
174 writel_relaxed(pdc_type, pdc_base + IRQ_i_CFG +
175 (pin_out * sizeof(uint32_t)));
176
177 do {
178 config = readl_relaxed(pdc_base + IRQ_i_CFG +
179 (pin_out * sizeof(uint32_t)));
180 if (config == pdc_type)
181 break;
182 udelay(5);
183 } while (1);
184
185 trace_irq_pin_config("type_config", (u32)pin_out, (u32)d->hwirq,
186 pdc_type, 0);
187
188 /*
189 * If type is edge triggered, forward that as Rising edge as PDC
190 * takes care of converting falling edge to rising edge signal
191 */
192 if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
193 type = IRQ_TYPE_EDGE_RISING;
194
195 /*
196 * If type is level, then forward that as level high as PDC
197 * takes care of converting falling edge to rising edge signal
198 */
199 if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH))
200 type = IRQ_TYPE_LEVEL_HIGH;
201
202fwd_to_parent:
203
204 return irq_chip_set_type_parent(d, type);
205}
206
207static struct irq_chip qcom_pdc_gic_chip = {
208 .name = "PDC-GIC",
209 .irq_eoi = irq_chip_eoi_parent,
210 .irq_mask = qcom_pdc_gic_mask,
211 .irq_enable = qcom_pdc_gic_enable,
212 .irq_unmask = qcom_pdc_gic_unmask,
213 .irq_disable = qcom_pdc_gic_disable,
214 .irq_retrigger = irq_chip_retrigger_hierarchy,
215 .irq_set_type = qcom_pdc_gic_set_type,
216 .flags = IRQCHIP_MASK_ON_SUSPEND |
217 IRQCHIP_SET_TYPE_MASKED |
218 IRQCHIP_SKIP_SET_WAKE,
219 .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent,
220#ifdef CONFIG_SMP
221 .irq_set_affinity = irq_chip_set_affinity_parent,
222#endif
223};
224
225static int qcom_pdc_translate(struct irq_domain *d,
226 struct irq_fwspec *fwspec, unsigned long *hwirq, unsigned int *type)
227{
228 return d->parent->ops->translate(d->parent, fwspec, hwirq, type);
229}
230
231static int qcom_pdc_alloc(struct irq_domain *domain,
232 unsigned int virq, unsigned int nr_irqs, void *data)
233{
234 struct irq_fwspec *fwspec = data;
235 struct irq_fwspec parent_fwspec;
236 irq_hw_number_t hwirq;
237 int i;
238 unsigned int type;
239 int ret;
240
241 ret = qcom_pdc_translate(domain, fwspec, &hwirq, &type);
242 if (ret)
243 return -EINVAL;
244
245 for (i = 0; i < nr_irqs; i++)
246 irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
247 &qcom_pdc_gic_chip, domain->host_data);
248
249 parent_fwspec = *fwspec;
250 parent_fwspec.fwnode = domain->parent->fwnode;
251
252 return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs,
253 &parent_fwspec);
254}
255
256static const struct irq_domain_ops qcom_pdc_ops = {
257 .translate = qcom_pdc_translate,
258 .alloc = qcom_pdc_alloc,
259 .free = irq_domain_free_irqs_common,
260};
261
262int qcom_pdc_init(struct device_node *node,
263 struct device_node *parent, void *data)
264{
265 struct irq_domain *parent_domain;
266 int ret;
267 struct irq_domain *pdc_domain;
268
269 pdc_base = of_iomap(node, 0);
270 if (!pdc_base) {
271 pr_err("%s(): unable to map PDC registers\n", node->full_name);
272 return -ENXIO;
273 }
274
275 parent_domain = irq_find_host(parent);
276 if (!parent_domain) {
277 pr_err("unable to obtain PDC parent domain\n");
278 ret = -ENXIO;
279 goto failure;
280 }
281
282 pdc_domain = irq_domain_add_hierarchy(parent_domain, 0, MAX_IRQS,
283 node, &qcom_pdc_ops, data);
284 if (!pdc_domain) {
285 pr_err("GIC domain add failed\n");
286 ret = -ENOMEM;
287 goto failure;
288 }
289
290 pdc_domain->name = "qcom,pdc";
291
292 return 0;
293
294failure:
295 iounmap(pdc_base);
296
297 return ret;
298}
299EXPORT_SYMBOL(qcom_pdc_init);