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
27static 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
113static 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 */
238static 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
263static 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
287static 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
312static unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
313{
314	return ((chan & 0xffff) >> (16 - bf->length)) << bf->offset;
315}
316
317static 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
326static 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
348static 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
362static 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
395static 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
413static 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
441static 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
461static 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
470static 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
482static 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 = kzalloc(sizeof(struct fb_videomode) * videomode_num,
497			GFP_KERNEL);
498	if (!videomodes) {
499		dev_err(fbi->dev, "can't malloc video modes\n");
500		return -ENOMEM;
501	}
502	for (i = 0; i < videomode_num; i++)
503		mmpmode_to_fbmode(&videomodes[i], &mmp_modes[i]);
504	fb_videomode_to_modelist(videomodes, videomode_num, &info->modelist);
505
506	/* set videomode[0] as default mode */
507	memcpy(&fbi->mode, &videomodes[0], sizeof(struct fb_videomode));
508	fbi->output_fmt = mmp_modes[0].pix_fmt_out;
509	fb_videomode_to_var(&info->var, &fbi->mode);
510	mmp_path_set_mode(fbi->path, &mmp_modes[0]);
511
512	kfree(videomodes);
513	return videomode_num;
514}
515
516static int fb_info_setup(struct fb_info *info,
517			struct mmpfb_info *fbi)
518{
519	int ret = 0;
520	/* Initialise static fb parameters.*/
521	info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK |
522		FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN;
523	info->node = -1;
524	strcpy(info->fix.id, fbi->name);
525	info->fix.type = FB_TYPE_PACKED_PIXELS;
526	info->fix.type_aux = 0;
527	info->fix.xpanstep = 0;
528	info->fix.ypanstep = info->var.yres;
529	info->fix.ywrapstep = 0;
530	info->fix.accel = FB_ACCEL_NONE;
531	info->fix.smem_start = fbi->fb_start_dma;
532	info->fix.smem_len = fbi->fb_size;
533	info->fix.visual = (fbi->pix_fmt == PIXFMT_PSEUDOCOLOR) ?
534		FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
535	info->fix.line_length = info->var.xres_virtual *
536		info->var.bits_per_pixel / 8;
537	info->fbops = &mmpfb_ops;
538	info->pseudo_palette = fbi->pseudo_palette;
539	info->screen_base = fbi->fb_start;
540	info->screen_size = fbi->fb_size;
541
542	/* For FB framework: Allocate color map and Register framebuffer*/
543	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0)
544		ret = -ENOMEM;
545
546	return ret;
547}
548
549static void fb_info_clear(struct fb_info *info)
550{
551	fb_dealloc_cmap(&info->cmap);
552}
553
554static int mmpfb_probe(struct platform_device *pdev)
555{
556	struct mmp_buffer_driver_mach_info *mi;
557	struct fb_info *info;
558	struct mmpfb_info *fbi;
559	int ret, modes_num;
560
561	mi = pdev->dev.platform_data;
562	if (mi == NULL) {
563		dev_err(&pdev->dev, "no platform data defined\n");
564		return -EINVAL;
565	}
566
567	/* initialize fb */
568	info = framebuffer_alloc(sizeof(struct mmpfb_info), &pdev->dev);
569	if (info == NULL)
570		return -ENOMEM;
571	fbi = info->par;
572
573	/* init fb */
574	fbi->fb_info = info;
575	platform_set_drvdata(pdev, fbi);
576	fbi->dev = &pdev->dev;
577	fbi->name = mi->name;
578	fbi->pix_fmt = mi->default_pixfmt;
579	pixfmt_to_var(&info->var, fbi->pix_fmt);
580	mutex_init(&fbi->access_ok);
581
582	/* get display path by name */
583	fbi->path = mmp_get_path(mi->path_name);
584	if (!fbi->path) {
585		dev_err(&pdev->dev, "can't get the path %s\n", mi->path_name);
586		ret = -EINVAL;
587		goto failed_destroy_mutex;
588	}
589
590	dev_info(fbi->dev, "path %s get\n", fbi->path->name);
591
592	/* get overlay */
593	fbi->overlay = mmp_path_get_overlay(fbi->path, mi->overlay_id);
594	if (!fbi->overlay) {
595		ret = -EINVAL;
596		goto failed_destroy_mutex;
597	}
598	/* set fetch used */
599	mmp_overlay_set_fetch(fbi->overlay, mi->dmafetch_id);
600
601	modes_num = modes_setup(fbi);
602	if (modes_num < 0) {
603		ret = modes_num;
604		goto failed_destroy_mutex;
605	}
606
607	/*
608	 * if get modes success, means not hotplug panels, use caculated buffer
609	 * or use default size
610	 */
611	if (modes_num > 0) {
612		/* fix to 2* yres */
613		info->var.yres_virtual = info->var.yres * 2;
614
615		/* Allocate framebuffer memory: size = modes xy *4 */
616		fbi->fb_size = info->var.xres_virtual * info->var.yres_virtual
617				* info->var.bits_per_pixel / 8;
618	} else {
619		fbi->fb_size = MMPFB_DEFAULT_SIZE;
620	}
621
622	fbi->fb_start = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size),
623				&fbi->fb_start_dma, GFP_KERNEL);
624	if (fbi->fb_start == NULL) {
625		dev_err(&pdev->dev, "can't alloc framebuffer\n");
626		ret = -ENOMEM;
627		goto failed_destroy_mutex;
628	}
629	memset(fbi->fb_start, 0, fbi->fb_size);
630	dev_info(fbi->dev, "fb %dk allocated\n", fbi->fb_size/1024);
631
632	/* fb power on */
633	if (modes_num > 0)
634		mmpfb_power(fbi, 1);
635
636	ret = fb_info_setup(info, fbi);
637	if (ret < 0)
638		goto failed_free_buff;
639
640	ret = register_framebuffer(info);
641	if (ret < 0) {
642		dev_err(&pdev->dev, "Failed to register fb: %d\n", ret);
643		ret = -ENXIO;
644		goto failed_clear_info;
645	}
646
647	dev_info(fbi->dev, "loaded to /dev/fb%d <%s>.\n",
648		info->node, info->fix.id);
649
650#ifdef CONFIG_LOGO
651	if (fbi->fb_start) {
652		fb_prepare_logo(info, 0);
653		fb_show_logo(info, 0);
654	}
655#endif
656
657	return 0;
658
659failed_clear_info:
660	fb_info_clear(info);
661failed_free_buff:
662	dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbi->fb_size), fbi->fb_start,
663		fbi->fb_start_dma);
664failed_destroy_mutex:
665	mutex_destroy(&fbi->access_ok);
666	dev_err(fbi->dev, "mmp-fb: frame buffer device init failed\n");
667
668	framebuffer_release(info);
669
670	return ret;
671}
672
673static struct platform_driver mmpfb_driver = {
674	.driver		= {
675		.name	= "mmp-fb",
676	},
677	.probe		= mmpfb_probe,
678};
679
680static int mmpfb_init(void)
681{
682	return platform_driver_register(&mmpfb_driver);
683}
684module_init(mmpfb_init);
685
686MODULE_AUTHOR("Zhou Zhu <zhou.zhu@marvell.com>");
687MODULE_DESCRIPTION("Framebuffer driver for Marvell displays");
688MODULE_LICENSE("GPL");
689