summaryrefslogtreecommitdiff
path: root/drivers/video
diff options
context:
space:
mode:
authorShawn Guo <shawn.guo@freescale.com>2014-06-12 15:12:20 +0800
committerNitin Garg <nitin.garg@freescale.com>2015-01-15 21:16:22 -0600
commit2a1072f8e8c55a1987b9074abf0b3c3d149dc958 (patch)
treec64f355917f75df2d97637bfbd42c0ca2e4e2b4f /drivers/video
parentc6a50017ca3d71309384267d1889daa995945c8f (diff)
ENGR00317981: video: forward mxc video drivers to 3.14 kernel
Forward imx_3.10_y mxc video drivers to 3.14 kernel. Signed-off-by: Shawn Guo <shawn.guo@freescale.com>
Diffstat (limited to 'drivers/video')
-rw-r--r--drivers/video/Kconfig4
-rw-r--r--drivers/video/Makefile1
-rw-r--r--drivers/video/mxc/Kconfig72
-rw-r--r--drivers/video/mxc/Makefile9
-rw-r--r--drivers/video/mxc/crtc.h57
-rw-r--r--drivers/video/mxc/epdc_regs.h442
-rw-r--r--drivers/video/mxc/hannstar_cabc.c89
-rw-r--r--drivers/video/mxc/ldb.c891
-rw-r--r--drivers/video/mxc/mipi_dsi.c963
-rw-r--r--drivers/video/mxc/mipi_dsi.h113
-rw-r--r--drivers/video/mxc/mxc_dispdrv.c148
-rw-r--r--drivers/video/mxc/mxc_dispdrv.h52
-rw-r--r--drivers/video/mxc/mxc_edid.c762
-rw-r--r--drivers/video/mxc/mxc_epdc_fb.c5541
-rw-r--r--drivers/video/mxc/mxc_hdmi.c2916
-rw-r--r--drivers/video/mxc/mxc_ipuv3_fb.c2583
-rw-r--r--drivers/video/mxc/mxc_lcdif.c237
-rw-r--r--drivers/video/mxc/mxcfb_hx8369_wvga.c449
-rw-r--r--drivers/video/mxc/mxsfb_sii902x.c532
19 files changed, 15861 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index a3b92081bf8a..329f44aa5366 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2491,6 +2491,10 @@ source "drivers/video/exynos/Kconfig"
source "drivers/video/mmp/Kconfig"
source "drivers/video/backlight/Kconfig"
+if ARCH_MXC
+source "drivers/video/mxc/Kconfig"
+endif
+
if VT
source "drivers/video/console/Kconfig"
endif
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index ae17ddf49a00..2dcc9cf1f4f4 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_FB_KYRO) += kyro/
obj-$(CONFIG_FB_SAVAGE) += savage/
obj-$(CONFIG_FB_GEODE) += geode/
obj-$(CONFIG_FB_MBX) += mbx/
+obj-$(CONFIG_FB_MXC) += mxc/
obj-$(CONFIG_FB_NEOMAGIC) += neofb.o
obj-$(CONFIG_FB_3DFX) += tdfxfb.o
obj-$(CONFIG_FB_CONTROL) += controlfb.o
diff --git a/drivers/video/mxc/Kconfig b/drivers/video/mxc/Kconfig
new file mode 100644
index 000000000000..7f2967f95afa
--- /dev/null
+++ b/drivers/video/mxc/Kconfig
@@ -0,0 +1,72 @@
+config FB_MXC
+ tristate "MXC Framebuffer support"
+ depends on FB
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ select FB_MODE_HELPERS
+ default y
+ help
+ This is a framebuffer device for the MXC LCD Controller.
+ See <http://www.linux-fbdev.org/> for information on framebuffer
+ devices.
+
+ If you plan to use the LCD display with your MXC system, say
+ Y here.
+
+config FB_MXC_SYNC_PANEL
+ depends on FB_MXC
+ tristate "Synchronous Panel Framebuffer"
+
+config FB_MXC_LDB
+ tristate "MXC LDB"
+ depends on FB_MXC_SYNC_PANEL
+ depends on MXC_IPU_V3 || FB_MXS
+ select VIDEOMODE_HELPERS
+
+config FB_MXC_MIPI_DSI
+ tristate "MXC MIPI_DSI"
+ depends on FB_MXC_SYNC_PANEL
+ depends on MXC_IPU_V3
+
+config FB_MXC_TRULY_WVGA_SYNC_PANEL
+ tristate "TRULY WVGA Panel"
+ depends on FB_MXC_SYNC_PANEL
+ depends on FB_MXC_MIPI_DSI
+
+config FB_MXC_HDMI
+ depends on FB_MXC_SYNC_PANEL
+ depends on MXC_IPU_V3
+ depends on I2C
+ tristate "MXC HDMI driver support"
+ select MFD_MXC_HDMI
+ help
+ Driver for the on-chip MXC HDMI controller.
+
+config FB_MXC_EDID
+ depends on FB_MXC && I2C
+ tristate "MXC EDID support"
+ default y
+
+config FB_MXC_EINK_PANEL
+ depends on FB_MXC
+ depends on DMA_ENGINE
+ select FB_DEFERRED_IO
+ tristate "E-Ink Panel Framebuffer"
+
+config FB_MXC_EINK_AUTO_UPDATE_MODE
+ bool "E-Ink Auto-update Mode Support"
+ default n
+ depends on FB_MXC_EINK_PANEL
+
+config FB_MXS_SII902X
+ tristate "Si Image SII9022 DVI/HDMI Interface Chip"
+ depends on FB_MXS && I2C
+
+config HANNSTAR_CABC
+ tristate "Hannstar CABC function"
+ help
+ Say yes here to support switching on/off Hannstar CABC
+ function. This function turns backlight density of a
+ display panel automatically according to the content
+ shown on the panel.
diff --git a/drivers/video/mxc/Makefile b/drivers/video/mxc/Makefile
new file mode 100644
index 000000000000..c0fec9e79727
--- /dev/null
+++ b/drivers/video/mxc/Makefile
@@ -0,0 +1,9 @@
+obj-$(CONFIG_FB_MXC_LDB) += ldb.o
+obj-$(CONFIG_FB_MXC_MIPI_DSI) += mipi_dsi.o
+obj-$(CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL) += mxcfb_hx8369_wvga.o
+obj-$(CONFIG_FB_MXC_HDMI) += mxc_hdmi.o
+obj-$(CONFIG_FB_MXC_EDID) += mxc_edid.o
+obj-$(CONFIG_FB_MXC_SYNC_PANEL) += mxc_dispdrv.o mxc_lcdif.o mxc_ipuv3_fb.o
+obj-$(CONFIG_FB_MXC_EINK_PANEL) += mxc_epdc_fb.o
+obj-$(CONFIG_FB_MXS_SII902X) += mxsfb_sii902x.o
+obj-$(CONFIG_HANNSTAR_CABC) += hannstar_cabc.o
diff --git a/drivers/video/mxc/crtc.h b/drivers/video/mxc/crtc.h
new file mode 100644
index 000000000000..8bd1c1b95bd1
--- /dev/null
+++ b/drivers/video/mxc/crtc.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 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
+ */
+#ifndef __CRTC__
+#define __CRTC__
+
+enum crtc {
+ CRTC_IPU_DI0,
+ CRTC_IPU_DI1,
+ CRTC_IPU1_DI0,
+ CRTC_IPU1_DI1,
+ CRTC_IPU2_DI0,
+ CRTC_IPU2_DI1,
+ CRTC_LCDIF,
+ CRTC_LCDIF1,
+ CRTC_LCDIF2,
+ CRTC_MAX,
+};
+
+struct ipu_di_crtc_map {
+ enum crtc crtc;
+ int ipu_id;
+ int ipu_di;
+};
+
+static const struct ipu_di_crtc_map ipu_di_crtc_maps[] = {
+ {CRTC_IPU1_DI0, 0 , 0}, {CRTC_IPU1_DI1, 0 , 1},
+ {CRTC_IPU2_DI0, 1 , 0}, {CRTC_IPU2_DI1, 1 , 1},
+};
+
+static inline int ipu_di_to_crtc(struct device *dev, int ipu_id,
+ int ipu_di, enum crtc *crtc)
+{
+ int i = 0;
+
+ for (; i < ARRAY_SIZE(ipu_di_crtc_maps); i++)
+ if (ipu_di_crtc_maps[i].ipu_id == ipu_id &&
+ ipu_di_crtc_maps[i].ipu_di == ipu_di) {
+ *crtc = ipu_di_crtc_maps[i].crtc;
+ return 0;
+ }
+
+ dev_err(dev, "failed to get valid ipu di crtc "
+ "ipu_id %d, ipu_di %d\n", ipu_id, ipu_di);
+ return -EINVAL;
+}
+
+#endif
diff --git a/drivers/video/mxc/epdc_regs.h b/drivers/video/mxc/epdc_regs.h
new file mode 100644
index 000000000000..d4e6e525b7b9
--- /dev/null
+++ b/drivers/video/mxc/epdc_regs.h
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#ifndef __EPDC_REGS_INCLUDED__
+#define __EPDC_REGS_INCLUDED__
+
+extern void __iomem *epdc_base;
+
+/*************************************
+ * Register addresses
+ **************************************/
+
+#define EPDC_CTRL (epdc_base + 0x000)
+#define EPDC_CTRL_SET (epdc_base + 0x004)
+#define EPDC_CTRL_CLEAR (epdc_base + 0x008)
+#define EPDC_CTRL_TOGGLE (epdc_base + 0x00C)
+#define EPDC_WVADDR (epdc_base + 0x020)
+#define EPDC_WB_ADDR (epdc_base + 0x030)
+#define EPDC_RES (epdc_base + 0x040)
+#define EPDC_FORMAT (epdc_base + 0x050)
+#define EPDC_FORMAT_SET (epdc_base + 0x054)
+#define EPDC_FORMAT_CLEAR (epdc_base + 0x058)
+#define EPDC_FORMAT_TOGGLE (epdc_base + 0x05C)
+#define EPDC_FIFOCTRL (epdc_base + 0x0A0)
+#define EPDC_FIFOCTRL_SET (epdc_base + 0x0A4)
+#define EPDC_FIFOCTRL_CLEAR (epdc_base + 0x0A8)
+#define EPDC_FIFOCTRL_TOGGLE (epdc_base + 0x0AC)
+#define EPDC_UPD_ADDR (epdc_base + 0x100)
+#define EPDC_UPD_STRIDE (epdc_base + 0x110)
+#define EPDC_UPD_CORD (epdc_base + 0x120)
+#define EPDC_UPD_SIZE (epdc_base + 0x140)
+#define EPDC_UPD_CTRL (epdc_base + 0x160)
+#define EPDC_UPD_FIXED (epdc_base + 0x180)
+#define EPDC_TEMP (epdc_base + 0x1A0)
+#define EPDC_AUTOWV_LUT (epdc_base + 0x1C0)
+#define EPDC_TCE_CTRL (epdc_base + 0x200)
+#define EPDC_TCE_SDCFG (epdc_base + 0x220)
+#define EPDC_TCE_GDCFG (epdc_base + 0x240)
+#define EPDC_TCE_HSCAN1 (epdc_base + 0x260)
+#define EPDC_TCE_HSCAN2 (epdc_base + 0x280)
+#define EPDC_TCE_VSCAN (epdc_base + 0x2A0)
+#define EPDC_TCE_OE (epdc_base + 0x2C0)
+#define EPDC_TCE_POLARITY (epdc_base + 0x2E0)
+#define EPDC_TCE_TIMING1 (epdc_base + 0x300)
+#define EPDC_TCE_TIMING2 (epdc_base + 0x310)
+#define EPDC_TCE_TIMING3 (epdc_base + 0x320)
+#define EPDC_PIGEON_CTRL0 (epdc_base + 0x380)
+#define EPDC_PIGEON_CTRL1 (epdc_base + 0x390)
+#define EPDC_IRQ_MASK1 (epdc_base + 0x3C0)
+#define EPDC_IRQ_MASK1_SET (epdc_base + 0x3C4)
+#define EPDC_IRQ_MASK1_CLEAR (epdc_base + 0x3C8)
+#define EPDC_IRQ_MASK1_TOGGLE (epdc_base + 0x3CC)
+#define EPDC_IRQ_MASK2 (epdc_base + 0x3D0)
+#define EPDC_IRQ_MASK2_SET (epdc_base + 0x3D4)
+#define EPDC_IRQ_MASK2_CLEAR (epdc_base + 0x3D8)
+#define EPDC_IRQ_MASK2_TOGGLE (epdc_base + 0x3DC)
+#define EPDC_IRQ1 (epdc_base + 0x3E0)
+#define EPDC_IRQ1_SET (epdc_base + 0x3E4)
+#define EPDC_IRQ1_CLEAR (epdc_base + 0x3E8)
+#define EPDC_IRQ1_TOGGLE (epdc_base + 0x3EC)
+#define EPDC_IRQ2 (epdc_base + 0x3F0)
+#define EPDC_IRQ2_SET (epdc_base + 0x3F4)
+#define EPDC_IRQ2_CLEAR (epdc_base + 0x3F8)
+#define EPDC_IRQ2_TOGGLE (epdc_base + 0x3FC)
+#define EPDC_IRQ_MASK (epdc_base + 0x400)
+#define EPDC_IRQ_MASK_SET (epdc_base + 0x404)
+#define EPDC_IRQ_MASK_CLEAR (epdc_base + 0x408)
+#define EPDC_IRQ_MASK_TOGGLE (epdc_base + 0x40C)
+#define EPDC_IRQ (epdc_base + 0x420)
+#define EPDC_IRQ_SET (epdc_base + 0x424)
+#define EPDC_IRQ_CLEAR (epdc_base + 0x428)
+#define EPDC_IRQ_TOGGLE (epdc_base + 0x42C)
+#define EPDC_STATUS_LUTS (epdc_base + 0x440)
+#define EPDC_STATUS_LUTS_SET (epdc_base + 0x444)
+#define EPDC_STATUS_LUTS_CLEAR (epdc_base + 0x448)
+#define EPDC_STATUS_LUTS_TOGGLE (epdc_base + 0x44C)
+#define EPDC_STATUS_LUTS2 (epdc_base + 0x450)
+#define EPDC_STATUS_LUTS2_SET (epdc_base + 0x454)
+#define EPDC_STATUS_LUTS2_CLEAR (epdc_base + 0x458)
+#define EPDC_STATUS_LUTS2_TOGGLE (epdc_base + 0x45C)
+#define EPDC_STATUS_NEXTLUT (epdc_base + 0x460)
+#define EPDC_STATUS_COL (epdc_base + 0x480)
+#define EPDC_STATUS_COL2 (epdc_base + 0x490)
+#define EPDC_STATUS (epdc_base + 0x4A0)
+#define EPDC_STATUS_SET (epdc_base + 0x4A4)
+#define EPDC_STATUS_CLEAR (epdc_base + 0x4A8)
+#define EPDC_STATUS_TOGGLE (epdc_base + 0x4AC)
+#define EPDC_UPD_COL_CORD (epdc_base + 0x4C0)
+#define EPDC_UPD_COL_SIZE (epdc_base + 0x4E0)
+#define EPDC_DEBUG (epdc_base + 0x500)
+#define EPDC_DEBUG_LUT (epdc_base + 0x530)
+#define EPDC_HIST1_PARAM (epdc_base + 0x600)
+#define EPDC_HIST2_PARAM (epdc_base + 0x610)
+#define EPDC_HIST4_PARAM (epdc_base + 0x620)
+#define EPDC_HIST8_PARAM0 (epdc_base + 0x630)
+#define EPDC_HIST8_PARAM1 (epdc_base + 0x640)
+#define EPDC_HIST16_PARAM0 (epdc_base + 0x650)
+#define EPDC_HIST16_PARAM1 (epdc_base + 0x660)
+#define EPDC_HIST16_PARAM2 (epdc_base + 0x670)
+#define EPDC_HIST16_PARAM3 (epdc_base + 0x680)
+#define EPDC_GPIO (epdc_base + 0x700)
+#define EPDC_VERSION (epdc_base + 0x7F0)
+#define EPDC_PIGEON_0_0 (epdc_base + 0x800)
+#define EPDC_PIGEON_0_1 (epdc_base + 0x810)
+#define EPDC_PIGEON_0_2 (epdc_base + 0x820)
+#define EPDC_PIGEON_1_0 (epdc_base + 0x840)
+#define EPDC_PIGEON_1_1 (epdc_base + 0x850)
+#define EPDC_PIGEON_1_2 (epdc_base + 0x860)
+#define EPDC_PIGEON_2_0 (epdc_base + 0x880)
+#define EPDC_PIGEON_2_1 (epdc_base + 0x890)
+#define EPDC_PIGEON_2_2 (epdc_base + 0x8A0)
+#define EPDC_PIGEON_3_0 (epdc_base + 0x8C0)
+#define EPDC_PIGEON_3_1 (epdc_base + 0x8D0)
+#define EPDC_PIGEON_3_2 (epdc_base + 0x8E0)
+#define EPDC_PIGEON_4_0 (epdc_base + 0x900)
+#define EPDC_PIGEON_4_1 (epdc_base + 0x910)
+#define EPDC_PIGEON_4_2 (epdc_base + 0x920)
+#define EPDC_PIGEON_5_0 (epdc_base + 0x940)
+#define EPDC_PIGEON_5_1 (epdc_base + 0x950)
+#define EPDC_PIGEON_5_2 (epdc_base + 0x960)
+#define EPDC_PIGEON_6_0 (epdc_base + 0x980)
+#define EPDC_PIGEON_6_1 (epdc_base + 0x990)
+#define EPDC_PIGEON_6_2 (epdc_base + 0x9A0)
+#define EPDC_PIGEON_7_0 (epdc_base + 0x9C0)
+#define EPDC_PIGEON_7_1 (epdc_base + 0x9D0)
+#define EPDC_PIGEON_7_2 (epdc_base + 0x9E0)
+#define EPDC_PIGEON_8_0 (epdc_base + 0xA00)
+#define EPDC_PIGEON_8_1 (epdc_base + 0xA10)
+#define EPDC_PIGEON_8_2 (epdc_base + 0xA20)
+#define EPDC_PIGEON_9_0 (epdc_base + 0xA40)
+#define EPDC_PIGEON_9_1 (epdc_base + 0xA50)
+#define EPDC_PIGEON_9_2 (epdc_base + 0xA60)
+#define EPDC_PIGEON_10_0 (epdc_base + 0xA80)
+#define EPDC_PIGEON_10_1 (epdc_base + 0xA90)
+#define EPDC_PIGEON_10_2 (epdc_base + 0xAA0)
+#define EPDC_PIGEON_11_0 (epdc_base + 0xAC0)
+#define EPDC_PIGEON_11_1 (epdc_base + 0xAD0)
+#define EPDC_PIGEON_11_2 (epdc_base + 0xAE0)
+#define EPDC_PIGEON_12_0 (epdc_base + 0xB00)
+#define EPDC_PIGEON_12_1 (epdc_base + 0xB10)
+#define EPDC_PIGEON_12_2 (epdc_base + 0xB20)
+#define EPDC_PIGEON_13_0 (epdc_base + 0xB40)
+#define EPDC_PIGEON_13_1 (epdc_base + 0xB50)
+#define EPDC_PIGEON_13_2 (epdc_base + 0xB60)
+#define EPDC_PIGEON_14_0 (epdc_base + 0xB80)
+#define EPDC_PIGEON_14_1 (epdc_base + 0xB90)
+#define EPDC_PIGEON_14_2 (epdc_base + 0xBA0)
+#define EPDC_PIGEON_15_0 (epdc_base + 0xBC0)
+#define EPDC_PIGEON_15_1 (epdc_base + 0xBD0)
+#define EPDC_PIGEON_15_2 (epdc_base + 0xBE0)
+#define EPDC_WB_ADDR_TCE (epdc_base + 0xC10)
+
+/*
+ * Register field definitions
+ */
+
+enum {
+/* EPDC_CTRL field values */
+ EPDC_CTRL_SFTRST = 0x80000000,
+ EPDC_CTRL_CLKGATE = 0x40000000,
+ EPDC_CTRL_SRAM_POWERDOWN = 0x100,
+ EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0,
+ EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0,
+ EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40,
+ EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80,
+ EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0,
+ EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30,
+ EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0,
+ EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10,
+ EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20,
+ EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30,
+ EPDC_CTRL_BURST_LEN_8_8 = 0x1,
+ EPDC_CTRL_BURST_LEN_8_16 = 0,
+
+/* EPDC_RES field values */
+ EPDC_RES_VERTICAL_MASK = 0x1FFF0000,
+ EPDC_RES_VERTICAL_OFFSET = 16,
+ EPDC_RES_HORIZONTAL_MASK = 0x1FFF,
+ EPDC_RES_HORIZONTAL_OFFSET = 0,
+
+/* EPDC_FORMAT field values */
+ EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000,
+ EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000,
+ EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16,
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK = 0x700,
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200,
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300,
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400,
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500,
+ EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0,
+ EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1,
+ EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2,
+ EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3,
+
+/* EPDC_FIFOCTRL field values */
+ EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000,
+ EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000,
+ EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16,
+ EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00,
+ EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8,
+ EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF,
+ EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0,
+
+/* EPDC_UPD_CORD field values */
+ EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000,
+ EPDC_UPD_CORD_YCORD_OFFSET = 16,
+ EPDC_UPD_CORD_XCORD_MASK = 0x1FFF,
+ EPDC_UPD_CORD_XCORD_OFFSET = 0,
+
+/* EPDC_UPD_SIZE field values */
+ EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000,
+ EPDC_UPD_SIZE_HEIGHT_OFFSET = 16,
+ EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF,
+ EPDC_UPD_SIZE_WIDTH_OFFSET = 0,
+
+/* EPDC_UPD_CTRL field values */
+ EPDC_UPD_CTRL_USE_FIXED = 0x80000000,
+ EPDC_UPD_CTRL_LUT_SEL_MASK = 0x3F0000,
+ EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16,
+ EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00,
+ EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8,
+ EPDC_UPD_CTRL_AUTOWV_PAUSE = 0x8,
+ EPDC_UPD_CTRL_AUTOWV = 0x4,
+ EPDC_UPD_CTRL_DRY_RUN = 0x2,
+ EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1,
+
+/* EPDC_UPD_FIXED field values */
+ EPDC_UPD_FIXED_FIXNP_EN = 0x80000000,
+ EPDC_UPD_FIXED_FIXCP_EN = 0x40000000,
+ EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00,
+ EPDC_UPD_FIXED_FIXNP_OFFSET = 8,
+ EPDC_UPD_FIXED_FIXCP_MASK = 0xFF,
+ EPDC_UPD_FIXED_FIXCP_OFFSET = 0,
+
+/* EPDC_AUTOWV_LUT field values */
+ EPDC_AUTOWV_LUT_DATA_MASK = 0xFF0000,
+ EPDC_AUTOWV_LUT_DATA_OFFSET = 16,
+ EPDC_AUTOWV_LUT_ADDR_MASK = 0xFF,
+ EPDC_AUTOWV_LUT_ADDR_OFFSET = 0,
+
+/* EPDC_TCE_CTRL field values */
+ EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000,
+ EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16,
+ EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00,
+ EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10,
+ EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200,
+ EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000,
+ EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100,
+ EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80,
+ EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40,
+ EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20,
+ EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10,
+ EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8,
+ EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4,
+ EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1,
+ EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2,
+ EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3,
+
+/* EPDC_TCE_SDCFG field values */
+ EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000,
+ EPDC_TCE_SDCFG_SDSHR = 0x100000,
+ EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000,
+ EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16,
+ EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0,
+ EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000,
+ EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000,
+ EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF,
+ EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0,
+
+/* EPDC_TCE_GDCFG field values */
+ EPDC_TCE_SDCFG_GDRL = 0x10,
+ EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2,
+ EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1,
+ EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0,
+
+/* EPDC_TCE_HSCAN1 field values */
+ EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000,
+ EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16,
+ EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF,
+ EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0,
+
+/* EPDC_TCE_HSCAN2 field values */
+ EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000,
+ EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16,
+ EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF,
+ EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0,
+
+/* EPDC_TCE_VSCAN field values */
+ EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000,
+ EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16,
+ EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00,
+ EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8,
+ EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF,
+ EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0,
+
+/* EPDC_TCE_OE field values */
+ EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000,
+ EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24,
+ EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000,
+ EPDC_TCE_OE_SDOED_DLY_OFFSET = 16,
+ EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00,
+ EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8,
+ EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF,
+ EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0,
+
+/* EPDC_TCE_POLARITY field values */
+ EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10,
+ EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8,
+ EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4,
+ EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2,
+ EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1,
+
+/* EPDC_TCE_TIMING1 field values */
+ EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00,
+ EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10,
+ EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20,
+ EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30,
+ EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8,
+ EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0,
+ EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1,
+ EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2,
+ EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3,
+
+/* EPDC_TCE_TIMING2 field values */
+ EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000,
+ EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16,
+ EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF,
+ EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0,
+
+/* EPDC_TCE_TIMING3 field values */
+ EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000,
+ EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16,
+ EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF,
+ EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0,
+
+/* EPDC_IRQ_MASK/EPDC_IRQ field values */
+ EPDC_IRQ_WB_CMPLT_IRQ = 0x10000,
+ EPDC_IRQ_LUT_COL_IRQ = 0x20000,
+ EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000,
+ EPDC_IRQ_FRAME_END_IRQ = 0x80000,
+ EPDC_IRQ_BUS_ERROR_IRQ = 0x100000,
+ EPDC_IRQ_TCE_IDLE_IRQ = 0x200000,
+ EPDC_IRQ_UPD_DONE_IRQ = 0x400000,
+ EPDC_IRQ_PWR_IRQ = 0x800000,
+
+/* EPDC_STATUS_NEXTLUT field values */
+ EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100,
+ EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0x3F,
+ EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0,
+
+/* EPDC_STATUS field values */
+ EPDC_STATUS_HISTOGRAM_CP_MASK = 0x1F0000,
+ EPDC_STATUS_HISTOGRAM_CP_OFFSET = 16,
+ EPDC_STATUS_HISTOGRAM_NP_MASK = 0x1F00,
+ EPDC_STATUS_HISTOGRAM_NP_OFFSET = 8,
+ EPDC_STATUS_UPD_VOID = 0x8,
+ EPDC_STATUS_LUTS_UNDERRUN = 0x4,
+ EPDC_STATUS_LUTS_BUSY = 0x2,
+ EPDC_STATUS_WB_BUSY = 0x1,
+
+/* EPDC_UPD_COL_CORD field values */
+ EPDC_UPD_COL_CORD_YCORD_MASK = 0x1FFF0000,
+ EPDC_UPD_COL_CORD_YCORD_OFFSET = 16,
+ EPDC_UPD_COL_CORD_XCORD_MASK = 0x1FFF,
+ EPDC_UPD_COL_CORD_XCORD_OFFSET = 0,
+
+/* EPDC_UPD_COL_SIZE field values */
+ EPDC_UPD_COL_SIZE_HEIGHT_MASK = 0x1FFF0000,
+ EPDC_UPD_COL_SIZE_HEIGHT_OFFSET = 16,
+ EPDC_UPD_COL_SIZE_WIDTH_MASK = 0x1FFF,
+ EPDC_UPD_COL_SIZE_WIDTH_OFFSET = 0,
+
+/* EPDC_DEBUG field values */
+ EPDC_DEBUG_UNDERRUN_RECOVER = 0x2,
+ EPDC_DEBUG_COLLISION_OFF = 0x1,
+
+/* EPDC_HISTx_PARAM field values */
+ EPDC_HIST_PARAM_VALUE0_MASK = 0x1F,
+ EPDC_HIST_PARAM_VALUE0_OFFSET = 0,
+ EPDC_HIST_PARAM_VALUE1_MASK = 0x1F00,
+ EPDC_HIST_PARAM_VALUE1_OFFSET = 8,
+ EPDC_HIST_PARAM_VALUE2_MASK = 0x1F0000,
+ EPDC_HIST_PARAM_VALUE2_OFFSET = 16,
+ EPDC_HIST_PARAM_VALUE3_MASK = 0x1F000000,
+ EPDC_HIST_PARAM_VALUE3_OFFSET = 24,
+ EPDC_HIST_PARAM_VALUE4_MASK = 0x1F,
+ EPDC_HIST_PARAM_VALUE4_OFFSET = 0,
+ EPDC_HIST_PARAM_VALUE5_MASK = 0x1F00,
+ EPDC_HIST_PARAM_VALUE5_OFFSET = 8,
+ EPDC_HIST_PARAM_VALUE6_MASK = 0x1F0000,
+ EPDC_HIST_PARAM_VALUE6_OFFSET = 16,
+ EPDC_HIST_PARAM_VALUE7_MASK = 0x1F000000,
+ EPDC_HIST_PARAM_VALUE7_OFFSET = 24,
+ EPDC_HIST_PARAM_VALUE8_MASK = 0x1F,
+ EPDC_HIST_PARAM_VALUE8_OFFSET = 0,
+ EPDC_HIST_PARAM_VALUE9_MASK = 0x1F00,
+ EPDC_HIST_PARAM_VALUE9_OFFSET = 8,
+ EPDC_HIST_PARAM_VALUE10_MASK = 0x1F0000,
+ EPDC_HIST_PARAM_VALUE10_OFFSET = 16,
+ EPDC_HIST_PARAM_VALUE11_MASK = 0x1F000000,
+ EPDC_HIST_PARAM_VALUE11_OFFSET = 24,
+ EPDC_HIST_PARAM_VALUE12_MASK = 0x1F,
+ EPDC_HIST_PARAM_VALUE12_OFFSET = 0,
+ EPDC_HIST_PARAM_VALUE13_MASK = 0x1F00,
+ EPDC_HIST_PARAM_VALUE13_OFFSET = 8,
+ EPDC_HIST_PARAM_VALUE14_MASK = 0x1F0000,
+ EPDC_HIST_PARAM_VALUE14_OFFSET = 16,
+ EPDC_HIST_PARAM_VALUE15_MASK = 0x1F000000,
+ EPDC_HIST_PARAM_VALUE15_OFFSET = 24,
+
+/* EPDC_GPIO field values */
+ EPDC_GPIO_PWRCOM = 0x40,
+ EPDC_GPIO_PWRCTRL_MASK = 0x3C,
+ EPDC_GPIO_PWRCTRL_OFFSET = 2,
+ EPDC_GPIO_BDR_MASK = 0x3,
+ EPDC_GPIO_BDR_OFFSET = 0,
+
+/* EPDC_VERSION field values */
+ EPDC_VERSION_MAJOR_MASK = 0xFF000000,
+ EPDC_VERSION_MAJOR_OFFSET = 24,
+ EPDC_VERSION_MINOR_MASK = 0xFF0000,
+ EPDC_VERSION_MINOR_OFFSET = 16,
+ EPDC_VERSION_STEP_MASK = 0xFFFF,
+ EPDC_VERSION_STEP_OFFSET = 0,
+};
+
+#endif /* __EPDC_REGS_INCLUDED__ */
diff --git a/drivers/video/mxc/hannstar_cabc.c b/drivers/video/mxc/hannstar_cabc.c
new file mode 100644
index 000000000000..c1d8442948be
--- /dev/null
+++ b/drivers/video/mxc/hannstar_cabc.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+
+#define DRIVER_NAME "hannstar-cabc"
+
+static const struct of_device_id cabc_dt_ids[] = {
+ { .compatible = "hannstar,cabc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cabc_dt_ids);
+
+static int cabc_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node, *pp;
+ int cabc_gpio, ret = 0;
+ bool cabc_enable;
+ unsigned long gpio_flag;
+ enum of_gpio_flags flags;
+
+ for_each_child_of_node(np, pp) {
+ if (!of_find_property(pp, "gpios", NULL)) {
+ dev_warn(&pdev->dev, "Found interface without "
+ "gpios\n");
+ continue;
+ }
+
+ cabc_gpio = of_get_gpio_flags(pp, 0, &flags);
+ if (!gpio_is_valid(cabc_gpio)) {
+ ret = cabc_gpio;
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "Failed to get gpio flags, "
+ "error: %d\n", ret);
+ return ret;
+ }
+
+ cabc_enable = of_property_read_bool(pp, "cabc-enable");
+
+ if (flags & OF_GPIO_ACTIVE_LOW)
+ gpio_flag = cabc_enable ?
+ GPIOF_OUT_INIT_LOW : GPIOF_OUT_INIT_HIGH;
+ else
+ gpio_flag = cabc_enable ?
+ GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW;
+
+ devm_gpio_request_one(&pdev->dev, cabc_gpio,
+ gpio_flag, "hannstar-cabc");
+ }
+
+ return ret;
+}
+
+static struct platform_driver cabc_driver = {
+ .probe = cabc_probe,
+ .driver = {
+ .of_match_table = cabc_dt_ids,
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+module_platform_driver(cabc_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Hannstar CABC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/video/mxc/ldb.c b/drivers/video/mxc/ldb.c
new file mode 100644
index 000000000000..4739df7fcca1
--- /dev/null
+++ b/drivers/video/mxc/ldb.c
@@ -0,0 +1,891 @@
+/*
+ * Copyright (C) 2012-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+#include "mxc_dispdrv.h"
+
+#define DRIVER_NAME "ldb"
+
+#define LDB_BGREF_RMODE_INT (0x1 << 15)
+
+#define LDB_DI1_VS_POL_ACT_LOW (0x1 << 10)
+#define LDB_DI0_VS_POL_ACT_LOW (0x1 << 9)
+
+#define LDB_BIT_MAP_CH1_JEIDA (0x1 << 8)
+#define LDB_BIT_MAP_CH0_JEIDA (0x1 << 6)
+
+#define LDB_DATA_WIDTH_CH1_24 (0x1 << 7)
+#define LDB_DATA_WIDTH_CH0_24 (0x1 << 5)
+
+#define LDB_CH1_MODE_MASK (0x3 << 2)
+#define LDB_CH1_MODE_EN_TO_DI1 (0x3 << 2)
+#define LDB_CH1_MODE_EN_TO_DI0 (0x1 << 2)
+#define LDB_CH0_MODE_MASK (0x3 << 0)
+#define LDB_CH0_MODE_EN_TO_DI1 (0x3 << 0)
+#define LDB_CH0_MODE_EN_TO_DI0 (0x1 << 0)
+
+#define LDB_SPLIT_MODE_EN (0x1 << 4)
+
+#define INVALID_BUS_REG (~0UL)
+
+struct crtc_mux {
+ enum crtc crtc;
+ u32 val;
+};
+
+struct bus_mux {
+ int reg;
+ int shift;
+ int mask;
+ int crtc_mux_num;
+ const struct crtc_mux *crtcs;
+};
+
+struct ldb_info {
+ bool split_cap;
+ bool dual_cap;
+ bool ext_bgref_cap;
+ int ctrl_reg;
+ int bus_mux_num;
+ const struct bus_mux *buses;
+};
+
+struct ldb_data;
+
+struct ldb_chan {
+ struct ldb_data *ldb;
+ struct fb_info *fbi;
+ struct videomode vm;
+ enum crtc crtc;
+ int chno;
+ bool is_used;
+ bool online;
+};
+
+struct ldb_data {
+ struct regmap *regmap;
+ struct device *dev;
+ struct mxc_dispdrv_handle *mddh;
+ struct ldb_chan chan[2];
+ int bus_mux_num;
+ const struct bus_mux *buses;
+ int primary_chno;
+ int ctrl_reg;
+ u32 ctrl;
+ bool spl_mode;
+ bool dual_mode;
+ struct clk *di_clk[4];
+ struct clk *ldb_di_clk[2];
+ struct clk *div_3_5_clk[2];
+ struct clk *div_7_clk[2];
+ struct clk *div_sel_clk[2];
+};
+
+static const struct crtc_mux imx6q_lvds0_crtc_mux[] = {
+ {
+ .crtc = CRTC_IPU1_DI0,
+ .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI0,
+ }, {
+ .crtc = CRTC_IPU1_DI1,
+ .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU1_DI1,
+ }, {
+ .crtc = CRTC_IPU2_DI0,
+ .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI0,
+ }, {
+ .crtc = CRTC_IPU2_DI1,
+ .val = IMX6Q_GPR3_LVDS0_MUX_CTL_IPU2_DI1,
+ }
+};
+
+static const struct crtc_mux imx6q_lvds1_crtc_mux[] = {
+ {
+ .crtc = CRTC_IPU1_DI0,
+ .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI0,
+ }, {
+ .crtc = CRTC_IPU1_DI1,
+ .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU1_DI1,
+ }, {
+ .crtc = CRTC_IPU2_DI0,
+ .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI0,
+ }, {
+ .crtc = CRTC_IPU2_DI1,
+ .val = IMX6Q_GPR3_LVDS1_MUX_CTL_IPU2_DI1,
+ }
+};
+
+static const struct bus_mux imx6q_ldb_buses[] = {
+ {
+ .reg = IOMUXC_GPR3,
+ .shift = 6,
+ .mask = IMX6Q_GPR3_LVDS0_MUX_CTL_MASK,
+ .crtc_mux_num = ARRAY_SIZE(imx6q_lvds0_crtc_mux),
+ .crtcs = imx6q_lvds0_crtc_mux,
+ }, {
+ .reg = IOMUXC_GPR3,
+ .shift = 8,
+ .mask = IMX6Q_GPR3_LVDS1_MUX_CTL_MASK,
+ .crtc_mux_num = ARRAY_SIZE(imx6q_lvds1_crtc_mux),
+ .crtcs = imx6q_lvds1_crtc_mux,
+ }
+};
+
+static const struct ldb_info imx6q_ldb_info = {
+ .split_cap = true,
+ .dual_cap = true,
+ .ext_bgref_cap = false,
+ .ctrl_reg = IOMUXC_GPR2,
+ .bus_mux_num = ARRAY_SIZE(imx6q_ldb_buses),
+ .buses = imx6q_ldb_buses,
+};
+
+static const struct crtc_mux imx6dl_lvds0_crtc_mux[] = {
+ {
+ .crtc = CRTC_IPU1_DI0,
+ .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI0,
+ }, {
+ .crtc = CRTC_IPU1_DI1,
+ .val = IMX6DL_GPR3_LVDS0_MUX_CTL_IPU1_DI1,
+ }, {
+ .crtc = CRTC_LCDIF1,
+ .val = IMX6DL_GPR3_LVDS0_MUX_CTL_LCDIF,
+ }
+};
+
+static const struct crtc_mux imx6dl_lvds1_crtc_mux[] = {
+ {
+ .crtc = CRTC_IPU1_DI0,
+ .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI0,
+ }, {
+ .crtc = CRTC_IPU1_DI1,
+ .val = IMX6DL_GPR3_LVDS1_MUX_CTL_IPU1_DI1,
+ }, {
+ .crtc = CRTC_LCDIF1,
+ .val = IMX6DL_GPR3_LVDS1_MUX_CTL_LCDIF,
+ }
+};
+
+static const struct bus_mux imx6dl_ldb_buses[] = {
+ {
+ .reg = IOMUXC_GPR3,
+ .shift = 6,
+ .mask = IMX6DL_GPR3_LVDS0_MUX_CTL_MASK,
+ .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds0_crtc_mux),
+ .crtcs = imx6dl_lvds0_crtc_mux,
+ }, {
+ .reg = IOMUXC_GPR3,
+ .shift = 8,
+ .mask = IMX6DL_GPR3_LVDS1_MUX_CTL_MASK,
+ .crtc_mux_num = ARRAY_SIZE(imx6dl_lvds1_crtc_mux),
+ .crtcs = imx6dl_lvds1_crtc_mux,
+ }
+};
+
+static const struct ldb_info imx6dl_ldb_info = {
+ .split_cap = true,
+ .dual_cap = true,
+ .ext_bgref_cap = false,
+ .ctrl_reg = IOMUXC_GPR2,
+ .bus_mux_num = ARRAY_SIZE(imx6dl_ldb_buses),
+ .buses = imx6dl_ldb_buses,
+};
+
+static const struct crtc_mux imx6sx_lvds_crtc_mux[] = {
+ {
+ .crtc = CRTC_LCDIF1,
+ .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF1,
+ }, {
+ .crtc = CRTC_LCDIF2,
+ .val = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_LCDIF2,
+ }
+};
+
+static const struct bus_mux imx6sx_ldb_buses[] = {
+ {
+ .reg = IOMUXC_GPR5,
+ .shift = 3,
+ .mask = IMX6SX_GPR5_DISP_MUX_LDB_CTRL_MASK,
+ .crtc_mux_num = ARRAY_SIZE(imx6sx_lvds_crtc_mux),
+ .crtcs = imx6sx_lvds_crtc_mux,
+ }
+};
+
+static const struct ldb_info imx6sx_ldb_info = {
+ .split_cap = false,
+ .dual_cap = false,
+ .ext_bgref_cap = false,
+ .ctrl_reg = IOMUXC_GPR6,
+ .bus_mux_num = ARRAY_SIZE(imx6sx_ldb_buses),
+ .buses = imx6sx_ldb_buses,
+};
+
+static const struct crtc_mux imx53_lvds0_crtc_mux[] = {
+ { .crtc = CRTC_IPU1_DI0, },
+};
+
+static const struct crtc_mux imx53_lvds1_crtc_mux[] = {
+ { .crtc = CRTC_IPU1_DI1, }
+};
+
+static const struct bus_mux imx53_ldb_buses[] = {
+ {
+ .reg = INVALID_BUS_REG,
+ .crtc_mux_num = ARRAY_SIZE(imx53_lvds0_crtc_mux),
+ .crtcs = imx53_lvds0_crtc_mux,
+ }, {
+ .reg = INVALID_BUS_REG,
+ .crtc_mux_num = ARRAY_SIZE(imx53_lvds1_crtc_mux),
+ .crtcs = imx53_lvds1_crtc_mux,
+ }
+};
+
+static const struct ldb_info imx53_ldb_info = {
+ .split_cap = true,
+ .dual_cap = false,
+ .ext_bgref_cap = true,
+ .ctrl_reg = IOMUXC_GPR2,
+ .bus_mux_num = ARRAY_SIZE(imx53_ldb_buses),
+ .buses = imx53_ldb_buses,
+};
+
+static const struct of_device_id ldb_dt_ids[] = {
+ { .compatible = "fsl,imx6q-ldb", .data = &imx6q_ldb_info, },
+ { .compatible = "fsl,imx6dl-ldb", .data = &imx6dl_ldb_info, },
+ { .compatible = "fsl,imx6sx-ldb", .data = &imx6sx_ldb_info, },
+ { .compatible = "fsl,imx53-ldb", .data = &imx53_ldb_info, },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ldb_dt_ids);
+
+static int ldb_init(struct mxc_dispdrv_handle *mddh,
+ struct mxc_dispdrv_setting *setting)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ struct device *dev = ldb->dev;
+ struct fb_info *fbi = setting->fbi;
+ struct ldb_chan *chan;
+ struct fb_videomode fb_vm;
+ int chno;
+
+ chno = ldb->chan[ldb->primary_chno].is_used ?
+ !ldb->primary_chno : ldb->primary_chno;
+
+ chan = &ldb->chan[chno];
+
+ if (chan->is_used) {
+ dev_err(dev, "LVDS channel%d is already used\n", chno);
+ return -EBUSY;
+ }
+ if (!chan->online) {
+ dev_err(dev, "LVDS channel%d is not online\n", chno);
+ return -ENODEV;
+ }
+
+ chan->is_used = true;
+
+ chan->fbi = fbi;
+
+ fb_videomode_from_videomode(&chan->vm, &fb_vm);
+
+ INIT_LIST_HEAD(&fbi->modelist);
+ fb_add_videomode(&fb_vm, &fbi->modelist);
+ fb_videomode_to_var(&fbi->var, &fb_vm);
+
+ setting->crtc = chan->crtc;
+
+ return 0;
+}
+
+static int get_di_clk_id(struct ldb_chan chan, int *id)
+{
+ struct ldb_data *ldb = chan.ldb;
+ int i = 0, chno = chan.chno, mask, shift;
+ enum crtc crtc;
+ u32 val;
+
+ /* no pre-muxing, such as mx53 */
+ if (ldb->buses[chno].reg == INVALID_BUS_REG) {
+ *id = chno;
+ return 0;
+ }
+
+ for (; i < ldb->buses[chno].crtc_mux_num; i++) {
+ crtc = ldb->buses[chno].crtcs[i].crtc;
+ val = ldb->buses[chno].crtcs[i].val;
+ mask = ldb->buses[chno].mask;
+ shift = ldb->buses[chno].shift;
+ if (chan.crtc == crtc) {
+ *id = (val & mask) >> shift;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int get_mux_val(struct bus_mux bus_mux, enum crtc crtc,
+ u32 *mux_val)
+{
+ int i = 0;
+
+ for (; i < bus_mux.crtc_mux_num; i++)
+ if (bus_mux.crtcs[i].crtc == crtc) {
+ *mux_val = bus_mux.crtcs[i].val;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int find_ldb_chno(struct ldb_data *ldb,
+ struct fb_info *fbi, int *chno)
+{
+ struct device *dev = ldb->dev;
+ int i = 0;
+
+ for (; i < 2; i++)
+ if (ldb->chan[i].fbi == fbi) {
+ *chno = ldb->chan[i].chno;
+ return 0;
+ }
+ dev_err(dev, "failed to find channel number\n");
+ return -EINVAL;
+}
+
+static void ldb_disable(struct mxc_dispdrv_handle *mddh,
+ struct fb_info *fbi);
+
+static int ldb_setup(struct mxc_dispdrv_handle *mddh,
+ struct fb_info *fbi)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ struct ldb_chan chan;
+ struct device *dev = ldb->dev;
+ struct clk *ldb_di_parent, *ldb_di_sel, *ldb_di_sel_parent;
+ struct clk *other_ldb_di_sel = NULL;
+ struct bus_mux bus_mux;
+ int ret = 0, id = 0, chno, other_chno;
+ unsigned long serial_clk;
+ u32 mux_val;
+
+ ret = find_ldb_chno(ldb, fbi, &chno);
+ if (ret < 0)
+ return ret;
+
+ other_chno = chno ? 0 : 1;
+
+ chan = ldb->chan[chno];
+
+ bus_mux = ldb->buses[chno];
+
+ ret = get_di_clk_id(chan, &id);
+ if (ret < 0) {
+ dev_err(dev, "failed to get ch%d di clk id\n",
+ chan.chno);
+ return ret;
+ }
+
+ ret = get_mux_val(bus_mux, chan.crtc, &mux_val);
+ if (ret < 0) {
+ dev_err(dev, "failed to get ch%d mux val\n",
+ chan.chno);
+ return ret;
+ }
+
+ /*
+ * ldb_di_sel_parent(plls) -> ldb_di_sel ->
+ *
+ * -> div_3_5[chno] ->
+ * -> | |-> div_sel[chno] ->
+ * -> div_7[chno] ->
+ *
+ * -> ldb_di[chno] -> di[id]
+ */
+ clk_set_parent(ldb->di_clk[id], ldb->ldb_di_clk[chno]);
+ ldb_di_parent = ldb->spl_mode ? ldb->div_3_5_clk[chno] :
+ ldb->div_7_clk[chno];
+ clk_set_parent(ldb->div_sel_clk[chno], ldb_di_parent);
+ ldb_di_sel = clk_get_parent(ldb_di_parent);
+ ldb_di_sel_parent = clk_get_parent(ldb_di_sel);
+ serial_clk = ldb->spl_mode ? chan.vm.pixelclock * 7 / 2 :
+ chan.vm.pixelclock * 7;
+ clk_set_rate(ldb_di_sel_parent, serial_clk);
+
+ /*
+ * split mode or dual mode:
+ * clock tree for the other channel
+ */
+ if (ldb->spl_mode) {
+ clk_set_parent(ldb->div_sel_clk[other_chno],
+ ldb->div_3_5_clk[other_chno]);
+ other_ldb_di_sel =
+ clk_get_parent(ldb->div_3_5_clk[other_chno]);;
+ }
+
+ if (ldb->dual_mode) {
+ clk_set_parent(ldb->div_sel_clk[other_chno],
+ ldb->div_7_clk[other_chno]);
+ other_ldb_di_sel =
+ clk_get_parent(ldb->div_7_clk[other_chno]);;
+ }
+
+ if (ldb->spl_mode || ldb->dual_mode)
+ clk_set_parent(other_ldb_di_sel, ldb_di_sel_parent);
+
+ if (!(chan.fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)) {
+ if (ldb->spl_mode && bus_mux.reg == INVALID_BUS_REG)
+ /* no pre-muxing, such as mx53 */
+ ldb->ctrl |= (id == 0 ? LDB_DI0_VS_POL_ACT_LOW :
+ LDB_DI1_VS_POL_ACT_LOW);
+ else
+ ldb->ctrl |= (chno == 0 ? LDB_DI0_VS_POL_ACT_LOW :
+ LDB_DI1_VS_POL_ACT_LOW);
+ }
+
+ if (bus_mux.reg != INVALID_BUS_REG)
+ regmap_update_bits(ldb->regmap, bus_mux.reg,
+ bus_mux.mask, mux_val);
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
+
+ /* disable channel for correct sequence */
+ ldb_disable(mddh, fbi);
+
+ return ret;
+}
+
+static int ldb_enable(struct mxc_dispdrv_handle *mddh,
+ struct fb_info *fbi)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ struct ldb_chan chan;
+ struct device *dev = ldb->dev;
+ struct bus_mux bus_mux;
+ int ret = 0, id = 0, chno, other_chno;
+
+ ret = find_ldb_chno(ldb, fbi, &chno);
+ if (ret < 0)
+ return ret;
+
+ chan = ldb->chan[chno];
+
+ bus_mux = ldb->buses[chno];
+
+ if (ldb->spl_mode || ldb->dual_mode) {
+ other_chno = chno ? 0 : 1;
+ clk_prepare_enable(ldb->ldb_di_clk[other_chno]);
+ }
+
+ if ((ldb->spl_mode || ldb->dual_mode) &&
+ bus_mux.reg == INVALID_BUS_REG) {
+ /* no pre-muxing, such as mx53 */
+ ret = get_di_clk_id(chan, &id);
+ if (ret < 0) {
+ dev_err(dev, "failed to get ch%d di clk id\n",
+ chan.chno);
+ return ret;
+ }
+
+ ldb->ctrl |= id ?
+ (LDB_CH0_MODE_EN_TO_DI1 | LDB_CH1_MODE_EN_TO_DI1) :
+ (LDB_CH0_MODE_EN_TO_DI0 | LDB_CH1_MODE_EN_TO_DI0);
+ } else {
+ if (ldb->spl_mode || ldb->dual_mode)
+ ldb->ctrl |= LDB_CH0_MODE_EN_TO_DI0 |
+ LDB_CH1_MODE_EN_TO_DI0;
+ else
+ ldb->ctrl |= chno ? LDB_CH1_MODE_EN_TO_DI1 :
+ LDB_CH0_MODE_EN_TO_DI0;
+ }
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
+ return 0;
+}
+
+static void ldb_disable(struct mxc_dispdrv_handle *mddh,
+ struct fb_info *fbi)
+{
+ struct ldb_data *ldb = mxc_dispdrv_getdata(mddh);
+ int ret, chno, other_chno;
+
+ ret = find_ldb_chno(ldb, fbi, &chno);
+ if (ret < 0)
+ return;
+
+ if (ldb->spl_mode || ldb->dual_mode) {
+ ldb->ctrl &= ~(LDB_CH1_MODE_MASK | LDB_CH0_MODE_MASK);
+ other_chno = chno ? 0 : 1;
+ clk_disable_unprepare(ldb->ldb_di_clk[other_chno]);
+ } else {
+ ldb->ctrl &= ~(chno ? LDB_CH1_MODE_MASK :
+ LDB_CH0_MODE_MASK);
+ }
+
+ regmap_write(ldb->regmap, ldb->ctrl_reg, ldb->ctrl);
+ return;
+}
+
+static struct mxc_dispdrv_driver ldb_drv = {
+ .name = DRIVER_NAME,
+ .init = ldb_init,
+ .setup = ldb_setup,
+ .enable = ldb_enable,
+ .disable = ldb_disable
+};
+
+enum {
+ LVDS_BIT_MAP_SPWG,
+ LVDS_BIT_MAP_JEIDA,
+};
+
+static const char *ldb_bit_mappings[] = {
+ [LVDS_BIT_MAP_SPWG] = "spwg",
+ [LVDS_BIT_MAP_JEIDA] = "jeida",
+};
+
+static int of_get_data_mapping(struct device_node *np)
+{
+ const char *bm;
+ int ret, i;
+
+ ret = of_property_read_string(np, "fsl,data-mapping", &bm);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(ldb_bit_mappings); i++)
+ if (!strcasecmp(bm, ldb_bit_mappings[i]))
+ return i;
+
+ return -EINVAL;
+}
+
+static const char *ldb_crtc_mappings[] = {
+ [CRTC_IPU_DI0] = "ipu-di0",
+ [CRTC_IPU_DI1] = "ipu-di1",
+ [CRTC_IPU1_DI0] = "ipu1-di0",
+ [CRTC_IPU1_DI1] = "ipu1-di1",
+ [CRTC_IPU2_DI0] = "ipu2-di0",
+ [CRTC_IPU2_DI1] = "ipu2-di1",
+ [CRTC_LCDIF] = "lcdif",
+ [CRTC_LCDIF1] = "lcdif1",
+ [CRTC_LCDIF2] = "lcdif2",
+};
+
+static enum crtc of_get_crtc_mapping(struct device_node *np)
+{
+ const char *cm;
+ enum crtc i;
+ int ret;
+
+ ret = of_property_read_string(np, "crtc", &cm);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(ldb_crtc_mappings); i++)
+ if (!strcasecmp(cm, ldb_crtc_mappings[i])) {
+ switch (i) {
+ case CRTC_IPU_DI0:
+ i = CRTC_IPU1_DI0;
+ break;
+ case CRTC_IPU_DI1:
+ i = CRTC_IPU1_DI1;
+ break;
+ case CRTC_LCDIF:
+ i = CRTC_LCDIF1;
+ break;
+ default:
+ break;
+ }
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int mux_count(struct ldb_data *ldb)
+{
+ int i, j, count = 0;
+ bool should_count[CRTC_MAX];
+ enum crtc crtc;
+
+ for (i = 0; i < CRTC_MAX; i++)
+ should_count[i] = true;
+
+ for (i = 0; i < ldb->bus_mux_num; i++) {
+ for (j = 0; j < ldb->buses[i].crtc_mux_num; j++) {
+ crtc = ldb->buses[i].crtcs[j].crtc;
+ if (should_count[crtc]) {
+ count++;
+ should_count[crtc] = false;
+ }
+ }
+ }
+
+ return count;
+}
+
+static bool is_valid_crtc(struct ldb_data *ldb, enum crtc crtc,
+ int chno)
+{
+ int i = 0;
+
+ if (chno > ldb->bus_mux_num - 1)
+ return false;
+
+ for (; i < ldb->buses[chno].crtc_mux_num; i++)
+ if (ldb->buses[chno].crtcs[i].crtc == crtc)
+ return true;
+
+ return false;
+}
+
+static int ldb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id =
+ of_match_device(ldb_dt_ids, dev);
+ const struct ldb_info *ldb_info =
+ (const struct ldb_info *)of_id->data;
+ struct device_node *np = dev->of_node, *child;
+ struct ldb_data *ldb;
+ bool ext_ref;
+ int i, data_width, mapping, child_count = 0;
+ char clkname[16];
+
+ ldb = devm_kzalloc(dev, sizeof(*ldb), GFP_KERNEL);
+ if (!ldb)
+ return -ENOMEM;
+
+ ldb->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+ if (IS_ERR(ldb->regmap)) {
+ dev_err(dev, "failed to get parent regmap\n");
+ return PTR_ERR(ldb->regmap);
+ }
+
+ ldb->dev = dev;
+ ldb->bus_mux_num = ldb_info->bus_mux_num;
+ ldb->buses = ldb_info->buses;
+ ldb->ctrl_reg = ldb_info->ctrl_reg;
+ ldb->primary_chno = -1;
+
+ ext_ref = of_property_read_bool(np, "ext-ref");
+ if (!ext_ref && ldb_info->ext_bgref_cap)
+ ldb->ctrl |= LDB_BGREF_RMODE_INT;
+
+ ldb->spl_mode = of_property_read_bool(np, "split-mode");
+ if (ldb->spl_mode) {
+ if (ldb_info->split_cap) {
+ ldb->ctrl |= LDB_SPLIT_MODE_EN;
+ dev_info(dev, "split mode\n");
+ } else {
+ dev_err(dev, "cannot support split mode\n");
+ return -EINVAL;
+ }
+ }
+
+ ldb->dual_mode = of_property_read_bool(np, "dual-mode");
+ if (ldb->dual_mode) {
+ if (ldb_info->dual_cap) {
+ dev_info(dev, "dual mode\n");
+ } else {
+ dev_err(dev, "cannot support dual mode\n");
+ return -EINVAL;
+ }
+ }
+
+ if (ldb->dual_mode && ldb->spl_mode) {
+ dev_err(dev, "cannot support dual mode and split mode "
+ "simultaneously\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < mux_count(ldb); i++) {
+ sprintf(clkname, "di%d_sel", i);
+ ldb->di_clk[i] = devm_clk_get(dev, clkname);
+ if (IS_ERR(ldb->di_clk[i])) {
+ dev_err(dev, "failed to get clk %s\n", clkname);
+ return PTR_ERR(ldb->di_clk[i]);
+ }
+ }
+
+ for_each_child_of_node(np, child) {
+ struct ldb_chan *chan;
+ enum crtc crtc;
+ bool is_primary;
+ int ret;
+
+ ret = of_property_read_u32(child, "reg", &i);
+ if (ret || i < 0 || i > 1 || i >= ldb->bus_mux_num) {
+ dev_err(dev, "wrong LVDS channel number\n");
+ return -EINVAL;
+ }
+
+ if ((ldb->spl_mode || ldb->dual_mode) && i > 0) {
+ dev_warn(dev, "split mode or dual mode, ignoring "
+ "second output\n");
+ continue;
+ }
+
+ if (!of_device_is_available(child))
+ continue;
+
+ if (++child_count > ldb->bus_mux_num) {
+ dev_err(dev, "too many LVDS channels\n");
+ return -EINVAL;
+ }
+
+ chan = &ldb->chan[i];
+ chan->chno = i;
+ chan->ldb = ldb;
+ chan->online = true;
+
+ is_primary = of_property_read_bool(child, "primary");
+
+ if (ldb->bus_mux_num == 1 || (ldb->primary_chno == -1 &&
+ (is_primary || ldb->spl_mode || ldb->dual_mode)))
+ ldb->primary_chno = chan->chno;
+
+ ret = of_property_read_u32(child, "fsl,data-width",
+ &data_width);
+ if (ret || (data_width != 18 && data_width != 24)) {
+ dev_err(dev, "data width not specified or invalid\n");
+ return -EINVAL;
+ }
+
+ mapping = of_get_data_mapping(child);
+ switch (mapping) {
+ case LVDS_BIT_MAP_SPWG:
+ if (data_width == 24) {
+ if (i == 0 || ldb->spl_mode || ldb->dual_mode)
+ ldb->ctrl |= LDB_DATA_WIDTH_CH0_24;
+ if (i == 1 || ldb->spl_mode || ldb->dual_mode)
+ ldb->ctrl |= LDB_DATA_WIDTH_CH1_24;
+ }
+ break;
+ case LVDS_BIT_MAP_JEIDA:
+ if (data_width == 18) {
+ dev_err(dev, "JEIDA only support 24bit\n");
+ return -EINVAL;
+ }
+ if (i == 0 || ldb->spl_mode || ldb->dual_mode)
+ ldb->ctrl |= LDB_DATA_WIDTH_CH0_24 |
+ LDB_BIT_MAP_CH0_JEIDA;
+ if (i == 1 || ldb->spl_mode || ldb->dual_mode)
+ ldb->ctrl |= LDB_DATA_WIDTH_CH1_24 |
+ LDB_BIT_MAP_CH1_JEIDA;
+ break;
+ default:
+ dev_err(dev, "data mapping not specified or invalid\n");
+ return -EINVAL;
+ }
+
+ crtc = of_get_crtc_mapping(child);
+ if (is_valid_crtc(ldb, crtc, chan->chno)) {
+ ldb->chan[i].crtc = crtc;
+ } else {
+ dev_err(dev, "crtc not specified or invalid\n");
+ return -EINVAL;
+ }
+
+ ret = of_get_videomode(child, &chan->vm, 0);
+ if (ret)
+ return -EINVAL;
+
+ sprintf(clkname, "ldb_di%d", i);
+ ldb->ldb_di_clk[i] = devm_clk_get(dev, clkname);
+ if (IS_ERR(ldb->ldb_di_clk[i])) {
+ dev_err(dev, "failed to get clk %s\n", clkname);
+ return PTR_ERR(ldb->ldb_di_clk[i]);
+ }
+
+ sprintf(clkname, "ldb_di%d_div_3_5", i);
+ ldb->div_3_5_clk[i] = devm_clk_get(dev, clkname);
+ if (IS_ERR(ldb->div_3_5_clk[i])) {
+ dev_err(dev, "failed to get clk %s\n", clkname);
+ return PTR_ERR(ldb->div_3_5_clk[i]);
+ }
+
+ sprintf(clkname, "ldb_di%d_div_7", i);
+ ldb->div_7_clk[i] = devm_clk_get(dev, clkname);
+ if (IS_ERR(ldb->div_7_clk[i])) {
+ dev_err(dev, "failed to get clk %s\n", clkname);
+ return PTR_ERR(ldb->div_7_clk[i]);
+ }
+
+ sprintf(clkname, "ldb_di%d_div_sel", i);
+ ldb->div_sel_clk[i] = devm_clk_get(dev, clkname);
+ if (IS_ERR(ldb->div_sel_clk[i])) {
+ dev_err(dev, "failed to get clk %s\n", clkname);
+ return PTR_ERR(ldb->div_sel_clk[i]);
+ }
+ }
+
+ if (child_count == 0) {
+ dev_err(dev, "failed to find valid LVDS channel\n");
+ return -EINVAL;
+ }
+
+ if (ldb->primary_chno == -1) {
+ dev_err(dev, "failed to know primary channel\n");
+ return -EINVAL;
+ }
+
+ ldb->mddh = mxc_dispdrv_register(&ldb_drv);
+ mxc_dispdrv_setdata(ldb->mddh, ldb);
+ dev_set_drvdata(&pdev->dev, ldb);
+
+ return 0;
+}
+
+static int ldb_remove(struct platform_device *pdev)
+{
+ struct ldb_data *ldb = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_puthandle(ldb->mddh);
+ mxc_dispdrv_unregister(ldb->mddh);
+ return 0;
+}
+
+static struct platform_driver ldb_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = ldb_dt_ids,
+ },
+ .probe = ldb_probe,
+ .remove = ldb_remove,
+};
+
+module_platform_driver(ldb_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("LDB driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
diff --git a/drivers/video/mxc/mipi_dsi.c b/drivers/video/mxc/mipi_dsi.c
new file mode 100644
index 000000000000..ca1b53f61b59
--- /dev/null
+++ b/drivers/video/mxc/mipi_dsi.c
@@ -0,0 +1,963 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/ipu.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
+#include <linux/mipi_dsi.h>
+#include <linux/module.h>
+#include <linux/mxcfb.h>
+#include <linux/backlight.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <video/mipi_display.h>
+
+#include "mipi_dsi.h"
+
+#define DISPDRV_MIPI "mipi_dsi"
+#define ROUND_UP(x) ((x)+1)
+#define NS2PS_RATIO (1000)
+#define NUMBER_OF_CHUNKS (0x8)
+#define NULL_PKT_SIZE (0x8)
+#define PHY_BTA_MAXTIME (0xd00)
+#define PHY_LP2HS_MAXTIME (0x40)
+#define PHY_HS2LP_MAXTIME (0x40)
+#define PHY_STOP_WAIT_TIME (0x20)
+#define DSI_CLKMGR_CFG_CLK_DIV (0x107)
+#define DSI_GEN_PLD_DATA_BUF_ENTRY (0x10)
+#define MIPI_MUX_CTRL(v) (((v) & 0x3) << 4)
+#define MIPI_LCD_SLEEP_MODE_DELAY (120)
+#define MIPI_DSI_REG_RW_TIMEOUT (20)
+#define MIPI_DSI_PHY_TIMEOUT (10)
+
+static struct mipi_dsi_match_lcd mipi_dsi_lcd_db[] = {
+#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL
+ {
+ "TRULY-WVGA",
+ {mipid_hx8369_get_lcd_videomode, mipid_hx8369_lcd_setup}
+ },
+#endif
+ {
+ "", {NULL, NULL}
+ }
+};
+
+struct _mipi_dsi_phy_pll_clk {
+ u32 max_phy_clk;
+ u32 config;
+};
+
+/* configure data for DPHY PLL 27M reference clk out */
+static const struct _mipi_dsi_phy_pll_clk mipi_dsi_phy_pll_clk_table[] = {
+ {1000, 0x74}, /* 950-1000MHz */
+ {950, 0x54}, /* 900-950Mhz */
+ {900, 0x34}, /* 850-900Mhz */
+ {850, 0x14}, /* 800-850MHz */
+ {800, 0x32}, /* 750-800MHz */
+ {750, 0x12}, /* 700-750Mhz */
+ {700, 0x30}, /* 650-700Mhz */
+ {650, 0x10}, /* 600-650MHz */
+ {600, 0x2e}, /* 550-600MHz */
+ {550, 0x0e}, /* 500-550Mhz */
+ {500, 0x2c}, /* 450-500Mhz */
+ {450, 0x0c}, /* 400-450MHz */
+ {400, 0x4a}, /* 360-400MHz */
+ {360, 0x2a}, /* 330-360Mhz */
+ {330, 0x48}, /* 300-330Mhz */
+ {300, 0x28}, /* 270-300MHz */
+ {270, 0x08}, /* 250-270MHz */
+ {250, 0x46}, /* 240-250Mhz */
+ {240, 0x26}, /* 210-240Mhz */
+ {210, 0x06}, /* 200-210MHz */
+ {200, 0x44}, /* 180-200MHz */
+ {180, 0x24}, /* 160-180MHz */
+ {160, 0x04}, /* 150-160MHz */
+};
+
+static int valid_mode(int pixel_fmt)
+{
+ return ((pixel_fmt == IPU_PIX_FMT_RGB24) ||
+ (pixel_fmt == IPU_PIX_FMT_BGR24) ||
+ (pixel_fmt == IPU_PIX_FMT_RGB666) ||
+ (pixel_fmt == IPU_PIX_FMT_RGB565) ||
+ (pixel_fmt == IPU_PIX_FMT_BGR666) ||
+ (pixel_fmt == IPU_PIX_FMT_RGB332));
+}
+
+static inline void mipi_dsi_read_register(struct mipi_dsi_info *mipi_dsi,
+ u32 reg, u32 *val)
+{
+ *val = ioread32(mipi_dsi->mmio_base + reg);
+ dev_dbg(&mipi_dsi->pdev->dev, "read_reg:0x%02x, val:0x%08x.\n",
+ reg, *val);
+}
+
+static inline void mipi_dsi_write_register(struct mipi_dsi_info *mipi_dsi,
+ u32 reg, u32 val)
+{
+ iowrite32(val, mipi_dsi->mmio_base + reg);
+ dev_dbg(&mipi_dsi->pdev->dev, "\t\twrite_reg:0x%02x, val:0x%08x.\n",
+ reg, val);
+}
+
+int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi_dsi,
+ u8 data_type, const u32 *buf, int len)
+{
+ u32 val;
+ u32 status = 0;
+ int write_len = len;
+ uint32_t timeout = 0;
+
+ if (len) {
+ /* generic long write command */
+ while (len / DSI_GEN_PLD_DATA_BUF_SIZE) {
+ mipi_dsi_write_register(mipi_dsi,
+ MIPI_DSI_GEN_PLD_DATA, *buf);
+ buf++;
+ len -= DSI_GEN_PLD_DATA_BUF_SIZE;
+ mipi_dsi_read_register(mipi_dsi,
+ MIPI_DSI_CMD_PKT_STATUS, &status);
+ while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) ==
+ DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi,
+ MIPI_DSI_CMD_PKT_STATUS, &status);
+ }
+ }
+ /* write the remainder bytes */
+ if (len > 0) {
+ while ((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) ==
+ DSI_CMD_PKT_STATUS_GEN_PLD_W_FULL) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi,
+ MIPI_DSI_CMD_PKT_STATUS, &status);
+ }
+ mipi_dsi_write_register(mipi_dsi,
+ MIPI_DSI_GEN_PLD_DATA, *buf);
+ }
+
+ val = data_type | ((write_len & DSI_GEN_HDR_DATA_MASK)
+ << DSI_GEN_HDR_DATA_SHIFT);
+ } else {
+ /* generic short write command */
+ val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK)
+ << DSI_GEN_HDR_DATA_SHIFT);
+ }
+
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status);
+ while ((status & DSI_CMD_PKT_STATUS_GEN_CMD_FULL) ==
+ DSI_CMD_PKT_STATUS_GEN_CMD_FULL) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS,
+ &status);
+ }
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val);
+
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &status);
+ while (!((status & DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) ==
+ DSI_CMD_PKT_STATUS_GEN_CMD_EMPTY) ||
+ !((status & DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY) ==
+ DSI_CMD_PKT_STATUS_GEN_PLD_W_EMPTY)) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS,
+ &status);
+ }
+
+ return 0;
+}
+
+int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi_dsi,
+ u8 data_type, u32 *buf, int len)
+{
+ u32 val;
+ int read_len = 0;
+ uint32_t timeout = 0;
+
+ if (!len) {
+ mipi_dbg("%s, len = 0 invalid error!\n", __func__);
+ return -EINVAL;
+ }
+
+ val = data_type | ((*buf & DSI_GEN_HDR_DATA_MASK)
+ << DSI_GEN_HDR_DATA_SHIFT);
+ memset(buf, 0, len);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_GEN_HDR, val);
+
+ /* wait for cmd to sent out */
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val);
+ while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) !=
+ DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS,
+ &val);
+ }
+ /* wait for entire response stroed in FIFO */
+ while ((val & DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) ==
+ DSI_CMD_PKT_STATUS_GEN_RD_CMD_BUSY) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_REG_RW_TIMEOUT)
+ return -EIO;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS,
+ &val);
+ }
+
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS, &val);
+ while (!(val & DSI_CMD_PKT_STATUS_GEN_PLD_R_EMPTY)) {
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_GEN_PLD_DATA, buf);
+ read_len += DSI_GEN_PLD_DATA_BUF_SIZE;
+ buf++;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_PKT_STATUS,
+ &val);
+ if (read_len == (DSI_GEN_PLD_DATA_BUF_ENTRY *
+ DSI_GEN_PLD_DATA_BUF_SIZE))
+ break;
+ }
+
+ if ((len <= read_len) &&
+ ((len + DSI_GEN_PLD_DATA_BUF_SIZE) >= read_len))
+ return 0;
+ else {
+ dev_err(&mipi_dsi->pdev->dev,
+ "actually read_len:%d != len:%d.\n", read_len, len);
+ return -ERANGE;
+ }
+}
+
+int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi_dsi,
+ u8 cmd, const u32 *param, int num)
+{
+ int err = 0;
+ u32 buf[DSI_CMD_BUF_MAXSIZE];
+
+ switch (cmd) {
+ case MIPI_DCS_EXIT_SLEEP_MODE:
+ case MIPI_DCS_ENTER_SLEEP_MODE:
+ case MIPI_DCS_SET_DISPLAY_ON:
+ case MIPI_DCS_SET_DISPLAY_OFF:
+ buf[0] = cmd;
+ err = mipi_dsi_pkt_write(mipi_dsi,
+ MIPI_DSI_DCS_SHORT_WRITE, buf, 0);
+ break;
+
+ default:
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command:0x%x Not supported!\n", cmd);
+ break;
+ }
+
+ return err;
+}
+
+static void mipi_dsi_dphy_init(struct mipi_dsi_info *mipi_dsi,
+ u32 cmd, u32 data)
+{
+ u32 val;
+ u32 timeout = 0;
+
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL,
+ DSI_PHY_IF_CTRL_RESET);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_POWERUP);
+
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1,
+ (0x10000 | cmd));
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL1, (0 | data));
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 2);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TST_CTRL0, 0);
+ val = DSI_PHY_RSTZ_EN_CLK | DSI_PHY_RSTZ_DISABLE_RST |
+ DSI_PHY_RSTZ_DISABLE_SHUTDOWN;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, val);
+
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val);
+ while ((val & DSI_PHY_STATUS_LOCK) != DSI_PHY_STATUS_LOCK) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_PHY_TIMEOUT) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "Error: phy lock timeout!\n");
+ break;
+ }
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val);
+ }
+ timeout = 0;
+ while ((val & DSI_PHY_STATUS_STOPSTATE_CLK_LANE) !=
+ DSI_PHY_STATUS_STOPSTATE_CLK_LANE) {
+ msleep(1);
+ timeout++;
+ if (timeout == MIPI_DSI_PHY_TIMEOUT) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "Error: phy lock lane timeout!\n");
+ break;
+ }
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_PHY_STATUS, &val);
+ }
+}
+
+static void mipi_dsi_enable_controller(struct mipi_dsi_info *mipi_dsi,
+ bool init)
+{
+ u32 val;
+ u32 lane_byte_clk_period;
+ struct fb_videomode *mode = mipi_dsi->mode;
+ struct mipi_lcd_config *lcd_config = mipi_dsi->lcd_config;
+
+ if (init) {
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP,
+ DSI_PWRUP_RESET);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ,
+ DSI_PHY_RSTZ_RST);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CLKMGR_CFG,
+ DSI_CLKMGR_CFG_CLK_DIV);
+
+ if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
+ val = DSI_DPI_CFG_VSYNC_ACT_LOW;
+ if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
+ val |= DSI_DPI_CFG_HSYNC_ACT_LOW;
+ if ((mode->sync & FB_SYNC_OE_LOW_ACT))
+ val |= DSI_DPI_CFG_DATAEN_ACT_LOW;
+ if (MIPI_RGB666_LOOSELY == lcd_config->dpi_fmt)
+ val |= DSI_DPI_CFG_EN18LOOSELY;
+ val |= (lcd_config->dpi_fmt & DSI_DPI_CFG_COLORCODE_MASK)
+ << DSI_DPI_CFG_COLORCODE_SHIFT;
+ val |= (lcd_config->virtual_ch & DSI_DPI_CFG_VID_MASK)
+ << DSI_DPI_CFG_VID_SHIFT;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_DPI_CFG, val);
+
+ val = DSI_PCKHDL_CFG_EN_BTA |
+ DSI_PCKHDL_CFG_EN_ECC_RX |
+ DSI_PCKHDL_CFG_EN_CRC_RX;
+
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PCKHDL_CFG, val);
+
+ val = (mode->xres & DSI_VID_PKT_CFG_VID_PKT_SZ_MASK)
+ << DSI_VID_PKT_CFG_VID_PKT_SZ_SHIFT;
+ val |= (NUMBER_OF_CHUNKS & DSI_VID_PKT_CFG_NUM_CHUNKS_MASK)
+ << DSI_VID_PKT_CFG_NUM_CHUNKS_SHIFT;
+ val |= (NULL_PKT_SIZE & DSI_VID_PKT_CFG_NULL_PKT_SZ_MASK)
+ << DSI_VID_PKT_CFG_NULL_PKT_SZ_SHIFT;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_PKT_CFG, val);
+
+ /* enable LP mode when TX DCS cmd and enable DSI command mode */
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG,
+ MIPI_DSI_CMD_MODE_CFG_EN_LOWPOWER);
+
+ /* mipi lane byte clk period in ns unit */
+ lane_byte_clk_period = NS2PS_RATIO /
+ (lcd_config->max_phy_clk / BITS_PER_BYTE);
+ val = ROUND_UP(mode->hsync_len * mode->pixclock /
+ NS2PS_RATIO / lane_byte_clk_period)
+ << DSI_TME_LINE_CFG_HSA_TIME_SHIFT;
+ val |= ROUND_UP(mode->left_margin * mode->pixclock /
+ NS2PS_RATIO / lane_byte_clk_period)
+ << DSI_TME_LINE_CFG_HBP_TIME_SHIFT;
+ val |= ROUND_UP((mode->left_margin + mode->right_margin +
+ mode->hsync_len + mode->xres) * mode->pixclock
+ / NS2PS_RATIO / lane_byte_clk_period)
+ << DSI_TME_LINE_CFG_HLINE_TIME_SHIFT;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_TMR_LINE_CFG, val);
+
+ val = ((mode->vsync_len & DSI_VTIMING_CFG_VSA_LINES_MASK)
+ << DSI_VTIMING_CFG_VSA_LINES_SHIFT);
+ val |= ((mode->upper_margin & DSI_VTIMING_CFG_VBP_LINES_MASK)
+ << DSI_VTIMING_CFG_VBP_LINES_SHIFT);
+ val |= ((mode->lower_margin & DSI_VTIMING_CFG_VFP_LINES_MASK)
+ << DSI_VTIMING_CFG_VFP_LINES_SHIFT);
+ val |= ((mode->yres & DSI_VTIMING_CFG_V_ACT_LINES_MASK)
+ << DSI_VTIMING_CFG_V_ACT_LINES_SHIFT);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VTIMING_CFG, val);
+
+ val = ((PHY_BTA_MAXTIME & DSI_PHY_TMR_CFG_BTA_TIME_MASK)
+ << DSI_PHY_TMR_CFG_BTA_TIME_SHIFT);
+ val |= ((PHY_LP2HS_MAXTIME & DSI_PHY_TMR_CFG_LP2HS_TIME_MASK)
+ << DSI_PHY_TMR_CFG_LP2HS_TIME_SHIFT);
+ val |= ((PHY_HS2LP_MAXTIME & DSI_PHY_TMR_CFG_HS2LP_TIME_MASK)
+ << DSI_PHY_TMR_CFG_HS2LP_TIME_SHIFT);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_TMR_CFG, val);
+
+ val = (((lcd_config->data_lane_num - 1) &
+ DSI_PHY_IF_CFG_N_LANES_MASK)
+ << DSI_PHY_IF_CFG_N_LANES_SHIFT);
+ val |= ((PHY_STOP_WAIT_TIME & DSI_PHY_IF_CFG_WAIT_TIME_MASK)
+ << DSI_PHY_IF_CFG_WAIT_TIME_SHIFT);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CFG, val);
+
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &val);
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &val);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, 0);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, 0);
+
+ mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND,
+ mipi_dsi->dphy_pll_config);
+ } else {
+ mipi_dsi_dphy_init(mipi_dsi, DSI_PHY_CLK_INIT_COMMAND,
+ mipi_dsi->dphy_pll_config);
+ }
+}
+
+static void mipi_dsi_disable_controller(struct mipi_dsi_info *mipi_dsi)
+{
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL,
+ DSI_PHY_IF_CTRL_RESET);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP, DSI_PWRUP_RESET);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_RSTZ, DSI_PHY_RSTZ_RST);
+}
+
+static irqreturn_t mipi_dsi_irq_handler(int irq, void *data)
+{
+ u32 mask0;
+ u32 mask1;
+ u32 status0;
+ u32 status1;
+ struct mipi_dsi_info *mipi_dsi;
+
+ mipi_dsi = (struct mipi_dsi_info *)data;
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST0, &status0);
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_ST1, &status1);
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK0, &mask0);
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_ERROR_MSK1, &mask1);
+
+ if ((status0 & (~mask0)) || (status1 & (~mask1))) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "mipi_dsi IRQ status0:0x%x, status1:0x%x!\n",
+ status0, status1);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static inline void mipi_dsi_set_mode(struct mipi_dsi_info *mipi_dsi,
+ bool cmd_mode)
+{
+ u32 val;
+
+ if (cmd_mode) {
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP,
+ DSI_PWRUP_RESET);
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val);
+ val |= MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, 0);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP,
+ DSI_PWRUP_POWERUP);
+ } else {
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP,
+ DSI_PWRUP_RESET);
+ /* Disable Command mode when tranfering video data */
+ mipi_dsi_read_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, &val);
+ val &= ~MIPI_DSI_CMD_MODE_CFG_EN_CMD_MODE;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_CMD_MODE_CFG, val);
+ val = DSI_VID_MODE_CFG_EN | DSI_VID_MODE_CFG_EN_BURSTMODE |
+ DSI_VID_MODE_CFG_EN_LP_MODE;
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_VID_MODE_CFG, val);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PWR_UP,
+ DSI_PWRUP_POWERUP);
+ mipi_dsi_write_register(mipi_dsi, MIPI_DSI_PHY_IF_CTRL,
+ DSI_PHY_IF_CTRL_TX_REQ_CLK_HS);
+ }
+}
+
+static int mipi_dsi_power_on(struct mxc_dispdrv_handle *disp)
+{
+ int err;
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ if (!mipi_dsi->dsi_power_on) {
+ clk_prepare_enable(mipi_dsi->dphy_clk);
+ clk_prepare_enable(mipi_dsi->cfg_clk);
+ mipi_dsi_enable_controller(mipi_dsi, false);
+ mipi_dsi_set_mode(mipi_dsi, false);
+ /* host send pclk/hsync/vsync for two frames before sleep-out */
+ msleep((1000/mipi_dsi->mode->refresh + 1) << 1);
+ mipi_dsi_set_mode(mipi_dsi, true);
+ err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_EXIT_SLEEP_MODE,
+ NULL, 0);
+ if (err) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command sleep-in error!\n");
+ }
+ msleep(MIPI_LCD_SLEEP_MODE_DELAY);
+ mipi_dsi_set_mode(mipi_dsi, false);
+ mipi_dsi->dsi_power_on = 1;
+ }
+
+ return 0;
+}
+
+void mipi_dsi_power_off(struct mxc_dispdrv_handle *disp)
+{
+ int err;
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ if (mipi_dsi->dsi_power_on) {
+ mipi_dsi_set_mode(mipi_dsi, true);
+ err = mipi_dsi_dcs_cmd(mipi_dsi, MIPI_DCS_ENTER_SLEEP_MODE,
+ NULL, 0);
+ if (err) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "MIPI DSI DCS Command display on error!\n");
+ }
+ /* To allow time for the supply voltages
+ * and clock circuits to stabilize.
+ */
+ msleep(5);
+ /* video stream timing on */
+ mipi_dsi_set_mode(mipi_dsi, false);
+ msleep(MIPI_LCD_SLEEP_MODE_DELAY);
+
+ mipi_dsi_set_mode(mipi_dsi, true);
+ mipi_dsi_disable_controller(mipi_dsi);
+ mipi_dsi->dsi_power_on = 0;
+ clk_disable_unprepare(mipi_dsi->dphy_clk);
+ clk_disable_unprepare(mipi_dsi->cfg_clk);
+ }
+}
+
+static int mipi_dsi_lcd_init(struct mipi_dsi_info *mipi_dsi,
+ struct mxc_dispdrv_setting *setting)
+{
+ int err;
+ int size;
+ int i;
+ struct fb_videomode *mipi_lcd_modedb;
+ struct fb_videomode mode;
+ struct device *dev = &mipi_dsi->pdev->dev;
+
+ for (i = 0; i < ARRAY_SIZE(mipi_dsi_lcd_db); i++) {
+ if (!strcmp(mipi_dsi->lcd_panel,
+ mipi_dsi_lcd_db[i].lcd_panel)) {
+ mipi_dsi->lcd_callback =
+ &mipi_dsi_lcd_db[i].lcd_callback;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(mipi_dsi_lcd_db)) {
+ dev_err(dev, "failed to find supported lcd panel.\n");
+ return -EINVAL;
+ }
+ /* get the videomode in the order: cmdline->platform data->driver */
+ mipi_dsi->lcd_callback->get_mipi_lcd_videomode(&mipi_lcd_modedb, &size,
+ &mipi_dsi->lcd_config);
+ err = fb_find_mode(&setting->fbi->var, setting->fbi,
+ setting->dft_mode_str,
+ mipi_lcd_modedb, size, NULL,
+ setting->default_bpp);
+ if (err != 1)
+ fb_videomode_to_var(&setting->fbi->var, mipi_lcd_modedb);
+
+ INIT_LIST_HEAD(&setting->fbi->modelist);
+ for (i = 0; i < size; i++) {
+ fb_var_to_videomode(&mode, &setting->fbi->var);
+ if (fb_mode_is_equal(&mode, mipi_lcd_modedb + i)) {
+ err = fb_add_videomode(mipi_lcd_modedb + i,
+ &setting->fbi->modelist);
+ /* Note: only support fb mode from driver */
+ mipi_dsi->mode = mipi_lcd_modedb + i;
+ break;
+ }
+ }
+ if ((err < 0) || (size == i)) {
+ dev_err(dev, "failed to add videomode.\n");
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mipi_dsi_phy_pll_clk_table); i++) {
+ if (mipi_dsi_phy_pll_clk_table[i].max_phy_clk <
+ mipi_dsi->lcd_config->max_phy_clk)
+ break;
+ }
+ if ((i == ARRAY_SIZE(mipi_dsi_phy_pll_clk_table)) ||
+ (mipi_dsi->lcd_config->max_phy_clk >
+ mipi_dsi_phy_pll_clk_table[0].max_phy_clk)) {
+ dev_err(dev, "failed to find data in"
+ "mipi_dsi_phy_pll_clk_table.\n");
+ return -EINVAL;
+ }
+ mipi_dsi->dphy_pll_config = mipi_dsi_phy_pll_clk_table[--i].config;
+ dev_dbg(dev, "dphy_pll_config:0x%x.\n", mipi_dsi->dphy_pll_config);
+
+ return 0;
+}
+
+static int mipi_dsi_enable(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ int err;
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ if (!mipi_dsi->lcd_inited) {
+ err = clk_prepare_enable(mipi_dsi->dphy_clk);
+ err |= clk_prepare_enable(mipi_dsi->cfg_clk);
+ if (err)
+ dev_err(&mipi_dsi->pdev->dev,
+ "clk enable error:%d!\n", err);
+ mipi_dsi_enable_controller(mipi_dsi, true);
+ err = mipi_dsi->lcd_callback->mipi_lcd_setup(
+ mipi_dsi);
+ if (err < 0) {
+ dev_err(&mipi_dsi->pdev->dev,
+ "failed to init mipi lcd.");
+ clk_disable_unprepare(mipi_dsi->dphy_clk);
+ clk_disable_unprepare(mipi_dsi->cfg_clk);
+ return err;
+ }
+ mipi_dsi_set_mode(mipi_dsi, false);
+ mipi_dsi->dsi_power_on = 1;
+ mipi_dsi->lcd_inited = 1;
+ }
+ mipi_dsi_power_on(mipi_dsi->disp_mipi);
+
+ return 0;
+}
+
+static void mipi_dsi_disable(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+}
+
+static int mipi_dsi_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ struct mipi_dsi_info *mipi_dsi = mxc_dispdrv_getdata(disp);
+ struct device *dev = &mipi_dsi->pdev->dev;
+ int ret = 0;
+
+ if (!valid_mode(setting->if_fmt)) {
+ dev_warn(dev, "Input pixel format not valid"
+ "use default RGB24\n");
+ setting->if_fmt = IPU_PIX_FMT_RGB24;
+ }
+
+ ret = ipu_di_to_crtc(dev, mipi_dsi->dev_id,
+ mipi_dsi->disp_id, &setting->crtc);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_dsi_lcd_init(mipi_dsi, setting);
+ if (ret) {
+ dev_err(dev, "failed to init mipi dsi lcd\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "MIPI DSI dispdrv inited!\n");
+ return ret;
+}
+
+static void mipi_dsi_disp_deinit(struct mxc_dispdrv_handle *disp)
+{
+ struct mipi_dsi_info *mipi_dsi;
+
+ mipi_dsi = mxc_dispdrv_getdata(disp);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+ if (mipi_dsi->bl)
+ backlight_device_unregister(mipi_dsi->bl);
+}
+
+static struct mxc_dispdrv_driver mipi_dsi_drv = {
+ .name = DISPDRV_MIPI,
+ .init = mipi_dsi_disp_init,
+ .deinit = mipi_dsi_disp_deinit,
+ .enable = mipi_dsi_enable,
+ .disable = mipi_dsi_disable,
+};
+
+static int imx6q_mipi_dsi_get_mux(int dev_id, int disp_id)
+{
+ if (dev_id > 1 || disp_id > 1)
+ return -EINVAL;
+
+ return (dev_id << 5) | (disp_id << 4);
+}
+
+static struct mipi_dsi_bus_mux imx6q_mipi_dsi_mux[] = {
+ {
+ .reg = IOMUXC_GPR3,
+ .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK,
+ .get_mux = imx6q_mipi_dsi_get_mux,
+ },
+};
+
+static int imx6dl_mipi_dsi_get_mux(int dev_id, int disp_id)
+{
+ if (dev_id > 1 || disp_id > 1)
+ return -EINVAL;
+
+ /* MIPI DSI source is LCDIF */
+ if (dev_id)
+ disp_id = 0;
+
+ return (dev_id << 5) | (disp_id << 4);
+}
+
+static struct mipi_dsi_bus_mux imx6dl_mipi_dsi_mux[] = {
+ {
+ .reg = IOMUXC_GPR3,
+ .mask = IMX6Q_GPR3_MIPI_MUX_CTL_MASK,
+ .get_mux = imx6dl_mipi_dsi_get_mux,
+ },
+};
+
+static const struct of_device_id imx_mipi_dsi_dt_ids[] = {
+ { .compatible = "fsl,imx6q-mipi-dsi", .data = imx6q_mipi_dsi_mux, },
+ { .compatible = "fsl,imx6dl-mipi-dsi", .data = imx6dl_mipi_dsi_mux, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, imx_mipi_dsi_dt_ids);
+
+/**
+ * This function is called by the driver framework to initialize the MIPI DSI
+ * device.
+ *
+ * @param pdev The device structure for the MIPI DSI passed in by the
+ * driver framework.
+ *
+ * @return Returns 0 on success or negative error code on error
+ */
+static int mipi_dsi_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id =
+ of_match_device(of_match_ptr(imx_mipi_dsi_dt_ids),
+ &pdev->dev);
+ struct mipi_dsi_info *mipi_dsi;
+ struct resource *res;
+ u32 dev_id, disp_id;
+ const char *lcd_panel;
+ unsigned int mux;
+ int ret = 0;
+
+ mipi_dsi = devm_kzalloc(&pdev->dev, sizeof(*mipi_dsi), GFP_KERNEL);
+ if (!mipi_dsi)
+ return -ENOMEM;
+
+ ret = of_property_read_string(np, "lcd_panel", &lcd_panel);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read of property lcd_panel\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(np, "dev_id", &dev_id);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read of property dev_id\n");
+ return ret;
+ }
+ ret = of_property_read_u32(np, "disp_id", &disp_id);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to read of property disp_id\n");
+ return ret;
+ }
+ mipi_dsi->dev_id = dev_id;
+ mipi_dsi->disp_id = disp_id;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get platform resource 0\n");
+ return -ENODEV;
+ }
+
+ if (!devm_request_mem_region(&pdev->dev, res->start,
+ resource_size(res), pdev->name))
+ return -EBUSY;
+
+ mipi_dsi->mmio_base = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!mipi_dsi->mmio_base)
+ return -EBUSY;
+
+ mipi_dsi->irq = platform_get_irq(pdev, 0);
+ if (mipi_dsi->irq < 0) {
+ dev_err(&pdev->dev, "failed get device irq\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(&pdev->dev, mipi_dsi->irq,
+ mipi_dsi_irq_handler,
+ 0, "mipi_dsi", mipi_dsi);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request irq\n");
+ return ret;
+ }
+
+ mipi_dsi->dphy_clk = devm_clk_get(&pdev->dev, "mipi_pllref_clk");
+ if (IS_ERR(mipi_dsi->dphy_clk)) {
+ dev_err(&pdev->dev, "failed to get dphy pll_ref_clk\n");
+ return PTR_ERR(mipi_dsi->dphy_clk);
+ }
+
+ mipi_dsi->cfg_clk = devm_clk_get(&pdev->dev, "mipi_cfg_clk");
+ if (IS_ERR(mipi_dsi->cfg_clk)) {
+ dev_err(&pdev->dev, "failed to get cfg_clk\n");
+ return PTR_ERR(mipi_dsi->cfg_clk);
+ }
+
+ mipi_dsi->disp_power_on = devm_regulator_get(&pdev->dev,
+ "disp-power-on");
+ if (!IS_ERR(mipi_dsi->disp_power_on)) {
+ ret = regulator_enable(mipi_dsi->disp_power_on);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable display "
+ "power regulator, err=%d\n", ret);
+ return ret;
+ }
+ } else {
+ mipi_dsi->disp_power_on = NULL;
+ }
+
+ ret = device_reset(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to reset: %d\n", ret);
+ goto dev_reset_fail;
+ }
+
+ if (of_id)
+ mipi_dsi->bus_mux = of_id->data;
+
+ mipi_dsi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
+ if (IS_ERR(mipi_dsi->regmap)) {
+ dev_err(&pdev->dev, "failed to get parent regmap\n");
+ ret = PTR_ERR(mipi_dsi->regmap);
+ goto get_parent_regmap_fail;
+ }
+
+ mux = mipi_dsi->bus_mux->get_mux(dev_id, disp_id);
+ if (mux >= 0)
+ regmap_update_bits(mipi_dsi->regmap, mipi_dsi->bus_mux->reg,
+ mipi_dsi->bus_mux->mask, mux);
+ else
+ dev_warn(&pdev->dev, "invalid dev_id or disp_id muxing\n");
+
+ mipi_dsi->lcd_panel = kstrdup(lcd_panel, GFP_KERNEL);
+ if (!mipi_dsi->lcd_panel) {
+ dev_err(&pdev->dev, "failed to allocate lcd panel name\n");
+ ret = -ENOMEM;
+ goto kstrdup_fail;
+ }
+
+ mipi_dsi->pdev = pdev;
+ mipi_dsi->disp_mipi = mxc_dispdrv_register(&mipi_dsi_drv);
+ if (IS_ERR(mipi_dsi->disp_mipi)) {
+ dev_err(&pdev->dev, "mxc_dispdrv_register error\n");
+ ret = PTR_ERR(mipi_dsi->disp_mipi);
+ goto dispdrv_reg_fail;
+ }
+
+ mxc_dispdrv_setdata(mipi_dsi->disp_mipi, mipi_dsi);
+ dev_set_drvdata(&pdev->dev, mipi_dsi);
+
+ dev_info(&pdev->dev, "i.MX MIPI DSI driver probed\n");
+ return ret;
+
+dispdrv_reg_fail:
+ kfree(mipi_dsi->lcd_panel);
+kstrdup_fail:
+get_parent_regmap_fail:
+dev_reset_fail:
+ if (mipi_dsi->disp_power_on)
+ regulator_disable(mipi_dsi->disp_power_on);
+ return ret;
+}
+
+static void mipi_dsi_shutdown(struct platform_device *pdev)
+{
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ mipi_dsi_power_off(mipi_dsi->disp_mipi);
+}
+
+static int mipi_dsi_remove(struct platform_device *pdev)
+{
+ struct mipi_dsi_info *mipi_dsi = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_puthandle(mipi_dsi->disp_mipi);
+ mxc_dispdrv_unregister(mipi_dsi->disp_mipi);
+
+ if (mipi_dsi->disp_power_on)
+ regulator_disable(mipi_dsi->disp_power_on);
+
+ kfree(mipi_dsi->lcd_panel);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver mipi_dsi_driver = {
+ .driver = {
+ .of_match_table = imx_mipi_dsi_dt_ids,
+ .name = "mxc_mipi_dsi",
+ },
+ .probe = mipi_dsi_probe,
+ .remove = mipi_dsi_remove,
+ .shutdown = mipi_dsi_shutdown,
+};
+
+static int __init mipi_dsi_init(void)
+{
+ int err;
+
+ err = platform_driver_register(&mipi_dsi_driver);
+ if (err) {
+ pr_err("mipi_dsi_driver register failed\n");
+ return -ENODEV;
+ }
+ pr_info("MIPI DSI driver module loaded\n");
+ return 0;
+}
+
+static void __exit mipi_dsi_cleanup(void)
+{
+ platform_driver_unregister(&mipi_dsi_driver);
+}
+
+module_init(mipi_dsi_init);
+module_exit(mipi_dsi_cleanup);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX MIPI DSI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/mipi_dsi.h b/drivers/video/mxc/mipi_dsi.h
new file mode 100644
index 000000000000..a7145a583718
--- /dev/null
+++ b/drivers/video/mxc/mipi_dsi.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __MIPI_DSI_H__
+#define __MIPI_DSI_H__
+
+#include <linux/regmap.h>
+#include "mxc_dispdrv.h"
+
+#ifdef DEBUG
+#define mipi_dbg(fmt, ...) printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
+#else
+#define mipi_dbg(fmt, ...)
+#endif
+
+#define DSI_CMD_BUF_MAXSIZE (32)
+
+/* DPI interface pixel color coding map */
+enum mipi_dsi_dpi_fmt {
+ MIPI_RGB565_PACKED = 0,
+ MIPI_RGB565_LOOSELY,
+ MIPI_RGB565_CONFIG3,
+ MIPI_RGB666_PACKED,
+ MIPI_RGB666_LOOSELY,
+ MIPI_RGB888,
+};
+
+struct mipi_lcd_config {
+ u32 virtual_ch;
+ u32 data_lane_num;
+ /* device max DPHY clock in MHz unit */
+ u32 max_phy_clk;
+ enum mipi_dsi_dpi_fmt dpi_fmt;
+};
+
+struct mipi_dsi_info;
+struct mipi_dsi_lcd_callback {
+ /* callback for lcd panel operation */
+ void (*get_mipi_lcd_videomode)(struct fb_videomode **, int *,
+ struct mipi_lcd_config **);
+ int (*mipi_lcd_setup)(struct mipi_dsi_info *);
+
+};
+
+struct mipi_dsi_match_lcd {
+ char *lcd_panel;
+ struct mipi_dsi_lcd_callback lcd_callback;
+};
+
+struct mipi_dsi_bus_mux {
+ int reg;
+ int mask;
+ int (*get_mux) (int dev_id, int disp_id);
+};
+
+/* driver private data */
+struct mipi_dsi_info {
+ struct platform_device *pdev;
+ void __iomem *mmio_base;
+ struct regmap *regmap;
+ const struct mipi_dsi_bus_mux *bus_mux;
+ int dsi_power_on;
+ int lcd_inited;
+ u32 dphy_pll_config;
+ int dev_id;
+ int disp_id;
+ char *lcd_panel;
+ int irq;
+ struct clk *dphy_clk;
+ struct clk *cfg_clk;
+ struct mxc_dispdrv_handle *disp_mipi;
+ struct fb_videomode *mode;
+ struct regulator *disp_power_on;
+ struct mipi_lcd_config *lcd_config;
+ /* board related power control */
+ struct backlight_device *bl;
+ /* callback for lcd panel operation */
+ struct mipi_dsi_lcd_callback *lcd_callback;
+};
+
+int mipi_dsi_pkt_write(struct mipi_dsi_info *mipi,
+ u8 data_type, const u32 *buf, int len);
+int mipi_dsi_pkt_read(struct mipi_dsi_info *mipi,
+ u8 data_type, u32 *buf, int len);
+int mipi_dsi_dcs_cmd(struct mipi_dsi_info *mipi,
+ u8 cmd, const u32 *param, int num);
+
+#ifdef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL
+void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size,
+ struct mipi_lcd_config **data);
+int mipid_hx8369_lcd_setup(struct mipi_dsi_info *);
+#endif
+
+#ifndef CONFIG_FB_MXC_TRULY_WVGA_SYNC_PANEL
+#error "Please configure MIPI LCD panel, we cannot find one!"
+#endif
+
+#endif
diff --git a/drivers/video/mxc/mxc_dispdrv.c b/drivers/video/mxc/mxc_dispdrv.c
new file mode 100644
index 000000000000..805c1c98b0f9
--- /dev/null
+++ b/drivers/video/mxc/mxc_dispdrv.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011-2014 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
+ */
+
+/*!
+ * @file mxc_dispdrv.c
+ * @brief mxc display driver framework.
+ *
+ * A display device driver could call mxc_dispdrv_register(drv) in its dev_probe() function.
+ * Move all dev_probe() things into mxc_dispdrv_driver->init(), init() function should init
+ * and feedback setting;
+ * Move all dev_remove() things into mxc_dispdrv_driver->deinit();
+ * Move all dev_suspend() things into fb_notifier for SUSPEND, if there is;
+ * Move all dev_resume() things into fb_notifier for RESUME, if there is;
+ *
+ * mxc fb driver could call mxc_dispdrv_gethandle(name, setting) before a fb
+ * need be added, with fbi param passing by setting, after
+ * mxc_dispdrv_gethandle() return, FB driver should get the basic setting
+ * about fbi info and crtc.
+ *
+ * @ingroup Framebuffer
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/string.h>
+#include "mxc_dispdrv.h"
+
+static LIST_HEAD(dispdrv_list);
+static DEFINE_MUTEX(dispdrv_lock);
+
+struct mxc_dispdrv_entry {
+ /* Note: drv always the first element */
+ struct mxc_dispdrv_driver *drv;
+ bool active;
+ void *priv;
+ struct list_head list;
+};
+
+struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv)
+{
+ struct mxc_dispdrv_entry *new;
+
+ mutex_lock(&dispdrv_lock);
+
+ new = kzalloc(sizeof(struct mxc_dispdrv_entry), GFP_KERNEL);
+ if (!new) {
+ mutex_unlock(&dispdrv_lock);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ new->drv = drv;
+ list_add_tail(&new->list, &dispdrv_list);
+
+ mutex_unlock(&dispdrv_lock);
+
+ return (struct mxc_dispdrv_handle *)new;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_register);
+
+int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle)
+{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
+ if (entry) {
+ mutex_lock(&dispdrv_lock);
+ list_del(&entry->list);
+ mutex_unlock(&dispdrv_lock);
+ kfree(entry);
+ return 0;
+ } else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_unregister);
+
+struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
+ struct mxc_dispdrv_setting *setting)
+{
+ int ret, found = 0;
+ struct mxc_dispdrv_entry *entry;
+
+ mutex_lock(&dispdrv_lock);
+ list_for_each_entry(entry, &dispdrv_list, list) {
+ if (!strcmp(entry->drv->name, name) && (entry->drv->init)) {
+ ret = entry->drv->init((struct mxc_dispdrv_handle *)
+ entry, setting);
+ if (ret >= 0) {
+ entry->active = true;
+ found = 1;
+ break;
+ }
+ }
+ }
+ mutex_unlock(&dispdrv_lock);
+
+ return found ? (struct mxc_dispdrv_handle *)entry:ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_gethandle);
+
+void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle)
+{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
+ mutex_lock(&dispdrv_lock);
+ if (entry && entry->active && entry->drv->deinit) {
+ entry->drv->deinit(handle);
+ entry->active = false;
+ }
+ mutex_unlock(&dispdrv_lock);
+
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_puthandle);
+
+int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data)
+{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
+ if (entry) {
+ entry->priv = data;
+ return 0;
+ } else
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_setdata);
+
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle)
+{
+ struct mxc_dispdrv_entry *entry = (struct mxc_dispdrv_entry *)handle;
+
+ if (entry) {
+ return entry->priv;
+ } else
+ return ERR_PTR(-EINVAL);
+}
+EXPORT_SYMBOL_GPL(mxc_dispdrv_getdata);
diff --git a/drivers/video/mxc/mxc_dispdrv.h b/drivers/video/mxc/mxc_dispdrv.h
new file mode 100644
index 000000000000..58d8a07d3380
--- /dev/null
+++ b/drivers/video/mxc/mxc_dispdrv.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2014 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
+ */
+#ifndef __MXC_DISPDRV_H__
+#define __MXC_DISPDRV_H__
+#include <linux/fb.h>
+#include "crtc.h"
+
+struct mxc_dispdrv_handle {
+ struct mxc_dispdrv_driver *drv;
+};
+
+struct mxc_dispdrv_setting {
+ /*input-feedback parameter*/
+ struct fb_info *fbi;
+ int if_fmt;
+ int default_bpp;
+ char *dft_mode_str;
+
+ /* feedback parameter */
+ enum crtc crtc;
+};
+
+struct mxc_dispdrv_driver {
+ const char *name;
+ int (*init) (struct mxc_dispdrv_handle *, struct mxc_dispdrv_setting *);
+ void (*deinit) (struct mxc_dispdrv_handle *);
+ /* display driver enable function for extension */
+ int (*enable) (struct mxc_dispdrv_handle *, struct fb_info *);
+ /* display driver disable function, called at early part of fb_blank */
+ void (*disable) (struct mxc_dispdrv_handle *, struct fb_info *);
+ /* display driver setup function, called at early part of fb_set_par */
+ int (*setup) (struct mxc_dispdrv_handle *, struct fb_info *fbi);
+};
+
+struct mxc_dispdrv_handle *mxc_dispdrv_register(struct mxc_dispdrv_driver *drv);
+int mxc_dispdrv_unregister(struct mxc_dispdrv_handle *handle);
+struct mxc_dispdrv_handle *mxc_dispdrv_gethandle(char *name,
+ struct mxc_dispdrv_setting *setting);
+void mxc_dispdrv_puthandle(struct mxc_dispdrv_handle *handle);
+int mxc_dispdrv_setdata(struct mxc_dispdrv_handle *handle, void *data);
+void *mxc_dispdrv_getdata(struct mxc_dispdrv_handle *handle);
+#endif
diff --git a/drivers/video/mxc/mxc_edid.c b/drivers/video/mxc/mxc_edid.c
new file mode 100644
index 000000000000..88b52682d99b
--- /dev/null
+++ b/drivers/video/mxc/mxc_edid.c
@@ -0,0 +1,762 @@
+/*
+ * Copyright 2009-2013 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
+ */
+
+/*!
+ * @defgroup Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+/*!
+ * @file mxc_edid.c
+ *
+ * @brief MXC EDID driver
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/i2c.h>
+#include <linux/fb.h>
+#include <video/mxc_edid.h>
+#include "../edid.h"
+
+#undef DEBUG /* define this for verbose EDID parsing output */
+#ifdef DEBUG
+#define DPRINTK(fmt, args...) printk(fmt, ## args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+const struct fb_videomode mxc_cea_mode[64] = {
+ /* #1: 640x480p@59.94/60Hz 4:3 */
+ [1] = {
+ NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #2: 720x480p@59.94/60Hz 4:3 */
+ [2] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #3: 720x480p@59.94/60Hz 16:9 */
+ [3] = {
+ NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #4: 1280x720p@59.94/60Hz 16:9 */
+ [4] = {
+ NULL, 60, 1280, 720, 13468, 220, 110, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #5: 1920x1080i@59.94/60Hz 16:9 */
+ [5] = {
+ NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #6: 720(1440)x480iH@59.94/60Hz 4:3 */
+ [6] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #7: 720(1440)x480iH@59.94/60Hz 16:9 */
+ [7] = {
+ NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #8: 720(1440)x240pH@59.94/60Hz 4:3 */
+ [8] = {
+ NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #9: 720(1440)x240pH@59.94/60Hz 16:9 */
+ [9] = {
+ NULL, 60, 1440, 240, 37108, 114, 38, 15, 4, 124, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #14: 1440x480p@59.94/60Hz 4:3 */
+ [14] = {
+ NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #15: 1440x480p@59.94/60Hz 16:9 */
+ [15] = {
+ NULL, 60, 1440, 480, 18500, 120, 32, 30, 9, 124, 6, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #16: 1920x1080p@60Hz 16:9 */
+ [16] = {
+ NULL, 60, 1920, 1080, 6734, 148, 88, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #17: 720x576pH@50Hz 4:3 */
+ [17] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #18: 720x576pH@50Hz 16:9 */
+ [18] = {
+ NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #19: 1280x720p@50Hz */
+ [19] = {
+ NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #20: 1920x1080i@50Hz */
+ [20] = {
+ NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_INTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #23: 720(1440)x288pH@50Hz 4:3 */
+ [23] = {
+ NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #24: 720(1440)x288pH@50Hz 16:9 */
+ [24] = {
+ NULL, 50, 1440, 288, 37037, 138, 24, 19, 2, 126, 3, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #29: 720(1440)x576pH@50Hz 4:3 */
+ [29] = {
+ NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, 0,
+ },
+ /* #30: 720(1440)x576pH@50Hz 16:9 */
+ [30] = {
+ NULL, 50, 1440, 576, 18518, 136, 24, 39, 5, 128, 5, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #31: 1920x1080p@50Hz */
+ [31] = {
+ NULL, 50, 1920, 1080, 6734, 148, 528, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #32: 1920x1080p@23.98/24Hz */
+ [32] = {
+ NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #33: 1920x1080p@25Hz */
+ [33] = {
+ NULL, 25, 1920, 1080, 13468, 148, 528, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #34: 1920x1080p@30Hz */
+ [34] = {
+ NULL, 30, 1920, 1080, 13468, 148, 88, 36, 4, 44, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0,
+ },
+ /* #41: 1280x720p@100Hz 16:9 */
+ [41] = {
+ NULL, 100, 1280, 720, 6734, 220, 440, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+ /* #47: 1280x720p@119.88/120Hz 16:9 */
+ [47] = {
+ NULL, 120, 1280, 720, 6734, 220, 110, 20, 5, 40, 5,
+ FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_16_9, 0
+ },
+};
+
+/*
+ * We have a special version of fb_mode_is_equal that ignores
+ * pixclock, since for many CEA modes, 2 frequencies are supported
+ * e.g. 640x480 @ 60Hz or 59.94Hz
+ */
+int mxc_edid_fb_mode_is_equal(bool use_aspect,
+ const struct fb_videomode *mode1,
+ const struct fb_videomode *mode2)
+{
+ u32 mask;
+
+ if (use_aspect)
+ mask = ~0;
+ else
+ mask = ~FB_VMODE_ASPECT_MASK;
+
+ return (mode1->xres == mode2->xres &&
+ mode1->yres == mode2->yres &&
+ mode1->hsync_len == mode2->hsync_len &&
+ mode1->vsync_len == mode2->vsync_len &&
+ mode1->left_margin == mode2->left_margin &&
+ mode1->right_margin == mode2->right_margin &&
+ mode1->upper_margin == mode2->upper_margin &&
+ mode1->lower_margin == mode2->lower_margin &&
+ mode1->sync == mode2->sync &&
+ /* refresh check, 59.94Hz and 60Hz have the same parameter
+ * in struct of mxc_cea_mode */
+ abs(mode1->refresh - mode2->refresh) <= 1 &&
+ (mode1->vmode & mask) == (mode2->vmode & mask));
+}
+
+static void get_detailed_timing(unsigned char *block,
+ struct fb_videomode *mode)
+{
+ mode->xres = H_ACTIVE;
+ mode->yres = V_ACTIVE;
+ mode->pixclock = PIXEL_CLOCK;
+ mode->pixclock /= 1000;
+ mode->pixclock = KHZ2PICOS(mode->pixclock);
+ mode->right_margin = H_SYNC_OFFSET;
+ mode->left_margin = (H_ACTIVE + H_BLANKING) -
+ (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH);
+ mode->upper_margin = V_BLANKING - V_SYNC_OFFSET -
+ V_SYNC_WIDTH;
+ mode->lower_margin = V_SYNC_OFFSET;
+ mode->hsync_len = H_SYNC_WIDTH;
+ mode->vsync_len = V_SYNC_WIDTH;
+ if (HSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (VSYNC_POSITIVE)
+ mode->sync |= FB_SYNC_VERT_HIGH_ACT;
+ mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) *
+ (V_ACTIVE + V_BLANKING));
+ if (INTERLACED) {
+ mode->yres *= 2;
+ mode->upper_margin *= 2;
+ mode->lower_margin *= 2;
+ mode->vsync_len *= 2;
+ mode->vmode |= FB_VMODE_INTERLACED;
+ }
+ mode->flag = FB_MODE_IS_DETAILED;
+
+ if ((H_SIZE / 16) == (V_SIZE / 9))
+ mode->vmode |= FB_VMODE_ASPECT_16_9;
+ else if ((H_SIZE / 4) == (V_SIZE / 3))
+ mode->vmode |= FB_VMODE_ASPECT_4_3;
+ else if ((mode->xres / 16) == (mode->yres / 9))
+ mode->vmode |= FB_VMODE_ASPECT_16_9;
+ else if ((mode->xres / 4) == (mode->yres / 3))
+ mode->vmode |= FB_VMODE_ASPECT_4_3;
+
+ if (mode->vmode & FB_VMODE_ASPECT_16_9)
+ DPRINTK("Aspect ratio: 16:9\n");
+ if (mode->vmode & FB_VMODE_ASPECT_4_3)
+ DPRINTK("Aspect ratio: 4:3\n");
+ DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000);
+ DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET,
+ H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING);
+ DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET,
+ V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING);
+ DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-",
+ (VSYNC_POSITIVE) ? "+" : "-");
+}
+
+int mxc_edid_parse_ext_blk(unsigned char *edid,
+ struct mxc_edid_cfg *cfg,
+ struct fb_monspecs *specs)
+{
+ char detail_timing_desc_offset;
+ struct fb_videomode *mode, *m;
+ unsigned char index = 0x0;
+ unsigned char *block;
+ int i, num = 0, revision;
+
+ if (edid[index++] != 0x2) /* only support cea ext block now */
+ return -1;
+ revision = edid[index++];
+ DPRINTK("cea extent revision %d\n", revision);
+ mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL);
+ if (mode == NULL)
+ return -1;
+
+ detail_timing_desc_offset = edid[index++];
+
+ if (revision >= 2) {
+ cfg->cea_underscan = (edid[index] >> 7) & 0x1;
+ cfg->cea_basicaudio = (edid[index] >> 6) & 0x1;
+ cfg->cea_ycbcr444 = (edid[index] >> 5) & 0x1;
+ cfg->cea_ycbcr422 = (edid[index] >> 4) & 0x1;
+
+ DPRINTK("CEA underscan %d\n", cfg->cea_underscan);
+ DPRINTK("CEA basicaudio %d\n", cfg->cea_basicaudio);
+ DPRINTK("CEA ycbcr444 %d\n", cfg->cea_ycbcr444);
+ DPRINTK("CEA ycbcr422 %d\n", cfg->cea_ycbcr422);
+ }
+
+ if (revision >= 3) {
+ /* short desc */
+ DPRINTK("CEA Short desc timmings\n");
+ index++;
+ while (index < detail_timing_desc_offset) {
+ unsigned char tagcode, blklen;
+
+ tagcode = (edid[index] >> 5) & 0x7;
+ blklen = (edid[index]) & 0x1f;
+
+ DPRINTK("Tagcode %x Len %d\n", tagcode, blklen);
+
+ switch (tagcode) {
+ case 0x2: /*Video data block*/
+ {
+ int cea_idx;
+ i = 0;
+ while (i < blklen) {
+ index++;
+ cea_idx = edid[index] & 0x7f;
+ if (cea_idx < ARRAY_SIZE(mxc_cea_mode) &&
+ (mxc_cea_mode[cea_idx].xres)) {
+ DPRINTK("Support CEA Format #%d\n", cea_idx);
+ mode[num] = mxc_cea_mode[cea_idx];
+ mode[num].flag |= FB_MODE_IS_STANDARD;
+ num++;
+ }
+ i++;
+ }
+ break;
+ }
+ case 0x3: /*Vendor specific data*/
+ {
+ unsigned char IEEE_reg_iden[3];
+ unsigned char deep_color;
+ unsigned char latency_present;
+ unsigned char I_latency_present;
+ unsigned char hdmi_video_present;
+ unsigned char hdmi_3d_present;
+ unsigned char hdmi_3d_multi_present;
+ unsigned char hdmi_vic_len;
+ unsigned char hdmi_3d_len;
+ unsigned char index_inc = 0;
+ unsigned char vsd_end;
+
+ vsd_end = index + blklen;
+
+ IEEE_reg_iden[0] = edid[index+1];
+ IEEE_reg_iden[1] = edid[index+2];
+ IEEE_reg_iden[2] = edid[index+3];
+ cfg->physical_address[0] = (edid[index+4] & 0xf0) >> 4;
+ cfg->physical_address[1] = (edid[index+4] & 0x0f);
+ cfg->physical_address[2] = (edid[index+5] & 0xf0) >> 4;
+ cfg->physical_address[3] = (edid[index+5] & 0x0f);
+
+ if ((IEEE_reg_iden[0] == 0x03) &&
+ (IEEE_reg_iden[1] == 0x0c) &&
+ (IEEE_reg_iden[2] == 0x00))
+ cfg->hdmi_cap = 1;
+
+ if (blklen > 5) {
+ deep_color = edid[index+6];
+ if (deep_color & 0x80)
+ cfg->vsd_support_ai = true;
+ if (deep_color & 0x40)
+ cfg->vsd_dc_48bit = true;
+ if (deep_color & 0x20)
+ cfg->vsd_dc_36bit = true;
+ if (deep_color & 0x10)
+ cfg->vsd_dc_30bit = true;
+ if (deep_color & 0x08)
+ cfg->vsd_dc_y444 = true;
+ if (deep_color & 0x01)
+ cfg->vsd_dvi_dual = true;
+ }
+
+ DPRINTK("VSD hdmi capability %d\n", cfg->hdmi_cap);
+ DPRINTK("VSD support ai %d\n", cfg->vsd_support_ai);
+ DPRINTK("VSD support deep color 48bit %d\n", cfg->vsd_dc_48bit);
+ DPRINTK("VSD support deep color 36bit %d\n", cfg->vsd_dc_36bit);
+ DPRINTK("VSD support deep color 30bit %d\n", cfg->vsd_dc_30bit);
+ DPRINTK("VSD support deep color y444 %d\n", cfg->vsd_dc_y444);
+ DPRINTK("VSD support dvi dual %d\n", cfg->vsd_dvi_dual);
+
+ if (blklen > 6)
+ cfg->vsd_max_tmdsclk_rate = edid[index+7] * 5;
+ DPRINTK("VSD MAX TMDS CLOCK RATE %d\n", cfg->vsd_max_tmdsclk_rate);
+
+ if (blklen > 7) {
+ latency_present = edid[index+8] >> 7;
+ I_latency_present = (edid[index+8] & 0x40) >> 6;
+ hdmi_video_present = (edid[index+8] & 0x20) >> 5;
+ cfg->vsd_cnc3 = (edid[index+8] & 0x8) >> 3;
+ cfg->vsd_cnc2 = (edid[index+8] & 0x4) >> 2;
+ cfg->vsd_cnc1 = (edid[index+8] & 0x2) >> 1;
+ cfg->vsd_cnc0 = edid[index+8] & 0x1;
+
+ DPRINTK("VSD cnc0 %d\n", cfg->vsd_cnc0);
+ DPRINTK("VSD cnc1 %d\n", cfg->vsd_cnc1);
+ DPRINTK("VSD cnc2 %d\n", cfg->vsd_cnc2);
+ DPRINTK("VSD cnc3 %d\n", cfg->vsd_cnc3);
+ DPRINTK("latency_present %d\n", latency_present);
+ DPRINTK("I_latency_present %d\n", I_latency_present);
+ DPRINTK("hdmi_video_present %d\n", hdmi_video_present);
+
+ } else {
+ index += blklen;
+ break;
+ }
+
+ index += 9;
+
+ /*latency present */
+ if (latency_present) {
+ cfg->vsd_video_latency = edid[index++];
+ cfg->vsd_audio_latency = edid[index++];
+
+ if (I_latency_present) {
+ cfg->vsd_I_video_latency = edid[index++];
+ cfg->vsd_I_audio_latency = edid[index++];
+ } else {
+ cfg->vsd_I_video_latency = cfg->vsd_video_latency;
+ cfg->vsd_I_audio_latency = cfg->vsd_audio_latency;
+ }
+
+ DPRINTK("VSD latency video_latency %d\n", cfg->vsd_video_latency);
+ DPRINTK("VSD latency audio_latency %d\n", cfg->vsd_audio_latency);
+ DPRINTK("VSD latency I_video_latency %d\n", cfg->vsd_I_video_latency);
+ DPRINTK("VSD latency I_audio_latency %d\n", cfg->vsd_I_audio_latency);
+ }
+
+ if (hdmi_video_present) {
+ hdmi_3d_present = edid[index] >> 7;
+ hdmi_3d_multi_present = (edid[index] & 0x60) >> 5;
+ index++;
+ hdmi_vic_len = (edid[index] & 0xe0) >> 5;
+ hdmi_3d_len = edid[index] & 0x1f;
+ index++;
+
+ DPRINTK("hdmi_3d_present %d\n", hdmi_3d_present);
+ DPRINTK("hdmi_3d_multi_present %d\n", hdmi_3d_multi_present);
+ DPRINTK("hdmi_vic_len %d\n", hdmi_vic_len);
+ DPRINTK("hdmi_3d_len %d\n", hdmi_3d_len);
+
+ if (hdmi_vic_len > 0) {
+ for (i = 0; i < hdmi_vic_len; i++) {
+ cfg->hdmi_vic[i] = edid[index++];
+ DPRINTK("HDMI_vic=%d\n", cfg->hdmi_vic[i]);
+ }
+ }
+
+ if (hdmi_3d_len > 0) {
+ if (hdmi_3d_present) {
+ if (hdmi_3d_multi_present == 0x1) {
+ cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1];
+ index_inc = 2;
+ } else if (hdmi_3d_multi_present == 0x2) {
+ cfg->hdmi_3d_struct_all = (edid[index] << 8) | edid[index+1];
+ cfg->hdmi_3d_mask_all = (edid[index+2] << 8) | edid[index+3];
+ index_inc = 4;
+ } else
+ index_inc = 0;
+ }
+
+ DPRINTK("HDMI 3d struct all =0x%x\n", cfg->hdmi_3d_struct_all);
+ DPRINTK("HDMI 3d mask all =0x%x\n", cfg->hdmi_3d_mask_all);
+
+ /* Read 2D vic 3D_struct */
+ if ((hdmi_3d_len - index_inc) > 0) {
+ DPRINTK("Support 3D video format\n");
+ i = 0;
+ while ((hdmi_3d_len - index_inc) > 0) {
+
+ cfg->hdmi_3d_format[i].vic_order_2d = edid[index+index_inc] >> 4;
+ cfg->hdmi_3d_format[i].struct_3d = edid[index+index_inc] & 0x0f;
+ index_inc++;
+
+ if (cfg->hdmi_3d_format[i].struct_3d == 8) {
+ cfg->hdmi_3d_format[i].detail_3d = edid[index+index_inc] >> 4;
+ index_inc++;
+ } else if (cfg->hdmi_3d_format[i].struct_3d > 8) {
+ cfg->hdmi_3d_format[i].detail_3d = 0;
+ index_inc++;
+ }
+
+ DPRINTK("vic_order_2d=%d, 3d_struct=%d, 3d_detail=0x%x\n",
+ cfg->hdmi_3d_format[i].vic_order_2d,
+ cfg->hdmi_3d_format[i].struct_3d,
+ cfg->hdmi_3d_format[i].detail_3d);
+ i++;
+ }
+ }
+ index += index_inc;
+ }
+ }
+
+ index = vsd_end;
+
+ break;
+ }
+ case 0x1: /*Audio data block*/
+ {
+ u8 audio_format, max_ch, byte1, byte2, byte3;
+
+ i = 0;
+ cfg->max_channels = 0;
+ cfg->sample_rates = 0;
+ cfg->sample_sizes = 0;
+
+ while (i < blklen) {
+ byte1 = edid[index + 1];
+ byte2 = edid[index + 2];
+ byte3 = edid[index + 3];
+ index += 3;
+ i += 3;
+
+ audio_format = byte1 >> 3;
+ max_ch = (byte1 & 0x07) + 1;
+
+ DPRINTK("Audio Format Descriptor : %2d\n", audio_format);
+ DPRINTK("Max Number of Channels : %2d\n", max_ch);
+ DPRINTK("Sample Rates : %02x\n", byte2);
+
+ /* ALSA can't specify specific compressed
+ * formats, so only care about PCM for now. */
+ if (audio_format == AUDIO_CODING_TYPE_LPCM) {
+ if (max_ch > cfg->max_channels)
+ cfg->max_channels = max_ch;
+
+ cfg->sample_rates |= byte2;
+ cfg->sample_sizes |= byte3 & 0x7;
+ DPRINTK("Sample Sizes : %02x\n",
+ byte3 & 0x7);
+ }
+ }
+ break;
+ }
+ case 0x4: /*Speaker allocation block*/
+ {
+ i = 0;
+ while (i < blklen) {
+ cfg->speaker_alloc = edid[index + 1];
+ index += 3;
+ i += 3;
+ DPRINTK("Speaker Alloc : %02x\n", cfg->speaker_alloc);
+ }
+ break;
+ }
+ case 0x7: /*User extended block*/
+ default:
+ /* skip */
+ DPRINTK("Not handle block, tagcode = 0x%x\n", tagcode);
+ index += blklen;
+ break;
+ }
+
+ index++;
+ }
+ }
+
+ /* long desc */
+ DPRINTK("CEA long desc timmings\n");
+ index = detail_timing_desc_offset;
+ block = edid + index;
+ while (index < (EDID_LENGTH - DETAILED_TIMING_DESCRIPTION_SIZE)) {
+ if (!(block[0] == 0x00 && block[1] == 0x00)) {
+ get_detailed_timing(block, &mode[num]);
+ num++;
+ }
+ block += DETAILED_TIMING_DESCRIPTION_SIZE;
+ index += DETAILED_TIMING_DESCRIPTION_SIZE;
+ }
+
+ if (!num) {
+ kfree(mode);
+ return 0;
+ }
+
+ m = kmalloc((num + specs->modedb_len) *
+ sizeof(struct fb_videomode), GFP_KERNEL);
+ if (!m)
+ return 0;
+
+ if (specs->modedb_len) {
+ memmove(m, specs->modedb,
+ specs->modedb_len * sizeof(struct fb_videomode));
+ kfree(specs->modedb);
+ }
+ memmove(m+specs->modedb_len, mode,
+ num * sizeof(struct fb_videomode));
+ kfree(mode);
+
+ specs->modedb_len += num;
+ specs->modedb = m;
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_edid_parse_ext_blk);
+
+static int mxc_edid_readblk(struct i2c_adapter *adp,
+ unsigned short addr, unsigned char *edid)
+{
+ int ret = 0, extblknum = 0;
+ unsigned char regaddr = 0x0;
+ struct i2c_msg msg[2] = {
+ {
+ .addr = addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &regaddr,
+ }, {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .len = EDID_LENGTH,
+ .buf = edid,
+ },
+ };
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+
+ if (edid[1] == 0x00)
+ return -ENOENT;
+
+ extblknum = edid[0x7E];
+
+ if (extblknum) {
+ regaddr = 128;
+ msg[1].buf = edid + EDID_LENGTH;
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID ext block\n");
+ return -EIO;
+ }
+ }
+
+ return extblknum;
+}
+
+static int mxc_edid_readsegblk(struct i2c_adapter *adp, unsigned short addr,
+ unsigned char *edid, int seg_num)
+{
+ int ret = 0;
+ unsigned char segment = 0x1, regaddr = 0;
+ struct i2c_msg msg[3] = {
+ {
+ .addr = 0x30,
+ .flags = 0,
+ .len = 1,
+ .buf = &segment,
+ }, {
+ .addr = addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &regaddr,
+ }, {
+ .addr = addr,
+ .flags = I2C_M_RD,
+ .len = EDID_LENGTH,
+ .buf = edid,
+ },
+ };
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+
+ if (seg_num == 2) {
+ regaddr = 128;
+ msg[2].buf = edid + EDID_LENGTH;
+
+ ret = i2c_transfer(adp, msg, ARRAY_SIZE(msg));
+ if (ret != ARRAY_SIZE(msg)) {
+ DPRINTK("unable to read EDID block\n");
+ return -EIO;
+ }
+ }
+
+ return ret;
+}
+
+int mxc_edid_var_to_vic(struct fb_var_screeninfo *var)
+{
+ int i;
+ struct fb_videomode m;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ fb_var_to_videomode(&m, var);
+ if (mxc_edid_fb_mode_is_equal(false, &m, &mxc_cea_mode[i]))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mxc_cea_mode))
+ return 0;
+
+ return i;
+}
+EXPORT_SYMBOL(mxc_edid_var_to_vic);
+
+int mxc_edid_mode_to_vic(const struct fb_videomode *mode)
+{
+ int i;
+ bool use_aspect = (mode->vmode & FB_VMODE_ASPECT_MASK);
+
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ if (mxc_edid_fb_mode_is_equal(use_aspect, mode, &mxc_cea_mode[i]))
+ break;
+ }
+
+ if (i == ARRAY_SIZE(mxc_cea_mode))
+ return 0;
+
+ return i;
+}
+EXPORT_SYMBOL(mxc_edid_mode_to_vic);
+
+/* make sure edid has 512 bytes*/
+int mxc_edid_read(struct i2c_adapter *adp, unsigned short addr,
+ unsigned char *edid, struct mxc_edid_cfg *cfg, struct fb_info *fbi)
+{
+ int ret = 0, extblknum;
+ if (!adp || !edid || !cfg || !fbi)
+ return -EINVAL;
+
+ memset(edid, 0, EDID_LENGTH*4);
+ memset(cfg, 0, sizeof(struct mxc_edid_cfg));
+
+ extblknum = mxc_edid_readblk(adp, addr, edid);
+ if (extblknum < 0)
+ return extblknum;
+
+ /* edid first block parsing */
+ memset(&fbi->monspecs, 0, sizeof(fbi->monspecs));
+ fb_edid_to_monspecs(edid, &fbi->monspecs);
+
+ if (extblknum) {
+ int i;
+
+ /* need read segment block? */
+ if (extblknum > 1) {
+ ret = mxc_edid_readsegblk(adp, addr,
+ edid + EDID_LENGTH*2, extblknum - 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ for (i = 1; i <= extblknum; i++)
+ /* edid ext block parsing */
+ mxc_edid_parse_ext_blk(edid + i*EDID_LENGTH,
+ cfg, &fbi->monspecs);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_edid_read);
+
diff --git a/drivers/video/mxc/mxc_epdc_fb.c b/drivers/video/mxc/mxc_epdc_fb.c
new file mode 100644
index 000000000000..e536e781db9e
--- /dev/null
+++ b/drivers/video/mxc/mxc_epdc_fb.c
@@ -0,0 +1,5541 @@
+/*
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*
+ * Based on STMP378X LCDIF
+ * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
+ */
+
+#include <linux/busfreq-imx6.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/cpufreq.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+#include <linux/dmaengine.h>
+#include <linux/pxp_dma.h>
+#include <linux/pm_runtime.h>
+#include <linux/mxcfb.h>
+#include <linux/mxcfb_epdc.h>
+#include <linux/gpio.h>
+#include <linux/regulator/driver.h>
+#include <linux/fsl_devices.h>
+#include <linux/bitops.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_data/dma-imx.h>
+#include <asm/cacheflush.h>
+
+#include "epdc_regs.h"
+
+/*
+ * Enable this define to have a default panel
+ * loaded during driver initialization
+ */
+/*#define DEFAULT_PANEL_HW_INIT*/
+
+#define NUM_SCREENS_MIN 2
+
+#define EPDC_V1_NUM_LUTS 16
+#define EPDC_V1_MAX_NUM_UPDATES 20
+#define EPDC_V2_NUM_LUTS 64
+#define EPDC_V2_MAX_NUM_UPDATES 64
+#define EPDC_MAX_NUM_BUFFERS 2
+#define INVALID_LUT (-1)
+#define DRY_RUN_NO_LUT 100
+
+/* Maximum update buffer image width due to v2.0 and v2.1 errata ERR005313. */
+#define EPDC_V2_MAX_UPDATE_WIDTH 2047
+#define EPDC_V2_ROTATION_ALIGNMENT 8
+
+#define DEFAULT_TEMP_INDEX 0
+#define DEFAULT_TEMP 20 /* room temp in deg Celsius */
+
+#define INIT_UPDATE_MARKER 0x12345678
+#define PAN_UPDATE_MARKER 0x12345679
+
+#define POWER_STATE_OFF 0
+#define POWER_STATE_ON 1
+
+#define MERGE_OK 0
+#define MERGE_FAIL 1
+#define MERGE_BLOCK 2
+
+static unsigned long default_bpp = 16;
+
+struct update_marker_data {
+ struct list_head full_list;
+ struct list_head upd_list;
+ u32 update_marker;
+ struct completion update_completion;
+ int lut_num;
+ bool collision_test;
+ bool waiting;
+};
+
+struct update_desc_list {
+ struct list_head list;
+ struct mxcfb_update_data upd_data;/* Update parameters */
+ u32 epdc_offs; /* Added to buffer ptr to resolve alignment */
+ u32 epdc_stride; /* Depends on rotation & whether we skip PxP */
+ struct list_head upd_marker_list; /* List of markers for this update */
+ u32 update_order; /* Numeric ordering value for update */
+};
+
+/* This structure represents a list node containing both
+ * a memory region allocated as an output buffer for the PxP
+ * update processing task, and the update description (mode, region, etc.) */
+struct update_data_list {
+ struct list_head list;
+ dma_addr_t phys_addr; /* Pointer to phys address of processed Y buf */
+ void *virt_addr;
+ struct update_desc_list *update_desc;
+ int lut_num; /* Assigned before update is processed into working buffer */
+ u64 collision_mask; /* Set when update creates collision */
+ /* Mask of the LUTs the update collides with */
+};
+
+struct mxc_epdc_fb_data {
+ struct fb_info info;
+ struct fb_var_screeninfo epdc_fb_var; /* Internal copy of screeninfo
+ so we can sync changes to it */
+ u32 pseudo_palette[16];
+ char fw_str[24];
+ struct list_head list;
+ struct imx_epdc_fb_mode *cur_mode;
+ struct imx_epdc_fb_platform_data *pdata;
+ int blank;
+ u32 max_pix_size;
+ ssize_t map_size;
+ dma_addr_t phys_start;
+ u32 fb_offset;
+ int default_bpp;
+ int native_width;
+ int native_height;
+ int num_screens;
+ int epdc_irq;
+ struct device *dev;
+ int power_state;
+ int wait_for_powerdown;
+ struct completion powerdown_compl;
+ struct clk *epdc_clk_axi;
+ struct clk *epdc_clk_pix;
+ struct regulator *display_regulator;
+ struct regulator *vcom_regulator;
+ struct regulator *v3p3_regulator;
+ bool fw_default_load;
+ int rev;
+
+ /* FB elements related to EPDC updates */
+ int num_luts;
+ int max_num_updates;
+ bool in_init;
+ bool hw_ready;
+ bool hw_initializing;
+ bool waiting_for_idle;
+ u32 auto_mode;
+ u32 upd_scheme;
+ struct list_head upd_pending_list;
+ struct list_head upd_buf_queue;
+ struct list_head upd_buf_free_list;
+ struct list_head upd_buf_collision_list;
+ struct update_data_list *cur_update;
+ struct mutex queue_mutex;
+ int trt_entries;
+ int temp_index;
+ u8 *temp_range_bounds;
+ struct mxcfb_waveform_modes wv_modes;
+ bool wv_modes_update;
+ u32 *waveform_buffer_virt;
+ u32 waveform_buffer_phys;
+ u32 waveform_buffer_size;
+ u32 *working_buffer_virt;
+ u32 working_buffer_phys;
+ u32 working_buffer_size;
+ dma_addr_t *phys_addr_updbuf;
+ void **virt_addr_updbuf;
+ u32 upd_buffer_num;
+ u32 max_num_buffers;
+ dma_addr_t phys_addr_copybuf; /* Phys address of copied update data */
+ void *virt_addr_copybuf; /* Used for PxP SW workaround */
+ u32 order_cnt;
+ struct list_head full_marker_list;
+ u32 *lut_update_order; /* Array size = number of luts */
+ u64 epdc_colliding_luts;
+ u64 luts_complete_wb;
+ struct completion updates_done;
+ struct delayed_work epdc_done_work;
+ struct workqueue_struct *epdc_submit_workqueue;
+ struct work_struct epdc_submit_work;
+ struct workqueue_struct *epdc_intr_workqueue;
+ struct work_struct epdc_intr_work;
+ bool waiting_for_wb;
+ bool waiting_for_lut;
+ bool waiting_for_lut15;
+ struct completion update_res_free;
+ struct completion lut15_free;
+ struct completion eof_event;
+ int eof_sync_period;
+ struct mutex power_mutex;
+ bool powering_down;
+ bool updates_active;
+ int pwrdown_delay;
+ unsigned long tce_prevent;
+ bool restrict_width; /* work around rev >=2.0 width and
+ stride restriction */
+
+ /* FB elements related to PxP DMA */
+ struct completion pxp_tx_cmpl;
+ struct pxp_channel *pxp_chan;
+ struct pxp_config_data pxp_conf;
+ struct dma_async_tx_descriptor *txd;
+ dma_cookie_t cookie;
+ struct scatterlist sg[2];
+ struct mutex pxp_mutex; /* protects access to PxP */
+};
+
+struct waveform_data_header {
+ unsigned int wi0;
+ unsigned int wi1;
+ unsigned int wi2;
+ unsigned int wi3;
+ unsigned int wi4;
+ unsigned int wi5;
+ unsigned int wi6;
+ unsigned int xwia:24;
+ unsigned int cs1:8;
+ unsigned int wmta:24;
+ unsigned int fvsn:8;
+ unsigned int luts:8;
+ unsigned int mc:8;
+ unsigned int trc:8;
+ unsigned int reserved0_0:8;
+ unsigned int eb:8;
+ unsigned int sb:8;
+ unsigned int reserved0_1:8;
+ unsigned int reserved0_2:8;
+ unsigned int reserved0_3:8;
+ unsigned int reserved0_4:8;
+ unsigned int reserved0_5:8;
+ unsigned int cs2:8;
+};
+
+struct mxcfb_waveform_data_file {
+ struct waveform_data_header wdh;
+ u32 *data; /* Temperature Range Table + Waveform Data */
+};
+
+static struct fb_videomode e60_v110_mode = {
+ .name = "E60_V110",
+ .refresh = 50,
+ .xres = 800,
+ .yres = 600,
+ .pixclock = 18604700,
+ .left_margin = 8,
+ .right_margin = 178,
+ .upper_margin = 4,
+ .lower_margin = 10,
+ .hsync_len = 20,
+ .vsync_len = 4,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+};
+
+static struct fb_videomode e60_v220_mode = {
+ .name = "E60_V220",
+ .refresh = 85,
+ .xres = 800,
+ .yres = 600,
+ .pixclock = 30000000,
+ .left_margin = 8,
+ .right_margin = 164,
+ .upper_margin = 4,
+ .lower_margin = 8,
+ .hsync_len = 4,
+ .vsync_len = 1,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+};
+
+static struct fb_videomode e060scm_mode = {
+ .name = "E060SCM",
+ .refresh = 85,
+ .xres = 800,
+ .yres = 600,
+ .pixclock = 26666667,
+ .left_margin = 8,
+ .right_margin = 100,
+ .upper_margin = 4,
+ .lower_margin = 8,
+ .hsync_len = 4,
+ .vsync_len = 1,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+};
+
+static struct fb_videomode e97_v110_mode = {
+ .name = "E97_V110",
+ .refresh = 50,
+ .xres = 1200,
+ .yres = 825,
+ .pixclock = 32000000,
+ .left_margin = 12,
+ .right_margin = 128,
+ .upper_margin = 4,
+ .lower_margin = 10,
+ .hsync_len = 20,
+ .vsync_len = 4,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+};
+
+static struct imx_epdc_fb_mode panel_modes[] = {
+ {
+ &e60_v110_mode,
+ 4, /* vscan_holdoff */
+ 10, /* sdoed_width */
+ 20, /* sdoed_delay */
+ 10, /* sdoez_width */
+ 20, /* sdoez_delay */
+ 428, /* gdclk_hp_offs */
+ 20, /* gdsp_offs */
+ 0, /* gdoe_offs */
+ 1, /* gdclk_offs */
+ 1, /* num_ce */
+ },
+ {
+ &e60_v220_mode,
+ 4, /* vscan_holdoff */
+ 10, /* sdoed_width */
+ 20, /* sdoed_delay */
+ 10, /* sdoez_width */
+ 20, /* sdoez_delay */
+ 465, /* gdclk_hp_offs */
+ 20, /* gdsp_offs */
+ 0, /* gdoe_offs */
+ 9, /* gdclk_offs */
+ 1, /* num_ce */
+ },
+ {
+ &e060scm_mode,
+ 4, /* vscan_holdoff */
+ 10, /* sdoed_width */
+ 20, /* sdoed_delay */
+ 10, /* sdoez_width */
+ 20, /* sdoez_delay */
+ 419, /* gdclk_hp_offs */
+ 20, /* gdsp_offs */
+ 0, /* gdoe_offs */
+ 5, /* gdclk_offs */
+ 1, /* num_ce */
+ },
+ {
+ &e97_v110_mode,
+ 8, /* vscan_holdoff */
+ 10, /* sdoed_width */
+ 20, /* sdoed_delay */
+ 10, /* sdoez_width */
+ 20, /* sdoez_delay */
+ 632, /* gdclk_hp_offs */
+ 20, /* gdsp_offs */
+ 0, /* gdoe_offs */
+ 1, /* gdclk_offs */
+ 3, /* num_ce */
+ }
+};
+
+static struct imx_epdc_fb_platform_data epdc_data = {
+ .epdc_mode = panel_modes,
+ .num_modes = ARRAY_SIZE(panel_modes),
+};
+
+void __iomem *epdc_base;
+
+struct mxc_epdc_fb_data *g_fb_data;
+
+/* forward declaration */
+static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data,
+ int temp);
+static void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data);
+static int mxc_epdc_fb_blank(int blank, struct fb_info *info);
+static int mxc_epdc_fb_init_hw(struct fb_info *info);
+static int pxp_process_update(struct mxc_epdc_fb_data *fb_data,
+ u32 src_width, u32 src_height,
+ struct mxcfb_rect *update_region);
+static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat);
+
+static void draw_mode0(struct mxc_epdc_fb_data *fb_data);
+static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data);
+
+static void do_dithering_processing_Y1_v1_0(
+ unsigned char *update_region_ptr,
+ struct mxcfb_rect *update_region,
+ unsigned long update_region_stride,
+ int *err_dist);
+static void do_dithering_processing_Y4_v1_0(
+ unsigned char *update_region_ptr,
+ struct mxcfb_rect *update_region,
+ unsigned long update_region_stride,
+ int *err_dist);
+
+#ifdef DEBUG
+static void dump_pxp_config(struct mxc_epdc_fb_data *fb_data,
+ struct pxp_config_data *pxp_conf)
+{
+ dev_info(fb_data->dev, "S0 fmt 0x%x",
+ pxp_conf->s0_param.pixel_fmt);
+ dev_info(fb_data->dev, "S0 width 0x%x",
+ pxp_conf->s0_param.width);
+ dev_info(fb_data->dev, "S0 height 0x%x",
+ pxp_conf->s0_param.height);
+ dev_info(fb_data->dev, "S0 ckey 0x%x",
+ pxp_conf->s0_param.color_key);
+ dev_info(fb_data->dev, "S0 ckey en 0x%x",
+ pxp_conf->s0_param.color_key_enable);
+
+ dev_info(fb_data->dev, "OL0 combine en 0x%x",
+ pxp_conf->ol_param[0].combine_enable);
+ dev_info(fb_data->dev, "OL0 fmt 0x%x",
+ pxp_conf->ol_param[0].pixel_fmt);
+ dev_info(fb_data->dev, "OL0 width 0x%x",
+ pxp_conf->ol_param[0].width);
+ dev_info(fb_data->dev, "OL0 height 0x%x",
+ pxp_conf->ol_param[0].height);
+ dev_info(fb_data->dev, "OL0 ckey 0x%x",
+ pxp_conf->ol_param[0].color_key);
+ dev_info(fb_data->dev, "OL0 ckey en 0x%x",
+ pxp_conf->ol_param[0].color_key_enable);
+ dev_info(fb_data->dev, "OL0 alpha 0x%x",
+ pxp_conf->ol_param[0].global_alpha);
+ dev_info(fb_data->dev, "OL0 alpha en 0x%x",
+ pxp_conf->ol_param[0].global_alpha_enable);
+ dev_info(fb_data->dev, "OL0 local alpha en 0x%x",
+ pxp_conf->ol_param[0].local_alpha_enable);
+
+ dev_info(fb_data->dev, "Out fmt 0x%x",
+ pxp_conf->out_param.pixel_fmt);
+ dev_info(fb_data->dev, "Out width 0x%x",
+ pxp_conf->out_param.width);
+ dev_info(fb_data->dev, "Out height 0x%x",
+ pxp_conf->out_param.height);
+
+ dev_info(fb_data->dev,
+ "drect left 0x%x right 0x%x width 0x%x height 0x%x",
+ pxp_conf->proc_data.drect.left, pxp_conf->proc_data.drect.top,
+ pxp_conf->proc_data.drect.width,
+ pxp_conf->proc_data.drect.height);
+ dev_info(fb_data->dev,
+ "srect left 0x%x right 0x%x width 0x%x height 0x%x",
+ pxp_conf->proc_data.srect.left, pxp_conf->proc_data.srect.top,
+ pxp_conf->proc_data.srect.width,
+ pxp_conf->proc_data.srect.height);
+ dev_info(fb_data->dev, "Scaling en 0x%x", pxp_conf->proc_data.scaling);
+ dev_info(fb_data->dev, "HFlip en 0x%x", pxp_conf->proc_data.hflip);
+ dev_info(fb_data->dev, "VFlip en 0x%x", pxp_conf->proc_data.vflip);
+ dev_info(fb_data->dev, "Rotation 0x%x", pxp_conf->proc_data.rotate);
+ dev_info(fb_data->dev, "BG Color 0x%x", pxp_conf->proc_data.bgcolor);
+}
+
+static void dump_epdc_reg(void)
+{
+ printk(KERN_DEBUG "\n\n");
+ printk(KERN_DEBUG "EPDC_CTRL 0x%x\n", __raw_readl(EPDC_CTRL));
+ printk(KERN_DEBUG "EPDC_WVADDR 0x%x\n", __raw_readl(EPDC_WVADDR));
+ printk(KERN_DEBUG "EPDC_WB_ADDR 0x%x\n", __raw_readl(EPDC_WB_ADDR));
+ printk(KERN_DEBUG "EPDC_RES 0x%x\n", __raw_readl(EPDC_RES));
+ printk(KERN_DEBUG "EPDC_FORMAT 0x%x\n", __raw_readl(EPDC_FORMAT));
+ printk(KERN_DEBUG "EPDC_FIFOCTRL 0x%x\n", __raw_readl(EPDC_FIFOCTRL));
+ printk(KERN_DEBUG "EPDC_UPD_ADDR 0x%x\n", __raw_readl(EPDC_UPD_ADDR));
+ printk(KERN_DEBUG "EPDC_UPD_STRIDE 0x%x\n", __raw_readl(EPDC_UPD_STRIDE));
+ printk(KERN_DEBUG "EPDC_UPD_FIXED 0x%x\n", __raw_readl(EPDC_UPD_FIXED));
+ printk(KERN_DEBUG "EPDC_UPD_CORD 0x%x\n", __raw_readl(EPDC_UPD_CORD));
+ printk(KERN_DEBUG "EPDC_UPD_SIZE 0x%x\n", __raw_readl(EPDC_UPD_SIZE));
+ printk(KERN_DEBUG "EPDC_UPD_CTRL 0x%x\n", __raw_readl(EPDC_UPD_CTRL));
+ printk(KERN_DEBUG "EPDC_TEMP 0x%x\n", __raw_readl(EPDC_TEMP));
+ printk(KERN_DEBUG "EPDC_AUTOWV_LUT 0x%x\n", __raw_readl(EPDC_AUTOWV_LUT));
+ printk(KERN_DEBUG "EPDC_TCE_CTRL 0x%x\n", __raw_readl(EPDC_TCE_CTRL));
+ printk(KERN_DEBUG "EPDC_TCE_SDCFG 0x%x\n", __raw_readl(EPDC_TCE_SDCFG));
+ printk(KERN_DEBUG "EPDC_TCE_GDCFG 0x%x\n", __raw_readl(EPDC_TCE_GDCFG));
+ printk(KERN_DEBUG "EPDC_TCE_HSCAN1 0x%x\n", __raw_readl(EPDC_TCE_HSCAN1));
+ printk(KERN_DEBUG "EPDC_TCE_HSCAN2 0x%x\n", __raw_readl(EPDC_TCE_HSCAN2));
+ printk(KERN_DEBUG "EPDC_TCE_VSCAN 0x%x\n", __raw_readl(EPDC_TCE_VSCAN));
+ printk(KERN_DEBUG "EPDC_TCE_OE 0x%x\n", __raw_readl(EPDC_TCE_OE));
+ printk(KERN_DEBUG "EPDC_TCE_POLARITY 0x%x\n", __raw_readl(EPDC_TCE_POLARITY));
+ printk(KERN_DEBUG "EPDC_TCE_TIMING1 0x%x\n", __raw_readl(EPDC_TCE_TIMING1));
+ printk(KERN_DEBUG "EPDC_TCE_TIMING2 0x%x\n", __raw_readl(EPDC_TCE_TIMING2));
+ printk(KERN_DEBUG "EPDC_TCE_TIMING3 0x%x\n", __raw_readl(EPDC_TCE_TIMING3));
+ printk(KERN_DEBUG "EPDC_PIGEON_CTRL0 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL0));
+ printk(KERN_DEBUG "EPDC_PIGEON_CTRL1 0x%x\n", __raw_readl(EPDC_PIGEON_CTRL1));
+ printk(KERN_DEBUG "EPDC_IRQ_MASK1 0x%x\n", __raw_readl(EPDC_IRQ_MASK1));
+ printk(KERN_DEBUG "EPDC_IRQ_MASK2 0x%x\n", __raw_readl(EPDC_IRQ_MASK2));
+ printk(KERN_DEBUG "EPDC_IRQ1 0x%x\n", __raw_readl(EPDC_IRQ1));
+ printk(KERN_DEBUG "EPDC_IRQ2 0x%x\n", __raw_readl(EPDC_IRQ2));
+ printk(KERN_DEBUG "EPDC_IRQ_MASK 0x%x\n", __raw_readl(EPDC_IRQ_MASK));
+ printk(KERN_DEBUG "EPDC_IRQ 0x%x\n", __raw_readl(EPDC_IRQ));
+ printk(KERN_DEBUG "EPDC_STATUS_LUTS 0x%x\n", __raw_readl(EPDC_STATUS_LUTS));
+ printk(KERN_DEBUG "EPDC_STATUS_LUTS2 0x%x\n", __raw_readl(EPDC_STATUS_LUTS2));
+ printk(KERN_DEBUG "EPDC_STATUS_NEXTLUT 0x%x\n", __raw_readl(EPDC_STATUS_NEXTLUT));
+ printk(KERN_DEBUG "EPDC_STATUS_COL1 0x%x\n", __raw_readl(EPDC_STATUS_COL));
+ printk(KERN_DEBUG "EPDC_STATUS_COL2 0x%x\n", __raw_readl(EPDC_STATUS_COL2));
+ printk(KERN_DEBUG "EPDC_STATUS 0x%x\n", __raw_readl(EPDC_STATUS));
+ printk(KERN_DEBUG "EPDC_UPD_COL_CORD 0x%x\n", __raw_readl(EPDC_UPD_COL_CORD));
+ printk(KERN_DEBUG "EPDC_UPD_COL_SIZE 0x%x\n", __raw_readl(EPDC_UPD_COL_SIZE));
+ printk(KERN_DEBUG "EPDC_DEBUG 0x%x\n", __raw_readl(EPDC_DEBUG));
+ printk(KERN_DEBUG "EPDC_DEBUG_LUT 0x%x\n", __raw_readl(EPDC_DEBUG_LUT));
+ printk(KERN_DEBUG "EPDC_HIST1_PARAM 0x%x\n", __raw_readl(EPDC_HIST1_PARAM));
+ printk(KERN_DEBUG "EPDC_HIST2_PARAM 0x%x\n", __raw_readl(EPDC_HIST2_PARAM));
+ printk(KERN_DEBUG "EPDC_HIST4_PARAM 0x%x\n", __raw_readl(EPDC_HIST4_PARAM));
+ printk(KERN_DEBUG "EPDC_HIST8_PARAM0 0x%x\n", __raw_readl(EPDC_HIST8_PARAM0));
+ printk(KERN_DEBUG "EPDC_HIST8_PARAM1 0x%x\n", __raw_readl(EPDC_HIST8_PARAM1));
+ printk(KERN_DEBUG "EPDC_HIST16_PARAM0 0x%x\n", __raw_readl(EPDC_HIST16_PARAM0));
+ printk(KERN_DEBUG "EPDC_HIST16_PARAM1 0x%x\n", __raw_readl(EPDC_HIST16_PARAM1));
+ printk(KERN_DEBUG "EPDC_HIST16_PARAM2 0x%x\n", __raw_readl(EPDC_HIST16_PARAM2));
+ printk(KERN_DEBUG "EPDC_HIST16_PARAM3 0x%x\n", __raw_readl(EPDC_HIST16_PARAM3));
+ printk(KERN_DEBUG "EPDC_GPIO 0x%x\n", __raw_readl(EPDC_GPIO));
+ printk(KERN_DEBUG "EPDC_VERSION 0x%x\n", __raw_readl(EPDC_VERSION));
+ printk(KERN_DEBUG "\n\n");
+}
+
+static void dump_update_data(struct device *dev,
+ struct update_data_list *upd_data_list)
+{
+ dev_info(dev,
+ "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, "
+ "LUT = %d, Coll Mask = 0x%llx, order = %d\n",
+ upd_data_list->update_desc->upd_data.update_region.left,
+ upd_data_list->update_desc->upd_data.update_region.top,
+ upd_data_list->update_desc->upd_data.update_region.width,
+ upd_data_list->update_desc->upd_data.update_region.height,
+ upd_data_list->update_desc->upd_data.waveform_mode,
+ upd_data_list->lut_num,
+ upd_data_list->collision_mask,
+ upd_data_list->update_desc->update_order);
+}
+
+static void dump_collision_list(struct mxc_epdc_fb_data *fb_data)
+{
+ struct update_data_list *plist;
+
+ dev_info(fb_data->dev, "Collision List:\n");
+ if (list_empty(&fb_data->upd_buf_collision_list))
+ dev_info(fb_data->dev, "Empty");
+ list_for_each_entry(plist, &fb_data->upd_buf_collision_list, list) {
+ dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ",
+ (u32)plist->virt_addr, plist->phys_addr);
+ dump_update_data(fb_data->dev, plist);
+ }
+}
+
+static void dump_free_list(struct mxc_epdc_fb_data *fb_data)
+{
+ struct update_data_list *plist;
+
+ dev_info(fb_data->dev, "Free List:\n");
+ if (list_empty(&fb_data->upd_buf_free_list))
+ dev_info(fb_data->dev, "Empty");
+ list_for_each_entry(plist, &fb_data->upd_buf_free_list, list)
+ dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ",
+ (u32)plist->virt_addr, plist->phys_addr);
+}
+
+static void dump_queue(struct mxc_epdc_fb_data *fb_data)
+{
+ struct update_data_list *plist;
+
+ dev_info(fb_data->dev, "Queue:\n");
+ if (list_empty(&fb_data->upd_buf_queue))
+ dev_info(fb_data->dev, "Empty");
+ list_for_each_entry(plist, &fb_data->upd_buf_queue, list) {
+ dev_info(fb_data->dev, "Virt Addr = 0x%x, Phys Addr = 0x%x ",
+ (u32)plist->virt_addr, plist->phys_addr);
+ dump_update_data(fb_data->dev, plist);
+ }
+}
+
+static void dump_desc_data(struct device *dev,
+ struct update_desc_list *upd_desc_list)
+{
+ dev_info(dev,
+ "X = %d, Y = %d, Width = %d, Height = %d, WaveMode = %d, "
+ "order = %d\n",
+ upd_desc_list->upd_data.update_region.left,
+ upd_desc_list->upd_data.update_region.top,
+ upd_desc_list->upd_data.update_region.width,
+ upd_desc_list->upd_data.update_region.height,
+ upd_desc_list->upd_data.waveform_mode,
+ upd_desc_list->update_order);
+}
+
+static void dump_pending_list(struct mxc_epdc_fb_data *fb_data)
+{
+ struct update_desc_list *plist;
+
+ dev_info(fb_data->dev, "Queue:\n");
+ if (list_empty(&fb_data->upd_pending_list))
+ dev_info(fb_data->dev, "Empty");
+ list_for_each_entry(plist, &fb_data->upd_pending_list, list)
+ dump_desc_data(fb_data->dev, plist);
+}
+
+static void dump_all_updates(struct mxc_epdc_fb_data *fb_data)
+{
+ dump_free_list(fb_data);
+ dump_queue(fb_data);
+ dump_collision_list(fb_data);
+ dev_info(fb_data->dev, "Current update being processed:\n");
+ if (fb_data->cur_update == NULL)
+ dev_info(fb_data->dev, "No current update\n");
+ else
+ dump_update_data(fb_data->dev, fb_data->cur_update);
+}
+#else
+static inline void dump_pxp_config(struct mxc_epdc_fb_data *fb_data,
+ struct pxp_config_data *pxp_conf) {}
+static inline void dump_epdc_reg(void) {}
+static inline void dump_update_data(struct device *dev,
+ struct update_data_list *upd_data_list) {}
+static inline void dump_collision_list(struct mxc_epdc_fb_data *fb_data) {}
+static inline void dump_free_list(struct mxc_epdc_fb_data *fb_data) {}
+static inline void dump_queue(struct mxc_epdc_fb_data *fb_data) {}
+static inline void dump_all_updates(struct mxc_epdc_fb_data *fb_data) {}
+
+#endif
+
+
+/********************************************************
+ * Start Low-Level EPDC Functions
+ ********************************************************/
+
+static inline void epdc_lut_complete_intr(int rev, u32 lut_num, bool enable)
+{
+ if (rev < 20) {
+ if (enable)
+ __raw_writel(1 << lut_num, EPDC_IRQ_MASK_SET);
+ else
+ __raw_writel(1 << lut_num, EPDC_IRQ_MASK_CLEAR);
+ } else {
+ if (enable) {
+ if (lut_num < 32)
+ __raw_writel(1 << lut_num, EPDC_IRQ_MASK1_SET);
+ else
+ __raw_writel(1 << (lut_num - 32),
+ EPDC_IRQ_MASK2_SET);
+ } else {
+ if (lut_num < 32)
+ __raw_writel(1 << lut_num,
+ EPDC_IRQ_MASK1_CLEAR);
+ else
+ __raw_writel(1 << (lut_num - 32),
+ EPDC_IRQ_MASK2_CLEAR);
+ }
+ }
+}
+
+static inline void epdc_working_buf_intr(bool enable)
+{
+ if (enable)
+ __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_SET);
+ else
+ __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ, EPDC_IRQ_MASK_CLEAR);
+}
+
+static inline void epdc_clear_working_buf_irq(void)
+{
+ __raw_writel(EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ,
+ EPDC_IRQ_CLEAR);
+}
+
+static inline void epdc_eof_intr(bool enable)
+{
+ if (enable)
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_SET);
+ else
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_MASK_CLEAR);
+}
+
+static inline void epdc_clear_eof_irq(void)
+{
+ __raw_writel(EPDC_IRQ_FRAME_END_IRQ, EPDC_IRQ_CLEAR);
+}
+
+static inline bool epdc_signal_eof(void)
+{
+ return (__raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ)
+ & EPDC_IRQ_FRAME_END_IRQ) ? true : false;
+}
+
+static inline void epdc_set_temp(u32 temp)
+{
+ __raw_writel(temp, EPDC_TEMP);
+}
+
+static inline void epdc_set_screen_res(u32 width, u32 height)
+{
+ u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width;
+ __raw_writel(val, EPDC_RES);
+}
+
+static inline void epdc_set_update_addr(u32 addr)
+{
+ __raw_writel(addr, EPDC_UPD_ADDR);
+}
+
+static inline void epdc_set_update_coord(u32 x, u32 y)
+{
+ u32 val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x;
+ __raw_writel(val, EPDC_UPD_CORD);
+}
+
+static inline void epdc_set_update_dimensions(u32 width, u32 height)
+{
+ u32 val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width;
+ __raw_writel(val, EPDC_UPD_SIZE);
+}
+
+static void epdc_set_update_waveform(struct mxcfb_waveform_modes *wv_modes)
+{
+ u32 val;
+
+ /* Configure the auto-waveform look-up table based on waveform modes */
+
+ /* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */
+ val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (0 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+ val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (1 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+ val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (2 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+ val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (3 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+ val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (4 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+ val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (5 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+ __raw_writel(val, EPDC_AUTOWV_LUT);
+}
+
+static void epdc_set_update_stride(u32 stride)
+{
+ __raw_writel(stride, EPDC_UPD_STRIDE);
+}
+
+static void epdc_submit_update(u32 lut_num, u32 waveform_mode, u32 update_mode,
+ bool use_dry_run, bool use_test_mode, u32 np_val)
+{
+ u32 reg_val = 0;
+
+ if (use_test_mode) {
+ reg_val |=
+ ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) &
+ EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN;
+
+ __raw_writel(reg_val, EPDC_UPD_FIXED);
+
+ reg_val = EPDC_UPD_CTRL_USE_FIXED;
+ } else {
+ __raw_writel(reg_val, EPDC_UPD_FIXED);
+ }
+
+ if (waveform_mode == WAVEFORM_MODE_AUTO)
+ reg_val |= EPDC_UPD_CTRL_AUTOWV;
+ else
+ reg_val |= ((waveform_mode <<
+ EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) &
+ EPDC_UPD_CTRL_WAVEFORM_MODE_MASK);
+
+ reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) |
+ ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) &
+ EPDC_UPD_CTRL_LUT_SEL_MASK) |
+ update_mode;
+
+ __raw_writel(reg_val, EPDC_UPD_CTRL);
+}
+
+static inline bool epdc_is_lut_complete(int rev, u32 lut_num)
+{
+ u32 val;
+ bool is_compl;
+ if (rev < 20) {
+ val = __raw_readl(EPDC_IRQ);
+ is_compl = val & (1 << lut_num) ? true : false;
+ } else if (lut_num < 32) {
+ val = __raw_readl(EPDC_IRQ1);
+ is_compl = val & (1 << lut_num) ? true : false;
+ } else {
+ val = __raw_readl(EPDC_IRQ2);
+ is_compl = val & (1 << (lut_num - 32)) ? true : false;
+ }
+
+ return is_compl;
+}
+
+static inline void epdc_clear_lut_complete_irq(int rev, u32 lut_num)
+{
+ if (rev < 20)
+ __raw_writel(1 << lut_num, EPDC_IRQ_CLEAR);
+ else if (lut_num < 32)
+ __raw_writel(1 << lut_num, EPDC_IRQ1_CLEAR);
+ else
+ __raw_writel(1 << (lut_num - 32), EPDC_IRQ2_CLEAR);
+}
+
+static inline bool epdc_is_lut_active(u32 lut_num)
+{
+ u32 val;
+ bool is_active;
+
+ if (lut_num < 32) {
+ val = __raw_readl(EPDC_STATUS_LUTS);
+ is_active = val & (1 << lut_num) ? true : false;
+ } else {
+ val = __raw_readl(EPDC_STATUS_LUTS2);
+ is_active = val & (1 << (lut_num - 32)) ? true : false;
+ }
+
+ return is_active;
+}
+
+static inline bool epdc_any_luts_active(int rev)
+{
+ bool any_active;
+
+ if (rev < 20)
+ any_active = __raw_readl(EPDC_STATUS_LUTS) ? true : false;
+ else
+ any_active = (__raw_readl(EPDC_STATUS_LUTS) |
+ __raw_readl(EPDC_STATUS_LUTS2)) ? true : false;
+
+ return any_active;
+}
+
+static inline bool epdc_any_luts_available(void)
+{
+ bool luts_available =
+ (__raw_readl(EPDC_STATUS_NEXTLUT) &
+ EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false;
+ return luts_available;
+}
+
+static inline int epdc_get_next_lut(void)
+{
+ u32 val =
+ __raw_readl(EPDC_STATUS_NEXTLUT) &
+ EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK;
+ return val;
+}
+
+static int epdc_choose_next_lut(int rev, int *next_lut)
+{
+ u64 luts_status, unprocessed_luts, used_luts;
+ /* Available LUTs are reduced to 16 in 5-bit waveform mode */
+ bool format_p5n = ((__raw_readl(EPDC_FORMAT) &
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) ==
+ EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N);
+
+ luts_status = __raw_readl(EPDC_STATUS_LUTS);
+ if ((rev < 20) || format_p5n)
+ luts_status &= 0xFFFF;
+ else
+ luts_status |= ((u64)__raw_readl(EPDC_STATUS_LUTS2) << 32);
+
+ if (rev < 20) {
+ unprocessed_luts = __raw_readl(EPDC_IRQ) & 0xFFFF;
+ } else {
+ unprocessed_luts = __raw_readl(EPDC_IRQ1) |
+ ((u64)__raw_readl(EPDC_IRQ2) << 32);
+ if (format_p5n)
+ unprocessed_luts &= 0xFFFF;
+ }
+
+ /*
+ * Note on unprocessed_luts: There is a race condition
+ * where a LUT completes, but has not been processed by
+ * IRQ handler workqueue, and then a new update request
+ * attempts to use that LUT. We prevent that here by
+ * ensuring that the LUT we choose doesn't have its IRQ
+ * bit set (indicating it has completed but not yet been
+ * processed).
+ */
+ used_luts = luts_status | unprocessed_luts;
+
+ /*
+ * Selecting a LUT to minimize incidence of TCE Underrun Error
+ * --------------------------------------------------------
+ * We want to find the lowest order LUT that is of greater
+ * order than all other active LUTs. If highest order LUT
+ * is active, then we want to choose the lowest order
+ * available LUT.
+ *
+ * NOTE: For EPDC version 2.0 and later, TCE Underrun error
+ * bug is fixed, so it doesn't matter which LUT is used.
+ */
+
+ if ((rev < 20) || format_p5n) {
+ *next_lut = fls64(used_luts);
+ if (*next_lut > 15)
+ *next_lut = ffz(used_luts);
+ } else {
+ if ((u32)used_luts != ~0UL)
+ *next_lut = ffz((u32)used_luts);
+ else if ((u32)(used_luts >> 32) != ~0UL)
+ *next_lut = ffz((u32)(used_luts >> 32)) + 32;
+ else
+ *next_lut = INVALID_LUT;
+ }
+
+ if (used_luts & 0x8000)
+ return 1;
+ else
+ return 0;
+}
+
+static inline bool epdc_is_working_buffer_busy(void)
+{
+ u32 val = __raw_readl(EPDC_STATUS);
+ bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false;
+
+ return is_busy;
+}
+
+static inline bool epdc_is_working_buffer_complete(void)
+{
+ u32 val = __raw_readl(EPDC_IRQ);
+ bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false;
+
+ return is_compl;
+}
+
+static inline bool epdc_is_lut_cancelled(void)
+{
+ u32 val = __raw_readl(EPDC_STATUS);
+ bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false;
+
+ return is_void;
+}
+
+static inline bool epdc_is_collision(void)
+{
+ u32 val = __raw_readl(EPDC_IRQ);
+ return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false;
+}
+
+static inline u64 epdc_get_colliding_luts(int rev)
+{
+ u32 val = __raw_readl(EPDC_STATUS_COL);
+ if (rev >= 20)
+ val |= (u64)__raw_readl(EPDC_STATUS_COL2) << 32;
+ return val;
+}
+
+static void epdc_set_horizontal_timing(u32 horiz_start, u32 horiz_end,
+ u32 hsync_width, u32 hsync_line_length)
+{
+ u32 reg_val =
+ ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) &
+ EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK)
+ | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) &
+ EPDC_TCE_HSCAN1_LINE_SYNC_MASK);
+ __raw_writel(reg_val, EPDC_TCE_HSCAN1);
+
+ reg_val =
+ ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) &
+ EPDC_TCE_HSCAN2_LINE_BEGIN_MASK)
+ | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) &
+ EPDC_TCE_HSCAN2_LINE_END_MASK);
+ __raw_writel(reg_val, EPDC_TCE_HSCAN2);
+}
+
+static void epdc_set_vertical_timing(u32 vert_start, u32 vert_end,
+ u32 vsync_width)
+{
+ u32 reg_val =
+ ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) &
+ EPDC_TCE_VSCAN_FRAME_BEGIN_MASK)
+ | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) &
+ EPDC_TCE_VSCAN_FRAME_END_MASK)
+ | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) &
+ EPDC_TCE_VSCAN_FRAME_SYNC_MASK);
+ __raw_writel(reg_val, EPDC_TCE_VSCAN);
+}
+
+static void epdc_init_settings(struct mxc_epdc_fb_data *fb_data)
+{
+ struct imx_epdc_fb_mode *epdc_mode = fb_data->cur_mode;
+ struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var;
+ u32 reg_val;
+ int num_ce;
+ int i;
+
+ /* Enable clocks to access EPDC regs */
+ clk_prepare_enable(fb_data->epdc_clk_axi);
+ clk_prepare_enable(fb_data->epdc_clk_pix);
+
+ /* Reset */
+ __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_SET);
+ while (!(__raw_readl(EPDC_CTRL) & EPDC_CTRL_CLKGATE))
+ ;
+ __raw_writel(EPDC_CTRL_SFTRST, EPDC_CTRL_CLEAR);
+
+ /* Enable clock gating (clear to enable) */
+ __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR);
+ while (__raw_readl(EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE))
+ ;
+
+ /* EPDC_CTRL */
+ reg_val = __raw_readl(EPDC_CTRL);
+ reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK;
+ reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP;
+ reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK;
+ reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP;
+ __raw_writel(reg_val, EPDC_CTRL_SET);
+
+ /* EPDC_FORMAT - 2bit TFT and 4bit Buf pixel format */
+ reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT
+ | EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N
+ | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) &
+ EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK);
+ __raw_writel(reg_val, EPDC_FORMAT);
+
+ /* EPDC_FIFOCTRL (disabled) */
+ reg_val =
+ ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) &
+ EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK)
+ | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) &
+ EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK)
+ | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) &
+ EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK);
+ __raw_writel(reg_val, EPDC_FIFOCTRL);
+
+ /* EPDC_TEMP - Use default temp to get index */
+ epdc_set_temp(mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP));
+
+ /* EPDC_RES */
+ epdc_set_screen_res(epdc_mode->vmode->xres, epdc_mode->vmode->yres);
+
+ /* EPDC_AUTOWV_LUT */
+ /* Initialize all auto-wavefrom look-up values to 2 - GC16 */
+ for (i = 0; i < 8; i++)
+ __raw_writel((2 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+ (i << EPDC_AUTOWV_LUT_ADDR_OFFSET), EPDC_AUTOWV_LUT);
+
+ /*
+ * EPDC_TCE_CTRL
+ * VSCAN_HOLDOFF = 4
+ * VCOM_MODE = MANUAL
+ * VCOM_VAL = 0
+ * DDR_MODE = DISABLED
+ * LVDS_MODE_CE = DISABLED
+ * LVDS_MODE = DISABLED
+ * DUAL_SCAN = DISABLED
+ * SDDO_WIDTH = 8bit
+ * PIXELS_PER_SDCLK = 4
+ */
+ reg_val =
+ ((epdc_mode->vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) &
+ EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK)
+ | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4;
+ __raw_writel(reg_val, EPDC_TCE_CTRL);
+
+ /* EPDC_TCE_HSCAN */
+ epdc_set_horizontal_timing(screeninfo->left_margin,
+ screeninfo->right_margin,
+ screeninfo->hsync_len,
+ screeninfo->hsync_len);
+
+ /* EPDC_TCE_VSCAN */
+ epdc_set_vertical_timing(screeninfo->upper_margin,
+ screeninfo->lower_margin,
+ screeninfo->vsync_len);
+
+ /* EPDC_TCE_OE */
+ reg_val =
+ ((epdc_mode->sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) &
+ EPDC_TCE_OE_SDOED_WIDTH_MASK)
+ | ((epdc_mode->sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) &
+ EPDC_TCE_OE_SDOED_DLY_MASK)
+ | ((epdc_mode->sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) &
+ EPDC_TCE_OE_SDOEZ_WIDTH_MASK)
+ | ((epdc_mode->sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) &
+ EPDC_TCE_OE_SDOEZ_DLY_MASK);
+ __raw_writel(reg_val, EPDC_TCE_OE);
+
+ /* EPDC_TCE_TIMING1 */
+ __raw_writel(0x0, EPDC_TCE_TIMING1);
+
+ /* EPDC_TCE_TIMING2 */
+ reg_val =
+ ((epdc_mode->gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) &
+ EPDC_TCE_TIMING2_GDCLK_HP_MASK)
+ | ((epdc_mode->gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) &
+ EPDC_TCE_TIMING2_GDSP_OFFSET_MASK);
+ __raw_writel(reg_val, EPDC_TCE_TIMING2);
+
+ /* EPDC_TCE_TIMING3 */
+ reg_val =
+ ((epdc_mode->gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) &
+ EPDC_TCE_TIMING3_GDOE_OFFSET_MASK)
+ | ((epdc_mode->gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) &
+ EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK);
+ __raw_writel(reg_val, EPDC_TCE_TIMING3);
+
+ /*
+ * EPDC_TCE_SDCFG
+ * SDCLK_HOLD = 1
+ * SDSHR = 1
+ * NUM_CE = 1
+ * SDDO_REFORMAT = FLIP_PIXELS
+ * SDDO_INVERT = DISABLED
+ * PIXELS_PER_CE = display horizontal resolution
+ */
+ num_ce = epdc_mode->num_ce;
+ if (num_ce == 0)
+ num_ce = 1;
+ reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR
+ | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) &
+ EPDC_TCE_SDCFG_NUM_CE_MASK)
+ | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS
+ | ((epdc_mode->vmode->xres/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) &
+ EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK);
+ __raw_writel(reg_val, EPDC_TCE_SDCFG);
+
+ /*
+ * EPDC_TCE_GDCFG
+ * GDRL = 1
+ * GDOE_MODE = 0;
+ * GDSP_MODE = 0;
+ */
+ reg_val = EPDC_TCE_SDCFG_GDRL;
+ __raw_writel(reg_val, EPDC_TCE_GDCFG);
+
+ /*
+ * EPDC_TCE_POLARITY
+ * SDCE_POL = ACTIVE LOW
+ * SDLE_POL = ACTIVE HIGH
+ * SDOE_POL = ACTIVE HIGH
+ * GDOE_POL = ACTIVE HIGH
+ * GDSP_POL = ACTIVE LOW
+ */
+ reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH
+ | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH
+ | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH;
+ __raw_writel(reg_val, EPDC_TCE_POLARITY);
+
+ /* EPDC_IRQ_MASK */
+ __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_MASK);
+
+ /*
+ * EPDC_GPIO
+ * PWRCOM = ?
+ * PWRCTRL = ?
+ * BDR = ?
+ */
+ reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK)
+ | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK);
+ __raw_writel(reg_val, EPDC_GPIO);
+
+ __raw_writel(fb_data->waveform_buffer_phys, EPDC_WVADDR);
+ __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR);
+ __raw_writel(fb_data->working_buffer_phys, EPDC_WB_ADDR_TCE);
+
+ /* Disable clock */
+ clk_disable_unprepare(fb_data->epdc_clk_axi);
+ clk_disable_unprepare(fb_data->epdc_clk_pix);
+}
+
+static void epdc_powerup(struct mxc_epdc_fb_data *fb_data)
+{
+ int ret = 0;
+ mutex_lock(&fb_data->power_mutex);
+
+ /*
+ * If power down request is pending, clear
+ * powering_down to cancel the request.
+ */
+ if (fb_data->powering_down)
+ fb_data->powering_down = false;
+
+ if (fb_data->power_state == POWER_STATE_ON) {
+ mutex_unlock(&fb_data->power_mutex);
+ return;
+ }
+
+ dev_dbg(fb_data->dev, "EPDC Powerup\n");
+
+ fb_data->updates_active = true;
+
+ /* Enable the v3p3 regulator */
+ ret = regulator_enable(fb_data->v3p3_regulator);
+ if (IS_ERR((void *)ret)) {
+ dev_err(fb_data->dev, "Unable to enable V3P3 regulator."
+ "err = 0x%x\n", ret);
+ mutex_unlock(&fb_data->power_mutex);
+ return;
+ }
+
+ msleep(1);
+
+ pm_runtime_get_sync(fb_data->dev);
+
+ /* Enable clocks to EPDC */
+ clk_prepare_enable(fb_data->epdc_clk_axi);
+ clk_prepare_enable(fb_data->epdc_clk_pix);
+
+ __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_CLEAR);
+
+ /* Enable power to the EPD panel */
+ ret = regulator_enable(fb_data->display_regulator);
+ if (IS_ERR((void *)ret)) {
+ dev_err(fb_data->dev, "Unable to enable DISPLAY regulator."
+ "err = 0x%x\n", ret);
+ mutex_unlock(&fb_data->power_mutex);
+ return;
+ }
+ ret = regulator_enable(fb_data->vcom_regulator);
+ if (IS_ERR((void *)ret)) {
+ dev_err(fb_data->dev, "Unable to enable VCOM regulator."
+ "err = 0x%x\n", ret);
+ mutex_unlock(&fb_data->power_mutex);
+ return;
+ }
+
+ fb_data->power_state = POWER_STATE_ON;
+
+ mutex_unlock(&fb_data->power_mutex);
+}
+
+static void epdc_powerdown(struct mxc_epdc_fb_data *fb_data)
+{
+ mutex_lock(&fb_data->power_mutex);
+
+ /* If powering_down has been cleared, a powerup
+ * request is pre-empting this powerdown request.
+ */
+ if (!fb_data->powering_down
+ || (fb_data->power_state == POWER_STATE_OFF)) {
+ mutex_unlock(&fb_data->power_mutex);
+ return;
+ }
+
+ dev_dbg(fb_data->dev, "EPDC Powerdown\n");
+
+ /* Disable power to the EPD panel */
+ regulator_disable(fb_data->vcom_regulator);
+ regulator_disable(fb_data->display_regulator);
+
+ /* Disable clocks to EPDC */
+ __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET);
+ clk_disable_unprepare(fb_data->epdc_clk_pix);
+ clk_disable_unprepare(fb_data->epdc_clk_axi);
+
+ pm_runtime_put_sync_suspend(fb_data->dev);
+
+ /* turn off the V3p3 */
+ regulator_disable(fb_data->v3p3_regulator);
+
+ fb_data->power_state = POWER_STATE_OFF;
+ fb_data->powering_down = false;
+
+ if (fb_data->wait_for_powerdown) {
+ fb_data->wait_for_powerdown = false;
+ complete(&fb_data->powerdown_compl);
+ }
+
+ mutex_unlock(&fb_data->power_mutex);
+}
+
+static void epdc_init_sequence(struct mxc_epdc_fb_data *fb_data)
+{
+ /* Initialize EPDC, passing pointer to EPDC registers */
+ epdc_init_settings(fb_data);
+ fb_data->in_init = true;
+ epdc_powerup(fb_data);
+ draw_mode0(fb_data);
+ /* Force power down event */
+ fb_data->powering_down = true;
+ epdc_powerdown(fb_data);
+ fb_data->updates_active = false;
+}
+
+static int mxc_epdc_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ u32 len;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ if (offset < info->fix.smem_len) {
+ /* mapping framebuffer memory */
+ len = info->fix.smem_len - offset;
+ vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT;
+ } else
+ return -EINVAL;
+
+ len = PAGE_ALIGN(len);
+ if (vma->vm_end - vma->vm_start > len)
+ return -EINVAL;
+
+ /* make buffers bufferable */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ dev_dbg(info->device, "mmap remap_pfn_range failed\n");
+ return -ENOBUFS;
+ }
+
+ return 0;
+}
+
+static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+ chan &= 0xffff;
+ chan >>= 16 - bf->length;
+ return chan << bf->offset;
+}
+
+static int mxc_epdc_fb_setcolreg(u_int regno, u_int red, u_int green,
+ u_int blue, u_int transp, struct fb_info *info)
+{
+ 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 (info->var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+ switch (info->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 = info->pseudo_palette;
+
+ val = _chan_to_field(red, &info->var.red);
+ val |= _chan_to_field(green, &info->var.green);
+ val |= _chan_to_field(blue, &info->var.blue);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+
+ return ret;
+}
+
+static int mxc_epdc_fb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
+{
+ int count, index, r;
+ u16 *red, *green, *blue, *transp;
+ u16 trans = 0xffff;
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ int i;
+
+ dev_dbg(fb_data->dev, "setcmap\n");
+
+ if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) {
+ /* Only support an 8-bit, 256 entry lookup */
+ if (cmap->len != 256)
+ return 1;
+
+ mxc_epdc_fb_flush_updates(fb_data);
+
+ mutex_lock(&fb_data->pxp_mutex);
+ /*
+ * Store colormap in pxp_conf structure for later transmit
+ * to PxP during update process to convert gray pixels.
+ *
+ * Since red=blue=green for pseudocolor visuals, we can
+ * just use red values.
+ */
+ for (i = 0; i < 256; i++)
+ fb_data->pxp_conf.proc_data.lut_map[i] = cmap->red[i] & 0xFF;
+
+ fb_data->pxp_conf.proc_data.lut_map_updated = true;
+
+ mutex_unlock(&fb_data->pxp_mutex);
+ } else {
+ red = cmap->red;
+ green = cmap->green;
+ blue = cmap->blue;
+ transp = cmap->transp;
+ index = cmap->start;
+
+ for (count = 0; count < cmap->len; count++) {
+ if (transp)
+ trans = *transp++;
+ r = mxc_epdc_fb_setcolreg(index++, *red++, *green++, *blue++,
+ trans, info);
+ if (r != 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static void adjust_coordinates(u32 xres, u32 yres, u32 rotation,
+ struct mxcfb_rect *update_region, struct mxcfb_rect *adj_update_region)
+{
+ u32 temp;
+
+ /* If adj_update_region == NULL, pass result back in update_region */
+ /* If adj_update_region == valid, use it to pass back result */
+ if (adj_update_region)
+ switch (rotation) {
+ case FB_ROTATE_UR:
+ adj_update_region->top = update_region->top;
+ adj_update_region->left = update_region->left;
+ adj_update_region->width = update_region->width;
+ adj_update_region->height = update_region->height;
+ break;
+ case FB_ROTATE_CW:
+ adj_update_region->top = update_region->left;
+ adj_update_region->left = yres -
+ (update_region->top + update_region->height);
+ adj_update_region->width = update_region->height;
+ adj_update_region->height = update_region->width;
+ break;
+ case FB_ROTATE_UD:
+ adj_update_region->width = update_region->width;
+ adj_update_region->height = update_region->height;
+ adj_update_region->top = yres -
+ (update_region->top + update_region->height);
+ adj_update_region->left = xres -
+ (update_region->left + update_region->width);
+ break;
+ case FB_ROTATE_CCW:
+ adj_update_region->left = update_region->top;
+ adj_update_region->top = xres -
+ (update_region->left + update_region->width);
+ adj_update_region->width = update_region->height;
+ adj_update_region->height = update_region->width;
+ break;
+ }
+ else
+ switch (rotation) {
+ case FB_ROTATE_UR:
+ /* No adjustment needed */
+ break;
+ case FB_ROTATE_CW:
+ temp = update_region->top;
+ update_region->top = update_region->left;
+ update_region->left = yres -
+ (temp + update_region->height);
+ temp = update_region->width;
+ update_region->width = update_region->height;
+ update_region->height = temp;
+ break;
+ case FB_ROTATE_UD:
+ update_region->top = yres -
+ (update_region->top + update_region->height);
+ update_region->left = xres -
+ (update_region->left + update_region->width);
+ break;
+ case FB_ROTATE_CCW:
+ temp = update_region->left;
+ update_region->left = update_region->top;
+ update_region->top = xres -
+ (temp + update_region->width);
+ temp = update_region->width;
+ update_region->width = update_region->height;
+ update_region->height = temp;
+ break;
+ }
+}
+
+/*
+ * Set fixed framebuffer parameters based on variable settings.
+ *
+ * @param info framebuffer information pointer
+ */
+static int mxc_epdc_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;
+ if (var->grayscale)
+ fix->visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
+ else
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ypanstep = 1;
+
+ return 0;
+}
+
+/*
+ * This routine actually sets the video mode. It's in here where we
+ * the hardware state info->par and fix which can be affected by the
+ * change in par. For this driver it doesn't do much.
+ *
+ */
+static int mxc_epdc_fb_set_par(struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ struct pxp_config_data *pxp_conf = &fb_data->pxp_conf;
+ struct pxp_proc_data *proc_data = &pxp_conf->proc_data;
+ struct fb_var_screeninfo *screeninfo = &fb_data->info.var;
+ struct imx_epdc_fb_mode *epdc_modes = fb_data->pdata->epdc_mode;
+ int i;
+ int ret;
+ __u32 xoffset_old, yoffset_old;
+
+ /*
+ * Can't change the FB parameters until current updates have completed.
+ * This function returns when all active updates are done.
+ */
+ mxc_epdc_fb_flush_updates(fb_data);
+
+ mutex_lock(&fb_data->queue_mutex);
+ /*
+ * Set all screeninfo except for xoffset/yoffset
+ * Subsequent call to pan_display will handle those.
+ */
+ xoffset_old = fb_data->epdc_fb_var.xoffset;
+ yoffset_old = fb_data->epdc_fb_var.yoffset;
+ fb_data->epdc_fb_var = *screeninfo;
+ fb_data->epdc_fb_var.xoffset = xoffset_old;
+ fb_data->epdc_fb_var.yoffset = yoffset_old;
+ mutex_unlock(&fb_data->queue_mutex);
+
+ mutex_lock(&fb_data->pxp_mutex);
+
+ /*
+ * Update PxP config data (used to process FB regions for updates)
+ * based on FB info and processing tasks required
+ */
+
+ /* Initialize non-channel-specific PxP parameters */
+ proc_data->drect.left = proc_data->srect.left = 0;
+ proc_data->drect.top = proc_data->srect.top = 0;
+ proc_data->drect.width = proc_data->srect.width = screeninfo->xres;
+ proc_data->drect.height = proc_data->srect.height = screeninfo->yres;
+ proc_data->scaling = 0;
+ proc_data->hflip = 0;
+ proc_data->vflip = 0;
+ proc_data->rotate = screeninfo->rotate;
+ proc_data->bgcolor = 0;
+ proc_data->overlay_state = 0;
+ proc_data->lut_transform = PXP_LUT_NONE;
+
+ /*
+ * configure S0 channel parameters
+ * Parameters should match FB format/width/height
+ */
+ if (screeninfo->grayscale)
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_GREY;
+ else {
+ switch (screeninfo->bits_per_pixel) {
+ case 16:
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+ break;
+ case 24:
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB24;
+ break;
+ case 32:
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB32;
+ break;
+ default:
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+ break;
+ }
+ }
+ pxp_conf->s0_param.width = screeninfo->xres_virtual;
+ pxp_conf->s0_param.height = screeninfo->yres;
+ pxp_conf->s0_param.color_key = -1;
+ pxp_conf->s0_param.color_key_enable = false;
+
+ /*
+ * Initialize Output channel parameters
+ * Output is Y-only greyscale
+ * Output width/height will vary based on update region size
+ */
+ pxp_conf->out_param.width = screeninfo->xres;
+ pxp_conf->out_param.height = screeninfo->yres;
+ pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY;
+
+ mutex_unlock(&fb_data->pxp_mutex);
+
+ /*
+ * If HW not yet initialized, check to see if we are being sent
+ * an initialization request.
+ */
+ if (!fb_data->hw_ready) {
+ struct fb_videomode mode;
+ u32 xres_temp;
+
+ fb_var_to_videomode(&mode, screeninfo);
+
+ /* When comparing requested fb mode,
+ we need to use unrotated dimensions */
+ if ((screeninfo->rotate == FB_ROTATE_CW) ||
+ (screeninfo->rotate == FB_ROTATE_CCW)) {
+ xres_temp = mode.xres;
+ mode.xres = mode.yres;
+ mode.yres = xres_temp;
+ }
+
+ /*
+ * If requested video mode does not match current video
+ * mode, search for a matching panel.
+ */
+ if (fb_data->cur_mode &&
+ !fb_mode_is_equal(fb_data->cur_mode->vmode,
+ &mode)) {
+ bool found_match = false;
+
+ /* Match videomode against epdc modes */
+ for (i = 0; i < fb_data->pdata->num_modes; i++) {
+ if (!fb_mode_is_equal(epdc_modes[i].vmode,
+ &mode))
+ continue;
+ fb_data->cur_mode = &epdc_modes[i];
+ found_match = true;
+ break;
+ }
+
+ if (!found_match) {
+ dev_err(fb_data->dev,
+ "Failed to match requested "
+ "video mode\n");
+ return EINVAL;
+ }
+ }
+
+ /* Found a match - Grab timing params */
+ screeninfo->left_margin = mode.left_margin;
+ screeninfo->right_margin = mode.right_margin;
+ screeninfo->upper_margin = mode.upper_margin;
+ screeninfo->lower_margin = mode.lower_margin;
+ screeninfo->hsync_len = mode.hsync_len;
+ screeninfo->vsync_len = mode.vsync_len;
+
+ fb_data->hw_initializing = true;
+
+ /* Initialize EPDC settings and init panel */
+ ret =
+ mxc_epdc_fb_init_hw((struct fb_info *)fb_data);
+ if (ret) {
+ dev_err(fb_data->dev,
+ "Failed to load panel waveform data\n");
+ return ret;
+ }
+ }
+
+ /*
+ * EOF sync delay (in us) should be equal to the vscan holdoff time
+ * VSCAN_HOLDOFF time = (VSCAN_HOLDOFF value + 1) * Vertical lines
+ * Add 25us for additional margin
+ */
+ fb_data->eof_sync_period = (fb_data->cur_mode->vscan_holdoff + 1) *
+ 1000000/(fb_data->cur_mode->vmode->refresh *
+ (fb_data->cur_mode->vmode->upper_margin +
+ fb_data->cur_mode->vmode->yres +
+ fb_data->cur_mode->vmode->lower_margin +
+ fb_data->cur_mode->vmode->vsync_len)) + 25;
+
+ mxc_epdc_fb_set_fix(info);
+
+ return 0;
+}
+
+static int mxc_epdc_fb_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+
+ if (!var->xres)
+ var->xres = 1;
+ if (!var->yres)
+ var->yres = 1;
+
+ if (var->xres_virtual < var->xoffset + var->xres)
+ var->xres_virtual = var->xoffset + var->xres;
+ if (var->yres_virtual < var->yoffset + var->yres)
+ var->yres_virtual = var->yoffset + var->yres;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+ (var->bits_per_pixel != 16) && (var->bits_per_pixel != 8))
+ var->bits_per_pixel = default_bpp;
+
+ switch (var->bits_per_pixel) {
+ case 8:
+ if (var->grayscale != 0) {
+ /*
+ * For 8-bit grayscale, R, G, and B offset are equal.
+ *
+ */
+ var->red.length = 8;
+ var->red.offset = 0;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 0;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ } else {
+ var->red.length = 3;
+ var->red.offset = 5;
+ var->red.msb_right = 0;
+
+ var->green.length = 3;
+ var->green.offset = 2;
+ var->green.msb_right = 0;
+
+ var->blue.length = 2;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ }
+ break;
+ case 16:
+ var->red.length = 5;
+ var->red.offset = 11;
+ var->red.msb_right = 0;
+
+ var->green.length = 6;
+ var->green.offset = 5;
+ var->green.msb_right = 0;
+
+ var->blue.length = 5;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 24:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 32:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 8;
+ var->transp.offset = 24;
+ var->transp.msb_right = 0;
+ break;
+ }
+
+ switch (var->rotate) {
+ case FB_ROTATE_UR:
+ case FB_ROTATE_UD:
+ var->xres = fb_data->native_width;
+ var->yres = fb_data->native_height;
+ break;
+ case FB_ROTATE_CW:
+ case FB_ROTATE_CCW:
+ var->xres = fb_data->native_height;
+ var->yres = fb_data->native_width;
+ break;
+ default:
+ /* Invalid rotation value */
+ var->rotate = 0;
+ dev_dbg(fb_data->dev, "Invalid rotation request\n");
+ return -EINVAL;
+ }
+
+ var->xres_virtual = ALIGN(var->xres, 32);
+ var->yres_virtual = ALIGN(var->yres, 128) * fb_data->num_screens;
+
+ var->height = -1;
+ var->width = -1;
+
+ return 0;
+}
+
+void mxc_epdc_fb_set_waveform_modes(struct mxcfb_waveform_modes *modes,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ mutex_lock(&fb_data->queue_mutex);
+
+ memcpy(&fb_data->wv_modes, modes, sizeof(struct mxcfb_waveform_modes));
+
+ /* Set flag to ensure that new waveform modes
+ * are programmed into EPDC before next update */
+ fb_data->wv_modes_update = true;
+
+ mutex_unlock(&fb_data->queue_mutex);
+}
+EXPORT_SYMBOL(mxc_epdc_fb_set_waveform_modes);
+
+static int mxc_epdc_fb_get_temp_index(struct mxc_epdc_fb_data *fb_data, int temp)
+{
+ int i;
+ int index = -1;
+
+ if (fb_data->trt_entries == 0) {
+ dev_err(fb_data->dev,
+ "No TRT exists...using default temp index\n");
+ return DEFAULT_TEMP_INDEX;
+ }
+
+ /* Search temperature ranges for a match */
+ for (i = 0; i < fb_data->trt_entries - 1; i++) {
+ if ((temp >= fb_data->temp_range_bounds[i])
+ && (temp < fb_data->temp_range_bounds[i+1])) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index < 0) {
+ dev_err(fb_data->dev,
+ "No TRT index match...using default temp index\n");
+ return DEFAULT_TEMP_INDEX;
+ }
+
+ dev_dbg(fb_data->dev, "Using temperature index %d\n", index);
+
+ return index;
+}
+
+int mxc_epdc_fb_set_temperature(int temperature, struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ /* Store temp index. Used later when configuring updates. */
+ mutex_lock(&fb_data->queue_mutex);
+ fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, temperature);
+ mutex_unlock(&fb_data->queue_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_epdc_fb_set_temperature);
+
+int mxc_epdc_fb_set_auto_update(u32 auto_mode, struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ dev_dbg(fb_data->dev, "Setting auto update mode to %d\n", auto_mode);
+
+ if ((auto_mode == AUTO_UPDATE_MODE_AUTOMATIC_MODE)
+ || (auto_mode == AUTO_UPDATE_MODE_REGION_MODE))
+ fb_data->auto_mode = auto_mode;
+ else {
+ dev_err(fb_data->dev, "Invalid auto update mode parameter.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_epdc_fb_set_auto_update);
+
+int mxc_epdc_fb_set_upd_scheme(u32 upd_scheme, struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ dev_dbg(fb_data->dev, "Setting optimization level to %d\n", upd_scheme);
+
+ /*
+ * Can't change the scheme until current updates have completed.
+ * This function returns when all active updates are done.
+ */
+ mxc_epdc_fb_flush_updates(fb_data);
+
+ if ((upd_scheme == UPDATE_SCHEME_SNAPSHOT)
+ || (upd_scheme == UPDATE_SCHEME_QUEUE)
+ || (upd_scheme == UPDATE_SCHEME_QUEUE_AND_MERGE))
+ fb_data->upd_scheme = upd_scheme;
+ else {
+ dev_err(fb_data->dev, "Invalid update scheme specified.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_epdc_fb_set_upd_scheme);
+
+static void copy_before_process(struct mxc_epdc_fb_data *fb_data,
+ struct update_data_list *upd_data_list)
+{
+ struct mxcfb_update_data *upd_data =
+ &upd_data_list->update_desc->upd_data;
+ int i;
+ unsigned char *temp_buf_ptr = fb_data->virt_addr_copybuf;
+ unsigned char *src_ptr;
+ struct mxcfb_rect *src_upd_region;
+ int temp_buf_stride;
+ int src_stride;
+ int bpp = fb_data->epdc_fb_var.bits_per_pixel;
+ int left_offs, right_offs;
+ int x_trailing_bytes, y_trailing_bytes;
+ int alt_buf_offset;
+
+ /* Set source buf pointer based on input source, panning, etc. */
+ if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) {
+ src_upd_region = &upd_data->alt_buffer_data.alt_update_region;
+ src_stride =
+ upd_data->alt_buffer_data.width * bpp/8;
+ alt_buf_offset = upd_data->alt_buffer_data.phys_addr -
+ fb_data->info.fix.smem_start;
+ src_ptr = fb_data->info.screen_base + alt_buf_offset
+ + src_upd_region->top * src_stride;
+ } else {
+ src_upd_region = &upd_data->update_region;
+ src_stride = fb_data->epdc_fb_var.xres_virtual * bpp/8;
+ src_ptr = fb_data->info.screen_base + fb_data->fb_offset
+ + src_upd_region->top * src_stride;
+ }
+
+ temp_buf_stride = ALIGN(src_upd_region->width, 8) * bpp/8;
+ left_offs = src_upd_region->left * bpp/8;
+ right_offs = src_upd_region->width * bpp/8;
+ x_trailing_bytes = (ALIGN(src_upd_region->width, 8)
+ - src_upd_region->width) * bpp/8;
+
+ for (i = 0; i < src_upd_region->height; i++) {
+ /* Copy the full line */
+ memcpy(temp_buf_ptr, src_ptr + left_offs,
+ src_upd_region->width * bpp/8);
+
+ /* Clear any unwanted pixels at the end of each line */
+ if (src_upd_region->width & 0x7) {
+ memset(temp_buf_ptr + right_offs, 0x0,
+ x_trailing_bytes);
+ }
+
+ temp_buf_ptr += temp_buf_stride;
+ src_ptr += src_stride;
+ }
+
+ /* Clear any unwanted pixels at the bottom of the end of each line */
+ if (src_upd_region->height & 0x7) {
+ y_trailing_bytes = (ALIGN(src_upd_region->height, 8)
+ - src_upd_region->height) *
+ ALIGN(src_upd_region->width, 8) * bpp/8;
+ memset(temp_buf_ptr, 0x0, y_trailing_bytes);
+ }
+}
+
+static int epdc_process_update(struct update_data_list *upd_data_list,
+ struct mxc_epdc_fb_data *fb_data)
+{
+ struct mxcfb_rect *src_upd_region; /* Region of src buffer for update */
+ struct mxcfb_rect pxp_upd_region;
+ u32 src_width, src_height;
+ u32 offset_from_4, bytes_per_pixel;
+ u32 post_rotation_xcoord, post_rotation_ycoord, width_pxp_blocks;
+ u32 pxp_input_offs, pxp_output_offs, pxp_output_shift;
+ u32 hist_stat = 0;
+ int width_unaligned, height_unaligned;
+ bool input_unaligned = false;
+ bool line_overflow = false;
+ int pix_per_line_added;
+ bool use_temp_buf = false;
+ struct mxcfb_rect temp_buf_upd_region;
+ struct update_desc_list *upd_desc_list = upd_data_list->update_desc;
+
+ int ret;
+
+ /*
+ * Gotta do a whole bunch of buffer ptr manipulation to
+ * work around HW restrictions for PxP & EPDC
+ * Note: Applies to pre-2.0 versions of EPDC/PxP
+ */
+
+ /*
+ * Are we using FB or an alternate (overlay)
+ * buffer for source of update?
+ */
+ if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER) {
+ src_width = upd_desc_list->upd_data.alt_buffer_data.width;
+ src_height = upd_desc_list->upd_data.alt_buffer_data.height;
+ src_upd_region = &upd_desc_list->upd_data.alt_buffer_data.alt_update_region;
+ } else {
+ src_width = fb_data->epdc_fb_var.xres_virtual;
+ src_height = fb_data->epdc_fb_var.yres;
+ src_upd_region = &upd_desc_list->upd_data.update_region;
+ }
+
+ bytes_per_pixel = fb_data->epdc_fb_var.bits_per_pixel/8;
+
+ /*
+ * SW workaround for PxP limitation (for pre-v2.0 HW)
+ *
+ * There are 3 cases where we cannot process the update data
+ * directly from the input buffer:
+ *
+ * 1) PxP must process 8x8 pixel blocks, and all pixels in each block
+ * are considered for auto-waveform mode selection. If the
+ * update region is not 8x8 aligned, additional unwanted pixels
+ * will be considered in auto-waveform mode selection.
+ *
+ * 2) PxP input must be 32-bit aligned, so any update
+ * address not 32-bit aligned must be shifted to meet the
+ * 32-bit alignment. The PxP will thus end up processing pixels
+ * outside of the update region to satisfy this alignment restriction,
+ * which can affect auto-waveform mode selection.
+ *
+ * 3) If input fails 32-bit alignment, and the resulting expansion
+ * of the processed region would add at least 8 pixels more per
+ * line than the original update line width, the EPDC would
+ * cause screen artifacts by incorrectly handling the 8+ pixels
+ * at the end of each line.
+ *
+ * Workaround is to copy from source buffer into a temporary
+ * buffer, which we pad with zeros to match the 8x8 alignment
+ * requirement. This temp buffer becomes the input to the PxP.
+ */
+ width_unaligned = src_upd_region->width & 0x7;
+ height_unaligned = src_upd_region->height & 0x7;
+
+ offset_from_4 = src_upd_region->left & 0x3;
+ input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ?
+ true : false;
+
+ pix_per_line_added = (offset_from_4 * bytes_per_pixel % 4)
+ / bytes_per_pixel;
+ if ((((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) ||
+ fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) &&
+ (ALIGN(src_upd_region->width, 8) <
+ ALIGN(src_upd_region->width + pix_per_line_added, 8)))
+ line_overflow = true;
+
+ /* Grab pxp_mutex here so that we protect access
+ * to copybuf in addition to the PxP structures */
+ mutex_lock(&fb_data->pxp_mutex);
+
+ if (((((width_unaligned || height_unaligned || input_unaligned) &&
+ (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO))
+ || line_overflow) && (fb_data->rev < 20)) ||
+ fb_data->restrict_width) {
+ dev_dbg(fb_data->dev, "Copying update before processing.\n");
+
+ /* Update to reflect what the new source buffer will be */
+ src_width = ALIGN(src_upd_region->width, 8);
+ src_height = ALIGN(src_upd_region->height, 8);
+
+ copy_before_process(fb_data, upd_data_list);
+
+ /*
+ * src_upd_region should now describe
+ * the new update buffer attributes.
+ */
+ temp_buf_upd_region.left = 0;
+ temp_buf_upd_region.top = 0;
+ temp_buf_upd_region.width = src_upd_region->width;
+ temp_buf_upd_region.height = src_upd_region->height;
+ src_upd_region = &temp_buf_upd_region;
+
+ use_temp_buf = true;
+ }
+
+ /*
+ * For pre-2.0 HW, input address must be 32-bit aligned
+ * Compute buffer offset to account for this PxP limitation
+ */
+ offset_from_4 = src_upd_region->left & 0x3;
+ input_unaligned = ((offset_from_4 * bytes_per_pixel % 4) != 0) ?
+ true : false;
+ if ((fb_data->rev < 20) && input_unaligned) {
+ /* Leave a gap between PxP input addr and update region pixels */
+ pxp_input_offs =
+ (src_upd_region->top * src_width + src_upd_region->left)
+ * bytes_per_pixel & 0xFFFFFFFC;
+ /* Update region left changes to reflect relative position to input ptr */
+ pxp_upd_region.left = (offset_from_4 * bytes_per_pixel % 4)
+ / bytes_per_pixel;
+ } else {
+ pxp_input_offs =
+ (src_upd_region->top * src_width + src_upd_region->left)
+ * bytes_per_pixel;
+ pxp_upd_region.left = 0;
+ }
+
+ pxp_upd_region.top = 0;
+
+ /*
+ * For version 2.0 and later of EPDC & PxP, if no rotation, we don't
+ * need to align width & height (rotation always requires 8-pixel
+ * width & height alignment, per PxP limitations)
+ */
+ if ((fb_data->epdc_fb_var.rotate == 0) && (fb_data->rev >= 20)) {
+ pxp_upd_region.width = src_upd_region->width;
+ pxp_upd_region.height = src_upd_region->height;
+ } else {
+ /* Update region dimensions to meet 8x8 pixel requirement */
+ pxp_upd_region.width = ALIGN(src_upd_region->width + pxp_upd_region.left, 8);
+ pxp_upd_region.height = ALIGN(src_upd_region->height, 8);
+ }
+
+ switch (fb_data->epdc_fb_var.rotate) {
+ case FB_ROTATE_UR:
+ default:
+ post_rotation_xcoord = pxp_upd_region.left;
+ post_rotation_ycoord = pxp_upd_region.top;
+ width_pxp_blocks = pxp_upd_region.width;
+ break;
+ case FB_ROTATE_CW:
+ width_pxp_blocks = pxp_upd_region.height;
+ post_rotation_xcoord = width_pxp_blocks - src_upd_region->height;
+ post_rotation_ycoord = pxp_upd_region.left;
+ break;
+ case FB_ROTATE_UD:
+ width_pxp_blocks = pxp_upd_region.width;
+ post_rotation_xcoord = width_pxp_blocks - src_upd_region->width - pxp_upd_region.left;
+ post_rotation_ycoord = pxp_upd_region.height - src_upd_region->height - pxp_upd_region.top;
+ break;
+ case FB_ROTATE_CCW:
+ width_pxp_blocks = pxp_upd_region.height;
+ post_rotation_xcoord = pxp_upd_region.top;
+ post_rotation_ycoord = pxp_upd_region.width - src_upd_region->width - pxp_upd_region.left;
+ break;
+ }
+
+ /* Update region start coord to force PxP to process full 8x8 regions */
+ pxp_upd_region.top &= ~0x7;
+ pxp_upd_region.left &= ~0x7;
+
+ if (fb_data->rev < 20) {
+ pxp_output_shift = ALIGN(post_rotation_xcoord, 8)
+ - post_rotation_xcoord;
+
+ pxp_output_offs = post_rotation_ycoord * width_pxp_blocks
+ + pxp_output_shift;
+
+ upd_desc_list->epdc_offs = ALIGN(pxp_output_offs, 8);
+ } else {
+ pxp_output_shift = 0;
+ pxp_output_offs = post_rotation_ycoord * width_pxp_blocks
+ + post_rotation_xcoord;
+
+ upd_desc_list->epdc_offs = pxp_output_offs;
+ }
+
+ upd_desc_list->epdc_stride = width_pxp_blocks;
+
+ /* Source address either comes from alternate buffer
+ provided in update data, or from the framebuffer. */
+ if (use_temp_buf)
+ sg_dma_address(&fb_data->sg[0]) =
+ fb_data->phys_addr_copybuf;
+ else if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_ALT_BUFFER)
+ sg_dma_address(&fb_data->sg[0]) =
+ upd_desc_list->upd_data.alt_buffer_data.phys_addr
+ + pxp_input_offs;
+ else {
+ sg_dma_address(&fb_data->sg[0]) =
+ fb_data->info.fix.smem_start + fb_data->fb_offset
+ + pxp_input_offs;
+ sg_set_page(&fb_data->sg[0],
+ virt_to_page(fb_data->info.screen_base),
+ fb_data->info.fix.smem_len,
+ offset_in_page(fb_data->info.screen_base));
+ }
+
+ /* Update sg[1] to point to output of PxP proc task */
+ sg_dma_address(&fb_data->sg[1]) = upd_data_list->phys_addr
+ + pxp_output_shift;
+ sg_set_page(&fb_data->sg[1], virt_to_page(upd_data_list->virt_addr),
+ fb_data->max_pix_size,
+ offset_in_page(upd_data_list->virt_addr));
+
+ /*
+ * Set PxP LUT transform type based on update flags.
+ */
+ fb_data->pxp_conf.proc_data.lut_transform = 0;
+ if (upd_desc_list->upd_data.flags & EPDC_FLAG_ENABLE_INVERSION)
+ fb_data->pxp_conf.proc_data.lut_transform |= PXP_LUT_INVERT;
+ if (upd_desc_list->upd_data.flags & EPDC_FLAG_FORCE_MONOCHROME)
+ fb_data->pxp_conf.proc_data.lut_transform |=
+ PXP_LUT_BLACK_WHITE;
+ if (upd_desc_list->upd_data.flags & EPDC_FLAG_USE_CMAP)
+ fb_data->pxp_conf.proc_data.lut_transform |=
+ PXP_LUT_USE_CMAP;
+
+ /*
+ * Toggle inversion processing if 8-bit
+ * inverted is the current pixel format.
+ */
+ if (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT_INVERTED)
+ fb_data->pxp_conf.proc_data.lut_transform ^= PXP_LUT_INVERT;
+
+ /* This is a blocking call, so upon return PxP tx should be done */
+ ret = pxp_process_update(fb_data, src_width, src_height,
+ &pxp_upd_region);
+ if (ret) {
+ dev_err(fb_data->dev, "Unable to submit PxP update task.\n");
+ mutex_unlock(&fb_data->pxp_mutex);
+ return ret;
+ }
+
+ /* If needed, enable EPDC HW while ePxP is processing */
+ if ((fb_data->power_state == POWER_STATE_OFF)
+ || fb_data->powering_down) {
+ epdc_powerup(fb_data);
+ }
+
+ /* This is a blocking call, so upon return PxP tx should be done */
+ ret = pxp_complete_update(fb_data, &hist_stat);
+ if (ret) {
+ dev_err(fb_data->dev, "Unable to complete PxP update task.\n");
+ mutex_unlock(&fb_data->pxp_mutex);
+ return ret;
+ }
+
+ mutex_unlock(&fb_data->pxp_mutex);
+
+ /* Update waveform mode from PxP histogram results */
+ if ((fb_data->rev <= 20) &&
+ (upd_desc_list->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) {
+ if (hist_stat & 0x1)
+ upd_desc_list->upd_data.waveform_mode =
+ fb_data->wv_modes.mode_du;
+ else if (hist_stat & 0x2)
+ upd_desc_list->upd_data.waveform_mode =
+ fb_data->wv_modes.mode_gc4;
+ else if (hist_stat & 0x4)
+ upd_desc_list->upd_data.waveform_mode =
+ fb_data->wv_modes.mode_gc8;
+ else if (hist_stat & 0x8)
+ upd_desc_list->upd_data.waveform_mode =
+ fb_data->wv_modes.mode_gc16;
+ else
+ upd_desc_list->upd_data.waveform_mode =
+ fb_data->wv_modes.mode_gc32;
+
+ dev_dbg(fb_data->dev, "hist_stat = 0x%x, new waveform = 0x%x\n",
+ hist_stat, upd_desc_list->upd_data.waveform_mode);
+ }
+
+ return 0;
+}
+
+static int epdc_submit_merge(struct update_desc_list *upd_desc_list,
+ struct update_desc_list *update_to_merge,
+ struct mxc_epdc_fb_data *fb_data)
+{
+ struct mxcfb_update_data *a, *b;
+ struct mxcfb_rect *arect, *brect;
+ struct mxcfb_rect combine;
+ bool use_flags = false;
+
+ a = &upd_desc_list->upd_data;
+ b = &update_to_merge->upd_data;
+ arect = &upd_desc_list->upd_data.update_region;
+ brect = &update_to_merge->upd_data.update_region;
+
+ /* Do not merge a dry-run collision test update */
+ if ((a->flags & EPDC_FLAG_TEST_COLLISION) ||
+ (b->flags & EPDC_FLAG_TEST_COLLISION))
+ return MERGE_BLOCK;
+
+ /*
+ * Updates with different flags must be executed sequentially.
+ * Halt the merge process to ensure this.
+ */
+ if (a->flags != b->flags) {
+ /*
+ * Special exception: if update regions are identical,
+ * we may be able to merge them.
+ */
+ if ((arect->left != brect->left) ||
+ (arect->top != brect->top) ||
+ (arect->width != brect->width) ||
+ (arect->height != brect->height))
+ return MERGE_BLOCK;
+
+ use_flags = true;
+ }
+
+ if (a->update_mode != b->update_mode)
+ a->update_mode = UPDATE_MODE_FULL;
+
+ if (a->waveform_mode != b->waveform_mode)
+ a->waveform_mode = WAVEFORM_MODE_AUTO;
+
+ if (arect->left > (brect->left + brect->width) ||
+ brect->left > (arect->left + arect->width) ||
+ arect->top > (brect->top + brect->height) ||
+ brect->top > (arect->top + arect->height))
+ return MERGE_FAIL;
+
+ combine.left = arect->left < brect->left ? arect->left : brect->left;
+ combine.top = arect->top < brect->top ? arect->top : brect->top;
+ combine.width = (arect->left + arect->width) >
+ (brect->left + brect->width) ?
+ (arect->left + arect->width - combine.left) :
+ (brect->left + brect->width - combine.left);
+ combine.height = (arect->top + arect->height) >
+ (brect->top + brect->height) ?
+ (arect->top + arect->height - combine.top) :
+ (brect->top + brect->height - combine.top);
+
+ /* Don't merge if combined width exceeds max width */
+ if (fb_data->restrict_width) {
+ u32 max_width = EPDC_V2_MAX_UPDATE_WIDTH;
+ u32 combined_width = combine.width;
+ if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR)
+ max_width -= EPDC_V2_ROTATION_ALIGNMENT;
+ if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_CW) ||
+ (fb_data->epdc_fb_var.rotate == FB_ROTATE_CCW))
+ combined_width = combine.height;
+ if (combined_width > max_width)
+ return MERGE_FAIL;
+ }
+
+ *arect = combine;
+
+ /* Use flags of the later update */
+ if (use_flags)
+ a->flags = b->flags;
+
+ /* Merge markers */
+ list_splice_tail(&update_to_merge->upd_marker_list,
+ &upd_desc_list->upd_marker_list);
+
+ /* Merged update should take on the earliest order */
+ upd_desc_list->update_order =
+ (upd_desc_list->update_order > update_to_merge->update_order) ?
+ upd_desc_list->update_order : update_to_merge->update_order;
+
+ return MERGE_OK;
+}
+
+static void epdc_submit_work_func(struct work_struct *work)
+{
+ int temp_index;
+ struct update_data_list *next_update, *temp_update;
+ struct update_desc_list *next_desc, *temp_desc;
+ struct update_marker_data *next_marker, *temp_marker;
+ struct mxc_epdc_fb_data *fb_data =
+ container_of(work, struct mxc_epdc_fb_data, epdc_submit_work);
+ struct update_data_list *upd_data_list = NULL;
+ struct mxcfb_rect adj_update_region, *upd_region;
+ bool end_merge = false;
+ bool is_transform;
+ u32 update_addr;
+ int *err_dist;
+ int ret;
+
+ /* Protect access to buffer queues and to update HW */
+ mutex_lock(&fb_data->queue_mutex);
+
+ /*
+ * Are any of our collision updates able to go now?
+ * Go through all updates in the collision list and check to see
+ * if the collision mask has been fully cleared
+ */
+ list_for_each_entry_safe(next_update, temp_update,
+ &fb_data->upd_buf_collision_list, list) {
+
+ if (next_update->collision_mask != 0)
+ continue;
+
+ dev_dbg(fb_data->dev, "A collision update is ready to go!\n");
+
+ /* Force waveform mode to auto for resubmitted collisions */
+ next_update->update_desc->upd_data.waveform_mode =
+ WAVEFORM_MODE_AUTO;
+
+ /*
+ * We have a collision cleared, so select it for resubmission.
+ * If an update is already selected, attempt to merge.
+ */
+ if (!upd_data_list) {
+ upd_data_list = next_update;
+ list_del_init(&next_update->list);
+ if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE)
+ /* If not merging, we have our update */
+ break;
+ } else {
+ switch (epdc_submit_merge(upd_data_list->update_desc,
+ next_update->update_desc,
+ fb_data)) {
+ case MERGE_OK:
+ dev_dbg(fb_data->dev,
+ "Update merged [collision]\n");
+ list_del_init(&next_update->update_desc->list);
+ kfree(next_update->update_desc);
+ next_update->update_desc = NULL;
+ list_del_init(&next_update->list);
+ /* Add to free buffer list */
+ list_add_tail(&next_update->list,
+ &fb_data->upd_buf_free_list);
+ break;
+ case MERGE_FAIL:
+ dev_dbg(fb_data->dev,
+ "Update not merged [collision]\n");
+ break;
+ case MERGE_BLOCK:
+ dev_dbg(fb_data->dev,
+ "Merge blocked [collision]\n");
+ end_merge = true;
+ break;
+ }
+
+ if (end_merge) {
+ end_merge = false;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Skip pending update list only if we found a collision
+ * update and we are not merging
+ */
+ if (!((fb_data->upd_scheme == UPDATE_SCHEME_QUEUE) &&
+ upd_data_list)) {
+ /*
+ * If we didn't find a collision update ready to go, we
+ * need to get a free buffer and match it to a pending update.
+ */
+
+ /*
+ * Can't proceed if there are no free buffers (and we don't
+ * already have a collision update selected)
+ */
+ if (!upd_data_list &&
+ list_empty(&fb_data->upd_buf_free_list)) {
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ list_for_each_entry_safe(next_desc, temp_desc,
+ &fb_data->upd_pending_list, list) {
+
+ dev_dbg(fb_data->dev, "Found a pending update!\n");
+
+ if (!upd_data_list) {
+ if (list_empty(&fb_data->upd_buf_free_list))
+ break;
+ upd_data_list =
+ list_entry(fb_data->upd_buf_free_list.next,
+ struct update_data_list, list);
+ list_del_init(&upd_data_list->list);
+ upd_data_list->update_desc = next_desc;
+ list_del_init(&next_desc->list);
+ if (fb_data->upd_scheme == UPDATE_SCHEME_QUEUE)
+ /* If not merging, we have an update */
+ break;
+ } else {
+ switch (epdc_submit_merge(upd_data_list->update_desc,
+ next_desc, fb_data)) {
+ case MERGE_OK:
+ dev_dbg(fb_data->dev,
+ "Update merged [queue]\n");
+ list_del_init(&next_desc->list);
+ kfree(next_desc);
+ break;
+ case MERGE_FAIL:
+ dev_dbg(fb_data->dev,
+ "Update not merged [queue]\n");
+ break;
+ case MERGE_BLOCK:
+ dev_dbg(fb_data->dev,
+ "Merge blocked [collision]\n");
+ end_merge = true;
+ break;
+ }
+
+ if (end_merge)
+ break;
+ }
+ }
+ }
+
+ /* Is update list empty? */
+ if (!upd_data_list) {
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ /*
+ * If no processing required, skip update processing
+ * No processing means:
+ * - FB unrotated
+ * - FB pixel format = 8-bit grayscale
+ * - No look-up transformations (inversion, posterization, etc.)
+ *
+ * Note: A bug with EPDC stride prevents us from skipping
+ * PxP in versions 2.0 and earlier of EPDC.
+ */
+ is_transform = upd_data_list->update_desc->upd_data.flags &
+ (EPDC_FLAG_ENABLE_INVERSION | EPDC_FLAG_USE_DITHERING_Y1 |
+ EPDC_FLAG_USE_DITHERING_Y4 | EPDC_FLAG_FORCE_MONOCHROME |
+ EPDC_FLAG_USE_CMAP) ? true : false;
+
+ if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) &&
+ (fb_data->epdc_fb_var.grayscale == GRAYSCALE_8BIT) &&
+ !is_transform && (fb_data->rev > 20) &&
+ !fb_data->restrict_width) {
+
+ /* If needed, enable EPDC HW while ePxP is processing */
+ if ((fb_data->power_state == POWER_STATE_OFF)
+ || fb_data->powering_down)
+ epdc_powerup(fb_data);
+
+ /*
+ * Set update buffer pointer to the start of
+ * the update region in the frame buffer.
+ */
+ upd_region = &upd_data_list->update_desc->upd_data.update_region;
+ update_addr = fb_data->info.fix.smem_start +
+ ((upd_region->top * fb_data->info.var.xres_virtual) +
+ upd_region->left) * fb_data->info.var.bits_per_pixel/8;
+ upd_data_list->update_desc->epdc_stride =
+ fb_data->info.var.xres_virtual *
+ fb_data->info.var.bits_per_pixel/8;
+ } else {
+ /* Select from PxP output buffers */
+ upd_data_list->phys_addr =
+ fb_data->phys_addr_updbuf[fb_data->upd_buffer_num];
+ upd_data_list->virt_addr =
+ fb_data->virt_addr_updbuf[fb_data->upd_buffer_num];
+ fb_data->upd_buffer_num++;
+ if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1)
+ fb_data->upd_buffer_num = 0;
+
+ /* Release buffer queues */
+ mutex_unlock(&fb_data->queue_mutex);
+
+ /* Perform PXP processing - EPDC power will also be enabled */
+ if (epdc_process_update(upd_data_list, fb_data)) {
+ dev_dbg(fb_data->dev, "PXP processing error.\n");
+ /* Protect access to buffer queues and to update HW */
+ mutex_lock(&fb_data->queue_mutex);
+ list_del_init(&upd_data_list->update_desc->list);
+ kfree(upd_data_list->update_desc);
+ upd_data_list->update_desc = NULL;
+ /* Add to free buffer list */
+ list_add_tail(&upd_data_list->list,
+ &fb_data->upd_buf_free_list);
+ /* Release buffer queues */
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ /* Protect access to buffer queues and to update HW */
+ mutex_lock(&fb_data->queue_mutex);
+
+ update_addr = upd_data_list->phys_addr
+ + upd_data_list->update_desc->epdc_offs;
+ }
+
+ /* Get rotation-adjusted coordinates */
+ adjust_coordinates(fb_data->epdc_fb_var.xres,
+ fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate,
+ &upd_data_list->update_desc->upd_data.update_region,
+ &adj_update_region);
+
+ /*
+ * Is the working buffer idle?
+ * If the working buffer is busy, we must wait for the resource
+ * to become free. The IST will signal this event.
+ */
+ if (fb_data->cur_update != NULL) {
+ dev_dbg(fb_data->dev, "working buf busy!\n");
+
+ /* Initialize event signalling an update resource is free */
+ init_completion(&fb_data->update_res_free);
+
+ fb_data->waiting_for_wb = true;
+
+ /* Leave spinlock while waiting for WB to complete */
+ mutex_unlock(&fb_data->queue_mutex);
+ wait_for_completion(&fb_data->update_res_free);
+ mutex_lock(&fb_data->queue_mutex);
+ }
+
+ /*
+ * Dithering Processing (Version 1.0 - for i.MX508 and i.MX6SL)
+ */
+ if (upd_data_list->update_desc->upd_data.flags &
+ EPDC_FLAG_USE_DITHERING_Y1) {
+
+ err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3
+ * sizeof(int), GFP_KERNEL);
+
+ /* Dithering Y8 -> Y1 */
+ do_dithering_processing_Y1_v1_0(
+ (uint8_t *)(upd_data_list->virt_addr +
+ upd_data_list->update_desc->epdc_offs),
+ &adj_update_region,
+ (fb_data->rev < 20) ?
+ ALIGN(adj_update_region.width, 8) :
+ adj_update_region.width,
+ err_dist);
+
+ kfree(err_dist);
+ } else if (upd_data_list->update_desc->upd_data.flags &
+ EPDC_FLAG_USE_DITHERING_Y4) {
+
+ err_dist = kzalloc((fb_data->info.var.xres_virtual + 3) * 3
+ * sizeof(int), GFP_KERNEL);
+
+ /* Dithering Y8 -> Y1 */
+ do_dithering_processing_Y4_v1_0(
+ (uint8_t *)(upd_data_list->virt_addr +
+ upd_data_list->update_desc->epdc_offs),
+ &adj_update_region,
+ (fb_data->rev < 20) ?
+ ALIGN(adj_update_region.width, 8) :
+ adj_update_region.width,
+ err_dist);
+
+ kfree(err_dist);
+ }
+
+ /*
+ * If there are no LUTs available,
+ * then we must wait for the resource to become free.
+ * The IST will signal this event.
+ */
+ if (!epdc_any_luts_available()) {
+ dev_dbg(fb_data->dev, "no luts available!\n");
+
+ /* Initialize event signalling an update resource is free */
+ init_completion(&fb_data->update_res_free);
+
+ fb_data->waiting_for_lut = true;
+
+ /* Leave spinlock while waiting for LUT to free up */
+ mutex_unlock(&fb_data->queue_mutex);
+ wait_for_completion(&fb_data->update_res_free);
+ mutex_lock(&fb_data->queue_mutex);
+ }
+
+ ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num);
+ /*
+ * If LUT15 is in use (for pre-EPDC v2.0 hardware):
+ * - Wait for LUT15 to complete is if TCE underrun prevent is enabled
+ * - If we go ahead with update, sync update submission with EOF
+ */
+ if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) {
+ dev_dbg(fb_data->dev, "Waiting for LUT15\n");
+
+ /* Initialize event signalling that lut15 is free */
+ init_completion(&fb_data->lut15_free);
+
+ fb_data->waiting_for_lut15 = true;
+
+ /* Leave spinlock while waiting for LUT to free up */
+ mutex_unlock(&fb_data->queue_mutex);
+ wait_for_completion(&fb_data->lut15_free);
+ mutex_lock(&fb_data->queue_mutex);
+
+ epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num);
+ } else if (ret && (fb_data->rev < 20)) {
+ /* Synchronize update submission time to reduce
+ chances of TCE underrun */
+ init_completion(&fb_data->eof_event);
+
+ epdc_eof_intr(true);
+
+ /* Leave spinlock while waiting for EOF event */
+ mutex_unlock(&fb_data->queue_mutex);
+ ret = wait_for_completion_timeout(&fb_data->eof_event,
+ msecs_to_jiffies(1000));
+ if (!ret) {
+ dev_err(fb_data->dev, "Missed EOF event!\n");
+ epdc_eof_intr(false);
+ }
+ udelay(fb_data->eof_sync_period);
+ mutex_lock(&fb_data->queue_mutex);
+
+ }
+
+ /* LUTs are available, so we get one here */
+ fb_data->cur_update = upd_data_list;
+
+ /* Reset mask for LUTS that have completed during WB processing */
+ fb_data->luts_complete_wb = 0;
+
+ /* If we are just testing for collision, we don't assign a LUT,
+ * so we don't need to update LUT-related resources. */
+ if (!(upd_data_list->update_desc->upd_data.flags
+ & EPDC_FLAG_TEST_COLLISION)) {
+ /* Associate LUT with update marker */
+ list_for_each_entry_safe(next_marker, temp_marker,
+ &upd_data_list->update_desc->upd_marker_list, upd_list)
+ next_marker->lut_num = fb_data->cur_update->lut_num;
+
+ /* Mark LUT with order */
+ fb_data->lut_update_order[upd_data_list->lut_num] =
+ upd_data_list->update_desc->update_order;
+
+ epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num,
+ true);
+ }
+
+ /* Enable Collision and WB complete IRQs */
+ epdc_working_buf_intr(true);
+
+ /* Program EPDC update to process buffer */
+ if (upd_data_list->update_desc->upd_data.temp != TEMP_USE_AMBIENT) {
+ temp_index = mxc_epdc_fb_get_temp_index(fb_data,
+ upd_data_list->update_desc->upd_data.temp);
+ epdc_set_temp(temp_index);
+ } else
+ epdc_set_temp(fb_data->temp_index);
+ epdc_set_update_addr(update_addr);
+ epdc_set_update_coord(adj_update_region.left, adj_update_region.top);
+ epdc_set_update_dimensions(adj_update_region.width,
+ adj_update_region.height);
+ if (fb_data->rev > 20)
+ epdc_set_update_stride(upd_data_list->update_desc->epdc_stride);
+ if (fb_data->wv_modes_update &&
+ (upd_data_list->update_desc->upd_data.waveform_mode
+ == WAVEFORM_MODE_AUTO)) {
+ epdc_set_update_waveform(&fb_data->wv_modes);
+ fb_data->wv_modes_update = false;
+ }
+
+ epdc_submit_update(upd_data_list->lut_num,
+ upd_data_list->update_desc->upd_data.waveform_mode,
+ upd_data_list->update_desc->upd_data.update_mode,
+ (upd_data_list->update_desc->upd_data.flags
+ & EPDC_FLAG_TEST_COLLISION) ? true : false,
+ false, 0);
+
+ /* Release buffer queues */
+ mutex_unlock(&fb_data->queue_mutex);
+}
+
+static int mxc_epdc_fb_send_single_update(struct mxcfb_update_data *upd_data,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+ struct update_data_list *upd_data_list = NULL;
+ struct mxcfb_rect *screen_upd_region; /* Region on screen to update */
+ int temp_index;
+ int ret;
+ struct update_desc_list *upd_desc;
+ struct update_marker_data *marker_data, *next_marker, *temp_marker;
+
+ /* Has EPDC HW been initialized? */
+ if (!fb_data->hw_ready) {
+ /* Throw message if we are not mid-initialization */
+ if (!fb_data->hw_initializing)
+ dev_err(fb_data->dev, "Display HW not properly"
+ "initialized. Aborting update.\n");
+ return -EPERM;
+ }
+
+ /* Check validity of update params */
+ if ((upd_data->update_mode != UPDATE_MODE_PARTIAL) &&
+ (upd_data->update_mode != UPDATE_MODE_FULL)) {
+ dev_err(fb_data->dev,
+ "Update mode 0x%x is invalid. Aborting update.\n",
+ upd_data->update_mode);
+ return -EINVAL;
+ }
+ if ((upd_data->waveform_mode > 255) &&
+ (upd_data->waveform_mode != WAVEFORM_MODE_AUTO)) {
+ dev_err(fb_data->dev,
+ "Update waveform mode 0x%x is invalid."
+ " Aborting update.\n",
+ upd_data->waveform_mode);
+ return -EINVAL;
+ }
+ if ((upd_data->update_region.left + upd_data->update_region.width > fb_data->epdc_fb_var.xres) ||
+ (upd_data->update_region.top + upd_data->update_region.height > fb_data->epdc_fb_var.yres)) {
+ dev_err(fb_data->dev,
+ "Update region is outside bounds of framebuffer."
+ "Aborting update.\n");
+ return -EINVAL;
+ }
+ if (upd_data->flags & EPDC_FLAG_USE_ALT_BUFFER) {
+ if ((upd_data->update_region.width !=
+ upd_data->alt_buffer_data.alt_update_region.width) ||
+ (upd_data->update_region.height !=
+ upd_data->alt_buffer_data.alt_update_region.height)) {
+ dev_err(fb_data->dev,
+ "Alternate update region dimensions must "
+ "match screen update region dimensions.\n");
+ return -EINVAL;
+ }
+ /* Validate physical address parameter */
+ if ((upd_data->alt_buffer_data.phys_addr <
+ fb_data->info.fix.smem_start) ||
+ (upd_data->alt_buffer_data.phys_addr >
+ fb_data->info.fix.smem_start + fb_data->map_size)) {
+ dev_err(fb_data->dev,
+ "Invalid physical address for alternate "
+ "buffer. Aborting update...\n");
+ return -EINVAL;
+ }
+ }
+
+ mutex_lock(&fb_data->queue_mutex);
+
+ /*
+ * If we are waiting to go into suspend, or the FB is blanked,
+ * we do not accept new updates
+ */
+ if ((fb_data->waiting_for_idle) ||
+ (fb_data->blank != FB_BLANK_UNBLANK)) {
+ dev_dbg(fb_data->dev, "EPDC not active."
+ "Update request abort.\n");
+ mutex_unlock(&fb_data->queue_mutex);
+ return -EPERM;
+ }
+
+ if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) {
+ int count = 0;
+ struct update_data_list *plist;
+
+ /*
+ * If next update is a FULL mode update, then we must
+ * ensure that all pending & active updates are complete
+ * before submitting the update. Otherwise, the FULL
+ * mode update may cause an endless collision loop with
+ * other updates. Block here until updates are flushed.
+ */
+ if (upd_data->update_mode == UPDATE_MODE_FULL) {
+ mutex_unlock(&fb_data->queue_mutex);
+ mxc_epdc_fb_flush_updates(fb_data);
+ mutex_lock(&fb_data->queue_mutex);
+ }
+
+ /* Count buffers in free buffer list */
+ list_for_each_entry(plist, &fb_data->upd_buf_free_list, list)
+ count++;
+
+ /* Use count to determine if we have enough
+ * free buffers to handle this update request */
+ if (count + fb_data->max_num_buffers
+ <= fb_data->max_num_updates) {
+ dev_err(fb_data->dev,
+ "No free intermediate buffers available.\n");
+ mutex_unlock(&fb_data->queue_mutex);
+ return -ENOMEM;
+ }
+
+ /* Grab first available buffer and delete from the free list */
+ upd_data_list =
+ list_entry(fb_data->upd_buf_free_list.next,
+ struct update_data_list, list);
+
+ list_del_init(&upd_data_list->list);
+ }
+
+ /*
+ * Create new update data structure, fill it with new update
+ * data and add it to the list of pending updates
+ */
+ upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL);
+ if (!upd_desc) {
+ dev_err(fb_data->dev,
+ "Insufficient system memory for update! Aborting.\n");
+ if (fb_data->upd_scheme == UPDATE_SCHEME_SNAPSHOT) {
+ list_add(&upd_data_list->list,
+ &fb_data->upd_buf_free_list);
+ }
+ mutex_unlock(&fb_data->queue_mutex);
+ return -EPERM;
+ }
+ /* Initialize per-update marker list */
+ INIT_LIST_HEAD(&upd_desc->upd_marker_list);
+ upd_desc->upd_data = *upd_data;
+ upd_desc->update_order = fb_data->order_cnt++;
+ list_add_tail(&upd_desc->list, &fb_data->upd_pending_list);
+
+ /* If marker specified, associate it with a completion */
+ if (upd_data->update_marker != 0) {
+ /* Allocate new update marker and set it up */
+ marker_data = kzalloc(sizeof(struct update_marker_data),
+ GFP_KERNEL);
+ if (!marker_data) {
+ dev_err(fb_data->dev, "No memory for marker!\n");
+ mutex_unlock(&fb_data->queue_mutex);
+ return -ENOMEM;
+ }
+ list_add_tail(&marker_data->upd_list,
+ &upd_desc->upd_marker_list);
+ marker_data->update_marker = upd_data->update_marker;
+ if (upd_desc->upd_data.flags & EPDC_FLAG_TEST_COLLISION)
+ marker_data->lut_num = DRY_RUN_NO_LUT;
+ else
+ marker_data->lut_num = INVALID_LUT;
+ init_completion(&marker_data->update_completion);
+ /* Add marker to master marker list */
+ list_add_tail(&marker_data->full_list,
+ &fb_data->full_marker_list);
+ }
+
+ if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) {
+ /* Queued update scheme processing */
+
+ mutex_unlock(&fb_data->queue_mutex);
+
+ /* Signal workqueue to handle new update */
+ queue_work(fb_data->epdc_submit_workqueue,
+ &fb_data->epdc_submit_work);
+
+ return 0;
+ }
+
+ /* Snapshot update scheme processing */
+
+ /* Set descriptor for current update, delete from pending list */
+ upd_data_list->update_desc = upd_desc;
+ list_del_init(&upd_desc->list);
+
+ mutex_unlock(&fb_data->queue_mutex);
+
+ /*
+ * Hold on to original screen update region, which we
+ * will ultimately use when telling EPDC where to update on panel
+ */
+ screen_upd_region = &upd_desc->upd_data.update_region;
+
+ /* Select from PxP output buffers */
+ upd_data_list->phys_addr =
+ fb_data->phys_addr_updbuf[fb_data->upd_buffer_num];
+ upd_data_list->virt_addr =
+ fb_data->virt_addr_updbuf[fb_data->upd_buffer_num];
+ fb_data->upd_buffer_num++;
+ if (fb_data->upd_buffer_num > fb_data->max_num_buffers-1)
+ fb_data->upd_buffer_num = 0;
+
+ ret = epdc_process_update(upd_data_list, fb_data);
+ if (ret) {
+ mutex_unlock(&fb_data->pxp_mutex);
+ return ret;
+ }
+
+ /* Pass selected waveform mode back to user */
+ upd_data->waveform_mode = upd_desc->upd_data.waveform_mode;
+
+ /* Get rotation-adjusted coordinates */
+ adjust_coordinates(fb_data->epdc_fb_var.xres,
+ fb_data->epdc_fb_var.yres, fb_data->epdc_fb_var.rotate,
+ &upd_desc->upd_data.update_region, NULL);
+
+ /* Grab lock for queue manipulation and update submission */
+ mutex_lock(&fb_data->queue_mutex);
+
+ /*
+ * Is the working buffer idle?
+ * If either the working buffer is busy, or there are no LUTs available,
+ * then we return and let the ISR handle the update later
+ */
+ if ((fb_data->cur_update != NULL) || !epdc_any_luts_available()) {
+ /* Add processed Y buffer to update list */
+ list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue);
+
+ /* Return and allow the update to be submitted by the ISR. */
+ mutex_unlock(&fb_data->queue_mutex);
+ return 0;
+ }
+
+ /* LUTs are available, so we get one here */
+ ret = epdc_choose_next_lut(fb_data->rev, &upd_data_list->lut_num);
+ if (ret && fb_data->tce_prevent && (fb_data->rev < 20)) {
+ dev_dbg(fb_data->dev, "Must wait for LUT15\n");
+ /* Add processed Y buffer to update list */
+ list_add_tail(&upd_data_list->list, &fb_data->upd_buf_queue);
+
+ /* Return and allow the update to be submitted by the ISR. */
+ mutex_unlock(&fb_data->queue_mutex);
+ return 0;
+ }
+
+ if (!(upd_data_list->update_desc->upd_data.flags
+ & EPDC_FLAG_TEST_COLLISION)) {
+
+ /* Save current update */
+ fb_data->cur_update = upd_data_list;
+
+ /* Reset mask for LUTS that have completed during WB processing */
+ fb_data->luts_complete_wb = 0;
+
+ /* Associate LUT with update marker */
+ list_for_each_entry_safe(next_marker, temp_marker,
+ &upd_data_list->update_desc->upd_marker_list, upd_list)
+ next_marker->lut_num = upd_data_list->lut_num;
+
+ /* Mark LUT as containing new update */
+ fb_data->lut_update_order[upd_data_list->lut_num] =
+ upd_desc->update_order;
+
+ epdc_lut_complete_intr(fb_data->rev, upd_data_list->lut_num,
+ true);
+ }
+
+ /* Clear status and Enable LUT complete and WB complete IRQs */
+ epdc_working_buf_intr(true);
+
+ /* Program EPDC update to process buffer */
+ epdc_set_update_addr(upd_data_list->phys_addr + upd_desc->epdc_offs);
+ epdc_set_update_coord(screen_upd_region->left, screen_upd_region->top);
+ epdc_set_update_dimensions(screen_upd_region->width,
+ screen_upd_region->height);
+ if (fb_data->rev > 20)
+ epdc_set_update_stride(upd_desc->epdc_stride);
+ if (upd_desc->upd_data.temp != TEMP_USE_AMBIENT) {
+ temp_index = mxc_epdc_fb_get_temp_index(fb_data,
+ upd_desc->upd_data.temp);
+ epdc_set_temp(temp_index);
+ } else
+ epdc_set_temp(fb_data->temp_index);
+ if (fb_data->wv_modes_update &&
+ (upd_desc->upd_data.waveform_mode == WAVEFORM_MODE_AUTO)) {
+ epdc_set_update_waveform(&fb_data->wv_modes);
+ fb_data->wv_modes_update = false;
+ }
+
+ epdc_submit_update(upd_data_list->lut_num,
+ upd_desc->upd_data.waveform_mode,
+ upd_desc->upd_data.update_mode,
+ (upd_desc->upd_data.flags
+ & EPDC_FLAG_TEST_COLLISION) ? true : false,
+ false, 0);
+
+ mutex_unlock(&fb_data->queue_mutex);
+ return 0;
+}
+
+int mxc_epdc_fb_send_update(struct mxcfb_update_data *upd_data,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ if (!fb_data->restrict_width) {
+ /* No width restriction, send entire update region */
+ return mxc_epdc_fb_send_single_update(upd_data, info);
+ } else {
+ int ret;
+ __u32 width, left;
+ __u32 marker;
+ __u32 *region_width, *region_left;
+ u32 max_upd_width = EPDC_V2_MAX_UPDATE_WIDTH;
+
+ /* Further restrict max width due to pxp rotation
+ * alignment requirement
+ */
+ if (fb_data->epdc_fb_var.rotate != FB_ROTATE_UR)
+ max_upd_width -= EPDC_V2_ROTATION_ALIGNMENT;
+
+ /* Select split of width or height based on rotation */
+ if ((fb_data->epdc_fb_var.rotate == FB_ROTATE_UR) ||
+ (fb_data->epdc_fb_var.rotate == FB_ROTATE_UD)) {
+ region_width = &upd_data->update_region.width;
+ region_left = &upd_data->update_region.left;
+ } else {
+ region_width = &upd_data->update_region.height;
+ region_left = &upd_data->update_region.top;
+ }
+
+ if (*region_width <= max_upd_width)
+ return mxc_epdc_fb_send_single_update(upd_data, info);
+
+ width = *region_width;
+ left = *region_left;
+ marker = upd_data->update_marker;
+ upd_data->update_marker = 0;
+
+ do {
+ *region_width = max_upd_width;
+ *region_left = left;
+ ret = mxc_epdc_fb_send_single_update(upd_data, info);
+ if (ret)
+ return ret;
+ width -= max_upd_width;
+ left += max_upd_width;
+ } while (width > max_upd_width);
+
+ *region_width = width;
+ *region_left = left;
+ upd_data->update_marker = marker;
+ return mxc_epdc_fb_send_single_update(upd_data, info);
+ }
+}
+EXPORT_SYMBOL(mxc_epdc_fb_send_update);
+
+int mxc_epdc_fb_wait_update_complete(struct mxcfb_update_marker_data *marker_data,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+ struct update_marker_data *next_marker;
+ struct update_marker_data *temp;
+ bool marker_found = false;
+ int ret = 0;
+
+ /* 0 is an invalid update_marker value */
+ if (marker_data->update_marker == 0)
+ return -EINVAL;
+
+ /*
+ * Find completion associated with update_marker requested.
+ * Note: If update completed already, marker will have been
+ * cleared, it won't be found, and function will just return.
+ */
+
+ /* Grab queue lock to protect access to marker list */
+ mutex_lock(&fb_data->queue_mutex);
+
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->full_marker_list, full_list) {
+ if (next_marker->update_marker == marker_data->update_marker) {
+ dev_dbg(fb_data->dev, "Waiting for marker %d\n",
+ marker_data->update_marker);
+ next_marker->waiting = true;
+ marker_found = true;
+ break;
+ }
+ }
+
+ mutex_unlock(&fb_data->queue_mutex);
+
+ /*
+ * If marker not found, it has either been signalled already
+ * or the update request failed. In either case, just return.
+ */
+ if (!marker_found)
+ return ret;
+
+ ret = wait_for_completion_timeout(&next_marker->update_completion,
+ msecs_to_jiffies(5000));
+ if (!ret) {
+ dev_err(fb_data->dev,
+ "Timed out waiting for update completion\n");
+ return -ETIMEDOUT;
+ }
+
+ marker_data->collision_test = next_marker->collision_test;
+
+ /* Free update marker object */
+ kfree(next_marker);
+
+ return ret;
+}
+EXPORT_SYMBOL(mxc_epdc_fb_wait_update_complete);
+
+int mxc_epdc_fb_set_pwrdown_delay(u32 pwrdown_delay,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ fb_data->pwrdown_delay = pwrdown_delay;
+
+ return 0;
+}
+EXPORT_SYMBOL(mxc_epdc_fb_set_pwrdown_delay);
+
+int mxc_epdc_get_pwrdown_delay(struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+
+ return fb_data->pwrdown_delay;
+}
+EXPORT_SYMBOL(mxc_epdc_get_pwrdown_delay);
+
+static int mxc_epdc_fb_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int ret = -EINVAL;
+
+ switch (cmd) {
+ case MXCFB_SET_WAVEFORM_MODES:
+ {
+ struct mxcfb_waveform_modes modes;
+ if (!copy_from_user(&modes, argp, sizeof(modes))) {
+ mxc_epdc_fb_set_waveform_modes(&modes, info);
+ ret = 0;
+ }
+ break;
+ }
+ case MXCFB_SET_TEMPERATURE:
+ {
+ int temperature;
+ if (!get_user(temperature, (int32_t __user *) arg))
+ ret = mxc_epdc_fb_set_temperature(temperature,
+ info);
+ break;
+ }
+ case MXCFB_SET_AUTO_UPDATE_MODE:
+ {
+ u32 auto_mode = 0;
+ if (!get_user(auto_mode, (__u32 __user *) arg))
+ ret = mxc_epdc_fb_set_auto_update(auto_mode,
+ info);
+ break;
+ }
+ case MXCFB_SET_UPDATE_SCHEME:
+ {
+ u32 upd_scheme = 0;
+ if (!get_user(upd_scheme, (__u32 __user *) arg))
+ ret = mxc_epdc_fb_set_upd_scheme(upd_scheme,
+ info);
+ break;
+ }
+ case MXCFB_SEND_UPDATE:
+ {
+ struct mxcfb_update_data upd_data;
+ if (!copy_from_user(&upd_data, argp,
+ sizeof(upd_data))) {
+ ret = mxc_epdc_fb_send_update(&upd_data, info);
+ if (ret == 0 && copy_to_user(argp, &upd_data,
+ sizeof(upd_data)))
+ ret = -EFAULT;
+ } else {
+ ret = -EFAULT;
+ }
+
+ break;
+ }
+ case MXCFB_WAIT_FOR_UPDATE_COMPLETE:
+ {
+ struct mxcfb_update_marker_data upd_marker_data;
+ if (!copy_from_user(&upd_marker_data, argp,
+ sizeof(upd_marker_data))) {
+ ret = mxc_epdc_fb_wait_update_complete(
+ &upd_marker_data, info);
+ if (copy_to_user(argp, &upd_marker_data,
+ sizeof(upd_marker_data)))
+ ret = -EFAULT;
+ } else {
+ ret = -EFAULT;
+ }
+
+ break;
+ }
+
+ case MXCFB_SET_PWRDOWN_DELAY:
+ {
+ int delay = 0;
+ if (!get_user(delay, (__u32 __user *) arg))
+ ret =
+ mxc_epdc_fb_set_pwrdown_delay(delay, info);
+ break;
+ }
+
+ case MXCFB_GET_PWRDOWN_DELAY:
+ {
+ int pwrdown_delay = mxc_epdc_get_pwrdown_delay(info);
+ if (put_user(pwrdown_delay,
+ (int __user *)argp))
+ ret = -EFAULT;
+ ret = 0;
+ break;
+ }
+
+ case MXCFB_GET_WORK_BUFFER:
+ {
+ /* copy the epdc working buffer to the user space */
+ struct mxc_epdc_fb_data *fb_data = info ?
+ (struct mxc_epdc_fb_data *)info:g_fb_data;
+ flush_cache_all();
+ outer_flush_all();
+ if (copy_to_user((void __user *)arg,
+ (const void *) fb_data->working_buffer_virt,
+ fb_data->working_buffer_size))
+ ret = -EFAULT;
+ else
+ ret = 0;
+ flush_cache_all();
+ outer_flush_all();
+ break;
+ }
+
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void mxc_epdc_fb_update_pages(struct mxc_epdc_fb_data *fb_data,
+ u16 y1, u16 y2)
+{
+ struct mxcfb_update_data update;
+
+ /* Do partial screen update, Update full horizontal lines */
+ update.update_region.left = 0;
+ update.update_region.width = fb_data->epdc_fb_var.xres;
+ update.update_region.top = y1;
+ update.update_region.height = y2 - y1;
+ update.waveform_mode = WAVEFORM_MODE_AUTO;
+ update.update_mode = UPDATE_MODE_FULL;
+ update.update_marker = 0;
+ update.temp = TEMP_USE_AMBIENT;
+ update.flags = 0;
+
+ mxc_epdc_fb_send_update(&update, &fb_data->info);
+}
+
+/* this is called back from the deferred io workqueue */
+static void mxc_epdc_fb_deferred_io(struct fb_info *info,
+ struct list_head *pagelist)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ struct page *page;
+ unsigned long beg, end;
+ int y1, y2, miny, maxy;
+
+ if (fb_data->auto_mode != AUTO_UPDATE_MODE_AUTOMATIC_MODE)
+ return;
+
+ miny = INT_MAX;
+ maxy = 0;
+ list_for_each_entry(page, pagelist, lru) {
+ beg = page->index << PAGE_SHIFT;
+ end = beg + PAGE_SIZE - 1;
+ y1 = beg / info->fix.line_length;
+ y2 = end / info->fix.line_length;
+ if (y2 >= fb_data->epdc_fb_var.yres)
+ y2 = fb_data->epdc_fb_var.yres - 1;
+ if (miny > y1)
+ miny = y1;
+ if (maxy < y2)
+ maxy = y2;
+ }
+
+ mxc_epdc_fb_update_pages(fb_data, miny, maxy);
+}
+
+void mxc_epdc_fb_flush_updates(struct mxc_epdc_fb_data *fb_data)
+{
+ int ret;
+
+ if (fb_data->in_init)
+ return;
+
+ /* Grab queue lock to prevent any new updates from being submitted */
+ mutex_lock(&fb_data->queue_mutex);
+
+ /*
+ * 3 places to check for updates that are active or pending:
+ * 1) Updates in the pending list
+ * 2) Update buffers in use (e.g., PxP processing)
+ * 3) Active updates to panel - We can key off of EPDC
+ * power state to know if we have active updates.
+ */
+ if (!list_empty(&fb_data->upd_pending_list) ||
+ !is_free_list_full(fb_data) ||
+ (fb_data->updates_active == true)) {
+ /* Initialize event signalling updates are done */
+ init_completion(&fb_data->updates_done);
+ fb_data->waiting_for_idle = true;
+
+ mutex_unlock(&fb_data->queue_mutex);
+ /* Wait for any currently active updates to complete */
+ ret = wait_for_completion_timeout(&fb_data->updates_done,
+ msecs_to_jiffies(8000));
+ if (!ret)
+ dev_err(fb_data->dev,
+ "Flush updates timeout! ret = 0x%x\n", ret);
+
+ mutex_lock(&fb_data->queue_mutex);
+ fb_data->waiting_for_idle = false;
+ }
+
+ mutex_unlock(&fb_data->queue_mutex);
+}
+
+static int mxc_epdc_fb_blank(int blank, struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ int ret;
+
+ dev_dbg(fb_data->dev, "blank = %d\n", blank);
+
+ if (fb_data->blank == blank)
+ return 0;
+
+ fb_data->blank = blank;
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ mxc_epdc_fb_flush_updates(fb_data);
+ /* Wait for powerdown */
+ mutex_lock(&fb_data->power_mutex);
+ if ((fb_data->power_state == POWER_STATE_ON) &&
+ (fb_data->pwrdown_delay == FB_POWERDOWN_DISABLE)) {
+
+ /* Powerdown disabled, so we disable EPDC manually */
+ int count = 0;
+ int sleep_ms = 10;
+
+ mutex_unlock(&fb_data->power_mutex);
+
+ /* If any active updates, wait for them to complete */
+ while (fb_data->updates_active) {
+ /* Timeout after 1 sec */
+ if ((count * sleep_ms) > 1000)
+ break;
+ msleep(sleep_ms);
+ count++;
+ }
+
+ fb_data->powering_down = true;
+ epdc_powerdown(fb_data);
+ } else if (fb_data->power_state != POWER_STATE_OFF) {
+ fb_data->wait_for_powerdown = true;
+ init_completion(&fb_data->powerdown_compl);
+ mutex_unlock(&fb_data->power_mutex);
+ ret = wait_for_completion_timeout(&fb_data->powerdown_compl,
+ msecs_to_jiffies(5000));
+ if (!ret) {
+ dev_err(fb_data->dev,
+ "No powerdown received!\n");
+ return -ETIMEDOUT;
+ }
+ } else
+ mutex_unlock(&fb_data->power_mutex);
+ break;
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ mxc_epdc_fb_flush_updates(fb_data);
+ break;
+ }
+ return 0;
+}
+
+static int mxc_epdc_fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ u_int y_bottom;
+
+ dev_dbg(info->device, "%s: var->yoffset %d, info->var.yoffset %d\n",
+ __func__, var->yoffset, info->var.yoffset);
+ /* check if var is valid; also, xpan is not supported */
+ if (!var || (var->xoffset != info->var.xoffset) ||
+ (var->yoffset + var->yres > var->yres_virtual)) {
+ dev_dbg(info->device, "x panning not supported\n");
+ return -EINVAL;
+ }
+
+ if ((fb_data->epdc_fb_var.xoffset == var->xoffset) &&
+ (fb_data->epdc_fb_var.yoffset == var->yoffset))
+ return 0; /* No change, do nothing */
+
+ y_bottom = var->yoffset;
+
+ if (!(var->vmode & FB_VMODE_YWRAP))
+ y_bottom += var->yres;
+
+ if (y_bottom > info->var.yres_virtual)
+ return -EINVAL;
+
+ mutex_lock(&fb_data->queue_mutex);
+
+ fb_data->fb_offset = (var->yoffset * var->xres_virtual + var->xoffset)
+ * (var->bits_per_pixel) / 8;
+
+ fb_data->epdc_fb_var.xoffset = var->xoffset;
+ fb_data->epdc_fb_var.yoffset = var->yoffset;
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+
+ mutex_unlock(&fb_data->queue_mutex);
+
+ return 0;
+}
+
+static struct fb_ops mxc_epdc_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = mxc_epdc_fb_check_var,
+ .fb_set_par = mxc_epdc_fb_set_par,
+ .fb_setcmap = mxc_epdc_fb_setcmap,
+ .fb_setcolreg = mxc_epdc_fb_setcolreg,
+ .fb_pan_display = mxc_epdc_fb_pan_display,
+ .fb_ioctl = mxc_epdc_fb_ioctl,
+ .fb_mmap = mxc_epdc_fb_mmap,
+ .fb_blank = mxc_epdc_fb_blank,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+};
+
+static struct fb_deferred_io mxc_epdc_fb_defio = {
+ .delay = HZ,
+ .deferred_io = mxc_epdc_fb_deferred_io,
+};
+
+static void epdc_done_work_func(struct work_struct *work)
+{
+ struct mxc_epdc_fb_data *fb_data =
+ container_of(work, struct mxc_epdc_fb_data,
+ epdc_done_work.work);
+ epdc_powerdown(fb_data);
+}
+
+static bool is_free_list_full(struct mxc_epdc_fb_data *fb_data)
+{
+ int count = 0;
+ struct update_data_list *plist;
+
+ /* Count buffers in free buffer list */
+ list_for_each_entry(plist, &fb_data->upd_buf_free_list, list)
+ count++;
+
+ /* Check to see if all buffers are in this list */
+ if (count == fb_data->max_num_updates)
+ return true;
+ else
+ return false;
+}
+
+static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
+{
+ struct mxc_epdc_fb_data *fb_data = dev_id;
+ u32 ints_fired, luts1_ints_fired, luts2_ints_fired;
+
+ /*
+ * If we just completed one-time panel init, bypass
+ * queue handling, clear interrupt and return
+ */
+ if (fb_data->in_init) {
+ if (epdc_is_working_buffer_complete()) {
+ epdc_working_buf_intr(false);
+ epdc_clear_working_buf_irq();
+ dev_dbg(fb_data->dev, "Cleared WB for init update\n");
+ }
+
+ if (epdc_is_lut_complete(fb_data->rev, 0)) {
+ epdc_lut_complete_intr(fb_data->rev, 0, false);
+ epdc_clear_lut_complete_irq(fb_data->rev, 0);
+ fb_data->in_init = false;
+ dev_dbg(fb_data->dev, "Cleared LUT complete for init update\n");
+ }
+
+ return IRQ_HANDLED;
+ }
+
+ ints_fired = __raw_readl(EPDC_IRQ_MASK) & __raw_readl(EPDC_IRQ);
+ if (fb_data->rev < 20) {
+ luts1_ints_fired = 0;
+ luts2_ints_fired = 0;
+ } else {
+ luts1_ints_fired = __raw_readl(EPDC_IRQ_MASK1) & __raw_readl(EPDC_IRQ1);
+ luts2_ints_fired = __raw_readl(EPDC_IRQ_MASK2) & __raw_readl(EPDC_IRQ2);
+ }
+
+ if (!(ints_fired || luts1_ints_fired || luts2_ints_fired))
+ return IRQ_HANDLED;
+
+ if (__raw_readl(EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) {
+ dev_err(fb_data->dev,
+ "TCE underrun! Will continue to update panel\n");
+ /* Clear TCE underrun IRQ */
+ __raw_writel(EPDC_IRQ_TCE_UNDERRUN_IRQ, EPDC_IRQ_CLEAR);
+ }
+
+ /* Check if we are waiting on EOF to sync a new update submission */
+ if (epdc_signal_eof()) {
+ epdc_eof_intr(false);
+ epdc_clear_eof_irq();
+ complete(&fb_data->eof_event);
+ }
+
+ /*
+ * Workaround for EPDC v2.0/v2.1 errata: Must read collision status
+ * before clearing IRQ, or else collision status for bits 16:63
+ * will be automatically cleared. So we read it here, and there is
+ * no conflict with using it in epdc_intr_work_func since the
+ * working buffer processing flow is strictly sequential (i.e.,
+ * only one WB processing done at a time, so the data grabbed
+ * here should be up-to-date and accurate when the WB processing
+ * completes. Also, note that there is no impact to other versions
+ * of EPDC by reading LUT status here.
+ */
+ if (fb_data->cur_update != NULL)
+ fb_data->epdc_colliding_luts = epdc_get_colliding_luts(fb_data->rev);
+
+ /* Clear the interrupt mask for any interrupts signalled */
+ __raw_writel(ints_fired, EPDC_IRQ_MASK_CLEAR);
+ __raw_writel(luts1_ints_fired, EPDC_IRQ_MASK1_CLEAR);
+ __raw_writel(luts2_ints_fired, EPDC_IRQ_MASK2_CLEAR);
+
+ dev_dbg(fb_data->dev, "EPDC interrupts fired = 0x%x, "
+ "LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n",
+ ints_fired, luts1_ints_fired, luts2_ints_fired);
+
+ queue_work(fb_data->epdc_intr_workqueue,
+ &fb_data->epdc_intr_work);
+
+ return IRQ_HANDLED;
+}
+
+static void epdc_intr_work_func(struct work_struct *work)
+{
+ struct mxc_epdc_fb_data *fb_data =
+ container_of(work, struct mxc_epdc_fb_data, epdc_intr_work);
+ struct update_data_list *collision_update;
+ struct mxcfb_rect *next_upd_region;
+ struct update_marker_data *next_marker;
+ struct update_marker_data *temp;
+ int temp_index;
+ u64 temp_mask;
+ u32 lut;
+ bool ignore_collision = false;
+ int i;
+ bool wb_lut_done = false;
+ bool free_update = true;
+ int next_lut, epdc_next_lut_15;
+ u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled;
+ u32 epdc_collision;
+ u64 epdc_irq_stat;
+ bool epdc_waiting_on_wb;
+ u32 coll_coord, coll_size;
+ struct mxcfb_rect coll_region;
+
+ /* Protect access to buffer queues and to update HW */
+ mutex_lock(&fb_data->queue_mutex);
+
+ /* Capture EPDC status one time to limit exposure to race conditions */
+ epdc_luts_active = epdc_any_luts_active(fb_data->rev);
+ epdc_wb_busy = epdc_is_working_buffer_busy();
+ epdc_lut_cancelled = epdc_is_lut_cancelled();
+ epdc_luts_avail = epdc_any_luts_available();
+ epdc_collision = epdc_is_collision();
+ if (fb_data->rev < 20)
+ epdc_irq_stat = __raw_readl(EPDC_IRQ);
+ else
+ epdc_irq_stat = (u64)__raw_readl(EPDC_IRQ1) |
+ ((u64)__raw_readl(EPDC_IRQ2) << 32);
+ epdc_waiting_on_wb = (fb_data->cur_update != NULL) ? true : false;
+
+ /* Free any LUTs that have completed */
+ for (i = 0; i < fb_data->num_luts; i++) {
+ if ((epdc_irq_stat & (1ULL << i)) == 0)
+ continue;
+
+ dev_dbg(fb_data->dev, "LUT %d completed\n", i);
+
+ /* Disable IRQ for completed LUT */
+ epdc_lut_complete_intr(fb_data->rev, i, false);
+
+ /*
+ * Go through all updates in the collision list and
+ * unmask any updates that were colliding with
+ * the completed LUT.
+ */
+ list_for_each_entry(collision_update,
+ &fb_data->upd_buf_collision_list, list) {
+ collision_update->collision_mask =
+ collision_update->collision_mask & ~(1 << i);
+ }
+
+ epdc_clear_lut_complete_irq(fb_data->rev, i);
+
+ fb_data->luts_complete_wb |= 1ULL << i;
+
+ fb_data->lut_update_order[i] = 0;
+
+ /* Signal completion if submit workqueue needs a LUT */
+ if (fb_data->waiting_for_lut) {
+ complete(&fb_data->update_res_free);
+ fb_data->waiting_for_lut = false;
+ }
+
+ /* Signal completion if LUT15 free and is needed */
+ if (fb_data->waiting_for_lut15 && (i == 15)) {
+ complete(&fb_data->lut15_free);
+ fb_data->waiting_for_lut15 = false;
+ }
+
+ /* Detect race condition where WB and its LUT complete
+ (i.e. full update completes) in one swoop */
+ if (epdc_waiting_on_wb &&
+ (i == fb_data->cur_update->lut_num))
+ wb_lut_done = true;
+
+ /* Signal completion if anyone waiting on this LUT */
+ if (!wb_lut_done)
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->full_marker_list,
+ full_list) {
+ if (next_marker->lut_num != i)
+ continue;
+
+ /* Found marker to signal - remove from list */
+ list_del_init(&next_marker->full_list);
+
+ /* Signal completion of update */
+ dev_dbg(fb_data->dev, "Signaling marker %d\n",
+ next_marker->update_marker);
+ if (next_marker->waiting)
+ complete(&next_marker->update_completion);
+ else
+ kfree(next_marker);
+ }
+ }
+
+ /* Check to see if all updates have completed */
+ if (list_empty(&fb_data->upd_pending_list) &&
+ is_free_list_full(fb_data) &&
+ !epdc_waiting_on_wb &&
+ !epdc_luts_active) {
+
+ fb_data->updates_active = false;
+
+ if (fb_data->pwrdown_delay != FB_POWERDOWN_DISABLE) {
+ /*
+ * Set variable to prevent overlapping
+ * enable/disable requests
+ */
+ fb_data->powering_down = true;
+
+ /* Schedule task to disable EPDC HW until next update */
+ schedule_delayed_work(&fb_data->epdc_done_work,
+ msecs_to_jiffies(fb_data->pwrdown_delay));
+
+ /* Reset counter to reduce chance of overflow */
+ fb_data->order_cnt = 0;
+ }
+
+ if (fb_data->waiting_for_idle)
+ complete(&fb_data->updates_done);
+ }
+
+ /* Is Working Buffer busy? */
+ if (epdc_wb_busy) {
+ /* Can't submit another update until WB is done */
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ /*
+ * Were we waiting on working buffer?
+ * If so, update queues and check for collisions
+ */
+ if (epdc_waiting_on_wb) {
+ dev_dbg(fb_data->dev, "\nWorking buffer completed\n");
+
+ /* Signal completion if submit workqueue was waiting on WB */
+ if (fb_data->waiting_for_wb) {
+ complete(&fb_data->update_res_free);
+ fb_data->waiting_for_wb = false;
+ }
+
+ if (fb_data->cur_update->update_desc->upd_data.flags
+ & EPDC_FLAG_TEST_COLLISION) {
+ /* This was a dry run to test for collision */
+
+ /* Signal marker */
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->full_marker_list,
+ full_list) {
+ if (next_marker->lut_num != DRY_RUN_NO_LUT)
+ continue;
+
+ if (epdc_collision)
+ next_marker->collision_test = true;
+ else
+ next_marker->collision_test = false;
+
+ dev_dbg(fb_data->dev,
+ "In IRQ, collision_test = %d\n",
+ next_marker->collision_test);
+
+ /* Found marker to signal - remove from list */
+ list_del_init(&next_marker->full_list);
+
+ /* Signal completion of update */
+ dev_dbg(fb_data->dev, "Signaling marker "
+ "for dry-run - %d\n",
+ next_marker->update_marker);
+ complete(&next_marker->update_completion);
+ }
+ } else if (epdc_lut_cancelled && !epdc_collision) {
+ /*
+ * Note: The update may be cancelled (void) if all
+ * pixels collided. In that case we handle it as a
+ * collision, not a cancel.
+ */
+
+ /* Clear LUT status (might be set if no AUTOWV used) */
+
+ /*
+ * Disable and clear IRQ for the LUT used.
+ * Even though LUT is cancelled in HW, the LUT
+ * complete bit may be set if AUTOWV not used.
+ */
+ epdc_lut_complete_intr(fb_data->rev,
+ fb_data->cur_update->lut_num, false);
+ epdc_clear_lut_complete_irq(fb_data->rev,
+ fb_data->cur_update->lut_num);
+
+ fb_data->lut_update_order[fb_data->cur_update->lut_num] = 0;
+
+ /* Signal completion if submit workqueue needs a LUT */
+ if (fb_data->waiting_for_lut) {
+ complete(&fb_data->update_res_free);
+ fb_data->waiting_for_lut = false;
+ }
+
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->cur_update->update_desc->upd_marker_list,
+ upd_list) {
+
+ /* Del from per-update & full list */
+ list_del_init(&next_marker->upd_list);
+ list_del_init(&next_marker->full_list);
+
+ /* Signal completion of update */
+ dev_dbg(fb_data->dev,
+ "Signaling marker (cancelled) %d\n",
+ next_marker->update_marker);
+ if (next_marker->waiting)
+ complete(&next_marker->update_completion);
+ else
+ kfree(next_marker);
+ }
+ } else if (epdc_collision) {
+ /* Real update (no dry-run), collision occurred */
+
+ /* Check list of colliding LUTs, and add to our collision mask */
+ fb_data->cur_update->collision_mask =
+ fb_data->epdc_colliding_luts;
+
+ /* Clear collisions that completed since WB began */
+ fb_data->cur_update->collision_mask &=
+ ~fb_data->luts_complete_wb;
+
+ dev_dbg(fb_data->dev, "Collision mask = 0x%llx\n",
+ fb_data->epdc_colliding_luts);
+
+ /* For EPDC 2.0 and later, minimum collision bounds
+ are provided by HW. Recompute new bounds here. */
+ if ((fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT)
+ && (fb_data->rev >= 20)) {
+ u32 xres, yres, rotate;
+ struct mxcfb_rect *cur_upd_rect =
+ &fb_data->cur_update->update_desc->upd_data.update_region;
+
+ /* Get collision region coords from EPDC */
+ coll_coord = __raw_readl(EPDC_UPD_COL_CORD);
+ coll_size = __raw_readl(EPDC_UPD_COL_SIZE);
+ coll_region.left =
+ (coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK)
+ >> EPDC_UPD_COL_CORD_XCORD_OFFSET;
+ coll_region.top =
+ (coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK)
+ >> EPDC_UPD_COL_CORD_YCORD_OFFSET;
+ coll_region.width =
+ (coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK)
+ >> EPDC_UPD_COL_SIZE_WIDTH_OFFSET;
+ coll_region.height =
+ (coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK)
+ >> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET;
+ dev_dbg(fb_data->dev, "Coll region: l = %d, "
+ "t = %d, w = %d, h = %d\n",
+ coll_region.left, coll_region.top,
+ coll_region.width, coll_region.height);
+
+ /* Convert coords back to orig orientation */
+ switch (fb_data->epdc_fb_var.rotate) {
+ case FB_ROTATE_CW:
+ xres = fb_data->epdc_fb_var.yres;
+ yres = fb_data->epdc_fb_var.xres;
+ rotate = FB_ROTATE_CCW;
+ break;
+ case FB_ROTATE_UD:
+ xres = fb_data->epdc_fb_var.xres;
+ yres = fb_data->epdc_fb_var.yres;
+ rotate = FB_ROTATE_UD;
+ break;
+ case FB_ROTATE_CCW:
+ xres = fb_data->epdc_fb_var.yres;
+ yres = fb_data->epdc_fb_var.xres;
+ rotate = FB_ROTATE_CW;
+ break;
+ default:
+ xres = fb_data->epdc_fb_var.xres;
+ yres = fb_data->epdc_fb_var.yres;
+ rotate = FB_ROTATE_UR;
+ break;
+ }
+ adjust_coordinates(xres, yres, rotate,
+ &coll_region, cur_upd_rect);
+
+ dev_dbg(fb_data->dev, "Adj coll region: l = %d, "
+ "t = %d, w = %d, h = %d\n",
+ cur_upd_rect->left, cur_upd_rect->top,
+ cur_upd_rect->width,
+ cur_upd_rect->height);
+ }
+
+ /*
+ * If we collide with newer updates, then
+ * we don't need to re-submit the update. The
+ * idea is that the newer updates should take
+ * precedence anyways, so we don't want to
+ * overwrite them.
+ */
+ for (temp_mask = fb_data->cur_update->collision_mask, lut = 0;
+ temp_mask != 0;
+ lut++, temp_mask = temp_mask >> 1) {
+ if (!(temp_mask & 0x1))
+ continue;
+
+ if (fb_data->lut_update_order[lut] >=
+ fb_data->cur_update->update_desc->update_order) {
+ dev_dbg(fb_data->dev,
+ "Ignoring collision with"
+ "newer update.\n");
+ ignore_collision = true;
+ break;
+ }
+ }
+
+ if (!ignore_collision) {
+ free_update = false;
+ /*
+ * If update has markers, clear the LUTs to
+ * avoid signalling that they have completed.
+ */
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->cur_update->update_desc->upd_marker_list,
+ upd_list)
+ next_marker->lut_num = INVALID_LUT;
+
+ /* Move to collision list */
+ list_add_tail(&fb_data->cur_update->list,
+ &fb_data->upd_buf_collision_list);
+ }
+ }
+
+ /* Do we need to free the current update descriptor? */
+ if (free_update) {
+ /* Handle condition where WB & LUT are both complete */
+ if (wb_lut_done)
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->cur_update->update_desc->upd_marker_list,
+ upd_list) {
+
+ /* Del from per-update & full list */
+ list_del_init(&next_marker->upd_list);
+ list_del_init(&next_marker->full_list);
+
+ /* Signal completion of update */
+ dev_dbg(fb_data->dev,
+ "Signaling marker (wb) %d\n",
+ next_marker->update_marker);
+ if (next_marker->waiting)
+ complete(&next_marker->update_completion);
+ else
+ kfree(next_marker);
+ }
+
+ /* Free marker list and update descriptor */
+ kfree(fb_data->cur_update->update_desc);
+
+ /* Add to free buffer list */
+ list_add_tail(&fb_data->cur_update->list,
+ &fb_data->upd_buf_free_list);
+
+ /* Check to see if all updates have completed */
+ if (list_empty(&fb_data->upd_pending_list) &&
+ is_free_list_full(fb_data) &&
+ !epdc_luts_active) {
+
+ fb_data->updates_active = false;
+
+ if (fb_data->pwrdown_delay !=
+ FB_POWERDOWN_DISABLE) {
+ /*
+ * Set variable to prevent overlapping
+ * enable/disable requests
+ */
+ fb_data->powering_down = true;
+
+ /* Schedule EPDC disable */
+ schedule_delayed_work(&fb_data->epdc_done_work,
+ msecs_to_jiffies(fb_data->pwrdown_delay));
+
+ /* Reset counter to reduce chance of overflow */
+ fb_data->order_cnt = 0;
+ }
+
+ if (fb_data->waiting_for_idle)
+ complete(&fb_data->updates_done);
+ }
+ }
+
+ /* Clear current update */
+ fb_data->cur_update = NULL;
+
+ /* Clear IRQ for working buffer */
+ epdc_working_buf_intr(false);
+ epdc_clear_working_buf_irq();
+ }
+
+ if (fb_data->upd_scheme != UPDATE_SCHEME_SNAPSHOT) {
+ /* Queued update scheme processing */
+
+ /* Schedule task to submit collision and pending update */
+ if (!fb_data->powering_down)
+ queue_work(fb_data->epdc_submit_workqueue,
+ &fb_data->epdc_submit_work);
+
+ /* Release buffer queues */
+ mutex_unlock(&fb_data->queue_mutex);
+
+ return;
+ }
+
+ /* Snapshot update scheme processing */
+
+ /* Check to see if any LUTs are free */
+ if (!epdc_luts_avail) {
+ dev_dbg(fb_data->dev, "No luts available.\n");
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ epdc_next_lut_15 = epdc_choose_next_lut(fb_data->rev, &next_lut);
+ /* Check to see if there is a valid LUT to use */
+ if (epdc_next_lut_15 && fb_data->tce_prevent && (fb_data->rev < 20)) {
+ dev_dbg(fb_data->dev, "Must wait for LUT15\n");
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ }
+
+ /*
+ * Are any of our collision updates able to go now?
+ * Go through all updates in the collision list and check to see
+ * if the collision mask has been fully cleared
+ */
+ list_for_each_entry(collision_update,
+ &fb_data->upd_buf_collision_list, list) {
+
+ if (collision_update->collision_mask != 0)
+ continue;
+
+ dev_dbg(fb_data->dev, "A collision update is ready to go!\n");
+ /*
+ * We have a collision cleared, so select it
+ * and we will retry the update
+ */
+ fb_data->cur_update = collision_update;
+ list_del_init(&fb_data->cur_update->list);
+ break;
+ }
+
+ /*
+ * If we didn't find a collision update ready to go,
+ * we try to grab one from the update queue
+ */
+ if (fb_data->cur_update == NULL) {
+ /* Is update list empty? */
+ if (list_empty(&fb_data->upd_buf_queue)) {
+ dev_dbg(fb_data->dev, "No pending updates.\n");
+
+ /* No updates pending, so we are done */
+ mutex_unlock(&fb_data->queue_mutex);
+ return;
+ } else {
+ dev_dbg(fb_data->dev, "Found a pending update!\n");
+
+ /* Process next item in update list */
+ fb_data->cur_update =
+ list_entry(fb_data->upd_buf_queue.next,
+ struct update_data_list, list);
+ list_del_init(&fb_data->cur_update->list);
+ }
+ }
+
+ /* Use LUT selected above */
+ fb_data->cur_update->lut_num = next_lut;
+
+ /* Associate LUT with update markers */
+ list_for_each_entry_safe(next_marker, temp,
+ &fb_data->cur_update->update_desc->upd_marker_list, upd_list)
+ next_marker->lut_num = fb_data->cur_update->lut_num;
+
+ /* Mark LUT as containing new update */
+ fb_data->lut_update_order[fb_data->cur_update->lut_num] =
+ fb_data->cur_update->update_desc->update_order;
+
+ /* Enable Collision and WB complete IRQs */
+ epdc_working_buf_intr(true);
+ epdc_lut_complete_intr(fb_data->rev, fb_data->cur_update->lut_num, true);
+
+ /* Program EPDC update to process buffer */
+ next_upd_region =
+ &fb_data->cur_update->update_desc->upd_data.update_region;
+ if (fb_data->cur_update->update_desc->upd_data.temp
+ != TEMP_USE_AMBIENT) {
+ temp_index = mxc_epdc_fb_get_temp_index(fb_data,
+ fb_data->cur_update->update_desc->upd_data.temp);
+ epdc_set_temp(temp_index);
+ } else
+ epdc_set_temp(fb_data->temp_index);
+ epdc_set_update_addr(fb_data->cur_update->phys_addr +
+ fb_data->cur_update->update_desc->epdc_offs);
+ epdc_set_update_coord(next_upd_region->left, next_upd_region->top);
+ epdc_set_update_dimensions(next_upd_region->width,
+ next_upd_region->height);
+ if (fb_data->rev > 20)
+ epdc_set_update_stride(fb_data->cur_update->update_desc->epdc_stride);
+ if (fb_data->wv_modes_update &&
+ (fb_data->cur_update->update_desc->upd_data.waveform_mode
+ == WAVEFORM_MODE_AUTO)) {
+ epdc_set_update_waveform(&fb_data->wv_modes);
+ fb_data->wv_modes_update = false;
+ }
+
+ epdc_submit_update(fb_data->cur_update->lut_num,
+ fb_data->cur_update->update_desc->upd_data.waveform_mode,
+ fb_data->cur_update->update_desc->upd_data.update_mode,
+ false, false, 0);
+
+ /* Release buffer queues */
+ mutex_unlock(&fb_data->queue_mutex);
+
+ return;
+}
+
+static void draw_mode0(struct mxc_epdc_fb_data *fb_data)
+{
+ u32 *upd_buf_ptr;
+ int i;
+ struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var;
+ u32 xres, yres;
+
+ upd_buf_ptr = (u32 *)fb_data->info.screen_base;
+
+ epdc_working_buf_intr(true);
+ epdc_lut_complete_intr(fb_data->rev, 0, true);
+
+ /* Use unrotated (native) width/height */
+ if ((screeninfo->rotate == FB_ROTATE_CW) ||
+ (screeninfo->rotate == FB_ROTATE_CCW)) {
+ xres = screeninfo->yres;
+ yres = screeninfo->xres;
+ } else {
+ xres = screeninfo->xres;
+ yres = screeninfo->yres;
+ }
+
+ /* Program EPDC update to process buffer */
+ epdc_set_update_addr(fb_data->phys_start);
+ epdc_set_update_coord(0, 0);
+ epdc_set_update_dimensions(xres, yres);
+ if (fb_data->rev > 20)
+ epdc_set_update_stride(0);
+ epdc_submit_update(0, fb_data->wv_modes.mode_init, UPDATE_MODE_FULL,
+ false, true, 0xFF);
+
+ dev_dbg(fb_data->dev, "Mode0 update - Waiting for LUT to complete...\n");
+
+ /* Will timeout after ~4-5 seconds */
+
+ for (i = 0; i < 40; i++) {
+ if (!epdc_is_lut_active(0)) {
+ dev_dbg(fb_data->dev, "Mode0 init complete\n");
+ return;
+ }
+ msleep(100);
+ }
+
+ dev_err(fb_data->dev, "Mode0 init failed!\n");
+
+ return;
+}
+
+
+static void mxc_epdc_fb_fw_handler(const struct firmware *fw,
+ void *context)
+{
+ struct mxc_epdc_fb_data *fb_data = context;
+ int ret;
+ struct mxcfb_waveform_data_file *wv_file;
+ int wv_data_offs;
+ int i;
+ struct mxcfb_update_data update;
+ struct mxcfb_update_marker_data upd_marker_data;
+ struct fb_var_screeninfo *screeninfo = &fb_data->epdc_fb_var;
+ u32 xres, yres;
+ struct clk *epdc_parent;
+ unsigned long rounded_parent_rate, epdc_pix_rate,
+ rounded_pix_clk, target_pix_clk;
+
+ if (fw == NULL) {
+ /* If default FW file load failed, we give up */
+ if (fb_data->fw_default_load)
+ return;
+
+ /* Try to load default waveform */
+ dev_dbg(fb_data->dev,
+ "Can't find firmware. Trying fallback fw\n");
+ fb_data->fw_default_load = true;
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "imx/epdc.fw", fb_data->dev, GFP_KERNEL, fb_data,
+ mxc_epdc_fb_fw_handler);
+ if (ret)
+ dev_err(fb_data->dev,
+ "Failed request_firmware_nowait err %d\n", ret);
+
+ return;
+ }
+
+ wv_file = (struct mxcfb_waveform_data_file *)fw->data;
+
+ /* Get size and allocate temperature range table */
+ fb_data->trt_entries = wv_file->wdh.trc + 1;
+ fb_data->temp_range_bounds = kzalloc(fb_data->trt_entries, GFP_KERNEL);
+
+ for (i = 0; i < fb_data->trt_entries; i++)
+ dev_dbg(fb_data->dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i));
+
+ /* Copy TRT data */
+ memcpy(fb_data->temp_range_bounds, &wv_file->data, fb_data->trt_entries);
+
+ /* Set default temperature index using TRT and room temp */
+ fb_data->temp_index = mxc_epdc_fb_get_temp_index(fb_data, DEFAULT_TEMP);
+
+ /* Get offset and size for waveform data */
+ wv_data_offs = sizeof(wv_file->wdh) + fb_data->trt_entries + 1;
+ fb_data->waveform_buffer_size = fw->size - wv_data_offs;
+
+ /* Allocate memory for waveform data */
+ fb_data->waveform_buffer_virt = dma_alloc_coherent(fb_data->dev,
+ fb_data->waveform_buffer_size,
+ &fb_data->waveform_buffer_phys,
+ GFP_DMA | GFP_KERNEL);
+ if (fb_data->waveform_buffer_virt == NULL) {
+ dev_err(fb_data->dev, "Can't allocate mem for waveform!\n");
+ return;
+ }
+
+ memcpy(fb_data->waveform_buffer_virt, (u8 *)(fw->data) + wv_data_offs,
+ fb_data->waveform_buffer_size);
+
+ release_firmware(fw);
+
+ /* Enable clocks to access EPDC regs */
+ clk_prepare_enable(fb_data->epdc_clk_axi);
+
+ target_pix_clk = fb_data->cur_mode->vmode->pixclock;
+ /* Enable pix clk for EPDC */
+ clk_prepare_enable(fb_data->epdc_clk_pix);
+ rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk);
+
+ if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+ (rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) {
+ /* Can't get close enough without changing parent clk */
+ epdc_parent = clk_get_parent(fb_data->epdc_clk_pix);
+ rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk);
+
+ epdc_pix_rate = target_pix_clk;
+ while (epdc_pix_rate < rounded_parent_rate)
+ epdc_pix_rate *= 2;
+ clk_set_rate(epdc_parent, epdc_pix_rate);
+
+ rounded_pix_clk = clk_round_rate(fb_data->epdc_clk_pix, target_pix_clk);
+ if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+ (rounded_pix_clk <= target_pix_clk - target_pix_clk/100)))
+ /* Still can't get a good clock, provide warning */
+ dev_err(fb_data->dev, "Unable to get an accurate EPDC pix clk"
+ "desired = %lu, actual = %lu\n", target_pix_clk,
+ rounded_pix_clk);
+ }
+
+ clk_set_rate(fb_data->epdc_clk_pix, rounded_pix_clk);
+
+ epdc_init_sequence(fb_data);
+
+ /* Disable clocks */
+ clk_disable_unprepare(fb_data->epdc_clk_axi);
+ clk_disable_unprepare(fb_data->epdc_clk_pix);
+
+ fb_data->hw_ready = true;
+ fb_data->hw_initializing = false;
+
+ /* Use unrotated (native) width/height */
+ if ((screeninfo->rotate == FB_ROTATE_CW) ||
+ (screeninfo->rotate == FB_ROTATE_CCW)) {
+ xres = screeninfo->yres;
+ yres = screeninfo->xres;
+ } else {
+ xres = screeninfo->xres;
+ yres = screeninfo->yres;
+ }
+
+ update.update_region.left = 0;
+ update.update_region.width = xres;
+ update.update_region.top = 0;
+ update.update_region.height = yres;
+ update.update_mode = UPDATE_MODE_FULL;
+ update.waveform_mode = WAVEFORM_MODE_AUTO;
+ update.update_marker = INIT_UPDATE_MARKER;
+ update.temp = TEMP_USE_AMBIENT;
+ update.flags = 0;
+
+ upd_marker_data.update_marker = update.update_marker;
+
+ mxc_epdc_fb_send_update(&update, &fb_data->info);
+
+ /* Block on initial update */
+ ret = mxc_epdc_fb_wait_update_complete(&upd_marker_data,
+ &fb_data->info);
+ if (ret < 0)
+ dev_err(fb_data->dev,
+ "Wait for initial update complete failed."
+ " Error = 0x%x", ret);
+}
+
+static int mxc_epdc_fb_init_hw(struct fb_info *info)
+{
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+ int ret;
+
+ /*
+ * Create fw search string based on ID string in selected videomode.
+ * Format is "imx/epdc_[panel string].fw"
+ */
+ if (fb_data->cur_mode) {
+ strcat(fb_data->fw_str, "imx/epdc_");
+ strcat(fb_data->fw_str, fb_data->cur_mode->vmode->name);
+ strcat(fb_data->fw_str, ".fw");
+ }
+
+ fb_data->fw_default_load = false;
+
+ ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ fb_data->fw_str, fb_data->dev, GFP_KERNEL,
+ fb_data, mxc_epdc_fb_fw_handler);
+ if (ret)
+ dev_dbg(fb_data->dev,
+ "Failed request_firmware_nowait err %d\n", ret);
+
+ return ret;
+}
+
+static ssize_t store_update(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mxcfb_update_data update;
+ struct fb_info *info = dev_get_drvdata(device);
+ struct mxc_epdc_fb_data *fb_data = (struct mxc_epdc_fb_data *)info;
+
+ if (strncmp(buf, "direct", 6) == 0)
+ update.waveform_mode = fb_data->wv_modes.mode_du;
+ else if (strncmp(buf, "gc16", 4) == 0)
+ update.waveform_mode = fb_data->wv_modes.mode_gc16;
+ else if (strncmp(buf, "gc4", 3) == 0)
+ update.waveform_mode = fb_data->wv_modes.mode_gc4;
+
+ /* Now, request full screen update */
+ update.update_region.left = 0;
+ update.update_region.width = fb_data->epdc_fb_var.xres;
+ update.update_region.top = 0;
+ update.update_region.height = fb_data->epdc_fb_var.yres;
+ update.update_mode = UPDATE_MODE_FULL;
+ update.temp = TEMP_USE_AMBIENT;
+ update.update_marker = 0;
+ update.flags = 0;
+
+ mxc_epdc_fb_send_update(&update, info);
+
+ return count;
+}
+
+static struct device_attribute fb_attrs[] = {
+ __ATTR(update, S_IRUGO|S_IWUSR, NULL, store_update),
+};
+
+static const struct of_device_id imx_epdc_dt_ids[] = {
+ { .compatible = "fsl,imx6dl-epdc", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_epdc_dt_ids);
+
+int mxc_epdc_fb_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct pinctrl *pinctrl;
+ struct mxc_epdc_fb_data *fb_data;
+ struct resource *res;
+ struct fb_info *info;
+ char *options, *opt;
+ char *panel_str = NULL;
+ char name[] = "mxcepdcfb";
+ struct fb_videomode *vmode;
+ int xres_virt, yres_virt, buf_size;
+ int xres_virt_rot, yres_virt_rot, pix_size_rot;
+ struct fb_var_screeninfo *var_info;
+ struct fb_fix_screeninfo *fix_info;
+ struct pxp_config_data *pxp_conf;
+ struct pxp_proc_data *proc_data;
+ struct scatterlist *sg;
+ struct update_data_list *upd_list;
+ struct update_data_list *plist, *temp_list;
+ int i;
+ unsigned long x_mem_size = 0;
+ u32 val;
+ int irq;
+
+ fb_data = (struct mxc_epdc_fb_data *)framebuffer_alloc(
+ sizeof(struct mxc_epdc_fb_data), &pdev->dev);
+ if (fb_data == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Get platform data and check validity */
+ fb_data->pdata = &epdc_data;
+ if ((fb_data->pdata == NULL) || (fb_data->pdata->num_modes < 1)
+ || (fb_data->pdata->epdc_mode == NULL)
+ || (fb_data->pdata->epdc_mode->vmode == NULL)) {
+ ret = -EINVAL;
+ goto out_fbdata;
+ }
+
+ if (fb_get_options(name, &options)) {
+ ret = -ENODEV;
+ goto out_fbdata;
+ }
+
+ fb_data->tce_prevent = 0;
+
+ if (options)
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (!*opt)
+ continue;
+
+ if (!strncmp(opt, "bpp=", 4))
+ fb_data->default_bpp =
+ simple_strtoul(opt + 4, NULL, 0);
+ else if (!strncmp(opt, "x_mem=", 6))
+ x_mem_size = memparse(opt + 6, NULL);
+ else if (!strncmp(opt, "tce_prevent", 11))
+ fb_data->tce_prevent = 1;
+ else
+ panel_str = opt;
+ }
+
+ fb_data->dev = &pdev->dev;
+
+ if (!fb_data->default_bpp)
+ fb_data->default_bpp = 16;
+
+ /* Set default (first defined mode) before searching for a match */
+ fb_data->cur_mode = &fb_data->pdata->epdc_mode[0];
+
+ if (panel_str)
+ for (i = 0; i < fb_data->pdata->num_modes; i++)
+ if (!strcmp(fb_data->pdata->epdc_mode[i].vmode->name,
+ panel_str)) {
+ fb_data->cur_mode =
+ &fb_data->pdata->epdc_mode[i];
+ break;
+ }
+
+ vmode = fb_data->cur_mode->vmode;
+
+ platform_set_drvdata(pdev, fb_data);
+ info = &fb_data->info;
+
+ /* Allocate color map for the FB */
+ ret = fb_alloc_cmap(&info->cmap, 256, 0);
+ if (ret)
+ goto out_fbdata;
+
+ dev_dbg(&pdev->dev, "resolution %dx%d, bpp %d\n",
+ vmode->xres, vmode->yres, fb_data->default_bpp);
+
+ /*
+ * GPU alignment restrictions dictate framebuffer parameters:
+ * - 32-byte alignment for buffer width
+ * - 128-byte alignment for buffer height
+ * => 4K buffer alignment for buffer start
+ */
+ xres_virt = ALIGN(vmode->xres, 32);
+ yres_virt = ALIGN(vmode->yres, 128);
+ fb_data->max_pix_size = PAGE_ALIGN(xres_virt * yres_virt);
+
+ /*
+ * Have to check to see if aligned buffer size when rotated
+ * is bigger than when not rotated, and use the max
+ */
+ xres_virt_rot = ALIGN(vmode->yres, 32);
+ yres_virt_rot = ALIGN(vmode->xres, 128);
+ pix_size_rot = PAGE_ALIGN(xres_virt_rot * yres_virt_rot);
+ fb_data->max_pix_size = (fb_data->max_pix_size > pix_size_rot) ?
+ fb_data->max_pix_size : pix_size_rot;
+
+ buf_size = fb_data->max_pix_size * fb_data->default_bpp/8;
+
+ /* Compute the number of screens needed based on X memory requested */
+ if (x_mem_size > 0) {
+ fb_data->num_screens = DIV_ROUND_UP(x_mem_size, buf_size);
+ if (fb_data->num_screens < NUM_SCREENS_MIN)
+ fb_data->num_screens = NUM_SCREENS_MIN;
+ else if (buf_size * fb_data->num_screens > SZ_16M)
+ fb_data->num_screens = SZ_16M / buf_size;
+ } else
+ fb_data->num_screens = NUM_SCREENS_MIN;
+
+ fb_data->map_size = buf_size * fb_data->num_screens;
+ dev_dbg(&pdev->dev, "memory to allocate: %d\n", fb_data->map_size);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res == NULL) {
+ ret = -ENODEV;
+ goto out_cmap;
+ }
+
+ epdc_base = devm_request_and_ioremap(&pdev->dev, res);
+ if (epdc_base == NULL) {
+ ret = -ENOMEM;
+ goto out_cmap;
+ }
+
+ /* Allocate FB memory */
+ info->screen_base = dma_alloc_writecombine(&pdev->dev,
+ fb_data->map_size,
+ &fb_data->phys_start,
+ GFP_DMA | GFP_KERNEL);
+
+ if (info->screen_base == NULL) {
+ ret = -ENOMEM;
+ goto out_cmap;
+ }
+ dev_dbg(&pdev->dev, "allocated at %p:0x%x\n", info->screen_base,
+ fb_data->phys_start);
+
+ var_info = &info->var;
+ var_info->activate = FB_ACTIVATE_TEST;
+ var_info->bits_per_pixel = fb_data->default_bpp;
+ var_info->xres = vmode->xres;
+ var_info->yres = vmode->yres;
+ var_info->xres_virtual = xres_virt;
+ /* Additional screens allow for panning and buffer flipping */
+ var_info->yres_virtual = yres_virt * fb_data->num_screens;
+
+ var_info->pixclock = vmode->pixclock;
+ var_info->left_margin = vmode->left_margin;
+ var_info->right_margin = vmode->right_margin;
+ var_info->upper_margin = vmode->upper_margin;
+ var_info->lower_margin = vmode->lower_margin;
+ var_info->hsync_len = vmode->hsync_len;
+ var_info->vsync_len = vmode->vsync_len;
+ var_info->vmode = FB_VMODE_NONINTERLACED;
+
+ switch (fb_data->default_bpp) {
+ case 32:
+ case 24:
+ var_info->red.offset = 16;
+ var_info->red.length = 8;
+ var_info->green.offset = 8;
+ var_info->green.length = 8;
+ var_info->blue.offset = 0;
+ var_info->blue.length = 8;
+ break;
+
+ case 16:
+ var_info->red.offset = 11;
+ var_info->red.length = 5;
+ var_info->green.offset = 5;
+ var_info->green.length = 6;
+ var_info->blue.offset = 0;
+ var_info->blue.length = 5;
+ break;
+
+ case 8:
+ /*
+ * For 8-bit grayscale, R, G, and B offset are equal.
+ *
+ */
+ var_info->grayscale = GRAYSCALE_8BIT;
+
+ var_info->red.length = 8;
+ var_info->red.offset = 0;
+ var_info->red.msb_right = 0;
+ var_info->green.length = 8;
+ var_info->green.offset = 0;
+ var_info->green.msb_right = 0;
+ var_info->blue.length = 8;
+ var_info->blue.offset = 0;
+ var_info->blue.msb_right = 0;
+ break;
+
+ default:
+ dev_err(&pdev->dev, "unsupported bitwidth %d\n",
+ fb_data->default_bpp);
+ ret = -EINVAL;
+ goto out_dma_fb;
+ }
+
+ fix_info = &info->fix;
+
+ strcpy(fix_info->id, "mxc_epdc_fb");
+ fix_info->type = FB_TYPE_PACKED_PIXELS;
+ fix_info->visual = FB_VISUAL_TRUECOLOR;
+ fix_info->xpanstep = 0;
+ fix_info->ypanstep = 0;
+ fix_info->ywrapstep = 0;
+ fix_info->accel = FB_ACCEL_NONE;
+ fix_info->smem_start = fb_data->phys_start;
+ fix_info->smem_len = fb_data->map_size;
+ fix_info->ypanstep = 0;
+
+ fb_data->native_width = vmode->xres;
+ fb_data->native_height = vmode->yres;
+
+ info->fbops = &mxc_epdc_fb_ops;
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->pseudo_palette = fb_data->pseudo_palette;
+ info->screen_size = info->fix.smem_len;
+ info->flags = FBINFO_FLAG_DEFAULT;
+
+ mxc_epdc_fb_set_fix(info);
+
+ fb_data->auto_mode = AUTO_UPDATE_MODE_REGION_MODE;
+ fb_data->upd_scheme = UPDATE_SCHEME_QUEUE_AND_MERGE;
+
+ /* Initialize our internal copy of the screeninfo */
+ fb_data->epdc_fb_var = *var_info;
+ fb_data->fb_offset = 0;
+ fb_data->eof_sync_period = 0;
+
+ fb_data->epdc_clk_axi = clk_get(fb_data->dev, "epdc_axi");
+ if (IS_ERR(fb_data->epdc_clk_axi)) {
+ dev_err(&pdev->dev, "Unable to get EPDC AXI clk."
+ "err = %d\n", (int)fb_data->epdc_clk_axi);
+ ret = -ENODEV;
+ goto out_dma_fb;
+ }
+ fb_data->epdc_clk_pix = clk_get(fb_data->dev, "epdc_pix");
+ if (IS_ERR(fb_data->epdc_clk_pix)) {
+ dev_err(&pdev->dev, "Unable to get EPDC pix clk."
+ "err = %d\n", (int)fb_data->epdc_clk_pix);
+ ret = -ENODEV;
+ goto out_dma_fb;
+ }
+
+ clk_prepare_enable(fb_data->epdc_clk_axi);
+ val = __raw_readl(EPDC_VERSION);
+ clk_disable_unprepare(fb_data->epdc_clk_axi);
+ fb_data->rev = ((val & EPDC_VERSION_MAJOR_MASK) >>
+ EPDC_VERSION_MAJOR_OFFSET) * 10
+ + ((val & EPDC_VERSION_MINOR_MASK) >>
+ EPDC_VERSION_MINOR_OFFSET);
+ dev_dbg(&pdev->dev, "EPDC version = %d\n", fb_data->rev);
+
+ if (fb_data->rev < 20) {
+ fb_data->num_luts = EPDC_V1_NUM_LUTS;
+ fb_data->max_num_updates = EPDC_V1_MAX_NUM_UPDATES;
+ } else {
+ fb_data->num_luts = EPDC_V2_NUM_LUTS;
+ fb_data->max_num_updates = EPDC_V2_MAX_NUM_UPDATES;
+ if (vmode->xres > EPDC_V2_MAX_UPDATE_WIDTH)
+ fb_data->restrict_width = true;
+ }
+ fb_data->max_num_buffers = EPDC_MAX_NUM_BUFFERS;
+
+ /*
+ * Initialize lists for pending updates,
+ * active update requests, update collisions,
+ * and freely available updates.
+ */
+ INIT_LIST_HEAD(&fb_data->upd_pending_list);
+ INIT_LIST_HEAD(&fb_data->upd_buf_queue);
+ INIT_LIST_HEAD(&fb_data->upd_buf_free_list);
+ INIT_LIST_HEAD(&fb_data->upd_buf_collision_list);
+
+ /* Allocate update buffers and add them to the list */
+ for (i = 0; i < fb_data->max_num_updates; i++) {
+ upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL);
+ if (upd_list == NULL) {
+ ret = -ENOMEM;
+ goto out_upd_lists;
+ }
+
+ /* Add newly allocated buffer to free list */
+ list_add(&upd_list->list, &fb_data->upd_buf_free_list);
+ }
+
+ fb_data->virt_addr_updbuf =
+ kzalloc(sizeof(void *) * fb_data->max_num_buffers, GFP_KERNEL);
+ fb_data->phys_addr_updbuf =
+ kzalloc(sizeof(dma_addr_t) * fb_data->max_num_buffers,
+ GFP_KERNEL);
+ for (i = 0; i < fb_data->max_num_buffers; i++) {
+ /*
+ * Allocate memory for PxP output buffer.
+ * Each update buffer is 1 byte per pixel, and can
+ * be as big as the full-screen frame buffer
+ */
+ fb_data->virt_addr_updbuf[i] =
+ kmalloc(fb_data->max_pix_size, GFP_KERNEL);
+ fb_data->phys_addr_updbuf[i] =
+ virt_to_phys(fb_data->virt_addr_updbuf[i]);
+ if (fb_data->virt_addr_updbuf[i] == NULL) {
+ ret = -ENOMEM;
+ goto out_upd_buffers;
+ }
+
+ dev_dbg(fb_data->info.device, "allocated %d bytes @ 0x%08X\n",
+ fb_data->max_pix_size, fb_data->phys_addr_updbuf[i]);
+ }
+
+ /* Counter indicating which update buffer should be used next. */
+ fb_data->upd_buffer_num = 0;
+
+ /*
+ * Allocate memory for PxP SW workaround buffer
+ * These buffers are used to hold copy of the update region,
+ * before sending it to PxP for processing.
+ */
+ fb_data->virt_addr_copybuf =
+ dma_alloc_coherent(fb_data->info.device, fb_data->max_pix_size*2,
+ &fb_data->phys_addr_copybuf,
+ GFP_DMA | GFP_KERNEL);
+ if (fb_data->virt_addr_copybuf == NULL) {
+ ret = -ENOMEM;
+ goto out_upd_buffers;
+ }
+
+ fb_data->working_buffer_size = vmode->yres * vmode->xres * 2;
+ /* Allocate memory for EPDC working buffer */
+ fb_data->working_buffer_virt =
+ dma_alloc_coherent(&pdev->dev, fb_data->working_buffer_size,
+ &fb_data->working_buffer_phys,
+ GFP_DMA | GFP_KERNEL);
+ if (fb_data->working_buffer_virt == NULL) {
+ dev_err(&pdev->dev, "Can't allocate mem for working buf!\n");
+ ret = -ENOMEM;
+ goto out_copybuffer;
+ }
+
+ /* Initialize EPDC pins */
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ dev_err(&pdev->dev, "can't get/select pinctrl\n");
+ ret = PTR_ERR(pinctrl);
+ goto out_copybuffer;
+ }
+
+ fb_data->in_init = false;
+
+ fb_data->hw_ready = false;
+ fb_data->hw_initializing = false;
+
+ /*
+ * Set default waveform mode values.
+ * Should be overwritten via ioctl.
+ */
+ fb_data->wv_modes.mode_init = 0;
+ fb_data->wv_modes.mode_du = 1;
+ fb_data->wv_modes.mode_gc4 = 3;
+ fb_data->wv_modes.mode_gc8 = 2;
+ fb_data->wv_modes.mode_gc16 = 2;
+ fb_data->wv_modes.mode_gc32 = 2;
+ fb_data->wv_modes_update = true;
+
+ /* Initialize marker list */
+ INIT_LIST_HEAD(&fb_data->full_marker_list);
+
+ /* Initialize all LUTs to inactive */
+ fb_data->lut_update_order =
+ kzalloc(fb_data->num_luts * sizeof(u32 *), GFP_KERNEL);
+ for (i = 0; i < fb_data->num_luts; i++)
+ fb_data->lut_update_order[i] = 0;
+
+ INIT_DELAYED_WORK(&fb_data->epdc_done_work, epdc_done_work_func);
+ fb_data->epdc_submit_workqueue = alloc_workqueue("EPDC Submit",
+ WQ_MEM_RECLAIM | WQ_HIGHPRI |
+ WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+ INIT_WORK(&fb_data->epdc_submit_work, epdc_submit_work_func);
+ fb_data->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt",
+ WQ_MEM_RECLAIM | WQ_HIGHPRI |
+ WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+ INIT_WORK(&fb_data->epdc_intr_work, epdc_intr_work_func);
+
+ /* Retrieve EPDC IRQ num */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "cannot get IRQ resource\n");
+ ret = -ENODEV;
+ goto out_dma_work_buf;
+ }
+ fb_data->epdc_irq = irq;
+
+ /* Register IRQ handler */
+ ret = devm_request_irq(&pdev->dev, fb_data->epdc_irq,
+ mxc_epdc_irq_handler, 0, "epdc", fb_data);
+ if (ret) {
+ dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
+ fb_data->epdc_irq, ret);
+ ret = -ENODEV;
+ goto out_dma_work_buf;
+ }
+
+ info->fbdefio = &mxc_epdc_fb_defio;
+#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE
+ fb_deferred_io_init(info);
+#endif
+
+ /* get pmic regulators */
+ fb_data->display_regulator = devm_regulator_get(&pdev->dev, "DISPLAY");
+ if (IS_ERR(fb_data->display_regulator)) {
+ dev_err(&pdev->dev, "Unable to get display PMIC regulator."
+ "err = 0x%x\n", (int)fb_data->display_regulator);
+ ret = -ENODEV;
+ goto out_dma_work_buf;
+ }
+ fb_data->vcom_regulator = devm_regulator_get(&pdev->dev, "VCOM");
+ if (IS_ERR(fb_data->vcom_regulator)) {
+ dev_err(&pdev->dev, "Unable to get VCOM regulator."
+ "err = 0x%x\n", (int)fb_data->vcom_regulator);
+ ret = -ENODEV;
+ goto out_dma_work_buf;
+ }
+ fb_data->v3p3_regulator = devm_regulator_get(&pdev->dev, "V3P3");
+ if (IS_ERR(fb_data->v3p3_regulator)) {
+ dev_err(&pdev->dev, "Unable to get V3P3 regulator."
+ "err = 0x%x\n", (int)fb_data->vcom_regulator);
+ ret = -ENODEV;
+ goto out_dma_work_buf;
+ }
+
+ if (device_create_file(info->dev, &fb_attrs[0]))
+ dev_err(&pdev->dev, "Unable to create file from fb_attrs\n");
+
+ fb_data->cur_update = NULL;
+
+ mutex_init(&fb_data->queue_mutex);
+ mutex_init(&fb_data->pxp_mutex);
+ mutex_init(&fb_data->power_mutex);
+
+ /*
+ * Fill out PxP config data structure based on FB info and
+ * processing tasks required
+ */
+ pxp_conf = &fb_data->pxp_conf;
+ proc_data = &pxp_conf->proc_data;
+
+ /* Initialize non-channel-specific PxP parameters */
+ proc_data->drect.left = proc_data->srect.left = 0;
+ proc_data->drect.top = proc_data->srect.top = 0;
+ proc_data->drect.width = proc_data->srect.width = fb_data->info.var.xres;
+ proc_data->drect.height = proc_data->srect.height = fb_data->info.var.yres;
+ proc_data->scaling = 0;
+ proc_data->hflip = 0;
+ proc_data->vflip = 0;
+ proc_data->rotate = 0;
+ proc_data->bgcolor = 0;
+ proc_data->overlay_state = 0;
+ proc_data->lut_transform = PXP_LUT_NONE;
+ proc_data->lut_map = NULL;
+
+ /*
+ * We initially configure PxP for RGB->YUV conversion,
+ * and only write out Y component of the result.
+ */
+
+ /*
+ * Initialize S0 channel parameters
+ * Parameters should match FB format/width/height
+ */
+ pxp_conf->s0_param.pixel_fmt = PXP_PIX_FMT_RGB565;
+ pxp_conf->s0_param.width = fb_data->info.var.xres_virtual;
+ pxp_conf->s0_param.height = fb_data->info.var.yres;
+ pxp_conf->s0_param.color_key = -1;
+ pxp_conf->s0_param.color_key_enable = false;
+
+ /*
+ * Initialize OL0 channel parameters
+ * No overlay will be used for PxP operation
+ */
+ for (i = 0; i < 8; i++) {
+ pxp_conf->ol_param[i].combine_enable = false;
+ pxp_conf->ol_param[i].width = 0;
+ pxp_conf->ol_param[i].height = 0;
+ pxp_conf->ol_param[i].pixel_fmt = PXP_PIX_FMT_RGB565;
+ pxp_conf->ol_param[i].color_key_enable = false;
+ pxp_conf->ol_param[i].color_key = -1;
+ pxp_conf->ol_param[i].global_alpha_enable = false;
+ pxp_conf->ol_param[i].global_alpha = 0;
+ pxp_conf->ol_param[i].local_alpha_enable = false;
+ }
+
+ /*
+ * Initialize Output channel parameters
+ * Output is Y-only greyscale
+ * Output width/height will vary based on update region size
+ */
+ pxp_conf->out_param.width = fb_data->info.var.xres;
+ pxp_conf->out_param.height = fb_data->info.var.yres;
+ pxp_conf->out_param.stride = pxp_conf->out_param.width;
+ pxp_conf->out_param.pixel_fmt = PXP_PIX_FMT_GREY;
+
+ /* Initialize color map for conversion of 8-bit gray pixels */
+ fb_data->pxp_conf.proc_data.lut_map = kmalloc(256, GFP_KERNEL);
+ if (fb_data->pxp_conf.proc_data.lut_map == NULL) {
+ dev_err(&pdev->dev, "Can't allocate mem for lut map!\n");
+ ret = -ENOMEM;
+ goto out_dma_work_buf;
+ }
+ for (i = 0; i < 256; i++)
+ fb_data->pxp_conf.proc_data.lut_map[i] = i;
+
+ fb_data->pxp_conf.proc_data.lut_map_updated = true;
+
+ /*
+ * Ensure this is set to NULL here...we will initialize pxp_chan
+ * later in our thread.
+ */
+ fb_data->pxp_chan = NULL;
+
+ /* Initialize Scatter-gather list containing 2 buffer addresses. */
+ sg = fb_data->sg;
+ sg_init_table(sg, 2);
+
+ /*
+ * For use in PxP transfers:
+ * sg[0] holds the FB buffer pointer
+ * sg[1] holds the Output buffer pointer (configured before TX request)
+ */
+ sg_dma_address(&sg[0]) = info->fix.smem_start;
+ sg_set_page(&sg[0], virt_to_page(info->screen_base),
+ info->fix.smem_len, offset_in_page(info->screen_base));
+
+ fb_data->order_cnt = 0;
+ fb_data->waiting_for_wb = false;
+ fb_data->waiting_for_lut = false;
+ fb_data->waiting_for_lut15 = false;
+ fb_data->waiting_for_idle = false;
+ fb_data->blank = FB_BLANK_UNBLANK;
+ fb_data->power_state = POWER_STATE_OFF;
+ fb_data->powering_down = false;
+ fb_data->wait_for_powerdown = false;
+ fb_data->updates_active = false;
+ fb_data->pwrdown_delay = 0;
+
+ /* Register FB */
+ ret = register_framebuffer(info);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "register_framebuffer failed with error %d\n", ret);
+ goto out_lutmap;
+ }
+
+ g_fb_data = fb_data;
+
+ pm_runtime_enable(fb_data->dev);
+
+#ifdef DEFAULT_PANEL_HW_INIT
+ ret = mxc_epdc_fb_init_hw((struct fb_info *)fb_data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize HW!\n");
+ }
+#endif
+
+ goto out;
+
+out_lutmap:
+ kfree(fb_data->pxp_conf.proc_data.lut_map);
+out_dma_work_buf:
+ dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size,
+ fb_data->working_buffer_virt, fb_data->working_buffer_phys);
+out_copybuffer:
+ dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2,
+ fb_data->virt_addr_copybuf,
+ fb_data->phys_addr_copybuf);
+out_upd_buffers:
+ for (i = 0; i < fb_data->max_num_buffers; i++)
+ if (fb_data->virt_addr_updbuf[i] != NULL)
+ kfree(fb_data->virt_addr_updbuf[i]);
+ if (fb_data->virt_addr_updbuf != NULL)
+ kfree(fb_data->virt_addr_updbuf);
+ if (fb_data->phys_addr_updbuf != NULL)
+ kfree(fb_data->phys_addr_updbuf);
+out_upd_lists:
+ list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list,
+ list) {
+ list_del(&plist->list);
+ kfree(plist);
+ }
+out_dma_fb:
+ dma_free_writecombine(&pdev->dev, fb_data->map_size, info->screen_base,
+ fb_data->phys_start);
+
+out_cmap:
+ fb_dealloc_cmap(&info->cmap);
+out_fbdata:
+ kfree(fb_data);
+out:
+ return ret;
+}
+
+static int mxc_epdc_fb_remove(struct platform_device *pdev)
+{
+ struct update_data_list *plist, *temp_list;
+ struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev);
+ int i;
+
+ mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &fb_data->info);
+
+ flush_workqueue(fb_data->epdc_submit_workqueue);
+ destroy_workqueue(fb_data->epdc_submit_workqueue);
+
+ unregister_framebuffer(&fb_data->info);
+
+ for (i = 0; i < fb_data->max_num_buffers; i++)
+ if (fb_data->virt_addr_updbuf[i] != NULL)
+ kfree(fb_data->virt_addr_updbuf[i]);
+ if (fb_data->virt_addr_updbuf != NULL)
+ kfree(fb_data->virt_addr_updbuf);
+ if (fb_data->phys_addr_updbuf != NULL)
+ kfree(fb_data->phys_addr_updbuf);
+
+ dma_free_writecombine(&pdev->dev, fb_data->working_buffer_size,
+ fb_data->working_buffer_virt,
+ fb_data->working_buffer_phys);
+ if (fb_data->waveform_buffer_virt != NULL)
+ dma_free_writecombine(&pdev->dev, fb_data->waveform_buffer_size,
+ fb_data->waveform_buffer_virt,
+ fb_data->waveform_buffer_phys);
+ if (fb_data->virt_addr_copybuf != NULL)
+ dma_free_writecombine(&pdev->dev, fb_data->max_pix_size*2,
+ fb_data->virt_addr_copybuf,
+ fb_data->phys_addr_copybuf);
+ list_for_each_entry_safe(plist, temp_list, &fb_data->upd_buf_free_list,
+ list) {
+ list_del(&plist->list);
+ kfree(plist);
+ }
+#ifdef CONFIG_FB_MXC_EINK_AUTO_UPDATE_MODE
+ fb_deferred_io_cleanup(&fb_data->info);
+#endif
+
+ dma_free_writecombine(&pdev->dev, fb_data->map_size, fb_data->info.screen_base,
+ fb_data->phys_start);
+
+ /* Release PxP-related resources */
+ if (fb_data->pxp_chan != NULL)
+ dma_release_channel(&fb_data->pxp_chan->dma_chan);
+
+ fb_dealloc_cmap(&fb_data->info.cmap);
+
+ framebuffer_release(&fb_data->info);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int mxc_epdc_fb_suspend(struct device *dev)
+{
+ struct mxc_epdc_fb_data *data = dev_get_drvdata(dev);
+ int ret;
+
+ data->pwrdown_delay = FB_POWERDOWN_DISABLE;
+ ret = mxc_epdc_fb_blank(FB_BLANK_POWERDOWN, &data->info);
+ if (ret)
+ goto out;
+
+out:
+ return ret;
+}
+
+static int mxc_epdc_fb_resume(struct device *dev)
+{
+ struct mxc_epdc_fb_data *data = dev_get_drvdata(dev);
+
+ mxc_epdc_fb_blank(FB_BLANK_UNBLANK, &data->info);
+ epdc_init_settings(data);
+ data->updates_active = false;
+
+ return 0;
+}
+#else
+#define mxc_epdc_fb_suspend NULL
+#define mxc_epdc_fb_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int mxc_epdc_fb_runtime_suspend(struct device *dev)
+{
+ release_bus_freq(BUS_FREQ_HIGH);
+ dev_dbg(dev, "epdc busfreq high release.\n");
+
+ return 0;
+}
+
+static int mxc_epdc_fb_runtime_resume(struct device *dev)
+{
+ request_bus_freq(BUS_FREQ_HIGH);
+ dev_dbg(dev, "epdc busfreq high request.\n");
+
+ return 0;
+}
+#else
+#define mxc_epdc_fb_runtime_suspend NULL
+#define mxc_epdc_fb_runtime_resume NULL
+#endif
+
+static const struct dev_pm_ops mxc_epdc_fb_pm_ops = {
+ SET_RUNTIME_PM_OPS(mxc_epdc_fb_runtime_suspend,
+ mxc_epdc_fb_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(mxc_epdc_fb_suspend, mxc_epdc_fb_resume)
+};
+
+static void mxc_epdc_fb_shutdown(struct platform_device *pdev)
+{
+ struct mxc_epdc_fb_data *fb_data = platform_get_drvdata(pdev);
+
+ /* Disable power to the EPD panel */
+ if (regulator_is_enabled(fb_data->vcom_regulator))
+ regulator_disable(fb_data->vcom_regulator);
+ if (regulator_is_enabled(fb_data->display_regulator))
+ regulator_disable(fb_data->display_regulator);
+
+ /* Disable clocks to EPDC */
+ clk_prepare_enable(fb_data->epdc_clk_axi);
+ clk_prepare_enable(fb_data->epdc_clk_pix);
+ __raw_writel(EPDC_CTRL_CLKGATE, EPDC_CTRL_SET);
+ clk_disable_unprepare(fb_data->epdc_clk_pix);
+ clk_disable_unprepare(fb_data->epdc_clk_axi);
+
+ /* turn off the V3p3 */
+ if (regulator_is_enabled(fb_data->v3p3_regulator))
+ regulator_disable(fb_data->v3p3_regulator);
+}
+
+static struct platform_driver mxc_epdc_fb_driver = {
+ .probe = mxc_epdc_fb_probe,
+ .remove = mxc_epdc_fb_remove,
+ .shutdown = mxc_epdc_fb_shutdown,
+ .driver = {
+ .name = "imx_epdc_fb",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(imx_epdc_dt_ids),
+ .pm = &mxc_epdc_fb_pm_ops,
+ },
+};
+
+/* Callback function triggered after PxP receives an EOF interrupt */
+static void pxp_dma_done(void *arg)
+{
+ struct pxp_tx_desc *tx_desc = to_tx_desc(arg);
+ struct dma_chan *chan = tx_desc->txd.chan;
+ struct pxp_channel *pxp_chan = to_pxp_channel(chan);
+ struct mxc_epdc_fb_data *fb_data = pxp_chan->client;
+
+ /* This call will signal wait_for_completion_timeout() in send_buffer_to_pxp */
+ complete(&fb_data->pxp_tx_cmpl);
+}
+
+static bool chan_filter(struct dma_chan *chan, void *arg)
+{
+ if (imx_dma_is_pxp(chan))
+ return true;
+ else
+ return false;
+}
+
+/* Function to request PXP DMA channel */
+static int pxp_chan_init(struct mxc_epdc_fb_data *fb_data)
+{
+ dma_cap_mask_t mask;
+ struct dma_chan *chan;
+
+ /*
+ * Request a free channel
+ */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_PRIVATE, mask);
+ chan = dma_request_channel(mask, chan_filter, NULL);
+ if (!chan) {
+ dev_err(fb_data->dev, "Unsuccessfully received channel!!!!\n");
+ return -EBUSY;
+ }
+
+ fb_data->pxp_chan = to_pxp_channel(chan);
+ fb_data->pxp_chan->client = fb_data;
+
+ init_completion(&fb_data->pxp_tx_cmpl);
+
+ return 0;
+}
+
+/*
+ * Function to call PxP DMA driver and send our latest FB update region
+ * through the PxP and out to an intermediate buffer.
+ * Note: This is a blocking call, so upon return the PxP tx should be complete.
+ */
+static int pxp_process_update(struct mxc_epdc_fb_data *fb_data,
+ u32 src_width, u32 src_height,
+ struct mxcfb_rect *update_region)
+{
+ dma_cookie_t cookie;
+ struct scatterlist *sg = fb_data->sg;
+ struct dma_chan *dma_chan;
+ struct pxp_tx_desc *desc;
+ struct dma_async_tx_descriptor *txd;
+ struct pxp_config_data *pxp_conf = &fb_data->pxp_conf;
+ struct pxp_proc_data *proc_data = &fb_data->pxp_conf.proc_data;
+ int i, ret;
+ int length;
+
+ dev_dbg(fb_data->dev, "Starting PxP Send Buffer\n");
+
+ /* First, check to see that we have acquired a PxP Channel object */
+ if (fb_data->pxp_chan == NULL) {
+ /*
+ * PxP Channel has not yet been created and initialized,
+ * so let's go ahead and try
+ */
+ ret = pxp_chan_init(fb_data);
+ if (ret) {
+ /*
+ * PxP channel init failed, and we can't use the
+ * PxP until the PxP DMA driver has loaded, so we abort
+ */
+ dev_err(fb_data->dev, "PxP chan init failed\n");
+ return -ENODEV;
+ }
+ }
+
+ /*
+ * Init completion, so that we
+ * can be properly informed of the completion
+ * of the PxP task when it is done.
+ */
+ init_completion(&fb_data->pxp_tx_cmpl);
+
+ dma_chan = &fb_data->pxp_chan->dma_chan;
+
+ txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg, 2,
+ DMA_TO_DEVICE,
+ DMA_PREP_INTERRUPT,
+ NULL);
+ if (!txd) {
+ dev_err(fb_data->info.device,
+ "Error preparing a DMA transaction descriptor.\n");
+ return -EIO;
+ }
+
+ txd->callback_param = txd;
+ txd->callback = pxp_dma_done;
+
+ /*
+ * Configure PxP for processing of new update region
+ * The rest of our config params were set up in
+ * probe() and should not need to be changed.
+ */
+ pxp_conf->s0_param.width = src_width;
+ pxp_conf->s0_param.height = src_height;
+ proc_data->srect.top = update_region->top;
+ proc_data->srect.left = update_region->left;
+ proc_data->srect.width = update_region->width;
+ proc_data->srect.height = update_region->height;
+
+ /*
+ * Because only YUV/YCbCr image can be scaled, configure
+ * drect equivalent to srect, as such do not perform scaling.
+ */
+ proc_data->drect.top = 0;
+ proc_data->drect.left = 0;
+ proc_data->drect.width = proc_data->srect.width;
+ proc_data->drect.height = proc_data->srect.height;
+
+ /* PXP expects rotation in terms of degrees */
+ proc_data->rotate = fb_data->epdc_fb_var.rotate * 90;
+ if (proc_data->rotate > 270)
+ proc_data->rotate = 0;
+
+ pxp_conf->out_param.width = update_region->width;
+ pxp_conf->out_param.height = update_region->height;
+
+ if ((proc_data->rotate == 90) || (proc_data->rotate == 270))
+ pxp_conf->out_param.stride = update_region->height;
+ else
+ pxp_conf->out_param.stride = update_region->width;
+
+ /* For EPDC v2.0, we need output to be 64-bit
+ * aligned since EPDC stride does not work. */
+ if (fb_data->rev <= 20)
+ pxp_conf->out_param.stride = ALIGN(pxp_conf->out_param.stride, 8);
+
+
+ desc = to_tx_desc(txd);
+ length = desc->len;
+ for (i = 0; i < length; i++) {
+ if (i == 0) {/* S0 */
+ memcpy(&desc->proc_data, proc_data, sizeof(struct pxp_proc_data));
+ pxp_conf->s0_param.paddr = sg_dma_address(&sg[0]);
+ memcpy(&desc->layer_param.s0_param, &pxp_conf->s0_param,
+ sizeof(struct pxp_layer_param));
+ } else if (i == 1) {
+ pxp_conf->out_param.paddr = sg_dma_address(&sg[1]);
+ memcpy(&desc->layer_param.out_param, &pxp_conf->out_param,
+ sizeof(struct pxp_layer_param));
+ }
+ /* TODO: OverLay */
+
+ desc = desc->next;
+ }
+
+ /* Submitting our TX starts the PxP processing task */
+ cookie = txd->tx_submit(txd);
+ if (cookie < 0) {
+ dev_err(fb_data->info.device, "Error sending FB through PxP\n");
+ return -EIO;
+ }
+
+ fb_data->txd = txd;
+
+ /* trigger ePxP */
+ dma_async_issue_pending(dma_chan);
+
+ return 0;
+}
+
+static int pxp_complete_update(struct mxc_epdc_fb_data *fb_data, u32 *hist_stat)
+{
+ int ret;
+ /*
+ * Wait for completion event, which will be set
+ * through our TX callback function.
+ */
+ ret = wait_for_completion_timeout(&fb_data->pxp_tx_cmpl, HZ / 10);
+ if (ret <= 0) {
+ dev_info(fb_data->info.device,
+ "PxP operation failed due to %s\n",
+ ret < 0 ? "user interrupt" : "timeout");
+ dma_release_channel(&fb_data->pxp_chan->dma_chan);
+ fb_data->pxp_chan = NULL;
+ return ret ? : -ETIMEDOUT;
+ }
+
+ if ((fb_data->pxp_conf.proc_data.lut_transform & EPDC_FLAG_USE_CMAP) &&
+ fb_data->pxp_conf.proc_data.lut_map_updated)
+ fb_data->pxp_conf.proc_data.lut_map_updated = false;
+
+ *hist_stat = to_tx_desc(fb_data->txd)->hist_status;
+ dma_release_channel(&fb_data->pxp_chan->dma_chan);
+ fb_data->pxp_chan = NULL;
+
+ dev_dbg(fb_data->dev, "TX completed\n");
+
+ return 0;
+}
+
+/*
+ * Different dithering algorithm can be used. We chose
+ * to implement Bill Atkinson's algorithm as an example
+ * Thanks Bill Atkinson for his dithering algorithm.
+ */
+
+/*
+ * Dithering algorithm implementation - Y8->Y1 version 1.0 for i.MX
+ */
+static void do_dithering_processing_Y1_v1_0(
+ unsigned char *update_region_ptr,
+ struct mxcfb_rect *update_region,
+ unsigned long update_region_stride,
+ int *err_dist)
+{
+
+ /* create a temp error distribution array */
+ int bwPix;
+ int y;
+ int col;
+ int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error;
+ int width_3 = update_region->width + 3;
+ char *y8buf;
+ int x_offset = 0;
+
+ /* prime a few elements the error distribution array */
+ for (y = 0; y < update_region->height; y++) {
+ /* Dithering the Y8 in sbuf to BW suitable for A2 waveform */
+ err_dist_l0 = err_dist + (width_3) * (y % 3);
+ err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3);
+ err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3);
+
+ y8buf = update_region_ptr + x_offset;
+
+ /* scan the line and convert the Y8 to BW */
+ for (col = 1; col <= update_region->width; col++) {
+ bwPix = *(err_dist_l0 + col) + *y8buf;
+
+ if (bwPix >= 128) {
+ *y8buf++ = 0xff;
+ distrib_error = (bwPix - 255) >> 3;
+ } else {
+ *y8buf++ = 0;
+ distrib_error = bwPix >> 3;
+ }
+
+ /* modify the error distribution buffer */
+ *(err_dist_l0 + col + 2) += distrib_error;
+ *(err_dist_l1 + col + 1) += distrib_error;
+ *(err_dist_l0 + col + 1) += distrib_error;
+ *(err_dist_l1 + col - 1) += distrib_error;
+ *(err_dist_l1 + col) += distrib_error;
+ *(err_dist_l2 + col) = distrib_error;
+ }
+ x_offset += update_region_stride;
+ }
+
+ flush_cache_all();
+ outer_flush_all();
+}
+
+/*
+ * Dithering algorithm implementation - Y8->Y4 version 1.0 for i.MX
+ */
+
+static void do_dithering_processing_Y4_v1_0(
+ unsigned char *update_region_ptr,
+ struct mxcfb_rect *update_region,
+ unsigned long update_region_stride,
+ int *err_dist)
+{
+
+ /* create a temp error distribution array */
+ int gcPix;
+ int y;
+ int col;
+ int *err_dist_l0, *err_dist_l1, *err_dist_l2, distrib_error;
+ int width_3 = update_region->width + 3;
+ char *y8buf;
+ int x_offset = 0;
+
+ /* prime a few elements the error distribution array */
+ for (y = 0; y < update_region->height; y++) {
+ /* Dithering the Y8 in sbuf to Y4 */
+ err_dist_l0 = err_dist + (width_3) * (y % 3);
+ err_dist_l1 = err_dist + (width_3) * ((y + 1) % 3);
+ err_dist_l2 = err_dist + (width_3) * ((y + 2) % 3);
+
+ y8buf = update_region_ptr + x_offset;
+
+ /* scan the line and convert the Y8 to Y4 */
+ for (col = 1; col <= update_region->width; col++) {
+ gcPix = *(err_dist_l0 + col) + *y8buf;
+
+ if (gcPix > 255)
+ gcPix = 255;
+ else if (gcPix < 0)
+ gcPix = 0;
+
+ distrib_error = (*y8buf - (gcPix & 0xf0)) >> 3;
+
+ *y8buf++ = gcPix & 0xf0;
+
+ /* modify the error distribution buffer */
+ *(err_dist_l0 + col + 2) += distrib_error;
+ *(err_dist_l1 + col + 1) += distrib_error;
+ *(err_dist_l0 + col + 1) += distrib_error;
+ *(err_dist_l1 + col - 1) += distrib_error;
+ *(err_dist_l1 + col) += distrib_error;
+ *(err_dist_l2 + col) = distrib_error;
+ }
+ x_offset += update_region_stride;
+ }
+
+ flush_cache_all();
+ outer_flush_all();
+}
+
+module_platform_driver(mxc_epdc_fb_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC EPDC framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
diff --git a/drivers/video/mxc/mxc_hdmi.c b/drivers/video/mxc/mxc_hdmi.c
new file mode 100644
index 000000000000..bcd031df5b9d
--- /dev/null
+++ b/drivers/video/mxc/mxc_hdmi.c
@@ -0,0 +1,2916 @@
+/*
+ * Copyright (C) 2011-2014 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+/*
+ * SH-Mobile High-Definition Multimedia Interface (HDMI) driver
+ * for SLISHDMI13T and SLIPHDMIT IP cores
+ *
+ * Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/uaccess.h>
+#include <linux/cpufreq.h>
+#include <linux/firmware.h>
+#include <linux/kthread.h>
+#include <linux/regulator/driver.h>
+#include <linux/fsl_devices.h>
+#include <linux/ipu.h>
+#include <linux/regmap.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/of_device.h>
+
+#include <linux/console.h>
+#include <linux/types.h>
+
+#include "../edid.h"
+#include <video/mxc_edid.h>
+#include <video/mxc_hdmi.h>
+#include "mxc_dispdrv.h"
+
+#include <linux/mfd/mxc-hdmi-core.h>
+
+#define DISPDRV_HDMI "hdmi"
+#define HDMI_EDID_LEN 512
+
+/* status codes for reading edid */
+#define HDMI_EDID_SUCCESS 0
+#define HDMI_EDID_FAIL -1
+#define HDMI_EDID_SAME -2
+#define HDMI_EDID_NO_MODES -3
+
+#define NUM_CEA_VIDEO_MODES 64
+#define DEFAULT_VIDEO_MODE 16 /* 1080P */
+
+#define RGB 0
+#define YCBCR444 1
+#define YCBCR422_16BITS 2
+#define YCBCR422_8BITS 3
+#define XVYCC444 4
+
+/*
+ * We follow a flowchart which is in the "Synopsys DesignWare Courses
+ * HDMI Transmitter Controller User Guide, 1.30a", section 3.1
+ * (dwc_hdmi_tx_user.pdf)
+ *
+ * Below are notes that say "HDMI Initialization Step X"
+ * These correspond to the flowchart.
+ */
+
+/*
+ * We are required to configure VGA mode before reading edid
+ * in HDMI Initialization Step B
+ */
+static const struct fb_videomode vga_mode = {
+ /* 640x480 @ 60 Hz, 31.5 kHz hsync */
+ NULL, 60, 640, 480, 39721, 48, 16, 33, 10, 96, 2, 0,
+ FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, FB_MODE_IS_VESA,
+};
+
+enum hdmi_datamap {
+ RGB444_8B = 0x01,
+ RGB444_10B = 0x03,
+ RGB444_12B = 0x05,
+ RGB444_16B = 0x07,
+ YCbCr444_8B = 0x09,
+ YCbCr444_10B = 0x0B,
+ YCbCr444_12B = 0x0D,
+ YCbCr444_16B = 0x0F,
+ YCbCr422_8B = 0x16,
+ YCbCr422_10B = 0x14,
+ YCbCr422_12B = 0x12,
+};
+
+enum hdmi_colorimetry {
+ eITU601,
+ eITU709,
+};
+
+struct hdmi_vmode {
+ bool mDVI;
+ bool mHSyncPolarity;
+ bool mVSyncPolarity;
+ bool mInterlaced;
+ bool mDataEnablePolarity;
+
+ unsigned int mPixelClock;
+ unsigned int mPixelRepetitionInput;
+ unsigned int mPixelRepetitionOutput;
+};
+
+struct hdmi_data_info {
+ unsigned int enc_in_format;
+ unsigned int enc_out_format;
+ unsigned int enc_color_depth;
+ unsigned int colorimetry;
+ unsigned int pix_repet_factor;
+ unsigned int hdcp_enable;
+ unsigned int rgb_out_enable;
+ struct hdmi_vmode video_mode;
+};
+
+struct hdmi_phy_reg_config {
+ /* HDMI PHY register config for pass HCT */
+ u16 reg_vlev;
+ u16 reg_cksymtx;
+};
+
+struct mxc_hdmi {
+ struct platform_device *pdev;
+ struct platform_device *core_pdev;
+ struct mxc_dispdrv_handle *disp_mxc_hdmi;
+ struct fb_info *fbi;
+ struct clk *hdmi_isfr_clk;
+ struct clk *hdmi_iahb_clk;
+ struct delayed_work hotplug_work;
+ struct delayed_work hdcp_hdp_work;
+
+ struct notifier_block nb;
+
+ struct hdmi_data_info hdmi_data;
+ int vic;
+ struct mxc_edid_cfg edid_cfg;
+ u8 edid[HDMI_EDID_LEN];
+ bool fb_reg;
+ bool cable_plugin;
+ u8 blank;
+ bool dft_mode_set;
+ char *dft_mode_str;
+ int default_bpp;
+ u8 latest_intr_stat;
+ bool irq_enabled;
+ spinlock_t irq_lock;
+ bool phy_enabled;
+ struct fb_videomode default_mode;
+ struct fb_videomode previous_non_vga_mode;
+ bool requesting_vga_for_initialization;
+
+ int *gpr_base;
+ int *gpr_hdmi_base;
+ int *gpr_sdma_base;
+ int cpu_type;
+ int cpu_version;
+ struct hdmi_phy_reg_config phy_config;
+
+ struct pinctrl *pinctrl;
+};
+
+static int hdmi_major;
+static struct class *hdmi_class;
+
+struct i2c_client *hdmi_i2c;
+struct mxc_hdmi *g_hdmi;
+
+static bool hdmi_inited;
+static bool hdcp_init;
+
+extern const struct fb_videomode mxc_cea_mode[64];
+extern void mxc_hdmi_cec_handle(u16 cec_stat);
+
+static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event);
+static void hdmi_enable_overflow_interrupts(void);
+static void hdmi_disable_overflow_interrupts(void);
+
+static struct platform_device_id imx_hdmi_devtype[] = {
+ {
+ .name = "hdmi-imx6DL",
+ .driver_data = IMX6DL_HDMI,
+ }, {
+ .name = "hdmi-imx6Q",
+ .driver_data = IMX6Q_HDMI,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(platform, imx_hdmi_devtype);
+
+static const struct of_device_id imx_hdmi_dt_ids[] = {
+ { .compatible = "fsl,imx6dl-hdmi-video", .data = &imx_hdmi_devtype[IMX6DL_HDMI], },
+ { .compatible = "fsl,imx6q-hdmi-video", .data = &imx_hdmi_devtype[IMX6Q_HDMI], },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
+
+static inline int cpu_is_imx6dl(struct mxc_hdmi *hdmi)
+{
+ return hdmi->cpu_type == IMX6DL_HDMI;
+}
+#ifdef DEBUG
+static void dump_fb_videomode(struct fb_videomode *m)
+{
+ pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ m->refresh, m->xres, m->yres, m->pixclock, m->left_margin,
+ m->right_margin, m->upper_margin, m->lower_margin,
+ m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag);
+}
+#else
+static void dump_fb_videomode(struct fb_videomode *m)
+{}
+#endif
+
+static ssize_t mxc_hdmi_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ strcpy(buf, hdmi->fbi->fix.id);
+ sprintf(buf+strlen(buf), "\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(fb_name, S_IRUGO, mxc_hdmi_show_name, NULL);
+
+static ssize_t mxc_hdmi_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (hdmi->cable_plugin == false)
+ strcpy(buf, "plugout\n");
+ else
+ strcpy(buf, "plugin\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(cable_state, S_IRUGO, mxc_hdmi_show_state, NULL);
+
+static ssize_t mxc_hdmi_show_edid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+ int i, j, len = 0;
+
+ for (j = 0; j < HDMI_EDID_LEN/16; j++) {
+ for (i = 0; i < 16; i++)
+ len += sprintf(buf+len, "0x%02X ",
+ hdmi->edid[j*16 + i]);
+ len += sprintf(buf+len, "\n");
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(edid, S_IRUGO, mxc_hdmi_show_edid, NULL);
+
+static ssize_t mxc_hdmi_show_rgb_out_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (hdmi->hdmi_data.rgb_out_enable == true)
+ strcpy(buf, "RGB out\n");
+ else
+ strcpy(buf, "YCbCr out\n");
+
+ return strlen(buf);
+}
+
+static ssize_t mxc_hdmi_store_rgb_out_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+ unsigned long value;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &value);
+ if (ret)
+ return ret;
+
+ hdmi->hdmi_data.rgb_out_enable = value;
+
+ /* Reconfig HDMI for output color space change */
+ mxc_hdmi_setup(hdmi, 0);
+
+ return count;
+}
+
+static DEVICE_ATTR(rgb_out_enable, S_IRUGO | S_IWUSR,
+ mxc_hdmi_show_rgb_out_enable,
+ mxc_hdmi_store_rgb_out_enable);
+
+static ssize_t mxc_hdmi_show_hdcp_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (hdmi->hdmi_data.hdcp_enable == false)
+ strcpy(buf, "hdcp disable\n");
+ else
+ strcpy(buf, "hdcp enable\n");
+
+ return strlen(buf);
+
+}
+
+static ssize_t mxc_hdmi_store_hdcp_enable(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
+ char event_string[32];
+ char *envp[] = { event_string, NULL };
+ unsigned long value;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &value);
+ if (ret)
+ return ret;
+
+ hdmi->hdmi_data.hdcp_enable = value;
+
+ /* Reconfig HDMI for HDCP */
+ mxc_hdmi_setup(hdmi, 0);
+
+ if (hdmi->hdmi_data.hdcp_enable == false) {
+ sprintf(event_string, "EVENT=hdcpdisable");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+ } else {
+ sprintf(event_string, "EVENT=hdcpenable");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+ }
+
+ return count;
+
+}
+
+static DEVICE_ATTR(hdcp_enable, S_IRUGO | S_IWUSR,
+ mxc_hdmi_show_hdcp_enable, mxc_hdmi_store_hdcp_enable);
+
+/*!
+ * this submodule is responsible for the video data synchronization.
+ * for example, for RGB 4:4:4 input, the data map is defined as
+ * pin{47~40} <==> R[7:0]
+ * pin{31~24} <==> G[7:0]
+ * pin{15~8} <==> B[7:0]
+ */
+static void hdmi_video_sample(struct mxc_hdmi *hdmi)
+{
+ int color_format = 0;
+ u8 val;
+
+ if (hdmi->hdmi_data.enc_in_format == RGB) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x01;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x03;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x05;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_format = 0x07;
+ else
+ return;
+ } else if (hdmi->hdmi_data.enc_in_format == YCBCR444) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x09;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x0B;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x0D;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_format = 0x0F;
+ else
+ return;
+ } else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) {
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_format = 0x16;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_format = 0x14;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_format = 0x12;
+ else
+ return;
+ }
+
+ val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE |
+ ((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) &
+ HDMI_TX_INVID0_VIDEO_MAPPING_MASK);
+ hdmi_writeb(val, HDMI_TX_INVID0);
+
+ /* Enable TX stuffing: When DE is inactive, fix the output data to 0 */
+ val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE |
+ HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE |
+ HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE;
+ hdmi_writeb(val, HDMI_TX_INSTUFFING);
+ hdmi_writeb(0x0, HDMI_TX_GYDATA0);
+ hdmi_writeb(0x0, HDMI_TX_GYDATA1);
+ hdmi_writeb(0x0, HDMI_TX_RCRDATA0);
+ hdmi_writeb(0x0, HDMI_TX_RCRDATA1);
+ hdmi_writeb(0x0, HDMI_TX_BCBDATA0);
+ hdmi_writeb(0x0, HDMI_TX_BCBDATA1);
+}
+
+static int isColorSpaceConversion(struct mxc_hdmi *hdmi)
+{
+ return (hdmi->hdmi_data.enc_in_format !=
+ hdmi->hdmi_data.enc_out_format);
+}
+
+static int isColorSpaceDecimation(struct mxc_hdmi *hdmi)
+{
+ return ((hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) &&
+ (hdmi->hdmi_data.enc_in_format == RGB ||
+ hdmi->hdmi_data.enc_in_format == YCBCR444));
+}
+
+static int isColorSpaceInterpolation(struct mxc_hdmi *hdmi)
+{
+ return ((hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) &&
+ (hdmi->hdmi_data.enc_out_format == RGB
+ || hdmi->hdmi_data.enc_out_format == YCBCR444));
+}
+
+/*!
+ * update the color space conversion coefficients.
+ */
+static void update_csc_coeffs(struct mxc_hdmi *hdmi)
+{
+ unsigned short csc_coeff[3][4];
+ unsigned int csc_scale = 1;
+ u8 val;
+ bool coeff_selected = false;
+
+ if (isColorSpaceConversion(hdmi)) { /* csc needed */
+ if (hdmi->hdmi_data.enc_out_format == RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x6926;
+ csc_coeff[0][2] = 0x74fd;
+ csc_coeff[0][3] = 0x010e;
+
+ csc_coeff[1][0] = 0x2000;
+ csc_coeff[1][1] = 0x2cdd;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x7e9a;
+
+ csc_coeff[2][0] = 0x2000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x38b4;
+ csc_coeff[2][3] = 0x7e3b;
+
+ csc_scale = 1;
+ coeff_selected = true;
+ } else if (hdmi->hdmi_data.colorimetry == eITU709) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x7106;
+ csc_coeff[0][2] = 0x7a02;
+ csc_coeff[0][3] = 0x00a7;
+
+ csc_coeff[1][0] = 0x2000;
+ csc_coeff[1][1] = 0x3264;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x7e6d;
+
+ csc_coeff[2][0] = 0x2000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x3b61;
+ csc_coeff[2][3] = 0x7e25;
+
+ csc_scale = 1;
+ coeff_selected = true;
+ }
+ } else if (hdmi->hdmi_data.enc_in_format == RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601) {
+ csc_coeff[0][0] = 0x2591;
+ csc_coeff[0][1] = 0x1322;
+ csc_coeff[0][2] = 0x074b;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x6535;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x7acc;
+ csc_coeff[1][3] = 0x0200;
+
+ csc_coeff[2][0] = 0x6acd;
+ csc_coeff[2][1] = 0x7534;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0200;
+
+ csc_scale = 0;
+ coeff_selected = true;
+ } else if (hdmi->hdmi_data.colorimetry == eITU709) {
+ csc_coeff[0][0] = 0x2dc5;
+ csc_coeff[0][1] = 0x0d9b;
+ csc_coeff[0][2] = 0x049e;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x62f0;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x7d11;
+ csc_coeff[1][3] = 0x0200;
+
+ csc_coeff[2][0] = 0x6756;
+ csc_coeff[2][1] = 0x78ab;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0200;
+
+ csc_scale = 0;
+ coeff_selected = true;
+ }
+ }
+ }
+
+ if (!coeff_selected) {
+ csc_coeff[0][0] = 0x2000;
+ csc_coeff[0][1] = 0x0000;
+ csc_coeff[0][2] = 0x0000;
+ csc_coeff[0][3] = 0x0000;
+
+ csc_coeff[1][0] = 0x0000;
+ csc_coeff[1][1] = 0x2000;
+ csc_coeff[1][2] = 0x0000;
+ csc_coeff[1][3] = 0x0000;
+
+ csc_coeff[2][0] = 0x0000;
+ csc_coeff[2][1] = 0x0000;
+ csc_coeff[2][2] = 0x2000;
+ csc_coeff[2][3] = 0x0000;
+
+ csc_scale = 1;
+ }
+
+ /* Update CSC parameters in HDMI CSC registers */
+ hdmi_writeb((unsigned char)(csc_coeff[0][0] & 0xFF),
+ HDMI_CSC_COEF_A1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][0] >> 8),
+ HDMI_CSC_COEF_A1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][1] & 0xFF),
+ HDMI_CSC_COEF_A2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][1] >> 8),
+ HDMI_CSC_COEF_A2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][2] & 0xFF),
+ HDMI_CSC_COEF_A3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][2] >> 8),
+ HDMI_CSC_COEF_A3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][3] & 0xFF),
+ HDMI_CSC_COEF_A4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[0][3] >> 8),
+ HDMI_CSC_COEF_A4_MSB);
+
+ hdmi_writeb((unsigned char)(csc_coeff[1][0] & 0xFF),
+ HDMI_CSC_COEF_B1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][0] >> 8),
+ HDMI_CSC_COEF_B1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][1] & 0xFF),
+ HDMI_CSC_COEF_B2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][1] >> 8),
+ HDMI_CSC_COEF_B2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][2] & 0xFF),
+ HDMI_CSC_COEF_B3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][2] >> 8),
+ HDMI_CSC_COEF_B3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][3] & 0xFF),
+ HDMI_CSC_COEF_B4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[1][3] >> 8),
+ HDMI_CSC_COEF_B4_MSB);
+
+ hdmi_writeb((unsigned char)(csc_coeff[2][0] & 0xFF),
+ HDMI_CSC_COEF_C1_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][0] >> 8),
+ HDMI_CSC_COEF_C1_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][1] & 0xFF),
+ HDMI_CSC_COEF_C2_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][1] >> 8),
+ HDMI_CSC_COEF_C2_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][2] & 0xFF),
+ HDMI_CSC_COEF_C3_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][2] >> 8),
+ HDMI_CSC_COEF_C3_MSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][3] & 0xFF),
+ HDMI_CSC_COEF_C4_LSB);
+ hdmi_writeb((unsigned char)(csc_coeff[2][3] >> 8),
+ HDMI_CSC_COEF_C4_MSB);
+
+ val = hdmi_readb(HDMI_CSC_SCALE);
+ val &= ~HDMI_CSC_SCALE_CSCSCALE_MASK;
+ val |= csc_scale & HDMI_CSC_SCALE_CSCSCALE_MASK;
+ hdmi_writeb(val, HDMI_CSC_SCALE);
+}
+
+static void hdmi_video_csc(struct mxc_hdmi *hdmi)
+{
+ int color_depth = 0;
+ int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE;
+ int decimation = 0;
+ u8 val;
+
+ /* YCC422 interpolation to 444 mode */
+ if (isColorSpaceInterpolation(hdmi))
+ interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1;
+ else if (isColorSpaceDecimation(hdmi))
+ decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3;
+
+ if (hdmi->hdmi_data.enc_color_depth == 8)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 10)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 12)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP;
+ else if (hdmi->hdmi_data.enc_color_depth == 16)
+ color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP;
+ else
+ return;
+
+ /*configure the CSC registers */
+ hdmi_writeb(interpolation | decimation, HDMI_CSC_CFG);
+ val = hdmi_readb(HDMI_CSC_SCALE);
+ val &= ~HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK;
+ val |= color_depth;
+ hdmi_writeb(val, HDMI_CSC_SCALE);
+
+ update_csc_coeffs(hdmi);
+}
+
+/*!
+ * HDMI video packetizer is used to packetize the data.
+ * for example, if input is YCC422 mode or repeater is used,
+ * data should be repacked this module can be bypassed.
+ */
+static void hdmi_video_packetize(struct mxc_hdmi *hdmi)
+{
+ unsigned int color_depth = 0;
+ unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit;
+ unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP;
+ struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data;
+ u8 val;
+
+ if (hdmi_data->enc_out_format == RGB
+ || hdmi_data->enc_out_format == YCBCR444) {
+ if (hdmi_data->enc_color_depth == 0)
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
+ else if (hdmi_data->enc_color_depth == 8) {
+ color_depth = 4;
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
+ } else if (hdmi_data->enc_color_depth == 10)
+ color_depth = 5;
+ else if (hdmi_data->enc_color_depth == 12)
+ color_depth = 6;
+ else if (hdmi_data->enc_color_depth == 16)
+ color_depth = 7;
+ else
+ return;
+ } else if (hdmi_data->enc_out_format == YCBCR422_8BITS) {
+ if (hdmi_data->enc_color_depth == 0 ||
+ hdmi_data->enc_color_depth == 8)
+ remap_size = HDMI_VP_REMAP_YCC422_16bit;
+ else if (hdmi_data->enc_color_depth == 10)
+ remap_size = HDMI_VP_REMAP_YCC422_20bit;
+ else if (hdmi_data->enc_color_depth == 12)
+ remap_size = HDMI_VP_REMAP_YCC422_24bit;
+ else
+ return;
+ output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422;
+ } else
+ return;
+
+ /* HDMI not support deep color,
+ * because IPU MAX support color depth is 24bit */
+ color_depth = 0;
+
+ /* set the packetizer registers */
+ val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) &
+ HDMI_VP_PR_CD_COLOR_DEPTH_MASK) |
+ ((hdmi_data->pix_repet_factor <<
+ HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) &
+ HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
+ hdmi_writeb(val, HDMI_VP_PR_CD);
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~HDMI_VP_STUFF_PR_STUFFING_MASK;
+ val |= HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ /* Data from pixel repeater block */
+ if (hdmi_data->pix_repet_factor > 1) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_PR_EN_MASK |
+ HDMI_VP_CONF_BYPASS_SELECT_MASK);
+ val |= HDMI_VP_CONF_PR_EN_ENABLE |
+ HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else { /* data from packetizer block */
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_PR_EN_MASK |
+ HDMI_VP_CONF_BYPASS_SELECT_MASK);
+ val |= HDMI_VP_CONF_PR_EN_DISABLE |
+ HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ }
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~HDMI_VP_STUFF_IDEFAULT_PHASE_MASK;
+ val |= 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ hdmi_writeb(remap_size, HDMI_VP_REMAP);
+
+ if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
+ HDMI_VP_CONF_PP_EN_ENABLE |
+ HDMI_VP_CONF_YCC422_EN_DISABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
+ HDMI_VP_CONF_PP_EN_DISABLE |
+ HDMI_VP_CONF_YCC422_EN_ENABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) {
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
+ HDMI_VP_CONF_PP_EN_ENMASK |
+ HDMI_VP_CONF_YCC422_EN_MASK);
+ val |= HDMI_VP_CONF_BYPASS_EN_ENABLE |
+ HDMI_VP_CONF_PP_EN_DISABLE |
+ HDMI_VP_CONF_YCC422_EN_DISABLE;
+ hdmi_writeb(val, HDMI_VP_CONF);
+ } else {
+ return;
+ }
+
+ val = hdmi_readb(HDMI_VP_STUFF);
+ val &= ~(HDMI_VP_STUFF_PP_STUFFING_MASK |
+ HDMI_VP_STUFF_YCC422_STUFFING_MASK);
+ val |= HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE |
+ HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE;
+ hdmi_writeb(val, HDMI_VP_STUFF);
+
+ val = hdmi_readb(HDMI_VP_CONF);
+ val &= ~HDMI_VP_CONF_OUTPUT_SELECTOR_MASK;
+ val |= output_select;
+ hdmi_writeb(val, HDMI_VP_CONF);
+}
+
+#if 0
+/* Force a fixed color screen */
+static void hdmi_video_force_output(struct mxc_hdmi *hdmi, unsigned char force)
+{
+ u8 val;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (force) {
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
+ hdmi_writeb(0xFF, HDMI_FC_DBGTMDS0); /* B */
+ val = hdmi_readb(HDMI_FC_DBGFORCE);
+ val |= HDMI_FC_DBGFORCE_FORCEVIDEO;
+ hdmi_writeb(val, HDMI_FC_DBGFORCE);
+ } else {
+ val = hdmi_readb(HDMI_FC_DBGFORCE);
+ val &= ~HDMI_FC_DBGFORCE_FORCEVIDEO;
+ hdmi_writeb(val, HDMI_FC_DBGFORCE);
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
+ hdmi_writeb(0x00, HDMI_FC_DBGTMDS0); /* B */
+ }
+}
+#endif
+
+static inline void hdmi_phy_test_clear(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTCLR_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) &
+ HDMI_PHY_TST0_TSTCLR_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_enable(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTEN_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTEN_OFFSET) &
+ HDMI_PHY_TST0_TSTEN_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_clock(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ u8 val = hdmi_readb(HDMI_PHY_TST0);
+ val &= ~HDMI_PHY_TST0_TSTCLK_MASK;
+ val |= (bit << HDMI_PHY_TST0_TSTCLK_OFFSET) &
+ HDMI_PHY_TST0_TSTCLK_MASK;
+ hdmi_writeb(val, HDMI_PHY_TST0);
+}
+
+static inline void hdmi_phy_test_din(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ hdmi_writeb(bit, HDMI_PHY_TST1);
+}
+
+static inline void hdmi_phy_test_dout(struct mxc_hdmi *hdmi,
+ unsigned char bit)
+{
+ hdmi_writeb(bit, HDMI_PHY_TST2);
+}
+
+static bool hdmi_phy_wait_i2c_done(struct mxc_hdmi *hdmi, int msec)
+{
+ unsigned char val = 0;
+ val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
+ while (val == 0) {
+ udelay(1000);
+ if (msec-- == 0)
+ return false;
+ val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
+ }
+ return true;
+}
+
+static void hdmi_phy_i2c_write(struct mxc_hdmi *hdmi, unsigned short data,
+ unsigned char addr)
+{
+ hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
+ hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+ hdmi_writeb((unsigned char)(data >> 8),
+ HDMI_PHY_I2CM_DATAO_1_ADDR);
+ hdmi_writeb((unsigned char)(data >> 0),
+ HDMI_PHY_I2CM_DATAO_0_ADDR);
+ hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_WRITE,
+ HDMI_PHY_I2CM_OPERATION_ADDR);
+ hdmi_phy_wait_i2c_done(hdmi, 1000);
+}
+
+#if 0
+static unsigned short hdmi_phy_i2c_read(struct mxc_hdmi *hdmi,
+ unsigned char addr)
+{
+ unsigned short data;
+ unsigned char msb = 0, lsb = 0;
+ hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
+ hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
+ hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_READ,
+ HDMI_PHY_I2CM_OPERATION_ADDR);
+ hdmi_phy_wait_i2c_done(hdmi, 1000);
+ msb = hdmi_readb(HDMI_PHY_I2CM_DATAI_1_ADDR);
+ lsb = hdmi_readb(HDMI_PHY_I2CM_DATAI_0_ADDR);
+ data = (msb << 8) | lsb;
+ return data;
+}
+
+static int hdmi_phy_i2c_write_verify(struct mxc_hdmi *hdmi, unsigned short data,
+ unsigned char addr)
+{
+ unsigned short val = 0;
+ hdmi_phy_i2c_write(hdmi, data, addr);
+ val = hdmi_phy_i2c_read(hdmi, addr);
+ return (val == data);
+}
+#endif
+
+static bool hdmi_edid_wait_i2c_done(struct mxc_hdmi *hdmi, int msec)
+{
+ unsigned char val = 0;
+ val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2;
+ while (val == 0) {
+
+ udelay(1000);
+ if (msec-- == 0) {
+ dev_dbg(&hdmi->pdev->dev,
+ "HDMI EDID i2c operation time out!!\n");
+ return false;
+ }
+ val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2;
+ }
+ return true;
+}
+
+static u8 hdmi_edid_i2c_read(struct mxc_hdmi *hdmi,
+ u8 addr, u8 blockno)
+{
+ u8 spointer = blockno / 2;
+ u8 edidaddress = ((blockno % 2) * 0x80) + addr;
+ u8 data;
+
+ hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0);
+ hdmi_writeb(edidaddress, HDMI_I2CM_ADDRESS);
+ hdmi_writeb(spointer, HDMI_I2CM_SEGADDR);
+ if (spointer == 0)
+ hdmi_writeb(HDMI_I2CM_OPERATION_READ,
+ HDMI_I2CM_OPERATION);
+ else
+ hdmi_writeb(HDMI_I2CM_OPERATION_READ_EXT,
+ HDMI_I2CM_OPERATION);
+
+ hdmi_edid_wait_i2c_done(hdmi, 1000);
+ data = hdmi_readb(HDMI_I2CM_DATAI);
+ hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0);
+ return data;
+}
+
+
+/* "Power-down enable (active low)"
+ * That mean that power up == 1! */
+static void mxc_hdmi_phy_enable_power(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_PDZ_OFFSET,
+ HDMI_PHY_CONF0_PDZ_MASK);
+}
+
+static void mxc_hdmi_phy_enable_tmds(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_ENTMDS_OFFSET,
+ HDMI_PHY_CONF0_ENTMDS_MASK);
+}
+
+static void mxc_hdmi_phy_gen2_pddq(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
+ HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
+}
+
+static void mxc_hdmi_phy_gen2_txpwron(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
+ HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
+}
+
+#if 0
+static void mxc_hdmi_phy_gen2_enhpdrxsense(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET,
+ HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK);
+}
+#endif
+
+static void mxc_hdmi_phy_sel_data_en_pol(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_SELDATAENPOL_OFFSET,
+ HDMI_PHY_CONF0_SELDATAENPOL_MASK);
+}
+
+static void mxc_hdmi_phy_sel_interface_control(u8 enable)
+{
+ hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
+ HDMI_PHY_CONF0_SELDIPIF_OFFSET,
+ HDMI_PHY_CONF0_SELDIPIF_MASK);
+}
+
+static int hdmi_phy_configure(struct mxc_hdmi *hdmi, unsigned char pRep,
+ unsigned char cRes, int cscOn)
+{
+ u8 val;
+ u8 msec;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* color resolution 0 is 8 bit colour depth */
+ if (cRes == 0)
+ cRes = 8;
+
+ if (pRep != 0)
+ return false;
+ else if (cRes != 8 && cRes != 12)
+ return false;
+
+ /* Enable csc path */
+ if (cscOn)
+ val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH;
+ else
+ val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS;
+
+ hdmi_writeb(val, HDMI_MC_FLOWCTRL);
+
+ /* gen2 tx power off */
+ mxc_hdmi_phy_gen2_txpwron(0);
+
+ /* gen2 pddq */
+ mxc_hdmi_phy_gen2_pddq(1);
+
+ /* PHY reset */
+ hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
+ hdmi_writeb(HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
+
+ hdmi_writeb(HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
+
+ hdmi_phy_test_clear(hdmi, 1);
+ hdmi_writeb(HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
+ HDMI_PHY_I2CM_SLAVE_ADDR);
+ hdmi_phy_test_clear(hdmi, 0);
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock < 0) {
+ dev_dbg(&hdmi->pdev->dev, "Pixel clock (%d) must be positive\n",
+ hdmi->hdmi_data.video_mode.mPixelClock);
+ return false;
+ }
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock <= 45250000) {
+ switch (cRes) {
+ case 8:
+ /* PLL/MPLL Cfg */
+ hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); /* GMPCTRL */
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x21e1, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x41e2, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 92500000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x0140, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x2141, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x4142, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 148500000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x20a1, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x40a2, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ default:
+ return false;
+ }
+ } else {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x2001, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x4002, 0x06);
+ hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
+ default:
+ return false;
+ }
+ }
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock <= 54000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); /* CURRCTRL */
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 58400000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 72000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 74250000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 118800000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else if (hdmi->hdmi_data.video_mode.mPixelClock <= 216000000) {
+ switch (cRes) {
+ case 8:
+ hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
+ break;
+ case 10:
+ hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
+ break;
+ case 12:
+ hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
+ break;
+ default:
+ return false;
+ }
+ } else {
+ dev_err(&hdmi->pdev->dev,
+ "Pixel clock %d - unsupported by HDMI\n",
+ hdmi->hdmi_data.video_mode.mPixelClock);
+ return false;
+ }
+
+ hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
+ hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
+ /* RESISTANCE TERM 133Ohm Cfg */
+ hdmi_phy_i2c_write(hdmi, 0x0005, 0x19); /* TXTERM */
+ /* PREEMP Cgf 0.00 */
+ hdmi_phy_i2c_write(hdmi, 0x800d, 0x09); /* CKSYMTXCTRL */
+ /* TX/CK LVL 10 */
+ hdmi_phy_i2c_write(hdmi, 0x01ad, 0x0E); /* VLEVCTRL */
+
+ /* Board specific setting for PHY register 0x09, 0x0e to pass HCT */
+ if (hdmi->phy_config.reg_cksymtx != 0)
+ hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_cksymtx, 0x09);
+
+ if (hdmi->phy_config.reg_vlev != 0)
+ hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_vlev, 0x0E);
+
+ /* REMOVE CLK TERM */
+ hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
+
+ if (hdmi->hdmi_data.video_mode.mPixelClock > 148500000) {
+ hdmi_phy_i2c_write(hdmi, 0x800b, 0x09);
+ hdmi_phy_i2c_write(hdmi, 0x0129, 0x0E);
+ }
+
+ mxc_hdmi_phy_enable_power(1);
+
+ /* toggle TMDS enable */
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_tmds(1);
+
+ /* gen2 tx power on */
+ mxc_hdmi_phy_gen2_txpwron(1);
+ mxc_hdmi_phy_gen2_pddq(0);
+
+ /*Wait for PHY PLL lock */
+ msec = 4;
+ val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
+ while (val == 0) {
+ udelay(1000);
+ if (msec-- == 0) {
+ dev_dbg(&hdmi->pdev->dev, "PHY PLL not locked\n");
+ return false;
+ }
+ val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
+ }
+
+ return true;
+}
+
+static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi)
+{
+ int i;
+ bool cscon = false;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Never do phy init if pixel clock is gated.
+ * Otherwise HDMI PHY will get messed up and generate an overflow
+ * interrupt that can't be cleared or detected by accessing the
+ * status register. */
+ if (!hdmi->fb_reg || !hdmi->cable_plugin
+ || (hdmi->blank != FB_BLANK_UNBLANK))
+ return;
+
+ if (!hdmi->hdmi_data.video_mode.mDVI)
+ hdmi_enable_overflow_interrupts();
+
+ /*check csc whether needed activated in HDMI mode */
+ cscon = (isColorSpaceConversion(hdmi) &&
+ !hdmi->hdmi_data.video_mode.mDVI);
+
+ /* HDMI Phy spec says to do the phy initialization sequence twice */
+ for (i = 0 ; i < 2 ; i++) {
+ mxc_hdmi_phy_sel_data_en_pol(1);
+ mxc_hdmi_phy_sel_interface_control(0);
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_power(0);
+
+ /* Enable CSC */
+ hdmi_phy_configure(hdmi, 0, 8, cscon);
+ }
+
+ hdmi->phy_enabled = true;
+}
+
+static void hdmi_config_AVI(struct mxc_hdmi *hdmi)
+{
+ u8 val;
+ u8 pix_fmt;
+ u8 under_scan;
+ u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry;
+ struct fb_videomode mode;
+ const struct fb_videomode *edid_mode;
+ bool aspect_16_9;
+
+ dev_dbg(&hdmi->pdev->dev, "set up AVI frame\n");
+
+ fb_var_to_videomode(&mode, &hdmi->fbi->var);
+ /* Use mode from list extracted from EDID to get aspect ratio */
+ if (!list_empty(&hdmi->fbi->modelist)) {
+ edid_mode = fb_find_nearest_mode(&mode, &hdmi->fbi->modelist);
+ if (edid_mode->vmode & FB_VMODE_ASPECT_16_9)
+ aspect_16_9 = true;
+ else
+ aspect_16_9 = false;
+ } else
+ aspect_16_9 = false;
+
+ /********************************************
+ * AVI Data Byte 1
+ ********************************************/
+ if (hdmi->hdmi_data.enc_out_format == YCBCR444)
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444;
+ else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS)
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422;
+ else
+ pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB;
+
+ if (hdmi->edid_cfg.cea_underscan)
+ under_scan = HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN;
+ else
+ under_scan = HDMI_FC_AVICONF0_SCAN_INFO_NODATA;
+
+ /*
+ * Active format identification data is present in the AVI InfoFrame.
+ * Under scan info, no bar data
+ */
+ val = pix_fmt | under_scan |
+ HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT |
+ HDMI_FC_AVICONF0_BAR_DATA_NO_DATA;
+
+ hdmi_writeb(val, HDMI_FC_AVICONF0);
+
+ /********************************************
+ * AVI Data Byte 2
+ ********************************************/
+
+ /* Set the Aspect Ratio */
+ if (aspect_16_9) {
+ act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9;
+ coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9;
+ } else {
+ act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3;
+ coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3;
+ }
+
+ /* Set up colorimetry */
+ if (hdmi->hdmi_data.enc_out_format == XVYCC444) {
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO;
+ if (hdmi->hdmi_data.colorimetry == eITU601)
+ ext_colorimetry =
+ HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ else /* hdmi->hdmi_data.colorimetry == eITU709 */
+ ext_colorimetry =
+ HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709;
+ } else if (hdmi->hdmi_data.enc_out_format != RGB) {
+ if (hdmi->hdmi_data.colorimetry == eITU601)
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE;
+ else /* hdmi->hdmi_data.colorimetry == eITU709 */
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR;
+ ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ } else { /* Carries no data */
+ colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA;
+ ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
+ }
+
+ val = colorimetry | coded_ratio | act_ratio;
+ hdmi_writeb(val, HDMI_FC_AVICONF1);
+
+ /********************************************
+ * AVI Data Byte 3
+ ********************************************/
+
+ val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry |
+ HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT |
+ HDMI_FC_AVICONF2_SCALING_NONE;
+ hdmi_writeb(val, HDMI_FC_AVICONF2);
+
+ /********************************************
+ * AVI Data Byte 4
+ ********************************************/
+ hdmi_writeb(hdmi->vic, HDMI_FC_AVIVID);
+
+ /********************************************
+ * AVI Data Byte 5
+ ********************************************/
+
+ /* Set up input and output pixel repetition */
+ val = (((hdmi->hdmi_data.video_mode.mPixelRepetitionInput + 1) <<
+ HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) &
+ HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) |
+ ((hdmi->hdmi_data.video_mode.mPixelRepetitionOutput <<
+ HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) &
+ HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK);
+ hdmi_writeb(val, HDMI_FC_PRCONF);
+
+ /* IT Content and quantization range = don't care */
+ val = HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS |
+ HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED;
+ hdmi_writeb(val, HDMI_FC_AVICONF3);
+
+ /********************************************
+ * AVI Data Bytes 6-13
+ ********************************************/
+ hdmi_writeb(0, HDMI_FC_AVIETB0);
+ hdmi_writeb(0, HDMI_FC_AVIETB1);
+ hdmi_writeb(0, HDMI_FC_AVISBB0);
+ hdmi_writeb(0, HDMI_FC_AVISBB1);
+ hdmi_writeb(0, HDMI_FC_AVIELB0);
+ hdmi_writeb(0, HDMI_FC_AVIELB1);
+ hdmi_writeb(0, HDMI_FC_AVISRB0);
+ hdmi_writeb(0, HDMI_FC_AVISRB1);
+}
+
+/*!
+ * this submodule is responsible for the video/audio data composition.
+ */
+static void hdmi_av_composer(struct mxc_hdmi *hdmi)
+{
+ u8 inv_val;
+ struct fb_info *fbi = hdmi->fbi;
+ struct fb_videomode fb_mode;
+ struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
+ int hblank, vblank;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_var_to_videomode(&fb_mode, &fbi->var);
+
+ vmode->mHSyncPolarity = ((fb_mode.sync & FB_SYNC_HOR_HIGH_ACT) != 0);
+ vmode->mVSyncPolarity = ((fb_mode.sync & FB_SYNC_VERT_HIGH_ACT) != 0);
+ vmode->mInterlaced = ((fb_mode.vmode & FB_VMODE_INTERLACED) != 0);
+ vmode->mPixelClock = (fb_mode.xres + fb_mode.left_margin +
+ fb_mode.right_margin + fb_mode.hsync_len) * (fb_mode.yres +
+ fb_mode.upper_margin + fb_mode.lower_margin +
+ fb_mode.vsync_len) * fb_mode.refresh;
+
+ dev_dbg(&hdmi->pdev->dev, "final pixclk = %d\n", vmode->mPixelClock);
+
+ /* Set up HDMI_FC_INVIDCONF */
+ inv_val = (vmode->mVSyncPolarity ?
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW);
+
+ inv_val |= (vmode->mHSyncPolarity ?
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW);
+
+ inv_val |= (vmode->mDataEnablePolarity ?
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW);
+
+ if (hdmi->vic == 39)
+ inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH;
+ else
+ inv_val |= (vmode->mInterlaced ?
+ HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH :
+ HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW);
+
+ inv_val |= (vmode->mInterlaced ?
+ HDMI_FC_INVIDCONF_IN_I_P_INTERLACED :
+ HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE);
+
+ inv_val |= (vmode->mDVI ?
+ HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE :
+ HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE);
+
+ hdmi_writeb(inv_val, HDMI_FC_INVIDCONF);
+
+ /* Set up horizontal active pixel region width */
+ hdmi_writeb(fb_mode.xres >> 8, HDMI_FC_INHACTV1);
+ hdmi_writeb(fb_mode.xres, HDMI_FC_INHACTV0);
+
+ /* Set up vertical blanking pixel region width */
+ hdmi_writeb(fb_mode.yres >> 8, HDMI_FC_INVACTV1);
+ hdmi_writeb(fb_mode.yres, HDMI_FC_INVACTV0);
+
+ /* Set up horizontal blanking pixel region width */
+ hblank = fb_mode.left_margin + fb_mode.right_margin +
+ fb_mode.hsync_len;
+ hdmi_writeb(hblank >> 8, HDMI_FC_INHBLANK1);
+ hdmi_writeb(hblank, HDMI_FC_INHBLANK0);
+
+ /* Set up vertical blanking pixel region width */
+ vblank = fb_mode.upper_margin + fb_mode.lower_margin +
+ fb_mode.vsync_len;
+ hdmi_writeb(vblank, HDMI_FC_INVBLANK);
+
+ /* Set up HSYNC active edge delay width (in pixel clks) */
+ hdmi_writeb(fb_mode.right_margin >> 8, HDMI_FC_HSYNCINDELAY1);
+ hdmi_writeb(fb_mode.right_margin, HDMI_FC_HSYNCINDELAY0);
+
+ /* Set up VSYNC active edge delay (in pixel clks) */
+ hdmi_writeb(fb_mode.lower_margin, HDMI_FC_VSYNCINDELAY);
+
+ /* Set up HSYNC active pulse width (in pixel clks) */
+ hdmi_writeb(fb_mode.hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
+ hdmi_writeb(fb_mode.hsync_len, HDMI_FC_HSYNCINWIDTH0);
+
+ /* Set up VSYNC active edge delay (in pixel clks) */
+ hdmi_writeb(fb_mode.vsync_len, HDMI_FC_VSYNCINWIDTH);
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static int mxc_edid_read_internal(struct mxc_hdmi *hdmi, unsigned char *edid,
+ struct mxc_edid_cfg *cfg, struct fb_info *fbi)
+{
+ int extblknum;
+ int i, j, ret;
+ unsigned char *ediddata = edid;
+ unsigned char tmpedid[EDID_LENGTH];
+
+ dev_info(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (!edid || !cfg || !fbi)
+ return -EINVAL;
+
+ /* init HDMI I2CM for read edid*/
+ hdmi_writeb(0x0, HDMI_I2CM_DIV);
+ hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_HCNT_1_ADDR);
+ hdmi_writeb(0x79, HDMI_I2CM_SS_SCL_HCNT_0_ADDR);
+ hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_LCNT_1_ADDR);
+ hdmi_writeb(0x91, HDMI_I2CM_SS_SCL_LCNT_0_ADDR);
+
+ hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_HCNT_1_ADDR);
+ hdmi_writeb(0x0F, HDMI_I2CM_FS_SCL_HCNT_0_ADDR);
+ hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_LCNT_1_ADDR);
+ hdmi_writeb(0x21, HDMI_I2CM_FS_SCL_LCNT_0_ADDR);
+
+ hdmi_writeb(0x50, HDMI_I2CM_SLAVE);
+ hdmi_writeb(0x30, HDMI_I2CM_SEGADDR);
+
+ /* Umask edid interrupt */
+ hdmi_writeb(HDMI_I2CM_INT_DONE_POL,
+ HDMI_I2CM_INT);
+
+ hdmi_writeb(HDMI_I2CM_CTLINT_NAC_POL |
+ HDMI_I2CM_CTLINT_ARBITRATION_POL,
+ HDMI_I2CM_CTLINT);
+
+ /* reset edid data zero */
+ memset(edid, 0, EDID_LENGTH*4);
+ memset(cfg, 0, sizeof(struct mxc_edid_cfg));
+
+ /* Check first three byte of EDID head */
+ if (!(hdmi_edid_i2c_read(hdmi, 0, 0) == 0x00) ||
+ !(hdmi_edid_i2c_read(hdmi, 1, 0) == 0xFF) ||
+ !(hdmi_edid_i2c_read(hdmi, 2, 0) == 0xFF)) {
+ dev_info(&hdmi->pdev->dev, "EDID head check failed!");
+ return -ENOENT;
+ }
+
+ for (i = 0; i < 128; i++) {
+ *ediddata = hdmi_edid_i2c_read(hdmi, i, 0);
+ ediddata++;
+ }
+
+ extblknum = edid[0x7E];
+ if (extblknum < 0)
+ return extblknum;
+
+ if (extblknum) {
+ ediddata = edid + EDID_LENGTH;
+ for (i = 0; i < 128; i++) {
+ *ediddata = hdmi_edid_i2c_read(hdmi, i, 1);
+ ediddata++;
+ }
+ }
+
+ /* edid first block parsing */
+ memset(&fbi->monspecs, 0, sizeof(fbi->monspecs));
+ fb_edid_to_monspecs(edid, &fbi->monspecs);
+
+ ret = mxc_edid_parse_ext_blk(edid + EDID_LENGTH,
+ cfg, &fbi->monspecs);
+ if (ret < 0)
+ return -ENOENT;
+
+ /* need read segment block? */
+ if (extblknum > 1) {
+ for (j = 1; j <= extblknum; j++) {
+ for (i = 0; i < 128; i++)
+ *(tmpedid + 1) = hdmi_edid_i2c_read(hdmi, i, j);
+
+ /* edid ext block parsing */
+ ret = mxc_edid_parse_ext_blk(tmpedid + EDID_LENGTH,
+ cfg, &fbi->monspecs);
+ if (ret < 0)
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+static int mxc_hdmi_read_edid(struct mxc_hdmi *hdmi)
+{
+ int ret;
+ u8 edid_old[HDMI_EDID_LEN];
+ u8 clkdis;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* save old edid */
+ memcpy(edid_old, hdmi->edid, HDMI_EDID_LEN);
+
+ /* Read EDID via HDMI DDC when HDCP Enable */
+ if (!hdcp_init)
+ ret = mxc_edid_read(hdmi_i2c->adapter, hdmi_i2c->addr,
+ hdmi->edid, &hdmi->edid_cfg, hdmi->fbi);
+ else {
+
+ /* Disable HDCP clk */
+ if (hdmi->hdmi_data.hdcp_enable) {
+ clkdis = hdmi_readb(HDMI_MC_CLKDIS);
+ clkdis |= HDMI_MC_CLKDIS_HDCPCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+ }
+
+ ret = mxc_edid_read_internal(hdmi, hdmi->edid,
+ &hdmi->edid_cfg, hdmi->fbi);
+
+ /* Enable HDCP clk */
+ if (hdmi->hdmi_data.hdcp_enable) {
+ clkdis = hdmi_readb(HDMI_MC_CLKDIS);
+ clkdis &= ~HDMI_MC_CLKDIS_HDCPCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+ }
+
+ }
+ if (ret < 0)
+ return HDMI_EDID_FAIL;
+
+ /* Save edid cfg for audio driver */
+ hdmi_set_edid_cfg(&hdmi->edid_cfg);
+
+ if (!memcmp(edid_old, hdmi->edid, HDMI_EDID_LEN)) {
+ dev_info(&hdmi->pdev->dev, "same edid\n");
+ return HDMI_EDID_SAME;
+ }
+
+ if (hdmi->fbi->monspecs.modedb_len == 0) {
+ dev_info(&hdmi->pdev->dev, "No modes read from edid\n");
+ return HDMI_EDID_NO_MODES;
+ }
+
+ return HDMI_EDID_SUCCESS;
+}
+
+static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ if (!hdmi->phy_enabled)
+ return;
+
+ hdmi_disable_overflow_interrupts();
+
+ /* Setting PHY to reset status */
+ hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
+
+ /* Power down PHY */
+ mxc_hdmi_phy_enable_tmds(0);
+ mxc_hdmi_phy_enable_power(0);
+ mxc_hdmi_phy_gen2_txpwron(0);
+ mxc_hdmi_phy_gen2_pddq(1);
+
+ hdmi->phy_enabled = false;
+ dev_dbg(&hdmi->pdev->dev, "%s - exit\n", __func__);
+}
+
+/* HDMI Initialization Step B.4 */
+static void mxc_hdmi_enable_video_path(struct mxc_hdmi *hdmi)
+{
+ u8 clkdis;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* control period minimum duration */
+ hdmi_writeb(12, HDMI_FC_CTRLDUR);
+ hdmi_writeb(32, HDMI_FC_EXCTRLDUR);
+ hdmi_writeb(1, HDMI_FC_EXCTRLSPAC);
+
+ /* Set to fill TMDS data channels */
+ hdmi_writeb(0x0B, HDMI_FC_CH0PREAM);
+ hdmi_writeb(0x16, HDMI_FC_CH1PREAM);
+ hdmi_writeb(0x21, HDMI_FC_CH2PREAM);
+
+ /* Enable pixel clock and tmds data path */
+ clkdis = 0x7F;
+ clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+
+ clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+
+ /* Enable csc path */
+ if (isColorSpaceConversion(hdmi)) {
+ clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+ }
+}
+
+static void hdmi_enable_audio_clk(struct mxc_hdmi *hdmi)
+{
+ u8 clkdis;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ clkdis = hdmi_readb(HDMI_MC_CLKDIS);
+ clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE;
+ hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
+}
+
+/* Workaround to clear the overflow condition */
+static void mxc_hdmi_clear_overflow(struct mxc_hdmi *hdmi)
+{
+ int count;
+ u8 val;
+
+ /* TMDS software reset */
+ hdmi_writeb((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ);
+
+ val = hdmi_readb(HDMI_FC_INVIDCONF);
+
+ if (cpu_is_imx6dl(hdmi)) {
+ hdmi_writeb(val, HDMI_FC_INVIDCONF);
+ return;
+ }
+
+ for (count = 0 ; count < 5 ; count++)
+ hdmi_writeb(val, HDMI_FC_INVIDCONF);
+}
+
+static void hdmi_enable_overflow_interrupts(void)
+{
+ pr_debug("%s\n", __func__);
+ hdmi_writeb(0, HDMI_FC_MASK2);
+ hdmi_writeb(0, HDMI_IH_MUTE_FC_STAT2);
+}
+
+static void hdmi_disable_overflow_interrupts(void)
+{
+ pr_debug("%s\n", __func__);
+ hdmi_writeb(HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK,
+ HDMI_IH_MUTE_FC_STAT2);
+ hdmi_writeb(0xff, HDMI_FC_MASK2);
+}
+
+static void mxc_hdmi_notify_fb(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Don't notify if we aren't registered yet */
+ WARN_ON(!hdmi->fb_reg);
+
+ /* disable the phy before ipu changes mode */
+ mxc_hdmi_phy_disable(hdmi);
+
+ /*
+ * Note that fb_set_var will block. During this time,
+ * FB_EVENT_MODE_CHANGE callback will happen.
+ * So by the end of this function, mxc_hdmi_setup()
+ * will be done.
+ */
+ hdmi->fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ hdmi->fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(hdmi->fbi, &hdmi->fbi->var);
+ hdmi->fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static void mxc_hdmi_edid_rebuild_modelist(struct mxc_hdmi *hdmi)
+{
+ int i;
+ struct fb_videomode *mode;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ console_lock();
+
+ fb_destroy_modelist(&hdmi->fbi->modelist);
+ fb_add_videomode(&vga_mode, &hdmi->fbi->modelist);
+
+ for (i = 0; i < hdmi->fbi->monspecs.modedb_len; i++) {
+ /*
+ * We might check here if mode is supported by HDMI.
+ * We do not currently support interlaced modes.
+ * And add CEA modes in the modelist.
+ */
+ mode = &hdmi->fbi->monspecs.modedb[i];
+
+ if (!(mode->vmode & FB_VMODE_INTERLACED) &&
+ (mxc_edid_mode_to_vic(mode) != 0)) {
+
+ dev_dbg(&hdmi->pdev->dev, "Added mode %d:", i);
+ dev_dbg(&hdmi->pdev->dev,
+ "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n",
+ hdmi->fbi->monspecs.modedb[i].xres,
+ hdmi->fbi->monspecs.modedb[i].yres,
+ hdmi->fbi->monspecs.modedb[i].refresh,
+ hdmi->fbi->monspecs.modedb[i].vmode,
+ hdmi->fbi->monspecs.modedb[i].flag);
+
+ fb_add_videomode(mode, &hdmi->fbi->modelist);
+ }
+ }
+
+ console_unlock();
+}
+
+static void mxc_hdmi_default_edid_cfg(struct mxc_hdmi *hdmi)
+{
+ /* Default setting HDMI working in HDMI mode */
+ hdmi->edid_cfg.hdmi_cap = true;
+}
+
+static void mxc_hdmi_default_modelist(struct mxc_hdmi *hdmi)
+{
+ u32 i;
+ const struct fb_videomode *mode;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* If not EDID data read, set up default modelist */
+ dev_info(&hdmi->pdev->dev, "No modes read from edid\n");
+ dev_info(&hdmi->pdev->dev, "create default modelist\n");
+
+ console_lock();
+
+ fb_destroy_modelist(&hdmi->fbi->modelist);
+
+ /*Add all no interlaced CEA mode to default modelist */
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ mode = &mxc_cea_mode[i];
+ if (!(mode->vmode & FB_VMODE_INTERLACED) && (mode->xres != 0))
+ fb_add_videomode(mode, &hdmi->fbi->modelist);
+ }
+
+ console_unlock();
+}
+
+static void mxc_hdmi_set_mode_to_vga_dvi(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi_disable_overflow_interrupts();
+
+ fb_videomode_to_var(&hdmi->fbi->var, &vga_mode);
+
+ hdmi->requesting_vga_for_initialization = true;
+ mxc_hdmi_notify_fb(hdmi);
+ hdmi->requesting_vga_for_initialization = false;
+}
+
+static void mxc_hdmi_set_mode(struct mxc_hdmi *hdmi)
+{
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+ struct fb_var_screeninfo var;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Set the default mode only once. */
+ if (!hdmi->dft_mode_set) {
+ fb_videomode_to_var(&var, &hdmi->default_mode);
+ hdmi->dft_mode_set = true;
+ } else
+ fb_videomode_to_var(&var, &hdmi->previous_non_vga_mode);
+
+ fb_var_to_videomode(&m, &var);
+ dump_fb_videomode(&m);
+
+ mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ if (!mode) {
+ pr_err("%s: could not find mode in modelist\n", __func__);
+ return;
+ }
+
+ /* If video mode same as previous, init HDMI again */
+ if (fb_mode_is_equal(&hdmi->previous_non_vga_mode, mode)) {
+ dev_dbg(&hdmi->pdev->dev,
+ "%s: Video mode same as previous\n", __func__);
+ /* update fbi mode in case modelist is updated */
+ hdmi->fbi->mode = (struct fb_videomode *)mode;
+ /* update hdmi setting in case EDID data updated */
+ mxc_hdmi_setup(hdmi, 0);
+ } else {
+ dev_dbg(&hdmi->pdev->dev, "%s: New video mode\n", __func__);
+ mxc_hdmi_set_mode_to_vga_dvi(hdmi);
+ fb_videomode_to_var(&hdmi->fbi->var, mode);
+ dump_fb_videomode((struct fb_videomode *)mode);
+ mxc_hdmi_notify_fb(hdmi);
+ }
+
+}
+
+static void mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi)
+{
+ int edid_status;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi->cable_plugin = true;
+
+ /* HDMI Initialization Step C */
+ edid_status = mxc_hdmi_read_edid(hdmi);
+
+ /* Read EDID again if first EDID read failed */
+ if (edid_status == HDMI_EDID_NO_MODES ||
+ edid_status == HDMI_EDID_FAIL) {
+ dev_info(&hdmi->pdev->dev, "Read EDID again\n");
+ edid_status = mxc_hdmi_read_edid(hdmi);
+ }
+
+ /* HDMI Initialization Steps D, E, F */
+ switch (edid_status) {
+ case HDMI_EDID_SUCCESS:
+ mxc_hdmi_edid_rebuild_modelist(hdmi);
+ break;
+
+ /* Nothing to do if EDID same */
+ case HDMI_EDID_SAME:
+ break;
+
+ case HDMI_EDID_FAIL:
+ mxc_hdmi_default_edid_cfg(hdmi);
+ /* No break here */
+ case HDMI_EDID_NO_MODES:
+ default:
+ mxc_hdmi_default_modelist(hdmi);
+ break;
+ }
+
+ /* Setting video mode */
+ mxc_hdmi_set_mode(hdmi);
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+}
+
+static int mxc_hdmi_power_on(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+ mxc_hdmi_phy_init(hdmi);
+ return 0;
+}
+
+static void mxc_hdmi_power_off(struct mxc_dispdrv_handle *disp,
+ struct fb_info *fbi)
+{
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+ mxc_hdmi_phy_disable(hdmi);
+}
+
+static void mxc_hdmi_cable_disconnected(struct mxc_hdmi *hdmi)
+{
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Disable All HDMI clock */
+ hdmi_writeb(0xff, HDMI_MC_CLKDIS);
+
+ mxc_hdmi_phy_disable(hdmi);
+
+ hdmi_disable_overflow_interrupts();
+
+ hdmi->cable_plugin = false;
+}
+
+static void hotplug_worker(struct work_struct *work)
+{
+ struct delayed_work *delay_work = to_delayed_work(work);
+ struct mxc_hdmi *hdmi =
+ container_of(delay_work, struct mxc_hdmi, hotplug_work);
+ u32 phy_int_stat, phy_int_pol, phy_int_mask;
+ u8 val;
+ unsigned long flags;
+ char event_string[32];
+ char *envp[] = { event_string, NULL };
+
+ phy_int_stat = hdmi->latest_intr_stat;
+ phy_int_pol = hdmi_readb(HDMI_PHY_POL0);
+
+ dev_dbg(&hdmi->pdev->dev, "phy_int_stat=0x%x, phy_int_pol=0x%x\n",
+ phy_int_stat, phy_int_pol);
+
+ /* check cable status */
+ if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) {
+ /* cable connection changes */
+ if (phy_int_pol & HDMI_PHY_HPD) {
+ /* Plugin event */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n");
+ mxc_hdmi_cable_connected(hdmi);
+
+ /* Make HPD intr active low to capture unplug event */
+ val = hdmi_readb(HDMI_PHY_POL0);
+ val &= ~HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_POL0);
+
+ hdmi_set_cable_state(1);
+
+ sprintf(event_string, "EVENT=plugin");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+#ifdef CONFIG_MXC_HDMI_CEC
+ mxc_hdmi_cec_handle(0x80);
+#endif
+ } else if (!(phy_int_pol & HDMI_PHY_HPD)) {
+ /* Plugout event */
+ dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n");
+ hdmi_set_cable_state(0);
+ mxc_hdmi_abort_stream();
+ mxc_hdmi_cable_disconnected(hdmi);
+
+ /* Make HPD intr active high to capture plugin event */
+ val = hdmi_readb(HDMI_PHY_POL0);
+ val |= HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_POL0);
+
+ sprintf(event_string, "EVENT=plugout");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+#ifdef CONFIG_MXC_HDMI_CEC
+ mxc_hdmi_cec_handle(0x100);
+#endif
+
+ } else
+ dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n");
+ }
+
+ /* Lock here to ensure full powerdown sequence
+ * completed before next interrupt processed */
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ /* Re-enable HPD interrupts */
+ phy_int_mask = hdmi_readb(HDMI_PHY_MASK0);
+ phy_int_mask &= ~HDMI_PHY_HPD;
+ hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0);
+
+ /* Unmute interrupts */
+ hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+
+ if (hdmi_readb(HDMI_IH_FC_STAT2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK)
+ mxc_hdmi_clear_overflow(hdmi);
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+}
+
+static void hdcp_hdp_worker(struct work_struct *work)
+{
+ struct delayed_work *delay_work = to_delayed_work(work);
+ struct mxc_hdmi *hdmi =
+ container_of(delay_work, struct mxc_hdmi, hdcp_hdp_work);
+ char event_string[32];
+ char *envp[] = { event_string, NULL };
+
+ /* HDCP interrupt */
+ sprintf(event_string, "EVENT=hdcpint");
+ kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
+
+ /* Unmute interrupts in HDCP application*/
+}
+
+static irqreturn_t mxc_hdmi_hotplug(int irq, void *data)
+{
+ struct mxc_hdmi *hdmi = data;
+ u8 val, intr_stat;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ /* Check and clean packet overflow interrupt.*/
+ if (hdmi_readb(HDMI_IH_FC_STAT2) &
+ HDMI_IH_FC_STAT2_OVERFLOW_MASK) {
+ mxc_hdmi_clear_overflow(hdmi);
+
+ dev_dbg(&hdmi->pdev->dev, "Overflow interrupt received\n");
+ /* clear irq status */
+ hdmi_writeb(HDMI_IH_FC_STAT2_OVERFLOW_MASK,
+ HDMI_IH_FC_STAT2);
+ }
+
+ /*
+ * We could not disable the irq. Probably the audio driver
+ * has enabled it. Masking off the HDMI interrupts using
+ * HDMI registers.
+ */
+ /* Capture status - used in hotplug_worker ISR */
+ intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0);
+
+ if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
+
+ dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n");
+ hdmi->latest_intr_stat = intr_stat;
+
+ /* Mute interrupts until handled */
+
+ val = hdmi_readb(HDMI_IH_MUTE_PHY_STAT0);
+ val |= HDMI_IH_MUTE_PHY_STAT0_HPD;
+ hdmi_writeb(val, HDMI_IH_MUTE_PHY_STAT0);
+
+ val = hdmi_readb(HDMI_PHY_MASK0);
+ val |= HDMI_PHY_HPD;
+ hdmi_writeb(val, HDMI_PHY_MASK0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ schedule_delayed_work(&(hdmi->hotplug_work), msecs_to_jiffies(20));
+ }
+
+ /* Check HDCP interrupt state */
+ if (hdmi->hdmi_data.hdcp_enable) {
+ val = hdmi_readb(HDMI_A_APIINTSTAT);
+ if (val != 0) {
+ /* Mute interrupts until interrupt handled */
+ val = 0xFF;
+ hdmi_writeb(val, HDMI_A_APIINTMSK);
+ schedule_delayed_work(&(hdmi->hdcp_hdp_work), msecs_to_jiffies(50));
+ }
+ }
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+ return IRQ_HANDLED;
+}
+
+static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event)
+{
+ struct fb_videomode m;
+ const struct fb_videomode *edid_mode;
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_var_to_videomode(&m, &hdmi->fbi->var);
+ dump_fb_videomode(&m);
+
+ dev_dbg(&hdmi->pdev->dev, "%s - video mode changed\n", __func__);
+
+ hdmi->vic = 0;
+ if (!hdmi->requesting_vga_for_initialization) {
+ /* Save mode if this isn't the result of requesting
+ * vga default. */
+ memcpy(&hdmi->previous_non_vga_mode, &m,
+ sizeof(struct fb_videomode));
+ if (!list_empty(&hdmi->fbi->modelist)) {
+ edid_mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ pr_debug("edid mode ");
+ dump_fb_videomode((struct fb_videomode *)edid_mode);
+ /* update fbi mode */
+ hdmi->fbi->mode = (struct fb_videomode *)edid_mode;
+ hdmi->vic = mxc_edid_mode_to_vic(edid_mode);
+ }
+ }
+
+ hdmi_disable_overflow_interrupts();
+
+ dev_dbg(&hdmi->pdev->dev, "CEA mode used vic=%d\n", hdmi->vic);
+ if (hdmi->edid_cfg.hdmi_cap)
+ hdmi->hdmi_data.video_mode.mDVI = false;
+ else {
+ dev_dbg(&hdmi->pdev->dev, "CEA mode vic=%d work in DVI\n", hdmi->vic);
+ hdmi->hdmi_data.video_mode.mDVI = true;
+ }
+
+ if ((hdmi->vic == 6) || (hdmi->vic == 7) ||
+ (hdmi->vic == 21) || (hdmi->vic == 22) ||
+ (hdmi->vic == 2) || (hdmi->vic == 3) ||
+ (hdmi->vic == 17) || (hdmi->vic == 18))
+ hdmi->hdmi_data.colorimetry = eITU601;
+ else
+ hdmi->hdmi_data.colorimetry = eITU709;
+
+ if ((hdmi->vic == 10) || (hdmi->vic == 11) ||
+ (hdmi->vic == 12) || (hdmi->vic == 13) ||
+ (hdmi->vic == 14) || (hdmi->vic == 15) ||
+ (hdmi->vic == 25) || (hdmi->vic == 26) ||
+ (hdmi->vic == 27) || (hdmi->vic == 28) ||
+ (hdmi->vic == 29) || (hdmi->vic == 30) ||
+ (hdmi->vic == 35) || (hdmi->vic == 36) ||
+ (hdmi->vic == 37) || (hdmi->vic == 38))
+ hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 1;
+ else
+ hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 0;
+
+ hdmi->hdmi_data.video_mode.mPixelRepetitionInput = 0;
+
+ /* TODO: Get input format from IPU (via FB driver iface) */
+ hdmi->hdmi_data.enc_in_format = RGB;
+
+ hdmi->hdmi_data.enc_out_format = RGB;
+
+ /* YCbCr only enabled in HDMI mode */
+ if (!hdmi->hdmi_data.video_mode.mDVI &&
+ !hdmi->hdmi_data.rgb_out_enable) {
+ if (hdmi->edid_cfg.cea_ycbcr444)
+ hdmi->hdmi_data.enc_out_format = YCBCR444;
+ else if (hdmi->edid_cfg.cea_ycbcr422)
+ hdmi->hdmi_data.enc_out_format = YCBCR422_8BITS;
+ }
+
+ /* IPU not support depth color output */
+ hdmi->hdmi_data.enc_color_depth = 8;
+ hdmi->hdmi_data.pix_repet_factor = 0;
+ hdmi->hdmi_data.video_mode.mDataEnablePolarity = true;
+
+ /* HDMI Initialization Step B.1 */
+ hdmi_av_composer(hdmi);
+
+ /* HDMI Initializateion Step B.2 */
+ mxc_hdmi_phy_init(hdmi);
+
+ /* HDMI Initialization Step B.3 */
+ mxc_hdmi_enable_video_path(hdmi);
+
+ /* not for DVI mode */
+ if (hdmi->hdmi_data.video_mode.mDVI)
+ dev_dbg(&hdmi->pdev->dev, "%s DVI mode\n", __func__);
+ else {
+ dev_dbg(&hdmi->pdev->dev, "%s CEA mode\n", __func__);
+
+ /* HDMI Initialization Step E - Configure audio */
+ hdmi_clk_regenerator_update_pixel_clock(hdmi->fbi->var.pixclock);
+ hdmi_enable_audio_clk(hdmi);
+
+ /* HDMI Initialization Step F - Configure AVI InfoFrame */
+ hdmi_config_AVI(hdmi);
+ }
+
+ hdmi_video_packetize(hdmi);
+ hdmi_video_csc(hdmi);
+ hdmi_video_sample(hdmi);
+
+ mxc_hdmi_clear_overflow(hdmi);
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n\n", __func__);
+
+}
+
+/* Wait until we are registered to enable interrupts */
+static void mxc_hdmi_fb_registered(struct mxc_hdmi *hdmi)
+{
+ unsigned long flags;
+
+ if (hdmi->fb_reg)
+ return;
+
+ spin_lock_irqsave(&hdmi->irq_lock, flags);
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ hdmi_writeb(HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
+ HDMI_PHY_I2CM_INT_ADDR);
+
+ hdmi_writeb(HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
+ HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
+ HDMI_PHY_I2CM_CTLINT_ADDR);
+
+ /* enable cable hot plug irq */
+ hdmi_writeb((u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ /* Unmute interrupts */
+ hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
+
+ hdmi->fb_reg = true;
+
+ spin_unlock_irqrestore(&hdmi->irq_lock, flags);
+
+}
+
+static int mxc_hdmi_fb_event(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct fb_event *event = v;
+ struct mxc_hdmi *hdmi = container_of(nb, struct mxc_hdmi, nb);
+
+ if (strcmp(event->info->fix.id, hdmi->fbi->fix.id))
+ return 0;
+
+ switch (val) {
+ case FB_EVENT_FB_REGISTERED:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_REGISTERED\n");
+ mxc_hdmi_fb_registered(hdmi);
+ hdmi_set_registered(1);
+ break;
+
+ case FB_EVENT_FB_UNREGISTERED:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_UNREGISTERED\n");
+ hdmi->fb_reg = false;
+ hdmi_set_registered(0);
+ break;
+
+ case FB_EVENT_MODE_CHANGE:
+ dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_MODE_CHANGE\n");
+ if (hdmi->fb_reg)
+ mxc_hdmi_setup(hdmi, val);
+ break;
+
+ case FB_EVENT_BLANK:
+ if ((*((int *)event->data) == FB_BLANK_UNBLANK) &&
+ (*((int *)event->data) != hdmi->blank)) {
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_BLANK - UNBLANK\n");
+
+ hdmi->blank = *((int *)event->data);
+
+ if (hdmi->fb_reg && hdmi->cable_plugin)
+ mxc_hdmi_setup(hdmi, val);
+ hdmi_set_blank_state(1);
+
+ } else if (*((int *)event->data) != hdmi->blank) {
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_BLANK - BLANK\n");
+ hdmi_set_blank_state(0);
+ mxc_hdmi_abort_stream();
+
+ mxc_hdmi_phy_disable(hdmi);
+
+ hdmi->blank = *((int *)event->data);
+ } else
+ dev_dbg(&hdmi->pdev->dev,
+ "FB BLANK state no changed!\n");
+
+ break;
+
+ case FB_EVENT_SUSPEND:
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_SUSPEND\n");
+
+ if (hdmi->blank == FB_BLANK_UNBLANK) {
+ mxc_hdmi_phy_disable(hdmi);
+ clk_disable(hdmi->hdmi_iahb_clk);
+ clk_disable(hdmi->hdmi_isfr_clk);
+ }
+ break;
+
+ case FB_EVENT_RESUME:
+ dev_dbg(&hdmi->pdev->dev,
+ "event=FB_EVENT_RESUME\n");
+
+ if (hdmi->blank == FB_BLANK_UNBLANK) {
+ clk_enable(hdmi->hdmi_iahb_clk);
+ clk_enable(hdmi->hdmi_isfr_clk);
+ mxc_hdmi_phy_init(hdmi);
+ }
+ break;
+
+ }
+ return 0;
+}
+
+static void hdmi_init_route(struct mxc_hdmi *hdmi)
+{
+ uint32_t hdmi_mux_setting, reg;
+ int ipu_id, disp_id;
+
+ ipu_id = mxc_hdmi_ipu_id;
+ disp_id = mxc_hdmi_disp_id;
+
+ if ((ipu_id > 1) || (ipu_id < 0)) {
+ pr_err("Invalid IPU select for HDMI: %d. Set to 0\n", ipu_id);
+ ipu_id = 0;
+ }
+
+ if ((disp_id > 1) || (disp_id < 0)) {
+ pr_err("Invalid DI select for HDMI: %d. Set to 0\n", disp_id);
+ disp_id = 0;
+ }
+
+ reg = readl(hdmi->gpr_hdmi_base);
+
+ /* Configure the connection between IPU1/2 and HDMI */
+ hdmi_mux_setting = 2*ipu_id + disp_id;
+
+ /* GPR3, bits 2-3 = HDMI_MUX_CTL */
+ reg &= ~0xd;
+ reg |= hdmi_mux_setting << 2;
+
+ writel(reg, hdmi->gpr_hdmi_base);
+
+ /* Set HDMI event as SDMA event2 for HDMI audio */
+ reg = readl(hdmi->gpr_sdma_base);
+ reg |= 0x1;
+ writel(reg, hdmi->gpr_sdma_base);
+}
+
+static void hdmi_hdcp_get_property(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+
+ /* Check hdcp enable by dts.*/
+ hdcp_init = of_property_read_bool(np, "fsl,hdcp");
+ if (hdcp_init)
+ dev_dbg(&pdev->dev, "hdcp enable\n");
+ else
+ dev_dbg(&pdev->dev, "hdcp disable\n");
+}
+
+static void hdmi_get_of_property(struct mxc_hdmi *hdmi)
+{
+ struct platform_device *pdev = hdmi->pdev;
+ struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *of_id =
+ of_match_device(imx_hdmi_dt_ids, &pdev->dev);
+ int ret;
+ u32 phy_reg_vlev = 0, phy_reg_cksymtx = 0;
+
+ if (of_id) {
+ pdev->id_entry = of_id->data;
+ hdmi->cpu_type = pdev->id_entry->driver_data;
+ }
+
+ /* HDMI PHY register vlev and cksymtx preperty is optional.
+ * It is for specific board to pass HCT electrical part.
+ * Default value will been setting in HDMI PHY config function
+ * if it is not define in device tree.
+ */
+ ret = of_property_read_u32(np, "fsl,phy_reg_vlev", &phy_reg_vlev);
+ if (ret)
+ dev_dbg(&pdev->dev, "No board specific HDMI PHY vlev\n");
+
+ ret = of_property_read_u32(np, "fsl,phy_reg_cksymtx", &phy_reg_cksymtx);
+ if (ret)
+ dev_dbg(&pdev->dev, "No board specific HDMI PHY cksymtx\n");
+
+ /* Specific phy config */
+ hdmi->phy_config.reg_cksymtx = phy_reg_cksymtx;
+ hdmi->phy_config.reg_vlev = phy_reg_vlev;
+
+}
+
+/* HDMI Initialization Step A */
+static int mxc_hdmi_disp_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ int ret = 0;
+ u32 i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+ int irq = platform_get_irq(hdmi->pdev, 0);
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ /* Check hdmi disp init once */
+ if (hdmi_inited) {
+ dev_err(&hdmi->pdev->dev,
+ "Error only one HDMI output support now!\n");
+ return -1;
+ }
+
+ hdmi_get_of_property(hdmi);
+
+ if (irq < 0)
+ return -ENODEV;
+
+ /* Setting HDMI default to blank state */
+ hdmi->blank = FB_BLANK_POWERDOWN;
+
+ ret = ipu_di_to_crtc(&hdmi->pdev->dev, mxc_hdmi_ipu_id,
+ mxc_hdmi_disp_id, &setting->crtc);
+ if (ret < 0)
+ return ret;
+
+ setting->if_fmt = IPU_PIX_FMT_RGB24;
+
+ hdmi->dft_mode_str = setting->dft_mode_str;
+ hdmi->default_bpp = setting->default_bpp;
+ dev_dbg(&hdmi->pdev->dev, "%s - default mode %s bpp=%d\n",
+ __func__, hdmi->dft_mode_str, hdmi->default_bpp);
+
+ hdmi->fbi = setting->fbi;
+
+ hdmi_init_route(hdmi);
+
+ hdmi->hdmi_isfr_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr");
+ if (IS_ERR(hdmi->hdmi_isfr_clk)) {
+ ret = PTR_ERR(hdmi->hdmi_isfr_clk);
+ dev_err(&hdmi->pdev->dev,
+ "Unable to get HDMI clk: %d\n", ret);
+ goto egetclk1;
+ }
+
+ ret = clk_prepare_enable(hdmi->hdmi_isfr_clk);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Cannot enable HDMI isfr clock: %d\n", ret);
+ goto erate1;
+ }
+
+ hdmi->hdmi_iahb_clk = clk_get(&hdmi->pdev->dev, "hdmi_iahb");
+ if (IS_ERR(hdmi->hdmi_iahb_clk)) {
+ ret = PTR_ERR(hdmi->hdmi_iahb_clk);
+ dev_err(&hdmi->pdev->dev,
+ "Unable to get HDMI clk: %d\n", ret);
+ goto egetclk2;
+ }
+
+ ret = clk_prepare_enable(hdmi->hdmi_iahb_clk);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Cannot enable HDMI iahb clock: %d\n", ret);
+ goto erate2;
+ }
+
+ dev_dbg(&hdmi->pdev->dev, "Enabled HDMI clocks\n");
+
+ /* Init DDC pins for HDCP */
+ if (hdcp_init) {
+ hdmi->pinctrl = devm_pinctrl_get_select_default(&hdmi->pdev->dev);
+ if (IS_ERR(hdmi->pinctrl)) {
+ dev_err(&hdmi->pdev->dev, "can't get/select DDC pinctrl\n");
+ goto erate2;
+ }
+ }
+
+ /* Product and revision IDs */
+ dev_info(&hdmi->pdev->dev,
+ "Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n",
+ hdmi_readb(HDMI_DESIGN_ID),
+ hdmi_readb(HDMI_REVISION_ID),
+ hdmi_readb(HDMI_PRODUCT_ID0),
+ hdmi_readb(HDMI_PRODUCT_ID1));
+
+ /* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
+ * N and cts values before enabling phy */
+ hdmi_init_clk_regenerator();
+
+ INIT_LIST_HEAD(&hdmi->fbi->modelist);
+
+ spin_lock_init(&hdmi->irq_lock);
+
+ /* Set the default mode and modelist when disp init. */
+ fb_find_mode(&hdmi->fbi->var, hdmi->fbi,
+ hdmi->dft_mode_str, NULL, 0, NULL,
+ hdmi->default_bpp);
+
+ console_lock();
+
+ fb_destroy_modelist(&hdmi->fbi->modelist);
+
+ /*Add all no interlaced CEA mode to default modelist */
+ for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
+ mode = &mxc_cea_mode[i];
+ if (!(mode->vmode & FB_VMODE_INTERLACED) && (mode->xres != 0))
+ fb_add_videomode(mode, &hdmi->fbi->modelist);
+ }
+
+ console_unlock();
+
+ /* Find a nearest mode in default modelist */
+ fb_var_to_videomode(&m, &hdmi->fbi->var);
+ dump_fb_videomode(&m);
+
+ hdmi->dft_mode_set = false;
+ /* Save default video mode */
+ memcpy(&hdmi->default_mode, &m, sizeof(struct fb_videomode));
+
+ mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
+ if (!mode) {
+ pr_err("%s: could not find mode in modelist\n", __func__);
+ return -1;
+ }
+
+ fb_videomode_to_var(&hdmi->fbi->var, mode);
+
+ /* update fbi mode */
+ hdmi->fbi->mode = (struct fb_videomode *)mode;
+
+ /* Default setting HDMI working in HDMI mode*/
+ hdmi->edid_cfg.hdmi_cap = true;
+
+ INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker);
+ INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker);
+
+ /* Configure registers related to HDMI interrupt
+ * generation before registering IRQ. */
+ hdmi_writeb(HDMI_PHY_HPD, HDMI_PHY_POL0);
+
+ /* Clear Hotplug interrupts */
+ hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
+
+ hdmi->nb.notifier_call = mxc_hdmi_fb_event;
+ ret = fb_register_client(&hdmi->nb);
+ if (ret < 0)
+ goto efbclient;
+
+ memset(&hdmi->hdmi_data, 0, sizeof(struct hdmi_data_info));
+
+ /* Default HDMI working in RGB mode */
+ hdmi->hdmi_data.rgb_out_enable = true;
+
+ ret = devm_request_irq(&hdmi->pdev->dev, irq, mxc_hdmi_hotplug, IRQF_SHARED,
+ dev_name(&hdmi->pdev->dev), hdmi);
+ if (ret < 0) {
+ dev_err(&hdmi->pdev->dev,
+ "Unable to request irq: %d\n", ret);
+ goto ereqirq;
+ }
+
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_fb_name);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for fb name\n");
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_cable_state);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for cable state\n");
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_edid);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for edid\n");
+
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_rgb_out_enable);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for rgb out enable\n");
+
+ ret = device_create_file(&hdmi->pdev->dev, &dev_attr_hdcp_enable);
+ if (ret < 0)
+ dev_warn(&hdmi->pdev->dev,
+ "cound not create sys node for hdcp enable\n");
+
+ dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
+
+ hdmi_inited = true;
+
+ return ret;
+
+efbclient:
+ free_irq(irq, hdmi);
+ereqirq:
+ clk_disable_unprepare(hdmi->hdmi_iahb_clk);
+erate2:
+ clk_put(hdmi->hdmi_iahb_clk);
+egetclk2:
+ clk_disable_unprepare(hdmi->hdmi_isfr_clk);
+erate1:
+ clk_put(hdmi->hdmi_isfr_clk);
+egetclk1:
+ dev_dbg(&hdmi->pdev->dev, "%s error exit\n", __func__);
+
+ return ret;
+}
+
+static void mxc_hdmi_disp_deinit(struct mxc_dispdrv_handle *disp)
+{
+ struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
+
+ dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
+
+ fb_unregister_client(&hdmi->nb);
+
+ clk_disable_unprepare(hdmi->hdmi_isfr_clk);
+ clk_put(hdmi->hdmi_isfr_clk);
+ clk_disable_unprepare(hdmi->hdmi_iahb_clk);
+ clk_put(hdmi->hdmi_iahb_clk);
+
+ platform_device_unregister(hdmi->pdev);
+
+ hdmi_inited = false;
+}
+
+static struct mxc_dispdrv_driver mxc_hdmi_drv = {
+ .name = DISPDRV_HDMI,
+ .init = mxc_hdmi_disp_init,
+ .deinit = mxc_hdmi_disp_deinit,
+ .enable = mxc_hdmi_power_on,
+ .disable = mxc_hdmi_power_off,
+};
+
+
+static int mxc_hdmi_open(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static long mxc_hdmi_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int __user *argp = (void __user *)arg;
+ int ret = 0;
+
+ switch (cmd) {
+ case HDMI_IOC_GET_RESOURCE:
+ ret = copy_to_user(argp, &g_hdmi->hdmi_data,
+ sizeof(g_hdmi->hdmi_data)) ? -EFAULT : 0;
+ break;
+ case HDMI_IOC_GET_CPU_TYPE:
+ *argp = g_hdmi->cpu_type;
+ break;
+ default:
+ pr_debug("Unsupport cmd %d\n", cmd);
+ break;
+ }
+ return ret;
+}
+
+static int mxc_hdmi_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations mxc_hdmi_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_hdmi_open,
+ .release = mxc_hdmi_release,
+ .unlocked_ioctl = mxc_hdmi_ioctl,
+};
+
+
+static int mxc_hdmi_probe(struct platform_device *pdev)
+{
+ struct mxc_hdmi *hdmi;
+ struct device *temp_class;
+ struct resource *res;
+ int ret = 0;
+
+ /* Check I2C driver is loaded and available
+ * check hdcp function is enable by dts */
+ hdmi_hdcp_get_property(pdev);
+ if (!hdmi_i2c && !hdcp_init)
+ return -ENODEV;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENOENT;
+
+ hdmi = devm_kzalloc(&pdev->dev,
+ sizeof(struct mxc_hdmi),
+ GFP_KERNEL);
+ if (!hdmi) {
+ dev_err(&pdev->dev, "Cannot allocate device data\n");
+ ret = -ENOMEM;
+ goto ealloc;
+ }
+ g_hdmi = hdmi;
+
+ hdmi_major = register_chrdev(hdmi_major, "mxc_hdmi", &mxc_hdmi_fops);
+ if (hdmi_major < 0) {
+ printk(KERN_ERR "HDMI: unable to get a major for HDMI\n");
+ ret = -EBUSY;
+ goto ealloc;
+ }
+
+ hdmi_class = class_create(THIS_MODULE, "mxc_hdmi");
+ if (IS_ERR(hdmi_class)) {
+ ret = PTR_ERR(hdmi_class);
+ goto err_out_chrdev;
+ }
+
+ temp_class = device_create(hdmi_class, NULL, MKDEV(hdmi_major, 0),
+ NULL, "mxc_hdmi");
+ if (IS_ERR(temp_class)) {
+ ret = PTR_ERR(temp_class);
+ goto err_out_class;
+ }
+
+ hdmi->pdev = pdev;
+
+ hdmi->core_pdev = platform_device_alloc("mxc_hdmi_core", -1);
+ if (!hdmi->core_pdev) {
+ pr_err("%s failed platform_device_alloc for hdmi core\n",
+ __func__);
+ ret = -ENOMEM;
+ goto ecore;
+ }
+
+ hdmi->gpr_base = ioremap(res->start, resource_size(res));
+ if (!hdmi->gpr_base) {
+ dev_err(&pdev->dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto eiomap;
+ }
+
+ hdmi->gpr_hdmi_base = hdmi->gpr_base + 3;
+ hdmi->gpr_sdma_base = hdmi->gpr_base;
+
+ hdmi_inited = false;
+
+ hdmi->disp_mxc_hdmi = mxc_dispdrv_register(&mxc_hdmi_drv);
+ if (IS_ERR(hdmi->disp_mxc_hdmi)) {
+ dev_err(&pdev->dev, "Failed to register dispdrv - 0x%x\n",
+ (int)hdmi->disp_mxc_hdmi);
+ ret = (int)hdmi->disp_mxc_hdmi;
+ goto edispdrv;
+ }
+ mxc_dispdrv_setdata(hdmi->disp_mxc_hdmi, hdmi);
+
+ platform_set_drvdata(pdev, hdmi);
+
+ return 0;
+edispdrv:
+ iounmap(hdmi->gpr_base);
+eiomap:
+ platform_device_put(hdmi->core_pdev);
+ecore:
+ kfree(hdmi);
+err_out_class:
+ device_destroy(hdmi_class, MKDEV(hdmi_major, 0));
+ class_destroy(hdmi_class);
+err_out_chrdev:
+ unregister_chrdev(hdmi_major, "mxc_hdmi");
+ealloc:
+ return ret;
+}
+
+static int mxc_hdmi_remove(struct platform_device *pdev)
+{
+ struct mxc_hdmi *hdmi = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
+
+ fb_unregister_client(&hdmi->nb);
+
+ mxc_dispdrv_puthandle(hdmi->disp_mxc_hdmi);
+ mxc_dispdrv_unregister(hdmi->disp_mxc_hdmi);
+ iounmap(hdmi->gpr_base);
+ /* No new work will be scheduled, wait for running ISR */
+ free_irq(irq, hdmi);
+ kfree(hdmi);
+ g_hdmi = NULL;
+
+ return 0;
+}
+
+static struct platform_driver mxc_hdmi_driver = {
+ .probe = mxc_hdmi_probe,
+ .remove = mxc_hdmi_remove,
+ .driver = {
+ .name = "mxc_hdmi",
+ .of_match_table = imx_hdmi_dt_ids,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mxc_hdmi_init(void)
+{
+ return platform_driver_register(&mxc_hdmi_driver);
+}
+module_init(mxc_hdmi_init);
+
+static void __exit mxc_hdmi_exit(void)
+{
+ if (hdmi_major > 0) {
+ device_destroy(hdmi_class, MKDEV(hdmi_major, 0));
+ class_destroy(hdmi_class);
+ unregister_chrdev(hdmi_major, "mxc_hdmi");
+ hdmi_major = 0;
+ }
+
+ platform_driver_unregister(&mxc_hdmi_driver);
+}
+module_exit(mxc_hdmi_exit);
+
+static int mxc_hdmi_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
+ return -ENODEV;
+
+ hdmi_i2c = client;
+
+ return 0;
+}
+
+static int mxc_hdmi_i2c_remove(struct i2c_client *client)
+{
+ hdmi_i2c = NULL;
+ return 0;
+}
+
+static const struct of_device_id imx_hdmi_i2c_match[] = {
+ { .compatible = "fsl,imx6-hdmi-i2c", },
+ { /* sentinel */ }
+};
+
+static const struct i2c_device_id mxc_hdmi_i2c_id[] = {
+ { "mxc_hdmi_i2c", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, mxc_hdmi_i2c_id);
+
+static struct i2c_driver mxc_hdmi_i2c_driver = {
+ .driver = {
+ .name = "mxc_hdmi_i2c",
+ .of_match_table = imx_hdmi_i2c_match,
+ },
+ .probe = mxc_hdmi_i2c_probe,
+ .remove = mxc_hdmi_i2c_remove,
+ .id_table = mxc_hdmi_i2c_id,
+};
+
+static int __init mxc_hdmi_i2c_init(void)
+{
+ return i2c_add_driver(&mxc_hdmi_i2c_driver);
+}
+
+static void __exit mxc_hdmi_i2c_exit(void)
+{
+ i2c_del_driver(&mxc_hdmi_i2c_driver);
+}
+
+module_init(mxc_hdmi_i2c_init);
+module_exit(mxc_hdmi_i2c_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
diff --git a/drivers/video/mxc/mxc_ipuv3_fb.c b/drivers/video/mxc/mxc_ipuv3_fb.c
new file mode 100644
index 000000000000..9d074f9e7a80
--- /dev/null
+++ b/drivers/video/mxc/mxc_ipuv3_fb.c
@@ -0,0 +1,2583 @@
+/*
+ * Copyright 2004-2014 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
+ */
+
+/*!
+ * @defgroup Framebuffer Framebuffer Driver for SDC and ADC.
+ */
+
+/*!
+ * @file mxcfb.c
+ *
+ * @brief MXC Frame buffer driver for SDC
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/fsl_devices.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/ipu.h>
+#include <linux/ipu-v3.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mxcfb.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include "mxc_dispdrv.h"
+
+/*
+ * Driver name
+ */
+#define MXCFB_NAME "mxc_sdc_fb"
+
+/* Display port number */
+#define MXCFB_PORT_NUM 2
+/*!
+ * Structure containing the MXC specific framebuffer information.
+ */
+struct mxcfb_info {
+ int default_bpp;
+ int cur_blank;
+ int next_blank;
+ ipu_channel_t ipu_ch;
+ int ipu_id;
+ int ipu_di;
+ u32 ipu_di_pix_fmt;
+ bool ipu_int_clk;
+ bool overlay;
+ bool alpha_chan_en;
+ bool late_init;
+ bool first_set_par;
+ dma_addr_t alpha_phy_addr0;
+ dma_addr_t alpha_phy_addr1;
+ void *alpha_virt_addr0;
+ void *alpha_virt_addr1;
+ uint32_t alpha_mem_len;
+ uint32_t ipu_ch_irq;
+ uint32_t ipu_ch_nf_irq;
+ uint32_t ipu_alp_ch_irq;
+ uint32_t cur_ipu_buf;
+ uint32_t cur_ipu_alpha_buf;
+
+ u32 pseudo_palette[16];
+
+ bool mode_found;
+ struct completion flip_complete;
+ struct completion alpha_flip_complete;
+ struct completion vsync_complete;
+
+ void *ipu;
+ struct fb_info *ovfbi;
+
+ struct mxc_dispdrv_handle *dispdrv;
+
+ struct fb_var_screeninfo cur_var;
+};
+
+struct mxcfb_pfmt {
+ u32 fb_pix_fmt;
+ int bpp;
+ struct fb_bitfield red;
+ struct fb_bitfield green;
+ struct fb_bitfield blue;
+ struct fb_bitfield transp;
+};
+
+static const struct mxcfb_pfmt mxcfb_pfmts[] = {
+ /* pixel bpp red green blue transp */
+ {IPU_PIX_FMT_RGB565, 16, {11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0} },
+ {IPU_PIX_FMT_RGB24, 24, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, { 0, 0, 0} },
+ {IPU_PIX_FMT_BGR24, 24, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0} },
+ {IPU_PIX_FMT_RGB32, 32, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, {24, 8, 0} },
+ {IPU_PIX_FMT_BGR32, 32, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, {24, 8, 0} },
+ {IPU_PIX_FMT_ABGR32, 32, {24, 8, 0}, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0} },
+};
+
+struct mxcfb_alloc_list {
+ struct list_head list;
+ dma_addr_t phy_addr;
+ void *cpu_addr;
+ u32 size;
+};
+
+enum {
+ BOTH_ON,
+ SRC_ON,
+ TGT_ON,
+ BOTH_OFF
+};
+
+static bool g_dp_in_use[2];
+LIST_HEAD(fb_alloc_list);
+
+/* Return default standard(RGB) pixel format */
+static uint32_t bpp_to_pixfmt(int bpp)
+{
+ uint32_t pixfmt = 0;
+
+ switch (bpp) {
+ case 24:
+ pixfmt = IPU_PIX_FMT_BGR24;
+ break;
+ case 32:
+ pixfmt = IPU_PIX_FMT_BGR32;
+ break;
+ case 16:
+ pixfmt = IPU_PIX_FMT_RGB565;
+ break;
+ }
+ return pixfmt;
+}
+
+static inline int bitfield_is_equal(struct fb_bitfield f1,
+ struct fb_bitfield f2)
+{
+ return !memcmp(&f1, &f2, sizeof(f1));
+}
+
+static int pixfmt_to_var(uint32_t pixfmt, struct fb_var_screeninfo *var)
+{
+ int i, ret = -1;
+
+ for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
+ if (pixfmt == mxcfb_pfmts[i].fb_pix_fmt) {
+ var->red = mxcfb_pfmts[i].red;
+ var->green = mxcfb_pfmts[i].green;
+ var->blue = mxcfb_pfmts[i].blue;
+ var->transp = mxcfb_pfmts[i].transp;
+ var->bits_per_pixel = mxcfb_pfmts[i].bpp;
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+static int bpp_to_var(int bpp, struct fb_var_screeninfo *var)
+{
+ uint32_t pixfmt = 0;
+
+ pixfmt = bpp_to_pixfmt(bpp);
+ if (pixfmt)
+ return pixfmt_to_var(pixfmt, var);
+ else
+ return -1;
+}
+
+static int check_var_pixfmt(struct fb_var_screeninfo *var)
+{
+ int i, ret = -1;
+
+ for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
+ if (bitfield_is_equal(var->red, mxcfb_pfmts[i].red) &&
+ bitfield_is_equal(var->green, mxcfb_pfmts[i].green) &&
+ bitfield_is_equal(var->blue, mxcfb_pfmts[i].blue) &&
+ bitfield_is_equal(var->transp, mxcfb_pfmts[i].transp) &&
+ var->bits_per_pixel == mxcfb_pfmts[i].bpp) {
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+static uint32_t fbi_to_pixfmt(struct fb_info *fbi)
+{
+ int i;
+ uint32_t pixfmt = 0;
+
+ if (fbi->var.nonstd)
+ return fbi->var.nonstd;
+
+ for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
+ if (bitfield_is_equal(fbi->var.red, mxcfb_pfmts[i].red) &&
+ bitfield_is_equal(fbi->var.green, mxcfb_pfmts[i].green) &&
+ bitfield_is_equal(fbi->var.blue, mxcfb_pfmts[i].blue) &&
+ bitfield_is_equal(fbi->var.transp, mxcfb_pfmts[i].transp)) {
+ pixfmt = mxcfb_pfmts[i].fb_pix_fmt;
+ break;
+ }
+ }
+
+ if (pixfmt == 0)
+ dev_err(fbi->device, "cannot get pixel format\n");
+
+ return pixfmt;
+}
+
+static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id)
+{
+ int i;
+ struct mxcfb_info *mxc_fbi;
+ struct fb_info *fbi = NULL;
+
+ for (i = 0; i < num_registered_fb; i++) {
+ mxc_fbi =
+ ((struct mxcfb_info *)(registered_fb[i]->par));
+
+ if ((mxc_fbi->ipu_ch == ipu_ch) &&
+ (mxc_fbi->ipu_id == ipu_id)) {
+ fbi = registered_fb[i];
+ break;
+ }
+ }
+ return fbi;
+}
+
+static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id);
+static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id);
+static int mxcfb_blank(int blank, struct fb_info *info);
+static int mxcfb_map_video_memory(struct fb_info *fbi);
+static int mxcfb_unmap_video_memory(struct fb_info *fbi);
+
+/*
+ * Set fixed framebuffer parameters based on variable settings.
+ *
+ * @param info framebuffer information pointer
+ */
+static int mxcfb_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->ywrapstep = 1;
+ fix->ypanstep = 1;
+
+ return 0;
+}
+
+static int _setup_disp_channel1(struct fb_info *fbi)
+{
+ ipu_channel_params_t params;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ memset(&params, 0, sizeof(params));
+
+ if (mxc_fbi->ipu_ch == MEM_DC_SYNC) {
+ params.mem_dc_sync.di = mxc_fbi->ipu_di;
+ if (fbi->var.vmode & FB_VMODE_INTERLACED)
+ params.mem_dc_sync.interlaced = true;
+ params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+ params.mem_dc_sync.in_pixel_fmt = fbi_to_pixfmt(fbi);
+ } else {
+ params.mem_dp_bg_sync.di = mxc_fbi->ipu_di;
+ if (fbi->var.vmode & FB_VMODE_INTERLACED)
+ params.mem_dp_bg_sync.interlaced = true;
+ params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+ params.mem_dp_bg_sync.in_pixel_fmt = fbi_to_pixfmt(fbi);
+ if (mxc_fbi->alpha_chan_en)
+ params.mem_dp_bg_sync.alpha_chan_en = true;
+ }
+ ipu_init_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, &params);
+
+ return 0;
+}
+
+static int _setup_disp_channel2(struct fb_info *fbi)
+{
+ int retval = 0;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+ int fb_stride;
+ unsigned long base;
+ unsigned int fr_xoff, fr_yoff, fr_w, fr_h;
+
+ switch (fbi_to_pixfmt(fbi)) {
+ case IPU_PIX_FMT_YUV420P2:
+ case IPU_PIX_FMT_YVU420P:
+ case IPU_PIX_FMT_NV12:
+ case IPU_PIX_FMT_YUV422P:
+ case IPU_PIX_FMT_YVU422P:
+ case IPU_PIX_FMT_YUV420P:
+ case IPU_PIX_FMT_YUV444P:
+ fb_stride = fbi->var.xres_virtual;
+ break;
+ default:
+ fb_stride = fbi->fix.line_length;
+ }
+
+ base = fbi->fix.smem_start;
+ fr_xoff = fbi->var.xoffset;
+ fr_w = fbi->var.xres_virtual;
+ if (!(fbi->var.vmode & FB_VMODE_YWRAP)) {
+ dev_dbg(fbi->device, "Y wrap disabled\n");
+ fr_yoff = fbi->var.yoffset % fbi->var.yres;
+ fr_h = fbi->var.yres;
+ base += fbi->fix.line_length * fbi->var.yres *
+ (fbi->var.yoffset / fbi->var.yres);
+ } else {
+ dev_dbg(fbi->device, "Y wrap enabled\n");
+ fr_yoff = fbi->var.yoffset;
+ fr_h = fbi->var.yres_virtual;
+ }
+ base += fr_yoff * fb_stride + fr_xoff;
+
+ mxc_fbi->cur_ipu_buf = 2;
+ init_completion(&mxc_fbi->flip_complete);
+ /*
+ * We don't need to wait for vsync at the first time
+ * we do pan display after fb is initialized, as IPU will
+ * switch to the newly selected buffer automatically,
+ * so we call complete() for both mxc_fbi->flip_complete
+ * and mxc_fbi->alpha_flip_complete.
+ */
+ complete(&mxc_fbi->flip_complete);
+ if (mxc_fbi->alpha_chan_en) {
+ mxc_fbi->cur_ipu_alpha_buf = 1;
+ init_completion(&mxc_fbi->alpha_flip_complete);
+ complete(&mxc_fbi->alpha_flip_complete);
+ }
+
+ retval = ipu_init_channel_buffer(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ fbi_to_pixfmt(fbi),
+ fbi->var.xres, fbi->var.yres,
+ fb_stride,
+ fbi->var.rotate,
+ base,
+ base,
+ fbi->var.accel_flags &
+ FB_ACCEL_DOUBLE_FLAG ? 0 : base,
+ 0, 0);
+ if (retval) {
+ dev_err(fbi->device,
+ "ipu_init_channel_buffer error %d\n", retval);
+ return retval;
+ }
+
+ /* update u/v offset */
+ ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER,
+ fbi_to_pixfmt(fbi),
+ fr_w,
+ fr_h,
+ fr_w,
+ 0, 0,
+ fr_yoff,
+ fr_xoff);
+
+ if (mxc_fbi->alpha_chan_en) {
+ retval = ipu_init_channel_buffer(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ IPU_PIX_FMT_GENERIC,
+ fbi->var.xres, fbi->var.yres,
+ fbi->var.xres,
+ fbi->var.rotate,
+ mxc_fbi->alpha_phy_addr1,
+ mxc_fbi->alpha_phy_addr0,
+ 0,
+ 0, 0);
+ if (retval) {
+ dev_err(fbi->device,
+ "ipu_init_channel_buffer error %d\n", retval);
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+static bool mxcfb_need_to_set_par(struct fb_info *fbi)
+{
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ if ((fbi->var.activate & FB_ACTIVATE_FORCE) &&
+ (fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW)
+ return true;
+
+ /*
+ * Ignore xoffset and yoffset update,
+ * because pan display handles this case.
+ */
+ mxc_fbi->cur_var.xoffset = fbi->var.xoffset;
+ mxc_fbi->cur_var.yoffset = fbi->var.yoffset;
+
+ return !!memcmp(&mxc_fbi->cur_var, &fbi->var,
+ sizeof(struct fb_var_screeninfo));
+}
+
+/*
+ * Set framebuffer parameters and change the operating mode.
+ *
+ * @param info framebuffer information pointer
+ */
+static int mxcfb_set_par(struct fb_info *fbi)
+{
+ int retval = 0;
+ u32 mem_len, alpha_mem_len;
+ ipu_di_signal_cfg_t sig_cfg;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ int16_t ov_pos_x = 0, ov_pos_y = 0;
+ int ov_pos_ret = 0;
+ struct mxcfb_info *mxc_fbi_fg = NULL;
+ bool ovfbi_enable = false;
+
+ if (ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi)) &&
+ mxc_fbi->alpha_chan_en) {
+ dev_err(fbi->device, "Bad pixel format for "
+ "graphics plane fb\n");
+ return -EINVAL;
+ }
+
+ if (mxc_fbi->ovfbi)
+ mxc_fbi_fg = (struct mxcfb_info *)mxc_fbi->ovfbi->par;
+
+ if (mxc_fbi->ovfbi && mxc_fbi_fg)
+ if (mxc_fbi_fg->next_blank == FB_BLANK_UNBLANK)
+ ovfbi_enable = true;
+
+ if (!mxcfb_need_to_set_par(fbi))
+ return 0;
+
+ dev_dbg(fbi->device, "Reconfiguring framebuffer\n");
+
+ if (fbi->var.xres == 0 || fbi->var.yres == 0)
+ return 0;
+
+ if (ovfbi_enable) {
+ ov_pos_ret = ipu_disp_get_window_pos(
+ mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch,
+ &ov_pos_x, &ov_pos_y);
+ if (ov_pos_ret < 0)
+ dev_err(fbi->device, "Get overlay pos failed, dispdrv:%s.\n",
+ mxc_fbi->dispdrv->drv->name);
+
+ ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq);
+ ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq);
+ ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq);
+ ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq);
+ ipu_disable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, true);
+ ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
+ }
+
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true);
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+
+ /*
+ * Disable IPU hsp clock if it is enabled for an
+ * additional time in ipu common driver.
+ */
+ if (mxc_fbi->first_set_par && mxc_fbi->late_init)
+ ipu_disable_hsp_clk(mxc_fbi->ipu);
+
+ mxcfb_set_fix(fbi);
+
+ mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
+ if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) {
+ if (fbi->fix.smem_start)
+ mxcfb_unmap_video_memory(fbi);
+
+ if (mxcfb_map_video_memory(fbi) < 0)
+ return -ENOMEM;
+ }
+
+ if (mxc_fbi->first_set_par) {
+ /*
+ * Clear the screen in case uboot fb pixel format is not
+ * the same to kernel fb pixel format.
+ */
+ if (mxc_fbi->late_init)
+ memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+ mxc_fbi->first_set_par = false;
+ }
+
+ if (mxc_fbi->alpha_chan_en) {
+ alpha_mem_len = fbi->var.xres * fbi->var.yres;
+ if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) ||
+ (alpha_mem_len > mxc_fbi->alpha_mem_len)) {
+ if (mxc_fbi->alpha_phy_addr0)
+ dma_free_coherent(fbi->device,
+ mxc_fbi->alpha_mem_len,
+ mxc_fbi->alpha_virt_addr0,
+ mxc_fbi->alpha_phy_addr0);
+ if (mxc_fbi->alpha_phy_addr1)
+ dma_free_coherent(fbi->device,
+ mxc_fbi->alpha_mem_len,
+ mxc_fbi->alpha_virt_addr1,
+ mxc_fbi->alpha_phy_addr1);
+
+ mxc_fbi->alpha_virt_addr0 =
+ dma_alloc_coherent(fbi->device,
+ alpha_mem_len,
+ &mxc_fbi->alpha_phy_addr0,
+ GFP_DMA | GFP_KERNEL);
+
+ mxc_fbi->alpha_virt_addr1 =
+ dma_alloc_coherent(fbi->device,
+ alpha_mem_len,
+ &mxc_fbi->alpha_phy_addr1,
+ GFP_DMA | GFP_KERNEL);
+ if (mxc_fbi->alpha_virt_addr0 == NULL ||
+ mxc_fbi->alpha_virt_addr1 == NULL) {
+ dev_err(fbi->device, "mxcfb: dma alloc for"
+ " alpha buffer failed.\n");
+ if (mxc_fbi->alpha_virt_addr0)
+ dma_free_coherent(fbi->device,
+ mxc_fbi->alpha_mem_len,
+ mxc_fbi->alpha_virt_addr0,
+ mxc_fbi->alpha_phy_addr0);
+ if (mxc_fbi->alpha_virt_addr1)
+ dma_free_coherent(fbi->device,
+ mxc_fbi->alpha_mem_len,
+ mxc_fbi->alpha_virt_addr1,
+ mxc_fbi->alpha_phy_addr1);
+ return -ENOMEM;
+ }
+ mxc_fbi->alpha_mem_len = alpha_mem_len;
+ }
+ }
+
+ if (mxc_fbi->next_blank != FB_BLANK_UNBLANK)
+ return retval;
+
+ if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->setup) {
+ retval = mxc_fbi->dispdrv->drv->setup(mxc_fbi->dispdrv, fbi);
+ if (retval < 0) {
+ dev_err(fbi->device, "setup error, dispdrv:%s.\n",
+ mxc_fbi->dispdrv->drv->name);
+ return -EINVAL;
+ }
+ }
+
+ _setup_disp_channel1(fbi);
+ if (ovfbi_enable)
+ _setup_disp_channel1(mxc_fbi->ovfbi);
+
+ if (!mxc_fbi->overlay) {
+ uint32_t out_pixel_fmt;
+
+ memset(&sig_cfg, 0, sizeof(sig_cfg));
+ if (fbi->var.vmode & FB_VMODE_INTERLACED)
+ sig_cfg.interlaced = true;
+ out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
+ if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
+ sig_cfg.odd_field_first = true;
+ if (mxc_fbi->ipu_int_clk)
+ sig_cfg.int_clk = true;
+ if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
+ sig_cfg.Hsync_pol = true;
+ if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
+ sig_cfg.Vsync_pol = true;
+ if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL))
+ sig_cfg.clk_pol = true;
+ if (fbi->var.sync & FB_SYNC_DATA_INVERT)
+ sig_cfg.data_pol = true;
+ if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT))
+ sig_cfg.enable_pol = true;
+ if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
+ sig_cfg.clkidle_en = true;
+
+ dev_dbg(fbi->device, "pixclock = %ul Hz\n",
+ (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL));
+
+ if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di,
+ (PICOS2KHZ(fbi->var.pixclock)) * 1000UL,
+ fbi->var.xres, fbi->var.yres,
+ out_pixel_fmt,
+ fbi->var.left_margin,
+ fbi->var.hsync_len,
+ fbi->var.right_margin,
+ fbi->var.upper_margin,
+ fbi->var.vsync_len,
+ fbi->var.lower_margin,
+ 0, sig_cfg) != 0) {
+ dev_err(fbi->device,
+ "mxcfb: Error initializing panel.\n");
+ return -EINVAL;
+ }
+
+ fbi->mode =
+ (struct fb_videomode *)fb_match_mode(&fbi->var,
+ &fbi->modelist);
+
+ ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0);
+ }
+
+ retval = _setup_disp_channel2(fbi);
+ if (retval) {
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ return retval;
+ }
+
+ if (ovfbi_enable) {
+ if (ov_pos_ret >= 0)
+ ipu_disp_set_window_pos(
+ mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch,
+ ov_pos_x, ov_pos_y);
+ retval = _setup_disp_channel2(mxc_fbi->ovfbi);
+ if (retval) {
+ ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ return retval;
+ }
+ }
+
+ ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ if (ovfbi_enable)
+ ipu_enable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
+
+ if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->enable) {
+ retval = mxc_fbi->dispdrv->drv->enable(mxc_fbi->dispdrv, fbi);
+ if (retval < 0) {
+ dev_err(fbi->device, "enable error, dispdrv:%s.\n",
+ mxc_fbi->dispdrv->drv->name);
+ return -EINVAL;
+ }
+ }
+
+ mxc_fbi->cur_var = fbi->var;
+
+ return retval;
+}
+
+static int _swap_channels(struct fb_info *fbi_from,
+ struct fb_info *fbi_to, bool both_on)
+{
+ int retval, tmp;
+ ipu_channel_t old_ch;
+ struct fb_info *ovfbi;
+ struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
+ struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
+
+ if (both_on) {
+ ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true);
+ ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
+ }
+
+ /* switch the mxc fbi parameters */
+ old_ch = mxc_fbi_from->ipu_ch;
+ mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch;
+ mxc_fbi_to->ipu_ch = old_ch;
+ tmp = mxc_fbi_from->ipu_ch_irq;
+ mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
+ mxc_fbi_to->ipu_ch_irq = tmp;
+ tmp = mxc_fbi_from->ipu_ch_nf_irq;
+ mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
+ mxc_fbi_to->ipu_ch_nf_irq = tmp;
+ ovfbi = mxc_fbi_from->ovfbi;
+ mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi;
+ mxc_fbi_to->ovfbi = ovfbi;
+
+ _setup_disp_channel1(fbi_from);
+ retval = _setup_disp_channel2(fbi_from);
+ if (retval)
+ return retval;
+
+ /* switch between dp and dc, disable old idmac, enable new idmac */
+ retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch);
+ ipu_uninit_channel(mxc_fbi_from->ipu, old_ch);
+
+ if (both_on) {
+ _setup_disp_channel1(fbi_to);
+ retval = _setup_disp_channel2(fbi_to);
+ if (retval)
+ return retval;
+ ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
+ }
+
+ return retval;
+}
+
+static int swap_channels(struct fb_info *fbi_from)
+{
+ int i;
+ int swap_mode;
+ ipu_channel_t ch_to;
+ struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
+ struct fb_info *fbi_to = NULL;
+ struct mxcfb_info *mxc_fbi_to;
+
+ /* what's the target channel? */
+ if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC)
+ ch_to = MEM_DC_SYNC;
+ else
+ ch_to = MEM_BG_SYNC;
+
+ fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id);
+ if (!fbi_to)
+ return -1;
+ mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
+
+ ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
+ ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
+ ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from);
+ ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to);
+ ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
+ ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
+ ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, fbi_from);
+ ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, fbi_to);
+
+ if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) {
+ if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
+ swap_mode = BOTH_ON;
+ else
+ swap_mode = SRC_ON;
+ } else {
+ if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
+ swap_mode = TGT_ON;
+ else
+ swap_mode = BOTH_OFF;
+ }
+
+ switch (swap_mode) {
+ case BOTH_ON:
+ /* disable target->switch src->enable target */
+ _swap_channels(fbi_from, fbi_to, true);
+ break;
+ case SRC_ON:
+ /* just switch src */
+ _swap_channels(fbi_from, fbi_to, false);
+ break;
+ case TGT_ON:
+ /* just switch target */
+ _swap_channels(fbi_to, fbi_from, false);
+ break;
+ case BOTH_OFF:
+ /* switch directly, no more need to do */
+ mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch;
+ mxc_fbi_from->ipu_ch = ch_to;
+ i = mxc_fbi_from->ipu_ch_irq;
+ mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
+ mxc_fbi_to->ipu_ch_irq = i;
+ i = mxc_fbi_from->ipu_ch_nf_irq;
+ mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
+ mxc_fbi_to->ipu_ch_nf_irq = i;
+ break;
+ default:
+ break;
+ }
+
+ if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq,
+ mxcfb_irq_handler, IPU_IRQF_ONESHOT,
+ MXCFB_NAME, fbi_from) != 0) {
+ dev_err(fbi_from->device, "Error registering irq %d\n",
+ mxc_fbi_from->ipu_ch_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
+ if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq,
+ mxcfb_irq_handler, IPU_IRQF_ONESHOT,
+ MXCFB_NAME, fbi_to) != 0) {
+ dev_err(fbi_to->device, "Error registering irq %d\n",
+ mxc_fbi_to->ipu_ch_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
+ if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq,
+ mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT,
+ MXCFB_NAME, fbi_from) != 0) {
+ dev_err(fbi_from->device, "Error registering irq %d\n",
+ mxc_fbi_from->ipu_ch_nf_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
+ if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq,
+ mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT,
+ MXCFB_NAME, fbi_to) != 0) {
+ dev_err(fbi_to->device, "Error registering irq %d\n",
+ mxc_fbi_to->ipu_ch_nf_irq);
+ return -EBUSY;
+ }
+ ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
+
+ return 0;
+}
+
+/*
+ * Check framebuffer variable parameters and adjust to valid values.
+ *
+ * @param var framebuffer variable parameters
+ *
+ * @param info framebuffer information pointer
+ */
+static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ u32 vtotal;
+ u32 htotal;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;
+
+
+ if (var->xres == 0 || var->yres == 0)
+ return 0;
+
+ /* fg should not bigger than bg */
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
+ struct fb_info *fbi_tmp;
+ int bg_xres = 0, bg_yres = 0;
+ int16_t pos_x, pos_y;
+
+ bg_xres = var->xres;
+ bg_yres = var->yres;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp) {
+ bg_xres = fbi_tmp->var.xres;
+ bg_yres = fbi_tmp->var.yres;
+ }
+
+ ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y);
+
+ if ((var->xres + pos_x) > bg_xres)
+ var->xres = bg_xres - pos_x;
+ if ((var->yres + pos_y) > bg_yres)
+ var->yres = bg_yres - pos_y;
+ }
+
+ if (var->rotate > IPU_ROTATE_VERT_FLIP)
+ var->rotate = IPU_ROTATE_NONE;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres * 3;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+ (var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) &&
+ (var->bits_per_pixel != 8))
+ var->bits_per_pixel = 16;
+
+ if (check_var_pixfmt(var))
+ /* Fall back to default */
+ bpp_to_var(var->bits_per_pixel, var);
+
+ if (var->pixclock < 1000) {
+ htotal = var->xres + var->right_margin + var->hsync_len +
+ var->left_margin;
+ vtotal = var->yres + var->lower_margin + var->vsync_len +
+ var->upper_margin;
+ var->pixclock = (vtotal * htotal * 6UL) / 100UL;
+ var->pixclock = KHZ2PICOS(var->pixclock);
+ dev_dbg(info->device,
+ "pixclock set for 60Hz refresh = %u ps\n",
+ var->pixclock);
+ }
+
+ var->height = -1;
+ var->width = -1;
+ var->grayscale = 0;
+
+ return 0;
+}
+
+static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+ chan &= 0xffff;
+ chan >>= 16 - bf->length;
+ return chan << bf->offset;
+}
+
+static int mxcfb_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;
+}
+
+/*
+ * Function to handle custom ioctls for MXC framebuffer.
+ *
+ * @param inode inode struct
+ *
+ * @param file file struct
+ *
+ * @param cmd Ioctl command to handle
+ *
+ * @param arg User pointer to command arguments
+ *
+ * @param fbi framebuffer information pointer
+ */
+static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
+{
+ int retval = 0;
+ int __user *argp = (void __user *)arg;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ switch (cmd) {
+ case MXCFB_SET_GBL_ALPHA:
+ {
+ struct mxcfb_gbl_alpha ga;
+
+ if (copy_from_user(&ga, (void *)arg, sizeof(ga))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (ipu_disp_set_global_alpha(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ (bool)ga.enable,
+ ga.alpha)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ if (ga.enable)
+ mxc_fbi->alpha_chan_en = false;
+
+ if (ga.enable)
+ dev_dbg(fbi->device,
+ "Set global alpha of %s to %d\n",
+ fbi->fix.id, ga.alpha);
+ break;
+ }
+ case MXCFB_SET_LOC_ALPHA:
+ {
+ struct mxcfb_loc_alpha la;
+ bool bad_pixfmt =
+ ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi));
+
+ if (copy_from_user(&la, (void *)arg, sizeof(la))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (la.enable && !la.alpha_in_pixel) {
+ struct fb_info *fbi_tmp;
+ ipu_channel_t ipu_ch;
+
+ if (bad_pixfmt) {
+ dev_err(fbi->device, "Bad pixel format "
+ "for graphics plane fb\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ mxc_fbi->alpha_chan_en = true;
+
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC)
+ ipu_ch = MEM_BG_SYNC;
+ else if (mxc_fbi->ipu_ch == MEM_BG_SYNC)
+ ipu_ch = MEM_FG_SYNC;
+ else {
+ retval = -EINVAL;
+ break;
+ }
+
+ fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ ((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false;
+ } else
+ mxc_fbi->alpha_chan_en = false;
+
+ if (ipu_disp_set_global_alpha(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ !(bool)la.enable, 0)) {
+ retval = -EINVAL;
+ break;
+ }
+
+ fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) |
+ FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+ mxcfb_set_par(fbi);
+
+ la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0;
+ la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1;
+ if (copy_to_user((void *)arg, &la, sizeof(la))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ if (la.enable)
+ dev_dbg(fbi->device,
+ "Enable DP local alpha for %s\n",
+ fbi->fix.id);
+ break;
+ }
+ case MXCFB_SET_LOC_ALP_BUF:
+ {
+ unsigned long base;
+ uint32_t ipu_alp_ch_irq;
+
+ if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) ||
+ (mxc_fbi->ipu_ch == MEM_BG_SYNC)) &&
+ (mxc_fbi->alpha_chan_en))) {
+ dev_err(fbi->device,
+ "Should use background or overlay "
+ "framebuffer to set the alpha buffer "
+ "number\n");
+ return -EINVAL;
+ }
+
+ if (get_user(base, argp))
+ return -EFAULT;
+
+ if (base != mxc_fbi->alpha_phy_addr0 &&
+ base != mxc_fbi->alpha_phy_addr1) {
+ dev_err(fbi->device,
+ "Wrong alpha buffer physical address "
+ "%lu\n", base);
+ return -EINVAL;
+ }
+
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC)
+ ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
+ else
+ ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
+
+ retval = wait_for_completion_timeout(
+ &mxc_fbi->alpha_flip_complete, HZ/2);
+ if (retval == 0) {
+ dev_err(fbi->device, "timeout when waiting for alpha flip irq\n");
+ retval = -ETIMEDOUT;
+ break;
+ }
+
+ mxc_fbi->cur_ipu_alpha_buf =
+ !mxc_fbi->cur_ipu_alpha_buf;
+ if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->
+ cur_ipu_alpha_buf,
+ base) == 0) {
+ ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf);
+ ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
+ } else {
+ dev_err(fbi->device,
+ "Error updating %s SDC alpha buf %d "
+ "to address=0x%08lX\n",
+ fbi->fix.id,
+ mxc_fbi->cur_ipu_alpha_buf, base);
+ }
+ break;
+ }
+ case MXCFB_SET_CLR_KEY:
+ {
+ struct mxcfb_color_key key;
+ if (copy_from_user(&key, (void *)arg, sizeof(key))) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ key.enable,
+ key.color_key);
+ dev_dbg(fbi->device, "Set color key to 0x%08X\n",
+ key.color_key);
+ break;
+ }
+ case MXCFB_SET_GAMMA:
+ {
+ struct mxcfb_gamma gamma;
+ if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) {
+ retval = -EFAULT;
+ break;
+ }
+ retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu,
+ mxc_fbi->ipu_ch,
+ gamma.enable,
+ gamma.constk,
+ gamma.slopek);
+ break;
+ }
+ case MXCFB_WAIT_FOR_VSYNC:
+ {
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
+ /* BG should poweron */
+ struct mxcfb_info *bg_mxcfbi = NULL;
+ struct fb_info *fbi_tmp;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
+
+ if (!bg_mxcfbi) {
+ retval = -EINVAL;
+ break;
+ }
+ if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) {
+ retval = -EINVAL;
+ break;
+ }
+ }
+ if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) {
+ retval = -EINVAL;
+ break;
+ }
+
+ init_completion(&mxc_fbi->vsync_complete);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
+ retval = wait_for_completion_interruptible_timeout(
+ &mxc_fbi->vsync_complete, 1 * HZ);
+ if (retval == 0) {
+ dev_err(fbi->device,
+ "MXCFB_WAIT_FOR_VSYNC: timeout %d\n",
+ retval);
+ retval = -ETIME;
+ } else if (retval > 0) {
+ retval = 0;
+ }
+ break;
+ }
+ case FBIO_ALLOC:
+ {
+ int size;
+ struct mxcfb_alloc_list *mem;
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (mem == NULL)
+ return -ENOMEM;
+
+ if (get_user(size, argp))
+ return -EFAULT;
+
+ mem->size = PAGE_ALIGN(size);
+
+ mem->cpu_addr = dma_alloc_coherent(fbi->device, size,
+ &mem->phy_addr,
+ GFP_KERNEL);
+ if (mem->cpu_addr == NULL) {
+ kfree(mem);
+ return -ENOMEM;
+ }
+
+ list_add(&mem->list, &fb_alloc_list);
+
+ dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n",
+ mem->size, mem->phy_addr);
+
+ if (put_user(mem->phy_addr, argp))
+ return -EFAULT;
+
+ break;
+ }
+ case FBIO_FREE:
+ {
+ unsigned long offset;
+ struct mxcfb_alloc_list *mem;
+
+ if (get_user(offset, argp))
+ return -EFAULT;
+
+ retval = -EINVAL;
+ list_for_each_entry(mem, &fb_alloc_list, list) {
+ if (mem->phy_addr == offset) {
+ list_del(&mem->list);
+ dma_free_coherent(fbi->device,
+ mem->size,
+ mem->cpu_addr,
+ mem->phy_addr);
+ kfree(mem);
+ retval = 0;
+ break;
+ }
+ }
+
+ break;
+ }
+ case MXCFB_SET_OVERLAY_POS:
+ {
+ struct mxcfb_pos pos;
+ struct fb_info *bg_fbi = NULL;
+ struct mxcfb_info *bg_mxcfbi = NULL;
+
+ if (mxc_fbi->ipu_ch != MEM_FG_SYNC) {
+ dev_err(fbi->device, "Should use the overlay "
+ "framebuffer to set the position of "
+ "the overlay window\n");
+ retval = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user(&pos, (void *)arg, sizeof(pos))) {
+ retval = -EFAULT;
+ break;
+ }
+
+ bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (bg_fbi)
+ bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par));
+
+ if (bg_fbi == NULL) {
+ dev_err(fbi->device, "Cannot find the "
+ "background framebuffer\n");
+ retval = -ENOENT;
+ break;
+ }
+
+ /* if fb is unblank, check if the pos fit the display */
+ if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) {
+ if (fbi->var.xres + pos.x > bg_fbi->var.xres) {
+ if (bg_fbi->var.xres < fbi->var.xres)
+ pos.x = 0;
+ else
+ pos.x = bg_fbi->var.xres - fbi->var.xres;
+ }
+ if (fbi->var.yres + pos.y > bg_fbi->var.yres) {
+ if (bg_fbi->var.yres < fbi->var.yres)
+ pos.y = 0;
+ else
+ pos.y = bg_fbi->var.yres - fbi->var.yres;
+ }
+ }
+
+ retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ pos.x, pos.y);
+
+ if (copy_to_user((void *)arg, &pos, sizeof(pos))) {
+ retval = -EFAULT;
+ break;
+ }
+ break;
+ }
+ case MXCFB_GET_FB_IPU_CHAN:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_ch, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_DIFMT:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_di_pix_fmt, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_FB_IPU_DI:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->ipu_di, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_GET_FB_BLANK:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (put_user(mxc_fbi->cur_blank, argp))
+ return -EFAULT;
+ break;
+ }
+ case MXCFB_SET_DIFMT:
+ {
+ struct mxcfb_info *mxc_fbi =
+ (struct mxcfb_info *)fbi->par;
+
+ if (get_user(mxc_fbi->ipu_di_pix_fmt, argp))
+ return -EFAULT;
+
+ break;
+ }
+ case MXCFB_CSC_UPDATE:
+ {
+ struct mxcfb_csc_matrix csc;
+
+ if (copy_from_user(&csc, (void *) arg, sizeof(csc)))
+ return -EFAULT;
+
+ if ((mxc_fbi->ipu_ch != MEM_FG_SYNC) &&
+ (mxc_fbi->ipu_ch != MEM_BG_SYNC) &&
+ (mxc_fbi->ipu_ch != MEM_BG_ASYNC0))
+ return -EFAULT;
+ ipu_set_csc_coefficients(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ csc.param);
+ break;
+ }
+ default:
+ retval = -EINVAL;
+ }
+ return retval;
+}
+
+/*
+ * mxcfb_blank():
+ * Blank the display.
+ */
+static int mxcfb_blank(int blank, struct fb_info *info)
+{
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;
+ int ret = 0;
+
+ dev_dbg(info->device, "blank = %d\n", blank);
+
+ if (mxc_fbi->cur_blank == blank)
+ return 0;
+
+ mxc_fbi->next_blank = blank;
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->disable)
+ mxc_fbi->dispdrv->drv->disable(mxc_fbi->dispdrv, info);
+ ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true);
+ if (mxc_fbi->ipu_di >= 0)
+ ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di);
+ ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
+ break;
+ case FB_BLANK_UNBLANK:
+ info->var.activate = (info->var.activate & ~FB_ACTIVATE_MASK) |
+ FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+ ret = mxcfb_set_par(info);
+ break;
+ }
+ if (!ret)
+ mxc_fbi->cur_blank = blank;
+ return ret;
+}
+
+/*
+ * Pan or Wrap the Display
+ *
+ * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
+ *
+ * @param var Variable screen buffer information
+ * @param info Framebuffer information pointer
+ */
+static int
+mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par,
+ *mxc_graphic_fbi = NULL;
+ u_int y_bottom;
+ unsigned int fr_xoff, fr_yoff, fr_w, fr_h;
+ unsigned long base, active_alpha_phy_addr = 0;
+ bool loc_alpha_en = false;
+ int fb_stride;
+ int i;
+ int ret;
+
+ /* no pan display during fb blank */
+ if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
+ struct mxcfb_info *bg_mxcfbi = NULL;
+ struct fb_info *fbi_tmp;
+
+ fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
+ if (fbi_tmp)
+ bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
+ if (!bg_mxcfbi)
+ return -EINVAL;
+ if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK)
+ return -EINVAL;
+ }
+ if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK)
+ return -EINVAL;
+
+ y_bottom = var->yoffset;
+
+ if (y_bottom > info->var.yres_virtual)
+ return -EINVAL;
+
+ switch (fbi_to_pixfmt(info)) {
+ case IPU_PIX_FMT_YUV420P2:
+ case IPU_PIX_FMT_YVU420P:
+ case IPU_PIX_FMT_NV12:
+ case IPU_PIX_FMT_YUV422P:
+ case IPU_PIX_FMT_YVU422P:
+ case IPU_PIX_FMT_YUV420P:
+ case IPU_PIX_FMT_YUV444P:
+ fb_stride = info->var.xres_virtual;
+ break;
+ default:
+ fb_stride = info->fix.line_length;
+ }
+
+ base = info->fix.smem_start;
+ fr_xoff = var->xoffset;
+ fr_w = info->var.xres_virtual;
+ if (!(var->vmode & FB_VMODE_YWRAP)) {
+ dev_dbg(info->device, "Y wrap disabled\n");
+ fr_yoff = var->yoffset % info->var.yres;
+ fr_h = info->var.yres;
+ base += info->fix.line_length * info->var.yres *
+ (var->yoffset / info->var.yres);
+ } else {
+ dev_dbg(info->device, "Y wrap enabled\n");
+ fr_yoff = var->yoffset;
+ fr_h = info->var.yres_virtual;
+ }
+ base += fr_yoff * fb_stride + fr_xoff;
+
+ /* Check if DP local alpha is enabled and find the graphic fb */
+ if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) {
+ for (i = 0; i < num_registered_fb; i++) {
+ char bg_id[] = "DISP3 BG";
+ char fg_id[] = "DISP3 FG";
+ char *idstr = registered_fb[i]->fix.id;
+ bg_id[4] += mxc_fbi->ipu_id;
+ fg_id[4] += mxc_fbi->ipu_id;
+ if ((strcmp(idstr, bg_id) == 0 ||
+ strcmp(idstr, fg_id) == 0) &&
+ ((struct mxcfb_info *)
+ (registered_fb[i]->par))->alpha_chan_en) {
+ loc_alpha_en = true;
+ mxc_graphic_fbi = (struct mxcfb_info *)
+ (registered_fb[i]->par);
+ active_alpha_phy_addr =
+ mxc_fbi->cur_ipu_alpha_buf ?
+ mxc_graphic_fbi->alpha_phy_addr1 :
+ mxc_graphic_fbi->alpha_phy_addr0;
+ dev_dbg(info->device, "Updating SDC alpha "
+ "buf %d address=0x%08lX\n",
+ !mxc_fbi->cur_ipu_alpha_buf,
+ active_alpha_phy_addr);
+ break;
+ }
+ }
+ }
+
+ ret = wait_for_completion_timeout(&mxc_fbi->flip_complete, HZ/2);
+ if (ret == 0) {
+ dev_err(info->device, "timeout when waiting for flip irq\n");
+ return -ETIMEDOUT;
+ }
+
+ ++mxc_fbi->cur_ipu_buf;
+ mxc_fbi->cur_ipu_buf %= 3;
+ mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
+
+ dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n",
+ info->fix.id, mxc_fbi->cur_ipu_buf, base);
+
+ if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ mxc_fbi->cur_ipu_buf, base) == 0) {
+ /* Update the DP local alpha buffer only for graphic plane */
+ if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi &&
+ ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf,
+ active_alpha_phy_addr) == 0) {
+ ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
+ IPU_ALPHA_IN_BUFFER,
+ mxc_fbi->cur_ipu_alpha_buf);
+ }
+
+ /* update u/v offset */
+ ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER,
+ fbi_to_pixfmt(info),
+ fr_w,
+ fr_h,
+ fr_w,
+ 0, 0,
+ fr_yoff,
+ fr_xoff);
+
+ ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
+ mxc_fbi->cur_ipu_buf);
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ } else {
+ dev_err(info->device,
+ "Error updating SDC buf %d to address=0x%08lX, "
+ "current buf %d, buf0 ready %d, buf1 ready %d, "
+ "buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base,
+ ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 0),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 1),
+ ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
+ IPU_INPUT_BUFFER, 2));
+ ++mxc_fbi->cur_ipu_buf;
+ mxc_fbi->cur_ipu_buf %= 3;
+ ++mxc_fbi->cur_ipu_buf;
+ mxc_fbi->cur_ipu_buf %= 3;
+ mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
+ ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
+ return -EBUSY;
+ }
+
+ dev_dbg(info->device, "Update complete\n");
+
+ info->var.yoffset = var->yoffset;
+
+ return 0;
+}
+
+/*
+ * Function to handle custom mmap for MXC framebuffer.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @param vma Pointer to vm_area_struct
+ */
+static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
+{
+ bool found = false;
+ u32 len;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ struct mxcfb_alloc_list *mem;
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ if (offset < fbi->fix.smem_len) {
+ /* mapping framebuffer memory */
+ len = fbi->fix.smem_len - offset;
+ vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
+ } else if ((vma->vm_pgoff ==
+ (mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) ||
+ (vma->vm_pgoff ==
+ (mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) {
+ len = mxc_fbi->alpha_mem_len;
+ } else {
+ list_for_each_entry(mem, &fb_alloc_list, list) {
+ if (offset == mem->phy_addr) {
+ found = true;
+ len = mem->size;
+ break;
+ }
+ }
+ if (!found)
+ return -EINVAL;
+ }
+
+ len = PAGE_ALIGN(len);
+ if (vma->vm_end - vma->vm_start > len)
+ return -EINVAL;
+
+ /* make buffers bufferable */
+ vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+
+ vma->vm_flags |= VM_IO;
+
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
+ return -ENOBUFS;
+ }
+
+ return 0;
+}
+
+/*!
+ * This structure contains the pointers to the control functions that are
+ * invoked by the core framebuffer driver to perform operations like
+ * blitting, rectangle filling, copy regions and cursor definition.
+ */
+static struct fb_ops mxcfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_set_par = mxcfb_set_par,
+ .fb_check_var = mxcfb_check_var,
+ .fb_setcolreg = mxcfb_setcolreg,
+ .fb_pan_display = mxcfb_pan_display,
+ .fb_ioctl = mxcfb_ioctl,
+ .fb_mmap = mxcfb_mmap,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_blank = mxcfb_blank,
+};
+
+static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id)
+{
+ struct fb_info *fbi = dev_id;
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ complete(&mxc_fbi->flip_complete);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id)
+{
+ struct fb_info *fbi = dev_id;
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ complete(&mxc_fbi->vsync_complete);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id)
+{
+ struct fb_info *fbi = dev_id;
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ complete(&mxc_fbi->alpha_flip_complete);
+ return IRQ_HANDLED;
+}
+
+/*
+ * Suspends the framebuffer and blanks the screen. Power management support
+ */
+static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct fb_info *fbi = platform_get_drvdata(pdev);
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+ int saved_blank;
+#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY
+ void *fbmem;
+#endif
+
+ if (mxc_fbi->ovfbi) {
+ struct mxcfb_info *mxc_fbi_fg =
+ (struct mxcfb_info *)mxc_fbi->ovfbi->par;
+
+ console_lock();
+ fb_set_suspend(mxc_fbi->ovfbi, 1);
+ saved_blank = mxc_fbi_fg->cur_blank;
+ mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
+ mxc_fbi_fg->next_blank = saved_blank;
+ console_unlock();
+ }
+
+ console_lock();
+ fb_set_suspend(fbi, 1);
+ saved_blank = mxc_fbi->cur_blank;
+ mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
+ mxc_fbi->next_blank = saved_blank;
+ console_unlock();
+
+ return 0;
+}
+
+/*
+ * Resumes the framebuffer and unblanks the screen. Power management support
+ */
+static int mxcfb_resume(struct platform_device *pdev)
+{
+ struct fb_info *fbi = platform_get_drvdata(pdev);
+ struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
+
+ console_lock();
+ mxcfb_blank(mxc_fbi->next_blank, fbi);
+ fb_set_suspend(fbi, 0);
+ console_unlock();
+
+ if (mxc_fbi->ovfbi) {
+ struct mxcfb_info *mxc_fbi_fg =
+ (struct mxcfb_info *)mxc_fbi->ovfbi->par;
+ console_lock();
+ mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi);
+ fb_set_suspend(mxc_fbi->ovfbi, 0);
+ console_unlock();
+ }
+
+ return 0;
+}
+
+/*
+ * Main framebuffer functions
+ */
+
+/*!
+ * Allocates the DRAM memory for the frame buffer. This buffer is remapped
+ * into a non-cached, non-buffered, memory region to allow palette and pixel
+ * writes to occur without flushing the cache. Once this area is remapped,
+ * all virtual memory access to the video memory should occur at the new region.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @return Error code indicating success or failure
+ */
+static int mxcfb_map_video_memory(struct fb_info *fbi)
+{
+ if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length)
+ fbi->fix.smem_len = fbi->var.yres_virtual *
+ fbi->fix.line_length;
+
+ fbi->screen_base = dma_alloc_writecombine(fbi->device,
+ fbi->fix.smem_len,
+ (dma_addr_t *)&fbi->fix.smem_start,
+ GFP_DMA | GFP_KERNEL);
+ if (fbi->screen_base == 0) {
+ dev_err(fbi->device, "Unable to allocate framebuffer memory\n");
+ fbi->fix.smem_len = 0;
+ fbi->fix.smem_start = 0;
+ return -EBUSY;
+ }
+
+ dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n",
+ (uint32_t) fbi->fix.smem_start, fbi->fix.smem_len);
+
+ fbi->screen_size = fbi->fix.smem_len;
+
+ /* Clear the screen */
+ memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+ return 0;
+}
+
+/*!
+ * De-allocates the DRAM memory for the frame buffer.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @return Error code indicating success or failure
+ */
+static int mxcfb_unmap_video_memory(struct fb_info *fbi)
+{
+ dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+ fbi->screen_base, fbi->fix.smem_start);
+ fbi->screen_base = 0;
+ fbi->fix.smem_start = 0;
+ fbi->fix.smem_len = 0;
+ return 0;
+}
+
+/*!
+ * Initializes the framebuffer information pointer. After allocating
+ * sufficient memory for the framebuffer structure, the fields are
+ * filled with custom information passed in from the configurable
+ * structures. This includes information such as bits per pixel,
+ * color maps, screen width/height and RGBA offsets.
+ *
+ * @return Framebuffer structure initialized with our information
+ */
+static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops)
+{
+ struct fb_info *fbi;
+ struct mxcfb_info *mxcfbi;
+
+ /*
+ * Allocate sufficient memory for the fb structure
+ */
+ fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev);
+ if (!fbi)
+ return NULL;
+
+ mxcfbi = (struct mxcfb_info *)fbi->par;
+
+ fbi->var.activate = FB_ACTIVATE_NOW;
+
+ fbi->fbops = ops;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->pseudo_palette = mxcfbi->pseudo_palette;
+
+ /*
+ * Allocate colormap
+ */
+ fb_alloc_cmap(&fbi->cmap, 16, 0);
+
+ return fbi;
+}
+
+static ssize_t show_disp_chan(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *info = dev_get_drvdata(dev);
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
+
+ if (mxcfbi->ipu_ch == MEM_BG_SYNC)
+ return sprintf(buf, "2-layer-fb-bg\n");
+ else if (mxcfbi->ipu_ch == MEM_FG_SYNC)
+ return sprintf(buf, "2-layer-fb-fg\n");
+ else if (mxcfbi->ipu_ch == MEM_DC_SYNC)
+ return sprintf(buf, "1-layer-fb\n");
+ else
+ return sprintf(buf, "err: no display chan\n");
+}
+
+static ssize_t swap_disp_chan(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fb_info *info = dev_get_drvdata(dev);
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
+ struct mxcfb_info *fg_mxcfbi = NULL;
+
+ console_lock();
+ /* swap only happen between DP-BG and DC, while DP-FG disable */
+ if (((mxcfbi->ipu_ch == MEM_BG_SYNC) &&
+ (strstr(buf, "1-layer-fb") != NULL)) ||
+ ((mxcfbi->ipu_ch == MEM_DC_SYNC) &&
+ (strstr(buf, "2-layer-fb-bg") != NULL))) {
+ struct fb_info *fbi_fg;
+
+ fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id);
+ if (fbi_fg)
+ fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par;
+
+ if (!fg_mxcfbi ||
+ fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) {
+ dev_err(dev,
+ "Can not switch while fb2(fb-fg) is on.\n");
+ console_unlock();
+ return count;
+ }
+
+ if (swap_channels(info) < 0)
+ dev_err(dev, "Swap display channel failed.\n");
+ }
+
+ console_unlock();
+ return count;
+}
+static DEVICE_ATTR(fsl_disp_property, S_IWUSR | S_IRUGO,
+ show_disp_chan, swap_disp_chan);
+
+static ssize_t show_disp_dev(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fb_info *info = dev_get_drvdata(dev);
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
+
+ if (mxcfbi->ipu_ch == MEM_FG_SYNC)
+ return sprintf(buf, "overlay\n");
+ else
+ return sprintf(buf, "%s\n", mxcfbi->dispdrv->drv->name);
+}
+static DEVICE_ATTR(fsl_disp_dev_property, S_IRUGO, show_disp_dev, NULL);
+
+static int mxcfb_get_crtc(struct device *dev, struct mxcfb_info *mxcfbi,
+ enum crtc crtc)
+{
+ int i = 0;
+
+ for (; i < ARRAY_SIZE(ipu_di_crtc_maps); i++)
+ if (ipu_di_crtc_maps[i].crtc == crtc) {
+ mxcfbi->ipu_id = ipu_di_crtc_maps[i].ipu_id;
+ mxcfbi->ipu_di = ipu_di_crtc_maps[i].ipu_di;
+ return 0;
+ }
+
+ dev_err(dev, "failed to get valid crtc\n");
+ return -EINVAL;
+}
+
+static int mxcfb_dispdrv_init(struct platform_device *pdev,
+ struct fb_info *fbi)
+{
+ struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+ struct mxc_dispdrv_setting setting;
+ char disp_dev[32], *default_dev = "lcd";
+ int ret = 0;
+
+ setting.if_fmt = plat_data->interface_pix_fmt;
+ setting.dft_mode_str = plat_data->mode_str;
+ setting.default_bpp = plat_data->default_bpp;
+ if (!setting.default_bpp)
+ setting.default_bpp = 16;
+ setting.fbi = fbi;
+ if (!strlen(plat_data->disp_dev)) {
+ memcpy(disp_dev, default_dev, strlen(default_dev));
+ disp_dev[strlen(default_dev)] = '\0';
+ } else {
+ memcpy(disp_dev, plat_data->disp_dev,
+ strlen(plat_data->disp_dev));
+ disp_dev[strlen(plat_data->disp_dev)] = '\0';
+ }
+
+ mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
+ if (IS_ERR(mxcfbi->dispdrv)) {
+ ret = PTR_ERR(mxcfbi->dispdrv);
+ dev_err(&pdev->dev, "NO mxc display driver found!\n");
+ return ret;
+ } else {
+ /* fix-up */
+ mxcfbi->ipu_di_pix_fmt = setting.if_fmt;
+ mxcfbi->default_bpp = setting.default_bpp;
+
+ ret = mxcfb_get_crtc(&pdev->dev, mxcfbi, setting.crtc);
+ if (ret)
+ return ret;
+
+ dev_dbg(&pdev->dev, "di_pixfmt:0x%x, bpp:0x%x, di:%d, ipu:%d\n",
+ setting.if_fmt, setting.default_bpp,
+ mxcfbi->ipu_di, mxcfbi->ipu_id);
+ }
+
+ dev_info(&pdev->dev, "registered mxc display driver %s\n", disp_dev);
+
+ return ret;
+}
+
+/*
+ * Parse user specified options (`video=trident:')
+ * example:
+ * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel
+ * video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,fbpix=RGB565
+ */
+static int mxcfb_option_setup(struct platform_device *pdev, struct fb_info *fbi)
+{
+ struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data;
+ char *options, *opt, *fb_mode_str = NULL;
+ char name[] = "mxcfb0";
+ uint32_t fb_pix_fmt = 0;
+
+ name[5] += pdev->id;
+ if (fb_get_options(name, &options)) {
+ dev_err(&pdev->dev, "Can't get fb option for %s!\n", name);
+ return -ENODEV;
+ }
+
+ if (!options || !*options)
+ return 0;
+
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (!*opt)
+ continue;
+
+ if (!strncmp(opt, "dev=", 4)) {
+ memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4);
+ pdata->disp_dev[strlen(opt) - 4] = '\0';
+ } else if (!strncmp(opt, "if=", 3)) {
+ if (!strncmp(opt+3, "RGB24", 5))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24;
+ else if (!strncmp(opt+3, "BGR24", 5))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24;
+ else if (!strncmp(opt+3, "GBR24", 5))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24;
+ else if (!strncmp(opt+3, "RGB565", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565;
+ else if (!strncmp(opt+3, "RGB666", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666;
+ else if (!strncmp(opt+3, "YUV444", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444;
+ else if (!strncmp(opt+3, "LVDS666", 7))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666;
+ else if (!strncmp(opt+3, "YUYV16", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV;
+ else if (!strncmp(opt+3, "UYVY16", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY;
+ else if (!strncmp(opt+3, "YVYU16", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU;
+ else if (!strncmp(opt+3, "VYUY16", 6))
+ pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY;
+ } else if (!strncmp(opt, "fbpix=", 6)) {
+ if (!strncmp(opt+6, "RGB24", 5))
+ fb_pix_fmt = IPU_PIX_FMT_RGB24;
+ else if (!strncmp(opt+6, "BGR24", 5))
+ fb_pix_fmt = IPU_PIX_FMT_BGR24;
+ else if (!strncmp(opt+6, "RGB32", 5))
+ fb_pix_fmt = IPU_PIX_FMT_RGB32;
+ else if (!strncmp(opt+6, "BGR32", 5))
+ fb_pix_fmt = IPU_PIX_FMT_BGR32;
+ else if (!strncmp(opt+6, "ABGR32", 6))
+ fb_pix_fmt = IPU_PIX_FMT_ABGR32;
+ else if (!strncmp(opt+6, "RGB565", 6))
+ fb_pix_fmt = IPU_PIX_FMT_RGB565;
+
+ if (fb_pix_fmt) {
+ pixfmt_to_var(fb_pix_fmt, &fbi->var);
+ pdata->default_bpp =
+ fbi->var.bits_per_pixel;
+ }
+ } else if (!strncmp(opt, "int_clk", 7)) {
+ pdata->int_clk = true;
+ continue;
+ } else if (!strncmp(opt, "bpp=", 4)) {
+ /* bpp setting cannot overwirte fbpix setting */
+ if (fb_pix_fmt)
+ continue;
+
+ pdata->default_bpp =
+ simple_strtoul(opt + 4, NULL, 0);
+
+ fb_pix_fmt = bpp_to_pixfmt(pdata->default_bpp);
+ if (fb_pix_fmt)
+ pixfmt_to_var(fb_pix_fmt, &fbi->var);
+ } else
+ fb_mode_str = opt;
+ }
+
+ if (fb_mode_str)
+ pdata->mode_str = fb_mode_str;
+
+ return 0;
+}
+
+static int mxcfb_register(struct fb_info *fbi)
+{
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+ struct fb_videomode m;
+ int ret = 0;
+ char bg0_id[] = "DISP3 BG";
+ char bg1_id[] = "DISP3 BG - DI1";
+ char fg_id[] = "DISP3 FG";
+
+ if (mxcfbi->ipu_di == 0) {
+ bg0_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, bg0_id);
+ } else if (mxcfbi->ipu_di == 1) {
+ bg1_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, bg1_id);
+ } else { /* Overlay */
+ fg_id[4] += mxcfbi->ipu_id;
+ strcpy(fbi->fix.id, fg_id);
+ }
+
+ mxcfb_check_var(&fbi->var, fbi);
+
+ mxcfb_set_fix(fbi);
+
+ /* Added first mode to fbi modelist. */
+ if (!fbi->modelist.next || !fbi->modelist.prev)
+ INIT_LIST_HEAD(&fbi->modelist);
+ fb_var_to_videomode(&m, &fbi->var);
+ fb_add_videomode(&m, &fbi->modelist);
+
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq,
+ mxcfb_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering EOF irq handler.\n");
+ ret = -EBUSY;
+ goto err0;
+ }
+ ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq);
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq,
+ mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering NFACK irq handler.\n");
+ ret = -EBUSY;
+ goto err1;
+ }
+ ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq);
+
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq,
+ mxcfb_alpha_irq_handler, IPU_IRQF_ONESHOT,
+ MXCFB_NAME, fbi) != 0) {
+ dev_err(fbi->device, "Error registering alpha irq "
+ "handler.\n");
+ ret = -EBUSY;
+ goto err2;
+ }
+
+ if (!mxcfbi->late_init) {
+ fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ fbi->flags |= FBINFO_MISC_USEREVENT;
+ ret = fb_set_var(fbi, &fbi->var);
+ fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ if (ret < 0) {
+ dev_err(fbi->device, "Error fb_set_var ret:%d\n", ret);
+ goto err3;
+ }
+
+ if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
+ console_lock();
+ ret = fb_blank(fbi, FB_BLANK_UNBLANK);
+ console_unlock();
+ if (ret < 0) {
+ dev_err(fbi->device,
+ "Error fb_blank ret:%d\n", ret);
+ goto err4;
+ }
+ }
+ } else {
+ /*
+ * Setup the channel again though bootloader
+ * has done this, then set_par() can stop the
+ * channel neatly and re-initialize it .
+ */
+ if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
+ console_lock();
+ _setup_disp_channel1(fbi);
+ ipu_enable_channel(mxcfbi->ipu, mxcfbi->ipu_ch);
+ console_unlock();
+ }
+ }
+
+
+ ret = register_framebuffer(fbi);
+ if (ret < 0)
+ goto err5;
+
+ return ret;
+err5:
+ if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
+ console_lock();
+ if (!mxcfbi->late_init)
+ fb_blank(fbi, FB_BLANK_POWERDOWN);
+ else {
+ ipu_disable_channel(mxcfbi->ipu, mxcfbi->ipu_ch,
+ true);
+ ipu_uninit_channel(mxcfbi->ipu, mxcfbi->ipu_ch);
+ }
+ console_unlock();
+ }
+err4:
+err3:
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
+err2:
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
+err1:
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
+err0:
+ return ret;
+}
+
+static void mxcfb_unregister(struct fb_info *fbi)
+{
+ struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
+
+ if (mxcfbi->ipu_alp_ch_irq != -1)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
+ if (mxcfbi->ipu_ch_irq)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
+ if (mxcfbi->ipu_ch_nf_irq)
+ ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
+
+ unregister_framebuffer(fbi);
+}
+
+static int mxcfb_setup_overlay(struct platform_device *pdev,
+ struct fb_info *fbi_bg, struct resource *res)
+{
+ struct fb_info *ovfbi;
+ struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
+ struct mxcfb_info *mxcfbi_fg;
+ int ret = 0;
+
+ ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
+ if (!ovfbi) {
+ ret = -ENOMEM;
+ goto init_ovfbinfo_failed;
+ }
+ mxcfbi_fg = (struct mxcfb_info *)ovfbi->par;
+
+ mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id);
+ if (IS_ERR(mxcfbi_fg->ipu)) {
+ ret = -ENODEV;
+ goto get_ipu_failed;
+ }
+ mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id;
+ mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF;
+ mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK;
+ mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
+ mxcfbi_fg->ipu_ch = MEM_FG_SYNC;
+ mxcfbi_fg->ipu_di = -1;
+ mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt;
+ mxcfbi_fg->overlay = true;
+ mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN;
+
+ /* Need dummy values until real panel is configured */
+ ovfbi->var.xres = 240;
+ ovfbi->var.yres = 320;
+
+ if (res && res->start && res->end) {
+ ovfbi->fix.smem_len = res->end - res->start + 1;
+ ovfbi->fix.smem_start = res->start;
+ ovfbi->screen_base = ioremap(
+ ovfbi->fix.smem_start,
+ ovfbi->fix.smem_len);
+ }
+
+ ret = mxcfb_register(ovfbi);
+ if (ret < 0)
+ goto register_ov_failed;
+
+ mxcfbi_bg->ovfbi = ovfbi;
+
+ return ret;
+
+register_ov_failed:
+get_ipu_failed:
+ fb_dealloc_cmap(&ovfbi->cmap);
+ framebuffer_release(ovfbi);
+init_ovfbinfo_failed:
+ return ret;
+}
+
+static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg)
+{
+ struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
+ struct fb_info *ovfbi = mxcfbi_bg->ovfbi;
+
+ mxcfb_unregister(ovfbi);
+
+ if (&ovfbi->cmap)
+ fb_dealloc_cmap(&ovfbi->cmap);
+ framebuffer_release(ovfbi);
+}
+
+static bool ipu_usage[2][2];
+static int ipu_test_set_usage(int ipu, int di)
+{
+ if (ipu_usage[ipu][di])
+ return -EBUSY;
+ else
+ ipu_usage[ipu][di] = true;
+ return 0;
+}
+
+static void ipu_clear_usage(int ipu, int di)
+{
+ ipu_usage[ipu][di] = false;
+}
+
+static int mxcfb_get_of_property(struct platform_device *pdev,
+ struct ipuv3_fb_platform_data *plat_data)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const char *disp_dev;
+ const char *mode_str;
+ const char *pixfmt;
+ int err;
+ int len;
+ u32 bpp, int_clk;
+ u32 late_init;
+
+ err = of_property_read_string(np, "disp_dev", &disp_dev);
+ if (err < 0) {
+ dev_dbg(&pdev->dev, "get of property disp_dev fail\n");
+ return err;
+ }
+ err = of_property_read_string(np, "mode_str", &mode_str);
+ if (err < 0)
+ dev_dbg(&pdev->dev, "get of property mode_str fail\n");
+ err = of_property_read_string(np, "interface_pix_fmt", &pixfmt);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property pix fmt fail\n");
+ return err;
+ }
+ err = of_property_read_u32(np, "default_bpp", &bpp);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property bpp fail\n");
+ return err;
+ }
+ err = of_property_read_u32(np, "int_clk", &int_clk);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property int_clk fail\n");
+ return err;
+ }
+ err = of_property_read_u32(np, "late_init", &late_init);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property late_init fail\n");
+ return err;
+ }
+
+ if (!strncmp(pixfmt, "RGB24", 5))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB24;
+ else if (!strncmp(pixfmt, "BGR24", 5))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_BGR24;
+ else if (!strncmp(pixfmt, "GBR24", 5))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_GBR24;
+ else if (!strncmp(pixfmt, "RGB565", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB565;
+ else if (!strncmp(pixfmt, "RGB666", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB666;
+ else if (!strncmp(pixfmt, "YUV444", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_YUV444;
+ else if (!strncmp(pixfmt, "LVDS666", 7))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_LVDS666;
+ else if (!strncmp(pixfmt, "YUYV16", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_YUYV;
+ else if (!strncmp(pixfmt, "UYVY16", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_UYVY;
+ else if (!strncmp(pixfmt, "YVYU16", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_YVYU;
+ else if (!strncmp(pixfmt, "VYUY16", 6))
+ plat_data->interface_pix_fmt = IPU_PIX_FMT_VYUY;
+ else {
+ dev_err(&pdev->dev, "err interface_pix_fmt!\n");
+ return -ENOENT;
+ }
+
+ len = min(sizeof(plat_data->disp_dev) - 1, strlen(disp_dev));
+ memcpy(plat_data->disp_dev, disp_dev, len);
+ plat_data->disp_dev[len] = '\0';
+ plat_data->mode_str = (char *)mode_str;
+ plat_data->default_bpp = bpp;
+ plat_data->int_clk = (bool)int_clk;
+ plat_data->late_init = (bool)late_init;
+ return err;
+}
+
+/*!
+ * Probe routine for the framebuffer driver. It is called during the
+ * driver binding process. The following functions are performed in
+ * this routine: Framebuffer initialization, Memory allocation and
+ * mapping, Framebuffer registration, IPU initialization.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mxcfb_probe(struct platform_device *pdev)
+{
+ struct ipuv3_fb_platform_data *plat_data;
+ struct fb_info *fbi;
+ struct mxcfb_info *mxcfbi;
+ struct resource *res;
+ int ret = 0;
+
+ dev_dbg(&pdev->dev, "%s enter\n", __func__);
+ pdev->id = of_alias_get_id(pdev->dev.of_node, "mxcfb");
+ if (pdev->id < 0) {
+ dev_err(&pdev->dev, "can not get alias id\n");
+ return pdev->id;
+ }
+
+ plat_data = devm_kzalloc(&pdev->dev, sizeof(struct
+ ipuv3_fb_platform_data), GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+ pdev->dev.platform_data = plat_data;
+
+ ret = mxcfb_get_of_property(pdev, plat_data);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "get mxcfb of property fail\n");
+ return ret;
+ }
+
+ /* Initialize FB structures */
+ fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
+ if (!fbi) {
+ ret = -ENOMEM;
+ goto init_fbinfo_failed;
+ }
+
+ ret = mxcfb_option_setup(pdev, fbi);
+ if (ret)
+ goto get_fb_option_failed;
+
+ mxcfbi = (struct mxcfb_info *)fbi->par;
+ mxcfbi->ipu_int_clk = plat_data->int_clk;
+ mxcfbi->late_init = plat_data->late_init;
+ mxcfbi->first_set_par = true;
+ ret = mxcfb_dispdrv_init(pdev, fbi);
+ if (ret < 0)
+ goto init_dispdrv_failed;
+
+ ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "ipu%d-di%d already in use\n",
+ mxcfbi->ipu_id, mxcfbi->ipu_di);
+ goto ipu_in_busy;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res && res->start && res->end) {
+ fbi->fix.smem_len = res->end - res->start + 1;
+ fbi->fix.smem_start = res->start;
+ fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len);
+ /* Do not clear the fb content drawn in bootloader. */
+ if (!mxcfbi->late_init)
+ memset(fbi->screen_base, 0, fbi->fix.smem_len);
+ }
+
+ mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id);
+ if (IS_ERR(mxcfbi->ipu)) {
+ ret = -ENODEV;
+ goto get_ipu_failed;
+ }
+
+ /* first user uses DP with alpha feature */
+ if (!g_dp_in_use[mxcfbi->ipu_id]) {
+ mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF;
+ mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK;
+ mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
+ mxcfbi->ipu_ch = MEM_BG_SYNC;
+ /* Unblank the primary fb only by default */
+ if (pdev->id == 0)
+ mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK;
+ else
+ mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
+
+ ret = mxcfb_register(fbi);
+ if (ret < 0)
+ goto mxcfb_register_failed;
+
+ ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch,
+ true, 0x80);
+ ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ ret = mxcfb_setup_overlay(pdev, fbi, res);
+
+ if (ret < 0) {
+ mxcfb_unregister(fbi);
+ goto mxcfb_setupoverlay_failed;
+ }
+
+ g_dp_in_use[mxcfbi->ipu_id] = true;
+
+ ret = device_create_file(mxcfbi->ovfbi->dev,
+ &dev_attr_fsl_disp_property);
+ if (ret)
+ dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
+ "file for disp property\n",
+ ret);
+
+ ret = device_create_file(mxcfbi->ovfbi->dev,
+ &dev_attr_fsl_disp_dev_property);
+ if (ret)
+ dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
+ "file for disp device "
+ "propety\n", ret);
+ } else {
+ mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
+ mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK;
+ mxcfbi->ipu_alp_ch_irq = -1;
+ mxcfbi->ipu_ch = MEM_DC_SYNC;
+ mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
+
+ ret = mxcfb_register(fbi);
+ if (ret < 0)
+ goto mxcfb_register_failed;
+ }
+
+ platform_set_drvdata(pdev, fbi);
+
+ ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property);
+ if (ret)
+ dev_err(&pdev->dev, "Error %d on creating file for disp "
+ "property\n", ret);
+
+ ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
+ if (ret)
+ dev_err(&pdev->dev, "Error %d on creating file for disp "
+ " device propety\n", ret);
+
+ return 0;
+
+mxcfb_setupoverlay_failed:
+mxcfb_register_failed:
+get_ipu_failed:
+ ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
+ipu_in_busy:
+init_dispdrv_failed:
+ fb_dealloc_cmap(&fbi->cmap);
+ framebuffer_release(fbi);
+get_fb_option_failed:
+init_fbinfo_failed:
+ return ret;
+}
+
+static int mxcfb_remove(struct platform_device *pdev)
+{
+ struct fb_info *fbi = platform_get_drvdata(pdev);
+ struct mxcfb_info *mxc_fbi = fbi->par;
+
+ if (!fbi)
+ return 0;
+
+ device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
+ device_remove_file(fbi->dev, &dev_attr_fsl_disp_property);
+ mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
+ mxcfb_unregister(fbi);
+ mxcfb_unmap_video_memory(fbi);
+
+ if (mxc_fbi->ovfbi) {
+ device_remove_file(mxc_fbi->ovfbi->dev,
+ &dev_attr_fsl_disp_dev_property);
+ device_remove_file(mxc_fbi->ovfbi->dev,
+ &dev_attr_fsl_disp_property);
+ mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
+ mxcfb_unsetup_overlay(fbi);
+ mxcfb_unmap_video_memory(mxc_fbi->ovfbi);
+ }
+
+ ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di);
+ if (&fbi->cmap)
+ fb_dealloc_cmap(&fbi->cmap);
+ framebuffer_release(fbi);
+ return 0;
+}
+
+static const struct of_device_id imx_mxcfb_dt_ids[] = {
+ { .compatible = "fsl,mxc_sdc_fb"},
+ { /* sentinel */ }
+};
+
+/*!
+ * This structure contains pointers to the power management callback functions.
+ */
+static struct platform_driver mxcfb_driver = {
+ .driver = {
+ .name = MXCFB_NAME,
+ .of_match_table = imx_mxcfb_dt_ids,
+ },
+ .probe = mxcfb_probe,
+ .remove = mxcfb_remove,
+ .suspend = mxcfb_suspend,
+ .resume = mxcfb_resume,
+};
+
+/*!
+ * Main entry function for the framebuffer. The function registers the power
+ * management callback functions with the kernel and also registers the MXCFB
+ * callback functions with the core Linux framebuffer driver \b fbmem.c
+ *
+ * @return Error code indicating success or failure
+ */
+int __init mxcfb_init(void)
+{
+ return platform_driver_register(&mxcfb_driver);
+}
+
+void mxcfb_exit(void)
+{
+ platform_driver_unregister(&mxcfb_driver);
+}
+
+module_init(mxcfb_init);
+module_exit(mxcfb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC framebuffer driver");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("fb");
diff --git a/drivers/video/mxc/mxc_lcdif.c b/drivers/video/mxc/mxc_lcdif.c
new file mode 100644
index 000000000000..d635eddbef70
--- /dev/null
+++ b/drivers/video/mxc/mxc_lcdif.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011-2014 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
+ */
+
+#include <linux/init.h>
+#include <linux/ipu.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mxcfb.h>
+#include <linux/of_device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+
+#include "mxc_dispdrv.h"
+
+struct mxc_lcd_platform_data {
+ u32 default_ifmt;
+ u32 ipu_id;
+ u32 disp_id;
+};
+
+struct mxc_lcdif_data {
+ struct platform_device *pdev;
+ struct mxc_dispdrv_handle *disp_lcdif;
+};
+
+#define DISPDRV_LCD "lcd"
+
+static struct fb_videomode lcdif_modedb[] = {
+ {
+ /* 800x480 @ 57 Hz , pixel clk @ 27MHz */
+ "CLAA-WVGA", 57, 800, 480, 37037, 40, 60, 10, 10, 20, 10,
+ FB_SYNC_CLK_LAT_FALL,
+ FB_VMODE_NONINTERLACED,
+ 0,},
+ {
+ /* 800x480 @ 60 Hz , pixel clk @ 32MHz */
+ "SEIKO-WVGA", 60, 800, 480, 29850, 89, 164, 23, 10, 10, 10,
+ FB_SYNC_CLK_LAT_FALL,
+ FB_VMODE_NONINTERLACED,
+ 0,},
+};
+static int lcdif_modedb_sz = ARRAY_SIZE(lcdif_modedb);
+
+static int lcdif_init(struct mxc_dispdrv_handle *disp,
+ struct mxc_dispdrv_setting *setting)
+{
+ int ret, i;
+ struct mxc_lcdif_data *lcdif = mxc_dispdrv_getdata(disp);
+ struct device *dev = &lcdif->pdev->dev;
+ struct mxc_lcd_platform_data *plat_data = dev->platform_data;
+ struct fb_videomode *modedb = lcdif_modedb;
+ int modedb_sz = lcdif_modedb_sz;
+
+ /* use platform defined ipu/di */
+ ret = ipu_di_to_crtc(dev, plat_data->ipu_id,
+ plat_data->disp_id, &setting->crtc);
+ if (ret < 0)
+ return ret;
+
+ ret = fb_find_mode(&setting->fbi->var, setting->fbi, setting->dft_mode_str,
+ modedb, modedb_sz, NULL, setting->default_bpp);
+ if (!ret) {
+ fb_videomode_to_var(&setting->fbi->var, &modedb[0]);
+ setting->if_fmt = plat_data->default_ifmt;
+ }
+
+ INIT_LIST_HEAD(&setting->fbi->modelist);
+ for (i = 0; i < modedb_sz; i++) {
+ struct fb_videomode m;
+ fb_var_to_videomode(&m, &setting->fbi->var);
+ if (fb_mode_is_equal(&m, &modedb[i])) {
+ fb_add_videomode(&modedb[i],
+ &setting->fbi->modelist);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void lcdif_deinit(struct mxc_dispdrv_handle *disp)
+{
+ /*TODO*/
+}
+
+static struct mxc_dispdrv_driver lcdif_drv = {
+ .name = DISPDRV_LCD,
+ .init = lcdif_init,
+ .deinit = lcdif_deinit,
+};
+
+static int lcd_get_of_property(struct platform_device *pdev,
+ struct mxc_lcd_platform_data *plat_data)
+{
+ struct device_node *np = pdev->dev.of_node;
+ int err;
+ u32 ipu_id, disp_id;
+ const char *default_ifmt;
+
+ err = of_property_read_string(np, "default_ifmt", &default_ifmt);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property default_ifmt fail\n");
+ return err;
+ }
+ err = of_property_read_u32(np, "ipu_id", &ipu_id);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property ipu_id fail\n");
+ return err;
+ }
+ err = of_property_read_u32(np, "disp_id", &disp_id);
+ if (err) {
+ dev_dbg(&pdev->dev, "get of property disp_id fail\n");
+ return err;
+ }
+
+ plat_data->ipu_id = ipu_id;
+ plat_data->disp_id = disp_id;
+ if (!strncmp(default_ifmt, "RGB24", 5))
+ plat_data->default_ifmt = IPU_PIX_FMT_RGB24;
+ else if (!strncmp(default_ifmt, "BGR24", 5))
+ plat_data->default_ifmt = IPU_PIX_FMT_BGR24;
+ else if (!strncmp(default_ifmt, "GBR24", 5))
+ plat_data->default_ifmt = IPU_PIX_FMT_GBR24;
+ else if (!strncmp(default_ifmt, "RGB565", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_RGB565;
+ else if (!strncmp(default_ifmt, "RGB666", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_RGB666;
+ else if (!strncmp(default_ifmt, "YUV444", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_YUV444;
+ else if (!strncmp(default_ifmt, "LVDS666", 7))
+ plat_data->default_ifmt = IPU_PIX_FMT_LVDS666;
+ else if (!strncmp(default_ifmt, "YUYV16", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_YUYV;
+ else if (!strncmp(default_ifmt, "UYVY16", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_UYVY;
+ else if (!strncmp(default_ifmt, "YVYU16", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_YVYU;
+ else if (!strncmp(default_ifmt, "VYUY16", 6))
+ plat_data->default_ifmt = IPU_PIX_FMT_VYUY;
+ else {
+ dev_err(&pdev->dev, "err default_ifmt!\n");
+ return -ENOENT;
+ }
+
+ return err;
+}
+
+static int mxc_lcdif_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct pinctrl *pinctrl;
+ struct mxc_lcdif_data *lcdif;
+ struct mxc_lcd_platform_data *plat_data;
+
+ dev_dbg(&pdev->dev, "%s enter\n", __func__);
+ lcdif = devm_kzalloc(&pdev->dev, sizeof(struct mxc_lcdif_data),
+ GFP_KERNEL);
+ if (!lcdif)
+ return -ENOMEM;
+ plat_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct mxc_lcd_platform_data),
+ GFP_KERNEL);
+ if (!plat_data)
+ return -ENOMEM;
+ pdev->dev.platform_data = plat_data;
+
+ ret = lcd_get_of_property(pdev, plat_data);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "get lcd of property fail\n");
+ return ret;
+ }
+
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ dev_err(&pdev->dev, "can't get/select pinctrl\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ lcdif->pdev = pdev;
+ lcdif->disp_lcdif = mxc_dispdrv_register(&lcdif_drv);
+ mxc_dispdrv_setdata(lcdif->disp_lcdif, lcdif);
+
+ dev_set_drvdata(&pdev->dev, lcdif);
+ dev_dbg(&pdev->dev, "%s exit\n", __func__);
+
+ return ret;
+}
+
+static int mxc_lcdif_remove(struct platform_device *pdev)
+{
+ struct mxc_lcdif_data *lcdif = dev_get_drvdata(&pdev->dev);
+
+ mxc_dispdrv_puthandle(lcdif->disp_lcdif);
+ mxc_dispdrv_unregister(lcdif->disp_lcdif);
+ kfree(lcdif);
+ return 0;
+}
+
+static const struct of_device_id imx_lcd_dt_ids[] = {
+ { .compatible = "fsl,lcd"},
+ { /* sentinel */ }
+};
+static struct platform_driver mxc_lcdif_driver = {
+ .driver = {
+ .name = "mxc_lcdif",
+ .of_match_table = imx_lcd_dt_ids,
+ },
+ .probe = mxc_lcdif_probe,
+ .remove = mxc_lcdif_remove,
+};
+
+static int __init mxc_lcdif_init(void)
+{
+ return platform_driver_register(&mxc_lcdif_driver);
+}
+
+static void __exit mxc_lcdif_exit(void)
+{
+ platform_driver_unregister(&mxc_lcdif_driver);
+}
+
+module_init(mxc_lcdif_init);
+module_exit(mxc_lcdif_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("i.MX ipuv3 LCD extern port driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/mxc/mxcfb_hx8369_wvga.c b/drivers/video/mxc/mxcfb_hx8369_wvga.c
new file mode 100644
index 000000000000..56e4f2866304
--- /dev/null
+++ b/drivers/video/mxc/mxcfb_hx8369_wvga.c
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/io.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/mipi_dsi.h>
+#include <linux/mxcfb.h>
+#include <linux/backlight.h>
+#include <video/mipi_display.h>
+
+#include "mipi_dsi.h"
+
+#define MIPI_DSI_MAX_RET_PACK_SIZE (0x4)
+
+#define HX8369BL_MAX_BRIGHT (255)
+#define HX8369BL_DEF_BRIGHT (255)
+
+#define HX8369_MAX_DPHY_CLK (800)
+#define HX8369_ONE_DATA_LANE (0x1)
+#define HX8369_TWO_DATA_LANE (0x2)
+
+#define HX8369_CMD_SETEXTC (0xB9)
+#define HX8369_CMD_SETEXTC_LEN (0x4)
+#define HX8369_CMD_SETEXTC_PARAM_1 (0x6983ff)
+
+#define HX8369_CMD_GETHXID (0xF4)
+#define HX8369_CMD_GETHXID_LEN (0x4)
+#define HX8369_ID (0x69)
+#define HX8369_ID_MASK (0xFF)
+
+#define HX8369_CMD_SETDISP (0xB2)
+#define HX8369_CMD_SETDISP_LEN (16)
+#define HX8369_CMD_SETDISP_1_HALT (0x00)
+#define HX8369_CMD_SETDISP_2_RES_MODE (0x23)
+#define HX8369_CMD_SETDISP_3_BP (0x03)
+#define HX8369_CMD_SETDISP_4_FP (0x03)
+#define HX8369_CMD_SETDISP_5_SAP (0x70)
+#define HX8369_CMD_SETDISP_6_GENON (0x00)
+#define HX8369_CMD_SETDISP_7_GENOFF (0xff)
+#define HX8369_CMD_SETDISP_8_RTN (0x00)
+#define HX8369_CMD_SETDISP_9_TEI (0x00)
+#define HX8369_CMD_SETDISP_10_TEP_UP (0x00)
+#define HX8369_CMD_SETDISP_11_TEP_LOW (0x00)
+#define HX8369_CMD_SETDISP_12_BP_PE (0x03)
+#define HX8369_CMD_SETDISP_13_FP_PE (0x03)
+#define HX8369_CMD_SETDISP_14_RTN_PE (0x00)
+#define HX8369_CMD_SETDISP_15_GON (0x01)
+
+#define HX8369_CMD_SETCYC (0xB4)
+#define HX8369_CMD_SETCYC_LEN (6)
+#define HX8369_CMD_SETCYC_PARAM_1 (0x5f1d00)
+#define HX8369_CMD_SETCYC_PARAM_2 (0x060e)
+
+#define HX8369_CMD_SETGIP (0xD5)
+#define HX8369_CMD_SETGIP_LEN (27)
+#define HX8369_CMD_SETGIP_PARAM_1 (0x030400)
+#define HX8369_CMD_SETGIP_PARAM_2 (0x1c050100)
+#define HX8369_CMD_SETGIP_PARAM_3 (0x00030170)
+#define HX8369_CMD_SETGIP_PARAM_4 (0x51064000)
+#define HX8369_CMD_SETGIP_PARAM_5 (0x41000007)
+#define HX8369_CMD_SETGIP_PARAM_6 (0x07075006)
+#define HX8369_CMD_SETGIP_PARAM_7 (0x040f)
+
+#define HX8369_CMD_SETPOWER (0xB1)
+#define HX8369_CMD_SETPOWER_LEN (20)
+#define HX8369_CMD_SETPOWER_PARAM_1 (0x340001)
+#define HX8369_CMD_SETPOWER_PARAM_2 (0x0f0f0006)
+#define HX8369_CMD_SETPOWER_PARAM_3 (0x3f3f322a)
+#define HX8369_CMD_SETPOWER_PARAM_4 (0xe6013a07)
+#define HX8369_CMD_SETPOWER_PARAM_5 (0xe6e6e6e6)
+
+#define HX8369_CMD_SETVCOM (0xB6)
+#define HX8369_CMD_SETVCOM_LEN (3)
+#define HX8369_CMD_SETVCOM_PARAM_1 (0x5656)
+
+#define HX8369_CMD_SETPANEL (0xCC)
+#define HX8369_CMD_SETPANEL_PARAM_1 (0x02)
+
+#define HX8369_CMD_SETGAMMA (0xE0)
+#define HX8369_CMD_SETGAMMA_LEN (35)
+#define HX8369_CMD_SETGAMMA_PARAM_1 (0x221d00)
+#define HX8369_CMD_SETGAMMA_PARAM_2 (0x2e3f3d38)
+#define HX8369_CMD_SETGAMMA_PARAM_3 (0x0f0d064a)
+#define HX8369_CMD_SETGAMMA_PARAM_4 (0x16131513)
+#define HX8369_CMD_SETGAMMA_PARAM_5 (0x1d001910)
+#define HX8369_CMD_SETGAMMA_PARAM_6 (0x3f3d3822)
+#define HX8369_CMD_SETGAMMA_PARAM_7 (0x0d064a2e)
+#define HX8369_CMD_SETGAMMA_PARAM_8 (0x1315130f)
+#define HX8369_CMD_SETGAMMA_PARAM_9 (0x191016)
+
+#define HX8369_CMD_SETMIPI (0xBA)
+#define HX8369_CMD_SETMIPI_LEN (14)
+#define HX8369_CMD_SETMIPI_PARAM_1 (0xc6a000)
+#define HX8369_CMD_SETMIPI_PARAM_2 (0x10000a00)
+#define HX8369_CMD_SETMIPI_ONELANE (0x10 << 24)
+#define HX8369_CMD_SETMIPI_TWOLANE (0x11 << 24)
+#define HX8369_CMD_SETMIPI_PARAM_3 (0x00026f30)
+#define HX8369_CMD_SETMIPI_PARAM_4 (0x4018)
+
+#define HX8369_CMD_SETPIXEL_FMT (0x3A)
+#define HX8369_CMD_SETPIXEL_FMT_24BPP (0x77)
+#define HX8369_CMD_SETPIXEL_FMT_18BPP (0x66)
+#define HX8369_CMD_SETPIXEL_FMT_16BPP (0x55)
+
+#define HX8369_CMD_SETCLUMN_ADDR (0x2A)
+#define HX8369_CMD_SETCLUMN_ADDR_LEN (5)
+#define HX8369_CMD_SETCLUMN_ADDR_PARAM_1 (0xdf0000)
+#define HX8369_CMD_SETCLUMN_ADDR_PARAM_2 (0x01)
+
+#define HX8369_CMD_SETPAGE_ADDR (0x2B)
+#define HX8369_CMD_SETPAGE_ADDR_LEN (5)
+#define HX8369_CMD_SETPAGE_ADDR_PARAM_1 (0x1f0000)
+#define HX8369_CMD_SETPAGE_ADDR_PARAM_2 (0x03)
+
+#define HX8369_CMD_WRT_DISP_BRIGHT (0x51)
+#define HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 (0xFF)
+
+#define HX8369_CMD_WRT_CABC_MIN_BRIGHT (0x5E)
+#define HX8369_CMD_WRT_CABC_MIN_BRIGHT_PARAM_1 (0x20)
+
+#define HX8369_CMD_WRT_CABC_CTRL (0x55)
+#define HX8369_CMD_WRT_CABC_CTRL_PARAM_1 (0x1)
+
+#define HX8369_CMD_WRT_CTRL_DISP (0x53)
+#define HX8369_CMD_WRT_CTRL_DISP_PARAM_1 (0x24)
+
+#define CHECK_RETCODE(ret) \
+do { \
+ if (ret < 0) { \
+ dev_err(&mipi_dsi->pdev->dev, \
+ "%s ERR: ret:%d, line:%d.\n", \
+ __func__, ret, __LINE__); \
+ return ret; \
+ } \
+} while (0)
+
+static int hx8369bl_brightness;
+static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi);
+
+static struct fb_videomode truly_lcd_modedb[] = {
+ {
+ "TRULY-WVGA", 64, 480, 800, 37880,
+ 8, 8,
+ 6, 6,
+ 8, 6,
+ FB_SYNC_OE_LOW_ACT,
+ FB_VMODE_NONINTERLACED,
+ 0,
+ },
+};
+
+static struct mipi_lcd_config lcd_config = {
+ .virtual_ch = 0x0,
+ .data_lane_num = HX8369_TWO_DATA_LANE,
+ .max_phy_clk = HX8369_MAX_DPHY_CLK,
+ .dpi_fmt = MIPI_RGB888,
+};
+void mipid_hx8369_get_lcd_videomode(struct fb_videomode **mode, int *size,
+ struct mipi_lcd_config **data)
+{
+ *mode = &truly_lcd_modedb[0];
+ *size = ARRAY_SIZE(truly_lcd_modedb);
+ *data = &lcd_config;
+}
+
+int mipid_hx8369_lcd_setup(struct mipi_dsi_info *mipi_dsi)
+{
+ u32 buf[DSI_CMD_BUF_MAXSIZE];
+ int err;
+
+ dev_dbg(&mipi_dsi->pdev->dev, "MIPI DSI LCD setup.\n");
+ buf[0] = HX8369_CMD_SETEXTC | (HX8369_CMD_SETEXTC_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE,
+ buf, HX8369_CMD_SETEXTC_LEN);
+ CHECK_RETCODE(err);
+ buf[0] = MIPI_DSI_MAX_RET_PACK_SIZE;
+ err = mipi_dsi_pkt_write(mipi_dsi,
+ MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE,
+ buf, 0);
+ CHECK_RETCODE(err);
+ buf[0] = HX8369_CMD_GETHXID;
+ err = mipi_dsi_pkt_read(mipi_dsi,
+ MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM,
+ buf, HX8369_CMD_GETHXID_LEN);
+ if (!err && ((buf[0] & HX8369_ID_MASK) == HX8369_ID)) {
+ dev_info(&mipi_dsi->pdev->dev,
+ "MIPI DSI LCD ID:0x%x.\n", buf[0]);
+ } else {
+ dev_err(&mipi_dsi->pdev->dev,
+ "mipi_dsi_pkt_read err:%d, data:0x%x.\n",
+ err, buf[0]);
+ dev_info(&mipi_dsi->pdev->dev,
+ "MIPI DSI LCD not detected!\n");
+ return err;
+ }
+
+ /* set LCD resolution as 480RGBx800, DPI interface,
+ * display operation mode: RGB data bypass GRAM mode.
+ */
+ buf[0] = HX8369_CMD_SETDISP | (HX8369_CMD_SETDISP_1_HALT << 8) |
+ (HX8369_CMD_SETDISP_2_RES_MODE << 16) |
+ (HX8369_CMD_SETDISP_3_BP << 24);
+ buf[1] = HX8369_CMD_SETDISP_4_FP | (HX8369_CMD_SETDISP_5_SAP << 8) |
+ (HX8369_CMD_SETDISP_6_GENON << 16) |
+ (HX8369_CMD_SETDISP_7_GENOFF << 24);
+ buf[2] = HX8369_CMD_SETDISP_8_RTN | (HX8369_CMD_SETDISP_9_TEI << 8) |
+ (HX8369_CMD_SETDISP_10_TEP_UP << 16) |
+ (HX8369_CMD_SETDISP_11_TEP_LOW << 24);
+ buf[3] = HX8369_CMD_SETDISP_12_BP_PE |
+ (HX8369_CMD_SETDISP_13_FP_PE << 8) |
+ (HX8369_CMD_SETDISP_14_RTN_PE << 16) |
+ (HX8369_CMD_SETDISP_15_GON << 24);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE,
+ buf, HX8369_CMD_SETDISP_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set display waveform cycle */
+ buf[0] = HX8369_CMD_SETCYC | (HX8369_CMD_SETCYC_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETCYC_PARAM_2;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE,
+ buf, HX8369_CMD_SETCYC_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set GIP timing output control */
+ buf[0] = HX8369_CMD_SETGIP | (HX8369_CMD_SETGIP_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETGIP_PARAM_2;
+ buf[2] = HX8369_CMD_SETGIP_PARAM_3;
+ buf[3] = HX8369_CMD_SETGIP_PARAM_4;
+ buf[4] = HX8369_CMD_SETGIP_PARAM_5;
+ buf[5] = HX8369_CMD_SETGIP_PARAM_6;
+ buf[6] = HX8369_CMD_SETGIP_PARAM_7;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf,
+ HX8369_CMD_SETGIP_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set power: standby, DC etc. */
+ buf[0] = HX8369_CMD_SETPOWER | (HX8369_CMD_SETPOWER_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETPOWER_PARAM_2;
+ buf[2] = HX8369_CMD_SETPOWER_PARAM_3;
+ buf[3] = HX8369_CMD_SETPOWER_PARAM_4;
+ buf[4] = HX8369_CMD_SETPOWER_PARAM_5;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf,
+ HX8369_CMD_SETPOWER_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set VCOM voltage. */
+ buf[0] = HX8369_CMD_SETVCOM | (HX8369_CMD_SETVCOM_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf,
+ HX8369_CMD_SETVCOM_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set Panel: BGR/RGB or Inversion. */
+ buf[0] = HX8369_CMD_SETPANEL | (HX8369_CMD_SETPANEL_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi,
+ MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM, buf, 0);
+ CHECK_RETCODE(err);
+
+ /* Set gamma curve related setting */
+ buf[0] = HX8369_CMD_SETGAMMA | (HX8369_CMD_SETGAMMA_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETGAMMA_PARAM_2;
+ buf[2] = HX8369_CMD_SETGAMMA_PARAM_3;
+ buf[3] = HX8369_CMD_SETGAMMA_PARAM_4;
+ buf[4] = HX8369_CMD_SETGAMMA_PARAM_5;
+ buf[5] = HX8369_CMD_SETGAMMA_PARAM_6;
+ buf[7] = HX8369_CMD_SETGAMMA_PARAM_7;
+ buf[7] = HX8369_CMD_SETGAMMA_PARAM_8;
+ buf[8] = HX8369_CMD_SETGAMMA_PARAM_9;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf,
+ HX8369_CMD_SETGAMMA_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set MIPI: DPHYCMD & DSICMD, data lane number */
+ buf[0] = HX8369_CMD_SETMIPI | (HX8369_CMD_SETMIPI_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETMIPI_PARAM_2;
+ buf[2] = HX8369_CMD_SETMIPI_PARAM_3;
+ if (lcd_config.data_lane_num == HX8369_ONE_DATA_LANE)
+ buf[2] |= HX8369_CMD_SETMIPI_ONELANE;
+ else
+ buf[2] |= HX8369_CMD_SETMIPI_TWOLANE;
+ buf[3] = HX8369_CMD_SETMIPI_PARAM_4;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE, buf,
+ HX8369_CMD_SETMIPI_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set pixel format:24bpp */
+ buf[0] = HX8369_CMD_SETPIXEL_FMT;
+ switch (lcd_config.dpi_fmt) {
+ case MIPI_RGB565_PACKED:
+ case MIPI_RGB565_LOOSELY:
+ case MIPI_RGB565_CONFIG3:
+ buf[0] |= (HX8369_CMD_SETPIXEL_FMT_16BPP << 8);
+ break;
+
+ case MIPI_RGB666_LOOSELY:
+ case MIPI_RGB666_PACKED:
+ buf[0] |= (HX8369_CMD_SETPIXEL_FMT_18BPP << 8);
+ break;
+
+ case MIPI_RGB888:
+ buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8);
+ break;
+
+ default:
+ buf[0] |= (HX8369_CMD_SETPIXEL_FMT_24BPP << 8);
+ break;
+ }
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+
+ /* Set column address: 0~479 */
+ buf[0] = HX8369_CMD_SETCLUMN_ADDR |
+ (HX8369_CMD_SETCLUMN_ADDR_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETCLUMN_ADDR_PARAM_2;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE,
+ buf, HX8369_CMD_SETCLUMN_ADDR_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set page address: 0~799 */
+ buf[0] = HX8369_CMD_SETPAGE_ADDR |
+ (HX8369_CMD_SETPAGE_ADDR_PARAM_1 << 8);
+ buf[1] = HX8369_CMD_SETPAGE_ADDR_PARAM_2;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_LONG_WRITE,
+ buf, HX8369_CMD_SETPAGE_ADDR_LEN);
+ CHECK_RETCODE(err);
+
+ /* Set display brightness related */
+ buf[0] = HX8369_CMD_WRT_DISP_BRIGHT |
+ (HX8369_CMD_WRT_DISP_BRIGHT_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+
+ buf[0] = HX8369_CMD_WRT_CABC_CTRL |
+ (HX8369_CMD_WRT_CABC_CTRL_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+
+ buf[0] = HX8369_CMD_WRT_CTRL_DISP |
+ (HX8369_CMD_WRT_CTRL_DISP_PARAM_1 << 8);
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+
+ /* exit sleep mode and set display on */
+ buf[0] = MIPI_DCS_EXIT_SLEEP_MODE;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+ /* To allow time for the supply voltages
+ * and clock circuits to stabilize.
+ */
+ msleep(5);
+ buf[0] = MIPI_DCS_SET_DISPLAY_ON;
+ err = mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM,
+ buf, 0);
+ CHECK_RETCODE(err);
+
+ err = mipid_init_backlight(mipi_dsi);
+ return err;
+}
+
+static int mipid_bl_update_status(struct backlight_device *bl)
+{
+ u32 buf;
+ int brightness = bl->props.brightness;
+ struct mipi_dsi_info *mipi_dsi = bl_get_data(bl);
+
+ if (bl->props.power != FB_BLANK_UNBLANK ||
+ bl->props.fb_blank != FB_BLANK_UNBLANK)
+ brightness = 0;
+
+ buf = HX8369_CMD_WRT_DISP_BRIGHT |
+ ((brightness & HX8369BL_MAX_BRIGHT) << 8);
+ mipi_dsi_pkt_write(mipi_dsi, MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM,
+ &buf, 0);
+
+ hx8369bl_brightness = brightness & HX8369BL_MAX_BRIGHT;
+
+ dev_dbg(&bl->dev, "mipid backlight bringtness:%d.\n", brightness);
+ return 0;
+}
+
+static int mipid_bl_get_brightness(struct backlight_device *bl)
+{
+ return hx8369bl_brightness;
+}
+
+static int mipi_bl_check_fb(struct backlight_device *bl, struct fb_info *fbi)
+{
+ return 0;
+}
+
+static const struct backlight_ops mipid_lcd_bl_ops = {
+ .update_status = mipid_bl_update_status,
+ .get_brightness = mipid_bl_get_brightness,
+ .check_fb = mipi_bl_check_fb,
+};
+
+static int mipid_init_backlight(struct mipi_dsi_info *mipi_dsi)
+{
+ struct backlight_properties props;
+ struct backlight_device *bl;
+
+ if (mipi_dsi->bl) {
+ pr_debug("mipid backlight already init!\n");
+ return 0;
+ }
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = HX8369BL_MAX_BRIGHT;
+ props.type = BACKLIGHT_RAW;
+ bl = backlight_device_register("mipid-bl", &mipi_dsi->pdev->dev,
+ mipi_dsi, &mipid_lcd_bl_ops, &props);
+ if (IS_ERR(bl)) {
+ pr_err("error %ld on backlight register\n", PTR_ERR(bl));
+ return PTR_ERR(bl);
+ }
+ mipi_dsi->bl = bl;
+ bl->props.power = FB_BLANK_UNBLANK;
+ bl->props.fb_blank = FB_BLANK_UNBLANK;
+ bl->props.brightness = HX8369BL_DEF_BRIGHT;
+
+ mipid_bl_update_status(bl);
+ return 0;
+}
diff --git a/drivers/video/mxc/mxsfb_sii902x.c b/drivers/video/mxc/mxsfb_sii902x.c
new file mode 100644
index 000000000000..531d3f66a116
--- /dev/null
+++ b/drivers/video/mxc/mxsfb_sii902x.c
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2010-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*!
+ * @defgroup Framebuffer Framebuffer Driver for Sii902x.
+ */
+
+/*!
+ * @file mxsfb_sii902x.c
+ *
+ * @brief Frame buffer driver for SII902x
+ *
+ * @ingroup Framebuffer
+ */
+
+/*!
+ * Include files
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/i2c.h>
+#include <linux/fsl_devices.h>
+#include <linux/interrupt.h>
+#include <linux/reset.h>
+#include <asm/mach-types.h>
+#include <video/mxc_edid.h>
+
+#define SII_EDID_LEN 512
+#define DRV_NAME "sii902x"
+
+struct sii902x_data {
+ struct i2c_client *client;
+ struct delayed_work det_work;
+ struct fb_info *fbi;
+ struct mxc_edid_cfg edid_cfg;
+ u8 cable_plugin;
+ u8 edid[SII_EDID_LEN];
+ bool waiting_for_fb;
+ bool dft_mode_set;
+ const char *mode_str;
+ int bits_per_pixel;
+} sii902x;
+
+static void sii902x_poweron(void);
+static void sii902x_poweroff(void);
+
+#ifdef DEBUG
+static void dump_fb_videomode(struct fb_videomode *m)
+{
+ pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
+ m->refresh, m->xres, m->yres, m->pixclock, m->left_margin,
+ m->right_margin, m->upper_margin, m->lower_margin,
+ m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag);
+}
+#else
+static void dump_fb_videomode(struct fb_videomode *m)
+{}
+#endif
+
+static __attribute__ ((unused)) void dump_regs(u8 reg, int len)
+{
+ u8 buf[50];
+ int i;
+
+ i2c_smbus_read_i2c_block_data(sii902x.client, reg, len, buf);
+ for (i = 0; i < len; i++)
+ dev_dbg(&sii902x.client->dev, "reg[0x%02X]: 0x%02X\n",
+ i+reg, buf[i]);
+}
+
+static ssize_t sii902x_show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ strcpy(buf, sii902x.fbi->fix.id);
+ sprintf(buf+strlen(buf), "\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(fb_name, S_IRUGO, sii902x_show_name, NULL);
+
+static ssize_t sii902x_show_state(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ if (sii902x.cable_plugin == 0)
+ strcpy(buf, "plugout\n");
+ else
+ strcpy(buf, "plugin\n");
+
+ return strlen(buf);
+}
+
+static DEVICE_ATTR(cable_state, S_IRUGO, sii902x_show_state, NULL);
+
+static ssize_t sii902x_show_edid(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int i, j, len = 0;
+
+ for (j = 0; j < SII_EDID_LEN/16; j++) {
+ for (i = 0; i < 16; i++)
+ len += sprintf(buf+len, "0x%02X ",
+ sii902x.edid[j*16 + i]);
+ len += sprintf(buf+len, "\n");
+ }
+
+ return len;
+}
+
+static DEVICE_ATTR(edid, S_IRUGO, sii902x_show_edid, NULL);
+
+static void sii902x_setup(struct fb_info *fbi)
+{
+ u16 data[4];
+ u32 refresh;
+ u8 *tmp;
+ int i;
+
+ dev_dbg(&sii902x.client->dev, "Sii902x: setup..\n");
+
+ /* Power up */
+ i2c_smbus_write_byte_data(sii902x.client, 0x1E, 0x00);
+
+ /* set TPI video mode */
+ data[0] = PICOS2KHZ(fbi->var.pixclock) / 10;
+ data[2] = fbi->var.hsync_len + fbi->var.left_margin +
+ fbi->var.xres + fbi->var.right_margin;
+ data[3] = fbi->var.vsync_len + fbi->var.upper_margin +
+ fbi->var.yres + fbi->var.lower_margin;
+ refresh = data[2] * data[3];
+ refresh = (PICOS2KHZ(fbi->var.pixclock) * 1000) / refresh;
+ data[1] = refresh * 100;
+ tmp = (u8 *)data;
+ for (i = 0; i < 8; i++)
+ i2c_smbus_write_byte_data(sii902x.client, i, tmp[i]);
+
+ /* input bus/pixel: full pixel wide (24bit), rising edge */
+ i2c_smbus_write_byte_data(sii902x.client, 0x08, 0x70);
+ /* Set input format to RGB */
+ i2c_smbus_write_byte_data(sii902x.client, 0x09, 0x00);
+ /* set output format to RGB */
+ i2c_smbus_write_byte_data(sii902x.client, 0x0A, 0x00);
+ /* audio setup */
+ i2c_smbus_write_byte_data(sii902x.client, 0x25, 0x00);
+ i2c_smbus_write_byte_data(sii902x.client, 0x26, 0x40);
+ i2c_smbus_write_byte_data(sii902x.client, 0x27, 0x00);
+}
+
+#ifdef CONFIG_FB_MODE_HELPERS
+static int sii902x_read_edid(struct fb_info *fbi)
+{
+ int old, dat, ret, cnt = 100;
+ unsigned short addr = 0x50;
+
+ dev_dbg(&sii902x.client->dev, "%s\n", __func__);
+
+ old = i2c_smbus_read_byte_data(sii902x.client, 0x1A);
+
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x4);
+ do {
+ cnt--;
+ msleep(10);
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A);
+ } while ((!(dat & 0x2)) && cnt);
+
+ if (!cnt) {
+ ret = -1;
+ goto done;
+ }
+
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, old | 0x06);
+
+ /* edid reading */
+ ret = mxc_edid_read(sii902x.client->adapter, addr,
+ sii902x.edid, &sii902x.edid_cfg, fbi);
+
+ cnt = 100;
+ do {
+ cnt--;
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, old & ~0x6);
+ msleep(10);
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x1A);
+ } while ((dat & 0x6) && cnt);
+
+ if (!cnt)
+ ret = -1;
+
+done:
+
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, old);
+ return ret;
+}
+#else
+static int sii902x_read_edid(struct fb_info *fbi)
+{
+ return -1;
+}
+#endif
+
+static void sii902x_cable_connected(void)
+{
+ int i;
+ const struct fb_videomode *mode;
+ struct fb_videomode m;
+
+ if (sii902x_read_edid(sii902x.fbi) < 0)
+ dev_err(&sii902x.client->dev,
+ "Sii902x: read edid fail\n");
+ else {
+ if (sii902x.fbi->monspecs.modedb_len > 0) {
+
+ fb_destroy_modelist(&sii902x.fbi->modelist);
+
+ for (i = 0; i < sii902x.fbi->monspecs.modedb_len; i++) {
+
+ mode = &sii902x.fbi->monspecs.modedb[i];
+
+ if (!(mode->vmode & FB_VMODE_INTERLACED)) {
+
+ dev_dbg(&sii902x.client->dev, "Added mode %d:", i);
+ dev_dbg(&sii902x.client->dev,
+ "xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n",
+ mode->xres, mode->yres, mode->refresh,
+ mode->vmode, mode->flag);
+
+ fb_add_videomode(mode, &sii902x.fbi->modelist);
+ }
+ }
+
+ /* Set the default mode only once. */
+ if (!sii902x.dft_mode_set &&
+ sii902x.mode_str && sii902x.bits_per_pixel) {
+
+ dev_dbg(&sii902x.client->dev, "%s: setting to default=%s bpp=%d\n",
+ __func__, sii902x.mode_str, sii902x.bits_per_pixel);
+
+ fb_find_mode(&sii902x.fbi->var, sii902x.fbi,
+ sii902x.mode_str, NULL, 0, NULL,
+ sii902x.bits_per_pixel);
+
+ sii902x.dft_mode_set = true;
+ }
+
+ fb_var_to_videomode(&m, &sii902x.fbi->var);
+ dump_fb_videomode(&m);
+
+ mode = fb_find_nearest_mode(&m,
+ &sii902x.fbi->modelist);
+
+ /* update fbi mode */
+ sii902x.fbi->mode = (struct fb_videomode *)mode;
+
+ fb_videomode_to_var(&sii902x.fbi->var, mode);
+
+ sii902x.fbi->var.activate |= FB_ACTIVATE_FORCE;
+ console_lock();
+ sii902x.fbi->flags |= FBINFO_MISC_USEREVENT;
+ fb_set_var(sii902x.fbi, &sii902x.fbi->var);
+ sii902x.fbi->flags &= ~FBINFO_MISC_USEREVENT;
+ console_unlock();
+ }
+ /* Power on sii902x */
+ sii902x_poweron();
+ }
+}
+
+static void det_worker(struct work_struct *work)
+{
+ int dat;
+ char event_string[16];
+ char *envp[] = { event_string, NULL };
+
+ dev_dbg(&sii902x.client->dev, "%s\n", __func__);
+
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x3D);
+ if (dat & 0x1) {
+ /* cable connection changes */
+ if (dat & 0x4) {
+ sii902x.cable_plugin = 1;
+ dev_dbg(&sii902x.client->dev, "EVENT=plugin\n");
+ sprintf(event_string, "EVENT=plugin");
+ sii902x_cable_connected();
+ } else {
+ sii902x.cable_plugin = 0;
+ dev_dbg(&sii902x.client->dev, "EVENT=plugout\n");
+ sprintf(event_string, "EVENT=plugout");
+ /* Power off sii902x */
+ sii902x_poweroff();
+ }
+ kobject_uevent_env(&sii902x.client->dev.kobj, KOBJ_CHANGE, envp);
+ }
+ i2c_smbus_write_byte_data(sii902x.client, 0x3D, dat);
+
+ dev_dbg(&sii902x.client->dev, "exit %s\n", __func__);
+
+}
+
+static irqreturn_t sii902x_detect_handler(int irq, void *data)
+{
+ if (sii902x.fbi)
+ schedule_delayed_work(&(sii902x.det_work), msecs_to_jiffies(20));
+ else
+ sii902x.waiting_for_fb = true;
+
+ return IRQ_HANDLED;
+}
+
+static int sii902x_fb_event(struct notifier_block *nb, unsigned long val, void *v)
+{
+ struct fb_event *event = v;
+ struct fb_info *fbi = event->info;
+
+ dev_dbg(&sii902x.client->dev, "%s event=0x%lx\n", __func__, val);
+
+ switch (val) {
+ case FB_EVENT_FB_REGISTERED:
+ if (sii902x.fbi == NULL) {
+ sii902x.fbi = fbi;
+ if (sii902x.waiting_for_fb)
+ det_worker(NULL);
+ }
+ fb_show_logo(fbi, 0);
+ break;
+ case FB_EVENT_MODE_CHANGE:
+ sii902x_setup(fbi);
+ break;
+ case FB_EVENT_BLANK:
+ if (*((int *)event->data) == FB_BLANK_UNBLANK) {
+ dev_dbg(&sii902x.client->dev, "FB_BLANK_UNBLANK\n");
+ sii902x_poweron();
+ } else {
+ dev_dbg(&sii902x.client->dev, "FB_BLANK_BLANK\n");
+ sii902x_poweroff();
+ }
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block nb = {
+ .notifier_call = sii902x_fb_event,
+};
+
+static int mxsfb_get_of_property(void)
+{
+ struct device_node *np = sii902x.client->dev.of_node;
+ const char *mode_str;
+ int bits_per_pixel, ret;
+
+ ret = of_property_read_string(np, "mode_str", &mode_str);
+ if (ret < 0) {
+ dev_warn(&sii902x.client->dev, "get of property mode_str fail\n");
+ return ret;
+ }
+ ret = of_property_read_u32(np, "bits-per-pixel", &bits_per_pixel);
+ if (ret) {
+ dev_warn(&sii902x.client->dev, "get of property bpp fail\n");
+ return ret;
+ }
+
+ sii902x.mode_str = mode_str;
+ sii902x.bits_per_pixel = bits_per_pixel;
+
+ return ret;
+}
+
+static int sii902x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int i, dat, ret;
+ struct fb_info edid_fbi;
+
+ memset(&sii902x, 0, sizeof(sii902x));
+
+ sii902x.client = client;
+
+ dev_dbg(&sii902x.client->dev, "%s\n", __func__);;
+
+ /* Reset sii902x */
+ ret = device_reset(&sii902x.client->dev);
+ if (ret) {
+ dev_err(&sii902x.client->dev, "failed to reset: %d\n", ret);
+ return -ENODEV;
+ }
+
+ /* Set 902x in hardware TPI mode on and jump out of D3 state */
+ if (i2c_smbus_write_byte_data(sii902x.client, 0xc7, 0x00) < 0) {
+ dev_err(&sii902x.client->dev,
+ "Sii902x: cound not find device\n");
+ return -ENODEV;
+ }
+
+ /* read device ID */
+ for (i = 10; i > 0; i--) {
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x1B);
+ printk(KERN_DEBUG "Sii902x: read id = 0x%02X", dat);
+ if (dat == 0xb0) {
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x1C);
+ printk(KERN_DEBUG "-0x%02X", dat);
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x1D);
+ printk(KERN_DEBUG "-0x%02X", dat);
+ dat = i2c_smbus_read_byte_data(sii902x.client, 0x30);
+ printk(KERN_DEBUG "-0x%02X\n", dat);
+ break;
+ }
+ }
+ if (i == 0) {
+ dev_err(&sii902x.client->dev,
+ "Sii902x: cound not find device\n");
+ return -ENODEV;
+ }
+
+ /* try to read edid */
+ ret = sii902x_read_edid(&edid_fbi);
+ if (ret < 0)
+ dev_warn(&sii902x.client->dev, "Can not read edid\n");
+
+ if (sii902x.client->irq) {
+ ret = request_irq(sii902x.client->irq, sii902x_detect_handler,
+ IRQF_TRIGGER_FALLING,
+ "SII902x_det", &sii902x);
+ if (ret < 0)
+ dev_warn(&sii902x.client->dev,
+ "Sii902x: cound not request det irq %d\n",
+ sii902x.client->irq);
+ else {
+ /*enable cable hot plug irq*/
+ i2c_smbus_write_byte_data(sii902x.client, 0x3c, 0x01);
+ INIT_DELAYED_WORK(&(sii902x.det_work), det_worker);
+ }
+ ret = device_create_file(&sii902x.client->dev, &dev_attr_fb_name);
+ if (ret < 0)
+ dev_warn(&sii902x.client->dev,
+ "Sii902x: cound not create sys node for fb name\n");
+ ret = device_create_file(&sii902x.client->dev, &dev_attr_cable_state);
+ if (ret < 0)
+ dev_warn(&sii902x.client->dev,
+ "Sii902x: cound not create sys node for cable state\n");
+ ret = device_create_file(&sii902x.client->dev, &dev_attr_edid);
+ if (ret < 0)
+ dev_warn(&sii902x.client->dev,
+ "Sii902x: cound not create sys node for edid\n");
+
+ }
+
+ mxsfb_get_of_property();
+ fb_register_client(&nb);
+
+ return 0;
+}
+
+static int sii902x_remove(struct i2c_client *client)
+{
+ fb_unregister_client(&nb);
+ sii902x_poweroff();
+
+ return 0;
+}
+
+static void sii902x_poweron(void)
+{
+ /* Turn on DVI or HDMI */
+ if (sii902x.edid_cfg.hdmi_cap)
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x01);
+ else
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x00);
+ return;
+}
+
+static void sii902x_poweroff(void)
+{
+ /* disable tmds before changing resolution */
+ if (sii902x.edid_cfg.hdmi_cap)
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x11);
+ else
+ i2c_smbus_write_byte_data(sii902x.client, 0x1A, 0x10);
+
+ return;
+}
+
+static const struct i2c_device_id sii902x_id[] = {
+ { DRV_NAME, 0},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sii902x_id);
+
+static const struct of_device_id sii902x_dt_ids[] = {
+ { .compatible = "SiI,sii902x", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sii902x_dt_ids);
+
+static struct i2c_driver sii902x_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = sii902x_dt_ids,
+ },
+ .probe = sii902x_probe,
+ .remove = sii902x_remove,
+ .id_table = sii902x_id,
+};
+
+module_i2c_driver(sii902x_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("SII902x DVI/HDMI driver");
+MODULE_LICENSE("GPL");