Browse Source

Merge branch 'audio_display-ti-linux-4.19.y' of git.ti.com:~jyrisarha/ti-linux-kernel/jyrisarhas-audio-video-linux-feature-tree into ti-linux-4.19.y

TI-Feature: audio-display
TI-Tree: git@git.ti.com:~jyrisarha/ti-linux-kernel/jyrisarhas-audio-video-linux-feature-tree.git
TI-Branch: audio_display-ti-linux-4.19.y

* 'audio_display-ti-linux-4.19.y' of git.ti.com:~jyrisarha/ti-linux-kernel/jyrisarhas-audio-video-linux-feature-tree:
  ARM: dts: da850-lcdk: Enable the analog mic input
  ARM: dts: da850-lcdk: Correct the sound card name
  ARM: dts: da850-lcdk: Correct the audio codec regulators
  ti_config_fragments: audio_display.cfg: enable CMA
  Input: goodix - put reset and irq into default state during probe
  arm64: dts: ti: Add overlay for OLDI-LCD1EVM Display and touch screen
  arm64: dts: ti: am654: Add DSS node
  arm64: dts: ti: am654: Add Main System Control Module node
  ti_config_fragments: Add TIDSS to audio_display.cfg
  drm/panel: simple: Add Rockteck RK101II01D-CT panel
  MAINTAINERS: add entry for tidss
  drm/tidss: dispc7: Map vidl1 hw plane to plane number 0
  drm/tidss: Add dispc7 for DSS7 support
  drm/tidss: add new driver for TI Keystone platforms
  dt-bindings: display/ti: add am65x-dss bindings
  dt-bindings: display/ti: add k2g-dss bindings

Signed-off-by: LCPD Auto Merger <lcpd_integration@list.ti.com>
LCPD Auto Merger 7 years ago
parent
commit
4f4ecb6f2a
35 changed files with 6200 additions and 22 deletions
  1. 8 0
      Documentation/devicetree/bindings/display/panel/rockteck,rk101ii01d-ct.txt
  2. 16 0
      Documentation/devicetree/bindings/display/ti/ti,am6-dss.txt
  3. 15 0
      Documentation/devicetree/bindings/display/ti/ti,k2g-dss.txt
  4. 1 0
      Documentation/devicetree/bindings/vendor-prefixes.txt
  5. 9 0
      MAINTAINERS
  6. 45 3
      arch/arm/boot/dts/da850-lcdk.dts
  7. 2 1
      arch/arm64/boot/dts/ti/Makefile
  8. 47 0
      arch/arm64/boot/dts/ti/k3-am65-main.dtsi
  9. 74 0
      arch/arm64/boot/dts/ti/k3-am654-evm-oldi-lcd1evm.dtso
  10. 2 0
      drivers/gpu/drm/Kconfig
  11. 1 0
      drivers/gpu/drm/Makefile
  12. 31 0
      drivers/gpu/drm/panel/panel-simple.c
  13. 30 0
      drivers/gpu/drm/tidss/Kconfig
  14. 14 0
      drivers/gpu/drm/tidss/Makefile
  15. 402 0
      drivers/gpu/drm/tidss/tidss_crtc.c
  16. 49 0
      drivers/gpu/drm/tidss/tidss_crtc.h
  17. 141 0
      drivers/gpu/drm/tidss/tidss_dispc.h
  18. 1507 0
      drivers/gpu/drm/tidss/tidss_dispc6.c
  19. 109 0
      drivers/gpu/drm/tidss/tidss_dispc6.h
  20. 2233 0
      drivers/gpu/drm/tidss/tidss_dispc7.c
  21. 204 0
      drivers/gpu/drm/tidss/tidss_dispc7.h
  22. 317 0
      drivers/gpu/drm/tidss/tidss_drv.c
  23. 40 0
      drivers/gpu/drm/tidss/tidss_drv.h
  24. 70 0
      drivers/gpu/drm/tidss/tidss_encoder.c
  25. 17 0
      drivers/gpu/drm/tidss/tidss_encoder.h
  26. 193 0
      drivers/gpu/drm/tidss/tidss_irq.c
  27. 25 0
      drivers/gpu/drm/tidss/tidss_irq.h
  28. 95 0
      drivers/gpu/drm/tidss/tidss_kms.c
  29. 14 0
      drivers/gpu/drm/tidss/tidss_kms.h
  30. 240 0
      drivers/gpu/drm/tidss/tidss_plane.c
  31. 29 0
      drivers/gpu/drm/tidss/tidss_plane.h
  32. 185 0
      drivers/gpu/drm/tidss/tidss_scale_coefs.c
  33. 22 0
      drivers/gpu/drm/tidss/tidss_scale_coefs.h
  34. 6 18
      drivers/input/touchscreen/goodix.c
  35. 7 0
      ti_config_fragments/audio_display.cfg

+ 8 - 0
Documentation/devicetree/bindings/display/panel/rockteck,rk101ii01d-ct.txt

@@ -0,0 +1,8 @@
+Rockteck 10.1" TFT 1280x800 Pixels
+- with LVDS interface, LED Backlight and capacitive touch panel
+
+Required properties:
+- compatible: should be "rockteck,rk101ii01d-ct"
+
+This binding is compatible with the simple-panel binding, which is specified
+in simple-panel.txt in this directory.

+ 16 - 0
Documentation/devicetree/bindings/display/ti/ti,am6-dss.txt

@@ -0,0 +1,16 @@
+Texas Instruments AM65x Display Subsystem
+==========================================
+
+Required properties:
+- compatible: "ti,am65x-dss", "ti,am6-dss"
+- reg: address and length of the register spaces for DSS submodules
+- reg-names: "common", "vidl1", "vid", "ovr1", "ovr2", "vp1", "vp2"
+- clocks: phandle to fclk, vp1, and vp2 clocks
+- clock-names: "fck", "vp1", "vp2"
+- interrupts: phandle to the DISPC interrupt
+- syscon: phandle to syscon device handling OLDI_PWRDN_TX (partition 1 of
+  AM654 CTRL MMR0)
+
+The DSS outputs are described using the device graphs as documented in
+Documentation/devicetree/bindings/graph.txt. AM6 DSS has a DPI output as port 0
+and an OLDI output as port 1.

+ 15 - 0
Documentation/devicetree/bindings/display/ti/ti,k2g-dss.txt

@@ -0,0 +1,15 @@
+Texas Instruments K2G Display Subsystem
+=======================================
+
+Required properties:
+- compatible: "ti,k2g-dss"
+- reg: address and length of the register spaces for DSS submodules
+- reg-names: "cfg", "common", "vid1", "ovr1", "vp1"
+- clocks: phandle to fclk and vp1 clocks
+- clock-names: "fck", "vp1"
+- interrupts: phandle to the DISPC interrupt
+
+The DSS outputs are described using the device graphs as documented in
+Documentation/devicetree/bindings/graph.txt. K2G DSS has a single DPI output as
+port 0.
+

+ 1 - 0
Documentation/devicetree/bindings/vendor-prefixes.txt

@@ -321,6 +321,7 @@ ricoh	Ricoh Co. Ltd.
 rikomagic	Rikomagic Tech Corp. Ltd
 rikomagic	Rikomagic Tech Corp. Ltd
 riscv	RISC-V Foundation
 riscv	RISC-V Foundation
 rockchip	Fuzhou Rockchip Electronics Co., Ltd
 rockchip	Fuzhou Rockchip Electronics Co., Ltd
+rocktech	Rocktech Displays Limited
 rohm	ROHM Semiconductor Co., Ltd
 rohm	ROHM Semiconductor Co., Ltd
 roofull	Shenzhen Roofull Technology Co, Ltd
 roofull	Shenzhen Roofull Technology Co, Ltd
 samsung	Samsung Semiconductor
 samsung	Samsung Semiconductor

+ 9 - 0
MAINTAINERS

@@ -4948,6 +4948,15 @@ F:	include/uapi/drm/v3d_drm.h
 F:	Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.txt
 F:	Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.txt
 T:	git git://anongit.freedesktop.org/drm/drm-misc
 T:	git git://anongit.freedesktop.org/drm/drm-misc
 
 
+DRM DRIVERS FOR TI KEYSTONE
+M:	Tomi Valkeinen <tomi.valkeinen@ti.com>
+R:	Jyri Sarha <jsarha@ti.com>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+F:	drivers/gpu/drm/tidss/
+F:	Documentation/devicetree/bindings/display/ti/ti,k2g-dss.txt
+F:	Documentation/devicetree/bindings/display/ti/ti,am6-dss.txt
+
 DRM DRIVERS FOR VC4
 DRM DRIVERS FOR VC4
 M:	Eric Anholt <eric@anholt.net>
 M:	Eric Anholt <eric@anholt.net>
 T:	git git://github.com/anholt/linux
 T:	git git://github.com/anholt/linux

+ 45 - 3
arch/arm/boot/dts/da850-lcdk.dts

@@ -39,17 +39,51 @@
 		};
 		};
 	};
 	};
 
 
+	vcc_5vd: fixedregulator-vcc_5vd {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc_5vd";
+		regulator-min-microvolt = <5000000>;
+		regulator-max-microvolt = <5000000>;
+		regulator-boot-on;
+	};
+
+	vcc_3v3d: fixedregulator-vcc_3v3d {
+		/* TPS650250 - VDCDC1 */
+		compatible = "regulator-fixed";
+		regulator-name = "vcc_3v3d";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		vin-supply = <&vcc_5vd>;
+		regulator-always-on;
+		regulator-boot-on;
+	};
+
+	vcc_1v8d: fixedregulator-vcc_1v8d {
+		/* TPS650250 - VDCDC2 */
+		compatible = "regulator-fixed";
+		regulator-name = "vcc_1v8d";
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <1800000>;
+		vin-supply = <&vcc_5vd>;
+		regulator-always-on;
+		regulator-boot-on;
+	};
+
 	sound {
 	sound {
 		compatible = "simple-audio-card";
 		compatible = "simple-audio-card";
-		simple-audio-card,name = "DA850/OMAP-L138 LCDK";
+		simple-audio-card,name = "DA850-OMAPL138 LCDK";
 		simple-audio-card,widgets =
 		simple-audio-card,widgets =
 			"Line", "Line In",
 			"Line", "Line In",
-			"Line", "Line Out";
+			"Line", "Line Out",
+			"Microphone", "Mic Jack";
 		simple-audio-card,routing =
 		simple-audio-card,routing =
 			"LINE1L", "Line In",
 			"LINE1L", "Line In",
 			"LINE1R", "Line In",
 			"LINE1R", "Line In",
 			"Line Out", "LLOUT",
 			"Line Out", "LLOUT",
-			"Line Out", "RLOUT";
+			"Line Out", "RLOUT",
+			"MIC3L", "Mic Jack",
+			"MIC3R", "Mic Jack",
+			"Mic Jack", "Mic Bias";
 		simple-audio-card,format = "dsp_b";
 		simple-audio-card,format = "dsp_b";
 		simple-audio-card,bitclock-master = <&link0_codec>;
 		simple-audio-card,bitclock-master = <&link0_codec>;
 		simple-audio-card,frame-master = <&link0_codec>;
 		simple-audio-card,frame-master = <&link0_codec>;
@@ -220,7 +254,15 @@
 		#sound-dai-cells = <0>;
 		#sound-dai-cells = <0>;
 		compatible = "ti,tlv320aic3106";
 		compatible = "ti,tlv320aic3106";
 		reg = <0x18>;
 		reg = <0x18>;
+		adc-settle-ms = <40>;
+		ai3x-micbias-vg = <1>;		/* 2.0V */
 		status = "okay";
 		status = "okay";
+
+		/* Regulators */
+		IOVDD-supply = <&vcc_3v3d>;
+		AVDD-supply = <&vcc_3v3d>;
+		DRVDD-supply = <&vcc_3v3d>;
+		DVDD-supply = <&vcc_1v8d>;
 	};
 	};
 };
 };
 
 

+ 2 - 1
arch/arm64/boot/dts/ti/Makefile

@@ -9,7 +9,8 @@
 DTC_FLAGS += -@
 DTC_FLAGS += -@
 
 
 dtb-$(CONFIG_ARCH_K3_AM6_SOC) += k3-am654-base-board.dtb \
 dtb-$(CONFIG_ARCH_K3_AM6_SOC) += k3-am654-base-board.dtb \
-	k3-am654-gp.dtbo
+	k3-am654-gp.dtbo \
+	k3-am654-evm-oldi-lcd1evm.dtbo
 
 
 dtb-$(CONFIG_ARCH_K3_AM6_SOC) += $(shell grep -s incbin $(srctree)/$(src)/*.its | grep dtb | cut -d "\"" -f 2)
 dtb-$(CONFIG_ARCH_K3_AM6_SOC) += $(shell grep -s incbin $(srctree)/$(src)/*.its | grep dtb | cut -d "\"" -f 2)
 
 

+ 47 - 0
arch/arm64/boot/dts/ti/k3-am65-main.dtsi

@@ -438,4 +438,51 @@
 		clocks = <&k3_clks 39 0>;
 		clocks = <&k3_clks 39 0>;
 		clock-names = "fck";
 		clock-names = "fck";
 	};
 	};
+
+	scm_conf: scm_conf@100000 {
+		compatible = "syscon", "simple-mfd";
+		reg = <0 0x00100000 0 0x1c000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges = <0x0 0x0 0x00100000 0x1c000>;
+	};
+
+	dss: dss@04a00000 {
+		compatible = "ti,am6-dss";
+		reg =	<0x0 0x04a00000 0x0 0x1000>, /* common */
+			<0x0 0x04a02000 0x0 0x1000>, /* vidl1 */
+			<0x0 0x04a06000 0x0 0x1000>, /* vid */
+			<0x0 0x04a07000 0x0 0x1000>, /* ovr1 */
+			<0x0 0x04a08000 0x0 0x1000>, /* ovr2 */
+			<0x0 0x04a0a000 0x0 0x1000>, /* vp1 */
+			<0x0 0x04a0b000 0x0 0x1000>; /* vp2 */
+		reg-names = "common", "vidl1", "vid",
+			"ovr1", "ovr2", "vp1", "vp2";
+
+		syscon = <&scm_conf>;
+
+		power-domains = <&k3_pds 67>;
+
+		clocks =	<&k3_clks 67 1>,
+				<&k3_clks 216 1>,
+				<&k3_clks 67 2>;
+		clock-names = "fck", "vp1", "vp2";
+
+		/*
+		 * Set vp2 clk (DPI_1_IN_CLK) mux to PLL4 via
+		 * DIV1. See "Figure 12-3365. DSS Integration"
+		 * in AM65x TRM for details.
+		 */
+		assigned-clocks = <&k3_clks 67 2>;
+		assigned-clock-parents = <&k3_clks 67 5>;
+
+		interrupts = <GIC_SPI 166 IRQ_TYPE_EDGE_RISING>;
+
+		status = "disabled";
+
+		dss_ports: ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+	};
 };
 };

+ 74 - 0
arch/arm64/boot/dts/ti/k3-am654-evm-oldi-lcd1evm.dtso

@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * OLDI-LCD1EVM Rocktech integrated panel and touch DT overlay for AM654-EVM.
+ *
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/pwm/pwm.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+  fragment@101 {
+	target-path = "/";
+
+	__overlay__ {
+		display0 {
+			compatible = "rockteck,rk101ii01d-ct";
+			backlight = <&lcd_bl>;
+			enable-gpios = <&pca9555 8 GPIO_ACTIVE_HIGH>;
+
+			port {
+				lcd_in0: endpoint {
+					remote-endpoint = <&oldi_out0>;
+				};
+			};
+		};
+
+		lcd_bl: backlight {
+			compatible = "pwm-backlight";
+			pwms = <&ecap0 0 50000 PWM_POLARITY_INVERTED>;
+			brightness-levels = <0 32 64 96 128 160 192 224 255>;
+			default-brightness-level = <8>;
+		};
+	};
+  };
+};
+
+&dss {
+	status = "ok";
+};
+
+&dss_ports {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	port@0 {
+		reg = <0>;
+
+		oldi_out0: endpoint {
+			remote-endpoint = <&lcd_in0>;
+		};
+	};
+};
+
+&main_i2c1 {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	gt928: touchscreen@14 {
+		status = "okay";
+		compatible = "goodix,gt928";
+		reg = <0x14>;
+
+		interrupt-parent = <&pca9554>;
+		interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
+		touchscreen-size-x = <1280>;
+		touchscreen-size-y = <800>;
+
+		reset-gpios = <&pca9555 9 GPIO_ACTIVE_HIGH>;
+		irq-gpios = <&pca9554 3 GPIO_ACTIVE_HIGH>;
+	};
+};

+ 2 - 0
drivers/gpu/drm/Kconfig

@@ -315,6 +315,8 @@ source "drivers/gpu/drm/tve200/Kconfig"
 
 
 source "drivers/gpu/drm/xen/Kconfig"
 source "drivers/gpu/drm/xen/Kconfig"
 
 
+source "drivers/gpu/drm/tidss/Kconfig"
+
 # Keep legacy drivers last
 # Keep legacy drivers last
 
 
 menuconfig DRM_LEGACY
 menuconfig DRM_LEGACY

+ 1 - 0
drivers/gpu/drm/Makefile

@@ -107,3 +107,4 @@ obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
 obj-$(CONFIG_DRM_TVE200) += tve200/
 obj-$(CONFIG_DRM_XEN) += xen/
 obj-$(CONFIG_DRM_XEN) += xen/
+obj-$(CONFIG_DRM_TIDSS) += tidss/

+ 31 - 0
drivers/gpu/drm/panel/panel-simple.c

@@ -1926,6 +1926,34 @@ static const struct panel_desc rocktech_rk070er9427 = {
 	.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
 	.bus_format = MEDIA_BUS_FMT_RGB666_1X18,
 };
 };
 
 
+static const struct drm_display_mode rocktech_rk101ii01d_ct_mode = {
+	.clock = 71100,
+	.hdisplay = 1280,
+	.hsync_start = 1280 + 48,
+	.hsync_end = 1280 + 48 + 32,
+	.htotal = 1280 + 48 + 32 + 80,
+	.vdisplay = 800,
+	.vsync_start = 800 + 2,
+	.vsync_end = 800 + 2 + 5,
+	.vtotal = 800 + 2 + 5 + 16,
+	.vrefresh = 60,
+};
+
+static const struct panel_desc rocktech_rk101ii01d_ct = {
+	.modes = &rocktech_rk101ii01d_ct_mode,
+	.num_modes = 1,
+	.size = {
+		.width = 217,
+		.height = 136,
+	},
+	.delay = {
+		.prepare = 50,
+		.disable = 50,
+	},
+	.bus_flags = DRM_BUS_FLAG_DE_HIGH,
+	.bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,
+};
+
 static const struct drm_display_mode samsung_lsn122dl01_c01_mode = {
 static const struct drm_display_mode samsung_lsn122dl01_c01_mode = {
 	.clock = 271560,
 	.clock = 271560,
 	.hdisplay = 2560,
 	.hdisplay = 2560,
@@ -2510,6 +2538,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 	}, {
 		.compatible = "rocktech,rk070er9427",
 		.compatible = "rocktech,rk070er9427",
 		.data = &rocktech_rk070er9427,
 		.data = &rocktech_rk070er9427,
+	}, {
+		.compatible = "rockteck,rk101ii01d-ct",
+		.data = &rocktech_rk101ii01d_ct,
 	}, {
 	}, {
 		.compatible = "samsung,lsn122dl01-c01",
 		.compatible = "samsung,lsn122dl01-c01",
 		.data = &samsung_lsn122dl01_c01,
 		.data = &samsung_lsn122dl01_c01,

+ 30 - 0
drivers/gpu/drm/tidss/Kconfig

@@ -0,0 +1,30 @@
+config DRM_TIDSS
+	tristate "DRM Support for TI Keystone"
+	depends on DRM && OF
+	depends on ARM || ARM64 || COMPILE_TEST
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	select VIDEOMODE_HELPERS
+	help
+	  The TI Keystone family SoCs introduced a new generation of
+	  Display SubSystem. They are called DSS6 and DSS7 and at the
+	  time of writing this these DSSs are found on 66AK2Gx and
+	  AM6x SoCs. Set this to Y or M to add display support for
+	  TI Keystone family platforms.
+
+if DRM_TIDSS
+
+config DRM_TIDSS_DSS6
+	bool "DSS6 support"
+	default y
+	help
+	  Support for DSS6 IP on K2G SoC
+
+config DRM_TIDSS_DSS7
+	bool "DSS7 support"
+	default y
+	help
+	  Support for DSS7 IP on AM6 SoC
+
+endif

+ 14 - 0
drivers/gpu/drm/tidss/Makefile

@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+
+tidss-y := tidss_crtc.o \
+	tidss_drv.o \
+	tidss_encoder.o \
+	tidss_irq.o \
+	tidss_kms.o \
+	tidss_plane.o \
+	tidss_scale_coefs.o
+
+tidss-$(CONFIG_DRM_TIDSS_DSS6) += tidss_dispc6.o
+tidss-$(CONFIG_DRM_TIDSS_DSS7) += tidss_dispc7.o
+
+obj-$(CONFIG_DRM_TIDSS) += tidss.o

+ 402 - 0
drivers/gpu/drm/tidss/tidss_crtc.c

@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+
+/* -----------------------------------------------------------------------------
+ * Page Flip
+ */
+
+static void tidss_crtc_finish_page_flip(struct tidss_crtc *tcrtc)
+{
+	struct drm_device *ddev = tcrtc->crtc.dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	bool busy;
+
+	/*
+	 * New settings are taken into use at VFP, and GO bit is cleared at
+	 * the same time. This happens before the vertical blank interrupt.
+	 * So there is a small change that the driver sets GO bit after VFP, but
+	 * before vblank, and we have to check for that case here.
+	 */
+	busy = tidss->dispc_ops->vp_go_busy(tidss->dispc, tcrtc->hw_videoport);
+	if (busy)
+		return;
+
+	spin_lock_irqsave(&ddev->event_lock, flags);
+
+	event = tcrtc->event;
+	tcrtc->event = NULL;
+
+	if (!event) {
+		spin_unlock_irqrestore(&ddev->event_lock, flags);
+		return;
+	}
+
+	drm_crtc_send_vblank_event(&tcrtc->crtc, event);
+
+	spin_unlock_irqrestore(&ddev->event_lock, flags);
+
+	drm_crtc_vblank_put(&tcrtc->crtc);
+}
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	drm_crtc_handle_vblank(crtc);
+
+	tidss_crtc_finish_page_flip(tcrtc);
+}
+
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	complete(&tcrtc->framedone_completion);
+}
+
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+
+	dev_err_ratelimited(crtc->dev->dev, "CRTC%u SYNC LOST: (irq %llx)\n",
+			    tcrtc->hw_videoport, irqstatus);
+}
+
+/* -----------------------------------------------------------------------------
+ * CRTC Functions
+ */
+
+static int tidss_crtc_atomic_check(struct drm_crtc *crtc,
+				   struct drm_crtc_state *state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	int r;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->enable)
+		return 0;
+
+	r = tidss->dispc_ops->vp_check(tidss->dispc, tcrtc->hw_videoport,
+				       state);
+
+	return r;
+}
+
+static void tidss_crtc_atomic_flush(struct drm_crtc *crtc,
+				    struct drm_crtc_state *old_crtc_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s, crtc enabled %d, event %p\n",
+		__func__, tcrtc->enabled, crtc->state->event);
+
+	/* Only flush the CRTC if it is currently enabled. */
+	if (!tcrtc->enabled)
+		return;
+
+	/* If the GO bit is stuck we better quit here. */
+	if (WARN_ON(tidss->dispc_ops->vp_go_busy(tidss->dispc,
+						 tcrtc->hw_videoport)))
+		return;
+
+	/* We should have event if CRTC is enabled through out this commit. */
+	WARN_ON(!crtc->state->event);
+
+	tidss->dispc_ops->vp_setup(tidss->dispc,
+				   tcrtc->hw_videoport,
+				   crtc->state);
+
+	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+	spin_lock_irq(&ddev->event_lock);
+	tidss->dispc_ops->vp_go(tidss->dispc, tcrtc->hw_videoport);
+
+	if (crtc->state->event) {
+		tcrtc->event = crtc->state->event;
+		crtc->state->event = NULL;
+	}
+
+	spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_enable(struct drm_crtc *crtc,
+				     struct drm_crtc_state *old_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	const struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	int r;
+
+	dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	r = tidss->dispc_ops->vp_set_clk_rate(tidss->dispc, tcrtc->hw_videoport,
+					      mode->clock * 1000);
+	if (r != 0)
+		return;
+
+	r = tidss->dispc_ops->vp_enable_clk(tidss->dispc, tcrtc->hw_videoport);
+	if (r != 0)
+		return;
+
+	tidss->dispc_ops->vp_setup(tidss->dispc, tcrtc->hw_videoport,
+				   crtc->state);
+
+	/* Turn vertical blanking interrupt reporting on. */
+	drm_crtc_vblank_on(crtc);
+
+	if (tidss->dispc_ops->vp_prepare)
+		tidss->dispc_ops->vp_prepare(tidss->dispc, tcrtc->hw_videoport,
+					     crtc->state);
+
+	tcrtc->enabled = true;
+
+	tidss->dispc_ops->vp_enable(tidss->dispc, tcrtc->hw_videoport,
+				    crtc->state);
+
+	spin_lock_irq(&ddev->event_lock);
+
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+
+	spin_unlock_irq(&ddev->event_lock);
+}
+
+static void tidss_crtc_atomic_disable(struct drm_crtc *crtc,
+				      struct drm_crtc_state *old_state)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s, event %p\n", __func__, crtc->state->event);
+
+	reinit_completion(&tcrtc->framedone_completion);
+
+	tidss->dispc_ops->vp_disable(tidss->dispc, tcrtc->hw_videoport);
+
+	if (!wait_for_completion_timeout(&tcrtc->framedone_completion,
+					 msecs_to_jiffies(500)))
+		dev_err(tidss->dev, "Timeout waiting for framedone on crtc %d",
+			tcrtc->hw_videoport);
+
+	if (tidss->dispc_ops->vp_unprepare)
+		tidss->dispc_ops->vp_unprepare(tidss->dispc,
+					       tcrtc->hw_videoport);
+
+	spin_lock_irq(&ddev->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&ddev->event_lock);
+
+	tcrtc->enabled = false;
+
+	drm_crtc_vblank_off(crtc);
+
+	tidss->dispc_ops->vp_disable_clk(tidss->dispc, tcrtc->hw_videoport);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static
+enum drm_mode_status tidss_crtc_mode_valid(struct drm_crtc *crtc,
+					   const struct drm_display_mode *mode)
+{
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	return tidss->dispc_ops->vp_mode_valid(tidss->dispc,
+					       tcrtc->hw_videoport,
+					       mode);
+}
+
+static const struct drm_crtc_helper_funcs crtc_helper_funcs = {
+	.atomic_check = tidss_crtc_atomic_check,
+	.atomic_flush = tidss_crtc_atomic_flush,
+	.atomic_enable = tidss_crtc_atomic_enable,
+	.atomic_disable = tidss_crtc_atomic_disable,
+
+	.mode_valid = tidss_crtc_mode_valid,
+};
+
+static void tidss_crtc_reset(struct drm_crtc *crtc)
+{
+	struct tidss_crtc_state *tcrtc;
+
+	if (crtc->state)
+		__drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+	kfree(crtc->state);
+
+	tcrtc = kzalloc(sizeof(*tcrtc), GFP_KERNEL);
+	if (!tcrtc) {
+		crtc->state = NULL;
+		return;
+	}
+
+	crtc->state = &tcrtc->base;
+	crtc->state->crtc = crtc;
+}
+
+static struct drm_crtc_state *
+tidss_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct tidss_crtc_state *state, *current_state;
+
+	if (WARN_ON(!crtc->state))
+		return NULL;
+
+	current_state = to_tidss_crtc_state(crtc->state);
+
+	state = kmalloc(sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+	state->bus_format = current_state->bus_format;
+	state->bus_flags = current_state->bus_flags;
+
+	return &state->base;
+}
+
+
+static int tidss_crtc_atomic_set_property(struct drm_crtc *crtc,
+					  struct drm_crtc_state *state,
+					  struct drm_property *property,
+					  uint64_t val)
+{
+	return -EINVAL;
+}
+
+static int tidss_crtc_atomic_get_property(struct drm_crtc *crtc,
+					  const struct drm_crtc_state *state,
+					  struct drm_property *property,
+					  uint64_t *val)
+{
+	return -EINVAL;
+}
+
+static int tidss_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	tidss_irq_enable_vblank(crtc);
+
+	return 0;
+}
+
+static void tidss_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss_irq_disable_vblank(crtc);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_crtc_funcs crtc_funcs = {
+	.reset = tidss_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = tidss_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.atomic_set_property = tidss_crtc_atomic_set_property,
+	.atomic_get_property = tidss_crtc_atomic_get_property,
+	.enable_vblank = tidss_crtc_enable_vblank,
+	.disable_vblank = tidss_crtc_disable_vblank,
+};
+
+static void tidss_crtc_install_properties(struct tidss_device *tidss,
+					  const struct tidss_vp_feat *vp_feat,
+					  struct drm_crtc *crtc)
+{
+}
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+				     struct drm_plane *primary)
+{
+	struct tidss_crtc *tcrtc;
+	struct drm_crtc *crtc;
+	const struct tidss_vp_feat *vp_feat;
+	unsigned int gamma_lut_size = 0;
+	int ret;
+
+	vp_feat = tidss->dispc_ops->vp_feat(tidss->dispc, hw_videoport);
+
+	tcrtc = devm_kzalloc(tidss->dev, sizeof(*tcrtc), GFP_KERNEL);
+	if (!tcrtc)
+		return ERR_PTR(-ENOMEM);
+
+	tcrtc->hw_videoport = hw_videoport;
+	init_completion(&tcrtc->framedone_completion);
+
+	crtc =  &tcrtc->crtc;
+
+	ret = drm_crtc_init_with_planes(tidss->ddev, crtc, primary,
+					NULL, &crtc_funcs, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_crtc_helper_add(crtc, &crtc_helper_funcs);
+
+	/*
+	 * The dispc API adapts to what ever size we ask from it no
+	 * matter what HW supports. X-server assumes 256 element gamma
+	 * tables so lets use that. Size of HW gamma table size is
+	 * found from struct tidss_vp_feat that is extracted with
+	 * dispc_vp_feats(). If gamma_size is 0 gamma table is not
+	 * supported.
+	 */
+	if (vp_feat->color.gamma_size)
+		gamma_lut_size = 256;
+
+	drm_crtc_enable_color_mgmt(crtc, 0, vp_feat->color.has_ctm,
+				   gamma_lut_size);
+	if (gamma_lut_size)
+		drm_mode_crtc_set_gamma_size(crtc, gamma_lut_size);
+
+	tidss_crtc_install_properties(tidss, vp_feat, crtc);
+
+	return tcrtc;
+}

+ 49 - 0
drivers/gpu/drm/tidss/tidss_crtc.h

@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_CRTC_H__
+#define __TIDSS_CRTC_H__
+
+#include <drm/drm_crtc.h>
+#include <linux/completion.h>
+#include <linux/wait.h>
+
+#include "tidss_dispc.h"
+
+#define to_tidss_crtc(c) container_of((c), struct tidss_crtc, crtc)
+
+struct tidss_crtc {
+	struct drm_crtc crtc;
+
+	u32 hw_videoport;
+
+	struct drm_pending_vblank_event *event;
+
+	/* has crtc_atomic_enable been called? */
+	bool enabled;
+
+	struct completion framedone_completion;
+};
+
+#define to_tidss_crtc_state(x) container_of(x, struct tidss_crtc_state, base)
+
+struct tidss_crtc_state {
+	/* Must be first. */
+	struct drm_crtc_state base;
+
+	u32 bus_format;
+	u32 bus_flags;
+};
+
+struct tidss_crtc *tidss_crtc_create(struct tidss_device *tidss, u32 hw_videoport,
+				     struct drm_plane *primary);
+
+
+void tidss_crtc_vblank_irq(struct drm_crtc *crtc);
+void tidss_crtc_framedone_irq(struct drm_crtc *crtc);
+void tidss_crtc_error_irq(struct drm_crtc *crtc, u64 irqstatus);
+
+#endif

+ 141 - 0
drivers/gpu/drm/tidss/tidss_dispc.h

@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC_H__
+#define __TIDSS_DISPC_H__
+
+struct dispc_device;
+struct tidss_device;
+
+struct drm_crtc_state;
+
+#define DSS_MAX_CHANNELS 8
+#define DSS_MAX_PLANES 8
+
+/*
+ * Based on the above 2 defines the bellow defines describe following
+ * u64 IRQ bits:
+ *
+ * bit group |dev |mrg0|mrg1|mrg2|mrg3|mrg4|mrg5|mrg6|mrg7|plane 0-7|<unused> |
+ * bit use   |Dfou|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|FEOL|UUUU|UUUU| | | | | |
+ * bit number|0-3 |4-7 |8-11|            12-35            |  36-43  |  44-63  |
+ *
+ * device bits:	D = OCP error
+ * WB bits:	f = frame done wb, o = wb buffer overflow,
+ *		u = wb buffer uncomplete
+ * vp bits:	F = frame done, E = vsync even, O = vsync odd, L = sync lost
+ * plane bits:	U = fifo underflow
+ */
+
+#define DSS_IRQ_DEVICE_OCP_ERR			BIT_ULL(0)
+
+#define DSS_IRQ_DEVICE_FRAMEDONEWB		BIT_ULL(1)
+#define DSS_IRQ_DEVICE_WBBUFFEROVERFLOW		BIT_ULL(2)
+#define DSS_IRQ_DEVICE_WBUNCOMPLETEERROR	BIT_ULL(3)
+#define DSS_IRQ_DEVICE_WB_MASK			GENMASK_ULL(3, 1)
+
+#define DSS_IRQ_VP_BIT_N(ch, bit)	(4 + 4 * (ch) + (bit))
+#define DSS_IRQ_PLANE_BIT_N(plane, bit) \
+	(DSS_IRQ_VP_BIT_N(DSS_MAX_CHANNELS, 0) + 1 * (plane) + (bit))
+
+#define DSS_IRQ_VP_BIT(ch, bit)	BIT_ULL(DSS_IRQ_VP_BIT_N((ch), (bit)))
+#define DSS_IRQ_PLANE_BIT(plane, bit) \
+	BIT_ULL(DSS_IRQ_PLANE_BIT_N((plane), (bit)))
+
+#define DSS_IRQ_VP_MASK(ch) \
+	GENMASK_ULL(DSS_IRQ_VP_BIT_N((ch), 3), DSS_IRQ_VP_BIT_N((ch), 0))
+#define DSS_IRQ_PLANE_MASK(plane) \
+	GENMASK_ULL(DSS_IRQ_PLANE_BIT_N((plane), 0), \
+		    DSS_IRQ_PLANE_BIT_N((plane), 0))
+
+#define DSS_IRQ_VP_FRAME_DONE(ch)	DSS_IRQ_VP_BIT((ch), 0)
+#define DSS_IRQ_VP_VSYNC_EVEN(ch)	DSS_IRQ_VP_BIT((ch), 1)
+#define DSS_IRQ_VP_VSYNC_ODD(ch)	DSS_IRQ_VP_BIT((ch), 2)
+#define DSS_IRQ_VP_SYNC_LOST(ch)	DSS_IRQ_VP_BIT((ch), 3)
+
+#define DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane)	DSS_IRQ_PLANE_BIT((plane), 0)
+
+struct tidss_vp_feat {
+	struct tidss_vp_color_feat {
+		u32 gamma_size;
+		bool has_ctm;
+	} color;
+};
+
+struct tidss_plane_feat {
+	struct tidss_plane_color_feat {
+		u32 encodings;
+		u32 ranges;
+		enum drm_color_encoding default_encoding;
+		enum drm_color_range default_range;
+	} color;
+	struct tidss_plane_blend_feat {
+		bool global_alpha;
+	} blend;
+};
+
+struct tidss_dispc_ops {
+	u64 (*read_and_clear_irqstatus)(struct dispc_device *dispc);
+	void (*write_irqenable)(struct dispc_device *dispc, u64 enable);
+
+	u32 (*get_memory_bandwidth_limit)(struct dispc_device *dispc);
+
+	int (*get_num_vps)(struct dispc_device *dispc);
+	const char *(*vp_name)(struct dispc_device *dispc,
+			       u32 hw_videoport);
+	const struct tidss_vp_feat *(*vp_feat)(struct dispc_device *dispc,
+					       u32 hw_videoport);
+	void (*vp_prepare)(struct dispc_device *dispc, u32 hw_videoport,
+			   const struct drm_crtc_state *state);
+	void (*vp_enable)(struct dispc_device *dispc, u32 hw_videoport,
+			  const struct drm_crtc_state *state);
+	void (*vp_disable)(struct dispc_device *dispc, u32 hw_videoport);
+	void (*vp_unprepare)(struct dispc_device *dispc, u32 hw_videoport);
+	bool (*vp_go_busy)(struct dispc_device *dispc,
+			   u32 hw_videoport);
+	void (*vp_go)(struct dispc_device *dispc, u32 hw_videoport);
+	enum drm_mode_status (*vp_mode_valid)(struct dispc_device *dispc,
+					u32 hw_videoport,
+					const struct drm_display_mode *mode);
+	int (*vp_check)(struct dispc_device *dispc, u32 hw_videoport,
+			const struct drm_crtc_state *state);
+	void (*vp_setup)(struct dispc_device *dispc, u32 hw_videoport,
+			 const struct drm_crtc_state *state);
+
+	int (*vp_set_clk_rate)(struct dispc_device *dispc,
+			       u32 hw_videoport, unsigned long rate);
+	int (*vp_enable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+	void (*vp_disable_clk)(struct dispc_device *dispc, u32 hw_videoport);
+
+	int (*get_num_planes)(struct dispc_device *dispc);
+	const char *(*plane_name)(struct dispc_device *dispc,
+				  u32 hw_plane);
+	const struct tidss_plane_feat *(*plane_feat)(struct dispc_device *dispc,
+						     u32 hw_plane);
+	int (*plane_enable)(struct dispc_device *dispc, u32 hw_plane,
+			    bool enable);
+	int (*plane_check)(struct dispc_device *dispc, u32 hw_plane,
+			   const struct drm_plane_state *state,
+			   u32 hw_videoport);
+	int (*plane_setup)(struct dispc_device *dispc, u32 hw_plane,
+			   const struct drm_plane_state *state,
+			   u32 hw_videoport);
+
+	int (*runtime_get)(struct dispc_device *dispc);
+	void (*runtime_put)(struct dispc_device *dispc);
+
+	int (*runtime_suspend)(struct dispc_device *dispc);
+	int (*runtime_resume)(struct dispc_device *dispc);
+
+	void (*remove)(struct dispc_device *dispc);
+
+	int (*modeset_init)(struct dispc_device *dispc);
+};
+
+int dispc6_init(struct tidss_device *tidss);
+int dispc7_init(struct tidss_device *tidss);
+
+#endif

+ 1507 - 0
drivers/gpu/drm/tidss/tidss_dispc6.c

@@ -0,0 +1,1507 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_plane.h"
+
+#include "tidss_dispc6.h"
+
+static const struct {
+	u32 fmt;
+	u32 port_width;
+} dispc6_bus_formats[] = {
+	{ MEDIA_BUS_FMT_RGB444_1X12, 12 },
+	{ MEDIA_BUS_FMT_RGB565_1X16, 16 },
+	{ MEDIA_BUS_FMT_RGB666_1X18, 18 },
+	{ MEDIA_BUS_FMT_RGB888_1X24, 24 },
+};
+
+/*
+ * TRM gives bitfields as start:end, where start is the higher bit
+ * number. For example 7:0
+ */
+
+#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end))
+#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
+#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end))
+#define FLD_MOD(orig, val, start, end) \
+	(((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end))
+
+
+#define REG_GET(dispc, idx, start, end)	\
+	FLD_GET(dispc6_read(dispc, idx), start, end)
+
+#define REG_FLD_MOD(dispc, idx, val, start, end) \
+	dispc6_write(dispc, idx, FLD_MOD(dispc6_read(dispc, idx), val, start, end))
+
+#define VID_REG_GET(dispc, plane, idx, start, end) \
+	FLD_GET(dispc6_vid_read(dispc, plane, idx), start, end)
+
+#define VID_REG_FLD_MOD(dispc, plane, idx, val, start, end) \
+	dispc6_vid_write(dispc, plane, idx, FLD_MOD(dispc6_vid_read(dispc, plane, idx), val, start, end))
+
+
+#define VP_REG_GET(dispc, vp, idx, start, end) \
+	FLD_GET(dispc6_vp_read(dispc, vp, idx), start, end)
+
+#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end)	\
+	dispc6_vp_write(dispc, vp, idx, FLD_MOD(dispc6_vp_read(dispc, vp, idx), val, start, end))
+
+#define DISPC6_GAMMA_TABLE_SIZE 256
+
+struct dispc_features {
+	/* XXX should these come from the .dts? Min pclk is not feature of DSS IP */
+	unsigned long min_pclk;
+	unsigned long max_pclk;
+};
+
+/* Note: 9MHz is a special allowed case, and is handled separately in the code */
+static const struct dispc_features k2g_dispc_feats = {
+	.min_pclk = 43750000,
+	.max_pclk = 150000000,
+};
+
+static const struct of_device_id dispc6_of_match[] = {
+	{ .compatible = "ti,k2g-dss", .data = &k2g_dispc_feats, },
+	{},
+};
+
+struct dispc_device {
+	struct device *dev;
+
+	void __iomem *base_cfg;
+	void __iomem *base_common;
+	void __iomem *base_vid1;
+	void __iomem *base_ovr1;
+	void __iomem *base_vp1;
+
+	const struct dispc_features *feat;
+
+	struct clk *fclk;
+	struct clk *vp_clk;
+
+	bool is_enabled;
+
+	u32 gamma_table[DISPC6_GAMMA_TABLE_SIZE];
+
+	struct tidss_device *tidss;
+};
+
+static void dispc6_write(struct dispc_device *dispc, u16 reg, u32 val)
+{
+	iowrite32(val, dispc->base_common + reg);
+}
+
+static u32 dispc6_read(struct dispc_device *dispc, u16 reg)
+{
+	return ioread32(dispc->base_common + reg);
+}
+
+static void dispc6_vid_write(struct dispc_device *dispc,
+			     u32 hw_plane, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vid1;
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vid_read(struct dispc_device *dispc,
+			   u32 hw_plane, u16 reg)
+{
+	void __iomem *base = dispc->base_vid1;
+
+	return ioread32(base + reg);
+}
+
+static void dispc6_ovr_write(struct dispc_device *dispc,
+			     u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_ovr1;
+
+	iowrite32(val, base + reg);
+}
+
+__maybe_unused
+static u32 dispc6_ovr_read(struct dispc_device *dispc,
+			   u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_ovr1;
+
+	return ioread32(base + reg);
+}
+
+static void dispc6_vp_write(struct dispc_device *dispc,
+			    u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vp1;
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc6_vp_read(struct dispc_device *dispc,
+			  u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_vp1;
+
+	return ioread32(base + reg);
+}
+
+static int dispc6_runtime_get(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "dispc_runtime_get\n");
+
+	r = pm_runtime_get_sync(dispc->dev);
+	WARN_ON(r < 0);
+	return r < 0 ? r : 0;
+}
+
+static void dispc6_runtime_put(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "dispc_runtime_put\n");
+
+	r = pm_runtime_put_sync(dispc->dev);
+	WARN_ON(r < 0);
+}
+
+static u64 dispc6_vp_irq_from_raw(u32 stat)
+{
+	u32 vp = 0;
+	u64 vp_stat = 0;
+
+	if (stat & BIT(0))
+		vp_stat |= DSS_IRQ_VP_FRAME_DONE(vp);
+	if (stat & BIT(1))
+		vp_stat |= DSS_IRQ_VP_VSYNC_EVEN(vp);
+	if (stat & BIT(2))
+		vp_stat |= DSS_IRQ_VP_VSYNC_ODD(vp);
+	if (stat & BIT(4))
+		vp_stat |= DSS_IRQ_VP_SYNC_LOST(vp);
+
+	return vp_stat;
+}
+
+static u32 dispc6_vp_irq_to_raw(u64 vpstat)
+{
+	u32 vp = 0;
+	u32 stat = 0;
+
+	if (vpstat & DSS_IRQ_VP_FRAME_DONE(vp))
+		stat |= BIT(0);
+	if (vpstat & DSS_IRQ_VP_VSYNC_EVEN(vp))
+		stat |= BIT(1);
+	if (vpstat & DSS_IRQ_VP_VSYNC_ODD(vp))
+		stat |= BIT(2);
+	if (vpstat & DSS_IRQ_VP_SYNC_LOST(vp))
+		stat |= BIT(4);
+
+	return stat;
+}
+
+static u64 dispc6_vid_irq_from_raw(u32 stat)
+{
+	u32 plane = 0;
+	u64 vid_stat = 0;
+
+	if (stat & BIT(0))
+		vid_stat |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane);
+
+	return vid_stat;
+}
+
+static u32 dispc6_vid_irq_to_raw(u64 vidstat)
+{
+	u32 plane = 0;
+	u32 stat = 0;
+
+	if (vidstat & DSS_IRQ_PLANE_FIFO_UNDERFLOW(plane))
+		stat |= BIT(0);
+
+	return stat;
+}
+
+
+static u64 dispc6_vp_read_irqstatus(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQSTATUS);
+
+	return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqstatus(struct dispc_device *dispc,
+				      u32 hw_videoport,
+				      u64 vpstat)
+{
+	u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQSTATUS, stat);
+}
+
+static u64 dispc6_vid_read_irqstatus(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQSTATUS);
+
+	return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqstatus(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       u64 vidstat)
+{
+	u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQSTATUS, stat);
+}
+
+
+static u64 dispc6_vp_read_irqenable(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc6_vp_read(dispc, hw_videoport, DISPC_VP_IRQENABLE);
+
+	return dispc6_vp_irq_from_raw(stat);
+}
+
+static void dispc6_vp_write_irqenable(struct dispc_device *dispc,
+				      u32 hw_videoport,
+				      u64 vpstat)
+{
+	u32 stat = dispc6_vp_irq_to_raw(vpstat);
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_IRQENABLE, stat);
+}
+
+static u64 dispc6_vid_read_irqenable(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc6_vid_read(dispc, hw_plane, DISPC_VID_IRQENABLE);
+
+	return dispc6_vid_irq_from_raw(stat);
+}
+
+static void dispc6_vid_write_irqenable(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       u64 vidstat)
+{
+	u32 stat = dispc6_vid_irq_to_raw(vidstat);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_IRQENABLE, stat);
+}
+
+
+static void dispc6_clear_irqstatus(struct dispc_device *dispc, u64 mask)
+{
+	dispc6_vp_write_irqstatus(dispc, 0, mask);
+	dispc6_vid_write_irqstatus(dispc, 0, mask);
+}
+
+static u64 dispc6_read_and_clear_irqstatus(struct dispc_device *dispc)
+{
+	u64 stat = 0;
+
+	/* always clear the top level irqstatus */
+	dispc6_write(dispc, DISPC_IRQSTATUS,
+		     dispc6_read(dispc, DISPC_IRQSTATUS));
+
+	stat |= dispc6_vp_read_irqstatus(dispc, 0);
+	stat |= dispc6_vid_read_irqstatus(dispc, 0);
+
+	dispc6_clear_irqstatus(dispc, stat);
+
+	return stat;
+}
+
+static u64 dispc6_read_irqenable(struct dispc_device *dispc)
+{
+	u64 stat = 0;
+
+	stat |= dispc6_vp_read_irqenable(dispc, 0);
+	stat |= dispc6_vid_read_irqenable(dispc, 0);
+
+	return stat;
+}
+
+static void dispc6_write_irqenable(struct dispc_device *dispc, u64 mask)
+{
+	u64 old_mask = dispc6_read_irqenable(dispc);
+
+	/* clear the irqstatus for newly enabled irqs */
+	dispc6_clear_irqstatus(dispc, (mask ^ old_mask) & mask);
+
+	dispc6_vp_write_irqenable(dispc, 0, mask);
+	dispc6_vid_write_irqenable(dispc, 0, mask);
+
+	dispc6_write(dispc, DISPC_IRQENABLE_SET, (1 << 0) | (1 << 7));
+
+	/* flush posted write */
+	dispc6_read_irqenable(dispc);
+}
+
+static void dispc6_set_num_datalines(struct dispc_device *dispc,
+				     u32 hw_videoport, int num_lines)
+{
+	int v;
+
+	switch (num_lines) {
+	case 12:
+		v = 0; break;
+	case 16:
+		v = 1; break;
+	case 18:
+		v = 2; break;
+	case 24:
+		v = 3; break;
+	case 30:
+		v = 4; break;
+	case 36:
+		v = 5; break;
+	default:
+		WARN_ON(1);
+		v = 3;
+		break;
+	}
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, v, 10, 8);
+}
+
+static void dispc6_vp_enable(struct dispc_device *dispc, u32 hw_videoport,
+			     const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	bool align, onoff, rf, ieo, ipc, ihs, ivs;
+	unsigned int i;
+	u32 port_width;
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+		if (dispc6_bus_formats[i].fmt != tstate->bus_format)
+			continue;
+
+		port_width = dispc6_bus_formats[i].port_width;
+		break;
+	}
+
+	if (WARN_ON(i == ARRAY_SIZE(dispc6_bus_formats)))
+		return;
+
+	dispc6_set_num_datalines(dispc, hw_videoport, port_width);
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_H,
+			FLD_VAL(hsw - 1, 7, 0) |
+			FLD_VAL(hfp - 1, 19, 8) |
+			FLD_VAL(hbp - 1, 31, 20));
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_V,
+			FLD_VAL(vsw - 1, 7, 0) |
+			FLD_VAL(vfp, 19, 8) |
+			FLD_VAL(vbp, 31, 20));
+
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		ivs = true;
+	else
+		ivs = false;
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		ihs = true;
+	else
+		ihs = false;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_DE_LOW)
+		ieo = true;
+	else
+		ieo = false;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE)
+		ipc = true;
+	else
+		ipc = false;
+
+	/* always use the 'rf' setting */
+	onoff = true;
+
+	if (tstate->bus_flags & DRM_BUS_FLAG_SYNC_NEGEDGE)
+		rf = false;
+	else
+		rf = true;
+
+	/* always use aligned syncs */
+	align = true;
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_POL_FREQ,
+			FLD_VAL(align, 18, 18) |
+			FLD_VAL(onoff, 17, 17) |
+			FLD_VAL(rf, 16, 16) |
+			FLD_VAL(ieo, 15, 15) |
+			FLD_VAL(ipc, 14, 14) |
+			FLD_VAL(ihs, 13, 13) |
+			FLD_VAL(ivs, 12, 12));
+
+	dispc6_vp_write(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN,
+			FLD_VAL(mode->hdisplay - 1, 11, 0) |
+			FLD_VAL(mode->vdisplay - 1, 27, 16));
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 0, 0);
+}
+
+static void dispc6_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0, 0, 0);
+}
+
+static bool dispc6_vp_go_busy(struct dispc_device *dispc,
+			      u32 hw_videoport)
+{
+	return VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL, 5, 5);
+}
+
+static void dispc6_vp_go(struct dispc_device *dispc,
+			 u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 5, 5);
+}
+
+static u16 c8_to_c12(u8 c8)
+{
+	u16 c12;
+
+	c12 = c8 << 4;
+
+	/* Replication logic: Copy c8 4 MSB to 4 LSB for full scale c12 */
+	c12 = c8 >> 4;
+
+	return c12;
+}
+
+static u64 argb8888_to_argb12121212(u32 argb8888)
+{
+	u8 a, r, g, b;
+	u64 v;
+
+	a = (argb8888 >> 24) & 0xff;
+	r = (argb8888 >> 16) & 0xff;
+	g = (argb8888 >> 8) & 0xff;
+	b = (argb8888 >> 0) & 0xff;
+
+	v = ((u64)c8_to_c12(a) << 36) | ((u64)c8_to_c12(r) << 24) |
+	    ((u64)c8_to_c12(g) << 12) | (u64)c8_to_c12(b);
+
+	return v;
+}
+
+static void dispc6_vp_set_default_color(struct dispc_device *dispc,
+					u32 hw_videoport, u32 default_color)
+{
+	u64 v;
+
+	v = argb8888_to_argb12121212(default_color);
+
+	dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff);
+	dispc6_ovr_write(dispc, 0, DISPC_OVR_DEFAULT_COLOR2,
+			 (v >> 32) & 0xffff);
+}
+
+static enum drm_mode_status dispc6_vp_mode_valid(struct dispc_device *dispc,
+						 u32 hw_videoport,
+						 const struct drm_display_mode *mode)
+{
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	/* special case for 9MHz */
+	if (mode->clock * 1000 < dispc->feat->min_pclk && mode->clock != 9000)
+		return MODE_CLOCK_LOW;
+
+	if (mode->clock * 1000 > dispc->feat->max_pclk)
+		return MODE_CLOCK_HIGH;
+
+	if (mode->hdisplay > 4096)
+		return MODE_BAD;
+
+	if (mode->vdisplay > 4096)
+		return MODE_BAD;
+
+	/* TODO: add interlace support */
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return MODE_NO_INTERLACE;
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	if (hsw < 1 || hsw > 256 ||
+	    hfp < 1 || hfp > 4096 ||
+	    hbp < 1 || hbp > 4096)
+		return MODE_BAD_HVALUE;
+
+	if (vsw < 1 || vsw > 256 ||
+	    vfp < 0 || vfp > 4095 ||
+	    vbp < 0 || vbp > 4095)
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+static int dispc6_vp_check(struct dispc_device *dispc, u32 hw_videoport,
+			   const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	enum drm_mode_status ok;
+	unsigned int i;
+
+	ok = dispc6_vp_mode_valid(dispc, hw_videoport, mode);
+	if (ok != MODE_OK) {
+		dev_dbg(dispc->dev, "%s: bad mode: %ux%u pclk %u kHz\n",
+			__func__, mode->hdisplay, mode->vdisplay, mode->clock);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_bus_formats); ++i) {
+		if (dispc6_bus_formats[i].fmt == tstate->bus_format)
+			break;
+	}
+
+	if (i == ARRAY_SIZE(dispc6_bus_formats)) {
+		dev_dbg(dispc->dev, "%s: Unsupported bus format: %u\n",
+			__func__, tstate->bus_format);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dispc6_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport)
+{
+	int ret = clk_prepare_enable(dispc->vp_clk);
+
+	if (ret)
+		dev_err(dispc->dev, "%s: enabling clk failed: %d\n", __func__,
+			ret);
+
+	return ret;
+}
+
+static void dispc6_vp_disable_clk(struct dispc_device *dispc,
+				  u32 hw_videoport)
+{
+	clk_disable_unprepare(dispc->vp_clk);
+}
+
+static int dispc6_vp_set_clk_rate(struct dispc_device *dispc,
+				  u32 hw_videoport, unsigned long rate)
+{
+	int r;
+	unsigned long new_rate;
+
+	r = clk_set_rate(dispc->vp_clk, rate);
+	if (r) {
+		dev_err(dispc->dev, "Failed to set vp clk rate to %lu\n",
+			rate);
+		return r;
+	}
+
+	new_rate = clk_get_rate(dispc->vp_clk);
+
+	if (rate != new_rate)
+		dev_warn(dispc->dev,
+			 "Failed to get exact pix clock %lu != %lu\n",
+			 rate, new_rate);
+
+	dev_dbg(dispc->dev, "New VP rate %lu Hz (requested %lu Hz)\n",
+		clk_get_rate(dispc->vp_clk), rate);
+
+	return 0;
+}
+
+/* CSC */
+
+struct color_conv_coef {
+	int ry, rcb, rcr;
+	int gy, gcb, gcr;
+	int by, bcb, bcr;
+	int roffset, goffset, boffset;
+	bool full_range;
+};
+
+static void dispc6_vid_write_color_conv_coefs(struct dispc_device *dispc,
+					      u32 hw_plane,
+					      const struct color_conv_coef *ct)
+{
+#define CVAL(x, y) (FLD_VAL(x, 26, 16) | FLD_VAL(y, 10, 0))
+
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(0), CVAL(ct->rcr, ct->ry));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(1), CVAL(ct->gy,  ct->rcb));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(2), CVAL(ct->gcb, ct->gcr));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(3), CVAL(ct->bcr, ct->by));
+	dispc6_vid_write(dispc, hw_plane,
+			 DISPC_VID_CONV_COEF(4), CVAL(0, ct->bcb));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(5),
+			 FLD_VAL(ct->roffset, 15, 3) |
+			 FLD_VAL(ct->goffset, 31, 19));
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_CONV_COEF(6),
+			 FLD_VAL(ct->boffset, 15, 3));
+
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+			ct->full_range, 11, 11);
+
+#undef CVAL
+}
+
+static void dispc6_vid_csc_setup(struct dispc_device *dispc)
+{
+	/* YUV -> RGB, ITU-R BT.601, full range */
+	const struct color_conv_coef coefs_yuv2rgb_bt601_full = {
+		256,   0,  358,
+		256, -88, -182,
+		256, 452,    0,
+		0, -2048, -2048,
+		true,
+	};
+
+	dispc6_vid_write_color_conv_coefs(dispc, 0, &coefs_yuv2rgb_bt601_full);
+}
+
+static void dispc6_vid_csc_enable(struct dispc_device *dispc,
+				  u32 hw_plane, bool enable)
+{
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9);
+}
+
+/* SCALER */
+
+static u32 dispc6_calc_fir_inc(u32 in, u32 out)
+{
+	return (u32)div_u64(0x200000ull * in, out);
+}
+
+struct dispc6_vid_fir_coefs {
+	s16 c2[16];
+	s16 c1[16];
+	u16 c0[9];
+};
+
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_null = {
+	.c2 = {	0 },
+	.c1 = { 0 },
+	.c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256,  },
+};
+
+/* M=8, Upscale x >= 1 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m8 = {
+	.c2 = {	0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2,  },
+	.c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20,  },
+	.c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312,  },
+};
+
+/* 5-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_5tap = {
+	.c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14,  },
+	.c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122,  },
+	.c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192,  },
+};
+
+/* 3-tap, M=22, Downscale Ratio 2.5 < x < 3 */
+static const struct dispc6_vid_fir_coefs dispc6_fir_coefs_m22_3tap = {
+	.c1 = { 100, 118, 136, 156, 176, 196, 216, 236, 0, 10, 20, 30, 40, 54, 68, 84,  },
+	.c0 = { 312, 310, 308, 302, 296, 286, 276, 266, 256,  },
+};
+
+enum dispc6_vid_fir_coef_set {
+	DISPC6_VID_FIR_COEF_HORIZ,
+	DISPC6_VID_FIR_COEF_HORIZ_UV,
+	DISPC6_VID_FIR_COEF_VERT,
+	DISPC6_VID_FIR_COEF_VERT_UV,
+};
+
+static void dispc6_vid_write_fir_coefs(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       enum dispc6_vid_fir_coef_set coef_set,
+				       const struct dispc6_vid_fir_coefs *coefs)
+{
+	static const u16 c0_regs[] = {
+		[DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0,
+		[DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C,
+		[DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0,
+		[DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C,
+	};
+
+	static const u16 c12_regs[] = {
+		[DISPC6_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12,
+		[DISPC6_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C,
+		[DISPC6_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12,
+		[DISPC6_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C,
+	};
+
+	const u16 c0_base = c0_regs[coef_set];
+	const u16 c12_base = c12_regs[coef_set];
+	int phase;
+
+	for (phase = 0; phase <= 8; ++phase) {
+		u16 reg = c0_base + phase * 4;
+		u16 c0 = coefs->c0[phase];
+
+		dispc6_vid_write(dispc, hw_plane, reg, c0);
+	}
+
+	for (phase = 0; phase <= 15; ++phase) {
+		u16 reg = c12_base + phase * 4;
+		s16 c1, c2;
+		u32 c12;
+
+		c1 = coefs->c1[phase];
+		c2 = coefs->c2[phase];
+		c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20);
+
+		dispc6_vid_write(dispc, hw_plane, reg, c12);
+	}
+}
+
+static void dispc6_vid_write_scale_coefs(struct dispc_device *dispc,
+					 u32 hw_plane)
+{
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_HORIZ_UV,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT,
+				   &dispc6_fir_coefs_null);
+	dispc6_vid_write_fir_coefs(dispc, hw_plane, DISPC6_VID_FIR_COEF_VERT_UV,
+				   &dispc6_fir_coefs_null);
+}
+
+static void dispc6_vid_set_scaling(struct dispc_device *dispc,
+				   u32 hw_plane,
+				   u32 orig_width, u32 orig_height,
+				   u32 out_width, u32 out_height,
+				   u32 fourcc)
+{
+	u32 in_w, in_h, in_w_uv, in_h_uv;
+	u32 fir_hinc, fir_vinc, fir_hinc_uv, fir_vinc_uv;
+	bool scale_x, scale_y;
+	bool five_taps = false;		/* XXX always 3-tap for now */
+
+	in_w = in_w_uv = orig_width;
+	in_h = in_h_uv = orig_height;
+
+	switch (fourcc) {
+	case DRM_FORMAT_NV12:
+		/* UV is subsampled by 2 horizontally and vertically */
+		in_h_uv >>= 1;
+		in_w_uv >>= 1;
+		break;
+
+	case DRM_FORMAT_YUYV:
+	case DRM_FORMAT_UYVY:
+		/* UV is subsampled by 2 horizontally */
+		in_w_uv >>= 1;
+		break;
+
+	default:
+		break;
+	}
+
+	scale_x = in_w != out_width || in_w_uv != out_width;
+	scale_y = in_h != out_height || in_h_uv != out_height;
+
+	/* HORIZONTAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_x, 7, 7);
+
+	/* VERTICAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, scale_y, 8, 8);
+
+	/* Skip the rest if no scaling is used */
+	if (!scale_x && !scale_y)
+		return;
+
+	/* VERTICAL 5-TAPS  */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, five_taps, 21, 21);
+
+	/* FIR INC */
+
+	fir_hinc = dispc6_calc_fir_inc(in_w, out_width);
+	fir_vinc = dispc6_calc_fir_inc(in_h, out_height);
+	fir_hinc_uv = dispc6_calc_fir_inc(in_w_uv, out_width);
+	fir_vinc_uv = dispc6_calc_fir_inc(in_h_uv, out_height);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH, fir_hinc);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV, fir_vinc);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRH2, fir_hinc_uv);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_FIRV2, fir_vinc_uv);
+
+	dispc6_vid_write_scale_coefs(dispc, hw_plane);
+}
+
+/* OTHER */
+
+static const struct {
+	u32 fourcc;
+	u8 dss_code;
+} dispc6_color_formats[] = {
+	{ DRM_FORMAT_ARGB4444, 0x0, },
+	{ DRM_FORMAT_ABGR4444, 0x1, },
+	{ DRM_FORMAT_RGBA4444, 0x2, },
+
+	{ DRM_FORMAT_RGB565, 0x3, },
+	{ DRM_FORMAT_BGR565, 0x4, },
+
+	{ DRM_FORMAT_ARGB1555, 0x5, },
+	{ DRM_FORMAT_ABGR1555, 0x6, },
+
+	{ DRM_FORMAT_ARGB8888, 0x7, },
+	{ DRM_FORMAT_ABGR8888, 0x8, },
+	{ DRM_FORMAT_RGBA8888, 0x9, },
+	{ DRM_FORMAT_BGRA8888, 0xa, },
+
+	{ DRM_FORMAT_XRGB8888, 0x27, },
+	{ DRM_FORMAT_XBGR8888, 0x28, },
+	{ DRM_FORMAT_RGBX8888, 0x29, },
+	{ DRM_FORMAT_BGRX8888, 0x2a, },
+
+	{ DRM_FORMAT_YUYV, 0x3e, },
+	{ DRM_FORMAT_UYVY, 0x3f, },
+
+	{ DRM_FORMAT_NV12, 0x3d, },
+};
+
+static bool dispc6_fourcc_is_yuv(u32 fourcc)
+{
+	switch (fourcc) {
+	case DRM_FORMAT_YUYV:
+	case DRM_FORMAT_UYVY:
+	case DRM_FORMAT_NV12:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static void dispc6_plane_set_pixel_format(struct dispc_device *dispc,
+					  u32 hw_plane, u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i) {
+		if (dispc6_color_formats[i].fourcc == fourcc) {
+			VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+					dispc6_color_formats[i].dss_code,
+					6, 1);
+			return;
+		}
+	}
+
+	WARN_ON(1);
+}
+
+static s32 pixinc(int pixels, u8 ps)
+{
+	if (pixels == 1)
+		return 1;
+	else if (pixels > 1)
+		return 1 + (pixels - 1) * ps;
+	else if (pixels < 0)
+		return 1 - (-pixels + 1) * ps;
+
+	WARN_ON(1);
+	return 0;
+}
+
+static
+const struct tidss_plane_feat *dispc6_plane_feat(struct dispc_device *dispc,
+						 u32 hw_plane)
+{
+	static const struct tidss_plane_feat pfeat = {
+		.color = {
+			.encodings = BIT(DRM_COLOR_YCBCR_BT601),
+			.ranges = BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+			.default_encoding = DRM_COLOR_YCBCR_BT601,
+			.default_range = DRM_COLOR_YCBCR_FULL_RANGE,
+		},
+		.blend = {
+			.global_alpha = false,
+		},
+	};
+
+	return &pfeat;
+}
+
+static int dispc6_plane_check(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	return 0; /* XXX: Dummy check function to be implemented later */
+}
+
+static int dispc6_plane_setup(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	u32 fourcc = state->fb->format->format;
+	dma_addr_t paddr = dispc7_plane_state_paddr(state);
+	u16 cpp = state->fb->format->cpp[0];
+	u32 fb_width = state->fb->pitches[0] / cpp;
+	u32 src_w = state->src_w >> 16;
+	u32 src_h = state->src_h >> 16;
+
+	dispc6_plane_set_pixel_format(dispc, hw_plane, fourcc);
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_0, paddr);
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_1, paddr);
+
+	if (state->fb->format->num_planes == 2) {
+		dma_addr_t p_uv_addr = dispc7_plane_state_p_uv_addr(state);
+
+		dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_0, p_uv_addr);
+		dispc6_vid_write(dispc, hw_plane, DISPC_VID_BA_UV_1, p_uv_addr);
+	}
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE,
+			 (src_w - 1) | ((src_h - 1) << 16));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC,
+			 pixinc(1, cpp));
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC,
+			 pixinc(1 + fb_width - src_w, cpp));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_POSITION,
+			 state->crtc_x | (state->crtc_y << 16));
+
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_SIZE,
+			 (state->crtc_w - 1) | ((state->crtc_h - 1) << 16));
+
+	dispc6_vid_set_scaling(dispc, hw_plane, src_w, src_h,
+			       state->crtc_w, state->crtc_h,
+			       fourcc);
+
+	/* enable YUV->RGB color conversion */
+	if (dispc6_fourcc_is_yuv(fourcc))
+		dispc6_vid_csc_enable(dispc, hw_plane, true);
+	else
+		dispc6_vid_csc_enable(dispc, hw_plane, false);
+
+	/* hw_videoport */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 0, 16, 14);
+
+	return 0;
+}
+
+static int dispc6_plane_enable(struct dispc_device *dispc,
+			       u32 hw_plane, bool enable)
+{
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0);
+	return 0;
+}
+
+static u32 dispc6_vid_get_fifo_size(struct dispc_device *dispc,
+				    u32 hw_plane)
+{
+	const u32 unit_size = 16;	/* 128-bits */
+
+	return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) *
+	       unit_size;
+}
+
+static void dispc6_vid_set_mflag_threshold(struct dispc_device *dispc,
+					   u32 hw_plane,
+					   u32 low, u32 high)
+{
+	dispc6_vid_write(dispc, hw_plane, DISPC_VID_MFLAG_THRESHOLD,
+			 FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
+}
+
+static void dispc6_mflag_setup(struct dispc_device *dispc)
+{
+	u32 hw_plane = 0;
+	const u32 unit_size = 16;	/* 128-bits */
+	u32 size = dispc6_vid_get_fifo_size(dispc, hw_plane);
+	u32 low, high;
+
+	/* MFLAG_CTRL */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 1, 1, 0);
+	/* MFLAG_START */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 2, 2);
+
+	/*
+	 * Simulation team suggests below thesholds:
+	 * HT = fifosize * 5 / 8;
+	 * LT = fifosize * 4 / 8;
+	 */
+
+	low = size * 4 / 8 / unit_size;
+	high = size * 5 / 8 / unit_size;
+
+	dispc6_vid_set_mflag_threshold(dispc, hw_plane, low, high);
+}
+
+static void dispc6_initial_config(struct dispc_device *dispc)
+{
+	dispc6_vid_csc_setup(dispc);
+	dispc6_mflag_setup(dispc);
+
+	/* Enable the gamma Shadow bit-field */
+	VP_REG_FLD_MOD(dispc, 0, DISPC_VP_CONFIG, 1, 2, 2);
+}
+
+static int dispc6_init_features(struct dispc_device *dispc)
+{
+	const struct of_device_id *match;
+
+	match = of_match_node(dispc6_of_match, dispc->dev->of_node);
+	if (!match) {
+		dev_err(dispc->dev, "Unsupported DISPC version\n");
+		return -ENODEV;
+	}
+
+	dispc->feat = match->data;
+
+	return 0;
+}
+
+static int dispc6_get_num_planes(struct dispc_device *dispc)
+{
+	return 1;
+}
+
+static int dispc6_get_num_vps(struct dispc_device *dispc)
+{
+	return 1;
+}
+
+static const struct tidss_vp_feat *dispc6_vp_feat(struct dispc_device *dispc,
+						  u32 hw_videoport)
+{
+	static const struct tidss_vp_feat vp_feat = {
+		.color = {
+			.gamma_size = DISPC6_GAMMA_TABLE_SIZE,
+			.has_ctm = false, /* Driver implementation missing */
+		},
+	};
+
+	return &vp_feat;
+}
+
+static void dispc6_vp_write_gamma_table(struct dispc_device *dispc,
+					u32 hw_videoport)
+{
+	u32 *table = dispc->gamma_table;
+	unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d\n", __func__, hw_videoport);
+
+	for (i = 0; i < hwlen; ++i) {
+		u32 v = table[i];
+
+		v |= i << 24;
+
+		dispc6_vp_write(dispc, hw_videoport, DISPC_VP_GAMMA_TABLE, v);
+	}
+}
+
+static void dispc6_restore_gamma_tables(struct dispc_device *dispc)
+{
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	dispc6_vp_write_gamma_table(dispc, 0);
+}
+
+static const struct drm_color_lut dispc6_vp_gamma_default_lut[] = {
+	{ .red = 0, .green = 0, .blue = 0, },
+	{ .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, },
+};
+
+static void dispc6_vp_set_gamma(struct dispc_device *dispc,
+				u32 hw_videoport,
+				const struct drm_color_lut *lut,
+				unsigned int length)
+{
+	u32 *table = dispc->gamma_table;
+	unsigned int hwlen = ARRAY_SIZE(dispc->gamma_table);
+	static const unsigned int hwbits = 8;
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n",
+		__func__, hw_videoport, length, hwlen);
+
+	if (lut == NULL || length < 2) {
+		lut = dispc6_vp_gamma_default_lut;
+		length = ARRAY_SIZE(dispc6_vp_gamma_default_lut);
+	}
+
+	for (i = 0; i < length - 1; ++i) {
+		unsigned int first = i * (hwlen - 1) / (length - 1);
+		unsigned int last = (i + 1) * (hwlen - 1) / (length - 1);
+		unsigned int w = last - first;
+		u16 r, g, b;
+		unsigned int j;
+
+		if (w == 0)
+			continue;
+
+		for (j = 0; j <= w; j++) {
+			r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w;
+			g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w;
+			b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w;
+
+			r >>= 16 - hwbits;
+			g >>= 16 - hwbits;
+			b >>= 16 - hwbits;
+
+			table[first + j] = (r << (hwbits * 2)) |
+					   (g << hwbits) | b;
+		}
+	}
+
+	if (dispc->is_enabled)
+		dispc6_vp_write_gamma_table(dispc, hw_videoport);
+}
+
+static void dispc6_vp_set_color_mgmt(struct dispc_device *dispc,
+				     u32 hw_videoport,
+				     const struct drm_crtc_state *state)
+{
+	struct drm_color_lut *lut = NULL;
+	unsigned int length = 0;
+
+	if (!state->color_mgmt_changed)
+		return;
+
+	if (state->gamma_lut) {
+		lut = (struct drm_color_lut *) state->gamma_lut->data;
+		length = state->gamma_lut->length / sizeof(*lut);
+	}
+
+	dispc6_vp_set_gamma(dispc, hw_videoport, lut, length);
+}
+
+static void dispc6_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
+			    const struct drm_crtc_state *state)
+{
+	dispc6_vp_set_default_color(dispc, hw_videoport, 0);
+	dispc6_vp_set_color_mgmt(dispc, hw_videoport, state);
+}
+
+static int dispc6_init_gamma_tables(struct dispc_device *dispc)
+{
+	dispc6_vp_set_gamma(dispc, 0, NULL, 0);
+
+	return 0;
+}
+
+static u32 dispc6_get_memory_bandwidth_limit(struct dispc_device *dispc)
+{
+	u32 limit = 0;
+
+	/* Optional maximum memory bandwidth */
+	of_property_read_u32(dispc->dev->of_node, "max-memory-bandwidth",
+			     &limit);
+
+	return limit;
+}
+
+static const char *dispc6_plane_name(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	return "vid1";
+}
+
+static const char *dispc6_vp_name(struct dispc_device *dispc,
+				  u32 hw_videoport)
+{
+	return "vp1";
+}
+
+static int dispc6_runtime_suspend(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "suspend\n");
+
+	dispc->is_enabled = false;
+
+	clk_disable_unprepare(dispc->fclk);
+
+	return 0;
+}
+
+static int dispc6_runtime_resume(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "resume\n");
+
+	clk_prepare_enable(dispc->fclk);
+
+	if (REG_GET(dispc, DISPC_SYSSTATUS, 0, 0) == 0)
+		dev_warn(dev, "DISPC FUNC RESET not done!\n");
+	if (REG_GET(dispc, DISPC_SYSSTATUS, 1, 1) == 0)
+		dev_warn(dev, "DISPC VP RESET not done!\n");
+
+	dispc6_initial_config(dispc);
+
+	dispc6_restore_gamma_tables(dispc);
+
+	dispc->is_enabled = true;
+
+	return 0;
+}
+
+static int dispc6_modeset_init(struct dispc_device *dispc)
+{
+	struct tidss_device *tidss = dispc->tidss;
+	struct device *dev = tidss->dev;
+	const u32 hw_videoport = 0;
+	const u32 crtc_mask = 1;
+	const u32 hw_plane_id = 0;
+	struct drm_panel *panel;
+	struct drm_bridge *bridge;
+	u32 enc_type;
+	int ret;
+	struct tidss_plane *tplane;
+	struct tidss_crtc *tcrtc;
+	struct drm_encoder *enc;
+	u32 fourccs[ARRAY_SIZE(dispc6_color_formats)];
+	unsigned int i;
+
+	/* first find if there is a connected panel/bridge */
+
+	ret = drm_of_find_panel_or_bridge(dev->of_node, hw_videoport, 0, &panel, &bridge);
+	if (ret) {
+		dev_dbg(dev, "no panel or bridge found\n");
+		return ret;
+	}
+
+	if (panel) {
+		dev_dbg(dev, "Setting up panel\n");
+
+		enc_type = DRM_MODE_ENCODER_DPI;
+
+		bridge = devm_drm_panel_bridge_add(dev, panel, DRM_MODE_CONNECTOR_DPI);
+		if (IS_ERR(bridge)) {
+			dev_err(dev, "failed to set up panel bridge\n");
+			return PTR_ERR(bridge);
+		}
+	} else {
+		enc_type = DRM_MODE_ENCODER_NONE;
+	}
+
+	/* then create a plane, a crtc and an encoder for the panel/bridge */
+
+	for (i = 0; i < ARRAY_SIZE(dispc6_color_formats); ++i)
+		fourccs[i] = dispc6_color_formats[i].fourcc;
+
+	tplane = tidss_plane_create(tidss, hw_plane_id, DRM_PLANE_TYPE_PRIMARY,
+				    crtc_mask, fourccs, ARRAY_SIZE(fourccs));
+	if (IS_ERR(tplane)) {
+		dev_err(tidss->dev, "plane create failed\n");
+		return PTR_ERR(tplane);
+	}
+
+	tidss->planes[tidss->num_planes++] = &tplane->plane;
+
+	tcrtc = tidss_crtc_create(tidss, hw_videoport, &tplane->plane);
+	if (IS_ERR(tcrtc)) {
+		dev_err(tidss->dev, "crtc create failed\n");
+		return PTR_ERR(tcrtc);
+	}
+
+	tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc;
+
+	enc = tidss_encoder_create(tidss, enc_type, 1 << tcrtc->crtc.index);
+	if (IS_ERR(enc)) {
+		dev_err(tidss->dev, "encoder create failed\n");
+		return PTR_ERR(enc);
+	}
+
+	ret = drm_bridge_attach(enc, bridge, NULL);
+	if (ret) {
+		dev_err(tidss->dev, "bridge attach failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dispc6_remove(struct dispc_device *dispc);
+
+static const struct tidss_dispc_ops dispc6_ops = {
+	.read_and_clear_irqstatus = dispc6_read_and_clear_irqstatus,
+	.write_irqenable = dispc6_write_irqenable,
+
+	.get_memory_bandwidth_limit = dispc6_get_memory_bandwidth_limit,
+
+	.get_num_vps = dispc6_get_num_vps,
+	.vp_name = dispc6_vp_name,
+	.vp_feat = dispc6_vp_feat,
+	.vp_enable = dispc6_vp_enable,
+	.vp_disable = dispc6_vp_disable,
+	.vp_go_busy = dispc6_vp_go_busy,
+	.vp_go = dispc6_vp_go,
+	.vp_mode_valid = dispc6_vp_mode_valid,
+	.vp_check = dispc6_vp_check,
+	.vp_setup = dispc6_vp_setup,
+
+	.vp_set_clk_rate = dispc6_vp_set_clk_rate,
+	.vp_enable_clk = dispc6_vp_enable_clk,
+	.vp_disable_clk = dispc6_vp_disable_clk,
+
+	.get_num_planes = dispc6_get_num_planes,
+	.plane_name = dispc6_plane_name,
+	.plane_feat = dispc6_plane_feat,
+	.plane_enable = dispc6_plane_enable,
+	.plane_check = dispc6_plane_check,
+	.plane_setup = dispc6_plane_setup,
+
+	.runtime_get = dispc6_runtime_get,
+	.runtime_put = dispc6_runtime_put,
+
+	.runtime_suspend = dispc6_runtime_suspend,
+	.runtime_resume = dispc6_runtime_resume,
+
+	.remove = dispc6_remove,
+
+	.modeset_init = dispc6_modeset_init,
+};
+
+static int dispc6_iomap_resource(struct platform_device *pdev, const char *name,
+				 void __iomem **base)
+{
+	struct resource *res;
+	void __iomem *b;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+	b = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(b)) {
+		dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name);
+		return PTR_ERR(b);
+	}
+
+	*base = b;
+
+	return 0;
+}
+
+int dispc6_init(struct tidss_device *tidss)
+{
+	struct device *dev = tidss->dev;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dispc_device *dispc;
+	int r;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dispc = devm_kzalloc(dev, sizeof(*dispc), GFP_KERNEL);
+	if (!dispc)
+		return -ENOMEM;
+
+	dispc->tidss = tidss;
+	dispc->dev = dev;
+
+	r = dispc6_init_features(dispc);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "cfg", &dispc->base_cfg);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "common", &dispc->base_common);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "vid1", &dispc->base_vid1);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "ovr1", &dispc->base_ovr1);
+	if (r)
+		goto err_free;
+
+	r = dispc6_iomap_resource(pdev, "vp1", &dispc->base_vp1);
+	if (r)
+		goto err_free;
+
+	dev_dbg(dev, "dispc6_bind: iores ok\n");
+
+	dispc->fclk = devm_clk_get(dev, "fck");
+	if (IS_ERR(dispc->fclk)) {
+		dev_err(dev, "Failed to get fclk\n");
+		r = PTR_ERR(dispc->fclk);
+		goto err_free;
+	}
+
+	dispc->vp_clk = devm_clk_get(dev, "vp1");
+	if (IS_ERR(dispc->vp_clk)) {
+		dev_err(dev, "Failed to get vp1 clk\n");
+		r = PTR_ERR(dispc->vp_clk);
+		goto err_free;
+	}
+
+	r = dispc6_init_gamma_tables(dispc);
+	if (r)
+		goto err_free;
+
+	tidss->dispc_ops = &dispc6_ops;
+	tidss->dispc = dispc;
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+err_free:
+	dev_err(dev, "%s failed: %d\n", __func__, r);
+	return r;
+}
+
+static void dispc6_remove(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "dispc6_unbind\n");
+
+	dispc->tidss->dispc_ops = NULL;
+	dispc->tidss->dispc = NULL;
+
+	dev_dbg(dev, "dispc6_unbind done\n");
+}

+ 109 - 0
drivers/gpu/drm/tidss/tidss_dispc6.h

@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC6_H__
+#define __TIDSS_DISPC6_H__
+
+/* COMMON */
+
+#define DISPC_REVISION			0x000
+#define DISPC_SYSCONFIG			0x004
+#define DISPC_SYSSTATUS			0x008
+
+#define DISPC_IRQ_EOI			0x020
+#define DISPC_IRQSTATUS_RAW		0x024
+#define DISPC_IRQSTATUS			0x028
+#define DISPC_IRQENABLE_SET		0x02c
+#define DISPC_IRQENABLE_CLR		0x030
+#define DISPC_IRQWAKEEN			0x034
+
+#define DISPC_GLOBAL_MFLAG_ATTRIBUTE	0x040
+#define DISPC_GLOBAL_BUFFER		0x044
+#define DISPC_BA0_FLIPIMMEDIATE_EN	0x048
+
+#define DISPC_DBG_CONTROL		0x04c
+#define DISPC_DBG_STATUS		0x050
+
+#define DISPC_CLKGATING_DISABLE		0x054
+
+/* VID */
+
+#define DISPC_VID_ACCUH_0		0x0
+#define DISPC_VID_ACCUH_1		0x4
+#define DISPC_VID_ACCUH2_0		0x8
+#define DISPC_VID_ACCUH2_1		0xc
+
+#define DISPC_VID_ACCUV_0		0x10
+#define DISPC_VID_ACCUV_1		0x14
+#define DISPC_VID_ACCUV2_0		0x18
+#define DISPC_VID_ACCUV2_1		0x1c
+
+#define DISPC_VID_ATTRIBUTES		0x20
+#define DISPC_VID_ATTRIBUTES2		0x24
+
+#define DISPC_VID_BA_0			0x28
+#define DISPC_VID_BA_1			0x2c
+#define DISPC_VID_BA_UV_0		0x30
+#define DISPC_VID_BA_UV_1		0x34
+#define DISPC_VID_BUF_SIZE_STATUS	0x38
+#define DISPC_VID_BUF_THRESHOLD		0x3c
+
+#define DISPC_VID_CONV_COEF(n)		(0x40 + (n) * 4)
+
+#define DISPC_VID_FIRH			0x5c
+#define DISPC_VID_FIRH2			0x60
+#define DISPC_VID_FIRV			0x64
+#define DISPC_VID_FIRV2			0x68
+
+#define DISPC_VID_FIR_COEFS_H0		0x6c
+#define DISPC_VID_FIR_COEF_H0(phase)	(0x6c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H0_C	0x90
+#define DISPC_VID_FIR_COEF_H0_C(phase)	(0x90 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_H12		0xb4
+#define DISPC_VID_FIR_COEF_H12(phase)	(0xb4 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H12_C	0xf4
+#define DISPC_VID_FIR_COEF_H12_C(phase)	(0xf4 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V0		0x134
+#define DISPC_VID_FIR_COEF_V0(phase)	(0x134 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V0_C	0x158
+#define DISPC_VID_FIR_COEF_V0_C(phase)	(0x158 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V12		0x17c
+#define DISPC_VID_FIR_COEF_V12(phase)	(0x17c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V12_C	0x1bc
+#define DISPC_VID_FIR_COEF_V12_C(phase)	(0x1bc + (phase) * 4)
+
+#define DISPC_VID_IRQENABLE		0x200
+#define DISPC_VID_IRQSTATUS		0x204
+
+#define DISPC_VID_MFLAG_THRESHOLD	0x208
+#define DISPC_VID_PICTURE_SIZE		0x20c
+#define DISPC_VID_PIXEL_INC		0x210
+#define DISPC_VID_POSITION		0x214
+#define DISPC_VID_PRELOAD		0x218
+#define DISPC_VID_ROW_INC		0x21c
+#define DISPC_VID_SIZE			0x220
+
+/* OVR */
+
+#define DISPC_OVR_DEFAULT_COLOR		0x08
+#define DISPC_OVR_DEFAULT_COLOR2	0x0c
+
+/* VP */
+
+#define DISPC_VP_CONFIG			0x00
+#define DISPC_VP_CONTROL		0x04
+#define DISPC_VP_GAMMA_TABLE		0x20
+#define DISPC_VP_IRQENABLE		0x3c
+#define DISPC_VP_IRQSTATUS		0x40
+#define DISPC_VP_POL_FREQ		0x4c
+#define DISPC_VP_SIZE_SCREEN		0x50
+#define DISPC_VP_TIMING_H		0x54
+#define DISPC_VP_TIMING_V		0x58
+
+#endif

+ 2233 - 0
drivers/gpu/drm/tidss/tidss_dispc7.c

@@ -0,0 +1,2233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_of.h>
+
+#include "tidss_drv.h"
+#include "tidss_crtc.h"
+#include "tidss_plane.h"
+#include "tidss_encoder.h"
+
+#include "tidss_dispc.h"
+#include "tidss_scale_coefs.h"
+#include "tidss_dispc7.h"
+
+static const struct dispc7_features dispc7_am6_feats = {
+	.min_pclk = 1000,
+	.max_pclk = 200000000,
+
+	.scaling = {
+		.in_width_max_5tap_rgb = 1280,
+		.in_width_max_3tap_rgb = 2560,
+		.in_width_max_5tap_yuv = 2560,
+		.in_width_max_3tap_yuv = 4096,
+		.upscale_limit = 16,
+		.downscale_limit_5tap = 4,
+		.downscale_limit_3tap = 2,
+		/*
+		 * The max supported pixel inc value is 255. The value
+		 * of pixel inc is calculated like this: 1+(xinc-1)*bpp.
+		 * The maximum bpp of all formats supported by the HW
+		 * is 8. So the maximum supported xinc value is 32,
+		 * because 1+(32-1)*8 < 255 < 1+(33-1)*4.
+		 */
+		.xinc_max = 32,
+	},
+
+	.num_vps = 2,
+	.vp_name = { "vp1", "vp2" },
+	.ovr_name = { "ovr1", "ovr2" },
+	.vpclk_name =  { "vp1", "vp2" },
+	.vp_bus_type = { DISPC7_VP_OLDI, DISPC7_VP_DPI },
+
+	.num_planes = 2,
+	/* note: vid is plane_id 0 and vidl1 is plane_id 1 */
+	.vid_name = { "vid", "vidl1" },
+	.vid_lite = { false, true, },
+	.vid_order = { 1, 0 },
+};
+
+static const struct of_device_id dispc7_of_table[] = {
+	{ .compatible = "ti,am6-dss", .data = &dispc7_am6_feats },
+	{ }
+};
+
+/*
+ * TRM gives bitfields as start:end, where start is the higher bit
+ * number. For example 7:0
+ */
+
+#define FLD_MASK(start, end)	(((1 << ((start) - (end) + 1)) - 1) << (end))
+#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
+#define FLD_GET(val, start, end) (((val) & FLD_MASK(start, end)) >> (end))
+#define FLD_MOD(orig, val, start, end) \
+	(((orig) & ~FLD_MASK(start, end)) | FLD_VAL(val, start, end))
+
+#define REG_GET(dispc, idx, start, end) \
+	FLD_GET(dispc7_read(dispc, idx), start, end)
+
+#define REG_FLD_MOD(dispc, idx, val, start, end) \
+	dispc7_write(dispc, idx, FLD_MOD(dispc7_read(dispc, idx), val, start, end))
+
+#define VID_REG_GET(dispc, hw_plane, idx, start, end) \
+	FLD_GET(dispc7_vid_read(dispc, hw_plane, idx), start, end)
+
+#define VID_REG_FLD_MOD(dispc, hw_plane, idx, val, start, end) \
+	dispc7_vid_write(dispc, hw_plane, idx, FLD_MOD(dispc7_vid_read(dispc, hw_plane, idx), val, start, end))
+
+#define VP_REG_GET(dispc, vp, idx, start, end) \
+	FLD_GET(dispc7_vp_read(dispc, vp, idx), start, end)
+
+#define VP_REG_FLD_MOD(dispc, vp, idx, val, start, end) \
+	dispc7_vp_write(dispc, vp, idx, FLD_MOD(dispc7_vp_read(dispc, vp, idx), val, start, end))
+
+#define OVR_REG_GET(dispc, ovr, idx, start, end) \
+	FLD_GET(dispc7_ovr_read(dispc, ovr, idx), start, end)
+
+#define OVR_REG_FLD_MOD(dispc, ovr, idx, val, start, end) \
+	dispc7_ovr_write(dispc, ovr, idx, FLD_MOD(dispc7_ovr_read(dispc, ovr, idx), val, start, end))
+
+#define DISPC7_GAMMA_TABLE_SIZE 256
+
+struct dss_vp_data {
+	u32 gamma_table[DISPC7_GAMMA_TABLE_SIZE];
+};
+
+struct dss_plane_data {
+	u32 zorder;
+	u32 hw_videoport;
+};
+
+struct dispc_device {
+	struct tidss_device *tidss;
+	struct device *dev;
+
+	void __iomem *base_common;
+	void __iomem *base_vid[DISPC7_MAX_PLANES];
+	void __iomem *base_ovr[DISPC7_MAX_PORTS];
+	void __iomem *base_vp[DISPC7_MAX_PORTS];
+
+	struct regmap *syscon;
+
+	struct clk *vp_clk[DISPC7_MAX_PORTS];
+
+	const struct dispc7_features *feat;
+
+	struct clk *fclk;
+
+	bool is_enabled;
+
+	struct dss_vp_data vp_data[DISPC7_MAX_PORTS];
+
+	struct dss_plane_data plane_data[DISPC7_MAX_PLANES];
+};
+
+
+static void dispc7_write(struct dispc_device *dispc, u16 reg, u32 val)
+{
+	iowrite32(val, dispc->base_common + reg);
+}
+
+static u32 dispc7_read(struct dispc_device *dispc, u16 reg)
+{
+	return ioread32(dispc->base_common + reg);
+}
+
+static void dispc7_vid_write(struct dispc_device *dispc, u32 hw_plane, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vid[hw_plane];
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc7_vid_read(struct dispc_device *dispc, u32 hw_plane, u16 reg)
+{
+	void __iomem *base = dispc->base_vid[hw_plane];
+
+	return ioread32(base + reg);
+}
+
+static void dispc7_ovr_write(struct dispc_device *dispc, u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_ovr[hw_videoport];
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc7_ovr_read(struct dispc_device *dispc, u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_ovr[hw_videoport];
+
+	return ioread32(base + reg);
+}
+
+static void dispc7_vp_write(struct dispc_device *dispc, u32 hw_videoport, u16 reg, u32 val)
+{
+	void __iomem *base = dispc->base_vp[hw_videoport];
+
+	iowrite32(val, base + reg);
+}
+
+static u32 dispc7_vp_read(struct dispc_device *dispc, u32 hw_videoport, u16 reg)
+{
+	void __iomem *base = dispc->base_vp[hw_videoport];
+
+	return ioread32(base + reg);
+}
+
+
+static int dispc7_runtime_get(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "%s\n", __func__);
+
+	r = pm_runtime_get_sync(dispc->dev);
+	WARN_ON(r < 0);
+	return r < 0 ? r : 0;
+}
+
+static void dispc7_runtime_put(struct dispc_device *dispc)
+{
+	int r;
+
+	dev_dbg(dispc->dev, "%s\n", __func__);
+
+	r = pm_runtime_put_sync(dispc->dev);
+	WARN_ON(r < 0);
+}
+
+static u64 dispc7_vp_irq_from_raw(u32 stat, u32 hw_videoport)
+{
+	u64 vp_stat = 0;
+
+	if (stat & BIT(0))
+		vp_stat |= DSS_IRQ_VP_FRAME_DONE(hw_videoport);
+	if (stat & BIT(1))
+		vp_stat |= DSS_IRQ_VP_VSYNC_EVEN(hw_videoport);
+	if (stat & BIT(2))
+		vp_stat |= DSS_IRQ_VP_VSYNC_ODD(hw_videoport);
+	if (stat & BIT(4))
+		vp_stat |= DSS_IRQ_VP_SYNC_LOST(hw_videoport);
+
+	return vp_stat;
+}
+
+static u32 dispc7_vp_irq_to_raw(u64 vpstat, u32 hw_videoport)
+{
+	u32 stat = 0;
+
+	if (vpstat & DSS_IRQ_VP_FRAME_DONE(hw_videoport))
+		stat |= BIT(0);
+	if (vpstat & DSS_IRQ_VP_VSYNC_EVEN(hw_videoport))
+		stat |= BIT(1);
+	if (vpstat & DSS_IRQ_VP_VSYNC_ODD(hw_videoport))
+		stat |= BIT(2);
+	if (vpstat & DSS_IRQ_VP_SYNC_LOST(hw_videoport))
+		stat |= BIT(4);
+
+	return stat;
+}
+
+static u64 dispc7_vid_irq_from_raw(u32 stat, u32 hw_plane)
+{
+	u64 vid_stat = 0;
+
+	if (stat & BIT(0))
+		vid_stat |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(hw_plane);
+
+	return vid_stat;
+}
+
+static u32 dispc7_vid_irq_to_raw(u64 vidstat, u32 hw_plane)
+{
+	u32 stat = 0;
+
+	if (vidstat & DSS_IRQ_PLANE_FIFO_UNDERFLOW(hw_plane))
+		stat |= BIT(0);
+
+	return stat;
+}
+
+static u64 dispc7_vp_read_irqstatus(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc7_read(dispc, DISPC_VP_IRQSTATUS(hw_videoport));
+
+	return dispc7_vp_irq_from_raw(stat, hw_videoport);
+}
+
+static void dispc7_vp_write_irqstatus(struct dispc_device *dispc,
+				      u32 hw_videoport, u64 vpstat)
+{
+	u32 stat = dispc7_vp_irq_to_raw(vpstat, hw_videoport);
+
+	dispc7_write(dispc, DISPC_VP_IRQSTATUS(hw_videoport), stat);
+}
+
+static u64 dispc7_vid_read_irqstatus(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc7_read(dispc, DISPC_VID_IRQSTATUS(hw_plane));
+
+	return dispc7_vid_irq_from_raw(stat, hw_plane);
+}
+
+static void dispc7_vid_write_irqstatus(struct dispc_device *dispc,
+				       u32 hw_plane, u64 vidstat)
+{
+	u32 stat = dispc7_vid_irq_to_raw(vidstat, hw_plane);
+
+	dispc7_write(dispc, DISPC_VID_IRQSTATUS(hw_plane), stat);
+}
+
+static u64 dispc7_vp_read_irqenable(struct dispc_device *dispc,
+				    u32 hw_videoport)
+{
+	u32 stat = dispc7_read(dispc, DISPC_VP_IRQENABLE(hw_videoport));
+
+	return dispc7_vp_irq_from_raw(stat, hw_videoport);
+}
+
+static void dispc7_vp_write_irqenable(struct dispc_device *dispc,
+				      u32 hw_videoport, u64 vpstat)
+{
+	u32 stat = dispc7_vp_irq_to_raw(vpstat, hw_videoport);
+
+	dispc7_write(dispc, DISPC_VP_IRQENABLE(hw_videoport), stat);
+}
+
+
+static u64 dispc7_vid_read_irqenable(struct dispc_device *dispc,
+				     u32 hw_plane)
+{
+	u32 stat = dispc7_read(dispc, DISPC_VID_IRQENABLE(hw_plane));
+
+	return dispc7_vid_irq_from_raw(stat, hw_plane);
+}
+
+static void dispc7_vid_write_irqenable(struct dispc_device *dispc,
+				       u32 hw_plane, u64 vidstat)
+{
+	u32 stat = dispc7_vid_irq_to_raw(vidstat, hw_plane);
+
+	dispc7_write(dispc, DISPC_VID_IRQENABLE(hw_plane), stat);
+}
+
+static void dispc7_clear_irqstatus(struct dispc_device *dispc, u64 clearmask)
+{
+	unsigned int i;
+	u32 top_clear = 0;
+
+	for (i = 0; i < dispc->feat->num_vps; ++i) {
+		if (clearmask & DSS_IRQ_VP_MASK(i)) {
+			dispc7_vp_write_irqstatus(dispc, i, clearmask);
+			top_clear |= BIT(i);
+		}
+	}
+	for (i = 0; i < dispc->feat->num_planes; ++i) {
+		if (clearmask & DSS_IRQ_PLANE_MASK(i)) {
+			dispc7_vid_write_irqstatus(dispc, i, clearmask);
+			top_clear |= BIT(4 + i);
+		}
+	}
+	dispc7_write(dispc, DISPC_IRQSTATUS, top_clear);
+
+	/* Flush posted writes */
+	dispc7_read(dispc, DISPC_IRQSTATUS);
+}
+
+static u64 dispc7_read_and_clear_irqstatus(struct dispc_device *dispc)
+{
+	u64 status = 0;
+	unsigned int i;
+
+	for (i = 0; i < dispc->feat->num_vps; ++i)
+		status |= dispc7_vp_read_irqstatus(dispc, i);
+
+	for (i = 0; i < dispc->feat->num_planes; ++i)
+		status |= dispc7_vid_read_irqstatus(dispc, i);
+
+	dispc7_clear_irqstatus(dispc, status);
+
+	return status;
+}
+
+static u64 dispc7_read_irqenable(struct dispc_device *dispc)
+{
+	u64 enable = 0;
+	unsigned int i;
+
+	for (i = 0; i < dispc->feat->num_vps; ++i)
+		enable |= dispc7_vp_read_irqenable(dispc, i);
+
+	for (i = 0; i < dispc->feat->num_planes; ++i)
+		enable |= dispc7_vid_read_irqenable(dispc, i);
+
+	return enable;
+}
+
+static void dispc7_write_irqenable(struct dispc_device *dispc, u64 mask)
+{
+	unsigned int i;
+	u32 main_enable = 0, main_disable = 0;
+	u64 old_mask;
+
+	old_mask = dispc7_read_irqenable(dispc);
+
+	/* clear the irqstatus for newly enabled irqs */
+	dispc7_clear_irqstatus(dispc, (old_mask ^ mask) & mask);
+
+	for (i = 0; i < dispc->feat->num_vps; ++i) {
+		dispc7_vp_write_irqenable(dispc, i, mask);
+		if (mask & DSS_IRQ_VP_MASK(i))
+			main_enable |= BIT(i);		/* VP IRQ */
+		else
+			main_disable |= BIT(i);		/* VP IRQ */
+	}
+
+	for (i = 0; i < dispc->feat->num_planes; ++i) {
+		dispc7_vid_write_irqenable(dispc, i, mask);
+		if (mask & DSS_IRQ_PLANE_MASK(i))
+			main_enable |= BIT(i + 4);	/* VID IRQ */
+		else
+			main_disable |= BIT(i + 4);	/* VID IRQ */
+	}
+
+	if (main_enable)
+		dispc7_write(dispc, DISPC_IRQENABLE_SET, main_enable);
+
+	if (main_disable)
+		dispc7_write(dispc, DISPC_IRQENABLE_CLR, main_disable);
+
+	/* Flush posted writes */
+	dispc7_read(dispc, DISPC_IRQENABLE_SET);
+}
+
+enum dispc7_oldi_mode { SPWG_18 = 0, JEIDA_24 = 1, SPWG_24 = 2 };
+
+struct dispc7_bus_format {
+	u32 bus_fmt;
+	u32 data_width;
+	enum dispc7_vp_bus_type bus_type;
+	enum dispc7_oldi_mode oldi_mode;
+};
+
+static const struct dispc7_bus_format dispc7_bus_formats[] = {
+	{ MEDIA_BUS_FMT_RGB444_1X12,		12, DISPC7_VP_DPI, 0 },
+	{ MEDIA_BUS_FMT_RGB565_1X16,		16, DISPC7_VP_DPI, 0 },
+	{ MEDIA_BUS_FMT_RGB666_1X18,		18, DISPC7_VP_DPI, 0 },
+	{ MEDIA_BUS_FMT_RGB888_1X24,		24, DISPC7_VP_DPI, 0 },
+	{ MEDIA_BUS_FMT_RGB666_1X7X3_SPWG,	18, DISPC7_VP_OLDI, SPWG_18 },
+	{ MEDIA_BUS_FMT_RGB888_1X7X4_SPWG,	24, DISPC7_VP_OLDI, SPWG_24 },
+	{ MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA,	24, DISPC7_VP_OLDI, JEIDA_24 },
+};
+
+static const
+struct dispc7_bus_format *dispc7_vp_find_bus_fmt(struct dispc_device *dispc,
+						 u32 hw_videoport,
+						 u32 bus_fmt, u32 bus_flags)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc7_bus_formats); ++i) {
+		if (dispc7_bus_formats[i].bus_fmt == bus_fmt)
+			return &dispc7_bus_formats[i];
+	}
+
+	return NULL;
+}
+
+static void dispc7_oldi_tx_power(struct dispc_device *dispc, bool power)
+{
+	u32 val = power ? 0 : CTRLMMR0P1_OLDI_PWRDN_TX;
+
+	if (WARN_ON(!dispc->syscon))
+		return;
+
+	regmap_update_bits(dispc->syscon, CTRLMMR0P1_OLDI_DAT0_IO_CTRL,
+			   CTRLMMR0P1_OLDI_PWRDN_TX, val);
+	regmap_update_bits(dispc->syscon, CTRLMMR0P1_OLDI_DAT1_IO_CTRL,
+			   CTRLMMR0P1_OLDI_PWRDN_TX, val);
+	regmap_update_bits(dispc->syscon, CTRLMMR0P1_OLDI_DAT2_IO_CTRL,
+			   CTRLMMR0P1_OLDI_PWRDN_TX, val);
+	regmap_update_bits(dispc->syscon, CTRLMMR0P1_OLDI_DAT3_IO_CTRL,
+			   CTRLMMR0P1_OLDI_PWRDN_TX, val);
+	regmap_update_bits(dispc->syscon, CTRLMMR0P1_OLDI_CLK_IO_CTRL,
+			   CTRLMMR0P1_OLDI_PWRDN_TX, val);
+}
+
+static void dispc7_set_num_datalines(struct dispc_device *dispc,
+				     u32 hw_videoport, int num_lines)
+{
+	int v;
+
+	switch (num_lines) {
+	case 12:
+		v = 0; break;
+	case 16:
+		v = 1; break;
+	case 18:
+		v = 2; break;
+	case 24:
+		v = 3; break;
+	case 30:
+		v = 4; break;
+	case 36:
+		v = 5; break;
+	default:
+		WARN_ON(1);
+		v = 3;
+	}
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, v, 10, 8);
+}
+
+static void dispc7_enable_oldi(struct dispc_device *dispc, u32 hw_videoport,
+			       const struct dispc7_bus_format *fmt)
+{
+	u32 oldi_cfg = 0;
+	u32 oldi_reset_bit = BIT(5 + hw_videoport);
+	int count = 0;
+
+	/*
+	 * On am6 DUALMODESYNC, MASTERSLAVE, MODE, and SRC are set
+	 * statically to 0.
+	 */
+
+	if (fmt->data_width == 24)
+		oldi_cfg |= BIT(8); /* MSB */
+	else if (fmt->data_width != 18)
+		dev_warn(dispc->dev, "%s: %d port width not supported\n",
+			 __func__, fmt->data_width);
+
+	oldi_cfg |= BIT(7); /* DEPOL */
+
+	oldi_cfg = FLD_MOD(oldi_cfg, fmt->oldi_mode, 3, 1);
+
+	oldi_cfg |= BIT(12); /* SOFTRST */
+
+	oldi_cfg |= BIT(0); /* ENABLE */
+
+	dispc7_vp_write(dispc, hw_videoport, DISPC_VP_DSS_OLDI_CFG, oldi_cfg);
+
+	while (!(oldi_reset_bit & dispc7_read(dispc, DSS_SYSSTATUS)) &&
+	       count < 10000)
+		count++;
+
+	if (!(oldi_reset_bit & dispc7_read(dispc, DSS_SYSSTATUS)))
+		dev_warn(dispc->dev, "%s: timeout waiting OLDI reset done\n",
+			 __func__);
+}
+
+static void dispc7_vp_prepare(struct dispc_device *dispc, u32 hw_videoport,
+			      const struct drm_crtc_state *state)
+{
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	const struct dispc7_bus_format *fmt;
+
+	fmt = dispc7_vp_find_bus_fmt(dispc, hw_videoport, tstate->bus_format,
+				     tstate->bus_flags);
+
+	if (WARN_ON(!fmt))
+		return;
+
+	if (WARN_ON(dispc->feat->vp_bus_type[hw_videoport] != fmt->bus_type))
+		return;
+
+	if (dispc->feat->vp_bus_type[hw_videoport] == DISPC7_VP_OLDI) {
+		dispc7_oldi_tx_power(dispc, true);
+
+		dispc7_enable_oldi(dispc, hw_videoport, fmt);
+	}
+}
+
+static void dispc7_vp_enable(struct dispc_device *dispc, u32 hw_videoport,
+			      const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	bool align, onoff, rf, ieo, ipc, ihs, ivs;
+	const struct dispc7_bus_format *fmt;
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	fmt = dispc7_vp_find_bus_fmt(dispc, hw_videoport, tstate->bus_format,
+				     tstate->bus_flags);
+
+	if (WARN_ON(!fmt))
+		return;
+
+	dispc7_set_num_datalines(dispc, hw_videoport, fmt->data_width);
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	dispc7_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_H,
+			FLD_VAL(hsw - 1, 7, 0) |
+			FLD_VAL(hfp - 1, 19, 8) |
+			FLD_VAL(hbp - 1, 31, 20));
+
+	dispc7_vp_write(dispc, hw_videoport, DISPC_VP_TIMING_V,
+			FLD_VAL(vsw - 1, 7, 0) |
+			FLD_VAL(vfp, 19, 8) |
+			FLD_VAL(vbp, 31, 20));
+
+	ivs = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+
+	ihs = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+	ieo = !!(tstate->bus_flags & DRM_BUS_FLAG_DE_LOW);
+
+	ipc = !!(tstate->bus_flags & DRM_BUS_FLAG_PIXDATA_NEGEDGE);
+
+	/* always use the 'rf' setting */
+	onoff = true;
+
+	rf = !!(tstate->bus_flags & DRM_BUS_FLAG_SYNC_POSEDGE);
+
+	/* always use aligned syncs */
+	align = true;
+
+	/* always use DE_HIGH for OLDI */
+	if (dispc->feat->vp_bus_type[hw_videoport] == DISPC7_VP_OLDI)
+		ieo = false;
+
+	dispc7_vp_write(dispc, hw_videoport, DISPC_VP_POL_FREQ,
+			FLD_VAL(align, 18, 18) |
+			FLD_VAL(onoff, 17, 17) |
+			FLD_VAL(rf, 16, 16) |
+			FLD_VAL(ieo, 15, 15) |
+			FLD_VAL(ipc, 14, 14) |
+			FLD_VAL(ihs, 13, 13) |
+			FLD_VAL(ivs, 12, 12));
+
+	dispc7_vp_write(dispc, hw_videoport, DISPC_VP_SIZE_SCREEN,
+			FLD_VAL(mode->hdisplay - 1, 11, 0) |
+			FLD_VAL(mode->vdisplay - 1, 27, 16));
+
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 0, 0);
+}
+
+static void dispc7_vp_disable(struct dispc_device *dispc, u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 0, 0, 0);
+}
+
+static void dispc7_vp_unprepare(struct dispc_device *dispc, u32 hw_videoport)
+{
+	if (dispc->feat->vp_bus_type[hw_videoport] == DISPC7_VP_OLDI) {
+		dispc7_vp_write(dispc, hw_videoport, DISPC_VP_DSS_OLDI_CFG, 0);
+
+		dispc7_oldi_tx_power(dispc, false);
+	}
+}
+
+static bool dispc7_vp_go_busy(struct dispc_device *dispc,
+			      u32 hw_videoport)
+{
+	return VP_REG_GET(dispc, hw_videoport, DISPC_VP_CONTROL, 5, 5);
+}
+
+static void dispc7_vp_go(struct dispc_device *dispc, u32 hw_videoport)
+{
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONTROL, 1, 5, 5);
+}
+
+enum c8_to_c12_mode { C8_TO_C12_REPLICATE, C8_TO_C12_MAX, C8_TO_C12_MIN };
+
+static u16 c8_to_c12(u8 c8, enum c8_to_c12_mode mode)
+{
+	u16 c12;
+
+	c12 = c8 << 4;
+
+	switch (mode) {
+	case C8_TO_C12_REPLICATE:
+		/* Copy c8 4 MSB to 4 LSB for full scale c12 */
+		c12 |= c8 >> 4;
+		break;
+	case C8_TO_C12_MAX:
+		c12 |= 0xF;
+		break;
+	default:
+	case C8_TO_C12_MIN:
+		break;
+	};
+
+	return c12;
+}
+
+static u64 argb8888_to_argb12121212(u32 argb8888, enum c8_to_c12_mode m)
+{
+	u8 a, r, g, b;
+	u64 v;
+
+	a = (argb8888 >> 24) & 0xff;
+	r = (argb8888 >> 16) & 0xff;
+	g = (argb8888 >> 8) & 0xff;
+	b = (argb8888 >> 0) & 0xff;
+
+	v = ((u64)c8_to_c12(a, m) << 36) | ((u64)c8_to_c12(r, m) << 24) |
+		((u64)c8_to_c12(g, m) << 12) | (u64)c8_to_c12(b, m);
+
+	return v;
+}
+
+static void dispc7_vp_set_default_color(struct dispc_device *dispc,
+					u32 hw_videoport, u32 default_color)
+{
+	u64 v;
+
+	v = argb8888_to_argb12121212(default_color, C8_TO_C12_REPLICATE);
+
+	dispc7_ovr_write(dispc, hw_videoport,
+			 DISPC_OVR_DEFAULT_COLOR, v & 0xffffffff);
+	dispc7_ovr_write(dispc, hw_videoport,
+			 DISPC_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff);
+}
+
+static enum drm_mode_status dispc7_vp_mode_valid(struct dispc_device *dispc,
+						 u32 hw_videoport,
+						 const struct drm_display_mode *mode)
+{
+	u32 hsw, hfp, hbp, vsw, vfp, vbp;
+
+	if (mode->clock * 1000 < dispc->feat->min_pclk)
+		return MODE_CLOCK_LOW;
+
+	if (mode->clock * 1000 > dispc->feat->max_pclk)
+		return MODE_CLOCK_HIGH;
+
+	if (mode->hdisplay > 4096)
+		return MODE_BAD;
+
+	if (mode->vdisplay > 4096)
+		return MODE_BAD;
+
+	/* TODO: add interlace support */
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return MODE_NO_INTERLACE;
+
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	hbp = mode->htotal - mode->hsync_end;
+
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+
+	if (hsw < 1 || hsw > 256 ||
+	    hfp < 1 || hfp > 4096 ||
+	    hbp < 1 || hbp > 4096)
+		return MODE_BAD_HVALUE;
+
+	if (vsw < 1 || vsw > 256 ||
+	    vfp < 0 || vfp > 4095 ||
+	    vbp < 0 || vbp > 4095)
+		return MODE_BAD_VVALUE;
+
+	return MODE_OK;
+}
+
+static int dispc7_vp_check(struct dispc_device *dispc, u32 hw_videoport,
+			   const struct drm_crtc_state *state)
+{
+	const struct drm_display_mode *mode = &state->adjusted_mode;
+	const struct tidss_crtc_state *tstate = to_tidss_crtc_state(state);
+	const struct dispc7_bus_format *fmt;
+	enum drm_mode_status ok;
+
+	ok = dispc7_vp_mode_valid(dispc, hw_videoport, mode);
+	if (ok != MODE_OK) {
+		dev_dbg(dispc->dev, "%s: bad mode: %ux%u pclk %u kHz\n",
+			__func__, mode->hdisplay, mode->vdisplay, mode->clock);
+		return -EINVAL;
+	}
+
+	fmt = dispc7_vp_find_bus_fmt(dispc, hw_videoport, tstate->bus_format,
+				     tstate->bus_flags);
+	if (!fmt) {
+		dev_dbg(dispc->dev, "%s: Unsupported bus format: %u\n",
+			__func__, tstate->bus_format);
+		return -EINVAL;
+	}
+
+	if (dispc->feat->vp_bus_type[hw_videoport] != fmt->bus_type) {
+		dev_dbg(dispc->dev, "%s: %s is not %s-port\n",
+			__func__, dispc->feat->vp_name[hw_videoport],
+			fmt->bus_type == DISPC7_VP_OLDI ? "OLDI" : "DPI");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dispc7_vp_enable_clk(struct dispc_device *dispc, u32 hw_videoport)
+{
+	int ret = clk_prepare_enable(dispc->vp_clk[hw_videoport]);
+
+	if (ret)
+		dev_err(dispc->dev, "%s: enabling clk failed: %d\n", __func__,
+			ret);
+
+	return ret;
+}
+
+static void dispc7_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport)
+{
+	clk_disable_unprepare(dispc->vp_clk[hw_videoport]);
+}
+
+/*
+ * Calculate the percentage difference between the requested pixel clock rate
+ * and the effective rate resulting from calculating the clock divider value.
+ */
+static unsigned int dispc7_pclk_diff(unsigned long rate,
+				     unsigned long real_rate)
+{
+	int r = rate / 100, rr = real_rate / 100;
+
+	return (unsigned int)(abs(((rr - r) * 100) / r));
+}
+
+static int dispc7_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
+				  unsigned long rate)
+{
+	int r;
+	unsigned long new_rate;
+
+	r = clk_set_rate(dispc->vp_clk[hw_videoport], rate);
+	if (r) {
+		dev_err(dispc->dev, "Failed to set vp%d clk rate to %lu\n",
+			hw_videoport, rate);
+		return r;
+	}
+
+	new_rate = clk_get_rate(dispc->vp_clk[hw_videoport]);
+
+	if (dispc7_pclk_diff(rate, new_rate) > 5)
+		dev_warn(dispc->dev,
+			 "Clock rate %lu differs over 5%% from requsted %lu\n",
+			 new_rate, rate);
+
+	dev_dbg(dispc->dev, "New VP%d rate %lu Hz (requested %lu Hz)\n",
+		hw_videoport, clk_get_rate(dispc->vp_clk[hw_videoport]), rate);
+
+	return 0;
+}
+
+/* OVR */
+static void dispc7_ovr_set_plane(struct dispc_device *dispc,
+				 u32 hw_plane, u32 hw_videoport,
+				 u32 x, u32 y, u32 zpos)
+{
+	OVR_REG_FLD_MOD(dispc, hw_videoport, DISPC_OVR_ATTRIBUTES(zpos),
+			hw_plane, 4, 1);
+	OVR_REG_FLD_MOD(dispc, hw_videoport, DISPC_OVR_ATTRIBUTES(zpos),
+			x, 17, 6);
+	OVR_REG_FLD_MOD(dispc, hw_videoport, DISPC_OVR_ATTRIBUTES(zpos),
+			y, 30, 19);
+}
+
+static void dispc7_ovr_enable_plane(struct dispc_device *dispc,
+				    u32 hw_videoport, u32 zpos, bool enable)
+{
+	OVR_REG_FLD_MOD(dispc, hw_videoport, DISPC_OVR_ATTRIBUTES(zpos),
+			!!enable, 0, 0);
+}
+
+/* CSC */
+enum csc_ctm {
+	CSC_RR, CSC_RG, CSC_RB,
+	CSC_GR, CSC_GG, CSC_GB,
+	CSC_BR, CSC_BG, CSC_BB,
+};
+
+enum csc_yuv2rgb {
+	CSC_RY, CSC_RCb, CSC_RCr,
+	CSC_GY, CSC_GCb, CSC_GCr,
+	CSC_BY, CSC_BCb, CSC_BCr,
+};
+
+enum csc_rgb2yuv {
+	CSC_YR,  CSC_YG,  CSC_YB,
+	CSC_CbR, CSC_CbG, CSC_CbB,
+	CSC_CrR, CSC_CrG, CSC_CrB,
+};
+
+struct dispc7_csc_coef {
+	void (*to_regval)(const struct dispc7_csc_coef *csc, u32 *regval);
+	int m[9];
+	int preoffset[3];
+	int postoffset[3];
+	enum { CLIP_LIMITED_RANGE = 0, CLIP_FULL_RANGE = 1, } cliping;
+	const char *name;
+};
+
+#define DISPC7_CSC_REGVAL_LEN 8
+
+static void dispc7_csc_offset_regval(const struct dispc7_csc_coef *csc,
+				     u32 *regval)
+{
+#define OVAL(x, y) (FLD_VAL(x, 15, 3) | FLD_VAL(y, 31, 19))
+	regval[5] = OVAL(csc->preoffset[0], csc->preoffset[1]);
+	regval[6] = OVAL(csc->preoffset[2], csc->postoffset[0]);
+	regval[7] = OVAL(csc->postoffset[1], csc->postoffset[2]);
+#undef OVAL
+}
+
+#define CVAL(x, y) (FLD_VAL(x, 10, 0) | FLD_VAL(y, 26, 16))
+static void dispc7_csc_yuv2rgb_regval(const struct dispc7_csc_coef *csc,
+				      u32 *regval)
+{
+	regval[0] = CVAL(csc->m[CSC_RY], csc->m[CSC_RCr]);
+	regval[1] = CVAL(csc->m[CSC_RCb], csc->m[CSC_GY]);
+	regval[2] = CVAL(csc->m[CSC_GCr], csc->m[CSC_GCb]);
+	regval[3] = CVAL(csc->m[CSC_BY], csc->m[CSC_BCr]);
+	regval[4] = CVAL(csc->m[CSC_BCb], 0);
+
+	dispc7_csc_offset_regval(csc, regval);
+}
+
+static void __maybe_unused dispc7_csc_rgb2yuv_regval(const struct dispc7_csc_coef *csc,
+				      u32 *regval)
+{
+	regval[0] = CVAL(csc->m[CSC_YR], csc->m[CSC_YG]);
+	regval[1] = CVAL(csc->m[CSC_YB], csc->m[CSC_CrR]);
+	regval[2] = CVAL(csc->m[CSC_CrG], csc->m[CSC_CrB]);
+	regval[3] = CVAL(csc->m[CSC_CbR], csc->m[CSC_CbG]);
+	regval[4] = CVAL(csc->m[CSC_CbB], 0);
+
+	dispc7_csc_offset_regval(csc, regval);
+}
+
+static void dispc7_csc_cpr_regval(const struct dispc7_csc_coef *csc,
+				  u32 *regval)
+{
+	regval[0] = CVAL(csc->m[CSC_RR], csc->m[CSC_RG]);
+	regval[1] = CVAL(csc->m[CSC_RB], csc->m[CSC_GR]);
+	regval[2] = CVAL(csc->m[CSC_GG], csc->m[CSC_GB]);
+	regval[3] = CVAL(csc->m[CSC_BR], csc->m[CSC_BG]);
+	regval[4] = CVAL(csc->m[CSC_BB], 0);
+
+	dispc7_csc_offset_regval(csc, regval);
+}
+#undef CVAL
+
+
+static void dispc7_vid_write_csc(struct dispc_device *dispc, u32 hw_plane,
+				 const struct dispc7_csc_coef *csc)
+{
+	static const u16 dispc_vid_csc_coef_reg[DISPC7_CSC_REGVAL_LEN] = {
+		DISPC_VID_CSC_COEF(0), DISPC_VID_CSC_COEF(1),
+		DISPC_VID_CSC_COEF(2), DISPC_VID_CSC_COEF(3),
+		DISPC_VID_CSC_COEF(4), DISPC_VID_CSC_COEF(5),
+		DISPC_VID_CSC_COEF(6), DISPC_VID_CSC_COEF7,
+	};
+	u32 regval[DISPC7_CSC_REGVAL_LEN];
+	unsigned int i;
+
+	csc->to_regval(csc, regval);
+
+	for (i = 0; i < ARRAY_SIZE(dispc_vid_csc_coef_reg); i++)
+		dispc7_vid_write(dispc, hw_plane, dispc_vid_csc_coef_reg[i],
+				regval[i]);
+}
+
+/* YUV -> RGB, ITU-R BT.601, full range */
+const static struct dispc7_csc_coef csc_yuv2rgb_bt601_full = {
+	dispc7_csc_yuv2rgb_regval,
+	{ 256,   0,  358,	/* ry, rcb, rcr |1.000  0.000  1.402|*/
+	  256, -88, -182,	/* gy, gcb, gcr |1.000 -0.344 -0.714|*/
+	  256, 452,    0, },	/* by, bcb, bcr |1.000  1.772  0.000|*/
+	{    0, -2048, -2048, },	/* full range */
+	{    0,     0,     0, },
+	CLIP_FULL_RANGE,
+	"BT.601 Full",
+};
+
+/* YUV -> RGB, ITU-R BT.601, limited range */
+const static struct dispc7_csc_coef csc_yuv2rgb_bt601_lim = {
+	dispc7_csc_yuv2rgb_regval,
+	{ 298,    0,  409,	/* ry, rcb, rcr |1.164  0.000  1.596|*/
+	  298, -100, -208,	/* gy, gcb, gcr |1.164 -0.392 -0.813|*/
+	  298,  516,    0, },	/* by, bcb, bcr |1.164  2.017  0.000|*/
+	{ -256, -2048, -2048, },	/* limited range */
+	{    0,     0,     0, },
+	CLIP_FULL_RANGE,
+	"BT.601 Limited",
+};
+
+/* YUV -> RGB, ITU-R BT.709, full range */
+const static struct dispc7_csc_coef csc_yuv2rgb_bt709_full = {
+	dispc7_csc_yuv2rgb_regval,
+	{ 256,	  0,  402,	/* ry, rcb, rcr |1.000	0.000  1.570|*/
+	  256,  -48, -120,	/* gy, gcb, gcr |1.000 -0.187 -0.467|*/
+	  256,  475,    0, },	/* by, bcb, bcr |1.000	1.856  0.000|*/
+	{    0, -2048, -2048, },	/* full range */
+	{    0,     0,     0, },
+	CLIP_FULL_RANGE,
+	"BT.709 Full",
+};
+
+/* YUV -> RGB, ITU-R BT.709, limited range */
+const static struct dispc7_csc_coef csc_yuv2rgb_bt709_lim = {
+	dispc7_csc_yuv2rgb_regval,
+	{ 298,    0,  459,	/* ry, rcb, rcr |1.164  0.000  1.793|*/
+	  298,  -55, -136,	/* gy, gcb, gcr |1.164 -0.213 -0.533|*/
+	  298,  541,    0, },	/* by, bcb, bcr |1.164  2.112  0.000|*/
+	{ -256, -2048, -2048, },	/* limited range */
+	{    0,     0,     0, },
+	CLIP_FULL_RANGE,
+	"BT.709 Limited",
+};
+
+static const struct {
+	enum drm_color_encoding encoding;
+	enum drm_color_range range;
+	const struct dispc7_csc_coef *csc;
+} dispc7_csc_table[] = {
+	{ DRM_COLOR_YCBCR_BT601, DRM_COLOR_YCBCR_FULL_RANGE,
+	  &csc_yuv2rgb_bt601_full, },
+	{ DRM_COLOR_YCBCR_BT601, DRM_COLOR_YCBCR_LIMITED_RANGE,
+	  &csc_yuv2rgb_bt601_lim, },
+	{ DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_FULL_RANGE,
+	  &csc_yuv2rgb_bt709_full, },
+	{ DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_LIMITED_RANGE,
+	  &csc_yuv2rgb_bt709_lim, },
+};
+
+static const
+struct dispc7_csc_coef *dispc7_find_csc(enum drm_color_encoding encoding,
+					enum drm_color_range range)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc7_csc_table); i++) {
+		if (dispc7_csc_table[i].encoding == encoding &&
+		    dispc7_csc_table[i].range == range) {
+			return dispc7_csc_table[i].csc;
+		}
+	}
+	return NULL;
+}
+
+static void dispc7_vid_csc_setup(struct dispc_device *dispc, u32 hw_plane,
+				 const struct drm_plane_state *state)
+{
+	const static struct dispc7_csc_coef *coef;
+
+	coef = dispc7_find_csc(state->color_encoding, state->color_range);
+	if (!coef) {
+		dev_err(dispc->dev, "%s: CSC (%u,%u) not found\n",
+			__func__, state->color_encoding, state->color_range);
+		return;
+	}
+
+	dispc7_vid_write_csc(dispc, hw_plane, coef);
+}
+
+static void dispc7_vid_csc_enable(struct dispc_device *dispc, u32 hw_plane,
+				  bool enable)
+{
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 9, 9);
+}
+
+/* SCALER */
+
+static u32 dispc7_calc_fir_inc(u32 in, u32 out)
+{
+	return (u32)div_u64(0x200000ull * in, out);
+}
+
+enum dispc7_vid_fir_coef_set {
+	DISPC7_VID_FIR_COEF_HORIZ,
+	DISPC7_VID_FIR_COEF_HORIZ_UV,
+	DISPC7_VID_FIR_COEF_VERT,
+	DISPC7_VID_FIR_COEF_VERT_UV,
+};
+
+static void dispc7_vid_write_fir_coefs(struct dispc_device *dispc,
+				       u32 hw_plane,
+				       enum dispc7_vid_fir_coef_set coef_set,
+				       const struct tidss_scale_coefs *coefs)
+{
+	static const u16 c0_regs[] = {
+		[DISPC7_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H0,
+		[DISPC7_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H0_C,
+		[DISPC7_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V0,
+		[DISPC7_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V0_C,
+	};
+
+	static const u16 c12_regs[] = {
+		[DISPC7_VID_FIR_COEF_HORIZ] = DISPC_VID_FIR_COEFS_H12,
+		[DISPC7_VID_FIR_COEF_HORIZ_UV] = DISPC_VID_FIR_COEFS_H12_C,
+		[DISPC7_VID_FIR_COEF_VERT] = DISPC_VID_FIR_COEFS_V12,
+		[DISPC7_VID_FIR_COEF_VERT_UV] = DISPC_VID_FIR_COEFS_V12_C,
+	};
+
+	const u16 c0_base = c0_regs[coef_set];
+	const u16 c12_base = c12_regs[coef_set];
+	int phase;
+
+	if (!coefs) {
+		dev_err(dispc->dev, "%s: No coefficients given.\n", __func__);
+		return;
+	}
+
+	for (phase = 0; phase <= 8; ++phase) {
+		u16 reg = c0_base + phase * 4;
+		u16 c0 = coefs->c0[phase];
+
+		dispc7_vid_write(dispc, hw_plane, reg, c0);
+	}
+
+	for (phase = 0; phase <= 15; ++phase) {
+		u16 reg = c12_base + phase * 4;
+		s16 c1, c2;
+		u32 c12;
+
+		c1 = coefs->c1[phase];
+		c2 = coefs->c2[phase];
+		c12 = FLD_VAL(c1, 19, 10) | FLD_VAL(c2, 29, 20);
+
+		dispc7_vid_write(dispc, hw_plane, reg, c12);
+	}
+}
+
+static bool dispc7_fourcc_is_yuv(u32 fourcc)
+{
+	switch (fourcc) {
+	case DRM_FORMAT_YUYV:
+	case DRM_FORMAT_UYVY:
+	case DRM_FORMAT_NV12:
+		return true;
+	default:
+		return false;
+	}
+}
+
+struct dispc7_scaling_params {
+	int xinc, yinc;
+	u32 in_w, in_h, in_w_uv, in_h_uv;
+	u32 fir_xinc, fir_yinc, fir_xinc_uv, fir_yinc_uv;
+	bool scale_x, scale_y;
+	const struct tidss_scale_coefs *xcoef, *ycoef, *xcoef_uv, *ycoef_uv;
+	bool five_taps;
+};
+
+static int dispc7_vid_calc_scaling(struct dispc_device *dispc,
+				   const struct drm_plane_state *state,
+				   struct dispc7_scaling_params *sp,
+				   bool lite_plane)
+{
+	const struct dispc7_features_scaling *f = &dispc->feat->scaling;
+	u32 fourcc = state->fb->format->format;
+	u32 in_width_max_5tap = f->in_width_max_5tap_rgb;
+	u32 in_width_max_3tap = f->in_width_max_3tap_rgb;
+	u32 downscale_limit;
+	u32 in_width_max;
+
+	memset(sp, 0, sizeof(*sp));
+	sp->xinc = sp->yinc = 1;
+	sp->in_w = sp->in_w_uv = state->src_w >> 16;
+	sp->in_h = sp->in_h_uv = state->src_h >> 16;
+
+	sp->scale_x = sp->in_w != state->crtc_w;
+	sp->scale_y = sp->in_h != state->crtc_h;
+
+	if (dispc7_fourcc_is_yuv(fourcc)) {
+		in_width_max_5tap = f->in_width_max_5tap_yuv;
+		in_width_max_3tap = f->in_width_max_3tap_yuv;
+
+		sp->in_w_uv >>= 1;
+		sp->scale_x = true;
+
+		if (fourcc == DRM_FORMAT_NV12) {
+			sp->in_h_uv >>= 1;
+			sp->scale_y = true;
+		}
+	}
+
+	/* Skip the rest if no scaling is used */
+	if ((!sp->scale_x && !sp->scale_y) || lite_plane)
+		return 0;
+
+	if (sp->in_w > in_width_max_5tap) {
+		sp->five_taps = false;
+		in_width_max = in_width_max_3tap;
+		downscale_limit = f->downscale_limit_3tap;
+	} else {
+		sp->five_taps = true;
+		in_width_max = in_width_max_5tap;
+		downscale_limit = f->downscale_limit_5tap;
+	}
+
+	if (sp->scale_x) {
+		sp->fir_xinc = dispc7_calc_fir_inc(sp->in_w, state->crtc_w);
+
+		if (sp->fir_xinc < dispc7_calc_fir_inc(1, f->upscale_limit)) {
+			dev_dbg(dispc->dev,
+				"%s: X-scaling factor %u/%u > %u\n",
+				__func__, state->crtc_w, state->src_w >> 16,
+				f->upscale_limit);
+			return -EINVAL;
+		}
+
+		if (sp->fir_xinc >= dispc7_calc_fir_inc(downscale_limit, 1)) {
+			sp->xinc = DIV_ROUND_UP(DIV_ROUND_UP(sp->in_w,
+							     state->crtc_w),
+						downscale_limit);
+
+			if (sp->xinc > f->xinc_max) {
+				dev_dbg(dispc->dev,
+					"%s: X-scaling factor %u/%u < 1/%u\n",
+					__func__, state->crtc_w,
+					state->src_w >> 16,
+					downscale_limit * f->xinc_max);
+				return -EINVAL;
+			}
+
+			sp->in_w = (state->src_w >> 16) / sp->xinc;
+		}
+
+		while (sp->in_w > in_width_max) {
+			sp->xinc++;
+			sp->in_w = (state->src_w >> 16) / sp->xinc;
+		}
+
+		if (sp->xinc > f->xinc_max) {
+			dev_dbg(dispc->dev,
+				"%s: Too wide input bufer %u > %u\n", __func__,
+				state->src_w >> 16, in_width_max * f->xinc_max);
+			return -EINVAL;
+		}
+
+		/*
+		 * We need even line length for YUV formats. Decimation
+		 * can lead to odd length, so we need to make it even
+		 * again.
+		 */
+		if (dispc7_fourcc_is_yuv(fourcc))
+			sp->in_w &= ~1;
+
+		sp->fir_xinc = dispc7_calc_fir_inc(sp->in_w, state->crtc_w);
+	}
+
+	if (sp->scale_y) {
+		sp->fir_yinc = dispc7_calc_fir_inc(sp->in_h, state->crtc_h);
+
+		if (sp->fir_yinc < dispc7_calc_fir_inc(1, f->upscale_limit)) {
+			dev_dbg(dispc->dev,
+				"%s: Y-scaling factor %u/%u > %u\n",
+				__func__, state->crtc_h, state->src_h >> 16,
+				f->upscale_limit);
+			return -EINVAL;
+		}
+
+		if (sp->fir_yinc >= dispc7_calc_fir_inc(downscale_limit, 1)) {
+			sp->yinc = DIV_ROUND_UP(DIV_ROUND_UP(sp->in_h,
+							     state->crtc_h),
+						downscale_limit);
+
+			sp->in_h /= sp->yinc;
+			sp->fir_yinc = dispc7_calc_fir_inc(sp->in_h,
+							   state->crtc_h);
+		}
+	}
+
+	dev_dbg(dispc->dev,
+		"%s: %ux%u decim %ux%u -> %ux%u firinc %u.%03ux%u.%03u taps %u -> %ux%u\n",
+		__func__, state->src_w >> 16, state->src_h >> 16,
+		sp->xinc, sp->yinc, sp->in_w, sp->in_h,
+		sp->fir_xinc / 0x200000u,
+		((sp->fir_xinc & 0x1FFFFFu) * 999u) / 0x1FFFFFu,
+		sp->fir_yinc / 0x200000u,
+		((sp->fir_yinc & 0x1FFFFFu) * 999u) / 0x1FFFFFu,
+		sp->five_taps ? 5 : 3,
+		state->crtc_w, state->crtc_h);
+
+	if (dispc7_fourcc_is_yuv(fourcc)) {
+		if (sp->scale_x) {
+			sp->in_w_uv /= sp->xinc;
+			sp->fir_xinc_uv = dispc7_calc_fir_inc(sp->in_w_uv,
+							      state->crtc_w);
+			sp->xcoef_uv = tidss_get_scale_coefs(dispc->dev,
+							     sp->fir_xinc_uv,
+							     true);
+		}
+		if (sp->scale_y) {
+			sp->in_h_uv /= sp->yinc;
+			sp->fir_yinc_uv = dispc7_calc_fir_inc(sp->in_h_uv,
+							      state->crtc_h);
+			sp->ycoef_uv = tidss_get_scale_coefs(dispc->dev,
+							     sp->fir_yinc_uv,
+							     sp->five_taps);
+		}
+	}
+
+	if (sp->scale_x)
+		sp->xcoef = tidss_get_scale_coefs(dispc->dev, sp->fir_xinc,
+						  true);
+
+	if (sp->scale_y)
+		sp->ycoef = tidss_get_scale_coefs(dispc->dev, sp->fir_yinc,
+						  sp->five_taps);
+
+	return 0;
+}
+
+static void dispc7_vid_set_scaling(struct dispc_device *dispc,
+				   u32 hw_plane,
+				   struct dispc7_scaling_params *sp,
+				   u32 fourcc)
+{
+	/* HORIZONTAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+			sp->scale_x, 7, 7);
+
+	/* VERTICAL RESIZE ENABLE */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+			sp->scale_y, 8, 8);
+
+	/* Skip the rest if no scaling is used */
+	if (!sp->scale_x && !sp->scale_y)
+		return;
+
+	/* VERTICAL 5-TAPS  */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+			sp->five_taps, 21, 21);
+
+	if (dispc7_fourcc_is_yuv(fourcc)) {
+		if (sp->scale_x) {
+			dispc7_vid_write(dispc, hw_plane, DISPC_VID_FIRH2,
+					 sp->fir_xinc_uv);
+			dispc7_vid_write_fir_coefs(dispc, hw_plane,
+						   DISPC7_VID_FIR_COEF_HORIZ_UV,
+						   sp->xcoef_uv);
+		}
+		if (sp->scale_y) {
+			dispc7_vid_write(dispc, hw_plane, DISPC_VID_FIRV2,
+					 sp->fir_yinc_uv);
+			dispc7_vid_write_fir_coefs(dispc, hw_plane,
+						   DISPC7_VID_FIR_COEF_VERT_UV,
+						   sp->ycoef_uv);
+		}
+	}
+
+	if (sp->scale_x) {
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_FIRH, sp->fir_xinc);
+		dispc7_vid_write_fir_coefs(dispc, hw_plane,
+					   DISPC7_VID_FIR_COEF_HORIZ,
+					   sp->xcoef);
+	}
+
+	if (sp->scale_y) {
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_FIRV, sp->fir_yinc);
+		dispc7_vid_write_fir_coefs(dispc, hw_plane,
+					   DISPC7_VID_FIR_COEF_VERT, sp->ycoef);
+	}
+}
+
+/* OTHER */
+
+static const struct {
+	u32 fourcc;
+	u8 dss_code;
+} dispc7_color_formats[] = {
+	{ DRM_FORMAT_ARGB4444, 0x0, },
+	{ DRM_FORMAT_ABGR4444, 0x1, },
+	{ DRM_FORMAT_RGBA4444, 0x2, },
+
+	{ DRM_FORMAT_RGB565, 0x3, },
+	{ DRM_FORMAT_BGR565, 0x4, },
+
+	{ DRM_FORMAT_ARGB1555, 0x5, },
+	{ DRM_FORMAT_ABGR1555, 0x6, },
+
+	{ DRM_FORMAT_ARGB8888, 0x7, },
+	{ DRM_FORMAT_ABGR8888, 0x8, },
+	{ DRM_FORMAT_RGBA8888, 0x9, },
+	{ DRM_FORMAT_BGRA8888, 0xa, },
+
+	{ DRM_FORMAT_RGB888, 0xb, },
+	{ DRM_FORMAT_BGR888, 0xc, },
+
+	{ DRM_FORMAT_ARGB2101010, 0xe, },
+	{ DRM_FORMAT_ABGR2101010, 0xf, },
+	{ DRM_FORMAT_RGBA1010102, 0x10, },
+	{ DRM_FORMAT_BGRA1010102, 0x11, },
+
+	{ DRM_FORMAT_XRGB4444, 0x20, },
+	{ DRM_FORMAT_XBGR4444, 0x21, },
+	{ DRM_FORMAT_RGBX4444, 0x22, },
+
+	{ DRM_FORMAT_ARGB1555, 0x25, },
+	{ DRM_FORMAT_ABGR1555, 0x26, },
+
+	{ DRM_FORMAT_XRGB8888, 0x27, },
+	{ DRM_FORMAT_XBGR8888, 0x28, },
+	{ DRM_FORMAT_RGBX8888, 0x29, },
+	{ DRM_FORMAT_BGRX8888, 0x2a, },
+
+	{ DRM_FORMAT_XRGB2101010, 0x2e, },
+	{ DRM_FORMAT_XBGR2101010, 0x2f, },
+	{ DRM_FORMAT_RGBX1010102, 0x30, },
+	{ DRM_FORMAT_BGRX1010102, 0x31, },
+
+	{ DRM_FORMAT_YUYV, 0x3e, },
+	{ DRM_FORMAT_UYVY, 0x3f, },
+
+	{ DRM_FORMAT_NV12, 0x3d, },
+};
+
+static void dispc7_plane_set_pixel_format(struct dispc_device *dispc,
+					  u32 hw_plane, u32 fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dispc7_color_formats); ++i) {
+		if (dispc7_color_formats[i].fourcc == fourcc) {
+			VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES,
+					dispc7_color_formats[i].dss_code,
+					6, 1);
+			return;
+		}
+	}
+
+	WARN_ON(1);
+}
+
+static s32 pixinc(int pixels, u8 ps)
+{
+	if (pixels == 1)
+		return 1;
+	else if (pixels > 1)
+		return 1 + (pixels - 1) * ps;
+	else if (pixels < 0)
+		return 1 - (-pixels + 1) * ps;
+
+	WARN_ON(1);
+	return 0;
+}
+
+static
+const struct tidss_plane_feat *dispc7_plane_feat(struct dispc_device *dispc,
+						 u32 hw_plane)
+{
+	static const struct tidss_plane_feat pfeat = {
+		.color = {
+			.encodings = (BIT(DRM_COLOR_YCBCR_BT601) |
+				      BIT(DRM_COLOR_YCBCR_BT709)),
+			.ranges = (BIT(DRM_COLOR_YCBCR_FULL_RANGE) |
+				   BIT(DRM_COLOR_YCBCR_LIMITED_RANGE)),
+			.default_encoding = DRM_COLOR_YCBCR_BT601,
+			.default_range = DRM_COLOR_YCBCR_FULL_RANGE,
+		},
+		.blend = {
+			.global_alpha = true,
+		},
+	};
+
+	return &pfeat;
+}
+
+static int dispc7_plane_check(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	bool lite = dispc->feat->vid_lite[hw_plane];
+	u32 fourcc = state->fb->format->format;
+	bool need_scaling = state->src_w >> 16 != state->crtc_w ||
+		state->src_h >> 16 != state->crtc_h;
+	struct dispc7_scaling_params scaling;
+	int ret;
+
+	if (dispc7_fourcc_is_yuv(fourcc)) {
+		if (!dispc7_find_csc(state->color_encoding,
+				     state->color_range)) {
+			dev_dbg(dispc->dev,
+				"%s: Unsupported CSC (%u,%u) for HW plane %u\n",
+				__func__, state->color_encoding,
+				state->color_range, hw_plane);
+			return -EINVAL;
+		}
+	}
+
+	if (need_scaling) {
+		if (lite) {
+			dev_dbg(dispc->dev,
+				"%s: Lite plane %u can't scale %ux%u!=%ux%u\n",
+				__func__, hw_plane,
+				state->src_w >> 16, state->src_h >> 16,
+				state->crtc_w, state->crtc_h);
+			return -EINVAL;
+		}
+		ret = dispc7_vid_calc_scaling(dispc, state, &scaling, false);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int dispc7_plane_setup(struct dispc_device *dispc, u32 hw_plane,
+			      const struct drm_plane_state *state,
+			      u32 hw_videoport)
+{
+	bool lite = dispc->feat->vid_lite[hw_plane];
+	u32 fourcc = state->fb->format->format;
+	u16 cpp = state->fb->format->cpp[0];
+	u32 fb_width = state->fb->pitches[0] / cpp;
+	dma_addr_t paddr = dispc7_plane_state_paddr(state);
+	struct dispc7_scaling_params scale;
+
+	dispc7_vid_calc_scaling(dispc, state, &scale, lite);
+
+	dispc7_plane_set_pixel_format(dispc, hw_plane, fourcc);
+
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_BA_0, paddr & 0xffffffff);
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_BA_EXT_0, (u64)paddr >> 32);
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_BA_1, paddr & 0xffffffff);
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_BA_EXT_1, (u64)paddr >> 32);
+
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_PICTURE_SIZE,
+			 (scale.in_w - 1) | ((scale.in_h - 1) << 16));
+
+	/* For YUV422 format we use the macropixel size for pixel inc */
+	if (fourcc == DRM_FORMAT_YUYV || fourcc == DRM_FORMAT_UYVY)
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC,
+				 pixinc(scale.xinc, cpp * 2));
+	else
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_PIXEL_INC,
+				 pixinc(scale.xinc, cpp));
+
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC,
+			 pixinc(1 + (scale.yinc * fb_width -
+				     scale.xinc * scale.in_w),
+				cpp));
+
+	if (state->fb->format->num_planes == 2) {
+		u16 cpp_uv = state->fb->format->cpp[1];
+		u32 fb_width_uv = state->fb->pitches[1] / cpp_uv;
+		dma_addr_t p_uv_addr = dispc7_plane_state_p_uv_addr(state);
+
+		dispc7_vid_write(dispc, hw_plane,
+				 DISPC_VID_BA_UV_0, p_uv_addr & 0xffffffff);
+		dispc7_vid_write(dispc, hw_plane,
+				 DISPC_VID_BA_UV_EXT_0, (u64)p_uv_addr >> 32);
+		dispc7_vid_write(dispc, hw_plane,
+				 DISPC_VID_BA_UV_1, p_uv_addr & 0xffffffff);
+		dispc7_vid_write(dispc, hw_plane,
+				 DISPC_VID_BA_UV_EXT_1, (u64)p_uv_addr >> 32);
+
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_ROW_INC_UV,
+				 pixinc(1 + (scale.yinc * fb_width_uv -
+					     scale.xinc * scale.in_w_uv),
+					cpp_uv));
+	}
+
+	if (!lite) {
+		dispc7_vid_write(dispc, hw_plane, DISPC_VID_SIZE,
+				 (state->crtc_w - 1) |
+				 ((state->crtc_h - 1) << 16));
+
+		dispc7_vid_set_scaling(dispc, hw_plane, &scale, fourcc);
+	}
+
+	/* enable YUV->RGB color conversion */
+	if (dispc7_fourcc_is_yuv(fourcc)) {
+		dispc7_vid_csc_setup(dispc, hw_plane, state);
+		dispc7_vid_csc_enable(dispc, hw_plane, true);
+	} else {
+		dispc7_vid_csc_enable(dispc, hw_plane, false);
+	}
+
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_GLOBAL_ALPHA,
+			 0xFF & (state->alpha >> 8));
+
+	/* Set pre-multiplied alpha as default. */
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, 1, 22, 22);
+
+	dispc7_ovr_set_plane(dispc, hw_plane, hw_videoport,
+			     state->crtc_x, state->crtc_y,
+			     state->normalized_zpos);
+
+	dispc->plane_data[hw_plane].zorder = state->normalized_zpos;
+	dispc->plane_data[hw_plane].hw_videoport = hw_videoport;
+
+	return 0;
+}
+
+static int dispc7_plane_enable(struct dispc_device *dispc,
+			       u32 hw_plane, bool enable)
+{
+	dispc7_ovr_enable_plane(dispc, dispc->plane_data[hw_plane].hw_videoport,
+				dispc->plane_data[hw_plane].zorder, enable);
+
+	VID_REG_FLD_MOD(dispc, hw_plane, DISPC_VID_ATTRIBUTES, !!enable, 0, 0);
+
+	return 0;
+}
+
+static u32 dispc7_vid_get_fifo_size(struct dispc_device *dispc,
+				    u32 hw_plane)
+{
+	const u32 unit_size = 16;	/* 128-bits */
+
+	return VID_REG_GET(dispc, hw_plane, DISPC_VID_BUF_SIZE_STATUS, 15, 0) *
+	       unit_size;
+}
+
+static void dispc7_vid_set_mflag_threshold(struct dispc_device *dispc,
+					   u32 hw_plane, u32 low, u32 high)
+{
+	dispc7_vid_write(dispc, hw_plane, DISPC_VID_MFLAG_THRESHOLD,
+			 FLD_VAL(high, 31, 16) | FLD_VAL(low, 15, 0));
+}
+
+static void dispc7_vid_mflag_setup(struct dispc_device *dispc,
+				   u32 hw_plane)
+{
+	const u32 unit_size = 16;	/* 128-bits */
+	u32 size = dispc7_vid_get_fifo_size(dispc, hw_plane);
+	u32 low, high;
+
+	/*
+	 * Simulation team suggests below thesholds:
+	 * HT = fifosize * 5 / 8;
+	 * LT = fifosize * 4 / 8;
+	 */
+
+	low = size * 4 / 8 / unit_size;
+	high = size * 5 / 8 / unit_size;
+
+	dispc7_vid_set_mflag_threshold(dispc, hw_plane, low, high);
+}
+
+static void dispc7_mflag_setup(struct dispc_device *dispc)
+{
+	unsigned int i;
+
+	/* MFLAG_CTRL = ENABLED */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 2, 1, 0);
+	/* MFLAG_START = MFLAGNORMALSTARTMODE */
+	REG_FLD_MOD(dispc, DISPC_GLOBAL_MFLAG_ATTRIBUTE, 0, 6, 6);
+
+	for (i = 0; i < dispc->feat->num_planes; i++)
+		dispc7_vid_mflag_setup(dispc, i);
+}
+
+static void dispc7_plane_init(struct dispc_device *dispc)
+{
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	/* FIFO underflows when scaling if preload is not high enough */
+	for (i = 0; i < dispc->feat->num_planes; i++)
+		if (!dispc->feat->vid_lite[i])
+			VID_REG_FLD_MOD(dispc, i, DISPC_VID_PRELOAD,
+					0x7FF, 11, 0);
+}
+
+static void dispc7_vp_init(struct dispc_device *dispc)
+{
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	/* Enable the gamma Shadow bit-field for all VPs*/
+	for (i = 0; i < dispc->feat->num_vps; i++)
+		VP_REG_FLD_MOD(dispc, i, DISPC_VP_CONFIG, 1, 2, 2);
+}
+
+static void dispc7_initial_config(struct dispc_device *dispc)
+{
+	dispc7_mflag_setup(dispc);
+	dispc7_plane_init(dispc);
+	dispc7_vp_init(dispc);
+}
+
+static int dispc7_get_num_planes(struct dispc_device *dispc)
+{
+	return dispc->feat->num_planes;
+}
+
+static int dispc7_get_num_vps(struct dispc_device *dispc)
+{
+	return dispc->feat->num_vps;
+}
+
+static const struct tidss_vp_feat *dispc7_vp_feat(struct dispc_device *dispc,
+						  u32 hw_videoport)
+{
+	static const struct tidss_vp_feat vp_feat = {
+		.color = {
+			.gamma_size = DISPC7_GAMMA_TABLE_SIZE,
+			.has_ctm = true,
+		},
+	};
+
+	return &vp_feat;
+}
+
+static void dispc7_vp_write_gamma_table(struct dispc_device *dispc,
+					u32 hw_videoport)
+{
+	u32 *table = dispc->vp_data[hw_videoport].gamma_table;
+	u32 hwlen = ARRAY_SIZE(dispc->vp_data[hw_videoport].gamma_table);
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d\n", __func__, hw_videoport);
+
+	for (i = 0; i < hwlen; ++i) {
+		u32 v = table[i];
+
+		v |= i << 24;
+
+		dispc7_vp_write(dispc, hw_videoport, DISPC_VP_GAMMA_TABLE, v);
+	}
+}
+
+static void dispc7_restore_gamma_tables(struct dispc_device *dispc)
+{
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	for (i = 0; i < dispc->feat->num_vps; i++)
+		dispc7_vp_write_gamma_table(dispc, i);
+}
+
+static const struct drm_color_lut dispc7_vp_gamma_default_lut[] = {
+	{ .red = 0, .green = 0, .blue = 0, },
+	{ .red = U16_MAX, .green = U16_MAX, .blue = U16_MAX, },
+};
+
+static void dispc7_vp_set_gamma(struct dispc_device *dispc,
+				u32 hw_videoport,
+				const struct drm_color_lut *lut,
+				unsigned int length)
+{
+	u32 *table = dispc->vp_data[hw_videoport].gamma_table;
+	u32 hwlen = ARRAY_SIZE(dispc->vp_data[hw_videoport].gamma_table);
+	static const unsigned int hwbits = 8;
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s: hw_videoport %d, lut len %u, hw len %u\n",
+		__func__, hw_videoport, length, hwlen);
+
+	if (lut == NULL || length < 2) {
+		lut = dispc7_vp_gamma_default_lut;
+		length = ARRAY_SIZE(dispc7_vp_gamma_default_lut);
+	}
+
+	for (i = 0; i < length - 1; ++i) {
+		unsigned int first = i * (hwlen - 1) / (length - 1);
+		unsigned int last = (i + 1) * (hwlen - 1) / (length - 1);
+		unsigned int w = last - first;
+		u16 r, g, b;
+		unsigned int j;
+
+		if (w == 0)
+			continue;
+
+		for (j = 0; j <= w; j++) {
+			r = (lut[i].red * (w - j) + lut[i + 1].red * j) / w;
+			g = (lut[i].green * (w - j) + lut[i + 1].green * j) / w;
+			b = (lut[i].blue * (w - j) + lut[i + 1].blue * j) / w;
+
+			r >>= 16 - hwbits;
+			g >>= 16 - hwbits;
+			b >>= 16 - hwbits;
+
+			table[first + j] = (r << (hwbits * 2)) |
+					   (g << hwbits) | b;
+		}
+	}
+
+	if (dispc->is_enabled)
+		dispc7_vp_write_gamma_table(dispc, hw_videoport);
+}
+
+static s16 dispc7_S31_32_to_s2_8(s64 coef)
+{
+	u64 sign_bit = 1ULL << 63;
+	u64 cbits = (u64) coef;
+	s16 ret = clamp_val(((cbits & ~sign_bit) >> 24), 0, 0x1FF);
+
+	if (cbits & sign_bit)
+		ret = -ret;
+
+	return ret;
+}
+
+static void dispc7_cpr_csc_from_ctm(const struct drm_color_ctm *ctm,
+				    struct dispc7_csc_coef *cpr)
+{
+	memset(cpr, 0, sizeof(*cpr));
+
+	cpr->to_regval = dispc7_csc_cpr_regval;
+	cpr->m[CSC_RR] = dispc7_S31_32_to_s2_8(ctm->matrix[0]);
+	cpr->m[CSC_RG] = dispc7_S31_32_to_s2_8(ctm->matrix[1]);
+	cpr->m[CSC_RB] = dispc7_S31_32_to_s2_8(ctm->matrix[2]);
+	cpr->m[CSC_GR] = dispc7_S31_32_to_s2_8(ctm->matrix[3]);
+	cpr->m[CSC_GG] = dispc7_S31_32_to_s2_8(ctm->matrix[4]);
+	cpr->m[CSC_GB] = dispc7_S31_32_to_s2_8(ctm->matrix[5]);
+	cpr->m[CSC_BR] = dispc7_S31_32_to_s2_8(ctm->matrix[6]);
+	cpr->m[CSC_BG] = dispc7_S31_32_to_s2_8(ctm->matrix[7]);
+	cpr->m[CSC_BB] = dispc7_S31_32_to_s2_8(ctm->matrix[8]);
+}
+
+static void dispc7_vp_write_csc(struct dispc_device *dispc, u32 hw_videoport,
+				const struct dispc7_csc_coef *csc)
+{
+	static const u16 dispc_vp_csc_coef_reg[DISPC7_CSC_REGVAL_LEN] = {
+		DISPC_VP_CSC_COEF0, DISPC_VP_CSC_COEF1, DISPC_VP_CSC_COEF2,
+		DISPC_VP_CSC_COEF3, DISPC_VP_CSC_COEF4, DISPC_VP_CSC_COEF5,
+		DISPC_VP_CSC_COEF6, DISPC_VP_CSC_COEF7,
+	};
+	u32 regval[DISPC7_CSC_REGVAL_LEN];
+	unsigned int i;
+
+	csc->to_regval(csc, regval);
+
+	for (i = 0; i < ARRAY_SIZE(regval); i++)
+		dispc7_vp_write(dispc, hw_videoport, dispc_vp_csc_coef_reg[i],
+				regval[i]);
+}
+
+static void dispc7_vp_set_color_mgmt(struct dispc_device *dispc,
+				     u32 hw_videoport,
+				     const struct drm_crtc_state *state)
+{
+	struct drm_color_lut *lut = NULL;
+	unsigned int length = 0;
+	bool colorconvenable = false;
+
+	if (!state->color_mgmt_changed)
+		return;
+
+	if (state->gamma_lut) {
+		lut = (struct drm_color_lut *) state->gamma_lut->data;
+		length = state->gamma_lut->length / sizeof(*lut);
+	}
+
+	if (state->ctm) {
+		struct drm_color_ctm *ctm =
+			(struct drm_color_ctm *) state->ctm->data;
+		struct dispc7_csc_coef cpr;
+
+		dispc7_cpr_csc_from_ctm(ctm, &cpr);
+		dispc7_vp_write_csc(dispc, hw_videoport, &cpr);
+
+		colorconvenable = true;
+	}
+
+	dispc7_vp_set_gamma(dispc, hw_videoport, lut, length);
+	VP_REG_FLD_MOD(dispc, hw_videoport, DISPC_VP_CONFIG,
+		       colorconvenable, 24, 24);
+}
+
+static void dispc7_vp_setup(struct dispc_device *dispc, u32 hw_videoport,
+			    const struct drm_crtc_state *state)
+{
+	dispc7_vp_set_default_color(dispc, hw_videoport, 0);
+	dispc7_vp_set_color_mgmt(dispc, hw_videoport, state);
+}
+
+static int dispc7_init_gamma_tables(struct dispc_device *dispc)
+{
+	unsigned int i;
+
+	dev_dbg(dispc->dev, "%s()\n", __func__);
+
+	for (i = 0; i < dispc->feat->num_vps; i++)
+		dispc7_vp_set_gamma(dispc, i, NULL, 0);
+
+	return 0;
+}
+
+static const char *dispc7_plane_name(struct dispc_device *dispc, u32 hw_plane)
+{
+	if (WARN_ON(hw_plane >= dispc->feat->num_planes))
+		return "ERROR";
+
+	return dispc->feat->vid_name[hw_plane];
+}
+
+static const char *dispc7_vp_name(struct dispc_device *dispc, u32 hw_videoport)
+{
+	if (WARN_ON(hw_videoport >= dispc->feat->num_vps))
+		return "ERROR";
+
+	return dispc->feat->vp_name[hw_videoport];
+}
+
+static int dispc7_runtime_suspend(struct dispc_device *dispc)
+{
+	dev_dbg(dispc->dev, "suspend\n");
+
+	dispc->is_enabled = false;
+
+	clk_disable_unprepare(dispc->fclk);
+
+	return 0;
+}
+
+static int dispc7_runtime_resume(struct dispc_device *dispc)
+{
+	dev_dbg(dispc->dev, "resume\n");
+
+	clk_prepare_enable(dispc->fclk);
+
+	if (REG_GET(dispc, DSS_SYSSTATUS, 0, 0) == 0)
+		dev_warn(dispc->dev, "DSS FUNC RESET not done!\n");
+
+	dev_dbg(dispc->dev, "OMAP DSS7 rev 0x%x\n",
+		dispc7_read(dispc, DSS_REVISION));
+
+	dev_dbg(dispc->dev, "VP RESETDONE %d,%d,%d\n",
+		REG_GET(dispc, DSS_SYSSTATUS, 1, 1),
+		REG_GET(dispc, DSS_SYSSTATUS, 2, 2),
+		REG_GET(dispc, DSS_SYSSTATUS, 3, 3));
+
+	dev_dbg(dispc->dev, "OLDI RESETDONE %d,%d,%d\n",
+		REG_GET(dispc, DSS_SYSSTATUS, 5, 5),
+		REG_GET(dispc, DSS_SYSSTATUS, 6, 6),
+		REG_GET(dispc, DSS_SYSSTATUS, 7, 7));
+
+	dev_dbg(dispc->dev, "DISPC IDLE %d\n",
+		REG_GET(dispc, DSS_SYSSTATUS, 9, 9));
+
+	dispc7_initial_config(dispc);
+
+	dispc7_restore_gamma_tables(dispc);
+
+	dispc->is_enabled = true;
+
+	return 0;
+}
+
+static int dispc7_modeset_init(struct dispc_device *dispc)
+{
+	struct tidss_device *tidss = dispc->tidss;
+	struct device *dev = tidss->dev;
+	u32 fourccs[ARRAY_SIZE(dispc7_color_formats)];
+	unsigned int i;
+
+	struct pipe {
+		u32 hw_videoport;
+		struct drm_bridge *bridge;
+		u32 enc_type;
+	};
+
+	u32 max_vps = dispc->feat->num_vps;
+	u32 max_planes = dispc->feat->num_planes;
+
+	struct pipe pipes[DISPC7_MAX_PORTS];
+	u32 num_pipes = 0;
+	u32 crtc_mask;
+
+	for (i = 0; i < ARRAY_SIZE(fourccs); ++i)
+		fourccs[i] = dispc7_color_formats[i].fourcc;
+
+	/* first find all the connected panels & bridges */
+
+	for (i = 0; i < max_vps; i++) {
+		struct drm_panel *panel;
+		struct drm_bridge *bridge;
+		u32 enc_type = DRM_MODE_ENCODER_NONE;
+		int ret;
+
+		ret = drm_of_find_panel_or_bridge(dev->of_node, i, 0,
+						  &panel, &bridge);
+		if (ret == -ENODEV) {
+			dev_dbg(dev, "no panel/bridge for port %d\n", i);
+			continue;
+		} else if (ret) {
+			dev_dbg(dev, "port %d probe returned %d\n", i, ret);
+			return ret;
+		}
+
+		if (panel) {
+			u32 conn_type;
+
+			dev_dbg(dev, "Setting up panel for port %d\n", i);
+
+			switch (dispc->feat->vp_bus_type[i]) {
+			case DISPC7_VP_OLDI:
+				enc_type = DRM_MODE_ENCODER_LVDS;
+				conn_type = DRM_MODE_CONNECTOR_LVDS;
+				break;
+			case DISPC7_VP_DPI:
+				enc_type = DRM_MODE_ENCODER_DPI;
+				conn_type = DRM_MODE_CONNECTOR_DPI;
+				break;
+			default:
+				conn_type = DRM_MODE_CONNECTOR_Unknown;
+				break;
+			}
+
+			bridge = devm_drm_panel_bridge_add(dev, panel,
+							   conn_type);
+			if (IS_ERR(bridge)) {
+				dev_err(dev, "failed to set up panel bridge for port %d\n", i);
+				return PTR_ERR(bridge);
+			}
+		}
+
+		pipes[num_pipes].hw_videoport = i;
+		pipes[num_pipes].bridge = bridge;
+		pipes[num_pipes].enc_type = enc_type;
+		num_pipes++;
+	}
+
+	/* all planes can be on any crtc */
+	crtc_mask = (1 << num_pipes) - 1;
+
+	/* then create a plane, a crtc and an encoder for each panel/bridge */
+
+	for (i = 0; i < num_pipes; ++i) {
+		struct tidss_plane *tplane;
+		struct tidss_crtc *tcrtc;
+		struct drm_encoder *enc;
+		u32 hw_plane_id = dispc->feat->vid_order[tidss->num_planes];
+		int ret;
+
+		tplane = tidss_plane_create(tidss, hw_plane_id,
+					    DRM_PLANE_TYPE_PRIMARY, crtc_mask,
+					    fourccs, ARRAY_SIZE(fourccs));
+		if (IS_ERR(tplane)) {
+			dev_err(tidss->dev, "plane create failed\n");
+			return PTR_ERR(tplane);
+		}
+
+		tidss->planes[tidss->num_planes++] = &tplane->plane;
+
+		tcrtc = tidss_crtc_create(tidss, pipes[i].hw_videoport,
+					  &tplane->plane);
+		if (IS_ERR(tcrtc)) {
+			dev_err(tidss->dev, "crtc create failed\n");
+			return PTR_ERR(tcrtc);
+		}
+
+		tidss->crtcs[tidss->num_crtcs++] = &tcrtc->crtc;
+
+		enc = tidss_encoder_create(tidss, pipes[i].enc_type,
+					   1 << tcrtc->crtc.index);
+		if (IS_ERR(enc)) {
+			dev_err(tidss->dev, "encoder create failed\n");
+			return PTR_ERR(enc);
+		}
+
+		ret = drm_bridge_attach(enc, pipes[i].bridge, NULL);
+		if (ret) {
+			dev_err(tidss->dev, "bridge attach failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	/* create overlay planes of the leftover planes */
+
+	while (tidss->num_planes < max_planes) {
+		struct tidss_plane *tplane;
+		u32 hw_plane_id = dispc->feat->vid_order[tidss->num_planes];
+
+		tplane = tidss_plane_create(tidss, hw_plane_id,
+					    DRM_PLANE_TYPE_OVERLAY, crtc_mask,
+					    fourccs, ARRAY_SIZE(fourccs));
+
+		if (IS_ERR(tplane)) {
+			dev_err(tidss->dev, "plane create failed\n");
+			return PTR_ERR(tplane);
+		}
+
+		tidss->planes[tidss->num_planes++] = &tplane->plane;
+	}
+
+	return 0;
+}
+
+static void dispc7_remove(struct dispc_device *dispc)
+{
+	struct device *dev = dispc->dev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	dispc->tidss->dispc_ops = NULL;
+	dispc->tidss->dispc = NULL;
+}
+
+static const struct tidss_dispc_ops dispc7_ops = {
+	.read_and_clear_irqstatus = dispc7_read_and_clear_irqstatus,
+	.write_irqenable = dispc7_write_irqenable,
+
+	.get_num_vps = dispc7_get_num_vps,
+	.vp_name = dispc7_vp_name,
+	.vp_feat = dispc7_vp_feat,
+	.vp_mode_valid = dispc7_vp_mode_valid,
+	.vp_check = dispc7_vp_check,
+	.vp_setup = dispc7_vp_setup,
+	.vp_prepare = dispc7_vp_prepare,
+	.vp_enable = dispc7_vp_enable,
+	.vp_disable = dispc7_vp_disable,
+	.vp_unprepare = dispc7_vp_unprepare,
+	.vp_go_busy = dispc7_vp_go_busy,
+	.vp_go = dispc7_vp_go,
+
+	.vp_set_clk_rate = dispc7_vp_set_clk_rate,
+	.vp_enable_clk = dispc7_vp_enable_clk,
+	.vp_disable_clk = dispc7_vp_disable_clk,
+
+	.get_num_planes = dispc7_get_num_planes,
+	.plane_name = dispc7_plane_name,
+	.plane_feat = dispc7_plane_feat,
+	.plane_enable = dispc7_plane_enable,
+	.plane_check = dispc7_plane_check,
+	.plane_setup = dispc7_plane_setup,
+
+	.runtime_get = dispc7_runtime_get,
+	.runtime_put = dispc7_runtime_put,
+
+	.runtime_suspend = dispc7_runtime_suspend,
+	.runtime_resume = dispc7_runtime_resume,
+
+	.remove = dispc7_remove,
+
+	.modeset_init = dispc7_modeset_init,
+};
+
+static int dispc7_iomap_resource(struct platform_device *pdev, const char *name,
+				 void __iomem **base)
+{
+	struct resource *res;
+	void __iomem *b;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+	if (!res) {
+		dev_err(&pdev->dev, "cannot get mem resource '%s'\n", name);
+		return -EINVAL;
+	}
+
+	b = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(b)) {
+		dev_err(&pdev->dev, "cannot ioremap resource '%s'\n", name);
+		return PTR_ERR(b);
+	}
+
+	*base = b;
+
+	return 0;
+}
+
+int dispc7_init(struct tidss_device *tidss)
+{
+	struct device *dev = tidss->dev;
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dispc_device *dispc;
+	const struct dispc7_features *feat;
+	unsigned int i;
+	int r = 0;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	feat = of_match_device(dispc7_of_table, dev)->data;
+
+	dispc = devm_kzalloc(dev, sizeof(*dispc), GFP_KERNEL);
+	if (!dispc)
+		return -ENOMEM;
+
+	dispc->tidss = tidss;
+	dispc->dev = dev;
+	dispc->feat = feat;
+
+	r = dispc7_iomap_resource(pdev, "common", &dispc->base_common);
+	if (r)
+		return r;
+
+	for (i = 0; i < dispc->feat->num_planes; i++) {
+		r = dispc7_iomap_resource(pdev, dispc->feat->vid_name[i],
+					  &dispc->base_vid[i]);
+		dev_dbg(dev, "%s: %u %s %d\n", __func__,
+			i, dispc->feat->vid_name[i], r);
+		if (r)
+			return r;
+	}
+
+	for (i = 0; i < dispc->feat->num_vps; i++) {
+		struct clk *clk;
+
+		r = dispc7_iomap_resource(pdev, dispc->feat->ovr_name[i],
+					  &dispc->base_ovr[i]);
+		dev_dbg(dev, "%s: %u %s %d\n", __func__,
+			i, dispc->feat->ovr_name[i], r);
+		if (r)
+			return r;
+
+		r = dispc7_iomap_resource(pdev, dispc->feat->vp_name[i],
+					  &dispc->base_vp[i]);
+		dev_dbg(dev, "%s: %u %s %d\n", __func__,
+			i, dispc->feat->vp_name[i], r);
+		if (r)
+			return r;
+
+		clk = devm_clk_get(dev, dispc->feat->vpclk_name[i]);
+		if (IS_ERR(clk)) {
+			dev_err(dev, "%s: Failed to get clk %s:%ld\n", __func__,
+				dispc->feat->vpclk_name[i], PTR_ERR(clk));
+			return PTR_ERR(clk);
+		}
+		dispc->vp_clk[i] = clk;
+	}
+
+	dispc->syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+	if (IS_ERR(dispc->syscon)) {
+		dev_err(dev, "%s: syscon_regmap_lookup_by_phandle failed %ld\n",
+			__func__, PTR_ERR(dispc->syscon));
+		return PTR_ERR(dispc->syscon);
+	}
+
+	dispc->fclk = devm_clk_get(dev, "fck");
+	if (IS_ERR(dispc->fclk)) {
+		dev_err(dev, "Failed to get fclk\n");
+		return PTR_ERR(dispc->fclk);
+	}
+	dev_dbg(dev, "DSS fclk %lu Hz\n", clk_get_rate(dispc->fclk));
+
+	r = dispc7_init_gamma_tables(dispc);
+	if (r)
+		return r;
+
+	tidss->dispc_ops = &dispc7_ops;
+	tidss->dispc = dispc;
+
+	return 0;
+}

+ 204 - 0
drivers/gpu/drm/tidss/tidss_dispc7.h

@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2016-2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC7_H
+#define __TIDSS_DISPC7_H
+
+#define DISPC7_MAX_PORTS	4
+#define DISPC7_MAX_PLANES	4
+
+struct dispc7_features_scaling {
+	u32 in_width_max_5tap_rgb;
+	u32 in_width_max_3tap_rgb;
+	u32 in_width_max_5tap_yuv;
+	u32 in_width_max_3tap_yuv;
+	u32 upscale_limit;
+	u32 downscale_limit_5tap;
+	u32 downscale_limit_3tap;
+	u32 xinc_max;
+};
+
+enum dispc7_vp_bus_type {
+	DISPC7_VP_DPI,
+	DISPC7_VP_OLDI,
+};
+
+struct dispc7_features {
+	/* XXX should these come from the .dts? Min pclk is not feature of DSS IP */
+	unsigned long min_pclk;
+	unsigned long max_pclk;
+
+	struct dispc7_features_scaling scaling;
+
+	u32 num_vps;
+	const char *vp_name[DISPC7_MAX_PORTS]; /* Should match dt reg names */
+	const char *ovr_name[DISPC7_MAX_PORTS]; /* Should match dt reg names */
+	const char *vpclk_name[DISPC7_MAX_PORTS]; /* Should match dt clk names */
+	const enum dispc7_vp_bus_type vp_bus_type[DISPC7_MAX_PORTS];
+	u32 num_planes;
+	const char *vid_name[DISPC7_MAX_PLANES]; /* Should match dt reg names */
+	bool vid_lite[DISPC7_MAX_PLANES];
+	u32 vid_order[DISPC7_MAX_PLANES];
+};
+
+#define DSS_REVISION			0x4
+#define DSS_SYSCONFIG			0x8
+#define DSS_SYSSTATUS			0x20
+#define DISPC_IRQ_EOI			0x24
+#define DISPC_IRQSTATUS_RAW		0x28
+#define DISPC_IRQSTATUS			0x2c
+#define DISPC_IRQENABLE_SET		0x30
+#define DISPC_IRQENABLE_CLR		0x40
+#define DISPC_VID_IRQENABLE(n)		(0x44 + (n) * 4)
+#define DISPC_VID_IRQSTATUS(n)		(0x58 + (n) * 4)
+#define DISPC_VP_IRQENABLE(n)		(0x70 + (n) * 4)
+#define DISPC_VP_IRQSTATUS(n)		(0x7c + (n) * 4)
+
+#define DISPC_GLOBAL_MFLAG_ATTRIBUTE	0x90
+#define DISPC_GLOBAL_OUTPUT_ENABLE	0x94
+#define DISPC_GLOBAL_BUFFER		0x98
+#define DSS_CBA_CFG			0x9c
+#define DISPC_DBG_CONTROL		0xa0
+#define DISPC_DBG_STATUS		0xa4
+#define DISPC_CLKGATING_DISABLE		0xa8
+#define DISPC_SECURE_DISABLE		0xac
+
+/* COMMON1 not implemented */
+
+/* VID */
+
+#define DISPC_VID_ACCUH_0		0x0
+#define DISPC_VID_ACCUH_1		0x4
+#define DISPC_VID_ACCUH2_0		0x8
+#define DISPC_VID_ACCUH2_1		0xc
+#define DISPC_VID_ACCUV_0		0x10
+#define DISPC_VID_ACCUV_1		0x14
+#define DISPC_VID_ACCUV2_0		0x18
+#define DISPC_VID_ACCUV2_1		0x1c
+#define DISPC_VID_ATTRIBUTES		0x20
+#define DISPC_VID_ATTRIBUTES2		0x24
+#define DISPC_VID_BA_0			0x28
+#define DISPC_VID_BA_1			0x2c
+#define DISPC_VID_BA_UV_0		0x30
+#define DISPC_VID_BA_UV_1		0x34
+#define DISPC_VID_BUF_SIZE_STATUS	0x38
+#define DISPC_VID_BUF_THRESHOLD		0x3c
+#define DISPC_VID_CSC_COEF(n)		(0x40 + (n) * 4)
+
+#define DISPC_VID_FIRH			0x5c
+#define DISPC_VID_FIRH2			0x60
+#define DISPC_VID_FIRV			0x64
+#define DISPC_VID_FIRV2			0x68
+
+#define DISPC_VID_FIR_COEFS_H0		0x6c
+#define DISPC_VID_FIR_COEF_H0(phase)	(0x6c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H0_C	0x90
+#define DISPC_VID_FIR_COEF_H0_C(phase)	(0x90 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_H12		0xb4
+#define DISPC_VID_FIR_COEF_H12(phase)	(0xb4 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_H12_C	0xf4
+#define DISPC_VID_FIR_COEF_H12_C(phase)	(0xf4 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V0		0x134
+#define DISPC_VID_FIR_COEF_V0(phase)	(0x134 + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V0_C	0x158
+#define DISPC_VID_FIR_COEF_V0_C(phase)	(0x158 + (phase) * 4)
+
+#define DISPC_VID_FIR_COEFS_V12		0x17c
+#define DISPC_VID_FIR_COEF_V12(phase)	(0x17c + (phase) * 4)
+#define DISPC_VID_FIR_COEFS_V12_C	0x1bc
+#define DISPC_VID_FIR_COEF_V12_C(phase)	(0x1bc + (phase) * 4)
+
+#define DISPC_VID_GLOBAL_ALPHA		0x1fc
+#define DISPC_VID_MFLAG_THRESHOLD	0x208
+#define DISPC_VID_PICTURE_SIZE		0x20c
+#define DISPC_VID_PIXEL_INC		0x210
+#define DISPC_VID_PRELOAD		0x218
+#define DISPC_VID_ROW_INC		0x21c
+#define DISPC_VID_SIZE			0x220
+#define DISPC_VID_BA_EXT_0		0x22c
+#define DISPC_VID_BA_EXT_1		0x230
+#define DISPC_VID_BA_UV_EXT_0		0x234
+#define DISPC_VID_BA_UV_EXT_1		0x238
+#define DISPC_VID_CSC_COEF7		0x23c
+#define DISPC_VID_ROW_INC_UV		0x248
+#define DISPC_VID_CLUT			0x260
+#define DISPC_VID_SAFETY_ATTRIBUTES	0x2a0
+#define DISPC_VID_SAFETY_CAPT_SIGNATURE	0x2a4
+#define DISPC_VID_SAFETY_POSITION	0x2a8
+#define DISPC_VID_SAFETY_REF_SIGNATURE	0x2ac
+#define DISPC_VID_SAFETY_SIZE		0x2b0
+#define DISPC_VID_SAFETY_LFSR_SEED	0x2b4
+#define DISPC_VID_LUMAKEY		0x2b8
+
+/* OVR */
+
+#define DISPC_OVR_CONFIG		0x0
+#define DISPC_OVR_DEFAULT_COLOR		0x8
+#define DISPC_OVR_DEFAULT_COLOR2	0xc
+#define DISPC_OVR_TRANS_COLOR_MAX	0x10
+#define DISPC_OVR_TRANS_COLOR_MAX2	0x14
+#define DISPC_OVR_TRANS_COLOR_MIN	0x18
+#define DISPC_OVR_TRANS_COLOR_MIN2	0x1c
+#define DISPC_OVR_ATTRIBUTES(n)		(0x20 + (n) * 4)
+
+/* VP */
+
+#define DISPC_VP_CONFIG				0x0
+#define DISPC_VP_CONTROL			0x4
+#define DISPC_VP_CSC_COEF0			0x8
+#define DISPC_VP_CSC_COEF1			0xc
+#define DISPC_VP_CSC_COEF2			0x10
+#define DISPC_VP_DATA_CYCLE_0			0x14
+#define DISPC_VP_DATA_CYCLE_1			0x18
+#define DISPC_VP_DATA_CYCLE_2			0x1c
+#define DISPC_VP_LINE_NUMBER			0x44
+#define DISPC_VP_POL_FREQ			0x4c
+#define DISPC_VP_SIZE_SCREEN			0x50
+#define DISPC_VP_TIMING_H			0x54
+#define DISPC_VP_TIMING_V			0x58
+#define DISPC_VP_CSC_COEF3			0x5c
+#define DISPC_VP_CSC_COEF4			0x60
+#define DISPC_VP_CSC_COEF5			0x64
+#define DISPC_VP_CSC_COEF6			0x68
+#define DISPC_VP_CSC_COEF7			0x6c
+#define DISPC_VP_SAFETY_ATTRIBUTES_0		0x70
+#define DISPC_VP_SAFETY_ATTRIBUTES_1		0x74
+#define DISPC_VP_SAFETY_ATTRIBUTES_2		0x78
+#define DISPC_VP_SAFETY_ATTRIBUTES_3		0x7c
+#define DISPC_VP_SAFETY_CAPT_SIGNATURE_0	0x90
+#define DISPC_VP_SAFETY_CAPT_SIGNATURE_1	0x94
+#define DISPC_VP_SAFETY_CAPT_SIGNATURE_2	0x98
+#define DISPC_VP_SAFETY_CAPT_SIGNATURE_3	0x9c
+#define DISPC_VP_SAFETY_POSITION_0		0xb0
+#define DISPC_VP_SAFETY_POSITION_1		0xb4
+#define DISPC_VP_SAFETY_POSITION_2		0xb8
+#define DISPC_VP_SAFETY_POSITION_3		0xbc
+#define DISPC_VP_SAFETY_REF_SIGNATURE_0		0xd0
+#define DISPC_VP_SAFETY_REF_SIGNATURE_1		0xd4
+#define DISPC_VP_SAFETY_REF_SIGNATURE_2		0xd8
+#define DISPC_VP_SAFETY_REF_SIGNATURE_3		0xdc
+#define DISPC_VP_SAFETY_SIZE_0			0xf0
+#define DISPC_VP_SAFETY_SIZE_1			0xf4
+#define DISPC_VP_SAFETY_SIZE_2			0xf8
+#define DISPC_VP_SAFETY_SIZE_3			0xfc
+#define DISPC_VP_SAFETY_LFSR_SEED		0x110
+#define DISPC_VP_GAMMA_TABLE			0x120
+#define DISPC_VP_DSS_OLDI_CFG			0x160
+#define DISPC_VP_DSS_OLDI_STATUS		0x164
+#define DISPC_VP_DSS_OLDI_LB			0x168
+
+/* CTRL_MMR0 access trough syscon */
+#define CTRLMMR0P1_OLDI_DAT0_IO_CTRL		0x41E0
+#define CTRLMMR0P1_OLDI_DAT1_IO_CTRL		0x41E4
+#define CTRLMMR0P1_OLDI_DAT2_IO_CTRL		0x41E8
+#define CTRLMMR0P1_OLDI_DAT3_IO_CTRL		0x41EC
+#define CTRLMMR0P1_OLDI_CLK_IO_CTRL		0x41F0
+
+#define CTRLMMR0P1_OLDI_PWRDN_TX		BIT(8)
+
+#endif /* __TIDSS_DISPC7_H */

+ 317 - 0
drivers/gpu/drm/tidss/tidss_drv.c

@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/console.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+
+#include "tidss_dispc.h"
+#include "tidss_drv.h"
+#include "tidss_irq.h"
+#include "tidss_kms.h"
+
+/* -----------------------------------------------------------------------------
+ * Device Information
+ */
+
+DEFINE_DRM_GEM_CMA_FOPS(tidss_fops);
+
+static struct drm_driver tidss_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME
+				| DRIVER_ATOMIC | DRIVER_HAVE_IRQ,
+	.gem_free_object_unlocked = drm_gem_cma_free_object,
+	.gem_vm_ops		= &drm_gem_cma_vm_ops,
+	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
+	.gem_prime_import	= drm_gem_prime_import,
+	.gem_prime_export	= drm_gem_prime_export,
+	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap		= drm_gem_cma_prime_mmap,
+	.dumb_create		= drm_gem_cma_dumb_create,
+	.fops			= &tidss_fops,
+	.name			= "tidss",
+	.desc			= "TI Keystone DSS",
+	.date			= "20180215",
+	.major			= 1,
+	.minor			= 0,
+
+	.irq_preinstall		= tidss_irq_preinstall,
+	.irq_postinstall	= tidss_irq_postinstall,
+	.irq_handler		= tidss_irq_handler,
+	.irq_uninstall		= tidss_irq_uninstall,
+};
+
+#ifdef CONFIG_PM
+/* -----------------------------------------------------------------------------
+ * Power management
+ */
+
+static int tidss_pm_runtime_suspend(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return tidss->dispc_ops->runtime_suspend(tidss->dispc);
+}
+
+static int tidss_pm_runtime_resume(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+	int r;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	r = tidss->dispc_ops->runtime_resume(tidss->dispc);
+	if (r)
+		return r;
+
+	tidss_irq_resume(tidss->ddev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int tidss_suspend(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return drm_mode_config_helper_suspend(tidss->ddev);
+}
+
+static int tidss_resume(struct device *dev)
+{
+	struct tidss_device *tidss = dev_get_drvdata(dev);
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	return drm_mode_config_helper_resume(tidss->ddev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops tidss_pm_ops = {
+	.runtime_suspend = tidss_pm_runtime_suspend,
+	.runtime_resume = tidss_pm_runtime_resume,
+	SET_SYSTEM_SLEEP_PM_OPS(tidss_suspend, tidss_resume)
+};
+
+#endif /* CONFIG_PM */
+
+/* -----------------------------------------------------------------------------
+ * Platform driver
+ */
+
+static int tidss_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tidss_device *tidss;
+	struct drm_device *ddev;
+	int ret;
+	int irq;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	tidss = devm_kzalloc(dev, sizeof(*tidss), GFP_KERNEL);
+	if (tidss == NULL)
+		return -ENOMEM;
+
+	tidss->dev = dev;
+	tidss->features = of_device_get_match_data(dev);
+
+	platform_set_drvdata(pdev, tidss);
+
+	ddev = drm_dev_alloc(&tidss_driver, dev);
+	if (IS_ERR(ddev))
+		return PTR_ERR(ddev);
+
+	tidss->ddev = ddev;
+	ddev->dev_private = tidss;
+
+	pm_runtime_enable(dev);
+
+	ret = tidss->features->dispc_init(tidss);
+	if (ret) {
+		dev_err(dev, "failed to initialize dispc: %d\n", ret);
+		goto err_disable_pm;
+	}
+
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force enable DISPC */
+	tidss->dispc_ops->runtime_resume(tidss->dispc);
+#endif
+
+	ret = tidss_modeset_init(tidss);
+	if (ret < 0) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "failed to init DRM/KMS (%d)\n", ret);
+		goto err_runtime_suspend;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		ret = irq;
+		dev_err(dev, "platform_get_irq failed: %d\n", ret);
+		goto err_modeset_cleanup;
+	}
+
+	ret = drm_irq_install(ddev, irq);
+	if (ret) {
+		dev_err(dev, "drm_irq_install failed: %d\n", ret);
+		goto err_modeset_cleanup;
+	}
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+	if (ddev->mode_config.num_connector) {
+		struct drm_fbdev_cma *fbdev;
+
+		fbdev = drm_fbdev_cma_init(ddev, 32,
+					   ddev->mode_config.num_connector);
+		if (IS_ERR(fbdev)) {
+			dev_err(tidss->dev, "fbdev init failed\n");
+			ret =  PTR_ERR(fbdev);
+			goto err_irq_uninstall;
+		}
+
+		tidss->fbdev = fbdev;
+	}
+#endif
+
+	drm_kms_helper_poll_init(ddev);
+
+	ret = drm_dev_register(ddev, 0);
+	if (ret) {
+		dev_err(dev, "failed to register DRM device\n");
+		goto err_poll_fini;
+	}
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+
+err_poll_fini:
+	drm_kms_helper_poll_fini(ddev);
+
+	if (tidss->fbdev)
+		drm_fbdev_cma_fini(tidss->fbdev);
+
+	drm_atomic_helper_shutdown(ddev);
+
+#ifdef CONFIG_DRM_FBDEV_EMULATION
+err_irq_uninstall:
+#endif
+	drm_irq_uninstall(ddev);
+
+err_modeset_cleanup:
+	drm_mode_config_cleanup(ddev);
+
+err_runtime_suspend:
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force disable DISPC */
+	tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+	tidss->dispc_ops->remove(tidss->dispc);
+
+err_disable_pm:
+	pm_runtime_disable(dev);
+
+	drm_dev_put(ddev);
+
+	return ret;
+}
+
+static int tidss_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tidss_device *tidss = platform_get_drvdata(pdev);
+	struct drm_device *ddev = tidss->ddev;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	drm_dev_unregister(ddev);
+
+	drm_kms_helper_poll_fini(ddev);
+
+	if (tidss->fbdev)
+		drm_fbdev_cma_fini(tidss->fbdev);
+
+	drm_atomic_helper_shutdown(ddev);
+
+	drm_irq_uninstall(ddev);
+
+	drm_mode_config_cleanup(ddev);
+
+#ifndef CONFIG_PM_SLEEP
+	/* no PM, so force disable DISPC */
+	tidss->dispc_ops->runtime_suspend(tidss->dispc);
+#endif
+
+	tidss->dispc_ops->remove(tidss->dispc);
+
+	pm_runtime_disable(dev);
+
+	drm_dev_put(ddev);
+
+	dev_dbg(dev, "%s done\n", __func__);
+
+	return 0;
+}
+
+#ifdef CONFIG_DRM_TIDSS_DSS6
+static const struct tidss_features tidss_k2g_features = {
+	.dispc_init = dispc6_init,
+};
+#endif
+
+#ifdef CONFIG_DRM_TIDSS_DSS7
+static const struct tidss_features tidss_am6_features = {
+	.dispc_init = dispc7_init,
+};
+#endif
+static const struct of_device_id tidss_of_table[] = {
+#ifdef CONFIG_DRM_TIDSS_DSS6
+	{ .compatible = "ti,k2g-dss", .data = &tidss_k2g_features },
+#endif
+#ifdef CONFIG_DRM_TIDSS_DSS7
+	{ .compatible = "ti,am6-dss", .data = &tidss_am6_features },
+#endif
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, tidss_of_table);
+
+static struct platform_driver tidss_platform_driver = {
+	.probe		= tidss_probe,
+	.remove		= tidss_remove,
+	.driver		= {
+		.name	= "tidss",
+#ifdef CONFIG_PM
+		.pm	= &tidss_pm_ops,
+#endif
+		.of_match_table = tidss_of_table,
+		.suppress_bind_attrs = true,
+	},
+};
+
+module_platform_driver(tidss_platform_driver);
+
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ti.com>");
+MODULE_DESCRIPTION("TI Keystone DSS Driver");
+MODULE_LICENSE("GPL v2");

+ 40 - 0
drivers/gpu/drm/tidss/tidss_drv.h

@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_DRV_H__
+#define __TIDSS_DRV_H__
+
+#include <linux/spinlock.h>
+
+struct tidss_device {
+	struct device *dev;		/* Underlying DSS device */
+	struct drm_device *ddev;	/* DRM device for DSS */
+
+	struct drm_fbdev_cma *fbdev;
+
+	struct dispc_device *dispc;
+	const struct tidss_dispc_ops *dispc_ops;
+
+	const struct tidss_features *features;
+
+	unsigned int num_crtcs;
+	struct drm_crtc *crtcs[8];
+
+	unsigned int num_planes;
+	struct drm_plane *planes[8];
+
+	spinlock_t wait_lock;	/* protects the irq masks */
+	u64 irq_mask;		/* enabled irqs in addition to wait_list */
+	u64 irq_uf_mask;	/* underflow irq bits for all planes */
+
+	struct drm_atomic_state *saved_state;
+};
+
+struct tidss_features {
+	int (*dispc_init)(struct tidss_device *tidss);
+};
+
+#endif

+ 70 - 0
drivers/gpu/drm/tidss/tidss_encoder.c

@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <linux/export.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_of.h>
+
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_crtc.h"
+
+/* -----------------------------------------------------------------------------
+ * Encoder
+ */
+
+static int tidss_encoder_atomic_check(struct drm_encoder *encoder,
+				      struct drm_crtc_state *crtc_state,
+				      struct drm_connector_state *conn_state)
+{
+	struct drm_device *ddev = encoder->dev;
+	struct tidss_crtc_state *tcrtc_state = to_tidss_crtc_state(crtc_state);
+	struct drm_display_info *di = &conn_state->connector->display_info;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	// XXX any cleaner way to set bus format and flags?
+	tcrtc_state->bus_format = di->bus_formats[0];
+	tcrtc_state->bus_flags = di->bus_flags;
+
+	return 0;
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+	.atomic_check = tidss_encoder_atomic_check,
+};
+
+static const struct drm_encoder_funcs encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+					 u32 encoder_type, u32 possible_crtcs)
+{
+	struct drm_encoder *enc;
+	int ret;
+
+	enc = devm_kzalloc(tidss->dev, sizeof(*enc), GFP_KERNEL);
+	if (!enc)
+		return ERR_PTR(-ENOMEM);
+
+	enc->possible_crtcs = possible_crtcs;
+
+	ret = drm_encoder_init(tidss->ddev, enc, &encoder_funcs,
+			       encoder_type, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_encoder_helper_add(enc, &encoder_helper_funcs);
+
+	dev_dbg(tidss->dev, "Encoder create done\n");
+
+	return enc;
+}

+ 17 - 0
drivers/gpu/drm/tidss/tidss_encoder.h

@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_ENCODER_H__
+#define __TIDSS_ENCODER_H__
+
+#include <drm/drm_encoder.h>
+
+struct tidss_device;
+
+struct drm_encoder *tidss_encoder_create(struct tidss_device *tidss,
+					 u32 encoder_type, u32 possible_crtcs);
+
+#endif

+ 193 - 0
drivers/gpu/drm/tidss/tidss_irq.c

@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+
+#include "tidss_irq.h"
+#include "tidss_drv.h"
+#include "tidss_dispc.h"
+#include "tidss_crtc.h"
+#include "tidss_plane.h"
+
+/* call with wait_lock and dispc runtime held */
+static void tidss_irq_update(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	assert_spin_locked(&tidss->wait_lock);
+
+	tidss->dispc_ops->write_irqenable(tidss->dispc, tidss->irq_mask);
+}
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	u32 hw_videoport = tcrtc->hw_videoport;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss->irq_mask |= DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+			   DSS_IRQ_VP_VSYNC_ODD(hw_videoport);
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+void tidss_irq_disable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *ddev = crtc->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+	u32 hw_videoport = tcrtc->hw_videoport;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss->irq_mask &= ~(DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+			     DSS_IRQ_VP_VSYNC_ODD(hw_videoport));
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}
+
+static void tidss_irq_fifo_underflow(struct tidss_device *tidss,
+				     u64 irqstatus)
+{
+	static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL,
+				      DEFAULT_RATELIMIT_BURST);
+	unsigned int i;
+	u64 masked;
+
+	spin_lock(&tidss->wait_lock);
+	masked = irqstatus & tidss->irq_uf_mask & tidss->irq_mask;
+	spin_unlock(&tidss->wait_lock);
+
+	if (!masked)
+		return;
+
+	if (!__ratelimit(&_rs))
+		return;
+
+	DRM_ERROR("FIFO underflow on ");
+
+	for (i = 0; i < DSS_MAX_PLANES; ++i) {
+		if (masked & DSS_IRQ_PLANE_FIFO_UNDERFLOW(i))
+			pr_cont("%u:%s ", i,
+				tidss->dispc_ops->plane_name(tidss->dispc, i));
+	}
+
+	pr_cont("(%016llx)\n", irqstatus);
+}
+
+static void tidss_irq_ocp_error_handler(struct drm_device *ddev,
+					u64 irqstatus)
+{
+	if (irqstatus & DSS_IRQ_DEVICE_OCP_ERR)
+		dev_err_ratelimited(ddev->dev, "OCP error\n");
+}
+
+irqreturn_t tidss_irq_handler(int irq, void *arg)
+{
+	struct drm_device *ddev = (struct drm_device *) arg;
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned int id;
+	u64 irqstatus;
+
+	if (WARN_ON(!ddev->irq_enabled))
+		return IRQ_NONE;
+
+	irqstatus = tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+	for (id = 0; id < tidss->num_crtcs; id++) {
+		struct drm_crtc *crtc = tidss->crtcs[id];
+		struct tidss_crtc *tcrtc = to_tidss_crtc(crtc);
+		u32 hw_videoport = tcrtc->hw_videoport;
+
+		if (irqstatus & (DSS_IRQ_VP_VSYNC_EVEN(hw_videoport) |
+				 DSS_IRQ_VP_VSYNC_ODD(hw_videoport)))
+			tidss_crtc_vblank_irq(crtc);
+
+		if (irqstatus & (DSS_IRQ_VP_FRAME_DONE(hw_videoport)))
+			tidss_crtc_framedone_irq(crtc);
+
+		if (irqstatus & DSS_IRQ_VP_SYNC_LOST(hw_videoport))
+			tidss_crtc_error_irq(crtc, irqstatus);
+	}
+
+	tidss_irq_ocp_error_handler(ddev, irqstatus);
+	tidss_irq_fifo_underflow(tidss, irqstatus);
+
+	return IRQ_HANDLED;
+}
+
+void tidss_irq_preinstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	spin_lock_init(&tidss->wait_lock);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+	tidss->dispc_ops->read_and_clear_irqstatus(tidss->dispc);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+int tidss_irq_postinstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned int i;
+	unsigned long flags;
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+
+	tidss->irq_mask = DSS_IRQ_DEVICE_OCP_ERR;
+
+	tidss->irq_uf_mask = 0;
+	for (i = 0; i < tidss->num_planes; ++i) {
+		struct tidss_plane *tplane = to_tidss_plane(tidss->planes[i]);
+
+		tidss->irq_uf_mask |= DSS_IRQ_PLANE_FIFO_UNDERFLOW(tplane->hw_plane_id);
+	}
+	tidss->irq_mask |= tidss->irq_uf_mask;
+
+	for (i = 0; i < tidss->num_crtcs; ++i) {
+		struct tidss_crtc *tcrtc = to_tidss_crtc(tidss->crtcs[i]);
+
+		tidss->irq_mask |= DSS_IRQ_VP_SYNC_LOST(tcrtc->hw_videoport);
+
+		tidss->irq_mask |= DSS_IRQ_VP_FRAME_DONE(tcrtc->hw_videoport);
+	}
+
+	tidss_irq_update(ddev);
+
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+
+	return 0;
+}
+
+void tidss_irq_uninstall(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+	tidss->dispc_ops->write_irqenable(tidss->dispc, 0);
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+void tidss_irq_resume(struct drm_device *ddev)
+{
+	struct tidss_device *tidss = ddev->dev_private;
+	unsigned long flags;
+
+	spin_lock_irqsave(&tidss->wait_lock, flags);
+	tidss_irq_update(ddev);
+	spin_unlock_irqrestore(&tidss->wait_lock, flags);
+}

+ 25 - 0
drivers/gpu/drm/tidss/tidss_irq.h

@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_IRQ_H__
+#define __TIDSS_IRQ_H__
+
+#include <linux/types.h>
+
+struct drm_crtc;
+struct drm_device;
+
+void tidss_irq_enable_vblank(struct drm_crtc *crtc);
+void tidss_irq_disable_vblank(struct drm_crtc *crtc);
+
+void tidss_irq_preinstall(struct drm_device *ddev);
+int tidss_irq_postinstall(struct drm_device *ddev);
+void tidss_irq_uninstall(struct drm_device *ddev);
+irqreturn_t tidss_irq_handler(int irq, void *arg);
+
+void tidss_irq_resume(struct drm_device *ddev);
+
+#endif

+ 95 - 0
drivers/gpu/drm/tidss/tidss_kms.c

@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_encoder.h"
+#include "tidss_kms.h"
+#include "tidss_plane.h"
+
+static void tidss_atomic_commit_tail(struct drm_atomic_state *old_state)
+{
+	struct drm_device *ddev = old_state->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->runtime_get(tidss->dispc);
+
+	drm_atomic_helper_commit_modeset_disables(ddev, old_state);
+	drm_atomic_helper_commit_planes(ddev, old_state, 0);
+	drm_atomic_helper_commit_modeset_enables(ddev, old_state);
+
+	drm_atomic_helper_commit_hw_done(old_state);
+	drm_atomic_helper_wait_for_flip_done(ddev, old_state);
+
+	drm_atomic_helper_cleanup_planes(ddev, old_state);
+
+	tidss->dispc_ops->runtime_put(tidss->dispc);
+}
+
+static const struct drm_mode_config_helper_funcs mode_config_helper_funcs = {
+	.atomic_commit_tail = tidss_atomic_commit_tail,
+};
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int tidss_modeset_init_properties(struct tidss_device *tidss)
+{
+	return 0;
+}
+
+int tidss_modeset_init(struct tidss_device *tidss)
+{
+	struct drm_device *ddev = tidss->ddev;
+	unsigned int i;
+	int ret;
+
+	dev_dbg(tidss->dev, "%s\n", __func__);
+
+	drm_mode_config_init(ddev);
+
+	ddev->mode_config.min_width = 8;
+	ddev->mode_config.min_height = 8;
+	ddev->mode_config.max_width = 8096;
+	ddev->mode_config.max_height = 8096;
+	ddev->mode_config.normalize_zpos = true;
+	ddev->mode_config.funcs = &mode_config_funcs;
+	ddev->mode_config.helper_private = &mode_config_helper_funcs;
+
+	ret = tidss_modeset_init_properties(tidss);
+	if (ret < 0)
+		return ret;
+
+	ret = tidss->dispc_ops->modeset_init(tidss->dispc);
+	if (ret)
+		return ret;
+
+	ret = drm_vblank_init(ddev, tidss->num_crtcs);
+	if (ret)
+		return ret;
+
+	/* Start with vertical blanking interrupt reporting disabled. */
+	for (i = 0; i < tidss->num_crtcs; ++i)
+		drm_crtc_vblank_reset(tidss->crtcs[i]);
+
+	drm_mode_config_reset(ddev);
+
+	dev_dbg(tidss->dev, "%s done\n", __func__);
+
+	return 0;
+}

+ 14 - 0
drivers/gpu/drm/tidss/tidss_kms.h

@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_KMS_H__
+#define __TIDSS_KMS_H__
+
+struct tidss_device;
+
+int tidss_modeset_init(struct tidss_device *tidss);
+
+#endif

+ 240 - 0
drivers/gpu/drm/tidss/tidss_plane.c

@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#include <drm/drmP.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_plane_helper.h>
+
+#include "tidss_crtc.h"
+#include "tidss_drv.h"
+#include "tidss_plane.h"
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	u32 x = state->src_x >> 16;
+	u32 y = state->src_y >> 16;
+
+	gem = drm_fb_cma_get_gem_obj(state->fb, 0);
+
+	return gem->paddr + fb->offsets[0] + x * fb->format->cpp[0] +
+		y * fb->pitches[0];
+}
+
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_cma_object *gem;
+	u32 x = state->src_x >> 16;
+	u32 y = state->src_y >> 16;
+
+	if (WARN_ON(state->fb->format->num_planes != 2))
+		return 0;
+
+	gem = drm_fb_cma_get_gem_obj(fb, 1);
+
+	return gem->paddr + fb->offsets[1] +
+		(x * fb->format->cpp[1] / fb->format->hsub) +
+		(y * fb->pitches[1] / fb->format->vsub);
+}
+
+static int tidss_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_plane_state *state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct drm_crtc_state *crtc_state;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	const struct drm_format_info *finfo;
+	u32 hw_videoport;
+	int ret;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->crtc) {
+		/*
+		 * The visible field is not reset by the DRM core but only
+		 * updated by drm_plane_helper_check_state(), set it manually.
+		 */
+		state->visible = false;
+		return 0;
+	}
+
+	crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+						  0,
+						  INT_MAX,
+						  true, true);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * The HW is only able to start drawing at subpixel boundary
+	 * (the two first checks bellow). At the end of a row the HW
+	 * can only jump integer number of subpixels forward to
+	 * beginning of next row. So we can only show picture with
+	 * integer subpixel width (the third check). However, after
+	 * reaching the end of the drawn picture the drawing starts
+	 * again at the absolute memory address where top left corner
+	 * position of the drawn picture is (so there is no need to
+	 * check for odd height).
+	 */
+
+	finfo = drm_format_info(state->fb->format->format);
+
+	if ((state->src_x >> 16) % finfo->hsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: x-position %u not divisible subpixel size %u\n",
+			__func__, (state->src_x >> 16), finfo->hsub);
+		return -EINVAL;
+	}
+
+	if ((state->src_y >> 16) % finfo->vsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: y-position %u not divisible subpixel size %u\n",
+			__func__, (state->src_y >> 16), finfo->vsub);
+		return -EINVAL;
+	}
+
+	if ((state->src_w >> 16) % finfo->hsub != 0) {
+		dev_dbg(ddev->dev,
+			"%s: src width %u not divisible by subpixel size %u\n",
+			 __func__, (state->src_w >> 16), finfo->hsub);
+		return -EINVAL;
+	}
+
+	if (!state->visible)
+		return 0;
+
+	hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+	return tidss->dispc_ops->plane_check(tidss->dispc,
+					     tplane->hw_plane_id,
+					     state, hw_videoport);
+}
+
+static void tidss_plane_atomic_update(struct drm_plane *plane,
+				      struct drm_plane_state *old_state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+	struct drm_plane_state *state = plane->state;
+	u32 hw_videoport;
+	int ret;
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	if (!state->visible) {
+		tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+					       false);
+		return;
+	}
+
+	hw_videoport = to_tidss_crtc(state->crtc)->hw_videoport;
+
+	ret = tidss->dispc_ops->plane_setup(tidss->dispc, tplane->hw_plane_id,
+					    state, hw_videoport);
+
+	if (ret) {
+		dev_err(plane->dev->dev, "Failed to setup plane %d\n",
+			tplane->hw_plane_id);
+		tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id,
+					       false);
+		return;
+	}
+
+	tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, true);
+}
+
+static void tidss_plane_atomic_disable(struct drm_plane *plane,
+				       struct drm_plane_state *old_state)
+{
+	struct drm_device *ddev = plane->dev;
+	struct tidss_device *tidss = ddev->dev_private;
+	struct tidss_plane *tplane = to_tidss_plane(plane);
+
+	dev_dbg(ddev->dev, "%s\n", __func__);
+
+	tidss->dispc_ops->plane_enable(tidss->dispc, tplane->hw_plane_id, false);
+}
+
+static const struct drm_plane_helper_funcs tidss_plane_helper_funcs = {
+	.atomic_check = tidss_plane_atomic_check,
+	.atomic_update = tidss_plane_atomic_update,
+	.atomic_disable = tidss_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs tidss_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.reset = drm_atomic_helper_plane_reset,
+	.destroy = drm_plane_cleanup,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+				       u32 hw_plane_id,	u32 plane_type,
+				       u32 crtc_mask, const u32 *formats,
+				       u32 num_formats)
+{
+	const struct tidss_plane_feat *pfeat;
+	struct tidss_plane *tplane;
+	enum drm_plane_type type;
+	u32 possible_crtcs;
+	u32 num_planes = tidss->dispc_ops->get_num_planes(tidss->dispc);
+	int ret;
+
+	pfeat = tidss->dispc_ops->plane_feat(tidss->dispc, hw_plane_id);
+
+	tplane = devm_kzalloc(tidss->dev, sizeof(*tplane), GFP_KERNEL);
+	if (!tplane)
+		return ERR_PTR(-ENOMEM);
+
+	tplane->hw_plane_id = hw_plane_id;
+
+	possible_crtcs = crtc_mask;
+	type = plane_type;
+
+	ret = drm_universal_plane_init(tidss->ddev, &tplane->plane,
+				       possible_crtcs,
+				       &tidss_plane_funcs,
+				       formats, num_formats,
+				       NULL, type, NULL);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	drm_plane_helper_add(&tplane->plane, &tidss_plane_helper_funcs);
+	if (num_planes > 1)
+		drm_plane_create_zpos_property(&tplane->plane, hw_plane_id, 0,
+					       num_planes - 1);
+
+	ret = drm_plane_create_color_properties(&tplane->plane,
+						pfeat->color.encodings,
+						pfeat->color.ranges,
+						pfeat->color.default_encoding,
+						pfeat->color.default_range);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (pfeat->blend.global_alpha) {
+		ret = drm_plane_create_alpha_property(&tplane->plane);
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	return tplane;
+}

+ 29 - 0
drivers/gpu/drm/tidss/tidss_plane.h

@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Tomi Valkeinen <tomi.valkeinen@ti.com>
+ */
+
+#ifndef __TIDSS_PLANE_H__
+#define __TIDSS_PLANE_H__
+
+#include "tidss_dispc.h"
+
+#define to_tidss_plane(p) container_of((p), struct tidss_plane, plane)
+
+struct tidss_plane {
+	struct drm_plane plane;
+
+	u32 hw_plane_id;
+
+};
+
+struct tidss_plane *tidss_plane_create(struct tidss_device *tidss,
+				       u32 hw_plane_id,	u32 plane_type,
+				       u32 crtc_mask, const u32 *formats,
+				       u32 num_formats);
+
+dma_addr_t dispc7_plane_state_paddr(const struct drm_plane_state *state);
+dma_addr_t dispc7_plane_state_p_uv_addr(const struct drm_plane_state *state);
+
+#endif

+ 185 - 0
drivers/gpu/drm/tidss/tidss_scale_coefs.c

@@ -0,0 +1,185 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include "tidss_scale_coefs.h"
+
+/*
+ * These are interpolated with a custom python script from DSS5
+ * (drivers/gpu/drm/omapdrm/dss/dispc_coef.c) coefficients.
+ */
+static const struct tidss_scale_coefs coef5_M32 = {
+	.c2 = { 28, 34, 40, 46, 52, 58, 64, 70, 0, 2, 4, 8, 12, 16, 20, 24, },
+	.c1 = { 132, 138, 144, 150, 156, 162, 168, 174, 76, 84, 92, 98, 104, 110, 116, 124, },
+	.c0 = { 192, 192, 192, 190, 188, 186, 184, 182, 180, },
+};
+static const struct tidss_scale_coefs coef5_M26 = {
+	.c2 = { 24, 28, 32, 38, 44, 50, 56, 64, 0, 2, 4, 6, 8, 12, 16, 20, },
+	.c1 = { 132, 138, 144, 152, 160, 166, 172, 178, 72, 80, 88, 94, 100, 108, 116, 124, },
+	.c0 = { 200, 202, 204, 202, 200, 196, 192, 188, 184, },
+};
+static const struct tidss_scale_coefs coef5_M22 = {
+	.c2 = { 16, 20, 24, 30, 36, 42, 48, 56, 0, 0, 0, 2, 4, 8, 12, 14, },
+	.c1 = { 132, 140, 148, 156, 164, 172, 180, 186, 64, 72, 80, 88, 96, 104, 112, 122, },
+	.c0 = { 216, 216, 216, 214, 212, 208, 204, 198, 192, },
+};
+static const struct tidss_scale_coefs coef5_M19 = {
+	.c2 = { 12, 14, 16, 22, 28, 34, 40, 48, 0, 0, 0, 2, 4, 4, 4, 8, },
+	.c1 = { 128, 140, 152, 160, 168, 176, 184, 192, 56, 64, 72, 82, 92, 100, 108, 118, },
+	.c0 = { 232, 232, 232, 226, 220, 218, 216, 208, 200, },
+};
+static const struct tidss_scale_coefs coef5_M16 = {
+	.c2 = { 0, 2, 4, 8, 12, 18, 24, 32, 0, 0, 0, -2, -4, -4, -4, -2, },
+	.c1 = { 124, 138, 152, 164, 176, 186, 196, 206, 40, 48, 56, 68, 80, 90, 100, 112, },
+	.c0 = { 264, 262, 260, 254, 248, 242, 236, 226, 216, },
+};
+static const struct tidss_scale_coefs coef5_M14 = {
+	.c2 = { -8, -6, -4, -2, 0, 6, 12, 18, 0, -2, -4, -6, -8, -8, -8, -8, },
+	.c1 = { 120, 134, 148, 164, 180, 194, 208, 220, 24, 32, 40, 52, 64, 78, 92, 106, },
+	.c0 = { 288, 286, 284, 280, 276, 266, 256, 244, 232, },
+};
+static const struct tidss_scale_coefs coef5_M13 = {
+	.c2 = { -12, -12, -12, -10, -8, -4, 0, 6, 0, -2, -4, -6, -8, -10, -12, -12, },
+	.c1 = { 112, 130, 148, 164, 180, 196, 212, 228, 12, 22, 32, 44, 56, 70, 84, 98, },
+	.c0 = { 312, 308, 304, 298, 292, 282, 272, 258, 244, },
+};
+static const struct tidss_scale_coefs coef5_M12 = {
+	.c2 = { -16, -18, -20, -18, -16, -14, -12, -6, 0, -2, -4, -6, -8, -10, -12, -14, },
+	.c1 = { 104, 124, 144, 164, 184, 202, 220, 238, 0, 10, 20, 30, 40, 56, 72, 88, },
+	.c0 = { 336, 332, 328, 320, 312, 300, 288, 272, 256, },
+};
+static const struct tidss_scale_coefs coef5_M11 = {
+	.c2 = { -20, -22, -24, -24, -24, -24, -24, -20, 0, -2, -4, -6, -8, -10, -12, -16, },
+	.c1 = { 92, 114, 136, 158, 180, 204, 228, 250, -16, -8, 0, 12, 24, 38, 52, 72, },
+	.c0 = { 368, 364, 360, 350, 340, 326, 312, 292, 272, },
+};
+static const struct tidss_scale_coefs coef5_M10 = {
+	.c2 = { -16, -20, -24, -28, -32, -34, -36, -34, 0, 0, 0, -2, -4, -8, -12, -14, },
+	.c1 = { 72, 96, 120, 148, 176, 204, 232, 260, -32, -26, -20, -10, 0, 16, 32, 52, },
+	.c0 = { 400, 398, 396, 384, 372, 354, 336, 312, 288, },
+};
+static const struct tidss_scale_coefs coef5_M9 = {
+	.c2 = { -12, -18, -24, -28, -32, -38, -44, -46, 0, 2, 4, 2, 0, -2, -4, -8, },
+	.c1 = { 40, 68, 96, 128, 160, 196, 232, 268, -48, -46, -44, -36, -28, -14, 0, 20, },
+	.c0 = { 456, 450, 444, 428, 412, 388, 364, 334, 304, },
+};
+static const struct tidss_scale_coefs coef5_M8 = {
+	.c2 = { 0, -4, -8, -16, -24, -32, -40, -48, 0, 2, 4, 6, 8, 6, 4, 2, },
+	.c1 = { 0, 28, 56, 94, 132, 176, 220, 266, -56, -60, -64, -62, -60, -50, -40, -20, },
+	.c0 = { 512, 506, 500, 478, 456, 424, 392, 352, 312, },
+};
+static const struct tidss_scale_coefs coef3_M32 = {
+	.c1 = { 108, 92, 76, 62, 48, 36, 24, 140, 256, 236, 216, 198, 180, 162, 144, 126, },
+	.c0 = { 296, 294, 292, 288, 284, 278, 272, 136, 256, },
+};
+static const struct tidss_scale_coefs coef3_M26 = {
+	.c1 = { 104, 90, 76, 60, 44, 32, 20, 138, 256, 236, 216, 198, 180, 160, 140, 122, },
+	.c0 = { 304, 300, 296, 292, 288, 282, 276, 138, 256, },
+};
+static const struct tidss_scale_coefs coef3_M22 = {
+	.c1 = { 100, 84, 68, 54, 40, 30, 20, 138, 256, 236, 216, 196, 176, 156, 136, 118, },
+	.c0 = { 312, 310, 308, 302, 296, 286, 276, 138, 256, },
+};
+static const struct tidss_scale_coefs coef3_M19 = {
+	.c1 = { 96, 80, 64, 50, 36, 26, 16, 136, 256, 236, 216, 194, 172, 152, 132, 114, },
+	.c0 = { 320, 318, 316, 310, 304, 292, 280, 140, 256, },
+};
+static const struct tidss_scale_coefs coef3_M16 = {
+	.c1 = { 88, 72, 56, 44, 32, 22, 12, 134, 256, 234, 212, 190, 168, 148, 128, 108, },
+	.c0 = { 336, 332, 328, 320, 312, 300, 288, 144, 256, },
+};
+static const struct tidss_scale_coefs coef3_M14 = {
+	.c1 = { 80, 64, 48, 36, 24, 16, 8, 132, 256, 232, 208, 186, 164, 142, 120, 100, },
+	.c0 = { 352, 348, 344, 334, 324, 310, 296, 148, 256, },
+};
+static const struct tidss_scale_coefs coef3_M13 = {
+	.c1 = { 72, 56, 40, 30, 20, 12, 4, 130, 256, 232, 208, 184, 160, 136, 112, 92, },
+	.c0 = { 368, 364, 360, 346, 332, 316, 300, 150, 256, },
+};
+static const struct tidss_scale_coefs coef3_M12 = {
+	.c1 = { 64, 50, 36, 26, 16, 10, 4, 130, 256, 230, 204, 178, 152, 128, 104, 84, },
+	.c0 = { 384, 378, 372, 358, 344, 324, 304, 152, 256, },
+};
+static const struct tidss_scale_coefs coef3_M11 = {
+	.c1 = { 56, 40, 24, 16, 8, 4, 0, 128, 256, 228, 200, 172, 144, 120, 96, 76, },
+	.c0 = { 400, 396, 392, 376, 360, 336, 312, 156, 256, },
+};
+static const struct tidss_scale_coefs coef3_M10 = {
+	.c1 = { 40, 26, 12, 6, 0, -2, -4, 126, 256, 226, 196, 166, 136, 110, 84, 62, },
+	.c0 = { 432, 424, 416, 396, 376, 348, 320, 160, 256, },
+};
+static const struct tidss_scale_coefs coef3_M9 = {
+	.c1 = { 24, 12, 0, -4, -8, -8, -8, 124, 256, 222, 188, 154, 120, 92, 64, 44, },
+	.c0 = { 464, 456, 448, 424, 400, 366, 332, 166, 256, },
+};
+static const struct tidss_scale_coefs coef3_M8 = {
+	.c1 = { 0, -8, -16, -16, -16, -12, -8, 124, 256, 214, 172, 134, 96, 66, 36, 18, },
+	.c0 = { 512, 502, 492, 462, 432, 390, 348, 174, 256, },
+};
+
+/* Nearest neigbor coefficients for testing */
+static const struct tidss_scale_coefs coefs_null = {
+	.c2 = { 0 },
+	.c1 = { 0 },
+	.c0 = { 512, 512, 512, 512, 512, 512, 512, 512, 256,  },
+};
+
+const struct tidss_scale_coefs *tidss_get_scale_coefs(struct device *dev,
+						      u32 firinc,
+						      bool five_taps)
+{
+	int i;
+	int inc;
+	static const struct {
+		int Mmin;
+		int Mmax;
+		const struct tidss_scale_coefs *coef3;
+		const struct tidss_scale_coefs *coef5;
+		const char *name;
+	} coefs[] = {
+		{ 27, 32, &coef3_M32, &coef5_M32, "M32" },
+		{ 23, 26, &coef3_M26, &coef5_M26, "M26" },
+		{ 20, 22, &coef3_M22, &coef5_M22, "M22" },
+		{ 17, 19, &coef3_M19, &coef5_M19, "M19" },
+		{ 15, 16, &coef3_M16, &coef5_M16, "M16" },
+		{ 14, 14, &coef3_M14, &coef5_M14, "M14" },
+		{ 13, 13, &coef3_M13, &coef5_M13, "M13" },
+		{ 12, 12, &coef3_M12, &coef5_M12, "M12" },
+		{ 11, 11, &coef3_M11, &coef5_M11, "M11" },
+		{ 10, 10, &coef3_M10, &coef5_M10, "M10" },
+		{  9,  9, &coef3_M9, &coef5_M9, "M9" },
+		{  4,  8, &coef3_M8, &coef5_M8, "M8" },
+		/*
+		 * When upscaling more than two times, blockiness and outlines
+		 * around the image are observed when M8 tables are used. M11,
+		 * M16 and M19 tables are used to prevent this.
+		 */
+		{  3,  3, &coef3_M11, &coef5_M11, "M11" },
+		{  2,  2, &coef3_M16, &coef5_M16, "M16" },
+		{  0,  1, &coef3_M19, &coef5_M19, "M19" },
+	};
+
+	/*
+	 * inc is result of 0x200000 * in_size / out_size. This dividing
+	 * by 0x40000 scales it down to 8 * in_size / out_size. After
+	 * division the actual scaling factor is 8/inc.
+	 */
+	inc = firinc / 0x40000;
+	for (i = 0; i < ARRAY_SIZE(coefs); ++i) {
+		if (inc >= coefs[i].Mmin && inc <= coefs[i].Mmax) {
+			if (five_taps)
+				return coefs[i].coef5;
+			else
+				return coefs[i].coef3;
+		}
+	}
+
+	dev_err(dev, "%s: Coefficients not found for firinc 0x%08x, inc %d\n",
+		__func__, firinc, inc);
+
+	return NULL;
+}

+ 22 - 0
drivers/gpu/drm/tidss/tidss_scale_coefs.h

@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+ * Author: Jyri Sarha <jsarha@ti.com>
+ */
+
+#ifndef __TIDSS_DISPC_COEF_H__
+#define __TIDSS_DISPC_COEF_H__
+
+#include <linux/types.h>
+
+struct tidss_scale_coefs {
+	s16 c2[16];
+	s16 c1[16];
+	u16 c0[9];
+};
+
+const struct tidss_scale_coefs *tidss_get_scale_coefs(struct device *dev,
+						      u32 firinc,
+						      bool five_taps);
+
+#endif

+ 6 - 18
drivers/input/touchscreen/goodix.c

@@ -484,31 +484,17 @@ static int goodix_reset(struct goodix_ts_data *ts)
 {
 {
 	int error;
 	int error;
 
 
-	/* begin select I2C slave addr */
-	error = gpiod_direction_output(ts->gpiod_rst, 0);
-	if (error)
-		return error;
-
 	msleep(20);				/* T2: > 10ms */
 	msleep(20);				/* T2: > 10ms */
 
 
 	/* HIGH: 0x28/0x29, LOW: 0xBA/0xBB */
 	/* HIGH: 0x28/0x29, LOW: 0xBA/0xBB */
-	error = gpiod_direction_output(ts->gpiod_int, ts->client->addr == 0x14);
-	if (error)
-		return error;
+	gpiod_set_value_cansleep(ts->gpiod_int, ts->client->addr == 0x14);
 
 
 	usleep_range(100, 2000);		/* T3: > 100us */
 	usleep_range(100, 2000);		/* T3: > 100us */
 
 
-	error = gpiod_direction_output(ts->gpiod_rst, 1);
-	if (error)
-		return error;
+	gpiod_set_value_cansleep(ts->gpiod_rst, 1);
 
 
 	usleep_range(6000, 10000);		/* T4: > 5ms */
 	usleep_range(6000, 10000);		/* T4: > 5ms */
 
 
-	/* end select I2C slave addr */
-	error = gpiod_direction_input(ts->gpiod_rst);
-	if (error)
-		return error;
-
 	error = goodix_int_sync(ts);
 	error = goodix_int_sync(ts);
 	if (error)
 	if (error)
 		return error;
 		return error;
@@ -532,7 +518,8 @@ static int goodix_get_gpio_config(struct goodix_ts_data *ts)
 	dev = &ts->client->dev;
 	dev = &ts->client->dev;
 
 
 	/* Get the interrupt GPIO pin number */
 	/* Get the interrupt GPIO pin number */
-	gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_INT_NAME, GPIOD_IN);
+	gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_INT_NAME,
+					GPIOD_OUT_LOW);
 	if (IS_ERR(gpiod)) {
 	if (IS_ERR(gpiod)) {
 		error = PTR_ERR(gpiod);
 		error = PTR_ERR(gpiod);
 		if (error != -EPROBE_DEFER)
 		if (error != -EPROBE_DEFER)
@@ -544,7 +531,8 @@ static int goodix_get_gpio_config(struct goodix_ts_data *ts)
 	ts->gpiod_int = gpiod;
 	ts->gpiod_int = gpiod;
 
 
 	/* Get the reset line GPIO pin number */
 	/* Get the reset line GPIO pin number */
-	gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_RST_NAME, GPIOD_IN);
+	gpiod = devm_gpiod_get_optional(dev, GOODIX_GPIO_RST_NAME,
+					GPIOD_OUT_LOW);
 	if (IS_ERR(gpiod)) {
 	if (IS_ERR(gpiod)) {
 		error = PTR_ERR(gpiod);
 		error = PTR_ERR(gpiod);
 		if (error != -EPROBE_DEFER)
 		if (error != -EPROBE_DEFER)

+ 7 - 0
ti_config_fragments/audio_display.cfg

@@ -2,6 +2,8 @@
 # TI Audio/Display config options
 # TI Audio/Display config options
 ##################################################
 ##################################################
 
 
+CONFIG_CMA=y
+CONFIG_DMA_CMA=y
 CONFIG_CMA_SIZE_MBYTES=24
 CONFIG_CMA_SIZE_MBYTES=24
 
 
 # backlight
 # backlight
@@ -49,6 +51,11 @@ CONFIG_DRM_OMAP_ENCODER_TFP410=y
 CONFIG_DRM_OMAP_CONNECTOR_DVI=y
 CONFIG_DRM_OMAP_CONNECTOR_DVI=y
 CONFIG_DRM_OMAP_PANEL_DPI=y
 CONFIG_DRM_OMAP_PANEL_DPI=y
 
 
+# tidss
+CONFIG_DRM_TIDSS=y
+CONFIG_DRM_TIDSS_DSS6=y
+CONFIG_DRM_TIDSS_DSS7=y
+
 # Disable unneeded features
 # Disable unneeded features
 
 
 CONFIG_VGA_ARB=n
 CONFIG_VGA_ARB=n