Add pandora misc driver.
[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 <asm/io.h>
18
19 /* HACK */
20 #include <../drivers/video/omap/dispc.h>
21
22 #ifndef CONFIG_PROC_FS
23 #error need CONFIG_PROC_FS
24 #endif
25
26 #define PND_PROC_DIR            "pandora"
27 #define PND_PROC_CPUMHZ         "cpu_mhz_max"
28 #define PND_PROC_VSYNC          "wait_vsync"
29
30 /*
31  * note:
32  * SYS_CLK is 26 MHz (see PRM_CLKSEL)
33  * ARM_FCLK = (SYS_CLK * M * 2) / ([N+1] * M2) / 2
34  * CM_CLKSEL1_PLL_MPU = N | (M << 8) | (M2 << 19)
35  */
36 static int get_fclk(void)
37 {
38         unsigned __iomem *base;
39         unsigned ret;
40
41         base = ioremap(0x48004000, 0x2000);
42         if (base == NULL) {
43                 printk(KERN_ERR "get_fclk: can't ioremap\n");
44                 return -1;
45         }
46
47         ret = base[0x940>>2];
48         iounmap(base);
49
50         if ((ret & ~0x7ff00) != 0x10000c) {
51                 printk(KERN_ERR "get_fclk: unexpected CM_CLKSEL1_PLL_MPU: "
52                                 "%08x\n", ret);
53                 return -1;
54         }
55
56         ret &= 0x7ff00;
57         ret >>= 8;
58
59         return (int)ret;
60 }
61
62 static void set_fclk(int val)
63 {
64         struct clk *pllclk, *fclk;
65         int ret;
66
67         if (val & ~0x7ff) {
68                 printk(KERN_ERR "set_fclk: value %d out of range\n", val);
69                 return;
70         }
71
72
73         pllclk = clk_get(NULL, "dpll1_ck");
74         if (IS_ERR(pllclk)) {
75                 printk(KERN_ERR "set_fclk: clk_get() failed: %li\n",
76                                 PTR_ERR(pllclk));
77                 return;
78         }
79
80         ret = clk_set_rate(pllclk, val * 1000000);
81         if (ret)
82                 printk(KERN_ERR "set_fclk: clk_set_rate(dpll1_ck) "
83                                 "failed: %li\n", PTR_ERR(pllclk));
84         msleep(100);
85
86         printk(KERN_INFO "dpll1_ck rate: %li\n", clk_get_rate(pllclk));
87         clk_put(pllclk);
88
89         fclk = clk_get(NULL, "arm_fck");
90         if (!IS_ERR(pllclk)) {
91                 printk(KERN_INFO "arm_fck  rate: %li\n", clk_get_rate(fclk));
92                 clk_put(fclk);
93         }
94
95         printk(KERN_INFO "PLL_MPU  rate: %i\n", get_fclk() * 1000000);
96 }
97
98 static int cpu_clk_read(char *page, char **start, off_t off, int count,
99                 int *eof, void *data)
100 {
101         char *p = page;
102         int len;
103
104         p += sprintf(p, "%d\n", get_fclk());
105
106         len = (p - page) - off;
107         if (len < 0)
108                 len = 0;
109
110         *eof = (len <= count) ? 1 : 0;
111         *start = page + off;
112
113         return len;
114 }
115
116 static int cpu_clk_write(struct file *file, const char __user *buffer,
117                 unsigned long count, void *data)
118 {
119         char buff[32];
120         unsigned long val;
121         int ret;
122
123         count = strncpy_from_user(buff, buffer,
124                         count < sizeof(buff) ? count : sizeof(buff) - 1);
125         buff[count] = 0;
126
127         ret = strict_strtoul(buff, 0, &val);
128         if (ret < 0) {
129                 printk(KERN_ERR "error %i parsing %s\n", ret, buff);
130                 return ret;
131         }
132
133         set_fclk(val);
134
135         return count;
136 }
137
138 /* ************************************************************************* */
139
140 static DECLARE_WAIT_QUEUE_HEAD(pnd_vsync_waitq);
141 static u32 pnd_vsync_counter;
142 static u32 pnd_vsync_last;
143 static u32 pnd_vsync_active;
144
145 static void pnd_vsync_callback(void *data)
146 {
147         pnd_vsync_counter++;
148         if (pnd_vsync_active)
149                 wake_up_interruptible(&pnd_vsync_waitq);
150 }
151
152 static int pnd_hook_vsync(void)
153 {
154         unsigned __iomem *base;
155         u32 val;
156         int ret;
157
158         ret = omap_dispc_request_irq(2, pnd_vsync_callback, NULL);
159         if (ret) {
160                 printk(KERN_ERR "failed to get irq from dispc driver\n");
161                 return -1;
162         }
163
164         base = ioremap(0x48050400, 0x400);
165         if (!base) {
166                 omap_dispc_free_irq(2, pnd_vsync_callback, NULL);
167                 printk(KERN_ERR "can't ioremap DISPC\n");
168                 return -1;
169         }
170
171         val = __raw_readl(base + 0x1c);
172         //printk("val: %08x\n", val);
173         val |= 2;
174         __raw_writel(val, base + 0x1c);
175
176         iounmap(base);
177         return 0;
178 }
179
180 static void pnd_unhook_vsync(void)
181 {
182         unsigned __iomem *base;
183
184         base = ioremap(0x48050400, 0x400);
185         if (!base) {
186                 printk(KERN_ERR "can't ioremap DISPC\n");
187         }
188         else {
189                 u32 val = __raw_readl(base + 0x1c);
190                 val &= ~2;
191                 __raw_writel(val, base + 0x1c);
192                 iounmap(base);
193         }
194
195         omap_dispc_free_irq(2, pnd_vsync_callback, NULL);
196 }
197
198 static int pnd_vsync_read(char *page, char **start, off_t off, int count,
199                 int *eof, void *data)
200 {
201         char *p = page;
202         int len, val = -1, ret, vcount;
203
204         vcount = pnd_vsync_counter;
205         pnd_vsync_active = 1;
206         ret = wait_event_interruptible_timeout(pnd_vsync_waitq,
207                         (vcount != pnd_vsync_counter), msecs_to_jiffies(250));
208         pnd_vsync_active = 0;
209         if (ret > 0)
210                 val = pnd_vsync_counter - pnd_vsync_last;
211
212         p += sprintf(p, "%d\n", val);
213         pnd_vsync_last = pnd_vsync_counter;
214
215         len = (p - page) - off;
216         if (len < 0)
217                 len = 0;
218
219         *eof = (len <= count) ? 1 : 0;
220         *start = page + off;
221
222         return len;
223 }
224
225 /* ************************************************************************* */
226
227 static struct proc_dir_entry *proc_dir;
228
229 static int pndctrl_init(void)
230 {
231         struct proc_dir_entry *pret;
232         int ret = -ENOMEM;
233
234         proc_dir = proc_mkdir(PND_PROC_DIR, NULL);
235         if (proc_dir == NULL) {
236                 printk(KERN_ERR "can't create proc dir.\n");
237                 return -ENOMEM;
238         }
239
240         pret = create_proc_entry(PND_PROC_CPUMHZ,
241                         S_IWUSR | S_IRUGO, proc_dir);
242         if (pret == NULL) {
243                 printk(KERN_ERR "can't create proc\n");
244                 goto fail0;
245         }
246
247         pret->read_proc = cpu_clk_read;
248         pret->write_proc = cpu_clk_write;
249
250         pret = create_proc_entry(PND_PROC_VSYNC,
251                         S_IRUGO, proc_dir);
252         if (pret == NULL) {
253                 printk(KERN_ERR "can't create proc\n");
254                 goto fail1;
255         }
256
257         pret->read_proc = pnd_vsync_read;
258
259         ret = pnd_hook_vsync();
260         if (ret) {
261                 printk(KERN_ERR "couldn't hook vsync\n");
262                 goto fail2;
263         }
264
265         printk(KERN_INFO "pndctrl loaded.\n");
266
267         return 0;
268
269 fail2:
270         remove_proc_entry(PND_PROC_VSYNC, proc_dir);
271 fail1:
272         remove_proc_entry(PND_PROC_CPUMHZ, proc_dir);
273 fail0:
274         remove_proc_entry(PND_PROC_DIR, NULL);
275         return ret;
276 }
277
278
279 static void pndctrl_cleanup(void)
280 {
281         pnd_unhook_vsync();
282         remove_proc_entry(PND_PROC_VSYNC, proc_dir);
283         remove_proc_entry(PND_PROC_CPUMHZ, proc_dir);
284         remove_proc_entry(PND_PROC_DIR, NULL);
285         printk("pndctrl unloaded.\n");
286 }
287
288
289 module_init(pndctrl_init);
290 module_exit(pndctrl_cleanup);
291
292 MODULE_AUTHOR("Grazvydas Ignotas");
293 MODULE_LICENSE("GPL");
294 MODULE_DESCRIPTION("Pandora Additional hw control");