• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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