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