1 /* 2 * linux/drivers/video/mmp/fb/mmpfb.c 3 * Framebuffer driver for Marvell Display controller. 4 * 5 * Copyright (C) 2012 Marvell Technology Group Ltd. 6 * Authors: Zhou Zhu <zzhu3@marvell.com> 7 * 8 * This program is free software; you can redistribute it and/or modify it 9 * under the terms of the GNU General Public License as published by the 10 * Free Software Foundation; either version 2 of the License, or (at your 11 * option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 16 * more details. 17 * 18 * You should have received a copy of the GNU General Public License along with 19 * this program. If not, see <http://www.gnu.org/licenses/>. 20 * 21 */ 22 #include <linux/module.h> 23 #include <linux/dma-mapping.h> 24 #include <linux/platform_device.h> 25 #include "mmpfb.h" 26 27 static int var_to_pixfmt(struct fb_var_screeninfo *var) 28 { 29 /* 30 * Pseudocolor mode? 31 */ 32 if (var->bits_per_pixel == 8) 33 return PIXFMT_PSEUDOCOLOR; 34 35 /* 36 * Check for YUV422PLANAR. 37 */ 38 if (var->bits_per_pixel == 16 && var->red.length == 8 && 39 var->green.length == 4 && var->blue.length == 4) { 40 if (var->green.offset >= var->blue.offset) 41 return PIXFMT_YUV422P; 42 else 43 return PIXFMT_YVU422P; 44 } 45 46 /* 47 * Check for YUV420PLANAR. 48 */ 49 if (var->bits_per_pixel == 12 && var->red.length == 8 && 50 var->green.length == 2 && var->blue.length == 2) { 51 if (var->green.offset >= var->blue.offset) 52 return PIXFMT_YUV420P; 53 else 54 return PIXFMT_YVU420P; 55 } 56 57 /* 58 * Check for YUV422PACK. 59 */ 60 if (var->bits_per_pixel == 16 && var->red.length == 16 && 61 var->green.length == 16 && var->blue.length == 16) { 62 if (var->red.offset == 0) 63 return PIXFMT_YUYV; 64 else if (var->green.offset >= var->blue.offset) 65 return PIXFMT_UYVY; 66 else 67 return PIXFMT_VYUY; 68 } 69 70 /* 71 * Check for 565/1555. 72 */ 73 if (var->bits_per_pixel == 16 && var->red.length <= 5 && 74 var->green.length <= 6 && var->blue.length <= 5) { 75 if (var->transp.length == 0) { 76 if (var->red.offset >= var->blue.offset) 77 return PIXFMT_RGB565; 78 else 79 return PIXFMT_BGR565; 80 } 81 } 82 83 /* 84 * Check for 888/A888. 85 */ 86 if (var->bits_per_pixel <= 32 && var->red.length <= 8 && 87 var->green.length <= 8 && var->blue.length <= 8) { 88 if (var->bits_per_pixel == 24 && var->transp.length == 0) { 89 if (var->red.offset >= var->blue.offset) 90 return PIXFMT_RGB888PACK; 91 else 92 return PIXFMT_BGR888PACK; 93 } 94 95 if (var->bits_per_pixel == 32 && var->transp.offset == 24) { 96 if (var->red.offset >= var->blue.offset) 97 return PIXFMT_RGBA888; 98 else 99 return PIXFMT_BGRA888; 100 } else { 101 if (var->red.offset >= var->blue.offset) 102 return PIXFMT_RGB888UNPACK; 103 else 104 return PIXFMT_BGR888UNPACK; 105 } 106 107 /* fall through */ 108 } 109 110 return -EINVAL; 111 } 112 113 static void pixfmt_to_var(struct fb_var_screeninfo *var, int pix_fmt) 114 { 115 switch (pix_fmt) { 116 case PIXFMT_RGB565: 117 var->bits_per_pixel = 16; 118 var->red.offset = 11; var->red.length = 5; 119 var->green.offset = 5; var->green.length = 6; 120 var->blue.offset = 0; var->blue.length = 5; 121 var->transp.offset = 0; var->transp.length = 0; 122 break; 123 case PIXFMT_BGR565: 124 var->bits_per_pixel = 16; 125 var->red.offset = 0; var->red.length = 5; 126 var->green.offset = 5; var->green.length = 6; 127 var->blue.offset = 11; var->blue.length = 5; 128 var->transp.offset = 0; var->transp.length = 0; 129 break; 130 case PIXFMT_RGB888UNPACK: 131 var->bits_per_pixel = 32; 132 var->red.offset = 16; var->red.length = 8; 133 var->green.offset = 8; var->green.length = 8; 134 var->blue.offset = 0; var->blue.length = 8; 135 var->transp.offset = 0; var->transp.length = 0; 136 break; 137 case PIXFMT_BGR888UNPACK: 138 var->bits_per_pixel = 32; 139 var->red.offset = 0; var->red.length = 8; 140 var->green.offset = 8; var->green.length = 8; 141 var->blue.offset = 16; var->blue.length = 8; 142 var->transp.offset = 0; var->transp.length = 0; 143 break; 144 case PIXFMT_RGBA888: 145 var->bits_per_pixel = 32; 146 var->red.offset = 16; var->red.length = 8; 147 var->green.offset = 8; var->green.length = 8; 148 var->blue.offset = 0; var->blue.length = 8; 149 var->transp.offset = 24; var->transp.length = 8; 150 break; 151 case PIXFMT_BGRA888: 152 var->bits_per_pixel = 32; 153 var->red.offset = 0; var->red.length = 8; 154 var->green.offset = 8; var->green.length = 8; 155 var->blue.offset = 16; var->blue.length = 8; 156 var->transp.offset = 24; var->transp.length = 8; 157 break; 158 case PIXFMT_RGB888PACK: 159 var->bits_per_pixel = 24; 160 var->red.offset = 16; var->red.length = 8; 161 var->green.offset = 8; var->green.length = 8; 162 var->blue.offset = 0; var->blue.length = 8; 163 var->transp.offset = 0; var->transp.length = 0; 164 break; 165 case PIXFMT_BGR888PACK: 166 var->bits_per_pixel = 24; 167 var->red.offset = 0; var->red.length = 8; 168 var->green.offset = 8; var->green.length = 8; 169 var->blue.offset = 16; var->blue.length = 8; 170 var->transp.offset = 0; var->transp.length = 0; 171 break; 172 case PIXFMT_YUV420P: 173 var->bits_per_pixel = 12; 174 var->red.offset = 4; var->red.length = 8; 175 var->green.offset = 2; var->green.length = 2; 176 var->blue.offset = 0; var->blue.length = 2; 177 var->transp.offset = 0; var->transp.length = 0; 178 break; 179 case PIXFMT_YVU420P: 180 var->bits_per_pixel = 12; 181 var->red.offset = 4; var->red.length = 8; 182 var->green.offset = 0; var->green.length = 2; 183 var->blue.offset = 2; var->blue.length = 2; 184 var->transp.offset = 0; var->transp.length = 0; 185 break; 186 case PIXFMT_YUV422P: 187 var->bits_per_pixel = 16; 188 var->red.offset = 8; var->red.length = 8; 189 var->green.offset = 4; var->green.length = 4; 190 var->blue.offset = 0; var->blue.length = 4; 191 var->transp.offset = 0; var->transp.length = 0; 192 break; 193 case PIXFMT_YVU422P: 194 var->bits_per_pixel = 16; 195 var->red.offset = 8; var->red.length = 8; 196 var->green.offset = 0; var->green.length = 4; 197 var->blue.offset = 4; var->blue.length = 4; 198 var->transp.offset = 0; var->transp.length = 0; 199 break; 200 case PIXFMT_UYVY: 201 var->bits_per_pixel = 16; 202 var->red.offset = 8; var->red.length = 16; 203 var->green.offset = 4; var->green.length = 16; 204 var->blue.offset = 0; var->blue.length = 16; 205 var->transp.offset = 0; var->transp.length = 0; 206 break; 207 case PIXFMT_VYUY: 208 var->bits_per_pixel = 16; 209 var->red.offset = 8; var->red.length = 16; 210 var->green.offset = 0; var->green.length = 16; 211 var->blue.offset = 4; var->blue.length = 16; 212 var->transp.offset = 0; var->transp.length = 0; 213 break; 214 case PIXFMT_YUYV: 215 var->bits_per_pixel = 16; 216 var->red.offset = 0; var->red.length = 16; 217 var->green.offset = 4; var->green.length = 16; 218 var->blue.offset = 8; var->blue.length = 16; 219 var->transp.offset = 0; var->transp.length = 0; 220 break; 221 case PIXFMT_PSEUDOCOLOR: 222 var->bits_per_pixel = 8; 223 var->red.offset = 0; var->red.length = 8; 224 var->green.offset = 0; var->green.length = 8; 225 var->blue.offset = 0; var->blue.length = 8; 226 var->transp.offset = 0; var->transp.length = 0; 227 break; 228 } 229 } 230 231 /* 232 * fb framework has its limitation: 233 * 1. input color/output color is not seprated 234 * 2. fb_videomode not include output color 235 * so for fb usage, we keep a output format which is not changed 236 * then it's added for mmpmode 237 */ 238 static void fbmode_to_mmpmode(struct mmp_mode *mode, 239 struct fb_videomode *videomode, int output_fmt) 240 { 241 u64 div_result = 1000000000000ll; 242 mode->name = videomode->name; 243 mode->refresh = videomode->refresh; 244 mode->xres = videomode->xres; 245 mode->yres = videomode->yres; 246 247 do_div(div_result, videomode->pixclock); 248 mode->pixclock_freq = (u32)div_result; 249 250 mode->left_margin = videomode->left_margin; 251 mode->right_margin = videomode->right_margin; 252 mode->upper_margin = videomode->upper_margin; 253 mode->lower_margin = videomode->lower_margin; 254 mode->hsync_len = videomode->hsync_len; 255 mode->vsync_len = videomode->vsync_len; 256 mode->hsync_invert = !!(videomode->sync & FB_SYNC_HOR_HIGH_ACT); 257 mode->vsync_invert = !!(videomode->sync & FB_SYNC_VERT_HIGH_ACT); 258 /* no defined flag in fb, use vmode>>3*/ 259 mode->invert_pixclock = !!(videomode->vmode & 8); 260 mode->pix_fmt_out = output_fmt; 261 } 262 263 static void mmpmode_to_fbmode(struct fb_videomode *videomode, 264 struct mmp_mode *mode) 265 { 266 u64 div_result = 1000000000000ll; 267 268 videomode->name = mode->name; 269 videomode->refresh = mode->refresh; 270 videomode->xres = mode->xres; 271 videomode->yres = mode->yres; 272 273 do_div(div_result, mode->pixclock_freq); 274 videomode->pixclock = (u32)div_result; 275 276 videomode->left_margin = mode->left_margin; 277 videomode->right_margin = mode->right_margin; 278 videomode->upper_margin = mode->upper_margin; 279 videomode->lower_margin = mode->lower_margin; 280 videomode->hsync_len = mode->hsync_len; 281 videomode->vsync_len = mode->vsync_len; 282 videomode->sync = (mode->hsync_invert ? FB_SYNC_HOR_HIGH_ACT : 0) 283 | (mode->vsync_invert ? FB_SYNC_VERT_HIGH_ACT : 0); 284 videomode->vmode = mode->invert_pixclock ? 8 : 0; 285 } 286 287 static int mmpfb_check_var(struct fb_var_screeninfo *var, 288 struct fb_info *info) 289 { 290 struct mmpfb_info *fbi = info->par; 291 292 if (var->bits_per_pixel == 8) 293 return -EINVAL; 294 /* 295 * Basic geometry sanity checks. 296 */ 297 if (var->xoffset + var->xres > var->xres_virtual) 298 return -EINVAL; 299 if (var->yoffset + var->yres > var->yres_virtual) 300 return -EINVAL; 301 302 /* 303 * Check size of framebuffer. 304 */ 305 if (var->xres_virtual * var->yres_virtual * 306 (var->bits_per_pixel >> 3) > fbi->fb_size) 307 return -EINVAL; 308 309 return 0; 310 } 311 312 static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) 313 { 314 return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset; 315 } 316 317 static u32 to_rgb(u16 red, u16 green, u16 blue) 318 { 319 red >>= 8; 320 green >>= 8; 321 blue >>= 8; 322 323 return (red << 16) | (green << 8) | blue; 324 } 325 326 static int mmpfb_setcolreg(unsigned int regno, unsigned int red, 327 unsigned int green, unsigned int blue, 328 unsigned int trans, struct fb_info *info) 329 { 330 struct mmpfb_info *fbi = info->par; 331 u32 val; 332 333 if (info->fix.visual == FB_VISUAL_TRUECOLOR && regno < 16) { 334 val = chan_to_field(red, &info->var.red); 335 val |= chan_to_field(green, &info->var.green); 336 val |= chan_to_field(blue , &info->var.blue); 337 fbi->pseudo_palette[regno] = val; 338 } 339 340 if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR && regno < 256) { 341 val = to_rgb(red, green, blue); 342 /* TODO */ 343 } 344 345 return 0; 346 } 347 348 static int mmpfb_pan_display(struct fb_var_screeninfo *var, 349 struct fb_info *info) 350 { 351 struct mmpfb_info *fbi = info->par; 352 struct mmp_addr addr; 353 354 memset(&addr, 0, sizeof(addr)); 355 addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) 356 * var->bits_per_pixel / 8 + fbi->fb_start_dma; 357 mmp_overlay_set_addr(fbi->overlay, &addr); 358 359 return 0; 360 } 361 362 static int var_update(struct fb_info *info) 363 { 364 struct mmpfb_info *fbi = info->par; 365 struct fb_var_screeninfo *var = &info->var; 366 struct fb_videomode *m; 367 int pix_fmt; 368 369 /* set pix_fmt */ 370 pix_fmt = var_to_pixfmt(var); 371 if (pix_fmt < 0) 372 return -EINVAL; 373 pixfmt_to_var(var, pix_fmt); 374 fbi->pix_fmt = pix_fmt; 375 376 /* set var according to best video mode*/ 377 m = (struct fb_videomode *)fb_match_mode(var, &info->modelist); 378 if (!m) { 379 dev_err(fbi->dev, "set par: no match mode, use best mode\n"); 380 m = (struct fb_videomode *)fb_find_best_mode(var, 381 &info->modelist); 382 fb_videomode_to_var(var, m); 383 } 384 memcpy(&fbi->mode, m, sizeof(struct fb_videomode)); 385 386 /* fix to 2* yres */ 387 var->yres_virtual = var->yres * 2; 388 info->fix.visual = (pix_fmt == PIXFMT_PSEUDOCOLOR) ? 389 FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; 390 info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; 391 info->fix.ypanstep = var->yres; 392 return 0; 393 } 394 395 static void mmpfb_set_win(struct fb_info *info) 396 { 397 struct mmpfb_info *fbi = info->par; 398 struct fb_var_screeninfo *var = &info->var; 399 struct mmp_win win; 400 u32 stride; 401 402 memset(&win, 0, sizeof(win)); 403 win.xsrc = win.xdst = fbi->mode.xres; 404 win.ysrc = win.ydst = fbi->mode.yres; 405 win.pix_fmt = fbi->pix_fmt; 406 stride = pixfmt_to_stride(win.pix_fmt); 407 win.pitch[0] = var->xres_virtual * stride; 408 win.pitch[1] = win.pitch[2] = 409 (stride == 1) ? (var->xres_virtual >> 1) : 0; 410 mmp_overlay_set_win(fbi->overlay, &win); 411 } 412 413 static int mmpfb_set_par(struct fb_info *info) 414 { 415 struct mmpfb_info *fbi = info->par; 416 struct fb_var_screeninfo *var = &info->var; 417 struct mmp_addr addr; 418 struct mmp_mode mode; 419 int ret; 420 421 ret = var_update(info); 422 if (ret != 0) 423 return ret; 424 425 /* set window/path according to new videomode */ 426 fbmode_to_mmpmode(&mode, &fbi->mode, fbi->output_fmt); 427 mmp_path_set_mode(fbi->path, &mode); 428 429 /* set window related info */ 430 mmpfb_set_win(info); 431 432 /* set address always */ 433 memset(&addr, 0, sizeof(addr)); 434 addr.phys[0] = (var->yoffset * var->xres_virtual + var->xoffset) 435 * var->bits_per_pixel / 8 + fbi->fb_start_dma; 436 mmp_overlay_set_addr(fbi->overlay, &addr); 437 438 return 0; 439 } 440 441 static void mmpfb_power(struct mmpfb_info *fbi, int power) 442 { 443 struct mmp_addr addr; 444 struct fb_var_screeninfo *var = &fbi->fb_info->var; 445 446 /* for power on, always set address/window again */ 447 if (power) { 448 /* set window related info */ 449 mmpfb_set_win(fbi->fb_info); 450 451 /* set address always */ 452 memset(&addr, 0, sizeof(addr)); 453 addr.phys[0] = fbi->fb_start_dma + 454 (var->yoffset * var->xres_virtual + var->xoffset) 455 * var->bits_per_pixel / 8; 456 mmp_overlay_set_addr(fbi->overlay, &addr); 457 } 458 mmp_overlay_set_onoff(fbi->overlay, power); 459 } 460 461 static int mmpfb_blank(int blank, struct fb_info *info) 462 { 463 struct mmpfb_info *fbi = info->par; 464 465 mmpfb_power(fbi, (blank == FB_BLANK_UNBLANK)); 466 467 return 0; 468 } 469 470 static struct fb_ops mmpfb_ops = { 471 .owner = THIS_MODULE, 472 .fb_blank = mmpfb_blank, 473 .fb_check_var = mmpfb_check_var, 474 .fb_set_par = mmpfb_set_par, 475 .fb_setcolreg = mmpfb_setcolreg, 476 .fb_pan_display = mmpfb_pan_display, 477 .fb_fillrect = cfb_fillrect, 478 .fb_copyarea = cfb_copyarea, 479 .fb_imageblit = cfb_imageblit, 480 }; 481 482 static int modes_setup(struct mmpfb_info *fbi) 483 { 484 struct fb_videomode *videomodes; 485 struct mmp_mode *mmp_modes; 486 struct fb_info *info = fbi->fb_info; 487 int videomode_num, i; 488 489 /* get videomodes from path */ 490 videomode_num = mmp_path_get_modelist(fbi->path, &mmp_modes); 491 if (!videomode_num) { 492 dev_warn(fbi->dev, "can't get videomode num\n"); 493 return 0; 494 } 495 /* put videomode list to info structure */ 496 videomodes = kcalloc(videomode_num, sizeof(struct fb_videomode), 497 GFP_KERNEL); 498 if (!videomodes) 499 return -ENOMEM; 500 501 for (i = 0; i < videomode_num; i++) 502 mmpmode_to_fbmode(&videomodes[i], &mmp_modes[i]); 503 fb_videomode_to_modelist(videomodes, videomode_num, &info->modelist); 504 505 /* set videomode[0] as default mode */ 506 memcpy(&fbi->mode, &videomodes[0], sizeof(struct fb_videomode)); 507 fbi->output_fmt = mmp_modes[0].pix_fmt_out; 508 fb_videomode_to_var(&info->var, &fbi->mode); 509 mmp_path_set_mode(fbi->path, &mmp_modes[0]); 510 511 kfree(videomodes); 512 return videomode_num; 513 } 514 515 static int fb_info_setup(struct fb_info *info, 516 struct mmpfb_info *fbi) 517 { 518 int ret = 0; 519 /* Initialise static fb parameters.*/ 520 info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK | 521 FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; 522 info->node = -1; 523 strcpy(info->fix.id, fbi->name); 524 info->fix.type = FB_TYPE_PACKED_PIXELS; 525 info->fix.type_aux = 0; 526 info->fix.xpanstep = 0; 527 info->fix.ypanstep = info->var.yres; 528 info->fix.ywrapstep = 0; 529 info->fix.accel = FB_ACCEL_NONE; 530 info->fix.smem_start = fbi->fb_start_dma; 531 info->fix.smem_len = fbi->fb_size; 532 info->fix.visual = (fbi->pix_fmt == PIXFMT_PSEUDOCOLOR) ? 533 FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR; 534 info->fix.line_length = info->var.xres_virtual * 535 info->var.bits_per_pixel / 8; 536 info->fbops = &mmpfb_ops; 537 info->pseudo_palette = fbi->pseudo_palette; 538 info->screen_base = fbi->fb_start; 539 info->screen_size = fbi->fb_size; 540 541 /* For FB framework: Allocate color map and Register framebuffer*/ 542 if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) 543 ret = -ENOMEM; 544 545 return ret; 546 } 547 548 static void fb_info_clear(struct fb_info *info) 549 { 550 fb_dealloc_cmap(&info->cmap); 551 } 552 553 static int mmpfb_probe(struct platform_device *pdev) 554 { 555 struct mmp_buffer_driver_mach_info *mi; 556 struct fb_info *info; 557 struct mmpfb_info *fbi; 558 int ret, modes_num; 559 560 mi = pdev->dev.platform_data; 561 if (mi == NULL) { 562 dev_err(&pdev->dev, "no platform data defined\n"); 563 return -EINVAL; 564 } 565 566 /* initialize fb */ 567 info = framebuffer_alloc(sizeof(struct mmpfb_info), &pdev->dev); 568 if (info == NULL) 569 return -ENOMEM; 570 fbi = info->par; 571 572 /* init fb */ 573 fbi->fb_info = info; 574 platform_set_drvdata(pdev, fbi); 575 fbi->dev = &pdev->dev; 576 fbi->name = mi->name; 577 fbi->pix_fmt = mi->default_pixfmt; 578 pixfmt_to_var(&info->var, fbi->pix_fmt); 579 mutex_init(&fbi->access_ok); 580 581 /* get display path by name */ 582 fbi->path = mmp_get_path(mi->path_name); 583 if (!fbi->path) { 584 dev_err(&pdev->dev, "can't get the path %s\n", mi->path_name); 585 ret = -EINVAL; 586 goto failed_destroy_mutex; 587 } 588 589 dev_info(fbi->dev, "path %s get\n", fbi->path->name); 590 591 /* get overlay */ 592 fbi->overlay = mmp_path_get_overlay(fbi->path, mi->overlay_id); 593 if (!fbi->overlay) { 594 ret = -EINVAL; 595 goto failed_destroy_mutex; 596 } 597 /* set fetch used */ 598 mmp_overlay_set_fetch(fbi->overlay, mi->dmafetch_id); 599 600 modes_num = modes_setup(fbi); 601 if (modes_num < 0) { 602 ret = modes_num; 603 goto failed_destroy_mutex; 604 } 605 606 /* 607 * if get modes success, means not hotplug panels, use caculated buffer 608 * or use default size 609 */ 610 if (modes_num > 0) { 611 /* fix to 2* yres */ 612 info->var.yres_virtual = info->var.yres * 2; 613 614 /* Allocate framebuffer memory: size = modes xy *4 */ 615 fbi->fb_size = info->var.xres_virtual * info->var.yres_virtual 616 * info->var.bits_per_pixel / 8; 617 } else { 618 fbi->fb_size = MMPFB_DEFAULT_SIZE; 619 } 620 621 fbi->fb_start = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), 622 &fbi->fb_start_dma, GFP_KERNEL); 623 if (fbi->fb_start == NULL) { 624 dev_err(&pdev->dev, "can't alloc framebuffer\n"); 625 ret = -ENOMEM; 626 goto failed_destroy_mutex; 627 } 628 memset(fbi->fb_start, 0, fbi->fb_size); 629 dev_info(fbi->dev, "fb %dk allocated\n", fbi->fb_size/1024); 630 631 /* fb power on */ 632 if (modes_num > 0) 633 mmpfb_power(fbi, 1); 634 635 ret = fb_info_setup(info, fbi); 636 if (ret < 0) 637 goto failed_free_buff; 638 639 ret = register_framebuffer(info); 640 if (ret < 0) { 641 dev_err(&pdev->dev, "Failed to register fb: %d\n", ret); 642 ret = -ENXIO; 643 goto failed_clear_info; 644 } 645 646 dev_info(fbi->dev, "loaded to /dev/fb%d <%s>.\n", 647 info->node, info->fix.id); 648 649 #ifdef CONFIG_LOGO 650 if (fbi->fb_start) { 651 fb_prepare_logo(info, 0); 652 fb_show_logo(info, 0); 653 } 654 #endif 655 656 return 0; 657 658 failed_clear_info: 659 fb_info_clear(info); 660 failed_free_buff: 661 dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start, 662 fbi->fb_start_dma); 663 failed_destroy_mutex: 664 mutex_destroy(&fbi->access_ok); 665 dev_err(fbi->dev, "mmp-fb: frame buffer device init failed\n"); 666 667 framebuffer_release(info); 668 669 return ret; 670 } 671 672 static struct platform_driver mmpfb_driver = { 673 .driver = { 674 .name = "mmp-fb", 675 }, 676 .probe = mmpfb_probe, 677 }; 678 679 static int mmpfb_init(void) 680 { 681 return platform_driver_register(&mmpfb_driver); 682 } 683 module_init(mmpfb_init); 684 685 MODULE_AUTHOR("Zhou Zhu <zhou.zhu@marvell.com>"); 686 MODULE_DESCRIPTION("Framebuffer driver for Marvell displays"); 687 MODULE_LICENSE("GPL"); 688