fbdev/amifb: Correct check for video memory size
[linux-2.6.git] / drivers / video / 68328fb.c
1 /*
2  *  linux/drivers/video/68328fb.c -- Low level implementation of the
3  *                                   mc68x328 LCD frame buffer device
4  *
5  *      Copyright (C) 2003 Georges Menie
6  *
7  *  This driver assumes an already configured controller (e.g. from config.c)
8  *  Keep the code clean of board specific initialization.
9  *
10  *  This code has not been tested with colors, colormap management functions
11  *  are minimal (no colormap data written to the 68328 registers...)
12  *
13  *  initial version of this driver:
14  *    Copyright (C) 1998,1999 Kenneth Albanowski <kjahds@kjahds.com>,
15  *                            The Silver Hammer Group, Ltd.
16  *
17  *  this version is based on :
18  *
19  *  linux/drivers/video/vfb.c -- Virtual frame buffer device
20  *
21  *      Copyright (C) 2002 James Simmons
22  *
23  *      Copyright (C) 1997 Geert Uytterhoeven
24  *
25  *  This file is subject to the terms and conditions of the GNU General Public
26  *  License. See the file COPYING in the main directory of this archive for
27  *  more details.
28  */
29
30 #include <linux/module.h>
31 #include <linux/kernel.h>
32 #include <linux/errno.h>
33 #include <linux/string.h>
34 #include <linux/mm.h>
35 #include <linux/vmalloc.h>
36 #include <linux/delay.h>
37 #include <linux/interrupt.h>
38 #include <asm/uaccess.h>
39 #include <linux/fb.h>
40 #include <linux/init.h>
41
42 #if defined(CONFIG_M68VZ328)
43 #include <asm/MC68VZ328.h>
44 #elif defined(CONFIG_M68EZ328)
45 #include <asm/MC68EZ328.h>
46 #elif defined(CONFIG_M68328)
47 #include <asm/MC68328.h>
48 #else
49 #error wrong architecture for the MC68x328 frame buffer device
50 #endif
51
52 #if defined(CONFIG_FB_68328_INVERT)
53 #define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO01
54 #else
55 #define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO10
56 #endif
57
58 static u_long videomemory;
59 static u_long videomemorysize;
60
61 static struct fb_info fb_info;
62 static u32 mc68x328fb_pseudo_palette[16];
63
64 static struct fb_var_screeninfo mc68x328fb_default __initdata = {
65         .red =          { 0, 8, 0 },
66         .green =        { 0, 8, 0 },
67         .blue =         { 0, 8, 0 },
68         .activate =     FB_ACTIVATE_TEST,
69         .height =       -1,
70         .width =        -1,
71         .pixclock =     20000,
72         .left_margin =  64,
73         .right_margin = 64,
74         .upper_margin = 32,
75         .lower_margin = 32,
76         .hsync_len =    64,
77         .vsync_len =    2,
78         .vmode =        FB_VMODE_NONINTERLACED,
79 };
80
81 static struct fb_fix_screeninfo mc68x328fb_fix __initdata = {
82         .id =           "68328fb",
83         .type =         FB_TYPE_PACKED_PIXELS,
84         .xpanstep =     1,
85         .ypanstep =     1,
86         .ywrapstep =    1,
87         .accel =        FB_ACCEL_NONE,
88 };
89
90     /*
91      *  Interface used by the world
92      */
93 int mc68x328fb_init(void);
94 int mc68x328fb_setup(char *);
95
96 static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
97                          struct fb_info *info);
98 static int mc68x328fb_set_par(struct fb_info *info);
99 static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
100                          u_int transp, struct fb_info *info);
101 static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
102                            struct fb_info *info);
103 static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma);
104
105 static struct fb_ops mc68x328fb_ops = {
106         .fb_check_var   = mc68x328fb_check_var,
107         .fb_set_par     = mc68x328fb_set_par,
108         .fb_setcolreg   = mc68x328fb_setcolreg,
109         .fb_pan_display = mc68x328fb_pan_display,
110         .fb_fillrect    = cfb_fillrect,
111         .fb_copyarea    = cfb_copyarea,
112         .fb_imageblit   = cfb_imageblit,
113         .fb_mmap        = mc68x328fb_mmap,
114 };
115
116     /*
117      *  Internal routines
118      */
119
120 static u_long get_line_length(int xres_virtual, int bpp)
121 {
122         u_long length;
123
124         length = xres_virtual * bpp;
125         length = (length + 31) & ~31;
126         length >>= 3;
127         return (length);
128 }
129
130     /*
131      *  Setting the video mode has been split into two parts.
132      *  First part, xxxfb_check_var, must not write anything
133      *  to hardware, it should only verify and adjust var.
134      *  This means it doesn't alter par but it does use hardware
135      *  data from it to check this var. 
136      */
137
138 static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
139                          struct fb_info *info)
140 {
141         u_long line_length;
142
143         /*
144          *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
145          *  as FB_VMODE_SMOOTH_XPAN is only used internally
146          */
147
148         if (var->vmode & FB_VMODE_CONUPDATE) {
149                 var->vmode |= FB_VMODE_YWRAP;
150                 var->xoffset = info->var.xoffset;
151                 var->yoffset = info->var.yoffset;
152         }
153
154         /*
155          *  Some very basic checks
156          */
157         if (!var->xres)
158                 var->xres = 1;
159         if (!var->yres)
160                 var->yres = 1;
161         if (var->xres > var->xres_virtual)
162                 var->xres_virtual = var->xres;
163         if (var->yres > var->yres_virtual)
164                 var->yres_virtual = var->yres;
165         if (var->bits_per_pixel <= 1)
166                 var->bits_per_pixel = 1;
167         else if (var->bits_per_pixel <= 8)
168                 var->bits_per_pixel = 8;
169         else if (var->bits_per_pixel <= 16)
170                 var->bits_per_pixel = 16;
171         else if (var->bits_per_pixel <= 24)
172                 var->bits_per_pixel = 24;
173         else if (var->bits_per_pixel <= 32)
174                 var->bits_per_pixel = 32;
175         else
176                 return -EINVAL;
177
178         if (var->xres_virtual < var->xoffset + var->xres)
179                 var->xres_virtual = var->xoffset + var->xres;
180         if (var->yres_virtual < var->yoffset + var->yres)
181                 var->yres_virtual = var->yoffset + var->yres;
182
183         /*
184          *  Memory limit
185          */
186         line_length =
187             get_line_length(var->xres_virtual, var->bits_per_pixel);
188         if (line_length * var->yres_virtual > videomemorysize)
189                 return -ENOMEM;
190
191         /*
192          * Now that we checked it we alter var. The reason being is that the video
193          * mode passed in might not work but slight changes to it might make it 
194          * work. This way we let the user know what is acceptable.
195          */
196         switch (var->bits_per_pixel) {
197         case 1:
198                 var->red.offset = 0;
199                 var->red.length = 1;
200                 var->green.offset = 0;
201                 var->green.length = 1;
202                 var->blue.offset = 0;
203                 var->blue.length = 1;
204                 var->transp.offset = 0;
205                 var->transp.length = 0;
206                 break;
207         case 8:
208                 var->red.offset = 0;
209                 var->red.length = 8;
210                 var->green.offset = 0;
211                 var->green.length = 8;
212                 var->blue.offset = 0;
213                 var->blue.length = 8;
214                 var->transp.offset = 0;
215                 var->transp.length = 0;
216                 break;
217         case 16:                /* RGBA 5551 */
218                 if (var->transp.length) {
219                         var->red.offset = 0;
220                         var->red.length = 5;
221                         var->green.offset = 5;
222                         var->green.length = 5;
223                         var->blue.offset = 10;
224                         var->blue.length = 5;
225                         var->transp.offset = 15;
226                         var->transp.length = 1;
227                 } else {        /* RGB 565 */
228                         var->red.offset = 0;
229                         var->red.length = 5;
230                         var->green.offset = 5;
231                         var->green.length = 6;
232                         var->blue.offset = 11;
233                         var->blue.length = 5;
234                         var->transp.offset = 0;
235                         var->transp.length = 0;
236                 }
237                 break;
238         case 24:                /* RGB 888 */
239                 var->red.offset = 0;
240                 var->red.length = 8;
241                 var->green.offset = 8;
242                 var->green.length = 8;
243                 var->blue.offset = 16;
244                 var->blue.length = 8;
245                 var->transp.offset = 0;
246                 var->transp.length = 0;
247                 break;
248         case 32:                /* RGBA 8888 */
249                 var->red.offset = 0;
250                 var->red.length = 8;
251                 var->green.offset = 8;
252                 var->green.length = 8;
253                 var->blue.offset = 16;
254                 var->blue.length = 8;
255                 var->transp.offset = 24;
256                 var->transp.length = 8;
257                 break;
258         }
259         var->red.msb_right = 0;
260         var->green.msb_right = 0;
261         var->blue.msb_right = 0;
262         var->transp.msb_right = 0;
263
264         return 0;
265 }
266
267 /* This routine actually sets the video mode. It's in here where we
268  * the hardware state info->par and fix which can be affected by the 
269  * change in par. For this driver it doesn't do much. 
270  */
271 static int mc68x328fb_set_par(struct fb_info *info)
272 {
273         info->fix.line_length = get_line_length(info->var.xres_virtual,
274                                                 info->var.bits_per_pixel);
275         return 0;
276 }
277
278     /*
279      *  Set a single color register. The values supplied are already
280      *  rounded down to the hardware's capabilities (according to the
281      *  entries in the var structure). Return != 0 for invalid regno.
282      */
283
284 static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
285                          u_int transp, struct fb_info *info)
286 {
287         if (regno >= 256)       /* no. of hw registers */
288                 return 1;
289         /*
290          * Program hardware... do anything you want with transp
291          */
292
293         /* grayscale works only partially under directcolor */
294         if (info->var.grayscale) {
295                 /* grayscale = 0.30*R + 0.59*G + 0.11*B */
296                 red = green = blue =
297                     (red * 77 + green * 151 + blue * 28) >> 8;
298         }
299
300         /* Directcolor:
301          *   var->{color}.offset contains start of bitfield
302          *   var->{color}.length contains length of bitfield
303          *   {hardwarespecific} contains width of RAMDAC
304          *   cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
305          *   RAMDAC[X] is programmed to (red, green, blue)
306          * 
307          * Pseudocolor:
308          *    uses offset = 0 && length = RAMDAC register width.
309          *    var->{color}.offset is 0
310          *    var->{color}.length contains width of DAC
311          *    cmap is not used
312          *    RAMDAC[X] is programmed to (red, green, blue)
313          * Truecolor:
314          *    does not use DAC. Usually 3 are present.
315          *    var->{color}.offset contains start of bitfield
316          *    var->{color}.length contains length of bitfield
317          *    cmap is programmed to (red << red.offset) | (green << green.offset) |
318          *                      (blue << blue.offset) | (transp << transp.offset)
319          *    RAMDAC does not exist
320          */
321 #define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
322         switch (info->fix.visual) {
323         case FB_VISUAL_TRUECOLOR:
324         case FB_VISUAL_PSEUDOCOLOR:
325                 red = CNVT_TOHW(red, info->var.red.length);
326                 green = CNVT_TOHW(green, info->var.green.length);
327                 blue = CNVT_TOHW(blue, info->var.blue.length);
328                 transp = CNVT_TOHW(transp, info->var.transp.length);
329                 break;
330         case FB_VISUAL_DIRECTCOLOR:
331                 red = CNVT_TOHW(red, 8);        /* expect 8 bit DAC */
332                 green = CNVT_TOHW(green, 8);
333                 blue = CNVT_TOHW(blue, 8);
334                 /* hey, there is bug in transp handling... */
335                 transp = CNVT_TOHW(transp, 8);
336                 break;
337         }
338 #undef CNVT_TOHW
339         /* Truecolor has hardware independent palette */
340         if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
341                 u32 v;
342
343                 if (regno >= 16)
344                         return 1;
345
346                 v = (red << info->var.red.offset) |
347                     (green << info->var.green.offset) |
348                     (blue << info->var.blue.offset) |
349                     (transp << info->var.transp.offset);
350                 switch (info->var.bits_per_pixel) {
351                 case 8:
352                         break;
353                 case 16:
354                         ((u32 *) (info->pseudo_palette))[regno] = v;
355                         break;
356                 case 24:
357                 case 32:
358                         ((u32 *) (info->pseudo_palette))[regno] = v;
359                         break;
360                 }
361                 return 0;
362         }
363         return 0;
364 }
365
366     /*
367      *  Pan or Wrap the Display
368      *
369      *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
370      */
371
372 static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
373                            struct fb_info *info)
374 {
375         if (var->vmode & FB_VMODE_YWRAP) {
376                 if (var->yoffset < 0
377                     || var->yoffset >= info->var.yres_virtual
378                     || var->xoffset)
379                         return -EINVAL;
380         } else {
381                 if (var->xoffset + var->xres > info->var.xres_virtual ||
382                     var->yoffset + var->yres > info->var.yres_virtual)
383                         return -EINVAL;
384         }
385         info->var.xoffset = var->xoffset;
386         info->var.yoffset = var->yoffset;
387         if (var->vmode & FB_VMODE_YWRAP)
388                 info->var.vmode |= FB_VMODE_YWRAP;
389         else
390                 info->var.vmode &= ~FB_VMODE_YWRAP;
391         return 0;
392 }
393
394     /*
395      *  Most drivers don't need their own mmap function 
396      */
397
398 static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
399 {
400 #ifndef MMU
401         /* this is uClinux (no MMU) specific code */
402
403         vma->vm_flags |= VM_RESERVED;
404         vma->vm_start = videomemory;
405
406         return 0;
407 #else
408         return -EINVAL;
409 #endif
410 }
411
412 int __init mc68x328fb_setup(char *options)
413 {
414 #if 0
415         char *this_opt;
416 #endif
417
418         if (!options || !*options)
419                 return 1;
420 #if 0
421         while ((this_opt = strsep(&options, ",")) != NULL) {
422                 if (!*this_opt)
423                         continue;
424                 if (!strncmp(this_opt, "disable", 7))
425                         mc68x328fb_enable = 0;
426         }
427 #endif
428         return 1;
429 }
430
431     /*
432      *  Initialisation
433      */
434
435 int __init mc68x328fb_init(void)
436 {
437 #ifndef MODULE
438         char *option = NULL;
439
440         if (fb_get_options("68328fb", &option))
441                 return -ENODEV;
442         mc68x328fb_setup(option);
443 #endif
444         /*
445          *  initialize the default mode from the LCD controller registers
446          */
447         mc68x328fb_default.xres = LXMAX;
448         mc68x328fb_default.yres = LYMAX+1;
449         mc68x328fb_default.xres_virtual = mc68x328fb_default.xres;
450         mc68x328fb_default.yres_virtual = mc68x328fb_default.yres;
451         mc68x328fb_default.bits_per_pixel = 1 + (LPICF & 0x01);
452         videomemory = LSSA;
453         videomemorysize = (mc68x328fb_default.xres_virtual+7) / 8 *
454                 mc68x328fb_default.yres_virtual * mc68x328fb_default.bits_per_pixel;
455
456         fb_info.screen_base = (void *)videomemory;
457         fb_info.fbops = &mc68x328fb_ops;
458         fb_info.var = mc68x328fb_default;
459         fb_info.fix = mc68x328fb_fix;
460         fb_info.fix.smem_start = videomemory;
461         fb_info.fix.smem_len = videomemorysize;
462         fb_info.fix.line_length =
463                 get_line_length(mc68x328fb_default.xres_virtual, mc68x328fb_default.bits_per_pixel);
464         fb_info.fix.visual = (mc68x328fb_default.bits_per_pixel) == 1 ?
465                 MC68X328FB_MONO_VISUAL : FB_VISUAL_PSEUDOCOLOR;
466         if (fb_info.var.bits_per_pixel == 1) {
467                 fb_info.var.red.length = fb_info.var.green.length = fb_info.var.blue.length = 1;
468                 fb_info.var.red.offset = fb_info.var.green.offset = fb_info.var.blue.offset = 0;
469         }
470         fb_info.pseudo_palette = &mc68x328fb_pseudo_palette;
471         fb_info.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN;
472
473         if (fb_alloc_cmap(&fb_info.cmap, 256, 0))
474                 return -ENOMEM;
475
476         if (register_framebuffer(&fb_info) < 0) {
477                 fb_dealloc_cmap(&fb_info.cmap);
478                 return -EINVAL;
479         }
480
481         printk(KERN_INFO
482                 "fb%d: %s frame buffer device\n", fb_info.node, fb_info.fix.id);
483         printk(KERN_INFO
484                 "fb%d: %dx%dx%d at 0x%08lx\n", fb_info.node,
485                 mc68x328fb_default.xres_virtual, mc68x328fb_default.yres_virtual,
486                 1 << mc68x328fb_default.bits_per_pixel, videomemory);
487
488         return 0;
489 }
490
491 module_init(mc68x328fb_init);
492
493 #ifdef MODULE
494
495 static void __exit mc68x328fb_cleanup(void)
496 {
497         unregister_framebuffer(&fb_info);
498         fb_dealloc_cmap(&fb_info.cmap);
499 }
500
501 module_exit(mc68x328fb_cleanup);
502
503 MODULE_LICENSE("GPL");
504 #endif                          /* MODULE */