1 /*
2 * Allwinner wakeup irq support.
3 *
4 * Copyright (C) 2019 Allwinner Technology, Inc.
5 * fanqinghua <fanqinghua@allwinnertech.com>
6 *
7 * SUNXI WakeupGen is the interrupt controller extension used along
8 * with ARM GIC to wake the CPU out from low power states on
9 * external interrupts. It is responsible for generating wakeup
10 * event from the incoming interrupts and enable bits. It is
11 * implemented in PMU always ON power domain. During normal operation,
12 * WakeupGen delivers external interrupts directly to the GIC.
13 *
14 * Copyright (C) 2017 Allwinner Technology, Inc.
15 * Author: Fan Qinghua <fanqinghua@allwinnertech.com>
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License version 2 as
19 * published by the Free Software Foundation.
20 */
21 #include <linux/module.h>
22 #include <linux/kernel.h>
23 #include <linux/init.h>
24 #include <linux/io.h>
25 #include <linux/irq.h>
26 #include <linux/of_irq.h>
27 #include <linux/irqchip.h>
28 #include <linux/irqdomain.h>
29 #include <linux/of_address.h>
30 #include <linux/platform_device.h>
31 #include <linux/cpu.h>
32 #include <linux/notifier.h>
33 #include <linux/cpu_pm.h>
34 #include <sunxi-sip.h>
35
36 #define GIC_SUPPORT_IRQS 1024
37
38 struct sunxi_irq_domain {
39 struct device *dev;
40 struct irq_domain *irqd;
41 };
42
set_wakeup_source(u32 wakeup_irq)43 static inline int set_wakeup_source(u32 wakeup_irq)
44 {
45 int result;
46
47 result = invoke_scp_fn_smc(SET_WAKEUP_SRC,
48 wakeup_irq, 0, 0);
49
50 return result;
51 }
52
clear_wakeup_source(u32 wakeup_irq)53 static inline int clear_wakeup_source(u32 wakeup_irq)
54 {
55 int result;
56
57 result = invoke_scp_fn_smc(CLEAR_WAKEUP_SRC,
58 wakeup_irq, 0, 0);
59
60 return result;
61 }
62
sunxi_irq_set_wake(struct irq_data * d,unsigned int on)63 static int sunxi_irq_set_wake(struct irq_data *d, unsigned int on)
64 {
65 if (on)
66 set_wakeup_source(SET_ROOT_WAKEUP_SOURCE(d->hwirq));
67 else
68 clear_wakeup_source(SET_ROOT_WAKEUP_SOURCE(d->hwirq));
69 /*
70 * Do *not* call into the parent, as the GIC doesn't have any
71 * wake-up facility...
72 */
73 return 0;
74 }
75
76 static struct irq_chip wakeupgen_chip = {
77 .name = "wakeupgen",
78 .irq_enable = irq_chip_enable_parent,
79 .irq_disable = irq_chip_disable_parent,
80 .irq_eoi = irq_chip_eoi_parent,
81 .irq_mask = irq_chip_mask_parent,
82 .irq_unmask = irq_chip_unmask_parent,
83 .irq_retrigger = irq_chip_retrigger_hierarchy,
84 .irq_set_wake = sunxi_irq_set_wake,
85 .irq_set_type = irq_chip_set_type_parent,
86 .irq_set_affinity = irq_chip_set_affinity_parent,
87 };
88
sunxi_domain_translate(struct irq_domain * d,struct irq_fwspec * fwspec,unsigned long * hwirq,unsigned int * type)89 static int sunxi_domain_translate(struct irq_domain *d,
90 struct irq_fwspec *fwspec,
91 unsigned long *hwirq,
92 unsigned int *type)
93 {
94 if (is_of_node(fwspec->fwnode)) {
95 if (fwspec->param_count != 3)
96 return -EINVAL;
97
98 /* No PPI should point to this domain */
99 if (fwspec->param[0] != 0)
100 return -EINVAL;
101
102 *hwirq = fwspec->param[1];
103 *type = fwspec->param[2];
104 return 0;
105 }
106
107 return -EINVAL;
108 }
109
sunxi_domain_alloc(struct irq_domain * domain,unsigned int irq,unsigned int nr_irqs,void * data)110 static int sunxi_domain_alloc(struct irq_domain *domain,
111 unsigned int irq,
112 unsigned int nr_irqs, void *data)
113 {
114 struct irq_fwspec *fwspec = data;
115 struct irq_fwspec parent_fwspec;
116 irq_hw_number_t hwirq;
117 int i;
118
119 if (fwspec->param_count != 3)
120 return -EINVAL; /* Not GIC compliant */
121 if (fwspec->param[0] != 0)
122 return -EINVAL; /* No PPI should point to this domain */
123
124 hwirq = fwspec->param[1];
125 if (hwirq >= GIC_SUPPORT_IRQS)
126 return -EINVAL; /* Can't deal with this */
127
128 for (i = 0; i < nr_irqs; i++)
129 irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
130 &wakeupgen_chip, NULL);
131
132 parent_fwspec = *fwspec;
133 parent_fwspec.fwnode = domain->parent->fwnode;
134 return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
135 &parent_fwspec);
136 }
137
138 static const struct irq_domain_ops sunxi_domain_ops = {
139 .translate = sunxi_domain_translate,
140 .alloc = sunxi_domain_alloc,
141 .free = irq_domain_free_irqs_common,
142 };
143
144 #ifndef MODULE
wakeupgen_init(struct device_node * node,struct device_node * parent)145 static int __init wakeupgen_init(struct device_node *node,
146 struct device_node *parent)
147 {
148 struct irq_domain *parent_domain, *domain;
149
150 if (!parent) {
151 pr_err("%s: no parent, giving up\n", node->full_name);
152 return -ENODEV;
153 }
154
155 parent_domain = irq_find_host(parent);
156 if (!parent_domain) {
157 pr_err("%s: unable to obtain parent domain\n", node->full_name);
158 return -ENXIO;
159 }
160
161 domain = irq_domain_add_hierarchy(parent_domain, 0, GIC_SUPPORT_IRQS,
162 node, &sunxi_domain_ops,
163 NULL);
164 if (!domain) {
165 pr_err("%s: failed to allocated domain\n", node->full_name);
166 return -ENOMEM;
167 }
168
169 return 0;
170 }
171 IRQCHIP_DECLARE(sunxi_wakeupgen, "allwinner,sunxi-wakeupgen", wakeupgen_init);
172 #else
sunxi_irq_domain_probe(struct platform_device * pdev)173 static int sunxi_irq_domain_probe(struct platform_device *pdev)
174 {
175 struct irq_domain *parent_domain;
176 struct sunxi_irq_domain *intr;
177 struct device_node *parent_node;
178 struct device *dev = &pdev->dev;
179
180 parent_node = of_irq_find_parent(dev_of_node(dev));
181 if (!parent_node) {
182 dev_err(dev, "Failed to get IRQ parent node\n");
183 return -ENODEV;
184 }
185
186 parent_domain = irq_find_host(parent_node);
187 if (!parent_domain) {
188 dev_err(dev, "Failed to find IRQ parent domain\n");
189 return -ENODEV;
190 }
191
192 intr = devm_kzalloc(dev, sizeof(*intr), GFP_KERNEL);
193 if (!intr)
194 return -ENOMEM;
195
196 intr->dev = dev;
197 intr->irqd = irq_domain_add_hierarchy(parent_domain, 0,
198 GIC_SUPPORT_IRQS,
199 dev_of_node(dev),
200 &sunxi_domain_ops,
201 NULL);
202 if (IS_ERR(intr->irqd)) {
203 dev_err(dev, "Failed to allocate IRQ domain\n");
204 return -ENOMEM;
205 }
206 platform_set_drvdata(pdev, intr);
207
208 return 0;
209 }
210
sunxi_irq_domain_remove(struct platform_device * pdev)211 static int sunxi_irq_domain_remove(struct platform_device *pdev)
212 {
213 struct sunxi_irq_domain *intr = platform_get_drvdata(pdev);
214
215 irq_domain_remove(intr->irqd);
216 return 0;
217 }
218
219 static const struct of_device_id sunxi_irq_domain_of_match[] = {
220 { .compatible = "allwinner,sunxi-wakeupgen", },
221 { },
222 };
223 MODULE_DEVICE_TABLE(of, sunxi_irq_domain_of_match);
224
225 static struct platform_driver sunxi_irq_domain_driver = {
226 .probe = sunxi_irq_domain_probe,
227 .remove = sunxi_irq_domain_remove,
228 .driver = {
229 .name = "sunxi_wakeupgen",
230 .of_match_table = sunxi_irq_domain_of_match,
231 },
232 };
233 module_platform_driver(sunxi_irq_domain_driver);
234
235 MODULE_LICENSE("GPL v2");
236 MODULE_DESCRIPTION("Allwinner wakeupgen driver");
237 MODULE_ALIAS("platform: sunxi_wakeupgen");
238 MODULE_AUTHOR("fanqinghua <fanqinghua@allwinnertech.com>");
239 MODULE_VERSION("1.0.0");
240 #endif
241