Mercurial > hg > minimon
comparison vfb/vfb.c @ 2:bac8ed8d6eb9
add jpeg coding and test stuff
| author | Peter Meerwald <pmeerw@pmeerw.net> |
|---|---|
| date | Sun, 08 May 2011 18:22:22 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1:111d4bbce605 | 2:bac8ed8d6eb9 |
|---|---|
| 1 /* | |
| 2 * linux/drivers/video/vfb.c -- Virtual frame buffer device | |
| 3 * | |
| 4 * Copyright (C) 2002 James Simmons | |
| 5 * | |
| 6 * Copyright (C) 1997 Geert Uytterhoeven | |
| 7 * | |
| 8 * This file is subject to the terms and conditions of the GNU General Public | |
| 9 * License. See the file COPYING in the main directory of this archive for | |
| 10 * more details. | |
| 11 */ | |
| 12 | |
| 13 #include <linux/module.h> | |
| 14 #include <linux/kernel.h> | |
| 15 #include <linux/errno.h> | |
| 16 #include <linux/string.h> | |
| 17 #include <linux/mm.h> | |
| 18 #include <linux/vmalloc.h> | |
| 19 #include <linux/delay.h> | |
| 20 #include <linux/interrupt.h> | |
| 21 #include <linux/platform_device.h> | |
| 22 | |
| 23 #include <linux/fb.h> | |
| 24 #include <linux/init.h> | |
| 25 | |
| 26 /* | |
| 27 * RAM we reserve for the frame buffer. This defines the maximum screen | |
| 28 * size | |
| 29 * | |
| 30 * The default can be overridden if the driver is compiled as a module | |
| 31 */ | |
| 32 | |
| 33 #define VIDEOMEMSIZE (1*1024*1024) /* 1 MB */ | |
| 34 | |
| 35 static void *videomemory; | |
| 36 static u_long videomemorysize = VIDEOMEMSIZE; | |
| 37 module_param(videomemorysize, ulong, 0); | |
| 38 | |
| 39 /********************************************************************** | |
| 40 * | |
| 41 * Memory management | |
| 42 * | |
| 43 **********************************************************************/ | |
| 44 static void *rvmalloc(unsigned long size) | |
| 45 { | |
| 46 void *mem; | |
| 47 unsigned long adr; | |
| 48 | |
| 49 size = PAGE_ALIGN(size); | |
| 50 mem = vmalloc_32(size); | |
| 51 if (!mem) | |
| 52 return NULL; | |
| 53 | |
| 54 memset(mem, 0, size); /* Clear the ram out, no junk to the user */ | |
| 55 adr = (unsigned long) mem; | |
| 56 while (size > 0) { | |
| 57 SetPageReserved(vmalloc_to_page((void *)adr)); | |
| 58 adr += PAGE_SIZE; | |
| 59 size -= PAGE_SIZE; | |
| 60 } | |
| 61 | |
| 62 return mem; | |
| 63 } | |
| 64 | |
| 65 static void rvfree(void *mem, unsigned long size) | |
| 66 { | |
| 67 unsigned long adr; | |
| 68 | |
| 69 if (!mem) | |
| 70 return; | |
| 71 | |
| 72 adr = (unsigned long) mem; | |
| 73 while ((long) size > 0) { | |
| 74 ClearPageReserved(vmalloc_to_page((void *)adr)); | |
| 75 adr += PAGE_SIZE; | |
| 76 size -= PAGE_SIZE; | |
| 77 } | |
| 78 vfree(mem); | |
| 79 } | |
| 80 | |
| 81 static struct fb_var_screeninfo vfb_default __devinitdata = { | |
| 82 .xres = 640, | |
| 83 .yres = 480, | |
| 84 .xres_virtual = 640, | |
| 85 .yres_virtual = 480, | |
| 86 .bits_per_pixel = 8, | |
| 87 .red = { 0, 8, 0 }, | |
| 88 .green = { 0, 8, 0 }, | |
| 89 .blue = { 0, 8, 0 }, | |
| 90 .activate = FB_ACTIVATE_TEST, | |
| 91 .height = -1, | |
| 92 .width = -1, | |
| 93 .pixclock = 20000, | |
| 94 .left_margin = 64, | |
| 95 .right_margin = 64, | |
| 96 .upper_margin = 32, | |
| 97 .lower_margin = 32, | |
| 98 .hsync_len = 64, | |
| 99 .vsync_len = 2, | |
| 100 .vmode = FB_VMODE_NONINTERLACED, | |
| 101 }; | |
| 102 | |
| 103 static struct fb_fix_screeninfo vfb_fix __devinitdata = { | |
| 104 .id = "Virtual FB", | |
| 105 .type = FB_TYPE_PACKED_PIXELS, | |
| 106 .visual = FB_VISUAL_PSEUDOCOLOR, | |
| 107 .xpanstep = 1, | |
| 108 .ypanstep = 1, | |
| 109 .ywrapstep = 1, | |
| 110 .accel = FB_ACCEL_NONE, | |
| 111 }; | |
| 112 | |
| 113 static int vfb_enable __initdata = 0; /* disabled by default */ | |
| 114 module_param(vfb_enable, bool, 0); | |
| 115 | |
| 116 static int vfb_check_var(struct fb_var_screeninfo *var, | |
| 117 struct fb_info *info); | |
| 118 static int vfb_set_par(struct fb_info *info); | |
| 119 static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, | |
| 120 u_int transp, struct fb_info *info); | |
| 121 static int vfb_pan_display(struct fb_var_screeninfo *var, | |
| 122 struct fb_info *info); | |
| 123 static int vfb_mmap(struct fb_info *info, | |
| 124 struct vm_area_struct *vma); | |
| 125 | |
| 126 static struct fb_ops vfb_ops = { | |
| 127 .fb_read = fb_sys_read, | |
| 128 .fb_write = fb_sys_write, | |
| 129 .fb_check_var = vfb_check_var, | |
| 130 .fb_set_par = vfb_set_par, | |
| 131 .fb_setcolreg = vfb_setcolreg, | |
| 132 .fb_pan_display = vfb_pan_display, | |
| 133 .fb_fillrect = sys_fillrect, | |
| 134 .fb_copyarea = sys_copyarea, | |
| 135 .fb_imageblit = sys_imageblit, | |
| 136 .fb_mmap = vfb_mmap, | |
| 137 }; | |
| 138 | |
| 139 /* | |
| 140 * Internal routines | |
| 141 */ | |
| 142 | |
| 143 static u_long get_line_length(int xres_virtual, int bpp) | |
| 144 { | |
| 145 u_long length; | |
| 146 | |
| 147 length = xres_virtual * bpp; | |
| 148 length = (length + 31) & ~31; | |
| 149 length >>= 3; | |
| 150 return (length); | |
| 151 } | |
| 152 | |
| 153 /* | |
| 154 * Setting the video mode has been split into two parts. | |
| 155 * First part, xxxfb_check_var, must not write anything | |
| 156 * to hardware, it should only verify and adjust var. | |
| 157 * This means it doesn't alter par but it does use hardware | |
| 158 * data from it to check this var. | |
| 159 */ | |
| 160 | |
| 161 static int vfb_check_var(struct fb_var_screeninfo *var, | |
| 162 struct fb_info *info) | |
| 163 { | |
| 164 u_long line_length; | |
| 165 | |
| 166 /* | |
| 167 * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! | |
| 168 * as FB_VMODE_SMOOTH_XPAN is only used internally | |
| 169 */ | |
| 170 | |
| 171 if (var->vmode & FB_VMODE_CONUPDATE) { | |
| 172 var->vmode |= FB_VMODE_YWRAP; | |
| 173 var->xoffset = info->var.xoffset; | |
| 174 var->yoffset = info->var.yoffset; | |
| 175 } | |
| 176 | |
| 177 /* | |
| 178 * Some very basic checks | |
| 179 */ | |
| 180 if (!var->xres) | |
| 181 var->xres = 1; | |
| 182 if (!var->yres) | |
| 183 var->yres = 1; | |
| 184 if (var->xres > var->xres_virtual) | |
| 185 var->xres_virtual = var->xres; | |
| 186 if (var->yres > var->yres_virtual) | |
| 187 var->yres_virtual = var->yres; | |
| 188 if (var->bits_per_pixel <= 1) | |
| 189 var->bits_per_pixel = 1; | |
| 190 else if (var->bits_per_pixel <= 8) | |
| 191 var->bits_per_pixel = 8; | |
| 192 else if (var->bits_per_pixel <= 16) | |
| 193 var->bits_per_pixel = 16; | |
| 194 else if (var->bits_per_pixel <= 24) | |
| 195 var->bits_per_pixel = 24; | |
| 196 else if (var->bits_per_pixel <= 32) | |
| 197 var->bits_per_pixel = 32; | |
| 198 else | |
| 199 return -EINVAL; | |
| 200 | |
| 201 if (var->xres_virtual < var->xoffset + var->xres) | |
| 202 var->xres_virtual = var->xoffset + var->xres; | |
| 203 if (var->yres_virtual < var->yoffset + var->yres) | |
| 204 var->yres_virtual = var->yoffset + var->yres; | |
| 205 | |
| 206 /* | |
| 207 * Memory limit | |
| 208 */ | |
| 209 line_length = | |
| 210 get_line_length(var->xres_virtual, var->bits_per_pixel); | |
| 211 if (line_length * var->yres_virtual > videomemorysize) | |
| 212 return -ENOMEM; | |
| 213 | |
| 214 /* | |
| 215 * Now that we checked it we alter var. The reason being is that the video | |
| 216 * mode passed in might not work but slight changes to it might make it | |
| 217 * work. This way we let the user know what is acceptable. | |
| 218 */ | |
| 219 switch (var->bits_per_pixel) { | |
| 220 case 1: | |
| 221 case 8: | |
| 222 var->red.offset = 0; | |
| 223 var->red.length = 8; | |
| 224 var->green.offset = 0; | |
| 225 var->green.length = 8; | |
| 226 var->blue.offset = 0; | |
| 227 var->blue.length = 8; | |
| 228 var->transp.offset = 0; | |
| 229 var->transp.length = 0; | |
| 230 break; | |
| 231 case 16: /* RGBA 5551 */ | |
| 232 if (var->transp.length) { | |
| 233 var->red.offset = 0; | |
| 234 var->red.length = 5; | |
| 235 var->green.offset = 5; | |
| 236 var->green.length = 5; | |
| 237 var->blue.offset = 10; | |
| 238 var->blue.length = 5; | |
| 239 var->transp.offset = 15; | |
| 240 var->transp.length = 1; | |
| 241 } else { /* RGB 565 */ | |
| 242 var->red.offset = 0; | |
| 243 var->red.length = 5; | |
| 244 var->green.offset = 5; | |
| 245 var->green.length = 6; | |
| 246 var->blue.offset = 11; | |
| 247 var->blue.length = 5; | |
| 248 var->transp.offset = 0; | |
| 249 var->transp.length = 0; | |
| 250 } | |
| 251 break; | |
| 252 case 24: /* RGB 888 */ | |
| 253 var->red.offset = 0; | |
| 254 var->red.length = 8; | |
| 255 var->green.offset = 8; | |
| 256 var->green.length = 8; | |
| 257 var->blue.offset = 16; | |
| 258 var->blue.length = 8; | |
| 259 var->transp.offset = 0; | |
| 260 var->transp.length = 0; | |
| 261 break; | |
| 262 case 32: /* RGBA 8888 */ | |
| 263 var->red.offset = 0; | |
| 264 var->red.length = 8; | |
| 265 var->green.offset = 8; | |
| 266 var->green.length = 8; | |
| 267 var->blue.offset = 16; | |
| 268 var->blue.length = 8; | |
| 269 var->transp.offset = 24; | |
| 270 var->transp.length = 8; | |
| 271 break; | |
| 272 } | |
| 273 var->red.msb_right = 0; | |
| 274 var->green.msb_right = 0; | |
| 275 var->blue.msb_right = 0; | |
| 276 var->transp.msb_right = 0; | |
| 277 | |
| 278 return 0; | |
| 279 } | |
| 280 | |
| 281 /* This routine actually sets the video mode. It's in here where we | |
| 282 * the hardware state info->par and fix which can be affected by the | |
| 283 * change in par. For this driver it doesn't do much. | |
| 284 */ | |
| 285 static int vfb_set_par(struct fb_info *info) | |
| 286 { | |
| 287 info->fix.line_length = get_line_length(info->var.xres_virtual, | |
| 288 info->var.bits_per_pixel); | |
| 289 return 0; | |
| 290 } | |
| 291 | |
| 292 /* | |
| 293 * Set a single color register. The values supplied are already | |
| 294 * rounded down to the hardware's capabilities (according to the | |
| 295 * entries in the var structure). Return != 0 for invalid regno. | |
| 296 */ | |
| 297 | |
| 298 static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, | |
| 299 u_int transp, struct fb_info *info) | |
| 300 { | |
| 301 if (regno >= 256) /* no. of hw registers */ | |
| 302 return 1; | |
| 303 /* | |
| 304 * Program hardware... do anything you want with transp | |
| 305 */ | |
| 306 | |
| 307 /* grayscale works only partially under directcolor */ | |
| 308 if (info->var.grayscale) { | |
| 309 /* grayscale = 0.30*R + 0.59*G + 0.11*B */ | |
| 310 red = green = blue = | |
| 311 (red * 77 + green * 151 + blue * 28) >> 8; | |
| 312 } | |
| 313 | |
| 314 /* Directcolor: | |
| 315 * var->{color}.offset contains start of bitfield | |
| 316 * var->{color}.length contains length of bitfield | |
| 317 * {hardwarespecific} contains width of RAMDAC | |
| 318 * cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset) | |
| 319 * RAMDAC[X] is programmed to (red, green, blue) | |
| 320 * | |
| 321 * Pseudocolor: | |
| 322 * var->{color}.offset is 0 unless the palette index takes less than | |
| 323 * bits_per_pixel bits and is stored in the upper | |
| 324 * bits of the pixel value | |
| 325 * var->{color}.length is set so that 1 << length is the number of available | |
| 326 * palette entries | |
| 327 * cmap is not used | |
| 328 * RAMDAC[X] is programmed to (red, green, blue) | |
| 329 * | |
| 330 * Truecolor: | |
| 331 * does not use DAC. Usually 3 are present. | |
| 332 * var->{color}.offset contains start of bitfield | |
| 333 * var->{color}.length contains length of bitfield | |
| 334 * cmap is programmed to (red << red.offset) | (green << green.offset) | | |
| 335 * (blue << blue.offset) | (transp << transp.offset) | |
| 336 * RAMDAC does not exist | |
| 337 */ | |
| 338 #define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16) | |
| 339 switch (info->fix.visual) { | |
| 340 case FB_VISUAL_TRUECOLOR: | |
| 341 case FB_VISUAL_PSEUDOCOLOR: | |
| 342 red = CNVT_TOHW(red, info->var.red.length); | |
| 343 green = CNVT_TOHW(green, info->var.green.length); | |
| 344 blue = CNVT_TOHW(blue, info->var.blue.length); | |
| 345 transp = CNVT_TOHW(transp, info->var.transp.length); | |
| 346 break; | |
| 347 case FB_VISUAL_DIRECTCOLOR: | |
| 348 red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ | |
| 349 green = CNVT_TOHW(green, 8); | |
| 350 blue = CNVT_TOHW(blue, 8); | |
| 351 /* hey, there is bug in transp handling... */ | |
| 352 transp = CNVT_TOHW(transp, 8); | |
| 353 break; | |
| 354 } | |
| 355 #undef CNVT_TOHW | |
| 356 /* Truecolor has hardware independent palette */ | |
| 357 if (info->fix.visual == FB_VISUAL_TRUECOLOR) { | |
| 358 u32 v; | |
| 359 | |
| 360 if (regno >= 16) | |
| 361 return 1; | |
| 362 | |
| 363 v = (red << info->var.red.offset) | | |
| 364 (green << info->var.green.offset) | | |
| 365 (blue << info->var.blue.offset) | | |
| 366 (transp << info->var.transp.offset); | |
| 367 switch (info->var.bits_per_pixel) { | |
| 368 case 8: | |
| 369 break; | |
| 370 case 16: | |
| 371 ((u32 *) (info->pseudo_palette))[regno] = v; | |
| 372 break; | |
| 373 case 24: | |
| 374 case 32: | |
| 375 ((u32 *) (info->pseudo_palette))[regno] = v; | |
| 376 break; | |
| 377 } | |
| 378 return 0; | |
| 379 } | |
| 380 return 0; | |
| 381 } | |
| 382 | |
| 383 /* | |
| 384 * Pan or Wrap the Display | |
| 385 * | |
| 386 * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag | |
| 387 */ | |
| 388 | |
| 389 static int vfb_pan_display(struct fb_var_screeninfo *var, | |
| 390 struct fb_info *info) | |
| 391 { | |
| 392 if (var->vmode & FB_VMODE_YWRAP) { | |
| 393 if (var->yoffset < 0 | |
| 394 || var->yoffset >= info->var.yres_virtual | |
| 395 || var->xoffset) | |
| 396 return -EINVAL; | |
| 397 } else { | |
| 398 if (var->xoffset + var->xres > info->var.xres_virtual || | |
| 399 var->yoffset + var->yres > info->var.yres_virtual) | |
| 400 return -EINVAL; | |
| 401 } | |
| 402 info->var.xoffset = var->xoffset; | |
| 403 info->var.yoffset = var->yoffset; | |
| 404 if (var->vmode & FB_VMODE_YWRAP) | |
| 405 info->var.vmode |= FB_VMODE_YWRAP; | |
| 406 else | |
| 407 info->var.vmode &= ~FB_VMODE_YWRAP; | |
| 408 return 0; | |
| 409 } | |
| 410 | |
| 411 /* | |
| 412 * Most drivers don't need their own mmap function | |
| 413 */ | |
| 414 | |
| 415 static int vfb_mmap(struct fb_info *info, | |
| 416 struct vm_area_struct *vma) | |
| 417 { | |
| 418 unsigned long start = vma->vm_start; | |
| 419 unsigned long size = vma->vm_end - vma->vm_start; | |
| 420 unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; | |
| 421 unsigned long page, pos; | |
| 422 | |
| 423 if (offset + size > info->fix.smem_len) { | |
| 424 return -EINVAL; | |
| 425 } | |
| 426 | |
| 427 pos = (unsigned long)info->fix.smem_start + offset; | |
| 428 | |
| 429 while (size > 0) { | |
| 430 page = vmalloc_to_pfn((void *)pos); | |
| 431 if (remap_pfn_range(vma, start, page, PAGE_SIZE, PAGE_SHARED)) { | |
| 432 return -EAGAIN; | |
| 433 } | |
| 434 start += PAGE_SIZE; | |
| 435 pos += PAGE_SIZE; | |
| 436 if (size > PAGE_SIZE) | |
| 437 size -= PAGE_SIZE; | |
| 438 else | |
| 439 size = 0; | |
| 440 } | |
| 441 | |
| 442 vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ | |
| 443 return 0; | |
| 444 | |
| 445 } | |
| 446 | |
| 447 #ifndef MODULE | |
| 448 /* | |
| 449 * The virtual framebuffer driver is only enabled if explicitly | |
| 450 * requested by passing 'video=vfb:' (or any actual options). | |
| 451 */ | |
| 452 static int __init vfb_setup(char *options) | |
| 453 { | |
| 454 char *this_opt; | |
| 455 | |
| 456 vfb_enable = 0; | |
| 457 | |
| 458 if (!options) | |
| 459 return 1; | |
| 460 | |
| 461 vfb_enable = 1; | |
| 462 | |
| 463 if (!*options) | |
| 464 return 1; | |
| 465 | |
| 466 while ((this_opt = strsep(&options, ",")) != NULL) { | |
| 467 if (!*this_opt) | |
| 468 continue; | |
| 469 /* Test disable for backwards compatibility */ | |
| 470 if (!strcmp(this_opt, "disable")) | |
| 471 vfb_enable = 0; | |
| 472 } | |
| 473 return 1; | |
| 474 } | |
| 475 #endif /* MODULE */ | |
| 476 | |
| 477 /* | |
| 478 * Initialisation | |
| 479 */ | |
| 480 | |
| 481 static int __devinit vfb_probe(struct platform_device *dev) | |
| 482 { | |
| 483 struct fb_info *info; | |
| 484 int retval = -ENOMEM; | |
| 485 | |
| 486 /* | |
| 487 * For real video cards we use ioremap. | |
| 488 */ | |
| 489 if (!(videomemory = rvmalloc(videomemorysize))) | |
| 490 return retval; | |
| 491 | |
| 492 /* | |
| 493 * VFB must clear memory to prevent kernel info | |
| 494 * leakage into userspace | |
| 495 * VGA-based drivers MUST NOT clear memory if | |
| 496 * they want to be able to take over vgacon | |
| 497 */ | |
| 498 memset(videomemory, 0, videomemorysize); | |
| 499 | |
| 500 info = framebuffer_alloc(sizeof(u32) * 256, &dev->dev); | |
| 501 if (!info) | |
| 502 goto err; | |
| 503 | |
| 504 info->screen_base = (char __iomem *)videomemory; | |
| 505 info->fbops = &vfb_ops; | |
| 506 | |
| 507 retval = fb_find_mode(&info->var, info, NULL, | |
| 508 NULL, 0, NULL, 8); | |
| 509 | |
| 510 if (!retval || (retval == 4)) | |
| 511 info->var = vfb_default; | |
| 512 vfb_fix.smem_start = (unsigned long) videomemory; | |
| 513 vfb_fix.smem_len = videomemorysize; | |
| 514 info->fix = vfb_fix; | |
| 515 info->pseudo_palette = info->par; | |
| 516 info->par = NULL; | |
| 517 info->flags = FBINFO_FLAG_DEFAULT; | |
| 518 | |
| 519 retval = fb_alloc_cmap(&info->cmap, 256, 0); | |
| 520 if (retval < 0) | |
| 521 goto err1; | |
| 522 | |
| 523 retval = register_framebuffer(info); | |
| 524 if (retval < 0) | |
| 525 goto err2; | |
| 526 platform_set_drvdata(dev, info); | |
| 527 | |
| 528 printk(KERN_INFO | |
| 529 "fb%d: Virtual frame buffer device, using %ldK of video memory\n", | |
| 530 info->node, videomemorysize >> 10); | |
| 531 return 0; | |
| 532 err2: | |
| 533 fb_dealloc_cmap(&info->cmap); | |
| 534 err1: | |
| 535 framebuffer_release(info); | |
| 536 err: | |
| 537 rvfree(videomemory, videomemorysize); | |
| 538 return retval; | |
| 539 } | |
| 540 | |
| 541 static int vfb_remove(struct platform_device *dev) | |
| 542 { | |
| 543 struct fb_info *info = platform_get_drvdata(dev); | |
| 544 | |
| 545 if (info) { | |
| 546 unregister_framebuffer(info); | |
| 547 rvfree(videomemory, videomemorysize); | |
| 548 fb_dealloc_cmap(&info->cmap); | |
| 549 framebuffer_release(info); | |
| 550 } | |
| 551 return 0; | |
| 552 } | |
| 553 | |
| 554 static struct platform_driver vfb_driver = { | |
| 555 .probe = vfb_probe, | |
| 556 .remove = vfb_remove, | |
| 557 .driver = { | |
| 558 .name = "vfb", | |
| 559 }, | |
| 560 }; | |
| 561 | |
| 562 static struct platform_device *vfb_device; | |
| 563 | |
| 564 static int __init vfb_init(void) | |
| 565 { | |
| 566 int ret = 0; | |
| 567 | |
| 568 #ifndef MODULE | |
| 569 char *option = NULL; | |
| 570 | |
| 571 if (fb_get_options("vfb", &option)) | |
| 572 return -ENODEV; | |
| 573 vfb_setup(option); | |
| 574 #endif | |
| 575 | |
| 576 if (!vfb_enable) | |
| 577 return -ENXIO; | |
| 578 | |
| 579 ret = platform_driver_register(&vfb_driver); | |
| 580 | |
| 581 if (!ret) { | |
| 582 vfb_device = platform_device_alloc("vfb", 0); | |
| 583 | |
| 584 if (vfb_device) | |
| 585 ret = platform_device_add(vfb_device); | |
| 586 else | |
| 587 ret = -ENOMEM; | |
| 588 | |
| 589 if (ret) { | |
| 590 platform_device_put(vfb_device); | |
| 591 platform_driver_unregister(&vfb_driver); | |
| 592 } | |
| 593 } | |
| 594 | |
| 595 return ret; | |
| 596 } | |
| 597 | |
| 598 module_init(vfb_init); | |
| 599 | |
| 600 #ifdef MODULE | |
| 601 static void __exit vfb_exit(void) | |
| 602 { | |
| 603 platform_device_unregister(vfb_device); | |
| 604 platform_driver_unregister(&vfb_driver); | |
| 605 } | |
| 606 | |
| 607 module_exit(vfb_exit); | |
| 608 | |
| 609 MODULE_LICENSE("GPL"); | |
| 610 #endif /* MODULE */ |
