diff options
Diffstat (limited to 'drivers/video/hecubafb.c')
-rw-r--r-- | drivers/video/hecubafb.c | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/drivers/video/hecubafb.c b/drivers/video/hecubafb.c new file mode 100644 index 000000000000..abfcb50364c8 --- /dev/null +++ b/drivers/video/hecubafb.c | |||
@@ -0,0 +1,471 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/hecubafb.c -- FB driver for Hecuba controller | ||
3 | * | ||
4 | * Copyright (C) 2006, Jaya Kumar | ||
5 | * This work was sponsored by CIS(M) Sdn Bhd | ||
6 | * | ||
7 | * This file is subject to the terms and conditions of the GNU General Public | ||
8 | * License. See the file COPYING in the main directory of this archive for | ||
9 | * more details. | ||
10 | * | ||
11 | * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven. | ||
12 | * This work was possible because of apollo display code from E-Ink's website | ||
13 | * http://support.eink.com/community | ||
14 | * All information used to write this code is from public material made | ||
15 | * available by E-Ink on its support site. Some commands such as 0xA4 | ||
16 | * were found by looping through cmd=0x00 thru 0xFF and supplying random | ||
17 | * values. There are other commands that the display is capable of, | ||
18 | * beyond the 5 used here but they are more complex. | ||
19 | * | ||
20 | * This driver is written to be used with the Hecuba display controller | ||
21 | * board, and tested with the EInk 800x600 display in 1 bit mode. | ||
22 | * The interface between Hecuba and the host is TTL based GPIO. The | ||
23 | * GPIO requirements are 8 writable data lines and 6 lines for control. | ||
24 | * Only 4 of the controls are actually used here but 6 for future use. | ||
25 | * The driver requires the IO addresses for data and control GPIO at | ||
26 | * load time. It is also possible to use this display with a standard | ||
27 | * PC parallel port. | ||
28 | * | ||
29 | * General notes: | ||
30 | * - User must set hecubafb_enable=1 to enable it | ||
31 | * - User must set dio_addr=0xIOADDR cio_addr=0xIOADDR c2io_addr=0xIOADDR | ||
32 | * | ||
33 | */ | ||
34 | |||
35 | #include <linux/module.h> | ||
36 | #include <linux/kernel.h> | ||
37 | #include <linux/errno.h> | ||
38 | #include <linux/string.h> | ||
39 | #include <linux/mm.h> | ||
40 | #include <linux/slab.h> | ||
41 | #include <linux/vmalloc.h> | ||
42 | #include <linux/delay.h> | ||
43 | #include <linux/interrupt.h> | ||
44 | #include <linux/fb.h> | ||
45 | #include <linux/init.h> | ||
46 | #include <linux/platform_device.h> | ||
47 | #include <linux/list.h> | ||
48 | #include <asm/uaccess.h> | ||
49 | |||
50 | /* Apollo controller specific defines */ | ||
51 | #define APOLLO_START_NEW_IMG 0xA0 | ||
52 | #define APOLLO_STOP_IMG_DATA 0xA1 | ||
53 | #define APOLLO_DISPLAY_IMG 0xA2 | ||
54 | #define APOLLO_ERASE_DISPLAY 0xA3 | ||
55 | #define APOLLO_INIT_DISPLAY 0xA4 | ||
56 | |||
57 | /* Hecuba interface specific defines */ | ||
58 | /* WUP is inverted, CD is inverted, DS is inverted */ | ||
59 | #define HCB_NWUP_BIT 0x01 | ||
60 | #define HCB_NDS_BIT 0x02 | ||
61 | #define HCB_RW_BIT 0x04 | ||
62 | #define HCB_NCD_BIT 0x08 | ||
63 | #define HCB_ACK_BIT 0x80 | ||
64 | |||
65 | /* Display specific information */ | ||
66 | #define DPY_W 600 | ||
67 | #define DPY_H 800 | ||
68 | |||
69 | struct hecubafb_par { | ||
70 | unsigned long dio_addr; | ||
71 | unsigned long cio_addr; | ||
72 | unsigned long c2io_addr; | ||
73 | unsigned char ctl; | ||
74 | struct fb_info *info; | ||
75 | unsigned int irq; | ||
76 | }; | ||
77 | |||
78 | static struct fb_fix_screeninfo hecubafb_fix __devinitdata = { | ||
79 | .id = "hecubafb", | ||
80 | .type = FB_TYPE_PACKED_PIXELS, | ||
81 | .visual = FB_VISUAL_MONO01, | ||
82 | .xpanstep = 0, | ||
83 | .ypanstep = 0, | ||
84 | .ywrapstep = 0, | ||
85 | .accel = FB_ACCEL_NONE, | ||
86 | }; | ||
87 | |||
88 | static struct fb_var_screeninfo hecubafb_var __devinitdata = { | ||
89 | .xres = DPY_W, | ||
90 | .yres = DPY_H, | ||
91 | .xres_virtual = DPY_W, | ||
92 | .yres_virtual = DPY_H, | ||
93 | .bits_per_pixel = 1, | ||
94 | .nonstd = 1, | ||
95 | }; | ||
96 | |||
97 | static unsigned long dio_addr; | ||
98 | static unsigned long cio_addr; | ||
99 | static unsigned long c2io_addr; | ||
100 | static unsigned long splashval; | ||
101 | static unsigned int nosplash; | ||
102 | static unsigned int hecubafb_enable; | ||
103 | static unsigned int irq; | ||
104 | |||
105 | static DECLARE_WAIT_QUEUE_HEAD(hecubafb_waitq); | ||
106 | |||
107 | static void hcb_set_ctl(struct hecubafb_par *par) | ||
108 | { | ||
109 | outb(par->ctl, par->cio_addr); | ||
110 | } | ||
111 | |||
112 | static unsigned char hcb_get_ctl(struct hecubafb_par *par) | ||
113 | { | ||
114 | return inb(par->c2io_addr); | ||
115 | } | ||
116 | |||
117 | static void hcb_set_data(struct hecubafb_par *par, unsigned char value) | ||
118 | { | ||
119 | outb(value, par->dio_addr); | ||
120 | } | ||
121 | |||
122 | static int __devinit apollo_init_control(struct hecubafb_par *par) | ||
123 | { | ||
124 | unsigned char ctl; | ||
125 | /* for init, we want the following setup to be set: | ||
126 | WUP = lo | ||
127 | ACK = hi | ||
128 | DS = hi | ||
129 | RW = hi | ||
130 | CD = lo | ||
131 | */ | ||
132 | |||
133 | /* write WUP to lo, DS to hi, RW to hi, CD to lo */ | ||
134 | par->ctl = HCB_NWUP_BIT | HCB_RW_BIT | HCB_NCD_BIT ; | ||
135 | par->ctl &= ~HCB_NDS_BIT; | ||
136 | hcb_set_ctl(par); | ||
137 | |||
138 | /* check ACK is not lo */ | ||
139 | ctl = hcb_get_ctl(par); | ||
140 | if ((ctl & HCB_ACK_BIT)) { | ||
141 | printk(KERN_ERR "Fail because ACK is already low\n"); | ||
142 | return -ENXIO; | ||
143 | } | ||
144 | |||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | static void hcb_wait_for_ack(struct hecubafb_par *par) | ||
149 | { | ||
150 | |||
151 | int timeout; | ||
152 | unsigned char ctl; | ||
153 | |||
154 | timeout=500; | ||
155 | do { | ||
156 | ctl = hcb_get_ctl(par); | ||
157 | if ((ctl & HCB_ACK_BIT)) | ||
158 | return; | ||
159 | udelay(1); | ||
160 | } while (timeout--); | ||
161 | printk(KERN_ERR "timed out waiting for ack\n"); | ||
162 | } | ||
163 | |||
164 | static void hcb_wait_for_ack_clear(struct hecubafb_par *par) | ||
165 | { | ||
166 | |||
167 | int timeout; | ||
168 | unsigned char ctl; | ||
169 | |||
170 | timeout=500; | ||
171 | do { | ||
172 | ctl = hcb_get_ctl(par); | ||
173 | if (!(ctl & HCB_ACK_BIT)) | ||
174 | return; | ||
175 | udelay(1); | ||
176 | } while (timeout--); | ||
177 | printk(KERN_ERR "timed out waiting for clear\n"); | ||
178 | } | ||
179 | |||
180 | static void apollo_send_data(struct hecubafb_par *par, unsigned char data) | ||
181 | { | ||
182 | /* set data */ | ||
183 | hcb_set_data(par, data); | ||
184 | |||
185 | /* set DS low */ | ||
186 | par->ctl |= HCB_NDS_BIT; | ||
187 | hcb_set_ctl(par); | ||
188 | |||
189 | hcb_wait_for_ack(par); | ||
190 | |||
191 | /* set DS hi */ | ||
192 | par->ctl &= ~(HCB_NDS_BIT); | ||
193 | hcb_set_ctl(par); | ||
194 | |||
195 | hcb_wait_for_ack_clear(par); | ||
196 | } | ||
197 | |||
198 | static void apollo_send_command(struct hecubafb_par *par, unsigned char data) | ||
199 | { | ||
200 | /* command so set CD to high */ | ||
201 | par->ctl &= ~(HCB_NCD_BIT); | ||
202 | hcb_set_ctl(par); | ||
203 | |||
204 | /* actually strobe with command */ | ||
205 | apollo_send_data(par, data); | ||
206 | |||
207 | /* clear CD back to low */ | ||
208 | par->ctl |= (HCB_NCD_BIT); | ||
209 | hcb_set_ctl(par); | ||
210 | } | ||
211 | |||
212 | /* main hecubafb functions */ | ||
213 | |||
214 | static void hecubafb_dpy_update(struct hecubafb_par *par) | ||
215 | { | ||
216 | int i; | ||
217 | unsigned char *buf = (unsigned char __force *)par->info->screen_base; | ||
218 | |||
219 | apollo_send_command(par, 0xA0); | ||
220 | |||
221 | for (i=0; i < (DPY_W*DPY_H/8); i++) { | ||
222 | apollo_send_data(par, *(buf++)); | ||
223 | } | ||
224 | |||
225 | apollo_send_command(par, 0xA1); | ||
226 | apollo_send_command(par, 0xA2); | ||
227 | } | ||
228 | |||
229 | /* this is called back from the deferred io workqueue */ | ||
230 | static void hecubafb_dpy_deferred_io(struct fb_info *info, | ||
231 | struct list_head *pagelist) | ||
232 | { | ||
233 | hecubafb_dpy_update(info->par); | ||
234 | } | ||
235 | |||
236 | static void hecubafb_fillrect(struct fb_info *info, | ||
237 | const struct fb_fillrect *rect) | ||
238 | { | ||
239 | struct hecubafb_par *par = info->par; | ||
240 | |||
241 | sys_fillrect(info, rect); | ||
242 | |||
243 | hecubafb_dpy_update(par); | ||
244 | } | ||
245 | |||
246 | static void hecubafb_copyarea(struct fb_info *info, | ||
247 | const struct fb_copyarea *area) | ||
248 | { | ||
249 | struct hecubafb_par *par = info->par; | ||
250 | |||
251 | sys_copyarea(info, area); | ||
252 | |||
253 | hecubafb_dpy_update(par); | ||
254 | } | ||
255 | |||
256 | static void hecubafb_imageblit(struct fb_info *info, | ||
257 | const struct fb_image *image) | ||
258 | { | ||
259 | struct hecubafb_par *par = info->par; | ||
260 | |||
261 | sys_imageblit(info, image); | ||
262 | |||
263 | hecubafb_dpy_update(par); | ||
264 | } | ||
265 | |||
266 | /* | ||
267 | * this is the slow path from userspace. they can seek and write to | ||
268 | * the fb. it's inefficient to do anything less than a full screen draw | ||
269 | */ | ||
270 | static ssize_t hecubafb_write(struct fb_info *info, const char __user *buf, | ||
271 | size_t count, loff_t *ppos) | ||
272 | { | ||
273 | unsigned long p; | ||
274 | int err=-EINVAL; | ||
275 | struct hecubafb_par *par; | ||
276 | unsigned int xres; | ||
277 | unsigned int fbmemlength; | ||
278 | |||
279 | p = *ppos; | ||
280 | par = info->par; | ||
281 | xres = info->var.xres; | ||
282 | fbmemlength = (xres * info->var.yres)/8; | ||
283 | |||
284 | if (p > fbmemlength) | ||
285 | return -ENOSPC; | ||
286 | |||
287 | err = 0; | ||
288 | if ((count + p) > fbmemlength) { | ||
289 | count = fbmemlength - p; | ||
290 | err = -ENOSPC; | ||
291 | } | ||
292 | |||
293 | if (count) { | ||
294 | char *base_addr; | ||
295 | |||
296 | base_addr = (char __force *)info->screen_base; | ||
297 | count -= copy_from_user(base_addr + p, buf, count); | ||
298 | *ppos += count; | ||
299 | err = -EFAULT; | ||
300 | } | ||
301 | |||
302 | hecubafb_dpy_update(par); | ||
303 | |||
304 | if (count) | ||
305 | return count; | ||
306 | |||
307 | return err; | ||
308 | } | ||
309 | |||
310 | static struct fb_ops hecubafb_ops = { | ||
311 | .owner = THIS_MODULE, | ||
312 | .fb_read = fb_sys_read, | ||
313 | .fb_write = hecubafb_write, | ||
314 | .fb_fillrect = hecubafb_fillrect, | ||
315 | .fb_copyarea = hecubafb_copyarea, | ||
316 | .fb_imageblit = hecubafb_imageblit, | ||
317 | }; | ||
318 | |||
319 | static struct fb_deferred_io hecubafb_defio = { | ||
320 | .delay = HZ, | ||
321 | .deferred_io = hecubafb_dpy_deferred_io, | ||
322 | }; | ||
323 | |||
324 | static int __devinit hecubafb_probe(struct platform_device *dev) | ||
325 | { | ||
326 | struct fb_info *info; | ||
327 | int retval = -ENOMEM; | ||
328 | int videomemorysize; | ||
329 | unsigned char *videomemory; | ||
330 | struct hecubafb_par *par; | ||
331 | |||
332 | videomemorysize = (DPY_W*DPY_H)/8; | ||
333 | |||
334 | if (!(videomemory = vmalloc(videomemorysize))) | ||
335 | return retval; | ||
336 | |||
337 | memset(videomemory, 0, videomemorysize); | ||
338 | |||
339 | info = framebuffer_alloc(sizeof(struct hecubafb_par), &dev->dev); | ||
340 | if (!info) | ||
341 | goto err; | ||
342 | |||
343 | info->screen_base = (char __iomem *) videomemory; | ||
344 | info->fbops = &hecubafb_ops; | ||
345 | |||
346 | info->var = hecubafb_var; | ||
347 | info->fix = hecubafb_fix; | ||
348 | info->fix.smem_len = videomemorysize; | ||
349 | par = info->par; | ||
350 | par->info = info; | ||
351 | |||
352 | if (!dio_addr || !cio_addr || !c2io_addr) { | ||
353 | printk(KERN_WARNING "no IO addresses supplied\n"); | ||
354 | goto err1; | ||
355 | } | ||
356 | par->dio_addr = dio_addr; | ||
357 | par->cio_addr = cio_addr; | ||
358 | par->c2io_addr = c2io_addr; | ||
359 | info->flags = FBINFO_FLAG_DEFAULT; | ||
360 | |||
361 | info->fbdefio = &hecubafb_defio; | ||
362 | fb_deferred_io_init(info); | ||
363 | |||
364 | retval = register_framebuffer(info); | ||
365 | if (retval < 0) | ||
366 | goto err1; | ||
367 | platform_set_drvdata(dev, info); | ||
368 | |||
369 | printk(KERN_INFO | ||
370 | "fb%d: Hecuba frame buffer device, using %dK of video memory\n", | ||
371 | info->node, videomemorysize >> 10); | ||
372 | |||
373 | /* this inits the dpy */ | ||
374 | apollo_init_control(par); | ||
375 | |||
376 | apollo_send_command(par, APOLLO_INIT_DISPLAY); | ||
377 | apollo_send_data(par, 0x81); | ||
378 | |||
379 | /* have to wait while display resets */ | ||
380 | udelay(1000); | ||
381 | |||
382 | /* if we were told to splash the screen, we just clear it */ | ||
383 | if (!nosplash) { | ||
384 | apollo_send_command(par, APOLLO_ERASE_DISPLAY); | ||
385 | apollo_send_data(par, splashval); | ||
386 | } | ||
387 | |||
388 | return 0; | ||
389 | err1: | ||
390 | framebuffer_release(info); | ||
391 | err: | ||
392 | vfree(videomemory); | ||
393 | return retval; | ||
394 | } | ||
395 | |||
396 | static int __devexit hecubafb_remove(struct platform_device *dev) | ||
397 | { | ||
398 | struct fb_info *info = platform_get_drvdata(dev); | ||
399 | |||
400 | if (info) { | ||
401 | fb_deferred_io_cleanup(info); | ||
402 | unregister_framebuffer(info); | ||
403 | vfree((void __force *)info->screen_base); | ||
404 | framebuffer_release(info); | ||
405 | } | ||
406 | return 0; | ||
407 | } | ||
408 | |||
409 | static struct platform_driver hecubafb_driver = { | ||
410 | .probe = hecubafb_probe, | ||
411 | .remove = hecubafb_remove, | ||
412 | .driver = { | ||
413 | .name = "hecubafb", | ||
414 | }, | ||
415 | }; | ||
416 | |||
417 | static struct platform_device *hecubafb_device; | ||
418 | |||
419 | static int __init hecubafb_init(void) | ||
420 | { | ||
421 | int ret; | ||
422 | |||
423 | if (!hecubafb_enable) { | ||
424 | printk(KERN_ERR "Use hecubafb_enable to enable the device\n"); | ||
425 | return -ENXIO; | ||
426 | } | ||
427 | |||
428 | ret = platform_driver_register(&hecubafb_driver); | ||
429 | if (!ret) { | ||
430 | hecubafb_device = platform_device_alloc("hecubafb", 0); | ||
431 | if (hecubafb_device) | ||
432 | ret = platform_device_add(hecubafb_device); | ||
433 | else | ||
434 | ret = -ENOMEM; | ||
435 | |||
436 | if (ret) { | ||
437 | platform_device_put(hecubafb_device); | ||
438 | platform_driver_unregister(&hecubafb_driver); | ||
439 | } | ||
440 | } | ||
441 | return ret; | ||
442 | |||
443 | } | ||
444 | |||
445 | static void __exit hecubafb_exit(void) | ||
446 | { | ||
447 | platform_device_unregister(hecubafb_device); | ||
448 | platform_driver_unregister(&hecubafb_driver); | ||
449 | } | ||
450 | |||
451 | module_param(nosplash, uint, 0); | ||
452 | MODULE_PARM_DESC(nosplash, "Disable doing the splash screen"); | ||
453 | module_param(hecubafb_enable, uint, 0); | ||
454 | MODULE_PARM_DESC(hecubafb_enable, "Enable communication with Hecuba board"); | ||
455 | module_param(dio_addr, ulong, 0); | ||
456 | MODULE_PARM_DESC(dio_addr, "IO address for data, eg: 0x480"); | ||
457 | module_param(cio_addr, ulong, 0); | ||
458 | MODULE_PARM_DESC(cio_addr, "IO address for control, eg: 0x400"); | ||
459 | module_param(c2io_addr, ulong, 0); | ||
460 | MODULE_PARM_DESC(c2io_addr, "IO address for secondary control, eg: 0x408"); | ||
461 | module_param(splashval, ulong, 0); | ||
462 | MODULE_PARM_DESC(splashval, "Splash pattern: 0x00 is black, 0x01 is white"); | ||
463 | module_param(irq, uint, 0); | ||
464 | MODULE_PARM_DESC(irq, "IRQ for the Hecuba board"); | ||
465 | |||
466 | module_init(hecubafb_init); | ||
467 | module_exit(hecubafb_exit); | ||
468 | |||
469 | MODULE_DESCRIPTION("fbdev driver for Hecuba board"); | ||
470 | MODULE_AUTHOR("Jaya Kumar"); | ||
471 | MODULE_LICENSE("GPL"); | ||