summaryrefslogtreecommitdiffstats
path: root/drivers/irqchip/irq-gic-pm.c
diff options
context:
space:
mode:
authorJon Hunter <jonathanh@nvidia.com>2016-06-07 11:12:34 -0400
committerMarc Zyngier <marc.zyngier@arm.com>2016-06-13 06:53:52 -0400
commit9c8edddfc9924cb473a7570c37ca466db70728f8 (patch)
treea6b1cae251ef6fa88d2a3310e30ddfc2cdc254bb /drivers/irqchip/irq-gic-pm.c
parent39f8f23d13666c4c5644e5add7d9598d9e798f22 (diff)
irqchip/gic: Add platform driver for non-root GICs that require RPM
Add a platform driver to support non-root GICs that require runtime power-management. Currently, only non-root GICs are supported because the functions, smp_cross_call() and set_handle_irq(), that need to be called for a root controller are located in the __init section and so cannot be called by the platform driver. The GIC platform driver re-uses many functions from the existing GIC driver including some functions to save and restore the GIC context during power transitions. The functions for saving and restoring the GIC context are currently only defined if CONFIG_CPU_PM is enabled and to ensure that these functions are always defined when the platform driver is enabled, a dependency on CONFIG_ARM_GIC_PM (which selects the platform driver) has been added. In order to re-use the private GIC initialisation code, a new public function, gic_of_init_child(), has been added which calls various private functions to initialise the GIC. This is different from the existing gic_of_init() because it only supports non-root GICs (ie. does not call smp_cross_call() is set_handle_irq()) and is not located in the __init section (so can be used by platform drivers). Furthermore, gic_of_init_child() dynamically allocates memory for the GIC chip data which is also different from gic_of_init(). There is no specific suspend handling for GICs registered as platform devices. Non-wakeup interrupts will be disabled by the kernel during late suspend, however, this alone will not power down the GIC if interrupts have been requested and not freed. Therefore, requestors of non-wakeup interrupts will need to free them on entering suspend in order to power-down the GIC. Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Signed-off-by: Marc Zyngier <marc.zyngier@arm.com>
Diffstat (limited to 'drivers/irqchip/irq-gic-pm.c')
-rw-r--r--drivers/irqchip/irq-gic-pm.c184
1 files changed, 184 insertions, 0 deletions
diff --git a/drivers/irqchip/irq-gic-pm.c b/drivers/irqchip/irq-gic-pm.c
new file mode 100644
index 000000000000..4cbffba3ff13
--- /dev/null
+++ b/drivers/irqchip/irq-gic-pm.c
@@ -0,0 +1,184 @@
1/*
2 * Copyright (C) 2016 NVIDIA CORPORATION, All Rights Reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16#include <linux/module.h>
17#include <linux/clk.h>
18#include <linux/of_device.h>
19#include <linux/of_irq.h>
20#include <linux/irqchip/arm-gic.h>
21#include <linux/platform_device.h>
22#include <linux/pm_clock.h>
23#include <linux/pm_runtime.h>
24#include <linux/slab.h>
25
26struct gic_clk_data {
27 unsigned int num_clocks;
28 const char *const *clocks;
29};
30
31static int gic_runtime_resume(struct device *dev)
32{
33 struct gic_chip_data *gic = dev_get_drvdata(dev);
34 int ret;
35
36 ret = pm_clk_resume(dev);
37 if (ret)
38 return ret;
39
40 /*
41 * On the very first resume, the pointer to the driver data
42 * will be NULL and this is intentional, because we do not
43 * want to restore the GIC on the very first resume. So if
44 * the pointer is not valid just return.
45 */
46 if (!gic)
47 return 0;
48
49 gic_dist_restore(gic);
50 gic_cpu_restore(gic);
51
52 return 0;
53}
54
55static int gic_runtime_suspend(struct device *dev)
56{
57 struct gic_chip_data *gic = dev_get_drvdata(dev);
58
59 gic_dist_save(gic);
60 gic_cpu_save(gic);
61
62 return pm_clk_suspend(dev);
63}
64
65static int gic_get_clocks(struct device *dev, const struct gic_clk_data *data)
66{
67 struct clk *clk;
68 unsigned int i;
69 int ret;
70
71 if (!dev || !data)
72 return -EINVAL;
73
74 ret = pm_clk_create(dev);
75 if (ret)
76 return ret;
77
78 for (i = 0; i < data->num_clocks; i++) {
79 clk = of_clk_get_by_name(dev->of_node, data->clocks[i]);
80 if (IS_ERR(clk)) {
81 dev_err(dev, "failed to get clock %s\n",
82 data->clocks[i]);
83 ret = PTR_ERR(clk);
84 goto error;
85 }
86
87 ret = pm_clk_add_clk(dev, clk);
88 if (ret) {
89 dev_err(dev, "failed to add clock at index %d\n", i);
90 clk_put(clk);
91 goto error;
92 }
93 }
94
95 return 0;
96
97error:
98 pm_clk_destroy(dev);
99
100 return ret;
101}
102
103static int gic_probe(struct platform_device *pdev)
104{
105 struct device *dev = &pdev->dev;
106 const struct gic_clk_data *data;
107 struct gic_chip_data *gic;
108 int ret, irq;
109
110 data = of_device_get_match_data(&pdev->dev);
111 if (!data) {
112 dev_err(&pdev->dev, "no device match found\n");
113 return -ENODEV;
114 }
115
116 irq = irq_of_parse_and_map(dev->of_node, 0);
117 if (!irq) {
118 dev_err(dev, "no parent interrupt found!\n");
119 return -EINVAL;
120 }
121
122 ret = gic_get_clocks(dev, data);
123 if (ret)
124 goto irq_dispose;
125
126 pm_runtime_enable(dev);
127
128 ret = pm_runtime_get_sync(dev);
129 if (ret < 0)
130 goto rpm_disable;
131
132 ret = gic_of_init_child(dev, &gic, irq);
133 if (ret)
134 goto rpm_put;
135
136 platform_set_drvdata(pdev, gic);
137
138 pm_runtime_put(dev);
139
140 dev_info(dev, "GIC IRQ controller registered\n");
141
142 return 0;
143
144rpm_put:
145 pm_runtime_put_sync(dev);
146rpm_disable:
147 pm_runtime_disable(dev);
148 pm_clk_destroy(dev);
149irq_dispose:
150 irq_dispose_mapping(irq);
151
152 return ret;
153}
154
155static const struct dev_pm_ops gic_pm_ops = {
156 SET_RUNTIME_PM_OPS(gic_runtime_suspend,
157 gic_runtime_resume, NULL)
158};
159
160static const char * const gic400_clocks[] = {
161 "clk",
162};
163
164static const struct gic_clk_data gic400_data = {
165 .num_clocks = ARRAY_SIZE(gic400_clocks),
166 .clocks = gic400_clocks,
167};
168
169static const struct of_device_id gic_match[] = {
170 { .compatible = "nvidia,tegra210-agic", .data = &gic400_data },
171 {},
172};
173MODULE_DEVICE_TABLE(of, gic_match);
174
175static struct platform_driver gic_driver = {
176 .probe = gic_probe,
177 .driver = {
178 .name = "gic",
179 .of_match_table = gic_match,
180 .pm = &gic_pm_ops,
181 }
182};
183
184builtin_platform_driver(gic_driver);