Эх сурвалжийг харах

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 жил өмнө
parent
commit
4f4ecb6f2a
35 өөрчлөгдсөн 6200 нэмэгдсэн , 22 устгасан
  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
 riscv	RISC-V Foundation
 rockchip	Fuzhou Rockchip Electronics Co., Ltd
+rocktech	Rocktech Displays Limited
 rohm	ROHM Semiconductor Co., Ltd
 roofull	Shenzhen Roofull Technology Co, Ltd
 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
 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
 M:	Eric Anholt <eric@anholt.net>
 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 {
 		compatible = "simple-audio-card";
-		simple-audio-card,name = "DA850/OMAP-L138 LCDK";
+		simple-audio-card,name = "DA850-OMAPL138 LCDK";
 		simple-audio-card,widgets =
 			"Line", "Line In",
-			"Line", "Line Out";
+			"Line", "Line Out",
+			"Microphone", "Mic Jack";
 		simple-audio-card,routing =
 			"LINE1L", "Line In",
 			"LINE1R", "Line In",
 			"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,bitclock-master = <&link0_codec>;
 		simple-audio-card,frame-master = <&link0_codec>;
@@ -220,7 +254,15 @@
 		#sound-dai-cells = <0>;
 		compatible = "ti,tlv320aic3106";
 		reg = <0x18>;
+		adc-settle-ms = <40>;
+		ai3x-micbias-vg = <1>;		/* 2.0V */
 		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 += -@
 
 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)
 

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

@@ -438,4 +438,51 @@
 		clocks = <&k3_clks 39 0>;
 		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/tidss/Kconfig"
+
 # Keep legacy drivers last
 
 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_TVE200) += tve200/
 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,
 };
 
+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 = {
 	.clock = 271560,
 	.hdisplay = 2560,
@@ -2510,6 +2538,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 		.compatible = "rocktech,rk070er9427",
 		.data = &rocktech_rk070er9427,
+	}, {
+		.compatible = "rockteck,rk101ii01d-ct",
+		.data = &rocktech_rk101ii01d_ct,
 	}, {
 		.compatible = "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;
 
-	/* begin select I2C slave addr */
-	error = gpiod_direction_output(ts->gpiod_rst, 0);
-	if (error)
-		return error;
-
 	msleep(20);				/* T2: > 10ms */
 
 	/* 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 */
 
-	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 */
 
-	/* end select I2C slave addr */
-	error = gpiod_direction_input(ts->gpiod_rst);
-	if (error)
-		return error;
-
 	error = goodix_int_sync(ts);
 	if (error)
 		return error;
@@ -532,7 +518,8 @@ static int goodix_get_gpio_config(struct goodix_ts_data *ts)
 	dev = &ts->client->dev;
 
 	/* 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)) {
 		error = PTR_ERR(gpiod);
 		if (error != -EPROBE_DEFER)
@@ -544,7 +531,8 @@ static int goodix_get_gpio_config(struct goodix_ts_data *ts)
 	ts->gpiod_int = gpiod;
 
 	/* 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)) {
 		error = PTR_ERR(gpiod);
 		if (error != -EPROBE_DEFER)

+ 7 - 0
ti_config_fragments/audio_display.cfg

@@ -2,6 +2,8 @@
 # TI Audio/Display config options
 ##################################################
 
+CONFIG_CMA=y
+CONFIG_DMA_CMA=y
 CONFIG_CMA_SIZE_MBYTES=24
 
 # backlight
@@ -49,6 +51,11 @@ CONFIG_DRM_OMAP_ENCODER_TFP410=y
 CONFIG_DRM_OMAP_CONNECTOR_DVI=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
 
 CONFIG_VGA_ARB=n