| 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 <[email protected]>,
|
| 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 <linux/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 | static u_long videomemory;
|
| 53 | static u_long videomemorysize;
|
| 54 |
|
| 55 | static struct fb_info fb_info;
|
| 56 | static u32 mc68x328fb_pseudo_palette[16];
|
| 57 |
|
| 58 | static struct fb_var_screeninfo mc68x328fb_default __initdata = {
|
| 59 | .red = { 0, 8, 0 },
|
| 60 | .green = { 0, 8, 0 },
|
| 61 | .blue = { 0, 8, 0 },
|
| 62 | .activate = FB_ACTIVATE_TEST,
|
| 63 | .height = -1,
|
| 64 | .width = -1,
|
| 65 | .pixclock = 20000,
|
| 66 | .left_margin = 64,
|
| 67 | .right_margin = 64,
|
| 68 | .upper_margin = 32,
|
| 69 | .lower_margin = 32,
|
| 70 | .hsync_len = 64,
|
| 71 | .vsync_len = 2,
|
| 72 | .vmode = FB_VMODE_NONINTERLACED,
|
| 73 | };
|
| 74 |
|
| 75 | static const struct fb_fix_screeninfo mc68x328fb_fix __initconst = {
|
| 76 | .id = "68328fb",
|
| 77 | .type = FB_TYPE_PACKED_PIXELS,
|
| 78 | .xpanstep = 1,
|
| 79 | .ypanstep = 1,
|
| 80 | .ywrapstep = 1,
|
| 81 | .accel = FB_ACCEL_NONE,
|
| 82 | };
|
| 83 |
|
| 84 | |
| 85 | * Interface used by the world
|
| 86 |
|
| 87 | static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
|
| 88 | struct fb_info *info);
|
| 89 | static int mc68x328fb_set_par(struct fb_info *info);
|
| 90 | static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
|
| 91 | u_int transp, struct fb_info *info);
|
| 92 | static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
|
| 93 | struct fb_info *info);
|
| 94 | static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma);
|
| 95 |
|
| 96 | static const struct fb_ops mc68x328fb_ops = {
|
| 97 | .owner = THIS_MODULE,
|
| 98 | __FB_DEFAULT_IOMEM_OPS_RDWR,
|
| 99 | .fb_check_var = mc68x328fb_check_var,
|
| 100 | .fb_set_par = mc68x328fb_set_par,
|
| 101 | .fb_setcolreg = mc68x328fb_setcolreg,
|
| 102 | .fb_pan_display = mc68x328fb_pan_display,
|
| 103 | __FB_DEFAULT_IOMEM_OPS_DRAW,
|
| 104 | .fb_mmap = mc68x328fb_mmap,
|
| 105 | };
|
| 106 |
|
| 107 | |
| 108 | * Internal routines
|
| 109 |
|
| 110 |
|
| 111 | static u_long get_line_length(int xres_virtual, int bpp)
|
| 112 | {
|
| 113 | u_long length;
|
| 114 |
|
| 115 | length = xres_virtual * bpp;
|
| 116 | length = (length + 31) & ~31;
|
| 117 | length >>= 3;
|
| 118 | return (length);
|
| 119 | }
|
| 120 |
|
| 121 | |
| 122 | * Setting the video mode has been split into two parts.
|
| 123 | * First part, xxxfb_check_var, must not write anything
|
| 124 | * to hardware, it should only verify and adjust var.
|
| 125 | * This means it doesn't alter par but it does use hardware
|
| 126 | * data from it to check this var.
|
| 127 |
|
| 128 |
|
| 129 | static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
|
| 130 | struct fb_info *info)
|
| 131 | {
|
| 132 | u_long line_length;
|
| 133 |
|
| 134 | |
| 135 | * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
|
| 136 | * as FB_VMODE_SMOOTH_XPAN is only used internally
|
| 137 |
|
| 138 |
|
| 139 | if (var->vmode & FB_VMODE_CONUPDATE) {
|
| 140 | var->vmode |= FB_VMODE_YWRAP;
|
| 141 | var->xoffset = info->var.xoffset;
|
| 142 | var->yoffset = info->var.yoffset;
|
| 143 | }
|
| 144 |
|
| 145 | |
| 146 | * Some very basic checks
|
| 147 |
|
| 148 | if (!var->xres)
|
| 149 | var->xres = 1;
|
| 150 | if (!var->yres)
|
| 151 | var->yres = 1;
|
| 152 | if (var->xres > var->xres_virtual)
|
| 153 | var->xres_virtual = var->xres;
|
| 154 | if (var->yres > var->yres_virtual)
|
| 155 | var->yres_virtual = var->yres;
|
| 156 | if (var->bits_per_pixel <= 1)
|
| 157 | var->bits_per_pixel = 1;
|
| 158 | else if (var->bits_per_pixel <= 8)
|
| 159 | var->bits_per_pixel = 8;
|
| 160 | else if (var->bits_per_pixel <= 16)
|
| 161 | var->bits_per_pixel = 16;
|
| 162 | else if (var->bits_per_pixel <= 24)
|
| 163 | var->bits_per_pixel = 24;
|
| 164 | else if (var->bits_per_pixel <= 32)
|
| 165 | var->bits_per_pixel = 32;
|
| 166 | else
|
| 167 | return -EINVAL;
|
| 168 |
|
| 169 | if (var->xres_virtual < var->xoffset + var->xres)
|
| 170 | var->xres_virtual = var->xoffset + var->xres;
|
| 171 | if (var->yres_virtual < var->yoffset + var->yres)
|
| 172 | var->yres_virtual = var->yoffset + var->yres;
|
| 173 |
|
| 174 | |
| 175 | * Memory limit
|
| 176 |
|
| 177 | line_length =
|
| 178 | get_line_length(var->xres_virtual, var->bits_per_pixel);
|
| 179 | if (line_length * var->yres_virtual > videomemorysize)
|
| 180 | return -ENOMEM;
|
| 181 |
|
| 182 | |
| 183 | * Now that we checked it we alter var. The reason being is that the video
|
| 184 | * mode passed in might not work but slight changes to it might make it
|
| 185 | * work. This way we let the user know what is acceptable.
|
| 186 |
|
| 187 | switch (var->bits_per_pixel) {
|
| 188 | case 1:
|
| 189 | var->red.offset = 0;
|
| 190 | var->red.length = 1;
|
| 191 | var->green.offset = 0;
|
| 192 | var->green.length = 1;
|
| 193 | var->blue.offset = 0;
|
| 194 | var->blue.length = 1;
|
| 195 | var->transp.offset = 0;
|
| 196 | var->transp.length = 0;
|
| 197 | break;
|
| 198 | case 8:
|
| 199 | var->red.offset = 0;
|
| 200 | var->red.length = 8;
|
| 201 | var->green.offset = 0;
|
| 202 | var->green.length = 8;
|
| 203 | var->blue.offset = 0;
|
| 204 | var->blue.length = 8;
|
| 205 | var->transp.offset = 0;
|
| 206 | var->transp.length = 0;
|
| 207 | break;
|
| 208 | case 16:
|
| 209 | if (var->transp.length) {
|
| 210 | var->red.offset = 0;
|
| 211 | var->red.length = 5;
|
| 212 | var->green.offset = 5;
|
| 213 | var->green.length = 5;
|
| 214 | var->blue.offset = 10;
|
| 215 | var->blue.length = 5;
|
| 216 | var->transp.offset = 15;
|
| 217 | var->transp.length = 1;
|
| 218 | } else {
|
| 219 | var->red.offset = 0;
|
| 220 | var->red.length = 5;
|
| 221 | var->green.offset = 5;
|
| 222 | var->green.length = 6;
|
| 223 | var->blue.offset = 11;
|
| 224 | var->blue.length = 5;
|
| 225 | var->transp.offset = 0;
|
| 226 | var->transp.length = 0;
|
| 227 | }
|
| 228 | break;
|
| 229 | case 24:
|
| 230 | var->red.offset = 0;
|
| 231 | var->red.length = 8;
|
| 232 | var->green.offset = 8;
|
| 233 | var->green.length = 8;
|
| 234 | var->blue.offset = 16;
|
| 235 | var->blue.length = 8;
|
| 236 | var->transp.offset = 0;
|
| 237 | var->transp.length = 0;
|
| 238 | break;
|
| 239 | case 32:
|
| 240 | var->red.offset = 0;
|
| 241 | var->red.length = 8;
|
| 242 | var->green.offset = 8;
|
| 243 | var->green.length = 8;
|
| 244 | var->blue.offset = 16;
|
| 245 | var->blue.length = 8;
|
| 246 | var->transp.offset = 24;
|
| 247 | var->transp.length = 8;
|
| 248 | break;
|
| 249 | }
|
| 250 | var->red.msb_right = 0;
|
| 251 | var->green.msb_right = 0;
|
| 252 | var->blue.msb_right = 0;
|
| 253 | var->transp.msb_right = 0;
|
| 254 |
|
| 255 | return 0;
|
| 256 | }
|
| 257 |
|
| 258 | |
| 259 | * the hardware state info->par and fix which can be affected by the
|
| 260 | * change in par. For this driver it doesn't do much.
|
| 261 |
|
| 262 | static int mc68x328fb_set_par(struct fb_info *info)
|
| 263 | {
|
| 264 | info->fix.line_length = get_line_length(info->var.xres_virtual,
|
| 265 | info->var.bits_per_pixel);
|
| 266 | return 0;
|
| 267 | }
|
| 268 |
|
| 269 | |
| 270 | * Set a single color register. The values supplied are already
|
| 271 | * rounded down to the hardware's capabilities (according to the
|
| 272 | * entries in the var structure). Return != 0 for invalid regno.
|
| 273 |
|
| 274 |
|
| 275 | static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
|
| 276 | u_int transp, struct fb_info *info)
|
| 277 | {
|
| 278 | if (regno >= 256)
|
| 279 | return 1;
|
| 280 | |
| 281 | * Program hardware... do anything you want with transp
|
| 282 |
|
| 283 |
|
| 284 |
|
| 285 | if (info->var.grayscale) {
|
| 286 |
|
| 287 | red = green = blue =
|
| 288 | (red * 77 + green * 151 + blue * 28) >> 8;
|
| 289 | }
|
| 290 |
|
| 291 | |
| 292 | * var->{color}.offset contains start of bitfield
|
| 293 | * var->{color}.length contains length of bitfield
|
| 294 | * {hardwarespecific} contains width of RAMDAC
|
| 295 | * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
|
| 296 | * RAMDAC[X] is programmed to (red, green, blue)
|
| 297 | *
|
| 298 | * Pseudocolor:
|
| 299 | * uses offset = 0 && length = RAMDAC register width.
|
| 300 | * var->{color}.offset is 0
|
| 301 | * var->{color}.length contains width of DAC
|
| 302 | * cmap is not used
|
| 303 | * RAMDAC[X] is programmed to (red, green, blue)
|
| 304 | * Truecolor:
|
| 305 | * does not use DAC. Usually 3 are present.
|
| 306 | * var->{color}.offset contains start of bitfield
|
| 307 | * var->{color}.length contains length of bitfield
|
| 308 | * cmap is programmed to (red << red.offset) | (green << green.offset) |
|
| 309 | * (blue << blue.offset) | (transp << transp.offset)
|
| 310 | * RAMDAC does not exist
|
| 311 |
|
| 312 | #define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
|
| 313 | switch (info->fix.visual) {
|
| 314 | case FB_VISUAL_TRUECOLOR:
|
| 315 | case FB_VISUAL_PSEUDOCOLOR:
|
| 316 | red = CNVT_TOHW(red, info->var.red.length);
|
| 317 | green = CNVT_TOHW(green, info->var.green.length);
|
| 318 | blue = CNVT_TOHW(blue, info->var.blue.length);
|
| 319 | transp = CNVT_TOHW(transp, info->var.transp.length);
|
| 320 | break;
|
| 321 | case FB_VISUAL_DIRECTCOLOR:
|
| 322 | red = CNVT_TOHW(red, 8);
|
| 323 | green = CNVT_TOHW(green, 8);
|
| 324 | blue = CNVT_TOHW(blue, 8);
|
| 325 |
|
| 326 | transp = CNVT_TOHW(transp, 8);
|
| 327 | break;
|
| 328 | }
|
| 329 | #undef CNVT_TOHW
|
| 330 |
|
| 331 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
|
| 332 | u32 v;
|
| 333 |
|
| 334 | if (regno >= 16)
|
| 335 | return 1;
|
| 336 |
|
| 337 | v = (red << info->var.red.offset) |
|
| 338 | (green << info->var.green.offset) |
|
| 339 | (blue << info->var.blue.offset) |
|
| 340 | (transp << info->var.transp.offset);
|
| 341 | switch (info->var.bits_per_pixel) {
|
| 342 | case 8:
|
| 343 | break;
|
| 344 | case 16:
|
| 345 | ((u32 *) (info->pseudo_palette))[regno] = v;
|
| 346 | break;
|
| 347 | case 24:
|
| 348 | case 32:
|
| 349 | ((u32 *) (info->pseudo_palette))[regno] = v;
|
| 350 | break;
|
| 351 | }
|
| 352 | return 0;
|
| 353 | }
|
| 354 | return 0;
|
| 355 | }
|
| 356 |
|
| 357 | |
| 358 | * Pan or Wrap the Display
|
| 359 | *
|
| 360 | * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
|
| 361 |
|
| 362 |
|
| 363 | static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
|
| 364 | struct fb_info *info)
|
| 365 | {
|
| 366 | if (var->vmode & FB_VMODE_YWRAP) {
|
| 367 | if (var->yoffset < 0
|
| 368 | || var->yoffset >= info->var.yres_virtual
|
| 369 | || var->xoffset)
|
| 370 | return -EINVAL;
|
| 371 | } else {
|
| 372 | if (var->xoffset + info->var.xres > info->var.xres_virtual ||
|
| 373 | var->yoffset + info->var.yres > info->var.yres_virtual)
|
| 374 | return -EINVAL;
|
| 375 | }
|
| 376 | info->var.xoffset = var->xoffset;
|
| 377 | info->var.yoffset = var->yoffset;
|
| 378 | if (var->vmode & FB_VMODE_YWRAP)
|
| 379 | info->var.vmode |= FB_VMODE_YWRAP;
|
| 380 | else
|
| 381 | info->var.vmode &= ~FB_VMODE_YWRAP;
|
| 382 | return 0;
|
| 383 | }
|
| 384 |
|
| 385 | |
| 386 | * Most drivers don't need their own mmap function
|
| 387 |
|
| 388 |
|
| 389 | static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
| 390 | {
|
| 391 | #ifndef MMU
|
| 392 |
|
| 393 |
|
| 394 | vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
|
| 395 | vma->vm_start = videomemory;
|
| 396 |
|
| 397 | return 0;
|
| 398 | #else
|
| 399 | return -EINVAL;
|
| 400 | #endif
|
| 401 | }
|
| 402 |
|
| 403 | static int __init mc68x328fb_setup(char *options)
|
| 404 | {
|
| 405 | if (!options || !*options)
|
| 406 | return 1;
|
| 407 | return 1;
|
| 408 | }
|
| 409 |
|
| 410 | |
| 411 | * Initialisation
|
| 412 |
|
| 413 |
|
| 414 | static int __init mc68x328fb_init(void)
|
| 415 | {
|
| 416 | #ifndef MODULE
|
| 417 | char *option = NULL;
|
| 418 |
|
| 419 | if (fb_get_options("68328fb", &option))
|
| 420 | return -ENODEV;
|
| 421 | mc68x328fb_setup(option);
|
| 422 | #endif
|
| 423 | |
| 424 | * initialize the default mode from the LCD controller registers
|
| 425 |
|
| 426 | mc68x328fb_default.xres = LXMAX;
|
| 427 | mc68x328fb_default.yres = LYMAX+1;
|
| 428 | mc68x328fb_default.xres_virtual = mc68x328fb_default.xres;
|
| 429 | mc68x328fb_default.yres_virtual = mc68x328fb_default.yres;
|
| 430 | mc68x328fb_default.bits_per_pixel = 1 + (LPICF & 0x01);
|
| 431 | videomemory = LSSA;
|
| 432 | videomemorysize = (mc68x328fb_default.xres_virtual+7) / 8 *
|
| 433 | mc68x328fb_default.yres_virtual * mc68x328fb_default.bits_per_pixel;
|
| 434 |
|
| 435 | fb_info.screen_base = (void *)videomemory;
|
| 436 | fb_info.fbops = &mc68x328fb_ops;
|
| 437 | fb_info.var = mc68x328fb_default;
|
| 438 | fb_info.fix = mc68x328fb_fix;
|
| 439 | fb_info.fix.smem_start = videomemory;
|
| 440 | fb_info.fix.smem_len = videomemorysize;
|
| 441 | fb_info.fix.line_length =
|
| 442 | get_line_length(mc68x328fb_default.xres_virtual, mc68x328fb_default.bits_per_pixel);
|
| 443 | fb_info.fix.visual = (mc68x328fb_default.bits_per_pixel) == 1 ?
|
| 444 | FB_VISUAL_MONO10 : FB_VISUAL_PSEUDOCOLOR;
|
| 445 | if (fb_info.var.bits_per_pixel == 1) {
|
| 446 | fb_info.var.red.length = fb_info.var.green.length = fb_info.var.blue.length = 1;
|
| 447 | fb_info.var.red.offset = fb_info.var.green.offset = fb_info.var.blue.offset = 0;
|
| 448 | }
|
| 449 | fb_info.pseudo_palette = &mc68x328fb_pseudo_palette;
|
| 450 | fb_info.flags = FBINFO_HWACCEL_YPAN;
|
| 451 |
|
| 452 | if (fb_alloc_cmap(&fb_info.cmap, 256, 0))
|
| 453 | return -ENOMEM;
|
| 454 |
|
| 455 | if (register_framebuffer(&fb_info) < 0) {
|
| 456 | fb_dealloc_cmap(&fb_info.cmap);
|
| 457 | return -EINVAL;
|
| 458 | }
|
| 459 |
|
| 460 | fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id);
|
| 461 | fb_info(&fb_info, "%dx%dx%d at 0x%08lx\n",
|
| 462 | mc68x328fb_default.xres_virtual,
|
| 463 | mc68x328fb_default.yres_virtual,
|
| 464 | 1 << mc68x328fb_default.bits_per_pixel, videomemory);
|
| 465 |
|
| 466 | return 0;
|
| 467 | }
|
| 468 |
|
| 469 | module_init(mc68x328fb_init);
|
| 470 |
|
| 471 | #ifdef MODULE
|
| 472 |
|
| 473 | static void __exit mc68x328fb_cleanup(void)
|
| 474 | {
|
| 475 | unregister_framebuffer(&fb_info);
|
| 476 | fb_dealloc_cmap(&fb_info.cmap);
|
| 477 | }
|
| 478 |
|
| 479 | module_exit(mc68x328fb_cleanup);
|
| 480 |
|
| 481 | MODULE_LICENSE("GPL");
|
| 482 | #endif
|