[PATCH] fbdev: prevent drivers that have hardware cursors from calling software curso...
[safe/jmp/linux-2.6] / drivers / video / au1100fb.c
1 /*
2  * BRIEF MODULE DESCRIPTION
3  *      Au1100 LCD Driver.
4  *
5  * Copyright 2002 MontaVista Software
6  * Author: MontaVista Software, Inc.
7  *              ppopov@mvista.com or source@mvista.com
8  *
9  * Copyright 2002 Alchemy Semiconductor
10  * Author: Alchemy Semiconductor
11  *
12  * Based on:
13  * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device
14  *  Created 28 Dec 1997 by Geert Uytterhoeven
15  *
16  *  This program is free software; you can redistribute  it and/or modify it
17  *  under  the terms of  the GNU General  Public License as published by the
18  *  Free Software Foundation;  either version 2 of the  License, or (at your
19  *  option) any later version.
20  *
21  *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
22  *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
23  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
24  *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
25  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
27  *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28  *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
29  *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  *  You should have received a copy of the  GNU General Public License along
33  *  with this program; if not, write  to the Free Software Foundation, Inc.,
34  *  675 Mass Ave, Cambridge, MA 02139, USA.
35  */
36
37 #include <linux/module.h>
38 #include <linux/kernel.h>
39 #include <linux/errno.h>
40 #include <linux/string.h>
41 #include <linux/mm.h>
42 #include <linux/tty.h>
43 #include <linux/slab.h>
44 #include <linux/delay.h>
45 #include <linux/fb.h>
46 #include <linux/init.h>
47 #include <linux/pci.h>
48
49 #include <asm/au1000.h>
50 #include <asm/pb1100.h>
51 #include "au1100fb.h"
52
53 #include <video/fbcon.h>
54 #include <video/fbcon-mfb.h>
55 #include <video/fbcon-cfb2.h>
56 #include <video/fbcon-cfb4.h>
57 #include <video/fbcon-cfb8.h>
58 #include <video/fbcon-cfb16.h>
59
60 /*
61  * Sanity check. If this is a new Au1100 based board, search for
62  * the PB1100 ifdefs to make sure you modify the code accordingly.
63  */
64 #if defined(CONFIG_MIPS_PB1100) || defined(CONFIG_MIPS_DB1100) || defined(CONFIG_MIPS_HYDROGEN3)
65 #else
66 error Unknown Au1100 board
67 #endif
68
69 #define CMAPSIZE 16
70
71 static int my_lcd_index; /* default is zero */
72 struct known_lcd_panels *p_lcd;
73 AU1100_LCD *p_lcd_reg = (AU1100_LCD *)AU1100_LCD_ADDR;
74
75 struct au1100fb_info {
76         struct fb_info_gen gen;
77         unsigned long fb_virt_start;
78         unsigned long fb_size;
79         unsigned long fb_phys;
80         int mmaped;
81         int nohwcursor;
82
83         struct { unsigned red, green, blue, pad; } palette[256];
84
85 #if defined(FBCON_HAS_CFB16)
86         u16 fbcon_cmap16[16];
87 #endif
88 };
89
90
91 struct au1100fb_par {
92         struct fb_var_screeninfo var;
93
94         int line_length;  // in bytes
95         int cmap_len;     // color-map length
96 };
97
98
99 static struct au1100fb_info fb_info;
100 static struct au1100fb_par current_par;
101 static struct display disp;
102
103 int au1100fb_init(void);
104 void au1100fb_setup(char *options, int *ints);
105 static int au1100fb_mmap(struct fb_info *fb, struct file *file,
106                 struct vm_area_struct *vma);
107 static int au1100_blank(int blank_mode, struct fb_info_gen *info);
108 static int au1100fb_ioctl(struct inode *inode, struct file *file, u_int cmd,
109                           u_long arg, int con, struct fb_info *info);
110
111 void au1100_nocursor(struct display *p, int mode, int xx, int yy){};
112
113 static struct fb_ops au1100fb_ops = {
114         .owner          = THIS_MODULE,
115         .fb_get_fix     = fbgen_get_fix,
116         .fb_get_var     = fbgen_get_var,
117         .fb_set_var     = fbgen_set_var,
118         .fb_get_cmap    = fbgen_get_cmap,
119         .fb_set_cmap    = fbgen_set_cmap,
120         .fb_pan_display = fbgen_pan_display,
121         .fb_ioctl       = au1100fb_ioctl,
122         .fb_mmap        = au1100fb_mmap,
123 };
124
125 static void au1100_detect(void)
126 {
127         /*
128          *  This function should detect the current video mode settings
129          *  and store it as the default video mode
130          */
131
132         /*
133          * Yeh, well, we're not going to change any settings so we're
134          * always stuck with the default ...
135          */
136
137 }
138
139 static int au1100_encode_fix(struct fb_fix_screeninfo *fix,
140                 const void *_par, struct fb_info_gen *_info)
141 {
142         struct au1100fb_info *info = (struct au1100fb_info *) _info;
143         struct au1100fb_par *par = (struct au1100fb_par *) _par;
144         struct fb_var_screeninfo *var = &par->var;
145
146         memset(fix, 0, sizeof(struct fb_fix_screeninfo));
147
148         fix->smem_start = info->fb_phys;
149         fix->smem_len = info->fb_size;
150         fix->type = FB_TYPE_PACKED_PIXELS;
151         fix->type_aux = 0;
152         fix->visual = (var->bits_per_pixel == 8) ?
153                 FB_VISUAL_PSEUDOCOLOR   : FB_VISUAL_TRUECOLOR;
154         fix->ywrapstep = 0;
155         fix->xpanstep = 1;
156         fix->ypanstep = 1;
157         fix->line_length = current_par.line_length;
158         return 0;
159 }
160
161 static void set_color_bitfields(struct fb_var_screeninfo *var)
162 {
163         switch (var->bits_per_pixel) {
164         case 8:
165                 var->red.offset = 0;
166                 var->red.length = 8;
167                 var->green.offset = 0;
168                 var->green.length = 8;
169                 var->blue.offset = 0;
170                 var->blue.length = 8;
171                 var->transp.offset = 0;
172                 var->transp.length = 0;
173                 break;
174         case 16:        /* RGB 565 */
175                 var->red.offset = 11;
176                 var->red.length = 5;
177                 var->green.offset = 5;
178                 var->green.length = 6;
179                 var->blue.offset = 0;
180                 var->blue.length = 5;
181                 var->transp.offset = 0;
182                 var->transp.length = 0;
183                 break;
184         }
185
186         var->red.msb_right = 0;
187         var->green.msb_right = 0;
188         var->blue.msb_right = 0;
189         var->transp.msb_right = 0;
190 }
191
192 static int au1100_decode_var(const struct fb_var_screeninfo *var,
193                 void *_par, struct fb_info_gen *_info)
194 {
195
196         struct au1100fb_par *par = (struct au1100fb_par *)_par;
197
198         /*
199          * Don't allow setting any of these yet: xres and yres don't
200          * make sense for LCD panels.
201          */
202         if (var->xres != p_lcd->xres ||
203             var->yres != p_lcd->yres ||
204             var->xres != p_lcd->xres ||
205             var->yres != p_lcd->yres) {
206                 return -EINVAL;
207         }
208         if(var->bits_per_pixel != p_lcd->bpp) {
209                 return -EINVAL;
210         }
211
212         memset(par, 0, sizeof(struct au1100fb_par));
213         par->var = *var;
214
215         /* FIXME */
216         switch (var->bits_per_pixel) {
217                 case 8:
218                         par->var.bits_per_pixel = 8;
219                         break;
220                 case 16:
221                         par->var.bits_per_pixel = 16;
222                         break;
223                 default:
224                         printk("color depth %d bpp not supported\n",
225                                         var->bits_per_pixel);
226                         return -EINVAL;
227
228         }
229         set_color_bitfields(&par->var);
230         par->cmap_len = (par->var.bits_per_pixel == 8) ? 256 : 16;
231         return 0;
232 }
233
234 static int au1100_encode_var(struct fb_var_screeninfo *var,
235                 const void *par, struct fb_info_gen *_info)
236 {
237
238         *var = ((struct au1100fb_par *)par)->var;
239         return 0;
240 }
241
242 static void
243 au1100_get_par(void *_par, struct fb_info_gen *_info)
244 {
245         *(struct au1100fb_par *)_par = current_par;
246 }
247
248 static void au1100_set_par(const void *par, struct fb_info_gen *info)
249 {
250         /* nothing to do: we don't change any settings */
251 }
252
253 static int au1100_getcolreg(unsigned regno, unsigned *red, unsigned *green,
254                          unsigned *blue, unsigned *transp,
255                          struct fb_info *info)
256 {
257
258         struct au1100fb_info* i = (struct au1100fb_info*)info;
259
260         if (regno > 255)
261                 return 1;
262
263         *red    = i->palette[regno].red;
264         *green  = i->palette[regno].green;
265         *blue   = i->palette[regno].blue;
266         *transp = 0;
267
268         return 0;
269 }
270
271 static int au1100_setcolreg(unsigned regno, unsigned red, unsigned green,
272                          unsigned blue, unsigned transp,
273                          struct fb_info *info)
274 {
275         struct au1100fb_info* i = (struct au1100fb_info *)info;
276         u32 rgbcol;
277
278         if (regno > 255)
279                 return 1;
280
281         i->palette[regno].red    = red;
282         i->palette[regno].green  = green;
283         i->palette[regno].blue   = blue;
284
285         switch(p_lcd->bpp) {
286 #ifdef FBCON_HAS_CFB8
287         case 8:
288                 red >>= 10;
289                 green >>= 10;
290                 blue >>= 10;
291                 p_lcd_reg->lcd_pallettebase[regno] = (blue&0x1f) |
292                         ((green&0x3f)<<5) | ((red&0x1f)<<11);
293                 break;
294 #endif
295 #ifdef FBCON_HAS_CFB16
296         case 16:
297                 i->fbcon_cmap16[regno] =
298                         ((red & 0xf800) >> 0) |
299                         ((green & 0xfc00) >> 5) |
300                         ((blue & 0xf800) >> 11);
301                 break;
302 #endif
303         default:
304                 break;
305         }
306
307         return 0;
308 }
309
310
311 static int  au1100_blank(int blank_mode, struct fb_info_gen *_info)
312 {
313
314         switch (blank_mode) {
315         case VESA_NO_BLANKING:
316                 /* turn on panel */
317                 //printk("turn on panel\n");
318 #ifdef CONFIG_MIPS_PB1100
319                 p_lcd_reg->lcd_control |= LCD_CONTROL_GO;
320                 au_writew(au_readw(PB1100_G_CONTROL) | p_lcd->mode_backlight,
321                         PB1100_G_CONTROL);
322 #endif
323 #ifdef CONFIG_MIPS_HYDROGEN3
324                 /*  Turn controller & power supply on,  GPIO213 */
325                 au_writel(0x20002000, 0xB1700008);
326                 au_writel(0x00040000, 0xB1900108);
327                 au_writel(0x01000100, 0xB1700008);
328 #endif
329                 au_sync();
330                 break;
331
332         case VESA_VSYNC_SUSPEND:
333         case VESA_HSYNC_SUSPEND:
334         case VESA_POWERDOWN:
335                 /* turn off panel */
336                 //printk("turn off panel\n");
337 #ifdef CONFIG_MIPS_PB1100
338                 au_writew(au_readw(PB1100_G_CONTROL) & ~p_lcd->mode_backlight,
339                         PB1100_G_CONTROL);
340                 p_lcd_reg->lcd_control &= ~LCD_CONTROL_GO;
341 #endif
342                 au_sync();
343                 break;
344         default:
345                 break;
346
347         }
348         return 0;
349 }
350
351 static void au1100_set_disp(const void *unused, struct display *disp,
352                          struct fb_info_gen *info)
353 {
354         disp->screen_base = (char *)fb_info.fb_virt_start;
355
356         switch (disp->var.bits_per_pixel) {
357 #ifdef FBCON_HAS_CFB8
358         case 8:
359                 disp->dispsw = &fbcon_cfb8;
360                 if (fb_info.nohwcursor)
361                         fbcon_cfb8.cursor = au1100_nocursor;
362                 break;
363 #endif
364 #ifdef FBCON_HAS_CFB16
365         case 16:
366                 disp->dispsw = &fbcon_cfb16;
367                 disp->dispsw_data = fb_info.fbcon_cmap16;
368                 if (fb_info.nohwcursor)
369                         fbcon_cfb16.cursor = au1100_nocursor;
370                 break;
371 #endif
372         default:
373                 disp->dispsw = &fbcon_dummy;
374                 disp->dispsw_data = NULL;
375                 break;
376         }
377 }
378
379 static int
380 au1100fb_mmap(struct fb_info *_fb,
381              struct file *file,
382              struct vm_area_struct *vma)
383 {
384         unsigned int len;
385         unsigned long start=0, off;
386         struct au1100fb_info *fb = (struct au1100fb_info *)_fb;
387
388         if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) {
389                 return -EINVAL;
390         }
391
392         start = fb_info.fb_phys & PAGE_MASK;
393         len = PAGE_ALIGN((start & ~PAGE_MASK) + fb_info.fb_size);
394
395         off = vma->vm_pgoff << PAGE_SHIFT;
396
397         if ((vma->vm_end - vma->vm_start + off) > len) {
398                 return -EINVAL;
399         }
400
401         off += start;
402         vma->vm_pgoff = off >> PAGE_SHIFT;
403
404         pgprot_val(vma->vm_page_prot) &= ~_CACHE_MASK;
405         //pgprot_val(vma->vm_page_prot) |= _CACHE_CACHABLE_NONCOHERENT;
406         pgprot_val(vma->vm_page_prot) |= (6 << 9); //CCA=6
407
408         /* This is an IO map - tell maydump to skip this VMA */
409         vma->vm_flags |= VM_IO;
410
411         if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
412                                 vma->vm_end - vma->vm_start,
413                                 vma->vm_page_prot)) {
414                 return -EAGAIN;
415         }
416
417         fb->mmaped = 1;
418         return 0;
419 }
420
421 int au1100_pan_display(const struct fb_var_screeninfo *var,
422                        struct fb_info_gen *info)
423 {
424         return 0;
425 }
426
427 static int au1100fb_ioctl(struct inode *inode, struct file *file, u_int cmd,
428                           u_long arg, int con, struct fb_info *info)
429 {
430         /* nothing to do yet */
431         return -EINVAL;
432 }
433
434 static struct fbgen_hwswitch au1100_switch = {
435         au1100_detect,
436         au1100_encode_fix,
437         au1100_decode_var,
438         au1100_encode_var,
439         au1100_get_par,
440         au1100_set_par,
441         au1100_getcolreg,
442         au1100_setcolreg,
443         au1100_pan_display,
444         au1100_blank,
445         au1100_set_disp
446 };
447
448
449 int au1100_setmode(void)
450 {
451         int words;
452
453         /* FIXME Need to accomodate for swivel mode and 12bpp, <8bpp*/
454         switch (p_lcd->mode_control & LCD_CONTROL_SM)
455         {
456                 case LCD_CONTROL_SM_0:
457                 case LCD_CONTROL_SM_180:
458                 words = (p_lcd->xres * p_lcd->yres * p_lcd->bpp) / 32;
459                         break;
460                 case LCD_CONTROL_SM_90:
461                 case LCD_CONTROL_SM_270:
462                         /* is this correct? */
463                 words = (p_lcd->xres * p_lcd->bpp) / 8;
464                         break;
465                 default:
466                         printk("mode_control reg not initialized\n");
467                         return -EINVAL;
468         }
469
470         /*
471          * Setup LCD controller
472          */
473
474         p_lcd_reg->lcd_control = p_lcd->mode_control;
475         p_lcd_reg->lcd_intstatus = 0;
476         p_lcd_reg->lcd_intenable = 0;
477         p_lcd_reg->lcd_horztiming = p_lcd->mode_horztiming;
478         p_lcd_reg->lcd_verttiming = p_lcd->mode_verttiming;
479         p_lcd_reg->lcd_clkcontrol = p_lcd->mode_clkcontrol;
480         p_lcd_reg->lcd_words = words - 1;
481         p_lcd_reg->lcd_dmaaddr0 = fb_info.fb_phys;
482
483         /* turn on panel */
484 #ifdef CONFIG_MIPS_PB1100
485         au_writew(au_readw(PB1100_G_CONTROL) | p_lcd->mode_backlight,
486                         PB1100_G_CONTROL);
487 #endif
488 #ifdef CONFIG_MIPS_HYDROGEN3
489         /*  Turn controller & power supply on,  GPIO213 */
490         au_writel(0x20002000, 0xB1700008);
491         au_writel(0x00040000, 0xB1900108);
492         au_writel(0x01000100, 0xB1700008);
493 #endif
494
495         p_lcd_reg->lcd_control |= LCD_CONTROL_GO;
496
497         return 0;
498 }
499
500
501 int __init au1100fb_init(void)
502 {
503         uint32 sys_clksrc;
504         unsigned long page;
505
506         /*
507         * Get the panel information/display mode and update the registry
508         */
509         p_lcd = &panels[my_lcd_index];
510
511         switch (p_lcd->mode_control & LCD_CONTROL_SM)
512         {
513                 case LCD_CONTROL_SM_0:
514                 case LCD_CONTROL_SM_180:
515                 p_lcd->xres =
516                         (p_lcd->mode_horztiming & LCD_HORZTIMING_PPL) + 1;
517                 p_lcd->yres =
518                         (p_lcd->mode_verttiming & LCD_VERTTIMING_LPP) + 1;
519                         break;
520                 case LCD_CONTROL_SM_90:
521                 case LCD_CONTROL_SM_270:
522                 p_lcd->yres =
523                         (p_lcd->mode_horztiming & LCD_HORZTIMING_PPL) + 1;
524                 p_lcd->xres =
525                         (p_lcd->mode_verttiming & LCD_VERTTIMING_LPP) + 1;
526                         break;
527         }
528
529         /*
530          * Panel dimensions x bpp must be divisible by 32
531          */
532         if (((p_lcd->yres * p_lcd->bpp) % 32) != 0)
533                 printk("VERT %% 32\n");
534         if (((p_lcd->xres * p_lcd->bpp) % 32) != 0)
535                 printk("HORZ %% 32\n");
536
537         /*
538          * Allocate LCD framebuffer from system memory
539          */
540         fb_info.fb_size = (p_lcd->xres * p_lcd->yres * p_lcd->bpp) / 8;
541
542         current_par.var.xres = p_lcd->xres;
543         current_par.var.xres_virtual = p_lcd->xres;
544         current_par.var.yres = p_lcd->yres;
545         current_par.var.yres_virtual = p_lcd->yres;
546         current_par.var.bits_per_pixel = p_lcd->bpp;
547
548         /* FIX!!! only works for 8/16 bpp */
549         current_par.line_length = p_lcd->xres * p_lcd->bpp / 8; /* in bytes */
550         fb_info.fb_virt_start = (unsigned long )
551                 __get_free_pages(GFP_ATOMIC | GFP_DMA,
552                                 get_order(fb_info.fb_size + 0x1000));
553         if (!fb_info.fb_virt_start) {
554                 printk("Unable to allocate fb memory\n");
555                 return -ENOMEM;
556         }
557         fb_info.fb_phys = virt_to_bus((void *)fb_info.fb_virt_start);
558
559         /*
560          * Set page reserved so that mmap will work. This is necessary
561          * since we'll be remapping normal memory.
562          */
563         for (page = fb_info.fb_virt_start;
564              page < PAGE_ALIGN(fb_info.fb_virt_start + fb_info.fb_size);
565              page += PAGE_SIZE) {
566                 SetPageReserved(virt_to_page(page));
567         }
568
569         memset((void *)fb_info.fb_virt_start, 0, fb_info.fb_size);
570
571         /* set freqctrl now to allow more time to stabilize */
572         /* zero-out out LCD bits */
573         sys_clksrc = au_readl(SYS_CLKSRC) & ~0x000003e0;
574         sys_clksrc |= p_lcd->mode_toyclksrc;
575         au_writel(sys_clksrc, SYS_CLKSRC);
576
577         /* FIXME add check to make sure auxpll is what is expected! */
578         au1100_setmode();
579
580         fb_info.gen.parsize = sizeof(struct au1100fb_par);
581         fb_info.gen.fbhw = &au1100_switch;
582
583         strcpy(fb_info.gen.info.modename, "Au1100 LCD");
584         fb_info.gen.info.changevar = NULL;
585         fb_info.gen.info.node = -1;
586
587         fb_info.gen.info.fbops = &au1100fb_ops;
588         fb_info.gen.info.disp = &disp;
589         fb_info.gen.info.switch_con = &fbgen_switch;
590         fb_info.gen.info.updatevar = &fbgen_update_var;
591         fb_info.gen.info.blank = &fbgen_blank;
592         fb_info.gen.info.flags = FBINFO_FLAG_DEFAULT;
593
594         /* This should give a reasonable default video mode */
595         fbgen_get_var(&disp.var, -1, &fb_info.gen.info);
596         fbgen_do_set_var(&disp.var, 1, &fb_info.gen);
597         fbgen_set_disp(-1, &fb_info.gen);
598         fbgen_install_cmap(0, &fb_info.gen);
599         if (register_framebuffer(&fb_info.gen.info) < 0)
600                 return -EINVAL;
601         printk(KERN_INFO "fb%d: %s frame buffer device\n",
602                         GET_FB_IDX(fb_info.gen.info.node),
603                         fb_info.gen.info.modename);
604
605         return 0;
606 }
607
608
609 void au1100fb_cleanup(struct fb_info *info)
610 {
611         unregister_framebuffer(info);
612 }
613
614
615 void au1100fb_setup(char *options, int *ints)
616 {
617         char* this_opt;
618         int i;
619         int num_panels = sizeof(panels)/sizeof(struct known_lcd_panels);
620
621
622         if (!options || !*options)
623                 return;
624
625         for(this_opt=strtok(options, ","); this_opt;
626             this_opt=strtok(NULL, ",")) {
627                 if (!strncmp(this_opt, "panel:", 6)) {
628 #if defined(CONFIG_MIPS_PB1100) || defined(CONFIG_MIPS_DB1100)
629                         /* Read Pb1100 Switch S10 ? */
630                         if (!strncmp(this_opt+6, "s10", 3))
631                         {
632                                 int panel;
633                                 panel = *(volatile int *)0xAE000008; /* BCSR SWITCHES */
634                                 panel >>= 8;
635                                 panel &= 0x0F;
636                                 if (panel >= num_panels) panel = 0;
637                                 my_lcd_index = panel;
638                         }
639                         else
640 #endif
641                         /* Get the panel name, everything else if fixed */
642                         for (i=0; i<num_panels; i++) {
643                                 if (!strncmp(this_opt+6, panels[i].panel_name,
644                                                         strlen(this_opt))) {
645                                         my_lcd_index = i;
646                                         break;
647                                 }
648                         }
649                 }
650                 else if (!strncmp(this_opt, "nohwcursor", 10)) {
651                         printk("nohwcursor\n");
652                         fb_info.nohwcursor = 1;
653                 }
654         }
655
656         printk("au1100fb: Panel %d %s\n", my_lcd_index,
657                 panels[my_lcd_index].panel_name);
658 }
659
660
661
662 #ifdef MODULE
663 MODULE_LICENSE("GPL");
664 int init_module(void)
665 {
666         return au1100fb_init();
667 }
668
669 void cleanup_module(void)
670 {
671         au1100fb_cleanup(void);
672 }
673
674 MODULE_AUTHOR("Pete Popov <ppopov@mvista.com>");
675 MODULE_DESCRIPTION("Au1100 LCD framebuffer device driver");
676 #endif /* MODULE */