blob: 936fd6b115df628f444e3bc2e92d1a33f0f7319c [file] [log] [blame]
Stephen Boyd0275f932012-01-23 18:42:35 -08001/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
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#include <linux/kernel.h>
14#include <linux/slab.h>
15#include <linux/init.h>
16#include <linux/err.h>
17#include <linux/module.h>
18#include <linux/spinlock.h>
19#include <linux/debugfs.h>
20#include <linux/list.h>
21#include <linux/seq_file.h>
Stephen Boydb60f3862012-02-22 15:18:45 -080022#include <linux/uaccess.h>
23#include <linux/string.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070024
25#include <mach/msm_xo.h>
26#include <mach/rpm.h>
Tianyi Gou41515e22011-09-01 19:37:43 -070027#include <mach/socinfo.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070028
29#include "rpm_resources.h"
30
31static DEFINE_SPINLOCK(msm_xo_lock);
32
33struct msm_xo {
34 unsigned votes[NUM_MSM_XO_MODES];
35 unsigned mode;
36 struct list_head voters;
37};
38
39struct msm_xo_voter {
40 const char *name;
41 unsigned mode;
42 struct msm_xo *xo;
43 struct list_head list;
44};
45
46static struct msm_xo msm_xo_sources[NUM_MSM_XO_IDS];
47
48#ifdef CONFIG_DEBUG_FS
Stephen Boydb60f3862012-02-22 15:18:45 -080049static const char *msm_xo_to_str[NUM_MSM_XO_IDS] = {
50 [MSM_XO_TCXO_D0] = "D0",
51 [MSM_XO_TCXO_D1] = "D1",
52 [MSM_XO_TCXO_A0] = "A0",
53 [MSM_XO_TCXO_A1] = "A1",
54 [MSM_XO_TCXO_A2] = "A2",
55 [MSM_XO_CORE] = "CORE",
56};
57
58static const char *msm_xo_mode_to_str[NUM_MSM_XO_MODES] = {
59 [MSM_XO_MODE_ON] = "ON",
60 [MSM_XO_MODE_PIN_CTRL] = "PIN",
61 [MSM_XO_MODE_OFF] = "OFF",
62};
63
64static int msm_xo_debugfs_open(struct inode *inode, struct file *filp)
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070065{
Stephen Boydb60f3862012-02-22 15:18:45 -080066 filp->private_data = inode->i_private;
67 return 0;
68}
69
70static ssize_t msm_xo_debugfs_read(struct file *filp, char __user *ubuf,
71 size_t cnt, loff_t *ppos)
72{
73 int r;
74 char buf[10];
75 struct msm_xo_voter *xo = filp->private_data;
76
77 r = snprintf(buf, sizeof(buf), "%s\n", msm_xo_mode_to_str[xo->mode]);
78 return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
79}
80
81static ssize_t msm_xo_debugfs_write(struct file *filp,
82 const char __user *ubuf, size_t cnt, loff_t *ppos)
83{
84 struct msm_xo_voter *xo = filp->private_data;
85 char buf[10], *b;
86 int i, ret;
87
88 if (cnt > sizeof(buf) - 1)
89 return -EINVAL;
90
91 if (copy_from_user(&buf, ubuf, cnt))
92 return -EFAULT;
93 buf[cnt] = '\0';
94 b = strstrip(buf);
95
96 for (i = 0; i < ARRAY_SIZE(msm_xo_mode_to_str); i++)
97 if (!strncasecmp(b, msm_xo_mode_to_str[i], sizeof(buf))) {
98 ret = msm_xo_mode_vote(xo, i);
99 return ret ? : cnt;
100 }
101
102 return -EINVAL;
103}
104
105static const struct file_operations msm_xo_debugfs_fops = {
106 .open = msm_xo_debugfs_open,
107 .read = msm_xo_debugfs_read,
108 .write = msm_xo_debugfs_write,
109};
110
111static struct dentry *xo_debugfs_root;
112static struct msm_xo_voter *xo_debugfs_voters[NUM_MSM_XO_IDS];
113
114static int __init msm_xo_init_debugfs_voters(void)
115{
116 int i;
117
118 xo_debugfs_root = debugfs_create_dir("msm_xo", NULL);
119 if (!xo_debugfs_root)
120 return -ENOMEM;
121
122 for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++) {
123 xo_debugfs_voters[i] = msm_xo_get(i, "debugfs");
124 if (IS_ERR(xo_debugfs_voters[i]))
125 goto err;
126 debugfs_create_file(msm_xo_to_str[i], S_IRUGO | S_IWUSR,
127 xo_debugfs_root, xo_debugfs_voters[i],
128 &msm_xo_debugfs_fops);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700129 }
Stephen Boydb60f3862012-02-22 15:18:45 -0800130 return 0;
131err:
132 while (--i >= 0)
133 msm_xo_put(xo_debugfs_voters[i]);
134 debugfs_remove_recursive(xo_debugfs_root);
135 return -ENOMEM;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700136}
137
138static void msm_xo_dump_xo(struct seq_file *m, struct msm_xo *xo,
139 const char *name)
140{
141 struct msm_xo_voter *voter;
142
Stephen Boydb60f3862012-02-22 15:18:45 -0800143 seq_printf(m, "CXO %-16s%s\n", name, msm_xo_mode_to_str[xo->mode]);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700144 list_for_each_entry(voter, &xo->voters, list)
145 seq_printf(m, " %s %-16s %s\n",
146 xo->mode == voter->mode ? "*" : " ",
147 voter->name,
Stephen Boydb60f3862012-02-22 15:18:45 -0800148 msm_xo_mode_to_str[voter->mode]);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700149}
150
151static int msm_xo_show_voters(struct seq_file *m, void *v)
152{
153 unsigned long flags;
Stephen Boydb60f3862012-02-22 15:18:45 -0800154 int i;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700155
156 spin_lock_irqsave(&msm_xo_lock, flags);
Stephen Boydb60f3862012-02-22 15:18:45 -0800157 for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++)
158 msm_xo_dump_xo(m, &msm_xo_sources[i], msm_xo_to_str[i]);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700159 spin_unlock_irqrestore(&msm_xo_lock, flags);
160
161 return 0;
162}
163
164static int msm_xo_voters_open(struct inode *inode, struct file *file)
165{
166 return single_open(file, msm_xo_show_voters, inode->i_private);
167}
168
169static const struct file_operations msm_xo_voters_ops = {
170 .open = msm_xo_voters_open,
171 .read = seq_read,
172 .llseek = seq_lseek,
173 .release = seq_release,
174};
175
176static int __init msm_xo_debugfs_init(void)
177{
Stephen Boydb60f3862012-02-22 15:18:45 -0800178 msm_xo_init_debugfs_voters();
179 if (!debugfs_create_file("xo_voters", S_IRUGO, NULL, NULL,
180 &msm_xo_voters_ops))
181 return -ENOMEM;
182 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700183}
Stephen Boyda70c5432012-02-10 14:31:45 -0800184#else
185static int __init msm_xo_debugfs_init(void) { return 0; }
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700186#endif
187
188static int msm_xo_update_vote(struct msm_xo *xo)
189{
190 int ret;
Stephen Boyd47239d52012-01-26 14:38:40 -0800191 unsigned vote, prev_vote = xo->mode;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700192 struct msm_rpm_iv_pair cmd;
193
194 if (xo->votes[MSM_XO_MODE_ON])
195 vote = MSM_XO_MODE_ON;
196 else if (xo->votes[MSM_XO_MODE_PIN_CTRL])
197 vote = MSM_XO_MODE_PIN_CTRL;
198 else
199 vote = MSM_XO_MODE_OFF;
200
201 if (vote == prev_vote)
202 return 0;
203
204 /*
205 * Change the vote here to simplify the TCXO logic. If the RPM
206 * command fails we'll rollback.
207 */
208 xo->mode = vote;
Stephen Boyd47239d52012-01-26 14:38:40 -0800209 cmd.id = MSM_RPM_ID_CXO_BUFFERS;
210 cmd.value = (msm_xo_sources[MSM_XO_TCXO_D0].mode << 0) |
211 (msm_xo_sources[MSM_XO_TCXO_D1].mode << 8) |
212 (msm_xo_sources[MSM_XO_TCXO_A0].mode << 16) |
213 (msm_xo_sources[MSM_XO_TCXO_A1].mode << 24) |
214 (msm_xo_sources[MSM_XO_TCXO_A2].mode << 28) |
215 /*
216 * 8660 RPM has XO_CORE at bit 18 and 8960 RPM has
217 * XO_CORE at bit 20. Since the opposite bit is
218 * reserved in both cases, just set both and be
219 * done with it.
220 */
221 ((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 20) |
222 ((msm_xo_sources[MSM_XO_CORE].mode ? 1 : 0) << 18);
223 ret = msm_rpm_set_noirq(MSM_RPM_CTX_SET_0, &cmd, 1);
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700224
225 if (ret)
226 xo->mode = prev_vote;
227
228 return ret;
229}
230
231static int __msm_xo_mode_vote(struct msm_xo_voter *xo_voter, unsigned mode)
232{
233 int ret;
234 struct msm_xo *xo = xo_voter->xo;
235
236 if (xo_voter->mode == mode)
237 return 0;
238
239 xo->votes[mode]++;
240 xo->votes[xo_voter->mode]--;
241 ret = msm_xo_update_vote(xo);
242 if (ret) {
243 xo->votes[xo_voter->mode]++;
244 xo->votes[mode]--;
245 goto out;
246 }
247 xo_voter->mode = mode;
248out:
249 return ret;
250}
251
252/**
253 * msm_xo_mode_vote() - Vote for an XO to be ON, OFF, or under PIN_CTRL
254 * @xo_voter - Valid handle returned from msm_xo_get()
255 * @mode - Mode to vote for (ON, OFF, PIN_CTRL)
256 *
257 * Vote for an XO to be either ON, OFF, or under PIN_CTRL. Votes are
258 * aggregated with ON taking precedence over PIN_CTRL taking precedence
259 * over OFF.
260 *
261 * This function returns 0 on success or a negative error code on failure.
262 */
263int msm_xo_mode_vote(struct msm_xo_voter *xo_voter, enum msm_xo_modes mode)
264{
265 int ret;
266 unsigned long flags;
Tianyi Gouc5314912011-10-25 16:44:54 -0700267
268 if (!xo_voter)
Tianyi Gou41515e22011-09-01 19:37:43 -0700269 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700270
Tianyi Gouc5314912011-10-25 16:44:54 -0700271 if (mode >= NUM_MSM_XO_MODES || IS_ERR(xo_voter))
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700272 return -EINVAL;
273
274 spin_lock_irqsave(&msm_xo_lock, flags);
275 ret = __msm_xo_mode_vote(xo_voter, mode);
276 spin_unlock_irqrestore(&msm_xo_lock, flags);
277
278 return ret;
279}
280EXPORT_SYMBOL(msm_xo_mode_vote);
281
282/**
283 * msm_xo_get() - Get a voting handle for an XO
284 * @xo_id - XO identifier
285 * @voter - Debug string to identify users
286 *
287 * XO voters vote for OFF by default. This function returns a pointer
288 * indicating success. An ERR_PTR is returned on failure.
289 *
290 * If XO voting is disabled, %NULL is returned.
291 */
292struct msm_xo_voter *msm_xo_get(enum msm_xo_ids xo_id, const char *voter)
293{
294 int ret;
295 unsigned long flags;
296 struct msm_xo_voter *xo_voter;
297
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700298 if (xo_id >= NUM_MSM_XO_IDS) {
299 ret = -EINVAL;
300 goto err;
301 }
302
303 xo_voter = kzalloc(sizeof(*xo_voter), GFP_KERNEL);
304 if (!xo_voter) {
305 ret = -ENOMEM;
306 goto err;
307 }
308
309 xo_voter->name = kstrdup(voter, GFP_KERNEL);
310 if (!xo_voter->name) {
311 ret = -ENOMEM;
312 goto err_name;
313 }
314
315 xo_voter->xo = &msm_xo_sources[xo_id];
316
317 /* Voters vote for OFF by default */
318 spin_lock_irqsave(&msm_xo_lock, flags);
319 xo_voter->xo->votes[MSM_XO_MODE_OFF]++;
320 list_add(&xo_voter->list, &xo_voter->xo->voters);
321 spin_unlock_irqrestore(&msm_xo_lock, flags);
322
323 return xo_voter;
324
325err_name:
326 kfree(xo_voter);
327err:
328 return ERR_PTR(ret);
329}
330EXPORT_SYMBOL(msm_xo_get);
331
332/**
333 * msm_xo_put() - Release a voting handle
334 * @xo_voter - Valid handle returned from msm_xo_get()
335 *
336 * Release a reference to an XO voting handle. This also removes the voter's
337 * vote, therefore calling msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF)
338 * beforehand is unnecessary.
339 */
340void msm_xo_put(struct msm_xo_voter *xo_voter)
341{
342 unsigned long flags;
343
Tianyi Gouc5314912011-10-25 16:44:54 -0700344 if (!xo_voter || IS_ERR(xo_voter))
345 return;
346
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700347 spin_lock_irqsave(&msm_xo_lock, flags);
348 __msm_xo_mode_vote(xo_voter, MSM_XO_MODE_OFF);
349 xo_voter->xo->votes[MSM_XO_MODE_OFF]--;
350 list_del(&xo_voter->list);
351 spin_unlock_irqrestore(&msm_xo_lock, flags);
352
353 kfree(xo_voter->name);
354 kfree(xo_voter);
355}
356EXPORT_SYMBOL(msm_xo_put);
357
358int __init msm_xo_init(void)
359{
Stephen Boyd47239d52012-01-26 14:38:40 -0800360 int i, ret;
361 struct msm_rpm_iv_pair cmd[1];
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700362
363 for (i = 0; i < ARRAY_SIZE(msm_xo_sources); i++)
364 INIT_LIST_HEAD(&msm_xo_sources[i].voters);
365
Stephen Boyd47239d52012-01-26 14:38:40 -0800366 cmd[0].id = MSM_RPM_ID_CXO_BUFFERS;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700367 cmd[0].value = 0;
Stephen Boyd47239d52012-01-26 14:38:40 -0800368 ret = msm_rpmrs_set(MSM_RPM_CTX_SET_0, cmd, ARRAY_SIZE(cmd));
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700369 if (ret)
Stephen Boyd47239d52012-01-26 14:38:40 -0800370 return ret;
Stephen Boyda70c5432012-02-10 14:31:45 -0800371 msm_xo_debugfs_init();
Stephen Boyd47239d52012-01-26 14:38:40 -0800372 return 0;
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -0700373}