pandora misc: add OPP limiter
[pandora-kernel.git] / drivers / misc / pandora.c
1 /*
2         pandora.c
3         Exports some additional hardware control to userspace.
4         Written by GraÅžvydas "notaz" Ignotas <notasas@gmail.com>
5
6         This program is free software; you can redistribute it and/or modify
7         it under the terms of the GNU General Public License as published by
8         the Free Software Foundation; version 2 of the License.
9 */
10
11 #include <linux/module.h>
12 #include <linux/kernel.h>
13 #include <linux/proc_fs.h>
14 #include <linux/uaccess.h>
15 #include <linux/delay.h>
16 #include <linux/clk.h>
17 #include <linux/io.h>
18 #include <linux/i2c/twl4030.h>
19
20 #ifndef CONFIG_PROC_FS
21 #error need CONFIG_PROC_FS
22 #endif
23
24 #define PND_PROC_CPUMHZ         "pandora/cpu_mhz_max"
25 #define PND_PROC_CPUOPP         "pandora/cpu_opp_max"
26
27 static int max_allowed_opp = 3; /* 3 on ED's request */
28 static int opp;
29
30 static void set_opp(int nopp)
31 {
32         static const unsigned char opp2volt[5] = { 30, 38, 48, 54, 60 };
33         unsigned char v, ov;
34
35         if (nopp>5) nopp=5;
36         if (nopp<1) nopp=1;
37         v = opp2volt[nopp-1];
38         twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, v, 0x5E);
39
40         ov = opp2volt[opp-1];
41         /*
42          * Rationale: Slowest slew rate of the TPS65950 SMPS is 4 mV/us.
43          * Each unit is 12.5mV, resulting in 3.125us/unit.
44          * Rounded up for a safe 4us/unit.
45         */
46         udelay(abs(v - ov) * 4);
47
48         opp = nopp;
49         printk(KERN_INFO "set_opp: OPP %d set\n", opp);
50 }
51
52 static int mhz2opp(int mhz)
53 {
54         int new_opp=1;
55         if (mhz<14) new_opp=3; /* Guard against -1 */
56         if (mhz>125) new_opp=2;
57         if (mhz>250) new_opp=3;
58         if (mhz>600) new_opp=4; /* Assumption: current pandoras OK with 600Mhz@OPP3 */
59         if (mhz>720) new_opp=5;
60
61         if (max_allowed_opp > 0 && new_opp > max_allowed_opp)
62                 new_opp = max_allowed_opp;
63
64         return new_opp;
65 }
66
67 static void preop_oppset(int mhz)
68 {
69         int new_opp = mhz2opp(mhz);
70         if (new_opp > opp)
71                 set_opp(new_opp);
72 }
73
74 static void postop_oppset(int mhz)
75 {
76         int new_opp = mhz2opp(mhz);
77         if (new_opp < opp)
78                 set_opp(new_opp);
79 }
80
81 /*
82  * note:
83  * SYS_CLK is 26 MHz (see PRM_CLKSEL)
84  * ARM_FCLK = (SYS_CLK * M * 2) / ([N+1] * M2) / 2
85  * CM_CLKSEL1_PLL_MPU = N | (M << 8) | (M2 << 19)
86  */
87 static int get_fclk(void)
88 {
89         unsigned __iomem *base;
90         unsigned n, m, m2;
91         unsigned ret;
92
93         base = ioremap(0x48004000, 0x2000);
94         if (base == NULL) {
95                 printk(KERN_ERR "get_fclk: can't ioremap\n");
96                 return -1;
97         }
98
99         ret = base[0x940 >> 2];
100         iounmap(base);
101
102         n = ret & 0xff;
103         m = (ret >> 8) & 0x7ff;
104         m2 = (ret >> 19) & 7;
105
106         if (m2 != 1 && m2 != 2 && m2 != 4) {
107                 printk(KERN_ERR "get_fclk: invalid divider %d\n", m2);
108                 return -1;
109         }
110
111         return 26 * m / ((n + 1) * m2);
112 }
113
114 static void set_fclk(int val)
115 {
116         struct clk *pllclk, *fclk;
117         int ret;
118
119         if (val & ~0x7ff) {
120                 printk(KERN_ERR "set_fclk: value %d out of range\n", val);
121                 return;
122         }
123
124
125         pllclk = clk_get(NULL, "dpll1_ck");
126         if (IS_ERR(pllclk)) {
127                 printk(KERN_ERR "set_fclk: clk_get() failed: %li\n",
128                                 PTR_ERR(pllclk));
129                 return;
130         }
131
132         preop_oppset(val);
133         ret = clk_set_rate(pllclk, val * 1000000);
134         if (ret)
135                 printk(KERN_ERR "set_fclk: clk_set_rate(dpll1_ck) "
136                                 "failed: %li\n", PTR_ERR(pllclk));
137         msleep(100);
138
139         printk(KERN_INFO "dpll1_ck rate: %li\n", clk_get_rate(pllclk));
140         clk_put(pllclk);
141
142         fclk = clk_get(NULL, "arm_fck");
143         if (!IS_ERR(pllclk)) {
144                 printk(KERN_INFO "arm_fck  rate: %li\n", clk_get_rate(fclk));
145                 clk_put(fclk);
146         }
147
148         printk(KERN_INFO "PLL_MPU  rate: %i\n", get_fclk() * 1000000);
149         postop_oppset(val);
150 }
151
152 static int proc_read_val(char *page, char **start, off_t off, int count,
153                 int *eof, int val)
154 {
155         char *p = page;
156         int len;
157
158         p += sprintf(p, "%d\n", val);
159
160         len = (p - page) - off;
161         if (len < 0)
162                 len = 0;
163
164         *eof = (len <= count) ? 1 : 0;
165         *start = page + off;
166
167         return len;
168 }
169
170 static int proc_write_val(struct file *file, const char __user *buffer,
171                 unsigned long count, unsigned long *val)
172 {
173         char buff[32];
174         int ret;
175
176         count = strncpy_from_user(buff, buffer,
177                         count < sizeof(buff) ? count : sizeof(buff) - 1);
178         buff[count] = 0;
179
180         ret = strict_strtoul(buff, 0, val);
181         if (ret < 0) {
182                 printk(KERN_ERR "error %i parsing %s\n", ret, buff);
183                 return ret;
184         }
185
186         return count;
187 }
188
189 static int cpu_clk_read(char *page, char **start, off_t off, int count,
190                 int *eof, void *data)
191 {
192         return proc_read_val(page, start, off, count, eof, get_fclk());
193 }
194
195 static int cpu_clk_write(struct file *file, const char __user *buffer,
196                 unsigned long count, void *data)
197 {
198         unsigned long val;
199         int ret;
200
201         ret = proc_write_val(file, buffer, count, &val);
202         if (ret < 0)
203                 return ret;
204
205         set_fclk(val);
206         return ret;
207 }
208
209 static int cpu_maxopp_read(char *page, char **start, off_t off, int count,
210                 int *eof, void *data)
211 {
212         return proc_read_val(page, start, off, count, eof, max_allowed_opp);
213 }
214
215 static int cpu_maxopp_write(struct file *file, const char __user *buffer,
216                 unsigned long count, void *data)
217 {
218         unsigned long val;
219         int ret;
220
221         ret = proc_write_val(file, buffer, count, &val);
222         if (ret < 0)
223                 return ret;
224
225         if (val < 1 || val > 6)
226                 return -EINVAL;
227
228         max_allowed_opp = val;
229         return ret;
230 }
231
232 static void proc_create_rw(const char *name, void *pdata,
233                            read_proc_t *read_proc, write_proc_t *write_proc)
234 {
235         struct proc_dir_entry *pret;
236         
237         pret = create_proc_entry(name, S_IWUGO | S_IRUGO, NULL);
238         if (pret == NULL) {
239                 proc_mkdir("pandora", NULL);
240                 pret = create_proc_entry(name, S_IWUGO | S_IRUGO, NULL);
241                 if (pret == NULL) {
242                         printk(KERN_ERR "failed to create proc file %s\n", name);
243                         return;
244                 }
245         }
246
247         pret->data = pdata;
248         pret->read_proc = read_proc;
249         pret->write_proc = write_proc;
250 }
251
252 /* ************************************************************************* */
253
254 static int pndctrl_init(void)
255 {
256         opp = mhz2opp(get_fclk());
257
258         proc_create_rw(PND_PROC_CPUMHZ, NULL, cpu_clk_read, cpu_clk_write);
259         proc_create_rw(PND_PROC_CPUOPP, NULL, cpu_maxopp_read, cpu_maxopp_write);
260
261         printk(KERN_INFO "pndctrl loaded.\n");
262         return 0;
263 }
264
265
266 static void pndctrl_cleanup(void)
267 {
268         remove_proc_entry(PND_PROC_CPUOPP, NULL);
269         remove_proc_entry(PND_PROC_CPUMHZ, NULL);
270         printk(KERN_INFO "pndctrl unloaded.\n");
271 }
272
273
274 module_init(pndctrl_init);
275 module_exit(pndctrl_cleanup);
276
277 MODULE_AUTHOR("Grazvydas Ignotas");
278 MODULE_LICENSE("GPL");
279 MODULE_DESCRIPTION("Pandora Additional hw control");