blob: 2ec2c7f4bb1b275caed48c2f5e65c87bd3737757 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* arch/arm/mach-msm/htc_pwrsink.c
2 *
3 * Copyright (C) 2008 HTC Corporation
4 * Copyright (C) 2008 Google, Inc.
5 * Author: San Mehat <san@google.com>
6 * Kant Kang <kant_kang@htc.com>
7 * Eiven Peng <eiven_peng@htc.com>
8 *
9 * This software is licensed under the terms of the GNU General Public
10 * License version 2, as published by the Free Software Foundation, and
11 * may be copied, distributed, and modified under those terms.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 */
19
20#include <linux/platform_device.h>
21#include <linux/module.h>
22#include <linux/device.h>
23#include <linux/debugfs.h>
24#include <linux/earlysuspend.h>
25#include <mach/msm_smd.h>
26#include <mach/htc_pwrsink.h>
27
28#include "smd_private.h"
29
30enum {
31 PWRSINK_DEBUG_CURR_CHANGE = 1U << 0,
32 PWRSINK_DEBUG_CURR_CHANGE_AUDIO = 1U << 1,
33};
34static int pwrsink_debug_mask;
35module_param_named(debug_mask, pwrsink_debug_mask, int,
36 S_IRUGO | S_IWUSR | S_IWGRP);
37
38static int initialized;
39static unsigned audio_path = 1; /* HTC_SND_DEVICE_SPEAKER = 1 */
40static struct pwr_sink_audio audio_sink_array[PWRSINK_AUDIO_LAST + 1];
41static struct pwr_sink *sink_array[PWRSINK_LAST + 1];
42static DEFINE_SPINLOCK(sink_lock);
43static DEFINE_SPINLOCK(audio_sink_lock);
44static unsigned long total_sink;
45static uint32_t *smem_total_sink;
46
47int htc_pwrsink_set(pwrsink_id_type id, unsigned percent_utilized)
48{
49 unsigned long flags;
50
51 if (!smem_total_sink)
52 smem_total_sink = smem_alloc(SMEM_ID_VENDOR0, sizeof(uint32_t));
53
54 if (!initialized)
55 return -EAGAIN;
56
57 if (id < 0 || id > PWRSINK_LAST)
58 return -EINVAL;
59
60 spin_lock_irqsave(&sink_lock, flags);
61
62 if (!sink_array[id]) {
63 spin_unlock_irqrestore(&sink_lock, flags);
64 return -ENOENT;
65 }
66
67 if (sink_array[id]->percent_util == percent_utilized) {
68 spin_unlock_irqrestore(&sink_lock, flags);
69 return 0;
70 }
71
72 total_sink -= (sink_array[id]->ua_max *
73 sink_array[id]->percent_util / 100);
74 sink_array[id]->percent_util = percent_utilized;
75 total_sink += (sink_array[id]->ua_max *
76 sink_array[id]->percent_util / 100);
77
78 if (smem_total_sink)
79 *smem_total_sink = total_sink / 1000;
80
81 pr_debug("htc_pwrsink: ID %d, Util %d%%, Total %lu uA %s\n",
82 id, percent_utilized, total_sink,
83 smem_total_sink ? "SET" : "");
84
85 spin_unlock_irqrestore(&sink_lock, flags);
86
87 return 0;
88}
89EXPORT_SYMBOL(htc_pwrsink_set);
90
91static void compute_audio_current(void)
92{
93 /* unsigned long flags; */
94 unsigned max_percent = 0;
95 int i, active_audio_sinks = 0;
96 pwrsink_audio_id_type last_active_audio_sink = 0;
97
98 /* Make sure this segment will be spinlocked
99 before computing by calling function. */
100 /* spin_lock_irqsave(&audio_sink_lock, flags); */
101 for (i = 0; i <= PWRSINK_AUDIO_LAST; ++i) {
102 max_percent = (audio_sink_array[i].percent > max_percent) ?
103 audio_sink_array[i].percent : max_percent;
104 if (audio_sink_array[i].percent > 0) {
105 active_audio_sinks++;
106 last_active_audio_sink = i;
107 }
108 }
109 if (active_audio_sinks == 0)
110 htc_pwrsink_set(PWRSINK_AUDIO, 0);
111 else if (active_audio_sinks == 1) {
112 pwrsink_audio_id_type laas = last_active_audio_sink;
113 /* TODO: add volume and routing path current. */
114 if (audio_path == 1) /* Speaker */
115 htc_pwrsink_set(PWRSINK_AUDIO,
116 audio_sink_array[laas].percent);
117 else
118 htc_pwrsink_set(PWRSINK_AUDIO,
119 audio_sink_array[laas].percent * 9 / 10);
120 } else if (active_audio_sinks > 1) {
121 /* TODO: add volume and routing path current. */
122 if (audio_path == 1) /* Speaker */
123 htc_pwrsink_set(PWRSINK_AUDIO, max_percent);
124 else
125 htc_pwrsink_set(PWRSINK_AUDIO, max_percent * 9 / 10);
126 }
127 /* spin_unlock_irqrestore(&audio_sink_lock, flags); */
128
129 if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
130 pr_info("%s: active_audio_sinks=%d, audio_path=%d\n", __func__,
131 active_audio_sinks, audio_path);
132}
133
134int htc_pwrsink_audio_set(pwrsink_audio_id_type id, unsigned percent_utilized)
135{
136 unsigned long flags;
137
138 if (id < 0 || id > PWRSINK_AUDIO_LAST)
139 return -EINVAL;
140
141 if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
142 pr_info("%s: id=%d, percent=%d, percent_old=%d\n", __func__,
143 id, percent_utilized, audio_sink_array[id].percent);
144
145 spin_lock_irqsave(&audio_sink_lock, flags);
146 if (audio_sink_array[id].percent == percent_utilized) {
147 spin_unlock_irqrestore(&audio_sink_lock, flags);
148 return 0;
149 }
150 audio_sink_array[id].percent = percent_utilized;
151 spin_unlock_irqrestore(&audio_sink_lock, flags);
152 compute_audio_current();
153 return 0;
154}
155EXPORT_SYMBOL(htc_pwrsink_audio_set);
156
157int htc_pwrsink_audio_volume_set(pwrsink_audio_id_type id, unsigned volume)
158{
159 unsigned long flags;
160
161 if (id < 0 || id > PWRSINK_AUDIO_LAST)
162 return -EINVAL;
163
164 if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
165 pr_info("%s: id=%d, volume=%d, volume_old=%d\n", __func__,
166 id, volume, audio_sink_array[id].volume);
167
168 spin_lock_irqsave(&audio_sink_lock, flags);
169 if (audio_sink_array[id].volume == volume) {
170 spin_unlock_irqrestore(&audio_sink_lock, flags);
171 return 0;
172 }
173 audio_sink_array[id].volume = volume;
174 spin_unlock_irqrestore(&audio_sink_lock, flags);
175 compute_audio_current();
176 return 0;
177}
178EXPORT_SYMBOL(htc_pwrsink_audio_volume_set);
179
180int htc_pwrsink_audio_path_set(unsigned path)
181{
182 unsigned long flags;
183
184 if (pwrsink_debug_mask & PWRSINK_DEBUG_CURR_CHANGE_AUDIO)
185 pr_info("%s: path=%d, path_old=%d\n",
186 __func__, path, audio_path);
187
188 spin_lock_irqsave(&audio_sink_lock, flags);
189 if (audio_path == path) {
190 spin_unlock_irqrestore(&audio_sink_lock, flags);
191 return 0;
192 }
193 audio_path = path;
194 spin_unlock_irqrestore(&audio_sink_lock, flags);
195 compute_audio_current();
196 return 0;
197}
198EXPORT_SYMBOL(htc_pwrsink_audio_path_set);
199
200void htc_pwrsink_suspend_early(struct early_suspend *h)
201{
202 htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 70);
203}
204
205int htc_pwrsink_suspend_late(struct platform_device *pdev, pm_message_t state)
206{
207 struct pwr_sink_platform_data *pdata = pdev->dev.platform_data;
208
209 if (pdata && pdata->suspend_late)
210 pdata->suspend_late(pdev, state);
211 else
212 htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 13);
213 return 0;
214}
215
216int htc_pwrsink_resume_early(struct platform_device *pdev)
217{
218 struct pwr_sink_platform_data *pdata = pdev->dev.platform_data;
219
220 if (pdata && pdata->resume_early)
221 pdata->resume_early(pdev);
222 else
223 htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 70);
224 return 0;
225}
226
227void htc_pwrsink_resume_late(struct early_suspend *h)
228{
229 htc_pwrsink_set(PWRSINK_SYSTEM_LOAD, 100);
230}
231
232struct early_suspend htc_pwrsink_early_suspend = {
233 .level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 1,
234 .suspend = htc_pwrsink_suspend_early,
235 .resume = htc_pwrsink_resume_late,
236};
237
238static int __init htc_pwrsink_probe(struct platform_device *pdev)
239{
240 struct pwr_sink_platform_data *pdata = pdev->dev.platform_data;
241 int i;
242
243 if (!pdata)
244 return -EINVAL;
245
246 total_sink = 0;
247 for (i = 0; i < pdata->num_sinks; i++) {
248 sink_array[pdata->sinks[i].id] = &pdata->sinks[i];
249 total_sink += (pdata->sinks[i].ua_max *
250 pdata->sinks[i].percent_util / 100);
251 }
252
253 initialized = 1;
254
255 if (pdata->suspend_early)
256 htc_pwrsink_early_suspend.suspend = pdata->suspend_early;
257 if (pdata->resume_late)
258 htc_pwrsink_early_suspend.resume = pdata->resume_late;
259 register_early_suspend(&htc_pwrsink_early_suspend);
260
261 return 0;
262}
263
264static struct platform_driver htc_pwrsink_driver = {
265 .probe = htc_pwrsink_probe,
266 .suspend_late = htc_pwrsink_suspend_late,
267 .resume_early = htc_pwrsink_resume_early,
268 .driver = {
269 .name = "htc_pwrsink",
270 .owner = THIS_MODULE,
271 },
272};
273
274static int __init htc_pwrsink_init(void)
275{
276 initialized = 0;
277 memset(sink_array, 0, sizeof(sink_array));
278 return platform_driver_register(&htc_pwrsink_driver);
279}
280
281module_init(htc_pwrsink_init);