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