blob: ea368ae2ba56b425dbed3661bf66e927cf0fe029 [file] [log] [blame]
Praveen Chidambaram85b7b282012-04-16 13:45:15 -06001/* Copyright (c) 2012, Code Aurora Forum. 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/module.h>
15#include <linux/kernel.h>
16#include <linux/init.h>
17#include <linux/slab.h>
18#include <linux/platform_device.h>
19#include <linux/of.h>
20#include <mach/mpm.h>
Girish Mahadevan40abbe12012-04-25 14:58:13 -060021#include "lpm_resources.h"
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -060022#include "pm.h"
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060023
24static struct msm_rpmrs_level *msm_lpm_levels;
25static int msm_lpm_level_count;
26
Girish Mahadevan40abbe12012-04-25 14:58:13 -060027static void msm_lpm_level_update(void)
28{
29 unsigned int lpm_level;
30 struct msm_rpmrs_level *level = NULL;
31
32 for (lpm_level = 0; lpm_level < msm_lpm_level_count; lpm_level++) {
33 level = &msm_lpm_levels[lpm_level];
34 level->available =
35 !msm_lpm_level_beyond_limit(&level->rs_limits);
36 }
37}
38
39int msm_lpm_enter_sleep(uint32_t sclk_count, void *limits,
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060040 bool from_idle, bool notify_rpm)
41{
Girish Mahadevan40abbe12012-04-25 14:58:13 -060042 int ret = 0;
43
44 ret = msm_lpmrs_enter_sleep((struct msm_rpmrs_limits *)limits,
45 from_idle, notify_rpm);
46 return ret;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060047}
48
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -060049static void msm_lpm_exit_sleep(void *limits, bool from_idle,
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060050 bool notify_rpm, bool collapsed)
51{
Girish Mahadevana9964a52012-06-29 10:14:09 -060052 msm_lpmrs_exit_sleep((struct msm_rpmrs_limits *)limits,
53 from_idle, notify_rpm, collapsed);
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060054}
55
Girish Mahadevan40abbe12012-04-25 14:58:13 -060056void msm_lpm_show_resources(void)
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060057{
58 /* TODO */
59 return;
60}
61
Stephen Boyd3f4bac22012-05-30 10:03:13 -070062s32 msm_cpuidle_get_deep_idle_latency(void)
63{
64 int i;
65 struct msm_rpmrs_level *level = msm_lpm_levels, *best = level;
66
67 if (!level)
68 return 0;
69
70 for (i = 0; i < msm_lpm_level_count; i++, level++) {
71 if (!level->available)
72 continue;
73 if (level->sleep_mode != MSM_PM_SLEEP_MODE_POWER_COLLAPSE)
74 continue;
75 /* Pick the first power collapse mode by default */
76 if (best->sleep_mode != MSM_PM_SLEEP_MODE_POWER_COLLAPSE)
77 best = level;
78 /* Find the lowest latency for power collapse */
79 if (level->latency_us < best->latency_us)
80 best = level;
81 }
82 return best->latency_us - 1;
83}
84
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -060085static void *msm_lpm_lowest_limits(bool from_idle,
86 enum msm_pm_sleep_mode sleep_mode, uint32_t latency_us,
87 uint32_t sleep_us, uint32_t *power)
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060088{
89 unsigned int cpu = smp_processor_id();
90 struct msm_rpmrs_level *best_level = NULL;
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -060091 uint32_t pwr;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060092 int i;
93
94 if (!msm_lpm_levels)
95 return NULL;
96
Girish Mahadevan40abbe12012-04-25 14:58:13 -060097 msm_lpm_level_update();
Praveen Chidambaram85b7b282012-04-16 13:45:15 -060098
99 for (i = 0; i < msm_lpm_level_count; i++) {
100 struct msm_rpmrs_level *level = &msm_lpm_levels[i];
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600101
102 if (!level->available)
103 continue;
104
105 if (sleep_mode != level->sleep_mode)
106 continue;
107
108 if (latency_us < level->latency_us)
109 continue;
110
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600111 if (sleep_us <= 1) {
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600112 pwr = level->energy_overhead;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600113 } else if (sleep_us <= level->time_overhead_us) {
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600114 pwr = level->energy_overhead / sleep_us;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600115 } else if ((sleep_us >> 10) > level->time_overhead_us) {
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600116 pwr = level->steady_state_power;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600117 } else {
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600118 pwr = level->steady_state_power;
119 pwr -= (level->time_overhead_us *
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600120 level->steady_state_power)/sleep_us;
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600121 pwr += level->energy_overhead / sleep_us;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600122 }
123
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600124 if (!best_level || best_level->rs_limits.power[cpu] >= pwr) {
125
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600126 level->rs_limits.latency_us[cpu] = level->latency_us;
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600127 level->rs_limits.power[cpu] = pwr;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600128 best_level = level;
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600129
130 if (power)
131 *power = pwr;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600132 }
133 }
134
135 return best_level ? &best_level->rs_limits : NULL;
136}
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600137static struct msm_pm_sleep_ops msm_lpm_ops = {
138 .lowest_limits = msm_lpm_lowest_limits,
139 .enter_sleep = msm_lpm_enter_sleep,
140 .exit_sleep = msm_lpm_exit_sleep,
141};
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600142
143static int __devinit msm_lpm_levels_probe(struct platform_device *pdev)
144{
145 struct msm_rpmrs_level *levels = NULL;
146 struct msm_rpmrs_level *level = NULL;
147 struct device_node *node = NULL;
148 char *key = NULL;
149 uint32_t val = 0;
150 int ret = 0;
151 uint32_t num_levels = 0;
152 int idx = 0;
153
154 for_each_child_of_node(pdev->dev.of_node, node)
155 num_levels++;
156
157 levels = kzalloc(num_levels * sizeof(struct msm_rpmrs_level),
158 GFP_KERNEL);
159 if (!levels)
160 return -ENOMEM;
161
162 for_each_child_of_node(pdev->dev.of_node, node) {
163 level = &levels[idx++];
164 level->available = false;
165
166 key = "qcom,mode";
167 ret = of_property_read_u32(node, key, &val);
168 if (ret)
169 goto fail;
170 level->sleep_mode = val;
171
172 key = "qcom,xo";
173 ret = of_property_read_u32(node, key, &val);
174 if (ret)
175 goto fail;
176 level->rs_limits.pxo = val;
177
178 key = "qcom,l2";
179 ret = of_property_read_u32(node, key, &val);
180 if (ret)
181 goto fail;
182 level->rs_limits.l2_cache = val;
183
184 key = "qcom,vdd-dig-upper-bound";
185 ret = of_property_read_u32(node, key, &val);
186 if (ret)
187 goto fail;
188 level->rs_limits.vdd_dig_upper_bound = val;
189
190 key = "qcom,vdd-dig-lower-bound";
191 ret = of_property_read_u32(node, key, &val);
192 if (ret)
193 goto fail;
Girish Mahadevan40abbe12012-04-25 14:58:13 -0600194 level->rs_limits.vdd_dig_lower_bound = val;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600195
196 key = "qcom,vdd-mem-upper-bound";
197 ret = of_property_read_u32(node, key, &val);
198 if (ret)
199 goto fail;
200 level->rs_limits.vdd_mem_upper_bound = val;
201
202 key = "qcom,vdd-mem-lower-bound";
203 ret = of_property_read_u32(node, key, &val);
204 if (ret)
205 goto fail;
Girish Mahadevan40abbe12012-04-25 14:58:13 -0600206 level->rs_limits.vdd_mem_lower_bound = val;
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600207
208 key = "qcom,latency-us";
209 ret = of_property_read_u32(node, key, &val);
210 if (ret)
211 goto fail;
212 level->latency_us = val;
213
214 key = "qcom,ss-power";
215 ret = of_property_read_u32(node, key, &val);
216 if (ret)
217 goto fail;
218 level->steady_state_power = val;
219
220 key = "qcom,energy-overhead";
221 ret = of_property_read_u32(node, key, &val);
222 if (ret)
223 goto fail;
224 level->energy_overhead = val;
225
226 key = "qcom,time-overhead";
227 ret = of_property_read_u32(node, key, &val);
228 if (ret)
229 goto fail;
230 level->time_overhead_us = val;
231
232 level->available = true;
233 }
234
235 msm_lpm_levels = levels;
236 msm_lpm_level_count = idx;
237
Mahesh Sivasubramanian6d06e3a2012-05-16 13:41:07 -0600238 msm_pm_set_sleep_ops(&msm_lpm_ops);
239
Praveen Chidambaram85b7b282012-04-16 13:45:15 -0600240 return 0;
241fail:
242 pr_err("%s: Error in name %s key %s\n", __func__, node->full_name, key);
243 kfree(levels);
244 return -EFAULT;
245}
246
247static struct of_device_id msm_lpm_levels_match_table[] = {
248 {.compatible = "qcom,lpm-levels"},
249 {},
250};
251
252static struct platform_driver msm_lpm_levels_driver = {
253 .probe = msm_lpm_levels_probe,
254 .driver = {
255 .name = "lpm-levels",
256 .owner = THIS_MODULE,
257 .of_match_table = msm_lpm_levels_match_table,
258 },
259};
260
261static int __init msm_lpm_levels_module_init(void)
262{
263 return platform_driver_register(&msm_lpm_levels_driver);
264}
265late_initcall(msm_lpm_levels_module_init);