summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan@agner.ch>2014-10-02 17:44:26 +0200
committerStefan Agner <stefan@agner.ch>2014-10-02 17:44:26 +0200
commit84668be6b1f133467ff743012d1195823073966a (patch)
tree19c6f1d110e16db8deff99ee05976159076bb9cd
parentdb2765a84b839c8f4c4463c209de48074eff19b4 (diff)
parentfb6cd094dd4f67e67abb21fdfac6712353bec246 (diff)
Merge branch 'vf610-dcu' into toradex_vf_3.17_next
Conflicts: arch/arm/boot/dts/vf610-colibri-eval-v3.dts arch/arm/boot/dts/vf610-colibri.dtsi arch/arm/boot/dts/vf610.dtsi
-rw-r--r--Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt67
-rw-r--r--arch/arm/boot/dts/vf-colibri-eval-v3.dtsi109
-rw-r--r--arch/arm/boot/dts/vf-colibri.dtsi33
-rw-r--r--arch/arm/boot/dts/vf500.dtsi18
-rw-r--r--arch/arm/boot/dts/vf610-colibri-eval-v3.dts2
-rw-r--r--arch/arm/boot/dts/vf610-colibri.dtsi2
-rw-r--r--arch/arm/boot/dts/vf610-twr.dts65
-rw-r--r--arch/arm/mach-imx/clk-vf610.c5
-rw-r--r--drivers/video/fbdev/Kconfig11
-rw-r--r--drivers/video/fbdev/Makefile1
-rw-r--r--drivers/video/fbdev/fsl-dcu-fb.c1174
-rw-r--r--include/dt-bindings/clock/vf610-clock.h3
12 files changed, 1487 insertions, 3 deletions
diff --git a/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt b/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt
new file mode 100644
index 000000000000..d0d50dd724d1
--- /dev/null
+++ b/Documentation/devicetree/bindings/fb/fsl-dcu-fb.txt
@@ -0,0 +1,67 @@
+* Freescale Display Control Unit (DCU)
+
+Required properties:
+- compatible: Should be "fsl,vf610-dcu". Supported chips include
+ Vybrid VF610.
+- reg: Address and length of the register set for DCU.
+- interrupts: Should contain DCU interrupts.
+- clocks: From common clock binding: handle to DCU clock.
+- clock-names: From common clock binding: Shall be "dcu".
+- tcon-controller: The phandle of TCON controller.
+- display: The phandle to display node.
+
+For the DCU initialization, we read data from TCON node.
+Required properties for TCON:
+- compatible: Should be "fsl,vf610-tcon". Supported chips include
+ Vybrid VF610.
+- reg: Address and length of the register set for TCON.
+- clocks: From common clock binding: handle to TCON clock.
+- clock-names: From common clock binding: Shall be "tcon".
+
+* display node
+
+Required properties:
+- bits-per-pixel: <24> for RGB888.
+
+Required sub-node:
+- display-timings: Refer to binding doc display-timing.txt for details.
+
+Examples:
+
+dcu0: dcu@40058000 {
+ compatible = "fsl,vf610-dcu";
+ reg = <0x40058000 0x1200>;
+ interrupts = <0 30 0x04>;
+ clocks = <&clks VF610_CLK_DCU0>;
+ clock-names = "dcu";
+ tcon-controller = <&tcon0>;
+ display = <&display>;
+
+ display: display@0 {
+ bits-per-pixel = <24>;
+
+ display-timings {
+ native-mode = <&timing0>;
+ timing0: nl4827hc19 {
+ clock-frequency = <10870000>;
+ hactive = <480>;
+ vactive = <272>;
+ hback-porch = <2>;
+ hfront-porch = <2>;
+ vback-porch = <1>;
+ vfront-porch = <1>;
+ hsync-len = <41>;
+ vsync-len = <2>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ };
+ };
+ };
+};
+
+tcon0: tcon@4003d000 {
+ compatible = "fsl,vf610-tcon";
+ reg = <0x4003d000 0x1000>;
+ clocks = <&clks VF610_CLK_TCON0>;
+ clock-names = "tcon";
+};
diff --git a/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi b/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi
index 80e8fbc6b0aa..192b65a0cebb 100644
--- a/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi
+++ b/arch/arm/boot/dts/vf-colibri-eval-v3.dtsi
@@ -26,6 +26,111 @@
status = "okay";
};
+&dcu0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_dcu0_1>;
+ display = <&display>;
+ status = "okay";
+
+ display: display@0 {
+ bits-per-pixel = <16>;
+
+ display-timings {
+ native-mode = <&timing_vga>;
+ /* Standard VGA timing */
+ timing_vga: 640x480 {
+ clock-frequency = <25175000>;
+ hactive = <640>;
+ vactive = <480>;
+ hback-porch = <40>;
+ hfront-porch = <24>;
+ vback-porch = <32>;
+ vfront-porch = <11>;
+ hsync-len = <96>;
+ vsync-len = <2>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pixelclk-active = <0>;
+ };
+ /* WVGA Timing, e.g. EDT ET070080DH6 */
+ timing_wvga: 800x480 {
+ clock-frequency = <33260000>;
+ hactive = <800>;
+ vactive = <480>;
+ hback-porch = <216>;
+ hfront-porch = <40>;
+ vback-porch = <35>;
+ vfront-porch = <10>;
+ hsync-len = <128>;
+ vsync-len = <2>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pixelclk-active = <0>;
+ };
+ /* WVGA Timing, TouchRevolution Fusion 7" */
+ timing_wvga2: 800x480pixclkact {
+ clock-frequency = <33260000>;
+ hactive = <800>;
+ vactive = <480>;
+ hback-porch = <216>;
+ hfront-porch = <40>;
+ vback-porch = <35>;
+ vfront-porch = <10>;
+ hsync-len = <128>;
+ vsync-len = <2>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pixelclk-active = <1>;
+ };
+ /* Standard SVGA timing */
+ timing_svga: 800x600 {
+ clock-frequency = <40000000>;
+ hactive = <800>;
+ vactive = <600>;
+ hback-porch = <88>;
+ hfront-porch = <40>;
+ vback-porch = <23>;
+ vfront-porch = <1>;
+ hsync-len = <128>;
+ vsync-len = <4>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ pixelclk-active = <0>;
+ };
+ /* TouchRevolution Fusion 10"/CLAA101NC05 10.1 inch */
+ timing_wsvga: 1024x600 {
+ clock-frequency = <48000000>;
+ hactive = <1024>;
+ vactive = <600>;
+ hback-porch = <104>;
+ hfront-porch = <43>;
+ vback-porch = <24>;
+ vfront-porch = <20>;
+ hsync-len = <5>;
+ vsync-len = <5>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pixelclk-active = <0>;
+ };
+ /* Standard XGA timing */
+ timing_xga: 1024x768 {
+ clock-frequency = <65000000>;
+ hactive = <1024>;
+ vactive = <768>;
+ hback-porch = <160>;
+ hfront-porch = <24>;
+ vback-porch = <29>;
+ vfront-porch = <3>;
+ hsync-len = <136>;
+ vsync-len = <6>;
+ hsync-active = <0>;
+ vsync-active = <0>;
+ pixelclk-active = <0>;
+ };
+ };
+ };
+};
+
&fec1 {
phy-mode = "rmii";
pinctrl-names = "default";
@@ -41,6 +146,10 @@
status = "okay";
};
+&tcon0 {
+ status = "okay";
+};
+
&uart0 {
status = "okay";
};
diff --git a/arch/arm/boot/dts/vf-colibri.dtsi b/arch/arm/boot/dts/vf-colibri.dtsi
index 0423a11a0853..96b70b66c13a 100644
--- a/arch/arm/boot/dts/vf-colibri.dtsi
+++ b/arch/arm/boot/dts/vf-colibri.dtsi
@@ -81,6 +81,39 @@
&iomuxc {
vf610-colibri {
+ pinctrl_dcu0_1: dcu0grp_1 {
+ fsl,pins = <
+ VF610_PAD_PTE0__DCU0_HSYNC 0x1902
+ VF610_PAD_PTE1__DCU0_VSYNC 0x1902
+ VF610_PAD_PTE2__DCU0_PCLK 0x1902
+ VF610_PAD_PTE4__DCU0_DE 0x1902
+ VF610_PAD_PTE5__DCU0_R0 0x1902
+ VF610_PAD_PTE6__DCU0_R1 0x1902
+ VF610_PAD_PTE7__DCU0_R2 0x1902
+ VF610_PAD_PTE8__DCU0_R3 0x1902
+ VF610_PAD_PTE9__DCU0_R4 0x1902
+ VF610_PAD_PTE10__DCU0_R5 0x1902
+ VF610_PAD_PTE11__DCU0_R6 0x1902
+ VF610_PAD_PTE12__DCU0_R7 0x1902
+ VF610_PAD_PTE13__DCU0_G0 0x1902
+ VF610_PAD_PTE14__DCU0_G1 0x1902
+ VF610_PAD_PTE15__DCU0_G2 0x1902
+ VF610_PAD_PTE16__DCU0_G3 0x1902
+ VF610_PAD_PTE17__DCU0_G4 0x1902
+ VF610_PAD_PTE18__DCU0_G5 0x1902
+ VF610_PAD_PTE19__DCU0_G6 0x1902
+ VF610_PAD_PTE20__DCU0_G7 0x1902
+ VF610_PAD_PTE21__DCU0_B0 0x1902
+ VF610_PAD_PTE22__DCU0_B1 0x1902
+ VF610_PAD_PTE23__DCU0_B2 0x1902
+ VF610_PAD_PTE24__DCU0_B3 0x1902
+ VF610_PAD_PTE25__DCU0_B4 0x1902
+ VF610_PAD_PTE26__DCU0_B5 0x1902
+ VF610_PAD_PTE27__DCU0_B6 0x1902
+ VF610_PAD_PTE28__DCU0_B7 0x1902
+ >;
+ };
+
pinctrl_gpio_ext: gpio_ext {
fsl,pins = <
VF610_PAD_PTD10__GPIO_89 0x22ed /* EXT_IO_0 */
diff --git a/arch/arm/boot/dts/vf500.dtsi b/arch/arm/boot/dts/vf500.dtsi
index 4f333b5b295b..303aba6cb115 100644
--- a/arch/arm/boot/dts/vf500.dtsi
+++ b/arch/arm/boot/dts/vf500.dtsi
@@ -231,6 +231,14 @@
status = "disabled";
};
+ tcon0: tcon@4003d000 {
+ compatible = "fsl,vf610-tcon";
+ reg = <0x4003d000 0x1000>;
+ clocks = <&clks VF610_CLK_TCON0>;
+ clock-names = "tcon";
+ status = "disabled";
+ };
+
wdog@4003e000 {
compatible = "fsl,vf610-wdt", "fsl,imx21-wdt";
reg = <0x4003e000 0x1000>;
@@ -332,6 +340,16 @@
fsl,anatop = <&anatop>;
};
+ dcu0: dcu@40058000 {
+ compatible = "fsl,vf610-dcu";
+ reg = <0x40058000 0x1200>;
+ interrupts = <0 30 0x04>;
+ clocks = <&clks VF610_CLK_DCU0>;
+ clock-names = "dcu";
+ tcon-controller = <&tcon0>;
+ status = "disabled";
+ };
+
i2c0: i2c@40066000 {
#address-cells = <1>;
#size-cells = <0>;
diff --git a/arch/arm/boot/dts/vf610-colibri-eval-v3.dts b/arch/arm/boot/dts/vf610-colibri-eval-v3.dts
index 10ebe99e2751..6101717e4742 100644
--- a/arch/arm/boot/dts/vf610-colibri-eval-v3.dts
+++ b/arch/arm/boot/dts/vf610-colibri-eval-v3.dts
@@ -14,4 +14,4 @@
/ {
model = "Toradex Colibri VF61 on Colibri Evaluation Board";
compatible = "toradex,vf610-colibri_vf61-on-eval", "toradex,vf610-colibri_vf61", "fsl,vf610";
-}; \ No newline at end of file
+};
diff --git a/arch/arm/boot/dts/vf610-colibri.dtsi b/arch/arm/boot/dts/vf610-colibri.dtsi
index 4a9ceffd3318..19fe045b8334 100644
--- a/arch/arm/boot/dts/vf610-colibri.dtsi
+++ b/arch/arm/boot/dts/vf610-colibri.dtsi
@@ -22,4 +22,4 @@
&L2 {
arm,data-latency = <2 1 2>;
arm,tag-latency = <3 2 3>;
-}; \ No newline at end of file
+};
diff --git a/arch/arm/boot/dts/vf610-twr.dts b/arch/arm/boot/dts/vf610-twr.dts
index 3fe8a8ffe114..501a2614f2da 100644
--- a/arch/arm/boot/dts/vf610-twr.dts
+++ b/arch/arm/boot/dts/vf610-twr.dts
@@ -120,6 +120,34 @@
status = "okay";
};
+&dcu0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_dcu0_1>;
+ display = <&display>;
+ status = "okay";
+
+ display: display@0 {
+ bits-per-pixel = <24>;
+
+ display-timings {
+ native-mode = <&timing0>;
+ timing0: nl4827hc19 {
+ clock-frequency = <10870000>;
+ hactive = <480>;
+ vactive = <272>;
+ hback-porch = <2>;
+ hfront-porch = <2>;
+ vback-porch = <1>;
+ vfront-porch = <1>;
+ hsync-len = <41>;
+ vsync-len = <2>;
+ hsync-active = <1>;
+ vsync-active = <1>;
+ };
+ };
+ };
+};
+
&fec0 {
phy-mode = "rmii";
pinctrl-names = "default";
@@ -158,6 +186,39 @@
>;
};
+ pinctrl_dcu0_1: dcu0grp_1 {
+ fsl,pins = <
+ VF610_PAD_PTE0__DCU0_HSYNC 0x42
+ VF610_PAD_PTE1__DCU0_VSYNC 0x42
+ VF610_PAD_PTE2__DCU0_PCLK 0x42
+ VF610_PAD_PTE4__DCU0_DE 0x42
+ VF610_PAD_PTE5__DCU0_R0 0x42
+ VF610_PAD_PTE6__DCU0_R1 0x42
+ VF610_PAD_PTE7__DCU0_R2 0x42
+ VF610_PAD_PTE8__DCU0_R3 0x42
+ VF610_PAD_PTE9__DCU0_R4 0x42
+ VF610_PAD_PTE10__DCU0_R5 0x42
+ VF610_PAD_PTE11__DCU0_R6 0x42
+ VF610_PAD_PTE12__DCU0_R7 0x42
+ VF610_PAD_PTE13__DCU0_G0 0x42
+ VF610_PAD_PTE14__DCU0_G1 0x42
+ VF610_PAD_PTE15__DCU0_G2 0x42
+ VF610_PAD_PTE16__DCU0_G3 0x42
+ VF610_PAD_PTE17__DCU0_G4 0x42
+ VF610_PAD_PTE18__DCU0_G5 0x42
+ VF610_PAD_PTE19__DCU0_G6 0x42
+ VF610_PAD_PTE20__DCU0_G7 0x42
+ VF610_PAD_PTE21__DCU0_B0 0x42
+ VF610_PAD_PTE22__DCU0_B1 0x42
+ VF610_PAD_PTE23__DCU0_B2 0x42
+ VF610_PAD_PTE24__DCU0_B3 0x42
+ VF610_PAD_PTE25__DCU0_B4 0x42
+ VF610_PAD_PTE26__DCU0_B5 0x42
+ VF610_PAD_PTE27__DCU0_B6 0x42
+ VF610_PAD_PTE28__DCU0_B7 0x42
+ >;
+ };
+
pinctrl_dspi0: dspi0grp {
fsl,pins = <
VF610_PAD_PTB19__DSPI0_CS0 0x1182
@@ -265,6 +326,10 @@
status = "okay";
};
+&tcon0 {
+ status = "okay";
+};
+
&uart1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
diff --git a/arch/arm/mach-imx/clk-vf610.c b/arch/arm/mach-imx/clk-vf610.c
index a17818475050..3c4b88e32c45 100644
--- a/arch/arm/mach-imx/clk-vf610.c
+++ b/arch/arm/mach-imx/clk-vf610.c
@@ -261,6 +261,8 @@ static void __init vf610_clocks_init(struct device_node *ccm_node)
clk[VF610_CLK_DCU1_DIV] = imx_clk_divider("dcu1_div", "dcu1_en", CCM_CSCDR3, 20, 3);
clk[VF610_CLK_DCU1] = imx_clk_gate2("dcu1", "dcu1_div", CCM_CCGR9, CCM_CCGRx_CGn(8));
+ clk[VF610_CLK_TCON0] = imx_clk_gate2("tcon0", "platform_bus", CCM_CCGR1, CCM_CCGRx_CGn(13));
+
clk[VF610_CLK_ESAI_SEL] = imx_clk_mux("esai_sel", CCM_CSCMR1, 20, 2, esai_sels, 4);
clk[VF610_CLK_ESAI_EN] = imx_clk_gate("esai_en", "esai_sel", CCM_CSCDR2, 30);
clk[VF610_CLK_ESAI_DIV] = imx_clk_divider("esai_div", "esai_en", CCM_CSCDR2, 24, 4);
@@ -336,6 +338,9 @@ static void __init vf610_clocks_init(struct device_node *ccm_node)
clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_AUDIO_EXT]);
clk_set_parent(clk[VF610_CLK_SAI3_SEL], clk[VF610_CLK_AUDIO_EXT]);
+ clk_set_parent(clk[VF610_CLK_DCU0_SEL], clk[VF610_CLK_PLL1_PFD2]);
+ clk_set_rate(clk[VF610_CLK_DCU0_DIV], 113200000);
+
for (i = 0; i < ARRAY_SIZE(clks_init_on); i++)
clk_prepare_enable(clk[clks_init_on[i]]);
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index e911b9c96e19..163f62463cfa 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -1972,6 +1972,17 @@ config FB_FSL_DIU
---help---
Framebuffer driver for the Freescale SoC DIU
+config FB_FSL_DCU
+ tristate "Freescale DCU framebuffer support"
+ depends on FB
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ select FB_MODE_HELPERS
+ select VIDEOMODE_HELPERS
+ ---help---
+ Framebuffer driver for the Freescale SoC DCU
+
config FB_W100
tristate "W100 frame buffer support"
depends on FB && ARCH_PXA
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index 1979afffccfe..471695d7b051 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_FB_IMX) += imxfb.o
obj-$(CONFIG_FB_S3C) += s3c-fb.o
obj-$(CONFIG_FB_S3C2410) += s3c2410fb.o
obj-$(CONFIG_FB_FSL_DIU) += fsl-diu-fb.o
+obj-$(CONFIG_FB_FSL_DCU) += fsl-dcu-fb.o
obj-$(CONFIG_FB_COBALT) += cobalt_lcdfb.o
obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o
obj-$(CONFIG_FB_PS3) += ps3fb.o
diff --git a/drivers/video/fbdev/fsl-dcu-fb.c b/drivers/video/fbdev/fsl-dcu-fb.c
new file mode 100644
index 000000000000..7593b7f84bf4
--- /dev/null
+++ b/drivers/video/fbdev/fsl-dcu-fb.c
@@ -0,0 +1,1174 @@
+/*
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU framebuffer device driver
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+#include <linux/pm_runtime.h>
+
+#define DRIVER_NAME "fsl-dcu-fb"
+
+#define DCU_DCU_MODE 0x0010
+#define DCU_MODE_BLEND_ITER(x) ((x) << 20)
+#define DCU_MODE_RASTER_EN (1 << 14)
+#define DCU_MODE_DCU_MODE(x) (x)
+#define DCU_MODE_DCU_MODE_MASK 0x03
+#define DCU_MODE_OFF 0
+#define DCU_MODE_NORMAL 1
+#define DCU_MODE_TEST 2
+#define DCU_MODE_COLORBAR 3
+
+#define DCU_BGND 0x0014
+#define DCU_BGND_R(x) ((x) << 16)
+#define DCU_BGND_G(x) ((x) << 8)
+#define DCU_BGND_B(x) (x)
+
+#define DCU_DISP_SIZE 0x0018
+#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16)
+#define DCU_DISP_SIZE_DELTA_X(x) (x)
+
+#define DCU_HSYN_PARA 0x001c
+#define DCU_HSYN_PARA_BP(x) ((x) << 22)
+#define DCU_HSYN_PARA_PW(x) ((x) << 11)
+#define DCU_HSYN_PARA_FP(x) (x)
+
+#define DCU_VSYN_PARA 0x0020
+#define DCU_VSYN_PARA_BP(x) ((x) << 22)
+#define DCU_VSYN_PARA_PW(x) ((x) << 11)
+#define DCU_VSYN_PARA_FP(x) (x)
+
+#define DCU_SYN_POL 0x0024
+#define DCU_SYN_POL_INV_PXCK_FALL (0 << 6)
+#define DCU_SYN_POL_NEG_REMAIN (0 << 5)
+#define DCU_SYN_POL_INV_VS_LOW (1 << 1)
+#define DCU_SYN_POL_INV_HS_LOW (1)
+
+#define DCU_THRESHOLD 0x0028
+#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16)
+#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8)
+#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x)
+
+#define DCU_INT_STATUS 0x002C
+#define DCU_INT_STATUS_UNDRUN (1 << 1)
+
+#define DCU_INT_MASK 0x0030
+#define DCU_INT_MASK_UNDRUN (1 << 1)
+
+#define DCU_DIV_RATIO 0x0054
+
+#define DCU_UPDATE_MODE 0x00cc
+#define DCU_UPDATE_MODE_MODE (1 << 31)
+#define DCU_UPDATE_MODE_READREG (1 << 30)
+
+#define DCU_CTRLDESCLN_1(x) (0x200 + (x) * 0x40)
+#define DCU_CTRLDESCLN_1_HEIGHT(x) ((x) << 16)
+#define DCU_CTRLDESCLN_1_WIDTH(x) (x)
+
+#define DCU_CTRLDESCLN_2(x) (0x204 + (x) * 0x40)
+#define DCU_CTRLDESCLN_2_POSY(x) ((x) << 16)
+#define DCU_CTRLDESCLN_2_POSX(x) (x)
+
+#define DCU_CTRLDESCLN_3(x) (0x208 + (x) * 0x40)
+
+#define DCU_CTRLDESCLN_4(x) (0x20c + (x) * 0x40)
+#define DCU_CTRLDESCLN_4_EN (1 << 31)
+#define DCU_CTRLDESCLN_4_TILE_EN (1 << 30)
+#define DCU_CTRLDESCLN_4_DATA_SEL_CLUT (1 << 29)
+#define DCU_CTRLDESCLN_4_SAFETY_EN (1 << 28)
+#define DCU_CTRLDESCLN_4_TRANS(x) ((x) << 20)
+#define DCU_CTRLDESCLN_4_BPP(x) ((x) << 16)
+#define DCU_CTRLDESCLN_4_RLE_EN (1 << 15)
+#define DCU_CTRLDESCLN_4_LUOFFS(x) ((x) << 4)
+#define DCU_CTRLDESCLN_4_BB_ON (1 << 2)
+#define DCU_CTRLDESCLN_4_AB(x) (x)
+
+#define DCU_CTRLDESCLN_5(x) (0x210 + (x) * 0x40)
+#define DCU_CTRLDESCLN_5_CKMAX_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_5_CKMAX_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_5_CKMAX_B(x) (x)
+
+#define DCU_CTRLDESCLN_6(x) (0x214 + (x) * 0x40)
+#define DCU_CTRLDESCLN_6_CKMIN_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_6_CKMIN_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_6_CKMIN_B(x) (x)
+
+#define DCU_CTRLDESCLN_7(x) (0x218 + (x) * 0x40)
+#define DCU_CTRLDESCLN_7_TILE_VER(x) ((x) << 16)
+#define DCU_CTRLDESCLN_7_TILE_HOR(x) (x)
+
+#define DCU_CTRLDESCLN_8(x) (0x21c + (x) * 0x40)
+#define DCU_CTRLDESCLN_8_FG_FCOLOR(x) (x)
+
+#define DCU_CTRLDESCLN_9(x) (0x220 + (x) * 0x40)
+#define DCU_CTRLDESCLN_9_BG_BCOLOR(x) (x)
+
+#define DCU_TOTAL_LAYER_NUM 64
+#define DCU_LAYER_NUM_MAX 6
+
+#define BPP_16_RGB565 4
+#define BPP_24_RGB888 5
+#define BPP_32_ARGB8888 6
+
+#define TCON_CTRL1 0x0000
+#define TCON_BYPASS_ENABLE (1 << 29)
+
+#define MFB_SET_ALPHA _IOW('M', 0, __u8)
+#define MFB_GET_ALPHA _IOR('M', 0, __u8)
+#define MFB_SET_LAYER _IOW('M', 4, struct layer_display_offset)
+#define MFB_GET_LAYER _IOR('M', 4, struct layer_display_offset)
+
+struct dcu_fb_data {
+ struct fb_info *fsl_dcu_info[DCU_LAYER_NUM_MAX];
+ struct device *dev;
+ void __iomem *reg_base;
+ unsigned int irq;
+ struct clk *clk;
+ struct fb_videomode *mode_db;
+ int modecnt;
+ struct fb_videomode native_mode;
+ u32 bits_per_pixel;
+ bool clk_pol_negedge;
+};
+
+struct layer_display_offset {
+ int x_layer_d;
+ int y_layer_d;
+};
+
+struct mfb_info {
+ int index;
+ char *id;
+ unsigned long pseudo_palette[16];
+ unsigned char alpha;
+ unsigned char blend;
+ unsigned int count;
+ int x_layer_d; /* layer display x offset to physical screen */
+ int y_layer_d; /* layer display y offset to physical screen */
+ struct dcu_fb_data *parent;
+};
+
+enum mfb_index {
+ LAYER0 = 0,
+ LAYER1,
+ LAYER2,
+ LAYER3,
+ LAYER4,
+ LAYER5,
+};
+
+static struct mfb_info mfb_template[] = {
+ {
+ .index = LAYER0,
+ .id = "Layer0",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 0,
+ .y_layer_d = 0,
+ },
+ {
+ .index = LAYER1,
+ .id = "Layer1",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 50,
+ .y_layer_d = 50,
+ },
+ {
+ .index = LAYER2,
+ .id = "Layer2",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 100,
+ .y_layer_d = 100,
+ },
+ {
+ .index = LAYER3,
+ .id = "Layer3",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 150,
+ .y_layer_d = 150,
+ },
+ {
+ .index = LAYER4,
+ .id = "Layer4",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 200,
+ .y_layer_d = 200,
+ },
+ {
+ .index = LAYER5,
+ .id = "Layer5",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 250,
+ .y_layer_d = 250,
+ },
+};
+
+static int enable_panel(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int bpp;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(var->yres) |
+ DCU_CTRLDESCLN_1_WIDTH(var->xres),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(mfbi->y_layer_d) |
+ DCU_CTRLDESCLN_2_POSX(mfbi->x_layer_d),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ writel(info->fix.smem_start,
+ dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ bpp = BPP_16_RGB565;
+ break;
+ case 24:
+ bpp = BPP_24_RGB888;
+ break;
+ case 32:
+ bpp = BPP_32_ARGB8888;
+ break;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ writel(DCU_CTRLDESCLN_4_EN |
+ DCU_CTRLDESCLN_4_TRANS(mfbi->alpha) |
+ DCU_CTRLDESCLN_4_BPP(bpp) |
+ DCU_CTRLDESCLN_4_AB(mfbi->blend),
+ dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0xff),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+ return 0;
+}
+
+static int disable_panel(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(0) |
+ DCU_CTRLDESCLN_1_WIDTH(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(0) | DCU_CTRLDESCLN_2_POSX(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+ return 0;
+}
+
+static void enable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int dcu_mode;
+
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static void disable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ writel(DCU_MODE_DCU_MODE(DCU_MODE_OFF),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static int fsl_dcu_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if (var->xoffset + info->var.xres > info->var.xres_virtual)
+ var->xoffset = info->var.xres_virtual - info->var.xres;
+
+ if (var->yoffset + info->var.yres > info->var.yres_virtual)
+ var->yoffset = info->var.yres_virtual - info->var.yres;
+
+ switch (var->bits_per_pixel) {
+ 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;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_calc_div(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned long long div;
+ div = (unsigned long long)(clk_get_rate(dcufb->clk) / 1000);
+ div *= info->var.pixclock;
+ do_div(div, 1000000000);
+
+ return div;
+}
+
+static void update_controller(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int div, pol = 0;
+
+ div = fsl_dcu_calc_div(info);
+ writel((div - 1), dcufb->reg_base + DCU_DIV_RATIO);
+
+ writel(DCU_DISP_SIZE_DELTA_Y(var->yres) |
+ DCU_DISP_SIZE_DELTA_X(var->xres / 16),
+ dcufb->reg_base + DCU_DISP_SIZE);
+
+ /* Horizontal and vertical sync parameters */
+ writel(DCU_HSYN_PARA_BP(var->left_margin) |
+ DCU_HSYN_PARA_PW(var->hsync_len) |
+ DCU_HSYN_PARA_FP(var->right_margin),
+ dcufb->reg_base + DCU_HSYN_PARA);
+
+ writel(DCU_VSYN_PARA_BP(var->upper_margin) |
+ DCU_VSYN_PARA_PW(var->vsync_len) |
+ DCU_VSYN_PARA_FP(var->lower_margin),
+ dcufb->reg_base + DCU_VSYN_PARA);
+
+ /* Reference Manual is wrong, INV_PXCK => 1 means falling edge! */
+ if (dcufb->clk_pol_negedge)
+ pol |= DCU_SYN_POL_INV_PXCK_FALL;
+
+ /* hsync:0 => active low => HS_LOW */
+ if (!(var->sync & FB_SYNC_HOR_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_HS_LOW;
+
+ /* vsync:0 => active low => VS_LOW */
+ if (!(var->sync & FB_SYNC_VERT_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_VS_LOW;
+
+ writel(pol, dcufb->reg_base + DCU_SYN_POL);
+
+ writel(DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0),
+ dcufb->reg_base + DCU_BGND);
+
+ writel(DCU_MODE_BLEND_ITER(DCU_LAYER_NUM_MAX) | DCU_MODE_RASTER_EN,
+ dcufb->reg_base + DCU_DCU_MODE);
+
+ writel(DCU_THRESHOLD_LS_BF_VS(0x3) | DCU_THRESHOLD_OUT_BUF_HIGH(0x78) |
+ DCU_THRESHOLD_OUT_BUF_LOW(0), dcufb->reg_base + DCU_THRESHOLD);
+}
+
+static int map_video_memory(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ u32 smem_len = info->fix.line_length * info->var.yres_virtual;
+
+ info->fix.smem_len = smem_len;
+
+ info->screen_base = dma_alloc_writecombine(info->device,
+ info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start,
+ GFP_KERNEL);
+ if (!info->screen_base) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+
+ memset(info->screen_base, 0, info->fix.smem_len);
+
+ return 0;
+}
+
+static void unmap_video_memory(struct fb_info *info)
+{
+ if (!info->screen_base)
+ return;
+
+ dma_free_writecombine(info->device, info->fix.smem_len,
+ info->screen_base, info->fix.smem_start);
+
+ info->screen_base = NULL;
+ info->fix.smem_start = 0;
+ info->fix.smem_len = 0;
+}
+
+static int fsl_dcu_set_layer(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct fb_var_screeninfo *var = &info->var;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ int pixel_offset;
+ unsigned long addr;
+
+ pixel_offset = (var->yoffset * var->xres_virtual) + var->xoffset;
+ addr = info->fix.smem_start +
+ (pixel_offset * (var->bits_per_pixel >> 3));
+
+ writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+
+ return 0;
+}
+
+static int fsl_dcu_set_par(struct fb_info *info)
+{
+ unsigned long len;
+ struct fb_var_screeninfo *var = &info->var;
+ struct fb_fix_screeninfo *fix = &info->fix;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->accel = FB_ACCEL_NONE;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ypanstep = 1;
+
+ len = info->var.yres_virtual * info->fix.line_length;
+ if (len != info->fix.smem_len) {
+ if (info->fix.smem_start)
+ unmap_video_memory(info);
+
+ if (map_video_memory(info)) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Only layer 0 could update LCD controller */
+ if (mfbi->index == LAYER0) {
+ update_controller(info);
+ enable_controller(info);
+ }
+
+ enable_panel(info);
+ return 0;
+}
+
+static inline __u32 CNVT_TOHW(__u32 val, __u32 width)
+{
+ return ((val<<width) + 0x7FFF - val) >> 16;
+}
+
+static int fsl_dcu_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info)
+{
+ unsigned int val;
+ int ret = -EINVAL;
+
+ /*
+ * 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;
+
+ red = CNVT_TOHW(red, info->var.red.length);
+ green = CNVT_TOHW(green, info->var.green.length);
+ blue = CNVT_TOHW(blue, info->var.blue.length);
+ transp = CNVT_TOHW(transp, info->var.transp.length);
+
+ val = (red << info->var.red.offset) |
+ (green << info->var.green.offset) |
+ (blue << info->var.blue.offset) |
+ (transp << info->var.transp.offset);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+
+ return ret;
+}
+
+static int fsl_dcu_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ if ((info->var.xoffset == var->xoffset) &&
+ (info->var.yoffset == var->yoffset))
+ return 0;
+
+ if ((var->xoffset + info->var.xres) > info->var.xres_virtual
+ || (var->yoffset + info->var.yres) > info->var.yres_virtual)
+ return -EINVAL;
+
+ info->var.xoffset = var->xoffset;
+ info->var.yoffset = var->yoffset;
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+
+ fsl_dcu_set_layer(info);
+
+ return 0;
+}
+
+static int fsl_dcu_blank(int blank_mode, struct fb_info *info)
+{
+ switch (blank_mode) {
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ disable_panel(info);
+ break;
+ case FB_BLANK_POWERDOWN:
+ disable_controller(info);
+ break;
+ case FB_BLANK_UNBLANK:
+ enable_panel(info);
+ break;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct layer_display_offset layer_d;
+ void __user *buf = (void __user *)arg;
+ unsigned char alpha;
+
+ switch (cmd) {
+ case MFB_SET_LAYER:
+ if (copy_from_user(&layer_d, buf, sizeof(layer_d)))
+ return -EFAULT;
+ mfbi->x_layer_d = layer_d.x_layer_d;
+ mfbi->y_layer_d = layer_d.y_layer_d;
+ fsl_dcu_set_par(info);
+ break;
+ case MFB_GET_LAYER:
+ layer_d.x_layer_d = mfbi->x_layer_d;
+ layer_d.y_layer_d = mfbi->y_layer_d;
+ if (copy_to_user(buf, &layer_d, sizeof(layer_d)))
+ return -EFAULT;
+ break;
+ case MFB_GET_ALPHA:
+ alpha = mfbi->alpha;
+ if (copy_to_user(buf, &alpha, sizeof(alpha)))
+ return -EFAULT;
+ break;
+ case MFB_SET_ALPHA:
+ if (copy_from_user(&alpha, buf, sizeof(alpha)))
+ return -EFAULT;
+ mfbi->blend = 1;
+ mfbi->alpha = alpha;
+ fsl_dcu_set_par(info);
+ break;
+ default:
+ dev_err(dcufb->dev, "unknown ioctl command (0x%08X)\n", cmd);
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static void reset_total_layers(struct dcu_fb_data *dcufb)
+{
+ int i;
+
+ for (i = 1; i < DCU_TOTAL_LAYER_NUM; i++) {
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_1(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_2(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_5(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_6(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_7(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_8(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_9(i));
+ }
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+}
+
+static int fsl_dcu_open(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ u32 int_mask = readl(dcufb->reg_base + DCU_INT_MASK);
+ int ret = 0;
+
+ mfbi->index = info->node;
+
+ mfbi->count++;
+ if (mfbi->count == 1) {
+ fsl_dcu_check_var(&info->var, info);
+ ret = fsl_dcu_set_par(info);
+ if (ret < 0)
+ mfbi->count--;
+ else
+ writel(int_mask & ~DCU_INT_MASK_UNDRUN,
+ dcufb->reg_base + DCU_INT_MASK);
+ }
+
+ return ret;
+}
+
+static int fsl_dcu_release(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ int ret = 0;
+
+ mfbi->count--;
+ if (mfbi->count == 0)
+ ret = disable_panel(info);
+
+ return ret;
+}
+
+static struct fb_ops fsl_dcu_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = fsl_dcu_check_var,
+ .fb_set_par = fsl_dcu_set_par,
+ .fb_setcolreg = fsl_dcu_setcolreg,
+ .fb_blank = fsl_dcu_blank,
+ .fb_pan_display = fsl_dcu_pan_display,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_ioctl = fsl_dcu_ioctl,
+ .fb_open = fsl_dcu_open,
+ .fb_release = fsl_dcu_release,
+};
+
+static int fsl_dcu_init_modelist(struct dcu_fb_data *dcufb)
+{
+ struct device_node *np = dcufb->dev->of_node;
+ struct device_node *display_np;
+ struct display_timings *timings;
+ int i;
+ int ret = 0;
+
+ display_np = of_parse_phandle(np, "display", 0);
+ if (!display_np) {
+ dev_err(dcufb->dev, "failed to find display phandle\n");
+ return -ENOENT;
+ }
+
+ ret = of_property_read_u32(display_np, "bits-per-pixel",
+ &dcufb->bits_per_pixel);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to get property bits-per-pixel\n");
+ goto put_display_node;
+ }
+
+ timings = of_get_display_timings(display_np);
+ if (!timings) {
+ dev_err(dcufb->dev, "failed to get display timings\n");
+ ret = -ENOENT;
+ goto put_display_node;
+ }
+
+ dcufb->mode_db = devm_kzalloc(dcufb->dev, sizeof(struct fb_videomode) *
+ timings->num_timings, GFP_KERNEL);
+ if (!dcufb->mode_db) {
+ ret = -ENOMEM;
+ goto put_display_node;
+ }
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct videomode vm;
+
+ ret = videomode_from_timings(timings, &vm, i);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ ret = fb_videomode_from_videomode(&vm, &dcufb->mode_db[i]);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ if (i == timings->native_mode) {
+ fb_videomode_from_videomode(&vm, &dcufb->native_mode);
+ dcufb->clk_pol_negedge = timings->timings[i]->flags &
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+ }
+
+ dcufb->modecnt++;
+ }
+
+ of_node_put(display_np);
+ return 0;
+
+free_dcu_mode_db:
+ kfree(dcufb->mode_db);
+ dcufb->mode_db = NULL;
+put_display_node:
+ of_node_put(display_np);
+ return ret;
+}
+
+static int parse_opt(struct dcu_fb_data *dcufb, char *this_opt, u32 *sync)
+{
+ if (!strncmp(this_opt, "hsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_HOR_HIGH_ACT;
+ } else if (!strncmp(this_opt, "vsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_VERT_HIGH_ACT;
+ } else if (!strncmp(this_opt, "pixclockpol:", 12))
+ dcufb->clk_pol_negedge =
+ !!simple_strtoul(this_opt+12, NULL, 0);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int fsl_dcu_parse_options(struct dcu_fb_data *dcufb,
+ struct fb_info *info, char *option)
+{
+ char *this_opt;
+ int ret = 0;
+ u32 sync = 0;
+
+ while ((this_opt = strsep(&option, ",")) != NULL) {
+ /* Parse driver specific arguments */
+ if (parse_opt(dcufb, this_opt, &sync) == 0)
+ continue;
+
+ /* No valid driver specific argument, has to be mode */
+ ret = fb_find_mode(&info->var, info, this_opt, dcufb->mode_db,
+ dcufb->modecnt, &dcufb->native_mode,
+ dcufb->bits_per_pixel);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Overwrite from command line */
+ info->var.sync = sync;
+ return 0;
+}
+
+static int install_framebuffer(struct fb_info *info, char *option)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct fb_videomode *mode = &dcufb->native_mode;
+ int ret;
+
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->fbops = &fsl_dcu_ops;
+ info->flags = FBINFO_FLAG_DEFAULT;
+ info->pseudo_palette = &mfbi->pseudo_palette;
+
+ fb_alloc_cmap(&info->cmap, 16, 0);
+
+ INIT_LIST_HEAD(&info->modelist);
+ fb_add_videomode(mode, &info->modelist);
+ fb_videomode_to_var(&info->var, mode);
+ info->var.bits_per_pixel = dcufb->bits_per_pixel;
+
+ /* Parse DCU option for every layer... */
+ ret = fsl_dcu_parse_options(dcufb, info, option);
+ if (ret < 0)
+ return ret;
+
+ fsl_dcu_check_var(&info->var, info);
+ ret = register_framebuffer(info);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to register framebuffer device\n");
+ return ret;
+ }
+
+ printk(KERN_INFO "fb%d: fb device registered successfully.\n",
+ info->node);
+ return 0;
+}
+
+static void uninstall_framebuffer(struct fb_info *info)
+{
+ unregister_framebuffer(info);
+ unmap_video_memory(info);
+
+ if (&info->cmap)
+ fb_dealloc_cmap(&info->cmap);
+}
+
+static irqreturn_t fsl_dcu_irq(int irq, void *dev_id)
+{
+ struct dcu_fb_data *dcufb = dev_id;
+ unsigned int status = readl(dcufb->reg_base + DCU_INT_STATUS);
+ u32 dcu_mode;
+
+ if (status & DCU_INT_STATUS_UNDRUN) {
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_OFF),
+ dcufb->reg_base + DCU_DCU_MODE);
+ udelay(1);
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL),
+ dcufb->reg_base + DCU_DCU_MODE);
+ }
+
+ writel(status, dcufb->reg_base + DCU_INT_STATUS);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int fsl_dcu_runtime_suspend(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(dcufb->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_runtime_resume(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_prepare_enable(dcufb->clk);
+
+ return 0;
+}
+#endif
+
+static int bypass_tcon(struct device_node *np)
+{
+ struct device_node *tcon_np;
+ struct platform_device *tcon_pdev;
+ struct clk *tcon_clk;
+ struct resource *res;
+ void __iomem *tcon_reg;
+
+ tcon_np = of_parse_phandle(np, "tcon-controller", 0);
+ if (!tcon_np)
+ return -EINVAL;
+
+ tcon_pdev = of_find_device_by_node(tcon_np);
+ if (!tcon_pdev)
+ return -EINVAL;
+
+ tcon_clk = devm_clk_get(&tcon_pdev->dev, "tcon");
+ if (IS_ERR(tcon_clk))
+ return PTR_ERR(tcon_clk);
+ clk_prepare_enable(tcon_clk);
+
+ res = platform_get_resource(tcon_pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ tcon_reg = devm_ioremap_resource(&tcon_pdev->dev, res);
+ if (IS_ERR(tcon_reg))
+ return PTR_ERR(tcon_reg);
+
+ writel(TCON_BYPASS_ENABLE, tcon_reg + TCON_CTRL1);
+ return 0;
+}
+
+static int fsl_dcu_probe(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb;
+ struct mfb_info *mfbi;
+ struct resource *res;
+ int ret = 0;
+ int i;
+ char *option = NULL;
+
+ fb_get_options("dcufb", &option);
+
+ if (option != NULL) {
+ dev_info(&pdev->dev, "using cmd options: %s\n", option);
+ if (!strcmp(option, "off"))
+ return -ENODEV;
+ }
+
+ dcufb = devm_kzalloc(&pdev->dev,
+ sizeof(struct dcu_fb_data), GFP_KERNEL);
+ if (!dcufb)
+ return -ENOMEM;
+
+ dcufb->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dcufb);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get memory IO resource\n");
+ return -ENODEV;
+ }
+
+ dcufb->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dcufb->reg_base)) {
+ dev_err(&pdev->dev, "could not ioremap resource\n");
+ return PTR_ERR(dcufb->reg_base);
+ }
+
+ dcufb->irq = platform_get_irq(pdev, 0);
+ if (!dcufb->irq) {
+ dev_err(&pdev->dev, "could not get irq\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(&pdev->dev, dcufb->irq, fsl_dcu_irq,
+ 0, DRIVER_NAME, dcufb);
+ if (ret) {
+ dev_err(&pdev->dev, "could not request irq\n");
+ goto failed_ioremap;
+ }
+
+ /* Put TCON in bypass mode, so the input signals from DCU are passed
+ * through TCON unchanged */
+ ret = bypass_tcon(pdev->dev.of_node);
+ if (ret) {
+ dev_err(&pdev->dev, "could not bypass TCON\n");
+ goto failed_bypasstcon;
+ }
+
+ dcufb->clk = devm_clk_get(&pdev->dev, "dcu");
+ if (IS_ERR(dcufb->clk)) {
+ ret = PTR_ERR(dcufb->clk);
+ dev_err(&pdev->dev, "could not get clock\n");
+ goto failed_getclock;
+ }
+ clk_prepare_enable(dcufb->clk);
+
+ pm_runtime_enable(dcufb->dev);
+ pm_runtime_get_sync(dcufb->dev);
+
+ ret = fsl_dcu_init_modelist(dcufb);
+ if (ret)
+ goto failed_alloc_framebuffer;
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ dcufb->fsl_dcu_info[i] =
+ framebuffer_alloc(sizeof(struct mfb_info), &pdev->dev);
+ if (!dcufb->fsl_dcu_info[i]) {
+ ret = -ENOMEM;
+ goto failed_alloc_framebuffer;
+ }
+
+ dcufb->fsl_dcu_info[i]->fix.smem_start = 0;
+
+ mfbi = dcufb->fsl_dcu_info[i]->par;
+ memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info));
+ mfbi->parent = dcufb;
+
+ ret = install_framebuffer(dcufb->fsl_dcu_info[i], option);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "could not register framebuffer %d\n", i);
+ goto failed_register_framebuffer;
+ }
+ }
+
+ if (option != NULL) {
+ ret = fsl_dcu_parse_options(dcufb, dcufb->fsl_dcu_info[0],
+ option);
+ if (ret < 0)
+ goto failed_alloc_framebuffer;
+ }
+
+ reset_total_layers(mfbi->parent);
+ return 0;
+
+failed_register_framebuffer:
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ if (dcufb->fsl_dcu_info[i])
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+failed_alloc_framebuffer:
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+failed_getclock:
+failed_bypasstcon:
+ free_irq(dcufb->irq, dcufb);
+failed_ioremap:
+ return ret;
+}
+
+static int fsl_dcu_remove(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ pm_runtime_get_sync(dcufb->dev);
+
+ disable_controller(dcufb->fsl_dcu_info[0]);
+
+ clk_disable_unprepare(dcufb->clk);
+ free_irq(dcufb->irq, dcufb);
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ uninstall_framebuffer(dcufb->fsl_dcu_info[i]);
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_dcu_pm_ops = {
+ SET_RUNTIME_PM_OPS(fsl_dcu_runtime_suspend,
+ fsl_dcu_runtime_resume, NULL)
+};
+
+static struct of_device_id fsl_dcu_dt_ids[] = {
+ {
+ .compatible = "fsl,vf610-dcu",
+ },
+ {}
+};
+
+static struct platform_driver fsl_dcu_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_dcu_dt_ids,
+ .pm = &fsl_dcu_pm_ops,
+ },
+ .probe = fsl_dcu_probe,
+ .remove = fsl_dcu_remove,
+};
+
+module_platform_driver(fsl_dcu_driver);
+
+MODULE_AUTHOR("Alison Wang");
+MODULE_DESCRIPTION("Freescale DCU framebuffer driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/dt-bindings/clock/vf610-clock.h b/include/dt-bindings/clock/vf610-clock.h
index d6b56b21539b..63fb8fda32e5 100644
--- a/include/dt-bindings/clock/vf610-clock.h
+++ b/include/dt-bindings/clock/vf610-clock.h
@@ -169,6 +169,7 @@
#define VF610_CLK_PLL7_MAIN 156
#define VF610_CLK_USBPHY0 157
#define VF610_CLK_USBPHY1 158
-#define VF610_CLK_END 159
+#define VF610_CLK_TCON0 159
+#define VF610_CLK_END 160
#endif /* __DT_BINDINGS_CLOCK_VF610_H */