diff --git a/Documentation/ABI/testing/configfs-usb-gadget-gud b/Documentation/ABI/testing/configfs-usb-gadget-gud
new file mode 100644
index 000000000..bbf32d494
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-gud
@@ -0,0 +1,13 @@
+What:		/config/usb-gadget/gadget/functions/gud.name
+Date:		Dec 2020
+KernelVersion:	5.10
+Description:
+		The attributes:
+
+		drm_dev - DRM device number
+		backlight_dev - Backlight device name (optional)
+				The backlight brightness scale should be
+				perceptual not linear.
+		compression - 8-bit mask of compressions to include.
+		formats - Byte string of GUD_PIXEL_FORMAT_* values to include.
+		connectors - 32-bit mask of connectors to include.
diff --git a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml
index 3c9b4054e..6915884d5 100644
--- a/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml
+++ b/Documentation/devicetree/bindings/leds/backlight/lm3630a-backlight.yaml
@@ -33,6 +33,19 @@ properties:
     description: GPIO to use to enable/disable the backlight (HWEN pin).
     maxItems: 1
 
+  boost_use_1mhz:
+    description: |
+      boolean, if present, change the boost converter switching frequency from
+      the default 500 kHz to 1 MHz
+    type: boolean
+
+  boost_frequency_shift:
+    description: |
+      boolean, if present, change boost converter switching frequency fromm 500
+      kHz to 560 kHz or from 1 Mhz to 1.12 Mhz, depending on the boost
+      converter switching frequency
+    type: boolean
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/regulator/tps65185.yaml b/Documentation/devicetree/bindings/regulator/tps65185.yaml
new file mode 100644
index 000000000..3f53b14b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/tps65185.yaml
@@ -0,0 +1,228 @@
+# make dt_binding_check DT_SCHEMA_FILES=tps65185.yaml
+
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/regulator/tps65185.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TPS65185x PMIC for E-Paper displays
+
+maintainers:
+  - ??? <???@???.??>
+
+description: |
+  The TPS65185x is a power management IC (PMIC) for EPD displays, providing
+  positive and negative driving rails for EPD displays, as well as a
+  bias voltage (VCOM) line and a separate 3.3 V output. The IC is controlled
+  via I2C.
+
+allOf:
+  - $ref: "regulator.yaml#"
+
+properties:
+  compatible:
+    const: ti,tps65185
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  '#io-channel-cells':
+    const: 1
+
+  powerup-gpios:
+    description: GPIO to control the vdrive supply
+    maxItems: 1
+
+  pwr_good-gpios:
+    description: GPIO indicating vdrive supply state
+    maxItems: 1
+
+  vcom_ctrl-gpios:
+    description: GPIO to control VCOM voltage output
+    maxItems: 1
+
+  vin-supply:
+    description: Input supply phandle for vin input
+
+  vin3p3-supply:
+    description: Input supply phandle for 3.3 Volt input
+
+  wakeup-gpios:
+    description: GPIO to wake up the device from sleep
+    maxItems: 1
+
+  ti,up-sequence:
+    description: Controls the activation order of the four rails
+      grouped in the vdrive regulator (VDDH, VPOS, VEE, and VNEG)
+      via the UPSEQ0 register.
+      TODO how to document the four numbers and the possible 4 values?
+      nr 1 VNEG 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4
+      nr 2 VEE 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4
+      nr 3 VPOS 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4
+      nr 4 VDDH 0 -> strobe1, 1 -> strobe2, 2 -> strobe3, 3 -> strobe 4
+    $ref: "/schemas/types.yaml#/definitions/uint32-array"
+    maxItems: 4
+
+  ti,up-delay-ms:
+    description: vdrive activation delays for the four phases (strobes).
+      possible delays are 3, 6, 9, 12 [ms]
+    maxItems: 4
+
+  ti,down-sequence:
+    description: TODO same as with up sequence
+    $ref: "/schemas/types.yaml#/definitions/uint32-array"
+    maxItems: 4
+
+  ti,down-delay-ms:
+    description: TODO same as with up delays
+    maxItems: 4
+
+  regulators:
+    type: object
+    description: List of regulators and its properties
+
+    properties:
+      "v3p3":
+        type: object
+        $ref: regulator.yaml#
+        description:
+          3.3 V output regulator
+          TODO How do we include the requirement for the PineNote to always
+          turn this on? Also, I think the driver at some locations assumes
+          this regulator stays on.
+
+        properties:
+          regulator-name: true
+          regulator-always-on: true
+          regulator-boot-on: true
+          regulator-min-microvolt: true
+          regulator-max-microvolt: true
+
+        required:
+          - regulator-name
+          - regulator-min-microvolt
+          - regulator-max-microvolt
+
+        additionalProperties: false
+
+      "vcom":
+        type: object
+        $ref: regulator.yaml#
+        description:
+          VCOM voltage (back-plane bias for the epd panel) output regulator,
+          -5.11 V to 0 V. (The minus sign is dropped here to conform to the
+          regulator framework).
+          TODO Make sure to note that by default the vcom value should be read
+          by the driver from the internal memory, but can also be specified
+          here.
+
+        properties:
+          regulator-name: true
+          regulator-always-on: true
+          regulator-boot-on: true
+
+        required:
+          - regulator-name
+
+        additionalProperties: false
+
+      "vdrive":
+        type: object
+        $ref: regulator.yaml#
+        description:
+          Controls the power rails (VDDH, VPOS, VEE, and VNEG).
+
+        properties:
+          regulator-name: true
+          regulator-always-on: true
+          # TODO: this should probably be false. It works, but should not be
+          # the default as constantly driving vdrive can damage the epd
+          # display
+          regulator-boot-on: true
+          # TODO: This is a virtual regulator in the sense that it groups four
+          # different voltages whose voltages we do not have (direct) control
+          # over. Are these values required?
+          regulator-min-microvolt: true
+          regulator-max-microvolt: true
+
+        required:
+          - regulator-name
+          - regulator-min-microvolt
+          - regulator-max-microvolt
+
+        additionalProperties: false
+
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - powerup-gpios
+  - pwr_good-gpios
+  - vcom_ctrl-gpios
+  - vin-supply
+  - vin3p3-supply
+  - wakeup-gpios
+  - ti,up-sequence
+  - ti,up-delay-ms
+  - ti,down-sequence
+  - ti,down-delay-ms
+  - regulators
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      ebc_pmic: pmic@68 {
+        compatible = "ti,tps65185";
+        reg = <0x68>;
+        interrupt-parent = <&gpio3>;
+        interrupts = <6 IRQ_TYPE_LEVEL_LOW>;
+        #io-channel-cells = <1>;
+        pinctrl-0 = <&ebc_pmic_pins>;
+        pinctrl-names = "default";
+        powerup-gpios = <&gpio3 8 GPIO_ACTIVE_HIGH>;
+        pwr_good-gpios = <&gpio3 7 GPIO_ACTIVE_HIGH>;
+        vcom_ctrl-gpios = <&gpio4 10 GPIO_ACTIVE_HIGH>;
+        vin-supply = <&vcc_bat>;
+        vin3p3-supply = <&vcc_3v3>;
+        wakeup-gpios = <&gpio3 5 GPIO_ACTIVE_HIGH>;
+        ti,up-sequence = <1>, <0>, <2>, <3>;
+        ti,up-delay-ms = <3>, <3>, <3>, <3>;
+        ti,down-sequence = <2>, <3>, <1>, <0>;
+        ti,down-delay-ms = <3>, <6>, <6>, <6>;
+
+        regulators {
+          v3p3: v3p3 {
+            regulator-name = "v3p3";
+            regulator-always-on;
+            regulator-min-microvolt = <3300000>;
+            regulator-max-microvolt = <3300000>;
+          };
+
+          vcom: vcom {
+            regulator-name = "vcom";
+            /* voltage range is board-specific */
+            /* regulator-min-microvolt = <930000>; */
+            /* regulator-max-microvolt = <930000>; */
+          };
+
+          vdrive: vdrive {
+            regulator-name = "vdrive";
+            regulator-min-microvolt = <15000000>;
+            regulator-max-microvolt = <15000000>;
+          };
+        };
+      };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index e08767323..ef133e67e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7863,7 +7863,9 @@ M:	Ruben Wauters <rubenru09@aol.com>
 S:	Maintained
 W:	https://github.com/notro/gud/wiki
 T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/ABI/testing/configfs-usb-gadget-gud
 F:	drivers/gpu/drm/gud/
+F:	drivers/usb/gadget/function/f_gud.c
 F:	include/drm/gud.h
 
 DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS
diff --git a/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts b/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts
index 6bbc4c675..92ef19676 100644
--- a/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3566-pinenote-v1.2.dts
@@ -16,3 +16,39 @@ &pmu_io_domains {
 &spk_amp {
 	VCC-supply = <&vcc_bat>;
 };
+
+&usb2phy0_otg {
+	port {
+		usb2phy0_typec_hs: endpoint {
+			remote-endpoint = <&usbc_hs>;
+		};
+	};
+};
+
+&wusb3801 {
+	status = "okay";
+
+	connector {
+		compatible = "usb-c-connector";
+		label = "USB-C";
+		vbus-supply = <&otg_switch>;
+		power-role = "dual";
+		try-power-role = "sink";
+		data-role = "dual";
+		typec-power-opmode = "default";
+		pd-disable;
+
+		ports {
+			#address-cells = <0x01>;
+			#size-cells = <0x00>;
+
+			port@0 {
+				reg = <0x00>;
+
+				usbc_hs: endpoint {
+					remote-endpoint = <&usb2phy0_typec_hs>;
+				};
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi
index 5c6f8cc40..bc8cb5945 100644
--- a/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3566-pinenote.dtsi
@@ -5,6 +5,7 @@
 #include <dt-bindings/input/input.h>
 #include <dt-bindings/leds/common.h>
 #include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/suspend/rockchip-rk3568.h>
 
 #include "rk3566.dtsi"
 
@@ -40,6 +41,51 @@ spk_amp: audio-amplifier {
 		sound-name-prefix = "Speaker Amp";
 	};
 
+	battery_cell: battery-cell {
+		compatible = "simple-battery";
+		charge-full-design-microamp-hours = <4000000>;
+		charge-term-current-microamp = <300000>;
+		constant-charge-current-max-microamp = <2000000>;
+		constant-charge-voltage-max-microvolt = <4200000>;
+		factory-internal-resistance-micro-ohms = <96000>;
+		voltage-max-design-microvolt = <4200000>;
+		voltage-min-design-microvolt = <3500000>;
+
+		ocv-capacity-celsius = <20>;
+		ocv-capacity-table-0 = <4168000 100>,
+				       <4109000 95>, <4066000 90>, <4023000 85>, <3985000 80>,
+				       <3954000 75>, <3924000 70>, <3897000 65>, <3866000 60>,
+				       <3826000 55>, <3804000 50>, <3789000 45>, <3777000 40>,
+				       <3770000 35>, <3763000 30>, <3750000 25>, <3732000 20>,
+				       <3710000 15>, <3680000 10>, <3670000  5>, <3500000  0>;
+	};
+
+	bt_sco_codec: bt-sco-codec {
+		compatible = "linux,bt-sco";
+		#sound-dai-cells = <1>;
+	};
+
+//	bt-sound {
+//		compatible = "simple-audio-card";
+//		#address-cells = <1>;
+//		#size-cells = <0>;
+//		simple-audio-card,name = "PineNote Bluetooth";
+//
+//		simple-audio-card,dai-link@0 {
+//			format = "i2s";
+//			frame-master = <&bt_link0_cpu>;
+//			bitclock-master = <&bt_link0_cpu>;
+//
+//			bt_link0_cpu: cpu {
+//				sound-dai = <&i2s2_2ch>;
+//			};
+//
+//			bt_link0_codec: codec {
+//				sound-dai = <&bt_sco_codec 0>;
+//			};
+//		};
+//	};
+
 	dmic_codec: dmic-codec {
 		compatible = "dmic-codec";
 		num-channels = <6>;
@@ -71,9 +117,43 @@ led-0 {
 			color = <LED_COLOR_ID_WHITE>;
 			function = LED_FUNCTION_CHARGING;
 			gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
+			linux,default-trigger = "rk817-battery-charging";
+			retain-state-suspended;
 		};
 	};
 
+	panel {
+		compatible = "eink,ed103tc2";
+
+		port {
+			panel_in_ebc: endpoint {
+				remote-endpoint = <&ebc_out_panel>;
+			};
+		};
+	};
+
+	rockchip-suspend {
+		compatible = "rockchip,pm-rk3568";
+		status = "okay";
+		rockchip,sleep-debug-en = <0>;
+		rockchip,sleep-mode-config = <
+			(0
+			| RKPM_SLP_CENTER_OFF
+			| RKPM_SLP_ARMOFF_LOGOFF
+			| RKPM_SLP_PMIC_LP
+			| RKPM_SLP_HW_PLLS_OFF
+			| RKPM_SLP_PMUALIVE_32K
+			| RKPM_SLP_OSC_DIS
+			| RKPM_SLP_32K_PVTM
+			)
+		>;
+		rockchip,wakeup-config = <
+			(0
+			| RKPM_GPIO_WKUP_EN
+			)
+		>;
+	};
+
 	sdio_pwrseq: sdio-pwrseq {
 		compatible = "mmc-pwrseq-simple";
 		clocks = <&rk817 1>;
@@ -131,6 +211,34 @@ link1_codec: codec {
 		};
 	};
 
+	spi-gpio {
+		compatible = "spi-gpio";
+		#address-cells = <1>;
+		#size-cells = <0>;
+		pinctrl-0 = <&ble_spi_pins>;
+		pinctrl-names = "default";
+		mosi-gpios = <&gpio3 RK_PC1 GPIO_ACTIVE_HIGH>;
+		sck-gpios = <&gpio3 RK_PC3 GPIO_ACTIVE_HIGH>;
+		num-chipselects = <0>;
+		status = "okay";
+
+		/* TODO: write binding */
+		bluetooth@0 {
+			compatible = "pine64,pinenote-ws8100-pen";
+			reg = <0>;
+			cs-gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_HIGH>;
+			host_state-gpios = <&gpio1 RK_PD6 GPIO_ACTIVE_HIGH>;
+			interrupt-parent = <&gpio0>;
+			interrupts = <RK_PB5 IRQ_TYPE_EDGE_RISING>;
+			pinctrl-0 = <&ble_host_state_h>, <&ble_int_l>, <&ble_rst_h>;
+			pinctrl-names = "default";
+			reset-gpios = <&gpio3 RK_PC0 GPIO_ACTIVE_HIGH>;
+			spi-3wire;
+			spi-max-frequency = <10000>;
+			spi-cs-inactive-delay-ns = <7000000>;
+		};
+	};
+
 	vbat_4g: regulator-vbat-4g {
 		compatible = "regulator-fixed";
 		regulator-name = "vbat_4g";
@@ -199,6 +307,27 @@ vdda_0v9: regulator-vdda-0v9 {
 	};
 };
 
+&cru {
+		// assigned-clocks = <&pmucru CLK_RTC_32K>, <&cru PLL_GPLL>, <&pmucru PLL_PPLL>, <&cru ACLK_RGA_PRE>, <&cru HCLK_RGA_PRE>;
+		assigned-clocks = <&pmucru CLK_RTC_32K>,
+  			<&cru ACLK_TOP_HIGH>, <&cru ACLK_TOP_LOW>,
+			<&cru PLL_GPLL>, <&pmucru PLL_PPLL>,
+ 			<&cru ACLK_RGA_PRE>, <&cru CPLL_250M>,
+			<&cru HCLK_RGA_PRE>, <&cru HCLK_EBC>,
+		    <&cru HCLK_JENC>, <&cru CPLL_333M>;
+		// assigned-clock-rates = <32768>, <1200000000>, <200000000>, <300000000>, <300000000>;
+		// assigned-clock-rates = <32768>, <1200000000>, <200000000>, <250000000>, <250000000>, <250000000>, <250000000>, <250000000>;
+		assigned-clock-rates = <32768>,
+				<500000000>, <400000000>,
+				 <1200000000>, <200000000>,
+				 <300000000>, <250000000>,
+				 <300000000>, <300000000>,
+				 <300000000>, <33333334>;
+		// stronger artifacting, I suspend instabilities due to too high clock frequencies
+		// assigned-clock-rates = <32768>, <1200000000>, <200000000>, <333400000>, <333400000>, <333400000>, <333400000>, <333400000>;
+		assigned-clock-parents = <&pmucru CLK_RTC32K_FRAC>;
+};
+
 &cpu0 {
 	cpu-supply = <&vdd_cpu>;
 };
@@ -215,6 +344,29 @@ &cpu3 {
 	cpu-supply = <&vdd_cpu>;
 };
 
+&ebc {
+	io-channels = <&ebc_pmic 0>;
+	panel-supply = <&v3p3>;
+	vcom-supply = <&vcom>;
+	vdrive-supply = <&vdrive>;
+	status = "okay";
+
+	port {
+		ebc_out_panel: endpoint {
+			remote-endpoint = <&panel_in_ebc>;
+		};
+	};
+};
+
+&eink {
+	status = "okay";
+};
+
+&gpu {
+	mali-supply = <&vdd_gpu_npu>;
+	status = "okay";
+};
+
 &i2c0 {
 	status = "okay";
 
@@ -260,6 +412,22 @@ rk817: pmic@20 {
 		vcc8-supply = <&vcc_sys>;
 		vcc9-supply = <&dcdc_boost>;
 
+		// used for  kernel 5.17
+		battery {
+			monitored-battery = <&battery_cell>;
+			rockchip,resistor-sense-micro-ohms = <10000>;
+			rockchip,sleep-enter-current-microamp = <150000>;
+			rockchip,sleep-filter-current-microamp = <100000>;
+		};
+
+		// newer kernels (at least >= 6.2)
+		rk817_charger: charger {
+		    monitored-battery = <&battery_cell>;
+		    rockchip,resistor-sense-micro-ohms = <10000>;
+		        rockchip,sleep-enter-current-microamp = <150000>;
+		    rockchip,sleep-filter-current-microamp = <100000>;
+		};
+
 		regulators {
 			vdd_logic: DCDC_REG1 {
 				regulator-name = "vdd_logic";
@@ -277,6 +445,7 @@ regulator-state-mem {
 
 			vdd_gpu_npu: DCDC_REG2 {
 				regulator-name = "vdd_gpu_npu";
+				regulator-always-on;
 				regulator-min-microvolt = <500000>;
 				regulator-max-microvolt = <1350000>;
 				regulator-ramp-delay = <6001>;
@@ -453,17 +622,43 @@ led-controller@36 {
 		pinctrl-names = "default";
 		#address-cells = <1>;
 		#size-cells = <0>;
+		/* ti,boost-frequency-hz = <500000>; */
+		/* ti,boost-frequency-hz = <560000>; */
+		/* ti,boost-frequency-hz = <1000000>; */
+		ti,boost-frequency-hz = <1120000>;
+
+		/* ti,boost-ovp-microvolt = <16000000>; */
+		/* ti,boost-ovp-microvolt = <24000000>; */
+		ti,boost-ovp-microvolt = <32000000>;
+		/* ti,boost-ovp-microvolt = <40000000>; */
+
+		ti,boost-ocp-microamp = <600000>;
+		/* ti,boost-ocp-microamp = <800000>; */
+		/* ti,boost-ocp-microamp = <1000000>; */
+		/* ti,boost-ocp-microamp = <1200000>; */
 
 		led@0 {
 			reg = <0>;
 			label = "backlight_cool";
 			default-brightness = <0>;
+			max-brightness = <255>;
+			// anything between 5000 (5 mA) and 28250 in 750 muA steps
+			/* ti,current-microamp = <5750>; */
+			ti,current-microamp = <12500>;
+			/* ti,current-microamp = <19250>; */
+			/* ti,current-microamp = <27500>; */
 		};
 
 		led@1 {
 			reg = <1>;
 			label = "backlight_warm";
 			default-brightness = <0>;
+			max-brightness = <255>;
+			// anything between 5000 (5 mA) and 28250 in 750 muA steps
+			/* ti,current-microamp = <5750>; */
+			ti,current-microamp = <12500>;
+			/* ti,current-microamp = <19250>; */
+			/* ti,current-microamp = <27500>; */
 		};
 	};
 
@@ -474,6 +669,7 @@ wusb3801: tcpc@60 {
 		interrupts = <RK_PA4 IRQ_TYPE_LEVEL_LOW>;
 		pinctrl-0 = <&tcpc_int_l>;
 		pinctrl-names = "default";
+		status = "okay";
 
 		connector {
 			compatible = "usb-c-connector";
@@ -499,6 +695,56 @@ typec_hs_usb2phy0: endpoint {
 			};
 		};
 	};
+
+	/* TODO: write binding */
+	ebc_pmic: pmic@68 {
+		compatible = "ti,tps65185";
+		reg = <0x68>;
+		interrupt-parent = <&gpio3>;
+		interrupts = <RK_PA6 IRQ_TYPE_LEVEL_LOW>;
+		#io-channel-cells = <1>;
+		pinctrl-0 = <&ebc_pmic_pins>;
+		pinctrl-names = "default";
+		powerup-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>;
+		pwr_good-gpios = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>;
+		vcom_ctrl-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_HIGH>;
+		vin-supply = <&vcc_bat>;
+		vin3p3-supply = <&vcc_3v3>;
+		wakeup-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>;
+		ti,up-sequence = <1>, <0>, <2>, <3>;
+		ti,up-delay-ms = <3>, <3>, <3>, <3>;
+		ti,down-sequence = <2>, <3>, <1>, <0>;
+		ti,down-delay-ms = <3>, <6>, <6>, <6>;
+
+		regulators {
+			v3p3: v3p3 {
+				regulator-name = "v3p3";
+				/* At least pinenote v1.1 and v1.2: Information provided by
+				 * smaeul (Samuel Holland): Allowing this regulator to turn off
+				 * can lead to interrupt storms
+				 * While writing a driver for the TPS65185, I ran into an
+				 * interrupt storm. The reason is that EBC_PMIC_INT is pulled
+				 * up to VCC_EINK1 through R2003. But this is the output of the
+				 * V3P3 switch. So that means the IRQ cannot be enabled while
+				 * V3P3 is turned off.
+				*/
+				regulator-always-on;
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+			};
+
+			vcom: vcom {
+				regulator-name = "vcom";
+				/* voltage range is board-specific */
+			};
+
+			vdrive: vdrive {
+				regulator-name = "vdrive";
+				regulator-min-microvolt = <15000000>;
+				regulator-max-microvolt = <15000000>;
+			};
+		};
+	};
 };
 
 &i2c5 {
@@ -514,6 +760,31 @@ accelerometer@18 {
 		st,drdy-int-pin = <1>;
 		vdd-supply = <&vcc_3v3>;
 		vddio-supply = <&vcc_3v3>;
+		// if we use panel-reflection=1 from the ebc driver, then the
+		// accelerometer must be rotated
+ 		mount-matrix = "-1", "0", "0",
+ 					   "0", "1", "0",
+ 					   "0", "0", "1";
+	};
+
+	touchscreen@24 {
+		compatible = "cypress,tt21000";
+		hid-descr-addr = <0x1>;
+		reg = <0x24>;
+		interrupt-parent = <&gpio0>;
+		interrupts = <RK_PA6 IRQ_TYPE_EDGE_FALLING>;
+		pinctrl-0 = <&ts_int_l>, <&ts_rst_l>;
+		pinctrl-names = "default";
+		reset-gpios = <&gpio0 RK_PA5 GPIO_ACTIVE_LOW>;
+		vdd-supply = <&vcc_3v3_pmu>;
+
+        touchscreen-max-pressure = <46>;
+		touchscreen-min-x = <10>;
+		touchscreen-min-y = <5>;
+		touchscreen-size-x = <1863>;
+		touchscreen-size-y = <1399>;
+		touchscreen-x-mm = <1864>;
+		touchscreen-y-mm = <1400>;
 	};
 };
 
@@ -523,6 +794,11 @@ &i2s1_8ch {
 	status = "okay";
 };
 
+&i2s2_2ch {
+	rockchip,trcm-sync-tx-only;
+	status = "okay";
+};
+
 &pdm {
 	pinctrl-0 = <&pdmm0_clk1>, <&pdmm0_sdi1>, <&pdmm0_sdi2>;
 	/* microphones are on channels 1 and 2 */
@@ -549,6 +825,29 @@ backlight_hwen_h: backlight-hwen-h {
 		};
 	};
 
+	ble {
+		ble_host_state_h: ble-host-state-h {
+			rockchip,pins = <1 RK_PD6 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		ble_int_l: ble-int-l {
+			rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_down>;
+		};
+
+		ble_rst_h: ble-rst-h {
+			rockchip,pins = <3 RK_PC0 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+
+		ble_spi_pins: ble-spi-pins {
+			rockchip,pins = /* data */
+					<3 RK_PC1 RK_FUNC_GPIO &pcfg_pull_none>,
+					/* cs */
+					<3 RK_PC2 RK_FUNC_GPIO &pcfg_pull_none>,
+					/* clk */
+					<3 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
 	bt {
 		bt_enable_h: bt-enable-h {
 			rockchip,pins = <0 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
@@ -563,6 +862,21 @@ bt_wake_h: bt-wake-h {
 		};
 	};
 
+	ebc-pmic {
+		ebc_pmic_pins: ebc-pmic-pins {
+			rockchip,pins = /* wakeup */
+					<3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>,
+					/* int */
+					<3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>,
+					/* pwr_good */
+					<3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>,
+					/* pwrup */
+					<3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>,
+					/* vcom_ctrl */
+					<4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
 	led {
 		led_pin: led-pin {
 			rockchip,pins = <3 RK_PC5 RK_FUNC_GPIO &pcfg_pull_none>;
@@ -611,6 +925,16 @@ tcpc_int_l: tcpc-int-l {
 		};
 	};
 
+	touchscreen {
+		ts_int_l: ts-int-l {
+			rockchip,pins = <0 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>;
+		};
+
+		ts_rst_l: ts-rst-l {
+			rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
 	vcc-wl {
 		vcc_wl_pin: vcc-wl-pin {
 			rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
@@ -681,16 +1005,19 @@ &uart1 {
 	pinctrl-names = "default";
 	uart-has-rtscts;
 	status = "okay";
+	rx-threshold = <1>;
+	tx-threshold = <1>;
 
 	bluetooth {
 		compatible = "brcm,bcm43438-bt";
 		clocks = <&rk817 1>;
 		clock-names = "lpo";
-		device-wakeup-gpios = <&gpio0 RK_PC2 GPIO_ACTIVE_HIGH>;
-		host-wakeup-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>;
+        host-wakeup-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>;
+        device-wakeup-gpios = <&gpio0 RK_PC2 GPIO_ACTIVE_HIGH>;
+        shutdown-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>;
+        max-speed = <3000000>;
 		pinctrl-0 = <&bt_enable_h>, <&bt_host_wake_l>, <&bt_wake_h>;
 		pinctrl-names = "default";
-		shutdown-gpios = <&gpio0 RK_PC4 GPIO_ACTIVE_HIGH>;
 		vbat-supply = <&vcc_wl>;
 		vddio-supply = <&vcca_1v8_pmu>;
 	};
@@ -718,3 +1045,7 @@ usb2phy0_typec_hs: endpoint {
 		};
 	};
 };
+
+&rga {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts
index a9021c524..c5a1068ca 100644
--- a/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3566-quartz64-a.dts
@@ -212,6 +212,20 @@ vcc_sys: regulator-vcc-sys {
 		vin-supply = <&vbus>;
 	};
 
+	vcc_sys_ebc: vcc_sys_ebc {
+		compatible = "regulator-fixed";
+		gpio = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+		pinctrl-names = "default";
+		pinctrl-0 = <&vcc_sys_ebc_h>;
+		regulator-always-on;
+		regulator-boot-on;
+		regulator-name = "vcc_sys_ebc";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		vin-supply = <&vcc_sys>;
+	};
+
 	/* sourced from vcc_sys, sdio module operates internally at 3.3v */
 	vcc_wl: regulator-vcc-wl {
 		compatible = "regulator-fixed";
@@ -222,6 +236,16 @@ vcc_wl: regulator-vcc-wl {
 		regulator-max-microvolt = <3300000>;
 		vin-supply = <&vcc_sys>;
 	};
+
+	panel {
+		compatible = "eink,ed103tc2";
+
+		port {
+			panel_in_ebc: endpoint {
+				remote-endpoint = <&ebc_out_panel>;
+			};
+		};
+	};
 };
 
 &combphy1 {
@@ -248,6 +272,24 @@ &cpu3 {
 	cpu-supply = <&vdd_cpu>;
 };
 
+&ebc {
+        status = "okay";
+        io-channels = <&ebc_pmic 0>;
+        panel-supply = <&v3p3>;
+        vcom-supply = <&vcom>;
+        vdrive-supply = <&vdrive>;
+
+        port {
+			ebc_out_panel: endpoint {
+				remote-endpoint = <&panel_in_ebc>;
+			};
+        };
+};
+
+&eink {
+        status = "okay";
+};
+
 &cpu_thermal {
 	trips {
 		cpu_hot: cpu_hot {
@@ -539,6 +581,64 @@ regulator-state-mem {
 	};
 };
 
+&i2c1{
+	status = "okay";
+	ebc_pmic: pmic@68 {
+			status = "okay";
+			compatible = "ti,tps65185";
+			// check
+			reg = <0x68>;
+			// changed 3->4
+			interrupt-parent = <&gpio4>;
+			// changed Q64-A (EBC_PMIC_INT)
+			//interrupts = <RK_PA6 IRQ_TYPE_LEVEL_LOW>;
+			interrupts = <RK_PB0 IRQ_TYPE_LEVEL_LOW>;
+			#io-channel-cells = <1>;
+			pinctrl-0 = <&ebc_pmic_pins>;
+			pinctrl-names = "default";
+			// [EBC_PMIC_ON] changed
+			/* powerup-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>; */
+			powerup-gpios = <&gpio4 RK_PC4 GPIO_ACTIVE_HIGH>;
+			// changed q64-A
+			/* pwr_good-gpios = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>; */
+			pwr_good-gpios = <&gpio4 RK_PB1 GPIO_ACTIVE_HIGH>;
+			// ok q64-A
+			vcom_ctrl-gpios = <&gpio4 RK_PB2 GPIO_ACTIVE_HIGH>;
+			// changed
+			vin-supply = <&vcc_sys_ebc>;
+			vin3p3-supply = <&vcc_sys_ebc>;
+			// changed q64-a
+			// wakeup-gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>;
+			wakeup-gpios = <&gpio4 RK_PC3 GPIO_ACTIVE_HIGH>;
+			ti,up-sequence = <1>, <0>, <2>, <3>;
+			ti,up-delay-ms = <3>, <3>, <3>, <3>;
+			ti,down-sequence = <2>, <3>, <1>, <0>;
+			ti,down-delay-ms = <3>, <6>, <6>, <6>;
+
+			regulators {
+					v3p3: v3p3 {
+							regulator-name = "v3p3";
+							regulator-always-on;
+							regulator-min-microvolt = <3300000>;
+							regulator-max-microvolt = <3300000>;
+					};
+
+					vcom: vcom {
+							regulator-name = "vcom";
+							/* voltage range is board-specific */
+							regulator-min-microvolt = <1170000>;
+                            regulator-max-microvolt = <1170000>;
+					};
+
+					vdrive: vdrive {
+							regulator-name = "vdrive";
+							regulator-min-microvolt = <15000000>;
+							regulator-max-microvolt = <15000000>;
+					};
+			};
+	};
+};
+
 /* i2c3 is exposed on con40
  * pin 3 - i2c3_sda_m0, pullup to vcc_3v3
  * pin 5 - i2c3_scl_m0, pullup to vcc_3v3
@@ -640,6 +740,31 @@ vcc_sd_h: vcc-sd-h {
 			rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
 		};
 	};
+
+	vcc_sys_ebc {
+		vcc_sys_ebc_h: vcc-sys-ebc-h {
+			rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
+
+	ebc-pmic {
+		ebc_pmic_pins: ebc-pmic-pins {
+			rockchip,pins = /* wakeup  EBC_PMIC_WAKEUP*/
+							/* <3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>, */
+							<4 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>,
+							/* int EBC_PMIC_INT */
+							/* <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>, */
+							<4 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>,
+							/* pwr_good EBC_PMIC_PG*/
+							/* <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_none>, */
+							<4 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>,
+							/* pwrup  EBC_PMIC_ON*/
+							/* <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>, */
+							<4 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>,
+							/* vcom_ctrl  EBC_VCOM */
+							<4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_none>;
+		};
+	};
 };
 
 &pmu_io_domains {
diff --git a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
index a2c4957a5..86e70bb25 100644
--- a/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x-base.dtsi
@@ -127,6 +127,61 @@ l3_cache: l3-cache {
 		cache-sets = <512>;
 	};
 
+	cpu0_opp_table: opp-table-0 {
+		compatible = "operating-points-v2";
+		opp-shared;
+
+		opp-408000000 {
+			opp-hz = /bits/ 64 <408000000>;
+			opp-microvolt = <900000 900000 1150000>;
+			clock-latency-ns = <40000>;
+		};
+
+		opp-600000000 {
+			opp-hz = /bits/ 64 <600000000>;
+			opp-microvolt = <900000 900000 1150000>;
+		};
+
+		opp-816000000 {
+			opp-hz = /bits/ 64 <816000000>;
+			opp-microvolt = <900000 900000 1150000>;
+			opp-suspend;
+		};
+
+		opp-1104000000 {
+			opp-hz = /bits/ 64 <1104000000>;
+			opp-microvolt = <900000 900000 1150000>;
+		};
+
+		opp-1416000000 {
+			opp-hz = /bits/ 64 <1416000000>;
+			opp-microvolt = <900000 900000 1150000>;
+		};
+
+		opp-1608000000 {
+			opp-hz = /bits/ 64 <1608000000>;
+			opp-microvolt = <975000 975000 1150000>;
+		};
+
+		opp-1800000000 {
+			opp-hz = /bits/ 64 <1800000000>;
+			opp-microvolt = <1050000 1050000 1150000>;
+		};
+	};
+
+	/* reserved-memory { */
+	/*      #address-cells = <0x00000002>; */
+	 /* #size-cells = <0x00000002>; */
+	/*      ranges; */
+
+	 /* ebcframebuffer: buffer@10900000 { */
+	/*              compatible = "shared-dma-pool"; */
+	/*              no-map; */
+	/*              reg = <0x00000000 0x10900000 0x00000000 0x02000000>; */
+	 /* }; */
+	/* }; */
+
+
 	display_subsystem: display-subsystem {
 		compatible = "rockchip,display-subsystem";
 		ports = <&vop_out>;
@@ -578,6 +633,29 @@ vpu: video-codec@fdea0400 {
 		power-domains = <&power RK3568_PD_VPU>;
 	};
 
+	ebc: ebc@fdec0000 {
+		compatible = "rockchip,rk3568-ebc";
+		reg = <0x0 0xfdec0000 0x0 0x5000>;
+		interrupts = <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&cru HCLK_EBC>, <&cru DCLK_EBC>, <&cru CPLL_333M>;
+		clock-names = "hclk", "dclk", "cpll_333m";
+		pinctrl-0 = <&ebc_pins>;
+		pinctrl-names = "default";
+		power-domains = <&power RK3568_PD_RGA>;
+		resets = <&cru SRST_H_EBC>, <&cru SRST_D_EBC>;
+		reset-names = "hclk", "dclk";
+		status = "disabled";
+	};
+
+	eink: eink@fdf00000 {
+	    compatible = "rockchip,rk3568-eink-tcon";
+	    reg = <0x0 0xfdf00000 0x0 0x74>;
+	    clocks = <&cru PCLK_EINK>, <&cru HCLK_EINK>;
+	    clock-names = "pclk", "hclk";
+	    interrupts = <GIC_SPI 178 IRQ_TYPE_LEVEL_HIGH>;
+	    status = "disabled";
+	};
+
 	vdpu_mmu: iommu@fdea0800 {
 		compatible = "rockchip,rk3568-iommu";
 		reg = <0x0 0xfdea0800 0x0 0x40>;
@@ -597,6 +675,7 @@ rga: rga@fdeb0000 {
 		resets = <&cru SRST_RGA_CORE>, <&cru SRST_A_RGA>, <&cru SRST_H_RGA>;
 		reset-names = "core", "axi", "ahb";
 		power-domains = <&power RK3568_PD_RGA>;
+		status = "disabled";
 	};
 
 	vepu: video-codec@fdee0000 {
@@ -1381,6 +1460,7 @@ uart1: serial@fe650000 {
 		clocks = <&cru SCLK_UART1>, <&cru PCLK_UART1>;
 		clock-names = "baudclk", "apb_pclk";
 		dmas = <&dmac0 2>, <&dmac0 3>;
+		dma-names = "tx", "rx";
 		pinctrl-0 = <&uart1m0_xfer>;
 		pinctrl-names = "default";
 		reg-io-width = <4>;
diff --git a/arch/arm64/configs/pinenote_defconfig b/arch/arm64/configs/pinenote_defconfig
new file mode 100644
index 000000000..7dc6c656b
--- /dev/null
+++ b/arch/arm64/configs/pinenote_defconfig
@@ -0,0 +1,1610 @@
+CONFIG_DEFAULT_HOSTNAME="pinenote"
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ_IDLE=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_JIT=y
+CONFIG_BPF_JIT_ALWAYS_ON=y
+CONFIG_IRQ_TIME_ACCOUNTING=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_LOG_BUF_SHIFT=18
+CONFIG_LOG_CPU_MAX_BUF_SHIFT=0
+CONFIG_CGROUPS=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CGROUP_SCHED=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_RT_GROUP_SCHED=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_RDMA=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_HUGETLB=y
+CONFIG_CPUSETS=y
+# CONFIG_PROC_PID_CPUSET is not set
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_CGROUP_MISC=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_CHECKPOINT_RESTORE=y
+CONFIG_BLK_DEV_INITRD=y
+# CONFIG_RD_BZIP2 is not set
+# CONFIG_RD_LZMA is not set
+# CONFIG_RD_XZ is not set
+# CONFIG_RD_LZO is not set
+# CONFIG_RD_LZ4 is not set
+CONFIG_EXPERT=y
+# CONFIG_UID16 is not set
+# CONFIG_KEXEC=y
+# CONFIG_KEXEC_CORE=y
+# CONFIG_CRASH_CORE=y
+#
+# CONFIG_SYSFS_SYSCALL is not set
+CONFIG_KALLSYMS_ALL=y
+CONFIG_USERFAULTFD=y
+CONFIG_PERF_EVENTS=y
+# CONFIG_SLUB_DEBUG is not set
+# CONFIG_COMPAT_BRK is not set
+CONFIG_ARCH_ROCKCHIP=y
+# CONFIG_ARM64_ERRATUM_826319 is not set
+# CONFIG_ARM64_ERRATUM_827319 is not set
+# CONFIG_ARM64_ERRATUM_824069 is not set
+# CONFIG_ARM64_ERRATUM_819472 is not set
+# CONFIG_ARM64_ERRATUM_832075 is not set
+# CONFIG_ARM64_ERRATUM_845719 is not set
+# CONFIG_ARM64_ERRATUM_843419 is not set
+# CONFIG_ARM64_ERRATUM_1418040 is not set
+# CONFIG_ARM64_ERRATUM_1165522 is not set
+# CONFIG_ARM64_ERRATUM_1319367 is not set
+# CONFIG_ARM64_ERRATUM_1286807 is not set
+# CONFIG_ARM64_ERRATUM_1463225 is not set
+# CONFIG_ARM64_ERRATUM_1542419 is not set
+# CONFIG_ARM64_ERRATUM_1508412 is not set
+# CONFIG_ARM64_ERRATUM_2051678 is not set
+# CONFIG_ARM64_ERRATUM_2054223 is not set
+# CONFIG_ARM64_ERRATUM_2067961 is not set
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+# CONFIG_CAVIUM_ERRATUM_30115 is not set
+# CONFIG_CAVIUM_TX2_ERRATUM_219 is not set
+# CONFIG_FUJITSU_ERRATUM_010001 is not set
+# CONFIG_HISILICON_ERRATUM_161600802 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1003 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_1009 is not set
+# CONFIG_QCOM_QDF2400_ERRATUM_0065 is not set
+# CONFIG_QCOM_FALKOR_ERRATUM_E1041 is not set
+# CONFIG_NVIDIA_CARMEL_CNP_ERRATUM is not set
+# CONFIG_SOCIONEXT_SYNQUACER_PREITS is not set
+CONFIG_SCHED_MC=y
+CONFIG_NR_CPUS=4
+CONFIG_HZ_1000=y
+# CONFIG_UNMAP_KERNEL_AT_EL0 is not set
+# CONFIG_ARM64_TAGGED_ADDR_ABI is not set
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_CP15_BARRIER_EMULATION=y
+# CONFIG_ARM64_HW_AFDBM is not set
+# CONFIG_ARM64_PTR_AUTH is not set
+# CONFIG_ARM64_AMU_EXTN is not set
+# CONFIG_ARM64_BTI is not set
+# CONFIG_ARM64_E0PD is not set
+# CONFIG_ARM64_EPAN is not set
+# CONFIG_ARM64_SVE is not set
+CONFIG_JUMP_LABEL=y
+# CONFIG_STACKPROTECTOR is not set
+CONFIG_MODULES=y
+CONFIG_MODULE_FORCE_LOAD=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODULE_FORCE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_BLK_DEV_THROTTLING=y
+# CONFIG_BLK_DEBUG_FS is not set
+# CONFIG_MQ_IOSCHED_DEADLINE is not set
+# CONFIG_MQ_IOSCHED_KYBER is not set
+CONFIG_CMA=y
+CONFIG_CMA_DEBUG=y
+CONFIG_CMA_DEBUGFS=y
+CONFIG_CMA_SYSFS=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_LOOP_MIN_COUNT=0
+CONFIG_SCSI=m
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_SCAN_ASYNC=y
+# CONFIG_SCSI_LOWLEVEL is not set
+CONFIG_MD=y
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_THIN_PROVISIONING=m
+# CONFIG_LEGACY_PTYS is not set
+# CONFIG_LDISC_AUTOLOAD is not set
+CONFIG_FB=y
+CONFIG_FRAMEBUFFER_CONSOLE=y
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER=y
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_RK808=y
+# CONFIG_VIRTIO_MENU is not set
+# CONFIG_VHOST_MENU is not set
+CONFIG_STAGING=y
+CONFIG_STAGING_MEDIA=y
+CONFIG_VIDEO_ROCKCHIP_VDEC=m
+# CONFIG_SURFACE_PLATFORMS is not set
+# CONFIG_FSL_ERRATUM_A008585 is not set
+# CONFIG_HISILICON_ERRATUM_161010101 is not set
+# CONFIG_ARM64_ERRATUM_858921 is not set
+CONFIG_EXTCON=y
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_XFS_FS=y
+# CONFIG_XFS_SUPPORT_V4 is not set
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+# CONFIG_DNOTIFY is not set
+CONFIG_QUOTA=y
+# CONFIG_PRINT_QUOTA_WARNING is not set
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_OVERLAY_FS=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_UDF_FS=m
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_UTF8=y
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_TMPFS_INODE64=y
+CONFIG_HUGETLBFS=y
+CONFIG_CONFIGFS_FS=y
+CONFIG_EFIVAR_FS=y
+# CONFIG_MISC_FILESYSTEMS is not set
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_ISO8859_1=y
+CONFIG_NLS_UTF8=y
+CONFIG_UNICODE=y
+CONFIG_KEYS=y
+CONFIG_DMA_CMA=y
+CONFIG_PRINTK_TIME=y
+# CONFIG_DEBUG_MISC is not set
+CONFIG_DEBUG_INFO_DWARF5=y
+CONFIG_DEBUG_INFO_REDUCED=y
+CONFIG_DEBUG_INFO_SPLIT=y
+CONFIG_MAGIC_SYSRQ=y
+# CONFIG_MAGIC_SYSRQ_SERIAL is not set
+CONFIG_DEBUG_FS=y
+CONFIG_PANIC_ON_OOPS=y
+CONFIG_PANIC_TIMEOUT=1
+CONFIG_SOFTLOCKUP_DETECTOR=y
+# CONFIG_DETECT_HUNG_TASK is not set
+CONFIG_WQ_WATCHDOG=y
+CONFIG_SCHEDSTATS=y
+CONFIG_CPU_HOTPLUG_STATE_CONTROL=y
+CONFIG_ENABLE_DEFAULT_TRACERS=y
+# CONFIG_UPROBE_EVENTS is not set
+# CONFIG_STRICT_DEVMEM is not set
+# CONFIG_RUNTIME_TESTING_MENU is not set
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
+CONFIG_DM_CRYPT=m
+CONFIG_KEXEC=y
+CONFIG_KEXEC_FILE=y
+CONFIG_KEXEC_CORE=y
+CONFIG_CRASH_CORE=y
+CONFIG_CRASH_DUMP=y
+CONFIG_PROC_VMCORE=y
+CONFIG_DYNAMIC_DEBUG=y
+CONFIG_PROC_KCORE=y
+CONFIG_RELOCATABLE=y
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_FUNCTION_GRAPH_RETVAL=y
+CONFIG_OF_OVERLAY=y
+
+##
+## file: arch/arm64/crypto/Kconfig
+##
+CONFIG_CRYPTO_GHASH_ARM64_CE=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_POLY1305_NEON=m
+CONFIG_CRYPTO_SHA1_ARM64_CE=m
+CONFIG_CRYPTO_SHA256_ARM64=m
+CONFIG_CRYPTO_SHA2_ARM64_CE=m
+CONFIG_CRYPTO_SHA512_ARM64=m
+CONFIG_CRYPTO_SHA512_ARM64_CE=m
+CONFIG_CRYPTO_SHA3_ARM64=m
+CONFIG_CRYPTO_POLYVAL_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64=m
+CONFIG_CRYPTO_AES_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64_CE_BLK=m
+CONFIG_CRYPTO_AES_ARM64_NEON_BLK=m
+CONFIG_CRYPTO_CHACHA20_NEON=m
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=m
+CONFIG_CRYPTO_CRCT10DIF_ARM64_CE=m
+
+##
+## file: block/Kconfig
+##
+CONFIG_BLOCK=y
+
+##
+## file: crypto/Kconfig
+##
+CONFIG_CRYPTO=y
+CONFIG_CRYPTO_MANAGER=y
+CONFIG_CRYPTO_NULL=m
+CONFIG_CRYPTO_PCRYPT=m
+CONFIG_CRYPTO_AUTHENC=m
+CONFIG_CRYPTO_RSA=m
+CONFIG_CRYPTO_DH=m
+CONFIG_CRYPTO_ECDH=m
+CONFIG_CRYPTO_ECDSA=m
+CONFIG_CRYPTO_ECRDSA=m
+CONFIG_CRYPTO_CURVE25519=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_AES_TI=m
+CONFIG_CRYPTO_BLOWFISH=m
+CONFIG_CRYPTO_DES=m
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_CHACHA20=m
+CONFIG_CRYPTO_CBC=m
+CONFIG_CRYPTO_CTR=m
+CONFIG_CRYPTO_CTS=m
+CONFIG_CRYPTO_ECB=m
+CONFIG_CRYPTO_XTS=m
+CONFIG_CRYPTO_AEGIS128=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_CCM=m
+CONFIG_CRYPTO_GCM=m
+CONFIG_CRYPTO_SEQIV=m
+CONFIG_CRYPTO_ECHAINIV=m
+CONFIG_CRYPTO_ESSIV=m
+CONFIG_CRYPTO_BLAKE2B=m
+CONFIG_CRYPTO_CMAC=m
+CONFIG_CRYPTO_GHASH=m
+CONFIG_CRYPTO_HMAC=m
+CONFIG_CRYPTO_MD5=m
+CONFIG_CRYPTO_MICHAEL_MIC=m
+CONFIG_CRYPTO_POLY1305=m
+CONFIG_CRYPTO_SHA1=m
+CONFIG_CRYPTO_SHA256=y
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_SHA3=m
+CONFIG_CRYPTO_STREEBOG=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_XXHASH=m
+CONFIG_CRYPTO_CRC32C=m
+CONFIG_CRYPTO_CRC32=m
+CONFIG_CRYPTO_CRCT10DIF=m
+CONFIG_CRYPTO_CRC64_ROCKSOFT=m
+CONFIG_CRYPTO_DEFLATE=m
+CONFIG_CRYPTO_LZO=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_ZSTD=m
+CONFIG_CRYPTO_ANSI_CPRNG=m
+CONFIG_CRYPTO_DRBG_MENU=m
+CONFIG_CRYPTO_DRBG_HASH=y
+CONFIG_CRYPTO_DRBG_CTR=y
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+# CONFIG_CRYPTO_USER_API_ENABLE_OBSOLETE is not set
+
+##
+## file: drivers/ata/Kconfig
+##
+CONFIG_ATA=m
+CONFIG_SATA_AHCI=m
+CONFIG_SATA_AHCI_PLATFORM=m
+CONFIG_AHCI_DWC=m
+
+##
+## file: drivers/base/Kconfig
+##
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+# CONFIG_ALLOW_DEV_COREDUMP is not set
+
+##
+## file: drivers/bluetooth/Kconfig
+##
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_BCM=y
+
+##
+## file: drivers/block/zram/Kconfig
+##
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_LZ4HC=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_DEFLATE=y
+CONFIG_ZRAM_BACKEND_842=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MEMORY_TRACKING=y
+CONFIG_ZRAM_MULTI_COMP=y
+
+##
+## file: drivers/base/firmware_loader/Kconfig
+##
+# CONFIG_FW_CACHE is not set
+
+##
+## file: drivers/char/hw_random/Kconfig
+##
+CONFIG_HW_RANDOM=m
+# CONFIG_HW_RANDOM_TIMERIOMEM is not set
+# CONFIG_HW_RANDOM_INTEL is not set
+# CONFIG_HW_RANDOM_AMD is not set
+# CONFIG_HW_RANDOM_ATMEL is not set
+# CONFIG_HW_RANDOM_BA431 is not set
+# CONFIG_HW_RANDOM_BCM2835 is not set
+# CONFIG_HW_RANDOM_IPROC_RNG200 is not set
+# CONFIG_HW_RANDOM_GEODE is not set
+# CONFIG_HW_RANDOM_N2RNG is not set
+# CONFIG_HW_RANDOM_VIA is not set
+# CONFIG_HW_RANDOM_IXP4XX is not set
+# CONFIG_HW_RANDOM_OMAP is not set
+# CONFIG_HW_RANDOM_OMAP3_ROM is not set
+# CONFIG_HW_RANDOM_OCTEON is not set
+# CONFIG_HW_RANDOM_PASEMI is not set
+# CONFIG_HW_RANDOM_VIRTIO is not set
+# CONFIG_HW_RANDOM_MXC_RNGA is not set
+# CONFIG_HW_RANDOM_IMX_RNGC is not set
+# CONFIG_HW_RANDOM_INGENIC_RNG is not set
+# CONFIG_HW_RANDOM_INGENIC_TRNG is not set
+# CONFIG_HW_RANDOM_NOMADIK is not set
+# CONFIG_HW_RANDOM_PSERIES is not set
+# CONFIG_HW_RANDOM_POWERNV is not set
+# CONFIG_HW_RANDOM_HISI is not set
+# CONFIG_HW_RANDOM_HISTB is not set
+# CONFIG_HW_RANDOM_ST is not set
+# CONFIG_HW_RANDOM_XGENE is not set
+# CONFIG_HW_RANDOM_STM32 is not set
+# CONFIG_HW_RANDOM_PIC32 is not set
+# CONFIG_HW_RANDOM_POLARFIRE_SOC is not set
+# CONFIG_HW_RANDOM_MESON is not set
+# CONFIG_HW_RANDOM_CAVIUM is not set
+# CONFIG_HW_RANDOM_MTK is not set
+# CONFIG_HW_RANDOM_S390 is not set
+# CONFIG_HW_RANDOM_EXYNOS is not set
+# CONFIG_HW_RANDOM_OPTEE is not set
+# CONFIG_HW_RANDOM_NPCM is not set
+# CONFIG_HW_RANDOM_KEYSTONE is not set
+# CONFIG_HW_RANDOM_CCTRNG is not set
+# CONFIG_HW_RANDOM_XIPHERA is not set
+# CONFIG_HW_RANDOM_ARM_SMCCC_TRNG is not set
+# CONFIG_HW_RANDOM_CN10K is not set
+# CONFIG_HW_RANDOM_JH7110 is not set
+CONFIG_HW_RANDOM_ROCKCHIP=m
+
+##
+## file: drivers/clk/Kconfig
+##
+CONFIG_COMMON_CLK=y
+CONFIG_COMMON_CLK_RK808=y
+CONFIG_COMMON_CLK_SCMI=y
+
+##
+## file: drivers/clk/rockchip/Kconfig
+##
+# CONFIG_CLK_PX30 is not set
+# CONFIG_CLK_RK3308 is not set
+# CONFIG_CLK_RK3328 is not set
+# CONFIG_CLK_RK3368 is not set
+# CONFIG_CLK_RK3399 is not set
+CONFIG_CLK_RK3568=y
+# CONFIG_CLK_RK3576 is not set
+# CONFIG_CLK_RK3588 is not set
+
+##
+## file: drivers/cpufreq/Kconfig
+##
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_SCHEDUTIL=y
+CONFIG_CPU_FREQ_GOV_PERFORMANCE=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_CPUFREQ_DT_PLATDEV=y
+
+##
+## file: drivers/cpufreq/Kconfig.arm
+##
+CONFIG_ARM_SCMI_CPUFREQ=m
+
+##
+## file: drivers/cpuidle/Kconfig
+##
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_IDLE_GOV_MENU=y
+CONFIG_CPU_IDLE_GOV_TEO=y
+
+##
+## file: drivers/cpuidle/Kconfig.arm
+##
+CONFIG_ARM_CPUIDLE=y
+CONFIG_ARM_PSCI_CPUIDLE=y
+CONFIG_ARM_PSCI_CPUIDLE_DOMAIN=y
+
+##
+## file: drivers/crypto/Kconfig
+##
+CONFIG_CRYPTO_HW=y
+CONFIG_CRYPTO_DEV_ROCKCHIP=m
+
+##
+## file: drivers/devfreq/Kconfig
+##
+CONFIG_PM_DEVFREQ=y
+CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y
+CONFIG_DEVFREQ_GOV_USERSPACE=m
+CONFIG_DEVFREQ_GOV_PASSIVE=m
+
+##
+## file: drivers/devfreq/event/Kconfig
+##
+CONFIG_PM_DEVFREQ_EVENT=y
+CONFIG_DEVFREQ_EVENT_ROCKCHIP_DFI=m
+
+##
+## file: drivers/dma/Kconfig
+##
+CONFIG_DMADEVICES=y
+CONFIG_PL330_DMA=y
+
+##
+## file: drivers/firmware/Kconfig
+##
+CONFIG_ARM_SDE_INTERFACE=y
+CONFIG_FIRMWARE_MEMMAP=y
+CONFIG_DMI_SYSFS=y
+CONFIG_ROCKCHIP_SIP=y
+
+##
+## file: drivers/firmware/arm_scmi/Kconfig
+##
+CONFIG_ARM_SCMI_PROTOCOL=y
+CONFIG_ARM_SCMI_POWER_CONTROL=y
+
+##
+## file: drivers/firmware/arm_scmi/transports/Kconfig
+##
+CONFIG_ARM_SCMI_TRANSPORT_MAILBOX=y
+CONFIG_ARM_SCMI_TRANSPORT_SMC=y
+
+##
+## file: drivers/firmware/efi/Kconfig
+##
+CONFIG_EFI_BOOTLOADER_CONTROL=y
+
+##
+## file: drivers/firmware/smccc/Kconfig
+##
+# CONFIG_ARM_SMCCC_SOC_ID is not set
+
+##
+## file: drivers/gpio/Kconfig
+##
+CONFIG_GPIOLIB=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_ROCKCHIP=m
+
+##
+## file: drivers/gpu/drm/Kconfig
+##
+CONFIG_DRM=m
+CONFIG_DRM_VGEM=m
+
+##
+## file: drivers/gpu/drm/bridge/Kconfig
+##
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+
+##
+## file: drivers/gpu/drm/panel/Kconfig
+##
+CONFIG_DRM_PANEL_SIMPLE=m
+
+##
+## file: drivers/gpu/drm/panfrost/Kconfig
+##
+CONFIG_DRM_PANFROST=m
+
+##
+## file: drivers/gpu/drm/rockchip/Kconfig
+##
+CONFIG_DRM_ROCKCHIP=m
+CONFIG_ROCKCHIP_VOP2=y
+CONFIG_ROCKCHIP_DW_HDMI=y
+CONFIG_ROCKCHIP_DW_MIPI_DSI=y
+CONFIG_ROCKCHIP_INNO_HDMI=y
+CONFIG_ROCKCHIP_LVDS=y
+CONFIG_DRM_ROCKCHIP_EBC=m
+
+##
+## file: drivers/hid/Kconfig
+##
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_MICROSOFT=y
+
+##
+## file: drivers/hid/i2c-hid/Kconfig
+##
+CONFIG_I2C_HID_OF=m
+CONFIG_I2C_HID_OF_GOODIX=m
+
+##
+## file: drivers/hid/usbhid/Kconfig
+##
+CONFIG_USB_HIDDEV=y
+
+##
+## file: drivers/hwmon/Kconfig
+##
+CONFIG_SENSORS_ARM_SCMI=m
+CONFIG_SENSORS_GPIO_FAN=m
+
+##
+## file: drivers/i2c/Kconfig
+##
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=y
+# CONFIG_I2C_HELPER_AUTO is not set
+
+##
+## file: drivers/i2c/algos/Kconfig
+##
+CONFIG_I2C_ALGOBIT=y
+
+##
+## file: drivers/i2c/busses/Kconfig
+##
+CONFIG_I2C_RK3X=y
+
+##
+## file: drivers/iio/Kconfig
+##
+CONFIG_IIO=m
+CONFIG_IIO_SW_TRIGGER=m
+
+##
+## file: drivers/iio/accel/Kconfig
+##
+CONFIG_IIO_ST_ACCEL_3AXIS=m
+CONFIG_IIO_ST_ACCEL_I2C_3AXIS=m
+
+##
+## file: drivers/iio/adc/Kconfig
+##
+CONFIG_ROCKCHIP_SARADC=m
+
+##
+## file: drivers/iio/common/scmi_sensors/Kconfig
+##
+CONFIG_IIO_SCMI=m
+
+##
+## file: drivers/input/Kconfig
+##
+CONFIG_INPUT_MOUSEDEV=m
+CONFIG_INPUT_EVDEV=y
+
+##
+## file: drivers/input/keyboard/Kconfig
+##
+CONFIG_KEYBOARD_ADC=m
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=y
+
+##
+## file: drivers/input/misc/Kconfig
+##
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_RK805_PWRKEY=y
+CONFIG_INPUT_WS8100_PEN=m
+
+##
+## file: drivers/input/mouse/Kconfig
+##
+# CONFIG_INPUT_MOUSE is not set
+
+##
+## file: drivers/input/serio/Kconfig
+##
+# CONFIG_SERIO is not set
+
+##
+## file: drivers/input/touchscreen/Kconfig
+##
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_CYTTSP5=m
+
+##
+## file: drivers/iommu/Kconfig
+##
+CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y
+CONFIG_ROCKCHIP_IOMMU=y
+
+##
+## file: drivers/leds/Kconfig
+##
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_FLASH=y
+CONFIG_LEDS_CLASS_MULTICOLOR=y
+CONFIG_LEDS_GPIO=y
+
+##
+## file: drivers/leds/trigger/Kconfig
+##
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_ACTIVITY=y
+CONFIG_LEDS_TRIGGER_GPIO=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=y
+CONFIG_LEDS_TRIGGER_CAMERA=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=y
+CONFIG_LEDS_TRIGGER_PATTERN=y
+CONFIG_LEDS_TRIGGER_TTY=y
+
+##
+## file: drivers/mailbox/Kconfig
+##
+CONFIG_MAILBOX=y
+CONFIG_ROCKCHIP_MBOX=y
+
+##
+## file: drivers/media/Kconfig
+##
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_PLATFORM_SUPPORT=y
+CONFIG_VIDEO_DEV=m
+
+##
+## file: drivers/media/cec/Kconfig
+##
+# CONFIG_MEDIA_CEC_SUPPORT is not set
+
+##
+## file: drivers/media/dvb-core/Kconfig
+##
+# CONFIG_DVB_NET is not set
+# CONFIG_DVB_DYNAMIC_MINORS is not set
+
+##
+## file: drivers/media/dvb-frontends/Kconfig
+##
+# CONFIG_DVB_MXL5XX is not set
+# CONFIG_DVB_STB0899 is not set
+# CONFIG_DVB_STB6100 is not set
+# CONFIG_DVB_STV090x is not set
+# CONFIG_DVB_STV0910 is not set
+# CONFIG_DVB_STV6110x is not set
+# CONFIG_DVB_STV6111 is not set
+# CONFIG_DVB_DRXK is not set
+# CONFIG_DVB_MN88472 is not set
+# CONFIG_DVB_MN88473 is not set
+# CONFIG_DVB_SI2165 is not set
+# CONFIG_DVB_TDA18271C2DD is not set
+# CONFIG_DVB_CX24110 is not set
+# CONFIG_DVB_CX24116 is not set
+# CONFIG_DVB_CX24117 is not set
+# CONFIG_DVB_CX24120 is not set
+# CONFIG_DVB_CX24123 is not set
+# CONFIG_DVB_DS3000 is not set
+# CONFIG_DVB_MB86A16 is not set
+# CONFIG_DVB_MT312 is not set
+# CONFIG_DVB_S5H1420 is not set
+# CONFIG_DVB_SI21XX is not set
+# CONFIG_DVB_STB6000 is not set
+# CONFIG_DVB_STV0288 is not set
+# CONFIG_DVB_STV0299 is not set
+# CONFIG_DVB_STV0900 is not set
+# CONFIG_DVB_STV6110 is not set
+# CONFIG_DVB_TDA10071 is not set
+# CONFIG_DVB_TDA10086 is not set
+# CONFIG_DVB_TDA8083 is not set
+# CONFIG_DVB_TDA8261 is not set
+# CONFIG_DVB_TDA826X is not set
+# CONFIG_DVB_TS2020 is not set
+# CONFIG_DVB_TUA6100 is not set
+# CONFIG_DVB_TUNER_CX24113 is not set
+# CONFIG_DVB_TUNER_ITD1000 is not set
+# CONFIG_DVB_VES1X93 is not set
+# CONFIG_DVB_ZL10036 is not set
+# CONFIG_DVB_ZL10039 is not set
+# CONFIG_DVB_CX22700 is not set
+# CONFIG_DVB_CX22702 is not set
+# CONFIG_DVB_CXD2820R is not set
+# CONFIG_DVB_CXD2841ER is not set
+# CONFIG_DVB_DIB3000MB is not set
+# CONFIG_DVB_DIB3000MC is not set
+# CONFIG_DVB_DIB7000M is not set
+# CONFIG_DVB_DIB7000P is not set
+# CONFIG_DVB_DIB9000 is not set
+# CONFIG_DVB_DRXD is not set
+# CONFIG_DVB_EC100 is not set
+# CONFIG_DVB_L64781 is not set
+# CONFIG_DVB_MT352 is not set
+# CONFIG_DVB_NXT6000 is not set
+# CONFIG_DVB_S5H1432 is not set
+# CONFIG_DVB_SP887X is not set
+# CONFIG_DVB_STV0367 is not set
+# CONFIG_DVB_TDA10048 is not set
+# CONFIG_DVB_TDA1004X is not set
+# CONFIG_DVB_ZD1301_DEMOD is not set
+# CONFIG_DVB_ZL10353 is not set
+# CONFIG_DVB_STV0297 is not set
+# CONFIG_DVB_TDA10021 is not set
+# CONFIG_DVB_TDA10023 is not set
+# CONFIG_DVB_VES1820 is not set
+# CONFIG_DVB_AU8522_DTV is not set
+# CONFIG_DVB_AU8522_V4L is not set
+# CONFIG_DVB_BCM3510 is not set
+# CONFIG_DVB_LG2160 is not set
+# CONFIG_DVB_LGDT3305 is not set
+# CONFIG_DVB_LGDT330X is not set
+# CONFIG_DVB_MXL692 is not set
+# CONFIG_DVB_NXT200X is not set
+# CONFIG_DVB_OR51132 is not set
+# CONFIG_DVB_OR51211 is not set
+# CONFIG_DVB_S5H1409 is not set
+# CONFIG_DVB_S5H1411 is not set
+# CONFIG_DVB_DIB8000 is not set
+# CONFIG_DVB_MB86A20S is not set
+# CONFIG_DVB_S921 is not set
+# CONFIG_DVB_MN88443X is not set
+# CONFIG_DVB_TC90522 is not set
+# CONFIG_DVB_PLL is not set
+# CONFIG_DVB_TUNER_DIB0070 is not set
+# CONFIG_DVB_TUNER_DIB0090 is not set
+# CONFIG_DVB_A8293 is not set
+# CONFIG_DVB_AF9033 is not set
+# CONFIG_DVB_ASCOT2E is not set
+# CONFIG_DVB_ATBM8830 is not set
+# CONFIG_DVB_HELENE is not set
+# CONFIG_DVB_HORUS3A is not set
+# CONFIG_DVB_ISL6405 is not set
+# CONFIG_DVB_ISL6421 is not set
+# CONFIG_DVB_ISL6423 is not set
+# CONFIG_DVB_IX2505V is not set
+# CONFIG_DVB_LGS8GL5 is not set
+# CONFIG_DVB_LGS8GXX is not set
+# CONFIG_DVB_LNBH25 is not set
+# CONFIG_DVB_LNBH29 is not set
+# CONFIG_DVB_LNBP21 is not set
+# CONFIG_DVB_LNBP22 is not set
+# CONFIG_DVB_M88RS2000 is not set
+# CONFIG_DVB_TDA665x is not set
+# CONFIG_DVB_CXD2099 is not set
+# CONFIG_DVB_SP2 is not set
+
+##
+## file: drivers/media/dvb-frontends/cxd2880/Kconfig
+##
+# CONFIG_DVB_CXD2880 is not set
+
+##
+## file: drivers/media/dvb-frontends/drx39xyj/Kconfig
+##
+# CONFIG_DVB_DRX39XYJ is not set
+
+##
+## file: drivers/media/platform/Kconfig
+##
+CONFIG_V4L_MEM2MEM_DRIVERS=y
+
+##
+## file: drivers/media/platform/rockchip/rga/Kconfig
+##
+CONFIG_VIDEO_ROCKCHIP_RGA=m
+
+##
+## file: drivers/media/platform/verisilicon/Kconfig
+##
+CONFIG_VIDEO_HANTRO=m
+CONFIG_VIDEO_HANTRO_ROCKCHIP=y
+
+##
+## file: drivers/media/radio/Kconfig
+##
+# CONFIG_RADIO_ADAPTERS is not set
+
+##
+## file: drivers/media/spi/Kconfig
+##
+# CONFIG_CXD2880_SPI_DRV is not set
+
+##
+## file: drivers/media/tuners/Kconfig
+##
+# CONFIG_MEDIA_TUNER_E4000 is not set
+# CONFIG_MEDIA_TUNER_FC0011 is not set
+# CONFIG_MEDIA_TUNER_FC0012 is not set
+# CONFIG_MEDIA_TUNER_FC0013 is not set
+# CONFIG_MEDIA_TUNER_FC2580 is not set
+# CONFIG_MEDIA_TUNER_IT913X is not set
+# CONFIG_MEDIA_TUNER_M88RS6000T is not set
+# CONFIG_MEDIA_TUNER_MAX2165 is not set
+# CONFIG_MEDIA_TUNER_MC44S803 is not set
+# CONFIG_MEDIA_TUNER_MSI001 is not set
+# CONFIG_MEDIA_TUNER_MT2060 is not set
+# CONFIG_MEDIA_TUNER_MT2063 is not set
+# CONFIG_MEDIA_TUNER_MT20XX is not set
+# CONFIG_MEDIA_TUNER_MT2131 is not set
+# CONFIG_MEDIA_TUNER_MT2266 is not set
+# CONFIG_MEDIA_TUNER_MXL301RF is not set
+# CONFIG_MEDIA_TUNER_MXL5005S is not set
+# CONFIG_MEDIA_TUNER_MXL5007T is not set
+# CONFIG_MEDIA_TUNER_QM1D1B0004 is not set
+# CONFIG_MEDIA_TUNER_QM1D1C0042 is not set
+# CONFIG_MEDIA_TUNER_QT1010 is not set
+# CONFIG_MEDIA_TUNER_R820T is not set
+# CONFIG_MEDIA_TUNER_SI2157 is not set
+# CONFIG_MEDIA_TUNER_SIMPLE is not set
+# CONFIG_MEDIA_TUNER_TDA18212 is not set
+# CONFIG_MEDIA_TUNER_TDA18218 is not set
+# CONFIG_MEDIA_TUNER_TDA18250 is not set
+# CONFIG_MEDIA_TUNER_TDA18271 is not set
+# CONFIG_MEDIA_TUNER_TDA827X is not set
+# CONFIG_MEDIA_TUNER_TDA8290 is not set
+# CONFIG_MEDIA_TUNER_TDA9887 is not set
+# CONFIG_MEDIA_TUNER_TEA5761 is not set
+# CONFIG_MEDIA_TUNER_TEA5767 is not set
+# CONFIG_MEDIA_TUNER_TUA9001 is not set
+# CONFIG_MEDIA_TUNER_XC2028 is not set
+# CONFIG_MEDIA_TUNER_XC4000 is not set
+# CONFIG_MEDIA_TUNER_XC5000 is not set
+
+##
+## file: drivers/media/usb/uvc/Kconfig
+##
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV=y
+
+##
+## file: drivers/mfd/Kconfig
+##
+CONFIG_MFD_RK8XX_I2C=y
+
+##
+## file: drivers/misc/Kconfig
+##
+CONFIG_SRAM=y
+
+##
+## file: drivers/mmc/Kconfig
+##
+CONFIG_MMC=y
+
+##
+## file: drivers/mmc/core/Kconfig
+##
+CONFIG_PWRSEQ_SIMPLE=y
+CONFIG_MMC_BLOCK_MINORS=32
+
+##
+## file: drivers/mmc/host/Kconfig
+##
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_DWCMSHC=y
+CONFIG_MMC_DW=y
+CONFIG_MMC_DW_ROCKCHIP=y
+
+##
+## file: drivers/mtd/spi-nor/Kconfig
+##
+CONFIG_MTD_SPI_NOR=m
+
+##
+## file: drivers/net/Kconfig
+##
+CONFIG_NETDEVICES=y
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_MACVLAN=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+
+##
+## file: drivers/net/ethernet/Kconfig
+##
+CONFIG_ETHERNET=y
+
+##
+## file: drivers/net/ethernet/stmicro/stmmac/Kconfig
+##
+CONFIG_STMMAC_ETH=m
+CONFIG_STMMAC_PLATFORM=m
+CONFIG_DWMAC_ROCKCHIP=m
+
+##
+## file: drivers/net/phy/Kconfig
+##
+CONFIG_MOTORCOMM_PHY=m
+
+##
+## file: drivers/net/usb/Kconfig
+##
+CONFIG_USB_NET_DRIVERS=m
+CONFIG_USB_USBNET=m
+# CONFIG_USB_NET_AX8817X is not set
+# CONFIG_USB_NET_AX88179_178A is not set
+CONFIG_USB_NET_CDC_EEM=m
+# CONFIG_USB_NET_NET1080 is not set
+# CONFIG_USB_NET_CDC_SUBSET is not set
+# CONFIG_USB_NET_ZAURUS is not set
+
+##
+## file: drivers/net/wireless/admtek/Kconfig
+##
+# CONFIG_WLAN_VENDOR_ADMTEK is not set
+
+##
+## file: drivers/net/wireless/ath/Kconfig
+##
+# CONFIG_WLAN_VENDOR_ATH is not set
+
+##
+## file: drivers/net/wireless/atmel/Kconfig
+##
+# CONFIG_WLAN_VENDOR_ATMEL is not set
+
+##
+## file: drivers/net/wireless/broadcom/brcm80211/brcmfmac/Kconfig
+##
+CONFIG_BRCMFMAC=m
+
+##
+## file: drivers/net/wireless/intel/Kconfig
+##
+# CONFIG_WLAN_VENDOR_INTEL is not set
+
+##
+## file: drivers/net/wireless/intersil/Kconfig
+##
+# CONFIG_WLAN_VENDOR_INTERSIL is not set
+
+##
+## file: drivers/net/wireless/marvell/Kconfig
+##
+# CONFIG_WLAN_VENDOR_MARVELL is not set
+
+##
+## file: drivers/net/wireless/mediatek/Kconfig
+##
+# CONFIG_WLAN_VENDOR_MEDIATEK is not set
+
+##
+## file: drivers/net/wireless/microchip/Kconfig
+##
+# CONFIG_WLAN_VENDOR_MICROCHIP is not set
+
+##
+## file: drivers/net/wireless/quantenna/Kconfig
+##
+# CONFIG_WLAN_VENDOR_QUANTENNA is not set
+
+##
+## file: drivers/net/wireless/ralink/Kconfig
+##
+# CONFIG_WLAN_VENDOR_RALINK is not set
+
+##
+## file: drivers/net/wireless/realtek/Kconfig
+##
+# CONFIG_WLAN_VENDOR_REALTEK is not set
+
+##
+## file: drivers/net/wireless/rsi/Kconfig
+##
+# CONFIG_WLAN_VENDOR_RSI is not set
+
+##
+## file: drivers/net/wireless/st/Kconfig
+##
+# CONFIG_WLAN_VENDOR_ST is not set
+
+##
+## file: drivers/net/wireless/ti/Kconfig
+##
+# CONFIG_WLAN_VENDOR_TI is not set
+
+##
+## file: drivers/net/wireless/zydas/Kconfig
+##
+# CONFIG_WLAN_VENDOR_ZYDAS is not set
+
+##
+## file: drivers/nvmem/Kconfig
+##
+CONFIG_NVMEM_ROCKCHIP_EFUSE=y
+CONFIG_NVMEM_ROCKCHIP_OTP=y
+
+##
+## file: drivers/pci/Kconfig
+##
+CONFIG_PCI=y
+
+##
+## file: drivers/pci/controller/dwc/Kconfig
+##
+CONFIG_PCIE_ROCKCHIP_DW_HOST=y
+CONFIG_PCIE_ROCKCHIP_DW_EP=y
+
+##
+## file: drivers/pci/endpoint/Kconfig
+##
+CONFIG_PCI_ENDPOINT=y
+
+##
+## file: drivers/perf/Kconfig
+##
+CONFIG_ARM_PMUV3=y
+
+##
+## file: drivers/pinctrl/Kconfig
+##
+CONFIG_PINCTRL_ROCKCHIP=y
+CONFIG_PINCTRL_SCMI=y
+
+##
+## file: drivers/phy/rockchip/Kconfig
+##
+CONFIG_PHY_ROCKCHIP_INNO_HDMI=m
+CONFIG_PHY_ROCKCHIP_INNO_USB2=m
+CONFIG_PHY_ROCKCHIP_INNO_CSIDPHY=m
+CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY=m
+CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY=m
+CONFIG_PHY_ROCKCHIP_PCIE=m
+
+##
+## file: drivers/pmdomain/arm/Kconfig
+##
+CONFIG_ARM_SCMI_PERF_DOMAIN=y
+CONFIG_ARM_SCMI_POWER_DOMAIN=y
+
+##
+## file: drivers/pmdomain/rockchip/Kconfig
+##
+CONFIG_ROCKCHIP_PM_DOMAINS=y
+
+##
+## file: drivers/power/supply/Kconfig
+##
+CONFIG_CHARGER_RK817=y
+
+##
+## file: drivers/pwm/Kconfig
+##
+CONFIG_PWM=y
+CONFIG_PWM_ROCKCHIP=y
+
+##
+## file: drivers/regulator/Kconfig
+##
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARM_SCMI=m
+CONFIG_REGULATOR_FAN53555=y
+CONFIG_REGULATOR_RK808=y
+CONFIG_REGULATOR_TPS65185=m
+
+##
+## file: drivers/reset/Kconfig
+##
+CONFIG_RESET_SCMI=y
+
+##
+## file: drivers/soc/rockchip/Kconfig
+##
+CONFIG_ROCKCHIP_GRF=y
+CONFIG_ROCKCHIP_IODOMAIN=y
+CONFIG_ROCKCHIP_SUSPEND_MODE=y
+
+##
+## file: drivers/spi/Kconfig
+##
+CONFIG_SPI=y
+CONFIG_SPI_BITBANG=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_ROCKCHIP=m
+CONFIG_SPI_ROCKCHIP_SFC=m
+CONFIG_SPI_SPIDEV=y
+
+##
+## file: drivers/thermal/Kconfig
+##
+CONFIG_THERMAL=y
+CONFIG_THERMAL_NETLINK=y
+CONFIG_THERMAL_STATISTICS=y
+CONFIG_THERMAL_GOV_FAIR_SHARE=y
+CONFIG_THERMAL_GOV_BANG_BANG=y
+CONFIG_THERMAL_GOV_USER_SPACE=y
+CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y
+CONFIG_CPU_THERMAL=y
+CONFIG_THERMAL_EMULATION=y
+CONFIG_ROCKCHIP_THERMAL=y
+
+##
+## file: drivers/tty/serdev/Kconfig
+##
+CONFIG_SERIAL_DEV_BUS=y
+
+##
+## file: drivers/tty/serial/Kconfig
+##
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+CONFIG_SERIAL_8250_DW=y
+CONFIG_SERIAL_OF_PLATFORM=y
+
+##
+## file: drivers/usb/Kconfig
+##
+CONFIG_USB=y
+
+##
+## file: drivers/usb/common/Kconfig
+##
+CONFIG_USB_LED_TRIG=y
+
+##
+## file: drivers/usb/core/Kconfig
+##
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_DYNAMIC_MINORS=y
+CONFIG_USB_OTG=y
+CONFIG_USB_OTG_FSM=m
+CONFIG_USB_LEDS_TRIGGER_USBPORT=y
+
+##
+## file: drivers/usb/dwc3/Kconfig
+##
+CONFIG_USB_DWC3=y
+
+##
+## file: drivers/usb/gadget/Kconfig
+##
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_F_GUD=y
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_MIDI2=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+
+##
+## file: drivers/usb/gadget/legacy/Kconfig
+##
+CONFIG_USB_GADGETFS=y
+CONFIG_USB_FUNCTIONFS=m
+CONFIG_USB_FUNCTIONFS_ETH=y
+CONFIG_USB_FUNCTIONFS_GENERIC=y
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_MULTI=m
+# CONFIG_USB_G_MULTI_RNDIS is not set
+
+##
+## file: drivers/usb/host/Kconfig
+##
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_EHCI_ROOT_HUB_TT=y
+CONFIG_USB_EHCI_HCD_PLATFORM=y
+CONFIG_USB_OHCI_HCD=y
+CONFIG_USB_OHCI_HCD_PLATFORM=y
+
+##
+## file: drivers/usb/phy/Kconfig
+##
+CONFIG_USB_PHY=y
+
+##
+## file: drivers/usb/roles/Kconfig
+##
+CONFIG_USB_ROLE_SWITCH=m
+
+##
+## file: drivers/usb/serial/Kconfig
+##
+CONFIG_USB_SERIAL=y
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_CP210X=m
+
+##
+## file: drivers/usb/storage/Kconfig
+##
+CONFIG_USB_STORAGE=m
+CONFIG_USB_UAS=m
+
+##
+## file: drivers/usb/typec/Kconfig
+##
+CONFIG_TYPEC=y
+CONFIG_TYPEC_WUSB3801=m
+
+##
+## file: drivers/video/backlight/Kconfig
+##
+CONFIG_BACKLIGHT_CLASS_DEVICE=y
+CONFIG_BACKLIGHT_LM3630A=y
+
+##
+## file: drivers/watchdog/Kconfig
+##
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_DW_WATCHDOG=y
+
+##
+## file: fs/Kconfig
+##
+CONFIG_FILE_LOCKING=y
+CONFIG_NETWORK_FILESYSTEMS=y
+
+##
+## file: fs/Kconfig.binfmt
+##
+CONFIG_COREDUMP=y
+
+##
+## file: fs/cachefiles/Kconfig
+##
+CONFIG_CACHEFILES=m
+
+##
+## file: fs/netfs/Kconfig
+##
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+
+##
+## file: fs/nfs/Kconfig
+##
+CONFIG_NFS_FS=m
+CONFIG_NFS_V2=m
+CONFIG_NFS_V3=m
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=m
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_NFS_V4_1_IMPLEMENTATION_ID_DOMAIN="kernel.org"
+# CONFIG_NFS_V4_1_MIGRATION is not set
+CONFIG_NFS_FSCACHE=y
+# CONFIG_NFS_USE_LEGACY_DNS is not set
+
+##
+## file: fs/proc/Kconfig
+##
+CONFIG_PROC_FS=y
+CONFIG_PROC_SYSCTL=y
+CONFIG_PROC_PAGE_MONITOR=y
+
+##
+## file: fs/smb/client/Kconfig
+##
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_FSCACHE=y
+
+##
+## file: init/Kconfig
+##
+CONFIG_MULTIUSER=y
+
+##
+## file: kernel/power/Kconfig
+##
+CONFIG_PM_AUTOSLEEP=y
+CONFIG_PM_WAKELOCKS=y
+CONFIG_PM_DEBUG=y
+CONFIG_PM_ADVANCED_DEBUG=y
+CONFIG_PM_TEST_SUSPEND=y
+CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
+CONFIG_ENERGY_MODEL=y
+
+##
+## file: mm/Kconfig
+##
+CONFIG_SWAP=y
+
+##
+## file: net/Kconfig
+##
+CONFIG_NET=y
+CONFIG_INET=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_CGROUP_NET_PRIO=y
+# CONFIG_ETHTOOL_NETLINK is not set
+
+##
+## file: net/8021q/Kconfig
+##
+CONFIG_VLAN_8021Q=m
+
+##
+## file: net/bluetooth/Kconfig
+##
+CONFIG_BT=m
+CONFIG_BT_LEDS=y
+
+##
+## file: net/bluetooth/bnep/Kconfig
+##
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+
+##
+## file: net/bluetooth/hidp/Kconfig
+##
+CONFIG_BT_HIDP=m
+
+##
+## file: net/bluetooth/rfcomm/Kconfig
+##
+CONFIG_BT_RFCOMM=y
+CONFIG_BT_RFCOMM_TTY=y
+
+##
+## file: net/bridge/Kconfig
+##
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+
+##
+## file: net/ipv4/Kconfig
+##
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_SYN_COOKIES=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+
+##
+## file: net/ipv4/netfilter/Kconfig
+##
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+
+##
+## file: net/ipv6/Kconfig
+##
+CONFIG_IPV6=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_SIT=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+
+##
+## file: net/ipv6/netfilter/Kconfig
+##
+CONFIG_IP6_NF_IPTABLES=m
+
+##
+## file: net/mac80211/Kconfig
+##
+CONFIG_MAC80211=m
+# CONFIG_MAC80211_RC_MINSTREL is not set
+CONFIG_MAC80211_LEDS=y
+CONFIG_MAC80211_DEBUGFS=y
+
+##
+## file: net/netfilter/Kconfig
+##
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_TABLES=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NETFILTER_XTABLES=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+
+##
+## file: net/netfilter/ipvs/Kconfig
+##
+CONFIG_IP_VS=m
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_NFCT=y
+
+##
+## file: net/netlink/Kconfig
+##
+CONFIG_NETLINK_DIAG=y
+
+##
+## file: net/packet/Kconfig
+##
+CONFIG_PACKET=y
+
+##
+## file: net/rfkill/Kconfig
+##
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+
+##
+## file: net/sched/Kconfig
+##
+CONFIG_NET_SCHED=y
+CONFIG_NET_CLS_CGROUP=m
+
+##
+## file: net/unix/Kconfig
+##
+CONFIG_UNIX=y
+
+##
+## file: net/wireless/Kconfig
+##
+CONFIG_CFG80211=m
+# CONFIG_CFG80211_CERTIFICATION_ONUS is not set
+CONFIG_CFG80211_REQUIRE_SIGNED_REGDB=y
+CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS=y
+CONFIG_CFG80211_DEFAULT_PS=y
+CONFIG_CFG80211_DEBUGFS=y
+# CONFIG_CFG80211_CRDA_SUPPORT is not set
+
+##
+## file: net/xfrm/Kconfig
+##
+CONFIG_XFRM_USER=m
+
+##
+## file: security/Kconfig
+##
+CONFIG_LSM="landlock"
+CONFIG_SECURITY=y
+
+##
+## file: security/landlock/Kconfig
+##
+CONFIG_SECURITY_LANDLOCK=y
+
+##
+## file: sound/Kconfig
+##
+CONFIG_SOUND=m
+CONFIG_SND=m
+
+##
+## file: sound/core/Kconfig
+##
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+# CONFIG_SND_SUPPORT_OLD_API is not set
+CONFIG_SND_DEBUG=y
+CONFIG_SND_DEBUG_VERBOSE=y
+
+##
+## file: sound/drivers/Kconfig
+##
+# CONFIG_SND_DRIVERS is not set
+
+##
+## file: sound/soc/Kconfig
+##
+CONFIG_SND_SOC=m
+
+##
+## file: sound/soc/codecs/Kconfig
+##
+CONFIG_SND_SOC_BT_SCO=m
+CONFIG_SND_SOC_DMIC=m
+CONFIG_SND_SOC_RK817=m
+CONFIG_SND_SOC_SIMPLE_AMPLIFIER=m
+CONFIG_SND_SOC_SPDIF=m
+
+##
+## file: sound/soc/generic/Kconfig
+##
+CONFIG_SND_SIMPLE_CARD=m
+
+##
+## file: sound/soc/rockchip/Kconfig
+##
+CONFIG_SND_SOC_ROCKCHIP=m
+CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=m
+CONFIG_SND_SOC_ROCKCHIP_PDM=m
+CONFIG_SND_SOC_ROCKCHIP_SPDIF=m
+
+##
+## file: sound/spi/Kconfig
+##
+# CONFIG_SND_SPI is not set
+
+##
+## file: sound/usb/Kconfig
+##
+CONFIG_SND_USB=y
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_AUDIO_MIDI_V2=y
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+
+##
+## file: sound/usb/line6/Kconfig
+##
+CONFIG_SND_USB_POD=m
+CONFIG_SND_USB_PODHD=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_USB_VARIAX=m
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index bbd2155d8..e062e6a03 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -178,6 +178,13 @@ config MTK_ADSP_IPC
 	  ADSP exists on some mtk processors.
 	  Client might use shared memory to exchange information with ADSP.
 
+config ROCKCHIP_SIP
+	tristate "Rockchip SIP interface"
+	depends on HAVE_ARM_SMCCC && ARCH_ROCKCHIP
+	help
+	  Say Y here if you want to enable SIP callbacks for Rockchip platforms
+	  This option enables support for communicating with the ATF.
+
 config SYSFB
 	bool
 	select BOOT_VESA_SUPPORT
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 4ddec2820..32850a0d1 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_FIRMWARE_MEMMAP)	+= memmap.o
 obj-$(CONFIG_MTK_ADSP_IPC)	+= mtk-adsp-ipc.o
 obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
 obj-$(CONFIG_FW_CFG_SYSFS)	+= qemu_fw_cfg.o
+obj-$(CONFIG_ROCKCHIP_SIP)	+= rockchip_sip.o
 obj-$(CONFIG_SYSFB)		+= sysfb.o
 obj-$(CONFIG_SYSFB_SIMPLEFB)	+= sysfb_simplefb.o
 obj-$(CONFIG_TH1520_AON_PROTOCOL) += thead,th1520-aon.o
diff --git a/drivers/firmware/rockchip_sip.c b/drivers/firmware/rockchip_sip.c
new file mode 100644
index 000000000..7fe034df9
--- /dev/null
+++ b/drivers/firmware/rockchip_sip.c
@@ -0,0 +1,547 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Copyright (C) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/rockchip/rockchip_sip.h>
+#include <asm/cputype.h>
+#ifdef CONFIG_ARM
+#include <asm/psci.h>
+#endif
+#include <asm/smp_plat.h>
+#include <uapi/linux/psci.h>
+#include <linux/ptrace.h>
+#include <linux/sched/clock.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#ifdef CONFIG_64BIT
+#define PSCI_FN_NATIVE(version, name)	PSCI_##version##_FN64_##name
+#else
+#define PSCI_FN_NATIVE(version, name)	PSCI_##version##_FN_##name
+#endif
+
+#define SIZE_PAGE(n)	((n) << 12)
+
+static struct arm_smccc_res __invoke_sip_fn_smc(unsigned long function_id,
+						unsigned long arg0,
+						unsigned long arg1,
+						unsigned long arg2)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_smc(function_id, arg0, arg1, arg2, 0, 0, 0, 0, &res);
+	return res;
+}
+
+struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2)
+{
+	return __invoke_sip_fn_smc(SIP_DRAM_CONFIG, arg0, arg1, arg2);
+}
+EXPORT_SYMBOL_GPL(sip_smc_dram);
+
+struct arm_smccc_res sip_smc_get_atf_version(void)
+{
+	return __invoke_sip_fn_smc(SIP_ATF_VERSION, 0, 0, 0);
+}
+EXPORT_SYMBOL_GPL(sip_smc_get_atf_version);
+
+struct arm_smccc_res sip_smc_get_sip_version(void)
+{
+	return __invoke_sip_fn_smc(SIP_SIP_VERSION, 0, 0, 0);
+}
+EXPORT_SYMBOL_GPL(sip_smc_get_sip_version);
+
+int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, ctrl, config1, config2);
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(sip_smc_set_suspend_mode);
+
+struct arm_smccc_res sip_smc_get_suspend_info(u32 info)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_SUSPEND_MODE, info, 0, 0);
+	return res;
+}
+EXPORT_SYMBOL_GPL(sip_smc_get_suspend_info);
+
+int sip_smc_virtual_poweroff(void)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), 0, 0, 0);
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(sip_smc_virtual_poweroff);
+
+int sip_smc_remotectl_config(u32 func, u32 data)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_REMOTECTL_CFG, func, data, 0);
+
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(sip_smc_remotectl_config);
+
+u32 sip_smc_secure_reg_read(u32 addr_phy)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_ACCESS_REG, 0, addr_phy, SECURE_REG_RD);
+	if (res.a0)
+		pr_err("%s error: %d, addr phy: 0x%x\n",
+		       __func__, (int)res.a0, addr_phy);
+
+	return res.a1;
+}
+EXPORT_SYMBOL_GPL(sip_smc_secure_reg_read);
+
+int sip_smc_secure_reg_write(u32 addr_phy, u32 val)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_ACCESS_REG, val, addr_phy, SECURE_REG_WR);
+	if (res.a0)
+		pr_err("%s error: %d, addr phy: 0x%x\n",
+		       __func__, (int)res.a0, addr_phy);
+
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(sip_smc_secure_reg_write);
+
+static void *sip_map(phys_addr_t start, size_t size)
+{
+	struct page **pages;
+	phys_addr_t page_start;
+	unsigned int page_count;
+	pgprot_t prot;
+	unsigned int i;
+	void *vaddr;
+
+	if (!pfn_valid(__phys_to_pfn(start)))
+		return ioremap(start, size);
+
+	page_start = start - offset_in_page(start);
+	page_count = DIV_ROUND_UP(size + offset_in_page(start), PAGE_SIZE);
+
+	prot = pgprot_noncached(PAGE_KERNEL);
+
+	pages = kmalloc_array(page_count, sizeof(struct page *), GFP_KERNEL);
+	if (!pages) {
+		pr_err("%s: Failed to allocate array for %u pages\n",
+		       __func__, page_count);
+		return NULL;
+	}
+
+	for (i = 0; i < page_count; i++)
+		pages[i] = phys_to_page(page_start + i * PAGE_SIZE);
+
+	vaddr = vmap(pages, page_count, VM_MAP, prot);
+	kfree(pages);
+
+	/*
+	 * Since vmap() uses page granularity, we must add the offset
+	 * into the page here, to get the byte granularity address
+	 * into the mapping to represent the actual "start" location.
+	 */
+	return vaddr + offset_in_page(start);
+}
+
+struct arm_smccc_res sip_smc_request_share_mem(u32 page_num,
+					       share_page_type_t page_type)
+{
+	struct arm_smccc_res res;
+	unsigned long share_mem_phy;
+
+	res = __invoke_sip_fn_smc(SIP_SHARE_MEM, page_num, page_type, 0);
+	if (IS_SIP_ERROR(res.a0))
+		goto error;
+
+	share_mem_phy = res.a1;
+	res.a1 = (unsigned long)sip_map(share_mem_phy, SIZE_PAGE(page_num));
+
+error:
+	return res;
+}
+EXPORT_SYMBOL_GPL(sip_smc_request_share_mem);
+
+struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2)
+{
+	return __invoke_sip_fn_smc(SIP_MCU_EL3FIQ_CFG, arg0, arg1, arg2);
+}
+EXPORT_SYMBOL_GPL(sip_smc_mcu_el3fiq);
+
+struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(PSCI_SIP_VPU_RESET, arg0, arg1, arg2);
+	return res;
+}
+EXPORT_SYMBOL_GPL(sip_smc_vpu_reset);
+
+struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_BUS_CFG, arg0, arg1, arg2);
+	return res;
+}
+EXPORT_SYMBOL_GPL(sip_smc_bus_config);
+
+struct arm_smccc_res sip_smc_lastlog_request(void)
+{
+	struct arm_smccc_res res;
+	void __iomem *addr1, *addr2;
+
+	res = __invoke_sip_fn_smc(SIP_LAST_LOG, local_clock(), 0, 0);
+	if (IS_SIP_ERROR(res.a0))
+		return res;
+
+	addr1 = sip_map(res.a1, res.a3);
+	if (!addr1) {
+		pr_err("%s: share memory buffer0 ioremap failed\n", __func__);
+		res.a0 = SIP_RET_INVALID_ADDRESS;
+		return res;
+	}
+	addr2 = sip_map(res.a2, res.a3);
+	if (!addr2) {
+		pr_err("%s: share memory buffer1 ioremap failed\n", __func__);
+		res.a0 = SIP_RET_INVALID_ADDRESS;
+		return res;
+	}
+
+	res.a1 = (unsigned long)addr1;
+	res.a2 = (unsigned long)addr2;
+
+	return res;
+}
+EXPORT_SYMBOL_GPL(sip_smc_lastlog_request);
+
+/************************** fiq debugger **************************************/
+/*
+ * AArch32 is not allowed to call SMC64(ATF framework does not support), so we
+ * don't change SIP_UARTDBG_FN to SIP_UARTDBG_CFG64 even when cpu is AArch32
+ * mode. Let ATF support SIP_UARTDBG_CFG, and we just initialize SIP_UARTDBG_FN
+ * depends on compile option(CONFIG_ARM or CONFIG_ARM64).
+ */
+#ifdef CONFIG_ARM64
+#define SIP_UARTDBG_FN		SIP_UARTDBG_CFG64
+#else
+#define SIP_UARTDBG_FN		SIP_UARTDBG_CFG
+static int firmware_64_32bit;
+#endif
+
+static int fiq_sip_enabled;
+static int fiq_target_cpu;
+static phys_addr_t ft_fiq_mem_phy;
+static void __iomem *ft_fiq_mem_base;
+static void (*sip_fiq_debugger_uart_irq_tf)(struct pt_regs _pt_regs,
+					    unsigned long cpu);
+int sip_fiq_debugger_is_enabled(void)
+{
+	return fiq_sip_enabled;
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_is_enabled);
+
+static struct pt_regs sip_fiq_debugger_get_pt_regs(void *reg_base,
+						   unsigned long sp_el1)
+{
+	struct pt_regs fiq_pt_regs;
+	__maybe_unused struct sm_nsec_ctx *nsec_ctx = reg_base;
+	__maybe_unused struct gp_regs_ctx *gp_regs = reg_base;
+
+#ifdef CONFIG_ARM64
+	/*
+	 * 64-bit ATF + 64-bit kernel
+	 */
+	/* copy cpu context: x0 ~ spsr_el3 */
+	memcpy(&fiq_pt_regs, reg_base, 8 * 31);
+
+	/* copy pstate: spsr_el3 */
+	memcpy(&fiq_pt_regs.pstate, reg_base + 0x110, 8);
+	fiq_pt_regs.sp = sp_el1;
+
+	/* copy pc: elr_el3 */
+	memcpy(&fiq_pt_regs.pc, reg_base + 0x118, 8);
+#else
+	if (firmware_64_32bit == FIRMWARE_ATF_64BIT) {
+		/*
+		 * 64-bit ATF + 32-bit kernel
+		 */
+		fiq_pt_regs.ARM_r0 = gp_regs->x0;
+		fiq_pt_regs.ARM_r1 = gp_regs->x1;
+		fiq_pt_regs.ARM_r2 = gp_regs->x2;
+		fiq_pt_regs.ARM_r3 = gp_regs->x3;
+		fiq_pt_regs.ARM_r4 = gp_regs->x4;
+		fiq_pt_regs.ARM_r5 = gp_regs->x5;
+		fiq_pt_regs.ARM_r6 = gp_regs->x6;
+		fiq_pt_regs.ARM_r7 = gp_regs->x7;
+		fiq_pt_regs.ARM_r8 = gp_regs->x8;
+		fiq_pt_regs.ARM_r9 = gp_regs->x9;
+		fiq_pt_regs.ARM_r10 = gp_regs->x10;
+		fiq_pt_regs.ARM_fp = gp_regs->x11;
+		fiq_pt_regs.ARM_ip = gp_regs->x12;
+		fiq_pt_regs.ARM_sp = gp_regs->x19;	/* aarch32 svc_r13 */
+		fiq_pt_regs.ARM_lr = gp_regs->x18;	/* aarch32 svc_r14 */
+		fiq_pt_regs.ARM_cpsr = gp_regs->spsr_el3;
+		fiq_pt_regs.ARM_pc = gp_regs->elr_el3;
+	} else {
+		/*
+		 * 32-bit tee firmware + 32-bit kernel
+		 */
+		fiq_pt_regs.ARM_r0 = nsec_ctx->r0;
+		fiq_pt_regs.ARM_r1 = nsec_ctx->r1;
+		fiq_pt_regs.ARM_r2 = nsec_ctx->r2;
+		fiq_pt_regs.ARM_r3 = nsec_ctx->r3;
+		fiq_pt_regs.ARM_r4 = nsec_ctx->r4;
+		fiq_pt_regs.ARM_r5 = nsec_ctx->r5;
+		fiq_pt_regs.ARM_r6 = nsec_ctx->r6;
+		fiq_pt_regs.ARM_r7 = nsec_ctx->r7;
+		fiq_pt_regs.ARM_r8 = nsec_ctx->r8;
+		fiq_pt_regs.ARM_r9 = nsec_ctx->r9;
+		fiq_pt_regs.ARM_r10 = nsec_ctx->r10;
+		fiq_pt_regs.ARM_fp = nsec_ctx->r11;
+		fiq_pt_regs.ARM_ip = nsec_ctx->r12;
+		fiq_pt_regs.ARM_sp = nsec_ctx->svc_sp;
+		fiq_pt_regs.ARM_lr = nsec_ctx->svc_lr;
+		fiq_pt_regs.ARM_cpsr = nsec_ctx->mon_spsr;
+
+		/*
+		 * 'nsec_ctx->mon_lr' is not the fiq break point's PC, because it will
+		 * be override as 'psci_fiq_debugger_uart_irq_tf_cb' for optee-os to
+		 * jump to fiq_debugger handler.
+		 *
+		 * As 'nsec_ctx->und_lr' is not used for kernel, so optee-os uses it to
+		 * deliver fiq break point's PC.
+		 *
+		 */
+		fiq_pt_regs.ARM_pc = nsec_ctx->und_lr;
+	}
+#endif
+
+	return fiq_pt_regs;
+}
+
+static void sip_fiq_debugger_uart_irq_tf_cb(unsigned long sp_el1,
+					    unsigned long offset,
+					    unsigned long cpu)
+{
+	struct pt_regs fiq_pt_regs;
+	char *cpu_context;
+
+	/* calling fiq handler */
+	if (ft_fiq_mem_base) {
+		cpu_context = (char *)ft_fiq_mem_base + offset;
+		fiq_pt_regs = sip_fiq_debugger_get_pt_regs(cpu_context, sp_el1);
+		sip_fiq_debugger_uart_irq_tf(fiq_pt_regs, cpu);
+	}
+
+	/* fiq handler done, return to EL3(then EL3 return to EL1 entry) */
+	__invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, UARTDBG_CFG_OSHDL_TO_OS);
+}
+
+int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, void *callback_fn)
+{
+	struct arm_smccc_res res;
+
+	/* init fiq debugger callback */
+	sip_fiq_debugger_uart_irq_tf = callback_fn;
+	res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, irq_id,
+				  (unsigned long)sip_fiq_debugger_uart_irq_tf_cb,
+				  UARTDBG_CFG_INIT);
+	if (IS_SIP_ERROR(res.a0)) {
+		pr_err("%s error: %d\n", __func__, (int)res.a0);
+		return res.a0;
+	}
+
+	/* share memory ioremap */
+	if (!ft_fiq_mem_base) {
+		ft_fiq_mem_phy = res.a1;
+		ft_fiq_mem_base = sip_map(ft_fiq_mem_phy,
+					  FIQ_UARTDBG_SHARE_MEM_SIZE);
+		if (!ft_fiq_mem_base) {
+			pr_err("%s: share memory ioremap failed\n", __func__);
+			return -ENOMEM;
+		}
+	}
+
+	fiq_sip_enabled = 1;
+
+	return SIP_RET_SUCCESS;
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_uart_irq_tf_init);
+
+static ulong cpu_logical_map_mpidr(u32 cpu)
+{
+#ifdef MODULE
+	/* Empirically, local "cpu_logical_map()" for rockchip platforms */
+	ulong mpidr = 0x00;
+
+	if (cpu < 4)
+		/* 0x00, 0x01, 0x02, 0x03 */
+		mpidr = cpu;
+	else if (cpu < 8)
+		/* 0x100, 0x101, 0x102, 0x103 */
+		mpidr = 0x100 | (cpu - 4);
+	else
+		pr_err("Unsupported map cpu: %d\n", cpu);
+
+	return mpidr;
+#else
+	return cpu_logical_map(cpu);
+#endif
+}
+
+int sip_fiq_debugger_switch_cpu(u32 cpu)
+{
+	struct arm_smccc_res res;
+
+	fiq_target_cpu = cpu;
+	res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, cpu_logical_map_mpidr(cpu),
+				  0, UARTDBG_CFG_OSHDL_CPUSW);
+	return res.a0;
+}
+
+int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_SWITCH_CPU,
+				  cur_cpu, target_cpu, flag);
+	return res.a0;
+}
+
+int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_SDEI_FIQ_DBG_GET_EVENT_ID,
+				  0, 0, 0);
+	*fiq = res.a1;
+	*sw_cpu = res.a2;
+	if (flag)
+		*flag = res.a3;
+
+	return res.a0;
+}
+
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_switch_cpu);
+
+void sip_fiq_debugger_enable_debug(bool enable)
+{
+	unsigned long val;
+
+	val = enable ? UARTDBG_CFG_OSHDL_DEBUG_ENABLE :
+		       UARTDBG_CFG_OSHDL_DEBUG_DISABLE;
+
+	__invoke_sip_fn_smc(SIP_UARTDBG_FN, 0, 0, val);
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_debug);
+
+int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate)
+{
+	struct arm_smccc_res res;
+
+	res = __invoke_sip_fn_smc(SIP_UARTDBG_FN, port_phyaddr, baudrate,
+				  UARTDBG_CFG_PRINT_PORT);
+	return res.a0;
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_set_print_port);
+
+int sip_fiq_debugger_request_share_memory(void)
+{
+	struct arm_smccc_res res;
+
+	/* request page share memory */
+	res = sip_smc_request_share_mem(FIQ_UARTDBG_PAGE_NUMS,
+					SHARE_PAGE_TYPE_UARTDBG);
+	if (IS_SIP_ERROR(res.a0))
+		return res.a0;
+
+	return SIP_RET_SUCCESS;
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_request_share_memory);
+
+int sip_fiq_debugger_get_target_cpu(void)
+{
+	return fiq_target_cpu;
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_get_target_cpu);
+
+void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu)
+{
+	u32 en;
+
+	fiq_target_cpu = tgt_cpu;
+	en = enable ? UARTDBG_CFG_FIQ_ENABEL : UARTDBG_CFG_FIQ_DISABEL;
+	__invoke_sip_fn_smc(SIP_UARTDBG_FN, tgt_cpu, 0, en);
+}
+EXPORT_SYMBOL_GPL(sip_fiq_debugger_enable_fiq);
+
+/******************************************************************************/
+#ifdef CONFIG_ARM
+static __init int sip_firmware_init(void)
+{
+	struct arm_smccc_res res;
+
+	if (!psci_smp_available())
+		return 0;
+
+	/*
+	 * OP-TEE works on kernel 3.10 and 4.4 and we have different sip
+	 * implement. We should tell OP-TEE the current rockchip sip version.
+	 */
+	res = __invoke_sip_fn_smc(SIP_SIP_VERSION, SIP_IMPLEMENT_V2,
+				  SECURE_REG_WR, 0);
+	if (IS_SIP_ERROR(res.a0))
+		pr_err("%s: set rockchip sip version v2 failed\n", __func__);
+
+	/*
+	 * Currently, we support:
+	 *
+	 *	1. 64-bit ATF + 64-bit kernel;
+	 *	2. 64-bit ATF + 32-bit kernel;
+	 *	3. 32-bit TEE + 32-bit kernel;
+	 *
+	 * We need to detect which case of above and record in firmware_64_32bit
+	 * We get info from cpuid and compare with all supported ARMv7 cpu.
+	 */
+	switch (read_cpuid_part()) {
+	case ARM_CPU_PART_CORTEX_A7:
+	case ARM_CPU_PART_CORTEX_A8:
+	case ARM_CPU_PART_CORTEX_A9:
+	case ARM_CPU_PART_CORTEX_A12:
+	case ARM_CPU_PART_CORTEX_A15:
+	case ARM_CPU_PART_CORTEX_A17:
+		firmware_64_32bit = FIRMWARE_TEE_32BIT;
+		break;
+	default:
+		firmware_64_32bit = FIRMWARE_ATF_64BIT;
+		break;
+	}
+
+	return 0;
+}
+arch_initcall(sip_firmware_init);
+#endif
+
+MODULE_DESCRIPTION("Rockchip SIP Call");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index ed85d0cee..4c15feed2 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -185,6 +185,12 @@ config DRM_LOAD_EDID_FIRMWARE
 
 source "drivers/gpu/drm/display/Kconfig"
 
+config DRM_EPD_HELPER
+	tristate
+	depends on DRM
+	help
+	  Choose this if you need the EPD (LUT, etc.) helper functions
+
 config DRM_TTM
 	tristate
 	depends on DRM && MMU
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index d26191717..9084bc106 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -121,6 +121,12 @@ drm_dma_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_dma.o
 drm_dma_helper-$(CONFIG_DRM_KMS_HELPER) += drm_fb_dma_helper.o
 obj-$(CONFIG_DRM_GEM_DMA_HELPER) += drm_dma_helper.o
 
+obj-$(CONFIG_DRM_EPD_HELPER) += drm_epd_helper.o
+
+# drm_cma_helper-y := drm_gem_cma_helper.o
+# drm_cma_helper-$(CONFIG_DRM_KMS_HELPER) += drm_fb_cma_helper.o
+# obj-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_cma_helper.o
+
 drm_shmem_helper-y := drm_gem_shmem_helper.o
 drm_shmem_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_shmem.o
 obj-$(CONFIG_DRM_GEM_SHMEM_HELPER) += drm_shmem_helper.o
@@ -188,7 +194,7 @@ obj-$(CONFIG_DRM_VKMS)	+= vkms/
 obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
 obj-$(CONFIG_DRM_NOVA) += nova/
 obj-$(CONFIG_DRM_EXYNOS) +=exynos/
-obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/
+obj-y			+=rockchip/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index a82d741e6..45fd0adb3 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -107,6 +107,36 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 }
 EXPORT_SYMBOL(drm_client_init);
 
+/**
+ * drm_client_init_from_id - Initialise a DRM client
+ * @minor_id: DRM minor id
+ * @client: DRM client
+ * @name: Client name
+ * @funcs: DRM client functions (optional)
+ *
+ * This function looks up the drm_device using the minor id and initializes the
+ * client using drm_client_init().
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+			    const char *name, const struct drm_client_funcs *funcs)
+{
+	struct drm_minor *minor;
+	int ret;
+
+	minor = drm_minor_acquire(&drm_minors_xa, minor_id);
+	if (IS_ERR(minor))
+		return PTR_ERR(minor);
+
+	ret = drm_client_init(minor->dev, client, name, funcs);
+	drm_minor_release(minor);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_init_from_id);
+
 /**
  * drm_client_register - Register client
  * @client: DRM client
@@ -120,12 +150,20 @@ EXPORT_SYMBOL(drm_client_init);
  * Registering a client generates a hotplug event that allows the client
  * to set up its display from pre-existing outputs. The client must have
  * initialized its state to able to handle the hotplug event successfully.
+ *
+ * Returns:
+ * True if the client has been registered, false if the DRM device has already
+ * been unregistered.
  */
-void drm_client_register(struct drm_client_dev *client)
+bool drm_client_register(struct drm_client_dev *client)
 {
 	struct drm_device *dev = client->dev;
+	int idx;
 	int ret;
 
+	if (!drm_dev_enter(client->dev, &idx))
+		return false;
+
 	mutex_lock(&dev->clientlist_mutex);
 	list_add(&client->list, &dev->clientlist);
 
@@ -145,6 +183,10 @@ void drm_client_register(struct drm_client_dev *client)
 			drm_dbg_kms(dev, "client hotplug ret=%d\n", ret);
 	}
 	mutex_unlock(&dev->clientlist_mutex);
+
+	drm_dev_exit(idx);
+
+	return true;
 }
 EXPORT_SYMBOL(drm_client_register);
 
diff --git a/drivers/gpu/drm/drm_client_modeset.c b/drivers/gpu/drm/drm_client_modeset.c
index 4a72f323e..c9f069461 100644
--- a/drivers/gpu/drm/drm_client_modeset.c
+++ b/drivers/gpu/drm/drm_client_modeset.c
@@ -89,6 +89,10 @@ static void drm_client_modeset_release(struct drm_client_dev *client)
 		}
 		modeset->num_connectors = 0;
 	}
+
+	kfree(client->properties);
+	client->properties = NULL;
+	client->num_properties = 0;
 }
 
 void drm_client_modeset_free(struct drm_client_dev *client)
@@ -944,6 +948,130 @@ int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width,
 }
 EXPORT_SYMBOL(drm_client_modeset_probe);
 
+/**
+ * drm_client_modeset_set() - Set modeset configuration
+ * @client: DRM client
+ * @connector: Connector
+ * @mode: Display mode
+ * @fb: Framebuffer
+ *
+ * This function releases any current modeset info, including properties, and
+ * sets the new modeset in the client's modeset array.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb)
+{
+	struct drm_mode_set *modeset;
+	int ret = -ENOENT;
+
+	mutex_lock(&client->modeset_mutex);
+
+	drm_client_modeset_release(client);
+
+	drm_client_for_each_modeset(modeset, client) {
+		if (!connector_has_possible_crtc(connector, modeset->crtc))
+			continue;
+
+		modeset->mode = drm_mode_duplicate(client->dev, mode);
+		if (!modeset->mode) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		drm_mode_set_crtcinfo(modeset->mode, CRTC_INTERLACE_HALVE_V);
+
+		drm_connector_get(connector);
+		modeset->connectors[modeset->num_connectors++] = connector;
+
+		modeset->fb = fb;
+		ret = 0;
+		break;
+	}
+
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set);
+
+/**
+ * drm_client_modeset_set_property() - Set a property on the current configuration
+ * @client: DRM client
+ * @obj: DRM Mode Object
+ * @prop: DRM Property
+ * @value: Property value
+ *
+ * Note: Currently only implemented for atomic drivers.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value)
+{
+	struct drm_client_property *properties;
+	int ret = 0;
+
+	if (!prop)
+		return -EINVAL;
+
+	if (!drm_drv_uses_atomic_modeset(client->dev))
+		return -EOPNOTSUPP;
+
+	mutex_lock(&client->modeset_mutex);
+
+	properties = krealloc(client->properties,
+			      (client->num_properties + 1) * sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	properties[client->num_properties].obj = obj;
+	properties[client->num_properties].prop = prop;
+	properties[client->num_properties].value = value;
+	client->properties = properties;
+	client->num_properties++;
+unlock:
+	mutex_unlock(&client->modeset_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_modeset_set_property);
+
+/**
+ * drm_client_modeset_set_rotation() - Set rotation on the current configuration
+ * @client: DRM client
+ * @value: Rotation value
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value)
+{
+	struct drm_plane *plane = NULL;
+	struct drm_mode_set *modeset;
+
+	mutex_lock(&client->modeset_mutex);
+	drm_client_for_each_modeset(modeset, client) {
+		if (modeset->mode) {
+			plane = modeset->crtc->primary;
+			break;
+		}
+	}
+	mutex_unlock(&client->modeset_mutex);
+
+	if (!plane)
+		return -ENOENT;
+
+	return drm_client_modeset_set_property(client, &plane->base,
+					       plane->rotation_property, value);
+}
+EXPORT_SYMBOL(drm_client_modeset_set_rotation);
+
 /**
  * drm_client_rotation() - Check the initial rotation value
  * @modeset: DRM modeset
@@ -1038,6 +1166,7 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 	struct drm_atomic_state *state;
 	struct drm_modeset_acquire_ctx ctx;
 	struct drm_mode_set *mode_set;
+	unsigned int i;
 	int ret;
 
 	drm_modeset_acquire_init(&ctx, 0);
@@ -1098,6 +1227,14 @@ static int drm_client_modeset_commit_atomic(struct drm_client_dev *client, bool
 		}
 	}
 
+	for (i = 0; i < client->num_properties; i++) {
+		struct drm_client_property *prop = &client->properties[i];
+
+		ret = drm_atomic_set_property(state, NULL, prop->obj, prop->prop, prop->value, false);
+		if (ret)
+			goto out_state;
+	}
+
 	if (check)
 		ret = drm_atomic_check_only(state);
 	else
@@ -1338,6 +1475,26 @@ int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned i
 }
 EXPORT_SYMBOL(drm_client_modeset_wait_for_vblank);
 
+/**
+ * drm_client_modeset_disable() - Disable all outputs
+ * @client: DRM client
+ *
+ * This function disables all outputs by first clearing the modeset array and
+ * then committing the empty modesets.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_modeset_disable(struct drm_client_dev *client)
+{
+	mutex_lock(&client->modeset_mutex);
+	drm_client_modeset_release(client);
+	mutex_unlock(&client->modeset_mutex);
+
+	return drm_client_modeset_commit(client);
+}
+EXPORT_SYMBOL(drm_client_modeset_disable);
+
 #ifdef CONFIG_DRM_KUNIT_TEST
 #include "tests/drm_client_modeset_test.c"
 #endif
diff --git a/drivers/gpu/drm/drm_epd_helper.c b/drivers/gpu/drm/drm_epd_helper.c
new file mode 100644
index 000000000..47a5c8257
--- /dev/null
+++ b/drivers/gpu/drm/drm_epd_helper.c
@@ -0,0 +1,711 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * Electrophoretic Display Helper Library
+ *
+ * Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_epd_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_print.h>
+
+/**
+ * DOC: overview
+ *
+ * This library provides functions for working with the lookup tables (LUTs)
+ * used by electrophoretic displays (EPDs). It fills in a LUT buffer based on
+ * the selected waveform, the panel temperature, and the buffer format needed
+ * by the driver.
+ */
+
+struct pvi_wbf_offset {
+	u8			b[3];
+};
+
+struct pvi_wbf_pointer {
+	struct pvi_wbf_offset	offset;
+	u8			checksum;
+};
+
+struct pvi_wbf_file_header {
+	__le32			checksum;		// 0x00
+	__le32			file_size;		// 0x04
+	__le32			serial;			// 0x08
+	u8			run_type;		// 0x0c
+	u8			fpl_platform;		// 0x0d
+	__le16			fpl_lot;		// 0x0e
+	u8			mode_version;		// 0x10
+	u8			wf_version;		// 0x11
+	u8			wf_subversion;		// 0x12
+	u8			wf_type;		// 0x13
+	u8			panel_size;		// 0x14
+	u8			amepd_part_number;	// 0x15
+	u8			wf_rev;			// 0x16
+	u8			frame_rate_bcd;		// 0x17
+	u8			frame_rate_hex;		// 0x18
+	u8			vcom_offset;		// 0x19
+	u8			unknown[2];		// 0x1a
+	struct pvi_wbf_offset	xwia;			// 0x1c
+	u8			cs1;			// 0x1f
+	struct pvi_wbf_offset	wmta;			// 0x20
+	u8			fvsn;			// 0x23
+	u8			luts;			// 0x24
+	u8			mode_count;		// 0x25
+	u8			temp_range_count;	// 0x26
+	u8			advanced_wf_flags;	// 0x27
+	u8			eb;			// 0x28
+	u8			sb;			// 0x29
+	u8			reserved[5];		// 0x2a
+	u8			cs2;			// 0x2f
+	u8			temp_range_table[];	// 0x30
+};
+static_assert(sizeof(struct pvi_wbf_file_header) == 0x30);
+
+struct pvi_wbf_mode_info {
+	u8			versions[2];
+	u8			format;
+	u8			modes[DRM_EPD_WF_MAX];
+};
+
+static const struct pvi_wbf_mode_info pvi_wbf_mode_info_table[] = {
+	{
+		.versions = {
+			0x09,
+		},
+		.format = DRM_EPD_LUT_4BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 1,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 3,
+			[DRM_EPD_WF_GLD16]	= 3,
+			[DRM_EPD_WF_A2]		= 4,
+			[DRM_EPD_WF_GCC16]	= 3,
+		},
+	},
+	{
+		.versions = {
+			0x12,
+		},
+		.format = DRM_EPD_LUT_4BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 7,
+			[DRM_EPD_WF_GC16]	= 3,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 5,
+			[DRM_EPD_WF_GLD16]	= 6,
+			[DRM_EPD_WF_A2]		= 4,
+			[DRM_EPD_WF_GCC16]	= 5,
+		},
+	},
+	{
+		.versions = {
+			0x16,
+		},
+		.format = DRM_EPD_LUT_5BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 1,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 4,
+			[DRM_EPD_WF_GLD16]	= 4,
+			[DRM_EPD_WF_A2]		= 6,
+			[DRM_EPD_WF_GCC16]	= 5,
+		},
+	},
+	{
+		.versions = {
+			0x18,
+			0x20,
+		},
+		.format = DRM_EPD_LUT_5BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 1,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 4,
+			[DRM_EPD_WF_GLD16]	= 5,
+			[DRM_EPD_WF_A2]		= 6,
+			[DRM_EPD_WF_GCC16]	= 4,
+		},
+	},
+	{
+		.versions = {
+			0x19,
+			0x43,
+		},
+		.format = DRM_EPD_LUT_5BIT,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 7,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 4,
+			[DRM_EPD_WF_GLD16]	= 5,
+			[DRM_EPD_WF_A2]		= 6,
+			[DRM_EPD_WF_GCC16]	= 4,
+		},
+	},
+	{
+		.versions = {
+			0x23,
+		},
+		.format = DRM_EPD_LUT_4BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 5,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 3,
+			[DRM_EPD_WF_GLD16]	= 3,
+			[DRM_EPD_WF_A2]		= 4,
+			[DRM_EPD_WF_GCC16]	= 3,
+		},
+	},
+	{
+		.versions = {
+			0x54,
+		},
+		.format = DRM_EPD_LUT_4BIT_PACKED,
+		.modes = {
+			[DRM_EPD_WF_RESET]	= 0,
+			[DRM_EPD_WF_DU]		= 1,
+			[DRM_EPD_WF_DU4]	= 1,
+			[DRM_EPD_WF_GC16]	= 2,
+			[DRM_EPD_WF_GL16]	= 3,
+			[DRM_EPD_WF_GLR16]	= 4,
+			[DRM_EPD_WF_GLD16]	= 4,
+			[DRM_EPD_WF_A2]		= 5,
+			[DRM_EPD_WF_GCC16]	= 4,
+		},
+	},
+};
+
+static const void *pvi_wbf_apply_offset(const struct drm_epd_lut_file *file,
+				        const struct pvi_wbf_offset *offset)
+{
+	u32 bytes = offset->b[0] | offset->b[1] << 8 | offset->b[2] << 16;
+
+	if (bytes >= file->fw->size)
+		return NULL;
+
+	return (const void *)file->header + bytes;
+}
+
+static const void *pvi_wbf_dereference(const struct drm_epd_lut_file *file,
+				       const struct pvi_wbf_pointer *ptr)
+{
+	u8 sum = ptr->offset.b[0] + ptr->offset.b[1] + ptr->offset.b[2];
+
+	if (ptr->checksum != sum)
+		return NULL;
+
+	return pvi_wbf_apply_offset(file, &ptr->offset);
+}
+
+int pvi_wbf_get_mode_index(const struct drm_epd_lut_file *file,
+				  enum drm_epd_waveform waveform)
+{
+	if (waveform >= DRM_EPD_WF_MAX)
+		return -EINVAL;
+
+	return file->mode_info->modes[waveform];
+}
+EXPORT_SYMBOL(pvi_wbf_get_mode_index);
+
+static int pvi_wbf_get_mode_info(struct drm_epd_lut_file *file)
+{
+	u8 mode_version = file->header->mode_version;
+	const struct pvi_wbf_mode_info *mode_info;
+	int i, v;
+
+	for (i = 0; i < ARRAY_SIZE(pvi_wbf_mode_info_table); i++) {
+		mode_info = &pvi_wbf_mode_info_table[i];
+
+		for (v = 0; v < ARRAY_SIZE(mode_info->versions); v++) {
+			if (mode_info->versions[v] == mode_version) {
+				file->mode_info = mode_info;
+				return 0;
+			}
+		}
+	}
+
+	drm_err(file->dev, "Unknown PVI waveform version 0x%02x\n",
+		mode_version);
+
+	return -ENOTSUPP;
+}
+
+static int pvi_wbf_get_temp_index(const struct drm_epd_lut_file *file,
+				  int temperature)
+{
+	const struct pvi_wbf_file_header *header = file->header;
+	int i;
+
+	for (i = 0; i < header->temp_range_count; i++)
+		if (temperature < header->temp_range_table[i])
+			return i - 1;
+
+	return header->temp_range_count - 1;
+}
+
+static int pvi_wbf_validate_header(struct drm_epd_lut_file *file)
+{
+	const struct pvi_wbf_file_header *header = file->header;
+	int ret;
+
+	if (le32_to_cpu(header->file_size) > file->fw->size)
+		return -EINVAL;
+
+	ret = pvi_wbf_get_mode_info(file);
+	if (ret)
+		return ret;
+
+	drm_info(file->dev, "Loaded %d-bit PVI waveform version 0x%02x\n",
+		 file->mode_info->format == DRM_EPD_LUT_5BIT_PACKED ? 5 : 4,
+		 header->mode_version);
+
+	return 0;
+}
+
+static void drm_epd_lut_file_free(struct drm_device *dev, void *res)
+{
+	struct drm_epd_lut_file *file = res;
+
+	release_firmware(file->fw);
+}
+
+/**
+ * drmm_epd_lut_file_init - Initialize a managed EPD LUT file
+ *
+ * @dev: The DRM device owning this LUT file
+ * @file: The LUT file to initialize
+ * @file_name: The filesystem name of the LUT file
+ *
+ * Return: negative errno on failure, 0 otherwise
+ */
+int drmm_epd_lut_file_init(struct drm_device *dev,
+			   struct drm_epd_lut_file *file,
+			   const char *file_name)
+{
+	int ret;
+
+	ret = request_firmware(&file->fw, file_name, dev->dev);
+	if (ret)
+		return ret;
+
+	ret = drmm_add_action_or_reset(dev, drm_epd_lut_file_free, file);
+	if (ret)
+		return ret;
+
+	file->dev = dev;
+	file->header = (const void *)file->fw->data;
+
+	ret = pvi_wbf_validate_header(file);
+	if (ret)
+		return ret;
+
+	/* Only 5-bit waveform files are supported by drm_epd_lut_convert.
+	 * Due to the structure of the input file, we need to fully decompose the
+	 * data and cannot retain the packed state*/
+	if (file->mode_info->format != DRM_EPD_LUT_5BIT)
+		return -ENOTSUPP;
+
+	return 0;
+}
+EXPORT_SYMBOL(drmm_epd_lut_file_init);
+
+/**
+ * drm_epd_lut_size_shift - Get the size of a LUT phase in power-of-2 bytes
+ *
+ * Note: the bit size referred to the formats refers to the number of
+ * transitions of a pixel. For example, the 5 bit lut indicates that we have
+ * 2^5 possible transitions, which in this case means that there are 32
+ * different states.
+ * 4 bit corresponds to 16 grayscales
+ *
+ * @format: One of the LUT buffer formats
+ *
+ * Return: buffer size shift amount
+ */
+static int drm_epd_lut_size_shift(enum drm_epd_lut_format format)
+{
+	switch (format) {
+	case DRM_EPD_LUT_4BIT:
+		/* (4 bits/pixel)^2 / 1 pixel/byte */
+		return 4 + 4;
+	case DRM_EPD_LUT_4BIT_PACKED:
+		/* (4 bits/pixel)^2 / 4 pixels/byte */
+		return 4 + 4 - 2;
+	case DRM_EPD_LUT_5BIT:
+		/* (5 bits/pixel)^2 / 1 pixel/byte */
+		return 5 + 5;
+	case DRM_EPD_LUT_5BIT_PACKED:
+		/* (5 bits/pixel)^2 / 4 pixels/byte */
+		return 5 + 5 - 2;
+	}
+
+	unreachable();
+}
+
+/* decode the lut into a DRM_EPD_LUT_5BIT structured buffer */
+static int pvi_wbf_decode_lut(struct drm_epd_lut *lut, const u8 *lut_data)
+{
+	unsigned int copies, max_bytes, size_shift, state, total_bytes;
+	struct drm_device *dev = lut->file->dev;
+	const u8 *in = lut_data;
+	u8 *out = lut->buf;
+	u8 token;
+
+	u32 x=0;
+	u32 y=0;
+	int frame=0;
+	// note: we hardcode 32 transitions (5-bit) here
+	int maxpic = 0x20;
+	/* Aug 15 11:26:37 pinenote kernel: ebc: size_shift: 10 max_bytes: 262144 */
+	/* Aug 15 11:26:37 pinenote kernel: ebc: total_bytes: 111616 */
+
+	size_shift = drm_epd_lut_size_shift(lut->file->mode_info->format);
+	max_bytes = lut->max_phases << size_shift;
+	total_bytes = 0;
+	state = 1;
+
+	/* Read tokens until reaching the end-of-input marker. */
+	while ((token = *in++) != 0xff) {
+		/* Special handling for the state switch token. */
+		if (token == 0xfc) {
+			state = !state;
+			token = *in++;
+		}
+
+		/*
+		 * State 0 is a sequence of data bytes.
+		 * State 1 is a sequence of [data byte, extra copies] pairs.
+		 */
+		copies = 1 + (state ? *in++ : 0);
+
+		total_bytes += (copies * 4);
+		if (total_bytes > max_bytes) {
+			drm_err(dev, "LUT contains too many phases\n");
+			lut->num_phases = 0;
+			return -EILSEQ;
+		}
+
+		// due to the values being stored in transposed form, we need to
+		// decompose each byte separately
+		while (copies--){
+			*(out + ((frame * 0x400) + (x + 0) * 0x20 + y)) = (token >> 0) & 0x3;
+			*(out + ((frame * 0x400) + (x + 1) * 0x20 + y)) = (token >> 2) & 0x3;
+			*(out + ((frame * 0x400) + (x + 2) * 0x20 + y)) = (token >> 4) & 0x3;
+			*(out + ((frame * 0x400) + (x + 3) * 0x20 + y)) = (token >> 6) & 0x3;
+
+			x += 4;
+			if (x >= maxpic){
+				x = 0;
+				y++;
+				if (y >= maxpic) {
+					y = 0;
+					frame++;
+				}
+			}
+		}
+	}
+
+	lut->num_phases = total_bytes >> size_shift;
+	if (total_bytes != lut->num_phases << size_shift) {
+		drm_err(dev, "LUT contains a partial phase\n");
+		lut->num_phases = 0;
+		return -EILSEQ;
+	}
+
+	drm_dbg_core(dev, "LUT contains %d phases (%ld => %ld bytes)\n",
+		     lut->num_phases, in - lut_data, out - lut->buf);
+
+	return 0;
+}
+
+static int pvi_wbf_get_lut(struct drm_epd_lut *lut,
+			   int mode_index, int temp_index)
+{
+	const struct pvi_wbf_pointer *mode_table, *temp_table;
+	const struct drm_epd_lut_file *file = lut->file;
+	const u8 *lut_data;
+	int ret;
+
+	mode_table = pvi_wbf_apply_offset(file, &file->header->wmta);
+	if (!mode_table)
+		return -EFAULT;
+
+	temp_table = pvi_wbf_dereference(file, &mode_table[mode_index]);
+	if (!temp_table)
+		return -EFAULT;
+
+	lut_data = pvi_wbf_dereference(file, &temp_table[temp_index]);
+	if (!lut_data)
+		return -EFAULT;
+
+	ret = pvi_wbf_decode_lut(lut, lut_data);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* we always convert from DRM_EPD_LUT_5BIT */
+static void drm_epd_lut_convert(const struct drm_epd_lut *lut)
+{
+	enum drm_epd_lut_format from = lut->file->mode_info->format;
+	enum drm_epd_lut_format to = lut->format;
+	u8 *buf = lut->buf;
+	size_t x, y;
+	unsigned int counter=0;
+
+	if (to == from)
+		return;
+
+	switch (to) {
+	case DRM_EPD_LUT_4BIT:
+		for (y = 0; y < 16 * lut->num_phases; ++y) {
+			for (x = 8; x--;) {
+				u8 byte = buf[16 * y + x];
+
+				buf[16 * y + 2 * x + 0] = (byte >> 0) & 0x03;
+				buf[16 * y + 2 * x + 1] = (byte >> 4) & 0x03;
+			}
+		}
+		for (; y < 16 * lut->max_phases; ++y) {
+			for (x = 8; x--;) {
+				buf[16 * y + 2 * x + 0] = 0;
+				buf[16 * y + 2 * x + 1] = 0;
+			}
+		}
+		break;
+	case DRM_EPD_LUT_4BIT_PACKED:
+		counter = 0;
+		for (y=0; y < 1024 * lut->num_phases; y=y+64){
+			for (x=0; x<32; x=x+8){
+				int index;
+				u8 byte1, byte2, byte3, byte4;
+
+				index = y + x;
+				byte1 = buf[index];
+				byte2 = buf[index + 2];
+				byte3 = buf[index + 4];
+				byte4 = buf[index + 6];
+
+				buf[counter] = byte1 | (byte2 << 2) |
+					(byte3 << 4) | (byte4 << 6);
+				counter++;
+			}
+		}
+		// clear the rest of the lut table
+		for (y=counter; y < 1024 * lut->max_phases; y++){
+			buf[y] = 0;
+		}
+
+		break;
+	case DRM_EPD_LUT_5BIT:
+		memset(buf + 256 * lut->num_phases, 0,
+		       256 * (lut->max_phases - lut->num_phases));
+		for (x = 256 * lut->num_phases; x--;) {
+			u8 byte = buf[x];
+
+			buf[4 * x + 0] = (byte >> 0) & 0x03;
+			buf[4 * x + 1] = (byte >> 2) & 0x03;
+			buf[4 * x + 2] = (byte >> 4) & 0x03;
+			buf[4 * x + 3] = (byte >> 6) & 0x03;
+		}
+		break;
+	case DRM_EPD_LUT_5BIT_PACKED:
+		/* Nothing to do. */
+		break;
+	}
+}
+
+static int drm_epd_lut_update(struct drm_epd_lut *lut,
+			      int mode_index, int temp_index)
+{
+	int ret;
+
+	ret = pvi_wbf_get_lut(lut, mode_index, temp_index);
+	if (ret)
+		return ret;
+
+	drm_epd_lut_convert(lut);
+
+	return 0;
+}
+
+/**
+ * drm_epd_lut_set_temperature - Update the LUT due to panel temperature change
+ *
+ * @lut: The LUT structure
+ * @temperateure: The current panel temperature in degrees Celsius
+ *
+ * Return: negative errno on failure, 1 if LUT was changed, 0 otherwise
+ */
+int drm_epd_lut_set_temperature(struct drm_epd_lut *lut,
+				int temperature)
+{
+	int temp_index;
+	int ret;
+
+	temp_index = pvi_wbf_get_temp_index(lut->file, temperature);
+	if (temp_index < 0)
+		return -ENOENT;
+
+	if (temp_index == lut->temp_index)
+		return 0;
+
+	drm_dbg_core(lut->file->dev, "LUT temperature changed (%d)\n",
+		     temperature);
+
+	ret = drm_epd_lut_update(lut, lut->mode_index, temp_index);
+	if (ret)
+		return ret;
+
+	lut->temp_index = temp_index;
+
+	return 1;
+}
+EXPORT_SYMBOL(drm_epd_lut_set_temperature);
+
+/**
+ * drm_epd_lut_set_waveform - Update the LUT due to waveform selection change
+ *
+ * @lut: The LUT structure
+ * @waveform: The desired waveform
+ *
+ * Return: negative errno on failure, 1 if LUT was changed, 0 otherwise
+ */
+int drm_epd_lut_set_waveform(struct drm_epd_lut *lut,
+			     enum drm_epd_waveform waveform)
+{
+	int mode_index;
+	int ret;
+
+	mode_index = pvi_wbf_get_mode_index(lut->file, waveform);
+	if (mode_index < 0)
+		return -ENOENT;
+
+	if (mode_index == lut->mode_index)
+		return 0;
+
+	drm_dbg_core(lut->file->dev, "LUT waveform changed (%d)\n",
+		     waveform);
+
+	ret = drm_epd_lut_update(lut, mode_index, lut->temp_index);
+	if (ret)
+		return ret;
+
+	lut->mode_index = mode_index;
+
+	return 1;
+}
+EXPORT_SYMBOL(drm_epd_lut_set_waveform);
+
+static void drm_epd_lut_free(struct drm_device *dev, void *res)
+{
+	struct drm_epd_lut *lut = res;
+
+	vfree(lut->buf);
+}
+
+/**
+ * drmm_epd_lut_init - Initialize a managed EPD LUT from a LUT file
+ *
+ * @dev: The DRM device owning this LUT
+ * @lut: The LUT to initialize
+ * @file_name: The file name of the waveform firmware
+ * @format: The LUT buffer format needed by the driver
+ * @max_phases: The maximum number of waveform phases supported by the driver
+ *
+ * Return: negative errno on failure, 0 otherwise
+ */
+int drmm_epd_lut_init(struct drm_epd_lut_file *file,
+		      struct drm_epd_lut *lut,
+		      enum drm_epd_lut_format format,
+		      unsigned int max_phases)
+{
+	size_t max_order;
+	int ret;
+
+	/* Allocate a buffer large enough to convert the LUT in place. */
+	max_order = max(
+			drm_epd_lut_size_shift(file->mode_info->format),
+			drm_epd_lut_size_shift(format)
+		);
+    /*
+     * due to the x/y switch we need to first convert to DRM_EPD_LUT_5BIT
+     * */
+    /* max_order = drm_epd_lut_size_shift(DRM_EPD_LUT_5BIT); */
+    // max_phases: 256
+	lut->buf = vmalloc(max_phases << max_order);
+	if (!lut->buf)
+		return -ENOMEM;
+	memset(lut->buf, 0, max_phases << max_order);
+
+	ret = drmm_add_action_or_reset(file->dev, drm_epd_lut_free, lut);
+	if (ret)
+		return ret;
+
+	lut->file	= file;
+	lut->format	= format;
+	lut->max_phases	= max_phases;
+	lut->num_phases	= 0;
+
+	/* Set sane initial values for the waveform and temperature. */
+	lut->mode_index = pvi_wbf_get_mode_index(file, DRM_EPD_WF_RESET);
+	if (lut->mode_index < 0)
+		return -ENOENT;
+
+	lut->temp_index = pvi_wbf_get_temp_index(file, 25);
+	if (lut->temp_index < 0)
+		return -ENOENT;
+
+	ret = drm_epd_lut_update(lut, lut->mode_index, lut->temp_index);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(drmm_epd_lut_init);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("DRM EPD waveform LUT library");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/gud/Kconfig b/drivers/gpu/drm/gud/Kconfig
index b4d213694..292bdd499 100644
--- a/drivers/gpu/drm/gud/Kconfig
+++ b/drivers/gpu/drm/gud/Kconfig
@@ -13,3 +13,9 @@ config DRM_GUD
 	  adapters.
 
 	  If M is selected the module will be called gud.
+
+config DRM_GUD_GADGET
+	tristate
+	depends on DRM
+	select LZ4_DECOMPRESS
+	select BACKLIGHT_CLASS_DEVICE
diff --git a/drivers/gpu/drm/gud/Makefile b/drivers/gpu/drm/gud/Makefile
index 68a1c622c..685003e79 100644
--- a/drivers/gpu/drm/gud/Makefile
+++ b/drivers/gpu/drm/gud/Makefile
@@ -2,3 +2,4 @@
 
 gud-y				:= gud_drv.o gud_pipe.o gud_connector.o
 obj-$(CONFIG_DRM_GUD)		+= gud.o
+obj-$(CONFIG_DRM_GUD_GADGET)	+= gud_gadget.o
diff --git a/drivers/gpu/drm/gud/gud_gadget.c b/drivers/gpu/drm/gud/gud_gadget.c
new file mode 100644
index 000000000..d82941bdf
--- /dev/null
+++ b/drivers/gpu/drm/gud/gud_gadget.c
@@ -0,0 +1,1386 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2020 Noralf Trønnes
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/lz4.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_mode_object.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+#include <drm/gud.h>
+
+#include "gud_internal.h"
+
+/*
+ * Concurrency:
+ * Calls into this module from f_gud_drm are serialized and run in process
+ * context except gud_gadget_ctrl_get() which is run in interrupt context.
+ *
+ * Termination:
+ * A DRM client can not release itself, only the DRM driver which resources the
+ * client uses can do that.
+ * This means that there are 2 paths to stop the gadget function:
+ * - Unregistering the DRM driver (module unload)
+ * - Disabling the USB gadget (configfs unbind)
+ *
+ * A use counter protects the gadget should the client go away. A kref is used
+ * to control the release of the gud_gadget structure shared by the 2 actors.
+ *
+ * Backlight:
+ * If there's a backlight device it's attached to the first connector.
+ */
+
+struct gud_gadget_connector {
+	struct drm_connector *connector;
+
+	const struct gud_property_req *properties;
+	unsigned int num_properties;
+	const char *tv_mode_enum_names;
+	unsigned int num_tv_mode_enum_names;
+	struct backlight_device *backlight;
+
+	spinlock_t lock; /* Protects the following members: */
+	enum drm_connector_status status;
+	unsigned int width_mm;
+	unsigned int height_mm;
+	struct gud_display_mode_req *modes;
+	unsigned int num_modes;
+	void *edid;
+	size_t edid_len;
+	bool changed;
+};
+
+struct gud_gadget {
+	struct kref refcount;
+	refcount_t usecnt;
+	struct drm_client_dev client;
+	struct backlight_device *backlight;
+	u8 compression;
+
+	const u8 *formats;
+	unsigned int format_count;
+
+	const struct gud_property_req *properties;
+	unsigned int num_properties;
+
+	struct gud_gadget_connector *connectors;
+	unsigned int connector_count;
+
+	struct drm_rect set_buffer_rect;
+	u32 set_buffer_length;
+	u8 set_buffer_compression;
+	u32 set_buffer_compressed_length;
+
+	struct drm_client_buffer *buffer;
+	struct drm_client_buffer *buffer_check;
+	u8 brightness;
+	bool check_ok;
+
+	size_t max_buffer_size;
+	void *work_buf;
+
+	struct work_struct flush_worker;
+	struct drm_rect flush_rect;
+};
+
+static int gud_gadget_probe_connector(struct gud_gadget_connector *gconn)
+{
+	struct drm_connector *connector = gconn->connector;
+	struct gud_display_mode_req *modes = NULL;
+	struct drm_device *drm = connector->dev;
+	struct drm_display_mode *mode;
+	void *edid_data, *edid = NULL;
+	unsigned int num_modes = 0;
+	size_t edid_len = 0;
+	unsigned long flags;
+	unsigned int i = 0;
+	int ret = 0;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	connector->funcs->fill_modes(connector,
+				     drm->mode_config.max_width,
+				     drm->mode_config.max_height);
+
+	list_for_each_entry(mode, &connector->modes, head)
+		num_modes++;
+
+	if (!num_modes)
+		goto update;
+
+	modes = kmalloc_array(num_modes, sizeof(*modes), GFP_KERNEL);
+	if (!modes) {
+		ret = -ENOMEM;
+		num_modes = 0;
+		goto update;
+	}
+
+	list_for_each_entry(mode, &connector->modes, head)
+		gud_from_display_mode(&modes[i++], mode);
+
+	if (!connector->edid_blob_ptr)
+		goto update;
+
+	edid_data = connector->edid_blob_ptr->data;
+	edid_len = connector->edid_blob_ptr->length;
+	if (!edid_data || !edid_len) {
+		edid_len = 0;
+		goto update;
+	}
+
+	edid = kmemdup(edid_data, edid_len, GFP_KERNEL);
+	if (!edid) {
+		ret = -ENOMEM;
+		edid_len = 0;
+	}
+
+update:
+	spin_lock_irqsave(&gconn->lock, flags);
+	if (gconn->status != connector->status || gconn->num_modes != num_modes ||
+	    gconn->edid_len != edid_len ||
+	    (gconn->modes && modes && memcmp(gconn->modes, modes, num_modes * sizeof(*modes))) ||
+	    (gconn->edid && edid && memcmp(gconn->edid, edid, edid_len)))
+		gconn->changed = true;
+	swap(gconn->modes, modes);
+	gconn->num_modes = num_modes;
+	swap(gconn->edid, edid);
+	gconn->edid_len = edid_len;
+	gconn->width_mm = connector->display_info.width_mm;
+	gconn->height_mm = connector->display_info.height_mm;
+	gconn->status = connector->status;
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	mutex_unlock(&drm->mode_config.mutex);
+
+	kfree(edid);
+	kfree(modes);
+
+	return ret;
+}
+
+static void gud_gadget_probe_connectors(struct gud_gadget *gdg)
+{
+	unsigned int i;
+
+	for (i = 0; i < gdg->connector_count; i++)
+		gud_gadget_probe_connector(&gdg->connectors[i]);
+}
+
+static bool gud_gadget_check_buffer(struct gud_gadget *gdg, struct drm_client_buffer *buffer,
+				    struct drm_display_mode *mode, u32 format)
+{
+	struct drm_framebuffer *fb;
+
+	if (!buffer)
+		return false;
+
+	fb = buffer->fb;
+
+	return fb->format->format == format &&
+	       fb->width == mode->hdisplay && fb->height == mode->vdisplay;
+}
+
+static bool gud_gadget_set_connector_property(struct drm_client_dev *client,
+					      struct drm_connector *connector,
+					      u16 prop, u64 val, int *ret)
+{
+	struct drm_mode_config *config = &connector->dev->mode_config;
+	struct drm_property *property;
+
+	switch (prop) {
+	case GUD_PROPERTY_TV_LEFT_MARGIN:
+		property = config->tv_left_margin_property;
+		break;
+	case GUD_PROPERTY_TV_RIGHT_MARGIN:
+		property = config->tv_right_margin_property;
+		break;
+	case GUD_PROPERTY_TV_TOP_MARGIN:
+		property = config->tv_top_margin_property;
+		break;
+	case GUD_PROPERTY_TV_BOTTOM_MARGIN:
+		property = config->tv_bottom_margin_property;
+		break;
+	case GUD_PROPERTY_TV_MODE:
+		property = config->tv_mode_property;
+		break;
+	case GUD_PROPERTY_TV_BRIGHTNESS:
+		property = config->tv_brightness_property;
+		break;
+	case GUD_PROPERTY_TV_CONTRAST:
+		property = config->tv_contrast_property;
+		break;
+	case GUD_PROPERTY_TV_FLICKER_REDUCTION:
+		property = config->tv_flicker_reduction_property;
+		break;
+	case GUD_PROPERTY_TV_OVERSCAN:
+		property = config->tv_overscan_property;
+		break;
+	case GUD_PROPERTY_TV_SATURATION:
+		property = config->tv_saturation_property;
+		break;
+	case GUD_PROPERTY_TV_HUE:
+		property = config->tv_hue_property;
+		break;
+	default:
+		return false;
+	}
+
+	*ret = drm_client_modeset_set_property(client, &connector->base, property, val);
+
+	return true;
+}
+
+static int gud_gadget_req_set_state_check(struct gud_gadget *gdg, unsigned int index,
+					  const struct gud_state_req *req, size_t size)
+{
+	struct drm_client_dev *client = &gdg->client;
+	struct drm_client_buffer *buffer;
+	struct drm_connector *connector;
+	unsigned int i, num_properties;
+	struct drm_display_mode mode;
+	u32 format;
+	int ret;
+
+	flush_work(&gdg->flush_worker);
+
+	if (index || size < sizeof(*req))
+		return -EPROTO;
+
+	if ((size - sizeof(*req)) % sizeof(*req->properties))
+		return -EPROTO;
+
+	num_properties = (size - sizeof(*req)) / sizeof(*req->properties);
+
+	memset(&mode, 0, sizeof(mode));
+	gud_to_display_mode(&mode, &req->mode);
+
+	gdg->check_ok = false;
+
+	if (!mode.hdisplay || !mode.vdisplay || req->format <= GUD_PIXEL_FORMAT_R1)
+		return -EINVAL;
+
+	format = gud_to_fourcc(req->format);
+	if (!format)
+		return -EINVAL;
+
+	if (req->connector >= gdg->connector_count)
+		return -EINVAL;
+
+	connector = gdg->connectors[req->connector].connector;
+
+	if (gdg->buffer_check) {
+		drm_client_buffer_delete(gdg->buffer_check);
+		gdg->buffer_check = NULL;
+	}
+
+	if (!gud_gadget_check_buffer(gdg, gdg->buffer, &mode, format)) {
+		buffer = drm_client_buffer_create_dumb(client, mode.hdisplay, mode.vdisplay,
+						       format);
+		if (IS_ERR(buffer))
+			return PTR_ERR(buffer);
+
+		gdg->buffer_check = buffer;
+	} else {
+		buffer = gdg->buffer;
+	}
+
+	ret = drm_client_modeset_set(client, connector, &mode, buffer->fb);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_properties; i++) {
+		u16 prop = le16_to_cpu(req->properties[i].prop);
+		u64 val = le64_to_cpu(req->properties[i].val);
+
+		if (gud_gadget_set_connector_property(client, connector, prop, val, &ret)) {
+			if (ret)
+				return ret;
+			continue;
+		}
+
+		switch (prop) {
+		case GUD_PROPERTY_BACKLIGHT_BRIGHTNESS:
+			if (val > 100)
+				return -EINVAL;
+			gdg->brightness = val;
+			break;
+		case GUD_PROPERTY_ROTATION:
+			/* DRM UAPI matches the protocol so use value directly */
+			ret = drm_client_modeset_set_rotation(client, val);
+			break;
+		default:
+			pr_err("%s: Unknown property: %u\n", __func__, prop);
+			continue;
+		}
+
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_check(&gdg->client);
+	if (ret)
+		return ret;
+
+	gdg->check_ok = true;
+
+	return 0;
+}
+
+static int gud_gadget_req_set_state_commit(struct gud_gadget *gdg, unsigned int index, size_t size)
+{
+	int ret;
+
+	if (index || size)
+		return -EPROTO;
+
+	if (!gdg->check_ok)
+		return -EINVAL;
+
+	if (gdg->backlight) {
+		int val, max_brightness = gdg->backlight->props.max_brightness;
+
+		val = DIV64_U64_ROUND_UP(gdg->brightness * max_brightness, 100);
+		ret = backlight_device_set_brightness(gdg->backlight, val);
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_client_modeset_commit(&gdg->client);
+	if (ret)
+		return ret;
+
+	if (gdg->buffer_check) {
+		drm_client_buffer_delete(gdg->buffer);
+		gdg->buffer = gdg->buffer_check;
+		gdg->buffer_check = NULL;
+	}
+
+	return 0;
+}
+
+/*
+ * Use a worker so the next frame can be received while the current frame is being flushed out.
+ * SPI panels often use 50ms to flush a full frame.
+ */
+static void gud_gadget_flush_worker(struct work_struct *work)
+{
+	struct gud_gadget *gdg = container_of(work, struct gud_gadget, flush_worker);
+	struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	int ret;
+
+	ret = drm_client_buffer_flush(buffer, &gdg->flush_rect);
+	if (ret)
+		pr_debug("%s: drm_client_buffer_flush: error=%d\n", __func__, ret);
+}
+
+static size_t gud_gadget_write_buffer_memcpy(struct drm_client_buffer *buffer,
+					     const void *src, size_t len,
+					     struct drm_rect *rect)
+{
+	unsigned int cpp = buffer->fb->format->cpp[0];
+	size_t src_pitch = drm_rect_width(rect) * cpp;
+	size_t dst_pitch = buffer->fb->pitches[0];
+	struct iosys_map dst;
+	unsigned int y;
+	int ret;
+
+	ret = drm_client_buffer_vmap(buffer, &dst);
+	if (ret)
+		return len;
+
+	iosys_map_incr(&dst, rect->y1 * dst_pitch + rect->x1 * cpp);
+
+	for (y = 0; y < drm_rect_height(rect) && len; y++) {
+		src_pitch = min(src_pitch, len);
+		iosys_map_memcpy_to(&dst, 0, src, src_pitch);
+		iosys_map_incr(&dst, dst_pitch);
+		src += src_pitch;
+		len -= src_pitch;
+	}
+
+	drm_client_buffer_vunmap(buffer);
+
+	return len;
+}
+
+static bool gud_gadget_check_rect(struct drm_client_buffer *buffer, struct drm_rect *rect)
+{
+	return buffer->fb && rect->x1 < rect->x2 && rect->y1 < rect->y2 &&
+	       rect->x2 <= buffer->fb->width && rect->y2 <= buffer->fb->height;
+}
+
+int gud_gadget_write_buffer(struct gud_gadget *gdg, const void *buf, size_t len)
+{
+	struct drm_client_buffer *buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	struct drm_rect *rect = &gdg->set_buffer_rect;
+	u8 compression = gdg->set_buffer_compression;
+	struct drm_framebuffer *fb;
+	size_t remain;
+	int ret;
+
+	pr_debug("%s: len=%zu compression=0x%x\n", __func__, len, compression);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	if (WARN_ON_ONCE(!buffer)) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (!gud_gadget_check_rect(buffer, rect)) {
+		pr_err("%s: Rectangle doesn't fit: " DRM_RECT_FMT "\n",
+		       __func__, DRM_RECT_ARG(rect));
+		ret = -EINVAL;
+		goto out;
+	}
+
+	fb = buffer->fb;
+
+	if (fb->funcs->dirty)
+		flush_work(&gdg->flush_worker);
+
+	if (compression & GUD_COMPRESSION_LZ4) {
+		if (len != gdg->set_buffer_compressed_length) {
+			pr_err("%s: Buffer compressed len differs: %zu != %u\n",
+			       __func__, len, gdg->set_buffer_compressed_length);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		ret = LZ4_decompress_safe(buf, gdg->work_buf, len, gdg->max_buffer_size);
+		if (ret < 0) {
+			pr_err("%s: Failed to decompress buffer\n", __func__);
+			ret = -EIO;
+			goto out;
+		}
+
+		buf = gdg->work_buf;
+		len = ret;
+	}
+
+	if (len != gdg->set_buffer_length) {
+		pr_err("%s: Buffer len differs: %zu != %u\n",
+		       __func__, len, gdg->set_buffer_length);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	remain = gud_gadget_write_buffer_memcpy(buffer, buf, len, rect);
+	if (remain) {
+		pr_err("%s: Failed to write buffer: remain=%zu\n", __func__, remain);
+		ret = -EIO;
+		goto out;
+	}
+
+	if (fb->funcs->dirty) {
+		gdg->flush_rect = *rect;
+		queue_work(system_long_wq, &gdg->flush_worker);
+	}
+
+	ret = 0;
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_gadget_write_buffer);
+
+int gud_gadget_req_set_buffer(struct gud_gadget *gdg, const struct gud_set_buffer_req *req)
+{
+	u32 compressed_length = le32_to_cpu(req->compressed_length);
+	u32 length = le32_to_cpu(req->length);
+	struct drm_client_buffer *buffer;
+	struct drm_rect rect;
+	int ret = 0;
+	u64 pitch;
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	buffer = gdg->buffer ? gdg->buffer : gdg->buffer_check;
+	if (!buffer) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	drm_rect_init(&rect, le32_to_cpu(req->x), le32_to_cpu(req->y),
+		      le32_to_cpu(req->width), le32_to_cpu(req->height));
+
+	pr_debug("%s: " DRM_RECT_FMT "\n", __func__, DRM_RECT_ARG(&rect));
+
+	if (!gud_gadget_check_rect(buffer, &rect)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	if (req->compression & ~GUD_COMPRESSION_LZ4) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	gdg->set_buffer_rect = rect;
+	gdg->set_buffer_length = length;
+
+	if (req->compression) {
+		if (!compressed_length) {
+			ret = -EINVAL;
+			goto out;
+		}
+		gdg->set_buffer_compression = req->compression;
+		gdg->set_buffer_compressed_length = compressed_length;
+		length = compressed_length;
+	} else {
+		gdg->set_buffer_compression = 0;
+		gdg->set_buffer_compressed_length = 0;
+	}
+
+	pitch = drm_format_info_min_pitch(buffer->fb->format, 0, drm_rect_width(&rect));
+	if (length > (drm_rect_height(&rect) * pitch)) {
+		pr_err("%s: Buffer is too big for rectangle: " DRM_RECT_FMT " len=%u\n",
+		       __func__, DRM_RECT_ARG(&rect), length);
+		ret = -EINVAL;
+		goto out;
+	}
+out:
+	refcount_dec(&gdg->usecnt);
+
+	return ret ? ret : length;
+}
+EXPORT_SYMBOL(gud_gadget_req_set_buffer);
+
+static void gud_gadget_delete_buffers(struct gud_gadget *gdg)
+{
+	drm_client_buffer_delete(gdg->buffer_check);
+	drm_client_buffer_delete(gdg->buffer);
+	gdg->buffer_check = NULL;
+	gdg->buffer = NULL;
+}
+
+int gud_gadget_disable_pipe(struct gud_gadget *gdg)
+{
+	int ret;
+
+	cancel_work_sync(&gdg->flush_worker);
+	ret = drm_client_modeset_disable(&gdg->client);
+	gud_gadget_delete_buffers(gdg);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_gadget_disable_pipe);
+
+static int gud_gadget_req_get_descriptor(struct gud_gadget *gdg, unsigned int index,
+					 void *data, size_t size)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct gud_display_descriptor_req desc;
+
+	if (index || !size)
+		return -EPROTO;
+
+	desc.magic = cpu_to_le32(GUD_DISPLAY_MAGIC);
+	desc.version = 1;
+	desc.max_buffer_size = cpu_to_le32(gdg->max_buffer_size);
+	desc.flags = cpu_to_le32(GUD_DISPLAY_FLAG_STATUS_ON_SET);
+	desc.compression = GUD_COMPRESSION_LZ4 & gdg->compression;
+
+	desc.min_width = cpu_to_le32(drm->mode_config.min_width);
+	desc.max_width = cpu_to_le32(drm->mode_config.max_width);
+	desc.min_height = cpu_to_le32(drm->mode_config.min_height);
+	desc.max_height = cpu_to_le32(drm->mode_config.max_height);
+
+	size = min(size, sizeof(desc));
+	memcpy(data, &desc, size);
+
+	return size;
+}
+
+static int gud_gadget_req_get_formats(struct gud_gadget *gdg, unsigned int index,
+				      void *data, size_t size)
+{
+	if (index || !size)
+		return -EPROTO;
+
+	size = min_t(size_t, size, gdg->format_count);
+	memcpy(data, gdg->formats, size);
+
+	return size;
+}
+
+static int gud_gadget_req_get_properties(struct gud_gadget *gdg, unsigned int index,
+					 void *data, size_t size)
+{
+	size = rounddown(size, sizeof(*gdg->properties));
+	if (index || !size)
+		return -EPROTO;
+
+	size = min(size, gdg->num_properties * sizeof(*gdg->properties));
+	memcpy(data, gdg->properties, size);
+
+	return size;
+}
+
+static void gud_gadget_req_get_connector(struct gud_gadget *gdg, unsigned int index,
+					 struct gud_connector_descriptor_req *desc)
+{
+	struct gud_gadget_connector *gconn;
+	u32 flags;
+
+	memset(desc, 0, sizeof(*desc));
+
+	gconn = &gdg->connectors[index];
+
+	switch (gconn->connector->connector_type) {
+	case DRM_MODE_CONNECTOR_VGA:
+		desc->connector_type = GUD_CONNECTOR_TYPE_VGA;
+		break;
+	case DRM_MODE_CONNECTOR_DVII:
+		fallthrough;
+	case DRM_MODE_CONNECTOR_DVID:
+		fallthrough;
+	case DRM_MODE_CONNECTOR_DVIA:
+		desc->connector_type = GUD_CONNECTOR_TYPE_DVI;
+		break;
+	case DRM_MODE_CONNECTOR_Composite:
+		desc->connector_type = GUD_CONNECTOR_TYPE_COMPOSITE;
+		break;
+	case DRM_MODE_CONNECTOR_SVIDEO:
+		desc->connector_type = GUD_CONNECTOR_TYPE_SVIDEO;
+		break;
+	case DRM_MODE_CONNECTOR_Component:
+		desc->connector_type = GUD_CONNECTOR_TYPE_COMPONENT;
+		break;
+	case DRM_MODE_CONNECTOR_DisplayPort:
+		desc->connector_type = GUD_CONNECTOR_TYPE_DISPLAYPORT;
+		break;
+	case DRM_MODE_CONNECTOR_HDMIA:
+		fallthrough;
+	case DRM_MODE_CONNECTOR_HDMIB:
+		desc->connector_type = GUD_CONNECTOR_TYPE_HDMI;
+		break;
+	default:
+		desc->connector_type = GUD_CONNECTOR_TYPE_PANEL;
+		break;
+	};
+
+	flags = GUD_CONNECTOR_FLAGS_POLL_STATUS;
+	if (gconn->connector->interlace_allowed)
+		flags |= GUD_CONNECTOR_FLAGS_INTERLACE;
+	if (gconn->connector->doublescan_allowed)
+		flags |= GUD_CONNECTOR_FLAGS_DOUBLESCAN;
+	desc->flags = cpu_to_le32(flags);
+}
+
+static int gud_gadget_req_get_connectors(struct gud_gadget *gdg, unsigned int index,
+					 struct gud_connector_descriptor_req *descs, size_t size)
+{
+	unsigned int i, num_connectors;
+
+	size = rounddown(size, sizeof(*descs));
+	if (index || !size)
+		return -EPROTO;
+
+	num_connectors = min_t(size_t, size / sizeof(*descs), gdg->connector_count);
+
+	for (i = 0; i < num_connectors; i++)
+		gud_gadget_req_get_connector(gdg, i, &descs[i]);
+
+	return num_connectors * sizeof(*descs);
+}
+
+static struct gud_gadget_connector *
+gud_gadget_get_gconn(struct gud_gadget *gdg, unsigned int index)
+{
+	if (index >= gdg->connector_count)
+		return NULL;
+
+	return &gdg->connectors[index];
+}
+
+static int gud_gadget_req_get_connector_properties(struct gud_gadget *gdg, unsigned int index,
+						   void *data, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+
+	size = rounddown(size, sizeof(*gconn->properties));
+	if (!size)
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	size = min(size, gconn->num_properties * sizeof(*gconn->properties));
+	memcpy(data, gconn->properties, size);
+
+	return size;
+}
+
+static int gud_gadget_req_get_connector_tv_mode_values(struct gud_gadget *gdg, unsigned int index,
+						       void *data, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+
+	size = rounddown(size, GUD_CONNECTOR_TV_MODE_NAME_LEN);
+	if (!size)
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	size = min_t(size_t, size, gconn->num_tv_mode_enum_names * GUD_CONNECTOR_TV_MODE_NAME_LEN);
+	memcpy(data, gconn->tv_mode_enum_names, size);
+
+	return size;
+}
+
+static int gud_gadget_req_get_connector_status(struct gud_gadget *gdg, unsigned int index,
+					       u8 *status, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+	unsigned long flags;
+
+	if (size != sizeof(*status))
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	spin_lock_irqsave(&gconn->lock, flags);
+
+	switch (gconn->status) {
+	case connector_status_disconnected:
+		*status = GUD_CONNECTOR_STATUS_DISCONNECTED;
+		break;
+	case connector_status_connected:
+		*status = GUD_CONNECTOR_STATUS_CONNECTED;
+		break;
+	default:
+		*status = GUD_CONNECTOR_STATUS_UNKNOWN;
+		break;
+
+	};
+
+	if (gconn->changed) {
+		*status |= GUD_CONNECTOR_STATUS_CHANGED;
+		gconn->changed = false;
+	}
+
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	return size;
+}
+
+static int gud_gadget_req_get_connector_modes(struct gud_gadget *gdg, unsigned int index,
+					      void *data, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+	unsigned long flags;
+
+	size = rounddown(size, sizeof(*gconn->modes));
+	if (!size)
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	spin_lock_irqsave(&gconn->lock, flags);
+	size = min(size, gconn->num_modes * sizeof(*gconn->modes));
+	memcpy(data, gconn->modes, size);
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	return size;
+}
+
+static int gud_gadget_req_get_connector_edid(struct gud_gadget *gdg, unsigned int index,
+					     void *data, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+	unsigned long flags;
+
+	size = rounddown(size, EDID_LENGTH);
+	if (!size)
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	spin_lock_irqsave(&gconn->lock, flags);
+	size = min(size, gconn->edid_len);
+	memcpy(data, gconn->edid, size);
+	spin_unlock_irqrestore(&gconn->lock, flags);
+
+	return size;
+}
+
+/* This runs in interrupt context */
+int gud_gadget_req_get(struct gud_gadget *gdg, u8 request, u16 index, void *data, size_t size)
+{
+	int ret;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	switch (request) {
+	case GUD_REQ_GET_DESCRIPTOR:
+		ret = gud_gadget_req_get_descriptor(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_FORMATS:
+		ret = gud_gadget_req_get_formats(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_PROPERTIES:
+		ret = gud_gadget_req_get_properties(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTORS:
+		ret = gud_gadget_req_get_connectors(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTOR_PROPERTIES:
+		ret = gud_gadget_req_get_connector_properties(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES:
+		ret = gud_gadget_req_get_connector_tv_mode_values(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTOR_STATUS:
+		ret = gud_gadget_req_get_connector_status(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTOR_MODES:
+		ret = gud_gadget_req_get_connector_modes(gdg, index, data, size);
+		break;
+	case GUD_REQ_GET_CONNECTOR_EDID:
+		ret = gud_gadget_req_get_connector_edid(gdg, index, data, size);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	refcount_dec(&gdg->usecnt);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_gadget_req_get);
+
+static int gud_gadget_req_set_connector_force_detect(struct gud_gadget *gdg, u16 index, size_t size)
+{
+	struct gud_gadget_connector *gconn;
+
+	if (size)
+		return -EPROTO;
+
+	gconn = gud_gadget_get_gconn(gdg, index);
+	if (!gconn)
+		return -EINVAL;
+
+	return gud_gadget_probe_connector(gconn);
+}
+
+static int gud_gadget_req_set_controller_enable(struct gud_gadget *gdg, u16 index,
+						const u8 *enable, size_t size)
+{
+	if (index || size != sizeof(*enable))
+		return -EPROTO;
+
+	return *enable ? 0 : gud_gadget_disable_pipe(gdg);
+}
+
+static int gud_gadget_req_set_display_enable(struct gud_gadget *gdg, u16 index,
+					     const u8 *enable, size_t size)
+{
+	if (index || size != sizeof(*enable))
+		return -EPROTO;
+
+	return drm_client_modeset_dpms(&gdg->client,
+				       *enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF);
+}
+
+int gud_gadget_req_set(struct gud_gadget *gdg, u8 request, u16 index, const void *data, size_t size)
+{
+	int ret;
+
+	pr_debug("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
+
+	if (!refcount_inc_not_zero(&gdg->usecnt))
+		return -ENODEV;
+
+	switch (request) {
+	case GUD_REQ_SET_CONNECTOR_FORCE_DETECT:
+		ret = gud_gadget_req_set_connector_force_detect(gdg, index, size);
+		break;
+	case GUD_REQ_SET_STATE_CHECK:
+		ret = gud_gadget_req_set_state_check(gdg, index, data, size);
+		break;
+	case GUD_REQ_SET_STATE_COMMIT:
+		ret = gud_gadget_req_set_state_commit(gdg, index, size);
+		break;
+	case GUD_REQ_SET_CONTROLLER_ENABLE:
+		ret = gud_gadget_req_set_controller_enable(gdg, index, data, size);
+		break;
+	case GUD_REQ_SET_DISPLAY_ENABLE:
+		ret = gud_gadget_req_set_display_enable(gdg, index, data, size);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	refcount_dec(&gdg->usecnt);
+
+	return ret;
+}
+EXPORT_SYMBOL(gud_gadget_req_set);
+
+static int gud_gadget_get_formats(struct gud_gadget *gdg, u8 *max_cpp, u8 *format_filter)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_plane *plane;
+	unsigned int i;
+	u8 *formats;
+	int ret;
+
+	*max_cpp = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	formats = kmalloc(plane->format_count, GFP_KERNEL);
+	if (!formats)
+		return -ENOMEM;
+
+	for (i = 0; i < plane->format_count; i++) {
+		const struct drm_format_info *info;
+		u8 format;
+
+		info = drm_format_info(plane->format_types[i]);
+		if (info->num_planes != 1)
+			continue;
+
+		format = gud_from_fourcc(info->format);
+		if (!format)
+			continue;
+
+		if (format_filter[0]) {
+			bool found = false;
+			unsigned int j;
+
+			for (j = 0; j < GUD_FORMATS_MAX_NUM; j++) {
+				if (!format_filter[j])
+					break;
+				if (format_filter[j] == format) {
+					found = true;
+					break;
+				}
+			}
+			if (!found)
+				continue;
+		}
+
+		if (*max_cpp < info->cpp[0])
+			*max_cpp = info->cpp[0];
+
+		formats[gdg->format_count++] = format;
+	}
+
+	if (!gdg->format_count) {
+		ret = -ENOENT;
+		goto err_free;
+	}
+
+	gdg->formats = formats;
+
+	return 0;
+
+err_free:
+	kfree(formats);
+
+	return ret;
+}
+
+static int gud_gadget_get_rotation_property(struct drm_device *drm, u16 *prop, u64 *val)
+{
+	struct drm_property_enum *prop_enum;
+	struct drm_plane *plane;
+	unsigned int num_props = 0;
+	u16 bitmask = 0;
+
+	drm_for_each_plane(plane, drm) {
+		if (plane->type == DRM_PLANE_TYPE_PRIMARY)
+			break;
+	}
+
+	if (!plane->rotation_property)
+		return 0;
+
+	list_for_each_entry(prop_enum, &plane->rotation_property->enum_list, head) {
+		num_props++;
+		bitmask |= BIT(prop_enum->value);
+	}
+
+	*prop = GUD_PROPERTY_ROTATION;
+	/* DRM UAPI matches the protocol so use value directly */
+	*val = bitmask;
+
+	return 1;
+}
+
+static int gud_gadget_get_properties(struct gud_gadget *gdg)
+{
+	struct gud_property_req *properties;
+	u16 prop;
+	u64 val;
+	int ret;
+
+	ret = gud_gadget_get_rotation_property(gdg->client.dev, &prop, &val);
+	if (ret <= 0)
+		return ret;
+
+	properties = kcalloc(1, sizeof(*properties), GFP_KERNEL);
+	if (!properties)
+		return -ENOMEM;
+
+	gdg->properties = properties;
+	gdg->num_properties++;
+
+	properties[0].prop = cpu_to_le16(prop);
+	properties[0].val = cpu_to_le64(val);
+
+	return 0;
+}
+
+static int gud_gadget_get_connector_properties(struct gud_gadget *gdg,
+					       struct gud_gadget_connector *gconn)
+{
+	struct drm_device *drm = gdg->client.dev;
+	struct drm_mode_config *config = &drm->mode_config;
+	struct drm_connector *connector = gconn->connector;
+	struct drm_object_properties *conn_props = connector->base.properties;
+	struct gud_property_req *properties;
+	unsigned int i, ret = 0;
+	u16 prop;
+	u64 val;
+
+	mutex_lock(&drm->mode_config.mutex);
+
+	if (!conn_props->count)
+		goto unlock;
+
+	/* Add room for possible backlight */
+	properties = kcalloc(conn_props->count + 1, sizeof(*properties), GFP_KERNEL);
+	if (!properties) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	gconn->properties = properties;
+
+	for (i = 0; i < conn_props->count; i++) {
+		struct drm_property *property = conn_props->properties[i];
+
+		if (property == config->tv_left_margin_property) {
+			prop = GUD_PROPERTY_TV_LEFT_MARGIN;
+			val = connector->state->tv.margins.left;
+		} else if (property == config->tv_right_margin_property) {
+			prop = GUD_PROPERTY_TV_RIGHT_MARGIN;
+			val = connector->state->tv.margins.right;
+		} else if (property == config->tv_top_margin_property) {
+			prop = GUD_PROPERTY_TV_TOP_MARGIN;
+			val = connector->state->tv.margins.top;
+		} else if (property == config->tv_bottom_margin_property) {
+			prop = GUD_PROPERTY_TV_BOTTOM_MARGIN;
+			val = connector->state->tv.margins.bottom;
+		} else if (property == config->tv_mode_property) {
+			struct drm_property_enum *prop_enum;
+			char *buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head)
+				gconn->num_tv_mode_enum_names++;
+
+			if (WARN_ON(!gconn->num_tv_mode_enum_names)) {
+				ret = -EINVAL;
+				goto unlock;
+			}
+
+			buf = kcalloc(gconn->num_tv_mode_enum_names,
+				      GUD_CONNECTOR_TV_MODE_NAME_LEN, GFP_KERNEL);
+			if (!buf) {
+				ret = -ENOMEM;
+				goto unlock;
+			}
+
+			gconn->tv_mode_enum_names = buf;
+
+			list_for_each_entry(prop_enum, &property->enum_list, head) {
+				strscpy(buf, prop_enum->name, GUD_CONNECTOR_TV_MODE_NAME_LEN);
+				buf += GUD_CONNECTOR_TV_MODE_NAME_LEN;
+			}
+
+			prop = GUD_PROPERTY_TV_MODE;
+			val = connector->state->tv.mode;
+		} else if (property == config->tv_brightness_property) {
+			prop = GUD_PROPERTY_TV_BRIGHTNESS;
+			val = connector->state->tv.brightness;
+		} else if (property == config->tv_contrast_property) {
+			prop = GUD_PROPERTY_TV_CONTRAST;
+			val = connector->state->tv.contrast;
+		} else if (property == config->tv_flicker_reduction_property) {
+			prop = GUD_PROPERTY_TV_FLICKER_REDUCTION;
+			val = connector->state->tv.flicker_reduction;
+		} else if (property == config->tv_overscan_property) {
+			prop = GUD_PROPERTY_TV_OVERSCAN;
+			val = connector->state->tv.overscan;
+		} else if (property == config->tv_saturation_property) {
+			prop = GUD_PROPERTY_TV_SATURATION;
+			val = connector->state->tv.saturation;
+		} else if (property == config->tv_hue_property) {
+			prop = GUD_PROPERTY_TV_HUE;
+			val = connector->state->tv.hue;
+		} else {
+			continue;
+		}
+
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+	}
+
+	if (!connector->index && gdg->backlight) {
+		struct backlight_properties *props = &gdg->backlight->props;
+
+		prop = GUD_PROPERTY_BACKLIGHT_BRIGHTNESS;
+		val = DIV64_U64_ROUND_UP(props->brightness * 100, props->max_brightness);
+		properties[gconn->num_properties].prop = cpu_to_le16(prop);
+		properties[gconn->num_properties++].val = cpu_to_le64(val);
+		gconn->backlight = gdg->backlight;
+	}
+unlock:
+	mutex_unlock(&drm->mode_config.mutex);
+
+	return ret;
+}
+
+static int gud_gadget_get_connectors(struct gud_gadget *gdg, u32 connectors_mask)
+{
+	struct gud_gadget_connector *connectors = NULL;
+	struct drm_connector_list_iter conn_iter;
+	struct drm_device *drm = gdg->client.dev;
+	unsigned int connector_count = 0;
+	struct drm_connector *connector;
+	int ret = 0;
+
+	drm_connector_list_iter_begin(drm, &conn_iter);
+	drm_client_for_each_connector_iter(connector, &conn_iter) {
+		struct gud_gadget_connector *tmp, *gconn;
+
+		if (!((BIT(connector->index)) & connectors_mask))
+			continue;
+
+		tmp = krealloc(connectors, (connector_count + 1) * sizeof(*connectors),
+			       GFP_KERNEL | __GFP_ZERO);
+		if (!tmp) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		connectors = tmp;
+		drm_connector_get(connector);
+		gconn = &connectors[connector_count++];
+		gconn->connector = connector;
+		spin_lock_init(&gconn->lock);
+
+		ret = gud_gadget_get_connector_properties(gdg, gconn);
+		if (ret)
+			break;
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	if (!connector_count)
+		ret = -ENOENT;
+
+	gdg->connectors = connectors;
+	gdg->connector_count = connector_count;
+
+	return ret;
+}
+
+static void gud_gadget_release(struct kref *kref)
+{
+	struct gud_gadget *gdg = container_of(kref, struct gud_gadget, refcount);
+
+	kfree(gdg);
+}
+
+static void gud_gadget_put(struct gud_gadget *gdg)
+{
+	kref_put(&gdg->refcount, gud_gadget_release);
+}
+
+static void gud_gadget_client_unregister(struct drm_client_dev *client)
+{
+	struct gud_gadget *gdg = container_of(client, struct gud_gadget, client);
+	int timeout = 10000 / 50;
+	unsigned int i;
+
+	/*
+	 * If usecnt doesn't drop to zero, try waiting for the gadget, but we
+	 * can't block the DRM driver forever. The worst case wait the gadget side
+	 * can hit are tens of seconds through the call to drm_client_modeset_commit().
+	 */
+	if (refcount_dec_and_test(&gdg->usecnt)) {
+		for (; timeout && refcount_read(&gdg->usecnt); timeout--)
+			msleep(50);
+	}
+
+	if (!timeout) {
+		pr_err("%s: Timeout waiting for gadget side, will leak memory\n", __func__);
+		return;
+	}
+
+	vfree(gdg->work_buf);
+	kfree(gdg->formats);
+	kfree(gdg->properties);
+
+	for (i = 0; i < gdg->connector_count; i++) {
+		struct gud_gadget_connector *gconn = &gdg->connectors[i];
+
+		drm_connector_put(gconn->connector);
+		kfree(gconn->properties);
+		kfree(gconn->tv_mode_enum_names);
+		kfree(gconn->modes);
+		kfree(gconn->edid);
+	}
+	kfree(gdg->connectors);
+
+	gud_gadget_delete_buffers(gdg);
+	drm_client_release(client);
+	gud_gadget_put(gdg);
+}
+
+static int gud_gadget_client_hotplug(struct drm_client_dev *client)
+{
+	struct gud_gadget *gdg = container_of(client, struct gud_gadget, client);
+
+	gud_gadget_probe_connectors(gdg);
+
+	return 0;
+}
+
+static const struct drm_client_funcs gdg_client_funcs = {
+	.owner		= THIS_MODULE,
+	.unregister	= gud_gadget_client_unregister,
+	.hotplug	= gud_gadget_client_hotplug,
+};
+
+struct gud_gadget *gud_gadget_init(unsigned int minor_id, const char *backlight,
+				   size_t *max_buffer_size, u8 compression, u8 *formats,
+				   u32 connectors)
+{
+	struct gud_gadget *gdg;
+	u8 max_cpp;
+	int ret;
+
+	gdg = kzalloc(sizeof(*gdg), GFP_KERNEL);
+	if (!gdg)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_WORK(&gdg->flush_worker, gud_gadget_flush_worker);
+	gdg->compression = compression;
+
+	ret = drm_client_init_from_id(minor_id, &gdg->client, "gud-drm-gadget", &gdg_client_funcs);
+	if (ret) {
+		pr_err("Failed to aquire minor=%u\n", minor_id);
+		kfree(gdg);
+		return ERR_PTR(ret);
+	}
+
+	refcount_set(&gdg->usecnt, 1);
+	/* The DRM driver (through the client) and f_gud_drm need one ref each */
+	kref_init(&gdg->refcount);
+	kref_get(&gdg->refcount);
+
+	if (backlight) {
+		gdg->backlight = backlight_device_get_by_name(backlight);
+		if (!gdg->backlight) {
+			pr_err("Failed to find backlight: %s\n", backlight);
+			ret = -ENODEV;
+			goto error_release;
+		}
+	}
+
+	ret = gud_gadget_get_formats(gdg, &max_cpp, formats);
+	if (ret) {
+		pr_err("Failed to get formats\n");
+		goto error_release;
+	}
+
+	*max_buffer_size = gdg->client.dev->mode_config.max_width *
+			   gdg->client.dev->mode_config.max_height * max_cpp;
+	/* f_gud_drm will kmalloc a buffer of this size */
+	*max_buffer_size = min_t(size_t, *max_buffer_size, KMALLOC_MAX_SIZE);
+
+	gdg->max_buffer_size = *max_buffer_size;
+	gdg->work_buf = vmalloc(gdg->max_buffer_size);
+	if (!gdg->work_buf) {
+		ret = -ENOMEM;
+		goto error_release;
+	}
+
+	ret = gud_gadget_get_properties(gdg);
+	if (ret) {
+		pr_err("Failed to get properties\n");
+		goto error_release;
+	}
+
+	ret = gud_gadget_get_connectors(gdg, connectors);
+	if (ret) {
+		pr_err("Failed to get connectors\n");
+		goto error_release;
+	}
+
+	if (!drm_client_register(&gdg->client)) {
+		pr_err("DRM device is gone\n");
+		ret = -ENODEV;
+		goto error_release;
+	}
+
+	gud_gadget_probe_connectors(gdg);
+
+	return gdg;
+
+error_release:
+	gud_gadget_client_unregister(&gdg->client);
+	gud_gadget_fini(gdg);
+
+	return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(gud_gadget_init);
+
+void gud_gadget_fini(struct gud_gadget *gdg)
+{
+	if (gdg->backlight)
+		put_device(&gdg->backlight->dev);
+	gud_gadget_put(gdg);
+}
+EXPORT_SYMBOL(gud_gadget_fini);
+
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/gud/gud_internal.h b/drivers/gpu/drm/gud/gud_internal.h
index d27c31648..a662b115a 100644
--- a/drivers/gpu/drm/gud/gud_internal.h
+++ b/drivers/gpu/drm/gud/gud_internal.h
@@ -153,6 +153,8 @@ static inline void gud_to_display_mode(struct drm_display_mode *dst,
 	dst->hsync_start = le16_to_cpu(src->hsync_start);
 	dst->hsync_end = le16_to_cpu(src->hsync_end);
 	dst->htotal = le16_to_cpu(src->htotal);
+	// Workaround for PineNote
+	dst->hskew = 64;
 	dst->vdisplay = le16_to_cpu(src->vdisplay);
 	dst->vsync_start = le16_to_cpu(src->vsync_start);
 	dst->vsync_end = le16_to_cpu(src->vsync_end);
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 162cc58c7..32540f79a 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -2214,6 +2214,254 @@ static const struct panel_desc edt_etmv570g2dhu = {
 	.connector_type = DRM_MODE_CONNECTOR_DPI,
 };
 
+static const struct drm_display_mode eink_ed103tc2_mode[] ={
+	/* clock frequency = refresh rate * (htotal * vtotal) / 1000 */
+	// 0.5 Hz
+	{
+		.clock = 1568,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 1 Hz
+	{
+		.clock = 3137,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 5 Hz
+	{
+		.clock = 15687,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 15 Hz
+	{
+		.clock = 47063,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 20 Hz
+	{
+		.clock = 62751,
+		/* .clock = 200000, */
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 25 Hz
+	{
+		.clock = 78439,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 30 Hz
+	{
+		.clock = 94127,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2,
+	},
+	// 40 Hz
+	{
+		.clock = 125502,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		/* .hskew = 0, */ // <- does not work
+		/* .hskew = 10, */
+		/* .hskew = 20, */
+		/* .hskew = 30, */
+		/* .hskew = 40, */
+		/* .hskew = 50, */
+		/* .hskew = 60, */
+		/* .hskew = 70, */
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2 |
+			DRM_MODE_TYPE_PREFERRED,
+	},
+	// 80 Hz
+	{
+		.clock = 251005,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		/* .hskew = 0, */ // <- does not work
+		/* .hskew = 10, */
+		/* .hskew = 20, */
+		/* .hskew = 30, */
+		/* .hskew = 40, */
+		/* .hskew = 50, */
+		/* .hskew = 60, */
+		/* .hskew = 70, */
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2
+	},
+	// 85 Hz
+	{
+		.clock = 266680,
+		.hdisplay = 1872,
+		.hsync_start = 1872 + 56,
+		.hsync_end = 1872 + 56 + 144,
+		.htotal = 1872 + 56 + 144 + 136,
+		.hskew = 64,
+		/* .hskew = 0, */ // <- does not work
+		/* .hskew = 10, */
+		/* .hskew = 20, */
+		/* .hskew = 30, */
+		/* .hskew = 40, */
+		/* .hskew = 50, */
+		/* .hskew = 60, */
+		/* .hskew = 70, */
+		.vdisplay = 1404,
+		.vsync_start = 1404 + 12,
+		.vsync_end = 1404 + 12 + 1,
+		.vtotal = 1404 + 12 + 1 + 4,
+		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2
+	},
+
+/* 	/1* modes that actually take the .clock seriously *1/ */
+/* 	{ */
+/* 		/1* this will lead to dclk == 250 Mhz *1/ */
+/* 		/1* .clock = 266693, *1/ */
+/* 		/1* request dclk == 200 MHz *1/ */
+/* 		.clock = 200000, */
+/* 		.hdisplay = 1872, */
+/* 		/1* hsync_start, for linux timing order, should be display + fp */
+/* 		 * according to the data sheet, display = ldl == 234 [dclk] = 1872 pixels, and fp == lel == 7 [dclk] == 56 pixels */
+/* 		 *1/ */
+/* 		/1* data sheet days sync == lsl == 18 sclk = 18 * 8 pixels = 144 pixels *1/ */
+/* 		.hsync_start = 1872 + 56, */
+/* 		.hsync_end = 1872 + 56 + 144, */
+/* 		.htotal = 1872 + 56 + 144 + 136, */
+/* 		/1* .hsync_start = 1872 + 184, *1/ */
+/* 		/1* .hsync_end = 1872 + 184 + 88, *1/ */
+/* 		/1* .htotal = 1872 + 184 + 88 + 64, *1/ */
+/* 		/1* .hskew = 136, *1/ */
+/* 		.hskew = 64, */
+/* 		.vdisplay = 1404, */
+/* 		.vsync_start = 1404 + 12, */
+/* 		.vsync_end = 1404 + 12 + 1, */
+/* 		.vtotal = 1404 + 12 + 1 + 4, */
+/* 		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */
+/* 			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */
+/* 	}, */
+/* 	{ */
+/* 		.clock = 220000, */
+/* 		.hdisplay = 1872, */
+/* 		.hsync_start = 1872 + 56, */
+/* 		.hsync_end = 1872 + 56 + 144, */
+/* 		.htotal = 1872 + 56 + 144 + 136, */
+/* 		.hskew = 64, */
+/* 		.vdisplay = 1404, */
+/* 		.vsync_start = 1404 + 12, */
+/* 		.vsync_end = 1404 + 12 + 1, */
+/* 		.vtotal = 1404 + 12 + 1 + 4, */
+/* 		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */
+/* 			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */
+/* 	}, */
+/* 	{ */
+/* 		.clock = 266693, */
+/* 		.hdisplay = 1872, */
+/* 		.hsync_start = 1872 + 56, */
+/* 		.hsync_end = 1872 + 56 + 144, */
+/* 		.htotal = 1872 + 56 + 144 + 136, */
+/* 		.hskew = 64, */
+/* 		.vdisplay = 1404, */
+/* 		.vsync_start = 1404 + 12, */
+/* 		.vsync_end = 1404 + 12 + 1, */
+/* 		.vtotal = 1404 + 12 + 1 + 4, */
+/* 		.flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC | */
+/* 			 DRM_MODE_FLAG_HSKEW | DRM_MODE_FLAG_CLKDIV2, */
+/* 	}, */
+};
+
+static const struct panel_desc eink_ed103tc2 = {
+	.modes = eink_ed103tc2_mode,
+	.num_modes = 10,
+	.bpc = 4,
+	.size = {
+		.width = 210,
+		.height = 157,
+	},
+	.bus_format = MEDIA_BUS_FMT_FIXED,
+	.bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
+	.connector_type = DRM_MODE_CONNECTOR_DPI,
+};
+
 static const struct display_timing eink_vb3300_kca_timing = {
 	.pixelclock = { 40000000, 40000000, 40000000 },
 	.hactive = { 334, 334, 334 },
@@ -5220,6 +5468,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 		.compatible = "edt,etmv570g2dhu",
 		.data = &edt_etmv570g2dhu,
+	}, {
+		.compatible = "eink,ed103tc2",
+		.data = &eink_ed103tc2,
 	}, {
 		.compatible = "eink,vb3300-kca",
 		.data = &eink_vb3300_kca,
diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig
index b7b025814..1ca0396a8 100644
--- a/drivers/gpu/drm/rockchip/Kconfig
+++ b/drivers/gpu/drm/rockchip/Kconfig
@@ -145,4 +145,26 @@ config ROCKCHIP_RK3066_HDMI
 	  This selects support for Rockchip SoC specific extensions
 	  for the RK3066 HDMI driver. If you want to enable
 	  HDMI on RK3066 based SoC, you should select this option.
+
+config DRM_ROCKCHIP_EBC
+	tristate "DRM Support for Rockchip EBC"
+	depends on DRM && IIO
+	select DRM_EPD_HELPER
+	select DRM_GEM_SHMEM_HELPER
+	select DRM_KMS_HELPER
+	select DRM_TTM_HELPER
+	help
+	  This selects DRM/KMS support for the Rockchip E-Book Controller (EBC).
+	  Choose this option if you have a Rockchip SoC and an electrophoretic
+	  display. This hardware and driver is separate from the normal Rockchip
+	  display hardware and DRM driver.
+
+config DRM_ROCKCHIP_EBC_3WIN_MODE
+	bool "Enable 3WIN mode for Rockchip EBC"
+	depends on DRM_ROCKCHIP_EBC
+	help
+	  This enables the module parameter direct_mode and
+	  disables compile-time optimisations removing support
+	  for 3WIN mode.
+
 endif
diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile
index 097f06239..eae5991e2 100644
--- a/drivers/gpu/drm/rockchip/Makefile
+++ b/drivers/gpu/drm/rockchip/Makefile
@@ -21,3 +20,10 @@ rockchipdrm-$(CONFIG_ROCKCHIP_RGB) += rockchip_rgb.o
 rockchipdrm-$(CONFIG_ROCKCHIP_RK3066_HDMI) += rk3066_hdmi.o
 
 obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o
+
+obj-$(CONFIG_DRM_ROCKCHIP_EBC) += rockchip_ebc.o rockchip_ebc_blit_neon.o
+
+ifeq ($(CONFIG_KERNEL_MODE_NEON),y)
+	CFLAGS_rockchip_ebc_blit_neon.o += $(CC_FLAGS_FPU) -O3
+	CFLAGS_REMOVE_rockchip_ebc_blit_neon.o += -mgeneral-regs-only
+endif
diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.c b/drivers/gpu/drm/rockchip/rockchip_ebc.c
new file mode 100644
index 000000000..6453460f3
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc.c
@@ -0,0 +1,2527 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2022 Samuel Holland <samuel@sholland.org>
+ * Copyright (C) 2025 hrdl <git@hrdl.eu>
+ */
+
+
+#include "linux/spinlock.h"
+#include <asm/simd.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/iio/consumer.h>
+#include <linux/irq.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/dma-mapping.h>
+#include <linux/uaccess.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/vmalloc.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_epd_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/rockchip_ebc_drm.h>
+#include <drm/drm_fbdev_shmem.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/clients/drm_client_setup.h>
+
+#include "rockchip_ebc.h"
+
+#define EBC_DSP_START			0x0000
+#define EBC_DSP_START_DSP_OUT_LOW		BIT(31)
+#define EBC_DSP_START_DSP_SDCE_WIDTH(x)		((x) << 16)
+#define EBC_DSP_START_DSP_EINK_MODE		BIT(13)
+#define EBC_DSP_START_SW_BURST_CTRL		BIT(12)
+#define EBC_DSP_START_DSP_FRM_TOTAL(x)		((x) << 2)
+#define EBC_DSP_START_DSP_RST			BIT(1)
+#define EBC_DSP_START_DSP_FRM_START		BIT(0)
+#define EBC_EPD_CTRL			0x0004
+#define EBC_EPD_CTRL_EINK_MODE_SWAP		BIT(31)
+#define EBC_EPD_CTRL_DSP_GD_END(x)		((x) << 16)
+#define EBC_EPD_CTRL_DSP_GD_ST(x)		((x) << 8)
+#define EBC_EPD_CTRL_DSP_THREE_WIN_MODE		BIT(7)
+#define EBC_EPD_CTRL_DSP_SDDW_MODE		BIT(6)
+#define EBC_EPD_CTRL_EPD_AUO			BIT(5)
+#define EBC_EPD_CTRL_EPD_PWR(x)			((x) << 2)
+#define EBC_EPD_CTRL_EPD_GDRL			BIT(1)
+#define EBC_EPD_CTRL_EPD_SDSHR			BIT(0)
+#define EBC_DSP_CTRL			0x0008
+#define EBC_DSP_CTRL_DSP_SWAP_MODE(x)		((x) << 30)
+#define EBC_DSP_CTRL_DSP_DIFF_MODE		BIT(29)
+#define EBC_DSP_CTRL_DSP_LUT_MODE		BIT(28)
+#define EBC_DSP_CTRL_DSP_VCOM_MODE		BIT(27)
+#define EBC_DSP_CTRL_DSP_GDOE_POL		BIT(26)
+#define EBC_DSP_CTRL_DSP_GDSP_POL		BIT(25)
+#define EBC_DSP_CTRL_DSP_GDCLK_POL		BIT(24)
+#define EBC_DSP_CTRL_DSP_SDCE_POL		BIT(23)
+#define EBC_DSP_CTRL_DSP_SDOE_POL		BIT(22)
+#define EBC_DSP_CTRL_DSP_SDLE_POL		BIT(21)
+#define EBC_DSP_CTRL_DSP_SDCLK_POL		BIT(20)
+#define EBC_DSP_CTRL_DSP_SDCLK_DIV(x)		((x) << 16)
+#define EBC_DSP_CTRL_DSP_BACKGROUND(x)		((x) << 0)
+#define EBC_DSP_HTIMING0		0x000c
+#define EBC_DSP_HTIMING0_DSP_HTOTAL(x)		((x) << 16)
+#define EBC_DSP_HTIMING0_DSP_HS_END(x)		((x) << 0)
+#define EBC_DSP_HTIMING1		0x0010
+#define EBC_DSP_HTIMING1_DSP_HACT_END(x)	((x) << 16)
+#define EBC_DSP_HTIMING1_DSP_HACT_ST(x)		((x) << 0)
+#define EBC_DSP_VTIMING0		0x0014
+#define EBC_DSP_VTIMING0_DSP_VTOTAL(x)		((x) << 16)
+#define EBC_DSP_VTIMING0_DSP_VS_END(x)		((x) << 0)
+#define EBC_DSP_VTIMING1		0x0018
+#define EBC_DSP_VTIMING1_DSP_VACT_END(x)	((x) << 16)
+#define EBC_DSP_VTIMING1_DSP_VACT_ST(x)		((x) << 0)
+#define EBC_DSP_ACT_INFO		0x001c
+#define EBC_DSP_ACT_INFO_DSP_HEIGHT(x)		((x) << 16)
+#define EBC_DSP_ACT_INFO_DSP_WIDTH(x)		((x) << 0)
+#define EBC_WIN_CTRL			0x0020
+#define EBC_WIN_CTRL_WIN2_FIFO_THRESHOLD(x)	((x) << 19)
+#define EBC_WIN_CTRL_WIN_EN			BIT(18)
+#define EBC_WIN_CTRL_AHB_INCR_NUM_REG(x)	((x) << 13)
+#define EBC_WIN_CTRL_AHB_BURST_REG(x)		((x) << 10)
+#define EBC_WIN_CTRL_WIN_FIFO_THRESHOLD(x)	((x) << 2)
+#define EBC_WIN_CTRL_WIN_FMT_Y4			(0x0 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_Y8			(0x1 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_XRGB8888		(0x2 << 0)
+#define EBC_WIN_CTRL_WIN_FMT_RGB565		(0x3 << 0)
+#define EBC_WIN_MST0			0x0024
+#define EBC_WIN_MST1			0x0028
+#define EBC_WIN_VIR			0x002c
+#define EBC_WIN_VIR_WIN_VIR_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_VIR_WIN_VIR_WIDTH(x)		((x) << 0)
+#define EBC_WIN_ACT			0x0030
+#define EBC_WIN_ACT_WIN_ACT_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_ACT_WIN_ACT_WIDTH(x)		((x) << 0)
+#define EBC_WIN_DSP			0x0034
+#define EBC_WIN_DSP_WIN_DSP_HEIGHT(x)		((x) << 16)
+#define EBC_WIN_DSP_WIN_DSP_WIDTH(x)		((x) << 0)
+#define EBC_WIN_DSP_ST			0x0038
+#define EBC_WIN_DSP_ST_WIN_DSP_YST(x)		((x) << 16)
+#define EBC_WIN_DSP_ST_WIN_DSP_XST(x)		((x) << 0)
+#define EBC_INT_STATUS			0x003c
+#define EBC_INT_STATUS_DSP_FRM_INT_NUM(x)	((x) << 12)
+#define EBC_INT_STATUS_LINE_FLAG_INT_CLR	BIT(11)
+#define EBC_INT_STATUS_DSP_FRM_INT_CLR		BIT(10)
+#define EBC_INT_STATUS_DSP_END_INT_CLR		BIT(9)
+#define EBC_INT_STATUS_FRM_END_INT_CLR		BIT(8)
+#define EBC_INT_STATUS_LINE_FLAG_INT_MSK	BIT(7)
+#define EBC_INT_STATUS_DSP_FRM_INT_MSK		BIT(6)
+#define EBC_INT_STATUS_DSP_END_INT_MSK		BIT(5)
+#define EBC_INT_STATUS_FRM_END_INT_MSK		BIT(4)
+#define EBC_INT_STATUS_LINE_FLAG_INT_ST		BIT(3)
+#define EBC_INT_STATUS_DSP_FRM_INT_ST		BIT(2)
+#define EBC_INT_STATUS_DSP_END_INT_ST		BIT(1)
+#define EBC_INT_STATUS_FRM_END_INT_ST		BIT(0)
+#define EBC_VCOM0			0x0040
+#define EBC_VCOM1			0x0044
+#define EBC_VCOM2			0x0048
+#define EBC_VCOM3			0x004c
+#define EBC_CONFIG_DONE			0x0050
+#define EBC_CONFIG_DONE_REG_CONFIG_DONE		BIT(0)
+#define EBC_VNUM			0x0054
+#define EBC_VNUM_DSP_VCNT(x)			((x) << 16)
+#define EBC_VNUM_LINE_FLAG_NUM(x)		((x) << 0)
+#define EBC_WIN_MST2			0x0058
+#define EBC_LUT_DATA			0x1000
+
+#define EBC_FRAME_PENDING		-1
+
+#define EBC_MAX_PHASES			256
+
+#define EBC_NUM_LUT_REGS		0x1000
+
+#define EBC_FRAME_TIMEOUT		msecs_to_jiffies(25)
+#define EBC_REFRESH_TIMEOUT		msecs_to_jiffies(3000)
+#define EBC_SUSPEND_DELAY_MS		2000
+
+#define EBC_FIRMWARE		"rockchip/ebc.wbf"
+MODULE_FIRMWARE(EBC_FIRMWARE);
+#define EBC_OFFCONTENT "rockchip/rockchip_ebc_default_screen_x4y4.bin"
+MODULE_FIRMWARE(EBC_OFFCONTENT);
+#define EBC_CUSTOM_WF "rockchip/custom_wf.bin"
+MODULE_FIRMWARE(EBC_CUSTOM_WF);
+
+static const char *custom_wf_magic_version = "CLUT0002";
+
+#define ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT			BIT(0)
+#define ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH			BIT(1)
+#define ROCKCHIP_EBC_WORK_ITEM_INIT				BIT(2)
+#define ROCKCHIP_EBC_WORK_ITEM_SUSPEND				BIT(3)
+#define ROCKCHIP_EBC_WORK_ITEM_RESCHEDULE			BIT(4)
+#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE		BIT(5)
+#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE			BIT(6)
+#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE	BIT(7)
+#define ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE	BIT(8)
+#define ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE	BIT(9)
+
+static const u8 dither_bayer_04[] = {
+	7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10, 7, 8, 2, 10,
+	12, 4, 14, 6, 12, 4, 14, 6, 12, 4, 14, 6, 12, 4, 14, 6,
+	3, 11, 1, 9, 3, 11, 1, 9, 3, 11, 1, 9, 3, 11, 1, 9,
+	15, 7, 13, 5, 15, 7, 13, 5, 15, 7, 13, 5, 15, 7, 13, 5,
+};
+
+// https://momentsingraphics.de/BlueNoise.html : 16_16/LDR_LLL1_0.png >> 4
+static const u8 dither_blue_noise_16[] = {
+	6, 3, 8, 10, 7, 12, 4, 11, 12, 3, 9, 5, 4, 2, 5, 15,
+	1, 6, 14, 13, 2, 15, 9, 1, 2, 6, 13, 10, 12, 8, 0, 10,
+	7, 11, 4, 0, 4, 10, 7, 5, 13, 8, 15, 1, 7, 3, 14, 13,
+	2, 12, 9, 8, 11, 6, 3, 14, 10, 3, 0, 11, 4, 15, 9, 4,
+	0, 15, 3, 5, 14, 0, 12, 1, 11, 6, 9, 12, 2, 5, 11, 6,
+	13, 10, 7, 2, 13, 9, 8, 4, 15, 5, 14, 3, 7, 9, 1, 8,
+	5, 12, 1, 15, 4, 2, 11, 7, 0, 2, 10, 6, 15, 11, 13, 3,
+	6, 11, 9, 7, 10, 6, 14, 8, 13, 9, 12, 0, 4, 1, 14, 2,
+	14, 1, 4, 0, 12, 3, 1, 12, 5, 3, 7, 13, 8, 5, 7, 9,
+	13, 8, 15, 10, 14, 6, 2, 15, 10, 1, 14, 11, 3, 12, 10, 0,
+	6, 11, 3, 5, 8, 11, 9, 4, 2, 8, 6, 9, 2, 15, 5, 3,
+	1, 4, 13, 2, 0, 4, 14, 7, 12, 15, 0, 4, 7, 1, 14, 8,
+	15, 10, 7, 12, 15, 6, 9, 0, 13, 10, 6, 13, 12, 5, 12, 10,
+	1, 5, 9, 1, 10, 11, 3, 1, 5, 4, 2, 8, 10, 3, 7, 2,
+	13, 14, 3, 8, 5, 14, 13, 7, 9, 15, 11, 1, 15, 6, 0, 8,
+	4, 11, 0, 13, 2, 6, 0, 8, 14, 5, 0, 7, 14, 12, 9, 11,
+};
+
+// https://momentsingraphics.de/BlueNoise.html : 32_32/LDR_LLL1_0.png >> 4
+static const u8 dither_blue_noise_32[] = {
+	9, 10, 13, 15, 9, 12, 13, 14, 8, 15, 2, 3, 15, 9, 6, 0, 15, 7, 3, 5, 4, 11, 14, 3, 7, 1, 4, 6, 9, 12, 5, 4,
+	15, 5, 3, 11, 7, 4, 1, 6, 4, 10, 13, 7, 5, 2, 13, 4, 8, 10, 1, 14, 2, 13, 7, 2, 15, 9, 11, 5, 0, 13, 1, 7,
+	2, 6, 1, 2, 14, 0, 10, 8, 11, 5, 0, 10, 8, 14, 11, 2, 11, 14, 12, 9, 7, 1, 10, 8, 5, 12, 2, 13, 14, 8, 3, 11,
+	9, 14, 13, 8, 6, 9, 13, 2, 15, 1, 9, 14, 1, 4, 10, 6, 5, 0, 6, 4, 15, 11, 5, 0, 13, 3, 7, 10, 7, 2, 10, 14,
+	1, 11, 5, 4, 12, 15, 5, 3, 7, 6, 12, 3, 6, 12, 1, 7, 13, 3, 8, 12, 0, 3, 14, 11, 6, 4, 15, 1, 4, 12, 5, 6,
+	8, 0, 10, 7, 1, 11, 0, 9, 10, 14, 4, 11, 8, 15, 3, 14, 9, 15, 11, 2, 9, 6, 13, 9, 1, 10, 6, 12, 9, 0, 15, 3,
+	4, 15, 9, 14, 3, 7, 4, 14, 13, 0, 8, 2, 5, 0, 10, 7, 2, 1, 5, 7, 13, 4, 7, 2, 11, 8, 14, 2, 8, 11, 13, 9,
+	12, 5, 2, 13, 6, 10, 12, 1, 6, 2, 10, 14, 12, 9, 4, 12, 6, 10, 14, 10, 8, 0, 15, 1, 14, 4, 0, 13, 3, 6, 2, 7,
+	14, 3, 11, 0, 8, 15, 3, 8, 11, 5, 15, 3, 7, 1, 13, 15, 0, 4, 13, 1, 3, 12, 10, 9, 5, 3, 11, 7, 15, 5, 12, 1,
+	10, 6, 8, 5, 1, 12, 5, 2, 10, 9, 1, 13, 11, 5, 3, 8, 11, 8, 7, 5, 15, 6, 4, 13, 7, 15, 9, 6, 0, 10, 3, 8,
+	13, 1, 13, 14, 10, 2, 7, 14, 13, 7, 6, 8, 0, 12, 9, 2, 6, 12, 2, 9, 11, 2, 8, 0, 12, 1, 10, 2, 8, 14, 15, 4,
+	2, 6, 9, 4, 7, 12, 9, 0, 4, 3, 12, 2, 15, 4, 7, 10, 15, 0, 14, 5, 0, 14, 11, 5, 8, 3, 13, 12, 4, 11, 5, 0,
+	12, 15, 11, 3, 1, 15, 5, 9, 13, 1, 14, 10, 6, 9, 14, 1, 4, 3, 10, 13, 8, 6, 12, 3, 15, 6, 4, 9, 1, 6, 9, 7,
+	4, 9, 0, 5, 8, 13, 2, 6, 11, 7, 4, 11, 0, 3, 13, 5, 12, 7, 8, 1, 4, 2, 9, 14, 1, 10, 7, 11, 15, 3, 13, 10,
+	1, 7, 14, 12, 6, 10, 3, 12, 14, 2, 8, 5, 12, 8, 2, 11, 8, 3, 11, 15, 12, 7, 0, 5, 11, 8, 0, 14, 2, 5, 0, 14,
+	6, 4, 10, 2, 15, 1, 8, 5, 0, 10, 15, 4, 1, 15, 10, 0, 14, 13, 0, 5, 6, 11, 15, 9, 4, 2, 12, 5, 11, 9, 8, 12,
+	11, 3, 13, 7, 4, 11, 13, 9, 7, 3, 13, 6, 14, 7, 5, 4, 6, 9, 2, 10, 13, 3, 1, 6, 13, 14, 9, 3, 7, 4, 15, 2,
+	8, 1, 15, 9, 2, 6, 0, 15, 4, 11, 1, 11, 9, 3, 11, 15, 7, 3, 14, 7, 8, 4, 12, 8, 5, 0, 8, 15, 1, 13, 6, 9,
+	13, 11, 5, 0, 14, 10, 8, 3, 12, 6, 8, 4, 0, 12, 1, 10, 0, 12, 5, 11, 1, 15, 13, 2, 10, 3, 6, 11, 1, 12, 3, 0,
+	4, 6, 8, 11, 6, 4, 12, 1, 10, 14, 2, 15, 10, 5, 8, 13, 2, 9, 14, 0, 4, 9, 5, 7, 11, 14, 4, 12, 7, 10, 5, 14,
+	10, 1, 12, 3, 13, 2, 14, 7, 5, 0, 7, 12, 3, 14, 4, 6, 4, 10, 3, 13, 10, 2, 6, 0, 15, 1, 9, 2, 5, 14, 9, 7,
+	2, 15, 7, 1, 10, 8, 0, 11, 9, 13, 4, 7, 11, 0, 9, 12, 15, 1, 6, 7, 8, 14, 12, 10, 3, 7, 8, 15, 0, 3, 1, 13,
+	4, 5, 9, 14, 4, 6, 15, 3, 2, 15, 10, 1, 5, 15, 7, 1, 13, 8, 11, 15, 2, 1, 4, 13, 5, 11, 13, 4, 10, 12, 8, 11,
+	14, 12, 0, 3, 10, 13, 5, 9, 11, 1, 6, 13, 8, 2, 11, 3, 5, 4, 0, 9, 5, 11, 7, 9, 2, 0, 12, 6, 2, 15, 6, 0,
+	8, 10, 13, 7, 11, 2, 0, 12, 6, 8, 14, 9, 3, 14, 6, 9, 14, 12, 14, 3, 10, 13, 3, 6, 15, 8, 3, 10, 7, 5, 9, 3,
+	6, 2, 4, 8, 5, 15, 7, 10, 2, 4, 3, 11, 1, 12, 4, 8, 2, 7, 9, 6, 1, 7, 14, 10, 12, 5, 14, 0, 13, 13, 1, 14,
+	4, 12, 15, 1, 9, 14, 3, 8, 13, 15, 0, 5, 7, 15, 0, 12, 10, 2, 0, 11, 13, 4, 0, 1, 3, 7, 9, 11, 4, 2, 8, 11,
+	14, 7, 10, 0, 6, 11, 4, 0, 6, 12, 10, 9, 13, 10, 6, 3, 15, 5, 14, 7, 15, 9, 8, 11, 13, 2, 15, 1, 6, 7, 12, 0,
+	2, 9, 3, 12, 5, 2, 12, 14, 9, 7, 1, 4, 2, 5, 1, 9, 13, 6, 10, 4, 3, 5, 7, 14, 5, 6, 10, 8, 12, 15, 10, 5,
+	13, 5, 15, 8, 14, 7, 8, 1, 5, 3, 14, 13, 8, 15, 11, 7, 1, 8, 0, 12, 2, 12, 1, 13, 3, 1, 12, 0, 4, 3, 1, 8,
+	11, 1, 4, 11, 0, 10, 15, 11, 9, 12, 7, 0, 10, 6, 3, 14, 4, 11, 14, 6, 8, 15, 4, 9, 10, 8, 5, 14, 7, 9, 13, 6,
+	12, 0, 8, 6, 2, 3, 5, 2, 0, 6, 11, 4, 12, 1, 9, 12, 5, 2, 13, 9, 0, 10, 6, 0, 11, 13, 15, 2, 10, 2, 15, 3,
+};
+
+static int default_hint = ROCKCHIP_EBC_HINT_BIT_DEPTH_Y4 | ROCKCHIP_EBC_HINT_THRESHOLD | ROCKCHIP_EBC_HINT_REDRAW;
+module_param(default_hint, int, 0644);
+MODULE_PARM_DESC(default_hint, "hint to use for pixels not covered otherwise");
+
+static int redraw_delay = 0;
+module_param(redraw_delay, int, 0644);
+MODULE_PARM_DESC(redraw_delay, "number of hardware frames to delay redraws");
+
+static int early_cancellation_addition = 2;
+module_param(early_cancellation_addition, int, 0644);
+MODULE_PARM_DESC(early_cancellation_addition, "number of additional frames to drive a pixel when cancelling it");
+
+static bool shrink_virtual_window = false;
+module_param(shrink_virtual_window, bool, 0644);
+MODULE_PARM_DESC(shrink_virtual_window, "shrink virtual window to ongoing clip");
+
+static bool direct_mode = true;
+#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE
+module_param(direct_mode, bool, 0444);
+MODULE_PARM_DESC(direct_mode, "Don't use the controller's 3WIN mode");
+#endif
+
+static int limit_fb_blits = -1;
+module_param(limit_fb_blits, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(split_area_limit, "how many fb blits to allow. -1 does not limit");
+
+static bool no_off_screen = false;
+module_param(no_off_screen, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(no_off_screen, "If set to true, do not apply the off screen on next loop exit");
+
+/* delay parameters used to delay the return of plane_atomic_atomic */
+/* see plane_atomic_update function for specific usage of these parameters */
+static int delay_a = 200;
+module_param(delay_a, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(delay_a, "delay_a");
+
+static int refresh_thread_wait_idle = 2000;
+module_param(refresh_thread_wait_idle, int, 0644);
+MODULE_PARM_DESC(refresh_thread_wait_idle, "Number of ms to wait and last frame start before stopping the refresh thread");
+
+#define DITHERING_BAYER 0
+#define DITHERING_BLUE_NOISE_16 1
+#define DITHERING_BLUE_NOISE_32 2
+
+static int dithering_method = 2;
+module_param(dithering_method, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(dithering_method, "Dithering method, 0-2");
+
+static int bw_threshold = 7;
+module_param(bw_threshold, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(bw_threshold, "black and white threshold");
+
+static int y2_dt_thresholds = 0x070f16;
+module_param(y2_dt_thresholds, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(y2_dt_thresholds, "int whose lowest three bytes indicate thresholds when dithering");
+
+static int y2_th_thresholds = 0x04080c;
+module_param(y2_th_thresholds, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(y2_th_thresholds, "int whose lowest three bytes indicate thresholds");
+
+static int dclk_select = 0;
+module_param(dclk_select, int, 0644);
+MODULE_PARM_DESC(dclk_select, "-1: use dclk from mode, 0: 200 MHz (default), 1: 250");
+
+static int temp_override = 0;
+module_param(temp_override, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(temp_override, "Values > 0 override the temperature");
+
+/* Values for testing should be multiples of 8 and >= 8
+ *
+ * */
+static int hskew_override = 0;
+module_param(hskew_override, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(hskew_override, "Override hskew value");
+
+static unsigned int rect_hint_batch = 20;
+module_param(rect_hint_batch, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(rect_hint_batch, "Batch size when reading rect_hints in ioctl");
+
+static int testing = 0;
+
+DEFINE_DRM_GEM_FOPS(rockchip_ebc_fops);
+
+static int ioctl_trigger_global_refresh(struct drm_device *dev, void *data,
+		struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_trigger_global_refresh *args = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+
+	if (args->trigger_global_refresh){
+		spin_lock(&ebc->work_item_lock);
+		ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH;
+		spin_unlock(&ebc->work_item_lock);
+		// try to trigger the refresh immediately
+		wake_up_process(ebc->refresh_thread);
+	}
+
+	return 0;
+}
+
+static int ioctl_set_off_screen(struct drm_device *dev, void *data,
+				struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_off_screen *args = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+	int res = 0;
+	void __user *ptr_off_screen = u64_to_user_ptr(args->ptr_screen_content);
+
+	if (access_ok(ptr_off_screen, ebc->num_pixels)) {
+		res = copy_from_user(ebc->final_off_screen,
+				     ptr_off_screen, ebc->num_pixels);
+		if (res)
+			pr_err("Could not copy off screen content from user-supplied data pointer (bytes not copied: %d)",
+			       res);
+	} else
+		res = -EFAULT;
+	return res;
+}
+
+struct ebc_crtc_state {
+	struct drm_crtc_state		base;
+	struct rockchip_ebc_ctx		*ctx;
+};
+
+	static inline struct ebc_crtc_state *
+to_ebc_crtc_state(struct drm_crtc_state *crtc_state)
+{
+	return container_of(crtc_state, struct ebc_crtc_state, base);
+}
+
+static int ioctl_extract_fbs(struct drm_device *dev, void *data,
+		struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_extract_fbs *args = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+	struct rockchip_ebc_ctx *ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx;
+	int res = 0;
+	unsigned long num_pixels = ebc->num_pixels;
+	int refresh_index = ctx->refresh_index;
+
+	if (args->ptr_packed_inner_outer_nextprev) {
+		if (access_ok(args->ptr_packed_inner_outer_nextprev, 3 * num_pixels))
+			res |= copy_to_user(
+				args->ptr_packed_inner_outer_nextprev,
+				ebc->packed_inner_outer_nextprev,
+				3 * num_pixels);
+		else
+			res |= -EFAULT;
+	}
+	if (args->ptr_hints) {
+		if (access_ok(args->ptr_hints, num_pixels))
+			res |= copy_to_user(args->ptr_hints,
+					    ctx->hints_buffer[refresh_index],
+					    num_pixels);
+		else
+			res |= -EFAULT;
+	}
+	if (args->ptr_prelim_target) {
+		if (access_ok(args->ptr_prelim_target, num_pixels))
+			res |= copy_to_user(
+				args->ptr_prelim_target,
+				ctx->prelim_target_buffer[refresh_index],
+				num_pixels);
+		else
+			res |= -EFAULT;
+	}
+	if (args->ptr_phase1) {
+		if (access_ok(args->ptr_phase1, num_pixels >> 2))
+			res |= copy_to_user(args->ptr_phase1, ebc->phase[0],
+					    ebc->phase_size);
+		else
+			res |= -EFAULT;
+	}
+	if (args->ptr_phase2) {
+		if (access_ok(args->ptr_phase2, num_pixels >> 2))
+			res |= copy_to_user(args->ptr_phase2, ebc->phase[1],
+					    ebc->phase_size);
+		else
+			res |= -EFAULT;
+	}
+	return res;
+}
+
+/** rockchip_ebc_apply_rect_hints() - Apply pixel hints from userspace
+ * @ebc: Driver data
+ * @num_rects: Number of rectangle in the rect_hints array.
+ * @rect_hints: Array of rectangle with associated pixel hints.
+ *
+ * Context: This function should only be called from userspace, as it calls copy_from_user()
+ *          a bunch of time. Additionally, kmalloc_array is call once to allocate a buffer.
+ *          It can sleep during initial allocation, and data copy from userland.
+ *          The function locks ebc->hints_ioctl_lock after copying a batch of rect_hints, and
+ *          releases it before the next copy.
+ */
+static int rockchip_ebc_apply_rect_hints(
+		struct rockchip_ebc *ebc, u32 num_rects,
+		struct drm_rockchip_ebc_rect_hint __user *rect_hints)
+{
+	int ret = 0;
+
+	if (!access_ok(rect_hints, num_rects * sizeof(*rect_hints))) {
+		return -EFAULT;
+	}
+
+	struct drm_rockchip_ebc_rect_hint *rect_hint_arr = kmalloc_array(min(rect_hint_batch, num_rects), sizeof(*rect_hints), GFP_KERNEL);
+	if (!rect_hint_arr) {
+		return -EFAULT;
+	}
+
+	size_t processed = 0;
+	while (num_rects > 0 && ret == 0) {
+		size_t to_process = min(rect_hint_batch, num_rects);
+		size_t copy_size = to_process * sizeof(*rect_hints);
+		size_t remain = copy_from_user(rect_hint_arr, rect_hints + processed, copy_size);
+
+		// I'm not even sure this can really happen, but for now this will do.
+		if (remain > 0) {
+			// Alternatively, we could do an early return here.
+			to_process = (copy_size - remain) % sizeof(*rect_hints);
+			ret = -EFAULT;
+		}
+
+		spin_lock(&ebc->hints_ioctl_lock);
+		// TODO: neon blit
+		for (int i = 0; i < to_process; ++i) {
+			struct drm_rect *r = (struct drm_rect *)&(rect_hint_arr[i].rect);
+			u8 hint = rect_hint_arr[i].hints & ROCKCHIP_EBC_HINT_MASK;
+
+			for (unsigned int y = max(0, r->y1);
+			     y < min(ebc->height, (u32)r->y2); ++y) {
+				unsigned int x1 = max(0, r->x1);
+				unsigned int x2 = min(ebc->pixel_pitch, (u32)r->x2);
+				unsigned int width = min(ebc->pixel_pitch, x2 - x1);
+				if (x1 < ebc->pixel_pitch)
+					memset(ebc->hints_ioctl + y * ebc->pixel_pitch + x1, hint, width);
+			}
+		}
+		spin_unlock(&ebc->hints_ioctl_lock);
+
+		num_rects -= to_process;
+		processed += to_process;
+	}
+
+	kfree(rect_hint_arr);
+	return ret;
+}
+
+static int ioctl_rect_hints(struct drm_device *dev, void *data,
+		struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_rect_hints *rect_hints = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+
+	// Alternatively, use separate buffer and only lock when copying final buffer
+	if (rect_hints->set_default_hint) {
+		int hint = rect_hints->default_hint & ROCKCHIP_EBC_HINT_MASK;
+		spin_lock(&ebc->hints_ioctl_lock);
+		memset(ebc->hints_ioctl, hint, ebc->num_pixels);
+		spin_unlock(&ebc->hints_ioctl_lock);
+		default_hint = hint;
+	}
+
+	if (rect_hints->num_rects)
+		return rockchip_ebc_apply_rect_hints(ebc, rect_hints->num_rects, u64_to_user_ptr(rect_hints->rect_hints));
+
+	return 0;
+}
+
+static int ioctl_mode(struct drm_device *dev, void *data,
+			       struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_mode *mode = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+	int ret = 0;
+
+	if (mode->set_driver_mode) {
+		spin_lock(&ebc->work_item_lock);
+		switch (mode->driver_mode) {
+		case ROCKCHIP_EBC_DRIVER_MODE_NORMAL:
+			ebc->work_item |=
+				ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE;
+			break;
+		case ROCKCHIP_EBC_DRIVER_MODE_FAST:
+			ebc->work_item |=
+				ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE;
+			break;
+		default:
+			ret |= -EINVAL;
+		}
+		spin_unlock(&ebc->work_item_lock);
+	} else {
+		mode->driver_mode = ebc->driver_mode;
+	}
+	if (mode->set_dither_mode) {
+		switch (mode->dither_mode) {
+		case ROCKCHIP_EBC_DITHER_MODE_BAYER:
+			ebc->dithering_texture_size_hint = 4;
+			ebc->dithering_texture = dither_bayer_04;
+			break;
+		case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16:
+			ebc->dithering_texture_size_hint = 4;
+			ebc->dithering_texture = dither_blue_noise_16;
+			ebc->dithering_texture_size_hint = 16;
+			break;
+		case ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32:
+			ebc->dithering_texture = dither_blue_noise_32;
+			ebc->dithering_texture_size_hint = 32;
+			break;
+		default:
+			ret |= -EINVAL;
+		}
+	} else {
+		if (ebc->dithering_texture == dither_bayer_04)
+			mode->dither_mode = ROCKCHIP_EBC_DITHER_MODE_BAYER;
+		else if (ebc->dithering_texture == dither_blue_noise_16)
+			mode->dither_mode =
+				ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16;
+		else if (ebc->dithering_texture == dither_blue_noise_32)
+			mode->dither_mode =
+				ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32;
+	}
+	if (mode->set_redraw_delay) {
+		if (mode->redraw_delay != redraw_delay) {
+			redraw_delay = mode->redraw_delay;
+			spin_lock(&ebc->work_item_lock);
+			ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT;
+			spin_unlock(&ebc->work_item_lock);
+		}
+	} else {
+		mode->redraw_delay = ebc->redraw_delay;
+	}
+
+	return ret;
+}
+
+static int ioctl_zero_waveform(struct drm_device *dev, void *data,
+			       struct drm_file *file_priv)
+{
+	struct drm_rockchip_ebc_zero_waveform *zero_waveform = data;
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+
+	if (zero_waveform->set_zero_waveform_mode) {
+		spin_lock(&ebc->work_item_lock);
+		if (zero_waveform->zero_waveform_mode)
+			ebc->work_item |=
+				ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE;
+		else
+			ebc->work_item |=
+				ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE;
+		spin_unlock(&ebc->work_item_lock);
+	} else {
+		zero_waveform->zero_waveform_mode =
+			ebc->driver_mode ==
+				ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
+	}
+
+	wake_up_process(ebc->refresh_thread);
+	return 0;
+}
+
+static int ioctl_phase_sequence(struct drm_device *dev, void *data,
+				struct drm_file *file_priv)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev->dev);
+
+	spin_lock(&ebc->phase_sequence_lock);
+	memcpy(ebc->phase_sequence, data,
+	       sizeof(struct drm_rockchip_ebc_phase_sequence));
+
+	// Driving
+	ebc->phase_sequence->num_seqs = min(ebc->phase_sequence->num_seqs,
+					    ROCKCHIP_EBC_MAX_PHASE_SEQUENCES);
+	for (int i = 0; i < ebc->phase_sequence->num_seqs; ++i) {
+		ebc->phase_sequence->elms[i].num_regions =
+			min(ebc->phase_sequence->elms[i].num_regions,
+			    ROCKCHIP_EBC_MAX_REGIONS);
+	}
+	spin_unlock(&ebc->phase_sequence_lock);
+	spin_lock(&ebc->work_item_lock);
+	ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE;
+	spin_unlock(&ebc->work_item_lock);
+
+	wake_up_process(ebc->refresh_thread);
+
+	return 0;
+}
+
+static const struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = {
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_GLOBAL_REFRESH,
+			  ioctl_trigger_global_refresh, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_OFF_SCREEN, ioctl_set_off_screen,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_EXTRACT_FBS, ioctl_extract_fbs,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_RECT_HINTS, ioctl_rect_hints,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_MODE, ioctl_mode, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_ZERO_WAVEFORM, ioctl_zero_waveform,
+			  DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(ROCKCHIP_EBC_PHASE_SEQUENCE, ioctl_phase_sequence,
+			  DRM_RENDER_ALLOW),
+};
+
+static const struct drm_driver rockchip_ebc_drm_driver = {
+	DRM_GEM_SHMEM_DRIVER_OPS,
+	DRM_FBDEV_SHMEM_DRIVER_OPS,
+	.major			= 0,
+	.minor			= 3,
+	.name			= "rockchip-ebc",
+	.desc			= "Rockchip E-Book Controller",
+	.driver_features	= DRIVER_ATOMIC | DRIVER_GEM | DRIVER_MODESET,
+	.fops			= &rockchip_ebc_fops,
+	.ioctls = ioctls,
+	.num_ioctls = DRM_ROCKCHIP_EBC_NUM_IOCTLS,
+};
+
+static const struct drm_mode_config_funcs rockchip_ebc_mode_config_funcs = {
+	.fb_create		= drm_gem_fb_create_with_dirty,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+static void rockchip_ebc_ctx_free(struct rockchip_ebc_ctx *ctx)
+{
+	pr_info("EBC: rockchip_ebc_ctx_free");
+
+	vfree(ctx->hints_buffer[0]);
+	vfree(ctx->hints_buffer[1]);
+	vfree(ctx->hints_buffer[2]);
+	vfree(ctx->prelim_target_buffer[0]);
+	vfree(ctx->prelim_target_buffer[1]);
+	vfree(ctx->prelim_target_buffer[2]);
+
+	kfree(ctx);
+}
+
+static struct rockchip_ebc_ctx *rockchip_ebc_ctx_alloc(struct rockchip_ebc *ebc)
+{
+	pr_debug("%s:%d\n", __func__, __LINE__);
+	struct rockchip_ebc_ctx *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return NULL;
+
+	ctx->hints_buffer[0] = vmalloc(ebc->num_pixels);
+	ctx->hints_buffer[1] = vmalloc(ebc->num_pixels);
+	ctx->hints_buffer[2] = vmalloc(ebc->num_pixels);
+	ctx->prelim_target_buffer[0] = vmalloc(ebc->num_pixels);
+	ctx->prelim_target_buffer[1] = vmalloc(ebc->num_pixels);
+	ctx->prelim_target_buffer[2] = vmalloc(ebc->num_pixels);
+
+	if (!(ctx->hints_buffer[0] && ctx->hints_buffer[1] && ctx->hints_buffer[2] && ctx->prelim_target_buffer[0] && ctx->prelim_target_buffer[1] && ctx->prelim_target_buffer[2])) {
+		rockchip_ebc_ctx_free(ctx);
+		return NULL;
+	}
+
+	kref_init(&ctx->kref);
+	spin_lock_init(&ctx->buffer_switch_lock);
+	for (int i = 0; i < 3; ++i) {
+		ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE;
+		ctx->src_clip_extended[i] = DRM_RECT_EMPTY_EXTANDABLE;
+	}
+
+	return ctx;
+}
+
+static void rockchip_ebc_ctx_release(struct kref *kref)
+{
+	struct rockchip_ebc_ctx *ctx =
+		container_of(kref, struct rockchip_ebc_ctx, kref);
+	pr_info("ebc: %s", __func__);
+
+	return rockchip_ebc_ctx_free(ctx);
+}
+
+static void rockchip_ebc_change_lut(struct rockchip_ebc *ebc)
+{
+	int temp_index;
+	struct drm_epd_lut_temp_v2 *lut;
+	struct drm_epd_lut_v2 *luts = &ebc->lut_custom;
+
+	for (temp_index = 0; temp_index < luts->num_temp_ranges - 1; ++temp_index) {
+		if (ebc->temperature <= luts->luts[temp_index].temp_upper)
+			break;
+	}
+	ebc->lut_custom_active = lut = luts->luts + temp_index;
+	int waiting_remaining = redraw_delay;
+	for (int i = lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_WAITING]; i < ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH; ++i) {
+		int waiting_this = max(0, min(waiting_remaining, 0x1f));
+		waiting_remaining -= waiting_this;
+		if (!waiting_remaining ||
+		    i == ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH - 1)
+			waiting_this |= 0x20;
+		for (int next = 0; next < 16; ++next) {
+			for (int prev = 0; prev < 16; ++prev) {
+				lut->lut[(prev << (4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)) + (next << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) + i] = waiting_this;
+			}
+		}
+	}
+	ebc->redraw_delay = redraw_delay = redraw_delay - waiting_remaining;
+	// TODO: generalise for temperature ranges for which more than 31 phases are required
+	ebc->inner_0_15 = lut->lut[(0xf << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT) +
+		lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_DU]];
+	ebc->inner_15_0 =
+		lut->lut[(0xf << (ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT + 4)) +
+			lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_DU]];
+}
+
+static void print_lut(struct rockchip_ebc *ebc)
+{
+	struct drm_epd_lut_temp_v2 *lut_active = ebc->lut_custom_active;
+	u8 *lut = lut_active->lut;
+	pr_info("%s temp_lower=%d temp_upper=%d offsets=%16ph", __func__, lut_active->temp_lower, lut_active->temp_upper, lut_active->offsets);
+	pr_info("%s lut=%64ph", __func__, lut + 15 * 64);
+}
+
+static void rockchip_ebc_blit_direct_phase(struct rockchip_ebc *ebc,
+					   u8 *phase_buffer,
+					   struct drm_mode_rect *rect, u8 phase)
+{
+	u8 phases[3] = {0x00, 0x55, 0xaa};
+	phase = phases[phase & 3];
+	int x1 = max(0, rect->x1);
+	int x2 = min((int) ebc->pixel_pitch, rect->x2);
+
+	u8 *line = phase_buffer;
+	for (int y = max(0, rect->y1); y < min((int) ebc->height, rect->y2); ++y) {
+		memset(line + y * ebc->phase_pitch + (x1 >> 2), phase, (x2 >> 2) - (x1 >> 2));
+	}
+}
+
+static void rockchip_ebc_phase_sequence(struct rockchip_ebc *ebc)
+{
+	struct drm_device *drm = &ebc->drm;
+	struct device *dev = drm->dev;
+
+	spin_lock(&ebc->phase_sequence_lock);
+	struct drm_rockchip_ebc_phase_sequence *ps = ebc->phase_sequence;
+
+	int previous_temp_override = temp_override;
+	u32 frame_counter;
+	if (ps->do_force_temperature)
+		temp_override = ps->force_temperature;
+
+	if (ps->do_init || ps->do_gc16)
+		rockchip_ebc_change_lut(ebc);
+
+	struct drm_epd_lut_temp_v2 *lut = ebc->lut_custom_active;
+	u8 phases[3] = { 0x00, 0x55, 0xaa };
+	u32 frame = 0;
+	bool awaiting_completion = false;
+
+	// TODO: consider performing do_init and go_gc16 in partial_refresh function
+	if (ps->do_init) {
+		u8 outer = lut->offsets[ROCKCHIP_EBC_CUSTOM_WF_INIT];
+		u8 inner = lut->lut[outer];
+		for (frame = 0;; frame++) {
+			u8 phase = phases[(inner & 0xc0) >> 6];
+			dma_addr_t phase_handle = ebc->phase_handles[phase % 2];
+			if (inner & 0x1f) {
+				memset(ebc->phase[frame % 2], phase, ebc->phase_size);
+				inner -= 1;
+			}
+			if (!(inner & 0x1f)) {
+				if (inner & 0x20)
+					break;
+				else
+					inner = lut->lut[++outer];
+			}
+			dma_sync_single_for_device(dev, phase_handle,
+						   ebc->phase_size,
+						   DMA_TO_DEVICE);
+			if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) {
+				pr_err("Frame %d timed out!\n", frame);
+			}
+			awaiting_completion = false;
+			regmap_write(ebc->regmap, EBC_WIN_MST0, phase_handle);
+			regmap_write(ebc->regmap, EBC_CONFIG_DONE,
+				     EBC_CONFIG_DONE_REG_CONFIG_DONE);
+			regmap_write(ebc->regmap, EBC_DSP_START,
+				     ebc->dsp_start |
+					     EBC_DSP_START_DSP_FRM_START);
+			awaiting_completion = true;
+		}
+		fsleep(ps->delay_ms * 1000);
+	}
+	if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) {
+		pr_err("Frame %d timed out!\n", frame);
+	}
+	awaiting_completion = false;
+
+	if (ps->do_gc16) {
+		// TODO:
+		fsleep(ps->delay_ms * 1000);
+	}
+
+	frame = 0;
+	for (int i_seq = 0; i_seq < ps->num_seqs; ++i_seq) {
+		struct drm_rockchip_ebc_phase_sequence_element *elm =
+			ps->elms + i_seq;
+		for (int i_frame = 0; i_frame < elm->num_frames;
+				      ++i_frame, ++frame) {
+			ktime_t t1 = ktime_get();
+			if (i_frame < 2) {
+				for (int i_region = 0;
+				     i_region < elm->num_regions; ++i_region) {
+					rockchip_ebc_blit_direct_phase(
+						ebc, ebc->phase[frame % 2],
+						elm->rect + i_region,
+						elm->phase[i_region]);
+				}
+			}
+			dma_sync_single_for_device(dev, ebc->phase_handles[frame % 2],
+						   ebc->phase_size,
+						   DMA_TO_DEVICE);
+			if (awaiting_completion && !wait_for_completion_timeout(&ebc->display_end, EBC_FRAME_TIMEOUT)) {
+				pr_err("Frame %d timed out!\n", frame);
+			}
+			awaiting_completion = false;
+			regmap_write(ebc->regmap, EBC_WIN_MST0,
+				     ebc->phase_handles[frame % 2]);
+			regmap_write(ebc->regmap, EBC_CONFIG_DONE,
+				     EBC_CONFIG_DONE_REG_CONFIG_DONE);
+			s64 delay_frame = elm->delay_ms * 1000 -
+				ktime_us_delta(ktime_get(), t1);
+			if (delay_frame > 0)
+				fsleep(delay_frame);
+			regmap_write(ebc->regmap, EBC_DSP_START,
+				     ebc->dsp_start |
+					     EBC_DSP_START_DSP_FRM_START);
+			awaiting_completion = true;
+		}
+
+	}
+	if (ps->do_force_temperature) {
+		temp_override = previous_temp_override;
+		rockchip_ebc_change_lut(ebc);
+	}
+
+	spin_unlock(&ebc->phase_sequence_lock);
+}
+
+static void rockchip_ebc_partial_refresh(struct rockchip_ebc *ebc,
+					 struct rockchip_ebc_ctx *ctx)
+{
+	struct drm_device *drm = &ebc->drm;
+	struct device *dev = drm->dev;
+	u32 frame;
+	ktime_t time_start_advance;
+	ktime_t time_advance_sync;
+	ktime_t time_sync_wait;
+	ktime_t times_wait_end[2];
+	u32 min_frame_delay = 1000000;
+	u32 max_frame_delay = 0;
+	struct drm_rect clip_incoming = DRM_RECT_EMPTY_EXTANDABLE;
+	struct drm_rect clip_ongoing = DRM_RECT_EMPTY_EXTANDABLE;
+
+	struct drm_rect clip_ongoing_or_waiting = clip_ongoing;
+	u32 work_item = ebc->work_item;
+	ktime_t time_last_start = ktime_get();
+
+	// TODO: move into logic setting these values
+	for (int i = 0; i < 16; ++i) {
+		u8 _sum = (i >= (y2_th_thresholds & 0xff)) + (i >= ((y2_th_thresholds >> 8) & 0xff)) + (i >= ((y2_th_thresholds >> 16) & 0xff));
+		((u8 *) ebc->lut_y2_y4)[i] = (_sum << 2) | _sum;
+	}
+	for (int i = 0; i < 32; ++i) {
+		u8 _sum = (i >= (y2_dt_thresholds & 0xff)) + (i >= ((y2_dt_thresholds >> 8) & 0xff)) + (i >= ((y2_dt_thresholds >> 16) & 0xff));
+		((u8 *) ebc->lut_y2_y4_dithered)[i] = (_sum << 2) | _sum;
+	}
+
+	spin_lock(&ctx->buffer_switch_lock);
+	ctx->refresh_index = ctx->next_refresh_index;
+	for (int i = 0; i < 3; ++i) {
+		if (ctx->not_after_others[i] & (1 << ctx->refresh_index)) {
+			rockchip_ebc_drm_rect_extend_rect(&clip_incoming,
+						  ctx->dst_clip + i);
+			ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE;
+		}
+	}
+	spin_unlock(&ctx->buffer_switch_lock);
+	u8 *prelim_target = ctx->prelim_target_buffer[ctx->refresh_index];
+	u8 *hints = ctx->hints_buffer[ctx->refresh_index];
+
+	bool awaiting_completion = false;
+	bool awaiting_start = false;
+	bool no_schedule_until_clip_empty = false;
+	int enabling_mode = ebc->driver_mode;
+	int zero_waveform_mode_num_zero_phase_buffers = 0;
+	bool inhibit_suspend = false;
+	bool is_suspending = false;
+	ktime_t time_last_report = ktime_get();
+	int num_frames_since_last_report = 0;
+	s64 max_advance_time_since_last_report = 0;
+
+	for (frame = 0;; frame++) {
+		u8 *phase_buffer = ebc->phase[frame % 2];
+		dma_addr_t phase_handle = ebc->phase_handles[frame % 2];
+		// Used to reset the second phase buffer in direct mode after last_phase
+		work_item = ebc->work_item;
+		bool skip_advance = false;
+
+		time_start_advance = ktime_get();
+		// All currently scheduled pixels are IDLE or WAITING have finished and we have a work item
+		if (drm_rect_width(&clip_ongoing) <= 0 && work_item) {
+			// Refresh work item
+			spin_lock(&ebc->work_item_lock);
+			work_item |= ebc->work_item;
+			ebc->work_item = 0;
+			spin_unlock(&ebc->work_item_lock);
+			if (ebc->driver_mode ==
+				    ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM &&
+			    !(work_item &
+			      ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE)) {
+				pr_err("Ignoring work items until zero waveform mode is left explicitly");
+				work_item = 0;
+			}
+			if ((work_item &
+				    ROCKCHIP_EBC_WORK_ITEM_DISABLE_ZERO_WAVEFORM_MODE) &&
+				   (ebc->driver_mode ==
+				    ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) {
+				pr_warn("Disabling zero waveform mode");
+				inhibit_suspend = false;
+				// Make sure to set a mode
+				if (!(work_item &
+				      (ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE |
+				       ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE |
+				       ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE))) {
+					work_item |=
+						ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE;
+				}
+			}
+			if ((work_item &
+			     ROCKCHIP_EBC_WORK_ITEM_ENABLE_ZERO_WAVEFORM_MODE) &&
+			    (ebc->driver_mode !=
+			     ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM)) {
+				pr_info("Enabling zero waveform mode, finishing ongoing updates");
+				// Finish ongoing updates to resume with a zero-only inner/outer buffer
+				no_schedule_until_clip_empty = true;
+				zero_waveform_mode_num_zero_phase_buffers = 0;
+				enabling_mode =
+					ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
+				inhibit_suspend = true;
+			} else if ((work_item &
+				    ROCKCHIP_EBC_WORK_ITEM_ENABLE_FAST_MODE) &&
+				   ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_FAST) {
+				no_schedule_until_clip_empty = true;
+				enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_FAST;
+				work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH;
+			} else if ((work_item &
+				    ROCKCHIP_EBC_WORK_ITEM_ENABLE_NORMAL_MODE) &&
+				   ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_NORMAL) {
+				no_schedule_until_clip_empty = true;
+				enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
+			} else if (work_item &
+				   ROCKCHIP_EBC_WORK_ITEM_ENABLE_PHASE_SEQUENCE_MODE) {
+				no_schedule_until_clip_empty = true;
+				enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE;
+			}
+			if (work_item & ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT) {
+				rockchip_ebc_change_lut(ebc);
+				print_lut(ebc);
+				// Reset inner and outer to make sure
+				// pixels that were WAITING stay
+				// valid. For simplicity, set to IDLE.
+				scoped_ksimd()
+					rockchip_ebc_reset_inner_outer_neon(ebc);
+			}
+			if (work_item & ROCKCHIP_EBC_WORK_ITEM_INIT) {
+				clip_ongoing_or_waiting = ebc->screen_rect;
+				memset(prelim_target, 0xff, ebc->num_pixels);
+				scoped_ksimd()
+					rockchip_ebc_schedule_advance_neon(ebc, prelim_target, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_INIT, 0, ROCKCHIP_EBC_HINT_REDRAW, true);
+				skip_advance = true;
+				no_schedule_until_clip_empty = true;
+			} else if (work_item & ROCKCHIP_EBC_WORK_ITEM_SUSPEND) {
+				clip_ongoing_or_waiting = ebc->screen_rect;
+				if (!no_off_screen) {
+					// Use the highest-quality waveform to minimize visible artifacts
+					scoped_ksimd()
+						rockchip_ebc_schedule_advance_neon(ebc, ebc->final_off_screen, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_GC16, 0, ROCKCHIP_EBC_HINT_REDRAW, true);
+				}
+				no_off_screen = false;
+				skip_advance = true;
+				no_schedule_until_clip_empty = true;
+				is_suspending = true;
+				ebc->suspend_was_requested = 1;
+			} else if (work_item &
+				   ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH) {
+				if (ebc->driver_mode ==
+					    ROCKCHIP_EBC_DRIVER_MODE_FAST ||
+				    enabling_mode ==
+				    ROCKCHIP_EBC_DRIVER_MODE_FAST) {
+					ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
+					enabling_mode =
+						ROCKCHIP_EBC_DRIVER_MODE_FAST;
+					// TODO: use NEON implementation
+					for (int i = 0; i < ebc->num_pixels; ++i) {
+						u8 prelim = prelim_target[i] & 0xf0;
+						prelim_target[i] = prelim | prelim >> 4;
+					}
+				}
+				clip_ongoing_or_waiting = ebc->screen_rect;
+				scoped_ksimd()
+					rockchip_ebc_schedule_advance_neon(ebc, prelim_target, hints, phase_buffer, &clip_ongoing, &clip_ongoing_or_waiting, 0, ROCKCHIP_EBC_CUSTOM_WF_GC16, 0, ROCKCHIP_EBC_HINT_REDRAW, true);
+				// Skip a single advance as we have performed one just now
+				skip_advance = true;
+				no_schedule_until_clip_empty = true;
+				ebc->suspend_was_requested = 0;
+			}
+			work_item = 0;
+		} else if (drm_rect_width(&clip_ongoing_or_waiting) <= 0 &&
+			   !inhibit_suspend &&
+			   (is_suspending ||
+			    ((drm_rect_width(&clip_incoming) <= 0) &&
+			     (ktime_ms_delta(ktime_get(), time_last_start) >
+			      refresh_thread_wait_idle)))) {
+			// Wait before yielding the refresh thread
+			is_suspending = false;
+			break;
+		} else if (!no_schedule_until_clip_empty && !work_item) {
+			rockchip_ebc_drm_rect_extend_rect(
+							  &clip_ongoing_or_waiting, &clip_incoming);
+			clip_incoming = DRM_RECT_EMPTY_EXTANDABLE;
+		}
+		pr_debug(
+			"%s frame=%d clip_ongoing=" DRM_RECT_FMT
+			" clip_ongoing_or_waiting=" DRM_RECT_FMT
+			" work_item=%d no_schedule_until_clip_empty=%d time_elapsed_since_last_start=%llu",
+			__func__, frame, DRM_RECT_ARG(&clip_ongoing),
+			DRM_RECT_ARG(&clip_ongoing_or_waiting), work_item,
+			no_schedule_until_clip_empty,
+			ktime_ms_delta(ktime_get(), time_last_start));
+		pr_debug("%s ebc->driver_mode=%d enabling_mode=%d", __func__, ebc->driver_mode, enabling_mode);
+		if (drm_rect_width(&clip_ongoing_or_waiting) > 0 &&
+		    !skip_advance) {
+			u8 force_hint = 0;
+			// Disable redraws of redraw_delay <= 0
+			u8 force_hint_mask = ebc->redraw_delay > 0 ? 0 : ROCKCHIP_EBC_HINT_REDRAW;
+			if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_FAST) {
+				scoped_ksimd()
+					rockchip_ebc_schedule_advance_fast_neon(
+						ebc, prelim_target, hints, phase_buffer,
+						&clip_ongoing, &clip_ongoing_or_waiting,
+						early_cancellation_addition, 0,
+						force_hint, force_hint_mask,
+						!no_schedule_until_clip_empty &&
+							!work_item);
+			} else if (ebc->driver_mode == ROCKCHIP_EBC_DRIVER_MODE_NORMAL) {
+				scoped_ksimd()
+					rockchip_ebc_schedule_advance_neon(
+						ebc, prelim_target, hints, phase_buffer,
+						&clip_ongoing, &clip_ongoing_or_waiting,
+						early_cancellation_addition, 0,
+						force_hint, force_hint_mask,
+						!no_schedule_until_clip_empty &&
+							!work_item);
+			}
+		}
+		if (drm_rect_width(&clip_ongoing) <= 0 &&
+		    no_schedule_until_clip_empty) {
+			if (enabling_mode ==
+				    ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM &&
+			    zero_waveform_mode_num_zero_phase_buffers < 2) {
+				memset(phase_buffer, 0, ebc->phase_size);
+				if (++zero_waveform_mode_num_zero_phase_buffers >=
+				    2) {
+					ebc->driver_mode =
+						ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
+					pr_info("Zero waveform mode enabled");
+				}
+			} else {
+				switch (enabling_mode) {
+				case ROCKCHIP_EBC_DRIVER_MODE_NORMAL:
+				case ROCKCHIP_EBC_DRIVER_MODE_FAST:
+					ebc->driver_mode = enabling_mode;
+					no_schedule_until_clip_empty = false;
+					break;
+				case ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE:
+					rockchip_ebc_phase_sequence(ebc);
+					enabling_mode = ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
+					break;
+				}
+			}
+		}
+		pr_debug("%s schedul2 frame=%d clip_ongoing=" DRM_RECT_FMT " clip_ongoing_or_waiting=" DRM_RECT_FMT,
+			 __func__, frame, DRM_RECT_ARG(&clip_ongoing), DRM_RECT_ARG(&clip_ongoing_or_waiting));
+
+		time_advance_sync = ktime_get();
+
+		u64 time_since_last_report =
+			ktime_ms_delta(time_advance_sync, time_last_report);
+		num_frames_since_last_report += 1;
+		max_advance_time_since_last_report =
+			max(max_advance_time_since_last_report,
+			    ktime_us_delta(time_advance_sync,
+					   time_start_advance));
+		if (time_since_last_report >= 1000) {
+			pr_debug("%s rate num_frames=%d max_advance=%llu us",
+				 __func__, num_frames_since_last_report,
+				 max_advance_time_since_last_report);
+			time_last_report = time_advance_sync;
+			num_frames_since_last_report = 0;
+			max_advance_time_since_last_report = 0;
+		}
+		awaiting_start = drm_rect_width(&clip_ongoing) > 0 ||
+				 ebc->driver_mode ==
+					 ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM;
+		if (awaiting_start) {
+			// TODO: make sure we've synced all zeros as well
+			int win_start = clip_ongoing.y1 * ebc->phase_pitch + (direct_mode ? clip_ongoing.x1 / 4 : clip_ongoing.x1);
+			int win_end = clip_ongoing.y2 * ebc->phase_pitch + (direct_mode ? (clip_ongoing.x2 + 3) / 4 : clip_ongoing.x2);
+			/*win_start = 0;*/
+			/*win_end = ebc->phase_size;*/
+			dma_sync_single_for_device(dev, phase_handle + win_start,
+						   win_end - win_start, DMA_TO_DEVICE);
+		}
+		time_sync_wait = ktime_get();
+
+		if (awaiting_completion && !wait_for_completion_timeout(
+									&ebc->display_end, EBC_FRAME_TIMEOUT))
+			drm_err(drm, "Frame %d timed out!\n", frame);
+		pr_debug("%s:%d frame completion event received", __func__, __LINE__);
+		times_wait_end[0] = ktime_get();
+		awaiting_completion = false;
+
+		if (awaiting_start) {
+			if (shrink_virtual_window) {
+				u16 adj_win_width = ((clip_ongoing.x2 + 7) & ~7) - (clip_ongoing.x1 & ~7);
+				unsigned int win_start_offset = ebc->act_width * clip_ongoing.y1 + (clip_ongoing.x1 & ~7);
+				pr_debug("%s clip_ongoing=" DRM_RECT_FMT " adj_win_width=%ud win_start_offset=%ud", __func__, DRM_RECT_ARG(&clip_ongoing), adj_win_width, win_start_offset);
+				regmap_write(ebc->regmap, EBC_WIN_VIR, EBC_WIN_VIR_WIN_VIR_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_VIR_WIN_VIR_WIDTH(ebc->pixel_pitch));
+				regmap_write(ebc->regmap, EBC_WIN_ACT, EBC_WIN_ACT_WIN_ACT_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_ACT_WIN_ACT_WIDTH(adj_win_width));
+				regmap_write(ebc->regmap, EBC_WIN_DSP, EBC_WIN_DSP_WIN_DSP_HEIGHT(drm_rect_height(&clip_ongoing)) | EBC_WIN_DSP_WIN_DSP_WIDTH(adj_win_width));
+				regmap_write(ebc->regmap, EBC_WIN_DSP_ST, EBC_WIN_DSP_ST_WIN_DSP_YST(ebc->vact_start + clip_ongoing.y1) | EBC_WIN_DSP_ST_WIN_DSP_XST(ebc->hact_start + clip_ongoing.x1 / 8));
+				regmap_write(ebc->regmap, direct_mode ? EBC_WIN_MST0 : EBC_WIN_MST2, phase_handle + (direct_mode ? win_start_offset / 4 : win_start_offset));
+			} else {
+				// TODO: restore other registers in case they were changed
+				regmap_write(ebc->regmap, direct_mode ? EBC_WIN_MST0 : EBC_WIN_MST2, phase_handle);
+			}
+			regmap_write(ebc->regmap, EBC_CONFIG_DONE,
+				     EBC_CONFIG_DONE_REG_CONFIG_DONE);
+			awaiting_completion = true;
+			awaiting_start = false;
+			if (testing < 2) {
+				regmap_write(
+					ebc->regmap, EBC_DSP_START,
+					ebc->dsp_start |
+						EBC_DSP_START_DSP_FRM_START);
+				pr_debug("%s:%d frame started", __func__, __LINE__);
+			}
+			time_last_start = ktime_get();
+		}
+
+		// at this point the ebc is working. It does not access the final
+		// buffer directly, and therefore we can use the time to switch
+		// buffers or wait for a new update.
+
+		u64 delta_advance = ktime_us_delta(time_advance_sync, time_start_advance);
+		u64 delta_sync = ktime_us_delta(time_sync_wait, time_advance_sync);
+		u64 delta_wait = ktime_us_delta(times_wait_end[0], time_sync_wait);
+		u64 delta_frame = frame > 0 ? ktime_us_delta(times_wait_end[0], times_wait_end[1]) : 0;
+		times_wait_end[1] = times_wait_end[0];
+		u64 work_total = delta_advance + delta_sync + delta_wait;
+		if (delta_frame > max_frame_delay && delta_frame <= 100000)
+			max_frame_delay = delta_frame;
+		if (delta_frame < min_frame_delay && delta_frame > 0 && delta_frame <= 100000)
+			min_frame_delay = delta_frame;
+		pr_debug(
+			 "%s: frame %i [us]: advance=%llu sync=%llu wait=%llu frame=%llu work_total=%llu",
+			 __func__, frame, delta_advance, delta_sync,
+			 delta_wait, delta_frame, work_total);
+
+		// TODO: don't assume 85 Hz, as non-direct mode uses 80 Hz
+		// We have about max(0, 11700 - delta_advance - delta_wait) [us] that we can wait until we start delaying things
+		s64 switch_buffer = 11700 - delta_advance - delta_wait - 1000;
+
+		// TODO: consider adding || !work_item
+		while(!is_suspending && ebc->driver_mode != ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM) {
+			spin_lock(&ctx->buffer_switch_lock);
+			ctx->refresh_index = ctx->next_refresh_index;
+			for (int i = 0; i < 3; ++i) {
+				// TODO: consider rejecting an update if it would incur a delay
+				if (ctx->not_after_others[i] & (1 << ctx->refresh_index)) {
+					rockchip_ebc_drm_rect_extend_rect(&clip_incoming,
+									  ctx->dst_clip + i);
+					ctx->dst_clip[i] = DRM_RECT_EMPTY_EXTANDABLE;
+				}
+			}
+			spin_unlock(&ctx->buffer_switch_lock);
+			// TODO: use predicted processing time based on clip_incoming, as clip_incoming can be significantly larger than the previous one
+			s64 time_us_buffer =
+				switch_buffer -
+					ktime_us_delta(ktime_get(), times_wait_end[0]);
+
+			if (time_us_buffer <= 0)
+				break;
+
+			fsleep(time_us_buffer);
+		}
+
+		prelim_target = ctx->prelim_target_buffer[ctx->refresh_index];
+		hints = ctx->hints_buffer[ctx->refresh_index];
+
+		if (kthread_should_stop()) {
+			break;
+		};
+	}
+}
+
+static void rockchip_ebc_upd_temp(struct rockchip_ebc *ebc)
+{
+	struct drm_device *drm = &ebc->drm;
+	int ret, temperature;
+	struct drm_epd_lut_temp_v2 *lut_active = ebc->lut_custom_active;
+	struct drm_epd_lut_v2 *luts = &ebc->lut_custom;
+
+	ret = iio_read_channel_processed(ebc->temperature_channel,
+					 &temperature);
+	pr_debug("%s ret=%d temperature=%d", __func__, ret, temperature);
+
+	if (ret < 0) {
+		drm_err(drm, "Failed to get temperature: %d\n", ret);
+	} else {
+		// Convert from millicelsius to celsius.
+		temperature /= 1000;
+		if (temp_override > 0){
+			printk(KERN_INFO "rockchip-ebc: override temperature from %i to %i\n", temperature, temp_override);
+			temperature = temp_override;
+		}
+		// Early cancellation is broken right now for lower temperatures
+		temperature = max(temperature, 19);
+		ebc->temperature = temperature;
+
+		// TODO: figure out exclusivity/inclusivity and lowest and highest temperature range
+		if ((temperature < lut_active->temp_lower &&
+		     lut_active->temp_lower != luts->luts[0].temp_lower) ||
+		    (temperature > lut_active->temp_upper &&
+		     lut_active->temp_upper !=
+		     luts->luts[luts->num_temp_ranges - 1].temp_upper)) {
+			spin_lock(&ebc->work_item_lock);
+			ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT;
+			spin_unlock(&ebc->work_item_lock);
+		}
+	}
+}
+
+static void rockchip_ebc_refresh(struct rockchip_ebc *ebc,
+				 struct rockchip_ebc_ctx *ctx)
+{
+	struct drm_device *drm = &ebc->drm;
+	struct device *dev = drm->dev;
+	int ret;
+	ktime_t time_start_resume = ktime_get();
+
+	// Resume synchronously before writing to any registers
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0) {
+		drm_err(drm, "Failed to resume: %d\n", ret);
+		return;
+	}
+	pr_debug("%s pm_runtime_resume_and_get took %lld ms", __func__, ktime_ms_delta(ktime_get(), time_start_resume));
+
+	// TODO: do only once and restore at resume
+	if (!direct_mode) {
+		// Another 8-9 ms. Is it okay to do this only once?
+		regmap_bulk_write(ebc->regmap, EBC_LUT_DATA, ebc->hardware_wf,
+				  EBC_NUM_LUT_REGS);
+		// TODO: do this only once
+		pr_debug("%s:%d hardware_wf written\n", __func__, __LINE__);
+		regmap_write(ebc->regmap, EBC_WIN_MST0, ebc->zero_handle);
+		regmap_write(ebc->regmap, EBC_WIN_MST1, ebc->zero_handle);
+		pr_debug("%s:%d EBC_WIN_MST? written\n", __func__, __LINE__);
+	}
+
+	regmap_write(ebc->regmap, EBC_DSP_START, ebc->dsp_start);
+
+	rockchip_ebc_partial_refresh(ebc, ctx);
+
+	/* Drive the output pins low once the refresh is complete. */
+	regmap_write(ebc->regmap, EBC_DSP_START,
+		     ebc->dsp_start |
+		     EBC_DSP_START_DSP_OUT_LOW);
+
+	pr_debug("%s:%d EBC_DSP_START to low\n", __func__, __LINE__);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+}
+
+static int rockchip_ebc_temp_upd_thread(void *data)
+{
+	struct rockchip_ebc *ebc = data;
+	while (!kthread_should_stop()) {
+		while (!kthread_should_park() && !kthread_should_stop()) {
+			set_current_state(TASK_RUNNING);
+			rockchip_ebc_upd_temp(ebc);
+			msleep_interruptible(10000);
+		}
+		if (!kthread_should_stop())
+			kthread_parkme();
+	}
+	return 0;
+}
+
+static int rockchip_ebc_refresh_thread(void *data)
+{
+	struct rockchip_ebc *ebc = data;
+	struct rockchip_ebc_ctx *ctx;
+	rockchip_ebc_change_lut(ebc);
+
+	while (!kthread_should_stop()) {
+		pr_debug("%s:%d\n", __func__, __LINE__);
+		/* The context will change each time the thread is unparked. */
+		ctx = to_ebc_crtc_state(READ_ONCE(ebc->crtc.state))->ctx;
+
+		// this means rockchip_ebc_crtc_atomic_disable does not trigger a global refresh
+		// TODO: consider dropping this condition
+		if(ebc->suspend_was_requested == 1) {
+			// this means we are coming out from suspend. Reset the buffers to
+			// the before-suspend state
+			spin_lock(&ebc->work_item_lock);
+			ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_GLOBAL_REFRESH;
+			spin_unlock(&ebc->work_item_lock);
+		}
+
+		// This shouldn't be necessary, but might be safer
+		memset(ebc->phase[0], 0, ebc->phase_size);
+		memset(ebc->phase[1], 0, ebc->phase_size);
+
+		while ((!kthread_should_park()) && (!kthread_should_stop())) {
+			rockchip_ebc_refresh(ebc, ctx);
+
+			set_current_state(TASK_IDLE);
+			if (!kthread_should_stop() && !kthread_should_park()) {
+				schedule();
+			}
+			__set_current_state(TASK_RUNNING);
+		}
+
+		if (!kthread_should_stop()){
+			kthread_parkme();
+		}
+	}
+
+	return 0;
+}
+
+static inline struct rockchip_ebc *crtc_to_ebc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct rockchip_ebc, crtc);
+}
+
+static int rockchip_ebc_set_dclk(struct rockchip_ebc *ebc,
+					  const struct drm_display_mode *mode)
+{
+	int rate;
+
+	if (direct_mode) {
+		rate = clk_set_rate(ebc->cpll_333m, 33333334);
+		if (rate < 0)
+			return rate;
+		rate = clk_set_rate(ebc->dclk, 34000000);
+		return rate;
+	}
+
+	switch (dclk_select) {
+		case -1:
+			// TODO: consider adjusting cpll_333m
+			rate = clk_set_rate(ebc->dclk, mode->clock * 1000);
+			break;
+		case 0:
+			rate = clk_set_rate(ebc->dclk, 200000000);
+			break;
+		case 1:
+			rate = clk_set_rate(ebc->cpll_333m, 250000000);
+			if (rate < 0)
+				return rate;
+			rate = clk_set_rate(ebc->dclk, 250000000);
+			break;
+		default:
+			rate = -EINVAL;
+	}
+	return rate;
+}
+
+static void rockchip_ebc_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+	struct drm_display_mode mode = crtc->state->adjusted_mode;
+	struct drm_display_mode sdck;
+	u16 hsync_width, vsync_width;
+	u16 hact_start, vact_start;
+	u16 pixels_per_sdck;
+	bool bus_16bit;
+	/* pr_info("ebc: %s", __func__); */
+	/* from drm_modes.h:
+	 * * The horizontal and vertical timings are defined per the following diagram.
+	 *
+	 * ::
+	 *
+	 *
+	 *               Active                 Front           Sync           Back
+	 *              Region                 Porch                          Porch
+	 *     <-----------------------><----------------><-------------><-------------->
+	 *       //////////////////////|
+	 *      ////////////////////// |
+	 *     //////////////////////  |..................               ................
+	 *                                                _______________
+	 *     <----- [hv]display ----->
+	 *     <------------- [hv]sync_start ------------>
+	 *     <--------------------- [hv]sync_end --------------------->
+	 *     <-------------------------------- [hv]total ----------------------------->*
+	 *
+	 * */
+
+	/*
+	 * Hardware needs horizontal timings in SDCK (source driver clock)
+	 * cycles, not pixels. Bus width is either 8 bits (normal) or 16 bits
+	 * (DRM_MODE_FLAG_CLKDIV2), and each pixel uses two data bits.
+	 */
+	bus_16bit = !!(mode.flags & DRM_MODE_FLAG_CLKDIV2);
+	pixels_per_sdck = bus_16bit ? 8 : 4;
+	sdck.hdisplay = mode.hdisplay / pixels_per_sdck;
+	sdck.hsync_start = mode.hsync_start / pixels_per_sdck;
+	sdck.hsync_end = mode.hsync_end / pixels_per_sdck;
+	sdck.htotal = mode.htotal / pixels_per_sdck;
+
+	if (hskew_override > 0){
+		pr_info(
+			"rockchip-ebc: overriding hskew value %i with new value: %i",
+				mode.hskew, hskew_override
+		);
+		sdck.hskew = hskew_override / pixels_per_sdck;
+	} else {
+		// use the value supplied via the panel mode
+		sdck.hskew = mode.hskew / pixels_per_sdck;
+	}
+
+	/*
+	 * Linux timing order is display/fp/sync/bp. Hardware timing order is
+	 * sync/bp/display/fp, aka sync/start/display/end.
+	 */
+	hact_start = sdck.htotal - sdck.hsync_start;
+	// mode.vtotal = 1404 + 12 + 1 + 4
+	// mode.vsync_start = 1404 + 12
+	// vact_start = 5
+	vact_start = mode.vtotal - mode.vsync_start;
+
+	hsync_width = sdck.hsync_end - sdck.hsync_start;
+	vsync_width = mode.vsync_end - mode.vsync_start;
+
+	// TODO: error checking
+	rockchip_ebc_set_dclk(ebc, &mode);
+
+	/* Display timings in ebc hardware:
+	+        * GD_ST
+	+        * GD_END
+	+        * HTOTAL
+	+        * HS_END
+	+        * HACT_END
+	+        * HACT_ST
+	+        * VTOTAL
+	+        * VS_END
+	+        * VACT_END
+	+        * VACT_ST
+	+        * DSP_ACT_HEIGHT
+	+        * DSP_ACT_WIDTH
+	+        *
+	+        * */
+
+	ebc->dsp_start = EBC_DSP_START_DSP_SDCE_WIDTH(sdck.hdisplay) |
+			 EBC_DSP_START_SW_BURST_CTRL;
+
+	ebc->act_width = mode.hdisplay;
+	ebc->act_height = mode.vdisplay;
+	ebc->vact_start = vact_start;
+	ebc->hact_start = hact_start;
+
+	regmap_write(ebc->regmap, EBC_EPD_CTRL,
+		     EBC_EPD_CTRL_DSP_GD_END(sdck.htotal - sdck.hskew) |
+			     EBC_EPD_CTRL_DSP_GD_ST(hsync_width + sdck.hskew) |
+			     EBC_EPD_CTRL_DSP_SDDW_MODE * bus_16bit |
+			     (direct_mode ? 0 :
+			      EBC_EPD_CTRL_DSP_THREE_WIN_MODE));
+
+	regmap_write(ebc->regmap, EBC_DSP_CTRL,
+		     /* no swap */
+		     EBC_DSP_CTRL_DSP_SWAP_MODE(bus_16bit ? 2 : 3) |
+		     EBC_DSP_CTRL_DSP_SDCLK_DIV(direct_mode ? 0 : pixels_per_sdck - 1));
+	regmap_write(ebc->regmap, EBC_DSP_HTIMING0,
+		     EBC_DSP_HTIMING0_DSP_HTOTAL(sdck.htotal) |
+		     /* sync end == sync width */
+		     EBC_DSP_HTIMING0_DSP_HS_END(hsync_width));
+	regmap_write(ebc->regmap, EBC_DSP_HTIMING1,
+		     EBC_DSP_HTIMING1_DSP_HACT_END(hact_start + sdck.hdisplay) |
+		     /* minus 1 for fixed delay in timing sequence */
+		     EBC_DSP_HTIMING1_DSP_HACT_ST(hact_start - 1));
+
+	/* vertical timings */
+	regmap_write(ebc->regmap, EBC_DSP_VTIMING0,
+		     EBC_DSP_VTIMING0_DSP_VTOTAL(mode.vtotal) |
+		     /* sync end == sync width */
+			 /* mw: when comparing the data sheets, this is equal to FEL of the display, translating to 12 frames
+			  * yet, here vsync_width computes to 1*/
+			 // = 1
+		     EBC_DSP_VTIMING0_DSP_VS_END(vsync_width));
+	regmap_write(ebc->regmap, EBC_DSP_VTIMING1,
+		     EBC_DSP_VTIMING1_DSP_VACT_END(vact_start + mode.vdisplay) |
+		     EBC_DSP_VTIMING1_DSP_VACT_ST(vact_start));
+
+	/* active region */
+	regmap_write(ebc->regmap, EBC_DSP_ACT_INFO,
+		     EBC_DSP_ACT_INFO_DSP_HEIGHT(mode.vdisplay) |
+		     EBC_DSP_ACT_INFO_DSP_WIDTH(mode.hdisplay));
+
+	/* misc */
+	regmap_write(ebc->regmap, EBC_WIN_CTRL,
+		     /* FIFO depth - 16 */
+		     EBC_WIN_CTRL_WIN2_FIFO_THRESHOLD(496) |
+		     EBC_WIN_CTRL_WIN_EN |
+		     /* INCR16 */
+		     EBC_WIN_CTRL_AHB_BURST_REG(7) |
+		     /* FIFO depth - 16 */
+		     EBC_WIN_CTRL_WIN_FIFO_THRESHOLD(240) |
+		     EBC_WIN_CTRL_WIN_FMT_Y4);
+
+	/* To keep things simple, always use a window size matching the CRTC. */
+	regmap_write(ebc->regmap, EBC_WIN_VIR,
+		     EBC_WIN_VIR_WIN_VIR_HEIGHT(mode.vdisplay) |
+		     EBC_WIN_VIR_WIN_VIR_WIDTH(mode.hdisplay));
+	regmap_write(ebc->regmap, EBC_WIN_ACT,
+		     EBC_WIN_ACT_WIN_ACT_HEIGHT(mode.vdisplay) |
+		     EBC_WIN_ACT_WIN_ACT_WIDTH(mode.hdisplay));
+	regmap_write(ebc->regmap, EBC_WIN_DSP,
+		     EBC_WIN_DSP_WIN_DSP_HEIGHT(mode.vdisplay) |
+		     EBC_WIN_DSP_WIN_DSP_WIDTH(mode.hdisplay));
+	regmap_write(ebc->regmap, EBC_WIN_DSP_ST,
+		     EBC_WIN_DSP_ST_WIN_DSP_YST(vact_start) |
+		     EBC_WIN_DSP_ST_WIN_DSP_XST(hact_start));
+}
+
+static int rockchip_ebc_crtc_atomic_check(struct drm_crtc *crtc,
+					  struct drm_atomic_state *state)
+{
+	struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+	struct ebc_crtc_state *ebc_crtc_state;
+	struct drm_crtc_state *crtc_state;
+	struct rockchip_ebc_ctx *ctx;
+	pr_debug("ebc: %s", __func__);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (!crtc_state->mode_changed)
+		return 0;
+
+	if (crtc_state->enable) {
+		struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+
+		int rate = rockchip_ebc_set_dclk(ebc, mode);
+		if (rate < 0)
+			return rate;
+		mode->clock = rate / 1000;
+
+		ctx = rockchip_ebc_ctx_alloc(ebc);
+		if (!ctx)
+			return -ENOMEM;
+	} else {
+		ctx = NULL;
+	}
+
+	ebc_crtc_state = to_ebc_crtc_state(crtc_state);
+	if (ebc_crtc_state->ctx)
+		kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+	ebc_crtc_state->ctx = ctx;
+
+	return 0;
+}
+
+static void rockchip_ebc_crtc_atomic_flush(struct drm_crtc *crtc,
+					   struct drm_atomic_state *state)
+{
+	// TODO: consider moving copying to final_buffer, moving of areas, setting switch_required, waking up process here
+	// TODO: also look at atomic_commit
+	pr_debug("ebc: %s", __func__);
+}
+
+static void rockchip_ebc_crtc_atomic_enable(struct drm_crtc *crtc,
+					    struct drm_atomic_state *state)
+{
+	struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+	struct drm_crtc_state *crtc_state;
+	pr_debug("ebc: %s", __func__);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (crtc_state->mode_changed) {
+		kthread_unpark(ebc->temp_upd_thread);
+		kthread_unpark(ebc->refresh_thread);
+	}
+}
+
+static void rockchip_ebc_crtc_atomic_disable(struct drm_crtc *crtc,
+					     struct drm_atomic_state *state)
+{
+	struct rockchip_ebc *ebc = crtc_to_ebc(crtc);
+	struct drm_crtc_state *crtc_state;
+	pr_debug("ebc: %s", __func__);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (crtc_state->mode_changed){
+		if (! ((ebc->refresh_thread->__state) & (TASK_DEAD))){
+			kthread_park(ebc->refresh_thread);
+		}
+		if (!(ebc->temp_upd_thread->__state & TASK_DEAD)) {
+			kthread_park(ebc->temp_upd_thread);
+		}
+	}
+}
+
+static const struct drm_crtc_helper_funcs rockchip_ebc_crtc_helper_funcs = {
+	.mode_set_nofb		= rockchip_ebc_crtc_mode_set_nofb,
+	.atomic_check		= rockchip_ebc_crtc_atomic_check,
+	.atomic_flush		= rockchip_ebc_crtc_atomic_flush,
+	.atomic_enable		= rockchip_ebc_crtc_atomic_enable,
+	.atomic_disable		= rockchip_ebc_crtc_atomic_disable,
+};
+
+static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc,
+					    struct drm_crtc_state *crtc_state);
+
+static void rockchip_ebc_crtc_reset(struct drm_crtc *crtc)
+{
+	struct ebc_crtc_state *ebc_crtc_state;
+	/* pr_info("ebc: %s", __func__); */
+
+	if (crtc->state)
+		rockchip_ebc_crtc_destroy_state(crtc, crtc->state);
+
+	ebc_crtc_state = kzalloc(sizeof(*ebc_crtc_state), GFP_KERNEL);
+	if (!ebc_crtc_state)
+		return;
+
+	__drm_atomic_helper_crtc_reset(crtc, &ebc_crtc_state->base);
+}
+
+static struct drm_crtc_state *
+rockchip_ebc_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct ebc_crtc_state *ebc_crtc_state;
+	/* pr_info("ebc: %s", __func__); */
+
+	if (!crtc->state)
+		return NULL;
+
+	ebc_crtc_state = kzalloc(sizeof(*ebc_crtc_state), GFP_KERNEL);
+	if (!ebc_crtc_state)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &ebc_crtc_state->base);
+
+	ebc_crtc_state->ctx = to_ebc_crtc_state(crtc->state)->ctx;
+	if (ebc_crtc_state->ctx)
+		kref_get(&ebc_crtc_state->ctx->kref);
+
+	return &ebc_crtc_state->base;
+}
+
+static void rockchip_ebc_crtc_destroy_state(struct drm_crtc *crtc,
+					    struct drm_crtc_state *crtc_state)
+{
+	struct ebc_crtc_state *ebc_crtc_state = to_ebc_crtc_state(crtc_state);
+
+	if (ebc_crtc_state->ctx)
+		kref_put(&ebc_crtc_state->ctx->kref, rockchip_ebc_ctx_release);
+
+	__drm_atomic_helper_crtc_destroy_state(&ebc_crtc_state->base);
+
+	kfree(ebc_crtc_state);
+}
+
+static const struct drm_crtc_funcs rockchip_ebc_crtc_funcs = {
+	.reset			= rockchip_ebc_crtc_reset,
+	.destroy		= drm_crtc_cleanup,
+	.set_config		= drm_atomic_helper_set_config,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.atomic_duplicate_state	= rockchip_ebc_crtc_duplicate_state,
+	.atomic_destroy_state	= rockchip_ebc_crtc_destroy_state,
+};
+
+/*
+ * Plane
+ */
+
+struct ebc_plane_state {
+	struct drm_shadow_plane_state	base;
+	struct drm_rect			clip;
+};
+
+static inline struct ebc_plane_state *
+to_ebc_plane_state(struct drm_plane_state *plane_state)
+{
+	return container_of(plane_state, struct ebc_plane_state, base.base);
+}
+
+static inline struct rockchip_ebc *plane_to_ebc(struct drm_plane *plane)
+{
+	return container_of(plane, struct rockchip_ebc, plane);
+}
+
+static int rockchip_ebc_plane_atomic_check(struct drm_plane *plane,
+					   struct drm_atomic_state *state)
+{
+	struct drm_atomic_helper_damage_iter iter;
+	struct ebc_plane_state *ebc_plane_state;
+	struct drm_plane_state *old_plane_state;
+	struct drm_plane_state *plane_state;
+	struct drm_crtc_state *crtc_state;
+	struct drm_rect clip;
+	int ret;
+
+	plane_state = drm_atomic_get_new_plane_state(state, plane);
+	if (!plane_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc);
+	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+						  DRM_PLANE_NO_SCALING,
+						  DRM_PLANE_NO_SCALING,
+						  false, true);
+	if (ret)
+		return ret;
+
+	ebc_plane_state = to_ebc_plane_state(plane_state);
+	old_plane_state = drm_atomic_get_old_plane_state(state, plane);
+	drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
+	drm_atomic_for_each_plane_damage(&iter, &clip) {
+		rockchip_ebc_drm_rect_extend_rect(&ebc_plane_state->clip, &clip);
+	}
+
+	return 0;
+}
+
+static void rockchip_ebc_plane_atomic_update(struct drm_plane *plane,
+					     struct drm_atomic_state *state)
+{
+	struct rockchip_ebc *ebc = plane_to_ebc(plane);
+	struct ebc_plane_state *ebc_plane_state;
+	struct drm_plane_state *plane_state;
+	struct drm_crtc_state *crtc_state;
+	struct rockchip_ebc_ctx *ctx;
+	struct drm_rect src;
+	const void *vaddr;
+	pr_debug("ebc %s", __func__);
+	plane_state = drm_atomic_get_new_plane_state(state, plane);
+	if (!plane_state->crtc)
+		return;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, plane_state->crtc);
+	ctx = to_ebc_crtc_state(crtc_state)->ctx;
+
+	drm_rect_fp_to_int(&src, &plane_state->src);
+
+	ebc_plane_state = to_ebc_plane_state(plane_state);
+	vaddr = ebc_plane_state->base.data[0].vaddr;
+
+	struct drm_rect src_clip = ebc_plane_state->clip;
+	ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE;
+	if (drm_rect_width(&src_clip) <= 0)
+		return;
+	// NEON 16 byte alignment
+	src_clip.x1 =
+		max(0, min(src_clip.x1 & ~15, (int)ebc->pixel_pitch - 16));
+	src_clip.x2 = min((src_clip.x2 + 15) & ~15, (int)ebc->pixel_pitch);
+
+	src_clip.y1 = max(0, src_clip.y1);
+	src_clip.y2 = min((int) ebc->height, src_clip.y2);
+
+	// This is the buffer we are allowed to modify, as it's not being read by the refresh thread
+	int idx_update = ctx->update_index;
+
+	// Also blit areas blitted to the other two buffers since the last time this one was modified
+	struct drm_rect src_clip_extended = src_clip;
+	rockchip_ebc_drm_rect_extend_rect(&src_clip_extended, ctx->src_clip_extended + idx_update);
+
+	rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 0,
+					  &src_clip);
+	rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 1,
+					  &src_clip);
+	rockchip_ebc_drm_rect_extend_rect(ctx->src_clip_extended + 2,
+					  &src_clip);
+	ctx->src_clip_extended[idx_update] = DRM_RECT_EMPTY_EXTANDABLE;
+
+	struct drm_rect dst_clip = src_clip;
+	struct drm_rect dst_clip_extended = src_clip_extended;
+
+	// Horizontal flip
+	dst_clip.x1 = plane_state->dst.x2 - src_clip.x2;
+	dst_clip.x2 = plane_state->dst.x2 - src_clip.x1;
+	dst_clip_extended.x1 = plane_state->dst.x2 - src_clip_extended.x2;
+	dst_clip_extended.x2 = plane_state->dst.x2 - src_clip_extended.x1;
+
+	// TODO: consider measuring required time and indicate to refresh thread when the next update is going to be available
+
+	pr_debug("%s dst_clip=" DRM_RECT_FMT, __func__, DRM_RECT_ARG(&dst_clip));
+	if (limit_fb_blits != 0) {
+		// the counter should only reach 0 here, -1 can only be externally set
+		limit_fb_blits -= (limit_fb_blits > 0) ? 1 : 0;
+
+		switch (plane_state->fb->format->format) {
+		case DRM_FORMAT_RGB565:
+			scoped_ksimd()
+				rockchip_ebc_blit_fb_rgb565_y4_hints_neon(
+					ebc, &dst_clip_extended,
+					ctx->prelim_target_buffer[idx_update],
+					ctx->hints_buffer[idx_update], vaddr,
+					plane_state->fb, &src_clip_extended);
+			break;
+		case DRM_FORMAT_XRGB8888:
+			scoped_ksimd()
+				rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon(
+					ebc, &dst_clip_extended,
+					ctx->prelim_target_buffer[idx_update],
+					ctx->hints_buffer[idx_update], vaddr,
+					plane_state->fb, &src_clip_extended);
+			break;
+		case DRM_FORMAT_R8:
+			scoped_ksimd()
+				rockchip_ebc_blit_fb_r8_y4_hints_neon(
+					ebc, &dst_clip_extended,
+					ctx->prelim_target_buffer[idx_update],
+					ctx->hints_buffer[idx_update], vaddr,
+					plane_state->fb, &src_clip_extended);
+			break;
+		}
+	}
+
+	// Don't extend until now to avoid out-of-order updates and allowing the refresh thread to clear this area in the meantime
+	spin_lock(&ctx->buffer_switch_lock);
+	for (int i = 0; i < 3; ++i) {
+		ctx->not_after_others[i] |= (1 << idx_update);
+	}
+	ctx->not_after_others[idx_update] = (1 << idx_update);
+	rockchip_ebc_drm_rect_extend_rect(ctx->dst_clip + idx_update,
+					  &dst_clip);
+	ctx->next_refresh_index = idx_update;
+	if ((ctx->update_index = (ctx->update_index + 1) % 3) ==
+	    ctx->refresh_index) {
+		ctx->update_index = (ctx->update_index + 1) % 3;
+	}
+	spin_unlock(&ctx->buffer_switch_lock);
+
+	wake_up_process(ebc->refresh_thread);
+}
+
+static const struct drm_plane_helper_funcs rockchip_ebc_plane_helper_funcs = {
+	/* .prepare_fb		= drm_gem_prepare_shadow_fb, */
+	/* .cleanup_fb		= drm_gem_cleanup_shadow_fb, */
+	.begin_fb_access = drm_gem_begin_shadow_fb_access,
+	.end_fb_access = drm_gem_end_shadow_fb_access,
+	.atomic_check		= rockchip_ebc_plane_atomic_check,
+	.atomic_update		= rockchip_ebc_plane_atomic_update,
+};
+
+static void rockchip_ebc_plane_destroy_state(struct drm_plane *plane,
+					     struct drm_plane_state *plane_state);
+
+static void rockchip_ebc_plane_reset(struct drm_plane *plane)
+{
+	struct ebc_plane_state *ebc_plane_state;
+	pr_info("ebc: %s", __func__);
+
+	if (plane->state)
+		rockchip_ebc_plane_destroy_state(plane, plane->state);
+
+	ebc_plane_state = kzalloc(sizeof(*ebc_plane_state), GFP_KERNEL);
+	if (!ebc_plane_state)
+		return;
+
+	__drm_gem_reset_shadow_plane(plane, &ebc_plane_state->base);
+	ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE;
+}
+
+static struct drm_plane_state *
+rockchip_ebc_plane_duplicate_state(struct drm_plane *plane)
+{
+	struct ebc_plane_state *ebc_plane_state;
+	/* pr_info("ebc: %s", __func__); */
+
+	if (!plane->state)
+		return NULL;
+
+	ebc_plane_state = kzalloc(sizeof(*ebc_plane_state), GFP_KERNEL);
+	if (!ebc_plane_state)
+		return NULL;
+
+	__drm_gem_duplicate_shadow_plane_state(plane, &ebc_plane_state->base);
+
+	ebc_plane_state->clip = DRM_RECT_EMPTY_EXTANDABLE;
+
+	return &ebc_plane_state->base.base;
+}
+
+static void rockchip_ebc_plane_destroy_state(struct drm_plane *plane,
+					     struct drm_plane_state *plane_state)
+{
+	struct ebc_plane_state *ebc_plane_state = to_ebc_plane_state(plane_state);
+	/* pr_info("ebc: %s", __func__); */
+
+	__drm_gem_destroy_shadow_plane_state(&ebc_plane_state->base);
+
+	kfree(ebc_plane_state);
+}
+
+static const struct drm_plane_funcs rockchip_ebc_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= rockchip_ebc_plane_reset,
+	.atomic_duplicate_state	= rockchip_ebc_plane_duplicate_state,
+	.atomic_destroy_state	= rockchip_ebc_plane_destroy_state,
+};
+
+static const u32 rockchip_ebc_plane_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_R8,
+};
+
+static const u64 rockchip_ebc_plane_format_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+static int rockchip_ebc_waveform_init(struct rockchip_ebc *ebc)
+{
+	int ret;
+	struct drm_device *drm = &ebc->drm;
+
+	const struct firmware * default_off_screen;
+	const struct firmware *custom_wf;
+
+	ret = drmm_epd_lut_file_init(drm, &ebc->lut_file, EBC_FIRMWARE);
+	if (ret)
+		return ret;
+
+	ret = drmm_epd_lut_init(&ebc->lut_file, &ebc->lut,
+				DRM_EPD_LUT_4BIT_PACKED, EBC_MAX_PHASES);
+	if (ret)
+		return ret;
+
+	if (!request_firmware(&custom_wf, EBC_CUSTOM_WF, drm->dev)) {
+		size_t temp_range_size = 8 + ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS + ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE;
+		if ((custom_wf->size - 12) % temp_range_size) {
+			drm_err(drm, "Length error when loading custom_wf.bin\n");
+			ret = -EINVAL;
+		} else if (memcmp(custom_wf_magic_version, custom_wf->data, 8)) {
+			drm_err(drm, "Versioned magic comparison failed. Got %8ph, expected %8ph\n", custom_wf->data, custom_wf_magic_version);
+			ret = -EINVAL;
+		} else {
+			unsigned int num_temp_ranges = (custom_wf->size - 12) / temp_range_size;
+			ebc->lut_custom.num_temp_ranges = num_temp_ranges;
+			ebc->lut_custom.luts = vzalloc(num_temp_ranges * sizeof(struct drm_epd_lut_temp_v2));
+			if (!ebc->lut_custom.luts) {
+				drm_err(drm, "Failed to allocate lut_custom.luts\n");
+				ret = -ENOMEM;
+			} else {
+				const u8 *fw_temp = custom_wf->data + 12;
+				for (int i = 0; i < num_temp_ranges; ++i) {
+					struct drm_epd_lut_temp_v2 *lut_temp = ebc->lut_custom.luts + i;
+					lut_temp->temp_lower = *fw_temp;
+					lut_temp->temp_upper = *(fw_temp + 4);
+					for (int wf = 0; wf < ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS; ++wf)
+						lut_temp->offsets[wf] = fw_temp[8+wf];
+					memcpy(lut_temp->lut, fw_temp + 8 + ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS, ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE);
+					fw_temp += temp_range_size;
+				}
+			}
+		}
+	} else {
+		drm_err(drm, "Unable to load custom_wf.bin\n");
+		ret = -EINVAL;
+	}
+	release_firmware(custom_wf);
+	if (ret)
+		return ret;
+
+	// check if there is a default off-screen. Only the lowest four bits will be used per pixel
+	if (!request_firmware(&default_off_screen, EBC_OFFCONTENT, drm->dev))
+	{
+		if (default_off_screen->size != ebc->num_pixels)
+			drm_err(drm, "Size of default off_screen data file is not %d\n", ebc->num_pixels);
+		else {
+			memcpy(ebc->final_off_screen, default_off_screen->data, ebc->num_pixels);
+		}
+	} else {
+		// fill the off-screen with some values
+		memset(ebc->final_off_screen, 0xff, ebc->num_pixels);
+	}
+	release_firmware(default_off_screen);
+	pr_debug("%s:%d\n", __func__, __LINE__);
+
+	return 0;
+}
+
+static int rockchip_ebc_drm_init(struct rockchip_ebc *ebc)
+{
+	struct drm_device *drm = &ebc->drm;
+	struct drm_bridge *bridge;
+	int ret;
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	drm->mode_config.max_width = DRM_SHADOW_PLANE_MAX_WIDTH;
+	drm->mode_config.max_height = DRM_SHADOW_PLANE_MAX_HEIGHT;
+	drm->mode_config.funcs = &rockchip_ebc_mode_config_funcs;
+	drm->mode_config.quirk_addfb_prefer_host_byte_order = true;
+
+	drm_plane_helper_add(&ebc->plane, &rockchip_ebc_plane_helper_funcs);
+	ret = drm_universal_plane_init(drm, &ebc->plane, 0,
+				       &rockchip_ebc_plane_funcs,
+				       rockchip_ebc_plane_formats,
+				       ARRAY_SIZE(rockchip_ebc_plane_formats),
+				       rockchip_ebc_plane_format_modifiers,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		return ret;
+
+	drm_plane_enable_fb_damage_clips(&ebc->plane);
+
+	drm_crtc_helper_add(&ebc->crtc, &rockchip_ebc_crtc_helper_funcs);
+	ret = drm_crtc_init_with_planes(drm, &ebc->crtc, &ebc->plane, NULL,
+					&rockchip_ebc_crtc_funcs, NULL);
+	if (ret)
+		return ret;
+
+	ebc->encoder.possible_crtcs = drm_crtc_mask(&ebc->crtc);
+	// todo: consider using drmm_simple_encoder_alloc()
+	// see: https://www.kernel.org/doc/html/latest/gpu/drm-kms-helpers.html?highlight=drm_simple_encoder_init#c.drm_simple_encoder_init
+	ret = drm_simple_encoder_init(drm, &ebc->encoder, DRM_MODE_ENCODER_NONE);
+	if (ret)
+		return ret;
+
+	bridge = devm_drm_of_get_bridge(drm->dev, drm->dev->of_node, 0, 0);
+	if (IS_ERR(bridge))
+		return PTR_ERR(bridge);
+
+	ret = drm_bridge_attach(&ebc->encoder, bridge, NULL, 0);
+	if (ret)
+		return ret;
+
+	drm_mode_config_reset(drm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		return ret;
+
+	drm_client_setup_with_fourcc(drm, DRM_FORMAT_RGB565);
+
+	return 0;
+}
+
+static int __maybe_unused rockchip_ebc_suspend(struct device *dev)
+{
+	int ret = pm_runtime_force_suspend(dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int __maybe_unused rockchip_ebc_resume(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+
+	pr_info("%s %lld", __func__, ktime_get());
+
+	pm_runtime_force_resume(dev);
+
+	return drm_mode_config_helper_resume(&ebc->drm);
+}
+
+static int rockchip_ebc_runtime_suspend(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+
+	regcache_cache_only(ebc->regmap, true);
+
+	clk_disable_unprepare(ebc->dclk);
+	clk_disable_unprepare(ebc->hclk);
+	regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies);
+
+	return 0;
+}
+
+static int rockchip_ebc_runtime_resume(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+	int ret;
+
+	ret = regulator_bulk_enable(EBC_NUM_SUPPLIES, ebc->supplies);
+	if (ret)
+		return ret;
+
+	ret = clk_prepare_enable(ebc->hclk);
+	if (ret)
+		goto err_disable_supplies;
+
+	ret = clk_prepare_enable(ebc->dclk);
+	if (ret)
+		goto err_disable_hclk;
+
+	/*
+	 * Do not restore the LUT registers here, because the temperature or
+	 * waveform may have changed since the last refresh. Instead, have the
+	 * refresh thread program the LUT during the next refresh.
+	 */
+	regcache_cache_only(ebc->regmap, false);
+	regcache_mark_dirty(ebc->regmap);
+	regcache_sync(ebc->regmap);
+
+	regmap_write(ebc->regmap, EBC_INT_STATUS,
+		     EBC_INT_STATUS_DSP_END_INT_CLR |
+		     EBC_INT_STATUS_LINE_FLAG_INT_MSK |
+		     EBC_INT_STATUS_DSP_FRM_INT_MSK |
+		     EBC_INT_STATUS_FRM_END_INT_MSK);
+
+	return 0;
+
+err_disable_hclk:
+	clk_disable_unprepare(ebc->hclk);
+err_disable_supplies:
+	regulator_bulk_disable(EBC_NUM_SUPPLIES, ebc->supplies);
+
+	return ret;
+}
+
+static int rockchip_ebc_prepare(struct device *dev)
+{
+	struct rockchip_ebc *ebc = dev_get_drvdata(dev);
+	int ret;
+
+	spin_lock(&ebc->work_item_lock);
+	ebc->work_item |= ROCKCHIP_EBC_WORK_ITEM_SUSPEND;
+	spin_unlock(&ebc->work_item_lock);
+
+	ret = drm_mode_config_helper_suspend(&ebc->drm);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct dev_pm_ops rockchip_ebc_dev_pm_ops = {
+	.prepare = rockchip_ebc_prepare,
+	SET_SYSTEM_SLEEP_PM_OPS(rockchip_ebc_suspend, rockchip_ebc_resume)
+	SET_RUNTIME_PM_OPS(rockchip_ebc_runtime_suspend,
+			   rockchip_ebc_runtime_resume, NULL)
+};
+
+static bool rockchip_ebc_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case EBC_DSP_START:
+	case EBC_INT_STATUS:
+	case EBC_CONFIG_DONE:
+	case EBC_VNUM:
+		return true;
+	default:
+		/* Do not cache the LUT registers. */
+		return reg > EBC_WIN_MST2;
+	}
+}
+
+static const struct regmap_config rockchip_ebc_regmap_config = {
+	.reg_bits	= 32,
+	.reg_stride	= 4,
+	.val_bits	= 32,
+	.volatile_reg	= rockchip_ebc_volatile_reg,
+	.max_register	= 0x4ffc, /* end of EBC_LUT_DATA */
+	.cache_type	= REGCACHE_FLAT,
+};
+
+static const char *const rockchip_ebc_supplies[EBC_NUM_SUPPLIES] = {
+	"panel",
+	"vcom",
+	"vdrive",
+};
+
+static irqreturn_t rockchip_ebc_irq(int irq, void *dev_id)
+{
+	struct rockchip_ebc *ebc = dev_id;
+	unsigned int status;
+
+	regmap_read(ebc->regmap, EBC_INT_STATUS, &status);
+
+	pr_debug("%s status=%d", __func__, status);
+	if (status & EBC_INT_STATUS_DSP_END_INT_ST) {
+		status |= EBC_INT_STATUS_DSP_END_INT_CLR;
+		complete(&ebc->display_end);
+	}
+
+	regmap_write(ebc->regmap, EBC_INT_STATUS, status);
+
+	return IRQ_HANDLED;
+}
+
+static int rockchip_ebc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rockchip_ebc *ebc;
+	void __iomem *base;
+	int i, ret;
+	pr_info("%s start", __func__);
+
+	if (dma_set_mask(dev, DMA_BIT_MASK(32))){
+		dev_warn(dev, "rockchip-ebc: No suitable DMA available\n");
+	}
+
+	ebc = devm_drm_dev_alloc(dev, &rockchip_ebc_drm_driver,
+				 struct rockchip_ebc, drm);
+	if (IS_ERR(ebc))
+		return PTR_ERR(ebc);
+
+	unsigned int width = 1872;
+	unsigned int height = 1404;
+	ebc->direct_mode = direct_mode;
+	ebc->gray4_pitch = width / 2;
+	ebc->gray4_size = width * height / 2;
+	ebc->phase_pitch = ebc->direct_mode ? width / 4 : width;
+	ebc->phase_size = ebc->phase_pitch * height;
+	ebc->num_pixels = width * height;
+	ebc->pixel_pitch = width;
+	ebc->height = height;
+	ebc->screen_rect = DRM_RECT_INIT(0, 0, width, height);
+
+	ebc->y4_threshold_y1 = bw_threshold;
+
+	switch (dithering_method) {
+	case DITHERING_BAYER:
+		ebc->dithering_texture = dither_bayer_04;
+		ebc->dithering_texture_size_hint = 4;
+		break;
+	case DITHERING_BLUE_NOISE_16:
+		ebc->dithering_texture = dither_blue_noise_16;
+		ebc->dithering_texture_size_hint = 16;
+		break;
+	case DITHERING_BLUE_NOISE_32:
+		fallthrough;
+	default:
+		ebc->dithering_texture = dither_blue_noise_32;
+		ebc->dithering_texture_size_hint = 32;
+		break;
+	}
+
+	ebc->final_off_screen = drmm_kzalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL);
+	ebc->packed_inner_outer_nextprev = vmalloc(3 *  ebc->num_pixels); // drmm_kmalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL);
+	if (!direct_mode) {
+		ebc->hardware_wf = drmm_kzalloc(&ebc->drm, 4 * EBC_NUM_LUT_REGS,
+						GFP_KERNEL);
+		ebc->zero = drmm_kzalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL | GFP_DMA);
+	}
+	ebc->hints_ioctl = vmalloc(ebc->num_pixels); // drmm_kmalloc(&ebc->drm, ebc->num_pixels, GFP_KERNEL);
+	ebc->phase[0] = drmm_kzalloc(&ebc->drm, ebc->phase_size, GFP_KERNEL | GFP_DMA);
+	ebc->phase[1] = drmm_kzalloc(&ebc->drm, ebc->phase_size, GFP_KERNEL | GFP_DMA);
+	if (!(ebc->final_off_screen && ebc->packed_inner_outer_nextprev &&
+	      (direct_mode || (ebc->hardware_wf && ebc->zero)) &&
+	      ebc->hints_ioctl && ebc->phase[0] &&
+	      ebc->phase[1])) {
+		return dev_err_probe(dev, -ENOMEM, "Failed to allocate buffers\n");
+	}
+	ebc->phase_sequence = drmm_kzalloc(
+		&ebc->drm, sizeof(struct drm_rockchip_ebc_phase_sequence),
+		GFP_KERNEL);
+	if (!ebc->phase_sequence)
+		return dev_err_probe(dev, -ENOMEM, "Failed to allocate phase_sequence buffer\n");
+	ebc->phase_handles[0] = dma_map_single(dev, ebc->phase[0], ebc->phase_size,
+					       DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, ebc->phase_handles[0])) {
+		return dev_err_probe(dev, -ENOMEM, "phase_handles[0] dma mapping error");
+	}
+	ebc->phase_handles[1] = dma_map_single(dev, ebc->phase[1], ebc->phase_size,
+					       DMA_TO_DEVICE);
+	if (dma_mapping_error(dev, ebc->phase_handles[1])) {
+		dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE);
+		return dev_err_probe(dev, -ENOMEM, "phase_handles[1] dma mapping error");
+	}
+	if (!direct_mode) {
+		ebc->zero_handle = dma_map_single(
+						  dev, ebc->zero, ebc->gray4_size, DMA_TO_DEVICE);
+		if (dma_mapping_error(dev, ebc->zero_handle)) {
+			dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE);
+			dma_unmap_single(dev, ebc->phase_handles[1], ebc->phase_size, DMA_TO_DEVICE);
+			return dev_err_probe(dev, -ENOMEM,
+					     "zero_handle dma mapping error");
+		}
+		dma_sync_single_for_device(dev, ebc->zero_handle, ebc->gray4_size, DMA_TO_DEVICE);
+	}
+	dma_sync_single_for_device(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE);
+	dma_sync_single_for_device(dev, ebc->phase_handles[1], ebc->phase_size,
+				   DMA_TO_DEVICE);
+
+	memset(ebc->hints_ioctl, default_hint & ROCKCHIP_EBC_HINT_MASK,
+	       ebc->num_pixels);
+
+	// Custom waveform
+	if (!direct_mode) {
+		*((u32 *) ebc->hardware_wf + 16) = 0x55555555;
+		*((u32 *) ebc->hardware_wf + 32) = 0xaaaaaaaa;
+	}
+
+	ebc->driver_mode = ROCKCHIP_EBC_DRIVER_MODE_NORMAL;
+	ebc->redraw_delay = redraw_delay;
+
+	// Sensible temperature default
+	ebc->temperature = temp_override > 0 ? temp_override : 25;
+	ebc->work_item = ROCKCHIP_EBC_WORK_ITEM_CHANGE_LUT |
+		ROCKCHIP_EBC_WORK_ITEM_INIT;
+
+	spin_lock_init(&ebc->work_item_lock);
+	spin_lock_init(&ebc->hints_ioctl_lock);
+	spin_lock_init(&ebc->phase_sequence_lock);
+	ebc->suspend_was_requested = 0;
+
+	platform_set_drvdata(pdev, ebc);
+	init_completion(&ebc->display_end);
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	ebc->regmap = devm_regmap_init_mmio(dev, base,
+					    &rockchip_ebc_regmap_config);
+	if (IS_ERR(ebc->regmap))
+		return PTR_ERR(ebc->regmap);
+
+	regcache_cache_only(ebc->regmap, true);
+
+	ebc->dclk = devm_clk_get(dev, "dclk");
+	if (IS_ERR(ebc->dclk))
+		return dev_err_probe(dev, PTR_ERR(ebc->dclk),
+				     "Failed to get dclk\n");
+
+	ebc->hclk = devm_clk_get(dev, "hclk");
+	if (IS_ERR(ebc->hclk))
+		return dev_err_probe(dev, PTR_ERR(ebc->hclk),
+				     "Failed to get hclk\n");
+
+	ebc->cpll_333m = devm_clk_get(dev, "cpll_333m");
+	if (IS_ERR(ebc->cpll_333m))
+		return dev_err_probe(dev, PTR_ERR(ebc->cpll_333m),
+				     "Failed to get cpll_333m\n");
+
+	ebc->temperature_channel = devm_iio_channel_get(dev, NULL);
+	if (IS_ERR(ebc->temperature_channel))
+		return dev_err_probe(dev, PTR_ERR(ebc->temperature_channel),
+				     "Failed to get temperature I/O channel\n");
+
+	for (i = 0; i < EBC_NUM_SUPPLIES; i++)
+		ebc->supplies[i].supply = rockchip_ebc_supplies[i];
+
+	ret = devm_regulator_bulk_get(dev, EBC_NUM_SUPPLIES, ebc->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get supplies\n");
+
+	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+			       rockchip_ebc_irq, 0, dev_name(dev), ebc);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+	pm_runtime_set_autosuspend_delay(dev, EBC_SUSPEND_DELAY_MS);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_enable(dev);
+	if (!pm_runtime_enabled(dev)) {
+		ret = rockchip_ebc_runtime_resume(&pdev->dev);
+		if (ret)
+			return ret;
+	}
+
+	ret = rockchip_ebc_waveform_init(ebc);
+	if (ret)
+		return ret;
+
+	// Make sure ebc->lut_custom_active is initialised
+	rockchip_ebc_change_lut(ebc);
+
+	ebc->temp_upd_thread =
+		kthread_create(rockchip_ebc_temp_upd_thread, ebc,
+			       "ebc-tempupd/%s", dev_name(dev));
+	if (IS_ERR(ebc->temp_upd_thread)) {
+		ret = dev_err_probe(
+			dev, PTR_ERR(ebc->temp_upd_thread),
+			"Failed to start temperature update thread");
+		goto err_disable_pm;
+	}
+	kthread_park(ebc->temp_upd_thread);
+
+	ebc->refresh_thread = kthread_create(rockchip_ebc_refresh_thread,
+					     ebc, "ebc-refresh/%s",
+					     dev_name(dev));
+	if (IS_ERR(ebc->refresh_thread)) {
+		ret = dev_err_probe(dev, PTR_ERR(ebc->refresh_thread),
+				    "Failed to start refresh thread\n");
+		goto err_disable_pm;
+	}
+
+	kthread_park(ebc->refresh_thread);
+	sched_set_fifo(ebc->refresh_thread);
+
+	ret = rockchip_ebc_drm_init(ebc);
+	if (ret)
+		return ret;
+
+	return 0;
+
+err_disable_pm:
+	pm_runtime_disable(dev);
+	if (!pm_runtime_status_suspended(dev))
+		rockchip_ebc_runtime_suspend(dev);
+
+	return ret;
+}
+
+static void rockchip_ebc_remove(struct platform_device *pdev)
+{
+	struct rockchip_ebc *ebc = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+	// pr_info("%s executing", __func__);
+
+	drm_dev_unregister(&ebc->drm);
+	kthread_stop(ebc->refresh_thread);
+	kthread_stop(ebc->temp_upd_thread);
+	drm_atomic_helper_shutdown(&ebc->drm);
+
+	dma_unmap_single(dev, ebc->phase_handles[0], ebc->phase_size, DMA_TO_DEVICE);
+	dma_unmap_single(dev, ebc->phase_handles[1], ebc->phase_size, DMA_TO_DEVICE);
+
+	if (!direct_mode) {
+		dma_unmap_single(dev, ebc->zero_handle, ebc->gray4_size,
+				 DMA_TO_DEVICE);
+	}
+
+	vfree(ebc->hints_ioctl);
+	vfree(ebc->packed_inner_outer_nextprev);
+
+	pm_runtime_disable(dev);
+	if (!pm_runtime_status_suspended(dev))
+		rockchip_ebc_runtime_suspend(dev);
+}
+
+static void rockchip_ebc_shutdown(struct platform_device *pdev)
+{
+	struct rockchip_ebc *ebc = platform_get_drvdata(pdev);
+	struct device *dev = &pdev->dev;
+	// pr_info("%s executing", __func__);
+
+	kthread_stop(ebc->refresh_thread);
+	kthread_stop(ebc->temp_upd_thread);
+	drm_atomic_helper_shutdown(&ebc->drm);
+
+	if (!pm_runtime_status_suspended(dev))
+		rockchip_ebc_runtime_suspend(dev);
+}
+
+static const struct of_device_id rockchip_ebc_of_match[] = {
+	{ .compatible = "rockchip,rk3568-ebc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rockchip_ebc_of_match);
+
+static struct platform_driver rockchip_ebc_driver = {
+	.probe		= rockchip_ebc_probe,
+	.remove		= rockchip_ebc_remove,
+	.shutdown	= rockchip_ebc_shutdown,
+	.driver		= {
+		.name		= "rockchip-ebc",
+		.of_match_table	= rockchip_ebc_of_match,
+		.pm		= &rockchip_ebc_dev_pm_ops,
+	},
+};
+module_platform_driver(rockchip_ebc_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>, Maximilian Weigand, hrdl <git@hrdl.eu>");
+MODULE_DESCRIPTION("Rockchip EBC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc.h b/drivers/gpu/drm/rockchip/rockchip_ebc.h
new file mode 100644
index 000000000..9d3ba1363
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc.h
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021-2022 Samuel Holland <samuel@sholland.org>
+   Copyright (C) 2025 hrdl <git@hrdl.eu>
+ */
+
+#ifndef _ROCKCHIP_EBC_H
+#define _ROCKCHIP_EBC_H
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <linux/kref.h>
+#include <linux/list.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock_types.h>
+#include <linux/types.h>
+
+#define ROCKCHIP_EBC_CUSTOM_WF_NUM_SEQS 6
+#define ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT 6
+#define ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH (1 << ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT)
+#define ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE (16 * 16 * ROCKCHIP_EBC_CUSTOM_WF_SEQ_LENGTH)
+
+// The order matters is related to various buffer formats and NEON optimisations
+
+#define ROCKCHIP_EBC_CUSTOM_WF_DU 0
+#define ROCKCHIP_EBC_CUSTOM_WF_DU4 1
+#define ROCKCHIP_EBC_CUSTOM_WF_GL16 2
+#define ROCKCHIP_EBC_CUSTOM_WF_GC16 3
+#define ROCKCHIP_EBC_CUSTOM_WF_INIT 4
+#define ROCKCHIP_EBC_CUSTOM_WF_WAITING 5
+
+#define EBC_NUM_SUPPLIES		3
+
+/*
+ * @lower_temp: Lower temperature limit for this waveform
+ * @upper_temp: Upper temperature limit for this waveform
+ * @offsets: offsets for DU, DU4, GL16, GC16, INIT
+ * @lut: compact LUT containing phase information (00, 01, 10) and inner nums at every outer num. Sequences start at offsets, are terminated by 0x00, and are index by src, dst, outer_num
+ */
+struct drm_epd_lut_temp_v2 {
+	s32	temp_lower;
+	s32	temp_upper;
+	// 16 bytes for NEON loading
+	u8	offsets[16];
+	u8	lut[ROCKCHIP_EBC_CUSTOM_WF_LUT_SIZE];
+};
+
+struct drm_epd_lut_v2 {
+	// A single LUT struct for every temperature range
+	unsigned int			num_temp_ranges;
+	struct drm_epd_lut_temp_v2	*luts;
+};
+
+struct rockchip_ebc {
+	struct clk			*dclk;
+	struct clk			*hclk;
+	struct clk			*cpll_333m;
+
+	// Hardware-related display timings
+	u32				dsp_start;
+	u16				act_width;
+	u16				act_height;
+	u16				hact_start;
+	u16				vact_start;
+
+	// ebc: indicate frame completion
+	struct completion		display_end;
+	struct iio_channel		*temperature_channel;
+	struct regmap			*regmap;
+	struct regulator_bulk_data	supplies[EBC_NUM_SUPPLIES];
+
+	// DRM
+	struct drm_crtc			crtc;
+	struct drm_device		drm;
+	struct drm_encoder		encoder;
+	struct drm_plane		plane;
+
+	// LUT holding waveform information
+	struct drm_epd_lut		lut;
+	struct drm_epd_lut_file		lut_file;
+	struct drm_epd_lut_v2		lut_custom;
+	struct drm_epd_lut_temp_v2	*lut_custom_active;
+	u8				inner_15_0;
+	u8				inner_0_15;
+
+	struct task_struct		*temp_upd_thread;
+	struct task_struct		*refresh_thread;
+
+	struct drm_rect			screen_rect;
+	// final buffer to display before suspending
+	u8				*final_off_screen;
+	spinlock_t			hints_ioctl_lock;
+	// Used to keep next/prev in 3WIN mode at zero. Read by controller
+	u8				*zero;
+	u8				*hardware_wf;
+	// inner: PPLI IIIII, indicating the current phase (0, 1, 2), whether this is the (L)ast entry, and the number of frames left until a phase shift is required
+	// outer: indicating the position in the driver-based LUT
+	// next_prev: NNNN PPPP, dispaly state after/before the ongoing/previous update finishes
+	u8				*packed_inner_outer_nextprev;
+	// Hints written by ioctl_rect_hints (for now) and read by rockchip_ebc_plane_atomic_update
+	u8				*hints_ioctl;
+	// Phase buffer, 1 pixel per byte in 3WIN mode and 4 in direct mode (not implemented yet). Written by refresh thread and read by controller. Flipped after every display frame
+	u8				*phase[2];
+	dma_addr_t			phase_handles[2];
+	dma_addr_t			zero_handle;
+	// Pitches and sizes
+	u32				gray4_pitch;
+	u32				gray4_size;
+	u32				phase_pitch;
+	u32				phase_size;
+	u32				num_pixels;
+	u32				pixel_pitch;
+	u32				height;
+	// Dithering / thresholding information
+	u8				y4_threshold_y1;
+	aligned_u64			lut_y2_y4[2];
+	aligned_u64			lut_y2_y4_dithered[4];
+	const u8			*dithering_texture;
+	// 4, 16, or 32 rows. Pitch is 16 for 4 and 16, and 32 for 32
+	u8				dithering_texture_size_hint;
+	// whether we use direct mode or 3WIN mode. Only 3WIN mode is implemented right now
+	bool				direct_mode;
+	int				driver_mode;
+	int				redraw_delay;
+	// Used to change the driver LUT due to temperature update, trigger a global refresh, or buffer changes
+	spinlock_t			work_item_lock;
+	u32				work_item;
+	// used to detect when we are suspending so we can do different things to
+	// the ebc display depending on whether we are sleeping or suspending
+	int				suspend_was_requested;
+	// Cached temperature in deg C
+	int				temperature;
+	struct drm_rockchip_ebc_phase_sequence	*phase_sequence;
+	spinlock_t			phase_sequence_lock;
+};
+
+/**
+ * struct rockchip_ebc_ctx - DRM-related context for performing display refreshes
+ *
+ * @kref: Reference count, maintained as part of the CRTC's atomic state
+ * @buffer_switch_lock: Lock protecting write-access to refresh_buffer_index
+ * @refresh_index: which buffer is being read by the refresh thread
+ * @update_index: which buffer is the next one to be written by atomic_update
+ * @dst_clip: clip i belonging to buffer i. Independent of the the other buffers
+ * @src_clip_extended: the union of clips != i blitted after i was blitted
+ * @prelim_target_buffer: triple-buffered prelim_target
+ * @hints_buffer: triple-buffered hints_buffer
+*/
+struct rockchip_ebc_ctx {
+	struct kref		kref;
+	spinlock_t		buffer_switch_lock;
+	int			next_refresh_index;
+	int			refresh_index;
+	int			update_index;
+	struct drm_rect		dst_clip[3];
+	struct drm_rect		src_clip_extended[3];
+	u8			*prelim_target_buffer[3];
+	u8			*hints_buffer[3];
+	u8			not_after_others[3];
+};
+
+/**
+ * struct rockchip_ebc_area - describes a damaged area of the display
+ *
+ * @list: Used to put this area in the state/context/refresh thread list
+ * @clip: The rectangular clip of this damage area
+ * @frame_begin: The frame number when this damage area starts being refreshed
+ */
+struct rockchip_ebc_area {
+	struct drm_rect			clip;
+};
+
+/**
+ * rockchip_ebc_drm_rect_extend - extend rect to include (x, y)
+ * @r: rectangle
+ * @x: x coordinate
+ * @y: y coordinate
+ */
+inline void rockchip_ebc_drm_rect_extend(struct drm_rect *r, int x, int y)
+{
+	r->x1 = min(r->x1, x);
+	r->x2 = max(r->x2, x + 1);
+	r->y1 = min(r->y1, y);
+	r->y2 = max(r->y2, y + 1);
+}
+
+/**
+ * rockchip_ebc_drm_rect_extend_rect - extend rect r1 to include r2
+ * @r: rectangle
+ * @x: x coordinate
+ * @y: y coordinate
+ */
+inline void rockchip_ebc_drm_rect_extend_rect(struct drm_rect *r1, const struct drm_rect *r2)
+{
+	r1->x1 = min(r1->x1, r2->x1);
+	r1->x2 = max(r1->x2, r2->x2);
+	r1->y1 = min(r1->y1, r2->y1);
+	r1->y2 = max(r1->y2, r2->y2);
+}
+
+void rockchip_ebc_schedule_advance_fast_neon(const struct rockchip_ebc *ebc,
+					const u8 *prelim_target, u8 *hints,
+					u8 *phase_buffer,
+					struct drm_rect *clip_ongoing,
+					struct drm_rect *clip_ongoing_or_waiting,
+					u8 early_cancellation_addition,
+					u8 force_wf, u8 force_hint,
+					u8 force_hint_mask, bool allow_schedule);
+
+void rockchip_ebc_schedule_advance_neon(const struct rockchip_ebc *ebc,
+					const u8 *prelim_target, u8 *hints,
+					u8 *phase_buffer,
+					struct drm_rect *clip_ongoing,
+					struct drm_rect *clip_ongoing_or_waiting,
+					u8 early_cancellation_addition,
+					u8 force_wf, u8 force_hint,
+					u8 force_hint_mask, bool allow_schedule);
+
+void rockchip_ebc_blit_y4_high_low_neon(u8 *dst, u8 *y4_high_src,
+					u8 *y4_low_src, unsigned int size);
+
+void rockchip_ebc_blit_y421_y4_neon(const struct rockchip_ebc *ebc,
+				    u8 *y4_dst, const u8 *y421_src,
+				    struct drm_rect *clip);
+
+void rockchip_ebc_blit_fb_rgb565_y4_hints_neon(const struct rockchip_ebc *ebc,
+					       struct drm_rect *dst_clip,
+					       u8 *prelim_target_atomic_update,
+					       u8 *hints_atomic_update,
+					       const void *vaddr,
+					       const struct drm_framebuffer *fb,
+					       const struct drm_rect *src_clip);
+
+void rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon(
+	const struct rockchip_ebc *ebc, struct drm_rect *dst_clip,
+	u8 *prelim_target_atomic_update, u8 *hints_atomic_update,
+	const void *vaddr, const struct drm_framebuffer *fb,
+	const struct drm_rect *src_clip);
+
+void rockchip_ebc_blit_fb_r8_y4_hints_neon(const struct rockchip_ebc *ebc,
+					   struct drm_rect *dst_clip,
+					   u8 *prelim_target_atomic_update,
+					   u8 *hints_atomic_update,
+					   const void *vaddr,
+					   const struct drm_framebuffer *fb,
+					   const struct drm_rect *src_clip);
+
+void rockchip_ebc_reset_inner_outer_neon(const struct rockchip_ebc *ebc);
+
+#define DRM_RECT_EMPTY_EXTANDABLE DRM_RECT_INIT(100000, 100000, -100000, -100000);
+
+#endif /* _ROCKCHIP_EBC_H */
diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c
new file mode 100644
index 000000000..2fae7d242
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.c
@@ -0,0 +1,1253 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 hrdl <git@hrdl.eu>
+ */
+
+#include <linux/module.h>
+#include <linux/prefetch.h>
+#include <linux/types.h>
+#include <drm/drm_epd_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_rect.h>
+#include "rockchip_ebc.h"
+
+#include <asm/neon-intrinsics.h>
+
+#define NEON_PR_INFO(_q8_name)                                          \
+	{                                                               \
+		vst1q_u8(u8_tmp, (_q8_name));                           \
+		pr_info("%s %s \t=%16ph", __func__, #_q8_name, u8_tmp); \
+	}
+
+#define NEON_PR_INFOS(_q8s_name)                                        \
+	{                                                               \
+		vst1_u8(u8_tmp, (_q8s_name));                           \
+		pr_info("%s %s \t=%8ph", __func__, #_q8s_name, u8_tmp); \
+	}
+
+// #define ROCKCHIP_EBC_DEBUG_NEON
+// #define ROCKCHIP_EBC_DEBUG_NEON_FAST
+
+void rockchip_ebc_schedule_advance_fast_neon(
+	const struct rockchip_ebc *ebc, const u8 *prelim_target, u8 *hints,
+	u8 *phase_buffer, struct drm_rect *clip_ongoing,
+	struct drm_rect *clip_ongoing_or_waiting,
+	u8 early_cancellation_addition, u8 force_wf, u8 force_hint,
+	u8 force_hint_mask, bool allow_schedule)
+{
+	unsigned int pixel_pitch = ebc->pixel_pitch;
+	unsigned int prefetch_pitch = (pixel_pitch >> 6) << 6;
+	unsigned int phases_prefetch_pitch = (pixel_pitch >> 8) << 6;
+	// unsigned int packed_pitch = 3 * pixel_pitch;
+	unsigned int phase_pitch = ebc->phase_pitch;
+	// struct drm_rect clip_ongoing_or_waiting_new = DRM_RECT_EMPTY_EXTANDABLE;
+
+#ifdef ROCKCHIP_EBC_DEBUG_NEON_FAST
+	aligned_u64 _tmp[2];
+	u8 *u8_tmp = (u8 *)_tmp;
+#endif
+
+	// 16 byte block size for NEON
+	unsigned int x_start = max(0, min(clip_ongoing_or_waiting->x1 & ~15,
+					  (int)ebc->pixel_pitch - 16));
+
+	uint8x16_t q8_0x00 = vdupq_n_u8(0x00);
+	uint8x16_t q8_0x01 = vdupq_n_u8(0x01);
+	uint8x16_t q8_0x0f = vdupq_n_u8(0x0f);
+	uint8x16_t q8_0x1f = vdupq_n_u8(0x1f);
+	uint8x16_t q8_inner_0_15 = vdupq_n_u8(ebc->inner_0_15);
+	uint8x16_t q8_inner_15_0 = vdupq_n_u8(ebc->inner_15_0);
+	uint8x16_t q8_early_cancellation_addition =
+		vdupq_n_u8(early_cancellation_addition);
+	uint8x16_t q8_allow_schedule =
+		vcgtq_u8(vdupq_n_u8(allow_schedule), q8_0x00);
+
+	uint32x2_t q32s_0x10 = vdup_n_u32(0x10);
+	uint16x4_t q16s_mins = vdup_n_u16(0xffff);
+	uint16x4_t q16s_maxs = vdup_n_u16(0);
+	uint64x1_t q64s_0x00 = vdup_n_u64(0);
+
+	for (unsigned int y = max(0, clip_ongoing_or_waiting->y1);
+	     y < min((int) ebc->height, clip_ongoing_or_waiting->y2); ++y) {
+		int offset = y * pixel_pitch + x_start;
+		u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev + offset * 3;
+		u8 *phases_line =
+			phase_buffer + y * phase_pitch + (x_start >> 2);
+		const u8 *prelim_target_line = prelim_target + offset;
+		uint32x2_t q32s_xy = vdup_n_u32(y << 16 | x_start);
+
+		for (unsigned int x = x_start; x < clip_ongoing_or_waiting->x2;
+		     x += 16, packed_inner_outer_nextprev_line += 48,
+			     phases_line += 4, prelim_target_line += 16) {
+			uint8x16x3_t q8_inner_outer_nextprev =
+				vld3q_u8(packed_inner_outer_nextprev_line);
+			/* __builtin_prefetch( */
+			/* 		   packed_inner_outer_nextprev_line + packed_pitch, 0, 0); */
+			uint8x16_t q8_inner = q8_inner_outer_nextprev.val[0];
+			uint8x16_t q8_outer = q8_inner_outer_nextprev.val[1];
+			uint8x16_t q8_next_prev =
+				q8_inner_outer_nextprev.val[2];
+			uint8x16_t q8_prelim_target =
+				vld1q_u8(prelim_target_line);
+			__builtin_prefetch(prelim_target_line + prefetch_pitch, 0, 0);
+
+			// Extract packed Y4 values and hints
+			uint8x16_t q8_inner_num = vandq_u8(q8_inner, q8_0x1f);
+			uint8x16_t q8_next = vshrq_n_u8(q8_next_prev, 4);
+			uint8x16_t q8_prev = vandq_u8(q8_next_prev, q8_0x0f);
+			uint8x16_t q8_prelim = vshrq_n_u8(q8_prelim_target, 4);
+
+			// Start transforming
+			uint8x16_t q8_inner_num_is_1 =
+				vceqq_u8(q8_inner_num, q8_0x01);
+			// Saturating subtraction
+			uint8x16_t q8_inner_num_new =
+				vqsubq_u8(q8_inner_num, q8_0x01);
+			// Restore phase and is_last information
+			uint8x16_t q8_inner_new =
+				vbslq_u8(q8_0x1f, q8_inner_num_new, q8_inner);
+
+			// These can be scheduled (next != target, next != prelim) or sent to WAITING (next == prelim, next != target)
+			// TODO: merge these into a single q8_inner_num <= 1
+			uint8x16_t q8_idle = vceqzq_u8(q8_inner);
+			uint8x16_t q8_idle_finish =
+				vorrq_u8(q8_idle, q8_inner_num_is_1);
+
+			// 1. Schedule: start target or prelim
+			uint8x16_t q8_start_scheduled = vandq_u8(
+				q8_allow_schedule,
+				vmvnq_u8(vceqq_u8(q8_prelim, q8_next)));
+			uint8x16_t q8_next_new = vbslq_u8(q8_start_scheduled,
+							  q8_prelim, q8_next);
+			uint8x16_t q8_prev_new =
+				vbslq_u8(q8_start_scheduled, q8_next, q8_prev);
+
+			uint8x16_t q8_next_prev_new = vorrq_u8(
+				vshlq_n_u8(q8_next_new, 4), q8_prev_new);
+
+			// 3. Move remaining finished ones to IDLE or WAITING
+			uint8x16_t q8_no_start_scheduled =
+				vmvnq_u8(q8_start_scheduled);
+			uint8x16_t q8_finish_no_start_scheduled =
+				vandq_u8(q8_inner_num_is_1, q8_no_start_scheduled);
+			uint8x16_t q8_outer_eq_0 = vceqzq_u8(q8_outer);
+			uint8x16_t q8_outer_gt_0 = vmvnq_u8(q8_outer_eq_0);
+			// Start waiting if:
+			// We finish, but we don't start schedule/redraw AND next != target, AND allow_schedule
+			// Or: We are not running AND we didn't schedule (next=prelim) AND next != target AND allow_schedule
+
+			uint8x16_t q8_start_idle = vandq_u8(
+				q8_finish_no_start_scheduled, q8_outer_gt_0);
+			uint8x16_t q8_start = q8_start_scheduled;
+
+			// 4. Get outer_new
+			uint8x16_t q8_outer_new = vbslq_u8(q8_inner_num_is_1, q8_0x00, q8_outer);
+			q8_outer_new =
+				vbslq_u8(q8_start, q8_0x01, q8_outer_new);
+			// Or: replace q8_wf with WAITING before the previous statement
+
+			// 5. Get inner_new
+			uint8x16_t q8_inner_new_from_lut =
+				vbslq_u8(vceqzq_u8(q8_next_new), q8_inner_15_0,
+					 q8_inner_0_15);
+			q8_inner_new =
+				vbslq_u8(q8_start,
+					 q8_inner_new_from_lut, q8_inner_new);
+			q8_inner_new =
+				vbslq_u8(q8_start_idle, q8_0x01, q8_inner_new);
+
+			// 6. Early cancellation
+			uint8x16_t q8_cancel =
+				vandq_u8(q8_start, vmvnq_u8(q8_idle_finish));
+			uint8x16_t q8_to_subtract =
+				vandq_u8(q8_cancel, q8_inner_num);
+			q8_inner_num_new =
+				vqsubq_u8(vandq_u8(q8_inner_new, q8_0x1f),
+					  q8_to_subtract);
+			q8_inner_num_new = vqaddq_u8(
+				q8_inner_num_new,
+				vandq_u8(q8_cancel,
+					 q8_early_cancellation_addition));
+			q8_inner_new = vbslq_u8(q8_0x1f, q8_inner_num_new,
+						q8_inner_new);
+			uint8x16x3_t q8_ionp_new = {{q8_inner_new, q8_outer_new, q8_next_prev_new}};
+			vst3q_u8(packed_inner_outer_nextprev_line, q8_ionp_new);
+
+#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE
+			if (!direct_mode) {
+				vst1q_u8(phases_line, vshrq_n_u8(q8_inner_new, 6);
+			} else
+#endif
+			{
+				uint16x8_t q16_phases = vreinterpretq_u16_u8(
+									     vshrq_n_u8(q8_inner_new, 6));
+				q16_phases = vorrq_u16(q16_phases,
+						       vshrq_n_u16(q16_phases, 6));
+				uint8x8_t q8s_phases1 = vmovn_u16(q16_phases);
+				q16_phases = vreinterpretq_u16_u8(
+								  vcombine_u8(q8s_phases1, q8s_phases1));
+				uint8x8_t q8s_phases = vmovn_u16(vorrq_u16(
+									  q16_phases, vshrq_n_u16(q16_phases, 4)));
+				__builtin_prefetch(phases_line + phases_prefetch_pitch, 1, 0);
+				vst1_lane_u32((u32 *)phases_line,
+					      vreinterpret_u32_u8(q8s_phases),
+					      0);
+			}
+
+#ifdef ROCKCHIP_EBC_DEBUG_NEON_FAST
+			if (x == 928 && y == 702) {
+				NEON_PR_INFO(q8_prelim_target);
+				NEON_PR_INFO(q8_finish_no_start_scheduled);
+				NEON_PR_INFO(q8_no_start_scheduled);
+				NEON_PR_INFO(q8_outer_gt_0);
+				NEON_PR_INFO(q8_start_idle);
+				NEON_PR_INFO(q8_next_prev_new);
+				NEON_PR_INFO(q8_next_prev);
+				NEON_PR_INFO(q8_inner);
+				NEON_PR_INFO(q8_outer);
+				NEON_PR_INFO(q8_allow_schedule);
+				NEON_PR_INFO(q8_inner_0_15);
+				NEON_PR_INFO(q8_inner_15_0);
+				NEON_PR_INFO(q8_inner_new);
+				NEON_PR_INFO(q8_inner_new_from_lut);
+				NEON_PR_INFO(q8_cancel);
+				NEON_PR_INFO(q8_to_subtract);
+				NEON_PR_INFO(q8_inner_num_new);
+				NEON_PR_INFO(q8_outer_new);
+				NEON_PR_INFO(q16_phases);
+				NEON_PR_INFOS(q8s_phases1);
+				NEON_PR_INFOS(q8s_phases);
+			}
+#endif
+
+			uint64x1_t q64s_any_ongoing = vreinterpret_u64_u8(
+				vorr_u8(vget_high_u8(q8_inner_new),
+					vget_low_u8(q8_inner_new)));
+			uint32x2_t q32s_mask =
+				vreinterpret_u32_u64(vcgt_u64(q64s_any_ongoing, q64s_0x00));
+			q16s_maxs = vmax_u16(q16s_maxs,
+					     vreinterpret_u16_u32(vand_u32(
+						     q32s_xy, q32s_mask)));
+			q16s_mins = vmin_u16(
+				q16s_mins,
+				vreinterpret_u16_u32(vorr_u32(
+					q32s_xy, vmvn_u32(q32s_mask))));
+			q32s_xy = vqadd_u32(q32s_xy, q32s_0x10);
+		}
+	}
+
+	aligned_u64 u64_mins, u64_maxs;
+	u16 *mins = (u16 *)&u64_mins;
+	u16 *maxs = (u16 *)&u64_maxs;
+	vst1_lane_u64(&u64_mins, vreinterpret_u64_u16(q16s_mins), 0);
+	vst1_lane_u64(&u64_maxs, vreinterpret_u64_u16(q16s_maxs), 0);
+	clip_ongoing->x1 = mins[0];
+	clip_ongoing->y1 = mins[1];
+	clip_ongoing->x2 = maxs[0] + 16;
+	clip_ongoing->y2 = maxs[1] + 1;
+	*clip_ongoing_or_waiting = *clip_ongoing;
+}
+EXPORT_SYMBOL(rockchip_ebc_schedule_advance_fast_neon);
+
+void rockchip_ebc_schedule_advance_neon(
+	const struct rockchip_ebc *ebc, const u8 *prelim_target, u8 *hints,
+	u8 *phase_buffer, struct drm_rect *clip_ongoing,
+	struct drm_rect *clip_ongoing_or_waiting,
+	u8 early_cancellation_addition, u8 force_wf, u8 force_hint,
+	u8 force_hint_mask, bool allow_schedule)
+{
+	bool direct_mode = ebc->direct_mode;
+	unsigned int pixel_pitch = ebc->pixel_pitch;
+	// unsigned int packed_pitch = 3 * pixel_pitch;
+	unsigned int phase_pitch = direct_mode ? ebc->phase_pitch :
+		ebc->pixel_pitch;
+	unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6;
+	unsigned int phases_prefetch_pitch = (pixel_pitch >> 8) << 6;
+	u8 *lut = ebc->lut_custom_active->lut;
+	prefetch_range(lut, sizeof(struct drm_epd_lut_temp_v2));
+
+	// 16 u16 values used as addresses for non-NEON code
+	aligned_u64 _idxs[4];
+	// 16 u8 values aligned for LUT using non-NEON code
+	aligned_u64 _lookup[2];
+
+	u16 *u16_idxs = (u16 *)_idxs;
+	u8 *u8_lookup = (u8 *)_lookup;
+	memset(u8_lookup, 0, 16);
+	// Initialise for q8_us_y4_binary_table
+	u8_lookup[0] = 0xff;
+	u8_lookup[15] = 0xff;
+
+#ifdef ROCKCHIP_EBC_DEBUG_NEON
+	aligned_u64 _tmp[2];
+	u8 *u8_tmp = (u8 *)_tmp;
+#endif
+
+	// 16 byte block size for NEON
+	unsigned int x_start = max(0, min(clip_ongoing_or_waiting->x1 & ~15,
+					  (int)ebc->pixel_pitch - 16));
+
+	u8 offset_waiting =
+		ebc->lut_custom_active->offsets[ROCKCHIP_EBC_CUSTOM_WF_WAITING];
+	uint8x16_t q8_0x00 = vdupq_n_u8(0x00);
+	uint8x16_t q8_0x01 = vdupq_n_u8(0x01);
+	uint8x16_t q8_0x03 = vdupq_n_u8(0x03);
+	uint8x16_t q8_0x0f = vdupq_n_u8(0x0f);
+	uint8x16_t q8_0x1f = vdupq_n_u8(0x1f);
+	uint8x16_t q8_0x20 = vdupq_n_u8(0x20);
+	uint8x16_t q8_0x21 = vdupq_n_u8(0x21);
+	uint8x16_t q8_0x80 = vdupq_n_u8(0x80);
+	uint8x16_t q8_offset_waiting = vdupq_n_u8(offset_waiting);
+	uint8x16_t q8_force_wf = vdupq_n_u8(force_wf);
+	uint8x16_t q8_force_hint = vdupq_n_u8(force_hint);
+	uint8x16_t q8_force_hint_mask = vdupq_n_u8(force_hint_mask);
+	uint8x16_t q8_offsets_table = vld1q_u8(ebc->lut_custom_active->offsets);
+	uint8x16_t q8_is_y4_binary_table = vld1q_u8(u8_lookup);
+	uint8x16_t q8_early_cancellation_addition =
+		vdupq_n_u8(early_cancellation_addition);
+	uint8x16_t q8_force_wf_gt0 = vcgtq_u8(q8_force_wf, q8_0x00);
+	uint8x16_t q8_allow_schedule =
+		vcgtq_u8(vdupq_n_u8(allow_schedule), q8_0x00);
+
+	uint32x2_t q32s_0x10 = vdup_n_u32(0x10);
+	uint16x4_t q16s_mins = vdup_n_u16(0xffff);
+	uint16x4_t q16s_maxs = vdup_n_u16(0);
+	uint64x1_t q64s_0x00 = vdup_n_u64(0);
+
+	for (unsigned int y = max(0, clip_ongoing_or_waiting->y1);
+	     y < min((int) ebc->height, clip_ongoing_or_waiting->y2); ++y) {
+		int offset = y * pixel_pitch + x_start;
+		u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev + offset * 3;
+		u8 *phases_line = phase_buffer + y * phase_pitch +
+				  (x_start >> (direct_mode ? 2 : 0));
+		const u8 *prelim_target_line =
+			prelim_target + offset;
+		u8 *hints_line = hints + offset;
+		uint32x2_t q32s_xy = vdup_n_u32(y << 16 | x_start);
+
+		for (unsigned int x = x_start; x < clip_ongoing_or_waiting->x2;
+		     x += 16, packed_inner_outer_nextprev_line += 48,
+				  phases_line += direct_mode ? 4 : 16,
+					  prelim_target_line += 16, hints_line += 16) {
+			uint8x16x3_t q8_inner_outer_nextprev =
+				vld3q_u8(packed_inner_outer_nextprev_line);
+			uint8x16_t q8_inner = q8_inner_outer_nextprev.val[0];
+			uint8x16_t q8_outer = q8_inner_outer_nextprev.val[1];
+			uint8x16_t q8_next_prev =
+				q8_inner_outer_nextprev.val[2];
+			uint8x16_t q8_prelim_target =
+				vld1q_u8(prelim_target_line);
+			uint8x16_t q8_hints = vld1q_u8(hints_line);
+			__builtin_prefetch(packed_inner_outer_nextprev_line + 3 * prefetch_pitch, 0, 0);
+			__builtin_prefetch(prelim_target_line + prefetch_pitch, 0, 0);
+			__builtin_prefetch(hints_line + prefetch_pitch, 0, 0);
+
+			// Extract packed Y4 values and hints
+			uint8x16_t q8_inner_num = vandq_u8(q8_inner, q8_0x1f);
+			uint8x16_t q8_inner_is_last =
+				vtstq_u8(q8_inner, q8_0x20);
+			uint8x16_t q8_next = vshrq_n_u8(q8_next_prev, 4);
+			uint8x16_t q8_prev = vandq_u8(q8_next_prev, q8_0x0f);
+			uint8x16_t q8_prelim = vshrq_n_u8(q8_prelim_target, 4);
+			uint8x16_t q8_target =
+				vandq_u8(q8_prelim_target, q8_0x0f);
+			q8_hints = vbslq_u8(q8_force_hint_mask, q8_force_hint,
+					    q8_hints);
+			uint8x16_t q8_wf_target =
+				vandq_u8(vshrq_n_u8(q8_hints, 4), q8_0x03);
+			q8_wf_target = vbslq_u8(q8_force_wf_gt0, q8_force_wf,
+						q8_wf_target);
+			uint8x16_t q8_hint_redraw = vtstq_u8(q8_hints, q8_0x80);
+			uint8x16_t q8_hint_noredraw = vmvnq_u8(q8_hint_redraw);
+
+			uint8x16_t q8_next_eq_target =
+				vceqq_u8(q8_next, q8_target);
+
+			// Start transforming
+			uint8x16_t q8_inner_num_is_1 =
+				vceqq_u8(q8_inner_num, q8_0x01);
+			// Saturating subtraction
+			uint8x16_t q8_inner_num_new =
+				vqsubq_u8(q8_inner_num, q8_0x01);
+			// Restore phase and is_last information
+			uint8x16_t q8_inner_new =
+				vbslq_u8(q8_0x1f, q8_inner_num_new, q8_inner);
+
+			// Transition these to WAITING or IDLE, allow rescheduling
+			uint8x16_t q8_finish =
+				vandq_u8(q8_inner_num_is_1, q8_inner_is_last);
+			// These can be rescheduled
+			uint8x16_t q8_waiting =
+				vcgeq_u8(q8_outer, q8_offset_waiting);
+			// Transition these to REDRAW, allow rescheduling
+			uint8x16_t q8_finish_waiting =
+				vandq_u8(q8_finish, q8_waiting);
+			// These can be scheduled, send to WAITING (hint changed in the meantime)
+			uint8x16_t q8_idle = vceqzq_u8(q8_inner);
+			uint8x16_t q8_waiting_idle =
+				vorrq_u8(q8_waiting, q8_idle);
+			uint8x16_t q8_waiting_idle_finish =
+				vorrq_u8(q8_waiting_idle, q8_finish);
+
+			// For scheduling: choose between target/prelim
+			uint8x16_t q8_use_target = vorrq_u8(
+				vorrq_u8(q8_finish_waiting, q8_hint_noredraw),
+				vorrq_u8(q8_next_eq_target, q8_force_wf_gt0));
+			uint8x16_t q8_target_or_prelim_new =
+				vbslq_u8(q8_use_target, q8_target, q8_prelim);
+			// Prelim uses waveform DU=0
+			uint8x16_t q8_wf =
+				vandq_u8(q8_wf_target, q8_use_target);
+			uint8x16_t q8_wf_is_du = vceqzq_u8(q8_wf);
+			uint8x16_t q8_target_or_prelim_is_binary = vqtbl1q_u8(
+				q8_is_y4_binary_table, q8_target_or_prelim_new);
+			uint8x16_t q8_next_is_binary =
+				vqtbl1q_u8(q8_is_y4_binary_table, q8_next);
+			uint8x16_t q8_prev_is_binary =
+				vqtbl1q_u8(q8_is_y4_binary_table, q8_prev);
+			uint8x16_t q8_next_and_prev_are_binary =
+				vandq_u8(q8_next_is_binary, q8_prev_is_binary);
+			uint8x16_t q8_outer_is_du = vceqq_u8(q8_outer, q8_0x01);
+			uint8x16_t q8_src_cancellable = vandq_u8(
+				q8_next_and_prev_are_binary, q8_outer_is_du);
+			uint8x16_t q8_dst_cancellable = vandq_u8(
+				q8_wf_is_du, q8_target_or_prelim_is_binary);
+
+			uint8x16_t q8_can_cancel = vandq_u8(q8_src_cancellable,
+							    q8_dst_cancellable);
+			uint8x16_t q8_can_start_or_cancel =
+				vorrq_u8(q8_waiting_idle_finish, q8_can_cancel);
+
+			// 1. Schedule: start target or prelim
+			uint8x16_t q8_start_scheduled = vandq_u8(
+				q8_allow_schedule,
+				vandq_u8(vmvnq_u8(vceqq_u8(
+						 q8_target_or_prelim_new,
+						 q8_next)),
+					 q8_can_start_or_cancel));
+			q8_start_scheduled =
+				vorrq_u8(q8_start_scheduled, q8_force_wf_gt0);
+			uint8x16_t q8_next_new =
+				vbslq_u8(q8_start_scheduled,
+					 q8_target_or_prelim_new, q8_next);
+			uint8x16_t q8_prev_new =
+				vbslq_u8(q8_start_scheduled, q8_next, q8_prev);
+
+
+			// 2. Redraw: start target if still desired an we finished waiting
+			uint8x16_t q8_start_redraw = vandq_u8(
+				q8_allow_schedule,
+				vandq_u8(
+					vmvnq_u8(vceqq_u8(q8_next, q8_target)),
+					vandq_u8(q8_finish_waiting,
+						 vmvnq_u8(q8_start_scheduled))));
+			q8_next_new = vbslq_u8(q8_start_redraw, q8_target,
+					       q8_next_new);
+			q8_prev_new =
+				vbslq_u8(q8_start_redraw, q8_next, q8_prev_new);
+			uint8x16_t q8_next_prev_new = vorrq_u8(
+							       vshlq_n_u8(q8_next_new, 4), q8_prev_new);
+
+			// 3. Move remaining finished ones to IDLE or WAITING
+			uint8x16_t q8_start_scheduled_or_redraw =
+				vorrq_u8(q8_start_scheduled, q8_start_redraw);
+			uint8x16_t q8_no_start_scheduled_or_redraw =
+				vmvnq_u8(q8_start_scheduled_or_redraw);
+			uint8x16_t q8_finish_no_start_scheduled_or_redraw =
+				vandq_u8(q8_finish,
+					 q8_no_start_scheduled_or_redraw);
+			uint8x16_t q8_outer_eq_0 = vceqzq_u8(q8_outer);
+			uint8x16_t q8_outer_gt_0 = vmvnq_u8(q8_outer_eq_0);
+			// Start waiting if:
+			// We finish, but we don't start schedule/redraw AND next != target, AND allow_schedule
+			// Or: We are not running AND we didn't schedule (next=prelim) AND next != target AND allow_schedule
+
+			uint8x16_t q8_start_waiting = vandq_u8(
+				vorrq_u8(
+					q8_finish_no_start_scheduled_or_redraw,
+					vandq_u8(
+						q8_outer_eq_0,
+						q8_no_start_scheduled_or_redraw)),
+				vandq_u8(q8_allow_schedule,
+					 vmvnq_u8(q8_next_eq_target)));
+			uint8x16_t q8_start_idle =
+				vandq_u8(q8_finish_no_start_scheduled_or_redraw,
+					 vandq_u8(vmvnq_u8(q8_start_waiting),
+						  q8_outer_gt_0));
+			uint8x16_t q8_start = vorrq_u8(
+						       q8_start_scheduled_or_redraw, q8_start_waiting);
+
+			// 4. Get outer_new
+			uint8x16_t q8_outer_new =
+				vandq_u8(vqaddq_u8(q8_outer, q8_0x01),
+					 vmvnq_u8(q8_inner_is_last));
+			q8_outer_new = vbslq_u8(q8_inner_num_is_1, q8_outer_new,
+						q8_outer);
+			q8_outer_new = vbslq_u8(
+				q8_start, vqtbl1q_u8(q8_offsets_table, q8_wf),
+				q8_outer_new);
+			// Or: replace q8_wf with WAITING before the previous statement
+			q8_outer_new = vbslq_u8(q8_start_waiting,
+						q8_offset_waiting,
+						q8_outer_new);
+
+			// 5. Get inner_new
+			uint16x8_t q16_idx_low =
+				vmovl_u8(vget_low_u8(q8_outer_new));
+			q16_idx_low = vaddq_u16(
+				q16_idx_low,
+				vqshlq_n_u16(
+					vmovl_u8(vget_low_u8(q8_prev_new)),
+					4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT));
+			q16_idx_low = vaddq_u16(
+				q16_idx_low,
+				vshll_n_u8(vget_low_u8(q8_next_new),
+					   ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT));
+			vst1q_u16(u16_idxs, q16_idx_low);
+			uint16x8_t q16_idx_high =
+				vmovl_u8(vget_high_u8(q8_outer_new));
+			q16_idx_high = vaddq_u16(
+				q16_idx_high,
+				vqshlq_n_u16(
+					vmovl_u8(vget_high_u8(q8_prev_new)),
+					4 + ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT));
+			q16_idx_high = vaddq_u16(
+				q16_idx_high,
+				vshll_n_u8(vget_high_u8(q8_next_new),
+					   ROCKCHIP_EBC_CUSTOM_WF_SEQ_SHIFT));
+			vst1q_u16(u16_idxs + 8, q16_idx_high);
+			u8_lookup[0] = lut[u16_idxs[0]];
+			u8_lookup[1] = lut[u16_idxs[1]];
+			u8_lookup[2] = lut[u16_idxs[2]];
+			u8_lookup[3] = lut[u16_idxs[3]];
+			u8_lookup[4] = lut[u16_idxs[4]];
+			u8_lookup[5] = lut[u16_idxs[5]];
+			u8_lookup[6] = lut[u16_idxs[6]];
+			u8_lookup[7] = lut[u16_idxs[7]];
+			u8_lookup[8] = lut[u16_idxs[8]];
+			u8_lookup[9] = lut[u16_idxs[9]];
+			u8_lookup[10] = lut[u16_idxs[10]];
+			u8_lookup[11] = lut[u16_idxs[11]];
+			u8_lookup[12] = lut[u16_idxs[12]];
+			u8_lookup[13] = lut[u16_idxs[13]];
+			u8_lookup[14] = lut[u16_idxs[14]];
+			u8_lookup[15] = lut[u16_idxs[15]];
+			uint8x16_t q8_inner_new_from_lut = vld1q_u8(u8_lookup);
+			q8_inner_new =
+				vbslq_u8(vorrq_u8(q8_inner_num_is_1, q8_start),
+					 q8_inner_new_from_lut, q8_inner_new);
+			q8_inner_new =
+				vbslq_u8(q8_start_idle, q8_0x21, q8_inner_new);
+
+			// 6. Early cancellation
+			// TODO: DU4 may be cancellable as well for binary src and dst
+			uint8x16_t q8_cancel = vandq_u8(
+				q8_start,
+				vandq_u8(q8_can_cancel,
+					 vmvnq_u8(q8_waiting_idle_finish)));
+			uint8x16_t q8_to_subtract =
+				vandq_u8(q8_cancel, q8_inner_num);
+			q8_inner_num_new =
+				vqsubq_u8(vandq_u8(q8_inner_new, q8_0x1f),
+					  q8_to_subtract);
+			q8_inner_num_new = vqaddq_u8(
+				q8_inner_num_new,
+				vandq_u8(q8_cancel,
+					 q8_early_cancellation_addition));
+			q8_inner_new = vbslq_u8(q8_0x1f, q8_inner_num_new,
+						q8_inner_new);
+			uint8x16x3_t q8_ionp_new = {{q8_inner_new, q8_outer_new, q8_next_prev_new}};
+			vst3q_u8(packed_inner_outer_nextprev_line, q8_ionp_new);
+
+			uint8x16_t q8_phases = vshrq_n_u8(q8_inner_new, 6);
+			uint16x8_t q16_phases;
+			uint8x8_t q8s_phases1, q8s_phases;
+
+#ifdef CONFIG_DRM_ROCKCHIP_EBC_3WIN_MODE
+			if (!direct_mode) {
+				vst1q_u8(phases_line, q8_phases);
+			} else
+#endif
+			{
+				q16_phases = vreinterpretq_u16_u8(q8_phases);
+				q16_phases = vorrq_u16(
+					q16_phases, vshrq_n_u16(q16_phases, 6));
+				q8s_phases1 = vmovn_u16(q16_phases);
+				q16_phases = vreinterpretq_u16_u8(
+					vcombine_u8(q8s_phases1, q8s_phases1));
+				q8s_phases = vmovn_u16(
+					vorrq_u16(q16_phases,
+						 vshrq_n_u16(q16_phases, 4)));
+		vst1_lane_u32((u32 *)phases_line,
+					      vreinterpret_u32_u8(q8s_phases),
+					      0);
+				__builtin_prefetch(phases_line + phases_prefetch_pitch, 1, 0);
+			}
+
+			#ifdef ROCKCHIP_EBC_DEBUG_NEON
+			if (x == 928 && y == 702) {
+				NEON_PR_INFO(q8_inner);
+				NEON_PR_INFO(q8_outer);
+				NEON_PR_INFO(q8_next_prev);
+				NEON_PR_INFO(q8_prelim_target);
+				NEON_PR_INFO(q8_hints);
+				NEON_PR_INFO(q8_inner_num);
+				NEON_PR_INFO(q8_inner_is_last);
+				NEON_PR_INFO(q8_next);
+				NEON_PR_INFO(q8_prev);
+				NEON_PR_INFO(q8_prelim);
+				NEON_PR_INFO(q8_target);
+				NEON_PR_INFO(q8_wf_target);
+				NEON_PR_INFO(q8_hint_redraw);
+				NEON_PR_INFO(q8_hint_noredraw);
+				NEON_PR_INFO(q8_next_eq_target);
+				NEON_PR_INFO(q8_use_target);
+				NEON_PR_INFO(q8_target_or_prelim_new);
+				NEON_PR_INFO(q8_wf);
+				NEON_PR_INFO(q8_wf_is_du);
+				NEON_PR_INFO(q8_target_or_prelim_is_binary);
+				NEON_PR_INFO(q8_next_is_binary);
+				NEON_PR_INFO(q8_prev_is_binary);
+				NEON_PR_INFO(q8_next_and_prev_are_binary);
+				NEON_PR_INFO(q8_src_cancellable);
+				NEON_PR_INFO(q8_dst_cancellable);
+				NEON_PR_INFO(q8_inner_num_is_1);
+				NEON_PR_INFO(q8_inner_num_new);
+				NEON_PR_INFO(q8_inner_new);
+				NEON_PR_INFO(q8_finish);
+				NEON_PR_INFO(q8_waiting);
+				NEON_PR_INFO(q8_finish_waiting);
+				NEON_PR_INFO(q8_idle);
+				NEON_PR_INFO(q8_waiting_idle);
+				NEON_PR_INFO(q8_waiting_idle_finish);
+				NEON_PR_INFO(q8_can_cancel);
+				NEON_PR_INFO(q8_can_start_or_cancel);
+				NEON_PR_INFO(q8_start_scheduled);
+				NEON_PR_INFO(q8_next_new);
+				NEON_PR_INFO(q8_prev_new);
+				NEON_PR_INFO(q8_start_redraw);
+				NEON_PR_INFO(q8_start_scheduled_or_redraw);
+				NEON_PR_INFO(q8_no_start_scheduled_or_redraw);
+				NEON_PR_INFO(q8_finish_no_start_scheduled_or_redraw);
+				NEON_PR_INFO(q8_start_idle);
+				NEON_PR_INFO(q8_start_waiting);
+				NEON_PR_INFO(q8_start);
+				NEON_PR_INFO(q8_outer_new);
+				NEON_PR_INFO(q8_inner_new);
+				NEON_PR_INFO(q8_inner_new_from_lut);
+				NEON_PR_INFO(q8_cancel);
+				NEON_PR_INFO(q8_to_subtract);
+				NEON_PR_INFO(q8_inner_num_new);
+				NEON_PR_INFO(q8_inner_new);
+				NEON_PR_INFO(q8_phases);
+				NEON_PR_INFO(q16_phases);
+				NEON_PR_INFOS(q8s_phases1);
+				NEON_PR_INFOS(q8s_phases);
+			}
+#endif // ROCKCHIP_EBC_DEBUG_NEON
+			uint8x16_t q8_ongoing = vandq_u8(
+				q8_inner_new,
+				vcltq_u8(q8_outer_new, q8_offset_waiting));
+			uint64x1_t q64s_any_ongoing_or_waiting =
+				vreinterpret_u64_u8(
+					vorr_u8(vget_high_u8(q8_inner_new),
+						vget_low_u8(q8_inner_new)));
+			uint64x1_t q64s_any_ongoing = vreinterpret_u64_u8(
+				vorr_u8(vget_high_u8(q8_ongoing),
+					vget_low_u8(q8_ongoing)));
+			q64s_any_ongoing_or_waiting = vcgt_u64(
+				q64s_any_ongoing_or_waiting, q64s_0x00);
+			q64s_any_ongoing =
+				vcgt_u64(q64s_any_ongoing, q64s_0x00);
+			uint32x2_t q32s_mask = vzip1_u32(
+				vreinterpret_u32_u64(q64s_any_ongoing),
+				vreinterpret_u32_u64(
+					q64s_any_ongoing_or_waiting));
+			q16s_maxs = vmax_u16(q16s_maxs,
+					     vreinterpret_u16_u32(vand_u32(
+						     q32s_xy, q32s_mask)));
+			q16s_mins = vmin_u16(
+				q16s_mins,
+				vreinterpret_u16_u32(vorr_u32(
+							      q32s_xy, vmvn_u32(q32s_mask))));
+
+			q32s_xy = vqadd_u32(q32s_xy, q32s_0x10);
+#ifdef ROCKCHIP_EBC_DEBUG_NEON
+			if (x == 928 && y == 702) {
+				NEON_PR_INFO(q8_inner_new);
+				NEON_PR_INFO(q8_outer_new);
+				NEON_PR_INFO(q8_ongoing);
+				NEON_PR_INFOS(q64s_any_ongoing_or_waiting);
+				NEON_PR_INFOS(q64s_any_ongoing);
+				NEON_PR_INFOS(q32s_mask);
+				NEON_PR_INFOS(q32s_maxs);
+				NEON_PR_INFOS(q32s_mins);
+				NEON_PR_INFOS(q32s_xy);
+			}
+#endif
+		}
+	}
+
+	aligned_u64 u64_mins, u64_maxs;
+	u16 *mins = (u16 *)&u64_mins;
+	u16 *maxs = (u16 *)&u64_maxs;
+	vst1_lane_u64(&u64_mins, vreinterpret_u64_u16(q16s_mins), 0);
+	vst1_lane_u64(&u64_maxs, vreinterpret_u64_u16(q16s_maxs), 0);
+	clip_ongoing->x1 = mins[0];
+	clip_ongoing->y1 = mins[1];
+	clip_ongoing->x2 = maxs[0] + 16;
+	clip_ongoing->y2 = maxs[1] + 1;
+	clip_ongoing_or_waiting->x1 = mins[2];
+	clip_ongoing_or_waiting->y1 = mins[3];
+	clip_ongoing_or_waiting->x2 = maxs[2] + 16;
+	clip_ongoing_or_waiting->y2 = maxs[3] + 1;
+}
+EXPORT_SYMBOL(rockchip_ebc_schedule_advance_neon);
+
+void rockchip_ebc_blit_y4_high_low_neon(u8 *dst, u8 *y4_high_src,
+					u8 *y4_low_src, unsigned int size)
+{
+	for (unsigned int i = 0; i < size;
+	     i += 16, dst += 16, y4_high_src += 16, y4_low_src += 16) {
+		vst1q_u8(dst,
+			 vorrq_u8(vld1q_u8(y4_high_src), vld1q_u8(y4_low_src)));
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_blit_y4_high_low_neon);
+
+/**
+ * rockchip_ebc_blit_y421_y4_neon - blit from y421 to y4
+ */
+void rockchip_ebc_blit_y421_y4_neon(const struct rockchip_ebc *ebc, u8 *y4_dst,
+				    const u8 *y421_src, struct drm_rect *clip)
+{
+	unsigned int y421_pitch = ebc->pixel_pitch;
+	unsigned int gray4_pitch = ebc->gray4_pitch;
+
+	// 16 byte block size for NEON
+	unsigned int x_start =
+		max(0, min(clip->x1 & ~15, (int)ebc->pixel_pitch - 16));
+	unsigned int x_end =
+		min((int)ebc->pixel_pitch, ((clip->x2 + 15) & ~15));
+	unsigned int x, y;
+
+	for (y = clip->y1; y < clip->y2; y++) {
+		const u8 *src_line = y421_src + y * y421_pitch + x_start;
+		u8 *dst_line = y4_dst + y * gray4_pitch + x_start / 2;
+		for (x = x_start; x < x_end;
+		     x += 16, src_line += 16, dst_line += 8) {
+			// ?A ?B ?C ?D
+			uint8x16_t q8_y421 = vld1q_u8(src_line);
+			// AA BB CC DD duplicate lower bits by left shifting and insert immediately
+			uint8x16_t q8_y4 = vsliq_n_u8(q8_y421, q8_y421, 4);
+			// AABB CCDD
+			uint16x8_t q16_y4 = vreinterpretq_u16_u8(q8_y4);
+			// Insert AB CD
+			vst1_u8(dst_line, vshrn_n_u16(q16_y4, 4));
+		}
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_blit_y421_y4_neon);
+
+/**
+ * rockchip_ebc_blit_fb_rgb565_y4_hints_neon - blit from RGB565 FB
+ *
+ * Flips output horizontally.
+ * HHHH 4444  (hints in higher bits, Y4 in lower bits)
+ */
+void rockchip_ebc_blit_fb_rgb565_y4_hints_neon(const struct rockchip_ebc *ebc,
+					       struct drm_rect *dst_clip,
+					       u8 *prelim_target_atomic_update,
+					       u8 *hints_atomic_update,
+					       const void *vaddr,
+					       const struct drm_framebuffer *fb,
+					       const struct drm_rect *src_clip)
+{
+	unsigned int rgb_pitch = fb->pitches[0];
+	unsigned int pixel_pitch = ebc->pixel_pitch;
+	unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6;
+
+	// 16 byte block size for NEON
+	unsigned int src_start_x = src_clip->x1;
+	unsigned int src_end_x = src_clip->x2;
+	unsigned int dst_start_x = dst_clip->x2 - 16;
+	unsigned int x, y;
+
+	// Force horizontal reflection for simplicity
+	u8 *dst_prelim_target = prelim_target_atomic_update +
+				dst_clip->y1 * pixel_pitch + dst_start_x;
+	const u8 *src = vaddr + src_clip->y1 * rgb_pitch +
+			src_start_x * fb->format->cpp[0];
+	const u8 *ioctl_hints =
+		ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x;
+	u8 *hints =
+		hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x;
+
+	// Thresholds and LUT for 4-level tresholding and 2-level threshold
+	uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4);
+	uint8x16x2_t q8_thresholds_y2_dither_table =
+		vld2q_u8((u8 *)ebc->lut_y2_y4_dithered);
+	uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1);
+
+	const u8 *dithering_texture = ebc->dithering_texture;
+	u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint;
+
+	// (256 * (np.array([.299, .587, .114])) * [255/31, 255/63, 255/31] / [8, 4, 8]).round()
+	// Factors were modified to reduce number of shifting or masking operations required
+	uint8x8_t q8_yuv_r = vdup_n_u8(79);
+	uint8x8_t q8_yuv_g = vdup_n_u8(152);
+	uint8x8_t q8_yuv_b = vdup_n_u8(30);
+
+	uint8x16_t q8_0x02 = vdupq_n_u8(0x02);
+	uint8x16_t q8_0x03 = vdupq_n_u8(0x03);
+	uint8x16_t q8_0x08 = vdupq_n_u8(0x08);
+	uint8x16_t q8_0x0f = vdupq_n_u8(0x0f);
+	uint8x16_t q8_0x40 = vdupq_n_u8(0x40);
+	uint8x8_t q8s_0xfc = vdup_n_u8(0xfc);
+	uint8x8_t q8s_0xf8 = vdup_n_u8(0xf8);
+
+	for (y = src_clip->y1; y < src_clip->y2; y++) {
+		const u16 *fb_line = (const u16 *) src;
+		u8 *prelim_target_line = dst_prelim_target;
+		const u8 *ioctl_hints_line = ioctl_hints;
+		u8 *hints_line = hints;
+
+		uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp;
+		if (dithering_texture_size_hint == 32) {
+			q8_dither_pattern0 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			q8_dither_pattern1 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			if (src_start_x & 16) {
+				q8_tmp = q8_dither_pattern0;
+				q8_dither_pattern0 = q8_dither_pattern1;
+				q8_dither_pattern1 = q8_tmp;
+			}
+		} else {
+			q8_dither_pattern0 = vld1q_u8(
+				dithering_texture +
+				16 * (y & (dithering_texture_size_hint - 1)));
+			q8_dither_pattern1 = q8_dither_pattern0;
+		}
+
+		for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16,
+		    prelim_target_line -= 16, ioctl_hints_line += 16,
+		    hints_line -= 16) {
+			uint8x8_t q8s_gray1, q8s_gray2;
+			uint8x16_t q8_gray;
+			uint16x8_t q16_gray;
+
+			// RGB -> Y8 using rounded YUV
+			// Load 8 RGB values
+			uint16x8_t q16_rgb = vld1q_u16(fb_line);
+
+			q16_gray = vmull_u8(q8_yuv_b,
+					    vmovn_u16(vshlq_n_u16(q16_rgb, 3)));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_g,
+					    vand_u8(vshrn_n_u16(q16_rgb, 3),
+						    q8s_0xfc));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_r,
+					    vand_u8(vshrn_n_u16(q16_rgb, 8),
+						    q8s_0xf8));
+			// Aa Bb Cc Dd lower-case bits still need to be discarded
+			q8s_gray1 = vshrn_n_u16(q16_gray, 8);
+
+			// Same for the next 8 RGB pixels
+			q16_rgb = vld1q_u16(fb_line + 8);
+			/* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */
+
+			q16_gray = vmull_u8(q8_yuv_b,
+					    vmovn_u16(vshlq_n_u16(q16_rgb, 3)));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_g,
+					    vand_u8(vshrn_n_u16(q16_rgb, 3),
+						    q8s_0xfc));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_r,
+					    vand_u8(vshrn_n_u16(q16_rgb, 8),
+						    q8s_0xf8));
+			q8s_gray2 = vshrn_n_u16(q16_gray, 8);
+
+			// Combine into single vector, discard lower bits
+			// 0A 0B 0C 0D
+			q8_gray = vshrq_n_u8(vrev64q_u8(vcombine_u8(q8s_gray2, q8s_gray1)),
+					     4);
+
+			uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line);
+			uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0])));
+			/* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */
+			uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40);
+			uint8x16_t q8_hint_waveform =
+				vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03);
+			uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint);
+
+			uint8x16_t q8_gray_dithered = vminq_u8(
+				vqsubq_u8(vqaddq_u8(q8_gray,
+						    q8_dither_pattern0),
+					  q8_0x08),
+				q8_0x0f);
+			uint8x16_t q8_gray_y2_dt =
+				vqtbl2q_u8(q8_thresholds_y2_dither_table,
+					   q8_gray_dithered);
+			uint8x16_t q8_gray_y2_th =
+				vqtbl1q_u8(q8_thresholds_y2_table, q8_gray);
+			uint8x16_t q8_gray_y1_dt = vandq_u8(
+				vcgeq_u8(q8_gray_dithered, q8_threshold_y1),
+				q8_0x0f);
+			uint8x16_t q8_gray_y1_th = vandq_u8(
+				vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f);
+			uint8x16_t q8_gray_y1 = vbslq_u8(
+				q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th);
+			uint8x16_t q8_gray_y2 = vbslq_u8(
+				q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th);
+			uint8x16_t q8_gray_y12 =
+				vbslq_u8(vceqzq_u8(q8_hint_waveform),
+					 q8_gray_y1, q8_gray_y2);
+
+			uint8x16_t q8_target =
+				vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02),
+					 q8_gray, q8_gray_y12);
+			// TODO: make dithering/thresholding configurable via a hint
+			uint8x16_t q8_prelim_target = vorrq_u8(
+				vshlq_n_u8(q8_gray_y1_dt, 4), q8_target);
+
+			__builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache
+			__builtin_prefetch(hints_line + prefetch_pitch, 1, 0);
+			vst1q_u8(hints_line, q8_hint_gray);
+			vst1q_u8(prelim_target_line, q8_prelim_target);
+
+			// Swap to support 32x32 dithering textures
+			q8_tmp = q8_dither_pattern0;
+			q8_dither_pattern0 = q8_dither_pattern1;
+			q8_dither_pattern1 = q8_tmp;
+		}
+
+		src += rgb_pitch;
+		dst_prelim_target += pixel_pitch;
+		ioctl_hints += pixel_pitch;
+		hints += pixel_pitch;
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_blit_fb_rgb565_y4_hints_neon);
+
+/**
+ * rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon - blit from XRGB8888 FB
+ *
+ * Flips output horizontally.
+ * HHHH 4444  (hints in higher bits, Y4 in lower bits)
+ */
+void rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon(
+	const struct rockchip_ebc *ebc, struct drm_rect *dst_clip,
+	u8 *prelim_target_atomic_update, u8 *hints_atomic_update,
+	const void *vaddr, const struct drm_framebuffer *fb,
+	const struct drm_rect *src_clip)
+{
+	unsigned int rgba_pitch = fb->pitches[0];
+	unsigned int pixel_pitch = ebc->pixel_pitch;
+	unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6;
+
+	// 16 byte block size for NEON
+	unsigned int src_start_x = src_clip->x1;
+	unsigned int src_end_x = src_clip->x2;
+	unsigned int dst_start_x = dst_clip->x2 - 16;
+	unsigned int x, y;
+
+	// Force horizontal reflection for simplicity
+	u8 *dst_prelim_target = prelim_target_atomic_update +
+				dst_clip->y1 * pixel_pitch + dst_start_x;
+	const u8 *src = vaddr + src_clip->y1 * rgba_pitch +
+			src_start_x * fb->format->cpp[0];
+	const u8 *ioctl_hints =
+		ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x;
+	u8 *hints =
+		hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x;
+
+	uint8x8_t q8_yuv_r = vdup_n_u8(76);
+	uint8x8_t q8_yuv_g = vdup_n_u8(150);
+	uint8x8_t q8_yuv_b = vdup_n_u8(29);
+
+	// Thresholds and LUT for 4-level tresholding and 2-level threshold
+	uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4);
+	uint8x16x2_t q8_thresholds_y2_dither_table =
+		vld2q_u8((u8 *)ebc->lut_y2_y4_dithered);
+	uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1);
+
+	const u8 *dithering_texture = ebc->dithering_texture;
+	u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint;
+
+	uint8x16_t q8_0x02 = vdupq_n_u8(0x02);
+	uint8x16_t q8_0x03 = vdupq_n_u8(0x03);
+	uint8x16_t q8_0x08 = vdupq_n_u8(0x08);
+	uint8x16_t q8_0x0f = vdupq_n_u8(0x0f);
+	uint8x16_t q8_0x40 = vdupq_n_u8(0x40);
+
+	for (y = src_clip->y1; y < src_clip->y2; y++) {
+		const u32 *fb_line = (const u32 *) src;
+		u8 *prelim_target_line = dst_prelim_target;
+		const u8 *ioctl_hints_line = ioctl_hints;
+		u8 *hints_line = hints;
+
+		uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp;
+		if (dithering_texture_size_hint == 32) {
+			q8_dither_pattern0 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			q8_dither_pattern1 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			if (src_start_x & 16) {
+				q8_tmp = q8_dither_pattern0;
+				q8_dither_pattern0 = q8_dither_pattern1;
+				q8_dither_pattern1 = q8_tmp;
+			}
+		} else {
+			q8_dither_pattern0 = vld1q_u8(
+				dithering_texture +
+				16 * (y & (dithering_texture_size_hint - 1)));
+			q8_dither_pattern1 = q8_dither_pattern0;
+		}
+
+		for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16,
+		    prelim_target_line -= 16, ioctl_hints_line += 16,
+		    hints_line -= 16) {
+			uint8x8_t q8s_gray1, q8s_gray2;
+			uint8x16_t q8_gray;
+			uint16x8_t q16_gray;
+
+			// RGB -> Y8 using rounded YUV
+			// Load 16 RGB values
+			uint8x16x4_t q8x4_rgba = vld4q_u8((const u8 *) fb_line);
+			q16_gray = vmull_u8(q8_yuv_r, vget_high_u8(q8x4_rgba.val[0]));
+			q16_gray =
+				vmlal_u8(q16_gray, q8_yuv_g, vget_high_u8(q8x4_rgba.val[1]));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_b, vget_high_u8(q8x4_rgba.val[2]));
+			// Aa Bb Cc Dd lower-case bits still need to be discarded
+			q8s_gray1 = vshrn_n_u16(q16_gray, 8);
+
+			// Same for the next 8 RGB pixels
+			__builtin_prefetch(fb_line + pixel_pitch, 0, 0);
+			q16_gray = vmull_u8(q8_yuv_r, vget_low_u8(q8x4_rgba.val[0]));
+			q16_gray =
+				vmlal_u8(q16_gray, q8_yuv_g, vget_low_u8(q8x4_rgba.val[1]));
+			q16_gray = vmlal_u8(q16_gray, q8_yuv_b, vget_low_u8(q8x4_rgba.val[2]));
+			/* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */
+			q8s_gray2 = vshrn_n_u16(q16_gray, 8);
+
+			// Combine into single vector, discard lower bits
+			// 0A 0B 0C 0D
+			q8_gray = vshrq_n_u8(vrev64q_u8(vcombine_u8(q8s_gray1, q8s_gray2)),
+					     4);
+
+			uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line);
+			uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0])));
+			/* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */
+			uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40);
+			uint8x16_t q8_hint_waveform =
+				vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03);
+			uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint);
+
+			uint8x16_t q8_gray_dithered = vminq_u8(
+				vqsubq_u8(vqaddq_u8(q8_gray,
+						    q8_dither_pattern0),
+					  q8_0x08),
+				q8_0x0f);
+			uint8x16_t q8_gray_y2_dt =
+				vqtbl2q_u8(q8_thresholds_y2_dither_table,
+					   q8_gray_dithered);
+			uint8x16_t q8_gray_y2_th =
+				vqtbl1q_u8(q8_thresholds_y2_table, q8_gray);
+			uint8x16_t q8_gray_y1_dt = vandq_u8(
+				vcgeq_u8(q8_gray_dithered, q8_threshold_y1),
+				q8_0x0f);
+			uint8x16_t q8_gray_y1_th = vandq_u8(
+				vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f);
+			uint8x16_t q8_gray_y1 = vbslq_u8(
+				q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th);
+			uint8x16_t q8_gray_y2 = vbslq_u8(
+				q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th);
+			uint8x16_t q8_gray_y12 =
+				vbslq_u8(vceqzq_u8(q8_hint_waveform),
+					 q8_gray_y1, q8_gray_y2);
+
+			uint8x16_t q8_target =
+				vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02),
+					 q8_gray, q8_gray_y12);
+			// TODO: make dithering/thresholding configurable via a hint
+			uint8x16_t q8_prelim_target = vorrq_u8(
+				vshlq_n_u8(q8_gray_y1_dt, 4), q8_target);
+
+			__builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache
+			__builtin_prefetch(hints_line + prefetch_pitch, 1, 0);
+			vst1q_u8(hints_line, q8_hint_gray);
+			vst1q_u8(prelim_target_line, q8_prelim_target);
+
+			// Swap to support 32x32 dithering textures
+			q8_tmp = q8_dither_pattern0;
+			q8_dither_pattern0 = q8_dither_pattern1;
+			q8_dither_pattern1 = q8_tmp;
+		}
+
+		src += rgba_pitch;
+		dst_prelim_target += pixel_pitch;
+		ioctl_hints += pixel_pitch;
+		hints += pixel_pitch;
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_blit_fb_xrgb8888_y4_hints_neon);
+
+/**
+ * rockchip_ebc_blit_fb_r8_y4_hints_neon - blit from R8 FB
+ *
+ * Flips output horizontally.
+ * HHHH 4444  (hints in higher bits, Y4 in lower bits)
+ */
+void rockchip_ebc_blit_fb_r8_y4_hints_neon(const struct rockchip_ebc *ebc,
+					   struct drm_rect *dst_clip,
+					   u8 *prelim_target_atomic_update,
+					   u8 *hints_atomic_update,
+					   const void *vaddr,
+					   const struct drm_framebuffer *fb,
+					   const struct drm_rect *src_clip)
+{
+	unsigned int r8_pitch = fb->pitches[0];
+	unsigned int pixel_pitch = ebc->pixel_pitch;
+	unsigned int prefetch_pitch = (pixel_pitch << 6) >> 6;
+
+	// 16 byte block size for NEON
+	unsigned int src_start_x = src_clip->x1;
+	unsigned int src_end_x = src_clip->x2;
+	unsigned int dst_start_x = dst_clip->x2 - 16;
+	unsigned int x, y;
+
+	// Force horizontal reflection for simplicity
+	u8 *dst_prelim_target = prelim_target_atomic_update +
+				dst_clip->y1 * pixel_pitch + dst_start_x;
+	const u8 *src = vaddr + src_clip->y1 * r8_pitch +
+			src_start_x * fb->format->cpp[0];
+	const u8 *ioctl_hints =
+		ebc->hints_ioctl + src_clip->y1 * pixel_pitch + src_start_x;
+	u8 *hints =
+		hints_atomic_update + dst_clip->y1 * pixel_pitch + dst_start_x;
+
+	// Thresholds and LUT for 4-level tresholding and 2-level threshold
+	uint8x16_t q8_thresholds_y2_table = vld1q_u8((u8 *)ebc->lut_y2_y4);
+	uint8x16x2_t q8_thresholds_y2_dither_table =
+		vld2q_u8((u8 *)ebc->lut_y2_y4_dithered);
+	uint8x16_t q8_threshold_y1 = vdupq_n_u8(ebc->y4_threshold_y1);
+
+	const u8 *dithering_texture = ebc->dithering_texture;
+	u8 dithering_texture_size_hint = ebc->dithering_texture_size_hint;
+
+	uint8x16_t q8_0x02 = vdupq_n_u8(0x02);
+	uint8x16_t q8_0x03 = vdupq_n_u8(0x03);
+	uint8x16_t q8_0x08 = vdupq_n_u8(0x08);
+	uint8x16_t q8_0x0f = vdupq_n_u8(0x0f);
+	uint8x16_t q8_0x40 = vdupq_n_u8(0x40);
+
+	for (y = src_clip->y1; y < src_clip->y2; y++) {
+		const u8 *fb_line = src;
+		u8 *prelim_target_line = dst_prelim_target;
+		const u8 *ioctl_hints_line = ioctl_hints;
+		u8 *hints_line = hints;
+
+		uint8x16_t q8_dither_pattern0, q8_dither_pattern1, q8_tmp;
+		if (dithering_texture_size_hint == 32) {
+			q8_dither_pattern0 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			q8_dither_pattern1 =
+				vld1q_u8(dithering_texture + 32 * (y & 31));
+			if (src_start_x & 16) {
+				q8_tmp = q8_dither_pattern0;
+				q8_dither_pattern0 = q8_dither_pattern1;
+				q8_dither_pattern1 = q8_tmp;
+			}
+		} else {
+			q8_dither_pattern0 = vld1q_u8(
+				dithering_texture +
+				16 * (y & (dithering_texture_size_hint - 1)));
+			q8_dither_pattern1 = q8_dither_pattern0;
+		}
+
+		for (x = src_start_x; x < src_end_x; x += 16, fb_line += 16,
+		    prelim_target_line -= 16, ioctl_hints_line += 16,
+		    hints_line -= 16) {
+			// Load 16 RGB values
+			uint64x1x2_t q64sx2_gray = vld2_u64((u64 *) fb_line);
+			/* __builtin_prefetch(fb_line + pixel_pitch, 0, 0); */
+
+			// Discard lower bits
+			// 0A 0B 0C 0D
+			uint8x16_t q8_gray = vshrq_n_u8(vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_gray.val[1], q64sx2_gray.val[0]))), 4);
+
+			uint64x1x2_t q64sx2_hint = vld2_u64((u64 *) ioctl_hints_line);
+			uint8x16_t q8_hint = vrev64q_u8(vreinterpretq_u8_u64(vcombine_u64(q64sx2_hint.val[1], q64sx2_hint.val[0])));
+			/* __builtin_prefetch(ioctl_hints_line + pixel_pitch, 0, 0); */
+			uint8x16_t q8_hint_dither = vtstq_u8(q8_hint, q8_0x40);
+			uint8x16_t q8_hint_waveform =
+				vandq_u8(vshrq_n_u8(q8_hint, 4), q8_0x03);
+			uint8x16_t q8_hint_gray = vorrq_u8(q8_gray, q8_hint);
+
+			uint8x16_t q8_gray_dithered = vminq_u8(
+				vqsubq_u8(vqaddq_u8(q8_gray,
+						    q8_dither_pattern0),
+					  q8_0x08),
+				q8_0x0f);
+			uint8x16_t q8_gray_y2_dt =
+				vqtbl2q_u8(q8_thresholds_y2_dither_table,
+					   q8_gray_dithered);
+			uint8x16_t q8_gray_y2_th =
+				vqtbl1q_u8(q8_thresholds_y2_table, q8_gray);
+			uint8x16_t q8_gray_y1_dt = vandq_u8(
+				vcgeq_u8(q8_gray_dithered, q8_threshold_y1),
+				q8_0x0f);
+			uint8x16_t q8_gray_y1_th = vandq_u8(
+				vcgeq_u8(q8_gray, q8_threshold_y1), q8_0x0f);
+			uint8x16_t q8_gray_y1 = vbslq_u8(
+				q8_hint_dither, q8_gray_y1_dt, q8_gray_y1_th);
+			uint8x16_t q8_gray_y2 = vbslq_u8(
+				q8_hint_dither, q8_gray_y2_dt, q8_gray_y2_th);
+			uint8x16_t q8_gray_y12 =
+				vbslq_u8(vceqzq_u8(q8_hint_waveform),
+					 q8_gray_y1, q8_gray_y2);
+
+			uint8x16_t q8_target =
+				vbslq_u8(vceqq_u8(q8_hint_waveform, q8_0x02),
+					 q8_gray, q8_gray_y12);
+			// TODO: make dithering/thresholding configurable via a hint
+			uint8x16_t q8_prelim_target = vorrq_u8(
+				vshlq_n_u8(q8_gray_y1_dt, 4), q8_target);
+
+			__builtin_prefetch(prelim_target_line + prefetch_pitch, 1, 0); // Maybe 1, 1 to keep in L3 cache
+			__builtin_prefetch(hints_line + prefetch_pitch, 1, 0);
+			vst1q_u8(hints_line, q8_hint_gray);
+			vst1q_u8(prelim_target_line, q8_prelim_target);
+
+			// Swap to support 32x32 dithering textures
+			q8_tmp = q8_dither_pattern0;
+			q8_dither_pattern0 = q8_dither_pattern1;
+			q8_dither_pattern1 = q8_tmp;
+		}
+
+		src += r8_pitch;
+		dst_prelim_target += pixel_pitch;
+		ioctl_hints += pixel_pitch;
+		hints += pixel_pitch;
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_blit_fb_r8_y4_hints_neon);
+
+void rockchip_ebc_reset_inner_outer_neon(const struct rockchip_ebc *ebc)
+{
+	u8 *packed_inner_outer_nextprev_line = ebc->packed_inner_outer_nextprev;
+	uint8x16_t q8_0x00 = vdupq_n_u8(0x00);
+	for (int i = 0; i < ebc->num_pixels; i += 16) {
+		uint8x16x3_t q8_inner_outer_nextprev = vld3q_u8(packed_inner_outer_nextprev_line);
+		uint8x16x3_t q8_inner_outer_nextprev_new = {
+			{ q8_0x00, q8_0x00, q8_inner_outer_nextprev.val[2] }
+		};
+		vst3q_u8(packed_inner_outer_nextprev_line,
+			 q8_inner_outer_nextprev_new);
+		packed_inner_outer_nextprev_line += 48;
+	}
+}
+EXPORT_SYMBOL(rockchip_ebc_reset_inner_outer_neon);
+
+MODULE_AUTHOR("hrdl <git@hrdl.eu>");
+MODULE_DESCRIPTION("Rockchip EBC NEON functions");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h
new file mode 100644
index 000000000..778e5736e
--- /dev/null
+++ b/drivers/gpu/drm/rockchip/rockchip_ebc_blit_neon.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) hrdl
+ * Author: hrdl <git@hrdl.eu>
+ */
+
+#ifndef _ROCKCHIP_EBC_BLIT_NEON_H
+#define _ROCKCHIP_EBC_BLIT_NEON_H
+
+void rockchip_ebc_blit_fb_xrgb8888_y4_neon(const struct rockchip_ebc_ctx *ctx,
+					   struct drm_rect *dst_clip,
+					   const void *vaddr,
+					   const struct drm_framebuffer *fb,
+					   const struct drm_rect *src_clip);
+
+void rockchip_ebc_blit_fb_xrgb8888_y4_thresholded4_neon(
+	const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip,
+	const void *vaddr, const struct drm_framebuffer *fb,
+	const struct drm_rect *src_clip, u8 invert, u8 fourtone_low_threshold,
+	u8 fourtone_mid_threshold, u8 fourtone_hi_threshold, bool dither);
+
+void rockchip_ebc_blit_fb_xrgb8888_y4_dithered2_texture_neon(
+	const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip,
+	const void *vaddr, const struct drm_framebuffer *fb,
+	const struct drm_rect *src_clip, u8 invert,
+	const u8 *dither_texture, int dither_texture_size);
+
+void rockchip_ebc_blit_fb_xrgb8888_y4_dithered2_neon(
+	const struct rockchip_ebc_ctx *ctx, struct drm_rect *dst_clip,
+	const void *vaddr, const struct drm_framebuffer *fb,
+	const struct drm_rect *src_clip, u8 invert);
+
+void rockchip_ebc_update_blit_fnum_prev_neon(const struct rockchip_ebc_ctx *ctx,
+					     u8 *prev, u8 *next, u8 *fnum,
+					     u8 *fnum_prev,
+					     struct drm_rect *clip,
+					     u8 last_phase);
+
+void rockchip_ebc_blit_direct_fnum_a2_neon(const struct rockchip_ebc_ctx *ctx,
+					u8 *phase, u8 *frame_num, u8 *next,
+					u8 *prev, const struct drm_epd_lut *lut,
+					u8 a2_shorten_waveform,
+					const struct drm_rect *clip);
+
+void rockchip_ebc_blit_frame_num_neon(const struct rockchip_ebc_ctx *ctx,
+				      u8 *dst, u8 phase,
+				      const struct drm_rect *clip,
+				      u8 *other_buffer, int last_phase,
+				      int frame, int check_blit_frame_num);
+
+void rockchip_ebc_schedule_and_blit_neon(
+	const struct rockchip_ebc_ctx *ctx, u8 *frame_num, u8 *next, u8 *final,
+	struct drm_rect *clip_ongoing_new_areas, struct rockchip_ebc_area *area,
+	u32 frame, u8 last_phase);
+
+void rockchip_ebc_schedule_cancel_blit_a2_neon(
+	const struct rockchip_ebc_ctx *ctx, u8 *frame_num, u8 *prev, u8 *next,
+	u8 *final, struct drm_rect *clip_ongoing_new_areas,
+	struct rockchip_ebc_area *area, u8 last_phase,
+	u8 early_cancellation_addition);
+
+#endif /* _ROCKCHIP_EBC_BLIT_NEON_H */
diff --git a/drivers/iio/accel/st_accel_i2c.c b/drivers/iio/accel/st_accel_i2c.c
index f24449500..5f6dd93c7 100644
--- a/drivers/iio/accel/st_accel_i2c.c
+++ b/drivers/iio/accel/st_accel_i2c.c
@@ -202,11 +202,51 @@ static int st_accel_i2c_probe(struct i2c_client *client)
 	return st_accel_common_probe(indio_dev);
 }
 
+static int __maybe_unused st_accel_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct iio_dev *indio_dev;
+	int ret;
+	struct st_sensor_data *sdata;
+
+	indio_dev = i2c_get_clientdata(client);
+	sdata = iio_priv(indio_dev);
+	pr_info("%s", __func__);
+	st_sensors_set_enable(indio_dev, false);
+	st_sensors_set_dataready_irq(indio_dev, false);
+	disable_irq(sdata->irq);
+
+	return 0;
+}
+
+static int __maybe_unused st_accel_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct iio_dev *indio_dev;
+	struct st_sensor_data *sdata;
+	int ret;
+
+	indio_dev = i2c_get_clientdata(client);
+	sdata = iio_priv(indio_dev);
+
+	pr_info("%s", __func__);
+	st_sensors_set_enable(indio_dev, true);
+	st_sensors_set_dataready_irq(indio_dev, true);
+	// enabling the irq here will reduce the number of spurious irqs, and the
+	// device will keep on working after resume.
+	// Still not enough...
+	enable_irq(sdata->irq);
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(st_accel_pm, st_accel_suspend, st_accel_resume);
+
 static struct i2c_driver st_accel_driver = {
 	.driver = {
 		.name = "st-accel-i2c",
 		.of_match_table = st_accel_of_match,
 		.acpi_match_table = st_accel_acpi_match,
+		.pm		= &st_accel_pm,
 	},
 	.probe = st_accel_i2c_probe,
 	.id_table = st_accel_id_table,
diff --git a/drivers/iio/common/st_sensors/st_sensors_trigger.c b/drivers/iio/common/st_sensors/st_sensors_trigger.c
index 8a8ab688d..d989f0e78 100644
--- a/drivers/iio/common/st_sensors/st_sensors_trigger.c
+++ b/drivers/iio/common/st_sensors/st_sensors_trigger.c
@@ -87,6 +87,16 @@ static irqreturn_t st_sensors_irq_thread(int irq, void *p)
 	    st_sensors_new_samples_available(indio_dev, sdata)) {
 		iio_trigger_poll_nested(p);
 	} else {
+		pr_info(
+			"st_sensors_irq_thread: %i returning IRQ_NONE (%i/%i/%i/%u) %s",
+			irq,
+		   	sdata->hw_irq_trigger,
+			st_sensors_new_samples_available(indio_dev, sdata),
+			sdata->num_data_channels,
+			sdata->odr,
+			trig->name
+		);
+		/* return IRQ_HANDLED; */
 		dev_dbg(indio_dev->dev.parent, "spurious IRQ\n");
 		return IRQ_NONE;
 	}
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 94a753fcb..bb51f2772 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -1003,4 +1003,12 @@ config INPUT_STPMIC1_ONKEY
 	  To compile this driver as a module, choose M here: the
 	  module will be called stpmic1_onkey.
 
+config INPUT_WS8100_PEN
+	tristate "Wisesun WS8100 BLE Pen support"
+	depends on SPI
+	help
+	  Say Y to enable support for the buttons on Bluetooth LE pens
+	  connected via a Wisesun WS8100 BLE controller, such as used on the
+	  Pine64 PineNote.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 415fc4e29..7151713af 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -93,6 +93,7 @@ obj-$(CONFIG_INPUT_TWL6040_VIBRA)	+= twl6040-vibra.o
 obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
 obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
 obj-$(CONFIG_INPUT_WM831X_ON)		+= wm831x-on.o
+obj-$(CONFIG_INPUT_WS8100_PEN)		+= ws8100-pen.o
 obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND)	+= xen-kbdfront.o
 obj-$(CONFIG_INPUT_YEALINK)		+= yealink.o
 obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR)	+= ideapad_slidebar.o
diff --git a/drivers/input/misc/ws8100-pen.c b/drivers/input/misc/ws8100-pen.c
new file mode 100644
index 000000000..ce6a31d10
--- /dev/null
+++ b/drivers/input/misc/ws8100-pen.c
@@ -0,0 +1,801 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * WS8100 BLE Pen Driver
+ *
+ * Copyright (c) 2022 Samuel Holland <samuel@sholland.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/spi/spi.h>
+
+#define WS8100_IRQ_PEN_VERSION_INFO	0x12
+#define WS8100_IRQ_PEN_BATTERY_INFO	0x13
+#define WS8100_IRQ_PEN_OTA_STATUS	0x14
+#define WS8100_IRQ_OTA_STATUS_A		0x45
+#define WS8100_IRQ_OTA_STATUS_B		0x46
+#define WS8100_IRQ_OTA_STATUS_C		0x47
+#define WS8100_IRQ_INPUT		0x66
+#define WS8100_IRQ_SCAN_COMPLETE	0x77
+
+#define WS8100_ADDRESS_LEN		6
+#define WS8100_BATTERY_LEN		2
+#define WS8100_COMMAND_LEN		3
+#define WS8100_READ_STATUS_LEN		2
+#define WS8100_VERSION_LEN		3
+
+#define WS8100_MAX_RX_LEN		10
+#define WS8100_MAX_SCAN_RESULTS		50
+#define WS8100_MAX_XFER_RETRY		3
+
+#define WS8100_RX_DATA_OFFSET		2
+#define WS8100_RX_OVERHEAD		3
+
+struct ws8100_pen {
+	struct completion	pen_battery_read_complete;
+	struct completion	pen_version_read_complete;
+	struct completion	scan_complete;
+	struct gpio_desc	*cs_gpio;
+	struct gpio_desc	*host_state_gpio;
+	struct gpio_desc	*reset_gpio;
+	struct input_dev	*input;
+	struct mutex		scan_results_mutex;
+	struct mutex		xfer_mutex;
+
+	ktime_t	pen_battery_time;
+	bool	pen_present;
+
+	u8	controller_version[WS8100_VERSION_LEN];
+	u8	pen_address[WS8100_ADDRESS_LEN + 1];
+	u8	pen_battery[WS8100_BATTERY_LEN];
+	u8	pen_version[WS8100_VERSION_LEN];
+	u8	scan_results[WS8100_MAX_SCAN_RESULTS][WS8100_ADDRESS_LEN];
+	u8	scan_result_count;
+};
+
+/*
+ * All transfers follow the same pattern:
+ *	1) Command, always 3 bytes:
+ *		[r/w]	Always 0x50 for "writes", 0x51 for "reads"
+ *		[op]	Determines what the command does
+ *		[sum]	Sum to 0x00
+ *	2) Transmitted data, optional, variable length:
+ *		[data]	Zero or more bytes, depending on the command
+ *		[sum]	Sum to 0x00, only present if there is data
+ *	3) Received data, variable length:
+ *		[r/w]	Copy of the byte from the command
+ *		[op]	Copy of the byte from the command
+ *		[data]	Zero or more bytes, depending on the command
+ *		[sum]	Sum to 0x00
+ */
+struct ws8100_cmd {
+	u8	command[WS8100_COMMAND_LEN];
+	u8	rx_data_len;
+	u16	tx_data_len;
+};
+
+#define WS8100_CMD(_name, _cmd0, _cmd1, _tx, _rx) \
+	static const struct ws8100_cmd _name = { \
+		.command	= { _cmd0, _cmd1, (0x100 - _cmd0 - _cmd1) }, \
+		.tx_data_len	= _tx, \
+		.rx_data_len	= _rx, \
+	}
+
+/*						 r/w    op   tx rx */
+WS8100_CMD(ws8100_cmd_read_from_pen,		0x51, 0x11,   2, 2);
+WS8100_CMD(ws8100_cmd_get_pen_version,		0x51, 0x12,   0, 3);
+WS8100_CMD(ws8100_cmd_get_pen_battery,		0x51, 0x13,   0, 2);
+WS8100_CMD(ws8100_cmd_get_pen_ota_status,	0x51, 0x14,   0, 1);
+WS8100_CMD(ws8100_cmd_start_pen_ota,		0x50, 0x16,   0, 0);
+WS8100_CMD(ws8100_cmd_set_suspend_mode,		0x50, 0x22,   2, 1);
+WS8100_CMD(ws8100_cmd_get_controller_version,	0x51, 0x33,   0, 3);
+WS8100_CMD(ws8100_cmd_start_ota,		0x50, 0x44,   7, 1);
+WS8100_CMD(ws8100_cmd_write_ota_data,		0x50, 0x45, 504, 1);
+WS8100_CMD(ws8100_cmd_cancel_ota,		0x50, 0x46,   0, 0);
+WS8100_CMD(ws8100_cmd_get_ota_status,		0x51, 0x47,   0, 1);
+WS8100_CMD(ws8100_cmd_stop_ota,			0x50, 0x48,   0, 0);
+WS8100_CMD(ws8100_cmd_set_default_pen_address,	0x50, 0x55,   7, 1);
+WS8100_CMD(ws8100_cmd_get_default_pen_address,	0x51, 0x55,   0, 7);
+WS8100_CMD(ws8100_cmd_get_input,		0x51, 0x66,   0, 1);
+WS8100_CMD(ws8100_cmd_start_scan,		0x50, 0x77,   0, 0);
+WS8100_CMD(ws8100_cmd_get_scan_result_count,	0x51, 0x78,   0, 1);
+WS8100_CMD(ws8100_cmd_get_irq,			0x51, 0x88,   0, 1);
+WS8100_CMD(ws8100_cmd_get_scan_result,		0x51, 0xc0,   0, 6);
+
+static int ws8100_pen_xfer(struct spi_device *spi,
+			   const struct ws8100_cmd *c,
+			   u8 *tx_data, u8 *rx_data)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	u8 rx_len = WS8100_RX_OVERHEAD + c->rx_data_len;
+	u8 rx_buf[WS8100_MAX_RX_LEN];
+	int ret, try;
+	u8 i, sum;
+	struct spi_transfer xfers[3] = {
+		{
+			.tx_buf	= c->command,
+			.len	= WS8100_COMMAND_LEN,
+			.delay	= { .value = c->tx_data_len ? 0 : 50 },
+		},
+		{
+			.tx_buf	= tx_data,
+			.len	= c->tx_data_len,
+			.delay	= { .value = 50 },
+		},
+		{
+			.rx_buf	= rx_buf,
+			.len	= rx_len,
+		},
+	};
+
+	if ((c->tx_data_len && !tx_data) || (c->rx_data_len && !rx_data))
+		return -EINVAL;
+	if (rx_len > sizeof(rx_buf))
+		return -EINVAL;
+
+	mutex_lock(&pen->xfer_mutex);
+
+	dev_dbg(&spi->dev, "send [%*ph] [%*ph]\n",
+		WS8100_COMMAND_LEN, c->command, c->tx_data_len, tx_data);
+
+	for (try = 0; try < WS8100_MAX_XFER_RETRY; ++try) {
+		if (try)
+			msleep(50);
+
+		gpiod_set_value_cansleep(pen->cs_gpio, 1);
+		msleep(10);
+		ret = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
+		gpiod_set_value_cansleep(pen->cs_gpio, 0);
+		if (ret)
+			goto out_unlock;
+
+		dev_dbg(&spi->dev, "recv [%*ph]\n", rx_len, rx_buf);
+
+		if (rx_buf[0] != c->command[0])
+			continue;
+		for (i = 0, sum = 0; i < rx_len; ++i)
+			sum += rx_buf[i];
+		if (sum)
+			continue;
+
+		memcpy(rx_data, &rx_buf[WS8100_RX_DATA_OFFSET], c->rx_data_len);
+		goto out_unlock;
+	}
+
+	ret = -EBADMSG;
+
+out_unlock:
+	mutex_unlock(&pen->xfer_mutex);
+
+	return ret;
+}
+
+static void ws8100_pen_input(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	int code = 0, ret, tool = 0;
+	u8 input_status;
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_input, NULL, &input_status);
+	if (ret) {
+		dev_warn(&spi->dev, "Failed to read input: %d\n", ret);
+		return;
+	}
+
+	dev_dbg(&spi->dev, "Got input 0x%02x\n", input_status);
+
+	switch (input_status) {
+	case 0x02: /* short press */
+		code = BTN_STYLUS;
+		tool = BTN_TOOL_RUBBER;
+		break;
+	case 0x20: /* long press */
+		code = KEY_BACK;
+		tool = BTN_TOOL_RUBBER;
+		break;
+	case 0x22: /* double press */
+		code = KEY_MACRO1;
+		tool = BTN_TOOL_RUBBER;
+		break;
+	case 0x23: /* leading edge */
+		tool = BTN_TOOL_RUBBER;
+		break;
+
+	case 0x04: /* short press */
+		code = BTN_STYLUS2;
+		tool = BTN_TOOL_PEN;
+		break;
+	case 0x40: /* long press */
+		code = KEY_FORWARD;
+		tool = BTN_TOOL_PEN;
+		break;
+	case 0x42: /* double press */
+		code = KEY_MACRO2;
+		tool = BTN_TOOL_PEN;
+		break;
+	case 0x43: /* leading edge */
+		tool = BTN_TOOL_PEN;
+		break;
+
+	case 0x01: /* short press */
+		code = BTN_STYLUS3;
+		break;
+	case 0x10: /* long press */
+		code = KEY_SLEEP;
+		break;
+	case 0x08: /* double press */
+		code = KEY_MACRO3;
+		break;
+
+	default:
+		dev_warn(&spi->dev, "Unknown input: 0x%02x\n", input_status);
+		break;
+	}
+
+	if (code) {
+		input_report_key(pen->input, code, 1);
+		input_sync(pen->input);
+		input_report_key(pen->input, code, 0);
+		if (tool)
+			input_report_key(pen->input, tool, 0);
+	} else {
+		if (tool)
+			input_report_key(pen->input, tool, 1);
+	}
+	input_event(pen->input, EV_MSC, MSC_SCAN, input_status);
+	input_sync(pen->input);
+}
+
+static void ws8100_pen_scan_complete(struct spi_device *spi)
+{
+	struct ws8100_cmd result_cmd = ws8100_cmd_get_scan_result;
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	u8 i, scan_result_count;
+	int ret;
+
+	mutex_lock(&pen->scan_results_mutex);
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_scan_result_count,
+			      NULL, &scan_result_count);
+	if (ret)
+		goto out_unlock;
+
+	pen->scan_result_count = min_t(u8, scan_result_count, WS8100_MAX_SCAN_RESULTS);
+	for (i = 0; i < pen->scan_result_count; ++i) {
+		ret = ws8100_pen_xfer(spi, &result_cmd,
+				      NULL, pen->scan_results[i]);
+		if (ret)
+			goto out_unlock;
+
+		result_cmd.command[1]++; /* increment the result index */
+		result_cmd.command[2]--; /* decrement the checksum to match */
+	}
+
+out_unlock:
+	if (ret) {
+		dev_warn(&spi->dev, "Failed to read scan results: %d\n", ret);
+		pen->scan_result_count = 0;
+	}
+
+	mutex_unlock(&pen->scan_results_mutex);
+}
+
+static irqreturn_t ws8100_pen_irq(int irq, void *data)
+{
+	struct spi_device *spi = data;
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	u8 irq_status;
+	int ret;
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_get_irq, NULL, &irq_status);
+	if (ret) {
+		dev_warn(&spi->dev, "Failed to read IRQ: %d\n", ret);
+		return IRQ_HANDLED;
+	}
+
+	dev_dbg(&spi->dev, "Got IRQ 0x%02x\n", irq_status);
+
+	switch (irq_status) {
+	case WS8100_IRQ_PEN_VERSION_INFO:
+		ws8100_pen_xfer(spi, &ws8100_cmd_get_pen_version,
+				NULL, pen->pen_version);
+		complete(&pen->pen_version_read_complete);
+		break;
+	case WS8100_IRQ_PEN_BATTERY_INFO:
+		ws8100_pen_xfer(spi, &ws8100_cmd_get_pen_battery,
+				NULL, pen->pen_battery);
+		complete(&pen->pen_battery_read_complete);
+		break;
+	case WS8100_IRQ_INPUT:
+		ws8100_pen_input(spi);
+		break;
+	case WS8100_IRQ_SCAN_COMPLETE:
+		ws8100_pen_scan_complete(spi);
+		complete(&pen->scan_complete);
+		break;
+	default:
+		dev_warn(&spi->dev, "Unknown IRQ: 0x%02x\n", irq_status);
+		break;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int ws8100_pen_get_controller_version(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+
+	return ws8100_pen_xfer(spi, &ws8100_cmd_get_controller_version,
+			       NULL, pen->controller_version);
+}
+
+static int ws8100_pen_get_pen_address(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+
+	return ws8100_pen_xfer(spi, &ws8100_cmd_get_default_pen_address,
+			       NULL, pen->pen_address);
+}
+
+static int ws8100_pen_get_pen_battery(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	u8 read_status[WS8100_READ_STATUS_LEN];
+	u8 battery_data[2] = { 0x02, 0xfe };
+	int ret;
+
+	reinit_completion(&pen->pen_battery_read_complete);
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_read_from_pen,
+			      battery_data, read_status);
+	if (ret)
+		return ret;
+
+	/* Fail early if no pen is connected. */
+	if (!read_status[0])
+		return -ENXIO;
+
+	/* Usually takes 100-200 ms. */
+	if (!wait_for_completion_timeout(&pen->pen_battery_read_complete,
+					 msecs_to_jiffies(500)))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int ws8100_pen_get_pen_version(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	u8 read_status[WS8100_READ_STATUS_LEN];
+	u8 version_data[2] = { 0x01, 0xff };
+	int ret;
+
+	reinit_completion(&pen->pen_version_read_complete);
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_read_from_pen,
+			      version_data, read_status);
+	if (ret)
+		return ret;
+
+	/* Fail early if no pen is connected. */
+	if (!read_status[0])
+		return -ENXIO;
+
+	/* Usually takes 100-200 ms. */
+	if (!wait_for_completion_timeout(&pen->pen_version_read_complete,
+					 msecs_to_jiffies(500)))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int ws8100_pen_scan(struct spi_device *spi)
+{
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	int ret;
+
+	reinit_completion(&pen->scan_complete);
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_start_scan, NULL, NULL);
+	if (ret)
+		return ret;
+
+	/* Scans usually take about 12 seconds, but sometimes longer. */
+	if (!wait_for_completion_interruptible_timeout(&pen->scan_complete,
+						       msecs_to_jiffies(20000)))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static const enum power_supply_property ws8100_pen_psy_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_SCOPE,
+};
+
+static int ws8100_pen_psy_get_proprty(struct power_supply *psy,
+				      enum power_supply_property psp,
+				      union power_supply_propval *val)
+{
+	struct spi_device *spi = power_supply_get_drvdata(psy);
+	struct ws8100_pen *pen = spi_get_drvdata(spi);
+	ktime_t now = ktime_get();
+	int ret;
+
+
+	if (ktime_after(now, pen->pen_battery_time)) {
+		ret = ws8100_pen_get_pen_battery(spi);
+		if (ret && ret != -ENXIO)
+			return ret;
+
+		pen->pen_battery_time = ktime_add_ns(now, NSEC_PER_SEC);
+		pen->pen_present = !ret;
+	}
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = pen->pen_battery[0]
+			? POWER_SUPPLY_STATUS_CHARGING
+			: POWER_SUPPLY_STATUS_DISCHARGING;
+		return 0;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = pen->pen_present;
+		return 0;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = pen->pen_battery[1];
+		return 0;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct power_supply_desc ws8100_pen_psy_desc = {
+	.name			= "ws8100_pen",
+	.type			= POWER_SUPPLY_TYPE_BATTERY,
+	.properties		= ws8100_pen_psy_properties,
+	.num_properties		= ARRAY_SIZE(ws8100_pen_psy_properties),
+	.get_property		= ws8100_pen_psy_get_proprty,
+};
+
+static ssize_t controller_version_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	struct spi_device *spi = to_spi_device(dev);
+	int ret;
+
+	ret = ws8100_pen_get_controller_version(spi);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%.*s\n", WS8100_VERSION_LEN,
+		       pen->controller_version);
+}
+
+static DEVICE_ATTR_RO(controller_version);
+
+static ssize_t host_state_show(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%d\n",
+		       gpiod_get_value_cansleep(pen->host_state_gpio));
+}
+
+static ssize_t host_state_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	bool state;
+	int ret;
+
+	ret = kstrtobool(buf, &state);
+	if (ret)
+		return ret;
+
+	gpiod_set_value_cansleep(pen->host_state_gpio, state);
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(host_state);
+
+static ssize_t pen_address_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	struct spi_device *spi = to_spi_device(dev);
+	int ret;
+
+	ret = ws8100_pen_get_pen_address(spi);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%pM\n", pen->pen_address);
+}
+
+static ssize_t pen_address_store(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	u8 address[WS8100_ADDRESS_LEN + 1];
+	u8 status, sum;
+	int i, ret;
+
+	ret = sscanf(buf, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+		     &address[0], &address[1], &address[2],
+		     &address[3], &address[4], &address[5]);
+	if (ret != WS8100_ADDRESS_LEN)
+		return -EINVAL;
+
+	for (i = 0, sum = 0; i < WS8100_ADDRESS_LEN; ++i)
+		sum += address[i];
+	address[WS8100_ADDRESS_LEN] = -sum;
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_set_default_pen_address,
+			      address, &status);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(pen_address);
+
+static ssize_t pen_battery_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	struct spi_device *spi = to_spi_device(dev);
+	int ret;
+
+	ret = ws8100_pen_get_pen_battery(spi);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%*ph\n", WS8100_BATTERY_LEN, pen->pen_battery);
+}
+
+static DEVICE_ATTR_RO(pen_battery);
+
+static ssize_t pen_version_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	struct spi_device *spi = to_spi_device(dev);
+	int ret;
+
+	ret = ws8100_pen_get_pen_version(spi);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%.*s\n", WS8100_VERSION_LEN, pen->pen_version);
+}
+
+static DEVICE_ATTR_RO(pen_version);
+
+static ssize_t scan_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+	ssize_t count;
+	u8 i;
+
+	mutex_lock(&pen->scan_results_mutex);
+
+	count = sprintf(buf, "Scan Results (%d)\n-----------------\n",
+			pen->scan_result_count);
+
+	for (i = 0; i < pen->scan_result_count; ++i)
+		count = sprintf(buf, "%s%pM\n", buf, pen->scan_results[i]);
+
+	mutex_unlock(&pen->scan_results_mutex);
+
+	return count;
+}
+
+static ssize_t scan_store(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	int ret;
+
+	ret = ws8100_pen_scan(spi);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_RW(scan);
+
+static ssize_t suspend_mode_store(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	u8 mode[2], status;
+	int ret;
+
+	ret = kstrtou8(buf, 10, &mode[0]);
+	if (ret)
+		return ret;
+	mode[1] = -mode[0];
+
+	ret = ws8100_pen_xfer(spi, &ws8100_cmd_set_suspend_mode, mode, &status);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static DEVICE_ATTR_WO(suspend_mode);
+
+static struct attribute *ws8100_pen_attributes[] = {
+	&dev_attr_controller_version.attr,
+	&dev_attr_host_state.attr,
+	&dev_attr_pen_address.attr,
+	&dev_attr_pen_battery.attr,
+	&dev_attr_pen_version.attr,
+	&dev_attr_scan.attr,
+	&dev_attr_suspend_mode.attr,
+	NULL
+};
+
+static const struct attribute_group *ws8100_pen_groups[] = {
+	&(const struct attribute_group) {
+		.attrs = ws8100_pen_attributes,
+	},
+	NULL
+};
+
+static int ws8100_pen_suspend(struct device *dev)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+
+	gpiod_set_value_cansleep(pen->host_state_gpio, 0);
+
+	return 0;
+}
+
+static int ws8100_pen_resume(struct device *dev)
+{
+	struct ws8100_pen *pen = dev_get_drvdata(dev);
+
+	gpiod_set_value_cansleep(pen->host_state_gpio, 1);
+
+	return 0;
+}
+
+static int ws8100_pen_probe(struct spi_device *spi)
+{
+	struct power_supply_config psy_cfg = { .drv_data = spi };
+	struct device *dev = &spi->dev;
+	struct power_supply *psy;
+	struct ws8100_pen *pen;
+	int code, ret;
+
+	pen = devm_kzalloc(dev, sizeof(*pen), GFP_KERNEL);
+	if (!pen)
+		return -ENOMEM;
+
+	spi_set_drvdata(spi, pen);
+	init_completion(&pen->pen_battery_read_complete);
+	init_completion(&pen->pen_version_read_complete);
+	init_completion(&pen->scan_complete);
+	mutex_init(&pen->scan_results_mutex);
+	mutex_init(&pen->xfer_mutex);
+
+	pen->cs_gpio = devm_gpiod_get(dev, "cs", GPIOD_OUT_LOW);
+	if (IS_ERR(pen->cs_gpio))
+		return dev_err_probe(dev, PTR_ERR(pen->cs_gpio),
+				     "Failed to get %s GPIO\n", "cs");
+
+	pen->host_state_gpio = devm_gpiod_get(dev, "host_state", GPIOD_OUT_HIGH);
+	if (IS_ERR(pen->host_state_gpio))
+		return dev_err_probe(dev, PTR_ERR(pen->host_state_gpio),
+				     "Failed to get %s GPIO\n", "host_state");
+
+	pen->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(pen->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(pen->reset_gpio),
+				     "Failed to get %s GPIO\n", "reset");
+
+	pen->input = devm_input_allocate_device(dev);
+	if (!pen->input)
+		return -ENOMEM;
+
+	input_set_drvdata(pen->input, spi);
+
+	pen->input->name = "ws8100_pen";
+	pen->input->phys = "ws8100_pen/input0";
+	pen->input->id.bustype = BUS_BLUETOOTH;
+
+	__set_bit(EV_KEY, pen->input->evbit);
+	__set_bit(KEY_SLEEP, pen->input->keybit);
+	__set_bit(KEY_BACK, pen->input->keybit);
+	__set_bit(KEY_FORWARD, pen->input->keybit);
+	__set_bit(BTN_TOOL_PEN, pen->input->keybit);
+	__set_bit(BTN_TOOL_RUBBER, pen->input->keybit);
+	__set_bit(BTN_STYLUS3, pen->input->keybit);
+	__set_bit(BTN_STYLUS, pen->input->keybit);
+	__set_bit(BTN_STYLUS2, pen->input->keybit);
+	for (code = KEY_MACRO1; code < KEY_MACRO4; ++code)
+		__set_bit(code, pen->input->keybit);
+
+	__set_bit(EV_MSC, pen->input->evbit);
+	__set_bit(MSC_SCAN, pen->input->mscbit);
+
+	ret = input_register_device(pen->input);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register input\n");
+
+	psy = devm_power_supply_register(dev, &ws8100_pen_psy_desc, &psy_cfg);
+	if (IS_ERR(psy))
+		return PTR_ERR(psy);
+
+	power_supply_powers(psy, &pen->input->dev);
+
+	ret = devm_request_threaded_irq(dev, spi->irq, NULL, ws8100_pen_irq,
+					IRQF_ONESHOT, dev_name(dev), spi);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+	/* Ensure the controller is fully reset. */
+	msleep(50);
+
+	gpiod_set_value_cansleep(pen->reset_gpio, 0);
+
+	return 0;
+}
+
+static void ws8100_pen_shutdown(struct spi_device *spi)
+{
+	ws8100_pen_suspend(&spi->dev);
+}
+
+static const struct spi_device_id ws8100_pen_id_table[] = {
+	{ .name = "pinenote-ws8100-pen" },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, ws8100_pen_id_table);
+
+static const struct of_device_id ws8100_pen_of_match[] = {
+	{ .compatible = "pine64,pinenote-ws8100-pen" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ws8100_pen_of_match);
+
+static SIMPLE_DEV_PM_OPS(ws8100_pen_pm_ops,
+			 ws8100_pen_suspend, ws8100_pen_resume);
+
+static struct spi_driver ws8100_pen_driver = {
+	.id_table	= ws8100_pen_id_table,
+	.probe		= ws8100_pen_probe,
+	.shutdown	= ws8100_pen_shutdown,
+	.driver		= {
+		.name		= "ws8100_pen",
+		.of_match_table	= ws8100_pen_of_match,
+		.dev_groups	= ws8100_pen_groups,
+		.pm		= &ws8100_pen_pm_ops,
+	},
+};
+
+module_spi_driver(ws8100_pen_driver);
+
+MODULE_DESCRIPTION("WS8100 BLE Pen Driver");
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/cyttsp5.c b/drivers/input/touchscreen/cyttsp5.c
index 47f427139..63e71f3b1 100644
--- a/drivers/input/touchscreen/cyttsp5.c
+++ b/drivers/input/touchscreen/cyttsp5.c
@@ -525,15 +525,66 @@ static int cyttsp5_get_sysinfo_regs(struct cyttsp5 *ts)
 	struct cyttsp5_sensing_conf_data_dev *scd_dev =
 		(struct cyttsp5_sensing_conf_data_dev *)
 		&ts->response_buf[HID_SYSINFO_SENSING_OFFSET];
+	u32 tmp;
 
 	cyttsp5_si_get_btn_data(ts);
 
 	scd->max_tch = scd_dev->max_num_of_tch_per_refresh_cycle;
-	scd->res_x = get_unaligned_le16(&scd_dev->res_x);
-	scd->res_y = get_unaligned_le16(&scd_dev->res_y);
-	scd->max_z = get_unaligned_le16(&scd_dev->max_z);
-	scd->len_x = get_unaligned_le16(&scd_dev->len_x);
-	scd->len_y = get_unaligned_le16(&scd_dev->len_y);
+
+	if (scd->max_tch == 0) {
+		dev_dbg(ts->dev, "Max touch points cannot be zero\n");
+		scd->max_tch = 2;
+	}
+
+	if(device_property_read_u32(ts->dev, "touchscreen-size-x", &tmp))
+		scd->res_x = get_unaligned_le16(&scd_dev->res_x);
+	else
+		scd->res_x = tmp;
+
+	if (scd->res_x == 0) {
+		dev_err(ts->dev, "ABS_X cannot be zero\n");
+		return -ENODATA;
+	}
+
+	if(device_property_read_u32(ts->dev, "touchscreen-size-y", &tmp))
+		scd->res_y = get_unaligned_le16(&scd_dev->res_y);
+	else
+		scd->res_y = tmp;
+
+	if (scd->res_y == 0) {
+		dev_err(ts->dev, "ABS_Y cannot be zero\n");
+		return -ENODATA;
+	}
+
+	if(device_property_read_u32(ts->dev, "touchscreen-max-pressure", &tmp))
+		scd->max_z = get_unaligned_le16(&scd_dev->max_z);
+	else
+		scd->max_z = tmp;
+
+	if (scd->max_z == 0) {
+		dev_err(ts->dev, "ABS_PRESSURE cannot be zero\n");
+		return -ENODATA;
+	}
+
+	if(device_property_read_u32(ts->dev, "touchscreen-x-mm", &tmp))
+		scd->len_x = get_unaligned_le16(&scd_dev->len_x);
+	else
+		scd->len_x = tmp;
+
+	if (scd->len_x == 0) {
+		dev_dbg(ts->dev, "Touchscreen size x cannot be zero\n");
+		scd->len_x = scd->res_x + 1;
+	}
+
+	if(device_property_read_u32(ts->dev, "touchscreen-y-mm", &tmp))
+		scd->len_y = get_unaligned_le16(&scd_dev->len_y);
+	else
+		scd->len_y = tmp;
+
+	if (scd->len_y == 0) {
+		dev_dbg(ts->dev, "Touchscreen size y cannot be zero\n");
+		scd->len_y = scd->res_y + 1;
+	}
 
 	return 0;
 }
@@ -933,6 +984,7 @@ static int cyttsp5_i2c_probe(struct i2c_client *client)
 
 static const struct of_device_id cyttsp5_of_match[] = {
 	{ .compatible = "cypress,tt21000", },
+	{ .compatible = "cypress,tma448", },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, cyttsp5_of_match);
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c
index 43ed742a1..1136fbf29 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.c
+++ b/drivers/media/platform/rockchip/rga/rga-hw.c
@@ -213,6 +213,11 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
 		}
 	}
 
+	dst_info.data.sw_dst_fmt_yuv400_en = ctx->yuv400_enable;
+	dst_info.data.sw_dst_fmt_y4_en = ctx->y4_enable;
+	dst_info.data.dither_down_en = ctx->dither_down_enable;
+	dst_info.data.dither_down_mode = ctx->dither_down_mode;
+
 	if (ctx->vflip)
 		src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X;
 
@@ -234,6 +239,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
 		break;
 	}
 
+
 	/*
 	 * Calculate the up/down scaling mode/factor.
 	 *
@@ -293,6 +299,7 @@ static void rga_cmd_set_trans_info(struct rga_ctx *ctx)
 	src_act_info.data.act_width = src_w - 1;
 
 	dst_vir_info.data.vir_stride = ctx->out.stride >> 2;
+
 	dst_act_info.data.act_height = dst_h - 1;
 	dst_act_info.data.act_width = dst_w - 1;
 
@@ -407,6 +414,10 @@ static void rga_cmd_set_mode(struct rga_ctx *ctx)
 	dest[(RGA_ALPHA_CTRL1 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl1.val;
 
 	dest[(RGA_MODE_CTRL - RGA_MODE_BASE_REG) >> 2] = mode.val;
+
+	// LUTS Y4
+	dest[(RGA_RST_Y4MAP_LUT0 - RGA_MODE_BASE_REG) >> 2] = ctx->y4map_lut0;
+	dest[(RGA_RST_Y4MAP_LUT1 - RGA_MODE_BASE_REG) >> 2] = ctx->y4map_lut1;
 }
 
 static void rga_cmd_set(struct rga_ctx *ctx,
diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h
index cc6bd7f5b..e451f5c7b 100644
--- a/drivers/media/platform/rockchip/rga/rga-hw.h
+++ b/drivers/media/platform/rockchip/rga/rga-hw.h
@@ -59,6 +59,8 @@
 #define RGA_PAT_CON 0x015c
 #define RGA_ROP_CON0 0x0160
 #define RGA_ROP_CON1 0x0164
+#define RGA_RST_Y4MAP_LUT0 0x0160
+#define RGA_RST_Y4MAP_LUT1 0x0164
 #define RGA_MASK_BASE 0x0168
 
 #define RGA_MMU_CTRL1 0x016C
@@ -321,8 +323,15 @@ union rga_dst_info {
 		/* [16:18] */
 		unsigned int csc_mode:2;
 		unsigned int csc_clip:1;
+		unsigned int sw_dst_csc_mode_2:1;
+		unsigned int sw_src1_csc_mode:2;
+		unsigned int sw_src1_csc_clip:1;
+		unsigned int reserved_1:1;
+		unsigned int sw_dst_fmt_yuv400_en:1;
+		unsigned int sw_dst_fmt_y4_en:1;
+		unsigned int sw_dst_nn_quantize_en:1;
 		/* [19:31] */
-		unsigned int reserved:13;
+		unsigned int reserved:6;
 	} data;
 };
 
diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c
index 43f6a8d99..66b6f30ba 100644
--- a/drivers/media/platform/rockchip/rga/rga.c
+++ b/drivers/media/platform/rockchip/rga/rga.c
@@ -135,17 +135,63 @@ static int rga_s_ctrl(struct v4l2_ctrl *ctrl)
 	spin_lock_irqsave(&ctx->rga->ctrl_lock, flags);
 	switch (ctrl->id) {
 	case V4L2_CID_HFLIP:
+		printk(KERN_INFO "V4L2_CID_HFLIP: %i -> %i\n", ctx->hflip, ctrl->val);
+		if (ctx->dither_down_enable){
+			// dither does not work with h/v-flip
+			spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
+			return -EINVAL;
+		}
 		ctx->hflip = ctrl->val;
 		break;
 	case V4L2_CID_VFLIP:
+		printk(KERN_INFO "V4L2_CID_VFLIP: %i -> %i\n", ctx->vflip, ctrl->val);
+		if (ctx->dither_down_enable){
+			// dither does not work with h/v-flip
+			spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
+			return -EINVAL;
+		}
 		ctx->vflip = ctrl->val;
 		break;
 	case V4L2_CID_ROTATE:
+		if (ctx->dither_down_enable){
+			// dither does not work with rotation
+			spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
+			return -EINVAL;
+		}
 		ctx->rotate = ctrl->val;
 		break;
 	case V4L2_CID_BG_COLOR:
 		ctx->fill_color = ctrl->val;
 		break;
+	case RGA_V4L2_CID_Y400_ENABLE:
+		printk(KERN_INFO "RGA_V4L2_CID_Y400_ENABLE: %i -> %i\n", ctx->yuv400_enable, ctrl->val);
+		ctx->yuv400_enable = ctrl->val;
+		break;
+	case RGA_V4L2_CID_DITHER_DOWN_ENABLE:
+		printk(KERN_INFO "RGA_V4L2_CID_DITHER_DOWN_ENABLE: %i -> %i\n", ctx->dither_down_enable, ctrl->val);
+		if (ctx->vflip || ctx->hflip || ctx->rotate != 0){
+			// dither does not work with rotation or flips
+			spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
+			return -EINVAL;
+		}
+		ctx->dither_down_enable = ctrl->val;
+		break;
+	case RGA_V4L2_CID_DITHER_DOWN_MODE:
+		printk(KERN_INFO "RGA_V4L2_CID_DITHER_DOWN_MODE: %i->%i\n", ctx->dither_down_mode, ctrl->val);
+		ctx->dither_down_mode = ctrl->val;
+		break;
+	case RGA_V4L2_CID_Y4_ENABLE:
+		printk(KERN_INFO "RGA_V4L2_CID_Y4_ENABLE: %u\n", ctrl->val);
+		ctx->y4_enable = ctrl->val;
+		break;
+	case RGA_V4L2_CID_Y4MAP_LUT0:
+		printk(KERN_INFO "RGA_V4L2_CID_Y4MAP_LUT0: %u\n", ctrl->val);
+		ctx->y4map_lut0 = ctrl->val;
+		break;
+	case RGA_V4L2_CID_Y4MAP_LUT1:
+		printk(KERN_INFO "RGA_V4L2_CID_Y4MAP_LUT1: %u\n", ctrl->val);
+		ctx->y4map_lut1 = ctrl->val;
+		break;
 	}
 	spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags);
 	return 0;
@@ -155,11 +201,78 @@ static const struct v4l2_ctrl_ops rga_ctrl_ops = {
 	.s_ctrl = rga_s_ctrl,
 };
 
+
+static const struct v4l2_ctrl_config ctrl_y400_enable = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_Y400_ENABLE,
+	.name = "Enable Y400 conversion",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 1,
+	.step = 1,
+	.def = 0,
+};
+
+static const struct v4l2_ctrl_config ctrl_dithering_down_enable = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_DITHER_DOWN_ENABLE,
+	.name = "Enable dither down",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 1,
+	.step = 1,
+	.def = 0,
+};
+
+static const struct v4l2_ctrl_config ctrl_dithering_down_mode = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_DITHER_DOWN_MODE,
+	.name = "Dither down mode",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 4,
+	.step = 1,
+	.def = 0,
+};
+
+static const struct v4l2_ctrl_config ctrl_y4_enable = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_Y4_ENABLE,
+	.name = "Enable Y4 conversion",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 1,
+	.step = 1,
+	.def = 0,
+};
+
+static const struct v4l2_ctrl_config ctrl_y4_map_lut0 = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_Y4MAP_LUT0,
+	.name = "Set Y4MAP LUT0",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 0xffffffff,
+	.step = 1,
+	.def = 0x76543210,
+};
+
+static const struct v4l2_ctrl_config ctrl_y4_map_lut1 = {
+	.ops = &rga_ctrl_ops,
+	.id = RGA_V4L2_CID_Y4MAP_LUT1,
+	.name = "Set Y4MAP LUT1",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.min = 0,
+	.max = 0xffffffff,
+	.step = 1,
+	.def = 0xfedcba98,
+};
+
 static int rga_setup_ctrls(struct rga_ctx *ctx)
 {
 	struct rockchip_rga *rga = ctx->rga;
 
-	v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4);
+	v4l2_ctrl_handler_init(&ctx->ctrl_handler, 10);
 
 	v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
 			  V4L2_CID_HFLIP, 0, 1, 1, 0);
@@ -173,6 +286,13 @@ static int rga_setup_ctrls(struct rga_ctx *ctx)
 	v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops,
 			  V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0);
 
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y400_enable, NULL);
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_dithering_down_enable, NULL);
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_dithering_down_mode, NULL);
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_enable, NULL);
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_map_lut0, NULL);
+	v4l2_ctrl_new_custom(&ctx->ctrl_handler, &ctrl_y4_map_lut1, NULL);
+
 	if (ctx->ctrl_handler.error) {
 		int err = ctx->ctrl_handler.error;
 
@@ -311,6 +431,17 @@ static struct rga_fmt formats[] = {
 		.y_div = 2,
 		.x_div = 2,
 	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y4,
+		.color_swap = RGA_COLOR_NONE_SWAP,
+		/* hard will switch from yuv420P to yuv400 when y4 conversion is
+		 * activated using the relevant registers*/
+		.hw_format = RGA_COLOR_FMT_YUV420P,
+		.depth = 4,
+		.uv_factor = 1,
+		.y_div = 1,
+		.x_div = 1,
+	},
 	{
 		.fourcc = V4L2_PIX_FMT_YUV422P,
 		.color_swap = RGA_COLOR_NONE_SWAP,
@@ -959,6 +1090,9 @@ static const struct of_device_id rockchip_rga_match[] = {
 	{
 		.compatible = "rockchip,rk3399-rga",
 	},
+	{
+		.compatible = "rockchip,rk356x-rga",
+	},
 	{},
 };
 
diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h
index 72a28b120..dccec0405 100644
--- a/drivers/media/platform/rockchip/rga/rga.h
+++ b/drivers/media/platform/rockchip/rga/rga.h
@@ -12,6 +12,14 @@
 #include <media/v4l2-device.h>
 
 #define RGA_NAME "rockchip-rga"
+// define custom v4l2 control ids so we can control the Y4/dithering options
+// via ioctl
+#define RGA_V4L2_CID_Y400_ENABLE (V4L2_CID_USER_RGA_BASE + 0)
+#define RGA_V4L2_CID_DITHER_DOWN_ENABLE (V4L2_CID_USER_RGA_BASE + 1)
+#define RGA_V4L2_CID_DITHER_DOWN_MODE (V4L2_CID_USER_RGA_BASE + 2)
+#define RGA_V4L2_CID_Y4_ENABLE (V4L2_CID_USER_RGA_BASE + 3)
+#define RGA_V4L2_CID_Y4MAP_LUT0 (V4L2_CID_USER_RGA_BASE + 4)
+#define RGA_V4L2_CID_Y4MAP_LUT1 (V4L2_CID_USER_RGA_BASE + 5)
 
 struct rga_fmt {
 	u32 fourcc;
@@ -66,6 +74,13 @@ struct rga_ctx {
 	u32 vflip;
 	u32 rotate;
 	u32 fill_color;
+
+	u32 y4map_lut0;
+	u32 y4map_lut1;
+	u32 yuv400_enable;
+	u32 y4_enable;
+	u32 dither_down_enable;
+	u32 dither_down_mode;
 };
 
 static inline struct rga_ctx *file_to_rga_ctx(struct file *filp)
diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c
index 554c591e1..f0279e86c 100644
--- a/drivers/media/v4l2-core/v4l2-common.c
+++ b/drivers/media/v4l2-core/v4l2-common.c
@@ -305,6 +305,7 @@ const struct v4l2_format_info *v4l2_format_info(u32 format)
 		{ .format = V4L2_PIX_FMT_YVU420,  .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
 		{ .format = V4L2_PIX_FMT_YUV422P, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 3, .bpp = { 1, 1, 1, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 },
 		{ .format = V4L2_PIX_FMT_GREY,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
+		{ .format = V4L2_PIX_FMT_Y4,    .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 },
 
 		/* Tiled YUV formats */
 		{ .format = V4L2_PIX_FMT_NV12_4L4, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 1, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 },
diff --git a/drivers/regulator/tps65185-regulator.c b/drivers/regulator/tps65185-regulator.c
new file mode 100644
index 000000000..a9de615e0
--- /dev/null
+++ b/drivers/regulator/tps65185-regulator.c
@@ -0,0 +1,782 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
+//
+
+#include <linux/completion.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_event.h>
+#include <linux/regmap.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/driver.h>
+
+#define TPS65185_REG_TMST_VALUE		0x00
+#define TPS65185_REG_ENABLE		0x01
+#define TPS65185_REG_VADJ		0x02
+#define TPS65185_REG_VCOM1		0x03
+#define TPS65185_REG_VCOM2		0x04
+#define TPS65185_REG_INT_EN1		0x05
+#define TPS65185_REG_INT_EN2		0x06
+#define TPS65185_REG_INT1		0x07
+#define TPS65185_REG_INT2		0x08
+#define TPS65185_REG_UPSEQ0		0x09
+#define TPS65185_REG_UPSEQ1		0x0a
+#define TPS65185_REG_DWNSEQ0		0x0b
+#define TPS65185_REG_DWNSEQ1		0x0c
+#define TPS65185_REG_TMST1		0x0d
+#define TPS65185_REG_TMST2		0x0e
+#define TPS65185_REG_PG			0x0f
+#define TPS65185_REG_REVID		0x10
+
+#define TPS65185_ENABLE_ACTIVE		BIT(7)
+#define TPS65185_ENABLE_STANDBY		BIT(6)
+#define TPS65185_ENABLE_V3P3_EN		BIT(5)
+#define TPS65185_ENABLE_VCOM_EN		BIT(4)
+#define TPS65185_ENABLE_VDDH_EN		BIT(3)
+#define TPS65185_ENABLE_VPOS_EN		BIT(2)
+#define TPS65185_ENABLE_VEE_EN		BIT(1)
+#define TPS65185_ENABLE_VNEG_EN		BIT(0)
+
+#define TPS65185_VADJ_VSET		(0x7 << 0)
+
+#define TPS65185_VCOM2_VCOM_MSB		BIT(0)
+
+#define TPS65185_INT1_DTX		BIT(7)
+#define TPS65185_INT1_TSD		BIT(6)
+#define TPS65185_INT1_HOT		BIT(5)
+#define TPS65185_INT1_TMST_HOT		BIT(4)
+#define TPS65185_INT1_TMST_COLD		BIT(3)
+#define TPS65185_INT1_UVLO		BIT(2)
+#define TPS65185_INT1_ACQC		BIT(1)
+#define TPS65185_INT1_PRGC		BIT(0)
+
+#define TPS65185_INT2_VB_UV		BIT(7)
+#define TPS65185_INT2_VDDH_UV		BIT(6)
+#define TPS65185_INT2_VN_UV		BIT(5)
+#define TPS65185_INT2_VPOS_UV		BIT(4)
+#define TPS65185_INT2_VEE_UV		BIT(3)
+#define TPS65185_INT2_VCOMF		BIT(2)
+#define TPS65185_INT2_VNEG_UV		BIT(1)
+#define TPS65185_INT2_EOC		BIT(0)
+
+#define TPS65185_TMST1_READ_THERM	BIT(7)
+#define TPS65185_TMST1_DT		(0x3 << 0)
+
+#define TPS65185_TMST2_TMST_COLD	(0xf << 4)
+#define TPS65185_TMST2_TMST_COLD_SHIFT	4
+#define TPS65185_TMST2_TMST_HOT		(0xf << 0)
+#define TPS65185_TMST2_TMST_HOT_SHIFT	0
+
+#define TPS65185_PG_VB_PG		BIT(7)
+#define TPS65185_PG_VDDH_PG		BIT(6)
+#define TPS65185_PG_VN_PG		BIT(5)
+#define TPS65185_PG_VPOS_PG		BIT(4)
+#define TPS65185_PG_VEE_PG		BIT(3)
+#define TPS65185_PG_VNEG_PG		BIT(1)
+
+#define TPS65185_TMST_DT_MIN_CELSIUS	2
+#define TPS65185_TMST_DT_MAX_CELSIUS	5
+
+#define TPS65185_TMST_COLD_MIN_CELSIUS	-7
+#define TPS65185_TMST_HOT_MIN_CELSIUS	42
+
+/*
+ * After waking up from sleep, TPS65185 waits for VN to discharge and all
+ * voltage references to start up before loading settings from EEPROM.
+ * Accessing registers too early after WAKEUP could override the values
+ * stored in the EEPROM with the default values.
+ */
+#define TPS65185_WAKEUP_DELAY_MS	50
+
+enum {
+	TPS65185_REGULATOR_V3P3,
+	TPS65185_REGULATOR_VCOM,
+	TPS65185_REGULATOR_VDRIVE,
+	TPS65185_NUM_REGULATORS
+};
+
+struct tps65185 {
+	struct gpio_desc 	*powerup_gpio;
+	struct gpio_desc	*pwr_good_gpio;
+	struct gpio_desc	*vcom_ctrl_gpio;
+	struct gpio_desc	*wakeup_gpio;
+	struct regmap		*regmap;
+	struct completion	temp_completion;
+	u32			total_up_delay_us;
+	u32			total_down_delay_us;
+	u8			int_en1;
+	u8			int_en2;
+};
+
+static const struct regulator_ops tps65185_v3p3_ops = {
+	.enable			= regulator_enable_regmap,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+};
+
+static const struct regulator_desc tps65185_v3p3_desc = {
+	.name			= "v3p3",
+	.supply_name		= "vin3p3",
+	.of_match		= "v3p3",
+	.regulators_node	= "regulators",
+	.ops			= &tps65185_v3p3_ops,
+	.owner			= THIS_MODULE,
+	.enable_reg		= TPS65185_REG_ENABLE,
+	.enable_mask		= TPS65185_ENABLE_V3P3_EN,
+};
+
+static int tps65185_vcom_set_voltage_sel(struct regulator_dev *rdev,
+					 unsigned int sel)
+{
+	int ret;
+
+	ret = regmap_write(rdev->regmap, TPS65185_REG_VCOM1, sel);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(rdev->regmap, TPS65185_REG_VCOM2,
+				  TPS65185_VCOM2_VCOM_MSB, sel >> 8);
+}
+
+static int tps65185_vcom_get_voltage_sel(struct regulator_dev *rdev)
+{
+	int ret, sel, val;
+
+	ret = regmap_read(rdev->regmap, TPS65185_REG_VCOM1, &sel);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(rdev->regmap, TPS65185_REG_VCOM2, &val);
+	if (ret)
+		return ret;
+
+	sel |= (val & TPS65185_VCOM2_VCOM_MSB) << 8;
+
+	return sel;
+}
+
+static const struct regulator_ops tps65185_vcom_ops = {
+	.enable			= regulator_enable_regmap,
+	.disable		= regulator_disable_regmap,
+	.is_enabled		= regulator_is_enabled_regmap,
+	.set_voltage_sel	= tps65185_vcom_set_voltage_sel,
+	.map_voltage		= regulator_map_voltage_linear,
+	.get_voltage_sel	= tps65185_vcom_get_voltage_sel,
+	.list_voltage		= regulator_list_voltage_linear,
+};
+
+static const struct regulator_desc tps65185_vcom_desc = {
+	.name			= "vcom",
+	.supply_name		= "vin",
+	.of_match		= "vcom",
+	.regulators_node	= "regulators",
+	.ops			= &tps65185_vcom_ops,
+	.owner			= THIS_MODULE,
+	.n_voltages		= 0x200,
+	.min_uV			= 0,
+	.uV_step		= 10000,
+	.enable_reg		= TPS65185_REG_ENABLE,
+	.enable_mask		= TPS65185_ENABLE_VCOM_EN,
+};
+
+static int tps65185_vdrive_enable_time(struct regulator_dev *rdev)
+{
+	struct tps65185 *tps = rdev_get_drvdata(rdev);
+
+	return tps->total_down_delay_us + tps->total_up_delay_us + 50000 + 1000000;
+}
+
+static int tps65185_vdrive_get_status(struct regulator_dev *rdev)
+{
+	struct tps65185 *tps = rdev_get_drvdata(rdev);
+	int mask, ret, val;
+
+	/* Are all rails reporting power good? */
+	if (tps->pwr_good_gpio) {
+		ret = gpiod_get_value_cansleep(tps->pwr_good_gpio);
+		if (ret == 1)
+			return REGULATOR_STATUS_ON;
+	} else {
+		ret = regmap_read(rdev->regmap, TPS65185_REG_PG, &val);
+		if (ret)
+			return ret;
+
+		mask = TPS65185_PG_VDDH_PG | TPS65185_PG_VPOS_PG |
+		       TPS65185_PG_VEE_PG  | TPS65185_PG_VNEG_PG;
+		if ((val & mask) == mask)
+			return REGULATOR_STATUS_ON;
+	}
+
+	ret = regmap_read(rdev->regmap, TPS65185_REG_ENABLE, &val);
+	if (ret)
+		return ret;
+
+	/* Are all rails disabled? */
+	mask = TPS65185_ENABLE_VDDH_EN | TPS65185_ENABLE_VPOS_EN |
+	       TPS65185_ENABLE_VEE_EN  | TPS65185_ENABLE_VNEG_EN;
+	if (!(val & mask))
+		return REGULATOR_STATUS_OFF;
+
+	/* Any other combination is an error. */
+	return REGULATOR_STATUS_ERROR;
+}
+
+static const struct regulator_ops tps65185_vdrive_ops = {
+	.enable			= regulator_enable_regmap,
+	.disable		= regulator_disable_regmap,
+	.set_voltage_sel	= regulator_set_voltage_sel_regmap,
+	.map_voltage		= regulator_map_voltage_iterate,
+	.get_voltage_sel	= regulator_get_voltage_sel_regmap,
+	.list_voltage		= regulator_list_voltage_table,
+	.enable_time		= tps65185_vdrive_enable_time,
+	.get_status		= tps65185_vdrive_get_status,
+};
+
+static const struct regulator_desc tps65185_vdrive_desc = {
+	.name			= "vdrive",
+	.supply_name		= "vin",
+	.of_match		= "vdrive",
+	.regulators_node	= "regulators",
+	.ops			= &tps65185_vdrive_ops,
+	.owner			= THIS_MODULE,
+	.n_voltages		= 7,
+	.linear_min_sel		= 3,
+	.volt_table		= (const unsigned int [7]) {
+		0, 0, 0,
+		15000000, 14750000, 14500000, 14250000
+	},
+	.vsel_reg		= TPS65185_REG_VADJ,
+	.vsel_mask		= TPS65185_VADJ_VSET,
+	.enable_reg		= TPS65185_REG_ENABLE,
+	.enable_mask		= TPS65185_ENABLE_ACTIVE |
+				  TPS65185_ENABLE_STANDBY,
+	.enable_val		= TPS65185_ENABLE_ACTIVE,
+	.disable_val		= TPS65185_ENABLE_STANDBY,
+	.poll_enabled_time	= 2500,
+};
+
+static const struct iio_event_spec tps65185_iio_temp_events[] = {
+	{
+		.type			= IIO_EV_TYPE_THRESH_ADAPTIVE,
+		.dir			= IIO_EV_DIR_EITHER,
+		.mask_separate		= BIT(IIO_EV_INFO_VALUE) |
+					  BIT(IIO_EV_INFO_ENABLE),
+	},
+};
+
+static const struct iio_chan_spec tps65185_iio_channels[] = {
+	{
+		.type			= IIO_TEMP,
+		.info_mask_separate	= BIT(IIO_CHAN_INFO_PROCESSED),
+		.event_spec		= tps65185_iio_temp_events,
+		.num_event_specs	= ARRAY_SIZE(tps65185_iio_temp_events),
+	},
+};
+
+static int tps65185_iio_read_temp(struct tps65185 *tps)
+{
+	reinit_completion(&tps->temp_completion);
+
+	return regmap_update_bits(tps->regmap, TPS65185_REG_TMST1,
+				  TPS65185_TMST1_READ_THERM,
+				  TPS65185_TMST1_READ_THERM);
+}
+
+static int tps65185_iio_read_raw(struct iio_dev *indio_dev,
+				 const struct iio_chan_spec *chan,
+				 int *val, int *val2, long mask)
+{
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	unsigned int temp;
+	int ret;
+
+	if (mask != IIO_CHAN_INFO_PROCESSED)
+		return -EINVAL;
+
+	ret = tps65185_iio_read_temp(tps);
+	if (ret)
+		return ret;
+
+	if (!wait_for_completion_timeout(&tps->temp_completion,
+					 msecs_to_jiffies(20)))
+		return -ETIMEDOUT;
+
+	ret = regmap_read(tps->regmap, TPS65185_REG_TMST_VALUE, &temp);
+	if (ret)
+		return ret;
+
+	*val = sign_extend32(temp, 7) * 1000;
+
+	return IIO_VAL_INT;
+}
+
+static int tps65185_read_event_config(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan,
+				      enum iio_event_type type,
+				      enum iio_event_direction dir)
+{
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	u8 mask = TPS65185_INT1_DTX;
+
+	return !!(tps->int_en1 & mask);
+}
+
+static int tps65185_write_event_config(struct iio_dev *indio_dev,
+				       const struct iio_chan_spec *chan,
+				       enum iio_event_type type,
+				       enum iio_event_direction dir, bool state)
+{
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	u8 mask = TPS65185_INT1_DTX;
+
+	if (state)
+		tps->int_en1 |= mask;
+	else
+		tps->int_en1 &= ~mask;
+
+	return regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1);
+}
+
+static int tps65185_read_event_value(struct iio_dev *indio_dev,
+				     const struct iio_chan_spec *chan,
+				     enum iio_event_type type,
+				     enum iio_event_direction dir,
+				     enum iio_event_info info,
+				     int *val, int *val2)
+{
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	unsigned int dt;
+	int ret;
+
+	ret = regmap_read(tps->regmap, TPS65185_REG_TMST1, &dt);
+	if (ret)
+		return ret;
+
+	*val = TPS65185_TMST_DT_MIN_CELSIUS + (dt & TPS65185_TMST1_DT);
+
+	return IIO_VAL_INT;
+}
+
+static int tps65185_write_event_value(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan,
+				      enum iio_event_type type,
+				      enum iio_event_direction dir,
+				      enum iio_event_info info,
+				      int val, int val2)
+{
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	unsigned int dt;
+
+	if (val < TPS65185_TMST_DT_MIN_CELSIUS ||
+	    val > TPS65185_TMST_DT_MAX_CELSIUS)
+		return -EINVAL;
+
+	dt = val - TPS65185_TMST_DT_MIN_CELSIUS;
+
+	return regmap_update_bits(tps->regmap, TPS65185_REG_TMST1,
+				  TPS65185_TMST1_DT, dt);
+}
+
+static const struct iio_info tps65185_iio_info = {
+	.read_raw		= tps65185_iio_read_raw,
+	.read_event_config	= tps65185_read_event_config,
+	.write_event_config	= tps65185_write_event_config,
+	.read_event_value	= tps65185_read_event_value,
+	.write_event_value	= tps65185_write_event_value,
+};
+
+static irqreturn_t tps65185_iio_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+
+	tps65185_iio_read_temp(tps);
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static void tps65185_iio_triggered_event_cleanup(void *indio_dev)
+{
+	iio_triggered_event_cleanup(indio_dev);
+}
+
+static irqreturn_t tps65185_irq_handler(int irq, void *p)
+{
+	struct iio_dev *indio_dev = p;
+	struct tps65185 *tps = iio_device_get_drvdata(indio_dev);
+	unsigned int status1, status2;
+	int ret;
+
+	ret = regmap_read(tps->regmap, TPS65185_REG_INT1, &status1);
+	if (!ret && (status1 & TPS65185_INT1_DTX))
+		iio_push_event(indio_dev,
+			       IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0,
+						    IIO_EV_TYPE_THRESH_ADAPTIVE,
+						    IIO_EV_DIR_EITHER),
+			       iio_get_time_ns(indio_dev));
+
+	if (!ret && (status1 & TPS65185_INT1_TSD))
+		printk(KERN_ERR "tps65185 TSD Thermal shutdown interrupt detected");
+	if (!ret && (status1 & TPS65185_INT1_HOT))
+		printk(KERN_ERR "tps65185 HOT Thermal shutdown early warning interrupt detected");
+	if (!ret && (status1 & TPS65185_INT1_TMST_HOT))
+		printk(KERN_ERR "tps65185 TMST HOT Thermistor hot interrupt detected");
+	if (!ret && (status1 & TPS65185_INT1_TMST_COLD))
+		printk(KERN_ERR "tps65185 TMST COLD Thermistor hot interrupt detected");
+	if (!ret && (status1 & TPS65185_INT1_UVLO))
+		printk(KERN_ERR "tps65185 UVLO VIN under voltage detect interrupt detected");
+	// ACQC INT
+	// PRGC Int
+
+	ret = regmap_read(tps->regmap, TPS65185_REG_INT2, &status2);
+
+	if (!ret && (status2 & TPS65185_INT2_VB_UV))
+		printk(KERN_ERR "tps65185 Positive Boost Converter (VB) undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_VDDH_UV))
+		printk(KERN_ERR "tps65185 VDDH undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_VN_UV))
+		printk(KERN_ERR "tps65185 Inverting Boost Converter (VB) undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_VPOS_UV))
+		printk(KERN_ERR "tps65185 VPOS undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_VEE_UV))
+		printk(KERN_ERR "tps65185 VEE undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_VCOMF))
+		printk(KERN_ERR "tps65185 VCOM fault detection");
+	if (!ret && (status2 & TPS65185_INT2_VNEG_UV))
+		printk(KERN_ERR "tps65185 VNEG undervoltage detected");
+	if (!ret && (status2 & TPS65185_INT2_EOC))
+		complete(&tps->temp_completion);
+
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_config tps65185_regmap_config = {
+	.reg_bits		= 8,
+	.val_bits		= 8,
+	.max_register		= TPS65185_REG_REVID,
+};
+
+static const struct {
+	u8		revid;
+	const char	*name;
+} tps65185_revisions[] = {
+	{ 0x45, "TPS65185 r1p0" },
+	{ 0x55, "TPS65185 r1p1" },
+	{ 0x65, "TPS65185 r1p2" },
+	{ 0x66, "TPS651851 r1p0" },
+};
+
+static const u8 tps65185_up_delay_units[] = { 3, 3, 3, 3 };
+static const u8 tps65185_down_delay_units[] = { 6, 6, 6, 2 };
+
+static int tps65185_set_delay(struct device *dev, const char *propname,
+			      struct regmap *regmap, unsigned int reg,
+			      const u8 *units, u32 *total)
+{
+	u32 delay[4];
+	int ret;
+
+	ret = of_property_read_u32_array(dev->of_node, propname, delay, 4);
+	if (ret)
+		return ret;
+
+	*total = (delay[0] + delay[1] + delay[2] + delay[3]) * USEC_PER_MSEC;
+
+	return regmap_write(regmap, reg,
+			    ((delay[0] / units[0] - 1) & 0x3) << 0 |
+			    ((delay[1] / units[1] - 1) & 0x3) << 2 |
+			    ((delay[2] / units[2] - 1) & 0x3) << 4 |
+			    ((delay[3] / units[3] - 1) & 0x3) << 6);
+}
+
+static int tps65185_set_sequence(struct device *dev, const char *propname,
+				 struct regmap *regmap, unsigned int reg)
+{
+	u32 sequence[4];
+	int ret;
+
+	ret = of_property_read_u32_array(dev->of_node, propname, sequence, 4);
+	if (ret)
+		return ret;
+
+	return regmap_write(regmap, reg,
+			    (sequence[0] & 0x3) << 0 |
+			    (sequence[1] & 0x3) << 2 |
+			    (sequence[2] & 0x3) << 4 |
+			    (sequence[3] & 0x3) << 6);
+}
+
+static int tps65185_set_config(struct device *dev, struct tps65185 *tps)
+{
+	int ret;
+
+	ret = tps65185_set_sequence(dev, "ti,up-sequence",
+				    tps->regmap, TPS65185_REG_UPSEQ0);
+	if (ret)
+		return ret;
+
+	ret = tps65185_set_delay(dev, "ti,up-delay-ms",
+				 tps->regmap, TPS65185_REG_UPSEQ1,
+				 tps65185_up_delay_units,
+				 &tps->total_up_delay_us);
+	if (ret)
+		return ret;
+
+	ret = tps65185_set_sequence(dev, "ti,down-sequence",
+				    tps->regmap, TPS65185_REG_DWNSEQ0);
+	if (ret)
+		return ret;
+
+	ret = tps65185_set_delay(dev, "ti,down-delay-ms",
+				 tps->regmap, TPS65185_REG_DWNSEQ1,
+				 tps65185_down_delay_units,
+				 &tps->total_down_delay_us);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int tps65185_probe(struct i2c_client *client)
+{
+	struct regulator_config config = {};
+	struct device *dev = &client->dev;
+	const char *revision = "unknown";
+	struct iio_dev *indio_dev;
+	struct tps65185 *tps;
+	int i, ret, revid;
+
+	tps = devm_kzalloc(dev, sizeof(*tps), GFP_KERNEL);
+	if (!tps)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, tps);
+	init_completion(&tps->temp_completion);
+
+	/*
+	 * Drive POWERUP and VCOM_CTRL low before driving WAKEUP high to avoid
+	 * powering up the output rails.
+	 */
+	tps->powerup_gpio = devm_gpiod_get_optional(dev, "powerup", GPIOD_OUT_LOW);
+	if (IS_ERR(tps->powerup_gpio))
+		return dev_err_probe(dev, PTR_ERR(tps->powerup_gpio),
+				     "Failed to get %s GPIO\n", "powerup");
+
+	tps->pwr_good_gpio = devm_gpiod_get_optional(dev, "pwr_good", GPIOD_IN);
+	if (IS_ERR(tps->pwr_good_gpio))
+		return dev_err_probe(dev, PTR_ERR(tps->pwr_good_gpio),
+				     "Failed to get %s GPIO\n", "pwr_good");
+
+	tps->vcom_ctrl_gpio = devm_gpiod_get_optional(dev, "vcom_ctrl", GPIOD_OUT_LOW);
+	if (IS_ERR(tps->vcom_ctrl_gpio))
+		return dev_err_probe(dev, PTR_ERR(tps->vcom_ctrl_gpio),
+				     "Failed to get %s GPIO\n", "vcom_ctrl");
+
+	tps->wakeup_gpio = devm_gpiod_get(dev, "wakeup", GPIOD_OUT_HIGH);
+	if (IS_ERR(tps->wakeup_gpio))
+		return dev_err_probe(dev, PTR_ERR(tps->wakeup_gpio),
+				     "Failed to get %s GPIO\n", "wakeup");
+
+	msleep(TPS65185_WAKEUP_DELAY_MS);
+
+	tps->regmap = devm_regmap_init_i2c(client, &tps65185_regmap_config);
+	if (IS_ERR(tps->regmap))
+		return PTR_ERR(tps->regmap);
+
+	ret = regmap_read(tps->regmap, TPS65185_REG_REVID, &revid);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(tps65185_revisions); ++i) {
+		if (revid == tps65185_revisions[i].revid) {
+			revision = tps65185_revisions[i].name;
+			break;
+		}
+	}
+	dev_info(dev, "detected %s\n", revision);
+
+	ret = tps65185_set_config(dev, tps);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to set config\n");
+
+	/* Power down all rails, but enable control by the powerup GPIO. */
+	ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE,
+			   TPS65185_ENABLE_STANDBY |
+			   TPS65185_ENABLE_VDDH_EN |
+			   TPS65185_ENABLE_VPOS_EN |
+			   TPS65185_ENABLE_VEE_EN |
+			   TPS65185_ENABLE_VNEG_EN);
+	if (ret)
+		return ret;
+
+	config.dev = dev;
+	config.driver_data = tps;
+	config.regmap = tps->regmap;
+
+	for (i = TPS65185_REGULATOR_V3P3; i < TPS65185_NUM_REGULATORS; ++i) {
+		const struct regulator_desc *rdesc;
+		struct regulator_dev *rdev;
+
+		switch (i) {
+		case TPS65185_REGULATOR_V3P3:
+			rdesc = &tps65185_v3p3_desc;
+			config.ena_gpiod = NULL;
+			break;
+		case TPS65185_REGULATOR_VCOM:
+			rdesc = &tps65185_vcom_desc;
+			config.ena_gpiod = tps->vcom_ctrl_gpio;
+			break;
+		case TPS65185_REGULATOR_VDRIVE:
+			rdesc = &tps65185_vdrive_desc;
+			config.ena_gpiod = tps->powerup_gpio;
+			break;
+		}
+
+		rdev = devm_regulator_register(dev, rdesc, &config);
+		if (IS_ERR(rdev))
+			return dev_err_probe(dev, PTR_ERR(rdev),
+					     "Failed to register %s\n",
+					     rdesc->name);
+
+		devm_gpiod_unhinge(dev, config.ena_gpiod);
+	}
+
+	indio_dev = devm_iio_device_alloc(dev, 0);
+	if (!indio_dev)
+		return -ENOMEM;
+
+	iio_device_set_drvdata(indio_dev, tps);
+
+	indio_dev->modes	= INDIO_DIRECT_MODE;
+	indio_dev->channels	= tps65185_iio_channels;
+	indio_dev->num_channels	= ARRAY_SIZE(tps65185_iio_channels);
+	indio_dev->name		= dev_name(dev);
+	indio_dev->info		= &tps65185_iio_info;
+
+	ret = iio_triggered_event_setup(indio_dev, NULL,
+					tps65185_iio_trigger_handler);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to add IIO trigger\n");
+
+	ret = devm_add_action_or_reset(dev, tps65185_iio_triggered_event_cleanup,
+				       indio_dev);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to register IIO device\n");
+
+	// disable all INT_EN1 interrupts
+	tps->int_en1 = 0xff;
+	ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1);
+	if (ret)
+		return ret;
+
+	//tps->int_en2 = TPS65185_INT2_EOC;
+	tps->int_en2 = 0xff;
+	ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN2, tps->int_en2);
+	if (ret)
+		return ret;
+
+	ret = devm_request_threaded_irq(dev, client->irq, NULL,
+					tps65185_irq_handler, IRQF_ONESHOT,
+					dev_name(dev), indio_dev);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+	return 0;
+}
+
+static int __maybe_unused tps65185_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct tps65185 *tps;
+	int ret;
+
+	tps = i2c_get_clientdata(client);
+	// set STANDBY of ENABLE reg to 1
+	// pull PWRUP pin low (falling edge)
+	// -> will go into either STANDBY or SLEEP, depending on WAKEUP pin
+	// we want STANDBY, so WAKUP=1
+	gpiod_set_value(tps->wakeup_gpio, 1);
+
+	// initiate standby
+	ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE,
+			TPS65185_ENABLE_STANDBY);
+	if (ret)
+		printk(KERN_ERR "tps65185 suspend: could not set standby bit");
+
+	return 0;
+}
+
+static int __maybe_unused tps65185_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	struct tps65185 *tps;
+	int ret;
+
+	tps = i2c_get_clientdata(client);
+
+	// todo: is the location correct here?
+	msleep(TPS65185_WAKEUP_DELAY_MS);
+
+	ret = tps65185_set_config(dev, tps);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to set config at resume\n");
+
+	// disable all INT_EN1 interrupts
+	tps->int_en1 = 0xff;
+	ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN1, tps->int_en1);
+	if (ret)
+		return ret;
+
+	// only enable temperature-is-ready irq
+	tps->int_en2 = 0xff;
+	ret = regmap_write(tps->regmap, TPS65185_REG_INT_EN2, tps->int_en2);
+	if (ret)
+		return ret;
+
+	// If I understand correctly regulator TPS65185_REGULATOR_VDRIVE is tied to
+	// the powerup pin - therefore when the ebc resumes the tps65185 is
+	// directly activated. I think its correct to do nothing here and wait.
+	// Similar to the probe function which also puts the pmic into standby
+	/* Power down all rails, but enable control by the powerup GPIO. */
+	ret = regmap_write(tps->regmap, TPS65185_REG_ENABLE,
+			   TPS65185_ENABLE_STANDBY |
+			   TPS65185_ENABLE_VDDH_EN |
+			   TPS65185_ENABLE_VPOS_EN |
+			   TPS65185_ENABLE_VEE_EN |
+			   TPS65185_ENABLE_VNEG_EN);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(tps65185_pm, tps65185_suspend, tps65185_resume);
+
+static const struct of_device_id tps65185_of_match[] = {
+	{ .compatible = "ti,tps65185" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, tps65185_of_match);
+
+static struct i2c_driver tps65185_driver = {
+	.probe	= tps65185_probe,
+	.driver		= {
+		.name		= "tps65185",
+		.pm		= &tps65185_pm,
+		.of_match_table	= tps65185_of_match,
+	},
+};
+module_i2c_driver(tps65185_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("TPS65185 PMIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/soc/rockchip/Kconfig b/drivers/soc/rockchip/Kconfig
index 785f60c6f..17ccb0fb1 100644
--- a/drivers/soc/rockchip/Kconfig
+++ b/drivers/soc/rockchip/Kconfig
@@ -30,4 +30,10 @@ config ROCKCHIP_DTPM
 	  on this platform. That will create all the power capping capable
 	  devices.
 
+config ROCKCHIP_SUSPEND_MODE
+	tristate "Rockchip suspend mode config"
+	depends on ROCKCHIP_SIP
+	help
+	  Say Y here if you want to set the suspend mode to the ATF.
+
 endif
diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile
index 23d414433..d62676283 100644
--- a/drivers/soc/rockchip/Makefile
+++ b/drivers/soc/rockchip/Makefile
@@ -5,3 +5,4 @@
 obj-$(CONFIG_ROCKCHIP_GRF) += grf.o
 obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o
 obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o
+obj-$(CONFIG_ROCKCHIP_SUSPEND_MODE) += rockchip_pm_config.o
diff --git a/drivers/soc/rockchip/rockchip_pm_config.c b/drivers/soc/rockchip/rockchip_pm_config.c
new file mode 100644
index 000000000..655241816
--- /dev/null
+++ b/drivers/soc/rockchip/rockchip_pm_config.c
@@ -0,0 +1,291 @@
+/*
+ * Rockchip Generic power configuration support.
+ *
+ * Copyright (c) 2017 ROCKCHIP, Co. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/bitops.h>
+#include <linux/cpu.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/rockchip/rockchip_sip.h>
+#include <linux/suspend.h>
+#include <dt-bindings/input/input.h>
+#include <../drivers/regulator/internal.h>
+
+#define PM_INVALID_GPIO			0xffff
+#define MAX_ON_OFF_REG_NUM		30
+#define MAX_ON_OFF_REG_PROP_NAME_LEN	60
+
+enum rk_pm_state {
+	RK_PM_MEM = 0,
+	RK_PM_MEM_LITE,
+	RK_PM_MEM_ULTRA,
+	RK_PM_STATE_MAX
+};
+
+static struct rk_on_off_regulator_list {
+	struct regulator_dev *on_reg_list[MAX_ON_OFF_REG_NUM];
+	struct regulator_dev *off_reg_list[MAX_ON_OFF_REG_NUM];
+} on_off_regs_list[RK_PM_STATE_MAX];
+
+static const struct of_device_id pm_match_table[] = {
+	{ .compatible = "rockchip,pm-px30",},
+	{ .compatible = "rockchip,pm-rk1808",},
+	{ .compatible = "rockchip,pm-rk322x",},
+	{ .compatible = "rockchip,pm-rk3288",},
+	{ .compatible = "rockchip,pm-rk3308",},
+	{ .compatible = "rockchip,pm-rk3328",},
+	{ .compatible = "rockchip,pm-rk3368",},
+	{ .compatible = "rockchip,pm-rk3399",},
+	{ .compatible = "rockchip,pm-rk3568",},
+	{ .compatible = "rockchip,pm-rv1126",},
+	{ },
+};
+
+static void rockchip_pm_virt_pwroff_prepare(void)
+{
+	int error;
+
+	regulator_suspend_prepare(PM_SUSPEND_MEM);
+
+	error = suspend_disable_secondary_cpus();
+	if (error) {
+		pr_err("Disable nonboot cpus failed!\n");
+		return;
+	}
+
+	sip_smc_set_suspend_mode(VIRTUAL_POWEROFF, 0, 1);
+	sip_smc_virtual_poweroff();
+}
+
+static int parse_on_off_regulator(struct device_node *node, enum rk_pm_state state)
+{
+	char on_prop_name[MAX_ON_OFF_REG_PROP_NAME_LEN] = {0};
+	char off_prop_name[MAX_ON_OFF_REG_PROP_NAME_LEN] = {0};
+	int i, j;
+	struct device_node *dn;
+	struct regulator_dev *reg;
+	struct regulator_dev **on_list;
+	struct regulator_dev **off_list;
+
+	switch (state) {
+	case RK_PM_MEM:
+		strncpy(on_prop_name, "rockchip,regulator-on-in-mem",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+		strncpy(off_prop_name, "rockchip,regulator-off-in-mem",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+	break;
+
+	case RK_PM_MEM_LITE:
+		strncpy(on_prop_name, "rockchip,regulator-on-in-mem-lite",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+		strncpy(off_prop_name, "rockchip,regulator-off-in-mem-lite",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+	break;
+
+	case RK_PM_MEM_ULTRA:
+		strncpy(on_prop_name, "rockchip,regulator-on-in-mem-ultra",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+		strncpy(off_prop_name, "rockchip,regulator-off-in-mem-ultra",
+			MAX_ON_OFF_REG_PROP_NAME_LEN);
+	break;
+
+	default:
+		return 0;
+	}
+
+	on_list = on_off_regs_list[state].on_reg_list;
+	off_list = on_off_regs_list[state].off_reg_list;
+
+	// if (of_find_property(node, on_prop_name, NULL)) {
+	// 	for (i = 0, j = 0;
+	// 	     (dn = of_parse_phandle(node, on_prop_name, i));
+	// 	     i++) {
+	// 		reg = of_find_regulator_by_node(dn);
+	// 		if (reg == NULL) {
+	// 			pr_warn("failed to find regulator %s for %s\n",
+	// 				dn->name, on_prop_name);
+	// 		} else {
+	// 			pr_debug("%s on regulator=%s\n", __func__,
+	// 				 reg->desc->name);
+	// 			on_list[j++] = reg;
+	// 		}
+	// 		of_node_put(dn);
+
+	// 		if (j >= MAX_ON_OFF_REG_NUM)
+	// 			return 0;
+	// 	}
+	// }
+
+	// if (of_find_property(node, off_prop_name, NULL)) {
+	// 	for (i = 0, j = 0;
+	// 	     (dn = of_parse_phandle(node, off_prop_name, i));
+	// 	     i++) {
+	// 		reg = of_find_regulator_by_node(dn);
+	// 		if (reg == NULL) {
+	// 			pr_warn("failed to find regulator %s for %s\n",
+	// 				dn->name, off_prop_name);
+	// 		} else {
+	// 			pr_debug("%s off regulator=%s\n", __func__,
+	// 				 reg->desc->name);
+	// 			off_list[j++] = reg;
+	// 		}
+	// 		of_node_put(dn);
+
+	// 		if (j >= MAX_ON_OFF_REG_NUM)
+	// 			return 0;
+	// 	}
+	// }
+
+	return 0;
+}
+
+static int pm_config_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *match_id;
+	struct device_node *node;
+	u32 mode_config = 0;
+	u32 wakeup_config = 0;
+	u32 pwm_regulator_config = 0;
+	int gpio_temp[10];
+	u32 sleep_debug_en = 0;
+	u32 apios_suspend = 0;
+	u32 virtual_poweroff_en = 0;
+	/* enum of_gpio_flags flags; */
+	int i = 0;
+	int length;
+
+	match_id = of_match_node(pm_match_table, pdev->dev.of_node);
+	if (!match_id)
+		return -ENODEV;
+
+	node = of_find_node_by_name(NULL, "rockchip-suspend");
+
+	if (IS_ERR_OR_NULL(node)) {
+		dev_err(&pdev->dev, "%s dev node err\n",  __func__);
+		return -ENODEV;
+	}
+
+	if (of_property_read_u32_array(node,
+				       "rockchip,sleep-mode-config",
+				       &mode_config, 1))
+		dev_warn(&pdev->dev, "not set sleep mode config\n");
+	else
+		sip_smc_set_suspend_mode(SUSPEND_MODE_CONFIG, mode_config, 0);
+
+	if (of_property_read_u32_array(node,
+				       "rockchip,wakeup-config",
+				       &wakeup_config, 1))
+		dev_warn(&pdev->dev, "not set wakeup-config\n");
+	else
+		sip_smc_set_suspend_mode(WKUP_SOURCE_CONFIG, wakeup_config, 0);
+
+	if (of_property_read_u32_array(node,
+				       "rockchip,pwm-regulator-config",
+				       &pwm_regulator_config, 1))
+		dev_warn(&pdev->dev, "not set pwm-regulator-config\n");
+	else
+		sip_smc_set_suspend_mode(PWM_REGULATOR_CONFIG,
+					 pwm_regulator_config,
+					 0);
+
+	/* length = of_gpio_named_count(node, "rockchip,power-ctrl"); */
+
+	/* if (length > 0 && length < 10) { */
+	/* 	for (i = 0; i < length; i++) { */
+	/* 		gpio_temp[i] = of_get_named_gpio_flags(node, */
+	/* 						     "rockchip,power-ctrl", */
+	/* 						     i, */
+	/* 						     &flags); */
+	/* 		if (!gpio_is_valid(gpio_temp[i])) */
+	/* 			break; */
+	/* 		sip_smc_set_suspend_mode(GPIO_POWER_CONFIG, */
+	/* 					 i, */
+	/* 					 gpio_temp[i]); */
+	/* 	} */
+	/* } */
+	sip_smc_set_suspend_mode(GPIO_POWER_CONFIG, i, PM_INVALID_GPIO);
+
+	if (!of_property_read_u32_array(node,
+					"rockchip,sleep-debug-en",
+					&sleep_debug_en, 1))
+		sip_smc_set_suspend_mode(SUSPEND_DEBUG_ENABLE,
+					 sleep_debug_en,
+					 0);
+
+	if (!of_property_read_u32_array(node,
+					"rockchip,apios-suspend",
+					&apios_suspend, 1))
+		sip_smc_set_suspend_mode(APIOS_SUSPEND_CONFIG,
+					 apios_suspend,
+					 0);
+
+	/* if (!of_property_read_u32_array(node, */
+	/* 				"rockchip,virtual-poweroff", */
+	/* 				&virtual_poweroff_en, 1) && */
+	/*     virtual_poweroff_en) */
+	/* 	pm_power_off_prepare = rockchip_pm_virt_pwroff_prepare; */
+
+	for (i = RK_PM_MEM; i < RK_PM_STATE_MAX; i++)
+		parse_on_off_regulator(node, i);
+
+	return 0;
+}
+
+static int pm_config_prepare(struct device *dev)
+{
+	int i;
+	suspend_state_t suspend_state = mem_sleep_current;
+	enum rk_pm_state state = suspend_state - PM_SUSPEND_MEM;
+	struct regulator_dev **on_list;
+	struct regulator_dev **off_list;
+
+	sip_smc_set_suspend_mode(LINUX_PM_STATE,
+				 suspend_state,
+				 0);
+
+	if (state >= RK_PM_STATE_MAX)
+		return 0;
+
+	on_list = on_off_regs_list[state].on_reg_list;
+	off_list = on_off_regs_list[state].off_reg_list;
+
+	for (i = 0; i < MAX_ON_OFF_REG_NUM && on_list[i]; i++)
+		regulator_suspend_enable(on_list[i], PM_SUSPEND_MEM);
+
+	for (i = 0; i < MAX_ON_OFF_REG_NUM && off_list[i]; i++)
+		regulator_suspend_disable(off_list[i], PM_SUSPEND_MEM);
+
+	return 0;
+}
+
+static const struct dev_pm_ops rockchip_pm_ops = {
+	.prepare = pm_config_prepare,
+};
+
+static struct platform_driver pm_driver = {
+	.probe = pm_config_probe,
+	.driver = {
+		.name = "rockchip-pm",
+		.of_match_table = pm_match_table,
+		.pm = &rockchip_pm_ops,
+	},
+};
+
+static int __init rockchip_pm_drv_register(void)
+{
+	return platform_driver_register(&pm_driver);
+}
+late_initcall_sync(rockchip_pm_drv_register);
+MODULE_DESCRIPTION("Rockchip suspend mode config");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c
index c8dadb532..658cad85c 100644
--- a/drivers/spi/spi-gpio.c
+++ b/drivers/spi/spi-gpio.c
@@ -6,6 +6,7 @@
  * Copyright (C) 2017 Linus Walleij
  */
 #include <linux/gpio/consumer.h>
+#include <linux/delay.h>
 #include <linux/kernel.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
@@ -85,7 +86,7 @@ static inline int getmiso(const struct spi_device *spi)
  * reaching even one Mbit/sec (except when we can inline bitops), so for now
  * we'll just assume we never need additional per-bit slowdowns.
  */
-#define spidelay(nsecs)	do {} while (0)
+#define spidelay(nsecs)	ndelay(nsecs)
 
 #include "spi-bitbang-txrx.h"
 
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 76521555e..6a765c899 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -223,6 +223,9 @@ config USB_F_PRINTER
 config USB_F_TCM
 	tristate
 
+config USB_F_GUD
+	tristate
+
 # this first set of drivers all depend on bulk-capable hardware.
 
 config USB_CONFIGFS
@@ -506,6 +509,15 @@ config USB_CONFIGFS_F_TCM
 	  Both protocols can work on USB2.0 and USB3.0.
 	  UAS utilizes the USB 3.0 feature called streams support.
 
+config USB_CONFIGFS_F_GUD
+	bool "GUD USB Display Gadget function"
+	depends on USB_CONFIGFS
+	depends on DRM
+	select DRM_GUD_GADGET
+	select USB_F_GUD
+	help
+	  This presents a DRM display device as a GUD USB Display.
+
 source "drivers/usb/gadget/legacy/Kconfig"
 
 endif # USB_GADGET
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 7ce163727..9e2c8ab5a 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -56,3 +56,5 @@ usb_f_printer-y			:= f_printer.o
 obj-$(CONFIG_USB_F_PRINTER)	+= usb_f_printer.o
 usb_f_tcm-y			:= f_tcm.o
 obj-$(CONFIG_USB_F_TCM)		+= usb_f_tcm.o
+usb_f_gud-y			:= f_gud.o
+obj-$(CONFIG_USB_F_GUD)		+= usb_f_gud.o
diff --git a/drivers/usb/gadget/function/f_gud.c b/drivers/usb/gadget/function/f_gud.c
new file mode 100644
index 000000000..826d5582d
--- /dev/null
+++ b/drivers/usb/gadget/function/f_gud.c
@@ -0,0 +1,866 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Noralf Trønnes
+ */
+
+//#define DEBUG
+
+#include <linux/configfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/workqueue.h>
+
+#include <drm/gud.h>
+
+struct f_gud {
+	struct usb_function func;
+	struct work_struct worker;
+	size_t max_buffer_size;
+
+	u8 interface_id;
+	struct usb_request *ctrl_req;
+	struct usb_request *status_req;
+
+	struct usb_ep *bulk_ep;
+	struct usb_request *bulk_req;
+
+	struct gud_gadget *gdg;
+
+	spinlock_t lock; /* Protects the following members: */
+	bool ctrl_pending;
+	bool status_pending;
+	bool bulk_pending;
+	bool disable_pending;
+	bool host_timeout;
+	int errno;
+	u16 request;
+	u16 value;
+};
+
+static inline struct f_gud *func_to_f_gud(struct usb_function *f)
+{
+	return container_of(f, struct f_gud, func);
+}
+
+struct f_gud_opts {
+	struct usb_function_instance func_inst;
+	struct mutex lock;
+	int refcnt;
+
+	unsigned int drm_dev;
+	const char *backlight_dev;
+	u8 compression;
+	u32 connectors;
+	u8 formats[GUD_FORMATS_MAX_NUM];
+};
+
+static inline struct f_gud_opts *fi_to_f_gud_opts(const struct usb_function_instance *fi)
+{
+	return container_of(fi, struct f_gud_opts, func_inst);
+}
+
+static inline struct f_gud_opts *ci_to_f_gud_opts(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct f_gud_opts, func_inst.group);
+}
+
+#define F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(name, addr, size)	\
+	static struct usb_endpoint_descriptor name = {		\
+		.bLength =		USB_DT_ENDPOINT_SIZE,	\
+		.bDescriptorType =	USB_DT_ENDPOINT,	\
+		.bEndpointAddress =	addr,			\
+		.bmAttributes =		USB_ENDPOINT_XFER_BULK,	\
+		.wMaxPacketSize =	cpu_to_le16(size),	\
+	}
+
+static struct usb_interface_descriptor f_gud_intf = {
+	.bLength =		USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bNumEndpoints =	1,
+	.bInterfaceClass =	USB_CLASS_VENDOR_SPEC,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_fs_out_desc, USB_DIR_OUT, 0);
+
+static struct usb_descriptor_header *f_gud_fs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_intf,
+	(struct usb_descriptor_header *)&f_gud_fs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_hs_out_desc, USB_DIR_OUT, 512);
+
+static struct usb_descriptor_header *f_gud_hs_function[] = {
+	(struct usb_descriptor_header *)&f_gud_intf,
+	(struct usb_descriptor_header *)&f_gud_hs_out_desc,
+	NULL,
+};
+
+F_MUD_DEFINE_BULK_ENDPOINT_DESCRIPTOR(f_gud_ss_out_desc, USB_DIR_OUT, 1024);
+
+static struct usb_ss_ep_comp_descriptor f_gud_ss_bulk_comp_desc = {
+	.bLength =		USB_DT_SS_EP_COMP_SIZE,
+	.bDescriptorType =	USB_DT_SS_ENDPOINT_COMP,
+};
+
+static struct usb_descriptor_header *f_gud_ss_function[] = {
+	(struct usb_descriptor_header *)&f_gud_intf,
+	(struct usb_descriptor_header *)&f_gud_ss_out_desc,
+	(struct usb_descriptor_header *)&f_gud_ss_bulk_comp_desc,
+	NULL,
+};
+
+static struct usb_string f_gud_string_defs[] = {
+	[0].s = "GUD USB Display",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings f_gud_string_table = {
+	.language =	0x0409,	/* en-us */
+	.strings =	f_gud_string_defs,
+};
+
+static struct usb_gadget_strings *f_gud_strings[] = {
+	&f_gud_string_table,
+	NULL,
+};
+
+static void f_gud_bulk_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud *fgd = req->context;
+	unsigned long flags;
+
+	if (req->status || req->actual != req->length)
+		return;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->bulk_pending = true;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_ctrl_req_set_buffer(struct f_gud *fgd, void *buf, size_t len)
+{
+	int ret;
+
+	if (len != sizeof(struct gud_set_buffer_req))
+		return -EINVAL;
+
+	ret = gud_gadget_req_set_buffer(fgd->gdg, buf);
+	if (ret < 0)
+		return ret;
+
+	if (ret > fgd->max_buffer_size)
+		return -EOVERFLOW;
+
+	fgd->bulk_req->length = ret;
+
+	return usb_ep_queue(fgd->bulk_ep, fgd->bulk_req, GFP_KERNEL);
+}
+
+static void f_gud_status_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+}
+
+static int f_gud_status_req_queue(struct f_gud *fgd, int errno)
+{
+	struct usb_composite_dev *cdev = fgd->func.config->cdev;
+	u8 *buf = fgd->status_req->buf;
+	int ret;
+
+	//pr_debug("%s: errno=%d\n", __func__, errno);
+
+	switch (errno) {
+	case 0:
+		*buf = GUD_STATUS_OK;
+		break;
+	case -EBUSY:
+		*buf = GUD_STATUS_BUSY;
+		break;
+	case -EOPNOTSUPP:
+		*buf = GUD_STATUS_REQUEST_NOT_SUPPORTED;
+		break;
+	case -EPROTO:
+		*buf = GUD_STATUS_PROTOCOL_ERROR;
+		break;
+	case -EINVAL:
+		*buf = GUD_STATUS_INVALID_PARAMETER;
+		break;
+	case -EOVERFLOW:
+		*buf = GUD_STATUS_PROTOCOL_ERROR;
+		break;
+	default:
+		*buf = GUD_STATUS_ERROR;
+		break;
+	}
+
+	ret = usb_ep_queue(cdev->gadget->ep0, fgd->status_req, GFP_ATOMIC);
+	//if (ret)
+	//	pr_debug("%s: ret=%d\n", __func__, ret);
+
+	return ret;
+}
+
+static void f_gud_worker(struct work_struct *work)
+{
+	struct f_gud *fgd = container_of(work, struct f_gud, worker);
+	bool ctrl_pending, bulk_pending, disable_pending;
+	struct gud_gadget *gdg = fgd->gdg;
+	unsigned long flags;
+	u16 request, value;
+	int ret;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	request = fgd->request;
+	value = fgd->value;
+	ctrl_pending = fgd->ctrl_pending;
+	bulk_pending = fgd->bulk_pending;
+	disable_pending = fgd->disable_pending;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	pr_debug("%s: bulk_pending=%u ctrl_pending=%u disable_pending=%u\n",
+		 __func__, bulk_pending, ctrl_pending, disable_pending);
+
+	if (disable_pending) {
+		gud_gadget_disable_pipe(gdg);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->disable_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+		return;
+	}
+
+	if (bulk_pending) {
+		struct usb_request *req = fgd->bulk_req;
+
+		ret = gud_gadget_write_buffer(gdg, req->buf, req->actual);
+		if (ret)
+			pr_err("%s: Failed to write buffer, error=%d\n", __func__, ret);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->bulk_pending = false;
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+
+	if (ctrl_pending) {
+		unsigned int length = fgd->ctrl_req->length;
+		void *buf = fgd->ctrl_req->buf;
+
+		if (request == GUD_REQ_SET_BUFFER)
+			ret = f_gud_ctrl_req_set_buffer(fgd, buf, length);
+		else
+			ret = gud_gadget_req_set(gdg, request, value, buf, length);
+
+		spin_lock_irqsave(&fgd->lock, flags);
+		fgd->ctrl_pending = false;
+		//pr_debug("%s: status_pending=%u\n", __func__, fgd->status_pending);
+		if (fgd->status_pending) {
+			fgd->status_pending = false;
+			f_gud_status_req_queue(fgd, ret);
+		} else {
+			fgd->errno = ret;
+		}
+		spin_unlock_irqrestore(&fgd->lock, flags);
+	}
+}
+
+static void f_gud_ctrl_req_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct f_gud *fgd = req->context;
+	unsigned long flags;
+	int ret = 0;
+
+	//pr_debug("%s: status=%d diff=%u\n", __func__, req->status, req->actual != req->length);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+
+	if (req->status)
+		ret = req->status;
+	else if (req->actual != req->length)
+		ret = -EREMOTEIO;
+	if (ret)
+		fgd->errno = ret;
+	else
+		fgd->ctrl_pending = true;
+
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	if (!ret)
+		queue_work(system_long_wq, &fgd->worker);
+}
+
+static int f_gud_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud *fgd = func_to_f_gud(f);
+	bool in = ctrl->bRequestType & USB_DIR_IN;
+	u16 length = le16_to_cpu(ctrl->wLength);
+	u16 value = le16_to_cpu(ctrl->wValue);
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&fgd->lock, flags);
+
+	pr_debug("%s: bRequest=0x%x length=%u ctrl_pending=%u status_pending=%u\n",
+		 __func__, ctrl->bRequest, length, fgd->ctrl_pending, fgd->status_pending);
+
+	if (fgd->status_pending) {
+		/* Host timed out on the previous status request, worker is running */
+		pr_debug("EBUSY: status_pending\n");
+		ret = -EBUSY;
+		fgd->status_pending = false;
+		fgd->host_timeout = true;
+	} else if (ctrl->bRequest == GUD_REQ_GET_STATUS) {
+		if (!in || length != sizeof(u8)) {
+			ret = -EINVAL;
+		} else if (fgd->ctrl_pending && !fgd->host_timeout) {
+			/* Worker isn't done yet, it will queue up status when done */
+			ret = 0;
+			fgd->status_pending = true;
+		} else {
+			ret = f_gud_status_req_queue(fgd, fgd->errno);
+		}
+	} else if (fgd->ctrl_pending) {
+		/* Host timed out on the previous request, worker is running */
+		pr_debug("EBUSY: ctrl_pending\n");
+		ret = -EBUSY;
+	} else if (in) {
+		if (length > USB_COMP_EP0_BUFSIZ) {
+			ret = -EOVERFLOW;
+		} else {
+			ret = gud_gadget_req_get(fgd->gdg, ctrl->bRequest, value,
+						 cdev->req->buf, length);
+			if (ret >= 0) {
+				cdev->req->length = ret;
+				cdev->req->zero = ret < length;
+				ret = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+			}
+		}
+	} else {
+		if (length > USB_COMP_EP0_BUFSIZ) {
+			ret = -EOVERFLOW;
+		} else {
+			fgd->host_timeout = false;
+			fgd->request = ctrl->bRequest;
+			fgd->value = value;
+			fgd->ctrl_req->length = length;
+			/* ->complete() can run inline and takes the lock. Preserve errno */
+			fgd->errno = 0;
+			spin_unlock_irqrestore(&fgd->lock, flags);
+			ret = usb_ep_queue(cdev->gadget->ep0, fgd->ctrl_req, GFP_ATOMIC);
+			spin_lock_irqsave(&fgd->lock, flags);
+			if (!ret)
+				ret = fgd->errno;
+		}
+	}
+
+	fgd->errno = ret < 0 ? ret : 0;
+
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	pr_debug("                 ret=%d\n", ret);
+
+	return ret;
+}
+
+static bool f_gud_req_match(struct usb_function *f, const struct usb_ctrlrequest *ctrl,
+			    bool config0)
+{
+	struct f_gud *fgd = func_to_f_gud(f);
+
+	if (config0)
+		return false;
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_VENDOR)
+		return false;
+
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+		return false;
+
+	return fgd->interface_id == le16_to_cpu(ctrl->wIndex);
+}
+
+static int f_gud_set_alt(struct usb_function *f, unsigned int intf, unsigned int alt)
+{
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct f_gud *fgd = func_to_f_gud(f);
+	unsigned long flags;
+
+	if (alt || intf != fgd->interface_id)
+		return -EINVAL;
+
+	if (!fgd->bulk_ep->desc) {
+		pr_debug("%s: init\n", __func__);
+		if (config_ep_by_speed(cdev->gadget, f, fgd->bulk_ep)) {
+			fgd->bulk_ep->desc = NULL;
+			return -EINVAL;
+		}
+	}
+
+	pr_debug("%s: reset\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+	usb_ep_enable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->disable_pending = false;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	return 0;
+}
+
+static void f_gud_disable(struct usb_function *f)
+{
+	struct f_gud *fgd = func_to_f_gud(f);
+	unsigned long flags;
+
+	pr_debug("%s\n", __func__);
+
+	usb_ep_disable(fgd->bulk_ep);
+
+	spin_lock_irqsave(&fgd->lock, flags);
+	fgd->ctrl_pending = false;
+	fgd->bulk_pending = false;
+	fgd->status_pending = false;
+	fgd->disable_pending = true;
+	fgd->errno = -ESHUTDOWN;
+	spin_unlock_irqrestore(&fgd->lock, flags);
+
+	queue_work(system_long_wq, &fgd->worker);
+}
+
+static struct usb_request *f_gud_alloc_request(struct usb_ep *ep, size_t length)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, GFP_KERNEL);
+	if (!req)
+		return NULL;
+
+	req->length = length;
+	req->buf = kmalloc(length, GFP_KERNEL);
+	if (!req->buf) {
+		usb_ep_free_request(ep, req);
+		return NULL;
+	}
+
+	return req;
+}
+
+static void f_gud_free_request(struct usb_ep *ep, struct usb_request **req)
+{
+	if (!*req)
+		return;
+
+	kfree((*req)->buf);
+	usb_ep_free_request(ep, *req);
+	*req = NULL;
+}
+
+static void f_gud_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud *fgd = func_to_f_gud(f);
+	struct usb_composite_dev *cdev = fgd->func.config->cdev;
+
+	flush_work(&fgd->worker);
+
+	gud_gadget_fini(fgd->gdg);
+	fgd->gdg = NULL;
+
+	f_gud_free_request(fgd->bulk_ep, &fgd->bulk_req);
+	f_gud_free_request(cdev->gadget->ep0, &fgd->status_req);
+	f_gud_free_request(cdev->gadget->ep0, &fgd->ctrl_req);
+	fgd->bulk_ep = NULL;
+
+	usb_free_all_descriptors(f);
+}
+
+static int f_gud_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct f_gud_opts *opts = fi_to_f_gud_opts(f->fi);
+	struct usb_composite_dev *cdev = c->cdev;
+	struct f_gud *fgd = func_to_f_gud(f);
+	struct gud_gadget *gdg;
+	struct usb_string *us;
+	int ret;
+
+	us = usb_gstrings_attach(cdev, f_gud_strings, ARRAY_SIZE(f_gud_string_defs));
+	if (IS_ERR(us))
+		return PTR_ERR(us);
+
+	f_gud_intf.iInterface = us[0].id;
+
+	ret = usb_interface_id(c, f);
+	if (ret < 0)
+		return ret;
+
+	fgd->interface_id = ret;
+	f_gud_intf.bInterfaceNumber = fgd->interface_id;
+
+	fgd->bulk_ep = usb_ep_autoconfig(cdev->gadget, &f_gud_fs_out_desc);
+	if (!fgd->bulk_ep)
+		return -ENODEV;
+
+	f_gud_hs_out_desc.bEndpointAddress = f_gud_fs_out_desc.bEndpointAddress;
+	f_gud_ss_out_desc.bEndpointAddress = f_gud_fs_out_desc.bEndpointAddress;
+
+	ret = usb_assign_descriptors(f, f_gud_fs_function, f_gud_hs_function,
+				     f_gud_ss_function, NULL);
+	if (ret)
+		return ret;
+
+	fgd->ctrl_req = f_gud_alloc_request(cdev->gadget->ep0, USB_COMP_EP0_BUFSIZ);
+	if (!fgd->ctrl_req) {
+		ret = -ENOMEM;
+		goto fail_free_descs;
+	}
+
+	fgd->ctrl_req->complete = f_gud_ctrl_req_complete;
+	fgd->ctrl_req->context = fgd;
+
+	fgd->status_req = f_gud_alloc_request(cdev->gadget->ep0, sizeof(u8));
+	if (!fgd->status_req) {
+		ret = -ENOMEM;
+		goto fail_free_reqs;
+	}
+
+	fgd->status_req->complete = f_gud_status_req_complete;
+	fgd->status_req->context = fgd;
+
+	gdg = gud_gadget_init(opts->drm_dev, opts->backlight_dev, &fgd->max_buffer_size,
+			      opts->compression, opts->formats, opts->connectors);
+	if (IS_ERR(gdg)) {
+		ret = PTR_ERR(gdg);
+		goto fail_free_reqs;
+	}
+
+	fgd->bulk_req = f_gud_alloc_request(fgd->bulk_ep, fgd->max_buffer_size);
+	if (!fgd->bulk_req) {
+		ret = -ENOMEM;
+		goto fail_free_reqs;
+	}
+
+	fgd->bulk_req->complete = f_gud_bulk_complete;
+	fgd->bulk_req->context = fgd;
+
+	fgd->gdg = gdg;
+
+	return 0;
+
+fail_free_reqs:
+	f_gud_free_request(fgd->bulk_ep, &fgd->bulk_req);
+	f_gud_free_request(cdev->gadget->ep0, &fgd->ctrl_req);
+	f_gud_free_request(cdev->gadget->ep0, &fgd->status_req);
+fail_free_descs:
+	usb_free_all_descriptors(f);
+
+	return ret;
+}
+
+static void f_gud_free_func(struct usb_function *f)
+{
+	struct f_gud_opts *opts = container_of(f->fi, struct f_gud_opts, func_inst);
+	struct f_gud *fgd = func_to_f_gud(f);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt--;
+	mutex_unlock(&opts->lock);
+
+	kfree(fgd);
+}
+
+static struct usb_function *f_gud_alloc_func(struct usb_function_instance *fi)
+{
+	struct f_gud_opts *opts = fi_to_f_gud_opts(fi);
+	struct usb_function *func;
+	struct f_gud *fgd;
+
+	fgd = kzalloc(sizeof(*fgd), GFP_KERNEL);
+	if (!fgd)
+		return ERR_PTR(-ENOMEM);
+
+	spin_lock_init(&fgd->lock);
+	INIT_WORK(&fgd->worker, f_gud_worker);
+
+	mutex_lock(&opts->lock);
+	opts->refcnt++;
+	mutex_unlock(&opts->lock);
+
+	func = &fgd->func;
+	func->name = "gud";
+	func->bind = f_gud_bind;
+	func->unbind = f_gud_unbind;
+	func->set_alt = f_gud_set_alt;
+	func->req_match = f_gud_req_match;
+	func->setup = f_gud_setup;
+	func->disable = f_gud_disable;
+	func->free_func = f_gud_free_func;
+
+	return func;
+}
+
+static ssize_t f_gud_opts_drm_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int result;
+
+	mutex_lock(&opts->lock);
+	result = sprintf(page, "%u\n", opts->drm_dev);
+	mutex_unlock(&opts->lock);
+
+	return result;
+}
+
+static ssize_t f_gud_opts_drm_dev_store(struct config_item *item, const char *page, size_t len)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	unsigned int num;
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = kstrtouint(page, 0, &num);
+	if (ret)
+		goto unlock;
+
+	opts->drm_dev = num;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_opts_, drm_dev);
+
+static ssize_t f_gud_opts_backlight_dev_show(struct config_item *item, char *page)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	ssize_t ret = 0;
+
+	mutex_lock(&opts->lock);
+	if (opts->backlight_dev)
+		ret = strscpy(page, opts->backlight_dev, PAGE_SIZE);
+	else
+		page[0] = '\0';
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+static ssize_t f_gud_opts_backlight_dev_store(struct config_item *item,
+					      const char *page, size_t len)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	ssize_t ret;
+	char *name;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	name = kstrndup(page, len, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto unlock;
+	}
+
+	kfree(opts->backlight_dev);
+	opts->backlight_dev = name;
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_opts_, backlight_dev);
+
+static ssize_t f_gud_opts_compression_show(struct config_item *item, char *page)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int ret;
+
+	mutex_lock(&opts->lock);
+	ret = sprintf(page, "0x%02x\n", opts->compression);
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+static ssize_t f_gud_opts_compression_store(struct config_item *item,
+					    const char *page, size_t len)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = kstrtou8(page, 0, &opts->compression);
+	if (ret)
+		goto unlock;
+
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_opts_, compression);
+
+static ssize_t f_gud_opts_formats_show(struct config_item *item,
+				       char *page)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int i;
+
+	mutex_lock(&opts->lock);
+	memcpy(page, opts->formats, GUD_FORMATS_MAX_NUM);
+	for (i = GUD_FORMATS_MAX_NUM; i > 0; i--) {
+		if (opts->formats[i - 1])
+			break;
+	}
+	mutex_unlock(&opts->lock);
+
+	return i;
+}
+
+static ssize_t f_gud_opts_formats_store(struct config_item *item,
+					const char *page, size_t len)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = len;
+	len = min_t(size_t, GUD_FORMATS_MAX_NUM, len);
+	memcpy(opts->formats, page, len);
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_opts_, formats);
+
+static ssize_t f_gud_opts_connectors_show(struct config_item *item, char *page)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int ret;
+
+	mutex_lock(&opts->lock);
+	ret = sprintf(page, "0x%08x\n", opts->connectors);
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+static ssize_t f_gud_opts_connectors_store(struct config_item *item,
+					   const char *page, size_t len)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+	int ret;
+
+	mutex_lock(&opts->lock);
+	if (opts->refcnt) {
+		ret = -EBUSY;
+		goto unlock;
+	}
+
+	ret = kstrtou32(page, 0, &opts->connectors);
+	if (ret)
+		goto unlock;
+
+	ret = len;
+unlock:
+	mutex_unlock(&opts->lock);
+
+	return ret;
+}
+
+CONFIGFS_ATTR(f_gud_opts_, connectors);
+
+static struct configfs_attribute *f_gud_attrs[] = {
+	&f_gud_opts_attr_drm_dev,
+	&f_gud_opts_attr_backlight_dev,
+	&f_gud_opts_attr_compression,
+	&f_gud_opts_attr_formats,
+	&f_gud_opts_attr_connectors,
+	NULL,
+};
+
+static void f_gud_attr_release(struct config_item *item)
+{
+	struct f_gud_opts *opts = ci_to_f_gud_opts(item);
+
+	usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations f_gud_item_ops = {
+	.release	= f_gud_attr_release,
+};
+
+static const struct config_item_type f_gud_func_type = {
+	.ct_item_ops	= &f_gud_item_ops,
+	.ct_attrs	= f_gud_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static void f_gud_free_func_inst(struct usb_function_instance *fi)
+{
+	struct f_gud_opts *opts = fi_to_f_gud_opts(fi);
+
+	mutex_destroy(&opts->lock);
+	kfree(opts->backlight_dev);
+	kfree(opts);
+}
+
+static struct usb_function_instance *f_gud_alloc_func_inst(void)
+{
+	struct f_gud_opts *opts;
+
+	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+	if (!opts)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&opts->lock);
+	opts->func_inst.free_func_inst = f_gud_free_func_inst;
+	opts->compression = ~0;
+	opts->connectors = ~0U;
+
+	config_group_init_type_name(&opts->func_inst.group, "", &f_gud_func_type);
+
+	return &opts->func_inst;
+}
+
+DECLARE_USB_FUNCTION_INIT(gud, f_gud_alloc_func_inst, f_gud_alloc_func);
+
+MODULE_DESCRIPTION("Generic USB Display Gadget");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/backlight/lm3630a_bl.c b/drivers/video/backlight/lm3630a_bl.c
index 37651c2b9..1eecb0ca5 100644
--- a/drivers/video/backlight/lm3630a_bl.c
+++ b/drivers/video/backlight/lm3630a_bl.c
@@ -85,18 +85,34 @@ static int lm3630a_chip_init(struct lm3630a_chip *pchip)
 {
 	int rval;
 	struct lm3630a_platform_data *pdata = pchip->pdata;
+	int value;
 
 	usleep_range(1000, 2000);
 	/* set Filter Strength Register */
 	rval = lm3630a_write(pchip, REG_FILTER_STRENGTH, 0x03);
 	/* set Cofig. register */
 	rval |= lm3630a_update(pchip, REG_CONFIG, 0x07, pdata->pwm_ctrl);
+	/* enable feedback on both strings */
+	/* rval |= lm3630a_update(pchip, REG_CONFIG, 0x18, 0x18); */
+
 	/* set boost control */
-	rval |= lm3630a_write(pchip, REG_BOOST, 0x38);
+	value = pdata->boost_fmode +
+		(pdata->boost_shift << 1) +
+		(pdata->boost_slow_start << 2) +
+		(pdata->boost_ocp << 3) +
+		(pdata->boost_ovp << 5);
+	/* value = 0x38;  // default */
+	printk(KERN_INFO "lm3630a: REG_BOOST, writing value: %i", value);
+	rval |= lm3630a_write(pchip, REG_BOOST, value);
+
+	pr_info("lm3630a: current_index_leda: %i", pdata->leda_current_index);
+	pr_info("lm3630a: current_index_ledb: %i", pdata->ledb_current_index);
 	/* set current A */
-	rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F);
+	rval |= lm3630a_update(pchip, REG_I_A, 0x1F, pdata->leda_current_index);
+	/* rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F); */
 	/* set current B */
-	rval |= lm3630a_write(pchip, REG_I_B, 0x1F);
+	/* rval |= lm3630a_write(pchip, REG_I_B, 0x1F); */
+	rval |= lm3630a_write(pchip, REG_I_B, pdata->ledb_current_index);
 	/* set control */
 	rval |= lm3630a_update(pchip, REG_CTRL, 0x14, pdata->leda_ctrl);
 	rval |= lm3630a_update(pchip, REG_CTRL, 0x0B, pdata->ledb_ctrl);
@@ -408,6 +424,8 @@ static int lm3630a_parse_bank(struct lm3630a_platform_data *pdata,
 	const char *label;
 	u32 bank, val;
 	bool linear;
+	u32 current_ma;
+	u8 current_index;
 
 	ret = fwnode_property_read_u32(node, "reg", &bank);
 	if (ret)
@@ -472,6 +490,27 @@ static int lm3630a_parse_bank(struct lm3630a_platform_data *pdata,
 			pdata->leda_max_brt = val;
 	}
 
+	ret = fwnode_property_read_u32(node, "ti,current-microamp", &current_ma);
+	pr_info("current-microamp %i", current_ma);
+	// convert to 5-bit value
+	current_index = -1;
+	for (int i=0; i < 31; i++){
+		pr_info("    checking %i = %i", i,  5000 + i * 750);
+		if (5000 + i * 750 == current_ma){
+			current_index = i;
+			break;
+		}
+	}
+	if (current_index == -1){
+		return -EINVAL;
+	}
+	if (!ret) {
+		if (bank)
+			pdata->leda_current_index = current_index;
+		else
+			pdata->ledb_current_index = current_index;
+	}
+
 	return 0;
 }
 
@@ -480,7 +519,77 @@ static int lm3630a_parse_node(struct lm3630a_chip *pchip,
 {
 	int ret = -ENODEV, seen_led_sources = 0;
 	struct fwnode_handle *node;
+	/* default reset values of the device */
+	u32 boost_frequency = 500000;
+	u32 boost_ovp = 24000000;
+	u32 boost_ocp = 1200000;
 
+	/* boost control */
+	ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-frequency-hz",
+				       &boost_frequency);
+	switch (boost_frequency) {
+		case 500000:
+			break;
+		case 560000:
+			pdata->boost_shift = true;
+			break;
+		case 1000000:
+			pdata->boost_fmode = true;
+			break;
+		case 1120000:
+			pdata->boost_fmode = true;
+			pdata->boost_shift = true;
+			break;
+		default:
+			dev_err(pchip->dev, "invalid boost frequency\n");
+			return -EINVAL;
+			break;
+	}
+	ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-ocp-microamp",
+				       &boost_ocp);
+	// todo: move values to header constants
+	switch(boost_ocp) {
+		case 600000:
+			pdata->boost_ocp = 0;
+			break;
+		case 800000:
+			pdata->boost_ocp = 1;
+			break;
+		case 1000000:
+			pdata->boost_ocp = 2;
+			break;
+		case 1200000:
+			pdata->boost_ocp = 3;
+			break;
+		default:
+			dev_err(pchip->dev, "invalid ocp value\n");
+			return -EINVAL;
+			break;
+	}
+
+	ret = fwnode_property_read_u32(dev_fwnode(pchip->dev), "ti,boost-ovp-microvolts",
+				       &boost_ovp);
+	// todo: move values to header constants
+	switch(boost_ovp) {
+		case 16000000:
+			pdata->boost_ovp = 0;
+			break;
+		case 24000000:
+			pdata->boost_ovp = 1;
+			break;
+		case 32000000:
+			pdata->boost_ovp = 2;
+			break;
+		case 40000000:
+			pdata->boost_ovp = 3;
+			break;
+		default:
+			dev_err(pchip->dev, "invalid ovp value\n");
+			return -EINVAL;
+			break;
+	}
+
+	/* led strings */
 	device_for_each_child_node(pchip->dev, node) {
 		ret = lm3630a_parse_bank(pdata, node, &seen_led_sources);
 		if (ret) {
@@ -530,6 +639,13 @@ static int lm3630a_probe(struct i2c_client *client)
 		pdata->leda_init_brt = LM3630A_MAX_BRIGHTNESS;
 		pdata->ledb_init_brt = LM3630A_MAX_BRIGHTNESS;
 
+		// todo: use header constants
+		pdata->boost_ovp = 1;
+		pdata->boost_ocp = 3;
+		pdata->boost_slow_start = 0;
+		pdata->boost_shift = 0;
+		pdata->boost_fmode = 0;
+
 		rval = lm3630a_parse_node(pchip, pdata);
 		if (rval) {
 			dev_err(&client->dev, "fail : parse node\n");
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index c972a8a33..d9316ec0c 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -17,6 +17,7 @@ struct drm_file;
 struct drm_framebuffer;
 struct drm_gem_object;
 struct drm_minor;
+struct drm_property;
 struct module;
 
 /**
@@ -95,6 +96,26 @@ struct drm_client_funcs {
 	int (*resume)(struct drm_client_dev *client);
 };
 
+/**
+ * struct drm_client_property - DRM client property
+ */
+struct drm_client_property {
+	/**
+	 * @obj: DRM Mode Object to which the property belongs.
+	 */
+	struct drm_mode_object *obj;
+
+	/**
+	 * @prop: DRM Property.
+	 */
+	struct drm_property *prop;
+
+	/**
+	 * @value: Property value.
+	 */
+	u64 value;
+};
+
 /**
  * struct drm_client_dev - DRM client instance
  */
@@ -128,7 +149,7 @@ struct drm_client_dev {
 	struct drm_file *file;
 
 	/**
-	 * @modeset_mutex: Protects @modesets.
+	 * @modeset_mutex: Protects @modesets and @properties.
 	 */
 	struct mutex modeset_mutex;
 
@@ -152,6 +173,16 @@ struct drm_client_dev {
 	 */
 	bool hotplug_pending;
 
+	/**
+	 * @properties: DRM properties attached to the configuration.
+	 */
+	struct drm_client_property *properties;
+
+	/**
+	 * @num_properties: Number of attached properties.
+	 */
+	unsigned int num_properties;
+
 	/**
 	 * @hotplug_failed:
 	 *
@@ -163,8 +194,10 @@ struct drm_client_dev {
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 		    const char *name, const struct drm_client_funcs *funcs);
+int drm_client_init_from_id(unsigned int minor_id, struct drm_client_dev *client,
+		    const char *name, const struct drm_client_funcs *funcs);
 void drm_client_release(struct drm_client_dev *client);
-void drm_client_register(struct drm_client_dev *client);
+bool drm_client_register(struct drm_client_dev *client);
 
 /**
  * struct drm_client_buffer - DRM client buffer
@@ -209,12 +242,18 @@ void drm_client_buffer_vunmap(struct drm_client_buffer *buffer);
 int drm_client_modeset_create(struct drm_client_dev *client);
 void drm_client_modeset_free(struct drm_client_dev *client);
 int drm_client_modeset_probe(struct drm_client_dev *client, unsigned int width, unsigned int height);
+int drm_client_modeset_set(struct drm_client_dev *client, struct drm_connector *connector,
+			   struct drm_display_mode *mode, struct drm_framebuffer *fb);
+int drm_client_modeset_set_property(struct drm_client_dev *client, struct drm_mode_object *obj,
+				    struct drm_property *prop, u64 value);
+int drm_client_modeset_set_rotation(struct drm_client_dev *client, u64 value);
 bool drm_client_rotation(struct drm_mode_set *modeset, unsigned int *rotation);
 int drm_client_modeset_check(struct drm_client_dev *client);
 int drm_client_modeset_commit_locked(struct drm_client_dev *client);
 int drm_client_modeset_commit(struct drm_client_dev *client);
 int drm_client_modeset_dpms(struct drm_client_dev *client, int mode);
 int drm_client_modeset_wait_for_vblank(struct drm_client_dev *client, unsigned int crtc_index);
+int drm_client_modeset_disable(struct drm_client_dev *client);
 
 /**
  * drm_client_for_each_modeset() - Iterate over client modesets
diff --git a/include/drm/drm_epd_helper.h b/include/drm/drm_epd_helper.h
new file mode 100644
index 000000000..6386fbce2
--- /dev/null
+++ b/include/drm/drm_epd_helper.h
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __DRM_EPD_HELPER_H__
+#define __DRM_EPD_HELPER_H__
+
+#define DRM_EPD_DEFAULT_TEMPERATURE	25
+
+struct drm_device;
+struct firmware;
+struct pvi_wbf_file_header;
+struct pvi_wbf_mode_info;
+
+/**
+ * enum drm_epd_waveform - Identifiers for waveforms used to drive EPD pixels
+ *
+ * @DRM_EPD_WF_RESET: Used to initialize the panel, ends with white
+ * @DRM_EPD_WF_A2: Fast transitions between black and white only
+ * @DRM_EPD_WF_DU: Transitions 16-level grayscale to monochrome
+ * @DRM_EPD_WF_DU4: Transitions 16-level grayscale to 4-level grayscale
+ * @DRM_EPD_WF_GC16: High-quality but flashy 16-level grayscale
+ * @DRM_EPD_WF_GCC16: Less flashy 16-level grayscale
+ * @DRM_EPD_WF_GL16: Less flashy 16-level grayscale
+ * @DRM_EPD_WF_GLR16: Less flashy 16-level grayscale, plus anti-ghosting
+ * @DRM_EPD_WF_GLD16: Less flashy 16-level grayscale, plus anti-ghosting
+ */
+enum drm_epd_waveform {
+	DRM_EPD_WF_RESET,
+	DRM_EPD_WF_A2,
+	DRM_EPD_WF_DU,
+	DRM_EPD_WF_DU4,
+	DRM_EPD_WF_GC16,
+	DRM_EPD_WF_GCC16,
+	DRM_EPD_WF_GL16,
+	DRM_EPD_WF_GLR16,
+	DRM_EPD_WF_GLD16,
+	DRM_EPD_WF_MAX
+};
+
+/**
+ * enum drm_epd_lut_format - EPD LUT buffer format
+ *
+ * @DRM_EPD_LUT_4BIT: 4-bit grayscale indexes, 1 byte per element
+ * @DRM_EPD_LUT_4BIT_PACKED: 4-bit grayscale indexes, 2 bits per element
+ * @DRM_EPD_LUT_5BIT: 5-bit grayscale indexes, 1 byte per element
+ * @DRM_EPD_LUT_5BIT_PACKED: 5-bit grayscale indexes, 2 bits per element
+ */
+enum drm_epd_lut_format {
+	DRM_EPD_LUT_4BIT,
+	DRM_EPD_LUT_4BIT_PACKED,
+	DRM_EPD_LUT_5BIT,
+	DRM_EPD_LUT_5BIT_PACKED,
+};
+
+/**
+ * struct drm_epd_lut_file - Describes a file containing EPD LUTs
+ *
+ * @dev: The DRM device owning this LUT file
+ * @fw: The firmware object holding the raw file contents
+ * @header: Vendor-specific LUT file header
+ * @mode_info: Vendor-specific information about available waveforms
+ */
+struct drm_epd_lut_file {
+	struct drm_device			*dev;
+	const struct firmware			*fw;
+	const struct pvi_wbf_file_header	*header;
+	const struct pvi_wbf_mode_info		*mode_info;
+};
+
+/**
+ * struct drm_epd_lut - Describes a particular LUT buffer
+ *
+ * @buf: The LUT, in the format requested by the driver
+ * @file: The file where this LUT was loaded from
+ * @format: The LUT buffer format needed by the driver
+ * @max_phases: The maximum number of waveform phases supported by the driver
+ * @num_phases: The number of waveform phases in the current LUT
+ * @mode_index: Private identifier for the current waveform
+ * @temp_index: Private identifier for the current temperature
+ */
+struct drm_epd_lut {
+	u8				*buf;
+	const struct drm_epd_lut_file	*file;
+	enum drm_epd_lut_format		format;
+	unsigned int			max_phases;
+	unsigned int			num_phases;
+	int				mode_index;
+	int				temp_index;
+};
+
+int pvi_wbf_get_mode_index(const struct drm_epd_lut_file *file,
+				  enum drm_epd_waveform waveform);
+
+int drmm_epd_lut_file_init(struct drm_device *dev,
+			   struct drm_epd_lut_file *file,
+			   const char *file_name);
+
+int drmm_epd_lut_init(struct drm_epd_lut_file *file,
+		      struct drm_epd_lut *lut,
+		      enum drm_epd_lut_format format,
+		      unsigned int max_phases);
+
+int drm_epd_lut_set_temperature(struct drm_epd_lut *lut,
+				int temperature);
+int drm_epd_lut_set_waveform(struct drm_epd_lut *lut,
+			     enum drm_epd_waveform waveform);
+
+#endif /* __DRM_EPD_HELPER_H__ */
diff --git a/include/drm/gud.h b/include/drm/gud.h
index c52a8ba4a..0d3d57e53 100644
--- a/include/drm/gud.h
+++ b/include/drm/gud.h
@@ -332,4 +332,16 @@ struct gud_state_req {
 /* Enable/disable display/output (DPMS), value is u8: 0/1 */
 #define GUD_REQ_SET_DISPLAY_ENABLE			0x64
 
+struct gud_gadget;
+
+struct gud_gadget *gud_gadget_init(unsigned int minor_id, const char *backlight,
+				   size_t *max_buffer_size, u8 compression, u8 *formats,
+				   u32 connectors);
+void gud_gadget_fini(struct gud_gadget *gdg);
+int gud_gadget_disable_pipe(struct gud_gadget *gdg);
+int gud_gadget_req_get(struct gud_gadget *gdg, u8 request, u16 index, void *data, size_t size);
+int gud_gadget_req_set(struct gud_gadget *gdg, u8 request, u16 index, const void *data, size_t size);
+int gud_gadget_req_set_buffer(struct gud_gadget *gdg, const struct gud_set_buffer_req *req);
+int gud_gadget_write_buffer(struct gud_gadget *gdg, const void *buf, size_t len);
+
 #endif
diff --git a/include/dt-bindings/suspend/rockchip-rk3568.h b/include/dt-bindings/suspend/rockchip-rk3568.h
new file mode 100644
index 000000000..91e89f64a
--- /dev/null
+++ b/include/dt-bindings/suspend/rockchip-rk3568.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
+/*
+ * Header providing constants for Rockchip suspend bindings.
+ *
+ * Copyright (C) 2021, Rockchip Electronics Co., Ltd.
+ * Author: XiaoDong.Huang
+ */
+
+#ifndef __DT_BINDINGS_SUSPEND_ROCKCHIP_RK3568_H__
+#define __DT_BINDINGS_SUSPEND_ROCKCHIP_RK3568_H__
+/******************************bits ops************************************/
+
+#ifndef BIT
+#define BIT(nr)				(1 << (nr))
+#endif
+
+#define RKPM_SLP_WFI			BIT(0)
+#define RKPM_SLP_ARMOFF			BIT(1)
+#define RKPM_SLP_CENTER_OFF		BIT(2)
+#define RKPM_SLP_ARMOFF_LOGOFF		BIT(3)
+#define RKPM_SLP_FROM_UBOOT		BIT(4)
+#define RKPM_SLP_PMIC_LP		BIT(5)
+#define RKPM_SLP_HW_PLLS_OFF		BIT(6)
+#define RKPM_SLP_PMUALIVE_32K		BIT(7)
+#define RKPM_SLP_OSC_DIS		BIT(8)
+#define RKPM_SLP_32K_EXT		BIT(9)
+#define RKPM_SLP_32K_PVTM		BIT(10)
+/* the wake up source */
+#define RKPM_CPU0_WKUP_EN		BIT(0)
+#define RKPM_CPU1_WKUP_EN		BIT(1)
+#define RKPM_CPU2_WKUP_EN		BIT(2)
+#define RKPM_CPU3_WKUP_EN		BIT(3)
+#define RKPM_GPIO_WKUP_EN		BIT(4)
+#define RKPM_UART0_WKUP_EN		BIT(5)
+#define RKPM_SDMMC0_WKUP_EN		BIT(6)
+#define RKPM_SDMMC1_WKUP_EN		BIT(7)
+#define RKPM_SDMMC2_WKUP_EN		BIT(8)
+#define RKPM_USB_WKUP_EN		BIT(9)
+#define RKPM_PCIE_WKUP_EN		BIT(10)
+#define RKPM_VAD_WKUP_EN		BIT(11)
+#define RKPM_TIMER_WKUP_EN		BIT(12)
+#define RKPM_PWM0_WKUP_EN		BIT(13)
+#define RKPM_TIMEOUT_WKUP_EN		BIT(14)
+#define RKPM_SFT_WKUP_EN		BIT(15)
+#define RKPM_USB_LINESTATE_WKUP_EN	BIT(16)
+
+#define RKPM_SLP_LDO1_ON		BIT(0)
+#define RKPM_SLP_LDO2_ON		BIT(1)
+#define RKPM_SLP_LDO3_ON		BIT(2)
+#define RKPM_SLP_LDO4_ON		BIT(3)
+#define RKPM_SLP_LDO5_ON		BIT(4)
+#define RKPM_SLP_LDO6_ON		BIT(5)
+#define RKPM_SLP_LDO7_ON		BIT(6)
+#define RKPM_SLP_LDO8_ON		BIT(7)
+#define RKPM_SLP_LDO9_ON		BIT(8)
+
+#endif
diff --git a/include/linux/platform_data/lm3630a_bl.h b/include/linux/platform_data/lm3630a_bl.h
index 530be9318..921ccf5ab 100644
--- a/include/linux/platform_data/lm3630a_bl.h
+++ b/include/linux/platform_data/lm3630a_bl.h
@@ -33,6 +33,8 @@ enum lm3630a_ledb_ctrl {
 };
 
 #define LM3630A_MAX_BRIGHTNESS 255
+#define LM3630A_BOOST_USE_1MHZ BIT(0)
+#define LM3630A_BOOST_SHIFT BIT(1)
 /*
  *@leda_label    : optional led a label.
  *@leda_init_brt : led a init brightness. 4~255
@@ -44,6 +46,8 @@ enum lm3630a_ledb_ctrl {
  *@ledb_ctrl     : led b disable, enable linear, enable exponential
  *@pwm_period    : pwm period
  *@pwm_ctrl      : pwm disable, bank a or b, active high or low
+ *@boost_shift_freq : activate the frequency shift of the boost converter
+ *@boost_use_1mhz : switch boost converter frequency from 500kHz to 1MHz
  */
 struct lm3630a_platform_data {
 
@@ -52,14 +56,22 @@ struct lm3630a_platform_data {
 	int leda_init_brt;
 	int leda_max_brt;
 	enum lm3630a_leda_ctrl leda_ctrl;
+	int leda_current_index;
 	/* led b config. */
 	const char *ledb_label;
 	int ledb_init_brt;
 	int ledb_max_brt;
 	enum lm3630a_ledb_ctrl ledb_ctrl;
+	int ledb_current_index;
 	/* pwm config. */
 	unsigned int pwm_period;
 	enum lm3630a_pwm_ctrl pwm_ctrl;
+	/* boost config */
+	int boost_ovp;
+	int boost_ocp;
+	bool boost_slow_start;
+	bool boost_shift;
+	bool boost_fmode;
 };
 
 #endif /* __LINUX_LM3630A_H */
diff --git a/include/linux/rockchip/rockchip_sip.h b/include/linux/rockchip/rockchip_sip.h
new file mode 100644
index 000000000..c77bbb78c
--- /dev/null
+++ b/include/linux/rockchip/rockchip_sip.h
@@ -0,0 +1,333 @@
+/* Copyright (c) 2016, Fuzhou Rockchip Electronics Co., Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef __ROCKCHIP_SIP_H
+#define __ROCKCHIP_SIP_H
+
+#include <linux/arm-smccc.h>
+#include <linux/io.h>
+
+/* SMC function IDs for SiP Service queries, compatible with kernel-3.10 */
+#define SIP_ATF_VERSION			0x82000001
+#define SIP_ACCESS_REG			0x82000002
+#define SIP_SUSPEND_MODE		0x82000003
+#define SIP_PENDING_CPUS		0x82000004
+#define SIP_UARTDBG_CFG			0x82000005
+#define SIP_UARTDBG_CFG64		0xc2000005
+#define SIP_MCU_EL3FIQ_CFG		0x82000006
+#define SIP_ACCESS_CHIP_STATE64		0xc2000006
+#define SIP_SECURE_MEM_CONFIG		0x82000007
+#define SIP_ACCESS_CHIP_EXTRA_STATE64	0xc2000007
+#define SIP_DRAM_CONFIG			0x82000008
+#define SIP_SHARE_MEM			0x82000009
+#define SIP_SIP_VERSION			0x8200000a
+#define SIP_REMOTECTL_CFG		0x8200000b
+#define PSCI_SIP_VPU_RESET		0x8200000c
+#define SIP_BUS_CFG			0x8200000d
+#define SIP_LAST_LOG			0x8200000e
+#define SIP_SCMI_AGENT0			0x82000010
+#define SIP_SCMI_AGENT1			0x82000011
+#define SIP_SCMI_AGENT2			0x82000012
+#define SIP_SCMI_AGENT3			0x82000013
+#define SIP_SCMI_AGENT4			0x82000014
+#define SIP_SCMI_AGENT5			0x82000015
+#define SIP_SCMI_AGENT6			0x82000016
+#define SIP_SCMI_AGENT7			0x82000017
+#define SIP_SCMI_AGENT8			0x82000018
+#define SIP_SCMI_AGENT9			0x82000019
+#define SIP_SCMI_AGENT10		0x8200001a
+#define SIP_SCMI_AGENT11		0x8200001b
+#define SIP_SCMI_AGENT12		0x8200001c
+#define SIP_SCMI_AGENT13		0x8200001d
+#define SIP_SCMI_AGENT14		0x8200001e
+#define SIP_SCMI_AGENT15		0x8200001f
+#define SIP_SDEI_FIQ_DBG_SWITCH_CPU	0x82000020
+#define SIP_SDEI_FIQ_DBG_GET_EVENT_ID	0x82000021
+
+/* Rockchip Sip version */
+#define SIP_IMPLEMENT_V1                (1)
+#define SIP_IMPLEMENT_V2                (2)
+
+/* Trust firmware version */
+#define ATF_VER_MAJOR(ver)		(((ver) >> 16) & 0xffff)
+#define ATF_VER_MINOR(ver)		(((ver) >> 0) & 0xffff)
+
+/* SIP_ACCESS_REG: read or write */
+#define SECURE_REG_RD			0x0
+#define SECURE_REG_WR			0x1
+
+/* Fiq debugger share memory: 8KB enough */
+#define FIQ_UARTDBG_PAGE_NUMS		2
+#define FIQ_UARTDBG_SHARE_MEM_SIZE	((FIQ_UARTDBG_PAGE_NUMS) * 4096)
+
+/* Error return code */
+#define IS_SIP_ERROR(x)			(!!(x))
+
+#define SIP_RET_SUCCESS			0
+#define SIP_RET_SMC_UNKNOWN		-1
+#define SIP_RET_NOT_SUPPORTED		-2
+#define SIP_RET_INVALID_PARAMS		-3
+#define SIP_RET_INVALID_ADDRESS		-4
+#define SIP_RET_DENIED			-5
+#define SIP_RET_SET_RATE_TIMEOUT	-6
+
+/* SIP_UARTDBG_CFG64 call types */
+#define UARTDBG_CFG_INIT		0xf0
+#define UARTDBG_CFG_OSHDL_TO_OS		0xf1
+#define UARTDBG_CFG_OSHDL_CPUSW		0xf3
+#define UARTDBG_CFG_OSHDL_DEBUG_ENABLE	0xf4
+#define UARTDBG_CFG_OSHDL_DEBUG_DISABLE	0xf5
+#define UARTDBG_CFG_PRINT_PORT		0xf7
+#define UARTDBG_CFG_FIQ_ENABEL		0xf8
+#define UARTDBG_CFG_FIQ_DISABEL		0xf9
+
+/* SIP_SUSPEND_MODE32 call types */
+#define SUSPEND_MODE_CONFIG		0x01
+#define WKUP_SOURCE_CONFIG		0x02
+#define PWM_REGULATOR_CONFIG		0x03
+#define GPIO_POWER_CONFIG		0x04
+#define SUSPEND_DEBUG_ENABLE		0x05
+#define APIOS_SUSPEND_CONFIG		0x06
+#define VIRTUAL_POWEROFF		0x07
+#define SUSPEND_WFI_TIME_MS		0x08
+#define LINUX_PM_STATE			0x09
+
+/* SIP_REMOTECTL_CFG call types */
+#define	REMOTECTL_SET_IRQ		0xf0
+#define REMOTECTL_SET_PWM_CH		0xf1
+#define REMOTECTL_SET_PWRKEY		0xf2
+#define REMOTECTL_GET_WAKEUP_STATE	0xf3
+#define REMOTECTL_ENABLE		0xf4
+/* wakeup state */
+#define REMOTECTL_PWRKEY_WAKEUP		0xdeadbeaf
+
+enum {
+	FIRMWARE_NONE,
+	FIRMWARE_TEE_32BIT,
+	FIRMWARE_ATF_32BIT,
+	FIRMWARE_ATF_64BIT,
+	FIRMWARE_END,
+};
+
+/* Share mem page types */
+typedef enum {
+	SHARE_PAGE_TYPE_INVALID = 0,
+	SHARE_PAGE_TYPE_UARTDBG,
+	SHARE_PAGE_TYPE_DDR,
+	SHARE_PAGE_TYPE_DDRDBG,
+	SHARE_PAGE_TYPE_DDRECC,
+	SHARE_PAGE_TYPE_LAST_LOG,
+	SHARE_PAGE_TYPE_MAX,
+} share_page_type_t;
+
+/*
+ * Rules: struct arm_smccc_res contains result and data, details:
+ *
+ * a0: error code(0: success, !0: error);
+ * a1~a3: data
+ */
+#if IS_ENABLED(CONFIG_ROCKCHIP_SIP)
+struct arm_smccc_res sip_smc_get_atf_version(void);
+struct arm_smccc_res sip_smc_get_sip_version(void);
+struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2);
+struct arm_smccc_res sip_smc_request_share_mem(u32 page_num,
+					       share_page_type_t page_type);
+struct arm_smccc_res sip_smc_mcu_el3fiq(u32 arg0, u32 arg1, u32 arg2);
+struct arm_smccc_res sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2);
+struct arm_smccc_res sip_smc_get_suspend_info(u32 info);
+struct arm_smccc_res sip_smc_lastlog_request(void);
+
+int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2);
+int sip_smc_virtual_poweroff(void);
+int sip_smc_remotectl_config(u32 func, u32 data);
+
+int sip_smc_secure_reg_write(u32 addr_phy, u32 val);
+u32 sip_smc_secure_reg_read(u32 addr_phy);
+struct arm_smccc_res sip_smc_bus_config(u32 arg0, u32 arg1, u32 arg2);
+
+/***************************fiq debugger **************************************/
+void sip_fiq_debugger_enable_fiq(bool enable, uint32_t tgt_cpu);
+void sip_fiq_debugger_enable_debug(bool enable);
+int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id, void *callback_fn);
+int sip_fiq_debugger_set_print_port(u32 port_phyaddr, u32 baudrate);
+int sip_fiq_debugger_request_share_memory(void);
+int sip_fiq_debugger_get_target_cpu(void);
+int sip_fiq_debugger_switch_cpu(u32 cpu);
+int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu, u32 flag);
+int sip_fiq_debugger_is_enabled(void);
+int sip_fiq_debugger_sdei_get_event_id(u32 *fiq, u32 *sw_cpu, u32 *flag);
+#else
+static inline struct arm_smccc_res sip_smc_get_atf_version(void)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res sip_smc_get_sip_version(void)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res sip_smc_dram(u32 arg0, u32 arg1, u32 arg2)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res sip_smc_request_share_mem
+			(u32 page_num, share_page_type_t page_type)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res sip_smc_mcu_el3fiq
+			(u32 arg0, u32 arg1, u32 arg2)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res
+sip_smc_vpu_reset(u32 arg0, u32 arg1, u32 arg2)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline struct arm_smccc_res sip_smc_lastlog_request(void)
+{
+	struct arm_smccc_res tmp = {0};
+	return tmp;
+}
+
+static inline int sip_smc_set_suspend_mode(u32 ctrl, u32 config1, u32 config2)
+{
+	return 0;
+}
+
+static inline int sip_smc_get_suspend_info(u32 info)
+{
+	return 0;
+}
+
+static inline int sip_smc_virtual_poweroff(void) { return 0; }
+static inline int sip_smc_remotectl_config(u32 func, u32 data) { return 0; }
+static inline u32 sip_smc_secure_reg_read(u32 addr_phy) { return 0; }
+static inline int sip_smc_secure_reg_write(u32 addr_phy, u32 val) { return 0; }
+static inline int sip_smc_soc_bus_div(u32 arg0, u32 arg1, u32 arg2)
+{
+	return 0;
+}
+
+/***************************fiq debugger **************************************/
+static inline void sip_fiq_debugger_enable_fiq
+			(bool enable, uint32_t tgt_cpu) { return; }
+
+static inline void sip_fiq_debugger_enable_debug(bool enable) { return; }
+static inline int sip_fiq_debugger_uart_irq_tf_init(u32 irq_id,
+						    void *callback_fn)
+{
+	return 0;
+}
+
+static inline int sip_fiq_debugger_set_print_port(u32 port_phyaddr,
+						  u32 baudrate)
+{
+	return 0;
+}
+
+static inline int sip_fiq_debugger_request_share_memory(void) { return 0; }
+static inline int sip_fiq_debugger_get_target_cpu(void) { return 0; }
+static inline int sip_fiq_debugger_switch_cpu(u32 cpu) { return 0; }
+static inline int sip_fiq_debugger_sdei_switch_cpu(u32 cur_cpu, u32 target_cpu,
+						   u32 flag) { return 0; }
+static inline int sip_fiq_debugger_is_enabled(void) { return 0; }
+#endif
+
+/* 32-bit OP-TEE context, never change order of members! */
+struct sm_nsec_ctx {
+	u32 usr_sp;
+	u32 usr_lr;
+	u32 irq_spsr;
+	u32 irq_sp;
+	u32 irq_lr;
+	u32 fiq_spsr;
+	u32 fiq_sp;
+	u32 fiq_lr;
+	u32 svc_spsr;
+	u32 svc_sp;
+	u32 svc_lr;
+	u32 abt_spsr;
+	u32 abt_sp;
+	u32 abt_lr;
+	u32 und_spsr;
+	u32 und_sp;
+	u32 und_lr;
+	u32 mon_lr;
+	u32 mon_spsr;
+	u32 r4;
+	u32 r5;
+	u32 r6;
+	u32 r7;
+	u32 r8;
+	u32 r9;
+	u32 r10;
+	u32 r11;
+	u32 r12;
+	u32 r0;
+	u32 r1;
+	u32 r2;
+	u32 r3;
+};
+
+/* 64-bit ATF context, never change order of members! */
+struct gp_regs_ctx {
+	u64 x0;
+	u64 x1;
+	u64 x2;
+	u64 x3;
+	u64 x4;
+	u64 x5;
+	u64 x6;
+	u64 x7;
+	u64 x8;
+	u64 x9;
+	u64 x10;
+	u64 x11;
+	u64 x12;
+	u64 x13;
+	u64 x14;
+	u64 x15;
+	u64 x16;
+	u64 x17;
+	u64 x18;
+	u64 x19;
+	u64 x20;
+	u64 x21;
+	u64 x22;
+	u64 x23;
+	u64 x24;
+	u64 x25;
+	u64 x26;
+	u64 x27;
+	u64 x28;
+	u64 x29;
+	u64 lr;
+	u64 sp_el0;
+	u64 scr_el3;
+	u64 runtime_sp;
+	u64 spsr_el3;
+	u64 elr_el3;
+};
+
+#endif
diff --git a/include/uapi/drm/rockchip_ebc_drm.h b/include/uapi/drm/rockchip_ebc_drm.h
new file mode 100644
index 000000000..f9b27f30f
--- /dev/null
+++ b/include/uapi/drm/rockchip_ebc_drm.h
@@ -0,0 +1,172 @@
+#ifndef __ROCKCHIP_EBC_DRM_H__
+#define __ROCKCHIP_EBC_DRM_H__
+
+#include "drm.h"
+#include "drm/drm_mode.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y1 0 << 4
+#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y2 1 << 4
+#define ROCKCHIP_EBC_HINT_BIT_DEPTH_Y4 2 << 4
+#define ROCKCHIP_EBC_HINT_BIT_DEPTH_MASK 3 << 4
+#define ROCKCHIP_EBC_HINT_THRESHOLD 0 << 6
+#define ROCKCHIP_EBC_HINT_DITHER 1 << 6
+#define ROCKCHIP_EBC_HINT_REDRAW 1 << 7
+#define ROCKCHIP_EBC_HINT_MASK 0xf0
+
+#define ROCKCHIP_EBC_DRIVER_MODE_NORMAL			0
+#define ROCKCHIP_EBC_DRIVER_MODE_FAST			1
+#define ROCKCHIP_EBC_DRIVER_MODE_ZERO_WAVEFORM		8
+#define ROCKCHIP_EBC_DRIVER_MODE_PHASE_SEQUENCE		9
+
+#define ROCKCHIP_EBC_DITHER_MODE_BAYER		0
+#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16	1
+#define ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32	2
+
+struct drm_rockchip_ebc_trigger_global_refresh {
+	bool trigger_global_refresh;
+};
+
+/**
+ * struct drm_rockchip_ebc_off_screen - Pointer to userspace buffer.
+ * @info1: unused.
+ * @ptr_screen_content: pointer to width * height * buffer containing
+ *   the horizontally flipped off screen. The highest four bits of each
+ *   byte are ignored.
+ */
+struct drm_rockchip_ebc_off_screen {
+	__u64 info1;
+	__u64 ptr_screen_content;
+};
+
+/**
+ * struct drm_rockchip_ebc_extract_fbs - Pointers to userspace buffers. Pointers
+   that are NULL are skipped without warning.
+ * @ptr_packed_inner_outer_nextprev: pointer to 3 * width * height buffer
+ *   containing packed tuples of inner counters, outer counters, and a
+ *   packed Y4 (next, prev) pairs.
+ * @ptr_hints: pointer to width * height buffer for hints.
+ * @ptr_prelim_target: pointer to width * height buffer containing packed Y4
+     (prelim, target) pairs.
+ * @ptr_phase1: pointer to width * height / 4 buffer containing the first of
+     the double-buffered phase buffers.
+ * @ptr_phase2: pointer to width * height / 4 buffer containing the second of
+     the double-buffered phase buffers.
+ */
+struct drm_rockchip_ebc_extract_fbs {
+	char *ptr_packed_inner_outer_nextprev;
+	char *ptr_hints;
+	char *ptr_prelim_target;
+	char *ptr_phase1;
+	char *ptr_phase2;
+};
+
+/**
+ * struct drm_rockchip_ebc_rect_hint - Pixels hint definition
+ * @hints: Pixel hints for all pixel in the rectangle.
+ * @padding: Padding for 64bit alignment.
+ * @rect: Rectangular region on which the hints should be applied.
+ */
+struct drm_rockchip_ebc_rect_hint {
+	__u8		hints;
+	__u8		padding[7];
+	struct drm_mode_rect	rect;
+};
+
+/**
+  * struct drm_mode_rect_hints
+  * @set_default_hint: Reset hints to their default before applying new ones.
+  * @default_hint: Hint to use for uncovered pixels.
+  * @padding: 64bit alignment padding.
+  * @num_rects: Number of rectangles contained in rect_hints
+  * @rect_hints: Pointer to an array of struct drm_rockchip_ebc_rect_hint.
+  */
+struct drm_rockchip_ebc_rect_hints {
+	__u8	set_default_hint;
+	__u8	default_hint;
+	__u8	padding[2];
+	__u32	num_rects;
+	__u64	rect_hints;
+};
+
+/**
+ * struct drm_rockchip_ebc_mode - Query and set driver/dither  modes and
+ *   redraw delay
+ * @set_driver_mode: apply driver_mode instead of reading it to the same
+ *   field.
+ * @driver_mode: one of ROCKCHIP_EBC_DRIVER_MODE_NORMAL or
+ *   ROCKCHIP_EBC_DRIVER_MODE_FAST.
+ * @set_dither_mode: apply dither_mode instead of reading it to the same
+ *   field.
+ * @dither_mode: one of ROCKCHIP_EBC_DITHER_MODE_BAYER,
+ *   ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_16, or
+ *   ROCKCHIP_EBC_DITHER_MODE_BLUE_NOISE_32.
+ * @redraw_delay: number of hardware frames to delay redraws.
+ * @set_redraw_delay: apply redraw_delay instead of reading it to the same
+ *  field.
+ */
+struct drm_rockchip_ebc_mode {
+	__u8	set_driver_mode;
+	__u8	driver_mode;
+	__u8	set_dither_mode;
+	__u8	dither_mode;
+	__u16	redraw_delay;
+	__u8	set_redraw_delay;
+	__u8	_pad;
+};
+
+/**
+ * struct drm_rockchip_ebc_zero_waveform- Query and enable/disable zero
+ *   waveform mode
+ * @set_zero_waveform_mode: apply zero_waveform_mode instead of reading it to
+ *   the same field.
+ * @zero_waveform_mode: 0 for disable(d), 1 for enable(d)
+ */
+struct drm_rockchip_ebc_zero_waveform {
+	__u8	set_zero_waveform_mode;
+	__u8	zero_waveform_mode;
+	__u8	_pad[6];
+};
+
+#define ROCKCHIP_EBC_MAX_PHASE_SEQUENCES 96
+#define ROCKCHIP_EBC_MAX_REGIONS 8
+
+struct drm_rockchip_ebc_phase_sequence_element {
+	__u32			delay_ms;
+	__u8			num_frames;
+	__u8			_pad[3];
+	__u32			num_regions;
+	struct drm_mode_rect	rect[ROCKCHIP_EBC_MAX_REGIONS];
+	__u8			phase[ROCKCHIP_EBC_MAX_REGIONS];
+};
+
+struct drm_rockchip_ebc_phase_sequence {
+	__u8						num_seqs;
+	__u8						do_init;
+	__u8						do_gc16;
+	__u8						gc_target;
+	__u8						do_force_temperature;
+	__u8						force_temperature;
+	__u8						_pad[6];
+	__u32						delay_ms;
+	struct drm_rockchip_ebc_phase_sequence_element elms[ROCKCHIP_EBC_MAX_PHASE_SEQUENCES];
+};
+
+#define DRM_ROCKCHIP_EBC_NUM_IOCTLS		0x07
+
+#define DRM_IOCTL_ROCKCHIP_EBC_GLOBAL_REFRESH	DRM_IOWR(DRM_COMMAND_BASE + 0x00, struct drm_rockchip_ebc_trigger_global_refresh)
+#define DRM_IOCTL_ROCKCHIP_EBC_OFF_SCREEN	DRM_IOW(DRM_COMMAND_BASE + 0x01, struct drm_rockchip_ebc_off_screen)
+#define DRM_IOCTL_ROCKCHIP_EBC_EXTRACT_FBS	DRM_IOWR(DRM_COMMAND_BASE + 0x02, struct drm_rockchip_ebc_extract_fbs)
+#define DRM_IOCTL_ROCKCHIP_EBC_RECT_HINTS	DRM_IOW(DRM_COMMAND_BASE + 0x03, struct drm_rockchip_ebc_rect_hints)
+#define DRM_IOCTL_ROCKCHIP_EBC_MODE		DRM_IOWR(DRM_COMMAND_BASE + 0x04, struct drm_rockchip_ebc_mode)
+#define DRM_IOCTL_ROCKCHIP_EBC_ZERO_WAVEFORM	DRM_IOWR(DRM_COMMAND_BASE + 0x05, struct drm_rockchip_ebc_zero_waveform)
+#define DRM_IOCTL_ROCKCHIP_EBC_PHASE_SEQUENCE	DRM_IOW(DRM_COMMAND_BASE + 0x06, struct drm_rockchip_ebc_phase_sequence)
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* __ROCKCHIP_EBC_DRM_H__*/
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index f84ed133a..4378671ad 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -204,16 +204,11 @@ enum v4l2_colorfx {
 #define V4L2_CID_USER_ASPEED_BASE		(V4L2_CID_USER_BASE + 0x11a0)
 
 /*
- * The base for Nuvoton NPCM driver controls.
- * We reserve 16 controls for this driver.
- */
-#define V4L2_CID_USER_NPCM_BASE			(V4L2_CID_USER_BASE + 0x11b0)
-
-/*
- * The base for THine THP7312 driver controls.
+ * The base for RGA driver controls.
  * We reserve 32 controls for this driver.
+ * hex(int("0x11a0", 16) + 16)
  */
-#define V4L2_CID_USER_THP7312_BASE		(V4L2_CID_USER_BASE + 0x11c0)
+#define V4L2_CID_USER_RGA_BASE		(V4L2_CID_USER_BASE + 0x11b0)
 
 /*
  * The base for the uvc driver controls.
diff --git a/scripts/package/debian/rules b/scripts/package/debian/rules
index a417a7f8b..5159f60e6 100755
--- a/scripts/package/debian/rules
+++ b/scripts/package/debian/rules
@@ -56,7 +56,7 @@ define binary
 	$(Q)dh_installchangelogs $(DH_OPTIONS)
 	$(Q)dh_compress $(DH_OPTIONS)
 	$(Q)dh_fixperms $(DH_OPTIONS)
-	$(Q)dh_gencontrol $(DH_OPTIONS) -- -f$(call mk-files,$@)
+	$(Q)dh_gencontrol $(DH_OPTIONS) -- -DProvides=wireguard-modules -f$(call mk-files,$@)
 	$(Q)dh_md5sums $(DH_OPTIONS)
 	$(Q)dh_builddeb $(DH_OPTIONS) -- $(addprefix -Z,$(KDEB_COMPRESS))
 endef
diff --git a/drivers/regulator/Kconfig.orig b/drivers/regulator/Kconfig
index d10b6f9..97ce6d7 100644
--- a/drivers/regulator/Kconfig.orig
+++ b/drivers/regulator/Kconfig
@@ -1709,15 +1709,13 @@ config REGULATOR_TPS65132
 	  power supply specifically designed for display panels.
 
 config REGULATOR_TPS65185
-	tristate "TI TPS65185 EPD regulator"
-	depends on I2C
+	tristate "TI TPS65185 PMIC for E-Paper Display"
+	depends on GPIOLIB && I2C && IIO
+	select IIO_TRIGGERED_EVENT
 	select REGMAP_I2C
 	help
-	  This driver supports the TPS65185 voltage regulator chip
-	  which is used to provide power to Electronic Paper Displays
-	  so it is found in E-Book readers.
-	  If HWWON is enabled, it also provides temperature measurement.
-
+	  This driver supports the voltage regulators on the TPS65185 PMIC
+	  specifically designed for e-paper display panels.
 
 config REGULATOR_TPS65217
 	tristate "TI TPS65217 Power regulators"
diff --git a/drivers/regulator/Makefile.orig b/drivers/regulator/Makefile
index 35639f3..c1eb8fd 100644
--- a/drivers/regulator/Makefile.orig
+++ b/drivers/regulator/Makefile
@@ -194,7 +194,7 @@ obj-$(CONFIG_REGULATOR_TPS65023) += tps65023-regulator.o
 obj-$(CONFIG_REGULATOR_TPS6507X) += tps6507x-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65086) += tps65086-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65090) += tps65090-regulator.o
-obj-$(CONFIG_REGULATOR_TPS65185) += tps65185.o
+obj-$(CONFIG_REGULATOR_TPS65185) += tps65185-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65217) += tps65217-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65218) += tps65218-regulator.o
 obj-$(CONFIG_REGULATOR_TPS65219) += tps65219-regulator.o
