Commit | Line | Data |
---|---|---|
97fb5e8d | 1 | // SPDX-License-Identifier: GPL-2.0-only |
7ce75bb2 LI |
2 | /* |
3 | * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. | |
4 | * Copyright (c) 2014,2015, Linaro Ltd. | |
5 | * | |
3b904b04 | 6 | * SAW power controller driver |
7ce75bb2 LI |
7 | */ |
8 | ||
7ce75bb2 LI |
9 | #include <linux/kernel.h> |
10 | #include <linux/init.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/slab.h> | |
13 | #include <linux/of.h> | |
14 | #include <linux/of_address.h> | |
15 | #include <linux/of_device.h> | |
16 | #include <linux/err.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/cpuidle.h> | |
19 | #include <linux/cpu_pm.h> | |
20 | #include <linux/qcom_scm.h> | |
60f3692b | 21 | #include <soc/qcom/spm.h> |
7ce75bb2 | 22 | |
7ce75bb2 LI |
23 | #include <asm/proc-fns.h> |
24 | #include <asm/suspend.h> | |
25 | ||
a871be6b SG |
26 | #include "dt_idle_states.h" |
27 | ||
60f3692b | 28 | struct cpuidle_qcom_spm_data { |
a871be6b | 29 | struct cpuidle_driver cpuidle_driver; |
60f3692b | 30 | struct spm_driver_data *spm; |
7ce75bb2 LI |
31 | }; |
32 | ||
7ce75bb2 LI |
33 | static int qcom_pm_collapse(unsigned long int unused) |
34 | { | |
35 | qcom_scm_cpu_power_down(QCOM_SCM_CPU_PWR_DOWN_L2_ON); | |
36 | ||
37 | /* | |
38 | * Returns here only if there was a pending interrupt and we did not | |
39 | * power down as a result. | |
40 | */ | |
41 | return -1; | |
42 | } | |
43 | ||
a871be6b | 44 | static int qcom_cpu_spc(struct spm_driver_data *drv) |
7ce75bb2 LI |
45 | { |
46 | int ret; | |
7ce75bb2 LI |
47 | |
48 | spm_set_low_power_mode(drv, PM_SLEEP_MODE_SPC); | |
49 | ret = cpu_suspend(0, qcom_pm_collapse); | |
50 | /* | |
51 | * ARM common code executes WFI without calling into our driver and | |
52 | * if the SPM mode is not reset, then we may accidently power down the | |
53 | * cpu when we intended only to gate the cpu clock. | |
54 | * Ensure the state is set to standby before returning. | |
55 | */ | |
56 | spm_set_low_power_mode(drv, PM_SLEEP_MODE_STBY); | |
57 | ||
58 | return ret; | |
59 | } | |
60 | ||
a871be6b SG |
61 | static int spm_enter_idle_state(struct cpuidle_device *dev, |
62 | struct cpuidle_driver *drv, int idx) | |
7ce75bb2 | 63 | { |
60f3692b ADR |
64 | struct cpuidle_qcom_spm_data *data = container_of(drv, struct cpuidle_qcom_spm_data, |
65 | cpuidle_driver); | |
a871be6b | 66 | |
60f3692b | 67 | return CPU_PM_CPU_IDLE_ENTER_PARAM(qcom_cpu_spc, idx, data->spm); |
7ce75bb2 LI |
68 | } |
69 | ||
a871be6b SG |
70 | static struct cpuidle_driver qcom_spm_idle_driver = { |
71 | .name = "qcom_spm", | |
72 | .owner = THIS_MODULE, | |
73 | .states[0] = { | |
74 | .enter = spm_enter_idle_state, | |
75 | .exit_latency = 1, | |
76 | .target_residency = 1, | |
77 | .power_usage = UINT_MAX, | |
78 | .name = "WFI", | |
79 | .desc = "ARM WFI", | |
80 | } | |
81 | }; | |
82 | ||
83 | static const struct of_device_id qcom_idle_state_match[] = { | |
84 | { .compatible = "qcom,idle-state-spc", .data = spm_enter_idle_state }, | |
7ce75bb2 LI |
85 | { }, |
86 | }; | |
87 | ||
60f3692b | 88 | static int spm_cpuidle_register(struct device *cpuidle_dev, int cpu) |
7ce75bb2 | 89 | { |
60f3692b ADR |
90 | struct platform_device *pdev = NULL; |
91 | struct device_node *cpu_node, *saw_node; | |
92 | struct cpuidle_qcom_spm_data *data = NULL; | |
a871be6b | 93 | int ret; |
7ce75bb2 | 94 | |
60f3692b ADR |
95 | cpu_node = of_cpu_device_node_get(cpu); |
96 | if (!cpu_node) | |
97 | return -ENODEV; | |
7ce75bb2 | 98 | |
60f3692b ADR |
99 | saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); |
100 | if (!saw_node) | |
101 | return -ENODEV; | |
7ce75bb2 | 102 | |
60f3692b ADR |
103 | pdev = of_find_device_by_node(saw_node); |
104 | of_node_put(saw_node); | |
105 | of_node_put(cpu_node); | |
106 | if (!pdev) | |
107 | return -ENODEV; | |
7ce75bb2 | 108 | |
60f3692b ADR |
109 | data = devm_kzalloc(cpuidle_dev, sizeof(*data), GFP_KERNEL); |
110 | if (!data) | |
111 | return -ENOMEM; | |
7ce75bb2 | 112 | |
60f3692b ADR |
113 | data->spm = dev_get_drvdata(&pdev->dev); |
114 | if (!data->spm) | |
115 | return -EINVAL; | |
7ce75bb2 | 116 | |
60f3692b ADR |
117 | data->cpuidle_driver = qcom_spm_idle_driver; |
118 | data->cpuidle_driver.cpumask = (struct cpumask *)cpumask_of(cpu); | |
7ce75bb2 | 119 | |
60f3692b ADR |
120 | ret = dt_init_idle_driver(&data->cpuidle_driver, |
121 | qcom_idle_state_match, 1); | |
122 | if (ret <= 0) | |
123 | return ret ? : -ENODEV; | |
7ce75bb2 | 124 | |
60f3692b ADR |
125 | return cpuidle_register(&data->cpuidle_driver, NULL); |
126 | } | |
7ce75bb2 | 127 | |
60f3692b | 128 | static int spm_cpuidle_drv_probe(struct platform_device *pdev) |
7ce75bb2 | 129 | { |
a871be6b SG |
130 | int cpu, ret; |
131 | ||
132 | if (!qcom_scm_is_available()) | |
133 | return -EPROBE_DEFER; | |
7ce75bb2 | 134 | |
52beb1fc SG |
135 | ret = qcom_scm_set_warm_boot_addr(cpu_resume_arm); |
136 | if (ret) | |
137 | return dev_err_probe(&pdev->dev, ret, "set warm boot addr failed"); | |
138 | ||
60f3692b ADR |
139 | for_each_possible_cpu(cpu) { |
140 | ret = spm_cpuidle_register(&pdev->dev, cpu); | |
141 | if (ret && ret != -ENODEV) { | |
142 | dev_err(&pdev->dev, | |
143 | "Cannot register for CPU%d: %d\n", cpu, ret); | |
144 | } | |
145 | } | |
7ce75bb2 | 146 | |
60f3692b ADR |
147 | return 0; |
148 | } | |
7ce75bb2 | 149 | |
60f3692b ADR |
150 | static struct platform_driver spm_cpuidle_driver = { |
151 | .probe = spm_cpuidle_drv_probe, | |
152 | .driver = { | |
153 | .name = "qcom-spm-cpuidle", | |
154 | .suppress_bind_attrs = true, | |
155 | }, | |
156 | }; | |
7ce75bb2 | 157 | |
0ee30ace SG |
158 | static bool __init qcom_spm_find_any_cpu(void) |
159 | { | |
160 | struct device_node *cpu_node, *saw_node; | |
161 | ||
162 | for_each_of_cpu_node(cpu_node) { | |
163 | saw_node = of_parse_phandle(cpu_node, "qcom,saw", 0); | |
164 | if (of_device_is_available(saw_node)) { | |
165 | of_node_put(saw_node); | |
166 | of_node_put(cpu_node); | |
167 | return true; | |
168 | } | |
169 | of_node_put(saw_node); | |
170 | } | |
171 | return false; | |
172 | } | |
173 | ||
60f3692b ADR |
174 | static int __init qcom_spm_cpuidle_init(void) |
175 | { | |
176 | struct platform_device *pdev; | |
177 | int ret; | |
7ce75bb2 | 178 | |
60f3692b | 179 | ret = platform_driver_register(&spm_cpuidle_driver); |
a871be6b SG |
180 | if (ret) |
181 | return ret; | |
182 | ||
0ee30ace SG |
183 | /* Make sure there is actually any CPU managed by the SPM */ |
184 | if (!qcom_spm_find_any_cpu()) | |
185 | return 0; | |
186 | ||
60f3692b ADR |
187 | pdev = platform_device_register_simple("qcom-spm-cpuidle", |
188 | -1, NULL, 0); | |
189 | if (IS_ERR(pdev)) { | |
190 | platform_driver_unregister(&spm_cpuidle_driver); | |
191 | return PTR_ERR(pdev); | |
192 | } | |
7ce75bb2 LI |
193 | |
194 | return 0; | |
195 | } | |
60f3692b | 196 | device_initcall(qcom_spm_cpuidle_init); |