All of lore.kernel.org
 help / color / mirror / Atom feed
From: Sascha Hauer <s.hauer@pengutronix.de>
To: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org, linux-fbdev@vger.kernel.org,
	Zhang Lily-R58066 <r58066@freescale.com>,
	Arnaud Patard <arnaud.patard@rtp-net.org>,
	Sascha Hauer <s.hauer@pengutronix.de>
Subject: [PATCH 5/9] Add i.MX5 framebuffer driver
Date: Thu,  9 Dec 2010 14:47:17 +0100	[thread overview]
Message-ID: <1291902441-24712-6-git-send-email-s.hauer@pengutronix.de> (raw)
In-Reply-To: <1291902441-24712-1-git-send-email-s.hauer@pengutronix.de>

This patch adds framebuffer support to the Freescale i.MX SoCs
equipped with an IPU v3, so far these are the i.MX50/51/53.

This driver has been tested on the i.MX51 babbage board with
both DVI and analog VGA in different resolutions and color depths.
It has also been tested on a custom i.MX51 board using a fixed
resolution panel.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/video/Kconfig  |   11 +
 drivers/video/Makefile |    1 +
 drivers/video/mx5fb.c  |  846 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 858 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/mx5fb.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 27c1fb4..1901915 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2236,6 +2236,17 @@ config FB_MX3
 	  far only synchronous displays are supported. If you plan to use
 	  an LCD display with your i.MX31 system, say Y here.
 
+config FB_MX5
+	tristate "MX5 Framebuffer support"
+	depends on FB && MFD_IMX_IPU_V3
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select FB_MODE_HELPERS
+	help
+	  This is a framebuffer device for the i.MX51 LCD Controller. If you
+	  plan to use an LCD display with your i.MX51 system, say Y here.
+
 config FB_BROADSHEET
 	tristate "E-Ink Broadsheet/Epson S1D13521 controller support"
 	depends on FB
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 485e8ed..ad408d2 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_FB_BF54X_LQ043)	  += bf54x-lq043fb.o
 obj-$(CONFIG_FB_BFIN_LQ035Q1)     += bfin-lq035q1-fb.o
 obj-$(CONFIG_FB_BFIN_T350MCQB)	  += bfin-t350mcqb-fb.o
 obj-$(CONFIG_FB_MX3)		  += mx3fb.o
+obj-$(CONFIG_FB_MX5)		  += mx5fb.o
 obj-$(CONFIG_FB_DA8XX)		  += da8xx-fb.o
 
 # the test framebuffer is last
diff --git a/drivers/video/mx5fb.c b/drivers/video/mx5fb.c
new file mode 100644
index 0000000..fd9baf4
--- /dev/null
+++ b/drivers/video/mx5fb.c
@@ -0,0 +1,846 @@
+/*
+ * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/console.h>
+#include <linux/mfd/imx-ipu-v3.h>
+#include <asm/uaccess.h>
+#include <mach/ipu-v3.h>
+
+#define DRIVER_NAME "imx-ipuv3-fb"
+
+struct imx_ipu_fb_info {
+	int			ipu_channel_num;
+	struct ipu_channel	*ipu_ch;
+	int			dc;
+	int			ipu_di;
+	u32			ipu_di_pix_fmt;
+	u32			ipu_in_pix_fmt;
+
+	u32			pseudo_palette[16];
+
+	struct ipu_dp		*dp;
+	struct dmfc_channel	*dmfc;
+	struct fb_info		*slave;
+	struct fb_info		*master;
+	bool			enabled;
+};
+
+static int imx_ipu_fb_set_fix(struct fb_info *info)
+{
+	struct fb_fix_screeninfo *fix = &info->fix;
+	struct fb_var_screeninfo *var = &info->var;
+
+	fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	fix->type = FB_TYPE_PACKED_PIXELS;
+	fix->accel = FB_ACCEL_NONE;
+	fix->visual = FB_VISUAL_TRUECOLOR;
+	fix->xpanstep = 1;
+	fix->ypanstep = 1;
+
+	return 0;
+}
+
+static int imx_ipu_fb_map_video_memory(struct fb_info *fbi)
+{
+	int size;
+
+	size = fbi->var.yres_virtual * fbi->fix.line_length;
+
+	if (fbi->screen_base) {
+		if (fbi->fix.smem_len >= size)
+			return 0;
+
+		dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+	}
+
+	fbi->screen_base = dma_alloc_writecombine(fbi->device,
+				size,
+				(dma_addr_t *)&fbi->fix.smem_start,
+				GFP_DMA);
+	if (fbi->screen_base == 0) {
+		dev_err(fbi->device, "Unable to allocate framebuffer memory (%d)\n",
+				fbi->fix.smem_len);
+		fbi->fix.smem_len = 0;
+		fbi->fix.smem_start = 0;
+		return -ENOMEM;
+	}
+
+	fbi->fix.smem_len = size;
+	fbi->screen_size = fbi->fix.smem_len;
+
+	dev_dbg(fbi->device, "allocated fb @ paddr=0x%08lx, size=%d\n",
+		fbi->fix.smem_start, fbi->fix.smem_len);
+
+	/* Clear the screen */
+	memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+	return 0;
+}
+
+static void imx_ipu_fb_enable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (mxc_fbi->enabled)
+		return;
+
+	ipu_di_enable(mxc_fbi->ipu_di);
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dc_enable_channel(mxc_fbi->dc);
+	ipu_dp_enable_channel(mxc_fbi->dp);
+	mxc_fbi->enabled = 1;
+}
+
+static void imx_ipu_fb_disable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (!mxc_fbi->enabled)
+		return;
+
+	ipu_dp_disable_channel(mxc_fbi->dp);
+	ipu_dc_disable_channel(mxc_fbi->dc);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+	ipu_di_disable(mxc_fbi->ipu_di);
+
+	mxc_fbi->enabled = 0;
+}
+
+static int calc_vref(struct fb_var_screeninfo *var)
+{
+	unsigned long htotal, vtotal;
+
+	htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin;
+	vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin;
+
+	if (!htotal || !vtotal)
+		return 60;
+
+	return PICOS2KHZ(var->pixclock) * 1000 / vtotal / htotal;
+}
+
+static int calc_bandwidth(struct fb_var_screeninfo *var, unsigned int vref)
+{
+	return var->xres * var->yres * vref;
+}
+
+static int imx_ipu_fb_set_par(struct fb_info *fbi)
+{
+	int ret;
+	struct ipu_di_signal_cfg sig_cfg;
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	u32 out_pixel_fmt;
+	int interlaced = 0;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable(fbi);
+
+	fbi->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	var->yres_virtual = var->yres;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	memset(&sig_cfg, 0, sizeof(sig_cfg));
+	out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		sig_cfg.interlaced = 1;
+	if (var->vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
+		sig_cfg.odd_field_first = 1;
+	if (var->sync & FB_SYNC_EXT)
+		sig_cfg.ext_clk = 1;
+	if (var->sync & FB_SYNC_HOR_HIGH_ACT)
+		sig_cfg.Hsync_pol = 1;
+	if (var->sync & FB_SYNC_VERT_HIGH_ACT)
+		sig_cfg.Vsync_pol = 1;
+	if (!(var->sync & FB_SYNC_CLK_LAT_FALL))
+		sig_cfg.clk_pol = 1;
+	if (var->sync & FB_SYNC_DATA_INVERT)
+		sig_cfg.data_pol = 1;
+	if (!(var->sync & FB_SYNC_OE_LOW_ACT))
+		sig_cfg.enable_pol = 1;
+	if (var->sync & FB_SYNC_CLK_IDLE_EN)
+		sig_cfg.clkidle_en = 1;
+
+	dev_dbg(fbi->device, "pixclock = %lu.%03lu MHz\n",
+		PICOS2KHZ(var->pixclock) / 1000,
+		PICOS2KHZ(var->pixclock) % 1000);
+
+	sig_cfg.width = var->xres;
+	sig_cfg.height = var->yres;
+	sig_cfg.pixel_fmt = out_pixel_fmt;
+	sig_cfg.h_start_width = var->left_margin;
+	sig_cfg.h_sync_width = var->hsync_len;
+	sig_cfg.h_end_width = var->right_margin;
+	sig_cfg.v_start_width = var->upper_margin;
+	sig_cfg.v_sync_width = var->vsync_len;
+	sig_cfg.v_end_width = var->lower_margin;
+	sig_cfg.v_to_h_sync = 0;
+
+	if (mxc_fbi->dp) {
+		ret = ipu_dp_setup_channel(mxc_fbi->dp, mxc_fbi->ipu_in_pix_fmt,
+				out_pixel_fmt, 1);
+		if (ret) {
+			dev_dbg(fbi->device, "initializing display processor failed with %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	ret = ipu_dc_init_sync(mxc_fbi->dc, mxc_fbi->ipu_di, interlaced,
+			out_pixel_fmt, fbi->var.xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing display controller failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_di_init_sync_panel(mxc_fbi->ipu_di,
+				PICOS2KHZ(var->pixclock) * 1000UL,
+				&sig_cfg);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing panel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	fbi->mode = (struct fb_videomode *)fb_match_mode(var, &fbi->modelist);
+	var->xoffset = var->yoffset = 0;
+
+	if (fbi->var.vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable(fbi);
+
+	return ret;
+}
+
+/*
+ * These are the bitfields for each
+ * display depth that we support.
+ */
+struct imxfb_rgb {
+	struct fb_bitfield	red;
+	struct fb_bitfield	green;
+	struct fb_bitfield	blue;
+	struct fb_bitfield	transp;
+};
+
+static struct imxfb_rgb def_rgb_8 = {
+	.red	= { .offset =  5, .length = 3, },
+	.green	= { .offset =  2, .length = 3, },
+	.blue	= { .offset =  0, .length = 2, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_16 = {
+	.red	= { .offset = 11, .length = 5, },
+	.green	= { .offset =  5, .length = 6, },
+	.blue	= { .offset =  0, .length = 5, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_24 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_32 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset = 24, .length = 8, },
+};
+
+/*
+ * Check framebuffer variable parameters and adjust to valid values.
+ *
+ * @param       var      framebuffer variable parameters
+ *
+ * @param       info     framebuffer information pointer
+ */
+static int imx_ipu_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	struct imxfb_rgb *rgb;
+
+	/* we don't support xpan, force xres_virtual to be equal to xres */
+	var->xres_virtual = var->xres;
+
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+
+	switch (var->bits_per_pixel) {
+	case 8:
+		rgb = &def_rgb_8;
+		break;
+	case 16:
+		rgb = &def_rgb_16;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_RGB565;
+		break;
+	case 24:
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+		break;
+	case 32:
+		rgb = &def_rgb_32;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR32;
+		break;
+	default:
+		var->bits_per_pixel = 24;
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+	}
+
+	var->red    = rgb->red;
+	var->green  = rgb->green;
+	var->blue   = rgb->blue;
+	var->transp = rgb->transp;
+
+	return 0;
+}
+
+static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+	chan &= 0xffff;
+	chan >>= 16 - bf->length;
+	return chan << bf->offset;
+}
+
+static int imx_ipu_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
+			   u_int trans, struct fb_info *fbi)
+{
+	unsigned int val;
+	int ret = 1;
+
+	/*
+	 * If greyscale is true, then we convert the RGB value
+	 * to greyscale no matter what visual we are using.
+	 */
+	if (fbi->var.grayscale)
+		red = green = blue = (19595 * red + 38470 * green +
+				      7471 * blue) >> 16;
+	switch (fbi->fix.visual) {
+	case FB_VISUAL_TRUECOLOR:
+		/*
+		 * 16-bit True Colour.  We encode the RGB value
+		 * according to the RGB bitfield information.
+		 */
+		if (regno < 16) {
+			u32 *pal = fbi->pseudo_palette;
+
+			val = chan_to_field(red, &fbi->var.red);
+			val |= chan_to_field(green, &fbi->var.green);
+			val |= chan_to_field(blue, &fbi->var.blue);
+
+			pal[regno] = val;
+			ret = 0;
+		}
+		break;
+
+	case FB_VISUAL_STATIC_PSEUDOCOLOR:
+	case FB_VISUAL_PSEUDOCOLOR:
+		break;
+	}
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank(int blank, struct fb_info *info)
+{
+	dev_dbg(info->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable(info);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable(info);
+		break;
+	}
+
+	return 0;
+}
+
+static int imx_ipu_fb_pan_display(struct fb_var_screeninfo *var,
+		struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	unsigned long base;
+	int ret;
+
+	if (info->var.yoffset == var->yoffset)
+		return 0;	/* No change, do nothing */
+
+	base = var->yoffset * var->xres_virtual * var->bits_per_pixel / 8;
+	base += info->fix.smem_start;
+
+	ret = ipu_wait_for_interrupt(IPU_IRQ_EOF(mxc_fbi->ipu_channel_num), 100);
+	if (ret)
+		return ret;
+
+	if (ipu_idmac_update_channel_buffer(mxc_fbi->ipu_ch, 0, base)) {
+		dev_err(info->device,
+			"Error updating SDC buf to address=0x%08lX\n", base);
+	}
+
+	info->var.yoffset = var->yoffset;
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank,
+};
+
+/*
+ * Overlay functions
+ */
+static int imx_ipu_fb_enable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dp_enable_fg(mxc_fbi->dp);
+
+	return 0;
+}
+
+static int imx_ipu_fb_disable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dp_disable_fg(mxc_fbi->dp);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+
+	return 0;
+}
+
+static int imx_ipu_fb_set_par_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct fb_var_screeninfo *var = &fbi->var;
+	struct fb_info *fbi_master = mxc_fbi->master;
+	struct fb_var_screeninfo *var_master = &fbi_master->var;
+	int ret;
+	int interlaced = 0;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable_overlay(fbi);
+
+	fbi->fix.line_length = var->xres_virtual *
+                                  var->bits_per_pixel / 8;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	ipu_dp_set_window_pos(mxc_fbi->dp, 64, 64);
+
+	var->xoffset = var->yoffset = 0;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var_master)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable_overlay(fbi);
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank_overlay(int blank, struct fb_info *fbi)
+{
+	dev_dbg(fbi->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable_overlay(fbi);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable_overlay(fbi);
+		break;
+	}
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_overlay_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par_overlay,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank_overlay,
+};
+
+static struct fb_info *imx_ipu_fb_init_fbinfo(struct device *dev, struct fb_ops *ops)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+
+	fbi = framebuffer_alloc(sizeof(struct imx_ipu_fb_info), dev);
+	if (!fbi)
+		return NULL;
+
+	BUG_ON(fbi->par == NULL);
+	mxc_fbi = fbi->par;
+
+	fbi->var.activate = FB_ACTIVATE_NOW;
+
+	fbi->fbops = ops;
+	fbi->flags = FBINFO_FLAG_DEFAULT;
+	fbi->pseudo_palette = mxc_fbi->pseudo_palette;
+
+	fb_alloc_cmap(&fbi->cmap, 16, 0);
+
+	return fbi;
+}
+
+static int imx_ipu_fb_init_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi;
+	struct imx_ipu_fb_info *ovl_mxc_fbi;
+	int ret;
+
+	ovlfbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_overlay_ops);
+	if (!ovlfbi)
+		return -ENOMEM;
+
+	ovl_mxc_fbi = ovlfbi->par;
+	ovl_mxc_fbi->ipu_ch = ipu_idmac_get(ipu_channel);
+	ovl_mxc_fbi->dmfc = ipu_dmfc_get(ipu_channel);
+	ovl_mxc_fbi->ipu_di = -1;
+	ovl_mxc_fbi->dp = mxc_fbi_master->dp;
+	ovl_mxc_fbi->master = fbi_master;
+	mxc_fbi_master->slave = ovlfbi;
+
+	ovlfbi->var.xres = 240;
+	ovlfbi->var.yres = 320;
+	ovlfbi->var.yres_virtual = ovlfbi->var.yres;
+	ovlfbi->var.xres_virtual = ovlfbi->var.xres;
+	imx_ipu_fb_check_var(&ovlfbi->var, ovlfbi);
+	imx_ipu_fb_set_fix(ovlfbi);
+
+	ret = register_framebuffer(ovlfbi);
+	if (ret) {
+		framebuffer_release(ovlfbi);
+		return ret;
+	}
+
+	ipu_dp_set_global_alpha(ovl_mxc_fbi->dp, 1, 0x80, 1);
+	ipu_dp_set_color_key(ovl_mxc_fbi->dp, 0, 0);
+
+	imx_ipu_fb_set_par_overlay(ovlfbi);
+
+	return 0;
+}
+
+static void imx_ipu_fb_exit_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi = mxc_fbi_master->slave;
+	struct imx_ipu_fb_info *ovl_mxc_fbi = ovlfbi->par;
+
+	imx_ipu_fb_blank_overlay(FB_BLANK_POWERDOWN, ovlfbi);
+
+	unregister_framebuffer(ovlfbi);
+
+	ipu_idmac_put(ovl_mxc_fbi->ipu_ch);
+	ipu_dmfc_free_bandwidth(ovl_mxc_fbi->dmfc);
+	ipu_dmfc_put(ovl_mxc_fbi->dmfc);
+
+	framebuffer_release(ovlfbi);
+}
+
+static int imx_ipu_fb_find_mode(struct fb_info *fbi)
+{
+	int ret;
+	struct fb_videomode *mode_array;
+	struct fb_modelist *modelist;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int i = 0;
+
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		i++;
+
+	mode_array = kmalloc(sizeof (struct fb_modelist) * i, GFP_KERNEL);
+	if (!mode_array)
+		return -ENOMEM;
+
+	i = 0;
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		mode_array[i++] = modelist->mode;
+
+	ret = fb_find_mode(&fbi->var, fbi, NULL, mode_array, i, NULL, 16);
+	if (ret == 0)
+		return -EINVAL;
+
+	dev_dbg(fbi->device, "found %dx%d-%d hs:%d:%d:%d vs:%d:%d:%d\n",
+			var->xres, var->yres, var->bits_per_pixel,
+			var->hsync_len, var->left_margin, var->right_margin,
+			var->vsync_len, var->upper_margin, var->lower_margin);
+
+	kfree(mode_array);
+
+	return 0;
+}
+
+static int __devinit imx_ipu_fb_probe(struct platform_device *pdev)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+	int ret = 0, i;
+
+	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+	fbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_ops);
+	if (!fbi)
+		return -ENOMEM;
+
+	mxc_fbi = fbi->par;
+
+	mxc_fbi->ipu_channel_num = plat_data->ipu_channel_bg;
+	mxc_fbi->dc = plat_data->dc_channel;
+	mxc_fbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt;
+	mxc_fbi->ipu_di = pdev->id;
+
+	mxc_fbi->ipu_ch = ipu_idmac_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_ipu;
+	}
+
+	mxc_fbi->dmfc = ipu_dmfc_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_dmfc;
+	}
+
+	if (plat_data->dp_channel >= 0) {
+		mxc_fbi->dp = ipu_dp_get(plat_data->dp_channel);
+		if (IS_ERR(mxc_fbi->dp)) {
+			ret = PTR_ERR(mxc_fbi->ipu_ch);
+			goto failed_request_dp;
+		}
+	}
+
+	fbi->var.yres_virtual = fbi->var.yres;
+
+	INIT_LIST_HEAD(&fbi->modelist);
+	for (i = 0; i < plat_data->num_modes; i++)
+		fb_add_videomode(&plat_data->modes[i], &fbi->modelist);
+
+	if (plat_data->flags & IMX_IPU_FB_USE_MODEDB) {
+		for (i = 0; i < num_fb_modes; i++)
+			fb_add_videomode(&fb_modes[i], &fbi->modelist);
+	}
+
+	imx_ipu_fb_find_mode(fbi);
+
+	imx_ipu_fb_check_var(&fbi->var, fbi);
+	imx_ipu_fb_set_fix(fbi);
+	ret = register_framebuffer(fbi);
+	if (ret < 0)
+		goto failed_register;
+
+	imx_ipu_fb_set_par(fbi);
+	imx_ipu_fb_blank(FB_BLANK_UNBLANK, fbi);
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_init_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	platform_set_drvdata(pdev, fbi);
+
+	return 0;
+
+failed_register:
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+failed_request_dp:
+	ipu_dmfc_put(mxc_fbi->dmfc);
+failed_request_dmfc:
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+failed_request_ipu:
+	fb_dealloc_cmap(&fbi->cmap);
+	framebuffer_release(fbi);
+
+	return ret;
+}
+
+static int __devexit imx_ipu_fb_remove(struct platform_device *pdev)
+{
+	struct fb_info *fbi = platform_get_drvdata(pdev);
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_exit_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	imx_ipu_fb_blank(FB_BLANK_POWERDOWN, fbi);
+
+	dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+
+	if (&fbi->cmap)
+		fb_dealloc_cmap(&fbi->cmap);
+
+	unregister_framebuffer(fbi);
+
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+	ipu_dmfc_free_bandwidth(mxc_fbi->dmfc);
+	ipu_dmfc_put(mxc_fbi->dmfc);
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+
+	framebuffer_release(fbi);
+
+	return 0;
+}
+
+static struct platform_driver imx_ipu_fb_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe = imx_ipu_fb_probe,
+	.remove = __devexit_p(imx_ipu_fb_remove),
+};
+
+static int __init imx_ipu_fb_init(void)
+{
+	return platform_driver_register(&imx_ipu_fb_driver);
+}
+
+static void __exit imx_ipu_fb_exit(void)
+{
+	platform_driver_unregister(&imx_ipu_fb_driver);
+}
+
+module_init(imx_ipu_fb_init);
+module_exit(imx_ipu_fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
-- 
1.7.2.3


WARNING: multiple messages have this Message-ID (diff)
From: Sascha Hauer <s.hauer@pengutronix.de>
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 5/9] Add i.MX5 framebuffer driver
Date: Thu, 09 Dec 2010 13:47:17 +0000	[thread overview]
Message-ID: <1291902441-24712-6-git-send-email-s.hauer@pengutronix.de> (raw)
In-Reply-To: <1291902441-24712-1-git-send-email-s.hauer@pengutronix.de>

This patch adds framebuffer support to the Freescale i.MX SoCs
equipped with an IPU v3, so far these are the i.MX50/51/53.

This driver has been tested on the i.MX51 babbage board with
both DVI and analog VGA in different resolutions and color depths.
It has also been tested on a custom i.MX51 board using a fixed
resolution panel.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/video/Kconfig  |   11 +
 drivers/video/Makefile |    1 +
 drivers/video/mx5fb.c  |  846 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 858 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/mx5fb.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 27c1fb4..1901915 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2236,6 +2236,17 @@ config FB_MX3
 	  far only synchronous displays are supported. If you plan to use
 	  an LCD display with your i.MX31 system, say Y here.
 
+config FB_MX5
+	tristate "MX5 Framebuffer support"
+	depends on FB && MFD_IMX_IPU_V3
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select FB_MODE_HELPERS
+	help
+	  This is a framebuffer device for the i.MX51 LCD Controller. If you
+	  plan to use an LCD display with your i.MX51 system, say Y here.
+
 config FB_BROADSHEET
 	tristate "E-Ink Broadsheet/Epson S1D13521 controller support"
 	depends on FB
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 485e8ed..ad408d2 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_FB_BF54X_LQ043)	  += bf54x-lq043fb.o
 obj-$(CONFIG_FB_BFIN_LQ035Q1)     += bfin-lq035q1-fb.o
 obj-$(CONFIG_FB_BFIN_T350MCQB)	  += bfin-t350mcqb-fb.o
 obj-$(CONFIG_FB_MX3)		  += mx3fb.o
+obj-$(CONFIG_FB_MX5)		  += mx5fb.o
 obj-$(CONFIG_FB_DA8XX)		  += da8xx-fb.o
 
 # the test framebuffer is last
diff --git a/drivers/video/mx5fb.c b/drivers/video/mx5fb.c
new file mode 100644
index 0000000..fd9baf4
--- /dev/null
+++ b/drivers/video/mx5fb.c
@@ -0,0 +1,846 @@
+/*
+ * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/console.h>
+#include <linux/mfd/imx-ipu-v3.h>
+#include <asm/uaccess.h>
+#include <mach/ipu-v3.h>
+
+#define DRIVER_NAME "imx-ipuv3-fb"
+
+struct imx_ipu_fb_info {
+	int			ipu_channel_num;
+	struct ipu_channel	*ipu_ch;
+	int			dc;
+	int			ipu_di;
+	u32			ipu_di_pix_fmt;
+	u32			ipu_in_pix_fmt;
+
+	u32			pseudo_palette[16];
+
+	struct ipu_dp		*dp;
+	struct dmfc_channel	*dmfc;
+	struct fb_info		*slave;
+	struct fb_info		*master;
+	bool			enabled;
+};
+
+static int imx_ipu_fb_set_fix(struct fb_info *info)
+{
+	struct fb_fix_screeninfo *fix = &info->fix;
+	struct fb_var_screeninfo *var = &info->var;
+
+	fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	fix->type = FB_TYPE_PACKED_PIXELS;
+	fix->accel = FB_ACCEL_NONE;
+	fix->visual = FB_VISUAL_TRUECOLOR;
+	fix->xpanstep = 1;
+	fix->ypanstep = 1;
+
+	return 0;
+}
+
+static int imx_ipu_fb_map_video_memory(struct fb_info *fbi)
+{
+	int size;
+
+	size = fbi->var.yres_virtual * fbi->fix.line_length;
+
+	if (fbi->screen_base) {
+		if (fbi->fix.smem_len >= size)
+			return 0;
+
+		dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+	}
+
+	fbi->screen_base = dma_alloc_writecombine(fbi->device,
+				size,
+				(dma_addr_t *)&fbi->fix.smem_start,
+				GFP_DMA);
+	if (fbi->screen_base = 0) {
+		dev_err(fbi->device, "Unable to allocate framebuffer memory (%d)\n",
+				fbi->fix.smem_len);
+		fbi->fix.smem_len = 0;
+		fbi->fix.smem_start = 0;
+		return -ENOMEM;
+	}
+
+	fbi->fix.smem_len = size;
+	fbi->screen_size = fbi->fix.smem_len;
+
+	dev_dbg(fbi->device, "allocated fb @ paddr=0x%08lx, size=%d\n",
+		fbi->fix.smem_start, fbi->fix.smem_len);
+
+	/* Clear the screen */
+	memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+	return 0;
+}
+
+static void imx_ipu_fb_enable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (mxc_fbi->enabled)
+		return;
+
+	ipu_di_enable(mxc_fbi->ipu_di);
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dc_enable_channel(mxc_fbi->dc);
+	ipu_dp_enable_channel(mxc_fbi->dp);
+	mxc_fbi->enabled = 1;
+}
+
+static void imx_ipu_fb_disable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (!mxc_fbi->enabled)
+		return;
+
+	ipu_dp_disable_channel(mxc_fbi->dp);
+	ipu_dc_disable_channel(mxc_fbi->dc);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+	ipu_di_disable(mxc_fbi->ipu_di);
+
+	mxc_fbi->enabled = 0;
+}
+
+static int calc_vref(struct fb_var_screeninfo *var)
+{
+	unsigned long htotal, vtotal;
+
+	htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin;
+	vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin;
+
+	if (!htotal || !vtotal)
+		return 60;
+
+	return PICOS2KHZ(var->pixclock) * 1000 / vtotal / htotal;
+}
+
+static int calc_bandwidth(struct fb_var_screeninfo *var, unsigned int vref)
+{
+	return var->xres * var->yres * vref;
+}
+
+static int imx_ipu_fb_set_par(struct fb_info *fbi)
+{
+	int ret;
+	struct ipu_di_signal_cfg sig_cfg;
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	u32 out_pixel_fmt;
+	int interlaced = 0;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable(fbi);
+
+	fbi->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	var->yres_virtual = var->yres;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	memset(&sig_cfg, 0, sizeof(sig_cfg));
+	out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		sig_cfg.interlaced = 1;
+	if (var->vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
+		sig_cfg.odd_field_first = 1;
+	if (var->sync & FB_SYNC_EXT)
+		sig_cfg.ext_clk = 1;
+	if (var->sync & FB_SYNC_HOR_HIGH_ACT)
+		sig_cfg.Hsync_pol = 1;
+	if (var->sync & FB_SYNC_VERT_HIGH_ACT)
+		sig_cfg.Vsync_pol = 1;
+	if (!(var->sync & FB_SYNC_CLK_LAT_FALL))
+		sig_cfg.clk_pol = 1;
+	if (var->sync & FB_SYNC_DATA_INVERT)
+		sig_cfg.data_pol = 1;
+	if (!(var->sync & FB_SYNC_OE_LOW_ACT))
+		sig_cfg.enable_pol = 1;
+	if (var->sync & FB_SYNC_CLK_IDLE_EN)
+		sig_cfg.clkidle_en = 1;
+
+	dev_dbg(fbi->device, "pixclock = %lu.%03lu MHz\n",
+		PICOS2KHZ(var->pixclock) / 1000,
+		PICOS2KHZ(var->pixclock) % 1000);
+
+	sig_cfg.width = var->xres;
+	sig_cfg.height = var->yres;
+	sig_cfg.pixel_fmt = out_pixel_fmt;
+	sig_cfg.h_start_width = var->left_margin;
+	sig_cfg.h_sync_width = var->hsync_len;
+	sig_cfg.h_end_width = var->right_margin;
+	sig_cfg.v_start_width = var->upper_margin;
+	sig_cfg.v_sync_width = var->vsync_len;
+	sig_cfg.v_end_width = var->lower_margin;
+	sig_cfg.v_to_h_sync = 0;
+
+	if (mxc_fbi->dp) {
+		ret = ipu_dp_setup_channel(mxc_fbi->dp, mxc_fbi->ipu_in_pix_fmt,
+				out_pixel_fmt, 1);
+		if (ret) {
+			dev_dbg(fbi->device, "initializing display processor failed with %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	ret = ipu_dc_init_sync(mxc_fbi->dc, mxc_fbi->ipu_di, interlaced,
+			out_pixel_fmt, fbi->var.xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing display controller failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_di_init_sync_panel(mxc_fbi->ipu_di,
+				PICOS2KHZ(var->pixclock) * 1000UL,
+				&sig_cfg);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing panel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	fbi->mode = (struct fb_videomode *)fb_match_mode(var, &fbi->modelist);
+	var->xoffset = var->yoffset = 0;
+
+	if (fbi->var.vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable(fbi);
+
+	return ret;
+}
+
+/*
+ * These are the bitfields for each
+ * display depth that we support.
+ */
+struct imxfb_rgb {
+	struct fb_bitfield	red;
+	struct fb_bitfield	green;
+	struct fb_bitfield	blue;
+	struct fb_bitfield	transp;
+};
+
+static struct imxfb_rgb def_rgb_8 = {
+	.red	= { .offset =  5, .length = 3, },
+	.green	= { .offset =  2, .length = 3, },
+	.blue	= { .offset =  0, .length = 2, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_16 = {
+	.red	= { .offset = 11, .length = 5, },
+	.green	= { .offset =  5, .length = 6, },
+	.blue	= { .offset =  0, .length = 5, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_24 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_32 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset = 24, .length = 8, },
+};
+
+/*
+ * Check framebuffer variable parameters and adjust to valid values.
+ *
+ * @param       var      framebuffer variable parameters
+ *
+ * @param       info     framebuffer information pointer
+ */
+static int imx_ipu_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	struct imxfb_rgb *rgb;
+
+	/* we don't support xpan, force xres_virtual to be equal to xres */
+	var->xres_virtual = var->xres;
+
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+
+	switch (var->bits_per_pixel) {
+	case 8:
+		rgb = &def_rgb_8;
+		break;
+	case 16:
+		rgb = &def_rgb_16;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_RGB565;
+		break;
+	case 24:
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+		break;
+	case 32:
+		rgb = &def_rgb_32;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR32;
+		break;
+	default:
+		var->bits_per_pixel = 24;
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+	}
+
+	var->red    = rgb->red;
+	var->green  = rgb->green;
+	var->blue   = rgb->blue;
+	var->transp = rgb->transp;
+
+	return 0;
+}
+
+static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+	chan &= 0xffff;
+	chan >>= 16 - bf->length;
+	return chan << bf->offset;
+}
+
+static int imx_ipu_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
+			   u_int trans, struct fb_info *fbi)
+{
+	unsigned int val;
+	int ret = 1;
+
+	/*
+	 * If greyscale is true, then we convert the RGB value
+	 * to greyscale no matter what visual we are using.
+	 */
+	if (fbi->var.grayscale)
+		red = green = blue = (19595 * red + 38470 * green +
+				      7471 * blue) >> 16;
+	switch (fbi->fix.visual) {
+	case FB_VISUAL_TRUECOLOR:
+		/*
+		 * 16-bit True Colour.  We encode the RGB value
+		 * according to the RGB bitfield information.
+		 */
+		if (regno < 16) {
+			u32 *pal = fbi->pseudo_palette;
+
+			val = chan_to_field(red, &fbi->var.red);
+			val |= chan_to_field(green, &fbi->var.green);
+			val |= chan_to_field(blue, &fbi->var.blue);
+
+			pal[regno] = val;
+			ret = 0;
+		}
+		break;
+
+	case FB_VISUAL_STATIC_PSEUDOCOLOR:
+	case FB_VISUAL_PSEUDOCOLOR:
+		break;
+	}
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank(int blank, struct fb_info *info)
+{
+	dev_dbg(info->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable(info);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable(info);
+		break;
+	}
+
+	return 0;
+}
+
+static int imx_ipu_fb_pan_display(struct fb_var_screeninfo *var,
+		struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	unsigned long base;
+	int ret;
+
+	if (info->var.yoffset = var->yoffset)
+		return 0;	/* No change, do nothing */
+
+	base = var->yoffset * var->xres_virtual * var->bits_per_pixel / 8;
+	base += info->fix.smem_start;
+
+	ret = ipu_wait_for_interrupt(IPU_IRQ_EOF(mxc_fbi->ipu_channel_num), 100);
+	if (ret)
+		return ret;
+
+	if (ipu_idmac_update_channel_buffer(mxc_fbi->ipu_ch, 0, base)) {
+		dev_err(info->device,
+			"Error updating SDC buf to address=0x%08lX\n", base);
+	}
+
+	info->var.yoffset = var->yoffset;
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank,
+};
+
+/*
+ * Overlay functions
+ */
+static int imx_ipu_fb_enable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dp_enable_fg(mxc_fbi->dp);
+
+	return 0;
+}
+
+static int imx_ipu_fb_disable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dp_disable_fg(mxc_fbi->dp);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+
+	return 0;
+}
+
+static int imx_ipu_fb_set_par_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct fb_var_screeninfo *var = &fbi->var;
+	struct fb_info *fbi_master = mxc_fbi->master;
+	struct fb_var_screeninfo *var_master = &fbi_master->var;
+	int ret;
+	int interlaced = 0;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable_overlay(fbi);
+
+	fbi->fix.line_length = var->xres_virtual *
+                                  var->bits_per_pixel / 8;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	ipu_dp_set_window_pos(mxc_fbi->dp, 64, 64);
+
+	var->xoffset = var->yoffset = 0;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var_master)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable_overlay(fbi);
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank_overlay(int blank, struct fb_info *fbi)
+{
+	dev_dbg(fbi->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable_overlay(fbi);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable_overlay(fbi);
+		break;
+	}
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_overlay_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par_overlay,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank_overlay,
+};
+
+static struct fb_info *imx_ipu_fb_init_fbinfo(struct device *dev, struct fb_ops *ops)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+
+	fbi = framebuffer_alloc(sizeof(struct imx_ipu_fb_info), dev);
+	if (!fbi)
+		return NULL;
+
+	BUG_ON(fbi->par = NULL);
+	mxc_fbi = fbi->par;
+
+	fbi->var.activate = FB_ACTIVATE_NOW;
+
+	fbi->fbops = ops;
+	fbi->flags = FBINFO_FLAG_DEFAULT;
+	fbi->pseudo_palette = mxc_fbi->pseudo_palette;
+
+	fb_alloc_cmap(&fbi->cmap, 16, 0);
+
+	return fbi;
+}
+
+static int imx_ipu_fb_init_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi;
+	struct imx_ipu_fb_info *ovl_mxc_fbi;
+	int ret;
+
+	ovlfbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_overlay_ops);
+	if (!ovlfbi)
+		return -ENOMEM;
+
+	ovl_mxc_fbi = ovlfbi->par;
+	ovl_mxc_fbi->ipu_ch = ipu_idmac_get(ipu_channel);
+	ovl_mxc_fbi->dmfc = ipu_dmfc_get(ipu_channel);
+	ovl_mxc_fbi->ipu_di = -1;
+	ovl_mxc_fbi->dp = mxc_fbi_master->dp;
+	ovl_mxc_fbi->master = fbi_master;
+	mxc_fbi_master->slave = ovlfbi;
+
+	ovlfbi->var.xres = 240;
+	ovlfbi->var.yres = 320;
+	ovlfbi->var.yres_virtual = ovlfbi->var.yres;
+	ovlfbi->var.xres_virtual = ovlfbi->var.xres;
+	imx_ipu_fb_check_var(&ovlfbi->var, ovlfbi);
+	imx_ipu_fb_set_fix(ovlfbi);
+
+	ret = register_framebuffer(ovlfbi);
+	if (ret) {
+		framebuffer_release(ovlfbi);
+		return ret;
+	}
+
+	ipu_dp_set_global_alpha(ovl_mxc_fbi->dp, 1, 0x80, 1);
+	ipu_dp_set_color_key(ovl_mxc_fbi->dp, 0, 0);
+
+	imx_ipu_fb_set_par_overlay(ovlfbi);
+
+	return 0;
+}
+
+static void imx_ipu_fb_exit_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi = mxc_fbi_master->slave;
+	struct imx_ipu_fb_info *ovl_mxc_fbi = ovlfbi->par;
+
+	imx_ipu_fb_blank_overlay(FB_BLANK_POWERDOWN, ovlfbi);
+
+	unregister_framebuffer(ovlfbi);
+
+	ipu_idmac_put(ovl_mxc_fbi->ipu_ch);
+	ipu_dmfc_free_bandwidth(ovl_mxc_fbi->dmfc);
+	ipu_dmfc_put(ovl_mxc_fbi->dmfc);
+
+	framebuffer_release(ovlfbi);
+}
+
+static int imx_ipu_fb_find_mode(struct fb_info *fbi)
+{
+	int ret;
+	struct fb_videomode *mode_array;
+	struct fb_modelist *modelist;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int i = 0;
+
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		i++;
+
+	mode_array = kmalloc(sizeof (struct fb_modelist) * i, GFP_KERNEL);
+	if (!mode_array)
+		return -ENOMEM;
+
+	i = 0;
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		mode_array[i++] = modelist->mode;
+
+	ret = fb_find_mode(&fbi->var, fbi, NULL, mode_array, i, NULL, 16);
+	if (ret = 0)
+		return -EINVAL;
+
+	dev_dbg(fbi->device, "found %dx%d-%d hs:%d:%d:%d vs:%d:%d:%d\n",
+			var->xres, var->yres, var->bits_per_pixel,
+			var->hsync_len, var->left_margin, var->right_margin,
+			var->vsync_len, var->upper_margin, var->lower_margin);
+
+	kfree(mode_array);
+
+	return 0;
+}
+
+static int __devinit imx_ipu_fb_probe(struct platform_device *pdev)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+	int ret = 0, i;
+
+	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+	fbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_ops);
+	if (!fbi)
+		return -ENOMEM;
+
+	mxc_fbi = fbi->par;
+
+	mxc_fbi->ipu_channel_num = plat_data->ipu_channel_bg;
+	mxc_fbi->dc = plat_data->dc_channel;
+	mxc_fbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt;
+	mxc_fbi->ipu_di = pdev->id;
+
+	mxc_fbi->ipu_ch = ipu_idmac_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_ipu;
+	}
+
+	mxc_fbi->dmfc = ipu_dmfc_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_dmfc;
+	}
+
+	if (plat_data->dp_channel >= 0) {
+		mxc_fbi->dp = ipu_dp_get(plat_data->dp_channel);
+		if (IS_ERR(mxc_fbi->dp)) {
+			ret = PTR_ERR(mxc_fbi->ipu_ch);
+			goto failed_request_dp;
+		}
+	}
+
+	fbi->var.yres_virtual = fbi->var.yres;
+
+	INIT_LIST_HEAD(&fbi->modelist);
+	for (i = 0; i < plat_data->num_modes; i++)
+		fb_add_videomode(&plat_data->modes[i], &fbi->modelist);
+
+	if (plat_data->flags & IMX_IPU_FB_USE_MODEDB) {
+		for (i = 0; i < num_fb_modes; i++)
+			fb_add_videomode(&fb_modes[i], &fbi->modelist);
+	}
+
+	imx_ipu_fb_find_mode(fbi);
+
+	imx_ipu_fb_check_var(&fbi->var, fbi);
+	imx_ipu_fb_set_fix(fbi);
+	ret = register_framebuffer(fbi);
+	if (ret < 0)
+		goto failed_register;
+
+	imx_ipu_fb_set_par(fbi);
+	imx_ipu_fb_blank(FB_BLANK_UNBLANK, fbi);
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_init_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	platform_set_drvdata(pdev, fbi);
+
+	return 0;
+
+failed_register:
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+failed_request_dp:
+	ipu_dmfc_put(mxc_fbi->dmfc);
+failed_request_dmfc:
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+failed_request_ipu:
+	fb_dealloc_cmap(&fbi->cmap);
+	framebuffer_release(fbi);
+
+	return ret;
+}
+
+static int __devexit imx_ipu_fb_remove(struct platform_device *pdev)
+{
+	struct fb_info *fbi = platform_get_drvdata(pdev);
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_exit_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	imx_ipu_fb_blank(FB_BLANK_POWERDOWN, fbi);
+
+	dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+
+	if (&fbi->cmap)
+		fb_dealloc_cmap(&fbi->cmap);
+
+	unregister_framebuffer(fbi);
+
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+	ipu_dmfc_free_bandwidth(mxc_fbi->dmfc);
+	ipu_dmfc_put(mxc_fbi->dmfc);
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+
+	framebuffer_release(fbi);
+
+	return 0;
+}
+
+static struct platform_driver imx_ipu_fb_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe = imx_ipu_fb_probe,
+	.remove = __devexit_p(imx_ipu_fb_remove),
+};
+
+static int __init imx_ipu_fb_init(void)
+{
+	return platform_driver_register(&imx_ipu_fb_driver);
+}
+
+static void __exit imx_ipu_fb_exit(void)
+{
+	platform_driver_unregister(&imx_ipu_fb_driver);
+}
+
+module_init(imx_ipu_fb_init);
+module_exit(imx_ipu_fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
-- 
1.7.2.3


WARNING: multiple messages have this Message-ID (diff)
From: s.hauer@pengutronix.de (Sascha Hauer)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 5/9] Add i.MX5 framebuffer driver
Date: Thu,  9 Dec 2010 14:47:17 +0100	[thread overview]
Message-ID: <1291902441-24712-6-git-send-email-s.hauer@pengutronix.de> (raw)
In-Reply-To: <1291902441-24712-1-git-send-email-s.hauer@pengutronix.de>

This patch adds framebuffer support to the Freescale i.MX SoCs
equipped with an IPU v3, so far these are the i.MX50/51/53.

This driver has been tested on the i.MX51 babbage board with
both DVI and analog VGA in different resolutions and color depths.
It has also been tested on a custom i.MX51 board using a fixed
resolution panel.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
---
 drivers/video/Kconfig  |   11 +
 drivers/video/Makefile |    1 +
 drivers/video/mx5fb.c  |  846 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 858 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/mx5fb.c

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 27c1fb4..1901915 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2236,6 +2236,17 @@ config FB_MX3
 	  far only synchronous displays are supported. If you plan to use
 	  an LCD display with your i.MX31 system, say Y here.
 
+config FB_MX5
+	tristate "MX5 Framebuffer support"
+	depends on FB && MFD_IMX_IPU_V3
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	select FB_MODE_HELPERS
+	help
+	  This is a framebuffer device for the i.MX51 LCD Controller. If you
+	  plan to use an LCD display with your i.MX51 system, say Y here.
+
 config FB_BROADSHEET
 	tristate "E-Ink Broadsheet/Epson S1D13521 controller support"
 	depends on FB
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 485e8ed..ad408d2 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -145,6 +145,7 @@ obj-$(CONFIG_FB_BF54X_LQ043)	  += bf54x-lq043fb.o
 obj-$(CONFIG_FB_BFIN_LQ035Q1)     += bfin-lq035q1-fb.o
 obj-$(CONFIG_FB_BFIN_T350MCQB)	  += bfin-t350mcqb-fb.o
 obj-$(CONFIG_FB_MX3)		  += mx3fb.o
+obj-$(CONFIG_FB_MX5)		  += mx5fb.o
 obj-$(CONFIG_FB_DA8XX)		  += da8xx-fb.o
 
 # the test framebuffer is last
diff --git a/drivers/video/mx5fb.c b/drivers/video/mx5fb.c
new file mode 100644
index 0000000..fd9baf4
--- /dev/null
+++ b/drivers/video/mx5fb.c
@@ -0,0 +1,846 @@
+/*
+ * Copyright 2004-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/console.h>
+#include <linux/mfd/imx-ipu-v3.h>
+#include <asm/uaccess.h>
+#include <mach/ipu-v3.h>
+
+#define DRIVER_NAME "imx-ipuv3-fb"
+
+struct imx_ipu_fb_info {
+	int			ipu_channel_num;
+	struct ipu_channel	*ipu_ch;
+	int			dc;
+	int			ipu_di;
+	u32			ipu_di_pix_fmt;
+	u32			ipu_in_pix_fmt;
+
+	u32			pseudo_palette[16];
+
+	struct ipu_dp		*dp;
+	struct dmfc_channel	*dmfc;
+	struct fb_info		*slave;
+	struct fb_info		*master;
+	bool			enabled;
+};
+
+static int imx_ipu_fb_set_fix(struct fb_info *info)
+{
+	struct fb_fix_screeninfo *fix = &info->fix;
+	struct fb_var_screeninfo *var = &info->var;
+
+	fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	fix->type = FB_TYPE_PACKED_PIXELS;
+	fix->accel = FB_ACCEL_NONE;
+	fix->visual = FB_VISUAL_TRUECOLOR;
+	fix->xpanstep = 1;
+	fix->ypanstep = 1;
+
+	return 0;
+}
+
+static int imx_ipu_fb_map_video_memory(struct fb_info *fbi)
+{
+	int size;
+
+	size = fbi->var.yres_virtual * fbi->fix.line_length;
+
+	if (fbi->screen_base) {
+		if (fbi->fix.smem_len >= size)
+			return 0;
+
+		dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+	}
+
+	fbi->screen_base = dma_alloc_writecombine(fbi->device,
+				size,
+				(dma_addr_t *)&fbi->fix.smem_start,
+				GFP_DMA);
+	if (fbi->screen_base == 0) {
+		dev_err(fbi->device, "Unable to allocate framebuffer memory (%d)\n",
+				fbi->fix.smem_len);
+		fbi->fix.smem_len = 0;
+		fbi->fix.smem_start = 0;
+		return -ENOMEM;
+	}
+
+	fbi->fix.smem_len = size;
+	fbi->screen_size = fbi->fix.smem_len;
+
+	dev_dbg(fbi->device, "allocated fb @ paddr=0x%08lx, size=%d\n",
+		fbi->fix.smem_start, fbi->fix.smem_len);
+
+	/* Clear the screen */
+	memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+	return 0;
+}
+
+static void imx_ipu_fb_enable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (mxc_fbi->enabled)
+		return;
+
+	ipu_di_enable(mxc_fbi->ipu_di);
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dc_enable_channel(mxc_fbi->dc);
+	ipu_dp_enable_channel(mxc_fbi->dp);
+	mxc_fbi->enabled = 1;
+}
+
+static void imx_ipu_fb_disable(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	if (!mxc_fbi->enabled)
+		return;
+
+	ipu_dp_disable_channel(mxc_fbi->dp);
+	ipu_dc_disable_channel(mxc_fbi->dc);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+	ipu_di_disable(mxc_fbi->ipu_di);
+
+	mxc_fbi->enabled = 0;
+}
+
+static int calc_vref(struct fb_var_screeninfo *var)
+{
+	unsigned long htotal, vtotal;
+
+	htotal = var->xres + var->right_margin + var->hsync_len + var->left_margin;
+	vtotal = var->yres + var->lower_margin + var->vsync_len + var->upper_margin;
+
+	if (!htotal || !vtotal)
+		return 60;
+
+	return PICOS2KHZ(var->pixclock) * 1000 / vtotal / htotal;
+}
+
+static int calc_bandwidth(struct fb_var_screeninfo *var, unsigned int vref)
+{
+	return var->xres * var->yres * vref;
+}
+
+static int imx_ipu_fb_set_par(struct fb_info *fbi)
+{
+	int ret;
+	struct ipu_di_signal_cfg sig_cfg;
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	u32 out_pixel_fmt;
+	int interlaced = 0;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable(fbi);
+
+	fbi->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+	var->yres_virtual = var->yres;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	memset(&sig_cfg, 0, sizeof(sig_cfg));
+	out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		sig_cfg.interlaced = 1;
+	if (var->vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
+		sig_cfg.odd_field_first = 1;
+	if (var->sync & FB_SYNC_EXT)
+		sig_cfg.ext_clk = 1;
+	if (var->sync & FB_SYNC_HOR_HIGH_ACT)
+		sig_cfg.Hsync_pol = 1;
+	if (var->sync & FB_SYNC_VERT_HIGH_ACT)
+		sig_cfg.Vsync_pol = 1;
+	if (!(var->sync & FB_SYNC_CLK_LAT_FALL))
+		sig_cfg.clk_pol = 1;
+	if (var->sync & FB_SYNC_DATA_INVERT)
+		sig_cfg.data_pol = 1;
+	if (!(var->sync & FB_SYNC_OE_LOW_ACT))
+		sig_cfg.enable_pol = 1;
+	if (var->sync & FB_SYNC_CLK_IDLE_EN)
+		sig_cfg.clkidle_en = 1;
+
+	dev_dbg(fbi->device, "pixclock = %lu.%03lu MHz\n",
+		PICOS2KHZ(var->pixclock) / 1000,
+		PICOS2KHZ(var->pixclock) % 1000);
+
+	sig_cfg.width = var->xres;
+	sig_cfg.height = var->yres;
+	sig_cfg.pixel_fmt = out_pixel_fmt;
+	sig_cfg.h_start_width = var->left_margin;
+	sig_cfg.h_sync_width = var->hsync_len;
+	sig_cfg.h_end_width = var->right_margin;
+	sig_cfg.v_start_width = var->upper_margin;
+	sig_cfg.v_sync_width = var->vsync_len;
+	sig_cfg.v_end_width = var->lower_margin;
+	sig_cfg.v_to_h_sync = 0;
+
+	if (mxc_fbi->dp) {
+		ret = ipu_dp_setup_channel(mxc_fbi->dp, mxc_fbi->ipu_in_pix_fmt,
+				out_pixel_fmt, 1);
+		if (ret) {
+			dev_dbg(fbi->device, "initializing display processor failed with %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	ret = ipu_dc_init_sync(mxc_fbi->dc, mxc_fbi->ipu_di, interlaced,
+			out_pixel_fmt, fbi->var.xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing display controller failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_di_init_sync_panel(mxc_fbi->ipu_di,
+				PICOS2KHZ(var->pixclock) * 1000UL,
+				&sig_cfg);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing panel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	fbi->mode = (struct fb_videomode *)fb_match_mode(var, &fbi->modelist);
+	var->xoffset = var->yoffset = 0;
+
+	if (fbi->var.vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable(fbi);
+
+	return ret;
+}
+
+/*
+ * These are the bitfields for each
+ * display depth that we support.
+ */
+struct imxfb_rgb {
+	struct fb_bitfield	red;
+	struct fb_bitfield	green;
+	struct fb_bitfield	blue;
+	struct fb_bitfield	transp;
+};
+
+static struct imxfb_rgb def_rgb_8 = {
+	.red	= { .offset =  5, .length = 3, },
+	.green	= { .offset =  2, .length = 3, },
+	.blue	= { .offset =  0, .length = 2, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_16 = {
+	.red	= { .offset = 11, .length = 5, },
+	.green	= { .offset =  5, .length = 6, },
+	.blue	= { .offset =  0, .length = 5, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_24 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset =  0, .length = 0, },
+};
+
+static struct imxfb_rgb def_rgb_32 = {
+	.red	= { .offset = 16, .length = 8, },
+	.green	= { .offset =  8, .length = 8, },
+	.blue	= { .offset =  0, .length = 8, },
+	.transp = { .offset = 24, .length = 8, },
+};
+
+/*
+ * Check framebuffer variable parameters and adjust to valid values.
+ *
+ * @param       var      framebuffer variable parameters
+ *
+ * @param       info     framebuffer information pointer
+ */
+static int imx_ipu_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	struct imxfb_rgb *rgb;
+
+	/* we don't support xpan, force xres_virtual to be equal to xres */
+	var->xres_virtual = var->xres;
+
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+
+	switch (var->bits_per_pixel) {
+	case 8:
+		rgb = &def_rgb_8;
+		break;
+	case 16:
+		rgb = &def_rgb_16;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_RGB565;
+		break;
+	case 24:
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+		break;
+	case 32:
+		rgb = &def_rgb_32;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR32;
+		break;
+	default:
+		var->bits_per_pixel = 24;
+		rgb = &def_rgb_24;
+		mxc_fbi->ipu_in_pix_fmt = IPU_PIX_FMT_BGR24;
+	}
+
+	var->red    = rgb->red;
+	var->green  = rgb->green;
+	var->blue   = rgb->blue;
+	var->transp = rgb->transp;
+
+	return 0;
+}
+
+static inline unsigned int chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+	chan &= 0xffff;
+	chan >>= 16 - bf->length;
+	return chan << bf->offset;
+}
+
+static int imx_ipu_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
+			   u_int trans, struct fb_info *fbi)
+{
+	unsigned int val;
+	int ret = 1;
+
+	/*
+	 * If greyscale is true, then we convert the RGB value
+	 * to greyscale no matter what visual we are using.
+	 */
+	if (fbi->var.grayscale)
+		red = green = blue = (19595 * red + 38470 * green +
+				      7471 * blue) >> 16;
+	switch (fbi->fix.visual) {
+	case FB_VISUAL_TRUECOLOR:
+		/*
+		 * 16-bit True Colour.  We encode the RGB value
+		 * according to the RGB bitfield information.
+		 */
+		if (regno < 16) {
+			u32 *pal = fbi->pseudo_palette;
+
+			val = chan_to_field(red, &fbi->var.red);
+			val |= chan_to_field(green, &fbi->var.green);
+			val |= chan_to_field(blue, &fbi->var.blue);
+
+			pal[regno] = val;
+			ret = 0;
+		}
+		break;
+
+	case FB_VISUAL_STATIC_PSEUDOCOLOR:
+	case FB_VISUAL_PSEUDOCOLOR:
+		break;
+	}
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank(int blank, struct fb_info *info)
+{
+	dev_dbg(info->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable(info);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable(info);
+		break;
+	}
+
+	return 0;
+}
+
+static int imx_ipu_fb_pan_display(struct fb_var_screeninfo *var,
+		struct fb_info *info)
+{
+	struct imx_ipu_fb_info *mxc_fbi = info->par;
+	unsigned long base;
+	int ret;
+
+	if (info->var.yoffset == var->yoffset)
+		return 0;	/* No change, do nothing */
+
+	base = var->yoffset * var->xres_virtual * var->bits_per_pixel / 8;
+	base += info->fix.smem_start;
+
+	ret = ipu_wait_for_interrupt(IPU_IRQ_EOF(mxc_fbi->ipu_channel_num), 100);
+	if (ret)
+		return ret;
+
+	if (ipu_idmac_update_channel_buffer(mxc_fbi->ipu_ch, 0, base)) {
+		dev_err(info->device,
+			"Error updating SDC buf to address=0x%08lX\n", base);
+	}
+
+	info->var.yoffset = var->yoffset;
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank,
+};
+
+/*
+ * Overlay functions
+ */
+static int imx_ipu_fb_enable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dmfc_enable_channel(mxc_fbi->dmfc);
+	ipu_idmac_enable_channel(mxc_fbi->ipu_ch);
+	ipu_dp_enable_fg(mxc_fbi->dp);
+
+	return 0;
+}
+
+static int imx_ipu_fb_disable_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+
+	ipu_dp_disable_fg(mxc_fbi->dp);
+	ipu_idmac_disable_channel(mxc_fbi->ipu_ch);
+	ipu_dmfc_disable_channel(mxc_fbi->dmfc);
+
+	return 0;
+}
+
+static int imx_ipu_fb_set_par_overlay(struct fb_info *fbi)
+{
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct fb_var_screeninfo *var = &fbi->var;
+	struct fb_info *fbi_master = mxc_fbi->master;
+	struct fb_var_screeninfo *var_master = &fbi_master->var;
+	int ret;
+	int interlaced = 0;
+	int enabled = mxc_fbi->enabled;
+
+	dev_dbg(fbi->device, "Reconfiguring framebuffer %dx%d-%d\n",
+		fbi->var.xres, fbi->var.yres, fbi->var.bits_per_pixel);
+
+	if (enabled)
+		imx_ipu_fb_disable_overlay(fbi);
+
+	fbi->fix.line_length = var->xres_virtual *
+                                  var->bits_per_pixel / 8;
+
+	ret = imx_ipu_fb_map_video_memory(fbi);
+	if (ret)
+		return ret;
+
+	ipu_dp_set_window_pos(mxc_fbi->dp, 64, 64);
+
+	var->xoffset = var->yoffset = 0;
+
+	if (var->vmode & FB_VMODE_INTERLACED)
+		interlaced = 1;
+
+	ret = ipu_idmac_init_channel_buffer(mxc_fbi->ipu_ch,
+					mxc_fbi->ipu_in_pix_fmt,
+					var->xres, var->yres,
+					fbi->fix.line_length,
+					IPU_ROTATE_NONE,
+					fbi->fix.smem_start,
+					0,
+					0, 0, interlaced);
+	if (ret) {
+		dev_dbg(fbi->device, "init channel buffer failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_init_channel(mxc_fbi->dmfc, var->xres);
+	if (ret) {
+		dev_dbg(fbi->device, "initializing dmfc channel failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	ret = ipu_dmfc_alloc_bandwidth(mxc_fbi->dmfc, calc_bandwidth(var, calc_vref(var_master)));
+	if (ret) {
+		dev_dbg(fbi->device, "allocating dmfc bandwidth failed with %d\n",
+				ret);
+		return ret;
+	}
+
+	if (enabled)
+		imx_ipu_fb_enable_overlay(fbi);
+
+	return ret;
+}
+
+static int imx_ipu_fb_blank_overlay(int blank, struct fb_info *fbi)
+{
+	dev_dbg(fbi->device, "blank = %d\n", blank);
+
+	switch (blank) {
+	case FB_BLANK_POWERDOWN:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_NORMAL:
+		imx_ipu_fb_disable_overlay(fbi);
+		break;
+	case FB_BLANK_UNBLANK:
+		imx_ipu_fb_enable_overlay(fbi);
+		break;
+	}
+
+	return 0;
+}
+
+static struct fb_ops imx_ipu_fb_overlay_ops = {
+	.owner		= THIS_MODULE,
+	.fb_set_par	= imx_ipu_fb_set_par_overlay,
+	.fb_check_var	= imx_ipu_fb_check_var,
+	.fb_setcolreg	= imx_ipu_fb_setcolreg,
+	.fb_pan_display	= imx_ipu_fb_pan_display,
+	.fb_fillrect	= cfb_fillrect,
+	.fb_copyarea	= cfb_copyarea,
+	.fb_imageblit	= cfb_imageblit,
+	.fb_blank	= imx_ipu_fb_blank_overlay,
+};
+
+static struct fb_info *imx_ipu_fb_init_fbinfo(struct device *dev, struct fb_ops *ops)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+
+	fbi = framebuffer_alloc(sizeof(struct imx_ipu_fb_info), dev);
+	if (!fbi)
+		return NULL;
+
+	BUG_ON(fbi->par == NULL);
+	mxc_fbi = fbi->par;
+
+	fbi->var.activate = FB_ACTIVATE_NOW;
+
+	fbi->fbops = ops;
+	fbi->flags = FBINFO_FLAG_DEFAULT;
+	fbi->pseudo_palette = mxc_fbi->pseudo_palette;
+
+	fb_alloc_cmap(&fbi->cmap, 16, 0);
+
+	return fbi;
+}
+
+static int imx_ipu_fb_init_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi;
+	struct imx_ipu_fb_info *ovl_mxc_fbi;
+	int ret;
+
+	ovlfbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_overlay_ops);
+	if (!ovlfbi)
+		return -ENOMEM;
+
+	ovl_mxc_fbi = ovlfbi->par;
+	ovl_mxc_fbi->ipu_ch = ipu_idmac_get(ipu_channel);
+	ovl_mxc_fbi->dmfc = ipu_dmfc_get(ipu_channel);
+	ovl_mxc_fbi->ipu_di = -1;
+	ovl_mxc_fbi->dp = mxc_fbi_master->dp;
+	ovl_mxc_fbi->master = fbi_master;
+	mxc_fbi_master->slave = ovlfbi;
+
+	ovlfbi->var.xres = 240;
+	ovlfbi->var.yres = 320;
+	ovlfbi->var.yres_virtual = ovlfbi->var.yres;
+	ovlfbi->var.xres_virtual = ovlfbi->var.xres;
+	imx_ipu_fb_check_var(&ovlfbi->var, ovlfbi);
+	imx_ipu_fb_set_fix(ovlfbi);
+
+	ret = register_framebuffer(ovlfbi);
+	if (ret) {
+		framebuffer_release(ovlfbi);
+		return ret;
+	}
+
+	ipu_dp_set_global_alpha(ovl_mxc_fbi->dp, 1, 0x80, 1);
+	ipu_dp_set_color_key(ovl_mxc_fbi->dp, 0, 0);
+
+	imx_ipu_fb_set_par_overlay(ovlfbi);
+
+	return 0;
+}
+
+static void imx_ipu_fb_exit_overlay(struct platform_device *pdev,
+		struct fb_info *fbi_master, int ipu_channel)
+{
+	struct imx_ipu_fb_info *mxc_fbi_master = fbi_master->par;
+	struct fb_info *ovlfbi = mxc_fbi_master->slave;
+	struct imx_ipu_fb_info *ovl_mxc_fbi = ovlfbi->par;
+
+	imx_ipu_fb_blank_overlay(FB_BLANK_POWERDOWN, ovlfbi);
+
+	unregister_framebuffer(ovlfbi);
+
+	ipu_idmac_put(ovl_mxc_fbi->ipu_ch);
+	ipu_dmfc_free_bandwidth(ovl_mxc_fbi->dmfc);
+	ipu_dmfc_put(ovl_mxc_fbi->dmfc);
+
+	framebuffer_release(ovlfbi);
+}
+
+static int imx_ipu_fb_find_mode(struct fb_info *fbi)
+{
+	int ret;
+	struct fb_videomode *mode_array;
+	struct fb_modelist *modelist;
+	struct fb_var_screeninfo *var = &fbi->var;
+	int i = 0;
+
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		i++;
+
+	mode_array = kmalloc(sizeof (struct fb_modelist) * i, GFP_KERNEL);
+	if (!mode_array)
+		return -ENOMEM;
+
+	i = 0;
+	list_for_each_entry(modelist, &fbi->modelist, list)
+		mode_array[i++] = modelist->mode;
+
+	ret = fb_find_mode(&fbi->var, fbi, NULL, mode_array, i, NULL, 16);
+	if (ret == 0)
+		return -EINVAL;
+
+	dev_dbg(fbi->device, "found %dx%d-%d hs:%d:%d:%d vs:%d:%d:%d\n",
+			var->xres, var->yres, var->bits_per_pixel,
+			var->hsync_len, var->left_margin, var->right_margin,
+			var->vsync_len, var->upper_margin, var->lower_margin);
+
+	kfree(mode_array);
+
+	return 0;
+}
+
+static int __devinit imx_ipu_fb_probe(struct platform_device *pdev)
+{
+	struct fb_info *fbi;
+	struct imx_ipu_fb_info *mxc_fbi;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+	int ret = 0, i;
+
+	pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+
+	fbi = imx_ipu_fb_init_fbinfo(&pdev->dev, &imx_ipu_fb_ops);
+	if (!fbi)
+		return -ENOMEM;
+
+	mxc_fbi = fbi->par;
+
+	mxc_fbi->ipu_channel_num = plat_data->ipu_channel_bg;
+	mxc_fbi->dc = plat_data->dc_channel;
+	mxc_fbi->ipu_di_pix_fmt = plat_data->interface_pix_fmt;
+	mxc_fbi->ipu_di = pdev->id;
+
+	mxc_fbi->ipu_ch = ipu_idmac_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_ipu;
+	}
+
+	mxc_fbi->dmfc = ipu_dmfc_get(plat_data->ipu_channel_bg);
+	if (IS_ERR(mxc_fbi->ipu_ch)) {
+		ret = PTR_ERR(mxc_fbi->ipu_ch);
+		goto failed_request_dmfc;
+	}
+
+	if (plat_data->dp_channel >= 0) {
+		mxc_fbi->dp = ipu_dp_get(plat_data->dp_channel);
+		if (IS_ERR(mxc_fbi->dp)) {
+			ret = PTR_ERR(mxc_fbi->ipu_ch);
+			goto failed_request_dp;
+		}
+	}
+
+	fbi->var.yres_virtual = fbi->var.yres;
+
+	INIT_LIST_HEAD(&fbi->modelist);
+	for (i = 0; i < plat_data->num_modes; i++)
+		fb_add_videomode(&plat_data->modes[i], &fbi->modelist);
+
+	if (plat_data->flags & IMX_IPU_FB_USE_MODEDB) {
+		for (i = 0; i < num_fb_modes; i++)
+			fb_add_videomode(&fb_modes[i], &fbi->modelist);
+	}
+
+	imx_ipu_fb_find_mode(fbi);
+
+	imx_ipu_fb_check_var(&fbi->var, fbi);
+	imx_ipu_fb_set_fix(fbi);
+	ret = register_framebuffer(fbi);
+	if (ret < 0)
+		goto failed_register;
+
+	imx_ipu_fb_set_par(fbi);
+	imx_ipu_fb_blank(FB_BLANK_UNBLANK, fbi);
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_init_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	platform_set_drvdata(pdev, fbi);
+
+	return 0;
+
+failed_register:
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+failed_request_dp:
+	ipu_dmfc_put(mxc_fbi->dmfc);
+failed_request_dmfc:
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+failed_request_ipu:
+	fb_dealloc_cmap(&fbi->cmap);
+	framebuffer_release(fbi);
+
+	return ret;
+}
+
+static int __devexit imx_ipu_fb_remove(struct platform_device *pdev)
+{
+	struct fb_info *fbi = platform_get_drvdata(pdev);
+	struct imx_ipu_fb_info *mxc_fbi = fbi->par;
+	struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+
+	if (plat_data->ipu_channel_fg >= 0 && plat_data->flags & IMX_IPU_FB_USE_OVERLAY)
+		imx_ipu_fb_exit_overlay(pdev, fbi, plat_data->ipu_channel_fg);
+
+	imx_ipu_fb_blank(FB_BLANK_POWERDOWN, fbi);
+
+	dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+			      fbi->screen_base, fbi->fix.smem_start);
+
+	if (&fbi->cmap)
+		fb_dealloc_cmap(&fbi->cmap);
+
+	unregister_framebuffer(fbi);
+
+	if (plat_data->dp_channel >= 0)
+		ipu_dp_put(mxc_fbi->dp);
+	ipu_dmfc_free_bandwidth(mxc_fbi->dmfc);
+	ipu_dmfc_put(mxc_fbi->dmfc);
+	ipu_idmac_put(mxc_fbi->ipu_ch);
+
+	framebuffer_release(fbi);
+
+	return 0;
+}
+
+static struct platform_driver imx_ipu_fb_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+	},
+	.probe = imx_ipu_fb_probe,
+	.remove = __devexit_p(imx_ipu_fb_remove),
+};
+
+static int __init imx_ipu_fb_init(void)
+{
+	return platform_driver_register(&imx_ipu_fb_driver);
+}
+
+static void __exit imx_ipu_fb_exit(void)
+{
+	platform_driver_unregister(&imx_ipu_fb_driver);
+}
+
+module_init(imx_ipu_fb_init);
+module_exit(imx_ipu_fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
-- 
1.7.2.3

  parent reply	other threads:[~2010-12-09 13:47 UTC|newest]

Thread overview: 118+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-12-09 13:47 [PATCH RFC] i.MX51 Framebuffer support Sascha Hauer
2010-12-09 13:47 ` Sascha Hauer
2010-12-09 13:47 ` Sascha Hauer
2010-12-09 13:47 ` [PATCH 1/9] ARM i.MX51: Add ipu clock support Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-15 15:40   ` Arnd Bergmann
2010-12-15 15:40     ` Arnd Bergmann
2010-12-15 15:40     ` Arnd Bergmann
2010-12-15 16:34     ` Russell King - ARM Linux
2010-12-15 16:34       ` Russell King - ARM Linux
2010-12-15 16:34       ` Russell King - ARM Linux
2010-12-15 16:49       ` Arnd Bergmann
2010-12-15 16:49         ` Arnd Bergmann
2010-12-15 16:49         ` Arnd Bergmann
2010-12-15 17:12         ` Russell King - ARM Linux
2010-12-15 17:12           ` Russell King - ARM Linux
2010-12-15 17:12           ` Russell King - ARM Linux
2010-12-09 13:47 ` [PATCH 2/9] ARM i.MX51: rename IPU irqs Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 14:34   ` Uwe Kleine-König
2010-12-09 14:34     ` Uwe Kleine-König
2010-12-09 14:34     ` 
2010-12-09 13:47 ` [PATCH 3/9] Add a mfd IPUv3 driver Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-12  5:21   ` Liu Ying
2010-12-12  5:21     ` Liu Ying
2010-12-13 11:23     ` Sascha Hauer
2010-12-13 11:23       ` Sascha Hauer
2010-12-13 11:23       ` Sascha Hauer
2010-12-14  4:05       ` Liu Ying
2010-12-14  4:05         ` Liu Ying
2010-12-14  4:05         ` Liu Ying
2010-12-14  8:40         ` Sascha Hauer
2010-12-14  8:40           ` Sascha Hauer
2010-12-14  8:40           ` Sascha Hauer
2010-12-14 13:13           ` Liu Ying
2010-12-14 13:13             ` Liu Ying
2010-12-14 13:13             ` Liu Ying
2010-12-09 13:47 ` [PATCH 4/9] fb: export fb mode db table Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2011-01-06  7:26   ` Paul Mundt
2011-01-06  7:26     ` Paul Mundt
2011-01-06  7:26     ` Paul Mundt
2011-01-06 10:04     ` Sascha Hauer
2011-01-06 10:04       ` Sascha Hauer
2011-01-06 10:04       ` Sascha Hauer
2010-12-09 13:47 ` Sascha Hauer [this message]
2010-12-09 13:47   ` [PATCH 5/9] Add i.MX5 framebuffer driver Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-12  6:13   ` Liu Ying
2010-12-12  6:13     ` Liu Ying
2010-12-12  6:13     ` Liu Ying
2010-12-13  7:23     ` Lothar Waßmann
2010-12-13  7:23       ` Lothar Waßmann
2010-12-13  7:23       ` Lothar Waßmann
2010-12-13 11:35       ` Liu Ying
2010-12-13 11:35         ` Liu Ying
2010-12-13 11:35         ` Liu Ying
2010-12-13 11:38     ` Sascha Hauer
2010-12-13 11:38       ` Sascha Hauer
2010-12-13 11:38       ` Sascha Hauer
2010-12-14  6:40       ` Liu Ying
2010-12-14  6:40         ` Liu Ying
2010-12-14  6:40         ` Liu Ying
2010-12-14  8:45         ` Sascha Hauer
2010-12-14  8:45           ` Sascha Hauer
2010-12-14  8:45           ` Sascha Hauer
2010-12-14 13:23           ` Liu Ying
2010-12-14 13:23             ` Liu Ying
2010-12-14 13:23             ` Liu Ying
2010-12-15 11:17             ` Sascha Hauer
2010-12-15 11:17               ` Sascha Hauer
2010-12-15 11:17               ` Sascha Hauer
2010-12-09 13:47 ` [PATCH 6/9] ARM i.MX51: Add IPU device support Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-15 15:49   ` Arnd Bergmann
2010-12-15 15:49     ` Arnd Bergmann
2010-12-15 15:49     ` Arnd Bergmann
2010-12-15 16:26     ` Arnaud Patard
2010-12-15 16:26       ` Arnaud Patard (Rtp)
2010-12-15 16:26       ` Arnaud Patard
2010-12-15 16:29       ` Arnd Bergmann
2010-12-15 16:29         ` Arnd Bergmann
2010-12-15 16:29         ` Arnd Bergmann
2010-12-09 13:47 ` [PATCH 7/9] ARM i.MX5: Allow to increase max zone order Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47 ` [PATCH 8/9] ARM i.MX5: increase dma consistent size for IPU support Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47 ` [PATCH 9/9] ARM i.MX51 babbage: Add framebuffer support Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-09 13:47   ` Sascha Hauer
2010-12-12  1:37   ` Liu Ying
2010-12-12  1:37     ` Liu Ying
2010-12-12  1:37     ` Liu Ying
2010-12-13 11:43     ` Sascha Hauer
2010-12-13 11:43       ` Sascha Hauer
2010-12-13 11:43       ` Sascha Hauer
2010-12-14  6:47       ` Liu Ying
2010-12-14  6:47         ` Liu Ying
2010-12-14  6:47         ` Liu Ying
2010-12-15  9:35 ` [PATCH RFC] i.MX51 Framebuffer support Peter Horton
2010-12-15 11:24   ` Sascha Hauer
     [not found] <33F32152BE7EC740BC2C838D2836AC8704A957@039-SN1MPN1-002.039d.mgd.msft.net>
2010-12-14 12:38 ` [PATCH 5/9] Add i.MX5 framebuffer driver Chen Jie-B02280
2010-12-14 12:38   ` Chen Jie-B02280
2010-12-15 14:38   ` s.hauer
2010-12-15 14:38     ` s.hauer at pengutronix.de
2010-12-15 14:38     ` s.hauer
2010-12-16  2:07     ` Chen Jie-B02280
2010-12-16  2:07       ` Chen Jie-B02280
2010-12-16  2:07       ` Chen Jie-B02280
2010-12-20 10:48 [PATCH v2] i.MX51 Framebuffer support Sascha Hauer
2010-12-20 10:48 ` [PATCH 5/9] Add i.MX5 framebuffer driver Sascha Hauer
2010-12-20 10:48   ` Sascha Hauer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1291902441-24712-6-git-send-email-s.hauer@pengutronix.de \
    --to=s.hauer@pengutronix.de \
    --cc=arnaud.patard@rtp-net.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-fbdev@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=r58066@freescale.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.