linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] drm: EPDC driver for i.MX6
@ 2022-02-06  8:00 Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
                   ` (5 more replies)
  0 siblings, 6 replies; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

Add a driver for the Electrophoretic Display Controller found in the
i.MX6 SoCs.

In combination with a driver for an EPD PMIC (like the TPS65185 or the
SY7636A), it works with the EPDC found in i.MX6SLL based devices and the
EPDC found in i.MX6SL devices.

Support for waveforms might be limited, there was no 4bit waveform found
which works with the 6SLL but it works with the vendor waveforms of the
Kobo Clara HD (6SLL), the Tolino Shine 2/3 (6SL).
On the 6SL devices, also the epdc_E060SCM.fw works but not as brilliant
as the vendor one.

It does not involve the PXP yet. The NXP/Freescale kernel fork uses that
for rotation and mysterious waveform handling. That is not planed to be
upstreamed in the first step.

Also it does not provide any special userspace API to fine-tune updates.
That is also IMHO something for a second step.

Andreas Kemnade (6):
  dt-bindings: display: imx: Add EPDC
  drm: Add skeleton for EPDC driver
  drm: mxc-epdc: Add display and waveform initialisation
  drm: mxc-epdc: Add update management
  ARM: dts: imx6sll: add EPDC
  arm: dts: imx6sl: Add EPDC

 .../bindings/display/imx/fsl,mxc-epdc.yaml    |  159 +++
 arch/arm/boot/dts/imx6sl.dtsi                 |    3 +
 arch/arm/boot/dts/imx6sll.dtsi                |    9 +
 drivers/gpu/drm/Kconfig                       |    2 +
 drivers/gpu/drm/Makefile                      |    1 +
 drivers/gpu/drm/mxc-epdc/Kconfig              |   15 +
 drivers/gpu/drm/mxc-epdc/Makefile             |    5 +
 drivers/gpu/drm/mxc-epdc/epdc_hw.c            |  497 +++++++
 drivers/gpu/drm/mxc-epdc/epdc_hw.h            |    8 +
 drivers/gpu/drm/mxc-epdc/epdc_regs.h          |  442 ++++++
 drivers/gpu/drm/mxc-epdc/epdc_update.c        | 1210 +++++++++++++++++
 drivers/gpu/drm/mxc-epdc/epdc_update.h        |    9 +
 drivers/gpu/drm/mxc-epdc/epdc_waveform.c      |  189 +++
 drivers/gpu/drm/mxc-epdc/epdc_waveform.h      |    7 +
 drivers/gpu/drm/mxc-epdc/mxc_epdc.h           |  151 ++
 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c       |  373 +++++
 16 files changed, 3080 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
 create mode 100644 drivers/gpu/drm/mxc-epdc/Kconfig
 create mode 100644 drivers/gpu/drm/mxc-epdc/Makefile
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_regs.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_update.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_update.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c

-- 
2.30.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  2022-02-11 15:46   ` Rob Herring
                     ` (2 more replies)
  2022-02-06  8:00 ` [RFC PATCH 2/6] drm: Add skeleton for EPDC driver Andreas Kemnade
                   ` (4 subsequent siblings)
  5 siblings, 3 replies; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

Add a binding for the Electrophoretic Display Controller found at least
in the i.MX6.
The timing subnode is directly here to avoid having display parameters
spread all over the plate.

Supplies are organized the same way as in the fbdev driver in the
NXP/Freescale kernel forks. The regulators used for that purpose,
like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
start a bunch of regulators of higher or negative voltage with a
well-defined timing. VCOM can be handled separately, but can also be
incorporated into that single bit.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
 1 file changed, 159 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
new file mode 100644
index 000000000000..7e0795cc3f70
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
@@ -0,0 +1,159 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,mxc-epdc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX6 EPDC
+
+maintainers:
+  - Andreas Kemnade <andreas@kemnade.info>
+
+description: |
+  The EPDC is a controller for handling electronic paper displays found in
+  i.MX6 SoCs.
+
+properties:
+  compatible:
+    enum:
+      - fsl,imx6sl-epdc
+      - fsl,imx6sll-epdc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: Bus clock
+      - description: Pixel clock
+
+  clock-names:
+    items:
+      - const: axi
+      - const: pix
+
+  interrupts:
+    maxItems: 1
+
+  vscan-holdoff:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  sdoed-width:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  sdoed-delay:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  sdoez-width:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  sdoez-delay:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  gdclk-hp-offs:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  gdsp-offs:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  gdoe-offs:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  gdclk-offs:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  num-ce:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    maxItems: 1
+
+  timing:
+    $ref: /display/panel/panel-timing.yaml#
+
+  DISPLAY-supply:
+    description:
+      A couple of +/- voltages automatically powered on in a defintive order
+
+  VCOM-supply:
+    description: compensation voltage
+
+  V3P3-supply:
+    description: V3P3 supply
+
+  epd-thermal-zone:
+    description:
+      Zone to get temperature of the EPD from, practically ambient temperature.
+
+
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - interrupts
+  - vscan-holdoff
+  - sdoed-width
+  - sdoed-delay
+  - sdoez-width
+  - sdoez-delay
+  - gdclk-hp-offs
+  - gdsp-offs
+  - gdoe-offs
+  - gdclk-offs
+  - num-ce
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx6sl-clock.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    epdc: epdc@20f4000 {
+        compatible = "fsl,imx6sl-epdc";
+        reg = <0x020f4000 0x4000>;
+        interrupts = <0 97 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clks IMX6SL_CLK_EPDC_AXI>, <&clks IMX6SL_CLK_EPDC_PIX>;
+        clock-names = "axi", "pix";
+
+        pinctrl-names = "default";
+        pinctrl-0 = <&pinctrl_epdc0>;
+        V3P3-supply = <&V3P3_reg>;
+        VCOM-supply = <&VCOM_reg>;
+        DISPLAY-supply = <&DISPLAY_reg>;
+        epd-thermal-zone = "epd-thermal";
+
+        vscan-holdoff = <4>;
+        sdoed-width = <10>;
+        sdoed-delay = <20>;
+        sdoez-width = <10>;
+        sdoez-delay = <20>;
+        gdclk-hp-offs = <562>;
+        gdsp-offs = <662>;
+        gdoe-offs = <0>;
+        gdclk-offs = <225>;
+        num-ce = <3>;
+        status = "okay";
+
+        timing {
+                clock-frequency = <80000000>;
+                hactive = <1448>;
+                hback-porch = <16>;
+                hfront-porch = <102>;
+                hsync-len = <28>;
+                vactive = <1072>;
+                vback-porch = <4>;
+                vfront-porch = <4>;
+                vsync-len = <2>;
+        };
+    };
+...
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 2/6] drm: Add skeleton for EPDC driver
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  2022-03-12 19:41   ` Jonathan Neuschäfer
  2022-02-06  8:00 ` [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation Andreas Kemnade
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

This driver is for the EPD controller in the i.MX SoCs. Add a skeleton
and basic things for the driver

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/gpu/drm/Kconfig                 |   2 +
 drivers/gpu/drm/Makefile                |   1 +
 drivers/gpu/drm/mxc-epdc/Kconfig        |  15 +
 drivers/gpu/drm/mxc-epdc/Makefile       |   5 +
 drivers/gpu/drm/mxc-epdc/epdc_regs.h    | 442 ++++++++++++++++++++++++
 drivers/gpu/drm/mxc-epdc/mxc_epdc.h     |  20 ++
 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c | 248 +++++++++++++
 7 files changed, 733 insertions(+)
 create mode 100644 drivers/gpu/drm/mxc-epdc/Kconfig
 create mode 100644 drivers/gpu/drm/mxc-epdc/Makefile
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_regs.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index b1f22e457fd0..6b6b44ff7556 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -390,6 +390,8 @@ source "drivers/gpu/drm/gud/Kconfig"
 
 source "drivers/gpu/drm/sprd/Kconfig"
 
+source "drivers/gpu/drm/mxc-epdc/Kconfig"
+
 config DRM_HYPERV
 	tristate "DRM Support for Hyper-V synthetic video device"
 	depends on DRM && PCI && MMU && HYPERV
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 301a44dc18e3..e5eb9815cf9a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -131,6 +131,7 @@ obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
 obj-$(CONFIG_DRM_TIDSS) += tidss/
+obj-$(CONFIG_DRM_MXC_EPDC) += mxc-epdc/
 obj-y			+= xlnx/
 obj-y			+= gud/
 obj-$(CONFIG_DRM_HYPERV) += hyperv/
diff --git a/drivers/gpu/drm/mxc-epdc/Kconfig b/drivers/gpu/drm/mxc-epdc/Kconfig
new file mode 100644
index 000000000000..3f5744161cff
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+config DRM_MXC_EPDC
+	tristate "i.MX EPD Controller"
+	depends on DRM && OF
+	depends on (COMPILE_TEST || ARCH_MXC)
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DMA_CMA if HAVE_DMA_CONTIGUOUS
+	select CMA if HAVE_DMA_CONTIGUOUS
+	help
+	  Choose this option if you have an i.MX system with an EPDC.
+	  It enables the usage of E-paper displays. A waveform is expected
+	  to be present in /lib/firmware/imx/epdc/epdc.fw
+
+	  If M is selected this module will be called mxc_epdc_drm.
diff --git a/drivers/gpu/drm/mxc-epdc/Makefile b/drivers/gpu/drm/mxc-epdc/Makefile
new file mode 100644
index 000000000000..a47ced72b7f6
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+mxc_epdc_drm-y := mxc_epdc_drv.o
+
+obj-$(CONFIG_DRM_MXC_EPDC) += mxc_epdc_drm.o
+
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_regs.h b/drivers/gpu/drm/mxc-epdc/epdc_regs.h
new file mode 100644
index 000000000000..83445c56d911
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_regs.h
@@ -0,0 +1,442 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2010-2013 Freescale Semiconductor, Inc. */
+
+#ifndef __EPDC_REGS_INCLUDED__
+#define __EPDC_REGS_INCLUDED__
+
+/*************************************
+ * Register addresses
+ **************************************/
+
+#define EPDC_CTRL			0x000
+#define EPDC_CTRL_SET			0x004
+#define EPDC_CTRL_CLEAR			0x008
+#define EPDC_CTRL_TOGGLE		0x00C
+#define EPDC_WB_ADDR_TCE_V3		0x010
+#define EPDC_WVADDR			0x020
+#define EPDC_WB_ADDR			0x030
+#define EPDC_RES			0x040
+#define EPDC_FORMAT			0x050
+#define EPDC_FORMAT_SET			0x054
+#define EPDC_FORMAT_CLEAR		0x058
+#define EPDC_FORMAT_TOGGLE		0x05C
+#define EPDC_WB_FIELD0			0x060
+#define EPDC_WB_FIELD0_SET		0x064
+#define EPDC_WB_FIELD0_CLEAR		0x068
+#define EPDC_WB_FIELD0_TOGGLE		0x06C
+#define EPDC_WB_FIELD1			0x070
+#define EPDC_WB_FIELD1_SET		0x074
+#define EPDC_WB_FIELD1_CLEAR		0x078
+#define EPDC_WB_FIELD1_TOGGLE		0x07C
+#define EPDC_WB_FIELD2			0x080
+#define EPDC_WB_FIELD2_SET		0x084
+#define EPDC_WB_FIELD2_CLEAR		0x088
+#define EPDC_WB_FIELD2_TOGGLE		0x08C
+#define EPDC_WB_FIELD3			0x090
+#define EPDC_WB_FIELD3_SET		0x094
+#define EPDC_WB_FIELD3_CLEAR		0x098
+#define EPDC_WB_FIELD3_TOGGLE		0x09C
+#define EPDC_FIFOCTRL			0x0A0
+#define EPDC_FIFOCTRL_SET		0x0A4
+#define EPDC_FIFOCTRL_CLEAR		0x0A8
+#define EPDC_FIFOCTRL_TOGGLE		0x0AC
+#define EPDC_UPD_ADDR			0x100
+#define EPDC_UPD_STRIDE			0x110
+#define EPDC_UPD_CORD			0x120
+#define EPDC_UPD_SIZE			0x140
+#define EPDC_UPD_CTRL			0x160
+#define EPDC_UPD_FIXED			0x180
+#define EPDC_TEMP			0x1A0
+#define EPDC_AUTOWV_LUT			0x1C0
+#define EPDC_TCE_CTRL			0x200
+#define EPDC_TCE_SDCFG			0x220
+#define EPDC_TCE_GDCFG			0x240
+#define EPDC_TCE_HSCAN1			0x260
+#define EPDC_TCE_HSCAN2			0x280
+#define EPDC_TCE_VSCAN			0x2A0
+#define EPDC_TCE_OE			0x2C0
+#define EPDC_TCE_POLARITY		0x2E0
+#define EPDC_TCE_TIMING1		0x300
+#define EPDC_TCE_TIMING2		0x310
+#define EPDC_TCE_TIMING3		0x320
+#define EPDC_PIGEON_CTRL0		0x380
+#define EPDC_PIGEON_CTRL1		0x390
+#define EPDC_IRQ_MASK1			0x3C0
+#define EPDC_IRQ_MASK1_SET		0x3C4
+#define EPDC_IRQ_MASK1_CLEAR		0x3C8
+#define EPDC_IRQ_MASK1_TOGGLE		0x3CC
+#define EPDC_IRQ_MASK2			0x3D0
+#define EPDC_IRQ_MASK2_SET		0x3D4
+#define EPDC_IRQ_MASK2_CLEAR		0x3D8
+#define EPDC_IRQ_MASK2_TOGGLE		0x3DC
+#define EPDC_IRQ1			0x3E0
+#define EPDC_IRQ1_SET			0x3E4
+#define EPDC_IRQ1_CLEAR			0x3E8
+#define EPDC_IRQ1_TOGGLE		0x3EC
+#define EPDC_IRQ2			0x3F0
+#define EPDC_IRQ2_SET			0x3F4
+#define EPDC_IRQ2_CLEAR			0x3F8
+#define EPDC_IRQ2_TOGGLE		0x3FC
+#define EPDC_IRQ_MASK			0x400
+#define EPDC_IRQ_MASK_SET		0x404
+#define EPDC_IRQ_MASK_CLEAR		0x408
+#define EPDC_IRQ_MASK_TOGGLE		0x40C
+#define EPDC_IRQ			0x420
+#define EPDC_IRQ_SET			0x424
+#define EPDC_IRQ_CLEAR			0x428
+#define EPDC_IRQ_TOGGLE			0x42C
+#define EPDC_STATUS_LUTS		0x440
+#define EPDC_STATUS_LUTS_SET		0x444
+#define EPDC_STATUS_LUTS_CLEAR		0x448
+#define EPDC_STATUS_LUTS_TOGGLE		0x44C
+#define EPDC_STATUS_LUTS2		0x450
+#define EPDC_STATUS_LUTS2_SET		0x454
+#define EPDC_STATUS_LUTS2_CLEAR		0x458
+#define EPDC_STATUS_LUTS2_TOGGLE	0x45C
+#define EPDC_STATUS_NEXTLUT		0x460
+#define EPDC_STATUS_COL			0x480
+#define EPDC_STATUS_COL2		0x490
+#define EPDC_STATUS			0x4A0
+#define EPDC_STATUS_SET			0x4A4
+#define EPDC_STATUS_CLEAR		0x4A8
+#define EPDC_STATUS_TOGGLE		0x4AC
+#define EPDC_UPD_COL_CORD		0x4C0
+#define EPDC_UPD_COL_SIZE		0x4E0
+#define EPDC_DEBUG			0x500
+#define EPDC_DEBUG_LUT			0x530
+#define EPDC_HIST1_PARAM		0x600
+#define EPDC_HIST2_PARAM		0x610
+#define EPDC_HIST4_PARAM		0x620
+#define EPDC_HIST8_PARAM0		0x630
+#define EPDC_HIST8_PARAM1		0x640
+#define EPDC_HIST16_PARAM0		0x650
+#define EPDC_HIST16_PARAM1		0x660
+#define EPDC_HIST16_PARAM2		0x670
+#define EPDC_HIST16_PARAM3		0x680
+#define EPDC_GPIO			0x700
+#define EPDC_VERSION			0x7F0
+#define EPDC_PIGEON_0_0			0x800
+#define EPDC_PIGEON_0_1			0x810
+#define EPDC_PIGEON_0_2			0x820
+#define EPDC_PIGEON_1_0			0x840
+#define EPDC_PIGEON_1_1			0x850
+#define EPDC_PIGEON_1_2			0x860
+#define EPDC_PIGEON_2_0			0x880
+#define EPDC_PIGEON_2_1			0x890
+#define EPDC_PIGEON_2_2			0x8A0
+#define EPDC_PIGEON_3_0			0x8C0
+#define EPDC_PIGEON_3_1			0x8D0
+#define EPDC_PIGEON_3_2			0x8E0
+#define EPDC_PIGEON_4_0			0x900
+#define EPDC_PIGEON_4_1			0x910
+#define EPDC_PIGEON_4_2			0x920
+#define EPDC_PIGEON_5_0			0x940
+#define EPDC_PIGEON_5_1			0x950
+#define EPDC_PIGEON_5_2			0x960
+#define EPDC_PIGEON_6_0			0x980
+#define EPDC_PIGEON_6_1			0x990
+#define EPDC_PIGEON_6_2			0x9A0
+#define EPDC_PIGEON_7_0			0x9C0
+#define EPDC_PIGEON_7_1			0x9D0
+#define EPDC_PIGEON_7_2			0x9E0
+#define EPDC_PIGEON_8_0			0xA00
+#define EPDC_PIGEON_8_1			0xA10
+#define EPDC_PIGEON_8_2			0xA20
+#define EPDC_PIGEON_9_0			0xA40
+#define EPDC_PIGEON_9_1			0xA50
+#define EPDC_PIGEON_9_2			0xA60
+#define EPDC_PIGEON_10_0		0xA80
+#define EPDC_PIGEON_10_1		0xA90
+#define EPDC_PIGEON_10_2		0xAA0
+#define EPDC_PIGEON_11_0		0xAC0
+#define EPDC_PIGEON_11_1		0xAD0
+#define EPDC_PIGEON_11_2		0xAE0
+#define EPDC_PIGEON_12_0		0xB00
+#define EPDC_PIGEON_12_1		0xB10
+#define EPDC_PIGEON_12_2		0xB20
+#define EPDC_PIGEON_13_0		0xB40
+#define EPDC_PIGEON_13_1		0xB50
+#define EPDC_PIGEON_13_2		0xB60
+#define EPDC_PIGEON_14_0		0xB80
+#define EPDC_PIGEON_14_1		0xB90
+#define EPDC_PIGEON_14_2		0xBA0
+#define EPDC_PIGEON_15_0		0xBC0
+#define EPDC_PIGEON_15_1		0xBD0
+#define EPDC_PIGEON_15_2		0xBE0
+#define EPDC_WB_ADDR_TCE		0xC10
+
+/*
+ * Register field definitions
+ */
+
+enum {
+/* EPDC_CTRL field values */
+	EPDC_CTRL_SFTRST = 0x80000000,
+	EPDC_CTRL_CLKGATE = 0x40000000,
+	EPDC_CTRL_SRAM_POWERDOWN = 0x100,
+	EPDC_CTRL_UPD_DATA_SWIZZLE_MASK = 0xC0,
+	EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP = 0,
+	EPDC_CTRL_UPD_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x40,
+	EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_SWAP = 0x80,
+	EPDC_CTRL_UPD_DATA_SWIZZLE_HWD_BYTE_SWAP = 0xC0,
+	EPDC_CTRL_LUT_DATA_SWIZZLE_MASK = 0x30,
+	EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP = 0,
+	EPDC_CTRL_LUT_DATA_SWIZZLE_ALL_BYTES_SWAP = 0x10,
+	EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_SWAP = 0x20,
+	EPDC_CTRL_LUT_DATA_SWIZZLE_HWD_BYTE_SWAP = 0x30,
+	EPDC_CTRL_BURST_LEN_8_8 = 0x1,
+	EPDC_CTRL_BURST_LEN_8_16 = 0,
+
+/* EPDC_RES field values */
+	EPDC_RES_VERTICAL_MASK = 0x1FFF0000,
+	EPDC_RES_VERTICAL_OFFSET = 16,
+	EPDC_RES_HORIZONTAL_MASK = 0x1FFF,
+	EPDC_RES_HORIZONTAL_OFFSET = 0,
+
+/* EPDC_FORMAT field values */
+	EPDC_FORMAT_BUF_PIXEL_SCALE_ROUND = 0x1000000,
+	EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK = 0xFF0000,
+	EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET = 16,
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK = 0x700,
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P2N = 0x200,
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P3N = 0x300,
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N = 0x400,
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N = 0x500,
+	EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT = 0x0,
+	EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT_VCOM = 0x1,
+	EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT = 0x2,
+	EPDC_FORMAT_TFT_PIXEL_FORMAT_4BIT_VCOM = 0x3,
+
+/* EPDC_FIFOCTRL field values */
+	EPDC_FIFOCTRL_ENABLE_PRIORITY = 0x80000000,
+	EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK = 0xFF0000,
+	EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET = 16,
+	EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK = 0xFF00,
+	EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET = 8,
+	EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK = 0xFF,
+	EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET = 0,
+
+/* EPDC_UPD_CORD field values */
+	EPDC_UPD_CORD_YCORD_MASK = 0x1FFF0000,
+	EPDC_UPD_CORD_YCORD_OFFSET = 16,
+	EPDC_UPD_CORD_XCORD_MASK = 0x1FFF,
+	EPDC_UPD_CORD_XCORD_OFFSET = 0,
+
+/* EPDC_UPD_SIZE field values */
+	EPDC_UPD_SIZE_HEIGHT_MASK = 0x1FFF0000,
+	EPDC_UPD_SIZE_HEIGHT_OFFSET = 16,
+	EPDC_UPD_SIZE_WIDTH_MASK = 0x1FFF,
+	EPDC_UPD_SIZE_WIDTH_OFFSET = 0,
+
+/* EPDC_UPD_CTRL field values */
+	EPDC_UPD_CTRL_USE_FIXED = 0x80000000,
+	EPDC_UPD_CTRL_LUT_SEL_MASK = 0x3F0000,
+	EPDC_UPD_CTRL_LUT_SEL_OFFSET = 16,
+	EPDC_UPD_CTRL_WAVEFORM_MODE_MASK = 0xFF00,
+	EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET = 8,
+	EPDC_UPD_CTRL_AUTOWV_PAUSE = 0x8,
+	EPDC_UPD_CTRL_AUTOWV = 0x4,
+	EPDC_UPD_CTRL_DRY_RUN = 0x2,
+	EPDC_UPD_CTRL_UPDATE_MODE_FULL = 0x1,
+
+/* EPDC_UPD_FIXED field values */
+	EPDC_UPD_FIXED_FIXNP_EN = 0x80000000,
+	EPDC_UPD_FIXED_FIXCP_EN = 0x40000000,
+	EPDC_UPD_FIXED_FIXNP_MASK = 0xFF00,
+	EPDC_UPD_FIXED_FIXNP_OFFSET = 8,
+	EPDC_UPD_FIXED_FIXCP_MASK = 0xFF,
+	EPDC_UPD_FIXED_FIXCP_OFFSET = 0,
+
+/* EPDC_AUTOWV_LUT field values */
+	EPDC_AUTOWV_LUT_DATA_MASK = 0xFF0000,
+	EPDC_AUTOWV_LUT_DATA_OFFSET = 16,
+	EPDC_AUTOWV_LUT_ADDR_MASK = 0xFF,
+	EPDC_AUTOWV_LUT_ADDR_OFFSET = 0,
+
+/* EPDC_TCE_CTRL field values */
+	EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK = 0x1FF0000,
+	EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET = 16,
+	EPDC_TCE_CTRL_VCOM_VAL_MASK = 0xC00,
+	EPDC_TCE_CTRL_VCOM_VAL_OFFSET = 10,
+	EPDC_TCE_CTRL_VCOM_MODE_AUTO = 0x200,
+	EPDC_TCE_CTRL_VCOM_MODE_MANUAL = 0x000,
+	EPDC_TCE_CTRL_DDR_MODE_ENABLE = 0x100,
+	EPDC_TCE_CTRL_LVDS_MODE_CE_ENABLE = 0x80,
+	EPDC_TCE_CTRL_LVDS_MODE_ENABLE = 0x40,
+	EPDC_TCE_CTRL_SCAN_DIR_1_UP = 0x20,
+	EPDC_TCE_CTRL_SCAN_DIR_0_UP = 0x10,
+	EPDC_TCE_CTRL_DUAL_SCAN_ENABLE = 0x8,
+	EPDC_TCE_CTRL_SDDO_WIDTH_16BIT = 0x4,
+	EPDC_TCE_CTRL_PIXELS_PER_SDCLK_2 = 1,
+	EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4 = 2,
+	EPDC_TCE_CTRL_PIXELS_PER_SDCLK_8 = 3,
+
+/* EPDC_TCE_SDCFG field values */
+	EPDC_TCE_SDCFG_SDCLK_HOLD = 0x200000,
+	EPDC_TCE_SDCFG_SDSHR = 0x100000,
+	EPDC_TCE_SDCFG_NUM_CE_MASK = 0xF0000,
+	EPDC_TCE_SDCFG_NUM_CE_OFFSET = 16,
+	EPDC_TCE_SDCFG_SDDO_REFORMAT_STANDARD = 0,
+	EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS = 0x4000,
+	EPDC_TCE_SDCFG_SDDO_INVERT_ENABLE = 0x2000,
+	EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK = 0x1FFF,
+	EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET = 0,
+
+/* EPDC_TCE_GDCFG field values */
+	EPDC_TCE_SDCFG_GDRL = 0x10,
+	EPDC_TCE_SDCFG_GDOE_MODE_DELAYED_GDCLK = 0x2,
+	EPDC_TCE_SDCFG_GDSP_MODE_FRAME_SYNC = 0x1,
+	EPDC_TCE_SDCFG_GDSP_MODE_ONE_LINE = 0x0,
+
+/* EPDC_TCE_HSCAN1 field values */
+	EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK = 0xFFF0000,
+	EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET = 16,
+	EPDC_TCE_HSCAN1_LINE_SYNC_MASK = 0xFFF,
+	EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET = 0,
+
+/* EPDC_TCE_HSCAN2 field values */
+	EPDC_TCE_HSCAN2_LINE_END_MASK = 0xFFF0000,
+	EPDC_TCE_HSCAN2_LINE_END_OFFSET = 16,
+	EPDC_TCE_HSCAN2_LINE_BEGIN_MASK = 0xFFF,
+	EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET = 0,
+
+/* EPDC_TCE_VSCAN field values */
+	EPDC_TCE_VSCAN_FRAME_END_MASK = 0xFF0000,
+	EPDC_TCE_VSCAN_FRAME_END_OFFSET = 16,
+	EPDC_TCE_VSCAN_FRAME_BEGIN_MASK = 0xFF00,
+	EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET = 8,
+	EPDC_TCE_VSCAN_FRAME_SYNC_MASK = 0xFF,
+	EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET = 0,
+
+/* EPDC_TCE_OE field values */
+	EPDC_TCE_OE_SDOED_WIDTH_MASK = 0xFF000000,
+	EPDC_TCE_OE_SDOED_WIDTH_OFFSET = 24,
+	EPDC_TCE_OE_SDOED_DLY_MASK = 0xFF0000,
+	EPDC_TCE_OE_SDOED_DLY_OFFSET = 16,
+	EPDC_TCE_OE_SDOEZ_WIDTH_MASK = 0xFF00,
+	EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET = 8,
+	EPDC_TCE_OE_SDOEZ_DLY_MASK = 0xFF,
+	EPDC_TCE_OE_SDOEZ_DLY_OFFSET = 0,
+
+/* EPDC_TCE_POLARITY field values */
+	EPDC_TCE_POLARITY_GDSP_POL_ACTIVE_HIGH = 0x10,
+	EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH = 0x8,
+	EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH = 0x4,
+	EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH = 0x2,
+	EPDC_TCE_POLARITY_SDCE_POL_ACTIVE_HIGH = 0x1,
+
+/* EPDC_TCE_TIMING1 field values */
+	EPDC_TCE_TIMING1_SDLE_SHIFT_NONE = 0x00,
+	EPDC_TCE_TIMING1_SDLE_SHIFT_1 = 0x10,
+	EPDC_TCE_TIMING1_SDLE_SHIFT_2 = 0x20,
+	EPDC_TCE_TIMING1_SDLE_SHIFT_3 = 0x30,
+	EPDC_TCE_TIMING1_SDCLK_INVERT = 0x8,
+	EPDC_TCE_TIMING1_SDCLK_SHIFT_NONE = 0,
+	EPDC_TCE_TIMING1_SDCLK_SHIFT_1CYCLE = 1,
+	EPDC_TCE_TIMING1_SDCLK_SHIFT_2CYCLES = 2,
+	EPDC_TCE_TIMING1_SDCLK_SHIFT_3CYCLES = 3,
+
+/* EPDC_TCE_TIMING2 field values */
+	EPDC_TCE_TIMING2_GDCLK_HP_MASK = 0xFFFF0000,
+	EPDC_TCE_TIMING2_GDCLK_HP_OFFSET = 16,
+	EPDC_TCE_TIMING2_GDSP_OFFSET_MASK = 0xFFFF,
+	EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET = 0,
+
+/* EPDC_TCE_TIMING3 field values */
+	EPDC_TCE_TIMING3_GDOE_OFFSET_MASK = 0xFFFF0000,
+	EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET = 16,
+	EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK = 0xFFFF,
+	EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET = 0,
+
+/* EPDC_IRQ_MASK/EPDC_IRQ field values */
+	EPDC_IRQ_WB_CMPLT_IRQ = 0x10000,
+	EPDC_IRQ_LUT_COL_IRQ = 0x20000,
+	EPDC_IRQ_TCE_UNDERRUN_IRQ = 0x40000,
+	EPDC_IRQ_FRAME_END_IRQ = 0x80000,
+	EPDC_IRQ_BUS_ERROR_IRQ = 0x100000,
+	EPDC_IRQ_TCE_IDLE_IRQ = 0x200000,
+	EPDC_IRQ_UPD_DONE_IRQ = 0x400000,
+	EPDC_IRQ_PWR_IRQ = 0x800000,
+
+/* EPDC_STATUS_NEXTLUT field values */
+	EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID = 0x100,
+	EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK = 0x3F,
+	EPDC_STATUS_NEXTLUT_NEXT_LUT_OFFSET = 0,
+
+/* EPDC_STATUS field values */
+	EPDC_STATUS_HISTOGRAM_CP_MASK = 0x1F0000,
+	EPDC_STATUS_HISTOGRAM_CP_OFFSET = 16,
+	EPDC_STATUS_HISTOGRAM_NP_MASK = 0x1F00,
+	EPDC_STATUS_HISTOGRAM_NP_OFFSET = 8,
+	EPDC_STATUS_UPD_VOID = 0x8,
+	EPDC_STATUS_LUTS_UNDERRUN = 0x4,
+	EPDC_STATUS_LUTS_BUSY = 0x2,
+	EPDC_STATUS_WB_BUSY = 0x1,
+
+/* EPDC_UPD_COL_CORD field values */
+	EPDC_UPD_COL_CORD_YCORD_MASK = 0x1FFF0000,
+	EPDC_UPD_COL_CORD_YCORD_OFFSET = 16,
+	EPDC_UPD_COL_CORD_XCORD_MASK = 0x1FFF,
+	EPDC_UPD_COL_CORD_XCORD_OFFSET = 0,
+
+/* EPDC_UPD_COL_SIZE field values */
+	EPDC_UPD_COL_SIZE_HEIGHT_MASK = 0x1FFF0000,
+	EPDC_UPD_COL_SIZE_HEIGHT_OFFSET = 16,
+	EPDC_UPD_COL_SIZE_WIDTH_MASK = 0x1FFF,
+	EPDC_UPD_COL_SIZE_WIDTH_OFFSET = 0,
+
+/* EPDC_DEBUG field values */
+	EPDC_DEBUG_UNDERRUN_RECOVER = 0x2,
+	EPDC_DEBUG_COLLISION_OFF = 0x1,
+
+/* EPDC_HISTx_PARAM field values */
+	EPDC_HIST_PARAM_VALUE0_MASK = 0x1F,
+	EPDC_HIST_PARAM_VALUE0_OFFSET = 0,
+	EPDC_HIST_PARAM_VALUE1_MASK = 0x1F00,
+	EPDC_HIST_PARAM_VALUE1_OFFSET = 8,
+	EPDC_HIST_PARAM_VALUE2_MASK = 0x1F0000,
+	EPDC_HIST_PARAM_VALUE2_OFFSET = 16,
+	EPDC_HIST_PARAM_VALUE3_MASK = 0x1F000000,
+	EPDC_HIST_PARAM_VALUE3_OFFSET = 24,
+	EPDC_HIST_PARAM_VALUE4_MASK = 0x1F,
+	EPDC_HIST_PARAM_VALUE4_OFFSET = 0,
+	EPDC_HIST_PARAM_VALUE5_MASK = 0x1F00,
+	EPDC_HIST_PARAM_VALUE5_OFFSET = 8,
+	EPDC_HIST_PARAM_VALUE6_MASK = 0x1F0000,
+	EPDC_HIST_PARAM_VALUE6_OFFSET = 16,
+	EPDC_HIST_PARAM_VALUE7_MASK = 0x1F000000,
+	EPDC_HIST_PARAM_VALUE7_OFFSET = 24,
+	EPDC_HIST_PARAM_VALUE8_MASK = 0x1F,
+	EPDC_HIST_PARAM_VALUE8_OFFSET = 0,
+	EPDC_HIST_PARAM_VALUE9_MASK = 0x1F00,
+	EPDC_HIST_PARAM_VALUE9_OFFSET = 8,
+	EPDC_HIST_PARAM_VALUE10_MASK = 0x1F0000,
+	EPDC_HIST_PARAM_VALUE10_OFFSET = 16,
+	EPDC_HIST_PARAM_VALUE11_MASK = 0x1F000000,
+	EPDC_HIST_PARAM_VALUE11_OFFSET = 24,
+	EPDC_HIST_PARAM_VALUE12_MASK = 0x1F,
+	EPDC_HIST_PARAM_VALUE12_OFFSET = 0,
+	EPDC_HIST_PARAM_VALUE13_MASK = 0x1F00,
+	EPDC_HIST_PARAM_VALUE13_OFFSET = 8,
+	EPDC_HIST_PARAM_VALUE14_MASK = 0x1F0000,
+	EPDC_HIST_PARAM_VALUE14_OFFSET = 16,
+	EPDC_HIST_PARAM_VALUE15_MASK = 0x1F000000,
+	EPDC_HIST_PARAM_VALUE15_OFFSET = 24,
+
+/* EPDC_GPIO field values */
+	EPDC_GPIO_PWRCOM = 0x40,
+	EPDC_GPIO_PWRCTRL_MASK = 0x3C,
+	EPDC_GPIO_PWRCTRL_OFFSET = 2,
+	EPDC_GPIO_BDR_MASK = 0x3,
+	EPDC_GPIO_BDR_OFFSET = 0,
+
+/* EPDC_VERSION field values */
+	EPDC_VERSION_MAJOR_MASK = 0xFF000000,
+	EPDC_VERSION_MAJOR_OFFSET = 24,
+	EPDC_VERSION_MINOR_MASK = 0xFF0000,
+	EPDC_VERSION_MINOR_OFFSET = 16,
+	EPDC_VERSION_STEP_MASK = 0xFFFF,
+	EPDC_VERSION_STEP_OFFSET = 0,
+};
+
+#endif	/* __EPDC_REGS_INCLUDED__ */
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
new file mode 100644
index 000000000000..c5f5280b574f
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2022 Andreas Kemnade */
+
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include <drm/drm_drv.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_simple_kms_helper.h>
+
+struct clk;
+struct regulator;
+struct mxc_epdc {
+	struct drm_device drm;
+	struct drm_simple_display_pipe pipe;
+	struct drm_connector connector;
+	struct display_timing timing;
+};
+
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
new file mode 100644
index 000000000000..c0b0a3bcdb57
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_file.h>
+#include <drm/drm_format_helper.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_prime.h>
+#include <drm/drm_probe_helper.h>
+#include "mxc_epdc.h"
+
+#define DRIVER_NAME "mxc_epdc"
+#define DRIVER_DESC "IMX EPDC"
+#define DRIVER_DATE "20220202"
+#define DRIVER_MAJOR 1
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+#define to_mxc_epdc(x) container_of(x, struct mxc_epdc, drm)
+
+static const struct drm_mode_config_funcs mxc_epdc_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check	   = drm_atomic_helper_check,
+	.atomic_commit	  = drm_atomic_helper_commit,
+};
+
+static struct mxc_epdc *
+drm_pipe_to_mxc_epdc(struct drm_simple_display_pipe *pipe)
+{
+	return container_of(pipe, struct mxc_epdc, pipe);
+}
+
+static struct mxc_epdc *
+drm_connector_to_mxc_epdc(struct drm_connector *connector)
+{
+	return container_of(connector, struct mxc_epdc, connector);
+}
+
+static void mxc_epdc_setup_mode_config(struct drm_device *drm)
+{
+	drm_mode_config_init(drm);
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+	/*
+	 * Maximum update buffer image width due to v2.0 and v2.1 errata
+	 * ERR005313.
+	 */
+	drm->mode_config.max_width = 2047;
+	drm->mode_config.max_height = 2048;
+	drm->mode_config.funcs = &mxc_epdc_mode_config_funcs;
+}
+
+
+DEFINE_DRM_GEM_CMA_FOPS(fops);
+
+static int mxc_epdc_get_modes(struct drm_connector *connector)
+{
+	struct mxc_epdc *priv = drm_connector_to_mxc_epdc(connector);
+	struct drm_display_mode *mode;
+	struct videomode vm;
+
+	videomode_from_timing(&priv->timing, &vm);
+	mode = drm_mode_create(connector->dev);
+	if (!mode) {
+		dev_err(priv->drm.dev, "failed to add mode\n");
+		return 0;
+	}
+
+	drm_display_mode_from_videomode(&vm, mode);
+	mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	return 1;
+}
+
+static const struct
+drm_connector_helper_funcs mxc_epdc_connector_helper_funcs = {
+	.get_modes = mxc_epdc_get_modes,
+};
+
+static const struct drm_connector_funcs mxc_epdc_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy  = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+int mxc_epdc_output(struct drm_device *drm)
+{
+	struct mxc_epdc *priv = to_mxc_epdc(drm);
+	int ret;
+
+	priv->connector.dpms = DRM_MODE_DPMS_OFF;
+	priv->connector.polled = 0;
+	drm_connector_helper_add(&priv->connector,
+				 &mxc_epdc_connector_helper_funcs);
+	ret = drm_connector_init(drm, &priv->connector,
+				 &mxc_epdc_connector_funcs,
+				 DRM_MODE_CONNECTOR_Unknown);
+	if (ret)
+		return ret;
+	ret = of_get_display_timing(drm->dev->of_node, "timing", &priv->timing);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void mxc_epdc_pipe_enable(struct drm_simple_display_pipe *pipe,
+				   struct drm_crtc_state *crtc_state,
+				   struct drm_plane_state *plane_state)
+{
+	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
+	struct drm_display_mode *m = &pipe->crtc.state->adjusted_mode;
+
+	dev_info(priv->drm.dev, "Mode: %d x %d\n", m->hdisplay, m->vdisplay);
+}
+
+static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
+
+	dev_dbg(priv->drm.dev, "pipe disable\n");
+}
+
+static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
+				   struct drm_plane_state *plane_state)
+{
+	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
+
+	dev_dbg(priv->drm.dev, "pipe update\n");
+}
+
+static const struct drm_simple_display_pipe_funcs mxc_epdc_funcs = {
+	.enable	 = mxc_epdc_pipe_enable,
+	.disable = mxc_epdc_pipe_disable,
+	.update	= mxc_epdc_pipe_update,
+	.prepare_fb = drm_gem_simple_display_pipe_prepare_fb,
+};
+
+
+static const uint32_t mxc_epdc_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+static struct drm_driver mxc_epdc_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops = &fops,
+	.dumb_create	    = drm_gem_cma_dumb_create,
+	.prime_handle_to_fd     = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle     = drm_gem_prime_fd_to_handle,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_mmap	 = drm_gem_prime_mmap,
+
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+	.patchlevel = DRIVER_PATCHLEVEL,
+};
+
+
+static int mxc_epdc_probe(struct platform_device *pdev)
+{
+	struct mxc_epdc *priv;
+	int ret;
+
+	priv = devm_drm_dev_alloc(&pdev->dev, &mxc_epdc_driver, struct mxc_epdc, drm);
+	if (IS_ERR(priv))
+		return PTR_ERR(priv);
+
+	platform_set_drvdata(pdev, priv);
+
+	mxc_epdc_setup_mode_config(&priv->drm);
+
+	ret = mxc_epdc_output(&priv->drm);
+	if (ret)
+		return ret;
+
+	drm_simple_display_pipe_init(&priv->drm, &priv->pipe, &mxc_epdc_funcs,
+				     mxc_epdc_formats,
+				     ARRAY_SIZE(mxc_epdc_formats),
+				     NULL,
+				     &priv->connector);
+	drm_plane_enable_fb_damage_clips(&priv->pipe.plane);
+
+	drm_mode_config_reset(&priv->drm);
+
+	ret = drm_dev_register(&priv->drm, 0);
+
+	drm_fbdev_generic_setup(&priv->drm, 32);
+	return 0;
+}
+
+static int mxc_epdc_remove(struct platform_device *pdev)
+{
+	struct mxc_epdc *priv = platform_get_drvdata(pdev);
+
+	drm_dev_unregister(&priv->drm);
+	drm_kms_helper_poll_fini(&priv->drm);
+	drm_mode_config_cleanup(&priv->drm);
+	return 0;
+}
+
+static const struct of_device_id imx_epdc_dt_ids[] = {
+	{ .compatible = "fsl,imx6sl-epdc", },
+	{ .compatible = "fsl,imx6sll-epdc", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_epdc_dt_ids);
+
+static struct platform_driver pdev = {
+	.driver = {
+		.name   = "mxc_epdc",
+		.of_match_table = of_match_ptr(imx_epdc_dt_ids),
+	},
+	.probe  = mxc_epdc_probe,
+	.remove = mxc_epdc_remove,
+};
+
+module_platform_driver(pdev);
+MODULE_DESCRIPTION("IMX EPDC driver");
+MODULE_LICENSE("GPL");
+
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 2/6] drm: Add skeleton for EPDC driver Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  2022-03-12 20:12   ` Jonathan Neuschäfer
  2022-02-06  8:00 ` [RFC PATCH 4/6] drm: mxc-epdc: Add update management Andreas Kemnade
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

Adds display parameter initialisation, display power up/down and
waveform loading

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/gpu/drm/mxc-epdc/Makefile        |   2 +-
 drivers/gpu/drm/mxc-epdc/epdc_hw.c       | 495 +++++++++++++++++++++++
 drivers/gpu/drm/mxc-epdc/epdc_hw.h       |   8 +
 drivers/gpu/drm/mxc-epdc/epdc_waveform.c | 189 +++++++++
 drivers/gpu/drm/mxc-epdc/epdc_waveform.h |   7 +
 drivers/gpu/drm/mxc-epdc/mxc_epdc.h      |  81 ++++
 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c  |  94 +++++
 7 files changed, 875 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_hw.h
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_waveform.h

diff --git a/drivers/gpu/drm/mxc-epdc/Makefile b/drivers/gpu/drm/mxc-epdc/Makefile
index a47ced72b7f6..0263ef2bf0db 100644
--- a/drivers/gpu/drm/mxc-epdc/Makefile
+++ b/drivers/gpu/drm/mxc-epdc/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-mxc_epdc_drm-y := mxc_epdc_drv.o
+mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_waveform.o
 
 obj-$(CONFIG_DRM_MXC_EPDC) += mxc_epdc_drm.o
 
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.c b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
new file mode 100644
index 000000000000..a74cbd237e0d
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include "mxc_epdc.h"
+#include "epdc_regs.h"
+#include "epdc_hw.h"
+#include "epdc_waveform.h"
+
+void mxc_epdc_powerup(struct mxc_epdc *priv)
+{
+	int ret = 0;
+
+	mutex_lock(&priv->power_mutex);
+
+	/*
+	 * If power down request is pending, clear
+	 * powering_down to cancel the request.
+	 */
+	if (priv->powering_down)
+		priv->powering_down = false;
+
+	if (priv->powered) {
+		mutex_unlock(&priv->power_mutex);
+		return;
+	}
+
+	dev_dbg(priv->drm.dev, "EPDC Powerup\n");
+
+	priv->updates_active = true;
+
+	/* Enable the v3p3 regulator */
+	ret = regulator_enable(priv->v3p3_regulator);
+	if (IS_ERR((void *)ret)) {
+		dev_err(priv->drm.dev,
+			"Unable to enable V3P3 regulator. err = 0x%x\n",
+			ret);
+		mutex_unlock(&priv->power_mutex);
+		return;
+	}
+
+	usleep_range(1000, 2000);
+
+	pm_runtime_get_sync(priv->drm.dev);
+
+	/* Enable clocks to EPDC */
+	clk_prepare_enable(priv->epdc_clk_axi);
+	clk_prepare_enable(priv->epdc_clk_pix);
+
+	epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_CLKGATE);
+
+	/* Enable power to the EPD panel */
+	ret = regulator_enable(priv->display_regulator);
+	if (IS_ERR((void *)ret)) {
+		dev_err(priv->drm.dev,
+			"Unable to enable DISPLAY regulator. err = 0x%x\n",
+			ret);
+		mutex_unlock(&priv->power_mutex);
+		return;
+	}
+
+	ret = regulator_enable(priv->vcom_regulator);
+	if (IS_ERR((void *)ret)) {
+		dev_err(priv->drm.dev,
+			"Unable to enable VCOM regulator. err = 0x%x\n",
+			ret);
+		mutex_unlock(&priv->power_mutex);
+		return;
+	}
+
+	priv->powered = true;
+
+	mutex_unlock(&priv->power_mutex);
+}
+
+void mxc_epdc_powerdown(struct mxc_epdc *priv)
+{
+	mutex_lock(&priv->power_mutex);
+
+	/* If powering_down has been cleared, a powerup
+	 * request is pre-empting this powerdown request.
+	 */
+	if (!priv->powering_down
+		|| (!priv->powered)) {
+		mutex_unlock(&priv->power_mutex);
+		return;
+	}
+
+	dev_dbg(priv->drm.dev, "EPDC Powerdown\n");
+
+	/* Disable power to the EPD panel */
+	regulator_disable(priv->vcom_regulator);
+	regulator_disable(priv->display_regulator);
+
+	/* Disable clocks to EPDC */
+	epdc_write(priv, EPDC_CTRL_SET, EPDC_CTRL_CLKGATE);
+	clk_disable_unprepare(priv->epdc_clk_pix);
+	clk_disable_unprepare(priv->epdc_clk_axi);
+
+	pm_runtime_put_sync_suspend(priv->drm.dev);
+
+	/* turn off the V3p3 */
+	regulator_disable(priv->v3p3_regulator);
+
+	priv->powered = false;
+	priv->powering_down = false;
+
+	if (priv->wait_for_powerdown) {
+		priv->wait_for_powerdown = false;
+		complete(&priv->powerdown_compl);
+	}
+
+	mutex_unlock(&priv->power_mutex);
+}
+
+static void epdc_set_horizontal_timing(struct mxc_epdc *priv, u32 horiz_start,
+				       u32 horiz_end,
+				       u32 hsync_width, u32 hsync_line_length)
+{
+	u32 reg_val =
+	    ((hsync_width << EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_OFFSET) &
+	     EPDC_TCE_HSCAN1_LINE_SYNC_WIDTH_MASK)
+	    | ((hsync_line_length << EPDC_TCE_HSCAN1_LINE_SYNC_OFFSET) &
+	       EPDC_TCE_HSCAN1_LINE_SYNC_MASK);
+	epdc_write(priv, EPDC_TCE_HSCAN1, reg_val);
+
+	reg_val =
+	    ((horiz_start << EPDC_TCE_HSCAN2_LINE_BEGIN_OFFSET) &
+	     EPDC_TCE_HSCAN2_LINE_BEGIN_MASK)
+	    | ((horiz_end << EPDC_TCE_HSCAN2_LINE_END_OFFSET) &
+	       EPDC_TCE_HSCAN2_LINE_END_MASK);
+	epdc_write(priv, EPDC_TCE_HSCAN2, reg_val);
+}
+
+static void epdc_set_vertical_timing(struct mxc_epdc *priv,
+				     u32 vert_start,
+				     u32 vert_end,
+				     u32 vsync_width)
+{
+	u32 reg_val =
+	    ((vert_start << EPDC_TCE_VSCAN_FRAME_BEGIN_OFFSET) &
+	     EPDC_TCE_VSCAN_FRAME_BEGIN_MASK)
+	    | ((vert_end << EPDC_TCE_VSCAN_FRAME_END_OFFSET) &
+	       EPDC_TCE_VSCAN_FRAME_END_MASK)
+	    | ((vsync_width << EPDC_TCE_VSCAN_FRAME_SYNC_OFFSET) &
+	       EPDC_TCE_VSCAN_FRAME_SYNC_MASK);
+	epdc_write(priv, EPDC_TCE_VSCAN, reg_val);
+}
+
+static inline void epdc_set_screen_res(struct mxc_epdc *priv,
+				       u32 width, u32 height)
+{
+	u32 val = (height << EPDC_RES_VERTICAL_OFFSET) | width;
+
+	epdc_write(priv, EPDC_RES, val);
+}
+
+
+void epdc_init_settings(struct mxc_epdc *priv, struct drm_display_mode *m)
+{
+	u32 reg_val;
+	int num_ce;
+	int i;
+
+	/* Enable clocks to access EPDC regs */
+	clk_prepare_enable(priv->epdc_clk_axi);
+	clk_prepare_enable(priv->epdc_clk_pix);
+
+	/* Reset */
+	epdc_write(priv, EPDC_CTRL_SET, EPDC_CTRL_SFTRST);
+	while (!(epdc_read(priv, EPDC_CTRL) & EPDC_CTRL_CLKGATE))
+		;
+	epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_SFTRST);
+
+	/* Enable clock gating (clear to enable) */
+	epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_CLKGATE);
+	while (epdc_read(priv, EPDC_CTRL) & (EPDC_CTRL_SFTRST | EPDC_CTRL_CLKGATE))
+		;
+
+	/* EPDC_CTRL */
+	reg_val = epdc_read(priv, EPDC_CTRL);
+	reg_val &= ~EPDC_CTRL_UPD_DATA_SWIZZLE_MASK;
+	reg_val |= EPDC_CTRL_UPD_DATA_SWIZZLE_NO_SWAP;
+	reg_val &= ~EPDC_CTRL_LUT_DATA_SWIZZLE_MASK;
+	reg_val |= EPDC_CTRL_LUT_DATA_SWIZZLE_NO_SWAP;
+	epdc_write(priv, EPDC_CTRL_SET, reg_val);
+
+	/* EPDC_FORMAT - 2bit TFT and buf_pix_fmt Buf pixel format */
+	reg_val = EPDC_FORMAT_TFT_PIXEL_FORMAT_2BIT
+		| priv->buf_pix_fmt
+	    | ((0x0 << EPDC_FORMAT_DEFAULT_TFT_PIXEL_OFFSET) &
+	       EPDC_FORMAT_DEFAULT_TFT_PIXEL_MASK);
+	epdc_write(priv, EPDC_FORMAT, reg_val);
+	if (priv->rev >= 30) {
+		if (priv->buf_pix_fmt == EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N) {
+			epdc_write(priv, EPDC_WB_FIELD2, 0xc554);
+			epdc_write(priv, EPDC_WB_FIELD1, 0xa004);
+		} else {
+			epdc_write(priv, EPDC_WB_FIELD2, 0xc443);
+			epdc_write(priv, EPDC_WB_FIELD1, 0xa003);
+		}
+	}
+
+	/* EPDC_FIFOCTRL (disabled) */
+	reg_val =
+	    ((100 << EPDC_FIFOCTRL_FIFO_INIT_LEVEL_OFFSET) &
+	     EPDC_FIFOCTRL_FIFO_INIT_LEVEL_MASK)
+	    | ((200 << EPDC_FIFOCTRL_FIFO_H_LEVEL_OFFSET) &
+	       EPDC_FIFOCTRL_FIFO_H_LEVEL_MASK)
+	    | ((100 << EPDC_FIFOCTRL_FIFO_L_LEVEL_OFFSET) &
+	       EPDC_FIFOCTRL_FIFO_L_LEVEL_MASK);
+	epdc_write(priv, EPDC_FIFOCTRL, reg_val);
+
+	/* EPDC_TEMP - Use default temp to get index */
+	epdc_write(priv, EPDC_TEMP,
+		   mxc_epdc_fb_get_temp_index(priv, TEMP_USE_AMBIENT));
+
+	/* EPDC_RES */
+	epdc_set_screen_res(priv, m->hdisplay, m->vdisplay);
+
+	/* EPDC_AUTOWV_LUT */
+	/* Initialize all auto-wavefrom look-up values to 2 - GC16 */
+	for (i = 0; i < 8; i++)
+		epdc_write(priv, EPDC_AUTOWV_LUT,
+			(2 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+			(i << EPDC_AUTOWV_LUT_ADDR_OFFSET));
+
+	/*
+	 * EPDC_TCE_CTRL
+	 * VSCAN_HOLDOFF = 4
+	 * VCOM_MODE = MANUAL
+	 * VCOM_VAL = 0
+	 * DDR_MODE = DISABLED
+	 * LVDS_MODE_CE = DISABLED
+	 * LVDS_MODE = DISABLED
+	 * DUAL_SCAN = DISABLED
+	 * SDDO_WIDTH = 8bit
+	 * PIXELS_PER_SDCLK = 4
+	 */
+	reg_val =
+	    ((priv->imx_mode.vscan_holdoff << EPDC_TCE_CTRL_VSCAN_HOLDOFF_OFFSET) &
+	     EPDC_TCE_CTRL_VSCAN_HOLDOFF_MASK)
+	    | EPDC_TCE_CTRL_PIXELS_PER_SDCLK_4;
+	epdc_write(priv, EPDC_TCE_CTRL, reg_val);
+
+	/* EPDC_TCE_HSCAN */
+	epdc_set_horizontal_timing(priv, m->hsync_start - m->hdisplay,
+				   m->htotal - m->hsync_end,
+				   m->hsync_end - m->hsync_start,
+				   m->hsync_end - m->hsync_start);
+
+	/* EPDC_TCE_VSCAN */
+	epdc_set_vertical_timing(priv, m->vsync_start - m->vdisplay,
+				 m->vtotal - m->vsync_end,
+				 m->vsync_end - m->vsync_start);
+
+	/* EPDC_TCE_OE */
+	reg_val =
+	    ((priv->imx_mode.sdoed_width << EPDC_TCE_OE_SDOED_WIDTH_OFFSET) &
+	     EPDC_TCE_OE_SDOED_WIDTH_MASK)
+	    | ((priv->imx_mode.sdoed_delay << EPDC_TCE_OE_SDOED_DLY_OFFSET) &
+	       EPDC_TCE_OE_SDOED_DLY_MASK)
+	    | ((priv->imx_mode.sdoez_width << EPDC_TCE_OE_SDOEZ_WIDTH_OFFSET) &
+	       EPDC_TCE_OE_SDOEZ_WIDTH_MASK)
+	    | ((priv->imx_mode.sdoez_delay << EPDC_TCE_OE_SDOEZ_DLY_OFFSET) &
+	       EPDC_TCE_OE_SDOEZ_DLY_MASK);
+	epdc_write(priv, EPDC_TCE_OE, reg_val);
+
+	/* EPDC_TCE_TIMING1 */
+	epdc_write(priv, EPDC_TCE_TIMING1, 0x0);
+
+	/* EPDC_TCE_TIMING2 */
+	reg_val =
+	    ((priv->imx_mode.gdclk_hp_offs << EPDC_TCE_TIMING2_GDCLK_HP_OFFSET) &
+	     EPDC_TCE_TIMING2_GDCLK_HP_MASK)
+	    | ((priv->imx_mode.gdsp_offs << EPDC_TCE_TIMING2_GDSP_OFFSET_OFFSET) &
+	       EPDC_TCE_TIMING2_GDSP_OFFSET_MASK);
+	epdc_write(priv, EPDC_TCE_TIMING2, reg_val);
+
+	/* EPDC_TCE_TIMING3 */
+	reg_val =
+	    ((priv->imx_mode.gdoe_offs << EPDC_TCE_TIMING3_GDOE_OFFSET_OFFSET) &
+	     EPDC_TCE_TIMING3_GDOE_OFFSET_MASK)
+	    | ((priv->imx_mode.gdclk_offs << EPDC_TCE_TIMING3_GDCLK_OFFSET_OFFSET) &
+	       EPDC_TCE_TIMING3_GDCLK_OFFSET_MASK);
+	epdc_write(priv, EPDC_TCE_TIMING3, reg_val);
+
+	/*
+	 * EPDC_TCE_SDCFG
+	 * SDCLK_HOLD = 1
+	 * SDSHR = 1
+	 * NUM_CE = 1
+	 * SDDO_REFORMAT = FLIP_PIXELS
+	 * SDDO_INVERT = DISABLED
+	 * PIXELS_PER_CE = display horizontal resolution
+	 */
+	num_ce = priv->imx_mode.num_ce;
+	if (num_ce == 0)
+		num_ce = 1;
+	reg_val = EPDC_TCE_SDCFG_SDCLK_HOLD | EPDC_TCE_SDCFG_SDSHR
+	    | ((num_ce << EPDC_TCE_SDCFG_NUM_CE_OFFSET) &
+	       EPDC_TCE_SDCFG_NUM_CE_MASK)
+	    | EPDC_TCE_SDCFG_SDDO_REFORMAT_FLIP_PIXELS
+	    | ((priv->epdc_mem_width/num_ce << EPDC_TCE_SDCFG_PIXELS_PER_CE_OFFSET) &
+	       EPDC_TCE_SDCFG_PIXELS_PER_CE_MASK);
+	epdc_write(priv, EPDC_TCE_SDCFG, reg_val);
+
+	/*
+	 * EPDC_TCE_GDCFG
+	 * GDRL = 1
+	 * GDOE_MODE = 0;
+	 * GDSP_MODE = 0;
+	 */
+	reg_val = EPDC_TCE_SDCFG_GDRL;
+	epdc_write(priv, EPDC_TCE_GDCFG, reg_val);
+
+	/*
+	 * EPDC_TCE_POLARITY
+	 * SDCE_POL = ACTIVE LOW
+	 * SDLE_POL = ACTIVE HIGH
+	 * SDOE_POL = ACTIVE HIGH
+	 * GDOE_POL = ACTIVE HIGH
+	 * GDSP_POL = ACTIVE LOW
+	 */
+	reg_val = EPDC_TCE_POLARITY_SDLE_POL_ACTIVE_HIGH
+	    | EPDC_TCE_POLARITY_SDOE_POL_ACTIVE_HIGH
+	    | EPDC_TCE_POLARITY_GDOE_POL_ACTIVE_HIGH;
+	epdc_write(priv, EPDC_TCE_POLARITY, reg_val);
+
+	/* EPDC_IRQ_MASK */
+	epdc_write(priv, EPDC_IRQ_MASK, EPDC_IRQ_TCE_UNDERRUN_IRQ);
+
+	/*
+	 * EPDC_GPIO
+	 * PWRCOM = ?
+	 * PWRCTRL = ?
+	 * BDR = ?
+	 */
+	reg_val = ((0 << EPDC_GPIO_PWRCTRL_OFFSET) & EPDC_GPIO_PWRCTRL_MASK)
+	    | ((0 << EPDC_GPIO_BDR_OFFSET) & EPDC_GPIO_BDR_MASK);
+	epdc_write(priv, EPDC_GPIO, reg_val);
+
+	epdc_write(priv, EPDC_WVADDR, priv->waveform_buffer_phys);
+	epdc_write(priv, EPDC_WB_ADDR, priv->working_buffer_phys);
+	if (priv->rev >= 30)
+		epdc_write(priv, EPDC_WB_ADDR_TCE_V3,
+			   priv->working_buffer_phys);
+	else
+		epdc_write(priv, EPDC_WB_ADDR_TCE,
+			   priv->working_buffer_phys);
+
+	/* Disable clock */
+	clk_disable_unprepare(priv->epdc_clk_axi);
+	clk_disable_unprepare(priv->epdc_clk_pix);
+}
+
+void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m)
+{
+	/* Initialize EPDC, passing pointer to EPDC registers */
+	struct clk *epdc_parent;
+	unsigned long rounded_parent_rate, epdc_pix_rate,
+			rounded_pix_clk, target_pix_clk;
+
+	/* Enable pix clk for EPDC */
+	clk_prepare_enable(priv->epdc_clk_axi);
+
+	target_pix_clk = m->clock * 1000;
+	rounded_pix_clk = clk_round_rate(priv->epdc_clk_pix, target_pix_clk);
+
+	if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+		(rounded_pix_clk <= target_pix_clk - target_pix_clk/100))) {
+		/* Can't get close enough without changing parent clk */
+		epdc_parent = clk_get_parent(priv->epdc_clk_pix);
+		rounded_parent_rate = clk_round_rate(epdc_parent, target_pix_clk);
+
+		epdc_pix_rate = target_pix_clk;
+		while (epdc_pix_rate < rounded_parent_rate)
+			epdc_pix_rate *= 2;
+		clk_set_rate(epdc_parent, epdc_pix_rate);
+
+		rounded_pix_clk = clk_round_rate(priv->epdc_clk_pix, target_pix_clk);
+		if (((rounded_pix_clk >= target_pix_clk + target_pix_clk/100) ||
+			(rounded_pix_clk <= target_pix_clk - target_pix_clk/100)))
+			/* Still can't get a good clock, provide warning */
+			dev_err(priv->drm.dev,
+				"Unable to get an accurate EPDC pix clk desired = %lu, actual = %lu\n",
+				target_pix_clk,
+				rounded_pix_clk);
+	}
+
+	clk_set_rate(priv->epdc_clk_pix, rounded_pix_clk);
+	clk_prepare_enable(priv->epdc_clk_pix);
+
+	epdc_init_settings(priv, m);
+
+	priv->in_init = true;
+	mxc_epdc_powerup(priv);
+	/* Force power down event */
+	priv->powering_down = true;
+	mxc_epdc_powerdown(priv);
+	priv->updates_active = false;
+
+	/* Disable clocks */
+	clk_disable_unprepare(priv->epdc_clk_axi);
+	clk_disable_unprepare(priv->epdc_clk_pix);
+	priv->hw_ready = true;
+	priv->hw_initializing = false;
+
+}
+
+int mxc_epdc_init_hw(struct mxc_epdc *priv)
+{
+	struct pinctrl *pinctrl;
+	const char *thermal = NULL;
+	u32 val;
+
+	/* get pmic regulators */
+	priv->display_regulator = devm_regulator_get(priv->drm.dev, "DISPLAY");
+	if (IS_ERR(priv->display_regulator))
+		return dev_err_probe(priv->drm.dev, PTR_ERR(priv->display_regulator),
+				     "Unable to get display PMIC regulator\n");
+
+	priv->vcom_regulator = devm_regulator_get(priv->drm.dev, "VCOM");
+	if (IS_ERR(priv->vcom_regulator))
+		return dev_err_probe(priv->drm.dev, PTR_ERR(priv->vcom_regulator),
+				     "Unable to get VCOM regulator\n");
+
+	priv->v3p3_regulator = devm_regulator_get(priv->drm.dev, "V3P3");
+	if (IS_ERR(priv->v3p3_regulator))
+		return dev_err_probe(priv->drm.dev, PTR_ERR(priv->v3p3_regulator),
+				     "Unable to get V3P3 regulator\n");
+
+	of_property_read_string(priv->drm.dev->of_node,
+				"epd-thermal-zone", &thermal);
+	if (thermal) {
+		priv->thermal = thermal_zone_get_zone_by_name(thermal);
+		if (IS_ERR(priv->thermal))
+			return dev_err_probe(priv->drm.dev, PTR_ERR(priv->thermal),
+					     "unable to get thermal");
+	}
+	priv->iobase = devm_platform_get_and_ioremap_resource(to_platform_device(priv->drm.dev),
+							      0, NULL);
+	if (priv->iobase == NULL)
+		return -ENOMEM;
+
+	priv->epdc_clk_axi = devm_clk_get(priv->drm.dev, "axi");
+	if (IS_ERR(priv->epdc_clk_axi))
+		return dev_err_probe(priv->drm.dev, PTR_ERR(priv->epdc_clk_axi),
+				     "Unable to get EPDC AXI clk\n");
+
+	priv->epdc_clk_pix = devm_clk_get(priv->drm.dev, "pix");
+	if (IS_ERR(priv->epdc_clk_pix))
+		return dev_err_probe(priv->drm.dev, PTR_ERR(priv->epdc_clk_pix),
+				     "Unable to get EPDC pix clk\n");
+
+	clk_prepare_enable(priv->epdc_clk_axi);
+	val = epdc_read(priv, EPDC_VERSION);
+	clk_disable_unprepare(priv->epdc_clk_axi);
+	priv->rev = ((val & EPDC_VERSION_MAJOR_MASK) >>
+				EPDC_VERSION_MAJOR_OFFSET) * 10
+			+ ((val & EPDC_VERSION_MINOR_MASK) >>
+				EPDC_VERSION_MINOR_OFFSET);
+	dev_dbg(priv->drm.dev, "EPDC version = %d\n", priv->rev);
+
+	if (priv->rev <= 20) {
+		dev_err(priv->drm.dev, "Unsupported version (%d)\n", priv->rev);
+		return -ENODEV;
+	}
+
+	/* Initialize EPDC pins */
+	pinctrl = devm_pinctrl_get_select_default(priv->drm.dev);
+	if (IS_ERR(pinctrl)) {
+		dev_err(priv->drm.dev, "can't get/select pinctrl\n");
+		return PTR_ERR(pinctrl);
+	}
+
+	mutex_init(&priv->power_mutex);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.h b/drivers/gpu/drm/mxc-epdc/epdc_hw.h
new file mode 100644
index 000000000000..dbf1f0d1e23e
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2022 Andreas Kemnade */
+void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m);
+int mxc_epdc_init_hw(struct mxc_epdc *priv);
+
+void mxc_epdc_powerup(struct mxc_epdc *priv);
+void mxc_epdc_powerdown(struct mxc_epdc *priv);
+
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_waveform.c b/drivers/gpu/drm/mxc-epdc/epdc_waveform.c
new file mode 100644
index 000000000000..4f2f199722d5
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_waveform.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include "mxc_epdc.h"
+
+#define DEFAULT_TEMP_INDEX      0
+#define DEFAULT_TEMP            20 /* room temp in deg Celsius */
+
+struct waveform_data_header {
+	unsigned int wi0;
+	unsigned int wi1;
+	unsigned int wi2;
+	unsigned int wi3;
+	unsigned int wi4;
+	unsigned int wi5;
+	unsigned int wi6;
+	unsigned int xwia:24;
+	unsigned int cs1:8;
+	unsigned int wmta:24;
+	unsigned int fvsn:8;
+	unsigned int luts:8;
+	unsigned int mc:8;
+	unsigned int trc:8;
+	unsigned int reserved0_0:8;
+	unsigned int eb:8;
+	unsigned int sb:8;
+	unsigned int reserved0_1:8;
+	unsigned int reserved0_2:8;
+	unsigned int reserved0_3:8;
+	unsigned int reserved0_4:8;
+	unsigned int reserved0_5:8;
+	unsigned int cs2:8;
+};
+
+struct mxcfb_waveform_data_file {
+	struct waveform_data_header wdh;
+	u32 *data;      /* Temperature Range Table + Waveform Data */
+};
+
+void mxc_epdc_set_update_waveform(struct mxc_epdc *priv,
+				  struct mxcfb_waveform_modes *wv_modes)
+{
+	u32 val;
+
+	/* Configure the auto-waveform look-up table based on waveform modes */
+
+	/* Entry 1 = DU, 2 = GC4, 3 = GC8, etc. */
+	val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(0 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+	val = (wv_modes->mode_du << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(1 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+	val = (wv_modes->mode_gc4 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(2 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+	val = (wv_modes->mode_gc8 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(3 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+	val = (wv_modes->mode_gc16 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(4 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+	val = (wv_modes->mode_gc32 << EPDC_AUTOWV_LUT_DATA_OFFSET) |
+		(5 << EPDC_AUTOWV_LUT_ADDR_OFFSET);
+	epdc_write(priv, EPDC_AUTOWV_LUT, val);
+}
+
+int mxc_epdc_fb_get_temp_index(struct mxc_epdc *priv, int temp)
+{
+	int i;
+	int index = -1;
+
+	if (temp == TEMP_USE_AMBIENT) {
+		if (priv->thermal) {
+			if (thermal_zone_get_temp(priv->thermal, &temp)) {
+				dev_err(priv->drm.dev,
+					"reading temperature failed");
+				return DEFAULT_TEMP_INDEX;
+			}
+			temp /= 1000;
+		} else
+			temp = DEFAULT_TEMP;
+	}
+
+	if (priv->trt_entries == 0) {
+		dev_err(priv->drm.dev,
+			"No TRT exists...using default temp index\n");
+		return DEFAULT_TEMP_INDEX;
+	}
+
+	/* Search temperature ranges for a match */
+	for (i = 0; i < priv->trt_entries - 1; i++) {
+		if ((temp >= priv->temp_range_bounds[i])
+			&& (temp < priv->temp_range_bounds[i+1])) {
+			index = i;
+			break;
+		}
+	}
+
+	if (index < 0) {
+		dev_err(priv->drm.dev,
+			"No TRT index match...using lowest/highest\n");
+		if (temp < priv->temp_range_bounds[0]) {
+			dev_dbg(priv->drm.dev, "temperature < minimum range\n");
+			return 0;
+		}
+
+		if (temp >= priv->temp_range_bounds[priv->trt_entries-1]) {
+			dev_dbg(priv->drm.dev, "temperature >= maximum range\n");
+			return priv->trt_entries-1;
+		}
+
+		return DEFAULT_TEMP_INDEX;
+	}
+
+	dev_dbg(priv->drm.dev, "Using temperature index %d\n", index);
+
+	return index;
+}
+
+
+
+int mxc_epdc_prepare_waveform(struct mxc_epdc *priv,
+			      const u8 *data, size_t size)
+{
+	const struct mxcfb_waveform_data_file *wv_file;
+	int wv_data_offs;
+	int i;
+
+	priv->wv_modes.mode_init = 0;
+	priv->wv_modes.mode_du = 1;
+	priv->wv_modes.mode_gc4 = 3;
+	priv->wv_modes.mode_gc8 = 2;
+	priv->wv_modes.mode_gc16 = 2;
+	priv->wv_modes.mode_gc32 = 2;
+	priv->wv_modes_update = true;
+
+	wv_file = (struct mxcfb_waveform_data_file *)data;
+
+	/* Get size and allocate temperature range table */
+	priv->trt_entries = wv_file->wdh.trc + 1;
+	priv->temp_range_bounds = devm_kzalloc(priv->drm.dev, priv->trt_entries, GFP_KERNEL);
+
+	for (i = 0; i < priv->trt_entries; i++)
+		dev_dbg(priv->drm.dev, "trt entry #%d = 0x%x\n", i, *((u8 *)&wv_file->data + i));
+
+	/* Copy TRT data */
+	memcpy(priv->temp_range_bounds, &wv_file->data, priv->trt_entries);
+
+	/* Set default temperature index using TRT and room temp */
+	priv->temp_index = mxc_epdc_fb_get_temp_index(priv, DEFAULT_TEMP);
+
+	/* Get offset and size for waveform data */
+	wv_data_offs = sizeof(wv_file->wdh) + priv->trt_entries + 1;
+	priv->waveform_buffer_size = size - wv_data_offs;
+
+	/* Allocate memory for waveform data */
+	priv->waveform_buffer_virt = dmam_alloc_coherent(priv->drm.dev,
+						priv->waveform_buffer_size,
+						&priv->waveform_buffer_phys,
+						GFP_DMA | GFP_KERNEL);
+	if (priv->waveform_buffer_virt == NULL) {
+		dev_err(priv->drm.dev, "Can't allocate mem for waveform!\n");
+		return -ENOMEM;
+	}
+
+	memcpy(priv->waveform_buffer_virt, (u8 *)(data) + wv_data_offs,
+		priv->waveform_buffer_size);
+
+	/* Read field to determine if 4-bit or 5-bit mode */
+	if ((wv_file->wdh.luts & 0xC) == 0x4)
+		priv->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N;
+	else
+		priv->buf_pix_fmt = EPDC_FORMAT_BUF_PIXEL_FORMAT_P4N;
+
+	dev_info(priv->drm.dev, "EPDC pix format: %x\n",
+		 priv->buf_pix_fmt);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_waveform.h b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
new file mode 100644
index 000000000000..c5c461b975cb
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2022 Andreas Kemnade */
+int mxc_epdc_fb_get_temp_index(struct mxc_epdc *priv, int temp);
+int mxc_epdc_prepare_waveform(struct mxc_epdc *priv,
+			      const u8 *waveform, size_t size);
+void mxc_epdc_set_update_waveform(struct mxc_epdc *priv,
+				  struct mxcfb_waveform_modes *wv_modes);
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
index c5f5280b574f..f7b1cbc4cc4e 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
@@ -8,6 +8,32 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_connector.h>
 #include <drm/drm_simple_kms_helper.h>
+#include <linux/thermal.h>
+#include "epdc_regs.h"
+
+#define TEMP_USE_AMBIENT			0x1000
+
+struct mxcfb_waveform_modes {
+	int mode_init;
+	int mode_du;
+	int mode_gc4;
+	int mode_gc8;
+	int mode_gc16;
+	int mode_gc32;
+};
+
+struct imx_epdc_fb_mode {
+	u32 vscan_holdoff;
+	u32 sdoed_width;
+	u32 sdoed_delay;
+	u32 sdoez_width;
+	u32 sdoez_delay;
+	u32 gdclk_hp_offs;
+	u32 gdsp_offs;
+	u32 gdoe_offs;
+	u32 gdclk_offs;
+	u32 num_ce;
+};
 
 struct clk;
 struct regulator;
@@ -16,5 +42,60 @@ struct mxc_epdc {
 	struct drm_simple_display_pipe pipe;
 	struct drm_connector connector;
 	struct display_timing timing;
+	struct imx_epdc_fb_mode imx_mode;
+	void __iomem *iobase;
+	struct completion powerdown_compl;
+	struct clk *epdc_clk_axi;
+	struct clk *epdc_clk_pix;
+	struct regulator *display_regulator;
+	struct regulator *vcom_regulator;
+	struct regulator *v3p3_regulator;
+	struct thermal_zone_device *thermal;
+	int rev;
+
+	dma_addr_t epdc_mem_phys;
+	void *epdc_mem_virt;
+	int epdc_mem_width;
+	int epdc_mem_height;
+	u32 *working_buffer_virt;
+	dma_addr_t working_buffer_phys;
+	u32 working_buffer_size;
+
+	/* waveform related stuff */
+	int trt_entries;
+	int temp_index;
+	u8 *temp_range_bounds;
+	int buf_pix_fmt;
+	struct mxcfb_waveform_modes wv_modes;
+	bool wv_modes_update;
+	u32 *waveform_buffer_virt;
+	dma_addr_t waveform_buffer_phys;
+	u32 waveform_buffer_size;
+
+	struct mutex power_mutex;
+	bool powered;
+	bool powering_down;
+	bool updates_active;
+	int wait_for_powerdown;
+	int pwrdown_delay;
+
+	/* elements related to EPDC updates */
+	int num_luts;
+	int max_num_updates;
+	bool in_init;
+	bool hw_ready;
+	bool hw_initializing;
+	bool waiting_for_idle;
+
 };
 
+static inline u32 epdc_read(struct mxc_epdc *priv, u32 reg)
+{
+	return readl(priv->iobase + reg);
+}
+
+static inline void epdc_write(struct mxc_epdc *priv, u32 reg, u32 data)
+{
+	writel(data, priv->iobase + reg);
+}
+
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
index c0b0a3bcdb57..4810e5c5bc6e 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
@@ -25,6 +25,8 @@
 #include <drm/drm_prime.h>
 #include <drm/drm_probe_helper.h>
 #include "mxc_epdc.h"
+#include "epdc_hw.h"
+#include "epdc_waveform.h"
 
 #define DRIVER_NAME "mxc_epdc"
 #define DRIVER_DESC "IMX EPDC"
@@ -122,6 +124,57 @@ int mxc_epdc_output(struct drm_device *drm)
 				 DRM_MODE_CONNECTOR_Unknown);
 	if (ret)
 		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "vscan-holdoff",
+				   &priv->imx_mode.vscan_holdoff);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "sdoed-width",
+				   &priv->imx_mode.sdoed_width);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "sdoed-delay",
+				   &priv->imx_mode.sdoed_delay);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "sdoez-width",
+				   &priv->imx_mode.sdoez_width);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "sdoez-delay",
+				   &priv->imx_mode.sdoez_delay);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "gdclk-hp-offs",
+				   &priv->imx_mode.gdclk_hp_offs);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "gdsp-offs",
+				   &priv->imx_mode.gdsp_offs);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "gdoe-offs",
+				   &priv->imx_mode.gdoe_offs);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "gdclk-offs",
+				   &priv->imx_mode.gdclk_offs);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32(drm->dev->of_node, "num-ce",
+				   &priv->imx_mode.num_ce);
+	if (ret)
+		return ret;
+
 	ret = of_get_display_timing(drm->dev->of_node, "timing", &priv->timing);
 	if (ret)
 		return ret;
@@ -137,6 +190,20 @@ static void mxc_epdc_pipe_enable(struct drm_simple_display_pipe *pipe,
 	struct drm_display_mode *m = &pipe->crtc.state->adjusted_mode;
 
 	dev_info(priv->drm.dev, "Mode: %d x %d\n", m->hdisplay, m->vdisplay);
+	priv->epdc_mem_width = m->hdisplay;
+	priv->epdc_mem_height = m->vdisplay;
+	priv->epdc_mem_virt = dma_alloc_wc(priv->drm.dev,
+					   m->hdisplay * m->vdisplay,
+					   &priv->epdc_mem_phys, GFP_DMA | GFP_KERNEL);
+	priv->working_buffer_size = m->hdisplay * m->vdisplay * 2;
+	priv->working_buffer_virt =
+		dma_alloc_coherent(priv->drm.dev,
+				   priv->working_buffer_size,
+				   &priv->working_buffer_phys,
+				   GFP_DMA | GFP_KERNEL);
+
+	if (priv->working_buffer_virt && priv->epdc_mem_virt)
+		mxc_epdc_init_sequence(priv, m);
 }
 
 static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
@@ -144,6 +211,19 @@ static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
 	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
 
 	dev_dbg(priv->drm.dev, "pipe disable\n");
+
+	if (priv->epdc_mem_virt) {
+		dma_free_wc(priv->drm.dev, priv->epdc_mem_width * priv->epdc_mem_height,
+			    priv->epdc_mem_virt, priv->epdc_mem_phys);
+		priv->epdc_mem_virt = NULL;
+	}
+
+	if (priv->working_buffer_virt) {
+		dma_free_wc(priv->drm.dev, priv->working_buffer_size,
+			    priv->working_buffer_virt,
+			    priv->working_buffer_phys);
+		priv->working_buffer_virt = NULL;
+	}
 }
 
 static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
@@ -187,6 +267,7 @@ static struct drm_driver mxc_epdc_driver = {
 static int mxc_epdc_probe(struct platform_device *pdev)
 {
 	struct mxc_epdc *priv;
+	const struct firmware *firmware;
 	int ret;
 
 	priv = devm_drm_dev_alloc(&pdev->dev, &mxc_epdc_driver, struct mxc_epdc, drm);
@@ -195,6 +276,19 @@ static int mxc_epdc_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, priv);
 
+	ret = mxc_epdc_init_hw(priv);
+	if (ret)
+		return ret;
+
+	ret = request_firmware(&firmware, "imx/epdc/epdc.fw", priv->drm.dev);
+	if (ret)
+		return ret;
+
+	ret = mxc_epdc_prepare_waveform(priv, firmware->data, firmware->size);
+	release_firmware(firmware);
+	if (ret)
+		return ret;
+
 	mxc_epdc_setup_mode_config(&priv->drm);
 
 	ret = mxc_epdc_output(&priv->drm);
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 4/6] drm: mxc-epdc: Add update management
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
                   ` (2 preceding siblings ...)
  2022-02-06  8:00 ` [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  2022-03-12 20:21   ` Jonathan Neuschäfer
  2022-02-06  8:00 ` [RFC PATCH 5/6] ARM: dts: imx6sll: add EPDC Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 6/6] arm: dts: imx6sl: Add EPDC Andreas Kemnade
  5 siblings, 1 reply; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

The EPDC can process some dirty rectangles at a time, pick them up and
forward them to the controller. Only processes not involving PXP are
supported at the moment. Due to that and to work with more waveforms,
there is some masking/shifting done. It was tested with the factory
waveforms of Kobo Clara HD, Tolino Shine 3, and Tolino Shine 2HD.
Also the waveform called epdc_E060SCM.fw from NXP BSP works with the
i.MX6SL devices.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 drivers/gpu/drm/mxc-epdc/Makefile       |    2 +-
 drivers/gpu/drm/mxc-epdc/epdc_hw.c      |    2 +
 drivers/gpu/drm/mxc-epdc/epdc_update.c  | 1210 +++++++++++++++++++++++
 drivers/gpu/drm/mxc-epdc/epdc_update.h  |    9 +
 drivers/gpu/drm/mxc-epdc/mxc_epdc.h     |   50 +
 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c |   33 +-
 6 files changed, 1304 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_update.c
 create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_update.h

diff --git a/drivers/gpu/drm/mxc-epdc/Makefile b/drivers/gpu/drm/mxc-epdc/Makefile
index 0263ef2bf0db..a55e2bfe824a 100644
--- a/drivers/gpu/drm/mxc-epdc/Makefile
+++ b/drivers/gpu/drm/mxc-epdc/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_waveform.o
+mxc_epdc_drm-y := mxc_epdc_drv.o epdc_hw.o epdc_update.o epdc_waveform.o
 
 obj-$(CONFIG_DRM_MXC_EPDC) += mxc_epdc_drm.o
 
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_hw.c b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
index a74cbd237e0d..22a065ac6992 100644
--- a/drivers/gpu/drm/mxc-epdc/epdc_hw.c
+++ b/drivers/gpu/drm/mxc-epdc/epdc_hw.c
@@ -20,6 +20,7 @@
 #include "mxc_epdc.h"
 #include "epdc_regs.h"
 #include "epdc_hw.h"
+#include "epdc_update.h"
 #include "epdc_waveform.h"
 
 void mxc_epdc_powerup(struct mxc_epdc *priv)
@@ -410,6 +411,7 @@ void mxc_epdc_init_sequence(struct mxc_epdc *priv, struct drm_display_mode *m)
 
 	priv->in_init = true;
 	mxc_epdc_powerup(priv);
+	mxc_epdc_draw_mode0(priv);
 	/* Force power down event */
 	priv->powering_down = true;
 	mxc_epdc_powerdown(priv);
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.c b/drivers/gpu/drm/mxc-epdc/epdc_update.c
new file mode 100644
index 000000000000..0c061982aa0b
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_update.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Copyright (C) 2022 Andreas Kemnade
+//
+/*
+ * based on the EPDC framebuffer driver
+ * Copyright (C) 2010-2016 Freescale Semiconductor, Inc.
+ * Copyright 2017 NXP
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include "mxc_epdc.h"
+#include "epdc_hw.h"
+#include "epdc_regs.h"
+#include "epdc_waveform.h"
+
+#define EPDC_V2_NUM_LUTS	64
+#define EPDC_V2_MAX_NUM_UPDATES 64
+#define INVALID_LUT	     (-1)
+#define DRY_RUN_NO_LUT	  100
+
+#define MERGE_OK	0
+#define MERGE_FAIL      1
+#define MERGE_BLOCK     2
+
+struct update_desc_list {
+	struct list_head list;
+	struct mxcfb_update_data upd_data;/* Update parameters */
+	u32 update_order;	/* Numeric ordering value for update */
+};
+
+/* This structure represents a list node containing both
+ * a memory region allocated as an output buffer for the PxP
+ * update processing task, and the update description (mode, region, etc.)
+ */
+struct update_data_list {
+	struct list_head list;
+	struct update_desc_list *update_desc;
+	int lut_num;		/* Assigned before update is processed into working buffer */
+	u64 collision_mask;	/* Set when update creates collision */
+				/* Mask of the LUTs the update collides with */
+};
+
+/********************************************************
+ * Start Low-Level EPDC Functions
+ ********************************************************/
+
+static inline void epdc_lut_complete_intr(struct mxc_epdc *priv, u32 lut_num, bool enable)
+{
+	if (enable) {
+		if (lut_num < 32)
+			epdc_write(priv, EPDC_IRQ_MASK1_SET, BIT(lut_num));
+		else
+			epdc_write(priv, EPDC_IRQ_MASK2_SET, BIT(lut_num - 32));
+	} else {
+		if (lut_num < 32)
+			epdc_write(priv, EPDC_IRQ_MASK1_CLEAR, BIT(lut_num));
+		else
+			epdc_write(priv, EPDC_IRQ_MASK2_CLEAR, BIT(lut_num - 32));
+	}
+}
+
+static inline void epdc_working_buf_intr(struct mxc_epdc *priv, bool enable)
+{
+	if (enable)
+		epdc_write(priv, EPDC_IRQ_MASK_SET, EPDC_IRQ_WB_CMPLT_IRQ);
+	else
+		epdc_write(priv, EPDC_IRQ_MASK_CLEAR, EPDC_IRQ_WB_CMPLT_IRQ);
+}
+
+static inline void epdc_clear_working_buf_irq(struct mxc_epdc *priv)
+{
+	epdc_write(priv, EPDC_IRQ_CLEAR,
+		   EPDC_IRQ_WB_CMPLT_IRQ | EPDC_IRQ_LUT_COL_IRQ);
+}
+
+static inline void epdc_eof_intr(struct mxc_epdc *priv, bool enable)
+{
+	if (enable)
+		epdc_write(priv, EPDC_IRQ_MASK_SET, EPDC_IRQ_FRAME_END_IRQ);
+	else
+		epdc_write(priv, EPDC_IRQ_MASK_CLEAR, EPDC_IRQ_FRAME_END_IRQ);
+}
+
+static inline void epdc_clear_eof_irq(struct mxc_epdc *priv)
+{
+	epdc_write(priv, EPDC_IRQ_CLEAR, EPDC_IRQ_FRAME_END_IRQ);
+}
+
+static inline bool epdc_signal_eof(struct mxc_epdc *priv)
+{
+	return (epdc_read(priv, EPDC_IRQ_MASK) & epdc_read(priv, EPDC_IRQ)
+		& EPDC_IRQ_FRAME_END_IRQ) ? true : false;
+}
+
+
+static void epdc_set_update_area(struct mxc_epdc *priv, u32 addr,
+				 u32 x, u32 y, u32 width, u32 height,
+				 u32 stride)
+{
+	u32 val;
+
+	epdc_write(priv, EPDC_UPD_ADDR, addr);
+	val = (y << EPDC_UPD_CORD_YCORD_OFFSET) | x;
+	epdc_write(priv, EPDC_UPD_CORD, val);
+	val = (height << EPDC_UPD_SIZE_HEIGHT_OFFSET) | width;
+	epdc_write(priv, EPDC_UPD_SIZE, val);
+	epdc_write(priv, EPDC_UPD_STRIDE, stride);
+}
+
+static bool is_free_list_full(struct mxc_epdc *priv)
+{
+	int count = 0;
+	struct update_data_list *plist;
+
+	/* Count buffers in free buffer list */
+	list_for_each_entry(plist, &priv->upd_buf_free_list, list)
+		count++;
+
+	/* Check to see if all buffers are in this list */
+	return (count == priv->max_num_updates);
+}
+
+static inline bool epdc_is_lut_complete(struct mxc_epdc *priv, u32 lut_num)
+{
+	u32 val;
+	bool is_compl;
+
+	if (lut_num < 32) {
+		val = epdc_read(priv, EPDC_IRQ1);
+		is_compl = val & BIT(lut_num) ? true : false;
+	} else {
+		val = epdc_read(priv, EPDC_IRQ2);
+		is_compl = val & BIT(lut_num - 32) ? true : false;
+	}
+
+	return is_compl;
+}
+static inline void epdc_clear_lut_complete_irq(struct mxc_epdc *priv, u32 lut_num)
+{
+	if (lut_num < 32)
+		epdc_write(priv, EPDC_IRQ1_CLEAR, BIT(lut_num));
+	else
+		epdc_write(priv, EPDC_IRQ2_CLEAR, BIT(lut_num - 32));
+}
+
+static inline bool epdc_is_working_buffer_busy(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_STATUS);
+	bool is_busy = (val & EPDC_STATUS_WB_BUSY) ? true : false;
+
+	return is_busy;
+}
+
+static inline bool epdc_is_working_buffer_complete(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_IRQ);
+	bool is_compl = (val & EPDC_IRQ_WB_CMPLT_IRQ) ? true : false;
+
+	return is_compl;
+}
+
+static inline bool epdc_is_lut_cancelled(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_STATUS);
+	bool is_void = (val & EPDC_STATUS_UPD_VOID) ? true : false;
+
+	return is_void;
+}
+
+static inline bool epdc_is_collision(struct mxc_epdc *priv)
+{
+	u32 val = epdc_read(priv, EPDC_IRQ);
+
+	return (val & EPDC_IRQ_LUT_COL_IRQ) ? true : false;
+}
+
+static inline u64 epdc_get_colliding_luts(struct mxc_epdc *priv)
+{
+	u64 val = (u64)(epdc_read(priv, EPDC_STATUS_COL));
+
+	val |= (u64)epdc_read(priv, EPDC_STATUS_COL2) << 32;
+	return val;
+}
+
+static irqreturn_t mxc_epdc_irq_handler(int irq, void *dev_id)
+{
+	struct mxc_epdc *priv = dev_id;
+	u32 ints_fired, luts1_ints_fired, luts2_ints_fired;
+
+	/*
+	 * If we just completed one-time panel init, bypass
+	 * queue handling, clear interrupt and return
+	 */
+	if (priv->in_init) {
+		if (epdc_is_working_buffer_complete(priv)) {
+			epdc_working_buf_intr(priv, false);
+			epdc_clear_working_buf_irq(priv);
+			dev_dbg(priv->drm.dev, "Cleared WB for init update\n");
+		}
+
+		if (epdc_is_lut_complete(priv, 0)) {
+			epdc_lut_complete_intr(priv, 0, false);
+			epdc_clear_lut_complete_irq(priv, 0);
+			priv->in_init = false;
+			dev_dbg(priv->drm.dev, "Cleared LUT complete for init update\n");
+		}
+
+		return IRQ_HANDLED;
+	}
+
+	ints_fired = epdc_read(priv, EPDC_IRQ_MASK) & epdc_read(priv, EPDC_IRQ);
+
+	luts1_ints_fired = epdc_read(priv, EPDC_IRQ_MASK1) & epdc_read(priv, EPDC_IRQ1);
+	luts2_ints_fired = epdc_read(priv, EPDC_IRQ_MASK2) & epdc_read(priv, EPDC_IRQ2);
+
+	if (!(ints_fired || luts1_ints_fired || luts2_ints_fired))
+		return IRQ_HANDLED;
+
+	if (epdc_read(priv, EPDC_IRQ) & EPDC_IRQ_TCE_UNDERRUN_IRQ) {
+		dev_err(priv->drm.dev,
+			"TCE underrun! Will continue to update panel\n");
+		/* Clear TCE underrun IRQ */
+		epdc_write(priv, EPDC_IRQ_CLEAR, EPDC_IRQ_TCE_UNDERRUN_IRQ);
+	}
+
+	/* Check if we are waiting on EOF to sync a new update submission */
+	if (epdc_signal_eof(priv)) {
+		epdc_eof_intr(priv, false);
+		epdc_clear_eof_irq(priv);
+	}
+
+	/*
+	 * Workaround for EPDC v2.0/v2.1 errata: Must read collision status
+	 * before clearing IRQ, or else collision status for bits 16:63
+	 * will be automatically cleared.  So we read it here, and there is
+	 * no conflict with using it in epdc_intr_work_func since the
+	 * working buffer processing flow is strictly sequential (i.e.,
+	 * only one WB processing done at a time, so the data grabbed
+	 * here should be up-to-date and accurate when the WB processing
+	 * completes.  Also, note that there is no impact to other versions
+	 * of EPDC by reading LUT status here.
+	 */
+	if (priv->cur_update != NULL)
+		priv->epdc_colliding_luts = epdc_get_colliding_luts(priv);
+
+	/* Clear the interrupt mask for any interrupts signalled */
+	epdc_write(priv, EPDC_IRQ_MASK_CLEAR, ints_fired);
+	epdc_write(priv, EPDC_IRQ_MASK1_CLEAR, luts1_ints_fired);
+	epdc_write(priv, EPDC_IRQ_MASK2_CLEAR, luts2_ints_fired);
+
+	dev_dbg(priv->drm.dev,
+		"EPDC interrupts fired = 0x%x, LUTS1 fired = 0x%x, LUTS2 fired = 0x%x\n",
+		ints_fired, luts1_ints_fired, luts2_ints_fired);
+
+	queue_work(priv->epdc_intr_workqueue,
+		&priv->epdc_intr_work);
+
+
+	return IRQ_HANDLED;
+}
+
+static void cleanup_update_list(void *data)
+{
+	struct mxc_epdc *priv = (struct mxc_epdc *)data;
+	struct update_data_list *plist, *temp_list;
+
+	list_for_each_entry_safe(plist, temp_list, &priv->upd_buf_free_list,
+			list) {
+		list_del(&plist->list);
+		kfree(plist);
+	}
+}
+
+static void epdc_done_work_func(struct work_struct *work)
+{
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc,
+			epdc_done_work.work);
+	mxc_epdc_powerdown(priv);
+}
+
+static int epdc_submit_merge(struct update_desc_list *upd_desc_list,
+				struct update_desc_list *update_to_merge,
+				struct mxc_epdc *priv)
+{
+	struct mxcfb_update_data *a, *b;
+	struct drm_rect *arect, *brect;
+
+	a = &upd_desc_list->upd_data;
+	b = &update_to_merge->upd_data;
+	arect = &upd_desc_list->upd_data.update_region;
+	brect = &update_to_merge->upd_data.update_region;
+
+	if (a->update_mode != b->update_mode)
+		a->update_mode = UPDATE_MODE_FULL;
+
+	if (a->waveform_mode != b->waveform_mode)
+		a->waveform_mode = WAVEFORM_MODE_AUTO;
+
+	if ((arect->x1 > brect->x2) ||
+	    (brect->x1 > arect->x2) ||
+	    (arect->y1 > brect->y2) ||
+	    (brect->y1 > arect->y2))
+		return MERGE_FAIL;
+
+	arect->x1 = min(arect->x1, brect->x1);
+	arect->x2 = max(arect->x2, brect->x2);
+	arect->y1 = min(arect->y1, brect->y1);
+	arect->y2 = max(arect->y2, brect->y2);
+
+	/* Merged update should take on the earliest order */
+	upd_desc_list->update_order = max(upd_desc_list->update_order,
+					  update_to_merge->update_order);
+
+	return MERGE_OK;
+}
+
+static void epdc_from_rgb_clear_lower_nibble(struct drm_rect *clip,
+					     void *vaddr, int pitch,
+					     u8 *dst, int dst_pitch)
+{
+	unsigned int x, y;
+
+	dst += clip->y1 * dst_pitch;
+
+	for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) {
+		u32 *src;
+
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++) {
+			u8 r = (*src & 0x00ff0000) >> 16;
+			u8 g = (*src & 0x0000ff00) >> 8;
+			u8 b =  *src & 0x000000ff;
+
+			/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
+			u8 gray = (3 * r + 6 * g + b) / 10;
+
+			/*
+			 * done in Tolino 3.0.x kernels via PXP_LUT_AA
+			 * needed for 5 bit waveforms
+			 */
+
+			dst[x] = gray & 0xF0;
+			src++;
+		}
+	}
+}
+
+/* found by experimentation, reduced number of levels of gray */
+static void epdc_from_rgb_shift(struct drm_rect *clip, void *vaddr, int pitch,
+				u8 *dst, int dst_pitch)
+{
+	unsigned int x, y;
+
+	dst += clip->y1 * dst_pitch;
+
+	for (y = clip->y1; y < clip->y2; y++, dst += dst_pitch) {
+		u32 *src;
+
+		src = vaddr + (y * pitch);
+		src += clip->x1;
+		for (x = clip->x1; x < clip->x2; x++) {
+			u8 r = (*src & 0x00ff0000) >> 16;
+			u8 g = (*src & 0x0000ff00) >> 8;
+			u8 b =  *src & 0x000000ff;
+
+			/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
+			u8 gray = (3 * r + 6 * g + b) / 10;
+
+			dst[x] = (gray >> 2) | 0xC0;
+			src++;
+		}
+	}
+}
+
+static void epdc_submit_update(struct mxc_epdc *priv,
+			       u32 lut_num, u32 waveform_mode, u32 update_mode,
+			       bool use_dry_run, bool use_test_mode, u32 np_val)
+{
+	u32 reg_val = 0;
+
+	if (use_test_mode) {
+		reg_val |=
+		    ((np_val << EPDC_UPD_FIXED_FIXNP_OFFSET) &
+		     EPDC_UPD_FIXED_FIXNP_MASK) | EPDC_UPD_FIXED_FIXNP_EN;
+
+		epdc_write(priv, EPDC_UPD_FIXED, reg_val);
+
+		reg_val = EPDC_UPD_CTRL_USE_FIXED;
+	} else {
+		epdc_write(priv, EPDC_UPD_FIXED, reg_val);
+	}
+
+	if (waveform_mode == WAVEFORM_MODE_AUTO)
+		reg_val |= EPDC_UPD_CTRL_AUTOWV;
+	else
+		reg_val |= ((waveform_mode <<
+				EPDC_UPD_CTRL_WAVEFORM_MODE_OFFSET) &
+				EPDC_UPD_CTRL_WAVEFORM_MODE_MASK);
+
+	reg_val |= (use_dry_run ? EPDC_UPD_CTRL_DRY_RUN : 0) |
+	    ((lut_num << EPDC_UPD_CTRL_LUT_SEL_OFFSET) &
+	     EPDC_UPD_CTRL_LUT_SEL_MASK) |
+	    update_mode;
+	epdc_write(priv, EPDC_UPD_CTRL, reg_val);
+}
+
+static inline bool epdc_is_lut_active(struct mxc_epdc *priv, u32 lut_num)
+{
+	u32 val;
+	bool is_active;
+
+	if (lut_num < 32) {
+		val = epdc_read(priv, EPDC_STATUS_LUTS);
+		is_active = val & BIT(lut_num) ? true : false;
+	} else {
+		val = epdc_read(priv, EPDC_STATUS_LUTS2);
+		is_active = val & BIT(lut_num - 32) ? true : false;
+	}
+
+	return is_active;
+}
+
+static inline bool epdc_any_luts_active(struct mxc_epdc *priv)
+{
+	bool any_active;
+
+	any_active = (epdc_read(priv, EPDC_STATUS_LUTS) |
+		      epdc_read(priv, EPDC_STATUS_LUTS2))	? true : false;
+
+	return any_active;
+}
+
+static inline bool epdc_any_luts_available(struct mxc_epdc *priv)
+{
+	bool luts_available =
+	    (epdc_read(priv, EPDC_STATUS_NEXTLUT) &
+	     EPDC_STATUS_NEXTLUT_NEXT_LUT_VALID) ? true : false;
+	return luts_available;
+}
+
+static inline int epdc_get_next_lut(struct mxc_epdc *priv)
+{
+	u32 val =
+	    epdc_read(priv, EPDC_STATUS_NEXTLUT) &
+	    EPDC_STATUS_NEXTLUT_NEXT_LUT_MASK;
+	return val;
+}
+
+static int epdc_choose_next_lut(struct mxc_epdc *priv, int *next_lut)
+{
+	u64 luts_status, unprocessed_luts, used_luts;
+	/* Available LUTs are reduced to 16 in 5-bit waveform mode */
+	bool format_p5n = ((epdc_read(priv, EPDC_FORMAT) &
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_MASK) ==
+	EPDC_FORMAT_BUF_PIXEL_FORMAT_P5N);
+
+	luts_status = epdc_read(priv, EPDC_STATUS_LUTS);
+	if (format_p5n)
+		luts_status &= 0xFFFF;
+	else
+		luts_status |= ((u64)epdc_read(priv, EPDC_STATUS_LUTS2) << 32);
+
+	unprocessed_luts = epdc_read(priv, EPDC_IRQ1) |
+		((u64)epdc_read(priv, EPDC_IRQ2) << 32);
+	if (format_p5n)
+		unprocessed_luts &= 0xFFFF;
+
+	/*
+	 * Note on unprocessed_luts: There is a race condition
+	 * where a LUT completes, but has not been processed by
+	 * IRQ handler workqueue, and then a new update request
+	 * attempts to use that LUT.  We prevent that here by
+	 * ensuring that the LUT we choose doesn't have its IRQ
+	 * bit set (indicating it has completed but not yet been
+	 * processed).
+	 */
+	used_luts = luts_status | unprocessed_luts;
+
+	if (format_p5n) {
+		*next_lut = fls64(used_luts);
+		if (*next_lut > 15)
+			*next_lut = ffz(used_luts);
+	} else {
+		if ((u32)used_luts != ~0UL)
+			*next_lut = ffz((u32)used_luts);
+		else if ((u32)(used_luts >> 32) != ~0UL)
+			*next_lut = ffz((u32)(used_luts >> 32)) + 32;
+		else
+			*next_lut = INVALID_LUT;
+	}
+
+	if (used_luts & 0x8000)
+		return 1;
+	else
+		return 0;
+}
+
+
+
+
+static void epdc_submit_work_func(struct work_struct *work)
+{
+	struct update_data_list *next_update, *temp_update;
+	struct update_desc_list *next_desc, *temp_desc;
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc, epdc_submit_work);
+	struct update_data_list *upd_data_list = NULL;
+	struct drm_rect adj_update_region, *upd_region;
+	bool end_merge = false;
+	u32 update_addr;
+	uint8_t *update_addr_virt;
+	int ret;
+
+	/* Protect access to buffer queues and to update HW */
+	mutex_lock(&priv->queue_mutex);
+
+	/*
+	 * Are any of our collision updates able to go now?
+	 * Go through all updates in the collision list and check to see
+	 * if the collision mask has been fully cleared
+	 */
+	list_for_each_entry_safe(next_update, temp_update,
+				&priv->upd_buf_collision_list, list) {
+
+		if (next_update->collision_mask != 0)
+			continue;
+
+		dev_dbg(priv->drm.dev, "A collision update is ready to go!\n");
+
+		/* Force waveform mode to auto for resubmitted collisions */
+		next_update->update_desc->upd_data.waveform_mode =
+			WAVEFORM_MODE_AUTO;
+
+		/*
+		 * We have a collision cleared, so select it for resubmission.
+		 * If an update is already selected, attempt to merge.
+		 */
+		if (!upd_data_list) {
+			upd_data_list = next_update;
+			list_del_init(&next_update->list);
+		} else {
+			switch (epdc_submit_merge(upd_data_list->update_desc,
+						  next_update->update_desc,
+						  priv)) {
+			case MERGE_OK:
+				dev_dbg(priv->drm.dev,
+					"Update merged [collision]\n");
+				list_del_init(&next_update->update_desc->list);
+				kfree(next_update->update_desc);
+				next_update->update_desc = NULL;
+				list_del_init(&next_update->list);
+				/* Add to free buffer list */
+				list_add_tail(&next_update->list,
+					 &priv->upd_buf_free_list);
+				break;
+			case MERGE_FAIL:
+				dev_dbg(priv->drm.dev,
+					"Update not merged [collision]\n");
+				break;
+			case MERGE_BLOCK:
+				dev_dbg(priv->drm.dev,
+					"Merge blocked [collision]\n");
+				end_merge = true;
+				break;
+			}
+
+			if (end_merge) {
+				end_merge = false;
+				break;
+			}
+		}
+	}
+
+	/*
+	 * If we didn't find a collision update ready to go, we
+	 * need to get a free buffer and match it to a pending update.
+	 */
+
+	/*
+	 * Can't proceed if there are no free buffers (and we don't
+	 * already have a collision update selected)
+	 */
+	if (!upd_data_list &&
+		list_empty(&priv->upd_buf_free_list)) {
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	list_for_each_entry_safe(next_desc, temp_desc,
+			&priv->upd_pending_list, list) {
+
+		dev_dbg(priv->drm.dev, "Found a pending update!\n");
+
+		if (!upd_data_list) {
+			if (list_empty(&priv->upd_buf_free_list))
+				break;
+			upd_data_list =
+				list_entry(priv->upd_buf_free_list.next,
+					struct update_data_list, list);
+			list_del_init(&upd_data_list->list);
+			upd_data_list->update_desc = next_desc;
+			list_del_init(&next_desc->list);
+		} else {
+			switch (epdc_submit_merge(upd_data_list->update_desc,
+					next_desc, priv)) {
+			case MERGE_OK:
+				dev_dbg(priv->drm.dev,
+					"Update merged [queue]\n");
+				list_del_init(&next_desc->list);
+				kfree(next_desc);
+				break;
+			case MERGE_FAIL:
+				dev_dbg(priv->drm.dev,
+					"Update not merged [queue]\n");
+				break;
+			case MERGE_BLOCK:
+				dev_dbg(priv->drm.dev,
+					"Merge blocked [collision]\n");
+				end_merge = true;
+				break;
+			}
+
+			if (end_merge)
+				break;
+		}
+	}
+
+	/* Is update list empty? */
+	if (!upd_data_list) {
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	if ((!priv->powered)
+		|| priv->powering_down)
+		mxc_epdc_powerup(priv);
+
+	/*
+	 * Set update buffer pointer to the start of
+	 * the update region in the frame buffer.
+	 */
+	upd_region = &upd_data_list->update_desc->upd_data.update_region;
+	update_addr = priv->epdc_mem_phys +
+		((upd_region->y1 * priv->epdc_mem_width) +
+		upd_region->x1);
+	update_addr_virt = (u8 *)(priv->epdc_mem_virt) +
+		((upd_region->y1 * priv->epdc_mem_width) +
+		upd_region->x1);
+
+	adj_update_region = upd_data_list->update_desc->upd_data.update_region;
+	/*
+	 * Is the working buffer idle?
+	 * If the working buffer is busy, we must wait for the resource
+	 * to become free. The IST will signal this event.
+	 */
+	if (priv->cur_update != NULL) {
+		dev_dbg(priv->drm.dev, "working buf busy!\n");
+
+		/* Initialize event signalling an update resource is free */
+		init_completion(&priv->update_res_free);
+
+		priv->waiting_for_wb = true;
+
+		/* Leave spinlock while waiting for WB to complete */
+		mutex_unlock(&priv->queue_mutex);
+		wait_for_completion(&priv->update_res_free);
+		mutex_lock(&priv->queue_mutex);
+	}
+
+	/*
+	 * If there are no LUTs available,
+	 * then we must wait for the resource to become free.
+	 * The IST will signal this event.
+	 */
+	if (!epdc_any_luts_available(priv)) {
+		dev_dbg(priv->drm.dev, "no luts available!\n");
+
+		/* Initialize event signalling an update resource is free */
+		init_completion(&priv->update_res_free);
+
+		priv->waiting_for_lut = true;
+
+		/* Leave spinlock while waiting for LUT to free up */
+		mutex_unlock(&priv->queue_mutex);
+		wait_for_completion(&priv->update_res_free);
+		mutex_lock(&priv->queue_mutex);
+	}
+
+	ret = epdc_choose_next_lut(priv, &upd_data_list->lut_num);
+
+	/* LUTs are available, so we get one here */
+	priv->cur_update = upd_data_list;
+
+	/* Reset mask for LUTS that have completed during WB processing */
+	priv->luts_complete_wb = 0;
+
+	/* Mark LUT with order */
+	priv->lut_update_order[upd_data_list->lut_num] =
+			upd_data_list->update_desc->update_order;
+
+	epdc_lut_complete_intr(priv, upd_data_list->lut_num,
+				       true);
+
+	/* Enable Collision and WB complete IRQs */
+	epdc_working_buf_intr(priv, true);
+
+	epdc_write(priv, EPDC_TEMP,
+		   mxc_epdc_fb_get_temp_index(priv, TEMP_USE_AMBIENT));
+
+	/* Program EPDC update to process buffer */
+
+	epdc_set_update_area(priv, update_addr,
+			     adj_update_region.x1, adj_update_region.y1,
+			     drm_rect_width(&adj_update_region),
+			     drm_rect_height(&adj_update_region),
+			     priv->epdc_mem_width);
+
+	if (priv->wv_modes_update &&
+		(upd_data_list->update_desc->upd_data.waveform_mode
+			== WAVEFORM_MODE_AUTO)) {
+		mxc_epdc_set_update_waveform(priv, &priv->wv_modes);
+		priv->wv_modes_update = false;
+	}
+
+	epdc_submit_update(priv, upd_data_list->lut_num,
+			   upd_data_list->update_desc->upd_data.waveform_mode,
+			   upd_data_list->update_desc->upd_data.update_mode,
+			   false,
+			   false, 0);
+
+	/* Release buffer queues */
+	mutex_unlock(&priv->queue_mutex);
+}
+
+void mxc_epdc_flush_updates(struct mxc_epdc *priv)
+{
+	int ret;
+
+	if (priv->in_init)
+		return;
+
+	/* Grab queue lock to prevent any new updates from being submitted */
+	mutex_lock(&priv->queue_mutex);
+
+	/*
+	 * 3 places to check for updates that are active or pending:
+	 *   1) Updates in the pending list
+	 *   2) Update buffers in use (e.g., PxP processing)
+	 *   3) Active updates to panel - We can key off of EPDC
+	 *      power state to know if we have active updates.
+	 */
+	if (!list_empty(&priv->upd_pending_list) ||
+		!is_free_list_full(priv) ||
+		(priv->updates_active == true)) {
+		/* Initialize event signalling updates are done */
+		init_completion(&priv->updates_done);
+		priv->waiting_for_idle = true;
+
+		mutex_unlock(&priv->queue_mutex);
+		/* Wait for any currently active updates to complete */
+		ret = wait_for_completion_timeout(&priv->updates_done,
+						msecs_to_jiffies(8000));
+		if (!ret)
+			dev_err(priv->drm.dev,
+				"Flush updates timeout! ret = 0x%x\n", ret);
+
+		mutex_lock(&priv->queue_mutex);
+		priv->waiting_for_idle = false;
+	}
+
+	mutex_unlock(&priv->queue_mutex);
+}
+
+void mxc_epdc_draw_mode0(struct mxc_epdc *priv)
+{
+	u32 *upd_buf_ptr;
+	int i;
+	u32 xres, yres;
+
+	upd_buf_ptr = (u32 *)priv->epdc_mem_virt;
+
+	epdc_working_buf_intr(priv, true);
+	epdc_lut_complete_intr(priv, 0, true);
+
+	/* Use unrotated (native) width/height */
+	xres = priv->epdc_mem_width;
+	yres = priv->epdc_mem_height;
+
+	/* Program EPDC update to process buffer */
+	epdc_set_update_area(priv, priv->epdc_mem_phys, 0, 0, xres, yres, 0);
+	epdc_submit_update(priv, 0, priv->wv_modes.mode_init, UPDATE_MODE_FULL,
+		false, true, 0xFF);
+
+	dev_dbg(priv->drm.dev, "Mode0 update - Waiting for LUT to complete...\n");
+
+	/* Will timeout after ~4-5 seconds */
+
+	for (i = 0; i < 40; i++) {
+		if (!epdc_is_lut_active(priv, 0)) {
+			dev_dbg(priv->drm.dev, "Mode0 init complete\n");
+			return;
+		}
+		msleep(100);
+	}
+
+	dev_err(priv->drm.dev, "Mode0 init failed!\n");
+}
+
+
+int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch, void *vaddr,
+				struct mxc_epdc *priv)
+{
+	struct update_desc_list *upd_desc;
+
+	if (priv->rev < 30)
+		epdc_from_rgb_clear_lower_nibble(clip, vaddr, pitch,
+						 (u8 *)priv->epdc_mem_virt,
+						 priv->epdc_mem_width);
+	else
+		epdc_from_rgb_shift(clip, vaddr, pitch,
+				    (u8 *)priv->epdc_mem_virt,
+				    priv->epdc_mem_width);
+
+	/* Has EPDC HW been initialized? */
+	if (!priv->hw_ready) {
+		/* Throw message if we are not mid-initialization */
+		if (!priv->hw_initializing)
+			dev_err(priv->drm.dev, "Display HW not properly initialized. Aborting update.\n");
+		return -EPERM;
+	}
+
+	mutex_lock(&priv->queue_mutex);
+
+	if (priv->waiting_for_idle) {
+		dev_dbg(priv->drm.dev, "EPDC not active. Update request abort.\n");
+		mutex_unlock(&priv->queue_mutex);
+		return -EPERM;
+	}
+
+
+	/*
+	 * Create new update data structure, fill it with new update
+	 * data and add it to the list of pending updates
+	 */
+	upd_desc = kzalloc(sizeof(struct update_desc_list), GFP_KERNEL);
+	if (!upd_desc) {
+		mutex_unlock(&priv->queue_mutex);
+		return -ENOMEM;
+	}
+	/* Initialize per-update marker list */
+	upd_desc->upd_data.update_region = *clip;
+	upd_desc->upd_data.waveform_mode = WAVEFORM_MODE_AUTO;
+	upd_desc->upd_data.temp = TEMP_USE_AMBIENT;
+	upd_desc->upd_data.update_mode = UPDATE_MODE_PARTIAL;
+	upd_desc->update_order = priv->order_cnt++;
+	list_add_tail(&upd_desc->list, &priv->upd_pending_list);
+
+	/* Queued update scheme processing */
+
+	mutex_unlock(&priv->queue_mutex);
+
+	/* Signal workqueue to handle new update */
+	queue_work(priv->epdc_submit_workqueue,
+		   &priv->epdc_submit_work);
+
+	return 0;
+}
+
+static void epdc_intr_work_func(struct work_struct *work)
+{
+	struct mxc_epdc *priv =
+		container_of(work, struct mxc_epdc, epdc_intr_work);
+	struct update_data_list *collision_update;
+	u64 temp_mask;
+	u32 lut;
+	bool ignore_collision = false;
+	int i;
+	bool wb_lut_done = false;
+	bool free_update = true;
+	u32 epdc_luts_active, epdc_wb_busy, epdc_luts_avail, epdc_lut_cancelled;
+	u32 epdc_collision;
+	u64 epdc_irq_stat;
+	bool epdc_waiting_on_wb;
+	u32 coll_coord, coll_size;
+
+	/* Protect access to buffer queues and to update HW */
+	mutex_lock(&priv->queue_mutex);
+
+	/* Capture EPDC status one time to limit exposure to race conditions */
+	epdc_luts_active = epdc_any_luts_active(priv);
+	epdc_wb_busy = epdc_is_working_buffer_busy(priv);
+	epdc_lut_cancelled = epdc_is_lut_cancelled(priv);
+	epdc_luts_avail = epdc_any_luts_available(priv);
+	epdc_collision = epdc_is_collision(priv);
+
+	epdc_irq_stat = (u64)epdc_read(priv, EPDC_IRQ1) |
+			((u64)epdc_read(priv, EPDC_IRQ2) << 32);
+	epdc_waiting_on_wb = (priv->cur_update != NULL) ? true : false;
+
+	/* Free any LUTs that have completed */
+	for (i = 0; i < priv->num_luts; i++) {
+		if ((epdc_irq_stat & (1ULL << i)) == 0)
+			continue;
+
+		dev_dbg(priv->drm.dev, "LUT %d completed\n", i);
+
+		/* Disable IRQ for completed LUT */
+		epdc_lut_complete_intr(priv, i, false);
+
+		/*
+		 * Go through all updates in the collision list and
+		 * unmask any updates that were colliding with
+		 * the completed LUT.
+		 */
+		list_for_each_entry(collision_update,
+				    &priv->upd_buf_collision_list, list) {
+			collision_update->collision_mask =
+			    collision_update->collision_mask & ~BIT(i);
+		}
+
+		epdc_clear_lut_complete_irq(priv, i);
+
+		priv->luts_complete_wb |= 1ULL << i;
+
+		priv->lut_update_order[i] = 0;
+
+		/* Signal completion if submit workqueue needs a LUT */
+		if (priv->waiting_for_lut) {
+			complete(&priv->update_res_free);
+			priv->waiting_for_lut = false;
+		}
+
+		/*
+		 * Detect race condition where WB and its LUT complete
+		 * (i.e. full update completes) in one swoop
+		 */
+		if (epdc_waiting_on_wb &&
+			(i == priv->cur_update->lut_num))
+			wb_lut_done = true;
+	}
+
+	/* Check to see if all updates have completed */
+	if (list_empty(&priv->upd_pending_list) &&
+		is_free_list_full(priv) &&
+		!epdc_waiting_on_wb &&
+		!epdc_luts_active) {
+
+		priv->updates_active = false;
+
+		if (priv->pwrdown_delay != FB_POWERDOWN_DISABLE) {
+			/*
+			 * Set variable to prevent overlapping
+			 * enable/disable requests
+			 */
+			priv->powering_down = true;
+
+			/* Schedule task to disable EPDC HW until next update */
+			schedule_delayed_work(&priv->epdc_done_work,
+				msecs_to_jiffies(priv->pwrdown_delay));
+
+			/* Reset counter to reduce chance of overflow */
+			priv->order_cnt = 0;
+		}
+
+		if (priv->waiting_for_idle)
+			complete(&priv->updates_done);
+	}
+
+	/* Is Working Buffer busy? */
+	if (epdc_wb_busy) {
+		/* Can't submit another update until WB is done */
+		mutex_unlock(&priv->queue_mutex);
+		return;
+	}
+
+	/*
+	 * Were we waiting on working buffer?
+	 * If so, update queues and check for collisions
+	 */
+	if (epdc_waiting_on_wb) {
+		dev_dbg(priv->drm.dev, "\nWorking buffer completed\n");
+
+		/* Signal completion if submit workqueue was waiting on WB */
+		if (priv->waiting_for_wb) {
+			complete(&priv->update_res_free);
+			priv->waiting_for_wb = false;
+		}
+
+		if (epdc_lut_cancelled && !epdc_collision) {
+			/*
+			 * Note: The update may be cancelled (void) if all
+			 * pixels collided. In that case we handle it as a
+			 * collision, not a cancel.
+			 */
+
+			/* Clear LUT status (might be set if no AUTOWV used) */
+
+			/*
+			 * Disable and clear IRQ for the LUT used.
+			 * Even though LUT is cancelled in HW, the LUT
+			 * complete bit may be set if AUTOWV not used.
+			 */
+			epdc_lut_complete_intr(priv,
+					priv->cur_update->lut_num, false);
+			epdc_clear_lut_complete_irq(priv,
+					priv->cur_update->lut_num);
+
+			priv->lut_update_order[priv->cur_update->lut_num] = 0;
+
+			/* Signal completion if submit workqueue needs a LUT */
+			if (priv->waiting_for_lut) {
+				complete(&priv->update_res_free);
+				priv->waiting_for_lut = false;
+			}
+		} else if (epdc_collision) {
+			/* Check list of colliding LUTs, and add to our collision mask */
+			priv->cur_update->collision_mask =
+			    priv->epdc_colliding_luts;
+
+			/* Clear collisions that completed since WB began */
+			priv->cur_update->collision_mask &=
+				~priv->luts_complete_wb;
+
+			dev_dbg(priv->drm.dev, "Collision mask = 0x%llx\n",
+			       priv->epdc_colliding_luts);
+
+			/*
+			 * For EPDC 2.0 and later, minimum collision bounds
+			 * are provided by HW.  Recompute new bounds here.
+			 */
+
+			coll_coord = epdc_read(priv, EPDC_UPD_COL_CORD);
+			coll_size = epdc_read(priv, EPDC_UPD_COL_SIZE);
+			drm_rect_init(&priv->cur_update->update_desc->upd_data.update_region,
+				(coll_coord & EPDC_UPD_COL_CORD_XCORD_MASK)
+					>> EPDC_UPD_COL_CORD_XCORD_OFFSET,
+				(coll_coord & EPDC_UPD_COL_CORD_YCORD_MASK)
+					>> EPDC_UPD_COL_CORD_YCORD_OFFSET,
+				((coll_size & EPDC_UPD_COL_SIZE_WIDTH_MASK)
+					>> EPDC_UPD_COL_SIZE_WIDTH_OFFSET),
+				((coll_size & EPDC_UPD_COL_SIZE_HEIGHT_MASK)
+					>> EPDC_UPD_COL_SIZE_HEIGHT_OFFSET));
+
+			/*
+			 * If we collide with newer updates, then
+			 * we don't need to re-submit the update. The
+			 * idea is that the newer updates should take
+			 * precedence anyways, so we don't want to
+			 * overwrite them.
+			 */
+			for (temp_mask = priv->cur_update->collision_mask, lut = 0;
+				temp_mask != 0;
+				lut++, temp_mask = temp_mask >> 1) {
+				if (!(temp_mask & 0x1))
+					continue;
+
+				if (priv->lut_update_order[lut] >=
+					priv->cur_update->update_desc->update_order) {
+					dev_dbg(priv->drm.dev,
+						"Ignoring collision with newer update.\n");
+					ignore_collision = true;
+					break;
+				}
+			}
+
+			if (!ignore_collision) {
+				free_update = false;
+
+				/* Move to collision list */
+				list_add_tail(&priv->cur_update->list,
+					 &priv->upd_buf_collision_list);
+			}
+		}
+
+		/* Do we need to free the current update descriptor? */
+		if (free_update) {
+			/* Free update descriptor */
+			kfree(priv->cur_update->update_desc);
+
+			/* Add to free buffer list */
+			list_add_tail(&priv->cur_update->list,
+				 &priv->upd_buf_free_list);
+
+			/* Check to see if all updates have completed */
+			if (list_empty(&priv->upd_pending_list) &&
+				is_free_list_full(priv) &&
+				!epdc_luts_active) {
+
+				priv->updates_active = false;
+
+				if (priv->pwrdown_delay !=
+						FB_POWERDOWN_DISABLE) {
+					/*
+					 * Set variable to prevent overlapping
+					 * enable/disable requests
+					 */
+					priv->powering_down = true;
+
+					/* Schedule EPDC disable */
+					schedule_delayed_work(&priv->epdc_done_work,
+						msecs_to_jiffies(priv->pwrdown_delay));
+
+					/* Reset counter to reduce chance of overflow */
+					priv->order_cnt = 0;
+				}
+
+				if (priv->waiting_for_idle)
+					complete(&priv->updates_done);
+			}
+		}
+
+		/* Clear current update */
+		priv->cur_update = NULL;
+
+		/* Clear IRQ for working buffer */
+		epdc_working_buf_intr(priv, false);
+		epdc_clear_working_buf_irq(priv);
+	}
+
+	/* Schedule task to submit collision and pending update */
+	if (!priv->powering_down)
+		queue_work(priv->epdc_submit_workqueue,
+			&priv->epdc_submit_work);
+
+	/* Release buffer queues */
+	mutex_unlock(&priv->queue_mutex);
+}
+
+
+int mxc_epdc_init_update(struct mxc_epdc *priv)
+{
+	struct update_data_list *upd_list;
+	int ret;
+	int irq;
+	int i;
+	/*
+	 * Initialize lists for pending updates,
+	 * active update requests, update collisions,
+	 * and freely available updates.
+	 */
+	priv->num_luts = EPDC_V2_NUM_LUTS;
+	priv->max_num_updates = EPDC_V2_MAX_NUM_UPDATES;
+
+	INIT_LIST_HEAD(&priv->upd_pending_list);
+	INIT_LIST_HEAD(&priv->upd_buf_queue);
+	INIT_LIST_HEAD(&priv->upd_buf_free_list);
+	INIT_LIST_HEAD(&priv->upd_buf_collision_list);
+
+	devm_add_action_or_reset(priv->drm.dev, cleanup_update_list, priv);
+	/* Allocate update buffers and add them to the list */
+	for (i = 0; i < priv->max_num_updates; i++) {
+		upd_list = kzalloc(sizeof(*upd_list), GFP_KERNEL);
+		if (upd_list == NULL)
+			return -ENOMEM;
+
+		/* Add newly allocated buffer to free list */
+		list_add(&upd_list->list, &priv->upd_buf_free_list);
+	}
+
+
+
+	/* Initialize marker list */
+	INIT_LIST_HEAD(&priv->full_marker_list);
+
+	/* Initialize all LUTs to inactive */
+	priv->lut_update_order =
+		devm_kzalloc(priv->drm.dev, priv->num_luts * sizeof(u32 *), GFP_KERNEL);
+	if (!priv->lut_update_order)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->num_luts; i++)
+		priv->lut_update_order[i] = 0;
+
+	INIT_DELAYED_WORK(&priv->epdc_done_work, epdc_done_work_func);
+	priv->epdc_submit_workqueue = alloc_workqueue("EPDC Submit",
+					WQ_MEM_RECLAIM | WQ_HIGHPRI |
+					WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+	INIT_WORK(&priv->epdc_submit_work, epdc_submit_work_func);
+	priv->epdc_intr_workqueue = alloc_workqueue("EPDC Interrupt",
+					WQ_MEM_RECLAIM | WQ_HIGHPRI |
+					WQ_CPU_INTENSIVE | WQ_UNBOUND, 1);
+	INIT_WORK(&priv->epdc_intr_work, epdc_intr_work_func);
+
+	mutex_init(&priv->queue_mutex);
+
+	/* Retrieve EPDC IRQ num */
+	irq = platform_get_irq(to_platform_device(priv->drm.dev), 0);
+	if (irq < 0) {
+		dev_err(priv->drm.dev, "cannot get IRQ resource\n");
+		return -ENODEV;
+	}
+	priv->epdc_irq = irq;
+
+	/* Register IRQ handler */
+	ret = devm_request_irq(priv->drm.dev, priv->epdc_irq,
+				mxc_epdc_irq_handler, 0, "epdc", priv);
+	if (ret) {
+		dev_err(priv->drm.dev, "request_irq (%d) failed with error %d\n",
+			priv->epdc_irq, ret);
+		return ret;
+	}
+
+	return 0;
+
+}
+
diff --git a/drivers/gpu/drm/mxc-epdc/epdc_update.h b/drivers/gpu/drm/mxc-epdc/epdc_update.h
new file mode 100644
index 000000000000..b6f30cf8eda7
--- /dev/null
+++ b/drivers/gpu/drm/mxc-epdc/epdc_update.h
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* Copyright (C) 2020 Andreas Kemnade */
+int mxc_epdc_send_single_update(struct drm_rect *clip, int pitch,
+				void *vaddr,
+				struct mxc_epdc *priv);
+void mxc_epdc_draw_mode0(struct mxc_epdc *priv);
+int mxc_epdc_init_update(struct mxc_epdc *priv);
+void mxc_epdc_flush_updates(struct mxc_epdc *priv);
+
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
index f7b1cbc4cc4e..db91e615bd89 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
@@ -12,6 +12,35 @@
 #include "epdc_regs.h"
 
 #define TEMP_USE_AMBIENT			0x1000
+#define GRAYSCALE_8BIT				0x1
+#define GRAYSCALE_8BIT_INVERTED			0x2
+#define GRAYSCALE_4BIT			  0x3
+#define GRAYSCALE_4BIT_INVERTED		 0x4
+
+#define AUTO_UPDATE_MODE_REGION_MODE		0
+#define AUTO_UPDATE_MODE_AUTOMATIC_MODE		1
+
+#define UPDATE_SCHEME_SNAPSHOT			0
+#define UPDATE_SCHEME_QUEUE			1
+#define UPDATE_SCHEME_QUEUE_AND_MERGE		2
+
+#define UPDATE_MODE_PARTIAL			0x0
+#define UPDATE_MODE_FULL			0x1
+
+#define WAVEFORM_MODE_GLR16			4
+#define WAVEFORM_MODE_GLD16			5
+#define WAVEFORM_MODE_AUTO			257
+
+#define FB_POWERDOWN_DISABLE			-1
+
+struct mxcfb_update_data {
+	struct drm_rect update_region;
+	u32 waveform_mode;
+	u32 update_mode;
+	int temp;
+	int dither_mode;
+	int quant_bit;
+};
 
 struct mxcfb_waveform_modes {
 	int mode_init;
@@ -87,6 +116,27 @@ struct mxc_epdc {
 	bool hw_initializing;
 	bool waiting_for_idle;
 
+	int order_cnt;
+	struct list_head upd_pending_list;
+	struct list_head upd_buf_queue;
+	struct list_head upd_buf_free_list;
+	struct list_head upd_buf_collision_list;
+	struct update_data_list *cur_update;
+	struct mutex queue_mutex;
+	int epdc_irq;
+	struct list_head full_marker_list;
+	u32 *lut_update_order;
+	u64 epdc_colliding_luts;
+	u64 luts_complete_wb;
+	struct completion updates_done;
+	struct delayed_work epdc_done_work;
+	struct workqueue_struct *epdc_submit_workqueue;
+	struct work_struct epdc_submit_work;
+	struct workqueue_struct *epdc_intr_workqueue;
+	struct work_struct epdc_intr_work;
+	bool waiting_for_wb;
+	bool waiting_for_lut;
+	struct completion update_res_free;
 };
 
 static inline u32 epdc_read(struct mxc_epdc *priv, u32 reg)
diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
index 4810e5c5bc6e..a0bde06187ca 100644
--- a/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
+++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
@@ -12,6 +12,7 @@
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_crtc_helper.h>
+#include <drm/drm_damage_helper.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_fb_cma_helper.h>
 #include <drm/drm_fb_helper.h>
@@ -26,6 +27,7 @@
 #include <drm/drm_probe_helper.h>
 #include "mxc_epdc.h"
 #include "epdc_hw.h"
+#include "epdc_update.h"
 #include "epdc_waveform.h"
 
 #define DRIVER_NAME "mxc_epdc"
@@ -211,6 +213,7 @@ static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
 	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
 
 	dev_dbg(priv->drm.dev, "pipe disable\n");
+	mxc_epdc_flush_updates(priv);
 
 	if (priv->epdc_mem_virt) {
 		dma_free_wc(priv->drm.dev, priv->epdc_mem_width * priv->epdc_mem_height,
@@ -227,11 +230,35 @@ static void mxc_epdc_pipe_disable(struct drm_simple_display_pipe *pipe)
 }
 
 static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
-				   struct drm_plane_state *plane_state)
+				   struct drm_plane_state *old_state)
 {
 	struct mxc_epdc *priv = drm_pipe_to_mxc_epdc(pipe);
+	struct drm_gem_cma_object *gem;
+	struct drm_atomic_helper_damage_iter iter;
+	struct drm_rect clip;
+
 
 	dev_dbg(priv->drm.dev, "pipe update\n");
+	if (!old_state->fb) {
+		dev_dbg(priv->drm.dev, "no fb, nothing to update\n");
+		return;
+	}
+
+	if (priv->epdc_mem_virt == NULL)
+		return;
+
+	gem = drm_fb_cma_get_gem_obj(old_state->fb, 0);
+	drm_atomic_helper_damage_iter_init(&iter, old_state, pipe->plane.state);
+	drm_atomic_for_each_plane_damage(&iter, &clip) {
+
+		dev_dbg(priv->drm.dev, "damaged: %d,%d-%d,%d\n",
+			clip.x1, clip.y1, clip.x2, clip.y2);
+
+		mxc_epdc_send_single_update(&clip, old_state->fb->pitches[0],
+					    gem->vaddr, priv);
+	}
+
+	return;
 }
 
 static const struct drm_simple_display_pipe_funcs mxc_epdc_funcs = {
@@ -280,6 +307,10 @@ static int mxc_epdc_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	ret = mxc_epdc_init_update(priv);
+	if (ret)
+		return ret;
+
 	ret = request_firmware(&firmware, "imx/epdc/epdc.fw", priv->drm.dev);
 	if (ret)
 		return ret;
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 5/6] ARM: dts: imx6sll: add EPDC
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
                   ` (3 preceding siblings ...)
  2022-02-06  8:00 ` [RFC PATCH 4/6] drm: mxc-epdc: Add update management Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  2022-02-06  8:00 ` [RFC PATCH 6/6] arm: dts: imx6sl: Add EPDC Andreas Kemnade
  5 siblings, 0 replies; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

The commercial variant has a controller for e-Paper displays.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 arch/arm/boot/dts/imx6sll.dtsi | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm/boot/dts/imx6sll.dtsi b/arch/arm/boot/dts/imx6sll.dtsi
index d4a000c3dde7..042e8a391b2f 100644
--- a/arch/arm/boot/dts/imx6sll.dtsi
+++ b/arch/arm/boot/dts/imx6sll.dtsi
@@ -643,6 +643,15 @@ pxp: pxp@20f0000 {
 				clock-names = "axi";
 			};
 
+			epdc: epdc@20f4000 {
+				compatible = "fsl,imx6sll-epdc";
+				reg = <0x020f4000 0x4000>;
+				interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clks IMX6SLL_CLK_EPDC_AXI>, <&clks IMX6SLL_CLK_EPDC_PIX>;
+				clock-names = "axi", "pix";
+				status = "disabled";
+			};
+
 			lcdif: lcd-controller@20f8000 {
 				compatible = "fsl,imx6sll-lcdif", "fsl,imx28-lcdif";
 				reg = <0x020f8000 0x4000>;
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [RFC PATCH 6/6] arm: dts: imx6sl: Add EPDC
  2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
                   ` (4 preceding siblings ...)
  2022-02-06  8:00 ` [RFC PATCH 5/6] ARM: dts: imx6sll: add EPDC Andreas Kemnade
@ 2022-02-06  8:00 ` Andreas Kemnade
  5 siblings, 0 replies; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-06  8:00 UTC (permalink / raw)
  To: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	andreas, dri-devel, devicetree, linux-arm-kernel, linux-kernel,
	alistair, samuel, josua.mayer, letux-kernel

Extend definition of EPDC.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
---
 arch/arm/boot/dts/imx6sl.dtsi | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm/boot/dts/imx6sl.dtsi b/arch/arm/boot/dts/imx6sl.dtsi
index c7d907c5c352..919e86e4fc74 100644
--- a/arch/arm/boot/dts/imx6sl.dtsi
+++ b/arch/arm/boot/dts/imx6sl.dtsi
@@ -765,8 +765,11 @@ pxp: pxp@20f0000 {
 			};
 
 			epdc: epdc@20f4000 {
+				compatible = "fsl,imx6sl-epdc";
 				reg = <0x020f4000 0x4000>;
 				interrupts = <0 97 IRQ_TYPE_LEVEL_HIGH>;
+				clocks = <&clks IMX6SL_CLK_EPDC_AXI>, <&clks IMX6SL_CLK_EPDC_PIX>;
+				clock-names = "axi", "pix";
 			};
 
 			lcdif: lcdif@20f8000 {
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
@ 2022-02-11 15:46   ` Rob Herring
  2022-02-14 22:45     ` Andreas Kemnade
  2022-02-17  9:21   ` Krzysztof Kozlowski
  2022-03-12 19:23   ` Jonathan Neuschäfer
  2 siblings, 1 reply; 18+ messages in thread
From: Rob Herring @ 2022-02-11 15:46 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, shawnguo, s.hauer, kernel, festevam,
	linux-imx, maarten.lankhorst, mripard, tzimmermann, dri-devel,
	devicetree, linux-arm-kernel, linux-kernel, alistair, samuel,
	josua.mayer, letux-kernel

On Sun, Feb 06, 2022 at 09:00:11AM +0100, Andreas Kemnade wrote:
> Add a binding for the Electrophoretic Display Controller found at least
> in the i.MX6.

The first version was in i.MX50 (I helped design the register 
interface). Is that version compatible?

> The timing subnode is directly here to avoid having display parameters
> spread all over the plate.
> 
> Supplies are organized the same way as in the fbdev driver in the
> NXP/Freescale kernel forks. The regulators used for that purpose,
> like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> start a bunch of regulators of higher or negative voltage with a
> well-defined timing. VCOM can be handled separately, but can also be
> incorporated into that single bit.
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
>  1 file changed, 159 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> new file mode 100644
> index 000000000000..7e0795cc3f70
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/imx/fsl,mxc-epdc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Freescale i.MX6 EPDC
> +
> +maintainers:
> +  - Andreas Kemnade <andreas@kemnade.info>
> +
> +description: |
> +  The EPDC is a controller for handling electronic paper displays found in
> +  i.MX6 SoCs.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - fsl,imx6sl-epdc
> +      - fsl,imx6sll-epdc

Not compatible with each other?

> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: Bus clock
> +      - description: Pixel clock
> +
> +  clock-names:
> +    items:
> +      - const: axi
> +      - const: pix
> +
> +  interrupts:
> +    maxItems: 1
> +

> +  vscan-holdoff:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoed-width:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoed-delay:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoez-width:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoez-delay:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdclk-hp-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdsp-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdoe-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdclk-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  num-ce:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1

All these need a vendor prefix and descriptions.

> +
> +  timing:
> +    $ref: /display/panel/panel-timing.yaml#
> +
> +  DISPLAY-supply:
> +    description:
> +      A couple of +/- voltages automatically powered on in a defintive order
> +
> +  VCOM-supply:
> +    description: compensation voltage
> +
> +  V3P3-supply:
> +    description: V3P3 supply
> +
> +  epd-thermal-zone:
> +    description:
> +      Zone to get temperature of the EPD from, practically ambient temperature.
> +
> +
> +

1 blank line.

> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +  - interrupts
> +  - vscan-holdoff
> +  - sdoed-width
> +  - sdoed-delay
> +  - sdoez-width
> +  - sdoez-delay
> +  - gdclk-hp-offs
> +  - gdsp-offs
> +  - gdoe-offs
> +  - gdclk-offs
> +  - num-ce
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/imx6sl-clock.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    epdc: epdc@20f4000 {
> +        compatible = "fsl,imx6sl-epdc";
> +        reg = <0x020f4000 0x4000>;
> +        interrupts = <0 97 IRQ_TYPE_LEVEL_HIGH>;
> +        clocks = <&clks IMX6SL_CLK_EPDC_AXI>, <&clks IMX6SL_CLK_EPDC_PIX>;
> +        clock-names = "axi", "pix";
> +
> +        pinctrl-names = "default";
> +        pinctrl-0 = <&pinctrl_epdc0>;
> +        V3P3-supply = <&V3P3_reg>;
> +        VCOM-supply = <&VCOM_reg>;
> +        DISPLAY-supply = <&DISPLAY_reg>;
> +        epd-thermal-zone = "epd-thermal";
> +
> +        vscan-holdoff = <4>;
> +        sdoed-width = <10>;
> +        sdoed-delay = <20>;
> +        sdoez-width = <10>;
> +        sdoez-delay = <20>;
> +        gdclk-hp-offs = <562>;
> +        gdsp-offs = <662>;
> +        gdoe-offs = <0>;
> +        gdclk-offs = <225>;
> +        num-ce = <3>;
> +        status = "okay";

Don't need status in examples.

> +
> +        timing {
> +                clock-frequency = <80000000>;
> +                hactive = <1448>;
> +                hback-porch = <16>;
> +                hfront-porch = <102>;
> +                hsync-len = <28>;
> +                vactive = <1072>;
> +                vback-porch = <4>;
> +                vfront-porch = <4>;
> +                vsync-len = <2>;
> +        };
> +    };
> +...
> -- 
> 2.30.2
> 
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-11 15:46   ` Rob Herring
@ 2022-02-14 22:45     ` Andreas Kemnade
  2022-02-16 23:52       ` Rob Herring
  0 siblings, 1 reply; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-14 22:45 UTC (permalink / raw)
  To: Rob Herring
  Cc: p.zabel, airlied, daniel, shawnguo, s.hauer, kernel, festevam,
	linux-imx, maarten.lankhorst, mripard, tzimmermann, dri-devel,
	devicetree, linux-arm-kernel, linux-kernel, alistair, samuel,
	josua.mayer, letux-kernel

Hi Rob,

On Fri, 11 Feb 2022 09:46:27 -0600
Rob Herring <robh@kernel.org> wrote:

> On Sun, Feb 06, 2022 at 09:00:11AM +0100, Andreas Kemnade wrote:
> > Add a binding for the Electrophoretic Display Controller found at least
> > in the i.MX6.  
> 
> The first version was in i.MX50 (I helped design the register 
> interface). Is that version compatible?
> 
it has some differences, but that could be detected by EPDC_VERSION
register. I do not own such a device, so I cannot fully check. I have
not seen any driver with devicetree for IMX5. For now I am rejecting
anything which has a EPDC version which I cannot check. 

> > The timing subnode is directly here to avoid having display parameters
> > spread all over the plate.
> > 
> > Supplies are organized the same way as in the fbdev driver in the
> > NXP/Freescale kernel forks. The regulators used for that purpose,
> > like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> > start a bunch of regulators of higher or negative voltage with a
> > well-defined timing. VCOM can be handled separately, but can also be
> > incorporated into that single bit.
> > 
> > Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> > ---
> >  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
> >  1 file changed, 159 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > new file mode 100644
> > index 000000000000..7e0795cc3f70
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > @@ -0,0 +1,159 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/display/imx/fsl,mxc-epdc.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Freescale i.MX6 EPDC
> > +
> > +maintainers:
> > +  - Andreas Kemnade <andreas@kemnade.info>
> > +
> > +description: |
> > +  The EPDC is a controller for handling electronic paper displays found in
> > +  i.MX6 SoCs.
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - fsl,imx6sl-epdc
> > +      - fsl,imx6sll-epdc  
> 
> Not compatible with each other?
> 
differences are detectable by EPDC_VERSION register, so probably so
problem. NXP/Freescale kernel uses
fsl,imx6dl-epdc
and 
fsl,imx7d-epdc (used also by imx6 devices with EPDC_VERSION = 3.0)
in their drivers.

fsl,imx6dl-epdc
fsl,imx6sl-epdc
fsl,imx6sll-epdc
fsl,imx7d-epdc
in their dtsis.

But the general rule is to use as less as possible compatible strings
if differences can be probed properly, so only one should be
sufficient? Which one?

Regards,
Andreas

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-14 22:45     ` Andreas Kemnade
@ 2022-02-16 23:52       ` Rob Herring
  0 siblings, 0 replies; 18+ messages in thread
From: Rob Herring @ 2022-02-16 23:52 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, shawnguo, s.hauer, kernel, festevam,
	linux-imx, maarten.lankhorst, mripard, tzimmermann, dri-devel,
	devicetree, linux-arm-kernel, linux-kernel, alistair, samuel,
	josua.mayer, letux-kernel

On Mon, Feb 14, 2022 at 11:45:17PM +0100, Andreas Kemnade wrote:
> Hi Rob,
> 
> On Fri, 11 Feb 2022 09:46:27 -0600
> Rob Herring <robh@kernel.org> wrote:
> 
> > On Sun, Feb 06, 2022 at 09:00:11AM +0100, Andreas Kemnade wrote:
> > > Add a binding for the Electrophoretic Display Controller found at least
> > > in the i.MX6.  
> > 
> > The first version was in i.MX50 (I helped design the register 
> > interface). Is that version compatible?
> > 
> it has some differences, but that could be detected by EPDC_VERSION
> register. I do not own such a device, so I cannot fully check. I have
> not seen any driver with devicetree for IMX5. For now I am rejecting
> anything which has a EPDC version which I cannot check. 
> 
> > > The timing subnode is directly here to avoid having display parameters
> > > spread all over the plate.
> > > 
> > > Supplies are organized the same way as in the fbdev driver in the
> > > NXP/Freescale kernel forks. The regulators used for that purpose,
> > > like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> > > start a bunch of regulators of higher or negative voltage with a
> > > well-defined timing. VCOM can be handled separately, but can also be
> > > incorporated into that single bit.
> > > 
> > > Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> > > ---
> > >  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
> > >  1 file changed, 159 insertions(+)
> > >  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > > 
> > > diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > > new file mode 100644
> > > index 000000000000..7e0795cc3f70
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > > @@ -0,0 +1,159 @@
> > > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/display/imx/fsl,mxc-epdc.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: Freescale i.MX6 EPDC
> > > +
> > > +maintainers:
> > > +  - Andreas Kemnade <andreas@kemnade.info>
> > > +
> > > +description: |
> > > +  The EPDC is a controller for handling electronic paper displays found in
> > > +  i.MX6 SoCs.
> > > +
> > > +properties:
> > > +  compatible:
> > > +    enum:
> > > +      - fsl,imx6sl-epdc
> > > +      - fsl,imx6sll-epdc  
> > 
> > Not compatible with each other?
> > 
> differences are detectable by EPDC_VERSION register, so probably so
> problem. NXP/Freescale kernel uses
> fsl,imx6dl-epdc
> and 
> fsl,imx7d-epdc (used also by imx6 devices with EPDC_VERSION = 3.0)
> in their drivers.
> 
> fsl,imx6dl-epdc
> fsl,imx6sl-epdc
> fsl,imx6sll-epdc
> fsl,imx7d-epdc
> in their dtsis.
> 
> But the general rule is to use as less as possible compatible strings
> if differences can be probed properly, so only one should be
> sufficient? Which one?

If you can probe all the differences, then just 'fsl,imx-epdc' is 
sufficient. Just document that so the next time around I don't forget 
and tell you it needs to be specific.

Rob

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
  2022-02-11 15:46   ` Rob Herring
@ 2022-02-17  9:21   ` Krzysztof Kozlowski
  2022-02-17 11:31     ` Andreas Kemnade
  2022-03-12 19:23   ` Jonathan Neuschäfer
  2 siblings, 1 reply; 18+ messages in thread
From: Krzysztof Kozlowski @ 2022-02-17  9:21 UTC (permalink / raw)
  To: Andreas Kemnade, p.zabel, airlied, daniel, robh+dt, shawnguo,
	s.hauer, kernel, festevam, linux-imx, maarten.lankhorst, mripard,
	tzimmermann, dri-devel, devicetree, linux-arm-kernel,
	linux-kernel, alistair, samuel, josua.mayer, letux-kernel

On 06/02/2022 09:00, Andreas Kemnade wrote:
> Add a binding for the Electrophoretic Display Controller found at least
> in the i.MX6.
> The timing subnode is directly here to avoid having display parameters
> spread all over the plate.
> 
> Supplies are organized the same way as in the fbdev driver in the
> NXP/Freescale kernel forks. The regulators used for that purpose,
> like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> start a bunch of regulators of higher or negative voltage with a
> well-defined timing. VCOM can be handled separately, but can also be
> incorporated into that single bit.
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
>  1 file changed, 159 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> new file mode 100644
> index 000000000000..7e0795cc3f70
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/imx/fsl,mxc-epdc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Freescale i.MX6 EPDC
> +
> +maintainers:
> +  - Andreas Kemnade <andreas@kemnade.info>
> +
> +description: |
> +  The EPDC is a controller for handling electronic paper displays found in
> +  i.MX6 SoCs.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - fsl,imx6sl-epdc
> +      - fsl,imx6sll-epdc
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: Bus clock
> +      - description: Pixel clock
> +
> +  clock-names:
> +    items:
> +      - const: axi
> +      - const: pix
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  vscan-holdoff:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1

Except what Rob already said, all these are not arrays, so maxItems is
not appropriate. You can define minimum/maximum values instead.

> +
> +  sdoed-width:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoed-delay:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoez-width:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  sdoez-delay:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdclk-hp-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdsp-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdoe-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  gdclk-offs:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  num-ce:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    maxItems: 1
> +
> +  timing:
> +    $ref: /display/panel/panel-timing.yaml#
> +
> +  DISPLAY-supply:
> +    description:
> +      A couple of +/- voltages automatically powered on in a defintive order

Typo, definitive?

> +
> +  VCOM-supply:
> +    description: compensation voltage
> +
> +  V3P3-supply:

All of supplies names - lowercase.

> +    description: V3P3 supply
> +
> +  epd-thermal-zone:
> +    description:
> +      Zone to get temperature of the EPD from, practically ambient temperature.

Is it a phandle?

> +
> +
> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +  - interrupts
> +  - vscan-holdoff
> +  - sdoed-width
> +  - sdoed-delay
> +  - sdoez-width
> +  - sdoez-delay
> +  - gdclk-hp-offs
> +  - gdsp-offs
> +  - gdoe-offs
> +  - gdclk-offs
> +  - num-ce
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/imx6sl-clock.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    epdc: epdc@20f4000 {

Generic node name, e.g. display-controller

> +        compatible = "fsl,imx6sl-epdc";
> +        reg = <0x020f4000 0x4000>;
> +        interrupts = <0 97 IRQ_TYPE_LEVEL_HIGH>;

s/0/GIC_SPI/

> +        clocks = <&clks IMX6SL_CLK_EPDC_AXI>, <&clks IMX6SL_CLK_EPDC_PIX>;
> +        clock-names = "axi", "pix";
> +


Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-17  9:21   ` Krzysztof Kozlowski
@ 2022-02-17 11:31     ` Andreas Kemnade
  2022-02-17 11:43       ` Krzysztof Kozlowski
  0 siblings, 1 reply; 18+ messages in thread
From: Andreas Kemnade @ 2022-02-17 11:31 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

On Thu, 17 Feb 2022 10:21:15 +0100
Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com> wrote:

> On 06/02/2022 09:00, Andreas Kemnade wrote:
> > Add a binding for the Electrophoretic Display Controller found at least
> > in the i.MX6.
> > The timing subnode is directly here to avoid having display parameters
> > spread all over the plate.
> > 
> > Supplies are organized the same way as in the fbdev driver in the
> > NXP/Freescale kernel forks. The regulators used for that purpose,
> > like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> > start a bunch of regulators of higher or negative voltage with a
> > well-defined timing. VCOM can be handled separately, but can also be
> > incorporated into that single bit.
> > 
> > Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> > ---
> >  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
> >  1 file changed, 159 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > 
[..]

> > +
> > +  DISPLAY-supply:
> > +    description:
> > +      A couple of +/- voltages automatically powered on in a defintive order  
> 
> Typo, definitive?
> 
yes, of course.

> > +
> > +  VCOM-supply:
> > +    description: compensation voltage
> > +
> > +  V3P3-supply:  
> 
> All of supplies names - lowercase.
> 
> > +    description: V3P3 supply
> > +
> > +  epd-thermal-zone:
> > +    description:
> > +      Zone to get temperature of the EPD from, practically ambient temperature.  
> 
> Is it a phandle?
> 
a string used in
       of_property_read_string(priv->drm.dev->of_node,
                                "epd-thermal-zone", &thermal);
        if (thermal) {
                priv->thermal = thermal_zone_get_zone_by_name(thermal);
                if (IS_ERR(priv->thermal))
                        return dev_err_probe(priv->drm.dev, PTR_ERR(priv->thermal),
                                             "unable to get thermal");
        }

[...]
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/imx6sl-clock.h>
> > +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> > +
> > +    epdc: epdc@20f4000 {  
> 
> Generic node name, e.g. display-controller
> 
hmm, does IHMO not make too much sense here. E.g. in the imx6sll.dtsi
we have lcd-controller next to it. So having epd-controller? But that
is exactly what epdc stands for.

Regards,
Andreas

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-17 11:31     ` Andreas Kemnade
@ 2022-02-17 11:43       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2022-02-17 11:43 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

On 17/02/2022 12:31, Andreas Kemnade wrote:
> On Thu, 17 Feb 2022 10:21:15 +0100
> Krzysztof Kozlowski <krzysztof.kozlowski@canonical.com> wrote:
> 
>> On 06/02/2022 09:00, Andreas Kemnade wrote:
>>> Add a binding for the Electrophoretic Display Controller found at least
>>> in the i.MX6.
>>> The timing subnode is directly here to avoid having display parameters
>>> spread all over the plate.
>>>
>>> Supplies are organized the same way as in the fbdev driver in the
>>> NXP/Freescale kernel forks. The regulators used for that purpose,
>>> like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
>>> start a bunch of regulators of higher or negative voltage with a
>>> well-defined timing. VCOM can be handled separately, but can also be
>>> incorporated into that single bit.
>>>
>>> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
>>> ---
>>>  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
>>>  1 file changed, 159 insertions(+)
>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
>>>
> [..]
> 
>>> +
>>> +  DISPLAY-supply:
>>> +    description:
>>> +      A couple of +/- voltages automatically powered on in a defintive order  
>>
>> Typo, definitive?
>>
> yes, of course.
> 
>>> +
>>> +  VCOM-supply:
>>> +    description: compensation voltage
>>> +
>>> +  V3P3-supply:  
>>
>> All of supplies names - lowercase.
>>
>>> +    description: V3P3 supply
>>> +
>>> +  epd-thermal-zone:
>>> +    description:
>>> +      Zone to get temperature of the EPD from, practically ambient temperature.  
>>
>> Is it a phandle?
>>
> a string used in
>        of_property_read_string(priv->drm.dev->of_node,
>                                 "epd-thermal-zone", &thermal);
>         if (thermal) {
>                 priv->thermal = thermal_zone_get_zone_by_name(thermal);
>                 if (IS_ERR(priv->thermal))
>                         return dev_err_probe(priv->drm.dev, PTR_ERR(priv->thermal),
>                                              "unable to get thermal");
>         }

OK, then:
$ref: /schemas/types.yaml#/definitions/string

> 
> [...]
>>> +examples:
>>> +  - |
>>> +    #include <dt-bindings/clock/imx6sl-clock.h>
>>> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
>>> +
>>> +    epdc: epdc@20f4000 {  
>>
>> Generic node name, e.g. display-controller
>>
> hmm, does IHMO not make too much sense here. E.g. in the imx6sll.dtsi
> we have lcd-controller next to it. So having epd-controller? But that
> is exactly what epdc stands for.

Still we have "lcd-controller", not "lcdc". Since this is only for epd,
then "epd-controller" seems reasonable. The same as we use
"interrupt-controller" (not "ic"), "dma-controller" (not "dmac" or "dc")
and so on. See also list of recommended generic names from DT specification.

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
  2022-02-11 15:46   ` Rob Herring
  2022-02-17  9:21   ` Krzysztof Kozlowski
@ 2022-03-12 19:23   ` Jonathan Neuschäfer
  2022-03-14 22:04     ` Andreas Kemnade
  2 siblings, 1 reply; 18+ messages in thread
From: Jonathan Neuschäfer @ 2022-03-12 19:23 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

[-- Attachment #1: Type: text/plain, Size: 2770 bytes --]

Hello Andreas,

Sorry for the delay, I finally got around to having a look at the
patchset.

Some comments from skimming the patches below, and in my other replies.


On Sun, Feb 06, 2022 at 09:00:11AM +0100, Andreas Kemnade wrote:
> Add a binding for the Electrophoretic Display Controller found at least
> in the i.MX6.
> The timing subnode is directly here to avoid having display parameters
> spread all over the plate.
> 
> Supplies are organized the same way as in the fbdev driver in the
> NXP/Freescale kernel forks. The regulators used for that purpose,
> like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> start a bunch of regulators of higher or negative voltage with a
> well-defined timing. VCOM can be handled separately, but can also be
> incorporated into that single bit.
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
>  1 file changed, 159 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> new file mode 100644
> index 000000000000..7e0795cc3f70
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> @@ -0,0 +1,159 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
[...]
> +  - vscan-holdoff
> +  - sdoed-width
> +  - sdoed-delay
> +  - sdoez-width
> +  - sdoez-delay
> +  - gdclk-hp-offs
> +  - gdsp-offs
> +  - gdoe-offs
> +  - gdclk-offs
> +  - num-ce

These parameters should perhaps have sane defaults in the driver, and be
optional in the DT.


> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/imx6sl-clock.h>
> +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> +
> +    epdc: epdc@20f4000 {
[...]
> +
> +        timing {
> +                clock-frequency = <80000000>;
> +                hactive = <1448>;
> +                hback-porch = <16>;
> +                hfront-porch = <102>;
> +                hsync-len = <28>;
> +                vactive = <1072>;
> +                vback-porch = <4>;
> +                vfront-porch = <4>;
> +                vsync-len = <2>;
> +        };
> +    };

The way you did it here, the timing parameters are directly under the
EPDC node in the DT, but I wonder if it would be better to have a
separate node for the display panel, which can then provide the timing
parameters either in the DT or in the panel driver (selected by compatible
string of the panel).


Jonathan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 2/6] drm: Add skeleton for EPDC driver
  2022-02-06  8:00 ` [RFC PATCH 2/6] drm: Add skeleton for EPDC driver Andreas Kemnade
@ 2022-03-12 19:41   ` Jonathan Neuschäfer
  0 siblings, 0 replies; 18+ messages in thread
From: Jonathan Neuschäfer @ 2022-03-12 19:41 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

[-- Attachment #1: Type: text/plain, Size: 2066 bytes --]

On Sun, Feb 06, 2022 at 09:00:12AM +0100, Andreas Kemnade wrote:
> This driver is for the EPD controller in the i.MX SoCs. Add a skeleton
> and basic things for the driver
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
>  drivers/gpu/drm/Kconfig                 |   2 +
>  drivers/gpu/drm/Makefile                |   1 +
>  drivers/gpu/drm/mxc-epdc/Kconfig        |  15 +
>  drivers/gpu/drm/mxc-epdc/Makefile       |   5 +
>  drivers/gpu/drm/mxc-epdc/epdc_regs.h    | 442 ++++++++++++++++++++++++
>  drivers/gpu/drm/mxc-epdc/mxc_epdc.h     |  20 ++
>  drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c | 248 +++++++++++++
>  7 files changed, 733 insertions(+)
>  create mode 100644 drivers/gpu/drm/mxc-epdc/Kconfig
>  create mode 100644 drivers/gpu/drm/mxc-epdc/Makefile
>  create mode 100644 drivers/gpu/drm/mxc-epdc/epdc_regs.h
>  create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc.h
>  create mode 100644 drivers/gpu/drm/mxc-epdc/mxc_epdc_drv.c
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index b1f22e457fd0..6b6b44ff7556 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -390,6 +390,8 @@ source "drivers/gpu/drm/gud/Kconfig"
>  
>  source "drivers/gpu/drm/sprd/Kconfig"
>  
> +source "drivers/gpu/drm/mxc-epdc/Kconfig"

I'd put it under gpu/drm/imx/epdc, perhaps.

> +int mxc_epdc_output(struct drm_device *drm)
> +{
> +	struct mxc_epdc *priv = to_mxc_epdc(drm);
> +	int ret;
> +
> +	priv->connector.dpms = DRM_MODE_DPMS_OFF;
> +	priv->connector.polled = 0;
> +	drm_connector_helper_add(&priv->connector,
> +				 &mxc_epdc_connector_helper_funcs);
> +	ret = drm_connector_init(drm, &priv->connector,
> +				 &mxc_epdc_connector_funcs,
> +				 DRM_MODE_CONNECTOR_Unknown);
> +	if (ret)
> +		return ret;
> +	ret = of_get_display_timing(drm->dev->of_node, "timing", &priv->timing);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

Possible to simplify to:

	return of_get_display_timing(drm->dev->of_node, "timing", &priv->timing);



Jonathan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation
  2022-02-06  8:00 ` [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation Andreas Kemnade
@ 2022-03-12 20:12   ` Jonathan Neuschäfer
  0 siblings, 0 replies; 18+ messages in thread
From: Jonathan Neuschäfer @ 2022-03-12 20:12 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

[-- Attachment #1: Type: text/plain, Size: 4899 bytes --]

On Sun, Feb 06, 2022 at 09:00:13AM +0100, Andreas Kemnade wrote:
> Adds display parameter initialisation, display power up/down and
> waveform loading
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---
[...]
> +	/* Enable the v3p3 regulator */
> +	ret = regulator_enable(priv->v3p3_regulator);
> +	if (IS_ERR((void *)ret)) {

	if (ret < 0)   is common enough to be understood.

> +		dev_err(priv->drm.dev,
> +			"Unable to enable V3P3 regulator. err = 0x%x\n",
> +			ret);
> +		mutex_unlock(&priv->power_mutex);
> +		return;
> +	}
> +
> +	usleep_range(1000, 2000);
> +
> +	pm_runtime_get_sync(priv->drm.dev);
> +
> +	/* Enable clocks to EPDC */
> +	clk_prepare_enable(priv->epdc_clk_axi);
> +	clk_prepare_enable(priv->epdc_clk_pix);
> +
> +	epdc_write(priv, EPDC_CTRL_CLEAR, EPDC_CTRL_CLKGATE);
> +
> +	/* Enable power to the EPD panel */
> +	ret = regulator_enable(priv->display_regulator);
> +	if (IS_ERR((void *)ret)) {

dito

> +		dev_err(priv->drm.dev,
> +			"Unable to enable DISPLAY regulator. err = 0x%x\n",
> +			ret);
> +		mutex_unlock(&priv->power_mutex);
> +		return;
> +	}
> +
> +	ret = regulator_enable(priv->vcom_regulator);
> +	if (IS_ERR((void *)ret)) {

dito

> +		dev_err(priv->drm.dev,
> +			"Unable to enable VCOM regulator. err = 0x%x\n",
> +			ret);
> +		mutex_unlock(&priv->power_mutex);
> +		return;
> +	}
> +
> +	priv->powered = true;
> +
> +	mutex_unlock(&priv->power_mutex);
> +}

[...]
> +	priv->rev = ((val & EPDC_VERSION_MAJOR_MASK) >>
> +				EPDC_VERSION_MAJOR_OFFSET) * 10
> +			+ ((val & EPDC_VERSION_MINOR_MASK) >>
> +				EPDC_VERSION_MINOR_OFFSET);

Instead of this transformation it might be (1) safer against unexpected
versions and (2) simpler, to store the EPDC_VERSION register content
directly.

Instead of

	if (priv->rev == 20) { ... }

we'd have

	if (priv->rev == 0x02000000) { ... }

or perhaps something along the lines of

	if (priv->rev == EPDC_REV(2, 0, 0)) { ... }

(using a macro that does the proper bitshifts).

> +	dev_dbg(priv->drm.dev, "EPDC version = %d\n", priv->rev);
> +
> +	if (priv->rev <= 20) {
> +		dev_err(priv->drm.dev, "Unsupported version (%d)\n", priv->rev);
> +		return -ENODEV;
> +	}
> +
> +	/* Initialize EPDC pins */
> +	pinctrl = devm_pinctrl_get_select_default(priv->drm.dev);
> +	if (IS_ERR(pinctrl)) {
> +		dev_err(priv->drm.dev, "can't get/select pinctrl\n");
> +		return PTR_ERR(pinctrl);
> +	}
> +
> +	mutex_init(&priv->power_mutex);
> +
> +	return 0;
> +}

[...]
> diff --git a/drivers/gpu/drm/mxc-epdc/epdc_waveform.h b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
> new file mode 100644
> index 000000000000..c5c461b975cb
> --- /dev/null
> +++ b/drivers/gpu/drm/mxc-epdc/epdc_waveform.h
> @@ -0,0 +1,7 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/* Copyright (C) 2022 Andreas Kemnade */
> +int mxc_epdc_fb_get_temp_index(struct mxc_epdc *priv, int temp);
> +int mxc_epdc_prepare_waveform(struct mxc_epdc *priv,
> +			      const u8 *waveform, size_t size);
> +void mxc_epdc_set_update_waveform(struct mxc_epdc *priv,
> +				  struct mxcfb_waveform_modes *wv_modes);
> diff --git a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
> index c5f5280b574f..f7b1cbc4cc4e 100644
> --- a/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
> +++ b/drivers/gpu/drm/mxc-epdc/mxc_epdc.h
> @@ -8,6 +8,32 @@
>  #include <drm/drm_drv.h>
>  #include <drm/drm_connector.h>
>  #include <drm/drm_simple_kms_helper.h>
> +#include <linux/thermal.h>
> +#include "epdc_regs.h"
> +
> +#define TEMP_USE_AMBIENT			0x1000

What's the significance of 0x1000 here? Is it a register value?


>  static void mxc_epdc_pipe_update(struct drm_simple_display_pipe *pipe,
> @@ -187,6 +267,7 @@ static struct drm_driver mxc_epdc_driver = {
>  static int mxc_epdc_probe(struct platform_device *pdev)
>  {
>  	struct mxc_epdc *priv;
> +	const struct firmware *firmware;
>  	int ret;
>  
>  	priv = devm_drm_dev_alloc(&pdev->dev, &mxc_epdc_driver, struct mxc_epdc, drm);
> @@ -195,6 +276,19 @@ static int mxc_epdc_probe(struct platform_device *pdev)
>  
>  	platform_set_drvdata(pdev, priv);
>  
> +	ret = mxc_epdc_init_hw(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = request_firmware(&firmware, "imx/epdc/epdc.fw", priv->drm.dev);

Thinking ahead to the point when we'll have multiple waveforms for
different modes...  What's your idea for a naming scheme to distinguish
the different waveform files, and should the default name be epdc.fw, or
perhaps something more specific?

> +	if (ret)
> +		return ret;
> +
> +	ret = mxc_epdc_prepare_waveform(priv, firmware->data, firmware->size);
> +	release_firmware(firmware);
> +	if (ret)
> +		return ret;
> +
>  	mxc_epdc_setup_mode_config(&priv->drm);
>  
>  	ret = mxc_epdc_output(&priv->drm);


Jonathan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 4/6] drm: mxc-epdc: Add update management
  2022-02-06  8:00 ` [RFC PATCH 4/6] drm: mxc-epdc: Add update management Andreas Kemnade
@ 2022-03-12 20:21   ` Jonathan Neuschäfer
  0 siblings, 0 replies; 18+ messages in thread
From: Jonathan Neuschäfer @ 2022-03-12 20:21 UTC (permalink / raw)
  To: Andreas Kemnade
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

[-- Attachment #1: Type: text/plain, Size: 2120 bytes --]

On Sun, Feb 06, 2022 at 09:00:14AM +0100, Andreas Kemnade wrote:
> The EPDC can process some dirty rectangles at a time, pick them up and
> forward them to the controller. Only processes not involving PXP are
> supported at the moment. Due to that and to work with more waveforms,
> there is some masking/shifting done. It was tested with the factory
> waveforms of Kobo Clara HD, Tolino Shine 3, and Tolino Shine 2HD.
> Also the waveform called epdc_E060SCM.fw from NXP BSP works with the
> i.MX6SL devices.
> 
> Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> ---

[...]
> +	adj_update_region = upd_data_list->update_desc->upd_data.update_region;
> +	/*
> +	 * Is the working buffer idle?
> +	 * If the working buffer is busy, we must wait for the resource
> +	 * to become free. The IST will signal this event.

What does IST mean?


> +void mxc_epdc_draw_mode0(struct mxc_epdc *priv)

What does mode 0 imply? An overview of the possible modes would be
appreciated.

> +{
> +	u32 *upd_buf_ptr;
> +	int i;
> +	u32 xres, yres;
> +
> +	upd_buf_ptr = (u32 *)priv->epdc_mem_virt;
> +
> +	epdc_working_buf_intr(priv, true);
> +	epdc_lut_complete_intr(priv, 0, true);
> +
> +	/* Use unrotated (native) width/height */
> +	xres = priv->epdc_mem_width;
> +	yres = priv->epdc_mem_height;
> +
> +	/* Program EPDC update to process buffer */
> +	epdc_set_update_area(priv, priv->epdc_mem_phys, 0, 0, xres, yres, 0);
> +	epdc_submit_update(priv, 0, priv->wv_modes.mode_init, UPDATE_MODE_FULL,
> +		false, true, 0xFF);
> +
> +	dev_dbg(priv->drm.dev, "Mode0 update - Waiting for LUT to complete...\n");
> +
> +	/* Will timeout after ~4-5 seconds */
> +
> +	for (i = 0; i < 40; i++) {
> +		if (!epdc_is_lut_active(priv, 0)) {
> +			dev_dbg(priv->drm.dev, "Mode0 init complete\n");
> +			return;
> +		}
> +		msleep(100);
> +	}
> +
> +	dev_err(priv->drm.dev, "Mode0 init failed!\n");
> +}

> +#define WAVEFORM_MODE_GLR16			4
> +#define WAVEFORM_MODE_GLD16			5
> +#define WAVEFORM_MODE_AUTO			257

(How) are these mode numbers related to "mode 0"?


Jonathan

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC
  2022-03-12 19:23   ` Jonathan Neuschäfer
@ 2022-03-14 22:04     ` Andreas Kemnade
  0 siblings, 0 replies; 18+ messages in thread
From: Andreas Kemnade @ 2022-03-14 22:04 UTC (permalink / raw)
  To: Jonathan Neuschäfer
  Cc: p.zabel, airlied, daniel, robh+dt, shawnguo, s.hauer, kernel,
	festevam, linux-imx, maarten.lankhorst, mripard, tzimmermann,
	dri-devel, devicetree, linux-arm-kernel, linux-kernel, alistair,
	samuel, josua.mayer, letux-kernel

On Sat, 12 Mar 2022 20:23:48 +0100
Jonathan Neuschäfer <j.neuschaefer@gmx.net> wrote:

> Hello Andreas,
> 
> Sorry for the delay, I finally got around to having a look at the
> patchset.
> 
> Some comments from skimming the patches below, and in my other replies.
> 
> 
> On Sun, Feb 06, 2022 at 09:00:11AM +0100, Andreas Kemnade wrote:
> > Add a binding for the Electrophoretic Display Controller found at least
> > in the i.MX6.
> > The timing subnode is directly here to avoid having display parameters
> > spread all over the plate.
> > 
> > Supplies are organized the same way as in the fbdev driver in the
> > NXP/Freescale kernel forks. The regulators used for that purpose,
> > like the TPS65185, the SY7636A and MAX17135 have typically a single bit to
> > start a bunch of regulators of higher or negative voltage with a
> > well-defined timing. VCOM can be handled separately, but can also be
> > incorporated into that single bit.
> > 
> > Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
> > ---
> >  .../bindings/display/imx/fsl,mxc-epdc.yaml    | 159 ++++++++++++++++++
> >  1 file changed, 159 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > new file mode 100644
> > index 000000000000..7e0795cc3f70
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/imx/fsl,mxc-epdc.yaml
> > @@ -0,0 +1,159 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---  
> [...]
> > +  - vscan-holdoff
> > +  - sdoed-width
> > +  - sdoed-delay
> > +  - sdoez-width
> > +  - sdoez-delay
> > +  - gdclk-hp-offs
> > +  - gdsp-offs
> > +  - gdoe-offs
> > +  - gdclk-offs
> > +  - num-ce  
> 
> These parameters should perhaps have sane defaults in the driver, and be
> optional in the DT.
> 
First of all I think I should document them better (as said in an
earlier review mail)

I doubt there are sane defaults, in vendor kernels, there is typically a
definition of these parameters and a video mode per display.

> 
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/clock/imx6sl-clock.h>
> > +    #include <dt-bindings/interrupt-controller/arm-gic.h>
> > +
> > +    epdc: epdc@20f4000 {  
> [...]
> > +
> > +        timing {
> > +                clock-frequency = <80000000>;
> > +                hactive = <1448>;
> > +                hback-porch = <16>;
> > +                hfront-porch = <102>;
> > +                hsync-len = <28>;
> > +                vactive = <1072>;
> > +                vback-porch = <4>;
> > +                vfront-porch = <4>;
> > +                vsync-len = <2>;
> > +        };
> > +    };  
> 
> The way you did it here, the timing parameters are directly under the
> EPDC node in the DT, but I wonder if it would be better to have a
> separate node for the display panel, which can then provide the timing
> parameters either in the DT or in the panel driver (selected by compatible
> string of the panel).
> 
IMHO it makes sense to store these timing parameters together with the
timing parameters from above. If that all somehow comes from a panel
driver, we need to design an interface for it. So for simplicity I
added the stuff just to the EPDC node.

Vendor kernel has this:
struct imx_epdc_fb_mode {
    struct fb_videomode *vmode;
    int vscan_holdoff;
    int sdoed_width;
    int sdoed_delay;
    int sdoez_width;
    int sdoez_delay;
    int gdclk_hp_offs;
    int gdsp_offs;
    int gdoe_offs;
    int gdclk_offs;
    int num_ce;
};

So things are basically combined here.

Regards,
Andreas

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2022-03-14 22:05 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-02-06  8:00 [RFC PATCH 0/6] drm: EPDC driver for i.MX6 Andreas Kemnade
2022-02-06  8:00 ` [RFC PATCH 1/6] dt-bindings: display: imx: Add EPDC Andreas Kemnade
2022-02-11 15:46   ` Rob Herring
2022-02-14 22:45     ` Andreas Kemnade
2022-02-16 23:52       ` Rob Herring
2022-02-17  9:21   ` Krzysztof Kozlowski
2022-02-17 11:31     ` Andreas Kemnade
2022-02-17 11:43       ` Krzysztof Kozlowski
2022-03-12 19:23   ` Jonathan Neuschäfer
2022-03-14 22:04     ` Andreas Kemnade
2022-02-06  8:00 ` [RFC PATCH 2/6] drm: Add skeleton for EPDC driver Andreas Kemnade
2022-03-12 19:41   ` Jonathan Neuschäfer
2022-02-06  8:00 ` [RFC PATCH 3/6] drm: mxc-epdc: Add display and waveform initialisation Andreas Kemnade
2022-03-12 20:12   ` Jonathan Neuschäfer
2022-02-06  8:00 ` [RFC PATCH 4/6] drm: mxc-epdc: Add update management Andreas Kemnade
2022-03-12 20:21   ` Jonathan Neuschäfer
2022-02-06  8:00 ` [RFC PATCH 5/6] ARM: dts: imx6sll: add EPDC Andreas Kemnade
2022-02-06  8:00 ` [RFC PATCH 6/6] arm: dts: imx6sl: Add EPDC Andreas Kemnade

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).