linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support
@ 2024-12-09  3:39 Liu Ying
  2024-12-09  3:39 ` [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units Liu Ying
                   ` (18 more replies)
  0 siblings, 19 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Hi,

This patch series aims to add Freescale i.MX8qxp Display Controller support.

The controller is comprised of three main components that include a blit
engine for 2D graphics accelerations, display controller for display output
processing, as well as a command sequencer.

Previous patch series attempts to do that can be found at:
https://patchwork.freedesktop.org/series/84524/

This series addresses Maxime's comments on the previous one:
a. Split the display controller into multiple internal devices.
   1) List display engine, pixel engine, interrupt controller and more as the
      controller's child devices.
   2) List display engine and pixel engine's processing units as their child
      devices.

b. Add minimal feature support.
   Only support two display pipelines with primary planes with XR24 fb,
   backed by two fetchunits.  No fetchunit dynamic allocation logic(to be done
   when necessary).

c. Use drm_dev_{enter, exit}().

Since this series changes a lot comparing to the previous one, I choose to
send it with a new patch series, not a new version.

To follow up i.MX8qxp TRM, I changed the controller name to "Display Controller"
instead of the previous "DPU".  "DPU" is only mentioned in the SoC block
diagram and represents the whole display subsystem which includes the display
controller and prefech engines, etc.

With an additional patch[1] for simple-pm-bus.c, this series facilitates
testing a LVDS panel on i.MX8qxp MEK.

Please do NOT merge patch 14-19.  They are only used to facilitate testing
the LVDS panel.

[1] https://lkml.org/lkml/2023/1/25/120

v6:
* Fix build warning by expanding sizeof(fu->name) from 13 to 21 in patch 10.
  (kernel test robot)

v5:
* Document display controller device's and some display controller internal
  devices' aliases in DT bindings. Hence, drop collected R-b tags from some
  patches for DT bindings. (Maxime)
* Replace .remove_new with .remove in all drivers. (Uwe)
* Select REGMAP and REGMAP_MMIO options in patch 9.
* Fix patch 9 & 10's commit message to state that display engine driver and
  pixel engine driver are component drivers instead of master/aggregate drivers.

v4:
* Collect Rob's R-b tags on DT binding patches(patch 1-5).
* Replace "fsl,iram" property with standard "sram" property in
  fsl,imx8qxp-dc-command-sequencer.yaml in patch 6. (Rob)
* Use regmap to define register map for all registers. (Dmitry)
* Use regmap APIs to access registers. (Dmitry)
* Inline some small functions. (Dmitry)
* Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
  function calls from KMS routine to initialization stage. (Dmitry)
* Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
  appropriate .h header files or .c source files. (Dmitry)
* Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
* Drop dc_drm->pe_rpm_count. (Dmitry)
* Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
* Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
  Instead, put it in struct dc_crtc.  (Dmitry)
* Call devm_request_irq() to request IRQs, instead of using drmm action.
  (Dmitry)
* Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
* Select DRM_CLIENT_SELECTION due to rebase.
* Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
* Use devm_kzalloc() to drmm_kzalloc() to allocate dc_* data strutures.
* Drop unnecessary private struct dc_*_priv from DC internal device drivers.
* Set suppress_bind_attrs driver flag to true in DC internal device drivers
  to avoid unnecessary sys interfaces to bind/unbind the drivers.
* Make some fetch unit operations be aware of fractional fetch unit index(0-7).
* Take DC interrupt controller driver as a standalone driver instead of a
  component driver.
* Replace drmm_kcalloc() with devm_kcalloc() to allocate an array for
  struct dc_ic_entry.
* Call platform_get_irq() from DC interrupt controller driver to make sure
  parent interrupt controller driver is probed first.
* Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
* Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
  rebase.
* Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
  register dc_drm_component_unbind_all() action.
* Request interrupts in dc_crtc_post_init() after encoder initialization to
  make sure next bridge is found first.
* Trivial tweaks.

v3:
* Collect Rob's R-b tag on the patch for adding fsl,imx8qxp-dc-intc.yaml.
* Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
  fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
  into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
* Document all processing units, command sequencer, axi performance counter
  and blit engine. (Rob)

v2:
* Drop fsl,dc-*-id DT properties from fsl,imx8qxp-dc*.yaml. (Krzysztof)
* Move port property from fsl,imx8qxp-dc-display-engine.yaml to
  fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
* Drop unneeded "|" from fsl,imx8qxp-dc-intc.yaml. (Krzysztof)
* Use generic pmu pattern property in fsl,imx8qxp-dc.yaml. (Krzysztof)
* Fix register range size in fsl,imx8qxp-dc*.yaml.
* Use OF alias id to get instance id from display driver.
* Find next bridge from TCon's port from display driver.
* Drop drm/drm_module.h include from dc-drv.c.
* Improve file list in MAINTAINERS. (Frank)
* Add entire i.MX8qxp display controller device tree for review. (Krzysztof)
* Add MIPI/LVDS subsystems device tree and a DT overlay for imx8qxp
  MEK to test a LVDS panel as an example. (Francesco)

Liu Ying (19):
  dt-bindings: display: imx: Add i.MX8qxp Display Controller processing
    units
  dt-bindings: display: imx: Add i.MX8qxp Display Controller blit engine
  dt-bindings: display: imx: Add i.MX8qxp Display Controller display
    engine
  dt-bindings: display: imx: Add i.MX8qxp Display Controller pixel
    engine
  dt-bindings: display: imx: Add i.MX8qxp Display Controller AXI
    performance counter
  dt-bindings: display: imx: Add i.MX8qxp Display Controller command
    sequencer
  dt-bindings: interrupt-controller: Add i.MX8qxp Display Controller
    interrupt controller
  dt-bindings: display: imx: Add i.MX8qxp Display Controller
  drm/imx: Add i.MX8qxp Display Controller display engine
  drm/imx: Add i.MX8qxp Display Controller pixel engine
  drm/imx: Add i.MX8qxp Display Controller interrupt controller
  drm/imx: Add i.MX8qxp Display Controller KMS
  MAINTAINERS: Add maintainer for i.MX8qxp Display Controller
  dt-bindings: phy: mixel, mipi-dsi-phy: Allow assigned-clock*
    properties
  dt-bindings: firmware: imx: Add SCU controlled display pixel link
    nodes
  arm64: dts: imx8qxp: Add display controller subsystem
  arm64: dts: imx8qxp: Add MIPI-LVDS combo subsystems
  arm64: dts: imx8qxp-mek: Enable display controller
  arm64: dts: imx8qxp-mek: Add MX8-DLVDS-LCD1 display module support

 ...sl,imx8qxp-dc-axi-performance-counter.yaml |  57 ++
 .../imx/fsl,imx8qxp-dc-blit-engine.yaml       | 204 +++++++
 .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++
 .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++
 .../imx/fsl,imx8qxp-dc-command-sequencer.yaml |  67 +++
 .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++
 .../imx/fsl,imx8qxp-dc-display-engine.yaml    | 157 +++++
 .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++
 .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++
 .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 +++++
 .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++
 .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 +++
 .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 ++
 .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++
 .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++
 .../imx/fsl,imx8qxp-dc-pixel-engine.yaml      | 250 ++++++++
 .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++
 .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++
 .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++
 .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 ++
 .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++
 .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++
 .../bindings/display/imx/fsl,imx8qxp-dc.yaml  | 240 ++++++++
 .../devicetree/bindings/firmware/fsl,scu.yaml |  20 +
 .../fsl,imx8qxp-dc-intc.yaml                  | 318 ++++++++++
 .../bindings/phy/mixel,mipi-dsi-phy.yaml      |   5 -
 MAINTAINERS                                   |   8 +
 arch/arm64/boot/dts/freescale/Makefile        |   4 +
 .../arm64/boot/dts/freescale/imx8-ss-dc0.dtsi | 408 +++++++++++++
 .../imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso | 183 ++++++
 arch/arm64/boot/dts/freescale/imx8qxp-mek.dts |  34 ++
 .../boot/dts/freescale/imx8qxp-ss-dc.dtsi     | 240 ++++++++
 .../dts/freescale/imx8qxp-ss-mipi-lvds.dtsi   | 437 ++++++++++++++
 arch/arm64/boot/dts/freescale/imx8qxp.dtsi    |  28 +-
 drivers/gpu/drm/imx/Kconfig                   |   1 +
 drivers/gpu/drm/imx/Makefile                  |   1 +
 drivers/gpu/drm/imx/dc/Kconfig                |  13 +
 drivers/gpu/drm/imx/dc/Makefile               |   7 +
 drivers/gpu/drm/imx/dc/dc-cf.c                | 162 +++++
 drivers/gpu/drm/imx/dc/dc-crtc.c              | 558 ++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-de.c                | 153 +++++
 drivers/gpu/drm/imx/dc/dc-de.h                |  60 ++
 drivers/gpu/drm/imx/dc/dc-drv.c               | 283 +++++++++
 drivers/gpu/drm/imx/dc/dc-drv.h               |  52 ++
 drivers/gpu/drm/imx/dc/dc-ed.c                | 270 +++++++++
 drivers/gpu/drm/imx/dc/dc-fg.c                | 374 ++++++++++++
 drivers/gpu/drm/imx/dc/dc-fl.c                | 180 ++++++
 drivers/gpu/drm/imx/dc/dc-fu.c                | 268 +++++++++
 drivers/gpu/drm/imx/dc/dc-fu.h                | 129 ++++
 drivers/gpu/drm/imx/dc/dc-fw.c                | 219 +++++++
 drivers/gpu/drm/imx/dc/dc-ic.c                | 279 +++++++++
 drivers/gpu/drm/imx/dc/dc-kms.c               | 143 +++++
 drivers/gpu/drm/imx/dc/dc-kms.h               |  58 ++
 drivers/gpu/drm/imx/dc/dc-lb.c                | 319 ++++++++++
 drivers/gpu/drm/imx/dc/dc-pe.c                | 133 +++++
 drivers/gpu/drm/imx/dc/dc-pe.h                | 100 ++++
 drivers/gpu/drm/imx/dc/dc-plane.c             | 241 ++++++++
 drivers/gpu/drm/imx/dc/dc-tc.c                | 138 +++++
 58 files changed, 7837 insertions(+), 6 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blit-engine.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-display-engine.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc.yaml
 create mode 100644 Documentation/devicetree/bindings/interrupt-controller/fsl,imx8qxp-dc-intc.yaml
 create mode 100644 arch/arm64/boot/dts/freescale/imx8-ss-dc0.dtsi
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-ss-mipi-lvds.dtsi
 create mode 100644 drivers/gpu/drm/imx/dc/Kconfig
 create mode 100644 drivers/gpu/drm/imx/dc/Makefile
 create mode 100644 drivers/gpu/drm/imx/dc/dc-cf.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-ed.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fg.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fl.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fu.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fu.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fw.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-ic.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-lb.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-pe.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-pe.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-tc.c

-- 
2.34.1



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

* [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-10 22:15   ` Rob Herring
  2024-12-09  3:39 ` [PATCH v6 02/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller blit engine Liu Ying
                   ` (17 subsequent siblings)
  18 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Freescale i.MX8qxp Display Controller is implemented as construction set of
building blocks with unified concept and standardized interfaces.  Document
all existing processing units.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Document aliases for processing units which have multiple instances in
  the Display Controller.  Drop Rob's previous R-b tag. (Maxime)

v4:
* Collect Rob's R-b tag.

v3:
* Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
  fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
  into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
* Document all processing units. (Rob)

v2:
* Drop fsl,dc-*-id DT properties. (Krzysztof)
* Add port property to fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
* Fix register range sizes in examples.

 .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++++++
 .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++++++
 .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++++++
 .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++++++
 .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++++++++
 .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 ++++++++++++++++++
 .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++++++
 .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 ++++++++
 .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 +++++
 .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++++++
 .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++++++
 .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++++++
 .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++++
 .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++++++++++
 .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 +++++++
 .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++++++++++
 .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++++++
 17 files changed, 1042 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
new file mode 100644
index 000000000000..7f800e72c3f3
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blitblend.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Blit Blend Unit
+
+description: |
+  Combines two input frames to a single output frame, all frames having the
+  same dimension.
+
+  Each Blit Blend Unit device should have an alias in the aliases node, in the
+  form of dc<x>-blitblend<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Blit Blend Unit
+  device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-blitblend
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    blitblend@56180920 {
+        compatible = "fsl,imx8qxp-dc-blitblend";
+        reg = <0x56180920 0x10>, <0x56183c00 0x3c>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
new file mode 100644
index 000000000000..351692d8942e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-clut.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Color Lookup Table
+
+description: |
+  The unit implements 3 look-up tables with 256 x 10 bit entries each. These
+  can be used for different kinds of applications. From 10-bit input values
+  only upper 8 bits are used.
+
+  The unit supports color lookup, index lookup, dithering and alpha masking.
+
+  Each Color Lookup Table device should have an alias in the aliases node, in
+  the form of dc<x>-clut<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Color Lookup Table
+  device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-clut
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    clut@56180880 {
+        compatible = "fsl,imx8qxp-dc-clut";
+        reg = <0x56180880 0x10>, <0x56182400 0x404>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
new file mode 100644
index 000000000000..cae19bc05d6e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-constframe.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Constant Frame
+
+description: |
+  The Constant Frame unit is used instead of a Fetch unit where generation of
+  constant color frames only is sufficient. This is the case for the background
+  planes of content and safety streams in a Display Controller.
+
+  The color can be setup to any RGBA value.
+
+  Each Constant Frame device should have an alias in the aliases node, in the
+  form of dc<x>-constframe<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Constant Frame device
+  instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-constframe
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    constframe@56180960 {
+        compatible = "fsl,imx8qxp-dc-constframe";
+        reg = <0x56180960 0xc>, <0x56184400 0x20>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
new file mode 100644
index 000000000000..cb0b4a05cfdc
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-dither.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Dither Unit
+
+description: |
+  The unit can increase the physical color resolution of a display from 5, 6, 7
+  or 8 bits per RGB channel to a virtual resolution of 10 bits. The physical
+  resolution can be set individually for each channel.
+
+  The resolution is increased by mixing the two physical colors that are nearest
+  to the virtual color code in a variable ratio either by time (temporal
+  dithering) or by position (spatial dithering).
+
+  An optimized algorithm for temporal dithering minimizes noise artifacts on the
+  output image.
+
+  The dither operation can be individually enabled or disabled for each pixel
+  using the alpha input bit.
+
+  Each Dither Unit device should have an alias in the aliases node, in the form
+  of dc<x>-dither<y>, where <x> is an integer specifying the Display Controller
+  instance and <y> is an integer specifying the Dither Unit device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-dither
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    dither@5618c400 {
+        compatible = "fsl,imx8qxp-dc-dither";
+        reg = <0x5618c400 0x14>;
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
new file mode 100644
index 000000000000..b382af1de1c0
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-extdst.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller External Destination Interface
+
+description: |
+  The External Destination unit is the interface between the internal pixel
+  processing pipeline of the Pixel Engine, which is 30-bit RGB plus 8-bit Alpha,
+  and a Display Engine.
+
+  It comprises the following built-in Gamma apply function.
+
+  +------X-----------------------+
+  |      |          ExtDst Unit  |
+  |      V                       |
+  |  +-------+                   |
+  |  | Gamma |                   |
+  |  +-------+                   |
+  |      |                       |
+  |      V                       +
+  +------X-----------------------+
+
+  The output format is 24-bit RGB plus 1-bit Alpha. Conversion from 10 to 8
+  bits is done by LSBit truncation.  Alpha output bit is 1 for input 255, 0
+  otherwise.
+
+  Each External Destination unit device should have an alias in the aliases
+  node, in the form of dc<x>-blitblend<y>, where <x> is an integer specifying
+  the Display Controller instance and <y> is an integer specifying the External
+  Destination unit device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-extdst
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+  interrupts:
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: shdload
+      - const: framecomplete
+      - const: seqcomplete
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    extdst@56180980 {
+        compatible = "fsl,imx8qxp-dc-extdst";
+        reg = <0x56180980 0x1c>, <0x56184800 0x28>;
+        reg-names = "pec", "cfg";
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <3>, <4>, <5>;
+        interrupt-names = "shdload", "framecomplete", "seqcomplete";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
new file mode 100644
index 000000000000..2141e692bc02
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
@@ -0,0 +1,147 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-fetchunit.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Fetch Unit
+
+description: |
+  The Fetch Unit is the interface between the AXI bus for source buffer access
+  and the internal pixel processing pipeline, which is 30-bit RGB plus 8-bit
+  Alpha.
+
+  It is used to generate foreground planes in Display Controllers and source
+  planes in Blit Engines, and comprises the following built-in functions to
+  convert a wide range of frame buffer types.
+
+  +---------X-----------------------------------------+
+  |         |                           Fetch Unit    |
+  |         V                                         |
+  |    +---------+                                    |
+  |    |         |                                    |
+  |    | Decode  | Decompression [Decode]             |
+  |    |         |                                    |
+  |    +---------+                                    |
+  |         |                                         |
+  |         V                                         |
+  |    +---------+                                    |
+  |    | Clip &  | Clip Window [All]                  |
+  |    | Overlay | Plane composition [Layer, Warp]    |
+  |    |         |                                    |
+  |    +---------+                                    |
+  |         |                                         |
+  |         V                                         |
+  |    +---------+                                    |
+  |    | Re-     | Flip/Rotate/Repl./Drop [All]       |
+  X--> | sample  | Perspective/Affine warping [Persp] |
+  | |  |         | Arbitrary warping [Warp, Persp]    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         |                                    |
+  | |  | Palette | Color Palette [Layer, Decode]      |
+  | |  |         |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  | Extract | Raw to RGBA/YUV [All]              |
+  | |  | &       | Bit width expansion [All]          |
+  | |  | Expand  |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         | Planar to packed                   |
+  | |->| Combine | [Decode, Warp, Persp]              |
+  | |  |         |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         | YUV422 to YUV444                   |
+  | |  | Chroma  | [Decode, Persp]                    |
+  | |  |         |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         | YUV to RGB                         |
+  | |  | Color   | [Warp, Persp, Decode, Layer]       |
+  | |  |         |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         | Gamma removal                      |
+  | |  | Gamma   | [Warp, Persp, Decode, Layer]       |
+  | |  |         |                                    |
+  | |  +---------+                                    |
+  | |       |                                         |
+  | |       V                                         |
+  | |  +---------+                                    |
+  | |  |         | Alpla multiply, RGB pre-multiply   |
+  |  ->| Multiply| [Warp, Persp, Decode, Layer]       |
+  |    |         |                                    |
+  |     ---------                                     |
+  |         |                                         |
+  |         V                                         |
+  |    +---------+                                    |
+  |    |         | Bilinear filter                    |
+  |    | Filter  | [Warp, Persp]                      |
+  |    |         |                                    |
+  |    +---------+                                    |
+  |         |                                         |
+  |         V                                         |
+  +---------X-----------------------------------------+
+
+  Note that different derivatives of the Fetch Unit exist. Each implements a
+  specific subset only of the pipeline stages shown above. Restrictions for the
+  units are specified in [square brackets].
+
+  Each Fetch Unit device should have an alias in the aliases node, in the form
+  of dc<x>-fetch<y><z>, where <x> is an integer specifying the Display
+  Controller instance, <y> specifies different derivative names(decode, eco,
+  layer and warp) and <z> is an integer specifying the Fetch Unit device
+  instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    enum:
+      - fsl,imx8qxp-dc-fetchdecode
+      - fsl,imx8qxp-dc-fetcheco
+      - fsl,imx8qxp-dc-fetchlayer
+      - fsl,imx8qxp-dc-fetchwarp
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+  fsl,prg:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Optional Prefetch Resolve Gasket associated with the Fetch Unit.
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    fetchlayer@56180ac0 {
+        compatible = "fsl,imx8qxp-dc-fetchlayer";
+        reg = <0x56180ac0 0xc>, <0x56188400 0x404>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
new file mode 100644
index 000000000000..e1bdcdd893b5
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-filter.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Filter Unit
+
+description: |
+  5x5 FIR filter with 25 programmable coefficients.
+
+  Typical applications are image blurring, sharpening or support for edge
+  detection algorithms.
+
+  Each Filter Unit device should have an alias in the aliases node, in the form
+  of dc<x>-filter<y>, where <x> is an integer specifying the Display Controller
+  instance and <y> is an integer specifying the Filter Unit device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-filter
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    filter@56180900 {
+        compatible = "fsl,imx8qxp-dc-filter";
+        reg = <0x56180900 0x10>, <0x56183800 0x30>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
new file mode 100644
index 000000000000..d784f6e59312
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-framegen.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Frame Generator
+
+description: |
+  The Frame Generator (FrameGen) module generates a programmable video timing
+  and optionally allows to synchronize the generated video timing to external
+  synchronization signals.
+
+  Each FrameGen device should have an alias in the aliases node, in the form of
+  dc<x>-framegen<y>, where <x> is an integer specifying the Display Controller
+  instance and <y> is an integer specifying the FrameGen device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-framegen
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 8
+
+  interrupt-names:
+    items:
+      - const: int0
+      - const: int1
+      - const: int2
+      - const: int3
+      - const: primsync_on
+      - const: primsync_off
+      - const: secsync_on
+      - const: secsync_off
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+    #include <dt-bindings/firmware/imx/rsrc.h>
+
+    framegen@5618b800 {
+        compatible = "fsl,imx8qxp-dc-framegen";
+        reg = <0x5618b800 0x98>;
+        clocks = <&dc0_disp_lpcg IMX_LPCG_CLK_0>;
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <18>, <19>, <20>, <21>, <41>, <42>, <43>, <44>;
+        interrupt-names = "int0", "int1", "int2", "int3",
+                          "primsync_on", "primsync_off",
+                          "secsync_on", "secsync_off";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
new file mode 100644
index 000000000000..895501566210
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-gammacor.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Gamma Correction Unit
+
+description: |
+  The unit supports non-linear color transformation.
+
+  Each Gamma Correction Unit device should have an alias in the aliases node,
+  in the form of dc<x>-gammacor<y>, where <x> is an integer specifying the
+  Display Controller instance and <y> is an integer specifying the Gamma
+  Correction Unit device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-gammacor
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    gammacor@5618c000 {
+        compatible = "fsl,imx8qxp-dc-gammacor";
+        reg = <0x5618c000 0x20>;
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
new file mode 100644
index 000000000000..50c0c1dacb41
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
@@ -0,0 +1,45 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-layerblend.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Layer Blend Unit
+
+description: |
+  Combines two input frames to a single output frame.
+
+  Each Layer Blend Unit device should have an alias in the aliases node, in the
+  form of dc<x>-layerblend<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Layer Blend Unit
+  device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-layerblend
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    layerblend@56180ba0 {
+        compatible = "fsl,imx8qxp-dc-layerblend";
+        reg = <0x56180ba0 0x10>, <0x5618a400 0x20>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
new file mode 100644
index 000000000000..8778526e1eb8
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-matrix.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Color Matrix
+
+description: |
+  The unit supports linear color transformation, alpha pre-multiply and
+  alpha masking.
+
+  Each Color Matrix device should have an alias in the aliases node, in the form
+  of dc<x>-matrix<y>, where <x> is an integer specifying the Display Controller
+  instance and <y> is an integer specifying the Color Matrix device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-matrix
+
+  reg:
+    minItems: 1
+    maxItems: 2
+
+  reg-names:
+    oneOf:
+      - const: cfg      # matrix in display engine
+      - items:          # matrix in pixel engine
+          - const: pec
+          - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    matrix@5618bc00 {
+        compatible = "fsl,imx8qxp-dc-matrix";
+        reg = <0x5618bc00 0x3c>;
+        reg-names = "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
new file mode 100644
index 000000000000..a724cf960250
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-rop.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Raster Operation Unit
+
+description: |
+  The unit can combine up to three input frames to a single output frame, all
+  having the same dimension.
+
+  The unit supports logic operations, arithmetic operations and packing.
+
+  Each Raster Operation Unit device should have an alias in the aliases node,
+  in the form of dc<x>-rop<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Raster Operation Unit
+  device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-rop
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    rop@56180860 {
+        compatible = "fsl,imx8qxp-dc-rop";
+        reg = <0x56180860 0x10>, <0x56182000 0x20>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
new file mode 100644
index 000000000000..66c12948ab09
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-safety.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Safety Unit
+
+description:
+  The unit allows corresponding processing units to be configured in a path
+  leading to multiple endpoints.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-safety
+
+  reg:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    safety@56180800 {
+        compatible = "fsl,imx8qxp-dc-safety";
+        reg = <0x56180800 0x1c>;
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
new file mode 100644
index 000000000000..a1d6673f2a05
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
@@ -0,0 +1,89 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Scaling Engine
+
+description: |
+  The unit can change the dimension of the input frame by nearest or linear
+  re-sampling with 1/32 sub pixel precision.
+
+  Internally it consist of two independent blocks for horizontal and vertical
+  scaling. The sequence of both operations is arbitrary.
+
+  Any frame dimensions between 1 and 16384 pixels in width and height are
+  supported, except that the vertical scaler has a frame width maximum
+  depending of the system's functional limitations.
+
+  In general all scale factors are supported inside the supported frame
+  dimensions. In range of scale factors 1/16..16 the filtered output colors
+  are LSBit precise (e.g. DC ripple free).
+
+                       +-----------+
+                       |   Line    |
+                       |  Buffer   |
+                       +-----------+
+                             ^
+                             |
+                             V
+                 |\    +-----------+
+           ------+ |   |           |
+          |      | +-->| Vertical  |----
+          |  ----+ |   |  Scaler   |    |
+          | |    |/    +-----------+    |
+          | |                           |
+          | |                           |
+          | |                           |     |\
+          |  ------------- -------------+-----+ |
+  Input --+               X                   | +--> Output
+          |  ------------- -------------+-----+ |
+          | |                           |     |/
+          | |                           |
+          | |    |\    +-----------+    |
+          |  ----+ |   |           |    |
+          |      | +-->| Horizontal|----
+           ------+ |   |  Scaler   |
+                 |/    +-----------+
+
+  The unit supports downscaling, upscaling, sub pixel translation and bob
+  de-interlacing.
+
+  Each Scaling Engine device should have an alias in the aliases node, in the
+  form of dc<x>-<y>scaler<z>, where <x> is an integer specifying the Display
+  Controller instance, <y> specifies scaling type(either "h" for horizontal
+  scaler or "v" for vertical scaler) and <z> is an integer specifying the
+  Scaling Engine device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    enum:
+      - fsl,imx8qxp-dc-hscaler
+      - fsl,imx8qxp-dc-vscaler
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+required:
+  - compatible
+  - reg
+  - reg-names
+
+additionalProperties: false
+
+examples:
+  - |
+    hscaler@561808c0 {
+        compatible = "fsl,imx8qxp-dc-hscaler";
+        reg = <0x561808c0 0x10>, <0x56183000 0x18>;
+        reg-names = "pec", "cfg";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
new file mode 100644
index 000000000000..5403e3359fcb
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-signature.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Signature Unit
+
+description: |
+  In order to control the correctness of display output, signature values can
+  be computed for each frame and compared against reference values. In case of
+  a mismatch (signature violation) a HW event can be triggered, for example a
+  SW interrupt.
+
+  This unit supports signature computation, reference check, evaluation windows,
+  alpha masking and panic modes.
+
+  Each Signature Unit device should have an alias in the aliases node, in the
+  form of dc<x>-signature<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Signature Unit device
+  instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-signature
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: shdload
+      - const: valid
+      - const: error
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    signature@5618d000 {
+        compatible = "fsl,imx8qxp-dc-signature";
+        reg = <0x5618d000 0x140>;
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <22>, <23>, <24>;
+        interrupt-names = "shdload", "valid", "error";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
new file mode 100644
index 000000000000..38d86e3dd6b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-store.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Store Unit
+
+description: |
+  The Store unit is the interface between the internal pixel processing
+  pipeline, which is 30-bit RGB plus 8-bit Alpha, and the AXI bus for
+  destination buffer access. It is used for the destination of Blit Engines.
+  It comprises a set of built-in functions to generate a wide range of buffer
+  formats. Note, that these are exactly inverse to corresponding functions in
+  the Fetch Unit.
+
+  +------X-------------------------+
+  |      |              Store Unit |
+  |      V                         |
+  |  +-------+                     |
+  |  | Gamma | Gamma apply         |
+  |  +-------+                     |
+  |      |                         |
+  |      V                         |
+  |  +-------+                     |
+  |  | Color | RGB to YUV          |
+  |  +-------+                     |
+  |      |                         |
+  |      V                         |
+  |  +-------+                     |
+  |  | Chroma| YUV444 to 422       |
+  |  +-------+                     |
+  |      |                         |
+  |      V                         |
+  |  +-------+                     |
+  |  | Reduce| Bit width reduction |
+  |  |       | dithering           |
+  |  +-------+                     |
+  |      |                         |
+  |      V                         |
+  |  +-------+                     |
+  |  | Pack  | RGBA/YUV to RAW     |
+  |  | Encode| or Compression      |
+  |  +-------+                     |
+  |      |                         |
+  |      V                         |
+  +------X-------------------------+
+
+  Each Store Unit device should have an alias in the aliases node, in the form
+  of dc<x>-store<y>, where <x> is an integer specifying the Display Controller
+  instance and <y> is an integer specifying the Store Unit device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-store
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+  interrupts:
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: shdload
+      - const: framecomplete
+      - const: seqcomplete
+
+  fsl,lts:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Optional Linear Tile Store associated with the Store Unit.
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    store@56180940 {
+        compatible = "fsl,imx8qxp-dc-store";
+        reg = <0x56180940 0x1c>, <0x56184000 0x5c>;
+        reg-names = "pec", "cfg";
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <0>, <1>, <2>;
+        interrupt-names = "shdload", "framecomplete", "seqcomplete";
+    };
diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
new file mode 100644
index 000000000000..87f02d92d55e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
@@ -0,0 +1,50 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-tcon.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Timing Controller
+
+description: |
+  The TCon can generate a wide range of customized synchronization signals and
+  does the mapping of the color bits to the output.
+
+  Each Timing Controller device should have an alias in the aliases node, in the
+  form of dc<x>-tcon<y>, where <x> is an integer specifying the Display
+  Controller instance and <y> is an integer specifying the Timing Controller
+  device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-tcon
+
+  reg:
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/properties/port
+    description: video output
+
+required:
+  - compatible
+  - reg
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    tcon@5618c800 {
+        compatible = "fsl,imx8qxp-dc-tcon";
+        reg = <0x5618c800 0x588>;
+
+        port {
+            dc0_disp0_dc0_pixel_combiner_ch0: endpoint {
+                remote-endpoint = <&dc0_pixel_combiner_ch0_dc0_disp0>;
+            };
+        };
+    };
-- 
2.34.1



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

* [PATCH v6 02/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller blit engine
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
  2024-12-09  3:39 ` [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 03/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller display engine Liu Ying
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller contains a blit engine for raster graphics.
It may read up to 3 source images from memory and computes one destination
image from it, which is written back to memory.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
v6:
* No change.

v5:
* No change.

v4:
* Collect Rob's R-b tag.

v3:
* New patch. (Rob)

 .../imx/fsl,imx8qxp-dc-blit-engine.yaml       | 204 ++++++++++++++++++
 1 file changed, 204 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blit-engine.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blit-engine.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blit-engine.yaml
new file mode 100644
index 000000000000..45db6da39e20
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blit-engine.yaml
@@ -0,0 +1,204 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blit-engine.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Blit Engine
+
+description: |
+  A blit operation (block based image transfer) reads up to 3 source images
+  from memory and computes one destination image from it, which is written
+  back to memory. The following basic operations are supported:
+
+  * Buffer Fill
+    Fills a buffer with constant color
+
+  * Buffer Copy
+    Copies one source to a destination buffer.
+
+  * Image Blend
+    Combines two source images by a blending equation and writes result to
+    destination (which can be one of the sources).
+
+  * Image Rop2/3
+    Combines up to three source images by a logical equation (raster operation)
+    and writes result to destination (which can be one of the sources).
+
+  * Image Flip
+    Mirrors the source image in horizontal and/or vertical direction.
+
+  * Format Convert
+    Convert between the supported color and buffer formats.
+
+  * Color Transform
+    Modify colors by linear or non-linear transformations.
+
+  * Image Scale
+    Changes size of the source image.
+
+  * Image Rotate
+    Rotates the source image by any angle.
+
+  * Image Filter
+    Performs an FIR filter operation on the source image.
+
+  * Image Warp
+    Performs a re-sampling of the source image with any pattern. The sample
+    point positions are read from a compressed coordinate buffer.
+
+  * Buffer Pack
+    Writes an image with color components stored in up to three different
+    buffers (planar formats) into a single buffer (packed format).
+
+  * Chroma Resample
+    Converts between different YUV formats that differ in chroma sampling rate
+    (4:4:4, 4:2:2, 4:2:0).
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-blit-engine
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: pec
+      - const: cfg
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^blitblend@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-blitblend
+
+  "^clut@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-clut
+
+  "^fetchdecode@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetchdecode
+
+  "^fetcheco@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetcheco
+
+  "^fetchwarp@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetchwarp
+
+  "^filter@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-filter
+
+  "^hscaler@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-hscaler
+
+  "^matrix@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-matrix
+
+  "^rop@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-rop
+
+  "^store@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-store
+
+  "^vscaler@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-vscaler
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    blit-engine@56180820 {
+        compatible = "fsl,imx8qxp-dc-blit-engine";
+        reg = <0x56180820 0x13c>, <0x56181000 0x3400>;
+        reg-names = "pec", "cfg";
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        fetchdecode@56180820 {
+            compatible = "fsl,imx8qxp-dc-fetchdecode";
+            reg = <0x56180820 0x10>, <0x56181000 0x404>;
+            reg-names = "pec", "cfg";
+        };
+
+        store@56180940 {
+            compatible = "fsl,imx8qxp-dc-store";
+            reg = <0x56180940 0x1c>, <0x56184000 0x5c>;
+            reg-names = "pec", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <0>, <1>, <2>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        };
+    };
-- 
2.34.1



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

* [PATCH v6 03/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller display engine
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
  2024-12-09  3:39 ` [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units Liu Ying
  2024-12-09  3:39 ` [PATCH v6 02/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller blit engine Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 04/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller display engine consists of all processing units
that operate in a display clock domain.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Document aliases.  Drop Rob's previous R-b tag. (Maxime)

v4:
* Collect Rob's R-b tag.

v3:
* No change.

v2:
* Drop fsl,dc-*-id DT properties. (Krzysztof)
* Drop port property. (Krzysztof)
* Fix register range sizes in example.

 .../imx/fsl,imx8qxp-dc-display-engine.yaml    | 157 ++++++++++++++++++
 1 file changed, 157 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-display-engine.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-display-engine.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-display-engine.yaml
new file mode 100644
index 000000000000..b5da0bf3a1f4
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-display-engine.yaml
@@ -0,0 +1,157 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-display-engine.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Display Engine
+
+description: |
+  All Processing Units that operate in a display clock domain. Pixel pipeline
+  is driven by a video timing and cannot be stalled. Implements all display
+  specific processing.
+
+  Each Display Engine device should have an alias in the aliases node, in the
+  form of dc<x>-display-engine<y>, where <x> is an integer specifying the
+  Display Controller instance and <y> is an integer specifying the Display
+  Engine device instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-display-engine
+
+  reg:
+    maxItems: 2
+
+  reg-names:
+    items:
+      - const: top
+      - const: cfg
+
+  resets:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 3
+
+  interrupt-names:
+    items:
+      - const: shdload
+      - const: framecomplete
+      - const: seqcomplete
+
+  power-domains:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^dither@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-dither
+
+  "^framegen@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-framegen
+
+  "^gammacor@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-gammacor
+
+  "^matrix@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-matrix
+
+  "^signature@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-signature
+
+  "^tcon@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-tcon
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - interrupt-names
+  - power-domains
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+    #include <dt-bindings/firmware/imx/rsrc.h>
+
+    display-engine@5618b400 {
+        compatible = "fsl,imx8qxp-dc-display-engine";
+        reg = <0x5618b400 0x14>, <0x5618b800 0x1c00>;
+        reg-names = "top", "cfg";
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <15>, <16>, <17>;
+        interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        power-domains = <&pd IMX_SC_R_DC_0_PLL_0>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        framegen@5618b800 {
+            compatible = "fsl,imx8qxp-dc-framegen";
+            reg = <0x5618b800 0x98>;
+            clocks = <&dc0_disp_lpcg IMX_LPCG_CLK_0>;
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <18>, <19>, <20>, <21>, <41>, <42>, <43>, <44>;
+            interrupt-names = "int0", "int1", "int2", "int3",
+                              "primsync_on", "primsync_off",
+                              "secsync_on", "secsync_off";
+        };
+
+        tcon@5618c800 {
+            compatible = "fsl,imx8qxp-dc-tcon";
+            reg = <0x5618c800 0x588>;
+
+            port {
+                dc0_disp0_dc0_pixel_combiner_ch0: endpoint {
+                    remote-endpoint = <&dc0_pixel_combiner_ch0_dc0_disp0>;
+                };
+            };
+        };
+    };
-- 
2.34.1



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

* [PATCH v6 04/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller pixel engine
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (2 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 03/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller display engine Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 05/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller AXI performance counter Liu Ying
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller pixel engine consists of all processing units
that operate in the AXI bus clock domain.  Command sequencer and interrupt
controller of the Display Controller work with AXI bus clock, but they are
not in pixel engine.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
v6:
* No change.

v5:
* No change.

v4:
* Collect Rob's R-b tag.

v3:
* No change.

v2:
* Drop fsl,dc-*-id DT properties from example. (Krzysztof)
* Fix register range sizes in example.

 .../imx/fsl,imx8qxp-dc-pixel-engine.yaml      | 250 ++++++++++++++++++
 1 file changed, 250 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml
new file mode 100644
index 000000000000..633443a6cc38
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml
@@ -0,0 +1,250 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-pixel-engine.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Pixel Engine
+
+description:
+  All Processing Units that operate in the AXI bus clock domain. Pixel
+  pipelines have the ability to stall when a destination is busy. Implements
+  all communication to memory resources and most of the image processing
+  functions. Interconnection of Processing Units is re-configurable.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-pixel-engine
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^blit-engine@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-blit-engine
+
+  "^constframe@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-constframe
+
+  "^extdst@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-extdst
+
+  "^fetchdecode@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetchdecode
+
+  "^fetcheco@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetcheco
+
+  "^fetchlayer@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetchlayer
+
+  "^fetchwarp@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-fetchwarp
+
+  "^hscaler@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-hscaler
+
+  "^layerblend@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-layerblend
+
+  "^matrix@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-matrix
+
+  "^safety@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-safety
+
+  "^vscaler@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-vscaler
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+
+    pixel-engine@56180800 {
+        compatible = "fsl,imx8qxp-dc-pixel-engine";
+        reg = <0x56180800 0xac00>;
+        clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        constframe@56180960 {
+            compatible = "fsl,imx8qxp-dc-constframe";
+            reg = <0x56180960 0xc>, <0x56184400 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        extdst@56180980 {
+            compatible = "fsl,imx8qxp-dc-extdst";
+            reg = <0x56180980 0x1c>, <0x56184800 0x28>;
+            reg-names = "pec", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <3>, <4>, <5>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        };
+
+        constframe@561809a0 {
+            compatible = "fsl,imx8qxp-dc-constframe";
+            reg = <0x561809a0 0xc>, <0x56184c00 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        extdst@561809c0 {
+            compatible = "fsl,imx8qxp-dc-extdst";
+            reg = <0x561809c0 0x1c>, <0x56185000 0x28>;
+            reg-names = "pec", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <6>, <7>, <8>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        };
+
+        constframe@561809e0 {
+            compatible = "fsl,imx8qxp-dc-constframe";
+            reg = <0x561809e0 0xc>, <0x56185400 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        extdst@56180a00 {
+            compatible = "fsl,imx8qxp-dc-extdst";
+            reg = <0x56180a00 0x1c>, <0x56185800 0x28>;
+            reg-names = "pec", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <9>, <10>, <11>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        };
+
+        constframe@56180a20 {
+            compatible = "fsl,imx8qxp-dc-constframe";
+            reg = <0x56180a20 0xc>, <0x56185c00 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        extdst@56180a40 {
+            compatible = "fsl,imx8qxp-dc-extdst";
+            reg = <0x56180a40 0x1c>, <0x56186000 0x28>;
+            reg-names = "pec", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <12>, <13>, <14>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+        };
+
+        fetchwarp@56180a60 {
+            compatible = "fsl,imx8qxp-dc-fetchwarp";
+            reg = <0x56180a60 0x10>, <0x56186400 0x190>;
+            reg-names = "pec", "cfg";
+        };
+
+        fetchlayer@56180ac0 {
+            compatible = "fsl,imx8qxp-dc-fetchlayer";
+            reg = <0x56180ac0 0xc>, <0x56188400 0x404>;
+            reg-names = "pec", "cfg";
+        };
+
+        layerblend@56180ba0 {
+            compatible = "fsl,imx8qxp-dc-layerblend";
+            reg = <0x56180ba0 0x10>, <0x5618a400 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        layerblend@56180bc0 {
+            compatible = "fsl,imx8qxp-dc-layerblend";
+            reg = <0x56180bc0 0x10>, <0x5618a800 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        layerblend@56180be0 {
+            compatible = "fsl,imx8qxp-dc-layerblend";
+            reg = <0x56180be0 0x10>, <0x5618ac00 0x20>;
+            reg-names = "pec", "cfg";
+        };
+
+        layerblend@56180c00 {
+            compatible = "fsl,imx8qxp-dc-layerblend";
+            reg = <0x56180c00 0x10>, <0x5618b000 0x20>;
+            reg-names = "pec", "cfg";
+        };
+    };
-- 
2.34.1



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

* [PATCH v6 05/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller AXI performance counter
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (3 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 04/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 06/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller command sequencer Liu Ying
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller contains a AXI performance counter which allows
measurement of average bandwidth and latency during operation.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
v6:
* No change.

v5:
* No change.

v4:
* Collect Rob's R-b tag.

v3:
* New patch. (Rob)

 ...sl,imx8qxp-dc-axi-performance-counter.yaml | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml
new file mode 100644
index 000000000000..1d6501afc7f2
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-axi-performance-counter.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller AXI Performance Counter
+
+description: |
+  Performance counters are provided to allow measurement of average bandwidth
+  and latency during operation. The following features are supported:
+
+  * Manual and timer controlled measurement mode.
+
+  * Measurement counters:
+    - GLOBAL_COUNTER for overall measurement time
+    - BUSY_COUNTER for number of data bus busy cycles
+    - DATA_COUNTER for number of data transfer cycles
+    - TRANSFER_COUNTER for number of transfers
+    - ADDRBUSY_COUNTER for number of address bus busy cycles
+    - LATENCY_COUNTER for average latency
+
+  * Counter overflow detection.
+
+  * Outstanding Transfer Counters (OTC) which are used for latency measurement
+    have to run immediately after reset, but can be disabled by software when
+    there is no need for latency measurement.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-axi-performance-counter
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+
+    pmu@5618f000 {
+        compatible = "fsl,imx8qxp-dc-axi-performance-counter";
+        reg = <0x5618f000 0x90>;
+        clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+    };
-- 
2.34.1



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

* [PATCH v6 06/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller command sequencer
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (4 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 05/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller AXI performance counter Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 07/19] dt-bindings: interrupt-controller: Add i.MX8qxp Display Controller interrupt controller Liu Ying
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller contains a command sequencer is designed to
autonomously process command lists.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* Replace "fsl,iram" property with standard "sram" property. (Rob)

v3:
* New patch. (Rob)

 .../imx/fsl,imx8qxp-dc-command-sequencer.yaml | 67 +++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml
new file mode 100644
index 000000000000..27118f4c0d28
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml
@@ -0,0 +1,67 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-command-sequencer.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller Command Sequencer
+
+description: |
+  The Command Sequencer is designed to autonomously process command lists.
+  By that it can load setups into the DC configuration and synchronize to
+  hardware events.  This releases a system's CPU from workload, because it
+  does not need to wait for certain events.  Also it simplifies SW architecture,
+  because no interrupt handlers are required.  Setups are read via AXI bus,
+  while write access to configuration registers occurs directly via an internal
+  bus.  This saves bandwidth for the AXI interconnect and improves the system
+  architecture in terms of safety aspects.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-command-sequencer
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 5
+
+  interrupt-names:
+    items:
+      - const: error
+      - const: sw0
+      - const: sw1
+      - const: sw2
+      - const: sw3
+
+  sram:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: phandle pointing to the mmio-sram device node
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+
+    command-sequencer@56180400 {
+        compatible = "fsl,imx8qxp-dc-command-sequencer";
+        reg = <0x56180400 0x1a4>;
+        clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+        interrupt-parent = <&dc0_intc>;
+        interrupts = <36>, <37>, <38>, <39>, <40>;
+        interrupt-names = "error", "sw0", "sw1", "sw2", "sw3";
+    };
-- 
2.34.1



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

* [PATCH v6 07/19] dt-bindings: interrupt-controller: Add i.MX8qxp Display Controller interrupt controller
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (5 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 06/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller command sequencer Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 08/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller Liu Ying
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller has a built-in interrupt controller to support
Enable/Status/Preset/Clear interrupt bit.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* Collect Rob's R-b tag.

v2:
* Drop unneeded "|". (Krzysztof)

 .../fsl,imx8qxp-dc-intc.yaml                  | 318 ++++++++++++++++++
 1 file changed, 318 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/interrupt-controller/fsl,imx8qxp-dc-intc.yaml

diff --git a/Documentation/devicetree/bindings/interrupt-controller/fsl,imx8qxp-dc-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/fsl,imx8qxp-dc-intc.yaml
new file mode 100644
index 000000000000..6985ee644a25
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/fsl,imx8qxp-dc-intc.yaml
@@ -0,0 +1,318 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/fsl,imx8qxp-dc-intc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller interrupt controller
+
+description: |
+  The Display Controller has a built-in interrupt controller with the following
+  features for all relevant HW events:
+
+  * Enable bit (mask)
+  * Status bit (set by an HW event)
+  * Preset bit (can be used by SW to set status)
+  * Clear bit (used by SW to reset the status)
+
+  Each interrupt can be connected as IRQ (maskable) and/or NMI (non-maskable).
+  Alternatively the un-masked trigger signals for all HW events are provided,
+  allowing it to use a global interrupt controller instead.
+
+  Each interrupt can be protected against SW running in user mode. In that case,
+  only privileged AHB access can control the interrupt status.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc-intc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  interrupt-controller: true
+
+  "#interrupt-cells":
+    const: 1
+
+  interrupts:
+    items:
+      - description: store9 shadow load interrupt(blit engine)
+      - description: store9 frame complete interrupt(blit engine)
+      - description: store9 sequence complete interrupt(blit engine)
+      - description:
+          extdst0 shadow load interrupt
+          (display controller, content stream 0)
+      - description:
+          extdst0 frame complete interrupt
+          (display controller, content stream 0)
+      - description:
+          extdst0 sequence complete interrupt
+          (display controller, content stream 0)
+      - description:
+          extdst4 shadow load interrupt
+          (display controller, safety stream 0)
+      - description:
+          extdst4 frame complete interrupt
+          (display controller, safety stream 0)
+      - description:
+          extdst4 sequence complete interrupt
+          (display controller, safety stream 0)
+      - description:
+          extdst1 shadow load interrupt
+          (display controller, content stream 1)
+      - description:
+          extdst1 frame complete interrupt
+          (display controller, content stream 1)
+      - description:
+          extdst1 sequence complete interrupt
+          (display controller, content stream 1)
+      - description:
+          extdst5 shadow load interrupt
+          (display controller, safety stream 1)
+      - description:
+          extdst5 frame complete interrupt
+          (display controller, safety stream 1)
+      - description:
+          extdst5 sequence complete interrupt
+          (display controller, safety stream 1)
+      - description:
+          disengcfg0 shadow load interrupt
+          (display controller, display stream 0)
+      - description:
+          disengcfg0 frame complete interrupt
+          (display controller, display stream 0)
+      - description:
+          disengcfg0 sequence complete interrupt
+          (display controller, display stream 0)
+      - description:
+          framegen0 programmable interrupt0
+          (display controller, display stream 0)
+      - description:
+          framegen0 programmable interrupt1
+          (display controller, display stream 0)
+      - description:
+          framegen0 programmable interrupt2
+          (display controller, display stream 0)
+      - description:
+          framegen0 programmable interrupt3
+          (display controller, display stream 0)
+      - description:
+          signature0 shadow load interrupt
+          (display controller, display stream 0)
+      - description:
+          signature0 measurement valid interrupt
+          (display controller, display stream 0)
+      - description:
+          signature0 error condition interrupt
+          (display controller, display stream 0)
+      - description:
+          disengcfg1 shadow load interrupt
+          (display controller, display stream 1)
+      - description:
+          disengcfg1 frame complete interrupt
+          (display controller, display stream 1)
+      - description:
+          disengcfg1 sequence complete interrupt
+          (display controller, display stream 1)
+      - description:
+          framegen1 programmable interrupt0
+          (display controller, display stream 1)
+      - description:
+          framegen1 programmable interrupt1
+          (display controller, display stream 1)
+      - description:
+          framegen1 programmable interrupt2
+          (display controller, display stream 1)
+      - description:
+          framegen1 programmable interrupt3
+          (display controller, display stream 1)
+      - description:
+          signature1 shadow load interrupt
+          (display controller, display stream 1)
+      - description:
+          signature1 measurement valid interrupt
+          (display controller, display stream 1)
+      - description:
+          signature1 error condition interrupt
+          (display controller, display stream 1)
+      - description: reserved
+      - description:
+          command sequencer error condition interrupt(command sequencer)
+      - description:
+          common control software interrupt0(common control)
+      - description:
+          common control software interrupt1(common control)
+      - description:
+          common control software interrupt2(common control)
+      - description:
+          common control software interrupt3(common control)
+      - description:
+          framegen0 synchronization status activated interrupt
+          (display controller, safety stream 0)
+      - description:
+          framegen0 synchronization status deactivated interrupt
+          (display controller, safety stream 0)
+      - description:
+          framegen0 synchronization status activated interrupt
+          (display controller, content stream 0)
+      - description:
+          framegen0 synchronization status deactivated interrupt
+          (display controller, content stream 0)
+      - description:
+          framegen1 synchronization status activated interrupt
+          (display controller, safety stream 1)
+      - description:
+          framegen1 synchronization status deactivated interrupt
+          (display controller, safety stream 1)
+      - description:
+          framegen1 synchronization status activated interrupt
+          (display controller, content stream 1)
+      - description:
+          framegen1 synchronization status deactivated interrupt
+          (display controller, content stream 1)
+    minItems: 49
+
+  interrupt-names:
+    items:
+      - const: store9_shdload
+      - const: store9_framecomplete
+      - const: store9_seqcomplete
+      - const: extdst0_shdload
+      - const: extdst0_framecomplete
+      - const: extdst0_seqcomplete
+      - const: extdst4_shdload
+      - const: extdst4_framecomplete
+      - const: extdst4_seqcomplete
+      - const: extdst1_shdload
+      - const: extdst1_framecomplete
+      - const: extdst1_seqcomplete
+      - const: extdst5_shdload
+      - const: extdst5_framecomplete
+      - const: extdst5_seqcomplete
+      - const: disengcfg_shdload0
+      - const: disengcfg_framecomplete0
+      - const: disengcfg_seqcomplete0
+      - const: framegen0_int0
+      - const: framegen0_int1
+      - const: framegen0_int2
+      - const: framegen0_int3
+      - const: sig0_shdload
+      - const: sig0_valid
+      - const: sig0_error
+      - const: disengcfg_shdload1
+      - const: disengcfg_framecomplete1
+      - const: disengcfg_seqcomplete1
+      - const: framegen1_int0
+      - const: framegen1_int1
+      - const: framegen1_int2
+      - const: framegen1_int3
+      - const: sig1_shdload
+      - const: sig1_valid
+      - const: sig1_error
+      - const: reserved
+      - const: cmdseq_error
+      - const: comctrl_sw0
+      - const: comctrl_sw1
+      - const: comctrl_sw2
+      - const: comctrl_sw3
+      - const: framegen0_primsync_on
+      - const: framegen0_primsync_off
+      - const: framegen0_secsync_on
+      - const: framegen0_secsync_off
+      - const: framegen1_primsync_on
+      - const: framegen1_primsync_off
+      - const: framegen1_secsync_on
+      - const: framegen1_secsync_off
+    minItems: 49
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupt-controller
+  - "#interrupt-cells"
+  - interrupts
+  - interrupt-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+
+    interrupt-controller@56180040 {
+        compatible = "fsl,imx8qxp-dc-intc";
+        reg = <0x56180040 0x60>;
+        clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+        interrupt-controller;
+        interrupt-parent = <&dc0_irqsteer>;
+        #interrupt-cells = <1>;
+        interrupts = <448>, <449>, <450>,  <64>,
+                      <65>,  <66>,  <67>,  <68>,
+                      <69>,  <70>, <193>, <194>,
+                     <195>, <196>, <197>,  <72>,
+                      <73>,  <74>,  <75>,  <76>,
+                      <77>,  <78>,  <79>,  <80>,
+                      <81>, <199>, <200>, <201>,
+                     <202>, <203>, <204>, <205>,
+                     <206>, <207>, <208>,   <5>,
+                       <0>,   <1>,   <2>,   <3>,
+                       <4>,  <82>,  <83>,  <84>,
+                      <85>, <209>, <210>, <211>,
+                     <212>;
+        interrupt-names = "store9_shdload",
+                          "store9_framecomplete",
+                          "store9_seqcomplete",
+                          "extdst0_shdload",
+                          "extdst0_framecomplete",
+                          "extdst0_seqcomplete",
+                          "extdst4_shdload",
+                          "extdst4_framecomplete",
+                          "extdst4_seqcomplete",
+                          "extdst1_shdload",
+                          "extdst1_framecomplete",
+                          "extdst1_seqcomplete",
+                          "extdst5_shdload",
+                          "extdst5_framecomplete",
+                          "extdst5_seqcomplete",
+                          "disengcfg_shdload0",
+                          "disengcfg_framecomplete0",
+                          "disengcfg_seqcomplete0",
+                          "framegen0_int0",
+                          "framegen0_int1",
+                          "framegen0_int2",
+                          "framegen0_int3",
+                          "sig0_shdload",
+                          "sig0_valid",
+                          "sig0_error",
+                          "disengcfg_shdload1",
+                          "disengcfg_framecomplete1",
+                          "disengcfg_seqcomplete1",
+                          "framegen1_int0",
+                          "framegen1_int1",
+                          "framegen1_int2",
+                          "framegen1_int3",
+                          "sig1_shdload",
+                          "sig1_valid",
+                          "sig1_error",
+                          "reserved",
+                          "cmdseq_error",
+                          "comctrl_sw0",
+                          "comctrl_sw1",
+                          "comctrl_sw2",
+                          "comctrl_sw3",
+                          "framegen0_primsync_on",
+                          "framegen0_primsync_off",
+                          "framegen0_secsync_on",
+                          "framegen0_secsync_off",
+                          "framegen1_primsync_on",
+                          "framegen1_primsync_off",
+                          "framegen1_secsync_on",
+                          "framegen1_secsync_off";
+    };
-- 
2.34.1



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

* [PATCH v6 08/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (6 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 07/19] dt-bindings: interrupt-controller: Add i.MX8qxp Display Controller interrupt controller Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine Liu Ying
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller(DC) is comprised of three main components that
include a blit engine for 2D graphics accelerations, display controller for
display output processing, as well as a command sequencer.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Document aliases.  Drop Rob's previous R-b tag. (Maxime)

v4:
* Collect Rob's R-b tag.

v3:
* No change.

v2:
* Drop fsl,dc-*-id DT properties from example. (Krzysztof)
* Use generic pmu pattern property. (Krzysztof)

 .../bindings/display/imx/fsl,imx8qxp-dc.yaml  | 240 ++++++++++++++++++
 1 file changed, 240 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc.yaml

diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc.yaml
new file mode 100644
index 000000000000..10cc83a7420f
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc.yaml
@@ -0,0 +1,240 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale i.MX8qxp Display Controller
+
+description: |
+  The Freescale i.MX8qxp Display Controller(DC) is comprised of three main
+  components that include a blit engine for 2D graphics accelerations, display
+  controller for display output processing, as well as a command sequencer.
+
+                                  Display buffers              Source buffers
+                                 (AXI read master)            (AXI read master)
+                                  | .......... |                  | | |
+      +---------------------------+------------+------------------+-+-+------+
+      | Display Controller (DC)   | .......... |                  | | |      |
+      |                           |            |                  | | |      |
+      |   @@@@@@@@@@@  +----------+------------+------------+     | | |      |
+  A   |  | Command   | |          V            V            |     | | |      |
+  X <-+->| Sequencer | |    @@@@@@@@@@@@@@@@@@@@@@@@@@@@    |     V V V      |
+  I   |  | (AXI CLK) | |   |                            |   |   @@@@@@@@@@   |
+      |   @@@@@@@@@@@  |   |       Pixel Engine         |   |  |          |  |
+      |       |        |   |         (AXI CLK)          |   |  |          |  |
+      |       V        |    @@@@@@@@@@@@@@@@@@@@@@@@@@@@    |  |          |  |
+  A   |   ***********  |       |   |            |   |       |  |   Blit   |  |
+  H <-+->| Configure | |       V   V            V   V       |  |  Engine  |  |
+  B   |  | (CFG CLK) | |    00000000000      11111111111    |  | (AXI CLK)|  |
+      |   ***********  |   |  Display  |    |  Display  |   |  |          |  |
+      |                |   |  Engine   |    |  Engine   |   |  |          |  |
+      |                |   | (Disp CLK)|    | (Disp CLK)|   |  |          |  |
+      |   @@@@@@@@@@@  |    00000000000      11111111111    |   @@@@@@@@@@   |
+  I   |  |  Common   | |         |                |         |       |        |
+  R <-+--|  Control  | |         |    Display     |         |       |        |
+  Q   |  | (AXI CLK) | |         |   Controller   |         |       |        |
+      |   @@@@@@@@@@@  +------------------------------------+       |        |
+      |                          |                |       ^         |        |
+      +--------------------------+----------------+-------+---------+--------+
+              ^                  |                |       |         |
+              |                  V                V       |         V
+       Clocks & Resets        Display          Display  Panic   Destination
+                              Output0          Output1 Control    buffer
+                                                              (AXI write master)
+
+  Each Display Controller device should have an alias in the aliases node, in
+  the form of dc<x>, where <x> is an integer specifying the Display Controller
+  instance.
+
+maintainers:
+  - Liu Ying <victor.liu@nxp.com>
+
+properties:
+  compatible:
+    const: fsl,imx8qxp-dc
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  resets:
+    maxItems: 2
+
+  reset-names:
+    items:
+      - const: axi
+      - const: cfg
+
+  power-domains:
+    maxItems: 1
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 1
+
+  ranges: true
+
+patternProperties:
+  "^command-sequencer@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-command-sequencer
+
+  "^display-engine@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-display-engine
+
+  "^interrupt-controller@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-intc
+
+  "^pixel-engine@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-pixel-engine
+
+  "^pmu@[0-9a-f]+$":
+    type: object
+    additionalProperties: true
+
+    properties:
+      compatible:
+        const: fsl,imx8qxp-dc-axi-performance-counter
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - power-domains
+  - "#address-cells"
+  - "#size-cells"
+  - ranges
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/imx8-lpcg.h>
+    #include <dt-bindings/firmware/imx/rsrc.h>
+
+    display-controller@56180000 {
+        compatible = "fsl,imx8qxp-dc";
+        reg = <0x56180000 0x40000>;
+        clocks = <&dc0_lpcg IMX_LPCG_CLK_4>;
+        power-domains = <&pd IMX_SC_R_DC_0>;
+        #address-cells = <1>;
+        #size-cells = <1>;
+        ranges;
+
+        interrupt-controller@56180040 {
+            compatible = "fsl,imx8qxp-dc-intc";
+            reg = <0x56180040 0x60>;
+            clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+            interrupt-controller;
+            interrupt-parent = <&dc0_irqsteer>;
+            #interrupt-cells = <1>;
+            interrupts = <448>, <449>, <450>,  <64>,
+                          <65>,  <66>,  <67>,  <68>,
+                          <69>,  <70>, <193>, <194>,
+                         <195>, <196>, <197>,  <72>,
+                          <73>,  <74>,  <75>,  <76>,
+                          <77>,  <78>,  <79>,  <80>,
+                          <81>, <199>, <200>, <201>,
+                         <202>, <203>, <204>, <205>,
+                         <206>, <207>, <208>,   <5>,
+                           <0>,   <1>,   <2>,   <3>,
+                           <4>,  <82>,  <83>,  <84>,
+                          <85>, <209>, <210>, <211>,
+                         <212>;
+            interrupt-names = "store9_shdload",
+                              "store9_framecomplete",
+                              "store9_seqcomplete",
+                              "extdst0_shdload",
+                              "extdst0_framecomplete",
+                              "extdst0_seqcomplete",
+                              "extdst4_shdload",
+                              "extdst4_framecomplete",
+                              "extdst4_seqcomplete",
+                              "extdst1_shdload",
+                              "extdst1_framecomplete",
+                              "extdst1_seqcomplete",
+                              "extdst5_shdload",
+                              "extdst5_framecomplete",
+                              "extdst5_seqcomplete",
+                              "disengcfg_shdload0",
+                              "disengcfg_framecomplete0",
+                              "disengcfg_seqcomplete0",
+                              "framegen0_int0",
+                              "framegen0_int1",
+                              "framegen0_int2",
+                              "framegen0_int3",
+                              "sig0_shdload",
+                              "sig0_valid",
+                              "sig0_error",
+                              "disengcfg_shdload1",
+                              "disengcfg_framecomplete1",
+                              "disengcfg_seqcomplete1",
+                              "framegen1_int0",
+                              "framegen1_int1",
+                              "framegen1_int2",
+                              "framegen1_int3",
+                              "sig1_shdload",
+                              "sig1_valid",
+                              "sig1_error",
+                              "reserved",
+                              "cmdseq_error",
+                              "comctrl_sw0",
+                              "comctrl_sw1",
+                              "comctrl_sw2",
+                              "comctrl_sw3",
+                              "framegen0_primsync_on",
+                              "framegen0_primsync_off",
+                              "framegen0_secsync_on",
+                              "framegen0_secsync_off",
+                              "framegen1_primsync_on",
+                              "framegen1_primsync_off",
+                              "framegen1_secsync_on",
+                              "framegen1_secsync_off";
+        };
+
+        pixel-engine@56180800 {
+            compatible = "fsl,imx8qxp-dc-pixel-engine";
+            reg = <0x56180800 0xac00>;
+            clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+            #address-cells = <1>;
+            #size-cells = <1>;
+            ranges;
+        };
+
+        display-engine@5618b400 {
+            compatible = "fsl,imx8qxp-dc-display-engine";
+            reg = <0x5618b400 0x14>, <0x5618b800 0x1c00>;
+            reg-names = "top", "cfg";
+            interrupt-parent = <&dc0_intc>;
+            interrupts = <15>, <16>, <17>;
+            interrupt-names = "shdload", "framecomplete", "seqcomplete";
+            power-domains = <&pd IMX_SC_R_DC_0_PLL_0>;
+            #address-cells = <1>;
+            #size-cells = <1>;
+            ranges;
+        };
+    };
-- 
2.34.1



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

* [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (7 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 08/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-12  8:02   ` Maxime Ripard
  2024-12-09  3:39 ` [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
                   ` (9 subsequent siblings)
  18 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller display engine consists of all processing
units that operate in a display clock domain.  Add minimal feature
support with FrameGen and TCon so that the engine can output display
timings.  The FrameGen driver, TCon driver and display engine driver
are components to be aggregated by a master registered in the upcoming
DRM driver.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Replace .remove_new with .remove in dc-{de,fg,tc}.c. (Uwe)
* Select REGMAP and REGMAP_MMIO Kconfig options.
* Fix commit message to state that display engine driver is a component driver
  instead of a master/aggregate driver.

v4:
* Use regmap to define register map for all registers. (Dmitry)
* Use regmap APIs to access registers. (Dmitry)
* Inline some small functions. (Dmitry)
* Move dc_fg_displaymode() and dc_fg_panic_displaymode() function calls from
  KMS routine to initialization stage. (Dmitry)
* Use devm_kzalloc() to drmm_kzalloc() to allocate dc_* data strutures.
* Drop unnecessary private struct dc_*_priv.
* Set suppress_bind_attrs driver flag to true to avoid unnecessary sys
  interfaces to bind/unbind the drivers.

v3:
* No change.

v2:
* Use OF alias id to get instance id.
* Add dev member to struct dc_tc.

 drivers/gpu/drm/imx/Kconfig     |   1 +
 drivers/gpu/drm/imx/Makefile    |   1 +
 drivers/gpu/drm/imx/dc/Kconfig  |   7 +
 drivers/gpu/drm/imx/dc/Makefile |   5 +
 drivers/gpu/drm/imx/dc/dc-de.c  | 153 +++++++++++++
 drivers/gpu/drm/imx/dc/dc-de.h  |  57 +++++
 drivers/gpu/drm/imx/dc/dc-drv.c |  32 +++
 drivers/gpu/drm/imx/dc/dc-drv.h |  24 ++
 drivers/gpu/drm/imx/dc/dc-fg.c  | 374 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-tc.c  | 138 ++++++++++++
 10 files changed, 792 insertions(+)
 create mode 100644 drivers/gpu/drm/imx/dc/Kconfig
 create mode 100644 drivers/gpu/drm/imx/dc/Makefile
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-de.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-drv.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fg.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-tc.c

diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 03535a15dd8f..3e8c6edbc17c 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+source "drivers/gpu/drm/imx/dc/Kconfig"
 source "drivers/gpu/drm/imx/dcss/Kconfig"
 source "drivers/gpu/drm/imx/ipuv3/Kconfig"
 source "drivers/gpu/drm/imx/lcdc/Kconfig"
diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile
index 86f38e7c7422..c7b317640d71 100644
--- a/drivers/gpu/drm/imx/Makefile
+++ b/drivers/gpu/drm/imx/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
+obj-$(CONFIG_DRM_IMX8_DC) += dc/
 obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
 obj-$(CONFIG_DRM_IMX) += ipuv3/
 obj-$(CONFIG_DRM_IMX_LCDC) += lcdc/
diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
new file mode 100644
index 000000000000..e1ef76d82830
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/Kconfig
@@ -0,0 +1,7 @@
+config DRM_IMX8_DC
+	tristate "Freescale i.MX8 Display Controller Graphics"
+	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
+	select REGMAP
+	select REGMAP_MMIO
+	help
+	  enable Freescale i.MX8 Display Controller(DC) graphics support
diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
new file mode 100644
index 000000000000..56de82d53d4d
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+imx8-dc-drm-objs := dc-de.o dc-drv.o dc-fg.o dc-tc.o
+
+obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
diff --git a/drivers/gpu/drm/imx/dc/dc-de.c b/drivers/gpu/drm/imx/dc/dc-de.c
new file mode 100644
index 000000000000..24cd37c1e4a8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-de.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define POLARITYCTRL		0xc
+#define  POLEN_HIGH		BIT(2)
+
+static const struct regmap_range dc_de_regmap_ranges[] = {
+	regmap_reg_range(POLARITYCTRL, POLARITYCTRL),
+};
+
+static const struct regmap_access_table dc_de_regmap_access_table = {
+	.yes_ranges = dc_de_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_de_regmap_ranges),
+};
+
+static const struct regmap_config dc_de_top_regmap_config = {
+	.name = "display-engine-top",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_de_regmap_access_table,
+	.rd_table = &dc_de_regmap_access_table,
+};
+
+static inline void dc_dec_init(struct dc_de *de)
+{
+	regmap_write_bits(de->reg_top, POLARITYCTRL, POLARITYCTRL, POLEN_HIGH);
+}
+
+static int dc_de_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	void __iomem *base_top;
+	struct dc_de *de;
+	int ret;
+
+	de = devm_kzalloc(dev, sizeof(*de), GFP_KERNEL);
+	if (!de)
+		return -ENOMEM;
+
+	base_top = devm_platform_ioremap_resource_byname(pdev, "top");
+	if (IS_ERR(base_top))
+		return PTR_ERR(base_top);
+
+	de->reg_top = devm_regmap_init_mmio(dev, base_top,
+					    &dc_de_top_regmap_config);
+	if (IS_ERR(de->reg_top))
+		return PTR_ERR(de->reg_top);
+
+	de->irq_shdld = platform_get_irq_byname(pdev, "shdload");
+	if (de->irq_shdld < 0)
+		return de->irq_shdld;
+
+	de->irq_framecomplete = platform_get_irq_byname(pdev, "framecomplete");
+	if (de->irq_framecomplete < 0)
+		return de->irq_framecomplete;
+
+	de->irq_seqcomplete = platform_get_irq_byname(pdev, "seqcomplete");
+	if (de->irq_seqcomplete < 0)
+		return de->irq_seqcomplete;
+
+	de->id = of_alias_get_id(dev->of_node, "dc0-display-engine");
+	if (de->id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", de->id);
+		return de->id;
+	}
+
+	de->dev = dev;
+
+	dev_set_drvdata(dev, de);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	dc_drm->de[de->id] = de;
+
+	return 0;
+}
+
+static const struct component_ops dc_de_ops = {
+	.bind = dc_de_bind,
+};
+
+static int dc_de_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = devm_of_platform_populate(&pdev->dev);
+	if (ret < 0)
+		return ret;
+
+	ret = component_add(&pdev->dev, &dc_de_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_de_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_de_ops);
+}
+
+static int dc_de_runtime_resume(struct device *dev)
+{
+	struct dc_de *de = dev_get_drvdata(dev);
+
+	dc_dec_init(de);
+	dc_fg_init(de->fg);
+	dc_tc_init(de->tc);
+
+	return 0;
+}
+
+static const struct dev_pm_ops dc_de_pm_ops = {
+	RUNTIME_PM_OPS(NULL, dc_de_runtime_resume, NULL)
+};
+
+static const struct of_device_id dc_de_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-display-engine", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_de_dt_ids);
+
+struct platform_driver dc_de_driver = {
+	.probe = dc_de_probe,
+	.remove = dc_de_remove,
+	.driver = {
+		.name = "imx8-dc-display-engine",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_de_dt_ids,
+		.pm = pm_sleep_ptr(&dc_de_pm_ops),
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
new file mode 100644
index 000000000000..17a44362118e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-de.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DISPLAY_ENGINE_H__
+#define __DC_DISPLAY_ENGINE_H__
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <drm/drm_modes.h>
+
+#define DC_DISPLAYS	2
+
+struct dc_fg {
+	struct device *dev;
+	struct regmap *reg;
+	struct clk *clk_disp;
+};
+
+struct dc_tc {
+	struct device *dev;
+	struct regmap *reg;
+};
+
+struct dc_de {
+	struct device *dev;
+	struct regmap *reg_top;
+	struct dc_fg *fg;
+	struct dc_tc *tc;
+	int irq_shdld;
+	int irq_framecomplete;
+	int irq_seqcomplete;
+	int id;
+};
+
+/* Frame Generator Unit */
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m);
+void dc_fg_enable(struct dc_fg *fg);
+void dc_fg_disable(struct dc_fg *fg);
+void dc_fg_shdtokgen(struct dc_fg *fg);
+u32 dc_fg_get_frame_index(struct dc_fg *fg);
+u32 dc_fg_get_line_index(struct dc_fg *fg);
+bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg);
+bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg);
+void dc_fg_secondary_clear_channel_status(struct dc_fg *fg);
+int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg);
+void dc_fg_enable_clock(struct dc_fg *fg);
+void dc_fg_disable_clock(struct dc_fg *fg);
+enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz);
+void dc_fg_init(struct dc_fg *fg);
+
+/* Timing Controller Unit */
+void dc_tc_init(struct dc_tc *tc);
+
+#endif /* __DC_DISPLAY_ENGINE_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
new file mode 100644
index 000000000000..e5910a82dd4d
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-drv.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include "dc-drv.h"
+
+static struct platform_driver * const dc_drivers[] = {
+	&dc_de_driver,
+	&dc_fg_driver,
+	&dc_tc_driver,
+};
+
+static int __init dc_drm_init(void)
+{
+	return platform_register_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
+}
+
+static void __exit dc_drm_exit(void)
+{
+	platform_unregister_drivers(dc_drivers, ARRAY_SIZE(dc_drivers));
+}
+
+module_init(dc_drm_init);
+module_exit(dc_drm_exit);
+
+MODULE_DESCRIPTION("i.MX8 Display Controller DRM Driver");
+MODULE_AUTHOR("Liu Ying <victor.liu@nxp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
new file mode 100644
index 000000000000..e1290d9a0a99
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-drv.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_DRV_H__
+#define __DC_DRV_H__
+
+#include <linux/platform_device.h>
+
+#include <drm/drm_device.h>
+
+#include "dc-de.h"
+
+struct dc_drm_device {
+	struct drm_device base;
+	struct dc_de *de[DC_DISPLAYS];
+};
+
+extern struct platform_driver dc_de_driver;
+extern struct platform_driver dc_fg_driver;
+extern struct platform_driver dc_tc_driver;
+
+#endif /* __DC_DRV_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-fg.c b/drivers/gpu/drm/imx/dc/dc-fg.c
new file mode 100644
index 000000000000..bb51fe6faee0
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fg.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <drm/drm_modes.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+
+#define FGSTCTRL		0x8
+#define  FGSYNCMODE_MASK	GENMASK(2, 1)
+#define  FGSYNCMODE(x)		FIELD_PREP(FGSYNCMODE_MASK, (x))
+#define  SHDEN			BIT(0)
+
+#define HTCFG1			0xc
+#define  HTOTAL(x)		FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  HACT(x)		FIELD_PREP(GENMASK(13, 0), (x))
+
+#define HTCFG2			0x10
+#define  HSEN			BIT(31)
+#define  HSBP(x)		FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  HSYNC(x)		FIELD_PREP(GENMASK(13, 0), ((x) - 1))
+
+#define VTCFG1			0x14
+#define  VTOTAL(x)		FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  VACT(x)		FIELD_PREP(GENMASK(13, 0), (x))
+
+#define VTCFG2			0x18
+#define  VSEN			BIT(31)
+#define  VSBP(x)		FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  VSYNC(x)		FIELD_PREP(GENMASK(13, 0), ((x) - 1))
+
+#define PKICKCONFIG		0x2c
+#define SKICKCONFIG		0x30
+#define  EN			BIT(31)
+#define  ROW(x)			FIELD_PREP(GENMASK(29, 16), (x))
+#define  COL(x)			FIELD_PREP(GENMASK(13, 0), (x))
+
+#define PACFG			0x54
+#define SACFG			0x58
+#define  STARTY(x)		FIELD_PREP(GENMASK(29, 16), ((x) + 1))
+#define  STARTX(x)		FIELD_PREP(GENMASK(13, 0), ((x) + 1))
+
+#define FGINCTRL		0x5c
+#define FGINCTRLPANIC		0x60
+#define  FGDM_MASK		GENMASK(2, 0)
+#define  ENPRIMALPHA		BIT(3)
+#define  ENSECALPHA		BIT(4)
+
+#define FGCCR			0x64
+#define  CCGREEN(x)		FIELD_PREP(GENMASK(19, 10), (x))
+
+#define FGENABLE		0x68
+#define  FGEN			BIT(0)
+
+#define FGSLR			0x6c
+#define  SHDTOKGEN		BIT(0)
+
+#define FGTIMESTAMP		0x74
+#define  FRAMEINDEX(x)		FIELD_GET(GENMASK(31, 14), (x))
+#define  LINEINDEX(x)		FIELD_GET(GENMASK(13, 0), (x))
+
+#define FGCHSTAT		0x78
+#define  SECSYNCSTAT		BIT(24)
+#define  SFIFOEMPTY		BIT(16)
+
+#define FGCHSTATCLR		0x7c
+#define  CLRSECSTAT		BIT(16)
+
+enum dc_fg_syncmode {
+	FG_SYNCMODE_OFF,	/* No side-by-side synchronization. */
+};
+
+enum dc_fg_dm {
+	FG_DM_CONSTCOL = 0x1,	/* Constant Color Background is shown. */
+	FG_DM_SEC_ON_TOP = 0x5,	/* Both inputs overlaid with secondary on top. */
+};
+
+static const struct regmap_range dc_fg_regmap_wr_ranges[] = {
+	regmap_reg_range(FGSTCTRL, VTCFG2),
+	regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
+	regmap_reg_range(PACFG, FGSLR),
+	regmap_reg_range(FGCHSTATCLR, FGCHSTATCLR),
+};
+
+static const struct regmap_range dc_fg_regmap_rd_ranges[] = {
+	regmap_reg_range(FGSTCTRL, VTCFG2),
+	regmap_reg_range(PKICKCONFIG, SKICKCONFIG),
+	regmap_reg_range(PACFG, FGSLR),
+	regmap_reg_range(FGTIMESTAMP, FGCHSTATCLR),
+};
+
+static const struct regmap_access_table dc_fg_regmap_wr_table = {
+	.yes_ranges = dc_fg_regmap_wr_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_wr_ranges),
+};
+
+static const struct regmap_access_table dc_fg_regmap_rd_table = {
+	.yes_ranges = dc_fg_regmap_rd_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_fg_regmap_rd_ranges),
+};
+
+static const struct regmap_config dc_fg_regmap_config = {
+	.name = "framegen",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_fg_regmap_wr_table,
+	.rd_table = &dc_fg_regmap_rd_table,
+};
+
+static inline void dc_fg_enable_shden(struct dc_fg *fg)
+{
+	regmap_write_bits(fg->reg, FGSTCTRL, SHDEN, SHDEN);
+}
+
+static inline void dc_fg_syncmode(struct dc_fg *fg, enum dc_fg_syncmode mode)
+{
+	regmap_write_bits(fg->reg, FGSTCTRL, FGSYNCMODE_MASK, FGSYNCMODE(mode));
+}
+
+void dc_fg_cfg_videomode(struct dc_fg *fg, struct drm_display_mode *m)
+{
+	u32 hact, htotal, hsync, hsbp;
+	u32 vact, vtotal, vsync, vsbp;
+	u32 kick_row, kick_col;
+	int ret;
+
+	hact = m->crtc_hdisplay;
+	htotal = m->crtc_htotal;
+	hsync = m->crtc_hsync_end - m->crtc_hsync_start;
+	hsbp = m->crtc_htotal - m->crtc_hsync_start;
+
+	vact = m->crtc_vdisplay;
+	vtotal = m->crtc_vtotal;
+	vsync = m->crtc_vsync_end - m->crtc_vsync_start;
+	vsbp = m->crtc_vtotal - m->crtc_vsync_start;
+
+	/* video mode */
+	regmap_write(fg->reg, HTCFG1, HACT(hact)   | HTOTAL(htotal));
+	regmap_write(fg->reg, HTCFG2, HSYNC(hsync) | HSBP(hsbp) | HSEN);
+	regmap_write(fg->reg, VTCFG1, VACT(vact)   | VTOTAL(vtotal));
+	regmap_write(fg->reg, VTCFG2, VSYNC(vsync) | VSBP(vsbp) | VSEN);
+
+	kick_col = hact + 1;
+	kick_row = vact;
+
+	/* pkickconfig */
+	regmap_write(fg->reg, PKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+	/* skikconfig */
+	regmap_write(fg->reg, SKICKCONFIG, COL(kick_col) | ROW(kick_row) | EN);
+
+	/* primary and secondary area position configuration */
+	regmap_write(fg->reg, PACFG, STARTX(0) | STARTY(0));
+	regmap_write(fg->reg, SACFG, STARTX(0) | STARTY(0));
+
+	/* alpha */
+	regmap_write_bits(fg->reg, FGINCTRL,      ENPRIMALPHA | ENSECALPHA, 0);
+	regmap_write_bits(fg->reg, FGINCTRLPANIC, ENPRIMALPHA | ENSECALPHA, 0);
+
+	/* constant color is green(used in panic mode)  */
+	regmap_write(fg->reg, FGCCR, CCGREEN(0x3ff));
+
+	ret = clk_set_rate(fg->clk_disp, m->clock * HZ_PER_KHZ);
+	if (ret < 0)
+		dev_err(fg->dev, "failed to set display clock rate: %d\n", ret);
+}
+
+static inline void dc_fg_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+	regmap_write_bits(fg->reg, FGINCTRL, FGDM_MASK, mode);
+}
+
+static inline void dc_fg_panic_displaymode(struct dc_fg *fg, enum dc_fg_dm mode)
+{
+	regmap_write_bits(fg->reg, FGINCTRLPANIC, FGDM_MASK, mode);
+}
+
+void dc_fg_enable(struct dc_fg *fg)
+{
+	regmap_write(fg->reg, FGENABLE, FGEN);
+}
+
+void dc_fg_disable(struct dc_fg *fg)
+{
+	regmap_write(fg->reg, FGENABLE, 0);
+}
+
+void dc_fg_shdtokgen(struct dc_fg *fg)
+{
+	regmap_write(fg->reg, FGSLR, SHDTOKGEN);
+}
+
+u32 dc_fg_get_frame_index(struct dc_fg *fg)
+{
+	u32 val;
+
+	regmap_read(fg->reg, FGTIMESTAMP, &val);
+
+	return FRAMEINDEX(val);
+}
+
+u32 dc_fg_get_line_index(struct dc_fg *fg)
+{
+	u32 val;
+
+	regmap_read(fg->reg, FGTIMESTAMP, &val);
+
+	return LINEINDEX(val);
+}
+
+bool dc_fg_wait_for_frame_index_moving(struct dc_fg *fg)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(100);
+	u32 frame_index, last_frame_index;
+
+	frame_index = dc_fg_get_frame_index(fg);
+	do {
+		last_frame_index = frame_index;
+		frame_index = dc_fg_get_frame_index(fg);
+	} while (last_frame_index == frame_index &&
+		 time_before(jiffies, timeout));
+
+	return last_frame_index != frame_index;
+}
+
+bool dc_fg_secondary_requests_to_read_empty_fifo(struct dc_fg *fg)
+{
+	u32 val;
+
+	regmap_read(fg->reg, FGCHSTAT, &val);
+
+	return !!(val & SFIFOEMPTY);
+}
+
+void dc_fg_secondary_clear_channel_status(struct dc_fg *fg)
+{
+	regmap_write(fg->reg, FGCHSTATCLR, CLRSECSTAT);
+}
+
+int dc_fg_wait_for_secondary_syncup(struct dc_fg *fg)
+{
+	unsigned int val;
+
+	return regmap_read_poll_timeout(fg->reg, FGCHSTAT, val,
+					val & SECSYNCSTAT, 5, 100000);
+}
+
+void dc_fg_enable_clock(struct dc_fg *fg)
+{
+	int ret;
+
+	ret = clk_prepare_enable(fg->clk_disp);
+	if (ret)
+		dev_err(fg->dev, "failed to enable display clock: %d\n", ret);
+}
+
+void dc_fg_disable_clock(struct dc_fg *fg)
+{
+	clk_disable_unprepare(fg->clk_disp);
+}
+
+enum drm_mode_status dc_fg_check_clock(struct dc_fg *fg, int clk_khz)
+{
+	unsigned long rounded_rate;
+
+	rounded_rate = clk_round_rate(fg->clk_disp, clk_khz * HZ_PER_KHZ);
+
+	if (rounded_rate != clk_khz * HZ_PER_KHZ)
+		return MODE_NOCLOCK;
+
+	return MODE_OK;
+}
+
+void dc_fg_init(struct dc_fg *fg)
+{
+	dc_fg_enable_shden(fg);
+	dc_fg_syncmode(fg, FG_SYNCMODE_OFF);
+	dc_fg_displaymode(fg, FG_DM_SEC_ON_TOP);
+	dc_fg_panic_displaymode(fg, FG_DM_CONSTCOL);
+}
+
+static int dc_fg_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	void __iomem *base;
+	struct dc_fg *fg;
+	struct dc_de *de;
+	int id;
+
+	fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
+	if (!fg)
+		return -ENOMEM;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	fg->reg = devm_regmap_init_mmio(dev, base, &dc_fg_regmap_config);
+	if (IS_ERR(fg->reg))
+		return PTR_ERR(fg->reg);
+
+	fg->clk_disp = devm_clk_get(dev, NULL);
+	if (IS_ERR(fg->clk_disp))
+		return dev_err_probe(dev, PTR_ERR(fg->clk_disp),
+				     "failed to get display clock\n");
+
+	id = of_alias_get_id(dev->of_node, "dc0-framegen");
+	if (id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", id);
+		return id;
+	}
+
+	fg->dev = dev;
+
+	de = dc_drm->de[id];
+	de->fg = fg;
+
+	return 0;
+}
+
+static const struct component_ops dc_fg_ops = {
+	.bind = dc_fg_bind,
+};
+
+static int dc_fg_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_fg_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_fg_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_fg_ops);
+}
+
+static const struct of_device_id dc_fg_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-framegen", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_fg_dt_ids);
+
+struct platform_driver dc_fg_driver = {
+	.probe = dc_fg_probe,
+	.remove = dc_fg_remove,
+	.driver = {
+		.name = "imx8-dc-framegen",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_fg_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-tc.c b/drivers/gpu/drm/imx/dc/dc-tc.c
new file mode 100644
index 000000000000..66e0efbc90ab
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-tc.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "dc-drv.h"
+#include "dc-de.h"
+
+#define TCON_CTRL	0x410
+#define  CTRL_RST_VAL	0x01401408
+
+/* red: MAPBIT 29-20, green: MAPBIT 19-10, blue: MAPBIT 9-0 */
+#define MAPBIT3_0	0x418
+#define MAPBIT7_4	0x41c
+#define MAPBIT11_8	0x420
+#define MAPBIT15_12	0x424
+#define MAPBIT19_16	0x428
+#define MAPBIT23_20	0x42c
+#define MAPBIT27_24	0x430
+#define MAPBIT31_28	0x434
+
+static const struct regmap_range dc_tc_regmap_ranges[] = {
+	regmap_reg_range(TCON_CTRL, TCON_CTRL),
+	regmap_reg_range(MAPBIT3_0, MAPBIT31_28),
+};
+
+static const struct regmap_access_table dc_tc_regmap_access_table = {
+	.yes_ranges = dc_tc_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_tc_regmap_ranges),
+};
+
+static const struct regmap_config dc_tc_regmap_config = {
+	.name = "tcon",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_tc_regmap_access_table,
+	.rd_table = &dc_tc_regmap_access_table,
+};
+
+/*
+ * The pixels reach TCON are always in 30-bit BGR format.
+ * The first bridge always receives pixels in 30-bit RGB format.
+ * So, map the format to MEDIA_BUS_FMT_RGB101010_1X30.
+ */
+static const u32 dc_tc_mapbit[] = {
+	0x17161514, 0x1b1a1918, 0x0b0a1d1c, 0x0f0e0d0c,
+	0x13121110, 0x03020100, 0x07060504, 0x00000908,
+};
+
+void dc_tc_init(struct dc_tc *tc)
+{
+	/* reset TCON_CTRL to POR default so that TCON works in bypass mode */
+	regmap_write(tc->reg, TCON_CTRL, CTRL_RST_VAL);
+
+	/* set format */
+	regmap_bulk_write(tc->reg, MAPBIT3_0, dc_tc_mapbit,
+			  ARRAY_SIZE(dc_tc_mapbit));
+}
+
+static int dc_tc_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	void __iomem *base;
+	struct dc_tc *tc;
+	struct dc_de *de;
+	int id;
+
+	tc = devm_kzalloc(dev, sizeof(*tc), GFP_KERNEL);
+	if (!tc)
+		return -ENOMEM;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+
+	tc->reg = devm_regmap_init_mmio(dev, base, &dc_tc_regmap_config);
+	if (IS_ERR(tc->reg))
+		return PTR_ERR(tc->reg);
+
+	id = of_alias_get_id(dev->of_node, "dc0-tcon");
+	if (id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", id);
+		return id;
+	}
+
+	de = dc_drm->de[id];
+	de->tc = tc;
+	de->tc->dev = dev;
+
+	return 0;
+}
+
+static const struct component_ops dc_tc_ops = {
+	.bind = dc_tc_bind,
+};
+
+static int dc_tc_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_tc_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_tc_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_tc_ops);
+}
+
+static const struct of_device_id dc_tc_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-tcon", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_tc_dt_ids);
+
+struct platform_driver dc_tc_driver = {
+	.probe = dc_tc_probe,
+	.remove = dc_tc_remove,
+	.driver = {
+		.name = "imx8-dc-tcon",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_tc_dt_ids,
+	},
+};
-- 
2.34.1



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

* [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (8 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-12  8:02   ` Maxime Ripard
  2024-12-09  3:39 ` [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller Liu Ying
                   ` (8 subsequent siblings)
  18 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller pixel engine consists of all processing
units that operate in the AXI bus clock domain.  Add drivers for
ConstFrame, ExtDst, FetchLayer, FetchWarp and LayerBlend units, as
well as a pixel engine driver, so that two displays with primary
planes can be supported.  The pixel engine driver and those unit
drivers are components to be aggregated by a master registered in
the upcoming DRM driver.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* Fix build warning by expanding sizeof(fu->name) from 13 to 21.
  (kernel test robot)

v5:
* Replace .remove_new with .remove in dc-{cf,de,fl,fw,lb,pe}.c. (Uwe)
* Fix commit message to state that pixel engine driver is a component driver
  instead of a master/aggregate driver.

v4:
* Use regmap to define register map for all registers. (Dmitry)
* Use regmap APIs to access registers. (Dmitry)
* Inline some small functions. (Dmitry)
* Move dc_lb_blendcontrol() function call from KMS routine to initialization
  stage. (Dmitry)
* Use devm_kzalloc() to drmm_kzalloc() to allocate dc_* data strutures.
* Drop unnecessary private struct dc_*_priv.
* Set suppress_bind_attrs driver flag to true to avoid unnecessary sys
  interfaces to bind/unbind the drivers.
* Make some fetch unit operations be aware of fractional fetch unit index(0-7).

v3:
* No change.

v2:
* Use OF alias id to get instance id.

 drivers/gpu/drm/imx/dc/Makefile |   3 +-
 drivers/gpu/drm/imx/dc/dc-cf.c  | 162 ++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-drv.c |   6 +
 drivers/gpu/drm/imx/dc/dc-drv.h |   8 +
 drivers/gpu/drm/imx/dc/dc-ed.c  | 270 +++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-fl.c  | 180 ++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-fu.c  | 268 +++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-fu.h  | 129 +++++++++++++
 drivers/gpu/drm/imx/dc/dc-fw.c  | 219 ++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-lb.c  | 319 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-pe.c  | 133 +++++++++++++
 drivers/gpu/drm/imx/dc/dc-pe.h  | 100 ++++++++++
 12 files changed, 1796 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/imx/dc/dc-cf.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-ed.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fl.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fu.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fu.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-fw.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-lb.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-pe.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-pe.h

diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
index 56de82d53d4d..2942ae6fd5bd 100644
--- a/drivers/gpu/drm/imx/dc/Makefile
+++ b/drivers/gpu/drm/imx/dc/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
-imx8-dc-drm-objs := dc-de.o dc-drv.o dc-fg.o dc-tc.o
+imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
+		    dc-fw.o dc-lb.o dc-pe.o dc-tc.o
 
 obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
diff --git a/drivers/gpu/drm/imx/dc/dc-cf.c b/drivers/gpu/drm/imx/dc/dc-cf.c
new file mode 100644
index 000000000000..266344054ac6
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-cf.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "dc-drv.h"
+#include "dc-pe.h"
+
+#define STATICCONTROL		0x8
+
+#define FRAMEDIMENSIONS		0xc
+#define  HEIGHT(x)		FIELD_PREP(GENMASK(29, 16), ((x) - 1))
+#define  WIDTH(x)		FIELD_PREP(GENMASK(13, 0), ((x) - 1))
+
+#define CONSTANTCOLOR		0x10
+#define  BLUE(x)		FIELD_PREP(GENMASK(15, 8), (x))
+
+static const struct regmap_range dc_cf_regmap_ranges[] = {
+	regmap_reg_range(STATICCONTROL, CONSTANTCOLOR),
+};
+
+static const struct regmap_access_table dc_cf_regmap_access_table = {
+	.yes_ranges = dc_cf_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_cf_regmap_ranges),
+};
+
+static const struct regmap_config dc_cf_cfg_regmap_config = {
+	.name = "constframe-cfg",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_cf_regmap_access_table,
+	.rd_table = &dc_cf_regmap_access_table,
+};
+
+static inline void dc_cf_enable_shden(struct dc_cf *cf)
+{
+	regmap_write(cf->reg_cfg, STATICCONTROL, SHDEN);
+}
+
+enum dc_link_id dc_cf_get_link_id(struct dc_cf *cf)
+{
+	return cf->link;
+}
+
+void dc_cf_framedimensions(struct dc_cf *cf, unsigned int w,
+			   unsigned int h)
+{
+	regmap_write(cf->reg_cfg, FRAMEDIMENSIONS, WIDTH(w) | HEIGHT(h));
+}
+
+void dc_cf_constantcolor_black(struct dc_cf *cf)
+{
+	regmap_write(cf->reg_cfg, CONSTANTCOLOR, 0);
+}
+
+void dc_cf_constantcolor_blue(struct dc_cf *cf)
+{
+	regmap_write(cf->reg_cfg, CONSTANTCOLOR, BLUE(0xff));
+}
+
+void dc_cf_init(struct dc_cf *cf)
+{
+	dc_cf_enable_shden(cf);
+}
+
+static int dc_cf_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe = dc_drm->pe;
+	void __iomem *base_cfg;
+	struct dc_cf *cf;
+	int id;
+
+	cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+	if (!cf)
+		return -ENOMEM;
+
+	base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+	if (IS_ERR(base_cfg))
+		return PTR_ERR(base_cfg);
+
+	cf->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
+					    &dc_cf_cfg_regmap_config);
+	if (IS_ERR(cf->reg_cfg))
+		return PTR_ERR(cf->reg_cfg);
+
+	id = of_alias_get_id(dev->of_node, "dc0-constframe");
+	if (id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", id);
+		return id;
+	}
+
+	switch (id) {
+	case 0:
+		pe->cf_cont[0] = cf;
+		cf->link = LINK_ID_CONSTFRAME0;
+		break;
+	case 1:
+		pe->cf_cont[1] = cf;
+		cf->link = LINK_ID_CONSTFRAME1;
+		break;
+	case 4:
+		pe->cf_safe[0] = cf;
+		cf->link = LINK_ID_CONSTFRAME4;
+		break;
+	case 5:
+		pe->cf_safe[1] = cf;
+		cf->link = LINK_ID_CONSTFRAME5;
+		break;
+	}
+
+	return 0;
+}
+
+static const struct component_ops dc_cf_ops = {
+	.bind = dc_cf_bind,
+};
+
+static int dc_cf_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_cf_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_cf_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_cf_ops);
+}
+
+static const struct of_device_id dc_cf_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-constframe", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_cf_dt_ids);
+
+struct platform_driver dc_cf_driver = {
+	.probe = dc_cf_probe,
+	.remove = dc_cf_remove,
+	.driver = {
+		.name = "imx8-dc-constframe",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_cf_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
index e5910a82dd4d..7c64acc863ad 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.c
+++ b/drivers/gpu/drm/imx/dc/dc-drv.c
@@ -9,8 +9,14 @@
 #include "dc-drv.h"
 
 static struct platform_driver * const dc_drivers[] = {
+	&dc_cf_driver,
 	&dc_de_driver,
+	&dc_ed_driver,
 	&dc_fg_driver,
+	&dc_fl_driver,
+	&dc_fw_driver,
+	&dc_lb_driver,
+	&dc_pe_driver,
 	&dc_tc_driver,
 };
 
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
index e1290d9a0a99..c687a36b8153 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.h
+++ b/drivers/gpu/drm/imx/dc/dc-drv.h
@@ -11,14 +11,22 @@
 #include <drm/drm_device.h>
 
 #include "dc-de.h"
+#include "dc-pe.h"
 
 struct dc_drm_device {
 	struct drm_device base;
 	struct dc_de *de[DC_DISPLAYS];
+	struct dc_pe *pe;
 };
 
+extern struct platform_driver dc_cf_driver;
+extern struct platform_driver dc_ed_driver;
 extern struct platform_driver dc_de_driver;
 extern struct platform_driver dc_fg_driver;
+extern struct platform_driver dc_fl_driver;
+extern struct platform_driver dc_fw_driver;
+extern struct platform_driver dc_lb_driver;
+extern struct platform_driver dc_pe_driver;
 extern struct platform_driver dc_tc_driver;
 
 #endif /* __DC_DRV_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-ed.c b/drivers/gpu/drm/imx/dc/dc-ed.c
new file mode 100644
index 000000000000..283777ca7be2
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-ed.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include "dc-drv.h"
+#include "dc-pe.h"
+
+#define PIXENGCFG_STATIC	0x8
+#define  POWERDOWN		BIT(4)
+#define  SYNC_MODE		BIT(8)
+#define  SINGLE			0
+#define  DIV_MASK		GENMASK(23, 16)
+#define  DIV(x)			FIELD_PREP(DIV_MASK, (x))
+#define  DIV_RESET		0x80
+
+#define PIXENGCFG_DYNAMIC	0xc
+
+#define PIXENGCFG_TRIGGER	0x14
+#define  SYNC_TRIGGER		BIT(0)
+
+#define STATICCONTROL		0x8
+#define  KICK_MODE		BIT(8)
+#define  EXTERNAL		BIT(8)
+#define  PERFCOUNTMODE		BIT(12)
+
+#define CONTROL			0xc
+#define  GAMMAAPPLYENABLE	BIT(0)
+
+static const struct regmap_range dc_ed_pec_regmap_access_ranges[] = {
+	regmap_reg_range(PIXENGCFG_STATIC, PIXENGCFG_STATIC),
+	regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
+	regmap_reg_range(PIXENGCFG_TRIGGER, PIXENGCFG_TRIGGER),
+};
+
+static const struct regmap_access_table dc_ed_pec_regmap_access_table = {
+	.yes_ranges = dc_ed_pec_regmap_access_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ed_pec_regmap_access_ranges),
+};
+
+static const struct regmap_range dc_ed_pec_regmap_volatile_ranges[] = {
+	regmap_reg_range(PIXENGCFG_TRIGGER, PIXENGCFG_TRIGGER),
+};
+
+static const struct regmap_access_table dc_ed_pec_regmap_volatile_table = {
+	.yes_ranges = dc_ed_pec_regmap_volatile_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ed_pec_regmap_volatile_ranges),
+};
+
+static const struct regmap_config dc_ed_pec_regmap_config = {
+	.name = "extdst-pec",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_ed_pec_regmap_access_table,
+	.rd_table = &dc_ed_pec_regmap_access_table,
+	.volatile_table = &dc_ed_pec_regmap_volatile_table,
+};
+
+static const struct regmap_range dc_ed_regmap_ranges[] = {
+	regmap_reg_range(STATICCONTROL, STATICCONTROL),
+	regmap_reg_range(CONTROL, CONTROL),
+};
+
+static const struct regmap_access_table dc_ed_regmap_access_table = {
+	.yes_ranges = dc_ed_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ed_regmap_ranges),
+};
+
+static const struct regmap_config dc_ed_cfg_regmap_config = {
+	.name = "extdst-cfg",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_ed_regmap_access_table,
+	.rd_table = &dc_ed_regmap_access_table,
+};
+
+static const enum dc_link_id src_sels[] = {
+	LINK_ID_NONE,
+	LINK_ID_CONSTFRAME0,
+	LINK_ID_CONSTFRAME1,
+	LINK_ID_CONSTFRAME4,
+	LINK_ID_CONSTFRAME5,
+	LINK_ID_LAYERBLEND3,
+	LINK_ID_LAYERBLEND2,
+	LINK_ID_LAYERBLEND1,
+	LINK_ID_LAYERBLEND0,
+};
+
+static inline void dc_ed_pec_enable_shden(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, SHDEN, SHDEN);
+}
+
+static inline void dc_ed_pec_poweron(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, POWERDOWN, 0);
+}
+
+static inline void dc_ed_pec_sync_mode_single(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, SYNC_MODE, SINGLE);
+}
+
+static inline void dc_ed_pec_div_reset(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_pec, PIXENGCFG_STATIC, DIV_MASK,
+			  DIV(DIV_RESET));
+}
+
+void dc_ed_pec_src_sel(struct dc_ed *ed, enum dc_link_id src)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(src_sels); i++) {
+		if (src_sels[i] == src) {
+			regmap_write(ed->reg_pec, PIXENGCFG_DYNAMIC, src);
+			return;
+		}
+	}
+}
+
+void dc_ed_pec_sync_trigger(struct dc_ed *ed)
+{
+	regmap_write(ed->reg_pec, PIXENGCFG_TRIGGER, SYNC_TRIGGER);
+}
+
+static inline void dc_ed_enable_shden(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
+}
+
+static inline void dc_ed_kick_mode_external(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_cfg, STATICCONTROL, KICK_MODE, EXTERNAL);
+}
+
+static inline void dc_ed_disable_perfcountmode(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_cfg, STATICCONTROL, PERFCOUNTMODE, 0);
+}
+
+static inline void dc_ed_disable_gamma_apply(struct dc_ed *ed)
+{
+	regmap_write_bits(ed->reg_cfg, CONTROL, GAMMAAPPLYENABLE, 0);
+}
+
+void dc_ed_init(struct dc_ed *ed)
+{
+	dc_ed_pec_src_sel(ed, LINK_ID_NONE);
+	dc_ed_pec_enable_shden(ed);
+	dc_ed_pec_poweron(ed);
+	dc_ed_pec_sync_mode_single(ed);
+	dc_ed_pec_div_reset(ed);
+	dc_ed_enable_shden(ed);
+	dc_ed_disable_perfcountmode(ed);
+	dc_ed_kick_mode_external(ed);
+	dc_ed_disable_gamma_apply(ed);
+}
+
+static int dc_ed_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe = dc_drm->pe;
+	void __iomem *base_pec;
+	void __iomem *base_cfg;
+	struct dc_ed *ed;
+	int id;
+
+	ed = devm_kzalloc(dev, sizeof(*ed), GFP_KERNEL);
+	if (!ed)
+		return -ENOMEM;
+
+	base_pec = devm_platform_ioremap_resource_byname(pdev, "pec");
+	if (IS_ERR(base_pec))
+		return PTR_ERR(base_pec);
+
+	base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+	if (IS_ERR(base_cfg))
+		return PTR_ERR(base_cfg);
+
+	ed->reg_pec = devm_regmap_init_mmio(dev, base_pec,
+					    &dc_ed_pec_regmap_config);
+	if (IS_ERR(ed->reg_pec))
+		return PTR_ERR(ed->reg_pec);
+
+	ed->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
+					    &dc_ed_cfg_regmap_config);
+	if (IS_ERR(ed->reg_cfg))
+		return PTR_ERR(ed->reg_cfg);
+
+	ed->irq_shdld = platform_get_irq_byname(pdev, "shdload");
+	if (ed->irq_shdld < 0)
+		return ed->irq_shdld;
+
+	id = of_alias_get_id(dev->of_node, "dc0-extdst");
+	if (id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", id);
+		return id;
+	}
+
+	ed->dev = dev;
+
+	switch (id) {
+	case 0:
+		pe->ed_cont[0] = ed;
+		break;
+	case 1:
+		pe->ed_cont[1] = ed;
+		break;
+	case 4:
+		pe->ed_safe[0] = ed;
+		break;
+	case 5:
+		pe->ed_safe[1] = ed;
+		break;
+	}
+
+	return 0;
+}
+
+static const struct component_ops dc_ed_ops = {
+	.bind = dc_ed_bind,
+};
+
+static int dc_ed_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_ed_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_ed_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_ed_ops);
+}
+
+static const struct of_device_id dc_ed_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-extdst", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_ed_dt_ids);
+
+struct platform_driver dc_ed_driver = {
+	.probe = dc_ed_probe,
+	.remove = dc_ed_remove,
+	.driver = {
+		.name = "imx8-dc-extdst",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_ed_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-fl.c b/drivers/gpu/drm/imx/dc/dc-fl.c
new file mode 100644
index 000000000000..7797ea59278a
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fl.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "dc-drv.h"
+#include "dc-fu.h"
+#include "dc-pe.h"
+
+#define BASEADDRESS(x)			(0x10 + FRAC_OFFSET * (x))
+#define SOURCEBUFFERATTRIBUTES(x)	(0x14 + FRAC_OFFSET * (x))
+#define SOURCEBUFFERDIMENSION(x)	(0x18 + FRAC_OFFSET * (x))
+#define COLORCOMPONENTBITS(x)		(0x1c + FRAC_OFFSET * (x))
+#define COLORCOMPONENTSHIFT(x)		(0x20 + FRAC_OFFSET * (x))
+#define LAYEROFFSET(x)			(0x24 + FRAC_OFFSET * (x))
+#define CLIPWINDOWOFFSET(x)		(0x28 + FRAC_OFFSET * (x))
+#define CLIPWINDOWDIMENSIONS(x)		(0x2c + FRAC_OFFSET * (x))
+#define CONSTANTCOLOR(x)		(0x30 + FRAC_OFFSET * (x))
+#define LAYERPROPERTY(x)		(0x34 + FRAC_OFFSET * (x))
+#define FRAMEDIMENSIONS			0x150
+
+struct dc_fl {
+	struct dc_fu fu;
+	int id;
+};
+
+static const struct regmap_range dc_fl_regmap_ranges[] = {
+	regmap_reg_range(STATICCONTROL, FRAMEDIMENSIONS),
+};
+
+static const struct regmap_access_table dc_fl_regmap_access_table = {
+	.yes_ranges = dc_fl_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_fl_regmap_ranges),
+};
+
+static const struct regmap_config dc_fl_cfg_regmap_config = {
+	.name = "fetchlayer-cfg",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_fl_regmap_access_table,
+	.rd_table = &dc_fl_regmap_access_table,
+};
+
+static void dc_fl_set_fmt(struct dc_fu *fu, enum dc_fu_frac frac,
+			  const struct drm_format_info *format)
+{
+	u32 bits = 0, shifts = 0;
+
+	dc_fu_set_src_bpp(fu, frac, format->cpp[0] * 8);
+
+	regmap_write_bits(fu->reg_cfg, LAYERPROPERTY(frac),
+			  YUVCONVERSIONMODE_MASK,
+			  YUVCONVERSIONMODE(YUVCONVERSIONMODE_OFF));
+
+	dc_fu_get_pixel_format_bits(fu, format->format, &bits);
+	dc_fu_get_pixel_format_shifts(fu, format->format, &shifts);
+
+	regmap_write(fu->reg_cfg, COLORCOMPONENTBITS(frac), bits);
+	regmap_write(fu->reg_cfg, COLORCOMPONENTSHIFT(frac), shifts);
+}
+
+static void dc_fl_set_framedimensions(struct dc_fu *fu, int w, int h)
+{
+	regmap_write(fu->reg_cfg, FRAMEDIMENSIONS,
+		     FRAMEWIDTH(w) | FRAMEHEIGHT(h));
+}
+
+static void dc_fl_init(struct dc_fu *fu)
+{
+	dc_fu_common_hw_init(fu);
+	dc_fu_shdldreq_sticky(fu, 0xff);
+}
+
+static void dc_fl_set_ops(struct dc_fu *fu)
+{
+	memcpy(&fu->ops, &dc_fu_common_ops, sizeof(dc_fu_common_ops));
+	fu->ops.init = dc_fl_init;
+	fu->ops.set_fmt = dc_fl_set_fmt;
+	fu->ops.set_framedimensions = dc_fl_set_framedimensions;
+}
+
+static int dc_fl_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe = dc_drm->pe;
+	void __iomem *base_cfg;
+	struct dc_fl *fl;
+	struct dc_fu *fu;
+	int i;
+
+	fl = devm_kzalloc(dev, sizeof(*fl), GFP_KERNEL);
+	if (!fl)
+		return -ENOMEM;
+
+	fu = &fl->fu;
+
+	base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+	if (IS_ERR(base_cfg))
+		return PTR_ERR(base_cfg);
+
+	fu->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
+					    &dc_fl_cfg_regmap_config);
+	if (IS_ERR(fu->reg_cfg))
+		return PTR_ERR(fu->reg_cfg);
+
+	fl->id = of_alias_get_id(dev->of_node, "dc0-fetchlayer");
+	if (fl->id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", fl->id);
+		return fl->id;
+	}
+
+	fu->link_id = LINK_ID_FETCHLAYER0;
+	fu->id = DC_FETCHUNIT_FL0;
+	for (i = 0; i < DC_FETCHUNIT_FRAC_NUM; i++) {
+		fu->reg_baseaddr[i]		  = BASEADDRESS(i);
+		fu->reg_sourcebufferattributes[i] = SOURCEBUFFERATTRIBUTES(i);
+		fu->reg_sourcebufferdimension[i]  = SOURCEBUFFERDIMENSION(i);
+		fu->reg_layeroffset[i]		  = LAYEROFFSET(i);
+		fu->reg_clipwindowoffset[i]	  = CLIPWINDOWOFFSET(i);
+		fu->reg_clipwindowdimensions[i]	  = CLIPWINDOWDIMENSIONS(i);
+		fu->reg_constantcolor[i]	  = CONSTANTCOLOR(i);
+		fu->reg_layerproperty[i]	  = LAYERPROPERTY(i);
+	}
+	snprintf(fu->name, sizeof(fu->name), "FetchLayer%d", fl->id);
+
+	dc_fl_set_ops(fu);
+
+	pe->fu_disp[fu->id] = fu;
+
+	return 0;
+}
+
+static const struct component_ops dc_fl_ops = {
+	.bind = dc_fl_bind,
+};
+
+static int dc_fl_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_fl_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_fl_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_fl_ops);
+}
+
+static const struct of_device_id dc_fl_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-fetchlayer", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_fl_dt_ids);
+
+struct platform_driver dc_fl_driver = {
+	.probe = dc_fl_probe,
+	.remove = dc_fl_remove,
+	.driver = {
+		.name = "imx8-dc-fetchlayer",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_fl_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-fu.c b/drivers/gpu/drm/imx/dc/dc-fu.c
new file mode 100644
index 000000000000..dda8e6c52be8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fu.c
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/math.h>
+
+#include "dc-fu.h"
+#include "dc-pe.h"
+
+/* STATICCONTROL */
+#define SHDLDREQSTICKY_MASK		GENMASK(31, 24)
+#define SHDLDREQSTICKY(x)		FIELD_PREP(SHDLDREQSTICKY_MASK, (x))
+#define BASEADDRESSAUTOUPDATE_MASK	GENMASK(23, 16)
+#define BASEADDRESSAUTOUPDATE(x)	FIELD_PREP(BASEADDRESSAUTOUPDATE_MASK, (x))
+
+/* BURSTBUFFERMANAGEMENT */
+#define SETBURSTLENGTH_MASK		GENMASK(12, 8)
+#define SETBURSTLENGTH(x)		FIELD_PREP(SETBURSTLENGTH_MASK, (x))
+#define SETNUMBUFFERS_MASK		GENMASK(7, 0)
+#define SETNUMBUFFERS(x)		FIELD_PREP(SETNUMBUFFERS_MASK, (x))
+#define LINEMODE_MASK			BIT(31)
+
+/* SOURCEBUFFERATTRIBUTES */
+#define BITSPERPIXEL_MASK		GENMASK(21, 16)
+#define BITSPERPIXEL(x)			FIELD_PREP(BITSPERPIXEL_MASK, (x))
+#define STRIDE_MASK			GENMASK(15, 0)
+#define STRIDE(x)			FIELD_PREP(STRIDE_MASK, (x) - 1)
+
+/* SOURCEBUFFERDIMENSION */
+#define LINEWIDTH(x)			FIELD_PREP(GENMASK(13, 0), (x))
+#define LINECOUNT(x)			FIELD_PREP(GENMASK(29, 16), (x))
+
+/* LAYEROFFSET */
+#define LAYERXOFFSET(x)			FIELD_PREP(GENMASK(14, 0), (x))
+#define LAYERYOFFSET(x)			FIELD_PREP(GENMASK(30, 16), (x))
+
+/* CLIPWINDOWOFFSET */
+#define CLIPWINDOWXOFFSET(x)		FIELD_PREP(GENMASK(14, 0), (x))
+#define CLIPWINDOWYOFFSET(x)		FIELD_PREP(GENMASK(30, 16), (x))
+
+/* CLIPWINDOWDIMENSIONS */
+#define CLIPWINDOWWIDTH(x)		FIELD_PREP(GENMASK(13, 0), (x) - 1)
+#define CLIPWINDOWHEIGHT(x)		FIELD_PREP(GENMASK(29, 16), (x) - 1)
+
+static const enum dc_fu_frac dc_fetchunit_all_fracs[] = {
+	DC_FETCHUNIT_FRAC0, DC_FETCHUNIT_FRAC1,
+	DC_FETCHUNIT_FRAC2, DC_FETCHUNIT_FRAC3,
+	DC_FETCHUNIT_FRAC4, DC_FETCHUNIT_FRAC5,
+	DC_FETCHUNIT_FRAC6, DC_FETCHUNIT_FRAC7
+};
+
+enum dc_linemode {
+	/*
+	 * Mandatory setting for operation in the Display Controller.
+	 * Works also for Blit Engine with marginal performance impact.
+	 */
+	LINEMODE_DISPLAY = 0,
+};
+
+struct dc_fu_pixel_format {
+	u32 pixel_format;
+	u32 bits;
+	u32 shifts;
+};
+
+static const struct dc_fu_pixel_format pixel_formats[] = {
+	{
+		DRM_FORMAT_XRGB8888,
+		R_BITS(8)   | G_BITS(8)   | B_BITS(8)   | A_BITS(0),
+		R_SHIFT(16) | G_SHIFT(8)  | B_SHIFT(0)  | A_SHIFT(0),
+	},
+};
+
+void dc_fu_get_pixel_format_bits(struct dc_fu *fu, u32 format, u32 *bits)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
+		if (pixel_formats[i].pixel_format == format) {
+			*bits = pixel_formats[i].bits;
+			return;
+		}
+	}
+}
+
+void
+dc_fu_get_pixel_format_shifts(struct dc_fu *fu, u32 format, u32 *shifts)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pixel_formats); i++) {
+		if (pixel_formats[i].pixel_format == format) {
+			*shifts = pixel_formats[i].shifts;
+			return;
+		}
+	}
+}
+
+static inline void dc_fu_enable_shden(struct dc_fu *fu)
+{
+	regmap_write_bits(fu->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
+}
+
+static inline void dc_fu_baddr_autoupdate(struct dc_fu *fu, u8 layer_mask)
+{
+	regmap_write_bits(fu->reg_cfg, STATICCONTROL,
+			  BASEADDRESSAUTOUPDATE_MASK,
+			  BASEADDRESSAUTOUPDATE(layer_mask));
+}
+
+void dc_fu_shdldreq_sticky(struct dc_fu *fu, u8 layer_mask)
+{
+	regmap_write_bits(fu->reg_cfg, STATICCONTROL, SHDLDREQSTICKY_MASK,
+			  SHDLDREQSTICKY(layer_mask));
+}
+
+static inline void dc_fu_set_linemode(struct dc_fu *fu, enum dc_linemode mode)
+{
+	regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT, LINEMODE_MASK,
+			  mode);
+}
+
+static inline void dc_fu_set_numbuffers(struct dc_fu *fu, unsigned int num)
+{
+	regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT,
+			  SETNUMBUFFERS_MASK, SETNUMBUFFERS(num));
+}
+
+static void dc_fu_set_burstlength(struct dc_fu *fu, dma_addr_t baddr)
+{
+	unsigned int burst_size, burst_length;
+
+	burst_size = 1 << __ffs(baddr);
+	burst_size = round_up(burst_size, 8);
+	burst_size = min(burst_size, 128U);
+	burst_length = burst_size / 8;
+
+	regmap_write_bits(fu->reg_cfg, BURSTBUFFERMANAGEMENT,
+			  SETBURSTLENGTH_MASK, SETBURSTLENGTH(burst_length));
+}
+
+static void dc_fu_set_baseaddress(struct dc_fu *fu, enum dc_fu_frac frac,
+				  dma_addr_t baddr)
+{
+	regmap_write(fu->reg_cfg, fu->reg_baseaddr[frac], baddr);
+}
+
+void dc_fu_set_src_bpp(struct dc_fu *fu, enum dc_fu_frac frac, unsigned int bpp)
+{
+	regmap_write_bits(fu->reg_cfg, fu->reg_sourcebufferattributes[frac],
+			  BITSPERPIXEL_MASK, BITSPERPIXEL(bpp));
+}
+
+static void dc_fu_set_src_stride(struct dc_fu *fu, enum dc_fu_frac frac,
+				 unsigned int stride)
+{
+	regmap_write_bits(fu->reg_cfg, fu->reg_sourcebufferattributes[frac],
+			  STRIDE_MASK, STRIDE(stride));
+}
+
+static void dc_fu_set_src_buf_dimensions(struct dc_fu *fu, enum dc_fu_frac frac,
+					 int w, int h)
+{
+	regmap_write(fu->reg_cfg, fu->reg_sourcebufferdimension[frac],
+		     LINEWIDTH(w) | LINECOUNT(h));
+}
+
+static inline void dc_fu_layeroffset(struct dc_fu *fu, enum dc_fu_frac frac,
+				     unsigned int x, unsigned int y)
+{
+	regmap_write(fu->reg_cfg, fu->reg_layeroffset[frac],
+		     LAYERXOFFSET(x) | LAYERYOFFSET(y));
+}
+
+static inline void dc_fu_clipoffset(struct dc_fu *fu, enum dc_fu_frac frac,
+				    unsigned int x, unsigned int y)
+{
+	regmap_write(fu->reg_cfg, fu->reg_clipwindowoffset[frac],
+		     CLIPWINDOWXOFFSET(x) | CLIPWINDOWYOFFSET(y));
+}
+
+static inline void dc_fu_clipdimensions(struct dc_fu *fu, enum dc_fu_frac frac,
+					unsigned int w, unsigned int h)
+{
+	regmap_write(fu->reg_cfg, fu->reg_clipwindowdimensions[frac],
+		     CLIPWINDOWWIDTH(w) | CLIPWINDOWHEIGHT(h));
+}
+
+static inline void
+dc_fu_set_pixel_blend_mode(struct dc_fu *fu, enum dc_fu_frac frac)
+{
+	regmap_write(fu->reg_cfg, fu->reg_layerproperty[frac], 0);
+	regmap_write(fu->reg_cfg, fu->reg_constantcolor[frac], 0);
+}
+
+static void dc_fu_enable_src_buf(struct dc_fu *fu, enum dc_fu_frac frac)
+{
+	regmap_write_bits(fu->reg_cfg, fu->reg_layerproperty[frac],
+			  SOURCEBUFFERENABLE, SOURCEBUFFERENABLE);
+}
+
+static void dc_fu_disable_src_buf(struct dc_fu *fu, enum dc_fu_frac frac)
+{
+	regmap_write_bits(fu->reg_cfg, fu->reg_layerproperty[frac],
+			  SOURCEBUFFERENABLE, 0);
+
+	if (fu->lb) {
+		dc_lb_pec_clken(fu->lb, CLKEN_DISABLE);
+		dc_lb_mode(fu->lb, LB_NEUTRAL);
+	}
+}
+
+static void dc_fu_set_layerblend(struct dc_fu *fu, struct dc_lb *lb)
+{
+	fu->lb = lb;
+}
+
+static enum dc_link_id dc_fu_get_link_id(struct dc_fu *fu)
+{
+	return fu->link_id;
+}
+
+static const char *dc_fu_get_name(struct dc_fu *fu)
+{
+	return fu->name;
+}
+
+const struct dc_fu_ops dc_fu_common_ops = {
+	.set_burstlength	= dc_fu_set_burstlength,
+	.set_baseaddress	= dc_fu_set_baseaddress,
+	.set_src_stride		= dc_fu_set_src_stride,
+	.set_src_buf_dimensions = dc_fu_set_src_buf_dimensions,
+	.enable_src_buf		= dc_fu_enable_src_buf,
+	.disable_src_buf	= dc_fu_disable_src_buf,
+	.set_layerblend		= dc_fu_set_layerblend,
+	.get_link_id		= dc_fu_get_link_id,
+	.get_name		= dc_fu_get_name,
+};
+
+const struct dc_fu_ops *dc_fu_get_ops(struct dc_fu *fu)
+{
+	return &fu->ops;
+}
+
+void dc_fu_common_hw_init(struct dc_fu *fu)
+{
+	enum dc_fu_frac frac;
+	int i;
+
+	dc_fu_baddr_autoupdate(fu, 0x0);
+	dc_fu_enable_shden(fu);
+	dc_fu_set_linemode(fu, LINEMODE_DISPLAY);
+	dc_fu_set_numbuffers(fu, 16);
+
+	for (i = 0; i < ARRAY_SIZE(dc_fetchunit_all_fracs); i++) {
+		frac = dc_fetchunit_all_fracs[i];
+
+		dc_fu_layeroffset(fu, frac, 0, 0);
+		dc_fu_clipoffset(fu, frac, 0, 0);
+		dc_fu_clipdimensions(fu, frac, 1, 1);
+		dc_fu_disable_src_buf(fu, frac);
+		dc_fu_set_pixel_blend_mode(fu, frac);
+	}
+}
diff --git a/drivers/gpu/drm/imx/dc/dc-fu.h b/drivers/gpu/drm/imx/dc/dc-fu.h
new file mode 100644
index 000000000000..cab71965964e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fu.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_FETCHUNIT_H__
+#define __DC_FETCHUNIT_H__
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "dc-pe.h"
+
+#define FRAC_OFFSET			0x28
+
+#define STATICCONTROL			0x8
+#define BURSTBUFFERMANAGEMENT		0xc
+
+/* COLORCOMPONENTBITS */
+#define R_BITS(x)			FIELD_PREP_CONST(GENMASK(27, 24), (x))
+#define G_BITS(x)			FIELD_PREP_CONST(GENMASK(19, 16), (x))
+#define B_BITS(x)			FIELD_PREP_CONST(GENMASK(11, 8), (x))
+#define A_BITS(x)			FIELD_PREP_CONST(GENMASK(3, 0), (x))
+
+/* COLORCOMPONENTSHIFT */
+#define R_SHIFT(x)			FIELD_PREP_CONST(GENMASK(28, 24), (x))
+#define G_SHIFT(x)			FIELD_PREP_CONST(GENMASK(20, 16), (x))
+#define B_SHIFT(x)			FIELD_PREP_CONST(GENMASK(12, 8), (x))
+#define A_SHIFT(x)			FIELD_PREP_CONST(GENMASK(4, 0), (x))
+
+/* LAYERPROPERTY */
+#define YUVCONVERSIONMODE_MASK		GENMASK(18, 17)
+#define YUVCONVERSIONMODE(x)		FIELD_PREP(YUVCONVERSIONMODE_MASK, (x))
+#define SOURCEBUFFERENABLE		BIT(31)
+
+/* FRAMEDIMENSIONS */
+#define FRAMEWIDTH(x)			FIELD_PREP(GENMASK(13, 0), (x))
+#define FRAMEHEIGHT(x)			FIELD_PREP(GENMASK(29, 16), (x))
+
+/* CONTROL */
+#define INPUTSELECT_MASK		GENMASK(4, 3)
+#define INPUTSELECT(x)			FIELD_PREP(INPUTSELECT_MASK, (x))
+#define RASTERMODE_MASK			GENMASK(2, 0)
+#define RASTERMODE(x)			FIELD_PREP(RASTERMODE_MASK, (x))
+
+enum dc_yuvconversionmode {
+	YUVCONVERSIONMODE_OFF,
+};
+
+enum dc_inputselect {
+	INPUTSELECT_INACTIVE,
+};
+
+enum dc_rastermode {
+	RASTERMODE_NORMAL,
+};
+
+enum {
+	DC_FETCHUNIT_FL0,
+	DC_FETCHUNIT_FW2,
+};
+
+enum dc_fu_frac {
+	DC_FETCHUNIT_FRAC0,
+	DC_FETCHUNIT_FRAC1,
+	DC_FETCHUNIT_FRAC2,
+	DC_FETCHUNIT_FRAC3,
+	DC_FETCHUNIT_FRAC4,
+	DC_FETCHUNIT_FRAC5,
+	DC_FETCHUNIT_FRAC6,
+	DC_FETCHUNIT_FRAC7,
+	DC_FETCHUNIT_FRAC_NUM,
+};
+
+struct dc_fu;
+struct dc_lb;
+
+struct dc_fu_ops {
+	void (*init)(struct dc_fu *fu);
+	void (*set_burstlength)(struct dc_fu *fu, dma_addr_t baddr);
+	void (*set_baseaddress)(struct dc_fu *fu, enum dc_fu_frac frac,
+				dma_addr_t baddr);
+	void (*set_src_stride)(struct dc_fu *fu, enum dc_fu_frac frac,
+			       unsigned int stride);
+	void (*set_src_buf_dimensions)(struct dc_fu *fu, enum dc_fu_frac frac,
+				       int w, int h);
+	void (*set_fmt)(struct dc_fu *fu, enum dc_fu_frac frac,
+			const struct drm_format_info *format);
+	void (*enable_src_buf)(struct dc_fu *fu, enum dc_fu_frac frac);
+	void (*disable_src_buf)(struct dc_fu *fu, enum dc_fu_frac frac);
+	void (*set_framedimensions)(struct dc_fu *fu, int w, int h);
+	void (*set_layerblend)(struct dc_fu *fu, struct dc_lb *lb);
+	enum dc_link_id (*get_link_id)(struct dc_fu *fu);
+	const char *(*get_name)(struct dc_fu *fu);
+};
+
+struct dc_fu {
+	struct regmap *reg_pec;
+	struct regmap *reg_cfg;
+	char name[21];
+	u32 reg_baseaddr[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_sourcebufferattributes[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_sourcebufferdimension[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_layeroffset[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_clipwindowoffset[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_clipwindowdimensions[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_constantcolor[DC_FETCHUNIT_FRAC_NUM];
+	u32 reg_layerproperty[DC_FETCHUNIT_FRAC_NUM];
+	unsigned int id;
+	enum dc_link_id link_id;
+	struct dc_fu_ops ops;
+	struct dc_lb *lb;
+};
+
+extern const struct dc_fu_ops dc_fu_common_ops;
+
+void dc_fu_get_pixel_format_bits(struct dc_fu *fu, u32 format, u32 *bits);
+void dc_fu_get_pixel_format_shifts(struct dc_fu *fu, u32 format, u32 *shifts);
+void dc_fu_shdldreq_sticky(struct dc_fu *fu, u8 layer_mask);
+void dc_fu_set_src_bpp(struct dc_fu *fu, enum dc_fu_frac frac, unsigned int bpp);
+void dc_fu_common_hw_init(struct dc_fu *fu);
+
+const struct dc_fu_ops *dc_fu_get_ops(struct dc_fu *fu);
+
+#endif /* __DC_FETCHUNIT_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-fw.c b/drivers/gpu/drm/imx/dc/dc-fw.c
new file mode 100644
index 000000000000..212302b026c0
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-fw.c
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "dc-drv.h"
+#include "dc-fu.h"
+#include "dc-pe.h"
+
+#define PIXENGCFG_DYNAMIC		0x8
+
+#define BASEADDRESS(x)			(0x10 + FRAC_OFFSET * (x))
+#define SOURCEBUFFERATTRIBUTES(x)	(0x14 + FRAC_OFFSET * (x))
+#define SOURCEBUFFERDIMENSION(x)	(0x18 + FRAC_OFFSET * (x))
+#define COLORCOMPONENTBITS(x)		(0x1c + FRAC_OFFSET * (x))
+#define COLORCOMPONENTSHIFT(x)		(0x20 + FRAC_OFFSET * (x))
+#define LAYEROFFSET(x)			(0x24 + FRAC_OFFSET * (x))
+#define CLIPWINDOWOFFSET(x)		(0x28 + FRAC_OFFSET * (x))
+#define CLIPWINDOWDIMENSIONS(x)		(0x2c + FRAC_OFFSET * (x))
+#define CONSTANTCOLOR(x)		(0x30 + FRAC_OFFSET * (x))
+#define LAYERPROPERTY(x)		(0x34 + FRAC_OFFSET * (x))
+#define FRAMEDIMENSIONS			0x150
+#define CONTROL				0x170
+
+struct dc_fw {
+	struct dc_fu fu;
+	int id;
+};
+
+static const struct regmap_range dc_fw_pec_regmap_access_ranges[] = {
+	regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
+};
+
+static const struct regmap_access_table dc_fw_pec_regmap_access_table = {
+	.yes_ranges = dc_fw_pec_regmap_access_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_fw_pec_regmap_access_ranges),
+};
+
+static const struct regmap_config dc_fw_pec_regmap_config = {
+	.name = "fetchwarp-pec",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_fw_pec_regmap_access_table,
+	.rd_table = &dc_fw_pec_regmap_access_table,
+};
+
+static const struct regmap_range dc_fw_regmap_ranges[] = {
+	regmap_reg_range(STATICCONTROL, FRAMEDIMENSIONS),
+	regmap_reg_range(CONTROL, CONTROL),
+};
+
+static const struct regmap_access_table dc_fw_regmap_access_table = {
+	.yes_ranges = dc_fw_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_fw_regmap_ranges),
+};
+
+static const struct regmap_config dc_fw_cfg_regmap_config = {
+	.name = "fetchwarp-cfg",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_fw_regmap_access_table,
+	.rd_table = &dc_fw_regmap_access_table,
+};
+
+static void dc_fw_set_fmt(struct dc_fu *fu, enum dc_fu_frac frac,
+			  const struct drm_format_info *format)
+{
+	u32 bits = 0, shifts = 0;
+
+	dc_fu_set_src_bpp(fu, frac, format->cpp[0] * 8);
+
+	regmap_write_bits(fu->reg_cfg, CONTROL, INPUTSELECT_MASK,
+			  INPUTSELECT(INPUTSELECT_INACTIVE));
+	regmap_write_bits(fu->reg_cfg, CONTROL, RASTERMODE_MASK,
+			  RASTERMODE(RASTERMODE_NORMAL));
+
+	regmap_write_bits(fu->reg_cfg, LAYERPROPERTY(frac),
+			  YUVCONVERSIONMODE_MASK,
+			  YUVCONVERSIONMODE(YUVCONVERSIONMODE_OFF));
+
+	dc_fu_get_pixel_format_bits(fu, format->format, &bits);
+	dc_fu_get_pixel_format_shifts(fu, format->format, &shifts);
+
+	regmap_write(fu->reg_cfg, COLORCOMPONENTBITS(frac), bits);
+	regmap_write(fu->reg_cfg, COLORCOMPONENTSHIFT(frac), shifts);
+}
+
+static void dc_fw_set_framedimensions(struct dc_fu *fu, int w, int h)
+{
+	regmap_write(fu->reg_cfg, FRAMEDIMENSIONS,
+		     FRAMEWIDTH(w) | FRAMEHEIGHT(h));
+}
+
+static void dc_fw_init(struct dc_fu *fu)
+{
+	regmap_write(fu->reg_pec, PIXENGCFG_DYNAMIC, LINK_ID_NONE);
+	dc_fu_common_hw_init(fu);
+	dc_fu_shdldreq_sticky(fu, 0xff);
+}
+
+static void dc_fw_set_ops(struct dc_fu *fu)
+{
+	memcpy(&fu->ops, &dc_fu_common_ops, sizeof(dc_fu_common_ops));
+	fu->ops.init = dc_fw_init;
+	fu->ops.set_fmt	= dc_fw_set_fmt;
+	fu->ops.set_framedimensions = dc_fw_set_framedimensions;
+}
+
+static int dc_fw_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe = dc_drm->pe;
+	void __iomem *base_pec;
+	void __iomem *base_cfg;
+	struct dc_fw *fw;
+	struct dc_fu *fu;
+	int i;
+
+	fw = devm_kzalloc(dev, sizeof(*fw), GFP_KERNEL);
+	if (!fw)
+		return -ENOMEM;
+
+	fu = &fw->fu;
+
+	base_pec = devm_platform_ioremap_resource_byname(pdev, "pec");
+	if (IS_ERR(base_pec))
+		return PTR_ERR(base_pec);
+
+	base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+	if (IS_ERR(base_cfg))
+		return PTR_ERR(base_cfg);
+
+	fu->reg_pec = devm_regmap_init_mmio(dev, base_pec,
+					    &dc_fw_pec_regmap_config);
+	if (IS_ERR(fu->reg_pec))
+		return PTR_ERR(fu->reg_pec);
+
+	fu->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
+					    &dc_fw_cfg_regmap_config);
+	if (IS_ERR(fu->reg_cfg))
+		return PTR_ERR(fu->reg_cfg);
+
+	fw->id = of_alias_get_id(dev->of_node, "dc0-fetchwarp");
+	if (fw->id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", fw->id);
+		return fw->id;
+	}
+
+	fu->link_id = LINK_ID_FETCHWARP2;
+	fu->id = DC_FETCHUNIT_FW2;
+	for (i = 0; i < DC_FETCHUNIT_FRAC_NUM; i++) {
+		fu->reg_baseaddr[i]		  = BASEADDRESS(i);
+		fu->reg_sourcebufferattributes[i] = SOURCEBUFFERATTRIBUTES(i);
+		fu->reg_sourcebufferdimension[i]  = SOURCEBUFFERDIMENSION(i);
+		fu->reg_layeroffset[i]		  = LAYEROFFSET(i);
+		fu->reg_clipwindowoffset[i]	  = CLIPWINDOWOFFSET(i);
+		fu->reg_clipwindowdimensions[i]	  = CLIPWINDOWDIMENSIONS(i);
+		fu->reg_constantcolor[i]	  = CONSTANTCOLOR(i);
+		fu->reg_layerproperty[i]	  = LAYERPROPERTY(i);
+	}
+	snprintf(fu->name, sizeof(fu->name), "FetchWarp%u", fw->id);
+
+	dc_fw_set_ops(fu);
+
+	pe->fu_disp[fu->id] = fu;
+
+	return 0;
+}
+
+static const struct component_ops dc_fw_ops = {
+	.bind = dc_fw_bind,
+};
+
+static int dc_fw_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_fw_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_fw_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_fw_ops);
+}
+
+static const struct of_device_id dc_fw_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-fetchwarp", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_fw_dt_ids);
+
+struct platform_driver dc_fw_driver = {
+	.probe = dc_fw_probe,
+	.remove = dc_fw_remove,
+	.driver = {
+		.name = "imx8-dc-fetchwarp",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_fw_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-lb.c b/drivers/gpu/drm/imx/dc/dc-lb.c
new file mode 100644
index 000000000000..af35d377126b
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-lb.c
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_blend.h>
+
+#include "dc-drv.h"
+#include "dc-pe.h"
+
+#define PIXENGCFG_DYNAMIC			0x8
+#define  PIXENGCFG_DYNAMIC_PRIM_SEL_MASK	GENMASK(5, 0)
+#define  PIXENGCFG_DYNAMIC_PRIM_SEL(x)		\
+		FIELD_PREP(PIXENGCFG_DYNAMIC_PRIM_SEL_MASK, (x))
+#define  PIXENGCFG_DYNAMIC_SEC_SEL_MASK		GENMASK(13, 8)
+#define  PIXENGCFG_DYNAMIC_SEC_SEL(x)		\
+		FIELD_PREP(PIXENGCFG_DYNAMIC_SEC_SEL_MASK, (x))
+
+#define STATICCONTROL				0x8
+#define  SHDTOKSEL_MASK				GENMASK(4, 3)
+#define  SHDTOKSEL(x)				FIELD_PREP(SHDTOKSEL_MASK, (x))
+#define  SHDLDSEL_MASK				GENMASK(2, 1)
+#define  SHDLDSEL(x)				FIELD_PREP(SHDLDSEL_MASK, (x))
+
+#define CONTROL					0xc
+#define  CTRL_MODE_MASK				BIT(0)
+#define  CTRL_MODE(x)				FIELD_PREP(CTRL_MODE_MASK, (x))
+
+#define BLENDCONTROL				0x10
+#define  ALPHA_MASK				GENMASK(23, 16)
+#define  ALPHA(x)				FIELD_PREP(ALPHA_MASK, (x))
+#define  PRIM_C_BLD_FUNC_MASK			GENMASK(2, 0)
+#define  PRIM_C_BLD_FUNC(x)			\
+		FIELD_PREP(PRIM_C_BLD_FUNC_MASK, (x))
+#define  SEC_C_BLD_FUNC_MASK			GENMASK(6, 4)
+#define  SEC_C_BLD_FUNC(x)			\
+		FIELD_PREP(SEC_C_BLD_FUNC_MASK, (x))
+#define  PRIM_A_BLD_FUNC_MASK			GENMASK(10, 8)
+#define  PRIM_A_BLD_FUNC(x)			\
+		FIELD_PREP(PRIM_A_BLD_FUNC_MASK, (x))
+#define  SEC_A_BLD_FUNC_MASK			GENMASK(14, 12)
+#define  SEC_A_BLD_FUNC(x)			\
+		FIELD_PREP(SEC_A_BLD_FUNC_MASK, (x))
+
+#define POSITION				0x14
+#define  XPOS_MASK				GENMASK(15, 0)
+#define  XPOS(x)				FIELD_PREP(XPOS_MASK, (x))
+#define  YPOS_MASK				GENMASK(31, 16)
+#define  YPOS(x)				FIELD_PREP(YPOS_MASK, (x))
+
+enum dc_lb_blend_func {
+	DC_LAYERBLEND_BLEND_ZERO,
+	DC_LAYERBLEND_BLEND_ONE,
+	DC_LAYERBLEND_BLEND_PRIM_ALPHA,
+	DC_LAYERBLEND_BLEND_ONE_MINUS_PRIM_ALPHA,
+	DC_LAYERBLEND_BLEND_SEC_ALPHA,
+	DC_LAYERBLEND_BLEND_ONE_MINUS_SEC_ALPHA,
+	DC_LAYERBLEND_BLEND_CONST_ALPHA,
+	DC_LAYERBLEND_BLEND_ONE_MINUS_CONST_ALPHA,
+};
+
+enum dc_lb_shadow_sel {
+	BOTH = 0x2,
+};
+
+static const struct regmap_range dc_lb_pec_regmap_access_ranges[] = {
+	regmap_reg_range(PIXENGCFG_DYNAMIC, PIXENGCFG_DYNAMIC),
+};
+
+static const struct regmap_access_table dc_lb_pec_regmap_access_table = {
+	.yes_ranges = dc_lb_pec_regmap_access_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_lb_pec_regmap_access_ranges),
+};
+
+static const struct regmap_config dc_lb_pec_regmap_config = {
+	.name = "layerblend-pec",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_lb_pec_regmap_access_table,
+	.rd_table = &dc_lb_pec_regmap_access_table,
+};
+
+static const struct regmap_range dc_lb_regmap_ranges[] = {
+	regmap_reg_range(STATICCONTROL, POSITION),
+};
+
+static const struct regmap_access_table dc_lb_regmap_access_table = {
+	.yes_ranges = dc_lb_regmap_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_lb_regmap_ranges),
+};
+
+static const struct regmap_config dc_lb_cfg_regmap_config = {
+	.name = "layerblend-cfg",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_lb_regmap_access_table,
+	.rd_table = &dc_lb_regmap_access_table,
+};
+
+static const enum dc_link_id lb_links[] = {
+	LINK_ID_LAYERBLEND0,
+	LINK_ID_LAYERBLEND1,
+	LINK_ID_LAYERBLEND2,
+	LINK_ID_LAYERBLEND3,
+};
+
+static const enum dc_link_id prim_sels[] = {
+	/* common options */
+	LINK_ID_NONE,
+	LINK_ID_CONSTFRAME0,
+	LINK_ID_CONSTFRAME1,
+	LINK_ID_CONSTFRAME4,
+	LINK_ID_CONSTFRAME5,
+	/*
+	 * special options:
+	 * layerblend(n) has n special options,
+	 * from layerblend0 to layerblend(n - 1), e.g.,
+	 * layerblend3 has 3 special options -
+	 * layerblend0/1/2.
+	 */
+	LINK_ID_LAYERBLEND0,
+	LINK_ID_LAYERBLEND1,
+	LINK_ID_LAYERBLEND2,
+	LINK_ID_LAYERBLEND3,
+};
+
+static const enum dc_link_id sec_sels[] = {
+	LINK_ID_NONE,
+	LINK_ID_FETCHWARP2,
+	LINK_ID_FETCHLAYER0,
+};
+
+enum dc_link_id dc_lb_get_link_id(struct dc_lb *lb)
+{
+	return lb->link;
+}
+
+void dc_lb_pec_dynamic_prim_sel(struct dc_lb *lb, enum dc_link_id prim)
+{
+	int fixed_sels_num = ARRAY_SIZE(prim_sels) - 4;
+	int i;
+
+	for (i = 0; i < fixed_sels_num + lb->id; i++) {
+		if (prim_sels[i] == prim) {
+			regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC,
+					  PIXENGCFG_DYNAMIC_PRIM_SEL_MASK,
+					  PIXENGCFG_DYNAMIC_PRIM_SEL(prim));
+			return;
+		}
+	}
+}
+
+void dc_lb_pec_dynamic_sec_sel(struct dc_lb *lb, enum dc_link_id sec)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sec_sels); i++) {
+		if (sec_sels[i] == sec) {
+			regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC,
+					  PIXENGCFG_DYNAMIC_SEC_SEL_MASK,
+					  PIXENGCFG_DYNAMIC_SEC_SEL(sec));
+			return;
+		}
+	}
+}
+
+void dc_lb_pec_clken(struct dc_lb *lb, enum dc_pec_clken clken)
+{
+	regmap_write_bits(lb->reg_pec, PIXENGCFG_DYNAMIC, CLKEN_MASK,
+			  CLKEN(clken));
+}
+
+static inline void dc_lb_enable_shden(struct dc_lb *lb)
+{
+	regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDEN, SHDEN);
+}
+
+static inline void dc_lb_shdtoksel(struct dc_lb *lb, enum dc_lb_shadow_sel sel)
+{
+	regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDTOKSEL_MASK,
+			  SHDTOKSEL(sel));
+}
+
+static inline void dc_lb_shdldsel(struct dc_lb *lb, enum dc_lb_shadow_sel sel)
+{
+	regmap_write_bits(lb->reg_cfg, STATICCONTROL, SHDLDSEL_MASK,
+			  SHDLDSEL(sel));
+}
+
+void dc_lb_mode(struct dc_lb *lb, enum dc_lb_mode mode)
+{
+	regmap_write_bits(lb->reg_cfg, CONTROL, CTRL_MODE_MASK, mode);
+}
+
+static inline void dc_lb_blendcontrol(struct dc_lb *lb)
+{
+	u32 val = PRIM_A_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
+		  SEC_A_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
+		  PRIM_C_BLD_FUNC(DC_LAYERBLEND_BLEND_ZERO) |
+		  SEC_C_BLD_FUNC(DC_LAYERBLEND_BLEND_CONST_ALPHA) |
+		  ALPHA(DRM_BLEND_ALPHA_OPAQUE >> 8);
+
+	regmap_write(lb->reg_cfg, BLENDCONTROL, val);
+}
+
+void dc_lb_position(struct dc_lb *lb, int x, int y)
+{
+	regmap_write(lb->reg_cfg, POSITION, XPOS(x) | YPOS(y));
+}
+
+unsigned int dc_lb_get_id(struct dc_lb *lb)
+{
+	return lb->id;
+}
+
+void dc_lb_init(struct dc_lb *lb)
+{
+	dc_lb_pec_dynamic_prim_sel(lb, LINK_ID_NONE);
+	dc_lb_pec_dynamic_sec_sel(lb, LINK_ID_NONE);
+	dc_lb_pec_clken(lb, CLKEN_DISABLE);
+	dc_lb_shdldsel(lb, BOTH);
+	dc_lb_shdtoksel(lb, BOTH);
+	dc_lb_blendcontrol(lb);
+	dc_lb_enable_shden(lb);
+}
+
+static int dc_lb_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe = dc_drm->pe;
+	void __iomem *base_pec;
+	void __iomem *base_cfg;
+	struct dc_lb *lb;
+
+	lb = devm_kzalloc(dev, sizeof(*lb), GFP_KERNEL);
+	if (!lb)
+		return -ENOMEM;
+
+	base_pec = devm_platform_ioremap_resource_byname(pdev, "pec");
+	if (IS_ERR(base_pec))
+		return PTR_ERR(base_pec);
+
+	base_cfg = devm_platform_ioremap_resource_byname(pdev, "cfg");
+	if (IS_ERR(base_cfg))
+		return PTR_ERR(base_cfg);
+
+	lb->reg_pec = devm_regmap_init_mmio(dev, base_pec,
+					    &dc_lb_pec_regmap_config);
+	if (IS_ERR(lb->reg_pec))
+		return PTR_ERR(lb->reg_pec);
+
+	lb->reg_cfg = devm_regmap_init_mmio(dev, base_cfg,
+					    &dc_lb_cfg_regmap_config);
+	if (IS_ERR(lb->reg_cfg))
+		return PTR_ERR(lb->reg_cfg);
+
+	lb->id = of_alias_get_id(dev->of_node, "dc0-layerblend");
+	if (lb->id < 0) {
+		dev_err(dev, "failed to get alias id: %d\n", lb->id);
+		return lb->id;
+	}
+
+	lb->link = lb_links[lb->id];
+
+	pe->lb[lb->id] = lb;
+
+	return 0;
+}
+
+static const struct component_ops dc_lb_ops = {
+	.bind = dc_lb_bind,
+};
+
+static int dc_lb_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = component_add(&pdev->dev, &dc_lb_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_lb_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_lb_ops);
+}
+
+static const struct of_device_id dc_lb_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-layerblend", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_lb_dt_ids);
+
+struct platform_driver dc_lb_driver = {
+	.probe = dc_lb_probe,
+	.remove = dc_lb_remove,
+	.driver = {
+		.name = "imx8-dc-layerblend",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_lb_dt_ids,
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-pe.c b/drivers/gpu/drm/imx/dc/dc-pe.c
new file mode 100644
index 000000000000..459fc3823953
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-pe.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+
+#include "dc-drv.h"
+#include "dc-fu.h"
+#include "dc-pe.h"
+
+static int dc_pe_bind(struct device *dev, struct device *master, void *data)
+{
+	struct dc_drm_device *dc_drm = data;
+	struct dc_pe *pe;
+	int ret;
+
+	pe = devm_kzalloc(dev, sizeof(*pe), GFP_KERNEL);
+	if (!pe)
+		return -ENOMEM;
+
+	pe->clk_axi = devm_clk_get(dev, NULL);
+	if (IS_ERR(pe->clk_axi))
+		return dev_err_probe(dev, PTR_ERR(pe->clk_axi),
+				     "failed to get AXI clock\n");
+
+	pe->dev = dev;
+
+	dev_set_drvdata(dev, pe);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	dc_drm->pe = pe;
+
+	return 0;
+}
+
+static const struct component_ops dc_pe_ops = {
+	.bind = dc_pe_bind,
+};
+
+static int dc_pe_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = devm_of_platform_populate(&pdev->dev);
+	if (ret < 0)
+		return ret;
+
+	ret = component_add(&pdev->dev, &dc_pe_ops);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component\n");
+
+	return 0;
+}
+
+static void dc_pe_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dc_pe_ops);
+}
+
+static int dc_pe_runtime_suspend(struct device *dev)
+{
+	struct dc_pe *pe = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(pe->clk_axi);
+
+	return 0;
+}
+
+static int dc_pe_runtime_resume(struct device *dev)
+{
+	struct dc_pe *pe = dev_get_drvdata(dev);
+	int i, ret;
+
+	ret = clk_prepare_enable(pe->clk_axi);
+	if (ret) {
+		dev_err(dev, "failed to enable AXI clock: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(pe->cf_safe); i++)
+		dc_cf_init(pe->cf_safe[i]);
+
+	for (i = 0; i < ARRAY_SIZE(pe->cf_cont); i++)
+		dc_cf_init(pe->cf_cont[i]);
+
+	for (i = 0; i < ARRAY_SIZE(pe->ed_safe); i++)
+		dc_ed_init(pe->ed_safe[i]);
+
+	for (i = 0; i < ARRAY_SIZE(pe->ed_cont); i++)
+		dc_ed_init(pe->ed_cont[i]);
+
+	for (i = 0; i < ARRAY_SIZE(pe->fu_disp); i++)
+		pe->fu_disp[i]->ops.init(pe->fu_disp[i]);
+
+	for (i = 0; i < ARRAY_SIZE(pe->lb); i++)
+		dc_lb_init(pe->lb[i]);
+
+	return 0;
+}
+
+static const struct dev_pm_ops dc_pe_pm_ops = {
+	RUNTIME_PM_OPS(dc_pe_runtime_suspend, dc_pe_runtime_resume, NULL)
+};
+
+static const struct of_device_id dc_pe_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-pixel-engine", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_pe_dt_ids);
+
+struct platform_driver dc_pe_driver = {
+	.probe = dc_pe_probe,
+	.remove = dc_pe_remove,
+	.driver = {
+		.name = "imx8-dc-pixel-engine",
+		.suppress_bind_attrs = true,
+		.of_match_table = dc_pe_dt_ids,
+		.pm = pm_sleep_ptr(&dc_pe_pm_ops),
+	},
+};
diff --git a/drivers/gpu/drm/imx/dc/dc-pe.h b/drivers/gpu/drm/imx/dc/dc-pe.h
new file mode 100644
index 000000000000..d0c1080e58d8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-pe.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_PIXEL_ENGINE_H__
+#define __DC_PIXEL_ENGINE_H__
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+#include "dc-de.h"
+
+#define SHDEN			BIT(0)
+
+#define CLKEN_MASK_SHIFT	24
+#define CLKEN_MASK		(0x3 << CLKEN_MASK_SHIFT)
+#define CLKEN(n)		((n) << CLKEN_MASK_SHIFT)
+
+#define DC_DISP_FETCHUNIT_CNT	2
+#define DC_LAYERBLEND_CNT	4
+
+enum dc_link_id {
+	LINK_ID_NONE		= 0x00,
+	LINK_ID_CONSTFRAME0	= 0x0c,
+	LINK_ID_CONSTFRAME4	= 0x0e,
+	LINK_ID_CONSTFRAME1	= 0x10,
+	LINK_ID_CONSTFRAME5	= 0x12,
+	LINK_ID_FETCHWARP2	= 0x14,
+	LINK_ID_FETCHLAYER0	= 0x1a,
+	LINK_ID_LAYERBLEND0	= 0x21,
+	LINK_ID_LAYERBLEND1	= 0x22,
+	LINK_ID_LAYERBLEND2	= 0x23,
+	LINK_ID_LAYERBLEND3	= 0x24,
+};
+
+enum dc_lb_mode {
+	LB_NEUTRAL,	/* Output is same as primary input. */
+	LB_BLEND,
+};
+
+enum dc_pec_clken {
+	CLKEN_DISABLE,
+	CLKEN_AUTOMATIC,
+};
+
+struct dc_cf {
+	struct regmap *reg_cfg;
+	enum dc_link_id link;
+};
+
+struct dc_ed {
+	struct device *dev;
+	struct regmap *reg_pec;
+	struct regmap *reg_cfg;
+	int irq_shdld;
+};
+
+struct dc_lb {
+	struct regmap *reg_pec;
+	struct regmap *reg_cfg;
+	int id;
+	enum dc_link_id link;
+};
+
+struct dc_pe {
+	struct device *dev;
+	struct clk *clk_axi;
+	struct dc_cf *cf_safe[DC_DISPLAYS];
+	struct dc_cf *cf_cont[DC_DISPLAYS];
+	struct dc_ed *ed_safe[DC_DISPLAYS];
+	struct dc_ed *ed_cont[DC_DISPLAYS];
+	struct dc_fu *fu_disp[DC_DISP_FETCHUNIT_CNT];
+	struct dc_lb *lb[DC_LAYERBLEND_CNT];
+};
+
+/* Constant Frame Unit */
+enum dc_link_id dc_cf_get_link_id(struct dc_cf *cf);
+void dc_cf_framedimensions(struct dc_cf *cf, unsigned int w, unsigned int h);
+void dc_cf_constantcolor_black(struct dc_cf *cf);
+void dc_cf_constantcolor_blue(struct dc_cf *cf);
+void dc_cf_init(struct dc_cf *cf);
+
+/* External Destination Unit */
+void dc_ed_pec_src_sel(struct dc_ed *ed, enum dc_link_id src);
+void dc_ed_pec_sync_trigger(struct dc_ed *ed);
+void dc_ed_init(struct dc_ed *ed);
+
+/* Layer Blend Unit */
+enum dc_link_id dc_lb_get_link_id(struct dc_lb *lb);
+void dc_lb_pec_dynamic_prim_sel(struct dc_lb *lb, enum dc_link_id prim);
+void dc_lb_pec_dynamic_sec_sel(struct dc_lb *lb, enum dc_link_id sec);
+void dc_lb_pec_clken(struct dc_lb *lb, enum dc_pec_clken clken);
+void dc_lb_mode(struct dc_lb *lb, enum dc_lb_mode mode);
+void dc_lb_position(struct dc_lb *lb, int x, int y);
+unsigned int dc_lb_get_id(struct dc_lb *lb);
+void dc_lb_init(struct dc_lb *lb);
+
+#endif /* __DC_PIXEL_ENGINE_H__ */
-- 
2.34.1



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

* [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (9 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-12  8:02   ` Maxime Ripard
  2024-12-09  3:39 ` [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS Liu Ying
                   ` (7 subsequent siblings)
  18 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller has a built-in interrupt controller to support
Enable/Status/Preset/Clear interrupt bit.  Add driver for it.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Replace .remove_new with .remove in dc-ic.c. (Uwe)

v4:
* Use regmap to define register map for all registers. (Dmitry)
* Use regmap APIs to access registers. (Dmitry)
* Use devm_kzalloc() to drmm_kzalloc() to allocate dc_ic_data data strutures.
* Set suppress_bind_attrs driver flag to true to avoid unnecessary sys
  interfaces to bind/unbind the driver.
* Take DC interrupt controller driver as a standalone driver instead of a
  component driver.
* Replace drmm_kcalloc() with devm_kcalloc() to allocate an array for
  struct dc_ic_entry.
* Call platform_get_irq() from DC interrupt controller driver to make sure
  parent interrupt controller driver is probed first.

v3:
* No change.

v2:
* No change.

 drivers/gpu/drm/imx/dc/Kconfig  |   1 +
 drivers/gpu/drm/imx/dc/Makefile |   2 +-
 drivers/gpu/drm/imx/dc/dc-drv.c |   1 +
 drivers/gpu/drm/imx/dc/dc-drv.h |   1 +
 drivers/gpu/drm/imx/dc/dc-ic.c  | 279 ++++++++++++++++++++++++++++++++
 5 files changed, 283 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/imx/dc/dc-ic.c

diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
index e1ef76d82830..1fc84c7475de 100644
--- a/drivers/gpu/drm/imx/dc/Kconfig
+++ b/drivers/gpu/drm/imx/dc/Kconfig
@@ -1,6 +1,7 @@
 config DRM_IMX8_DC
 	tristate "Freescale i.MX8 Display Controller Graphics"
 	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
+	select GENERIC_IRQ_CHIP
 	select REGMAP
 	select REGMAP_MMIO
 	help
diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
index 2942ae6fd5bd..1ce3e8a8db22 100644
--- a/drivers/gpu/drm/imx/dc/Makefile
+++ b/drivers/gpu/drm/imx/dc/Makefile
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 
 imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
-		    dc-fw.o dc-lb.o dc-pe.o dc-tc.o
+		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
 
 obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
index 7c64acc863ad..fd68861f770a 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.c
+++ b/drivers/gpu/drm/imx/dc/dc-drv.c
@@ -15,6 +15,7 @@ static struct platform_driver * const dc_drivers[] = {
 	&dc_fg_driver,
 	&dc_fl_driver,
 	&dc_fw_driver,
+	&dc_ic_driver,
 	&dc_lb_driver,
 	&dc_pe_driver,
 	&dc_tc_driver,
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
index c687a36b8153..3b11f4862c6c 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.h
+++ b/drivers/gpu/drm/imx/dc/dc-drv.h
@@ -25,6 +25,7 @@ extern struct platform_driver dc_de_driver;
 extern struct platform_driver dc_fg_driver;
 extern struct platform_driver dc_fl_driver;
 extern struct platform_driver dc_fw_driver;
+extern struct platform_driver dc_ic_driver;
 extern struct platform_driver dc_lb_driver;
 extern struct platform_driver dc_pe_driver;
 extern struct platform_driver dc_tc_driver;
diff --git a/drivers/gpu/drm/imx/dc/dc-ic.c b/drivers/gpu/drm/imx/dc/dc-ic.c
new file mode 100644
index 000000000000..f022f0374772
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-ic.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#define USERINTERRUPTMASK(n)	(0x8 + 4 * (n))
+#define INTERRUPTENABLE(n)	(0x10 + 4 * (n))
+#define INTERRUPTPRESET(n)	(0x18 + 4 * (n))
+#define INTERRUPTCLEAR(n)	(0x20 + 4 * (n))
+#define INTERRUPTSTATUS(n)	(0x28 + 4 * (n))
+#define USERINTERRUPTENABLE(n)	(0x40 + 4 * (n))
+#define USERINTERRUPTPRESET(n)	(0x48 + 4 * (n))
+#define USERINTERRUPTCLEAR(n)	(0x50 + 4 * (n))
+#define USERINTERRUPTSTATUS(n)	(0x58 + 4 * (n))
+
+#define IRQ_COUNT	49
+#define IRQ_RESERVED	35
+#define REG_NUM		2
+
+struct dc_ic_data {
+	struct regmap		*regs;
+	struct clk		*clk_axi;
+	int			irq[IRQ_COUNT];
+	struct irq_domain	*domain;
+};
+
+struct dc_ic_entry {
+	struct dc_ic_data *data;
+	int irq;
+};
+
+static const struct regmap_range dc_ic_regmap_write_ranges[] = {
+	regmap_reg_range(USERINTERRUPTMASK(0), INTERRUPTCLEAR(1)),
+	regmap_reg_range(USERINTERRUPTENABLE(0), USERINTERRUPTCLEAR(1)),
+};
+
+static const struct regmap_access_table dc_ic_regmap_write_table = {
+	.yes_ranges = dc_ic_regmap_write_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_write_ranges),
+};
+
+static const struct regmap_range dc_ic_regmap_read_ranges[] = {
+	regmap_reg_range(USERINTERRUPTMASK(0), USERINTERRUPTSTATUS(1)),
+};
+
+static const struct regmap_access_table dc_ic_regmap_read_table = {
+	.yes_ranges = dc_ic_regmap_read_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_read_ranges),
+};
+
+static const struct regmap_range dc_ic_regmap_volatile_ranges[] = {
+	regmap_reg_range(INTERRUPTPRESET(0), INTERRUPTCLEAR(1)),
+	regmap_reg_range(USERINTERRUPTPRESET(0), USERINTERRUPTCLEAR(1)),
+};
+
+static const struct regmap_access_table dc_ic_regmap_volatile_table = {
+	.yes_ranges = dc_ic_regmap_volatile_ranges,
+	.n_yes_ranges = ARRAY_SIZE(dc_ic_regmap_volatile_ranges),
+};
+
+static const struct regmap_config dc_ic_regmap_config = {
+	.name = "interrupt-controller",
+	.reg_bits = 32,
+	.reg_stride = 4,
+	.val_bits = 32,
+	.fast_io = true,
+	.wr_table = &dc_ic_regmap_write_table,
+	.rd_table = &dc_ic_regmap_read_table,
+	.volatile_table = &dc_ic_regmap_volatile_table,
+};
+
+static void dc_ic_irq_handler(struct irq_desc *desc)
+{
+	struct dc_ic_entry *entry = irq_desc_get_handler_data(desc);
+	struct dc_ic_data *data = entry->data;
+	unsigned int status, enable;
+	unsigned int virq;
+
+	chained_irq_enter(irq_desc_get_chip(desc), desc);
+
+	regmap_read(data->regs, USERINTERRUPTSTATUS(entry->irq / 32), &status);
+	regmap_read(data->regs, USERINTERRUPTENABLE(entry->irq / 32), &enable);
+
+	status &= enable;
+
+	if (status & BIT(entry->irq % 32)) {
+		virq = irq_linear_revmap(data->domain, entry->irq);
+		if (virq)
+			generic_handle_irq(virq);
+	}
+
+	chained_irq_exit(irq_desc_get_chip(desc), desc);
+}
+
+static const unsigned long unused_irq[REG_NUM] = {0x00000000, 0xfffe0008};
+
+static int dc_ic_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct irq_chip_generic *gc;
+	struct dc_ic_entry *entry;
+	struct irq_chip_type *ct;
+	struct dc_ic_data *data;
+	void __iomem *base;
+	int i, ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	entry = devm_kcalloc(dev, IRQ_COUNT, sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(base)) {
+		dev_err(dev, "failed to initialize reg\n");
+		return PTR_ERR(base);
+	}
+
+	data->regs = devm_regmap_init_mmio(dev, base, &dc_ic_regmap_config);
+	if (IS_ERR(data->regs))
+		return PTR_ERR(data->regs);
+
+	data->clk_axi = devm_clk_get(dev, NULL);
+	if (IS_ERR(data->clk_axi))
+		return dev_err_probe(dev, PTR_ERR(data->clk_axi),
+				     "failed to get AXI clock\n");
+
+	for (i = 0; i < IRQ_COUNT; i++) {
+		/* skip the reserved IRQ */
+		if (i == IRQ_RESERVED)
+			continue;
+
+		ret = platform_get_irq(pdev, i);
+		if (ret < 0)
+			return ret;
+	}
+
+	dev_set_drvdata(dev, data);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret < 0) {
+		dev_err(dev, "failed to get runtime PM sync: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < REG_NUM; i++) {
+		/* mask and clear all interrupts */
+		regmap_write(data->regs, USERINTERRUPTENABLE(i), 0x0);
+		regmap_write(data->regs, INTERRUPTENABLE(i), 0x0);
+		regmap_write(data->regs, USERINTERRUPTCLEAR(i), 0xffffffff);
+		regmap_write(data->regs, INTERRUPTCLEAR(i), 0xffffffff);
+
+		/* set all interrupts to user mode */
+		regmap_write(data->regs, USERINTERRUPTMASK(i), 0xffffffff);
+	}
+
+	data->domain = irq_domain_add_linear(dev->of_node, IRQ_COUNT,
+					     &irq_generic_chip_ops, data);
+	if (!data->domain) {
+		dev_err(dev, "failed to create IRQ domain\n");
+		pm_runtime_put(dev);
+		return -ENOMEM;
+	}
+	irq_domain_set_pm_device(data->domain, dev);
+
+	ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, "DC",
+					     handle_level_irq, 0, 0, 0);
+	if (ret) {
+		dev_err(dev, "failed to alloc generic IRQ chips: %d\n", ret);
+		irq_domain_remove(data->domain);
+		pm_runtime_put(dev);
+		return ret;
+	}
+
+	for (i = 0; i < IRQ_COUNT; i += 32) {
+		gc = irq_get_domain_generic_chip(data->domain, i);
+		gc->reg_base = base;
+		gc->unused = unused_irq[i / 32];
+		ct = gc->chip_types;
+		ct->chip.irq_ack = irq_gc_ack_set_bit;
+		ct->chip.irq_mask = irq_gc_mask_clr_bit;
+		ct->chip.irq_unmask = irq_gc_mask_set_bit;
+		ct->regs.ack = USERINTERRUPTCLEAR(i / 32);
+		ct->regs.mask = USERINTERRUPTENABLE(i / 32);
+	}
+
+	for (i = 0; i < IRQ_COUNT; i++) {
+		/* skip the reserved IRQ */
+		if (i == IRQ_RESERVED)
+			continue;
+
+		data->irq[i] = irq_of_parse_and_map(dev->of_node, i);
+
+		entry[i].data = data;
+		entry[i].irq = i;
+
+		irq_set_chained_handler_and_data(data->irq[i],
+						 dc_ic_irq_handler, &entry[i]);
+	}
+
+	return 0;
+}
+
+static void dc_ic_remove(struct platform_device *pdev)
+{
+	struct dc_ic_data *data = dev_get_drvdata(&pdev->dev);
+	int i;
+
+	for (i = 0; i < IRQ_COUNT; i++) {
+		if (i == IRQ_RESERVED)
+			continue;
+
+		irq_set_chained_handler_and_data(data->irq[i], NULL, NULL);
+	}
+
+	irq_domain_remove(data->domain);
+
+	pm_runtime_put_sync(&pdev->dev);
+}
+
+static int dc_ic_runtime_suspend(struct device *dev)
+{
+	struct dc_ic_data *data = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(data->clk_axi);
+
+	return 0;
+}
+
+static int dc_ic_runtime_resume(struct device *dev)
+{
+	struct dc_ic_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(data->clk_axi);
+	if (ret)
+		dev_err(dev, "failed to enable AXI clock: %d\n", ret);
+
+	return ret;
+}
+
+static const struct dev_pm_ops dc_ic_pm_ops = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
+				      pm_runtime_force_resume)
+	RUNTIME_PM_OPS(dc_ic_runtime_suspend, dc_ic_runtime_resume, NULL)
+};
+
+static const struct of_device_id dc_ic_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc-intc", },
+	{ /* sentinel */ }
+};
+
+struct platform_driver dc_ic_driver = {
+	.probe = dc_ic_probe,
+	.remove = dc_ic_remove,
+	.driver = {
+		.name = "imx8-dc-intc",
+		.suppress_bind_attrs = true,
+		.of_match_table	= dc_ic_dt_ids,
+		.pm = pm_sleep_ptr(&dc_ic_pm_ops),
+	},
+};
-- 
2.34.1



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

* [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (10 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09 16:17   ` Dmitry Baryshkov
  2024-12-12  8:02   ` Maxime Ripard
  2024-12-09  3:39 ` [PATCH v6 13/19] MAINTAINERS: Add maintainer for i.MX8qxp Display Controller Liu Ying
                   ` (6 subsequent siblings)
  18 siblings, 2 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

i.MX8qxp Display Controller(DC) is comprised of three main components that
include a blit engine for 2D graphics accelerations, display controller for
display output processing, as well as a command sequencer.  Add kernel
mode setting support for the display controller part with two CRTCs and
two primary planes(backed by FetchLayer and FetchWarp respectively).  The
registers of the display controller are accessed without command sequencer
involved, instead just by using CPU.  The command sequencer is supposed to
be used by the blit engine.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* Replace .remove_new with .remove in dc-drv.c. (Uwe)

v4:
* Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
  function calls from KMS routine to initialization stage. (Dmitry)
* Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
  appropriate .h header files or .c source files. (Dmitry)
* Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
* Drop dc_drm->pe_rpm_count. (Dmitry)
* Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
* Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
  Instead, put it in struct dc_crtc.  (Dmitry)
* Call devm_request_irq() to request IRQs, instead of using drmm action.
  (Dmitry)
* Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
* Select DRM_CLIENT_SELECTION due to rebase.
* Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
* Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
* Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
  rebase.
* Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
  register dc_drm_component_unbind_all() action.
* Request interrupts in dc_crtc_post_init() after encoder initialization to
  make sure next bridge is found first.

v3:
* No change.

v2:
* Find next bridge from TCon's port.
* Drop drm/drm_module.h include from dc-drv.c.

 drivers/gpu/drm/imx/dc/Kconfig    |   5 +
 drivers/gpu/drm/imx/dc/Makefile   |   5 +-
 drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
 drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
 drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
 drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
 drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
 drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
 9 files changed, 1274 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
 create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
 create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c

diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
index 1fc84c7475de..415993207f2e 100644
--- a/drivers/gpu/drm/imx/dc/Kconfig
+++ b/drivers/gpu/drm/imx/dc/Kconfig
@@ -1,6 +1,11 @@
 config DRM_IMX8_DC
 	tristate "Freescale i.MX8 Display Controller Graphics"
 	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
+	select DRM_CLIENT_SELECTION
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_DISPLAY_HELPER
+	select DRM_BRIDGE_CONNECTOR
 	select GENERIC_IRQ_CHIP
 	select REGMAP
 	select REGMAP_MMIO
diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
index 1ce3e8a8db22..b9d33c074984 100644
--- a/drivers/gpu/drm/imx/dc/Makefile
+++ b/drivers/gpu/drm/imx/dc/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
-imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
-		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
+imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
+		    dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
+		    dc-plane.o dc-tc.o
 
 obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
new file mode 100644
index 000000000000..fd6daa1807d8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/completion.h>
+#include <linux/container_of.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/pm_runtime.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+#include "dc-kms.h"
+#include "dc-pe.h"
+
+#define dc_crtc_dbg(crtc, fmt, ...)					\
+do {									\
+	typeof(crtc) _crtc = (crtc);					\
+	drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
+		    _crtc->base.id, _crtc->name, ##__VA_ARGS__);	\
+} while (0)
+
+#define dc_crtc_err(crtc, fmt, ...)					\
+do {									\
+	typeof(crtc) _crtc = (crtc);					\
+	drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
+		_crtc->base.id, _crtc->name, ##__VA_ARGS__);		\
+} while (0)
+
+#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)				\
+do {									\
+	unsigned long ret;						\
+	ret = wait_for_completion_timeout(&dc_crtc->c, HZ);		\
+	if (ret == 0)							\
+		dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",	\
+							__func__);	\
+} while (0)
+
+#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)					\
+do {									\
+	typeof(fg) _fg = (fg);						\
+	if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {		\
+		dc_fg_secondary_clear_channel_status(_fg);		\
+		dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",		\
+							__func__);	\
+	}								\
+} while (0)
+
+#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)			\
+do {									\
+	if (dc_fg_wait_for_secondary_syncup(fg))			\
+		dc_crtc_err(crtc,					\
+			"%s: FrameGen secondary channel isn't syncup\n",\
+							__func__);	\
+} while (0)
+
+static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct dc_crtc, base);
+}
+
+static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+
+	return dc_fg_get_frame_index(dc_crtc->fg);
+}
+
+static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+
+	enable_irq(dc_crtc->irq_dec_framecomplete);
+
+	return 0;
+}
+
+static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+
+	disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
+}
+
+static irqreturn_t
+dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
+{
+	struct dc_crtc *dc_crtc = dev_id;
+	struct drm_crtc *crtc = &dc_crtc->base;
+	unsigned long flags;
+
+	drm_crtc_handle_vblank(crtc);
+
+	spin_lock_irqsave(&crtc->dev->event_lock, flags);
+	if (dc_crtc->event) {
+		drm_crtc_send_vblank_event(crtc, dc_crtc->event);
+		dc_crtc->event = NULL;
+		drm_crtc_vblank_put(crtc);
+	}
+	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
+{
+	struct dc_crtc *dc_crtc = dev_id;
+
+	if (irq == dc_crtc->irq_dec_seqcomplete)
+		complete(&dc_crtc->dec_seqcomplete_done);
+	else if (irq == dc_crtc->irq_dec_shdld)
+		complete(&dc_crtc->dec_shdld_done);
+	else if (irq == dc_crtc->irq_ed_cont_shdld)
+		complete(&dc_crtc->ed_cont_shdld_done);
+	else if (irq == dc_crtc->irq_ed_safe_shdld)
+		complete(&dc_crtc->ed_safe_shdld_done);
+
+	return IRQ_HANDLED;
+}
+
+static const struct drm_crtc_funcs dc_crtc_funcs = {
+	.reset			= drm_atomic_helper_crtc_reset,
+	.destroy		= drm_crtc_cleanup,
+	.set_config		= drm_atomic_helper_set_config,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+	.get_vblank_counter	= dc_crtc_get_vblank_counter,
+	.enable_vblank		= dc_crtc_enable_vblank,
+	.disable_vblank		= dc_crtc_disable_vblank,
+	.get_vblank_timestamp	= drm_crtc_vblank_helper_get_vblank_timestamp,
+};
+
+static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
+{
+	struct drm_crtc *crtc = crtc_state->crtc;
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+
+	spin_lock_irq(&crtc->dev->event_lock);
+	if (crtc_state->event) {
+		WARN_ON(drm_crtc_vblank_get(crtc));
+		WARN_ON(dc_crtc->event);
+		dc_crtc->event = crtc_state->event;
+		crtc_state->event = NULL;
+	}
+	spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static enum drm_mode_status
+dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
+{
+	return dc_fg_check_clock(dc_crtc->fg, clk_khz);
+}
+
+static enum drm_mode_status
+dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	enum drm_mode_status status;
+
+	status = dc_crtc_check_clock(dc_crtc, mode->clock);
+	if (status != MODE_OK)
+		return status;
+
+	if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static int
+dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	enum drm_mode_status status;
+
+	status = dc_crtc_check_clock(dc_crtc, adj->clock);
+	if (status != MODE_OK)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void
+dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, crtc);
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	int idx, ret;
+
+	if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
+	    !new_crtc_state->active)
+		return;
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		return;
+
+	/* request pixel engine power-on when CRTC starts to be active */
+	ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
+	if (ret)
+		dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
+			    ret);
+
+	drm_dev_exit(idx);
+}
+
+static void
+dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *old_crtc_state =
+				drm_atomic_get_old_crtc_state(state, crtc);
+	struct drm_crtc_state *new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, crtc);
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	int idx;
+
+	if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
+	    (!old_crtc_state->active && !new_crtc_state->active))
+		return;
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		goto out;
+
+	enable_irq(dc_crtc->irq_ed_cont_shdld);
+
+	/* flush plane update out to display */
+	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
+
+	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
+
+	disable_irq(dc_crtc->irq_ed_cont_shdld);
+
+	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
+
+	drm_dev_exit(idx);
+
+out:
+	dc_crtc_queue_state_event(new_crtc_state);
+}
+
+static void
+dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, crtc);
+	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	enum dc_link_id cf_link;
+	int idx, ret;
+
+	dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
+
+	drm_crtc_vblank_on(crtc);
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		goto out;
+
+	/* request display engine power-on when CRTC is enabled */
+	ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
+	if (ret < 0)
+		dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
+			    ret);
+
+	enable_irq(dc_crtc->irq_dec_shdld);
+	enable_irq(dc_crtc->irq_ed_cont_shdld);
+	enable_irq(dc_crtc->irq_ed_safe_shdld);
+
+	dc_fg_cfg_videomode(dc_crtc->fg, adj);
+
+	dc_cf_framedimensions(dc_crtc->cf_cont,
+			      adj->crtc_hdisplay, adj->crtc_vdisplay);
+	dc_cf_framedimensions(dc_crtc->cf_safe,
+			      adj->crtc_hdisplay, adj->crtc_vdisplay);
+
+	/* constframe in safety stream shows blue frame */
+	dc_cf_constantcolor_blue(dc_crtc->cf_safe);
+	cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
+	dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
+
+	/* show CRTC background if no plane is enabled */
+	if (new_crtc_state->plane_mask == 0) {
+		/* constframe in content stream shows black frame */
+		dc_cf_constantcolor_black(dc_crtc->cf_cont);
+
+		cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
+		dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
+	}
+
+	dc_fg_enable_clock(dc_crtc->fg);
+	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
+	dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
+	dc_fg_shdtokgen(dc_crtc->fg);
+	dc_fg_enable(dc_crtc->fg);
+
+	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
+	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
+	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
+
+	disable_irq(dc_crtc->irq_ed_safe_shdld);
+	disable_irq(dc_crtc->irq_ed_cont_shdld);
+	disable_irq(dc_crtc->irq_dec_shdld);
+
+	DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
+
+	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
+
+	drm_dev_exit(idx);
+
+out:
+	dc_crtc_queue_state_event(new_crtc_state);
+}
+
+static void
+dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *new_crtc_state =
+				drm_atomic_get_new_crtc_state(state, crtc);
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	int idx, ret;
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		goto out;
+
+	enable_irq(dc_crtc->irq_dec_seqcomplete);
+	dc_fg_disable(dc_crtc->fg);
+	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
+	disable_irq(dc_crtc->irq_dec_seqcomplete);
+
+	dc_fg_disable_clock(dc_crtc->fg);
+
+	/* request pixel engine power-off as plane is off too */
+	ret = pm_runtime_put(dc_crtc->pe->dev);
+	if (ret)
+		dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
+			    ret);
+
+	/* request display engine power-off when CRTC is disabled */
+	ret = pm_runtime_put(dc_crtc->de->dev);
+	if (ret < 0)
+		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
+			    ret);
+
+	drm_dev_exit(idx);
+
+out:
+	drm_crtc_vblank_off(crtc);
+
+	spin_lock_irq(&crtc->dev->event_lock);
+	if (new_crtc_state->event && !new_crtc_state->active) {
+		drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
+		new_crtc_state->event = NULL;
+	}
+	spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	int ret;
+
+	if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
+		return;
+
+	dc_fg_disable_clock(dc_crtc->fg);
+
+	if (pm_runtime_active(dc_crtc->pe->dev)) {
+		ret = pm_runtime_put_sync(dc_crtc->pe->dev);
+		if (ret)
+			dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
+				    ret);
+	}
+
+	ret = pm_runtime_put_sync(dc_crtc->de->dev);
+	if (ret < 0)
+		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
+			    ret);
+}
+
+static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
+					 bool in_vblank_irq,
+					 int *vpos, int *hpos,
+					 ktime_t *stime, ktime_t *etime,
+					 const struct drm_display_mode *mode)
+{
+	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
+	int vdisplay = mode->crtc_vdisplay;
+	int vtotal = mode->crtc_vtotal;
+	bool reliable;
+	int line;
+	int idx;
+
+	if (stime)
+		*stime = ktime_get();
+
+	if (!drm_dev_enter(crtc->dev, &idx)) {
+		reliable = false;
+		*vpos = 0;
+		*hpos = 0;
+		goto out;
+	}
+
+	/* line index starts with 0 for the first active output line */
+	line = dc_fg_get_line_index(dc_crtc->fg);
+
+	if (line < vdisplay)
+		/* active scanout area - positive */
+		*vpos = line + 1;
+	else
+		/* inside vblank - negative */
+		*vpos = line - (vtotal - 1);
+
+	*hpos = 0;
+
+	reliable = true;
+
+	drm_dev_exit(idx);
+out:
+	if (etime)
+		*etime = ktime_get();
+
+	return reliable;
+}
+
+static const struct drm_crtc_helper_funcs dc_helper_funcs = {
+	.mode_valid		= dc_crtc_mode_valid,
+	.atomic_check		= dc_crtc_atomic_check,
+	.atomic_begin		= dc_crtc_atomic_begin,
+	.atomic_flush		= dc_crtc_atomic_flush,
+	.atomic_enable		= dc_crtc_atomic_enable,
+	.atomic_disable		= dc_crtc_atomic_disable,
+	.get_scanout_position	= dc_crtc_get_scanout_position,
+};
+
+static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
+{
+	struct {
+		struct device *dev;
+		unsigned int irq;
+		irqreturn_t (*irq_handler)(int irq, void *dev_id);
+	} irqs[DC_CRTC_IRQS] = {
+		{
+			dc_crtc->de->dev,
+			dc_crtc->irq_dec_framecomplete,
+			dc_crtc_dec_framecomplete_irq_handler,
+		}, {
+			dc_crtc->de->dev,
+			dc_crtc->irq_dec_seqcomplete,
+			dc_crtc_common_irq_handler,
+		}, {
+			dc_crtc->de->dev,
+			dc_crtc->irq_dec_shdld,
+			dc_crtc_common_irq_handler,
+		}, {
+			dc_crtc->ed_cont->dev,
+			dc_crtc->irq_ed_cont_shdld,
+			dc_crtc_common_irq_handler,
+		}, {
+			dc_crtc->ed_safe->dev,
+			dc_crtc->irq_ed_safe_shdld,
+			dc_crtc_common_irq_handler,
+		},
+	};
+	int i, ret;
+
+	for (i = 0; i < DC_CRTC_IRQS; i++) {
+		struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
+
+		ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
+				       irqs[i].irq_handler, IRQF_NO_AUTOEN,
+				       dev_name(irqs[i].dev), dc_crtc);
+		if (ret) {
+			dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
+				irqs[i].irq, ret);
+			return ret;
+		}
+
+		irq->dc_crtc = dc_crtc;
+		irq->irq = irqs[i].irq;
+	}
+
+	return 0;
+}
+
+int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
+{
+	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
+	struct drm_device *drm = &dc_drm->base;
+	struct dc_de *de = dc_drm->de[crtc_index];
+	struct dc_pe *pe = dc_drm->pe;
+	struct dc_plane *dc_primary;
+	int ret;
+
+	dc_crtc->de = de;
+	dc_crtc->pe = pe;
+
+	init_completion(&dc_crtc->dec_seqcomplete_done);
+	init_completion(&dc_crtc->dec_shdld_done);
+	init_completion(&dc_crtc->ed_cont_shdld_done);
+	init_completion(&dc_crtc->ed_safe_shdld_done);
+
+	dc_crtc->cf_cont = pe->cf_cont[crtc_index];
+	dc_crtc->cf_safe = pe->cf_safe[crtc_index];
+	dc_crtc->ed_cont = pe->ed_cont[crtc_index];
+	dc_crtc->ed_safe = pe->ed_safe[crtc_index];
+	dc_crtc->fg = de->fg;
+
+	dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
+	dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
+	dc_crtc->irq_dec_shdld = de->irq_shdld;
+	dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
+	dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
+
+	dc_primary = &dc_drm->dc_primary[crtc_index];
+	ret = dc_plane_init(dc_drm, dc_primary);
+	if (ret) {
+		dev_err(drm->dev,
+			"failed to init primary plane for display engine%u: %d\n",
+			de->id, ret);
+		return ret;
+	}
+
+	drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
+
+	ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
+					NULL, &dc_crtc_funcs, NULL);
+	if (ret)
+		dev_err(drm->dev,
+			"failed to add CRTC for display engine%u: %d\n",
+			de->id, ret);
+
+	return ret;
+}
+
+int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
+{
+	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
+	struct drm_device *drm = &dc_drm->base;
+
+	return dc_crtc_request_irqs(drm, dc_crtc);
+}
diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
index 17a44362118e..8a7b6c03a222 100644
--- a/drivers/gpu/drm/imx/dc/dc-de.h
+++ b/drivers/gpu/drm/imx/dc/dc-de.h
@@ -13,6 +13,9 @@
 
 #define DC_DISPLAYS	2
 
+#define DC_FRAMEGEN_MAX_FRAME_INDEX	0x3ffff
+#define DC_FRAMEGEN_MAX_CLOCK_KHZ	300000
+
 struct dc_fg {
 	struct device *dev;
 	struct regmap *reg;
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
index fd68861f770a..1e4b8afa3eec 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.c
+++ b/drivers/gpu/drm/imx/dc/dc-drv.c
@@ -3,11 +3,254 @@
  * Copyright 2024 NXP
  */
 
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/mod_devicetable.h>
 #include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
 
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_client_setup.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_of.h>
+
+#include "dc-de.h"
 #include "dc-drv.h"
 
+struct dc_priv {
+	struct drm_device *drm;
+	struct clk *clk_cfg;
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
+
+static struct drm_driver dc_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	DRM_GEM_DMA_DRIVER_OPS,
+	DRM_FBDEV_DMA_DRIVER_OPS,
+	.fops = &dc_drm_driver_fops,
+	.name = "imx8-dc",
+	.desc = "i.MX8 DC DRM graphics",
+	.date = "20240530",
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+};
+
+static void
+dc_add_components(struct device *dev, struct component_match **matchptr)
+{
+	struct device_node *child, *grandchild;
+
+	for_each_available_child_of_node(dev->of_node, child) {
+		/* The interrupt controller is not a component. */
+		if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
+			continue;
+
+		drm_of_component_match_add(dev, matchptr, component_compare_of,
+					   child);
+
+		for_each_available_child_of_node(child, grandchild)
+			drm_of_component_match_add(dev, matchptr,
+						   component_compare_of,
+						   grandchild);
+	}
+}
+
+static void dc_drm_component_unbind_all(void *ptr)
+{
+	struct dc_drm_device *dc_drm = ptr;
+	struct drm_device *drm = &dc_drm->base;
+
+	component_unbind_all(drm->dev, dc_drm);
+}
+
+static int dc_drm_bind(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+	struct dc_drm_device *dc_drm;
+	struct drm_device *drm;
+	int ret;
+
+	dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
+				    base);
+	if (IS_ERR(dc_drm))
+		return PTR_ERR(dc_drm);
+
+	drm = &dc_drm->base;
+
+	ret = component_bind_all(dev, dc_drm);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
+				       dc_drm);
+	if (ret)
+		return ret;
+
+	ret = dc_kms_init(dc_drm);
+	if (ret)
+		return ret;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret) {
+		dev_err(dev, "failed to register drm device: %d\n", ret);
+		goto err;
+	}
+
+	drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
+
+	priv->drm = drm;
+
+	return 0;
+
+err:
+	dc_kms_uninit(dc_drm);
+
+	return ret;
+}
+
+static void dc_drm_unbind(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+	struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
+	struct drm_device *drm = &dc_drm->base;
+	struct drm_crtc *crtc;
+
+	priv->drm = NULL;
+	drm_dev_unplug(drm);
+	dc_kms_uninit(dc_drm);
+	drm_atomic_helper_shutdown(drm);
+
+	drm_for_each_crtc(crtc, drm)
+		dc_crtc_disable_at_unbind(crtc);
+}
+
+static const struct component_master_ops dc_drm_ops = {
+	.bind = dc_drm_bind,
+	.unbind = dc_drm_unbind,
+};
+
+static int dc_probe(struct platform_device *pdev)
+{
+	struct component_match *match = NULL;
+	struct dc_priv *priv;
+	int ret;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(priv->clk_cfg))
+		return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
+				     "failed to get cfg clock\n");
+
+	dev_set_drvdata(&pdev->dev, priv);
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret)
+		return ret;
+
+	ret = devm_pm_runtime_enable(&pdev->dev);
+	if (ret)
+		return ret;
+
+	ret = devm_of_platform_populate(&pdev->dev);
+	if (ret)
+		return ret;
+
+	dc_add_components(&pdev->dev, &match);
+
+	ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "failed to add component master\n");
+
+	return 0;
+}
+
+static void dc_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &dc_drm_ops);
+}
+
+static int dc_runtime_suspend(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(priv->clk_cfg);
+
+	return 0;
+}
+
+static int dc_runtime_resume(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(priv->clk_cfg);
+	if (ret)
+		dev_err(dev, "failed to enable cfg clock: %d\n", ret);
+
+	return ret;
+}
+
+static int dc_suspend(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_suspend(priv->drm);
+}
+
+static int dc_resume(struct device *dev)
+{
+	struct dc_priv *priv = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_resume(priv->drm);
+}
+
+static void dc_shutdown(struct platform_device *pdev)
+{
+	struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
+
+	drm_atomic_helper_shutdown(priv->drm);
+}
+
+static const struct dev_pm_ops dc_pm_ops = {
+	RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
+	SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
+};
+
+static const struct of_device_id dc_dt_ids[] = {
+	{ .compatible = "fsl,imx8qxp-dc", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dc_dt_ids);
+
+static struct platform_driver dc_driver = {
+	.probe = dc_probe,
+	.remove = dc_remove,
+	.shutdown = dc_shutdown,
+	.driver = {
+		.name = "imx8-dc",
+		.of_match_table	= dc_dt_ids,
+		.pm = pm_sleep_ptr(&dc_pm_ops),
+	},
+};
+
 static struct platform_driver * const dc_drivers[] = {
 	&dc_cf_driver,
 	&dc_de_driver,
@@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
 	&dc_lb_driver,
 	&dc_pe_driver,
 	&dc_tc_driver,
+	&dc_driver,
 };
 
 static int __init dc_drm_init(void)
diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
index 3b11f4862c6c..39a771a13933 100644
--- a/drivers/gpu/drm/imx/dc/dc-drv.h
+++ b/drivers/gpu/drm/imx/dc/dc-drv.h
@@ -6,19 +6,38 @@
 #ifndef __DC_DRV_H__
 #define __DC_DRV_H__
 
+#include <linux/container_of.h>
 #include <linux/platform_device.h>
 
 #include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
 
 #include "dc-de.h"
+#include "dc-kms.h"
 #include "dc-pe.h"
 
 struct dc_drm_device {
 	struct drm_device base;
+	struct dc_crtc dc_crtc[DC_DISPLAYS];
+	struct dc_plane dc_primary[DC_DISPLAYS];
+	struct drm_encoder encoder[DC_DISPLAYS];
 	struct dc_de *de[DC_DISPLAYS];
 	struct dc_pe *pe;
 };
 
+static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
+{
+	return container_of(drm, struct dc_drm_device, base);
+}
+
+int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
+int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
+
+int dc_kms_init(struct dc_drm_device *dc_drm);
+void dc_kms_uninit(struct dc_drm_device *dc_drm);
+
+int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
+
 extern struct platform_driver dc_cf_driver;
 extern struct platform_driver dc_ed_driver;
 extern struct platform_driver dc_de_driver;
diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
new file mode 100644
index 000000000000..2b18aa37a4a8
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-kms.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/of.h>
+#include <linux/of_graph.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_mode_config.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "dc-de.h"
+#include "dc-drv.h"
+#include "dc-kms.h"
+
+static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
+					int crtc_index)
+{
+	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
+	struct drm_device *drm = &dc_drm->base;
+	struct drm_crtc *crtc = &dc_crtc->base;
+	struct drm_connector *connector;
+	struct device *dev = drm->dev;
+	struct drm_encoder *encoder;
+	struct drm_bridge *bridge;
+	int ret;
+
+	bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
+					0, 0);
+	if (IS_ERR(bridge)) {
+		ret = PTR_ERR(bridge);
+		if (ret == -ENODEV)
+			return 0;
+
+		return dev_err_probe(dev, ret,
+				     "failed to find bridge for CRTC%u\n",
+				     crtc->index);
+	}
+
+	encoder = &dc_drm->encoder[crtc_index];
+	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
+	if (ret) {
+		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
+			crtc->index, ret);
+		return ret;
+	}
+
+	encoder->possible_crtcs = drm_crtc_mask(crtc);
+
+	ret = drm_bridge_attach(encoder, bridge, NULL,
+				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+	if (ret) {
+		dev_err(dev,
+			"failed to attach bridge to encoder for CRTC%u: %d\n",
+			crtc->index, ret);
+		return ret;
+	}
+
+	connector = drm_bridge_connector_init(drm, encoder);
+	if (IS_ERR(connector)) {
+		ret = PTR_ERR(connector);
+		dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
+			crtc->index, ret);
+		return ret;
+	}
+
+	ret = drm_connector_attach_encoder(connector, encoder);
+	if (ret)
+		dev_err(dev,
+			"failed to attach encoder to connector for CRTC%u: %d\n",
+			crtc->index, ret);
+
+	return ret;
+}
+
+int dc_kms_init(struct dc_drm_device *dc_drm)
+{
+	struct drm_device *drm = &dc_drm->base;
+	int ret, i;
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	drm->mode_config.min_width = 60;
+	drm->mode_config.min_height = 60;
+	drm->mode_config.max_width = 8192;
+	drm->mode_config.max_height = 8192;
+	drm->mode_config.funcs = &dc_drm_mode_config_funcs;
+
+	drm->vblank_disable_immediate = true;
+	drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
+
+	for (i = 0; i < DC_DISPLAYS; i++) {
+		ret = dc_crtc_init(dc_drm, i);
+		if (ret)
+			return ret;
+
+		ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
+		if (ret)
+			return ret;
+	}
+
+	for (i = 0; i < DC_DISPLAYS; i++) {
+		ret = dc_crtc_post_init(dc_drm, i);
+		if (ret)
+			return ret;
+	}
+
+	ret = drm_vblank_init(drm, DC_DISPLAYS);
+	if (ret) {
+		dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
+		return ret;
+	}
+
+	drm_mode_config_reset(drm);
+
+	drm_kms_helper_poll_init(drm);
+
+	return 0;
+}
+
+void dc_kms_uninit(struct dc_drm_device *dc_drm)
+{
+	drm_kms_helper_poll_fini(&dc_drm->base);
+}
diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
new file mode 100644
index 000000000000..57f6e0c15f57
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-kms.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2024 NXP
+ */
+
+#ifndef __DC_KMS_H__
+#define __DC_KMS_H__
+
+#include <linux/completion.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_vblank.h>
+
+#include "dc-de.h"
+#include "dc-fu.h"
+#include "dc-pe.h"
+
+#define DC_CRTC_IRQS	5
+
+struct dc_crtc_irq {
+	struct dc_crtc *dc_crtc;
+	unsigned int irq;
+};
+
+struct dc_crtc {
+	struct drm_crtc base;
+	struct dc_de *de;
+	struct dc_pe *pe;
+	struct dc_cf *cf_cont;
+	struct dc_cf *cf_safe;
+	struct dc_ed *ed_cont;
+	struct dc_ed *ed_safe;
+	struct dc_fg *fg;
+	unsigned int irq_dec_framecomplete;
+	unsigned int irq_dec_seqcomplete;
+	unsigned int irq_dec_shdld;
+	unsigned int irq_ed_cont_shdld;
+	unsigned int irq_ed_safe_shdld;
+	struct completion dec_seqcomplete_done;
+	struct completion dec_shdld_done;
+	struct completion ed_safe_shdld_done;
+	struct completion ed_cont_shdld_done;
+	struct drm_pending_vblank_event *event;
+	struct dc_crtc_irq irqs[DC_CRTC_IRQS];
+};
+
+struct dc_plane {
+	struct drm_plane base;
+	struct dc_fu *fu;
+	struct dc_cf *cf;
+	struct dc_lb *lb;
+	struct dc_ed *ed;
+};
+
+void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
+
+#endif /* __DC_KMS_H__ */
diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
new file mode 100644
index 000000000000..78d0d2cd3451
--- /dev/null
+++ b/drivers/gpu/drm/imx/dc/dc-plane.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/container_of.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_print.h>
+
+#include "dc-drv.h"
+#include "dc-fu.h"
+#include "dc-kms.h"
+
+#define DC_PLANE_MAX_PITCH	0x10000
+#define DC_PLANE_MAX_PIX_CNT	8192
+
+#define dc_plane_dbg(plane, fmt, ...)					\
+do {									\
+	typeof(plane) _plane = (plane);					\
+	drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,			\
+		    _plane->base.id, _plane->name, ##__VA_ARGS__);	\
+} while (0)
+
+static const uint32_t dc_plane_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+static const struct drm_plane_funcs dc_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+};
+
+static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
+{
+	return container_of(plane, struct dc_plane, base);
+}
+
+static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
+					struct drm_crtc_state *crtc_state)
+{
+	if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
+	    state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
+	    state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
+		dc_plane_dbg(state->plane, "no off screen\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
+{
+	int src_h = drm_rect_height(&state->src) >> 16;
+	int src_w = drm_rect_width(&state->src) >> 16;
+
+	if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
+		dc_plane_dbg(state->plane, "invalid source resolution\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int dc_plane_check_fb(struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
+
+	/* base address alignment */
+	if (baseaddr & 0x3) {
+		dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
+		return -EINVAL;
+	}
+
+	/* pitches[0] range */
+	if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
+		dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
+		return -EINVAL;
+	}
+
+	/* pitches[0] alignment */
+	if (fb->pitches[0] & 0x3) {
+		dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int
+dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
+{
+	struct drm_plane_state *plane_state =
+				drm_atomic_get_new_plane_state(state, plane);
+	struct drm_crtc_state *crtc_state;
+	int ret;
+
+	/* ok to disable */
+	if (!plane_state->fb)
+		return 0;
+
+	if (!plane_state->crtc) {
+		dc_plane_dbg(plane, "no CRTC in plane state\n");
+		return -EINVAL;
+	}
+
+	crtc_state =
+		drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
+	if (WARN_ON(!crtc_state))
+		return -EINVAL;
+
+	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
+						  DRM_PLANE_NO_SCALING,
+						  DRM_PLANE_NO_SCALING,
+						  true, false);
+	if (ret) {
+		dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
+		return ret;
+	}
+
+	ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
+	if (ret)
+		return ret;
+
+	ret = dc_plane_check_max_source_resolution(plane_state);
+	if (ret)
+		return ret;
+
+	return dc_plane_check_fb(plane_state);
+}
+
+static void
+dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_state =
+				drm_atomic_get_new_plane_state(state, plane);
+	struct dc_plane *dplane = to_dc_plane(plane);
+	struct drm_framebuffer *fb = new_state->fb;
+	const struct dc_fu_ops *fu_ops;
+	struct dc_lb *lb = dplane->lb;
+	struct dc_fu *fu = dplane->fu;
+	dma_addr_t baseaddr;
+	int src_w, src_h;
+	int idx;
+
+	if (!drm_dev_enter(plane->dev, &idx))
+		return;
+
+	src_w = drm_rect_width(&new_state->src) >> 16;
+	src_h = drm_rect_height(&new_state->src) >> 16;
+
+	baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
+
+	fu_ops = dc_fu_get_ops(dplane->fu);
+
+	fu_ops->set_layerblend(fu, lb);
+	fu_ops->set_burstlength(fu, baseaddr);
+	fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
+	fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
+	fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
+	fu_ops->set_framedimensions(fu, src_w, src_h);
+	fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
+	fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
+
+	dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
+
+	dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
+	dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
+	dc_lb_mode(lb, LB_BLEND);
+	dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
+	dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
+
+	dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
+
+	/* set ExtDst's source to LayerBlend */
+	dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
+
+	drm_dev_exit(idx);
+}
+
+static void dc_plane_atomic_disable(struct drm_plane *plane,
+				    struct drm_atomic_state *state)
+{
+	struct dc_plane *dplane = to_dc_plane(plane);
+	const struct dc_fu_ops *fu_ops;
+	int idx;
+
+	if (!drm_dev_enter(plane->dev, &idx))
+		return;
+
+	/* disable fetchunit in shadow */
+	fu_ops = dc_fu_get_ops(dplane->fu);
+	fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
+
+	/* set ExtDst's source to ConstFrame */
+	dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
+
+	drm_dev_exit(idx);
+}
+
+static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
+	.atomic_check = dc_plane_atomic_check,
+	.atomic_update = dc_plane_atomic_update,
+	.atomic_disable = dc_plane_atomic_disable,
+};
+
+int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
+{
+	struct drm_plane *plane = &dc_plane->base;
+	int ret;
+
+	ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
+				       dc_plane_formats,
+				       ARRAY_SIZE(dc_plane_formats),
+				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		return ret;
+
+	drm_plane_helper_add(plane, &dc_plane_helper_funcs);
+
+	dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
+	dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
+	dc_plane->lb = dc_drm->pe->lb[plane->index];
+	dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
+
+	return 0;
+}
-- 
2.34.1



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

* [PATCH v6 13/19] MAINTAINERS: Add maintainer for i.MX8qxp Display Controller
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (11 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 14/19] dt-bindings: phy: mixel, mipi-dsi-phy: Allow assigned-clock* properties Liu Ying
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Add myself as the maintainer of i.MX8qxp Display Controller.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* Improve file list. (Frank)

 MAINTAINERS | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 77dbb2ff906f..e12e899d10ae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7695,6 +7695,14 @@ F:	Documentation/devicetree/bindings/display/imx/
 F:	drivers/gpu/drm/imx/ipuv3/
 F:	drivers/gpu/ipu-v3/
 
+DRM DRIVERS FOR FREESCALE IMX8 DISPLAY CONTROLLER
+M:	Liu Ying <victor.liu@nxp.com>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+T:	git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F:	Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc*.yaml
+F:	drivers/gpu/drm/imx/dc/
+
 DRM DRIVERS FOR FREESCALE IMX BRIDGE
 M:	Liu Ying <victor.liu@nxp.com>
 L:	dri-devel@lists.freedesktop.org
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 14/19] dt-bindings: phy: mixel, mipi-dsi-phy: Allow assigned-clock* properties
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (12 preceding siblings ...)
  2024-12-09  3:39 ` [PATCH v6 13/19] MAINTAINERS: Add maintainer for i.MX8qxp Display Controller Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 15/19] dt-bindings: firmware: imx: Add SCU controlled display pixel link nodes Liu Ying
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

assigned-clock* properties can be used by default now, so allow them.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch as needed by MIPI/LVDS subsystems device tree.

 .../devicetree/bindings/phy/mixel,mipi-dsi-phy.yaml          | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.yaml b/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.yaml
index 3c28ec50f097..286a4fcc977d 100644
--- a/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.yaml
+++ b/Documentation/devicetree/bindings/phy/mixel,mipi-dsi-phy.yaml
@@ -72,11 +72,6 @@ allOf:
           contains:
             const: fsl,imx8qxp-mipi-dphy
     then:
-      properties:
-        assigned-clocks: false
-        assigned-clock-parents: false
-        assigned-clock-rates: false
-
       required:
         - fsl,syscon
 
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 15/19] dt-bindings: firmware: imx: Add SCU controlled display pixel link nodes
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (13 preceding siblings ...)
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 14/19] dt-bindings: phy: mixel, mipi-dsi-phy: Allow assigned-clock* properties Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 16/19] arm64: dts: imx8qxp: Add display controller subsystem Liu Ying
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Document SCU controlled display pixel link child nodes.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch as needed by display controller subsystem device tree.

 .../devicetree/bindings/firmware/fsl,scu.yaml | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/Documentation/devicetree/bindings/firmware/fsl,scu.yaml b/Documentation/devicetree/bindings/firmware/fsl,scu.yaml
index 557e524786c2..1a920f013ad2 100644
--- a/Documentation/devicetree/bindings/firmware/fsl,scu.yaml
+++ b/Documentation/devicetree/bindings/firmware/fsl,scu.yaml
@@ -30,6 +30,26 @@ properties:
       Clock controller node that provides the clocks controlled by the SCU
     $ref: /schemas/clock/fsl,scu-clk.yaml
 
+  dc0-pixel-link0:
+    description:
+      Display pixel link0 in display controller subsystem0 controlled by the SCU
+    $ref: /schemas/display/bridge/fsl,imx8qxp-pixel-link.yaml
+
+  dc0-pixel-link1:
+    description:
+      Display pixel link1 in display controller subsystem0 controlled by the SCU
+    $ref: /schemas/display/bridge/fsl,imx8qxp-pixel-link.yaml
+
+  dc1-pixel-link0:
+    description:
+      Display pixel link0 in display controller subsystem1 controlled by the SCU
+    $ref: /schemas/display/bridge/fsl,imx8qxp-pixel-link.yaml
+
+  dc1-pixel-link1:
+    description:
+      Display pixel link1 in display controller subsystem1 controlled by the SCU
+    $ref: /schemas/display/bridge/fsl,imx8qxp-pixel-link.yaml
+
   gpio:
     description:
       Control the GPIO PINs on SCU domain over the firmware APIs
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 16/19] arm64: dts: imx8qxp: Add display controller subsystem
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (14 preceding siblings ...)
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 15/19] dt-bindings: firmware: imx: Add SCU controlled display pixel link nodes Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 17/19] arm64: dts: imx8qxp: Add MIPI-LVDS combo subsystems Liu Ying
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Add display controller subsystem in i.MX8qxp SoC.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch. (Krzysztof)

 .../arm64/boot/dts/freescale/imx8-ss-dc0.dtsi | 408 ++++++++++++++++++
 .../boot/dts/freescale/imx8qxp-ss-dc.dtsi     | 236 ++++++++++
 arch/arm64/boot/dts/freescale/imx8qxp.dtsi    |  25 +-
 3 files changed, 668 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm64/boot/dts/freescale/imx8-ss-dc0.dtsi
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi

diff --git a/arch/arm64/boot/dts/freescale/imx8-ss-dc0.dtsi b/arch/arm64/boot/dts/freescale/imx8-ss-dc0.dtsi
new file mode 100644
index 000000000000..0db345204b89
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx8-ss-dc0.dtsi
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <dt-bindings/clock/imx8-lpcg.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+dc0_axi_ext_clk: clock-dc0-axi-ext {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <800000000>;
+	clock-output-names = "dc0_axi_ext_clk";
+};
+
+dc0_axi_int_clk: clock-dc0-axi-int {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <400000000>;
+	clock-output-names = "dc0_axi_int_clk";
+};
+
+dc0_cfg_clk: clock-dc0-cfg {
+	compatible = "fixed-clock";
+	#clock-cells = <0>;
+	clock-frequency = <100000000>;
+	clock-output-names = "dc0_cfg_clk";
+};
+
+dc0_subsys: bus@56000000 {
+	compatible = "simple-bus";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	ranges = <0x56000000 0x0 0x56000000 0x1000000>;
+
+	dc0_irqsteer: interrupt-controller@56000000 {
+		compatible = "fsl,imx-irqsteer";
+		reg = <0x56000000 0x1000>;
+		interrupt-controller;
+		interrupt-parent = <&gic>;
+		#interrupt-cells = <1>;
+		interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 43 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 44 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 46 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&dc0_lis_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "ipg";
+		fsl,channel = <0>;
+		fsl,num-irqs = <512>;
+	};
+
+	dc0_disp_lpcg: clock-controller@56010000 {
+		reg = <0x56010000 0x4>;
+		#clock-cells = <1>;
+		clocks = <&clk IMX_SC_R_DC_0 IMX_SC_PM_CLK_MISC0>,
+			 <&clk IMX_SC_R_DC_0 IMX_SC_PM_CLK_MISC1>;
+		clock-indices = <IMX_LPCG_CLK_0>, <IMX_LPCG_CLK_1>;
+		clock-output-names = "dc0_disp0_lpcg_clk", "dc0_disp1_lpcg_clk";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+
+	dc0_lis_lpcg: clock-controller@56010004 {
+		reg = <0x56010004 0x4>;
+		#clock-cells = <1>;
+		clocks = <&dc0_cfg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "dc0_lis_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+
+	dc0_disp_ctrl_link_mst0_lpcg: clock-controller@56010008 {
+		reg = <0x56010008 0x4>;
+		#clock-cells = <1>;
+		clocks = <&dc0_cfg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "dc0_disp_ctrl_link_mst0_lpcg_msi_clk";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+
+	dc0_pixel_combiner_lpcg: clock-controller@56010010 {
+		reg = <0x56010010 0x4>;
+		#clock-cells = <1>;
+		clocks = <&dc0_cfg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "dc0_pixel_combiner_lpcg_apb_clk";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+
+	dc0_lpcg: clock-controller@56010014 {
+		reg = <0x56010014 0x4>;
+		#clock-cells = <1>;
+		clocks = <&dc0_cfg_clk>, <&dc0_axi_int_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>, <IMX_LPCG_CLK_5>;
+		clock-output-names = "dc0_lpcg_cfg_clk",
+				     "dc0_lpcg_axi_clk";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+
+	dc0_pc: pixel-combiner@56020000 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0x56020000 0x10000>;
+		clocks = <&dc0_pixel_combiner_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "apb";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+		status = "disabled";
+
+		channel@0 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <0>;
+			status = "disabled";
+
+			port@0 {
+				reg = <0>;
+
+				dc0_pixel_combiner_ch0_dc0_disp0: endpoint {
+					remote-endpoint = <&dc0_disp0_dc0_pixel_combiner_ch0>;
+				};
+			};
+
+			port@1 {
+				reg = <1>;
+
+				dc0_pixel_combiner_ch0_dc0_pixel_link0: endpoint {
+					remote-endpoint = <&dc0_pixel_link0_dc0_pixel_combiner_ch0>;
+				};
+			};
+		};
+
+		channel@1 {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			reg = <1>;
+			status = "disabled";
+
+			port@0 {
+				reg = <0>;
+
+				dc0_pixel_combiner_ch1_dc0_disp1: endpoint {
+					remote-endpoint = <&dc0_disp1_dc0_pixel_combiner_ch1>;
+				};
+			};
+
+			port@1 {
+				reg = <1>;
+
+				dc0_pixel_combiner_ch1_dc0_pixel_link1: endpoint {
+					remote-endpoint = <&dc0_pixel_link1_dc0_pixel_combiner_ch1>;
+				};
+			};
+		};
+	};
+
+	dc0: display-controller@56180000 {
+		reg = <0x56180000 0x40000>;
+		clocks = <&dc0_lpcg IMX_LPCG_CLK_4>;
+		power-domains = <&pd IMX_SC_R_DC_0>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		ranges;
+		status = "disabled";
+
+		dc0_intc: interrupt-controller@56180040 {
+			reg = <0x56180040 0x60>;
+			clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+			interrupt-controller;
+			interrupt-parent = <&dc0_irqsteer>;
+			#interrupt-cells = <1>;
+			interrupts = <448>, <449>, <450>,  <64>,
+				      <65>,  <66>,  <67>,  <68>,
+				      <69>,  <70>, <193>, <194>,
+				     <195>, <196>, <197>,  <72>,
+				      <73>,  <74>,  <75>,  <76>,
+				      <77>,  <78>,  <79>,  <80>,
+				      <81>, <199>, <200>, <201>,
+				     <202>, <203>, <204>, <205>,
+				     <206>, <207>, <208>,   <5>,
+				       <0>,   <1>,   <2>,   <3>,
+				       <4>,  <82>,  <83>,  <84>,
+				      <85>, <209>, <210>, <211>,
+				     <212>;
+			interrupt-names = "store9_shdload",
+					  "store9_framecomplete",
+					  "store9_seqcomplete",
+					  "extdst0_shdload",
+					  "extdst0_framecomplete",
+					  "extdst0_seqcomplete",
+					  "extdst4_shdload",
+					  "extdst4_framecomplete",
+					  "extdst4_seqcomplete",
+					  "extdst1_shdload",
+					  "extdst1_framecomplete",
+					  "extdst1_seqcomplete",
+					  "extdst5_shdload",
+					  "extdst5_framecomplete",
+					  "extdst5_seqcomplete",
+					  "disengcfg_shdload0",
+					  "disengcfg_framecomplete0",
+					  "disengcfg_seqcomplete0",
+					  "framegen0_int0",
+					  "framegen0_int1",
+					  "framegen0_int2",
+					  "framegen0_int3",
+					  "sig0_shdload",
+					  "sig0_valid",
+					  "sig0_error",
+					  "disengcfg_shdload1",
+					  "disengcfg_framecomplete1",
+					  "disengcfg_seqcomplete1",
+					  "framegen1_int0",
+					  "framegen1_int1",
+					  "framegen1_int2",
+					  "framegen1_int3",
+					  "sig1_shdload",
+					  "sig1_valid",
+					  "sig1_error",
+					  "reserved",
+					  "cmdseq_error",
+					  "comctrl_sw0",
+					  "comctrl_sw1",
+					  "comctrl_sw2",
+					  "comctrl_sw3",
+					  "framegen0_primsync_on",
+					  "framegen0_primsync_off",
+					  "framegen0_secsync_on",
+					  "framegen0_secsync_off",
+					  "framegen1_primsync_on",
+					  "framegen1_primsync_off",
+					  "framegen1_secsync_on",
+					  "framegen1_secsync_off";
+		};
+
+		dc0_pixel_engine: pixel-engine@56180800 {
+			reg = <0x56180800 0xac00>;
+			clocks = <&dc0_lpcg IMX_LPCG_CLK_5>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			ranges;
+
+			dc0_constframe0: constframe@56180960 {
+				reg = <0x56180960 0xc>, <0x56184400 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_extdst0: extdst@56180980 {
+				reg = <0x56180980 0x1c>, <0x56184800 0x28>;
+				reg-names = "pec", "cfg";
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <3>, <4>, <5>;
+				interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			};
+
+			dc0_constframe4: constframe@561809a0 {
+				reg = <0x561809a0 0xc>, <0x56184c00 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_extdst4: extdst@561809c0 {
+				reg = <0x561809c0 0x1c>, <0x56185000 0x28>;
+				reg-names = "pec", "cfg";
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <6>, <7>, <8>;
+				interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			};
+
+			dc0_constframe1: constframe@561809e0 {
+				reg = <0x561809e0 0xc>, <0x56185400 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_extdst1: extdst@56180a00 {
+				reg = <0x56180a00 0x1c>, <0x56185800 0x28>;
+				reg-names = "pec", "cfg";
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <9>, <10>, <11>;
+				interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			};
+
+			dc0_constframe5: constframe@56180a20 {
+				reg = <0x56180a20 0xc>, <0x56185c00 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_extdst5: extdst@56180a40 {
+				reg = <0x56180a40 0x1c>, <0x56186000 0x28>;
+				reg-names = "pec", "cfg";
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <12>, <13>, <14>;
+				interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			};
+
+			dc0_fetchwarp2: fetchwarp@56180a60 {
+				reg = <0x56180a60 0x10>, <0x56186400 0x190>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_fetchlayer0: fetchlayer@56180ac0 {
+				reg = <0x56180ac0 0xc>, <0x56188400 0x404>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_layerblend0: layerblend@56180ba0 {
+				reg = <0x56180ba0 0x10>, <0x5618a400 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_layerblend1: layerblend@56180bc0 {
+				reg = <0x56180bc0 0x10>, <0x5618a800 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_layerblend2: layerblend@56180be0 {
+				reg = <0x56180be0 0x10>, <0x5618ac00 0x20>;
+				reg-names = "pec", "cfg";
+			};
+
+			dc0_layerblend3: layerblend@56180c00 {
+				reg = <0x56180c00 0x10>, <0x5618b000 0x20>;
+				reg-names = "pec", "cfg";
+			};
+		};
+
+		dc0_display_engine0: display-engine@5618b400 {
+			reg = <0x5618b400 0x14>, <0x5618b800 0x1c00>;
+			reg-names = "top", "cfg";
+			interrupt-parent = <&dc0_intc>;
+			interrupts = <15>, <16>, <17>;
+			interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			power-domains = <&pd IMX_SC_R_DC_0_PLL_0>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			ranges;
+
+			dc0_framegen0: framegen@5618b800 {
+				reg = <0x5618b800 0x98>;
+				clocks = <&dc0_disp_lpcg IMX_LPCG_CLK_0>;
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <18>, <19>, <20>, <21>,
+					     <41>, <42>, <43>, <44>;
+				interrupt-names = "int0", "int1", "int2", "int3",
+						  "primsync_on", "primsync_off",
+						  "secsync_on", "secsync_off";
+			};
+
+			dc0_tcon0: tcon@5618c800 {
+				reg = <0x5618c800 0x588>;
+
+				port {
+					dc0_disp0_dc0_pixel_combiner_ch0: endpoint {
+						remote-endpoint = <&dc0_pixel_combiner_ch0_dc0_disp0>;
+					};
+				};
+			};
+		};
+
+		dc0_display_engine1: display-engine@5618b420 {
+			reg = <0x5618b420 0x14>, <0x5618d400 0x1c00>;
+			reg-names = "top", "cfg";
+			interrupt-parent = <&dc0_intc>;
+			interrupts = <25>, <26>, <27>;
+			interrupt-names = "shdload", "framecomplete", "seqcomplete";
+			power-domains = <&pd IMX_SC_R_DC_0_PLL_1>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			ranges;
+
+			dc0_framegen1: framegen@5618d400 {
+				reg = <0x5618d400 0x98>;
+				clocks = <&dc0_disp_lpcg IMX_LPCG_CLK_1>;
+				interrupt-parent = <&dc0_intc>;
+				interrupts = <28>, <29>, <30>, <31>,
+					     <45>, <46>, <47>, <48>;
+				interrupt-names = "int0", "int1", "int2", "int3",
+						  "primsync_on", "primsync_off",
+						  "secsync_on", "secsync_off";
+			};
+
+			dc0_tcon1: tcon@5618e400 {
+				reg = <0x5618e400 0x588>;
+
+				port {
+					dc0_disp1_dc0_pixel_combiner_ch1: endpoint {
+						remote-endpoint = <&dc0_pixel_combiner_ch1_dc0_disp1>;
+					};
+				};
+			};
+		};
+	};
+
+	dc0_pl_msi_bus: bus@56200000 {
+		reg = <0x56200000 0x100000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		interrupt-parent = <&dc0_irqsteer>;
+		interrupts = <320>;
+		ranges;
+		clocks = <&dc0_disp_ctrl_link_mst0_lpcg IMX_LPCG_CLK_4>,
+			 <&dc0_disp_ctrl_link_mst0_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "msi", "ahb";
+		power-domains = <&pd IMX_SC_R_DC_0>;
+	};
+};
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi b/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
new file mode 100644
index 000000000000..299720d8c99e
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+&dc0 {
+	compatible = "fsl,imx8qxp-dc";
+};
+
+&dc0_constframe0 {
+	compatible = "fsl,imx8qxp-dc-constframe";
+};
+
+&dc0_constframe1 {
+	compatible = "fsl,imx8qxp-dc-constframe";
+};
+
+&dc0_constframe4 {
+	compatible = "fsl,imx8qxp-dc-constframe";
+};
+
+&dc0_constframe5 {
+	compatible = "fsl,imx8qxp-dc-constframe";
+};
+
+&dc0_disp_ctrl_link_mst0_lpcg {
+	compatible = "fsl,imx8qxp-lpcg";
+};
+
+&dc0_disp_lpcg {
+	compatible = "fsl,imx8qxp-lpcg";
+};
+
+&dc0_display_engine0 {
+	compatible = "fsl,imx8qxp-dc-display-engine";
+};
+
+&dc0_display_engine1 {
+	compatible = "fsl,imx8qxp-dc-display-engine";
+};
+
+&dc0_extdst0 {
+	compatible = "fsl,imx8qxp-dc-extdst";
+};
+
+&dc0_extdst1 {
+	compatible = "fsl,imx8qxp-dc-extdst";
+};
+
+&dc0_extdst4 {
+	compatible = "fsl,imx8qxp-dc-extdst";
+};
+
+&dc0_extdst5 {
+	compatible = "fsl,imx8qxp-dc-extdst";
+};
+
+&dc0_fetchlayer0 {
+	compatible = "fsl,imx8qxp-dc-fetchlayer";
+};
+
+&dc0_fetchwarp2 {
+	compatible = "fsl,imx8qxp-dc-fetchwarp";
+};
+
+&dc0_framegen0 {
+	compatible = "fsl,imx8qxp-dc-framegen";
+};
+
+&dc0_framegen1 {
+	compatible = "fsl,imx8qxp-dc-framegen";
+};
+
+&dc0_intc {
+	compatible = "fsl,imx8qxp-dc-intc";
+};
+
+&dc0_layerblend0 {
+	compatible = "fsl,imx8qxp-dc-layerblend";
+};
+
+&dc0_layerblend1 {
+	compatible = "fsl,imx8qxp-dc-layerblend";
+};
+
+&dc0_layerblend2 {
+	compatible = "fsl,imx8qxp-dc-layerblend";
+};
+
+&dc0_layerblend3 {
+	compatible = "fsl,imx8qxp-dc-layerblend";
+};
+
+&dc0_lis_lpcg {
+	compatible = "fsl,imx8qxp-lpcg";
+};
+
+&dc0_lpcg {
+	compatible = "fsl,imx8qxp-lpcg";
+};
+
+&dc0_pc {
+	compatible = "fsl,imx8qxp-pixel-combiner";
+};
+
+&dc0_pixel_combiner_lpcg {
+	compatible = "fsl,imx8qxp-lpcg";
+};
+
+&dc0_pixel_engine {
+	compatible = "fsl,imx8qxp-dc-pixel-engine";
+};
+
+&dc0_pl_msi_bus {
+	compatible = "fsl,imx8qxp-display-pixel-link-msi-bus", "simple-pm-bus";
+};
+
+&dc0_tcon0 {
+	compatible = "fsl,imx8qxp-dc-tcon";
+};
+
+&dc0_tcon1 {
+	compatible = "fsl,imx8qxp-dc-tcon";
+};
+
+&scu {
+	dc0_pixel_link0: dc0-pixel-link0 {
+		compatible = "fsl,imx8qxp-dc-pixel-link";
+		fsl,dc-id = /bits/ 8 <0>;
+		fsl,dc-stream-id = /bits/ 8 <0>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* from dc0 pixel combiner channel0 */
+			port@0 {
+				reg = <0>;
+
+				dc0_pixel_link0_dc0_pixel_combiner_ch0: endpoint {
+					remote-endpoint = <&dc0_pixel_combiner_ch0_dc0_pixel_link0>;
+				};
+			};
+
+			/* to PXL2DPIs in MIPI/LVDS combo subsystems */
+			port@1 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <1>;
+				status = "disabled";
+
+				dc0_pixel_link0_mipi_lvds_0_pxl2dpi: endpoint@0 {
+					reg = <0>;
+				};
+
+				dc0_pixel_link0_mipi_lvds_1_pxl2dpi: endpoint@1 {
+					reg = <1>;
+				};
+			};
+
+			/* unused */
+			port@2 {
+				reg = <2>;
+				status = "disabled";
+			};
+
+			/* unused */
+			port@3 {
+				reg = <3>;
+				status = "disabled";
+			};
+
+			/* to imaging subsystem */
+			port@4 {
+				reg = <4>;
+				status = "disabled";
+			};
+		};
+	};
+
+	dc0_pixel_link1: dc0-pixel-link1 {
+		compatible = "fsl,imx8qxp-dc-pixel-link";
+		fsl,dc-id = /bits/ 8 <0>;
+		fsl,dc-stream-id = /bits/ 8 <1>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			/* from dc0 pixel combiner channel1 */
+			port@0 {
+				reg = <0>;
+
+				dc0_pixel_link1_dc0_pixel_combiner_ch1: endpoint {
+					remote-endpoint = <&dc0_pixel_combiner_ch1_dc0_pixel_link1>;
+				};
+			};
+
+			/* to PXL2DPIs in MIPI/LVDS combo subsystems */
+			port@1 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <1>;
+				status = "disabled";
+
+				dc0_pixel_link1_mipi_lvds_1_pxl2dpi: endpoint@0 {
+					reg = <0>;
+				};
+
+				dc0_pixel_link1_mipi_lvds_0_pxl2dpi: endpoint@1 {
+					reg = <1>;
+				};
+			};
+
+			/* to parallel display interface in ADMA subsystem */
+			port@2 {
+				reg = <2>;
+				status = "disabled";
+			};
+
+			/* unused */
+			port@3 {
+				reg = <3>;
+				status = "disabled";
+			};
+
+			/* unused */
+			port@4 {
+				reg = <4>;
+				status = "disabled";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp.dtsi b/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
index 05138326f0a5..35cc82cbbcd1 100644
--- a/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
@@ -20,6 +20,27 @@ / {
 	#size-cells = <2>;
 
 	aliases {
+		dc0 = &dc0;
+		dc0-constframe0 = &dc0_constframe0;
+		dc0-constframe1 = &dc0_constframe1;
+		dc0-constframe4 = &dc0_constframe4;
+		dc0-constframe5 = &dc0_constframe5;
+		dc0-display-engine0 = &dc0_display_engine0;
+		dc0-display-engine1 = &dc0_display_engine1;
+		dc0-extdst0 = &dc0_extdst0;
+		dc0-extdst1 = &dc0_extdst1;
+		dc0-extdst4 = &dc0_extdst4;
+		dc0-extdst5 = &dc0_extdst5;
+		dc0-fetchlayer0 = &dc0_fetchlayer0;
+		dc0-fetchwarp2 = &dc0_fetchwarp2;
+		dc0-framegen0 = &dc0_framegen0;
+		dc0-framegen1 = &dc0_framegen1;
+		dc0-layerblend0 = &dc0_layerblend0;
+		dc0-layerblend1 = &dc0_layerblend1;
+		dc0-layerblend2 = &dc0_layerblend2;
+		dc0-layerblend3 = &dc0_layerblend3;
+		dc0-tcon0 = &dc0_tcon0;
+		dc0-tcon1 = &dc0_tcon1;
 		ethernet0 = &fec1;
 		ethernet1 = &fec2;
 		gpio0 = &lsio_gpio0;
@@ -206,7 +227,7 @@ psci {
 		method = "smc";
 	};
 
-	system-controller {
+	scu: system-controller {
 		compatible = "fsl,imx-scu";
 		mbox-names = "tx0",
 			     "rx0",
@@ -323,6 +344,7 @@ map0 {
 	#include "imx8-ss-vpu.dtsi"
 	#include "imx8-ss-cm40.dtsi"
 	#include "imx8-ss-gpu0.dtsi"
+	#include "imx8-ss-dc0.dtsi"
 	#include "imx8-ss-adma.dtsi"
 	#include "imx8-ss-conn.dtsi"
 	#include "imx8-ss-ddr.dtsi"
@@ -332,6 +354,7 @@ map0 {
 
 #include "imx8qxp-ss-img.dtsi"
 #include "imx8qxp-ss-vpu.dtsi"
+#include "imx8qxp-ss-dc.dtsi"
 #include "imx8qxp-ss-adma.dtsi"
 #include "imx8qxp-ss-conn.dtsi"
 #include "imx8qxp-ss-lsio.dtsi"
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 17/19] arm64: dts: imx8qxp: Add MIPI-LVDS combo subsystems
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (15 preceding siblings ...)
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 16/19] arm64: dts: imx8qxp: Add display controller subsystem Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 18/19] arm64: dts: imx8qxp-mek: Enable display controller Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 19/19] arm64: dts: imx8qxp-mek: Add MX8-DLVDS-LCD1 display module support Liu Ying
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

The MIPI-LVDS combo subsystems are peripherals of pixel link MSI
bus in i.MX8qxp display controller subsystem.  Add the MIPI-LVDS
combo subsystems.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch. (Francesco)

 .../boot/dts/freescale/imx8qxp-ss-dc.dtsi     |   4 +
 .../dts/freescale/imx8qxp-ss-mipi-lvds.dtsi   | 437 ++++++++++++++++++
 arch/arm64/boot/dts/freescale/imx8qxp.dtsi    |   3 +
 3 files changed, 444 insertions(+)
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-ss-mipi-lvds.dtsi

diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi b/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
index 299720d8c99e..94c46a20597c 100644
--- a/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-ss-dc.dtsi
@@ -152,10 +152,12 @@ port@1 {
 
 				dc0_pixel_link0_mipi_lvds_0_pxl2dpi: endpoint@0 {
 					reg = <0>;
+					remote-endpoint = <&mipi_lvds_0_pxl2dpi_dc0_pixel_link0>;
 				};
 
 				dc0_pixel_link0_mipi_lvds_1_pxl2dpi: endpoint@1 {
 					reg = <1>;
+					remote-endpoint = <&mipi_lvds_1_pxl2dpi_dc0_pixel_link0>;
 				};
 			};
 
@@ -207,10 +209,12 @@ port@1 {
 
 				dc0_pixel_link1_mipi_lvds_1_pxl2dpi: endpoint@0 {
 					reg = <0>;
+					remote-endpoint = <&mipi_lvds_1_pxl2dpi_dc0_pixel_link1>;
 				};
 
 				dc0_pixel_link1_mipi_lvds_0_pxl2dpi: endpoint@1 {
 					reg = <1>;
+					remote-endpoint = <&mipi_lvds_0_pxl2dpi_dc0_pixel_link1>;
 				};
 			};
 
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-ss-mipi-lvds.dtsi b/arch/arm64/boot/dts/freescale/imx8qxp-ss-mipi-lvds.dtsi
new file mode 100644
index 000000000000..fa7e7c33518e
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-ss-mipi-lvds.dtsi
@@ -0,0 +1,437 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <dt-bindings/clock/imx8-lpcg.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+/ {
+	mipi_lvds_0_ipg_clk: clock-mipi-lvds0-ipg {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <120000000>;
+		clock-output-names = "mipi_lvds_0_ipg_clk";
+	};
+
+	mipi_lvds_1_ipg_clk: clock-mipi-lvds1-ipg {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <120000000>;
+		clock-output-names = "mipi_lvds_1_ipg_clk";
+	};
+};
+
+&dc0_pl_msi_bus {
+	mipi_lvds_0_irqsteer: interrupt-controller@56220000 {
+		compatible = "fsl,imx-irqsteer";
+		reg = <0x56220000 0x1000>;
+		interrupts = <GIC_SPI 59 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-controller;
+		interrupt-parent = <&gic>;
+		#interrupt-cells = <1>;
+		clocks = <&mipi_lvds_0_lis_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "ipg";
+		fsl,channel = <0>;
+		fsl,num-irqs = <32>;
+	};
+
+	mipi_lvds_0_csr: syscon@56221000 {
+		compatible = "fsl,imx8qxp-mipi-lvds-csr", "syscon", "simple-mfd";
+		reg = <0x56221000 0x1000>;
+		clocks = <&mipi_lvds_0_di_mipi_lvds_regs_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "ipg";
+
+		mipi_lvds_0_pxl2dpi: pxl2dpi {
+			compatible = "fsl,imx8qxp-pxl2dpi";
+			fsl,sc-resource = <IMX_SC_R_MIPI_0>;
+			power-domains = <&pd IMX_SC_R_MIPI_0>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+
+					mipi_lvds_0_pxl2dpi_dc0_pixel_link0: endpoint@0 {
+						reg = <0>;
+						remote-endpoint = <&dc0_pixel_link0_mipi_lvds_0_pxl2dpi>;
+						status = "disabled";
+					};
+
+					mipi_lvds_0_pxl2dpi_dc0_pixel_link1: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&dc0_pixel_link1_mipi_lvds_0_pxl2dpi>;
+						status = "disabled";
+					};
+				};
+
+				port@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+
+					mipi_lvds_0_pxl2dpi_mipi_lvds_0_ldb_ch0: endpoint@0 {
+						reg = <0>;
+						remote-endpoint = <&mipi_lvds_0_ldb_ch0_mipi_lvds_0_pxl2dpi>;
+						status = "disabled";
+					};
+
+					mipi_lvds_0_pxl2dpi_mipi_lvds_0_ldb_ch1: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&mipi_lvds_0_ldb_ch1_mipi_lvds_0_pxl2dpi>;
+						status = "disabled";
+					};
+				};
+			};
+		};
+
+		mipi_lvds_0_ldb: ldb {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "fsl,imx8qxp-ldb";
+			clocks = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_MISC2>,
+				 <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_BYPASS>;
+			clock-names = "pixel", "bypass";
+			assigned-clocks = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_MISC2>;
+			assigned-clock-parents = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_BYPASS>;
+			power-domains = <&pd IMX_SC_R_LVDS_0>;
+			status = "disabled";
+
+			channel@0 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0>;
+				phys = <&mipi_lvds_0_phy>;
+				phy-names = "lvds_phy";
+				status = "disabled";
+
+				port@0 {
+					reg = <0>;
+
+					mipi_lvds_0_ldb_ch0_mipi_lvds_0_pxl2dpi: endpoint {
+						remote-endpoint = <&mipi_lvds_0_pxl2dpi_mipi_lvds_0_ldb_ch0>;
+					};
+				};
+			};
+
+			channel@1 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <1>;
+				phys = <&mipi_lvds_0_phy>;
+				phy-names = "lvds_phy";
+				status = "disabled";
+
+				port@0 {
+					reg = <0>;
+
+					mipi_lvds_0_ldb_ch1_mipi_lvds_0_pxl2dpi: endpoint {
+						remote-endpoint = <&mipi_lvds_0_pxl2dpi_mipi_lvds_0_ldb_ch1>;
+					};
+				};
+			};
+		};
+	};
+
+	mipi_lvds_0_lis_lpcg: clock-controller@56223000 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56223000 0x4>;
+		#clock-cells = <1>;
+		clocks = <&mipi_lvds_0_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_0_lis_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_0>;
+	};
+
+	mipi_lvds_0_di_mipi_lvds_regs_lpcg: clock-controller@56223004 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56223004 0x4>;
+		#clock-cells = <1>;
+		clocks = <&mipi_lvds_0_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_0_di_mipi_lvds_regs_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_0>;
+	};
+
+	mipi_lvds_0_pwm_lpcg: clock-controller@5622300c {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x5622300c 0x4>;
+		#clock-cells = <1>;
+		clocks = <&clk IMX_SC_R_MIPI_0_PWM_0 IMX_SC_PM_CLK_PER>,
+			 <&mipi_lvds_0_ipg_clk>,
+			 <&mipi_lvds_0_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_0>,
+				<IMX_LPCG_CLK_4>,
+				<IMX_LPCG_CLK_1>;
+		clock-output-names = "mipi_lvds_0_pwm_lpcg_clk",
+				     "mipi_lvds_0_pwm_lpcg_ipg_clk",
+				     "mipi_lvds_0_pwm_lpcg_32k_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_0_PWM_0>;
+	};
+
+	mipi_lvds_0_i2c0_lpcg: clock-controller@56223010 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56223010 0x4>;
+		#clock-cells = <1>;
+		clocks = <&clk IMX_SC_R_MIPI_0_I2C_0 IMX_SC_PM_CLK_PER>,
+			 <&mipi_lvds_0_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_0>,
+				<IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_0_i2c0_lpcg_clk",
+				     "mipi_lvds_0_i2c0_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_0_I2C_0>;
+	};
+
+	mipi_lvds_0_pwm: pwm@56224000 {
+		compatible = "fsl,imx8qxp-pwm", "fsl,imx27-pwm";
+		reg = <0x56224000 0x1000>;
+		interrupt-parent = <&mipi_lvds_0_irqsteer>;
+		interrupts = <12>;
+		clocks = <&mipi_lvds_0_pwm_lpcg IMX_LPCG_CLK_4>,
+			 <&mipi_lvds_0_pwm_lpcg IMX_LPCG_CLK_0>;
+		clock-names = "ipg", "per";
+		assigned-clocks = <&clk IMX_SC_R_MIPI_0_PWM_0 IMX_SC_PM_CLK_PER>;
+		assigned-clock-rates = <24000000>;
+		#pwm-cells = <3>;
+		power-domains = <&pd IMX_SC_R_MIPI_0_PWM_0>;
+		status = "disabled";
+	};
+
+	mipi_lvds_0_i2c0: i2c@56226000 {
+		compatible = "fsl,imx8qxp-lpi2c", "fsl,imx7ulp-lpi2c";
+		reg = <0x56226000 0x1000>;
+		interrupt-parent = <&mipi_lvds_0_irqsteer>;
+		interrupts = <8>;
+		clocks = <&mipi_lvds_0_i2c0_lpcg IMX_LPCG_CLK_0>,
+			 <&mipi_lvds_0_i2c0_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "per", "ipg";
+		assigned-clocks = <&clk IMX_SC_R_MIPI_0_I2C_0 IMX_SC_PM_CLK_PER>;
+		assigned-clock-rates = <24000000>;
+		power-domains = <&pd IMX_SC_R_MIPI_0_I2C_0>;
+		status = "disabled";
+	};
+
+	mipi_lvds_0_phy: phy@56228300 {
+		compatible = "fsl,imx8qxp-mipi-dphy";
+		reg = <0x56228300 0x100>;
+		clocks = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_PHY>;
+		clock-names = "phy_ref";
+		assigned-clocks = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_PHY>;
+		assigned-clock-parents = <&clk IMX_SC_R_LVDS_0 IMX_SC_PM_CLK_BYPASS>;
+		#phy-cells = <0>;
+		fsl,syscon = <&mipi_lvds_0_csr>;
+		power-domains = <&pd IMX_SC_R_MIPI_0>;
+		status = "disabled";
+	};
+
+	mipi_lvds_1_irqsteer: interrupt-controller@56240000 {
+		compatible = "fsl,imx-irqsteer";
+		reg = <0x56240000 0x1000>;
+		interrupts = <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-controller;
+		interrupt-parent = <&gic>;
+		#interrupt-cells = <1>;
+		clocks = <&mipi_lvds_1_lis_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "ipg";
+		fsl,channel = <0>;
+		fsl,num-irqs = <32>;
+	};
+
+	mipi_lvds_1_csr: syscon@56241000 {
+		compatible = "fsl,imx8qxp-mipi-lvds-csr", "syscon", "simple-mfd";
+		reg = <0x56241000 0x1000>;
+		clocks = <&mipi_lvds_1_di_mipi_lvds_regs_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "ipg";
+
+		mipi_lvds_1_pxl2dpi: pxl2dpi {
+			compatible = "fsl,imx8qxp-pxl2dpi";
+			fsl,sc-resource = <IMX_SC_R_MIPI_1>;
+			power-domains = <&pd IMX_SC_R_MIPI_1>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+
+					mipi_lvds_1_pxl2dpi_dc0_pixel_link1: endpoint@0 {
+						reg = <0>;
+						remote-endpoint = <&dc0_pixel_link1_mipi_lvds_1_pxl2dpi>;
+						status = "disabled";
+					};
+
+					mipi_lvds_1_pxl2dpi_dc0_pixel_link0: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&dc0_pixel_link0_mipi_lvds_1_pxl2dpi>;
+						status = "disabled";
+					};
+				};
+
+				port@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+
+					mipi_lvds_1_pxl2dpi_mipi_lvds_1_ldb_ch0: endpoint@0 {
+						reg = <0>;
+						remote-endpoint = <&mipi_lvds_1_ldb_ch0_mipi_lvds_1_pxl2dpi>;
+						status = "disabled";
+					};
+
+					mipi_lvds_1_pxl2dpi_mipi_lvds_1_ldb_ch1: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&mipi_lvds_1_ldb_ch1_mipi_lvds_1_pxl2dpi>;
+						status = "disabled";
+					};
+				};
+			};
+		};
+
+		mipi_lvds_1_ldb: ldb {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			compatible = "fsl,imx8qxp-ldb";
+			clocks = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_MISC2>,
+				 <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_BYPASS>;
+			clock-names = "pixel", "bypass";
+			assigned-clocks = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_MISC2>;
+			assigned-clock-parents = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_BYPASS>;
+			power-domains = <&pd IMX_SC_R_LVDS_1>;
+			status = "disabled";
+
+			channel@0 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0>;
+				phys = <&mipi_lvds_1_phy>;
+				phy-names = "lvds_phy";
+				status = "disabled";
+
+				port@0 {
+					reg = <0>;
+
+					mipi_lvds_1_ldb_ch0_mipi_lvds_1_pxl2dpi: endpoint {
+						remote-endpoint = <&mipi_lvds_1_pxl2dpi_mipi_lvds_1_ldb_ch0>;
+					};
+				};
+			};
+
+			channel@1 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <1>;
+				phys = <&mipi_lvds_1_phy>;
+				phy-names = "lvds_phy";
+				status = "disabled";
+
+				port@0 {
+					reg = <0>;
+
+					mipi_lvds_1_ldb_ch1_mipi_lvds_1_pxl2dpi: endpoint {
+						remote-endpoint = <&mipi_lvds_1_pxl2dpi_mipi_lvds_1_ldb_ch1>;
+					};
+				};
+			};
+		};
+	};
+
+	mipi_lvds_1_lis_lpcg: clock-controller@56243000 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56243000 0x4>;
+		#clock-cells = <1>;
+		clocks = <&mipi_lvds_1_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_1_lis_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_1>;
+	};
+
+	mipi_lvds_1_di_mipi_lvds_regs_lpcg: clock-controller@56243004 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56243004 0x4>;
+		#clock-cells = <1>;
+		clocks = <&mipi_lvds_1_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_1_di_mipi_lvds_regs_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_1>;
+	};
+
+	mipi_lvds_1_pwm_lpcg: clock-controller@5624300c {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x5624300c 0x4>;
+		#clock-cells = <1>;
+		clocks = <&clk IMX_SC_R_MIPI_1_PWM_0 IMX_SC_PM_CLK_PER>,
+			 <&mipi_lvds_1_ipg_clk>,
+			 <&mipi_lvds_1_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_0>,
+				<IMX_LPCG_CLK_4>,
+				<IMX_LPCG_CLK_1>;
+		clock-output-names = "mipi_lvds_1_pwm_lpcg_clk",
+				     "mipi_lvds_1_pwm_lpcg_ipg_clk",
+				     "mipi_lvds_1_pwm_lpcg_32k_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_1_PWM_0>;
+	};
+
+	mipi_lvds_1_i2c0_lpcg: clock-controller@56243010 {
+		compatible = "fsl,imx8qxp-lpcg";
+		reg = <0x56243010 0x4>;
+		#clock-cells = <1>;
+		clocks = <&clk IMX_SC_R_MIPI_1_I2C_0 IMX_SC_PM_CLK_PER>,
+			 <&mipi_lvds_1_ipg_clk>;
+		clock-indices = <IMX_LPCG_CLK_0>,
+				<IMX_LPCG_CLK_4>;
+		clock-output-names = "mipi_lvds_1_i2c0_lpcg_clk",
+				     "mipi_lvds_1_i2c0_lpcg_ipg_clk";
+		power-domains = <&pd IMX_SC_R_MIPI_1_I2C_0>;
+	};
+
+	mipi_lvds_1_pwm: pwm@56244000 {
+		compatible = "fsl,imx8qxp-pwm", "fsl,imx27-pwm";
+		reg = <0x56244000 0x1000>;
+		interrupt-parent = <&mipi_lvds_1_irqsteer>;
+		interrupts = <12>;
+		clocks = <&mipi_lvds_1_pwm_lpcg IMX_LPCG_CLK_4>,
+			 <&mipi_lvds_1_pwm_lpcg IMX_LPCG_CLK_0>;
+		clock-names = "ipg", "per";
+		assigned-clocks = <&clk IMX_SC_R_MIPI_1_PWM_0 IMX_SC_PM_CLK_PER>;
+		assigned-clock-rates = <24000000>;
+		#pwm-cells = <3>;
+		power-domains = <&pd IMX_SC_R_MIPI_1_PWM_0>;
+		status = "disabled";
+	};
+
+	mipi_lvds_1_i2c0: i2c@56246000 {
+		compatible = "fsl,imx8qxp-lpi2c", "fsl,imx7ulp-lpi2c";
+		reg = <0x56246000 0x1000>;
+		interrupt-parent = <&mipi_lvds_1_irqsteer>;
+		interrupts = <8>;
+		clocks = <&mipi_lvds_1_i2c0_lpcg IMX_LPCG_CLK_0>,
+			 <&mipi_lvds_1_i2c0_lpcg IMX_LPCG_CLK_4>;
+		clock-names = "per", "ipg";
+		assigned-clocks = <&clk IMX_SC_R_MIPI_1_I2C_0 IMX_SC_PM_CLK_PER>;
+		assigned-clock-rates = <24000000>;
+		power-domains = <&pd IMX_SC_R_MIPI_1_I2C_0>;
+		status = "disabled";
+	};
+
+	mipi_lvds_1_phy: phy@56248300 {
+		compatible = "fsl,imx8qxp-mipi-dphy";
+		reg = <0x56248300 0x100>;
+		clocks = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_PHY>;
+		clock-names = "phy_ref";
+		assigned-clocks = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_PHY>;
+		assigned-clock-parents = <&clk IMX_SC_R_LVDS_1 IMX_SC_PM_CLK_BYPASS>;
+		#phy-cells = <0>;
+		fsl,syscon = <&mipi_lvds_1_csr>;
+		power-domains = <&pd IMX_SC_R_MIPI_1>;
+		status = "disabled";
+	};
+};
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp.dtsi b/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
index 35cc82cbbcd1..25adc1090dec 100644
--- a/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx8qxp.dtsi
@@ -55,6 +55,8 @@ aliases {
 		i2c1 = &i2c1;
 		i2c2 = &i2c2;
 		i2c3 = &i2c3;
+		mipi-dphy0 = &mipi_lvds_0_phy;
+		mipi-dphy1 = &mipi_lvds_1_phy;
 		mmc0 = &usdhc1;
 		mmc1 = &usdhc2;
 		mmc2 = &usdhc3;
@@ -355,6 +357,7 @@ map0 {
 #include "imx8qxp-ss-img.dtsi"
 #include "imx8qxp-ss-vpu.dtsi"
 #include "imx8qxp-ss-dc.dtsi"
+#include "imx8qxp-ss-mipi-lvds.dtsi"
 #include "imx8qxp-ss-adma.dtsi"
 #include "imx8qxp-ss-conn.dtsi"
 #include "imx8qxp-ss-lsio.dtsi"
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 18/19] arm64: dts: imx8qxp-mek: Enable display controller
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (16 preceding siblings ...)
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 17/19] arm64: dts: imx8qxp: Add MIPI-LVDS combo subsystems Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 19/19] arm64: dts: imx8qxp-mek: Add MX8-DLVDS-LCD1 display module support Liu Ying
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

Enable display controller for i.MX8qxp MEK.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch. (Francesco)

 arch/arm64/boot/dts/freescale/imx8qxp-mek.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts b/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
index be79c793213a..c7b4015c7bf7 100644
--- a/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
@@ -234,6 +234,10 @@ &asrc0 {
 	status = "okay";
 };
 
+&dc0 {
+	status = "okay";
+};
+
 &dsp {
 	memory-region = <&dsp_vdev0buffer>, <&dsp_vdev0vring0>,
 			<&dsp_vdev0vring1>, <&dsp_reserved>;
-- 
2.34.1



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

* [DO NOT MERGE PATCH v6 19/19] arm64: dts: imx8qxp-mek: Add MX8-DLVDS-LCD1 display module support
  2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
                   ` (17 preceding siblings ...)
  2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 18/19] arm64: dts: imx8qxp-mek: Enable display controller Liu Ying
@ 2024-12-09  3:39 ` Liu Ying
  18 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-09  3:39 UTC (permalink / raw)
  To: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy
  Cc: p.zabel, maarten.lankhorst, mripard, tzimmermann, airlied, simona,
	robh, krzk+dt, conor+dt, shawnguo, s.hauer, kernel, festevam,
	tglx, vkoul, kishon, aisheng.dong, agx, francesco, frank.li,
	dmitry.baryshkov, u.kleine-koenig

MX8-DLVDS-LCD1 display module integrates a KOE TX26D202VM0BWA LCD panel
and a touch IC.  Add an overlay to support the LCD panel on i.MX8qxp
MEK.  mipi_lvds_0_ldb channel0 and mipi_lvds_1_ldb channel1 send odd
and even pixels to the panel respectively.

Signed-off-by: Liu Ying <victor.liu@nxp.com>
---
v6:
* No change.

v5:
* No change.

v4:
* No change.

v3:
* No change.

v2:
* New patch. (Francesco)

 arch/arm64/boot/dts/freescale/Makefile        |   4 +
 .../imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso | 183 ++++++++++++++++++
 arch/arm64/boot/dts/freescale/imx8qxp-mek.dts |  30 +++
 3 files changed, 217 insertions(+)
 create mode 100644 arch/arm64/boot/dts/freescale/imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso

diff --git a/arch/arm64/boot/dts/freescale/Makefile b/arch/arm64/boot/dts/freescale/Makefile
index 42e6482a31cb..a22476e81cc7 100644
--- a/arch/arm64/boot/dts/freescale/Makefile
+++ b/arch/arm64/boot/dts/freescale/Makefile
@@ -254,6 +254,10 @@ dtb-$(CONFIG_ARCH_MXC) += imx8qxp-colibri-eval-v3.dtb
 dtb-$(CONFIG_ARCH_MXC) += imx8qxp-colibri-iris.dtb
 dtb-$(CONFIG_ARCH_MXC) += imx8qxp-colibri-iris-v2.dtb
 dtb-$(CONFIG_ARCH_MXC) += imx8qxp-mek.dtb
+
+imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd-dtbs += imx8qxp-mek.dtb imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtbo
+dtb-$(CONFIG_ARCH_MXC) += imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtb
+
 dtb-$(CONFIG_ARCH_MXC) += imx8qxp-tqma8xqp-mba8xx.dtb
 dtb-$(CONFIG_ARCH_MXC) += imx8ulp-evk.dtb
 dtb-$(CONFIG_ARCH_MXC) += imx93-9x9-qsb.dtb
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso b/arch/arm64/boot/dts/freescale/imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso
new file mode 100644
index 000000000000..7ddd90e68754
--- /dev/null
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-mek-mx8-dlvds-lcd1-lvds0-odd.dtso
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright 2024 NXP
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+
+&{/} {
+	panel-lvds0 {
+		compatible = "koe,tx26d202vm0bwa";
+		backlight = <&backlight_lvds1>;
+		power-supply = <&reg_vcc_per_3v3>;
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			port@0 {
+				reg = <0>;
+				dual-lvds-odd-pixels;
+
+				panel_lvds0_in: endpoint {
+					remote-endpoint = <&lvds0_out>;
+				};
+			};
+
+			port@1 {
+				reg = <1>;
+				dual-lvds-even-pixels;
+
+				panel_lvds1_in: endpoint {
+					remote-endpoint = <&lvds1_out>;
+				};
+			};
+		};
+	};
+};
+
+&backlight_lvds1 {
+	status = "okay";
+};
+
+&dc0_framegen0 {
+	assigned-clocks = <&clk IMX_SC_R_DC_0_PLL_0 IMX_SC_PM_CLK_PLL>,
+			  <&clk IMX_SC_R_DC_0 IMX_SC_PM_CLK_MISC0>;
+	assigned-clock-parents = <0>,
+				 <&clk IMX_SC_R_DC_0_PLL_0 IMX_SC_PM_CLK_PLL>;
+	assigned-clock-rates = <940320000>;
+};
+
+&dc0_pixel_link0 {
+	status = "okay";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@1 {
+			reg = <1>;
+
+			status = "okay";
+		};
+	};
+};
+
+&dc0_pc {
+	status = "okay";
+
+	channel@0 {
+		status = "okay";
+	};
+};
+
+&mipi_lvds_0_ldb {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	fsl,companion-ldb = <&mipi_lvds_1_ldb>;
+	status = "okay";
+
+	channel@0 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <0>;
+		status = "okay";
+
+		port@1 {
+			reg = <1>;
+
+			lvds0_out: endpoint {
+				remote-endpoint = <&panel_lvds0_in>;
+			};
+		};
+	};
+};
+
+&mipi_lvds_0_phy {
+	status = "okay";
+};
+
+&mipi_lvds_0_pxl2dpi {
+	fsl,companion-pxl2dpi = <&mipi_lvds_1_pxl2dpi>;
+	status = "okay";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			mipi_lvds_0_pxl2dpi_dc0_pixel_link0: endpoint@0 {
+				status = "okay";
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			mipi_lvds_0_pxl2dpi_mipi_lvds_0_ldb_ch0: endpoint@0 {
+				status = "okay";
+			};
+		};
+	};
+};
+
+&mipi_lvds_1_ldb {
+	#address-cells = <1>;
+	#size-cells = <0>;
+	status = "okay";
+
+	channel@1 {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		reg = <1>;
+		status = "okay";
+
+		port@1 {
+			reg = <1>;
+
+			lvds1_out: endpoint {
+				remote-endpoint = <&panel_lvds1_in>;
+			};
+		};
+	};
+};
+
+&mipi_lvds_1_phy {
+	status = "okay";
+};
+
+&mipi_lvds_1_pwm {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_pwm_mipi_lvds1>;
+	status = "okay";
+};
+
+&mipi_lvds_1_pxl2dpi {
+	status = "okay";
+
+	ports {
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		port@0 {
+			reg = <0>;
+
+			mipi_lvds_1_pxl2dpi_dc0_pixel_link0: endpoint@1 {
+				status = "okay";
+			};
+		};
+
+		port@1 {
+			reg = <1>;
+
+			mipi_lvds_1_pxl2dpi_mipi_lvds_1_ldb_ch1: endpoint@1 {
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts b/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
index c7b4015c7bf7..cb999be00c22 100644
--- a/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
+++ b/arch/arm64/boot/dts/freescale/imx8qxp-mek.dts
@@ -21,6 +21,16 @@ chosen {
 		stdout-path = &lpuart0;
 	};
 
+	backlight_lvds1: backlight-lvds1 {
+		compatible = "pwm-backlight";
+		pwms = <&mipi_lvds_1_pwm 0 100000 0>;
+		brightness-levels = <0 100>;
+		num-interpolated-steps = <100>;
+		default-brightness-level = <100>;
+		power-supply = <&reg_vcc_12v0>;
+		status = "disabled";
+	};
+
 	imx8x_cm4: imx8x-cm4 {
 		compatible = "fsl,imx8qxp-cm4";
 		mbox-names = "tx", "rx", "rxdb";
@@ -58,6 +68,20 @@ dsp_vdev0buffer: memory@94300000 {
 		};
 	};
 
+	reg_vcc_12v0: regulator-vcc-12v0 {
+		compatible = "regulator-fixed";
+		regulator-name = "VCC_12V0";
+		regulator-min-microvolt = <12000000>;
+		regulator-max-microvolt = <12000000>;
+	};
+
+	reg_vcc_per_3v3: regulator-vcc-per-3v3 {
+		compatible = "regulator-fixed";
+		regulator-name = "VCC_PER_3V3";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+	};
+
 	reg_usdhc2_vmmc: usdhc2-vmmc {
 		compatible = "regulator-fixed";
 		regulator-name = "SD1_SPWR";
@@ -785,6 +809,12 @@ IMX8QXP_FLEXCAN2_RX_ADMA_UART3_RX       0x06000020
 		>;
 	};
 
+	pinctrl_pwm_mipi_lvds1: mipilvds1pwmgrp {
+		fsl,pins = <
+			IMX8QXP_MIPI_DSI1_GPIO0_00_MIPI_DSI1_PWM0_OUT		0x00000020
+		>;
+	};
+
 	pinctrl_pcieb: pcieagrp {
 		fsl,pins = <
 			IMX8QXP_PCIE_CTRL0_PERST_B_LSIO_GPIO4_IO00		0x06000021
-- 
2.34.1



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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-09  3:39 ` [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS Liu Ying
@ 2024-12-09 16:17   ` Dmitry Baryshkov
  2024-12-11  7:43     ` Liu Ying
  2024-12-12  8:02   ` Maxime Ripard
  1 sibling, 1 reply; 35+ messages in thread
From: Dmitry Baryshkov @ 2024-12-09 16:17 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
> i.MX8qxp Display Controller(DC) is comprised of three main components that
> include a blit engine for 2D graphics accelerations, display controller for
> display output processing, as well as a command sequencer.  Add kernel
> mode setting support for the display controller part with two CRTCs and
> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
> registers of the display controller are accessed without command sequencer
> involved, instead just by using CPU.  The command sequencer is supposed to
> be used by the blit engine.
> 
> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> ---
> v6:
> * No change.
> 
> v5:
> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
> 
> v4:
> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
>   function calls from KMS routine to initialization stage. (Dmitry)
> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
>   appropriate .h header files or .c source files. (Dmitry)
> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
> * Drop dc_drm->pe_rpm_count. (Dmitry)
> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
>   Instead, put it in struct dc_crtc.  (Dmitry)
> * Call devm_request_irq() to request IRQs, instead of using drmm action.
>   (Dmitry)
> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
> * Select DRM_CLIENT_SELECTION due to rebase.
> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
>   rebase.
> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
>   register dc_drm_component_unbind_all() action.
> * Request interrupts in dc_crtc_post_init() after encoder initialization to
>   make sure next bridge is found first.
> 
> v3:
> * No change.
> 
> v2:
> * Find next bridge from TCon's port.
> * Drop drm/drm_module.h include from dc-drv.c.
> 
>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
>  9 files changed, 1274 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
> 
> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
> index 1fc84c7475de..415993207f2e 100644
> --- a/drivers/gpu/drm/imx/dc/Kconfig
> +++ b/drivers/gpu/drm/imx/dc/Kconfig
> @@ -1,6 +1,11 @@
>  config DRM_IMX8_DC
>  	tristate "Freescale i.MX8 Display Controller Graphics"
>  	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
> +	select DRM_CLIENT_SELECTION
> +	select DRM_GEM_DMA_HELPER
> +	select DRM_KMS_HELPER
> +	select DRM_DISPLAY_HELPER
> +	select DRM_BRIDGE_CONNECTOR
>  	select GENERIC_IRQ_CHIP
>  	select REGMAP
>  	select REGMAP_MMIO
> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
> index 1ce3e8a8db22..b9d33c074984 100644
> --- a/drivers/gpu/drm/imx/dc/Makefile
> +++ b/drivers/gpu/drm/imx/dc/Makefile
> @@ -1,6 +1,7 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
> -		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
> +		    dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
> +		    dc-plane.o dc-tc.o
>  
>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
> new file mode 100644
> index 000000000000..fd6daa1807d8
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
> @@ -0,0 +1,558 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2024 NXP
> + */
> +
> +#include <linux/completion.h>
> +#include <linux/container_of.h>
> +#include <linux/interrupt.h>
> +#include <linux/irqreturn.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_atomic_state_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "dc-de.h"
> +#include "dc-drv.h"
> +#include "dc-kms.h"
> +#include "dc-pe.h"
> +
> +#define dc_crtc_dbg(crtc, fmt, ...)					\
> +do {									\
> +	typeof(crtc) _crtc = (crtc);					\

Use exact type instead of typeof.

> +	drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
> +		    _crtc->base.id, _crtc->name, ##__VA_ARGS__);	\
> +} while (0)
> +
> +#define dc_crtc_err(crtc, fmt, ...)					\
> +do {									\
> +	typeof(crtc) _crtc = (crtc);					\
> +	drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
> +		_crtc->base.id, _crtc->name, ##__VA_ARGS__);		\
> +} while (0)
> +
> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)				\
> +do {									\
> +	unsigned long ret;						\
> +	ret = wait_for_completion_timeout(&dc_crtc->c, HZ);		\
> +	if (ret == 0)							\
> +		dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",	\
> +							__func__);	\
> +} while (0)
> +
> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)					\
> +do {									\
> +	typeof(fg) _fg = (fg);						\
> +	if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {		\
> +		dc_fg_secondary_clear_channel_status(_fg);		\
> +		dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",		\
> +							__func__);	\
> +	}								\
> +} while (0)
> +
> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)			\
> +do {									\
> +	if (dc_fg_wait_for_secondary_syncup(fg))			\
> +		dc_crtc_err(crtc,					\
> +			"%s: FrameGen secondary channel isn't syncup\n",\
> +							__func__);	\
> +} while (0)
> +
> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
> +{
> +	return container_of(crtc, struct dc_crtc, base);
> +}
> +
> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +
> +	return dc_fg_get_frame_index(dc_crtc->fg);
> +}
> +
> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +
> +	enable_irq(dc_crtc->irq_dec_framecomplete);
> +
> +	return 0;
> +}
> +
> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +
> +	disable_irq_nosync(dc_crtc->irq_dec_framecomplete);

Why is it _nosync?

> +}
> +
> +static irqreturn_t
> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
> +{
> +	struct dc_crtc *dc_crtc = dev_id;
> +	struct drm_crtc *crtc = &dc_crtc->base;
> +	unsigned long flags;
> +
> +	drm_crtc_handle_vblank(crtc);
> +
> +	spin_lock_irqsave(&crtc->dev->event_lock, flags);
> +	if (dc_crtc->event) {
> +		drm_crtc_send_vblank_event(crtc, dc_crtc->event);
> +		dc_crtc->event = NULL;
> +		drm_crtc_vblank_put(crtc);
> +	}
> +	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
> +{
> +	struct dc_crtc *dc_crtc = dev_id;
> +
> +	if (irq == dc_crtc->irq_dec_seqcomplete)
> +		complete(&dc_crtc->dec_seqcomplete_done);
> +	else if (irq == dc_crtc->irq_dec_shdld)
> +		complete(&dc_crtc->dec_shdld_done);
> +	else if (irq == dc_crtc->irq_ed_cont_shdld)
> +		complete(&dc_crtc->ed_cont_shdld_done);
> +	else if (irq == dc_crtc->irq_ed_safe_shdld)
> +		complete(&dc_crtc->ed_safe_shdld_done);

Is there any reason to have a single multiplex handler instead of having
4 separate handlers, each doing one simple thing?

> +
> +	return IRQ_HANDLED;
> +}
> +
> +static const struct drm_crtc_funcs dc_crtc_funcs = {
> +	.reset			= drm_atomic_helper_crtc_reset,
> +	.destroy		= drm_crtc_cleanup,
> +	.set_config		= drm_atomic_helper_set_config,
> +	.page_flip		= drm_atomic_helper_page_flip,
> +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
> +	.get_vblank_counter	= dc_crtc_get_vblank_counter,
> +	.enable_vblank		= dc_crtc_enable_vblank,
> +	.disable_vblank		= dc_crtc_disable_vblank,
> +	.get_vblank_timestamp	= drm_crtc_vblank_helper_get_vblank_timestamp,
> +};
> +
> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
> +{
> +	struct drm_crtc *crtc = crtc_state->crtc;
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +
> +	spin_lock_irq(&crtc->dev->event_lock);
> +	if (crtc_state->event) {
> +		WARN_ON(drm_crtc_vblank_get(crtc));
> +		WARN_ON(dc_crtc->event);
> +		dc_crtc->event = crtc_state->event;
> +		crtc_state->event = NULL;
> +	}
> +	spin_unlock_irq(&crtc->dev->event_lock);
> +}
> +
> +static enum drm_mode_status
> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
> +{
> +	return dc_fg_check_clock(dc_crtc->fg, clk_khz);
> +}

inline

> +
> +static enum drm_mode_status
> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	enum drm_mode_status status;
> +
> +	status = dc_crtc_check_clock(dc_crtc, mode->clock);
> +	if (status != MODE_OK)
> +		return status;
> +
> +	if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
> +		return MODE_CLOCK_HIGH;
> +
> +	return MODE_OK;
> +}
> +
> +static int
> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *new_crtc_state =
> +				drm_atomic_get_new_crtc_state(state, crtc);
> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	enum drm_mode_status status;
> +
> +	status = dc_crtc_check_clock(dc_crtc, adj->clock);
> +	if (status != MODE_OK)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
> +static void
> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *new_crtc_state =
> +				drm_atomic_get_new_crtc_state(state, crtc);
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	int idx, ret;
> +
> +	if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> +	    !new_crtc_state->active)
> +		return;
> +
> +	if (!drm_dev_enter(crtc->dev, &idx))
> +		return;
> +
> +	/* request pixel engine power-on when CRTC starts to be active */
> +	ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
> +	if (ret)
> +		dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
> +			    ret);
> +
> +	drm_dev_exit(idx);
> +}
> +
> +static void
> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *old_crtc_state =
> +				drm_atomic_get_old_crtc_state(state, crtc);
> +	struct drm_crtc_state *new_crtc_state =
> +				drm_atomic_get_new_crtc_state(state, crtc);
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	int idx;
> +
> +	if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> +	    (!old_crtc_state->active && !new_crtc_state->active))
> +		return;
> +
> +	if (!drm_dev_enter(crtc->dev, &idx))
> +		goto out;
> +
> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
> +
> +	/* flush plane update out to display */
> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> +
> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> +
> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
> +
> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> +
> +	drm_dev_exit(idx);
> +
> +out:
> +	dc_crtc_queue_state_event(new_crtc_state);
> +}
> +
> +static void
> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *new_crtc_state =
> +				drm_atomic_get_new_crtc_state(state, crtc);
> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	enum dc_link_id cf_link;
> +	int idx, ret;
> +
> +	dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
> +
> +	drm_crtc_vblank_on(crtc);
> +
> +	if (!drm_dev_enter(crtc->dev, &idx))
> +		goto out;
> +
> +	/* request display engine power-on when CRTC is enabled */
> +	ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
> +	if (ret < 0)
> +		dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
> +			    ret);
> +
> +	enable_irq(dc_crtc->irq_dec_shdld);
> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
> +	enable_irq(dc_crtc->irq_ed_safe_shdld);
> +
> +	dc_fg_cfg_videomode(dc_crtc->fg, adj);
> +
> +	dc_cf_framedimensions(dc_crtc->cf_cont,
> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
> +	dc_cf_framedimensions(dc_crtc->cf_safe,
> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
> +
> +	/* constframe in safety stream shows blue frame */
> +	dc_cf_constantcolor_blue(dc_crtc->cf_safe);
> +	cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
> +	dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
> +
> +	/* show CRTC background if no plane is enabled */
> +	if (new_crtc_state->plane_mask == 0) {
> +		/* constframe in content stream shows black frame */
> +		dc_cf_constantcolor_black(dc_crtc->cf_cont);
> +
> +		cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
> +		dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
> +	}
> +
> +	dc_fg_enable_clock(dc_crtc->fg);
> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> +	dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
> +	dc_fg_shdtokgen(dc_crtc->fg);
> +	dc_fg_enable(dc_crtc->fg);
> +
> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
> +
> +	disable_irq(dc_crtc->irq_ed_safe_shdld);
> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
> +	disable_irq(dc_crtc->irq_dec_shdld);
> +
> +	DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
> +
> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> +
> +	drm_dev_exit(idx);
> +
> +out:
> +	dc_crtc_queue_state_event(new_crtc_state);
> +}
> +
> +static void
> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *new_crtc_state =
> +				drm_atomic_get_new_crtc_state(state, crtc);
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	int idx, ret;
> +
> +	if (!drm_dev_enter(crtc->dev, &idx))
> +		goto out;
> +
> +	enable_irq(dc_crtc->irq_dec_seqcomplete);
> +	dc_fg_disable(dc_crtc->fg);
> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
> +	disable_irq(dc_crtc->irq_dec_seqcomplete);
> +
> +	dc_fg_disable_clock(dc_crtc->fg);
> +
> +	/* request pixel engine power-off as plane is off too */
> +	ret = pm_runtime_put(dc_crtc->pe->dev);
> +	if (ret)
> +		dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> +			    ret);
> +
> +	/* request display engine power-off when CRTC is disabled */
> +	ret = pm_runtime_put(dc_crtc->de->dev);

Can this be expressed as a devlink between PE and DE?

> +	if (ret < 0)
> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> +			    ret);
> +
> +	drm_dev_exit(idx);
> +
> +out:
> +	drm_crtc_vblank_off(crtc);
> +
> +	spin_lock_irq(&crtc->dev->event_lock);
> +	if (new_crtc_state->event && !new_crtc_state->active) {
> +		drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
> +		new_crtc_state->event = NULL;
> +	}
> +	spin_unlock_irq(&crtc->dev->event_lock);
> +}
> +
> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	int ret;
> +
> +	if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
> +		return;
> +
> +	dc_fg_disable_clock(dc_crtc->fg);
> +
> +	if (pm_runtime_active(dc_crtc->pe->dev)) {
> +		ret = pm_runtime_put_sync(dc_crtc->pe->dev);
> +		if (ret)
> +			dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> +				    ret);
> +	}
> +
> +	ret = pm_runtime_put_sync(dc_crtc->de->dev);
> +	if (ret < 0)
> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> +			    ret);
> +}
> +
> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
> +					 bool in_vblank_irq,
> +					 int *vpos, int *hpos,
> +					 ktime_t *stime, ktime_t *etime,
> +					 const struct drm_display_mode *mode)
> +{
> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> +	int vdisplay = mode->crtc_vdisplay;
> +	int vtotal = mode->crtc_vtotal;
> +	bool reliable;
> +	int line;
> +	int idx;
> +
> +	if (stime)
> +		*stime = ktime_get();
> +
> +	if (!drm_dev_enter(crtc->dev, &idx)) {
> +		reliable = false;
> +		*vpos = 0;
> +		*hpos = 0;
> +		goto out;
> +	}
> +
> +	/* line index starts with 0 for the first active output line */
> +	line = dc_fg_get_line_index(dc_crtc->fg);
> +
> +	if (line < vdisplay)
> +		/* active scanout area - positive */
> +		*vpos = line + 1;
> +	else
> +		/* inside vblank - negative */
> +		*vpos = line - (vtotal - 1);
> +
> +	*hpos = 0;
> +
> +	reliable = true;
> +
> +	drm_dev_exit(idx);
> +out:
> +	if (etime)
> +		*etime = ktime_get();
> +
> +	return reliable;
> +}
> +
> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
> +	.mode_valid		= dc_crtc_mode_valid,
> +	.atomic_check		= dc_crtc_atomic_check,
> +	.atomic_begin		= dc_crtc_atomic_begin,
> +	.atomic_flush		= dc_crtc_atomic_flush,
> +	.atomic_enable		= dc_crtc_atomic_enable,
> +	.atomic_disable		= dc_crtc_atomic_disable,
> +	.get_scanout_position	= dc_crtc_get_scanout_position,
> +};
> +
> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
> +{
> +	struct {
> +		struct device *dev;
> +		unsigned int irq;
> +		irqreturn_t (*irq_handler)(int irq, void *dev_id);
> +	} irqs[DC_CRTC_IRQS] = {
> +		{
> +			dc_crtc->de->dev,
> +			dc_crtc->irq_dec_framecomplete,
> +			dc_crtc_dec_framecomplete_irq_handler,
> +		}, {
> +			dc_crtc->de->dev,
> +			dc_crtc->irq_dec_seqcomplete,
> +			dc_crtc_common_irq_handler,
> +		}, {
> +			dc_crtc->de->dev,
> +			dc_crtc->irq_dec_shdld,
> +			dc_crtc_common_irq_handler,
> +		}, {
> +			dc_crtc->ed_cont->dev,
> +			dc_crtc->irq_ed_cont_shdld,
> +			dc_crtc_common_irq_handler,
> +		}, {
> +			dc_crtc->ed_safe->dev,
> +			dc_crtc->irq_ed_safe_shdld,
> +			dc_crtc_common_irq_handler,
> +		},
> +	};
> +	int i, ret;
> +
> +	for (i = 0; i < DC_CRTC_IRQS; i++) {
> +		struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
> +
> +		ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
> +				       irqs[i].irq_handler, IRQF_NO_AUTOEN,
> +				       dev_name(irqs[i].dev), dc_crtc);
> +		if (ret) {
> +			dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
> +				irqs[i].irq, ret);
> +			return ret;
> +		}
> +
> +		irq->dc_crtc = dc_crtc;
> +		irq->irq = irqs[i].irq;
> +	}
> +
> +	return 0;
> +}
> +
> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
> +{
> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> +	struct drm_device *drm = &dc_drm->base;
> +	struct dc_de *de = dc_drm->de[crtc_index];
> +	struct dc_pe *pe = dc_drm->pe;
> +	struct dc_plane *dc_primary;
> +	int ret;
> +
> +	dc_crtc->de = de;
> +	dc_crtc->pe = pe;
> +
> +	init_completion(&dc_crtc->dec_seqcomplete_done);
> +	init_completion(&dc_crtc->dec_shdld_done);
> +	init_completion(&dc_crtc->ed_cont_shdld_done);
> +	init_completion(&dc_crtc->ed_safe_shdld_done);
> +
> +	dc_crtc->cf_cont = pe->cf_cont[crtc_index];
> +	dc_crtc->cf_safe = pe->cf_safe[crtc_index];
> +	dc_crtc->ed_cont = pe->ed_cont[crtc_index];
> +	dc_crtc->ed_safe = pe->ed_safe[crtc_index];
> +	dc_crtc->fg = de->fg;
> +
> +	dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
> +	dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
> +	dc_crtc->irq_dec_shdld = de->irq_shdld;
> +	dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
> +	dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
> +
> +	dc_primary = &dc_drm->dc_primary[crtc_index];
> +	ret = dc_plane_init(dc_drm, dc_primary);
> +	if (ret) {
> +		dev_err(drm->dev,
> +			"failed to init primary plane for display engine%u: %d\n",
> +			de->id, ret);
> +		return ret;
> +	}
> +
> +	drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
> +
> +	ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
> +					NULL, &dc_crtc_funcs, NULL);
> +	if (ret)
> +		dev_err(drm->dev,
> +			"failed to add CRTC for display engine%u: %d\n",
> +			de->id, ret);
> +
> +	return ret;
> +}
> +
> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
> +{
> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> +	struct drm_device *drm = &dc_drm->base;
> +
> +	return dc_crtc_request_irqs(drm, dc_crtc);
> +}
> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
> index 17a44362118e..8a7b6c03a222 100644
> --- a/drivers/gpu/drm/imx/dc/dc-de.h
> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
> @@ -13,6 +13,9 @@
>  
>  #define DC_DISPLAYS	2
>  
> +#define DC_FRAMEGEN_MAX_FRAME_INDEX	0x3ffff
> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ	300000
> +
>  struct dc_fg {
>  	struct device *dev;
>  	struct regmap *reg;
> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
> index fd68861f770a..1e4b8afa3eec 100644
> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
> @@ -3,11 +3,254 @@
>   * Copyright 2024 NXP
>   */
>  
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/mod_devicetable.h>
>  #include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
>  #include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
>  
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_client_setup.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_dma.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_gem_dma_helper.h>
> +#include <drm/drm_managed.h>
> +#include <drm/drm_modeset_helper.h>
> +#include <drm/drm_of.h>
> +
> +#include "dc-de.h"
>  #include "dc-drv.h"
>  
> +struct dc_priv {
> +	struct drm_device *drm;
> +	struct clk *clk_cfg;
> +};
> +
> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
> +
> +static struct drm_driver dc_drm_driver = {
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	DRM_GEM_DMA_DRIVER_OPS,
> +	DRM_FBDEV_DMA_DRIVER_OPS,
> +	.fops = &dc_drm_driver_fops,
> +	.name = "imx8-dc",
> +	.desc = "i.MX8 DC DRM graphics",
> +	.date = "20240530",
> +	.major = 1,
> +	.minor = 0,
> +	.patchlevel = 0,
> +};
> +
> +static void
> +dc_add_components(struct device *dev, struct component_match **matchptr)
> +{
> +	struct device_node *child, *grandchild;
> +
> +	for_each_available_child_of_node(dev->of_node, child) {
> +		/* The interrupt controller is not a component. */
> +		if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
> +			continue;
> +
> +		drm_of_component_match_add(dev, matchptr, component_compare_of,
> +					   child);
> +
> +		for_each_available_child_of_node(child, grandchild)
> +			drm_of_component_match_add(dev, matchptr,
> +						   component_compare_of,
> +						   grandchild);
> +	}
> +}
> +
> +static void dc_drm_component_unbind_all(void *ptr)
> +{
> +	struct dc_drm_device *dc_drm = ptr;
> +	struct drm_device *drm = &dc_drm->base;
> +
> +	component_unbind_all(drm->dev, dc_drm);
> +}
> +
> +static int dc_drm_bind(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +	struct dc_drm_device *dc_drm;
> +	struct drm_device *drm;
> +	int ret;
> +
> +	dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
> +				    base);
> +	if (IS_ERR(dc_drm))
> +		return PTR_ERR(dc_drm);
> +
> +	drm = &dc_drm->base;
> +
> +	ret = component_bind_all(dev, dc_drm);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
> +				       dc_drm);
> +	if (ret)
> +		return ret;
> +
> +	ret = dc_kms_init(dc_drm);
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret) {
> +		dev_err(dev, "failed to register drm device: %d\n", ret);
> +		goto err;
> +	}
> +
> +	drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
> +
> +	priv->drm = drm;
> +
> +	return 0;
> +
> +err:
> +	dc_kms_uninit(dc_drm);
> +
> +	return ret;
> +}
> +
> +static void dc_drm_unbind(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +	struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
> +	struct drm_device *drm = &dc_drm->base;
> +	struct drm_crtc *crtc;
> +
> +	priv->drm = NULL;
> +	drm_dev_unplug(drm);
> +	dc_kms_uninit(dc_drm);
> +	drm_atomic_helper_shutdown(drm);
> +
> +	drm_for_each_crtc(crtc, drm)
> +		dc_crtc_disable_at_unbind(crtc);

There should be no need for that. drm_atomic_helper_shutdown() should
disable all the CRTCs.

Also, who is going to do drm_dev_unregister()? I don't see it in the
code.

> +}
> +
> +static const struct component_master_ops dc_drm_ops = {
> +	.bind = dc_drm_bind,
> +	.unbind = dc_drm_unbind,
> +};
> +
> +static int dc_probe(struct platform_device *pdev)
> +{
> +	struct component_match *match = NULL;
> +	struct dc_priv *priv;
> +	int ret;
> +
> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->clk_cfg))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
> +				     "failed to get cfg clock\n");
> +
> +	dev_set_drvdata(&pdev->dev, priv);
> +
> +	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_pm_runtime_enable(&pdev->dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_of_platform_populate(&pdev->dev);
> +	if (ret)
> +		return ret;
> +
> +	dc_add_components(&pdev->dev, &match);
> +
> +	ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
> +	if (ret)
> +		return dev_err_probe(&pdev->dev, ret,
> +				     "failed to add component master\n");
> +
> +	return 0;
> +}
> +
> +static void dc_remove(struct platform_device *pdev)
> +{
> +	component_master_del(&pdev->dev, &dc_drm_ops);
> +}
> +
> +static int dc_runtime_suspend(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(priv->clk_cfg);
> +
> +	return 0;
> +}
> +
> +static int dc_runtime_resume(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = clk_prepare_enable(priv->clk_cfg);
> +	if (ret)
> +		dev_err(dev, "failed to enable cfg clock: %d\n", ret);
> +
> +	return ret;
> +}
> +
> +static int dc_suspend(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +
> +	return drm_mode_config_helper_suspend(priv->drm);
> +}
> +
> +static int dc_resume(struct device *dev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(dev);
> +
> +	return drm_mode_config_helper_resume(priv->drm);
> +}
> +
> +static void dc_shutdown(struct platform_device *pdev)
> +{
> +	struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
> +
> +	drm_atomic_helper_shutdown(priv->drm);
> +}
> +
> +static const struct dev_pm_ops dc_pm_ops = {
> +	RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
> +	SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
> +};
> +
> +static const struct of_device_id dc_dt_ids[] = {
> +	{ .compatible = "fsl,imx8qxp-dc", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
> +
> +static struct platform_driver dc_driver = {
> +	.probe = dc_probe,
> +	.remove = dc_remove,
> +	.shutdown = dc_shutdown,
> +	.driver = {
> +		.name = "imx8-dc",
> +		.of_match_table	= dc_dt_ids,
> +		.pm = pm_sleep_ptr(&dc_pm_ops),
> +	},
> +};
> +
>  static struct platform_driver * const dc_drivers[] = {
>  	&dc_cf_driver,
>  	&dc_de_driver,
> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
>  	&dc_lb_driver,
>  	&dc_pe_driver,
>  	&dc_tc_driver,
> +	&dc_driver,
>  };
>  
>  static int __init dc_drm_init(void)
> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
> index 3b11f4862c6c..39a771a13933 100644
> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
> @@ -6,19 +6,38 @@
>  #ifndef __DC_DRV_H__
>  #define __DC_DRV_H__
>  
> +#include <linux/container_of.h>
>  #include <linux/platform_device.h>
>  
>  #include <drm/drm_device.h>
> +#include <drm/drm_encoder.h>
>  
>  #include "dc-de.h"
> +#include "dc-kms.h"
>  #include "dc-pe.h"
>  
>  struct dc_drm_device {
>  	struct drm_device base;
> +	struct dc_crtc dc_crtc[DC_DISPLAYS];
> +	struct dc_plane dc_primary[DC_DISPLAYS];
> +	struct drm_encoder encoder[DC_DISPLAYS];
>  	struct dc_de *de[DC_DISPLAYS];
>  	struct dc_pe *pe;
>  };
>  
> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
> +{
> +	return container_of(drm, struct dc_drm_device, base);
> +}
> +
> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
> +
> +int dc_kms_init(struct dc_drm_device *dc_drm);
> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
> +
> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
> +
>  extern struct platform_driver dc_cf_driver;
>  extern struct platform_driver dc_ed_driver;
>  extern struct platform_driver dc_de_driver;
> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
> new file mode 100644
> index 000000000000..2b18aa37a4a8
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
> @@ -0,0 +1,143 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2024 NXP
> + */
> +
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_bridge_connector.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_mode_config.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_simple_kms_helper.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "dc-de.h"
> +#include "dc-drv.h"
> +#include "dc-kms.h"
> +
> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
> +	.fb_create = drm_gem_fb_create,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
> +					int crtc_index)
> +{
> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> +	struct drm_device *drm = &dc_drm->base;
> +	struct drm_crtc *crtc = &dc_crtc->base;
> +	struct drm_connector *connector;
> +	struct device *dev = drm->dev;
> +	struct drm_encoder *encoder;
> +	struct drm_bridge *bridge;
> +	int ret;
> +
> +	bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
> +					0, 0);
> +	if (IS_ERR(bridge)) {
> +		ret = PTR_ERR(bridge);
> +		if (ret == -ENODEV)
> +			return 0;
> +
> +		return dev_err_probe(dev, ret,
> +				     "failed to find bridge for CRTC%u\n",
> +				     crtc->index);
> +	}
> +
> +	encoder = &dc_drm->encoder[crtc_index];
> +	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
> +	if (ret) {
> +		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
> +			crtc->index, ret);
> +		return ret;
> +	}
> +
> +	encoder->possible_crtcs = drm_crtc_mask(crtc);
> +
> +	ret = drm_bridge_attach(encoder, bridge, NULL,
> +				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> +	if (ret) {
> +		dev_err(dev,
> +			"failed to attach bridge to encoder for CRTC%u: %d\n",
> +			crtc->index, ret);
> +		return ret;
> +	}
> +
> +	connector = drm_bridge_connector_init(drm, encoder);
> +	if (IS_ERR(connector)) {
> +		ret = PTR_ERR(connector);
> +		dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
> +			crtc->index, ret);
> +		return ret;
> +	}
> +
> +	ret = drm_connector_attach_encoder(connector, encoder);
> +	if (ret)
> +		dev_err(dev,
> +			"failed to attach encoder to connector for CRTC%u: %d\n",
> +			crtc->index, ret);
> +
> +	return ret;
> +}
> +
> +int dc_kms_init(struct dc_drm_device *dc_drm)
> +{
> +	struct drm_device *drm = &dc_drm->base;
> +	int ret, i;
> +
> +	ret = drmm_mode_config_init(drm);
> +	if (ret)
> +		return ret;
> +
> +	drm->mode_config.min_width = 60;
> +	drm->mode_config.min_height = 60;
> +	drm->mode_config.max_width = 8192;
> +	drm->mode_config.max_height = 8192;
> +	drm->mode_config.funcs = &dc_drm_mode_config_funcs;
> +
> +	drm->vblank_disable_immediate = true;
> +	drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
> +
> +	for (i = 0; i < DC_DISPLAYS; i++) {
> +		ret = dc_crtc_init(dc_drm, i);
> +		if (ret)
> +			return ret;
> +
> +		ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (i = 0; i < DC_DISPLAYS; i++) {
> +		ret = dc_crtc_post_init(dc_drm, i);

Can you use .late_register for this?

> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = drm_vblank_init(drm, DC_DISPLAYS);
> +	if (ret) {
> +		dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
> +		return ret;
> +	}
> +
> +	drm_mode_config_reset(drm);
> +
> +	drm_kms_helper_poll_init(drm);
> +
> +	return 0;
> +}
> +
> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
> +{
> +	drm_kms_helper_poll_fini(&dc_drm->base);
> +}
> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
> new file mode 100644
> index 000000000000..57f6e0c15f57
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
> @@ -0,0 +1,58 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2024 NXP
> + */
> +
> +#ifndef __DC_KMS_H__
> +#define __DC_KMS_H__
> +
> +#include <linux/completion.h>
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "dc-de.h"
> +#include "dc-fu.h"
> +#include "dc-pe.h"
> +
> +#define DC_CRTC_IRQS	5
> +
> +struct dc_crtc_irq {
> +	struct dc_crtc *dc_crtc;
> +	unsigned int irq;
> +};
> +

Please provide some documentation for the structure in the form of the
kerneldoc. E.g. what is the difference between ed_cont and ed_safe?
The de and fg pointers are global, please don't cache them
unnecessarily.

> +struct dc_crtc {
> +	struct drm_crtc base;
> +	struct dc_de *de;
> +	struct dc_pe *pe;
> +	struct dc_cf *cf_cont;
> +	struct dc_cf *cf_safe;
> +	struct dc_ed *ed_cont;
> +	struct dc_ed *ed_safe;
> +	struct dc_fg *fg;
> +	unsigned int irq_dec_framecomplete;
> +	unsigned int irq_dec_seqcomplete;
> +	unsigned int irq_dec_shdld;
> +	unsigned int irq_ed_cont_shdld;
> +	unsigned int irq_ed_safe_shdld;
> +	struct completion dec_seqcomplete_done;
> +	struct completion dec_shdld_done;
> +	struct completion ed_safe_shdld_done;
> +	struct completion ed_cont_shdld_done;
> +	struct drm_pending_vblank_event *event;
> +	struct dc_crtc_irq irqs[DC_CRTC_IRQS];
> +};
> +
> +struct dc_plane {
> +	struct drm_plane base;
> +	struct dc_fu *fu;
> +	struct dc_cf *cf;
> +	struct dc_lb *lb;
> +	struct dc_ed *ed;
> +};
> +
> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
> +
> +#endif /* __DC_KMS_H__ */
> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
> new file mode 100644
> index 000000000000..78d0d2cd3451
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
> @@ -0,0 +1,241 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2024 NXP
> + */
> +
> +#include <linux/container_of.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_atomic_state_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fb_dma_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_plane_helper.h>
> +#include <drm/drm_print.h>
> +
> +#include "dc-drv.h"
> +#include "dc-fu.h"
> +#include "dc-kms.h"
> +
> +#define DC_PLANE_MAX_PITCH	0x10000
> +#define DC_PLANE_MAX_PIX_CNT	8192
> +
> +#define dc_plane_dbg(plane, fmt, ...)					\
> +do {									\
> +	typeof(plane) _plane = (plane);					\
> +	drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,			\
> +		    _plane->base.id, _plane->name, ##__VA_ARGS__);	\
> +} while (0)
> +
> +static const uint32_t dc_plane_formats[] = {
> +	DRM_FORMAT_XRGB8888,
> +};
> +
> +static const struct drm_plane_funcs dc_plane_funcs = {
> +	.update_plane		= drm_atomic_helper_update_plane,
> +	.disable_plane		= drm_atomic_helper_disable_plane,
> +	.destroy		= drm_plane_cleanup,
> +	.reset			= drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
> +{
> +	return container_of(plane, struct dc_plane, base);
> +}
> +
> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
> +					struct drm_crtc_state *crtc_state)
> +{
> +	if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
> +	    state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
> +	    state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
> +		dc_plane_dbg(state->plane, "no off screen\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
> +{
> +	int src_h = drm_rect_height(&state->src) >> 16;
> +	int src_w = drm_rect_width(&state->src) >> 16;
> +
> +	if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
> +		dc_plane_dbg(state->plane, "invalid source resolution\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int dc_plane_check_fb(struct drm_plane_state *state)
> +{
> +	struct drm_framebuffer *fb = state->fb;
> +	dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
> +
> +	/* base address alignment */
> +	if (baseaddr & 0x3) {
> +		dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
> +		return -EINVAL;
> +	}
> +
> +	/* pitches[0] range */
> +	if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
> +		dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
> +		return -EINVAL;
> +	}
> +
> +	/* pitches[0] alignment */
> +	if (fb->pitches[0] & 0x3) {
> +		dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
> +{
> +	struct drm_plane_state *plane_state =
> +				drm_atomic_get_new_plane_state(state, plane);
> +	struct drm_crtc_state *crtc_state;
> +	int ret;
> +
> +	/* ok to disable */
> +	if (!plane_state->fb)
> +		return 0;
> +
> +	if (!plane_state->crtc) {
> +		dc_plane_dbg(plane, "no CRTC in plane state\n");
> +		return -EINVAL;
> +	}
> +
> +	crtc_state =
> +		drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
> +	if (WARN_ON(!crtc_state))
> +		return -EINVAL;
> +
> +	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
> +						  DRM_PLANE_NO_SCALING,
> +						  DRM_PLANE_NO_SCALING,
> +						  true, false);
> +	if (ret) {
> +		dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
> +	if (ret)
> +		return ret;
> +
> +	ret = dc_plane_check_max_source_resolution(plane_state);
> +	if (ret)
> +		return ret;
> +
> +	return dc_plane_check_fb(plane_state);
> +}
> +
> +static void
> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
> +{
> +	struct drm_plane_state *new_state =
> +				drm_atomic_get_new_plane_state(state, plane);
> +	struct dc_plane *dplane = to_dc_plane(plane);
> +	struct drm_framebuffer *fb = new_state->fb;
> +	const struct dc_fu_ops *fu_ops;
> +	struct dc_lb *lb = dplane->lb;
> +	struct dc_fu *fu = dplane->fu;
> +	dma_addr_t baseaddr;
> +	int src_w, src_h;
> +	int idx;
> +
> +	if (!drm_dev_enter(plane->dev, &idx))
> +		return;
> +
> +	src_w = drm_rect_width(&new_state->src) >> 16;
> +	src_h = drm_rect_height(&new_state->src) >> 16;
> +
> +	baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
> +
> +	fu_ops = dc_fu_get_ops(dplane->fu);
> +
> +	fu_ops->set_layerblend(fu, lb);
> +	fu_ops->set_burstlength(fu, baseaddr);
> +	fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
> +	fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
> +	fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
> +	fu_ops->set_framedimensions(fu, src_w, src_h);
> +	fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
> +	fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
> +
> +	dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
> +
> +	dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
> +	dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
> +	dc_lb_mode(lb, LB_BLEND);
> +	dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
> +	dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
> +
> +	dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
> +
> +	/* set ExtDst's source to LayerBlend */
> +	dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
> +
> +	drm_dev_exit(idx);
> +}
> +
> +static void dc_plane_atomic_disable(struct drm_plane *plane,
> +				    struct drm_atomic_state *state)
> +{
> +	struct dc_plane *dplane = to_dc_plane(plane);
> +	const struct dc_fu_ops *fu_ops;
> +	int idx;
> +
> +	if (!drm_dev_enter(plane->dev, &idx))
> +		return;
> +
> +	/* disable fetchunit in shadow */
> +	fu_ops = dc_fu_get_ops(dplane->fu);
> +	fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
> +
> +	/* set ExtDst's source to ConstFrame */
> +	dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
> +
> +	drm_dev_exit(idx);
> +}
> +
> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
> +	.atomic_check = dc_plane_atomic_check,
> +	.atomic_update = dc_plane_atomic_update,
> +	.atomic_disable = dc_plane_atomic_disable,
> +};
> +
> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
> +{
> +	struct drm_plane *plane = &dc_plane->base;
> +	int ret;
> +
> +	ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
> +				       dc_plane_formats,
> +				       ARRAY_SIZE(dc_plane_formats),
> +				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
> +	if (ret)
> +		return ret;
> +
> +	drm_plane_helper_add(plane, &dc_plane_helper_funcs);
> +
> +	dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
> +	dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
> +	dc_plane->lb = dc_drm->pe->lb[plane->index];
> +	dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
> +
> +	return 0;
> +}
> -- 
> 2.34.1
> 

-- 
With best wishes
Dmitry


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

* Re: [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units
  2024-12-09  3:39 ` [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units Liu Ying
@ 2024-12-10 22:15   ` Rob Herring
  2024-12-11  3:05     ` Liu Ying
  0 siblings, 1 reply; 35+ messages in thread
From: Rob Herring @ 2024-12-10 22:15 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, krzk+dt, conor+dt, shawnguo, s.hauer, kernel,
	festevam, tglx, vkoul, kishon, aisheng.dong, agx, francesco,
	frank.li, dmitry.baryshkov, u.kleine-koenig

On Mon, Dec 09, 2024 at 11:39:05AM +0800, Liu Ying wrote:
> Freescale i.MX8qxp Display Controller is implemented as construction set of
> building blocks with unified concept and standardized interfaces.  Document
> all existing processing units.
> 
> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> ---
> v6:
> * No change.
> 
> v5:
> * Document aliases for processing units which have multiple instances in
>   the Display Controller.  Drop Rob's previous R-b tag. (Maxime)
> 
> v4:
> * Collect Rob's R-b tag.
> 
> v3:
> * Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
>   fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
>   into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
> * Document all processing units. (Rob)
> 
> v2:
> * Drop fsl,dc-*-id DT properties. (Krzysztof)
> * Add port property to fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
> * Fix register range sizes in examples.
> 
>  .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++++++
>  .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++++++
>  .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++++++
>  .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++++++
>  .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++++++++
>  .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 ++++++++++++++++++
>  .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++++++
>  .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 ++++++++
>  .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 +++++
>  .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++++++
>  .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++++++
>  .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++++++
>  .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++++
>  .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++++++++++
>  .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 +++++++
>  .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++++++++++
>  .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++++++
>  17 files changed, 1042 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
> new file mode 100644
> index 000000000000..7f800e72c3f3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
> @@ -0,0 +1,46 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blitblend.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Freescale i.MX8qxp Display Controller Blit Blend Unit
> +
> +description: |
> +  Combines two input frames to a single output frame, all frames having the
> +  same dimension.
> +
> +  Each Blit Blend Unit device should have an alias in the aliases node, in the
> +  form of dc<x>-blitblend<y>, where <x> is an integer specifying the Display
> +  Controller instance and <y> is an integer specifying the Blit Blend Unit
> +  device instance.

That's really an abuse of aliases. If you need to describe connections 
between components, use the graph binding like everyone else does for 
display path components.

Rob


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

* Re: [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units
  2024-12-10 22:15   ` Rob Herring
@ 2024-12-11  3:05     ` Liu Ying
  2024-12-11 14:55       ` Rob Herring
  0 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-11  3:05 UTC (permalink / raw)
  To: Rob Herring
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, krzk+dt, conor+dt, shawnguo, s.hauer, kernel,
	festevam, tglx, vkoul, kishon, aisheng.dong, agx, francesco,
	frank.li, dmitry.baryshkov, u.kleine-koenig

On 12/11/2024, Rob Herring wrote:
> On Mon, Dec 09, 2024 at 11:39:05AM +0800, Liu Ying wrote:
>> Freescale i.MX8qxp Display Controller is implemented as construction set of
>> building blocks with unified concept and standardized interfaces.  Document
>> all existing processing units.
>>
>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
>> ---
>> v6:
>> * No change.
>>
>> v5:
>> * Document aliases for processing units which have multiple instances in
>>   the Display Controller.  Drop Rob's previous R-b tag. (Maxime)
>>
>> v4:
>> * Collect Rob's R-b tag.
>>
>> v3:
>> * Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
>>   fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
>>   into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
>> * Document all processing units. (Rob)
>>
>> v2:
>> * Drop fsl,dc-*-id DT properties. (Krzysztof)
>> * Add port property to fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
>> * Fix register range sizes in examples.
>>
>>  .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++++++
>>  .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++++++++
>>  .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 ++++++++++++++++++
>>  .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 ++++++++
>>  .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 +++++
>>  .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++++++
>>  .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++++
>>  .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++++++++++
>>  .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 +++++++
>>  .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++++++++++
>>  .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++++++
>>  17 files changed, 1042 insertions(+)
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>> new file mode 100644
>> index 000000000000..7f800e72c3f3
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>> @@ -0,0 +1,46 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blitblend.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Freescale i.MX8qxp Display Controller Blit Blend Unit
>> +
>> +description: |
>> +  Combines two input frames to a single output frame, all frames having the
>> +  same dimension.
>> +
>> +  Each Blit Blend Unit device should have an alias in the aliases node, in the
>> +  form of dc<x>-blitblend<y>, where <x> is an integer specifying the Display
>> +  Controller instance and <y> is an integer specifying the Blit Blend Unit
>> +  device instance.
> 
> That's really an abuse of aliases. If you need to describe connections 
> between components, use the graph binding like everyone else does for 
> display path components.

I need to describe components' instance numbers which imply the connections
between components but not vice versa. If I use the graph binding, I cannot
get the instance numbers(0 or 1) of the two display engines(documented by
fsl,imx8qxp-dc-display-engine.yaml). If you have no objections, I may add the
instance numbers to compatible strings, like brcm,bcm2835-pixelvalve0.yaml.
What do you think?

> 
> Rob

-- 
Regards,
Liu Ying


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-09 16:17   ` Dmitry Baryshkov
@ 2024-12-11  7:43     ` Liu Ying
  2024-12-11 23:35       ` Dmitry Baryshkov
  0 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-11  7:43 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On 12/10/2024, Dmitry Baryshkov wrote:
> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
>> i.MX8qxp Display Controller(DC) is comprised of three main components that
>> include a blit engine for 2D graphics accelerations, display controller for
>> display output processing, as well as a command sequencer.  Add kernel
>> mode setting support for the display controller part with two CRTCs and
>> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
>> registers of the display controller are accessed without command sequencer
>> involved, instead just by using CPU.  The command sequencer is supposed to
>> be used by the blit engine.
>>
>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
>> ---
>> v6:
>> * No change.
>>
>> v5:
>> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
>>
>> v4:
>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
>>   function calls from KMS routine to initialization stage. (Dmitry)
>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
>>   appropriate .h header files or .c source files. (Dmitry)
>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
>> * Drop dc_drm->pe_rpm_count. (Dmitry)
>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
>>   Instead, put it in struct dc_crtc.  (Dmitry)
>> * Call devm_request_irq() to request IRQs, instead of using drmm action.
>>   (Dmitry)
>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
>> * Select DRM_CLIENT_SELECTION due to rebase.
>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
>>   rebase.
>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
>>   register dc_drm_component_unbind_all() action.
>> * Request interrupts in dc_crtc_post_init() after encoder initialization to
>>   make sure next bridge is found first.
>>
>> v3:
>> * No change.
>>
>> v2:
>> * Find next bridge from TCon's port.
>> * Drop drm/drm_module.h include from dc-drv.c.
>>
>>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
>>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
>>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
>>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
>>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
>>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
>>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
>>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
>>  9 files changed, 1274 insertions(+), 2 deletions(-)
>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
>>
>> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
>> index 1fc84c7475de..415993207f2e 100644
>> --- a/drivers/gpu/drm/imx/dc/Kconfig
>> +++ b/drivers/gpu/drm/imx/dc/Kconfig
>> @@ -1,6 +1,11 @@
>>  config DRM_IMX8_DC
>>  	tristate "Freescale i.MX8 Display Controller Graphics"
>>  	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
>> +	select DRM_CLIENT_SELECTION
>> +	select DRM_GEM_DMA_HELPER
>> +	select DRM_KMS_HELPER
>> +	select DRM_DISPLAY_HELPER
>> +	select DRM_BRIDGE_CONNECTOR
>>  	select GENERIC_IRQ_CHIP
>>  	select REGMAP
>>  	select REGMAP_MMIO
>> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
>> index 1ce3e8a8db22..b9d33c074984 100644
>> --- a/drivers/gpu/drm/imx/dc/Makefile
>> +++ b/drivers/gpu/drm/imx/dc/Makefile
>> @@ -1,6 +1,7 @@
>>  # SPDX-License-Identifier: GPL-2.0
>>  
>> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
>> -		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
>> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
>> +		    dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
>> +		    dc-plane.o dc-tc.o
>>  
>>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
>> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
>> new file mode 100644
>> index 000000000000..fd6daa1807d8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
>> @@ -0,0 +1,558 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright 2024 NXP
>> + */
>> +
>> +#include <linux/completion.h>
>> +#include <linux/container_of.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/irqreturn.h>
>> +#include <linux/pm_runtime.h>
>> +#include <linux/spinlock.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_atomic_state_helper.h>
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_device.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_modes.h>
>> +#include <drm/drm_modeset_helper_vtables.h>
>> +#include <drm/drm_plane.h>
>> +#include <drm/drm_print.h>
>> +#include <drm/drm_vblank.h>
>> +
>> +#include "dc-de.h"
>> +#include "dc-drv.h"
>> +#include "dc-kms.h"
>> +#include "dc-pe.h"
>> +
>> +#define dc_crtc_dbg(crtc, fmt, ...)					\
>> +do {									\
>> +	typeof(crtc) _crtc = (crtc);					\
> 
> Use exact type instead of typeof.

Will do.

> 
>> +	drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
>> +		    _crtc->base.id, _crtc->name, ##__VA_ARGS__);	\
>> +} while (0)
>> +
>> +#define dc_crtc_err(crtc, fmt, ...)					\
>> +do {									\
>> +	typeof(crtc) _crtc = (crtc);					\
>> +	drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
>> +		_crtc->base.id, _crtc->name, ##__VA_ARGS__);		\
>> +} while (0)
>> +
>> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)				\
>> +do {									\
>> +	unsigned long ret;						\
>> +	ret = wait_for_completion_timeout(&dc_crtc->c, HZ);		\
>> +	if (ret == 0)							\
>> +		dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",	\
>> +							__func__);	\
>> +} while (0)
>> +
>> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)					\
>> +do {									\
>> +	typeof(fg) _fg = (fg);						\
>> +	if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {		\
>> +		dc_fg_secondary_clear_channel_status(_fg);		\
>> +		dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",		\
>> +							__func__);	\
>> +	}								\
>> +} while (0)
>> +
>> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)			\
>> +do {									\
>> +	if (dc_fg_wait_for_secondary_syncup(fg))			\
>> +		dc_crtc_err(crtc,					\
>> +			"%s: FrameGen secondary channel isn't syncup\n",\
>> +							__func__);	\
>> +} while (0)
>> +
>> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
>> +{
>> +	return container_of(crtc, struct dc_crtc, base);
>> +}
>> +
>> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +
>> +	return dc_fg_get_frame_index(dc_crtc->fg);
>> +}
>> +
>> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +
>> +	enable_irq(dc_crtc->irq_dec_framecomplete);
>> +
>> +	return 0;
>> +}
>> +
>> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +
>> +	disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
> 
> Why is it _nosync?

Because disable_irq() can only be called from preemptible code according to
it's kerneldoc. If I use disable_irq() here, I get this with
CONFIG_DEBUG_ATOMIC_SLEEP enabled:

[   50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
[   50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0
[   50.623527] preempt_count: 10003, expected: 0
[   50.627888] RCU nest depth: 0, expected: 0
[   50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407
[   50.642092] Hardware name: Freescale i.MX8QXP MEK (DT)
[   50.647237] Call trace:
[   50.649687]  show_stack+0x18/0x24 (C)
[   50.653369]  dump_stack_lvl+0x80/0xb4
[   50.657043]  dump_stack+0x18/0x24
[   50.660370]  __might_resched+0x114/0x170
[   50.664306]  __might_sleep+0x48/0x98
[   50.667894]  disable_irq+0x24/0x60
[   50.671308]  dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm]
[   50.676912]  drm_vblank_disable_and_save+0xc0/0x108 [drm]
[   50.682533]  vblank_disable_fn+0x78/0x9c [drm]
[   50.687146]  drm_handle_vblank+0x238/0x2e8 [drm]
[   50.691932]  drm_crtc_handle_vblank+0x1c/0x28 [drm]
[   50.696980]  dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm]
[   50.703886]  __handle_irq_event_percpu+0x60/0x14c
[   50.708604]  handle_irq_event+0x4c/0xac
[   50.712443]  handle_level_irq+0xc0/0x1b0
[   50.716379]  generic_handle_irq+0x34/0x4c
[   50.720392]  dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm]
[   50.725727]  generic_handle_domain_irq+0x2c/0x44
[   50.730357]  imx_irqsteer_irq_handler+0xc0/0x1a0
[   50.734987]  generic_handle_domain_irq+0x2c/0x44
[   50.739609]  gic_handle_irq+0x4c/0x114
[   50.743362]  call_on_irq_stack+0x24/0x4c
[   50.747298]  do_interrupt_handler+0x80/0x84
[   50.751494]  el1_interrupt+0x34/0x68
[   50.755082]  el1h_64_irq_handler+0x18/0x24
[   50.759191]  el1h_64_irq+0x6c/0x70
[   50.762597]  default_idle_call+0x28/0x3c (P)
[   50.766879]  default_idle_call+0x24/0x3c (L)
[   50.771163]  do_idle+0x200/0x25c
[   50.774403]  cpu_startup_entry+0x34/0x3c
[   50.778338]  kernel_init+0x0/0x1d8
[   50.781752]  start_kernel+0x5c4/0x70c
[   50.785427]  __primary_switched+0x88/0x90

> 
>> +}
>> +
>> +static irqreturn_t
>> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct dc_crtc *dc_crtc = dev_id;
>> +	struct drm_crtc *crtc = &dc_crtc->base;
>> +	unsigned long flags;
>> +
>> +	drm_crtc_handle_vblank(crtc);
>> +
>> +	spin_lock_irqsave(&crtc->dev->event_lock, flags);
>> +	if (dc_crtc->event) {
>> +		drm_crtc_send_vblank_event(crtc, dc_crtc->event);
>> +		dc_crtc->event = NULL;
>> +		drm_crtc_vblank_put(crtc);
>> +	}
>> +	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
>> +{
>> +	struct dc_crtc *dc_crtc = dev_id;
>> +
>> +	if (irq == dc_crtc->irq_dec_seqcomplete)
>> +		complete(&dc_crtc->dec_seqcomplete_done);
>> +	else if (irq == dc_crtc->irq_dec_shdld)
>> +		complete(&dc_crtc->dec_shdld_done);
>> +	else if (irq == dc_crtc->irq_ed_cont_shdld)
>> +		complete(&dc_crtc->ed_cont_shdld_done);
>> +	else if (irq == dc_crtc->irq_ed_safe_shdld)
>> +		complete(&dc_crtc->ed_safe_shdld_done);
> 
> Is there any reason to have a single multiplex handler instead of having
> 4 separate handlers, each doing one simple thing?

Just thought that one irq handler is feasible to implement the common
handling logic. It's also ok to use 4 separate handlers with a little
performance improvement. I may change to use 4 separate handlers by
introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco.

> 
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static const struct drm_crtc_funcs dc_crtc_funcs = {
>> +	.reset			= drm_atomic_helper_crtc_reset,
>> +	.destroy		= drm_crtc_cleanup,
>> +	.set_config		= drm_atomic_helper_set_config,
>> +	.page_flip		= drm_atomic_helper_page_flip,
>> +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
>> +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
>> +	.get_vblank_counter	= dc_crtc_get_vblank_counter,
>> +	.enable_vblank		= dc_crtc_enable_vblank,
>> +	.disable_vblank		= dc_crtc_disable_vblank,
>> +	.get_vblank_timestamp	= drm_crtc_vblank_helper_get_vblank_timestamp,
>> +};
>> +
>> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
>> +{
>> +	struct drm_crtc *crtc = crtc_state->crtc;
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +
>> +	spin_lock_irq(&crtc->dev->event_lock);
>> +	if (crtc_state->event) {
>> +		WARN_ON(drm_crtc_vblank_get(crtc));
>> +		WARN_ON(dc_crtc->event);
>> +		dc_crtc->event = crtc_state->event;
>> +		crtc_state->event = NULL;
>> +	}
>> +	spin_unlock_irq(&crtc->dev->event_lock);
>> +}
>> +
>> +static enum drm_mode_status
>> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
>> +{
>> +	return dc_fg_check_clock(dc_crtc->fg, clk_khz);
>> +}
> 
> inline

Will do.

> 
>> +
>> +static enum drm_mode_status
>> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	enum drm_mode_status status;
>> +
>> +	status = dc_crtc_check_clock(dc_crtc, mode->clock);
>> +	if (status != MODE_OK)
>> +		return status;
>> +
>> +	if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
>> +		return MODE_CLOCK_HIGH;
>> +
>> +	return MODE_OK;
>> +}
>> +
>> +static int
>> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
>> +{
>> +	struct drm_crtc_state *new_crtc_state =
>> +				drm_atomic_get_new_crtc_state(state, crtc);
>> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	enum drm_mode_status status;
>> +
>> +	status = dc_crtc_check_clock(dc_crtc, adj->clock);
>> +	if (status != MODE_OK)
>> +		return -EINVAL;
>> +
>> +	return 0;
>> +}
>> +
>> +static void
>> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
>> +{
>> +	struct drm_crtc_state *new_crtc_state =
>> +				drm_atomic_get_new_crtc_state(state, crtc);
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	int idx, ret;
>> +
>> +	if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>> +	    !new_crtc_state->active)
>> +		return;
>> +
>> +	if (!drm_dev_enter(crtc->dev, &idx))
>> +		return;
>> +
>> +	/* request pixel engine power-on when CRTC starts to be active */
>> +	ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
>> +	if (ret)
>> +		dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
>> +			    ret);
>> +
>> +	drm_dev_exit(idx);
>> +}
>> +
>> +static void
>> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
>> +{
>> +	struct drm_crtc_state *old_crtc_state =
>> +				drm_atomic_get_old_crtc_state(state, crtc);
>> +	struct drm_crtc_state *new_crtc_state =
>> +				drm_atomic_get_new_crtc_state(state, crtc);
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	int idx;
>> +
>> +	if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>> +	    (!old_crtc_state->active && !new_crtc_state->active))
>> +		return;
>> +
>> +	if (!drm_dev_enter(crtc->dev, &idx))
>> +		goto out;
>> +
>> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
>> +
>> +	/* flush plane update out to display */
>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>> +
>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>> +
>> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
>> +
>> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>> +
>> +	drm_dev_exit(idx);
>> +
>> +out:
>> +	dc_crtc_queue_state_event(new_crtc_state);
>> +}
>> +
>> +static void
>> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>> +{
>> +	struct drm_crtc_state *new_crtc_state =
>> +				drm_atomic_get_new_crtc_state(state, crtc);
>> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	enum dc_link_id cf_link;
>> +	int idx, ret;
>> +
>> +	dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
>> +
>> +	drm_crtc_vblank_on(crtc);
>> +
>> +	if (!drm_dev_enter(crtc->dev, &idx))
>> +		goto out;
>> +
>> +	/* request display engine power-on when CRTC is enabled */
>> +	ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
>> +	if (ret < 0)
>> +		dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
>> +			    ret);
>> +
>> +	enable_irq(dc_crtc->irq_dec_shdld);
>> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
>> +	enable_irq(dc_crtc->irq_ed_safe_shdld);
>> +
>> +	dc_fg_cfg_videomode(dc_crtc->fg, adj);
>> +
>> +	dc_cf_framedimensions(dc_crtc->cf_cont,
>> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
>> +	dc_cf_framedimensions(dc_crtc->cf_safe,
>> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
>> +
>> +	/* constframe in safety stream shows blue frame */
>> +	dc_cf_constantcolor_blue(dc_crtc->cf_safe);
>> +	cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
>> +	dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
>> +
>> +	/* show CRTC background if no plane is enabled */
>> +	if (new_crtc_state->plane_mask == 0) {
>> +		/* constframe in content stream shows black frame */
>> +		dc_cf_constantcolor_black(dc_crtc->cf_cont);
>> +
>> +		cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
>> +		dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
>> +	}
>> +
>> +	dc_fg_enable_clock(dc_crtc->fg);
>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
>> +	dc_fg_shdtokgen(dc_crtc->fg);
>> +	dc_fg_enable(dc_crtc->fg);
>> +
>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
>> +
>> +	disable_irq(dc_crtc->irq_ed_safe_shdld);
>> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
>> +	disable_irq(dc_crtc->irq_dec_shdld);
>> +
>> +	DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
>> +
>> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>> +
>> +	drm_dev_exit(idx);
>> +
>> +out:
>> +	dc_crtc_queue_state_event(new_crtc_state);
>> +}
>> +
>> +static void
>> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>> +{
>> +	struct drm_crtc_state *new_crtc_state =
>> +				drm_atomic_get_new_crtc_state(state, crtc);
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	int idx, ret;
>> +
>> +	if (!drm_dev_enter(crtc->dev, &idx))
>> +		goto out;
>> +
>> +	enable_irq(dc_crtc->irq_dec_seqcomplete);
>> +	dc_fg_disable(dc_crtc->fg);
>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
>> +	disable_irq(dc_crtc->irq_dec_seqcomplete);
>> +
>> +	dc_fg_disable_clock(dc_crtc->fg);
>> +
>> +	/* request pixel engine power-off as plane is off too */
>> +	ret = pm_runtime_put(dc_crtc->pe->dev);
>> +	if (ret)
>> +		dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>> +			    ret);
>> +
>> +	/* request display engine power-off when CRTC is disabled */
>> +	ret = pm_runtime_put(dc_crtc->de->dev);
> 
> Can this be expressed as a devlink between PE and DE?

Looking at struct dc_{pe,de}, PE and DE are not dependent with each other,
i.e., no consumer/supplier relationship(note that blit engine in PE can work
by itself without DE) between them. So, it doesn't look right to link the two
devices.

> 
>> +	if (ret < 0)
>> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>> +			    ret);
>> +
>> +	drm_dev_exit(idx);
>> +
>> +out:
>> +	drm_crtc_vblank_off(crtc);
>> +
>> +	spin_lock_irq(&crtc->dev->event_lock);
>> +	if (new_crtc_state->event && !new_crtc_state->active) {
>> +		drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
>> +		new_crtc_state->event = NULL;
>> +	}
>> +	spin_unlock_irq(&crtc->dev->event_lock);
>> +}
>> +
>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	int ret;
>> +
>> +	if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
>> +		return;
>> +
>> +	dc_fg_disable_clock(dc_crtc->fg);
>> +
>> +	if (pm_runtime_active(dc_crtc->pe->dev)) {
>> +		ret = pm_runtime_put_sync(dc_crtc->pe->dev);
>> +		if (ret)
>> +			dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>> +				    ret);
>> +	}
>> +
>> +	ret = pm_runtime_put_sync(dc_crtc->de->dev);
>> +	if (ret < 0)
>> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>> +			    ret);
>> +}
>> +
>> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
>> +					 bool in_vblank_irq,
>> +					 int *vpos, int *hpos,
>> +					 ktime_t *stime, ktime_t *etime,
>> +					 const struct drm_display_mode *mode)
>> +{
>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>> +	int vdisplay = mode->crtc_vdisplay;
>> +	int vtotal = mode->crtc_vtotal;
>> +	bool reliable;
>> +	int line;
>> +	int idx;
>> +
>> +	if (stime)
>> +		*stime = ktime_get();
>> +
>> +	if (!drm_dev_enter(crtc->dev, &idx)) {
>> +		reliable = false;
>> +		*vpos = 0;
>> +		*hpos = 0;
>> +		goto out;
>> +	}
>> +
>> +	/* line index starts with 0 for the first active output line */
>> +	line = dc_fg_get_line_index(dc_crtc->fg);
>> +
>> +	if (line < vdisplay)
>> +		/* active scanout area - positive */
>> +		*vpos = line + 1;
>> +	else
>> +		/* inside vblank - negative */
>> +		*vpos = line - (vtotal - 1);
>> +
>> +	*hpos = 0;
>> +
>> +	reliable = true;
>> +
>> +	drm_dev_exit(idx);
>> +out:
>> +	if (etime)
>> +		*etime = ktime_get();
>> +
>> +	return reliable;
>> +}
>> +
>> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
>> +	.mode_valid		= dc_crtc_mode_valid,
>> +	.atomic_check		= dc_crtc_atomic_check,
>> +	.atomic_begin		= dc_crtc_atomic_begin,
>> +	.atomic_flush		= dc_crtc_atomic_flush,
>> +	.atomic_enable		= dc_crtc_atomic_enable,
>> +	.atomic_disable		= dc_crtc_atomic_disable,
>> +	.get_scanout_position	= dc_crtc_get_scanout_position,
>> +};
>> +
>> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
>> +{
>> +	struct {
>> +		struct device *dev;
>> +		unsigned int irq;
>> +		irqreturn_t (*irq_handler)(int irq, void *dev_id);
>> +	} irqs[DC_CRTC_IRQS] = {
>> +		{
>> +			dc_crtc->de->dev,
>> +			dc_crtc->irq_dec_framecomplete,
>> +			dc_crtc_dec_framecomplete_irq_handler,
>> +		}, {
>> +			dc_crtc->de->dev,
>> +			dc_crtc->irq_dec_seqcomplete,
>> +			dc_crtc_common_irq_handler,
>> +		}, {
>> +			dc_crtc->de->dev,
>> +			dc_crtc->irq_dec_shdld,
>> +			dc_crtc_common_irq_handler,
>> +		}, {
>> +			dc_crtc->ed_cont->dev,
>> +			dc_crtc->irq_ed_cont_shdld,
>> +			dc_crtc_common_irq_handler,
>> +		}, {
>> +			dc_crtc->ed_safe->dev,
>> +			dc_crtc->irq_ed_safe_shdld,
>> +			dc_crtc_common_irq_handler,
>> +		},
>> +	};
>> +	int i, ret;
>> +
>> +	for (i = 0; i < DC_CRTC_IRQS; i++) {
>> +		struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
>> +
>> +		ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
>> +				       irqs[i].irq_handler, IRQF_NO_AUTOEN,
>> +				       dev_name(irqs[i].dev), dc_crtc);
>> +		if (ret) {
>> +			dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
>> +				irqs[i].irq, ret);
>> +			return ret;
>> +		}
>> +
>> +		irq->dc_crtc = dc_crtc;
>> +		irq->irq = irqs[i].irq;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
>> +{
>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>> +	struct drm_device *drm = &dc_drm->base;
>> +	struct dc_de *de = dc_drm->de[crtc_index];
>> +	struct dc_pe *pe = dc_drm->pe;
>> +	struct dc_plane *dc_primary;
>> +	int ret;
>> +
>> +	dc_crtc->de = de;
>> +	dc_crtc->pe = pe;
>> +
>> +	init_completion(&dc_crtc->dec_seqcomplete_done);
>> +	init_completion(&dc_crtc->dec_shdld_done);
>> +	init_completion(&dc_crtc->ed_cont_shdld_done);
>> +	init_completion(&dc_crtc->ed_safe_shdld_done);
>> +
>> +	dc_crtc->cf_cont = pe->cf_cont[crtc_index];
>> +	dc_crtc->cf_safe = pe->cf_safe[crtc_index];
>> +	dc_crtc->ed_cont = pe->ed_cont[crtc_index];
>> +	dc_crtc->ed_safe = pe->ed_safe[crtc_index];
>> +	dc_crtc->fg = de->fg;
>> +
>> +	dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
>> +	dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
>> +	dc_crtc->irq_dec_shdld = de->irq_shdld;
>> +	dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
>> +	dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
>> +
>> +	dc_primary = &dc_drm->dc_primary[crtc_index];
>> +	ret = dc_plane_init(dc_drm, dc_primary);
>> +	if (ret) {
>> +		dev_err(drm->dev,
>> +			"failed to init primary plane for display engine%u: %d\n",
>> +			de->id, ret);
>> +		return ret;
>> +	}
>> +
>> +	drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
>> +
>> +	ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
>> +					NULL, &dc_crtc_funcs, NULL);
>> +	if (ret)
>> +		dev_err(drm->dev,
>> +			"failed to add CRTC for display engine%u: %d\n",
>> +			de->id, ret);
>> +
>> +	return ret;
>> +}
>> +
>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
>> +{
>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>> +	struct drm_device *drm = &dc_drm->base;
>> +
>> +	return dc_crtc_request_irqs(drm, dc_crtc);
>> +}
>> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
>> index 17a44362118e..8a7b6c03a222 100644
>> --- a/drivers/gpu/drm/imx/dc/dc-de.h
>> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
>> @@ -13,6 +13,9 @@
>>  
>>  #define DC_DISPLAYS	2
>>  
>> +#define DC_FRAMEGEN_MAX_FRAME_INDEX	0x3ffff
>> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ	300000
>> +
>>  struct dc_fg {
>>  	struct device *dev;
>>  	struct regmap *reg;
>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
>> index fd68861f770a..1e4b8afa3eec 100644
>> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
>> @@ -3,11 +3,254 @@
>>   * Copyright 2024 NXP
>>   */
>>  
>> +#include <linux/clk.h>
>> +#include <linux/component.h>
>> +#include <linux/device.h>
>> +#include <linux/dma-mapping.h>
>> +#include <linux/mod_devicetable.h>
>>  #include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_platform.h>
>>  #include <linux/platform_device.h>
>> +#include <linux/pm.h>
>> +#include <linux/pm_runtime.h>
>>  
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_client_setup.h>
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_fbdev_dma.h>
>> +#include <drm/drm_fourcc.h>
>> +#include <drm/drm_gem_dma_helper.h>
>> +#include <drm/drm_managed.h>
>> +#include <drm/drm_modeset_helper.h>
>> +#include <drm/drm_of.h>
>> +
>> +#include "dc-de.h"
>>  #include "dc-drv.h"
>>  
>> +struct dc_priv {
>> +	struct drm_device *drm;
>> +	struct clk *clk_cfg;
>> +};
>> +
>> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
>> +
>> +static struct drm_driver dc_drm_driver = {
>> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
>> +	DRM_GEM_DMA_DRIVER_OPS,
>> +	DRM_FBDEV_DMA_DRIVER_OPS,
>> +	.fops = &dc_drm_driver_fops,
>> +	.name = "imx8-dc",
>> +	.desc = "i.MX8 DC DRM graphics",
>> +	.date = "20240530",
>> +	.major = 1,
>> +	.minor = 0,
>> +	.patchlevel = 0,
>> +};
>> +
>> +static void
>> +dc_add_components(struct device *dev, struct component_match **matchptr)
>> +{
>> +	struct device_node *child, *grandchild;
>> +
>> +	for_each_available_child_of_node(dev->of_node, child) {
>> +		/* The interrupt controller is not a component. */
>> +		if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
>> +			continue;
>> +
>> +		drm_of_component_match_add(dev, matchptr, component_compare_of,
>> +					   child);
>> +
>> +		for_each_available_child_of_node(child, grandchild)
>> +			drm_of_component_match_add(dev, matchptr,
>> +						   component_compare_of,
>> +						   grandchild);
>> +	}
>> +}
>> +
>> +static void dc_drm_component_unbind_all(void *ptr)
>> +{
>> +	struct dc_drm_device *dc_drm = ptr;
>> +	struct drm_device *drm = &dc_drm->base;
>> +
>> +	component_unbind_all(drm->dev, dc_drm);
>> +}
>> +
>> +static int dc_drm_bind(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +	struct dc_drm_device *dc_drm;
>> +	struct drm_device *drm;
>> +	int ret;
>> +
>> +	dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
>> +				    base);
>> +	if (IS_ERR(dc_drm))
>> +		return PTR_ERR(dc_drm);
>> +
>> +	drm = &dc_drm->base;
>> +
>> +	ret = component_bind_all(dev, dc_drm);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
>> +				       dc_drm);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = dc_kms_init(dc_drm);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = drm_dev_register(drm, 0);
>> +	if (ret) {
>> +		dev_err(dev, "failed to register drm device: %d\n", ret);
>> +		goto err;
>> +	}
>> +
>> +	drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
>> +
>> +	priv->drm = drm;
>> +
>> +	return 0;
>> +
>> +err:
>> +	dc_kms_uninit(dc_drm);
>> +
>> +	return ret;
>> +}
>> +
>> +static void dc_drm_unbind(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +	struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
>> +	struct drm_device *drm = &dc_drm->base;
>> +	struct drm_crtc *crtc;
>> +
>> +	priv->drm = NULL;
>> +	drm_dev_unplug(drm);
>> +	dc_kms_uninit(dc_drm);
>> +	drm_atomic_helper_shutdown(drm);
>> +
>> +	drm_for_each_crtc(crtc, drm)
>> +		dc_crtc_disable_at_unbind(crtc);
> 
> There should be no need for that. drm_atomic_helper_shutdown() should
> disable all the CRTCs.

In case DRM device is unplugged, drm_atomic_helper_shutdown does not
effectively disable the CRTCs due to the bypassed logics wrapped by
drm_dev_{enter,exit}.  That's why dc_crtc_disable_at_unbind() is called
here to effectively disable the CRTCs.

> 
> Also, who is going to do drm_dev_unregister()? I don't see it in the
> code.

drm_dev_unplug() right above calls drm_dev_unregister().

> 
>> +}
>> +
>> +static const struct component_master_ops dc_drm_ops = {
>> +	.bind = dc_drm_bind,
>> +	.unbind = dc_drm_unbind,
>> +};
>> +
>> +static int dc_probe(struct platform_device *pdev)
>> +{
>> +	struct component_match *match = NULL;
>> +	struct dc_priv *priv;
>> +	int ret;
>> +
>> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
>> +	if (IS_ERR(priv->clk_cfg))
>> +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
>> +				     "failed to get cfg clock\n");
>> +
>> +	dev_set_drvdata(&pdev->dev, priv);
>> +
>> +	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = devm_pm_runtime_enable(&pdev->dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = devm_of_platform_populate(&pdev->dev);
>> +	if (ret)
>> +		return ret;
>> +
>> +	dc_add_components(&pdev->dev, &match);
>> +
>> +	ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
>> +	if (ret)
>> +		return dev_err_probe(&pdev->dev, ret,
>> +				     "failed to add component master\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static void dc_remove(struct platform_device *pdev)
>> +{
>> +	component_master_del(&pdev->dev, &dc_drm_ops);
>> +}
>> +
>> +static int dc_runtime_suspend(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +
>> +	clk_disable_unprepare(priv->clk_cfg);
>> +
>> +	return 0;
>> +}
>> +
>> +static int dc_runtime_resume(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +	int ret;
>> +
>> +	ret = clk_prepare_enable(priv->clk_cfg);
>> +	if (ret)
>> +		dev_err(dev, "failed to enable cfg clock: %d\n", ret);
>> +
>> +	return ret;
>> +}
>> +
>> +static int dc_suspend(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +
>> +	return drm_mode_config_helper_suspend(priv->drm);
>> +}
>> +
>> +static int dc_resume(struct device *dev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>> +
>> +	return drm_mode_config_helper_resume(priv->drm);
>> +}
>> +
>> +static void dc_shutdown(struct platform_device *pdev)
>> +{
>> +	struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
>> +
>> +	drm_atomic_helper_shutdown(priv->drm);
>> +}
>> +
>> +static const struct dev_pm_ops dc_pm_ops = {
>> +	RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
>> +	SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
>> +};
>> +
>> +static const struct of_device_id dc_dt_ids[] = {
>> +	{ .compatible = "fsl,imx8qxp-dc", },
>> +	{ /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
>> +
>> +static struct platform_driver dc_driver = {
>> +	.probe = dc_probe,
>> +	.remove = dc_remove,
>> +	.shutdown = dc_shutdown,
>> +	.driver = {
>> +		.name = "imx8-dc",
>> +		.of_match_table	= dc_dt_ids,
>> +		.pm = pm_sleep_ptr(&dc_pm_ops),
>> +	},
>> +};
>> +
>>  static struct platform_driver * const dc_drivers[] = {
>>  	&dc_cf_driver,
>>  	&dc_de_driver,
>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
>>  	&dc_lb_driver,
>>  	&dc_pe_driver,
>>  	&dc_tc_driver,
>> +	&dc_driver,
>>  };
>>  
>>  static int __init dc_drm_init(void)
>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
>> index 3b11f4862c6c..39a771a13933 100644
>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
>> @@ -6,19 +6,38 @@
>>  #ifndef __DC_DRV_H__
>>  #define __DC_DRV_H__
>>  
>> +#include <linux/container_of.h>
>>  #include <linux/platform_device.h>
>>  
>>  #include <drm/drm_device.h>
>> +#include <drm/drm_encoder.h>
>>  
>>  #include "dc-de.h"
>> +#include "dc-kms.h"
>>  #include "dc-pe.h"
>>  
>>  struct dc_drm_device {
>>  	struct drm_device base;
>> +	struct dc_crtc dc_crtc[DC_DISPLAYS];
>> +	struct dc_plane dc_primary[DC_DISPLAYS];
>> +	struct drm_encoder encoder[DC_DISPLAYS];
>>  	struct dc_de *de[DC_DISPLAYS];
>>  	struct dc_pe *pe;
>>  };
>>  
>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
>> +{
>> +	return container_of(drm, struct dc_drm_device, base);
>> +}
>> +
>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
>> +
>> +int dc_kms_init(struct dc_drm_device *dc_drm);
>> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
>> +
>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
>> +
>>  extern struct platform_driver dc_cf_driver;
>>  extern struct platform_driver dc_ed_driver;
>>  extern struct platform_driver dc_de_driver;
>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
>> new file mode 100644
>> index 000000000000..2b18aa37a4a8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
>> @@ -0,0 +1,143 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright 2024 NXP
>> + */
>> +
>> +#include <linux/of.h>
>> +#include <linux/of_graph.h>
>> +
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_bridge.h>
>> +#include <drm/drm_bridge_connector.h>
>> +#include <drm/drm_connector.h>
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_device.h>
>> +#include <drm/drm_encoder.h>
>> +#include <drm/drm_gem_framebuffer_helper.h>
>> +#include <drm/drm_mode_config.h>
>> +#include <drm/drm_print.h>
>> +#include <drm/drm_probe_helper.h>
>> +#include <drm/drm_simple_kms_helper.h>
>> +#include <drm/drm_vblank.h>
>> +
>> +#include "dc-de.h"
>> +#include "dc-drv.h"
>> +#include "dc-kms.h"
>> +
>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
>> +	.fb_create = drm_gem_fb_create,
>> +	.atomic_check = drm_atomic_helper_check,
>> +	.atomic_commit = drm_atomic_helper_commit,
>> +};
>> +
>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
>> +					int crtc_index)
>> +{
>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>> +	struct drm_device *drm = &dc_drm->base;
>> +	struct drm_crtc *crtc = &dc_crtc->base;
>> +	struct drm_connector *connector;
>> +	struct device *dev = drm->dev;
>> +	struct drm_encoder *encoder;
>> +	struct drm_bridge *bridge;
>> +	int ret;
>> +
>> +	bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
>> +					0, 0);
>> +	if (IS_ERR(bridge)) {
>> +		ret = PTR_ERR(bridge);
>> +		if (ret == -ENODEV)
>> +			return 0;
>> +
>> +		return dev_err_probe(dev, ret,
>> +				     "failed to find bridge for CRTC%u\n",
>> +				     crtc->index);
>> +	}
>> +
>> +	encoder = &dc_drm->encoder[crtc_index];
>> +	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
>> +	if (ret) {
>> +		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
>> +			crtc->index, ret);
>> +		return ret;
>> +	}
>> +
>> +	encoder->possible_crtcs = drm_crtc_mask(crtc);
>> +
>> +	ret = drm_bridge_attach(encoder, bridge, NULL,
>> +				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>> +	if (ret) {
>> +		dev_err(dev,
>> +			"failed to attach bridge to encoder for CRTC%u: %d\n",
>> +			crtc->index, ret);
>> +		return ret;
>> +	}
>> +
>> +	connector = drm_bridge_connector_init(drm, encoder);
>> +	if (IS_ERR(connector)) {
>> +		ret = PTR_ERR(connector);
>> +		dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
>> +			crtc->index, ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = drm_connector_attach_encoder(connector, encoder);
>> +	if (ret)
>> +		dev_err(dev,
>> +			"failed to attach encoder to connector for CRTC%u: %d\n",
>> +			crtc->index, ret);
>> +
>> +	return ret;
>> +}
>> +
>> +int dc_kms_init(struct dc_drm_device *dc_drm)
>> +{
>> +	struct drm_device *drm = &dc_drm->base;
>> +	int ret, i;
>> +
>> +	ret = drmm_mode_config_init(drm);
>> +	if (ret)
>> +		return ret;
>> +
>> +	drm->mode_config.min_width = 60;
>> +	drm->mode_config.min_height = 60;
>> +	drm->mode_config.max_width = 8192;
>> +	drm->mode_config.max_height = 8192;
>> +	drm->mode_config.funcs = &dc_drm_mode_config_funcs;
>> +
>> +	drm->vblank_disable_immediate = true;
>> +	drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
>> +
>> +	for (i = 0; i < DC_DISPLAYS; i++) {
>> +		ret = dc_crtc_init(dc_drm, i);
>> +		if (ret)
>> +			return ret;
>> +
>> +		ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	for (i = 0; i < DC_DISPLAYS; i++) {
>> +		ret = dc_crtc_post_init(dc_drm, i);
> 
> Can you use .late_register for this?

Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
additional userspace interfaces like debugfs interfaces. And, it seems that
everyone implementing this uses it to add debugfs interfaces. So, it will
kind of abuse it to do CRTC post initialization.

> 
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	ret = drm_vblank_init(drm, DC_DISPLAYS);
>> +	if (ret) {
>> +		dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	drm_mode_config_reset(drm);
>> +
>> +	drm_kms_helper_poll_init(drm);
>> +
>> +	return 0;
>> +}
>> +
>> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
>> +{
>> +	drm_kms_helper_poll_fini(&dc_drm->base);
>> +}
>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
>> new file mode 100644
>> index 000000000000..57f6e0c15f57
>> --- /dev/null
>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
>> @@ -0,0 +1,58 @@
>> +/* SPDX-License-Identifier: GPL-2.0+ */
>> +/*
>> + * Copyright 2024 NXP
>> + */
>> +
>> +#ifndef __DC_KMS_H__
>> +#define __DC_KMS_H__
>> +
>> +#include <linux/completion.h>
>> +
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_plane.h>
>> +#include <drm/drm_vblank.h>
>> +
>> +#include "dc-de.h"
>> +#include "dc-fu.h"
>> +#include "dc-pe.h"
>> +
>> +#define DC_CRTC_IRQS	5
>> +
>> +struct dc_crtc_irq {
>> +	struct dc_crtc *dc_crtc;
>> +	unsigned int irq;
>> +};
>> +
> 
> Please provide some documentation for the structure in the form of the
> kerneldoc. E.g. what is the difference between ed_cont and ed_safe?

Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the
difference between ed_cont(content stream) and ed_safe(safety stream).

> The de and fg pointers are global, please don't cache them
> unnecessarily.

Global? I don't catch your meaning, sorry. To me, it's handy to access de
and fg via the two pointers in struct dc_crtc.

> 
>> +struct dc_crtc {
>> +	struct drm_crtc base;
>> +	struct dc_de *de;
>> +	struct dc_pe *pe;
>> +	struct dc_cf *cf_cont;
>> +	struct dc_cf *cf_safe;
>> +	struct dc_ed *ed_cont;
>> +	struct dc_ed *ed_safe;
>> +	struct dc_fg *fg;
>> +	unsigned int irq_dec_framecomplete;
>> +	unsigned int irq_dec_seqcomplete;
>> +	unsigned int irq_dec_shdld;
>> +	unsigned int irq_ed_cont_shdld;
>> +	unsigned int irq_ed_safe_shdld;
>> +	struct completion dec_seqcomplete_done;
>> +	struct completion dec_shdld_done;
>> +	struct completion ed_safe_shdld_done;
>> +	struct completion ed_cont_shdld_done;
>> +	struct drm_pending_vblank_event *event;
>> +	struct dc_crtc_irq irqs[DC_CRTC_IRQS];
>> +};
>> +
>> +struct dc_plane {
>> +	struct drm_plane base;
>> +	struct dc_fu *fu;
>> +	struct dc_cf *cf;
>> +	struct dc_lb *lb;
>> +	struct dc_ed *ed;
>> +};
>> +
>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
>> +
>> +#endif /* __DC_KMS_H__ */
>> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
>> new file mode 100644
>> index 000000000000..78d0d2cd3451
>> --- /dev/null
>> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
>> @@ -0,0 +1,241 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Copyright 2024 NXP
>> + */
>> +
>> +#include <linux/container_of.h>
>> +
>> +#include <drm/drm_atomic.h>
>> +#include <drm/drm_atomic_helper.h>
>> +#include <drm/drm_atomic_state_helper.h>
>> +#include <drm/drm_crtc.h>
>> +#include <drm/drm_drv.h>
>> +#include <drm/drm_fb_dma_helper.h>
>> +#include <drm/drm_fourcc.h>
>> +#include <drm/drm_framebuffer.h>
>> +#include <drm/drm_gem_atomic_helper.h>
>> +#include <drm/drm_plane_helper.h>
>> +#include <drm/drm_print.h>
>> +
>> +#include "dc-drv.h"
>> +#include "dc-fu.h"
>> +#include "dc-kms.h"
>> +
>> +#define DC_PLANE_MAX_PITCH	0x10000
>> +#define DC_PLANE_MAX_PIX_CNT	8192
>> +
>> +#define dc_plane_dbg(plane, fmt, ...)					\
>> +do {									\
>> +	typeof(plane) _plane = (plane);					\
>> +	drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,			\
>> +		    _plane->base.id, _plane->name, ##__VA_ARGS__);	\
>> +} while (0)
>> +
>> +static const uint32_t dc_plane_formats[] = {
>> +	DRM_FORMAT_XRGB8888,
>> +};
>> +
>> +static const struct drm_plane_funcs dc_plane_funcs = {
>> +	.update_plane		= drm_atomic_helper_update_plane,
>> +	.disable_plane		= drm_atomic_helper_disable_plane,
>> +	.destroy		= drm_plane_cleanup,
>> +	.reset			= drm_atomic_helper_plane_reset,
>> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
>> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
>> +};
>> +
>> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
>> +{
>> +	return container_of(plane, struct dc_plane, base);
>> +}
>> +
>> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
>> +					struct drm_crtc_state *crtc_state)
>> +{
>> +	if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
>> +	    state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
>> +	    state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
>> +		dc_plane_dbg(state->plane, "no off screen\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
>> +{
>> +	int src_h = drm_rect_height(&state->src) >> 16;
>> +	int src_w = drm_rect_width(&state->src) >> 16;
>> +
>> +	if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
>> +		dc_plane_dbg(state->plane, "invalid source resolution\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int dc_plane_check_fb(struct drm_plane_state *state)
>> +{
>> +	struct drm_framebuffer *fb = state->fb;
>> +	dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
>> +
>> +	/* base address alignment */
>> +	if (baseaddr & 0x3) {
>> +		dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* pitches[0] range */
>> +	if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
>> +		dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	/* pitches[0] alignment */
>> +	if (fb->pitches[0] & 0x3) {
>> +		dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static int
>> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
>> +{
>> +	struct drm_plane_state *plane_state =
>> +				drm_atomic_get_new_plane_state(state, plane);
>> +	struct drm_crtc_state *crtc_state;
>> +	int ret;
>> +
>> +	/* ok to disable */
>> +	if (!plane_state->fb)
>> +		return 0;
>> +
>> +	if (!plane_state->crtc) {
>> +		dc_plane_dbg(plane, "no CRTC in plane state\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	crtc_state =
>> +		drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
>> +	if (WARN_ON(!crtc_state))
>> +		return -EINVAL;
>> +
>> +	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
>> +						  DRM_PLANE_NO_SCALING,
>> +						  DRM_PLANE_NO_SCALING,
>> +						  true, false);
>> +	if (ret) {
>> +		dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = dc_plane_check_max_source_resolution(plane_state);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return dc_plane_check_fb(plane_state);
>> +}
>> +
>> +static void
>> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
>> +{
>> +	struct drm_plane_state *new_state =
>> +				drm_atomic_get_new_plane_state(state, plane);
>> +	struct dc_plane *dplane = to_dc_plane(plane);
>> +	struct drm_framebuffer *fb = new_state->fb;
>> +	const struct dc_fu_ops *fu_ops;
>> +	struct dc_lb *lb = dplane->lb;
>> +	struct dc_fu *fu = dplane->fu;
>> +	dma_addr_t baseaddr;
>> +	int src_w, src_h;
>> +	int idx;
>> +
>> +	if (!drm_dev_enter(plane->dev, &idx))
>> +		return;
>> +
>> +	src_w = drm_rect_width(&new_state->src) >> 16;
>> +	src_h = drm_rect_height(&new_state->src) >> 16;
>> +
>> +	baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
>> +
>> +	fu_ops = dc_fu_get_ops(dplane->fu);
>> +
>> +	fu_ops->set_layerblend(fu, lb);
>> +	fu_ops->set_burstlength(fu, baseaddr);
>> +	fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
>> +	fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
>> +	fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
>> +	fu_ops->set_framedimensions(fu, src_w, src_h);
>> +	fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
>> +	fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
>> +
>> +	dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
>> +
>> +	dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
>> +	dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
>> +	dc_lb_mode(lb, LB_BLEND);
>> +	dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
>> +	dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
>> +
>> +	dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
>> +
>> +	/* set ExtDst's source to LayerBlend */
>> +	dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
>> +
>> +	drm_dev_exit(idx);
>> +}
>> +
>> +static void dc_plane_atomic_disable(struct drm_plane *plane,
>> +				    struct drm_atomic_state *state)
>> +{
>> +	struct dc_plane *dplane = to_dc_plane(plane);
>> +	const struct dc_fu_ops *fu_ops;
>> +	int idx;
>> +
>> +	if (!drm_dev_enter(plane->dev, &idx))
>> +		return;
>> +
>> +	/* disable fetchunit in shadow */
>> +	fu_ops = dc_fu_get_ops(dplane->fu);
>> +	fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
>> +
>> +	/* set ExtDst's source to ConstFrame */
>> +	dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
>> +
>> +	drm_dev_exit(idx);
>> +}
>> +
>> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
>> +	.atomic_check = dc_plane_atomic_check,
>> +	.atomic_update = dc_plane_atomic_update,
>> +	.atomic_disable = dc_plane_atomic_disable,
>> +};
>> +
>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
>> +{
>> +	struct drm_plane *plane = &dc_plane->base;
>> +	int ret;
>> +
>> +	ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
>> +				       dc_plane_formats,
>> +				       ARRAY_SIZE(dc_plane_formats),
>> +				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
>> +	if (ret)
>> +		return ret;
>> +
>> +	drm_plane_helper_add(plane, &dc_plane_helper_funcs);
>> +
>> +	dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
>> +	dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
>> +	dc_plane->lb = dc_drm->pe->lb[plane->index];
>> +	dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
>> +
>> +	return 0;
>> +}
>> -- 
>> 2.34.1
>>
> 

-- 
Regards,
Liu Ying


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

* Re: [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units
  2024-12-11  3:05     ` Liu Ying
@ 2024-12-11 14:55       ` Rob Herring
  2024-12-13  3:40         ` Liu Ying
  0 siblings, 1 reply; 35+ messages in thread
From: Rob Herring @ 2024-12-11 14:55 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, krzk+dt, conor+dt, shawnguo, s.hauer, kernel,
	festevam, tglx, vkoul, kishon, aisheng.dong, agx, francesco,
	frank.li, dmitry.baryshkov, u.kleine-koenig

On Wed, Dec 11, 2024 at 11:05:52AM +0800, Liu Ying wrote:
> On 12/11/2024, Rob Herring wrote:
> > On Mon, Dec 09, 2024 at 11:39:05AM +0800, Liu Ying wrote:
> >> Freescale i.MX8qxp Display Controller is implemented as construction set of
> >> building blocks with unified concept and standardized interfaces.  Document
> >> all existing processing units.
> >>
> >> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> >> ---
> >> v6:
> >> * No change.
> >>
> >> v5:
> >> * Document aliases for processing units which have multiple instances in
> >>   the Display Controller.  Drop Rob's previous R-b tag. (Maxime)
> >>
> >> v4:
> >> * Collect Rob's R-b tag.
> >>
> >> v3:
> >> * Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
> >>   fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
> >>   into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
> >> * Document all processing units. (Rob)
> >>
> >> v2:
> >> * Drop fsl,dc-*-id DT properties. (Krzysztof)
> >> * Add port property to fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
> >> * Fix register range sizes in examples.
> >>
> >>  .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++++++
> >>  .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++++++++
> >>  .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 ++++++++++++++++++
> >>  .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 ++++++++
> >>  .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 +++++
> >>  .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++++++
> >>  .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++++
> >>  .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++++++++++
> >>  .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 +++++++
> >>  .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++++++++++
> >>  .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++++++
> >>  17 files changed, 1042 insertions(+)
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
> >>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
> >>
> >> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
> >> new file mode 100644
> >> index 000000000000..7f800e72c3f3
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
> >> @@ -0,0 +1,46 @@
> >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >> +%YAML 1.2
> >> +---
> >> +$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blitblend.yaml#
> >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >> +
> >> +title: Freescale i.MX8qxp Display Controller Blit Blend Unit
> >> +
> >> +description: |
> >> +  Combines two input frames to a single output frame, all frames having the
> >> +  same dimension.
> >> +
> >> +  Each Blit Blend Unit device should have an alias in the aliases node, in the
> >> +  form of dc<x>-blitblend<y>, where <x> is an integer specifying the Display
> >> +  Controller instance and <y> is an integer specifying the Blit Blend Unit
> >> +  device instance.
> > 
> > That's really an abuse of aliases. If you need to describe connections 
> > between components, use the graph binding like everyone else does for 
> > display path components.
> 
> I need to describe components' instance numbers which imply the connections
> between components but not vice versa. If I use the graph binding, I cannot
> get the instance numbers(0 or 1) of the two display engines(documented by
> fsl,imx8qxp-dc-display-engine.yaml). If you have no objections, I may add the
> instance numbers to compatible strings, like brcm,bcm2835-pixelvalve0.yaml.
> What do you think?

You could have dc<x> and blitblend<y> aliases and use the graph to 
define the connections. But I'm not really a fan of adding custom 
aliases either. Why are the instance numbers important?

Are the programming models or features of the instances different? If 
so, then a different compatible or property describing the feature may 
be appropriate.

Rob


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-11  7:43     ` Liu Ying
@ 2024-12-11 23:35       ` Dmitry Baryshkov
  2024-12-13  6:07         ` Liu Ying
  0 siblings, 1 reply; 35+ messages in thread
From: Dmitry Baryshkov @ 2024-12-11 23:35 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote:
> On 12/10/2024, Dmitry Baryshkov wrote:
> > On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
> >> i.MX8qxp Display Controller(DC) is comprised of three main components that
> >> include a blit engine for 2D graphics accelerations, display controller for
> >> display output processing, as well as a command sequencer.  Add kernel
> >> mode setting support for the display controller part with two CRTCs and
> >> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
> >> registers of the display controller are accessed without command sequencer
> >> involved, instead just by using CPU.  The command sequencer is supposed to
> >> be used by the blit engine.
> >>
> >> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> >> ---
> >> v6:
> >> * No change.
> >>
> >> v5:
> >> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
> >>
> >> v4:
> >> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
> >>   function calls from KMS routine to initialization stage. (Dmitry)
> >> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
> >>   appropriate .h header files or .c source files. (Dmitry)
> >> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
> >> * Drop dc_drm->pe_rpm_count. (Dmitry)
> >> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
> >> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
> >>   Instead, put it in struct dc_crtc.  (Dmitry)
> >> * Call devm_request_irq() to request IRQs, instead of using drmm action.
> >>   (Dmitry)
> >> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
> >> * Select DRM_CLIENT_SELECTION due to rebase.
> >> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
> >> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
> >> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
> >>   rebase.
> >> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
> >>   register dc_drm_component_unbind_all() action.
> >> * Request interrupts in dc_crtc_post_init() after encoder initialization to
> >>   make sure next bridge is found first.
> >>
> >> v3:
> >> * No change.
> >>
> >> v2:
> >> * Find next bridge from TCon's port.
> >> * Drop drm/drm_module.h include from dc-drv.c.
> >>
> >>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
> >>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
> >>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
> >>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
> >>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
> >>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
> >>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
> >>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
> >>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
> >>  9 files changed, 1274 insertions(+), 2 deletions(-)
> >>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
> >>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
> >>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
> >>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
> >>
> >> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
> >> index 1fc84c7475de..415993207f2e 100644
> >> --- a/drivers/gpu/drm/imx/dc/Kconfig
> >> +++ b/drivers/gpu/drm/imx/dc/Kconfig
> >> @@ -1,6 +1,11 @@
> >>  config DRM_IMX8_DC
> >>  	tristate "Freescale i.MX8 Display Controller Graphics"
> >>  	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
> >> +	select DRM_CLIENT_SELECTION
> >> +	select DRM_GEM_DMA_HELPER
> >> +	select DRM_KMS_HELPER
> >> +	select DRM_DISPLAY_HELPER
> >> +	select DRM_BRIDGE_CONNECTOR
> >>  	select GENERIC_IRQ_CHIP
> >>  	select REGMAP
> >>  	select REGMAP_MMIO
> >> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
> >> index 1ce3e8a8db22..b9d33c074984 100644
> >> --- a/drivers/gpu/drm/imx/dc/Makefile
> >> +++ b/drivers/gpu/drm/imx/dc/Makefile
> >> @@ -1,6 +1,7 @@
> >>  # SPDX-License-Identifier: GPL-2.0
> >>  
> >> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
> >> -		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
> >> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
> >> +		    dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
> >> +		    dc-plane.o dc-tc.o
> >>  
> >>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
> >> new file mode 100644
> >> index 000000000000..fd6daa1807d8
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
> >> @@ -0,0 +1,558 @@
> >> +// SPDX-License-Identifier: GPL-2.0+
> >> +/*
> >> + * Copyright 2024 NXP
> >> + */
> >> +
> >> +#include <linux/completion.h>
> >> +#include <linux/container_of.h>
> >> +#include <linux/interrupt.h>
> >> +#include <linux/irqreturn.h>
> >> +#include <linux/pm_runtime.h>
> >> +#include <linux/spinlock.h>
> >> +
> >> +#include <drm/drm_atomic.h>
> >> +#include <drm/drm_atomic_helper.h>
> >> +#include <drm/drm_atomic_state_helper.h>
> >> +#include <drm/drm_crtc.h>
> >> +#include <drm/drm_device.h>
> >> +#include <drm/drm_drv.h>
> >> +#include <drm/drm_modes.h>
> >> +#include <drm/drm_modeset_helper_vtables.h>
> >> +#include <drm/drm_plane.h>
> >> +#include <drm/drm_print.h>
> >> +#include <drm/drm_vblank.h>
> >> +
> >> +#include "dc-de.h"
> >> +#include "dc-drv.h"
> >> +#include "dc-kms.h"
> >> +#include "dc-pe.h"
> >> +
> >> +#define dc_crtc_dbg(crtc, fmt, ...)					\
> >> +do {									\
> >> +	typeof(crtc) _crtc = (crtc);					\
> > 
> > Use exact type instead of typeof.
> 
> Will do.
> 
> > 
> >> +	drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
> >> +		    _crtc->base.id, _crtc->name, ##__VA_ARGS__);	\
> >> +} while (0)
> >> +
> >> +#define dc_crtc_err(crtc, fmt, ...)					\
> >> +do {									\
> >> +	typeof(crtc) _crtc = (crtc);					\
> >> +	drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
> >> +		_crtc->base.id, _crtc->name, ##__VA_ARGS__);		\
> >> +} while (0)
> >> +
> >> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)				\
> >> +do {									\
> >> +	unsigned long ret;						\
> >> +	ret = wait_for_completion_timeout(&dc_crtc->c, HZ);		\
> >> +	if (ret == 0)							\
> >> +		dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",	\
> >> +							__func__);	\
> >> +} while (0)
> >> +
> >> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)					\
> >> +do {									\
> >> +	typeof(fg) _fg = (fg);						\
> >> +	if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {		\
> >> +		dc_fg_secondary_clear_channel_status(_fg);		\
> >> +		dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",		\
> >> +							__func__);	\
> >> +	}								\
> >> +} while (0)
> >> +
> >> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)			\
> >> +do {									\
> >> +	if (dc_fg_wait_for_secondary_syncup(fg))			\
> >> +		dc_crtc_err(crtc,					\
> >> +			"%s: FrameGen secondary channel isn't syncup\n",\
> >> +							__func__);	\
> >> +} while (0)
> >> +
> >> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
> >> +{
> >> +	return container_of(crtc, struct dc_crtc, base);
> >> +}
> >> +
> >> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +
> >> +	return dc_fg_get_frame_index(dc_crtc->fg);
> >> +}
> >> +
> >> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +
> >> +	enable_irq(dc_crtc->irq_dec_framecomplete);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +
> >> +	disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
> > 
> > Why is it _nosync?
> 
> Because disable_irq() can only be called from preemptible code according to
> it's kerneldoc. If I use disable_irq() here, I get this with
> CONFIG_DEBUG_ATOMIC_SLEEP enabled:

Please add a one-line comment, like "nosync because of the atomic
context"

> 
> [   50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
> [   50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0
> [   50.623527] preempt_count: 10003, expected: 0
> [   50.627888] RCU nest depth: 0, expected: 0
> [   50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407
> [   50.642092] Hardware name: Freescale i.MX8QXP MEK (DT)
> [   50.647237] Call trace:
> [   50.649687]  show_stack+0x18/0x24 (C)
> [   50.653369]  dump_stack_lvl+0x80/0xb4
> [   50.657043]  dump_stack+0x18/0x24
> [   50.660370]  __might_resched+0x114/0x170
> [   50.664306]  __might_sleep+0x48/0x98
> [   50.667894]  disable_irq+0x24/0x60
> [   50.671308]  dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm]
> [   50.676912]  drm_vblank_disable_and_save+0xc0/0x108 [drm]
> [   50.682533]  vblank_disable_fn+0x78/0x9c [drm]
> [   50.687146]  drm_handle_vblank+0x238/0x2e8 [drm]
> [   50.691932]  drm_crtc_handle_vblank+0x1c/0x28 [drm]
> [   50.696980]  dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm]
> [   50.703886]  __handle_irq_event_percpu+0x60/0x14c
> [   50.708604]  handle_irq_event+0x4c/0xac
> [   50.712443]  handle_level_irq+0xc0/0x1b0
> [   50.716379]  generic_handle_irq+0x34/0x4c
> [   50.720392]  dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm]
> [   50.725727]  generic_handle_domain_irq+0x2c/0x44
> [   50.730357]  imx_irqsteer_irq_handler+0xc0/0x1a0
> [   50.734987]  generic_handle_domain_irq+0x2c/0x44
> [   50.739609]  gic_handle_irq+0x4c/0x114
> [   50.743362]  call_on_irq_stack+0x24/0x4c
> [   50.747298]  do_interrupt_handler+0x80/0x84
> [   50.751494]  el1_interrupt+0x34/0x68
> [   50.755082]  el1h_64_irq_handler+0x18/0x24
> [   50.759191]  el1h_64_irq+0x6c/0x70
> [   50.762597]  default_idle_call+0x28/0x3c (P)
> [   50.766879]  default_idle_call+0x24/0x3c (L)
> [   50.771163]  do_idle+0x200/0x25c
> [   50.774403]  cpu_startup_entry+0x34/0x3c
> [   50.778338]  kernel_init+0x0/0x1d8
> [   50.781752]  start_kernel+0x5c4/0x70c
> [   50.785427]  __primary_switched+0x88/0x90
> 
> > 
> >> +}
> >> +
> >> +static irqreturn_t
> >> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
> >> +{
> >> +	struct dc_crtc *dc_crtc = dev_id;
> >> +	struct drm_crtc *crtc = &dc_crtc->base;
> >> +	unsigned long flags;
> >> +
> >> +	drm_crtc_handle_vblank(crtc);
> >> +
> >> +	spin_lock_irqsave(&crtc->dev->event_lock, flags);
> >> +	if (dc_crtc->event) {
> >> +		drm_crtc_send_vblank_event(crtc, dc_crtc->event);
> >> +		dc_crtc->event = NULL;
> >> +		drm_crtc_vblank_put(crtc);
> >> +	}
> >> +	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> >> +
> >> +	return IRQ_HANDLED;
> >> +}
> >> +
> >> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
> >> +{
> >> +	struct dc_crtc *dc_crtc = dev_id;
> >> +
> >> +	if (irq == dc_crtc->irq_dec_seqcomplete)
> >> +		complete(&dc_crtc->dec_seqcomplete_done);
> >> +	else if (irq == dc_crtc->irq_dec_shdld)
> >> +		complete(&dc_crtc->dec_shdld_done);
> >> +	else if (irq == dc_crtc->irq_ed_cont_shdld)
> >> +		complete(&dc_crtc->ed_cont_shdld_done);
> >> +	else if (irq == dc_crtc->irq_ed_safe_shdld)
> >> +		complete(&dc_crtc->ed_safe_shdld_done);
> > 
> > Is there any reason to have a single multiplex handler instead of having
> > 4 separate handlers, each doing one simple thing?
> 
> Just thought that one irq handler is feasible to implement the common
> handling logic. It's also ok to use 4 separate handlers with a little
> performance improvement. I may change to use 4 separate handlers by
> introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco.

Just define them one by one, there is no need for a macro.

> 
> > 
> >> +
> >> +	return IRQ_HANDLED;
> >> +}
> >> +
> >> +static const struct drm_crtc_funcs dc_crtc_funcs = {
> >> +	.reset			= drm_atomic_helper_crtc_reset,
> >> +	.destroy		= drm_crtc_cleanup,
> >> +	.set_config		= drm_atomic_helper_set_config,
> >> +	.page_flip		= drm_atomic_helper_page_flip,
> >> +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
> >> +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
> >> +	.get_vblank_counter	= dc_crtc_get_vblank_counter,
> >> +	.enable_vblank		= dc_crtc_enable_vblank,
> >> +	.disable_vblank		= dc_crtc_disable_vblank,
> >> +	.get_vblank_timestamp	= drm_crtc_vblank_helper_get_vblank_timestamp,
> >> +};
> >> +
> >> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
> >> +{
> >> +	struct drm_crtc *crtc = crtc_state->crtc;
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +
> >> +	spin_lock_irq(&crtc->dev->event_lock);
> >> +	if (crtc_state->event) {
> >> +		WARN_ON(drm_crtc_vblank_get(crtc));
> >> +		WARN_ON(dc_crtc->event);
> >> +		dc_crtc->event = crtc_state->event;
> >> +		crtc_state->event = NULL;
> >> +	}
> >> +	spin_unlock_irq(&crtc->dev->event_lock);
> >> +}
> >> +
> >> +static enum drm_mode_status
> >> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
> >> +{
> >> +	return dc_fg_check_clock(dc_crtc->fg, clk_khz);
> >> +}
> > 
> > inline
> 
> Will do.
> 
> > 
> >> +
> >> +static enum drm_mode_status
> >> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	enum drm_mode_status status;
> >> +
> >> +	status = dc_crtc_check_clock(dc_crtc, mode->clock);
> >> +	if (status != MODE_OK)
> >> +		return status;
> >> +
> >> +	if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
> >> +		return MODE_CLOCK_HIGH;
> >> +
> >> +	return MODE_OK;
> >> +}
> >> +
> >> +static int
> >> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_crtc_state *new_crtc_state =
> >> +				drm_atomic_get_new_crtc_state(state, crtc);
> >> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	enum drm_mode_status status;
> >> +
> >> +	status = dc_crtc_check_clock(dc_crtc, adj->clock);
> >> +	if (status != MODE_OK)
> >> +		return -EINVAL;
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static void
> >> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_crtc_state *new_crtc_state =
> >> +				drm_atomic_get_new_crtc_state(state, crtc);
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	int idx, ret;
> >> +
> >> +	if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> >> +	    !new_crtc_state->active)
> >> +		return;
> >> +
> >> +	if (!drm_dev_enter(crtc->dev, &idx))
> >> +		return;
> >> +
> >> +	/* request pixel engine power-on when CRTC starts to be active */
> >> +	ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
> >> +	if (ret)
> >> +		dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
> >> +			    ret);
> >> +
> >> +	drm_dev_exit(idx);
> >> +}
> >> +
> >> +static void
> >> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_crtc_state *old_crtc_state =
> >> +				drm_atomic_get_old_crtc_state(state, crtc);
> >> +	struct drm_crtc_state *new_crtc_state =
> >> +				drm_atomic_get_new_crtc_state(state, crtc);
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	int idx;
> >> +
> >> +	if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> >> +	    (!old_crtc_state->active && !new_crtc_state->active))
> >> +		return;
> >> +
> >> +	if (!drm_dev_enter(crtc->dev, &idx))
> >> +		goto out;
> >> +
> >> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
> >> +
> >> +	/* flush plane update out to display */
> >> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> >> +
> >> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> >> +
> >> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
> >> +
> >> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> >> +
> >> +	drm_dev_exit(idx);
> >> +
> >> +out:
> >> +	dc_crtc_queue_state_event(new_crtc_state);
> >> +}
> >> +
> >> +static void
> >> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_crtc_state *new_crtc_state =
> >> +				drm_atomic_get_new_crtc_state(state, crtc);
> >> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	enum dc_link_id cf_link;
> >> +	int idx, ret;
> >> +
> >> +	dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
> >> +
> >> +	drm_crtc_vblank_on(crtc);
> >> +
> >> +	if (!drm_dev_enter(crtc->dev, &idx))
> >> +		goto out;
> >> +
> >> +	/* request display engine power-on when CRTC is enabled */
> >> +	ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
> >> +	if (ret < 0)
> >> +		dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
> >> +			    ret);
> >> +
> >> +	enable_irq(dc_crtc->irq_dec_shdld);
> >> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
> >> +	enable_irq(dc_crtc->irq_ed_safe_shdld);
> >> +
> >> +	dc_fg_cfg_videomode(dc_crtc->fg, adj);
> >> +
> >> +	dc_cf_framedimensions(dc_crtc->cf_cont,
> >> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
> >> +	dc_cf_framedimensions(dc_crtc->cf_safe,
> >> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
> >> +
> >> +	/* constframe in safety stream shows blue frame */
> >> +	dc_cf_constantcolor_blue(dc_crtc->cf_safe);
> >> +	cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
> >> +	dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
> >> +
> >> +	/* show CRTC background if no plane is enabled */
> >> +	if (new_crtc_state->plane_mask == 0) {
> >> +		/* constframe in content stream shows black frame */
> >> +		dc_cf_constantcolor_black(dc_crtc->cf_cont);
> >> +
> >> +		cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
> >> +		dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
> >> +	}
> >> +
> >> +	dc_fg_enable_clock(dc_crtc->fg);
> >> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> >> +	dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
> >> +	dc_fg_shdtokgen(dc_crtc->fg);
> >> +	dc_fg_enable(dc_crtc->fg);
> >> +
> >> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
> >> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> >> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
> >> +
> >> +	disable_irq(dc_crtc->irq_ed_safe_shdld);
> >> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
> >> +	disable_irq(dc_crtc->irq_dec_shdld);
> >> +
> >> +	DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
> >> +
> >> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> >> +
> >> +	drm_dev_exit(idx);
> >> +
> >> +out:
> >> +	dc_crtc_queue_state_event(new_crtc_state);
> >> +}
> >> +
> >> +static void
> >> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_crtc_state *new_crtc_state =
> >> +				drm_atomic_get_new_crtc_state(state, crtc);
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	int idx, ret;
> >> +
> >> +	if (!drm_dev_enter(crtc->dev, &idx))
> >> +		goto out;
> >> +
> >> +	enable_irq(dc_crtc->irq_dec_seqcomplete);
> >> +	dc_fg_disable(dc_crtc->fg);
> >> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
> >> +	disable_irq(dc_crtc->irq_dec_seqcomplete);
> >> +
> >> +	dc_fg_disable_clock(dc_crtc->fg);
> >> +
> >> +	/* request pixel engine power-off as plane is off too */
> >> +	ret = pm_runtime_put(dc_crtc->pe->dev);
> >> +	if (ret)
> >> +		dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> >> +			    ret);
> >> +
> >> +	/* request display engine power-off when CRTC is disabled */
> >> +	ret = pm_runtime_put(dc_crtc->de->dev);
> > 
> > Can this be expressed as a devlink between PE and DE?
> 
> Looking at struct dc_{pe,de}, PE and DE are not dependent with each other,
> i.e., no consumer/supplier relationship(note that blit engine in PE can work
> by itself without DE) between them. So, it doesn't look right to link the two
> devices.

Ack

> 
> > 
> >> +	if (ret < 0)
> >> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> >> +			    ret);
> >> +
> >> +	drm_dev_exit(idx);
> >> +
> >> +out:
> >> +	drm_crtc_vblank_off(crtc);
> >> +
> >> +	spin_lock_irq(&crtc->dev->event_lock);
> >> +	if (new_crtc_state->event && !new_crtc_state->active) {
> >> +		drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
> >> +		new_crtc_state->event = NULL;
> >> +	}
> >> +	spin_unlock_irq(&crtc->dev->event_lock);
> >> +}
> >> +
> >> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	int ret;
> >> +
> >> +	if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
> >> +		return;
> >> +
> >> +	dc_fg_disable_clock(dc_crtc->fg);
> >> +
> >> +	if (pm_runtime_active(dc_crtc->pe->dev)) {
> >> +		ret = pm_runtime_put_sync(dc_crtc->pe->dev);
> >> +		if (ret)
> >> +			dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> >> +				    ret);
> >> +	}
> >> +
> >> +	ret = pm_runtime_put_sync(dc_crtc->de->dev);
> >> +	if (ret < 0)
> >> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> >> +			    ret);
> >> +}
> >> +
> >> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
> >> +					 bool in_vblank_irq,
> >> +					 int *vpos, int *hpos,
> >> +					 ktime_t *stime, ktime_t *etime,
> >> +					 const struct drm_display_mode *mode)
> >> +{
> >> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >> +	int vdisplay = mode->crtc_vdisplay;
> >> +	int vtotal = mode->crtc_vtotal;
> >> +	bool reliable;
> >> +	int line;
> >> +	int idx;
> >> +
> >> +	if (stime)
> >> +		*stime = ktime_get();
> >> +
> >> +	if (!drm_dev_enter(crtc->dev, &idx)) {
> >> +		reliable = false;
> >> +		*vpos = 0;
> >> +		*hpos = 0;
> >> +		goto out;
> >> +	}
> >> +
> >> +	/* line index starts with 0 for the first active output line */
> >> +	line = dc_fg_get_line_index(dc_crtc->fg);
> >> +
> >> +	if (line < vdisplay)
> >> +		/* active scanout area - positive */
> >> +		*vpos = line + 1;
> >> +	else
> >> +		/* inside vblank - negative */
> >> +		*vpos = line - (vtotal - 1);
> >> +
> >> +	*hpos = 0;
> >> +
> >> +	reliable = true;
> >> +
> >> +	drm_dev_exit(idx);
> >> +out:
> >> +	if (etime)
> >> +		*etime = ktime_get();
> >> +
> >> +	return reliable;
> >> +}
> >> +
> >> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
> >> +	.mode_valid		= dc_crtc_mode_valid,
> >> +	.atomic_check		= dc_crtc_atomic_check,
> >> +	.atomic_begin		= dc_crtc_atomic_begin,
> >> +	.atomic_flush		= dc_crtc_atomic_flush,
> >> +	.atomic_enable		= dc_crtc_atomic_enable,
> >> +	.atomic_disable		= dc_crtc_atomic_disable,
> >> +	.get_scanout_position	= dc_crtc_get_scanout_position,
> >> +};
> >> +
> >> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
> >> +{
> >> +	struct {
> >> +		struct device *dev;
> >> +		unsigned int irq;
> >> +		irqreturn_t (*irq_handler)(int irq, void *dev_id);
> >> +	} irqs[DC_CRTC_IRQS] = {
> >> +		{
> >> +			dc_crtc->de->dev,
> >> +			dc_crtc->irq_dec_framecomplete,
> >> +			dc_crtc_dec_framecomplete_irq_handler,
> >> +		}, {
> >> +			dc_crtc->de->dev,
> >> +			dc_crtc->irq_dec_seqcomplete,
> >> +			dc_crtc_common_irq_handler,
> >> +		}, {
> >> +			dc_crtc->de->dev,
> >> +			dc_crtc->irq_dec_shdld,
> >> +			dc_crtc_common_irq_handler,
> >> +		}, {
> >> +			dc_crtc->ed_cont->dev,
> >> +			dc_crtc->irq_ed_cont_shdld,
> >> +			dc_crtc_common_irq_handler,
> >> +		}, {
> >> +			dc_crtc->ed_safe->dev,
> >> +			dc_crtc->irq_ed_safe_shdld,
> >> +			dc_crtc_common_irq_handler,
> >> +		},
> >> +	};
> >> +	int i, ret;
> >> +
> >> +	for (i = 0; i < DC_CRTC_IRQS; i++) {
> >> +		struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
> >> +
> >> +		ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
> >> +				       irqs[i].irq_handler, IRQF_NO_AUTOEN,
> >> +				       dev_name(irqs[i].dev), dc_crtc);
> >> +		if (ret) {
> >> +			dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
> >> +				irqs[i].irq, ret);
> >> +			return ret;
> >> +		}
> >> +
> >> +		irq->dc_crtc = dc_crtc;
> >> +		irq->irq = irqs[i].irq;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
> >> +{
> >> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +	struct dc_de *de = dc_drm->de[crtc_index];
> >> +	struct dc_pe *pe = dc_drm->pe;
> >> +	struct dc_plane *dc_primary;
> >> +	int ret;
> >> +
> >> +	dc_crtc->de = de;
> >> +	dc_crtc->pe = pe;
> >> +
> >> +	init_completion(&dc_crtc->dec_seqcomplete_done);
> >> +	init_completion(&dc_crtc->dec_shdld_done);
> >> +	init_completion(&dc_crtc->ed_cont_shdld_done);
> >> +	init_completion(&dc_crtc->ed_safe_shdld_done);
> >> +
> >> +	dc_crtc->cf_cont = pe->cf_cont[crtc_index];
> >> +	dc_crtc->cf_safe = pe->cf_safe[crtc_index];
> >> +	dc_crtc->ed_cont = pe->ed_cont[crtc_index];
> >> +	dc_crtc->ed_safe = pe->ed_safe[crtc_index];
> >> +	dc_crtc->fg = de->fg;
> >> +
> >> +	dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
> >> +	dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
> >> +	dc_crtc->irq_dec_shdld = de->irq_shdld;
> >> +	dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
> >> +	dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
> >> +
> >> +	dc_primary = &dc_drm->dc_primary[crtc_index];
> >> +	ret = dc_plane_init(dc_drm, dc_primary);
> >> +	if (ret) {
> >> +		dev_err(drm->dev,
> >> +			"failed to init primary plane for display engine%u: %d\n",
> >> +			de->id, ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
> >> +
> >> +	ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
> >> +					NULL, &dc_crtc_funcs, NULL);
> >> +	if (ret)
> >> +		dev_err(drm->dev,
> >> +			"failed to add CRTC for display engine%u: %d\n",
> >> +			de->id, ret);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
> >> +{
> >> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +
> >> +	return dc_crtc_request_irqs(drm, dc_crtc);
> >> +}
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
> >> index 17a44362118e..8a7b6c03a222 100644
> >> --- a/drivers/gpu/drm/imx/dc/dc-de.h
> >> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
> >> @@ -13,6 +13,9 @@
> >>  
> >>  #define DC_DISPLAYS	2
> >>  
> >> +#define DC_FRAMEGEN_MAX_FRAME_INDEX	0x3ffff
> >> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ	300000
> >> +
> >>  struct dc_fg {
> >>  	struct device *dev;
> >>  	struct regmap *reg;
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
> >> index fd68861f770a..1e4b8afa3eec 100644
> >> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
> >> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
> >> @@ -3,11 +3,254 @@
> >>   * Copyright 2024 NXP
> >>   */
> >>  
> >> +#include <linux/clk.h>
> >> +#include <linux/component.h>
> >> +#include <linux/device.h>
> >> +#include <linux/dma-mapping.h>
> >> +#include <linux/mod_devicetable.h>
> >>  #include <linux/module.h>
> >> +#include <linux/of.h>
> >> +#include <linux/of_platform.h>
> >>  #include <linux/platform_device.h>
> >> +#include <linux/pm.h>
> >> +#include <linux/pm_runtime.h>
> >>  
> >> +#include <drm/drm_atomic_helper.h>
> >> +#include <drm/drm_client_setup.h>
> >> +#include <drm/drm_crtc.h>
> >> +#include <drm/drm_drv.h>
> >> +#include <drm/drm_fbdev_dma.h>
> >> +#include <drm/drm_fourcc.h>
> >> +#include <drm/drm_gem_dma_helper.h>
> >> +#include <drm/drm_managed.h>
> >> +#include <drm/drm_modeset_helper.h>
> >> +#include <drm/drm_of.h>
> >> +
> >> +#include "dc-de.h"
> >>  #include "dc-drv.h"
> >>  
> >> +struct dc_priv {
> >> +	struct drm_device *drm;
> >> +	struct clk *clk_cfg;
> >> +};
> >> +
> >> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
> >> +
> >> +static struct drm_driver dc_drm_driver = {
> >> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> >> +	DRM_GEM_DMA_DRIVER_OPS,
> >> +	DRM_FBDEV_DMA_DRIVER_OPS,
> >> +	.fops = &dc_drm_driver_fops,
> >> +	.name = "imx8-dc",
> >> +	.desc = "i.MX8 DC DRM graphics",
> >> +	.date = "20240530",
> >> +	.major = 1,
> >> +	.minor = 0,
> >> +	.patchlevel = 0,
> >> +};
> >> +
> >> +static void
> >> +dc_add_components(struct device *dev, struct component_match **matchptr)
> >> +{
> >> +	struct device_node *child, *grandchild;
> >> +
> >> +	for_each_available_child_of_node(dev->of_node, child) {
> >> +		/* The interrupt controller is not a component. */
> >> +		if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
> >> +			continue;
> >> +
> >> +		drm_of_component_match_add(dev, matchptr, component_compare_of,
> >> +					   child);
> >> +
> >> +		for_each_available_child_of_node(child, grandchild)
> >> +			drm_of_component_match_add(dev, matchptr,
> >> +						   component_compare_of,
> >> +						   grandchild);
> >> +	}
> >> +}
> >> +
> >> +static void dc_drm_component_unbind_all(void *ptr)
> >> +{
> >> +	struct dc_drm_device *dc_drm = ptr;
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +
> >> +	component_unbind_all(drm->dev, dc_drm);
> >> +}
> >> +
> >> +static int dc_drm_bind(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +	struct dc_drm_device *dc_drm;
> >> +	struct drm_device *drm;
> >> +	int ret;
> >> +
> >> +	dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
> >> +				    base);
> >> +	if (IS_ERR(dc_drm))
> >> +		return PTR_ERR(dc_drm);
> >> +
> >> +	drm = &dc_drm->base;
> >> +
> >> +	ret = component_bind_all(dev, dc_drm);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
> >> +				       dc_drm);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = dc_kms_init(dc_drm);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = drm_dev_register(drm, 0);
> >> +	if (ret) {
> >> +		dev_err(dev, "failed to register drm device: %d\n", ret);
> >> +		goto err;
> >> +	}
> >> +
> >> +	drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
> >> +
> >> +	priv->drm = drm;
> >> +
> >> +	return 0;
> >> +
> >> +err:
> >> +	dc_kms_uninit(dc_drm);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static void dc_drm_unbind(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +	struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +	struct drm_crtc *crtc;
> >> +
> >> +	priv->drm = NULL;
> >> +	drm_dev_unplug(drm);
> >> +	dc_kms_uninit(dc_drm);
> >> +	drm_atomic_helper_shutdown(drm);
> >> +
> >> +	drm_for_each_crtc(crtc, drm)
> >> +		dc_crtc_disable_at_unbind(crtc);
> > 
> > There should be no need for that. drm_atomic_helper_shutdown() should
> > disable all the CRTCs.
> 
> In case DRM device is unplugged, drm_atomic_helper_shutdown does not
> effectively disable the CRTCs due to the bypassed logics wrapped by
> drm_dev_{enter,exit}.  That's why dc_crtc_disable_at_unbind() is called
> here to effectively disable the CRTCs.

I see. I haven't faced drm_dev_unplug() earlier. I checked, the "not
disabled" behaviour is documented and all other drivers don't perform
any kind of cleanup afterwards. Thus I think it's safe to drop the
dc_crtc_disable_at_unbind() unless it causes any kind of HW issues.

> 
> > 
> > Also, who is going to do drm_dev_unregister()? I don't see it in the
> > code.
> 
> drm_dev_unplug() right above calls drm_dev_unregister().
> 
> > 
> >> +}
> >> +
> >> +static const struct component_master_ops dc_drm_ops = {
> >> +	.bind = dc_drm_bind,
> >> +	.unbind = dc_drm_unbind,
> >> +};
> >> +
> >> +static int dc_probe(struct platform_device *pdev)
> >> +{
> >> +	struct component_match *match = NULL;
> >> +	struct dc_priv *priv;
> >> +	int ret;
> >> +
> >> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> >> +	if (!priv)
> >> +		return -ENOMEM;
> >> +
> >> +	priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
> >> +	if (IS_ERR(priv->clk_cfg))
> >> +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
> >> +				     "failed to get cfg clock\n");
> >> +
> >> +	dev_set_drvdata(&pdev->dev, priv);
> >> +
> >> +	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = devm_pm_runtime_enable(&pdev->dev);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = devm_of_platform_populate(&pdev->dev);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	dc_add_components(&pdev->dev, &match);
> >> +
> >> +	ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
> >> +	if (ret)
> >> +		return dev_err_probe(&pdev->dev, ret,
> >> +				     "failed to add component master\n");
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static void dc_remove(struct platform_device *pdev)
> >> +{
> >> +	component_master_del(&pdev->dev, &dc_drm_ops);
> >> +}
> >> +
> >> +static int dc_runtime_suspend(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +
> >> +	clk_disable_unprepare(priv->clk_cfg);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int dc_runtime_resume(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +	int ret;
> >> +
> >> +	ret = clk_prepare_enable(priv->clk_cfg);
> >> +	if (ret)
> >> +		dev_err(dev, "failed to enable cfg clock: %d\n", ret);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +static int dc_suspend(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +
> >> +	return drm_mode_config_helper_suspend(priv->drm);
> >> +}
> >> +
> >> +static int dc_resume(struct device *dev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(dev);
> >> +
> >> +	return drm_mode_config_helper_resume(priv->drm);
> >> +}
> >> +
> >> +static void dc_shutdown(struct platform_device *pdev)
> >> +{
> >> +	struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
> >> +
> >> +	drm_atomic_helper_shutdown(priv->drm);
> >> +}
> >> +
> >> +static const struct dev_pm_ops dc_pm_ops = {
> >> +	RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
> >> +	SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
> >> +};
> >> +
> >> +static const struct of_device_id dc_dt_ids[] = {
> >> +	{ .compatible = "fsl,imx8qxp-dc", },
> >> +	{ /* sentinel */ }
> >> +};
> >> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
> >> +
> >> +static struct platform_driver dc_driver = {
> >> +	.probe = dc_probe,
> >> +	.remove = dc_remove,
> >> +	.shutdown = dc_shutdown,
> >> +	.driver = {
> >> +		.name = "imx8-dc",
> >> +		.of_match_table	= dc_dt_ids,
> >> +		.pm = pm_sleep_ptr(&dc_pm_ops),
> >> +	},
> >> +};
> >> +
> >>  static struct platform_driver * const dc_drivers[] = {
> >>  	&dc_cf_driver,
> >>  	&dc_de_driver,
> >> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
> >>  	&dc_lb_driver,
> >>  	&dc_pe_driver,
> >>  	&dc_tc_driver,
> >> +	&dc_driver,
> >>  };
> >>  
> >>  static int __init dc_drm_init(void)
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
> >> index 3b11f4862c6c..39a771a13933 100644
> >> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
> >> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
> >> @@ -6,19 +6,38 @@
> >>  #ifndef __DC_DRV_H__
> >>  #define __DC_DRV_H__
> >>  
> >> +#include <linux/container_of.h>
> >>  #include <linux/platform_device.h>
> >>  
> >>  #include <drm/drm_device.h>
> >> +#include <drm/drm_encoder.h>
> >>  
> >>  #include "dc-de.h"
> >> +#include "dc-kms.h"
> >>  #include "dc-pe.h"
> >>  
> >>  struct dc_drm_device {
> >>  	struct drm_device base;
> >> +	struct dc_crtc dc_crtc[DC_DISPLAYS];
> >> +	struct dc_plane dc_primary[DC_DISPLAYS];
> >> +	struct drm_encoder encoder[DC_DISPLAYS];
> >>  	struct dc_de *de[DC_DISPLAYS];
> >>  	struct dc_pe *pe;
> >>  };
> >>  
> >> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
> >> +{
> >> +	return container_of(drm, struct dc_drm_device, base);
> >> +}
> >> +
> >> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
> >> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
> >> +
> >> +int dc_kms_init(struct dc_drm_device *dc_drm);
> >> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
> >> +
> >> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
> >> +
> >>  extern struct platform_driver dc_cf_driver;
> >>  extern struct platform_driver dc_ed_driver;
> >>  extern struct platform_driver dc_de_driver;
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
> >> new file mode 100644
> >> index 000000000000..2b18aa37a4a8
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
> >> @@ -0,0 +1,143 @@
> >> +// SPDX-License-Identifier: GPL-2.0+
> >> +/*
> >> + * Copyright 2024 NXP
> >> + */
> >> +
> >> +#include <linux/of.h>
> >> +#include <linux/of_graph.h>
> >> +
> >> +#include <drm/drm_atomic_helper.h>
> >> +#include <drm/drm_bridge.h>
> >> +#include <drm/drm_bridge_connector.h>
> >> +#include <drm/drm_connector.h>
> >> +#include <drm/drm_crtc.h>
> >> +#include <drm/drm_device.h>
> >> +#include <drm/drm_encoder.h>
> >> +#include <drm/drm_gem_framebuffer_helper.h>
> >> +#include <drm/drm_mode_config.h>
> >> +#include <drm/drm_print.h>
> >> +#include <drm/drm_probe_helper.h>
> >> +#include <drm/drm_simple_kms_helper.h>
> >> +#include <drm/drm_vblank.h>
> >> +
> >> +#include "dc-de.h"
> >> +#include "dc-drv.h"
> >> +#include "dc-kms.h"
> >> +
> >> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
> >> +	.fb_create = drm_gem_fb_create,
> >> +	.atomic_check = drm_atomic_helper_check,
> >> +	.atomic_commit = drm_atomic_helper_commit,
> >> +};
> >> +
> >> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
> >> +					int crtc_index)
> >> +{
> >> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +	struct drm_crtc *crtc = &dc_crtc->base;
> >> +	struct drm_connector *connector;
> >> +	struct device *dev = drm->dev;
> >> +	struct drm_encoder *encoder;
> >> +	struct drm_bridge *bridge;
> >> +	int ret;
> >> +
> >> +	bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
> >> +					0, 0);
> >> +	if (IS_ERR(bridge)) {
> >> +		ret = PTR_ERR(bridge);
> >> +		if (ret == -ENODEV)
> >> +			return 0;
> >> +
> >> +		return dev_err_probe(dev, ret,
> >> +				     "failed to find bridge for CRTC%u\n",
> >> +				     crtc->index);
> >> +	}
> >> +
> >> +	encoder = &dc_drm->encoder[crtc_index];
> >> +	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
> >> +	if (ret) {
> >> +		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
> >> +			crtc->index, ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	encoder->possible_crtcs = drm_crtc_mask(crtc);
> >> +
> >> +	ret = drm_bridge_attach(encoder, bridge, NULL,
> >> +				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> >> +	if (ret) {
> >> +		dev_err(dev,
> >> +			"failed to attach bridge to encoder for CRTC%u: %d\n",
> >> +			crtc->index, ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	connector = drm_bridge_connector_init(drm, encoder);
> >> +	if (IS_ERR(connector)) {
> >> +		ret = PTR_ERR(connector);
> >> +		dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
> >> +			crtc->index, ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	ret = drm_connector_attach_encoder(connector, encoder);
> >> +	if (ret)
> >> +		dev_err(dev,
> >> +			"failed to attach encoder to connector for CRTC%u: %d\n",
> >> +			crtc->index, ret);
> >> +
> >> +	return ret;
> >> +}
> >> +
> >> +int dc_kms_init(struct dc_drm_device *dc_drm)
> >> +{
> >> +	struct drm_device *drm = &dc_drm->base;
> >> +	int ret, i;
> >> +
> >> +	ret = drmm_mode_config_init(drm);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	drm->mode_config.min_width = 60;
> >> +	drm->mode_config.min_height = 60;
> >> +	drm->mode_config.max_width = 8192;
> >> +	drm->mode_config.max_height = 8192;
> >> +	drm->mode_config.funcs = &dc_drm_mode_config_funcs;
> >> +
> >> +	drm->vblank_disable_immediate = true;
> >> +	drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
> >> +
> >> +	for (i = 0; i < DC_DISPLAYS; i++) {
> >> +		ret = dc_crtc_init(dc_drm, i);
> >> +		if (ret)
> >> +			return ret;
> >> +
> >> +		ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
> >> +		if (ret)
> >> +			return ret;
> >> +	}
> >> +
> >> +	for (i = 0; i < DC_DISPLAYS; i++) {
> >> +		ret = dc_crtc_post_init(dc_drm, i);
> > 
> > Can you use .late_register for this?
> 
> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
> additional userspace interfaces like debugfs interfaces. And, it seems that
> everyone implementing this uses it to add debugfs interfaces. So, it will
> kind of abuse it to do CRTC post initialization.

Why can't they be requested earlier then?

> 
> > 
> >> +		if (ret)
> >> +			return ret;
> >> +	}
> >> +
> >> +	ret = drm_vblank_init(drm, DC_DISPLAYS);
> >> +	if (ret) {
> >> +		dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	drm_mode_config_reset(drm);
> >> +
> >> +	drm_kms_helper_poll_init(drm);
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
> >> +{
> >> +	drm_kms_helper_poll_fini(&dc_drm->base);
> >> +}
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
> >> new file mode 100644
> >> index 000000000000..57f6e0c15f57
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
> >> @@ -0,0 +1,58 @@
> >> +/* SPDX-License-Identifier: GPL-2.0+ */
> >> +/*
> >> + * Copyright 2024 NXP
> >> + */
> >> +
> >> +#ifndef __DC_KMS_H__
> >> +#define __DC_KMS_H__
> >> +
> >> +#include <linux/completion.h>
> >> +
> >> +#include <drm/drm_crtc.h>
> >> +#include <drm/drm_plane.h>
> >> +#include <drm/drm_vblank.h>
> >> +
> >> +#include "dc-de.h"
> >> +#include "dc-fu.h"
> >> +#include "dc-pe.h"
> >> +
> >> +#define DC_CRTC_IRQS	5
> >> +
> >> +struct dc_crtc_irq {
> >> +	struct dc_crtc *dc_crtc;
> >> +	unsigned int irq;
> >> +};
> >> +
> > 
> > Please provide some documentation for the structure in the form of the
> > kerneldoc. E.g. what is the difference between ed_cont and ed_safe?
> 
> Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the
> difference between ed_cont(content stream) and ed_safe(safety stream).
> 
> > The de and fg pointers are global, please don't cache them
> > unnecessarily.
> 
> Global? I don't catch your meaning, sorry. To me, it's handy to access de
> and fg via the two pointers in struct dc_crtc.

I had to spend some time understanding if they are per-CRTC or if there
are a single instances of those subdevices. Thus I suggest to access
them through dc_drm_device. You can ignore this suggestion though.

> 
> > 
> >> +struct dc_crtc {
> >> +	struct drm_crtc base;
> >> +	struct dc_de *de;
> >> +	struct dc_pe *pe;
> >> +	struct dc_cf *cf_cont;
> >> +	struct dc_cf *cf_safe;
> >> +	struct dc_ed *ed_cont;
> >> +	struct dc_ed *ed_safe;
> >> +	struct dc_fg *fg;
> >> +	unsigned int irq_dec_framecomplete;
> >> +	unsigned int irq_dec_seqcomplete;
> >> +	unsigned int irq_dec_shdld;
> >> +	unsigned int irq_ed_cont_shdld;
> >> +	unsigned int irq_ed_safe_shdld;
> >> +	struct completion dec_seqcomplete_done;
> >> +	struct completion dec_shdld_done;
> >> +	struct completion ed_safe_shdld_done;
> >> +	struct completion ed_cont_shdld_done;
> >> +	struct drm_pending_vblank_event *event;
> >> +	struct dc_crtc_irq irqs[DC_CRTC_IRQS];
> >> +};
> >> +
> >> +struct dc_plane {
> >> +	struct drm_plane base;
> >> +	struct dc_fu *fu;
> >> +	struct dc_cf *cf;
> >> +	struct dc_lb *lb;
> >> +	struct dc_ed *ed;
> >> +};
> >> +
> >> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
> >> +
> >> +#endif /* __DC_KMS_H__ */
> >> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
> >> new file mode 100644
> >> index 000000000000..78d0d2cd3451
> >> --- /dev/null
> >> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
> >> @@ -0,0 +1,241 @@
> >> +// SPDX-License-Identifier: GPL-2.0+
> >> +/*
> >> + * Copyright 2024 NXP
> >> + */
> >> +
> >> +#include <linux/container_of.h>
> >> +
> >> +#include <drm/drm_atomic.h>
> >> +#include <drm/drm_atomic_helper.h>
> >> +#include <drm/drm_atomic_state_helper.h>
> >> +#include <drm/drm_crtc.h>
> >> +#include <drm/drm_drv.h>
> >> +#include <drm/drm_fb_dma_helper.h>
> >> +#include <drm/drm_fourcc.h>
> >> +#include <drm/drm_framebuffer.h>
> >> +#include <drm/drm_gem_atomic_helper.h>
> >> +#include <drm/drm_plane_helper.h>
> >> +#include <drm/drm_print.h>
> >> +
> >> +#include "dc-drv.h"
> >> +#include "dc-fu.h"
> >> +#include "dc-kms.h"
> >> +
> >> +#define DC_PLANE_MAX_PITCH	0x10000
> >> +#define DC_PLANE_MAX_PIX_CNT	8192
> >> +
> >> +#define dc_plane_dbg(plane, fmt, ...)					\
> >> +do {									\
> >> +	typeof(plane) _plane = (plane);					\
> >> +	drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,			\
> >> +		    _plane->base.id, _plane->name, ##__VA_ARGS__);	\
> >> +} while (0)
> >> +
> >> +static const uint32_t dc_plane_formats[] = {
> >> +	DRM_FORMAT_XRGB8888,
> >> +};
> >> +
> >> +static const struct drm_plane_funcs dc_plane_funcs = {
> >> +	.update_plane		= drm_atomic_helper_update_plane,
> >> +	.disable_plane		= drm_atomic_helper_disable_plane,
> >> +	.destroy		= drm_plane_cleanup,
> >> +	.reset			= drm_atomic_helper_plane_reset,
> >> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
> >> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
> >> +};
> >> +
> >> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
> >> +{
> >> +	return container_of(plane, struct dc_plane, base);
> >> +}
> >> +
> >> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
> >> +					struct drm_crtc_state *crtc_state)
> >> +{
> >> +	if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
> >> +	    state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
> >> +	    state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
> >> +		dc_plane_dbg(state->plane, "no off screen\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
> >> +{
> >> +	int src_h = drm_rect_height(&state->src) >> 16;
> >> +	int src_w = drm_rect_width(&state->src) >> 16;
> >> +
> >> +	if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
> >> +		dc_plane_dbg(state->plane, "invalid source resolution\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int dc_plane_check_fb(struct drm_plane_state *state)
> >> +{
> >> +	struct drm_framebuffer *fb = state->fb;
> >> +	dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
> >> +
> >> +	/* base address alignment */
> >> +	if (baseaddr & 0x3) {
> >> +		dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	/* pitches[0] range */
> >> +	if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
> >> +		dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	/* pitches[0] alignment */
> >> +	if (fb->pitches[0] & 0x3) {
> >> +		dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static int
> >> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_plane_state *plane_state =
> >> +				drm_atomic_get_new_plane_state(state, plane);
> >> +	struct drm_crtc_state *crtc_state;
> >> +	int ret;
> >> +
> >> +	/* ok to disable */
> >> +	if (!plane_state->fb)
> >> +		return 0;
> >> +
> >> +	if (!plane_state->crtc) {
> >> +		dc_plane_dbg(plane, "no CRTC in plane state\n");
> >> +		return -EINVAL;
> >> +	}
> >> +
> >> +	crtc_state =
> >> +		drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
> >> +	if (WARN_ON(!crtc_state))
> >> +		return -EINVAL;
> >> +
> >> +	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
> >> +						  DRM_PLANE_NO_SCALING,
> >> +						  DRM_PLANE_NO_SCALING,
> >> +						  true, false);
> >> +	if (ret) {
> >> +		dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
> >> +		return ret;
> >> +	}
> >> +
> >> +	ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	ret = dc_plane_check_max_source_resolution(plane_state);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	return dc_plane_check_fb(plane_state);
> >> +}
> >> +
> >> +static void
> >> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
> >> +{
> >> +	struct drm_plane_state *new_state =
> >> +				drm_atomic_get_new_plane_state(state, plane);
> >> +	struct dc_plane *dplane = to_dc_plane(plane);
> >> +	struct drm_framebuffer *fb = new_state->fb;
> >> +	const struct dc_fu_ops *fu_ops;
> >> +	struct dc_lb *lb = dplane->lb;
> >> +	struct dc_fu *fu = dplane->fu;
> >> +	dma_addr_t baseaddr;
> >> +	int src_w, src_h;
> >> +	int idx;
> >> +
> >> +	if (!drm_dev_enter(plane->dev, &idx))
> >> +		return;
> >> +
> >> +	src_w = drm_rect_width(&new_state->src) >> 16;
> >> +	src_h = drm_rect_height(&new_state->src) >> 16;
> >> +
> >> +	baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
> >> +
> >> +	fu_ops = dc_fu_get_ops(dplane->fu);
> >> +
> >> +	fu_ops->set_layerblend(fu, lb);
> >> +	fu_ops->set_burstlength(fu, baseaddr);
> >> +	fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
> >> +	fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
> >> +	fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
> >> +	fu_ops->set_framedimensions(fu, src_w, src_h);
> >> +	fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
> >> +	fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
> >> +
> >> +	dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
> >> +
> >> +	dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
> >> +	dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
> >> +	dc_lb_mode(lb, LB_BLEND);
> >> +	dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
> >> +	dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
> >> +
> >> +	dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
> >> +
> >> +	/* set ExtDst's source to LayerBlend */
> >> +	dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
> >> +
> >> +	drm_dev_exit(idx);
> >> +}
> >> +
> >> +static void dc_plane_atomic_disable(struct drm_plane *plane,
> >> +				    struct drm_atomic_state *state)
> >> +{
> >> +	struct dc_plane *dplane = to_dc_plane(plane);
> >> +	const struct dc_fu_ops *fu_ops;
> >> +	int idx;
> >> +
> >> +	if (!drm_dev_enter(plane->dev, &idx))
> >> +		return;
> >> +
> >> +	/* disable fetchunit in shadow */
> >> +	fu_ops = dc_fu_get_ops(dplane->fu);
> >> +	fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
> >> +
> >> +	/* set ExtDst's source to ConstFrame */
> >> +	dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
> >> +
> >> +	drm_dev_exit(idx);
> >> +}
> >> +
> >> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
> >> +	.atomic_check = dc_plane_atomic_check,
> >> +	.atomic_update = dc_plane_atomic_update,
> >> +	.atomic_disable = dc_plane_atomic_disable,
> >> +};
> >> +
> >> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
> >> +{
> >> +	struct drm_plane *plane = &dc_plane->base;
> >> +	int ret;
> >> +
> >> +	ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
> >> +				       dc_plane_formats,
> >> +				       ARRAY_SIZE(dc_plane_formats),
> >> +				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
> >> +	if (ret)
> >> +		return ret;
> >> +
> >> +	drm_plane_helper_add(plane, &dc_plane_helper_funcs);
> >> +
> >> +	dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
> >> +	dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
> >> +	dc_plane->lb = dc_drm->pe->lb[plane->index];
> >> +	dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
> >> +
> >> +	return 0;
> >> +}
> >> -- 
> >> 2.34.1
> >>
> > 
> 
> -- 
> Regards,
> Liu Ying

-- 
With best wishes
Dmitry


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

* Re: [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine
  2024-12-09  3:39 ` [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine Liu Ying
@ 2024-12-12  8:02   ` Maxime Ripard
  0 siblings, 0 replies; 35+ messages in thread
From: Maxime Ripard @ 2024-12-12  8:02 UTC (permalink / raw)
  To: Liu Ying
  Cc: agx, airlied, aisheng.dong, conor+dt, devicetree,
	dmitry.baryshkov, dri-devel, festevam, francesco, frank.li, imx,
	kernel, kishon, krzk+dt, linux-arm-kernel, linux-kernel,
	linux-phy, maarten.lankhorst, mripard, p.zabel, robh, s.hauer,
	shawnguo, simona, tglx, tzimmermann, u.kleine-koenig, vkoul,
	Maxime Ripard

On Mon, 9 Dec 2024 11:39:13 +0800, Liu Ying wrote:
> i.MX8qxp Display Controller display engine consists of all processing
> units that operate in a display clock domain.  Add minimal feature
> support with FrameGen and TCon so that the engine can output display
> timings.  The FrameGen driver, TCon driver and display engine driver
> are components to be aggregated by a master registered in the upcoming
> 
> [ ... ]

Reviewed-by: Maxime Ripard <mripard@kernel.org>

Thanks!
Maxime


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

* Re: [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine
  2024-12-09  3:39 ` [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
@ 2024-12-12  8:02   ` Maxime Ripard
  0 siblings, 0 replies; 35+ messages in thread
From: Maxime Ripard @ 2024-12-12  8:02 UTC (permalink / raw)
  To: Liu Ying
  Cc: agx, airlied, aisheng.dong, conor+dt, devicetree,
	dmitry.baryshkov, dri-devel, festevam, francesco, frank.li, imx,
	kernel, kishon, krzk+dt, linux-arm-kernel, linux-kernel,
	linux-phy, maarten.lankhorst, mripard, p.zabel, robh, s.hauer,
	shawnguo, simona, tglx, tzimmermann, u.kleine-koenig, vkoul,
	Maxime Ripard

On Mon, 9 Dec 2024 11:39:14 +0800, Liu Ying wrote:
> i.MX8qxp Display Controller pixel engine consists of all processing
> units that operate in the AXI bus clock domain.  Add drivers for
> ConstFrame, ExtDst, FetchLayer, FetchWarp and LayerBlend units, as
> well as a pixel engine driver, so that two displays with primary
> planes can be supported.  The pixel engine driver and those unit
> 
> [ ... ]

Reviewed-by: Maxime Ripard <mripard@kernel.org>

Thanks!
Maxime


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

* Re: [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller
  2024-12-09  3:39 ` [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller Liu Ying
@ 2024-12-12  8:02   ` Maxime Ripard
  0 siblings, 0 replies; 35+ messages in thread
From: Maxime Ripard @ 2024-12-12  8:02 UTC (permalink / raw)
  To: Liu Ying
  Cc: agx, airlied, aisheng.dong, conor+dt, devicetree,
	dmitry.baryshkov, dri-devel, festevam, francesco, frank.li, imx,
	kernel, kishon, krzk+dt, linux-arm-kernel, linux-kernel,
	linux-phy, maarten.lankhorst, mripard, p.zabel, robh, s.hauer,
	shawnguo, simona, tglx, tzimmermann, u.kleine-koenig, vkoul,
	Maxime Ripard

On Mon, 9 Dec 2024 11:39:15 +0800, Liu Ying wrote:
> i.MX8qxp Display Controller has a built-in interrupt controller to support
> Enable/Status/Preset/Clear interrupt bit.  Add driver for it.
> 
> Signed-off-by: Liu Ying <victor.liu@nxp.com>

Reviewed-by: Maxime Ripard <mripard@kernel.org>

Thanks!
Maxime


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-09  3:39 ` [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS Liu Ying
  2024-12-09 16:17   ` Dmitry Baryshkov
@ 2024-12-12  8:02   ` Maxime Ripard
  1 sibling, 0 replies; 35+ messages in thread
From: Maxime Ripard @ 2024-12-12  8:02 UTC (permalink / raw)
  To: Liu Ying
  Cc: agx, airlied, aisheng.dong, conor+dt, devicetree,
	dmitry.baryshkov, dri-devel, festevam, francesco, frank.li, imx,
	kernel, kishon, krzk+dt, linux-arm-kernel, linux-kernel,
	linux-phy, maarten.lankhorst, mripard, p.zabel, robh, s.hauer,
	shawnguo, simona, tglx, tzimmermann, u.kleine-koenig, vkoul,
	Maxime Ripard

On Mon, 9 Dec 2024 11:39:16 +0800, Liu Ying wrote:
> i.MX8qxp Display Controller(DC) is comprised of three main components that
> include a blit engine for 2D graphics accelerations, display controller for
> display output processing, as well as a command sequencer.  Add kernel
> mode setting support for the display controller part with two CRTCs and
> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
> 
> [ ... ]

Reviewed-by: Maxime Ripard <mripard@kernel.org>

Thanks!
Maxime


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

* Re: [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units
  2024-12-11 14:55       ` Rob Herring
@ 2024-12-13  3:40         ` Liu Ying
  0 siblings, 0 replies; 35+ messages in thread
From: Liu Ying @ 2024-12-13  3:40 UTC (permalink / raw)
  To: Rob Herring
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, krzk+dt, conor+dt, shawnguo, s.hauer, kernel,
	festevam, tglx, vkoul, kishon, aisheng.dong, agx, francesco,
	frank.li, dmitry.baryshkov, u.kleine-koenig

On 12/11/2024, Rob Herring wrote:
> On Wed, Dec 11, 2024 at 11:05:52AM +0800, Liu Ying wrote:
>> On 12/11/2024, Rob Herring wrote:
>>> On Mon, Dec 09, 2024 at 11:39:05AM +0800, Liu Ying wrote:
>>>> Freescale i.MX8qxp Display Controller is implemented as construction set of
>>>> building blocks with unified concept and standardized interfaces.  Document
>>>> all existing processing units.
>>>>
>>>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
>>>> ---
>>>> v6:
>>>> * No change.
>>>>
>>>> v5:
>>>> * Document aliases for processing units which have multiple instances in
>>>>   the Display Controller.  Drop Rob's previous R-b tag. (Maxime)
>>>>
>>>> v4:
>>>> * Collect Rob's R-b tag.
>>>>
>>>> v3:
>>>> * Combine fsl,imx8qxp-dc-fetchunit-common.yaml,
>>>>   fsl,imx8qxp-dc-fetchlayer.yaml and fsl,imx8qxp-dc-fetchwarp.yaml
>>>>   into 1 schema doc fsl,imx8qxp-dc-fetchunit.yaml. (Rob)
>>>> * Document all processing units. (Rob)
>>>>
>>>> v2:
>>>> * Drop fsl,dc-*-id DT properties. (Krzysztof)
>>>> * Add port property to fsl,imx8qxp-dc-tcon.yaml. (Krzysztof)
>>>> * Fix register range sizes in examples.
>>>>
>>>>  .../display/imx/fsl,imx8qxp-dc-blitblend.yaml |  46 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-clut.yaml      |  49 ++++++
>>>>  .../imx/fsl,imx8qxp-dc-constframe.yaml        |  49 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-dither.yaml    |  49 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-extdst.yaml    |  77 +++++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-fetchunit.yaml | 147 ++++++++++++++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-filter.yaml    |  47 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-framegen.yaml  |  68 ++++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-gammacor.yaml  |  38 +++++
>>>>  .../imx/fsl,imx8qxp-dc-layerblend.yaml        |  45 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-matrix.yaml    |  48 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-rop.yaml       |  48 ++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-safety.yaml    |  34 ++++
>>>>  .../imx/fsl,imx8qxp-dc-scaling-engine.yaml    |  89 +++++++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-signature.yaml |  58 +++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-store.yaml     | 100 ++++++++++++
>>>>  .../display/imx/fsl,imx8qxp-dc-tcon.yaml      |  50 ++++++
>>>>  17 files changed, 1042 insertions(+)
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-clut.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-constframe.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-dither.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-extdst.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-fetchunit.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-filter.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-framegen.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-gammacor.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-layerblend.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-matrix.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-rop.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-safety.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-scaling-engine.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-signature.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-store.yaml
>>>>  create mode 100644 Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-tcon.yaml
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>>>> new file mode 100644
>>>> index 000000000000..7f800e72c3f3
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/display/imx/fsl,imx8qxp-dc-blitblend.yaml
>>>> @@ -0,0 +1,46 @@
>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/display/imx/fsl,imx8qxp-dc-blitblend.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Freescale i.MX8qxp Display Controller Blit Blend Unit
>>>> +
>>>> +description: |
>>>> +  Combines two input frames to a single output frame, all frames having the
>>>> +  same dimension.
>>>> +
>>>> +  Each Blit Blend Unit device should have an alias in the aliases node, in the
>>>> +  form of dc<x>-blitblend<y>, where <x> is an integer specifying the Display
>>>> +  Controller instance and <y> is an integer specifying the Blit Blend Unit
>>>> +  device instance.
>>>
>>> That's really an abuse of aliases. If you need to describe connections 
>>> between components, use the graph binding like everyone else does for 
>>> display path components.
>>
>> I need to describe components' instance numbers which imply the connections
>> between components but not vice versa. If I use the graph binding, I cannot
>> get the instance numbers(0 or 1) of the two display engines(documented by
>> fsl,imx8qxp-dc-display-engine.yaml). If you have no objections, I may add the
>> instance numbers to compatible strings, like brcm,bcm2835-pixelvalve0.yaml.
>> What do you think?
> 
> You could have dc<x> and blitblend<y> aliases and use the graph to 
> define the connections. But I'm not really a fan of adding custom 
> aliases either. Why are the instance numbers important?
> 
> Are the programming models or features of the instances different? If 
> so, then a different compatible or property describing the feature may 
> be appropriate.

The instances numbers are important mainly because the four ExtDsts(0/1/4/5)
belong to content or safety streams of the two Display Engines, plus the four
LayerBlends(0/1/2/3) in Pixel Engine have different numbers of input ports to
connect with the outputs of other LayerBlends, though the reason for LBs is
weak since the input ports can be expressed by the graph binding.

                                           CF0/1/4/5
                           PE               | | | |
                                            V V V V  primary layer cross bar
                          +------------------------------------------+
                          |                                          |
4 FUs + (VS4/5 + HS4/5) =>|               LB0/1/2/3                  |
   secondary layer        |                                          |
   cross bar              +------------------------------------------+
                             |          |              |          |
                             V          V              V          V
                          +-----+    +-----+        +-----+    +-----+
                          | ED0 |    | ED4 |        | ED5 |    | ED1 |
                          +-----+    +-----+        +-----+    +-----+
-----------------------------|----------|--------------|----------|-------------
                          content     safety        content     safety
                          stream0    stream0        stream1    stream1
                             |          |              |          |
                             |  DE0     V              V    DE1   |
                             |       +-----+        +-----+       |
                              ------>| FG0 |        | FG1 |<------
                                     +-----+        +-----+
                                        |              |
                                        V              V
                                       ...            ...

(Safety stream still is supposed to still function when content stream fails
over.)

LayerBlend primary layer selections:
static const enum dc_link_id prim_sels[] = {                                     
        /* common options */                                                     
        LINK_ID_NONE,                                                            
        LINK_ID_CONSTFRAME0,                                                     
        LINK_ID_CONSTFRAME1,                                                     
        LINK_ID_CONSTFRAME4,                                                     
        LINK_ID_CONSTFRAME5,                                                     
        /*                                                                       
         * special options:                                                      
         * layerblend(n) has n special options,                                  
         * from layerblend0 to layerblend(n - 1), e.g.,                          
         * layerblend3 has 3 special options -                                   
         * layerblend0/1/2.                                                      
         */                                                                      
        LINK_ID_LAYERBLEND0,                                                     
        LINK_ID_LAYERBLEND1,                                                     
        LINK_ID_LAYERBLEND2,                                                     
        LINK_ID_LAYERBLEND3,                                                     
};

People may argue that ED instance number is also not that important, because
content/safety stream can be inferred from FG input port numbers.  That's
true, but the connections between the internal devices are too complex and
I'm afraid it's an over-kill to use the graph binding. I list LB2 primary
layer selections and ED0 input selections here as examples:

--8<--
Selection of the source for the prim input of the layerblend2 module
0: disable      
10: blitblend9  
12: constframe0 
16: constframe1 
14: constframe4 
18: constframe5 
27: matrix4     
28: hscaler4    
29: vscaler4    
30: matrix5     
31: hscaler5    
32: vscaler5    
34: layerblend1 
33: layerblend0 
--8<--

--8<--
Selection of the source for the src input of the extdst0 module
0: disable      
10: blitblend9  
12: constframe0 
16: constframe1 
14: constframe4 
18: constframe5 
27: matrix4     
28: hscaler4    
29: vscaler4    
30: matrix5     
31: hscaler5    
32: vscaler5    
36: layerblend3 
35: layerblend2 
34: layerblend1 
33: layerblend0 
--8<--

TL;DR: I'd like to get instance numbers because of an appropriate programming
model _and_ different features of EDs and LBs(content/safety steam for EDs +
different input selections for LBs).  If no objections, I'll add instance
numbers to compatible strings in next version.

> 
> Rob

-- 
Regards,
Liu Ying


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-11 23:35       ` Dmitry Baryshkov
@ 2024-12-13  6:07         ` Liu Ying
  2024-12-13 13:28           ` Dmitry Baryshkov
  0 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-13  6:07 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On 12/12/2024, Dmitry Baryshkov wrote:
> On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote:
>> On 12/10/2024, Dmitry Baryshkov wrote:
>>> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
>>>> i.MX8qxp Display Controller(DC) is comprised of three main components that
>>>> include a blit engine for 2D graphics accelerations, display controller for
>>>> display output processing, as well as a command sequencer.  Add kernel
>>>> mode setting support for the display controller part with two CRTCs and
>>>> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
>>>> registers of the display controller are accessed without command sequencer
>>>> involved, instead just by using CPU.  The command sequencer is supposed to
>>>> be used by the blit engine.
>>>>
>>>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
>>>> ---
>>>> v6:
>>>> * No change.
>>>>
>>>> v5:
>>>> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
>>>>
>>>> v4:
>>>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
>>>>   function calls from KMS routine to initialization stage. (Dmitry)
>>>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
>>>>   appropriate .h header files or .c source files. (Dmitry)
>>>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
>>>> * Drop dc_drm->pe_rpm_count. (Dmitry)
>>>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
>>>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
>>>>   Instead, put it in struct dc_crtc.  (Dmitry)
>>>> * Call devm_request_irq() to request IRQs, instead of using drmm action.
>>>>   (Dmitry)
>>>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
>>>> * Select DRM_CLIENT_SELECTION due to rebase.
>>>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
>>>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
>>>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
>>>>   rebase.
>>>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
>>>>   register dc_drm_component_unbind_all() action.
>>>> * Request interrupts in dc_crtc_post_init() after encoder initialization to
>>>>   make sure next bridge is found first.
>>>>
>>>> v3:
>>>> * No change.
>>>>
>>>> v2:
>>>> * Find next bridge from TCon's port.
>>>> * Drop drm/drm_module.h include from dc-drv.c.
>>>>
>>>>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
>>>>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
>>>>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
>>>>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
>>>>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
>>>>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
>>>>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
>>>>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
>>>>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
>>>>  9 files changed, 1274 insertions(+), 2 deletions(-)
>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
>>>>
>>>> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
>>>> index 1fc84c7475de..415993207f2e 100644
>>>> --- a/drivers/gpu/drm/imx/dc/Kconfig
>>>> +++ b/drivers/gpu/drm/imx/dc/Kconfig
>>>> @@ -1,6 +1,11 @@
>>>>  config DRM_IMX8_DC
>>>>  	tristate "Freescale i.MX8 Display Controller Graphics"
>>>>  	depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
>>>> +	select DRM_CLIENT_SELECTION
>>>> +	select DRM_GEM_DMA_HELPER
>>>> +	select DRM_KMS_HELPER
>>>> +	select DRM_DISPLAY_HELPER
>>>> +	select DRM_BRIDGE_CONNECTOR
>>>>  	select GENERIC_IRQ_CHIP
>>>>  	select REGMAP
>>>>  	select REGMAP_MMIO
>>>> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
>>>> index 1ce3e8a8db22..b9d33c074984 100644
>>>> --- a/drivers/gpu/drm/imx/dc/Makefile
>>>> +++ b/drivers/gpu/drm/imx/dc/Makefile
>>>> @@ -1,6 +1,7 @@
>>>>  # SPDX-License-Identifier: GPL-2.0
>>>>  
>>>> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
>>>> -		    dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
>>>> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
>>>> +		    dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
>>>> +		    dc-plane.o dc-tc.o
>>>>  
>>>>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
>>>> new file mode 100644
>>>> index 000000000000..fd6daa1807d8
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
>>>> @@ -0,0 +1,558 @@
>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>> +/*
>>>> + * Copyright 2024 NXP
>>>> + */
>>>> +
>>>> +#include <linux/completion.h>
>>>> +#include <linux/container_of.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/irqreturn.h>
>>>> +#include <linux/pm_runtime.h>
>>>> +#include <linux/spinlock.h>
>>>> +
>>>> +#include <drm/drm_atomic.h>
>>>> +#include <drm/drm_atomic_helper.h>
>>>> +#include <drm/drm_atomic_state_helper.h>
>>>> +#include <drm/drm_crtc.h>
>>>> +#include <drm/drm_device.h>
>>>> +#include <drm/drm_drv.h>
>>>> +#include <drm/drm_modes.h>
>>>> +#include <drm/drm_modeset_helper_vtables.h>
>>>> +#include <drm/drm_plane.h>
>>>> +#include <drm/drm_print.h>
>>>> +#include <drm/drm_vblank.h>
>>>> +
>>>> +#include "dc-de.h"
>>>> +#include "dc-drv.h"
>>>> +#include "dc-kms.h"
>>>> +#include "dc-pe.h"
>>>> +
>>>> +#define dc_crtc_dbg(crtc, fmt, ...)					\
>>>> +do {									\
>>>> +	typeof(crtc) _crtc = (crtc);					\
>>>
>>> Use exact type instead of typeof.
>>
>> Will do.
>>
>>>
>>>> +	drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
>>>> +		    _crtc->base.id, _crtc->name, ##__VA_ARGS__);	\
>>>> +} while (0)
>>>> +
>>>> +#define dc_crtc_err(crtc, fmt, ...)					\
>>>> +do {									\
>>>> +	typeof(crtc) _crtc = (crtc);					\
>>>> +	drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,			\
>>>> +		_crtc->base.id, _crtc->name, ##__VA_ARGS__);		\
>>>> +} while (0)
>>>> +
>>>> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)				\
>>>> +do {									\
>>>> +	unsigned long ret;						\
>>>> +	ret = wait_for_completion_timeout(&dc_crtc->c, HZ);		\
>>>> +	if (ret == 0)							\
>>>> +		dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",	\
>>>> +							__func__);	\
>>>> +} while (0)
>>>> +
>>>> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)					\
>>>> +do {									\
>>>> +	typeof(fg) _fg = (fg);						\
>>>> +	if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {		\
>>>> +		dc_fg_secondary_clear_channel_status(_fg);		\
>>>> +		dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",		\
>>>> +							__func__);	\
>>>> +	}								\
>>>> +} while (0)
>>>> +
>>>> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)			\
>>>> +do {									\
>>>> +	if (dc_fg_wait_for_secondary_syncup(fg))			\
>>>> +		dc_crtc_err(crtc,					\
>>>> +			"%s: FrameGen secondary channel isn't syncup\n",\
>>>> +							__func__);	\
>>>> +} while (0)
>>>> +
>>>> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
>>>> +{
>>>> +	return container_of(crtc, struct dc_crtc, base);
>>>> +}
>>>> +
>>>> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +
>>>> +	return dc_fg_get_frame_index(dc_crtc->fg);
>>>> +}
>>>> +
>>>> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +
>>>> +	enable_irq(dc_crtc->irq_dec_framecomplete);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +
>>>> +	disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
>>>
>>> Why is it _nosync?
>>
>> Because disable_irq() can only be called from preemptible code according to
>> it's kerneldoc. If I use disable_irq() here, I get this with
>> CONFIG_DEBUG_ATOMIC_SLEEP enabled:
> 
> Please add a one-line comment, like "nosync because of the atomic
> context"

Will do.

> 
>>
>> [   50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
>> [   50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0
>> [   50.623527] preempt_count: 10003, expected: 0
>> [   50.627888] RCU nest depth: 0, expected: 0
>> [   50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407
>> [   50.642092] Hardware name: Freescale i.MX8QXP MEK (DT)
>> [   50.647237] Call trace:
>> [   50.649687]  show_stack+0x18/0x24 (C)
>> [   50.653369]  dump_stack_lvl+0x80/0xb4
>> [   50.657043]  dump_stack+0x18/0x24
>> [   50.660370]  __might_resched+0x114/0x170
>> [   50.664306]  __might_sleep+0x48/0x98
>> [   50.667894]  disable_irq+0x24/0x60
>> [   50.671308]  dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm]
>> [   50.676912]  drm_vblank_disable_and_save+0xc0/0x108 [drm]
>> [   50.682533]  vblank_disable_fn+0x78/0x9c [drm]
>> [   50.687146]  drm_handle_vblank+0x238/0x2e8 [drm]
>> [   50.691932]  drm_crtc_handle_vblank+0x1c/0x28 [drm]
>> [   50.696980]  dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm]
>> [   50.703886]  __handle_irq_event_percpu+0x60/0x14c
>> [   50.708604]  handle_irq_event+0x4c/0xac
>> [   50.712443]  handle_level_irq+0xc0/0x1b0
>> [   50.716379]  generic_handle_irq+0x34/0x4c
>> [   50.720392]  dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm]
>> [   50.725727]  generic_handle_domain_irq+0x2c/0x44
>> [   50.730357]  imx_irqsteer_irq_handler+0xc0/0x1a0
>> [   50.734987]  generic_handle_domain_irq+0x2c/0x44
>> [   50.739609]  gic_handle_irq+0x4c/0x114
>> [   50.743362]  call_on_irq_stack+0x24/0x4c
>> [   50.747298]  do_interrupt_handler+0x80/0x84
>> [   50.751494]  el1_interrupt+0x34/0x68
>> [   50.755082]  el1h_64_irq_handler+0x18/0x24
>> [   50.759191]  el1h_64_irq+0x6c/0x70
>> [   50.762597]  default_idle_call+0x28/0x3c (P)
>> [   50.766879]  default_idle_call+0x24/0x3c (L)
>> [   50.771163]  do_idle+0x200/0x25c
>> [   50.774403]  cpu_startup_entry+0x34/0x3c
>> [   50.778338]  kernel_init+0x0/0x1d8
>> [   50.781752]  start_kernel+0x5c4/0x70c
>> [   50.785427]  __primary_switched+0x88/0x90
>>
>>>
>>>> +}
>>>> +
>>>> +static irqreturn_t
>>>> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = dev_id;
>>>> +	struct drm_crtc *crtc = &dc_crtc->base;
>>>> +	unsigned long flags;
>>>> +
>>>> +	drm_crtc_handle_vblank(crtc);
>>>> +
>>>> +	spin_lock_irqsave(&crtc->dev->event_lock, flags);
>>>> +	if (dc_crtc->event) {
>>>> +		drm_crtc_send_vblank_event(crtc, dc_crtc->event);
>>>> +		dc_crtc->event = NULL;
>>>> +		drm_crtc_vblank_put(crtc);
>>>> +	}
>>>> +	spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
>>>> +
>>>> +	return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = dev_id;
>>>> +
>>>> +	if (irq == dc_crtc->irq_dec_seqcomplete)
>>>> +		complete(&dc_crtc->dec_seqcomplete_done);
>>>> +	else if (irq == dc_crtc->irq_dec_shdld)
>>>> +		complete(&dc_crtc->dec_shdld_done);
>>>> +	else if (irq == dc_crtc->irq_ed_cont_shdld)
>>>> +		complete(&dc_crtc->ed_cont_shdld_done);
>>>> +	else if (irq == dc_crtc->irq_ed_safe_shdld)
>>>> +		complete(&dc_crtc->ed_safe_shdld_done);
>>>
>>> Is there any reason to have a single multiplex handler instead of having
>>> 4 separate handlers, each doing one simple thing?
>>
>> Just thought that one irq handler is feasible to implement the common
>> handling logic. It's also ok to use 4 separate handlers with a little
>> performance improvement. I may change to use 4 separate handlers by
>> introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco.
> 
> Just define them one by one, there is no need for a macro.

Will do.

> 
>>
>>>
>>>> +
>>>> +	return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static const struct drm_crtc_funcs dc_crtc_funcs = {
>>>> +	.reset			= drm_atomic_helper_crtc_reset,
>>>> +	.destroy		= drm_crtc_cleanup,
>>>> +	.set_config		= drm_atomic_helper_set_config,
>>>> +	.page_flip		= drm_atomic_helper_page_flip,
>>>> +	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
>>>> +	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
>>>> +	.get_vblank_counter	= dc_crtc_get_vblank_counter,
>>>> +	.enable_vblank		= dc_crtc_enable_vblank,
>>>> +	.disable_vblank		= dc_crtc_disable_vblank,
>>>> +	.get_vblank_timestamp	= drm_crtc_vblank_helper_get_vblank_timestamp,
>>>> +};
>>>> +
>>>> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
>>>> +{
>>>> +	struct drm_crtc *crtc = crtc_state->crtc;
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +
>>>> +	spin_lock_irq(&crtc->dev->event_lock);
>>>> +	if (crtc_state->event) {
>>>> +		WARN_ON(drm_crtc_vblank_get(crtc));
>>>> +		WARN_ON(dc_crtc->event);
>>>> +		dc_crtc->event = crtc_state->event;
>>>> +		crtc_state->event = NULL;
>>>> +	}
>>>> +	spin_unlock_irq(&crtc->dev->event_lock);
>>>> +}
>>>> +
>>>> +static enum drm_mode_status
>>>> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
>>>> +{
>>>> +	return dc_fg_check_clock(dc_crtc->fg, clk_khz);
>>>> +}
>>>
>>> inline
>>
>> Will do.
>>
>>>
>>>> +
>>>> +static enum drm_mode_status
>>>> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	enum drm_mode_status status;
>>>> +
>>>> +	status = dc_crtc_check_clock(dc_crtc, mode->clock);
>>>> +	if (status != MODE_OK)
>>>> +		return status;
>>>> +
>>>> +	if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
>>>> +		return MODE_CLOCK_HIGH;
>>>> +
>>>> +	return MODE_OK;
>>>> +}
>>>> +
>>>> +static int
>>>> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_crtc_state *new_crtc_state =
>>>> +				drm_atomic_get_new_crtc_state(state, crtc);
>>>> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	enum drm_mode_status status;
>>>> +
>>>> +	status = dc_crtc_check_clock(dc_crtc, adj->clock);
>>>> +	if (status != MODE_OK)
>>>> +		return -EINVAL;
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void
>>>> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_crtc_state *new_crtc_state =
>>>> +				drm_atomic_get_new_crtc_state(state, crtc);
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	int idx, ret;
>>>> +
>>>> +	if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>>>> +	    !new_crtc_state->active)
>>>> +		return;
>>>> +
>>>> +	if (!drm_dev_enter(crtc->dev, &idx))
>>>> +		return;
>>>> +
>>>> +	/* request pixel engine power-on when CRTC starts to be active */
>>>> +	ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
>>>> +	if (ret)
>>>> +		dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
>>>> +			    ret);
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +}
>>>> +
>>>> +static void
>>>> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_crtc_state *old_crtc_state =
>>>> +				drm_atomic_get_old_crtc_state(state, crtc);
>>>> +	struct drm_crtc_state *new_crtc_state =
>>>> +				drm_atomic_get_new_crtc_state(state, crtc);
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	int idx;
>>>> +
>>>> +	if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>>>> +	    (!old_crtc_state->active && !new_crtc_state->active))
>>>> +		return;
>>>> +
>>>> +	if (!drm_dev_enter(crtc->dev, &idx))
>>>> +		goto out;
>>>> +
>>>> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
>>>> +
>>>> +	/* flush plane update out to display */
>>>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>>>> +
>>>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>>>> +
>>>> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
>>>> +
>>>> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +
>>>> +out:
>>>> +	dc_crtc_queue_state_event(new_crtc_state);
>>>> +}
>>>> +
>>>> +static void
>>>> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_crtc_state *new_crtc_state =
>>>> +				drm_atomic_get_new_crtc_state(state, crtc);
>>>> +	struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	enum dc_link_id cf_link;
>>>> +	int idx, ret;
>>>> +
>>>> +	dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
>>>> +
>>>> +	drm_crtc_vblank_on(crtc);
>>>> +
>>>> +	if (!drm_dev_enter(crtc->dev, &idx))
>>>> +		goto out;
>>>> +
>>>> +	/* request display engine power-on when CRTC is enabled */
>>>> +	ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
>>>> +	if (ret < 0)
>>>> +		dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
>>>> +			    ret);
>>>> +
>>>> +	enable_irq(dc_crtc->irq_dec_shdld);
>>>> +	enable_irq(dc_crtc->irq_ed_cont_shdld);
>>>> +	enable_irq(dc_crtc->irq_ed_safe_shdld);
>>>> +
>>>> +	dc_fg_cfg_videomode(dc_crtc->fg, adj);
>>>> +
>>>> +	dc_cf_framedimensions(dc_crtc->cf_cont,
>>>> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
>>>> +	dc_cf_framedimensions(dc_crtc->cf_safe,
>>>> +			      adj->crtc_hdisplay, adj->crtc_vdisplay);
>>>> +
>>>> +	/* constframe in safety stream shows blue frame */
>>>> +	dc_cf_constantcolor_blue(dc_crtc->cf_safe);
>>>> +	cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
>>>> +	dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
>>>> +
>>>> +	/* show CRTC background if no plane is enabled */
>>>> +	if (new_crtc_state->plane_mask == 0) {
>>>> +		/* constframe in content stream shows black frame */
>>>> +		dc_cf_constantcolor_black(dc_crtc->cf_cont);
>>>> +
>>>> +		cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
>>>> +		dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
>>>> +	}
>>>> +
>>>> +	dc_fg_enable_clock(dc_crtc->fg);
>>>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>>>> +	dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
>>>> +	dc_fg_shdtokgen(dc_crtc->fg);
>>>> +	dc_fg_enable(dc_crtc->fg);
>>>> +
>>>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
>>>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>>>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
>>>> +
>>>> +	disable_irq(dc_crtc->irq_ed_safe_shdld);
>>>> +	disable_irq(dc_crtc->irq_ed_cont_shdld);
>>>> +	disable_irq(dc_crtc->irq_dec_shdld);
>>>> +
>>>> +	DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
>>>> +
>>>> +	DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +
>>>> +out:
>>>> +	dc_crtc_queue_state_event(new_crtc_state);
>>>> +}
>>>> +
>>>> +static void
>>>> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_crtc_state *new_crtc_state =
>>>> +				drm_atomic_get_new_crtc_state(state, crtc);
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	int idx, ret;
>>>> +
>>>> +	if (!drm_dev_enter(crtc->dev, &idx))
>>>> +		goto out;
>>>> +
>>>> +	enable_irq(dc_crtc->irq_dec_seqcomplete);
>>>> +	dc_fg_disable(dc_crtc->fg);
>>>> +	DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
>>>> +	disable_irq(dc_crtc->irq_dec_seqcomplete);
>>>> +
>>>> +	dc_fg_disable_clock(dc_crtc->fg);
>>>> +
>>>> +	/* request pixel engine power-off as plane is off too */
>>>> +	ret = pm_runtime_put(dc_crtc->pe->dev);
>>>> +	if (ret)
>>>> +		dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>>>> +			    ret);
>>>> +
>>>> +	/* request display engine power-off when CRTC is disabled */
>>>> +	ret = pm_runtime_put(dc_crtc->de->dev);
>>>
>>> Can this be expressed as a devlink between PE and DE?
>>
>> Looking at struct dc_{pe,de}, PE and DE are not dependent with each other,
>> i.e., no consumer/supplier relationship(note that blit engine in PE can work
>> by itself without DE) between them. So, it doesn't look right to link the two
>> devices.
> 
> Ack
> 
>>
>>>
>>>> +	if (ret < 0)
>>>> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>>>> +			    ret);
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +
>>>> +out:
>>>> +	drm_crtc_vblank_off(crtc);
>>>> +
>>>> +	spin_lock_irq(&crtc->dev->event_lock);
>>>> +	if (new_crtc_state->event && !new_crtc_state->active) {
>>>> +		drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
>>>> +		new_crtc_state->event = NULL;
>>>> +	}
>>>> +	spin_unlock_irq(&crtc->dev->event_lock);
>>>> +}
>>>> +
>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	int ret;
>>>> +
>>>> +	if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
>>>> +		return;
>>>> +
>>>> +	dc_fg_disable_clock(dc_crtc->fg);
>>>> +
>>>> +	if (pm_runtime_active(dc_crtc->pe->dev)) {
>>>> +		ret = pm_runtime_put_sync(dc_crtc->pe->dev);
>>>> +		if (ret)
>>>> +			dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>>>> +				    ret);
>>>> +	}
>>>> +
>>>> +	ret = pm_runtime_put_sync(dc_crtc->de->dev);
>>>> +	if (ret < 0)
>>>> +		dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>>>> +			    ret);
>>>> +}
>>>> +
>>>> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
>>>> +					 bool in_vblank_irq,
>>>> +					 int *vpos, int *hpos,
>>>> +					 ktime_t *stime, ktime_t *etime,
>>>> +					 const struct drm_display_mode *mode)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>> +	int vdisplay = mode->crtc_vdisplay;
>>>> +	int vtotal = mode->crtc_vtotal;
>>>> +	bool reliable;
>>>> +	int line;
>>>> +	int idx;
>>>> +
>>>> +	if (stime)
>>>> +		*stime = ktime_get();
>>>> +
>>>> +	if (!drm_dev_enter(crtc->dev, &idx)) {
>>>> +		reliable = false;
>>>> +		*vpos = 0;
>>>> +		*hpos = 0;
>>>> +		goto out;
>>>> +	}
>>>> +
>>>> +	/* line index starts with 0 for the first active output line */
>>>> +	line = dc_fg_get_line_index(dc_crtc->fg);
>>>> +
>>>> +	if (line < vdisplay)
>>>> +		/* active scanout area - positive */
>>>> +		*vpos = line + 1;
>>>> +	else
>>>> +		/* inside vblank - negative */
>>>> +		*vpos = line - (vtotal - 1);
>>>> +
>>>> +	*hpos = 0;
>>>> +
>>>> +	reliable = true;
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +out:
>>>> +	if (etime)
>>>> +		*etime = ktime_get();
>>>> +
>>>> +	return reliable;
>>>> +}
>>>> +
>>>> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
>>>> +	.mode_valid		= dc_crtc_mode_valid,
>>>> +	.atomic_check		= dc_crtc_atomic_check,
>>>> +	.atomic_begin		= dc_crtc_atomic_begin,
>>>> +	.atomic_flush		= dc_crtc_atomic_flush,
>>>> +	.atomic_enable		= dc_crtc_atomic_enable,
>>>> +	.atomic_disable		= dc_crtc_atomic_disable,
>>>> +	.get_scanout_position	= dc_crtc_get_scanout_position,
>>>> +};
>>>> +
>>>> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
>>>> +{
>>>> +	struct {
>>>> +		struct device *dev;
>>>> +		unsigned int irq;
>>>> +		irqreturn_t (*irq_handler)(int irq, void *dev_id);
>>>> +	} irqs[DC_CRTC_IRQS] = {
>>>> +		{
>>>> +			dc_crtc->de->dev,
>>>> +			dc_crtc->irq_dec_framecomplete,
>>>> +			dc_crtc_dec_framecomplete_irq_handler,
>>>> +		}, {
>>>> +			dc_crtc->de->dev,
>>>> +			dc_crtc->irq_dec_seqcomplete,
>>>> +			dc_crtc_common_irq_handler,
>>>> +		}, {
>>>> +			dc_crtc->de->dev,
>>>> +			dc_crtc->irq_dec_shdld,
>>>> +			dc_crtc_common_irq_handler,
>>>> +		}, {
>>>> +			dc_crtc->ed_cont->dev,
>>>> +			dc_crtc->irq_ed_cont_shdld,
>>>> +			dc_crtc_common_irq_handler,
>>>> +		}, {
>>>> +			dc_crtc->ed_safe->dev,
>>>> +			dc_crtc->irq_ed_safe_shdld,
>>>> +			dc_crtc_common_irq_handler,
>>>> +		},
>>>> +	};
>>>> +	int i, ret;
>>>> +
>>>> +	for (i = 0; i < DC_CRTC_IRQS; i++) {
>>>> +		struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
>>>> +
>>>> +		ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
>>>> +				       irqs[i].irq_handler, IRQF_NO_AUTOEN,
>>>> +				       dev_name(irqs[i].dev), dc_crtc);
>>>> +		if (ret) {
>>>> +			dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
>>>> +				irqs[i].irq, ret);
>>>> +			return ret;
>>>> +		}
>>>> +
>>>> +		irq->dc_crtc = dc_crtc;
>>>> +		irq->irq = irqs[i].irq;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +	struct dc_de *de = dc_drm->de[crtc_index];
>>>> +	struct dc_pe *pe = dc_drm->pe;
>>>> +	struct dc_plane *dc_primary;
>>>> +	int ret;
>>>> +
>>>> +	dc_crtc->de = de;
>>>> +	dc_crtc->pe = pe;
>>>> +
>>>> +	init_completion(&dc_crtc->dec_seqcomplete_done);
>>>> +	init_completion(&dc_crtc->dec_shdld_done);
>>>> +	init_completion(&dc_crtc->ed_cont_shdld_done);
>>>> +	init_completion(&dc_crtc->ed_safe_shdld_done);
>>>> +
>>>> +	dc_crtc->cf_cont = pe->cf_cont[crtc_index];
>>>> +	dc_crtc->cf_safe = pe->cf_safe[crtc_index];
>>>> +	dc_crtc->ed_cont = pe->ed_cont[crtc_index];
>>>> +	dc_crtc->ed_safe = pe->ed_safe[crtc_index];
>>>> +	dc_crtc->fg = de->fg;
>>>> +
>>>> +	dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
>>>> +	dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
>>>> +	dc_crtc->irq_dec_shdld = de->irq_shdld;
>>>> +	dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
>>>> +	dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
>>>> +
>>>> +	dc_primary = &dc_drm->dc_primary[crtc_index];
>>>> +	ret = dc_plane_init(dc_drm, dc_primary);
>>>> +	if (ret) {
>>>> +		dev_err(drm->dev,
>>>> +			"failed to init primary plane for display engine%u: %d\n",
>>>> +			de->id, ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
>>>> +
>>>> +	ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
>>>> +					NULL, &dc_crtc_funcs, NULL);
>>>> +	if (ret)
>>>> +		dev_err(drm->dev,
>>>> +			"failed to add CRTC for display engine%u: %d\n",
>>>> +			de->id, ret);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +
>>>> +	return dc_crtc_request_irqs(drm, dc_crtc);
>>>> +}
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
>>>> index 17a44362118e..8a7b6c03a222 100644
>>>> --- a/drivers/gpu/drm/imx/dc/dc-de.h
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
>>>> @@ -13,6 +13,9 @@
>>>>  
>>>>  #define DC_DISPLAYS	2
>>>>  
>>>> +#define DC_FRAMEGEN_MAX_FRAME_INDEX	0x3ffff
>>>> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ	300000
>>>> +
>>>>  struct dc_fg {
>>>>  	struct device *dev;
>>>>  	struct regmap *reg;
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
>>>> index fd68861f770a..1e4b8afa3eec 100644
>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
>>>> @@ -3,11 +3,254 @@
>>>>   * Copyright 2024 NXP
>>>>   */
>>>>  
>>>> +#include <linux/clk.h>
>>>> +#include <linux/component.h>
>>>> +#include <linux/device.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/mod_devicetable.h>
>>>>  #include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_platform.h>
>>>>  #include <linux/platform_device.h>
>>>> +#include <linux/pm.h>
>>>> +#include <linux/pm_runtime.h>
>>>>  
>>>> +#include <drm/drm_atomic_helper.h>
>>>> +#include <drm/drm_client_setup.h>
>>>> +#include <drm/drm_crtc.h>
>>>> +#include <drm/drm_drv.h>
>>>> +#include <drm/drm_fbdev_dma.h>
>>>> +#include <drm/drm_fourcc.h>
>>>> +#include <drm/drm_gem_dma_helper.h>
>>>> +#include <drm/drm_managed.h>
>>>> +#include <drm/drm_modeset_helper.h>
>>>> +#include <drm/drm_of.h>
>>>> +
>>>> +#include "dc-de.h"
>>>>  #include "dc-drv.h"
>>>>  
>>>> +struct dc_priv {
>>>> +	struct drm_device *drm;
>>>> +	struct clk *clk_cfg;
>>>> +};
>>>> +
>>>> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
>>>> +
>>>> +static struct drm_driver dc_drm_driver = {
>>>> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
>>>> +	DRM_GEM_DMA_DRIVER_OPS,
>>>> +	DRM_FBDEV_DMA_DRIVER_OPS,
>>>> +	.fops = &dc_drm_driver_fops,
>>>> +	.name = "imx8-dc",
>>>> +	.desc = "i.MX8 DC DRM graphics",
>>>> +	.date = "20240530",
>>>> +	.major = 1,
>>>> +	.minor = 0,
>>>> +	.patchlevel = 0,
>>>> +};
>>>> +
>>>> +static void
>>>> +dc_add_components(struct device *dev, struct component_match **matchptr)
>>>> +{
>>>> +	struct device_node *child, *grandchild;
>>>> +
>>>> +	for_each_available_child_of_node(dev->of_node, child) {
>>>> +		/* The interrupt controller is not a component. */
>>>> +		if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
>>>> +			continue;
>>>> +
>>>> +		drm_of_component_match_add(dev, matchptr, component_compare_of,
>>>> +					   child);
>>>> +
>>>> +		for_each_available_child_of_node(child, grandchild)
>>>> +			drm_of_component_match_add(dev, matchptr,
>>>> +						   component_compare_of,
>>>> +						   grandchild);
>>>> +	}
>>>> +}
>>>> +
>>>> +static void dc_drm_component_unbind_all(void *ptr)
>>>> +{
>>>> +	struct dc_drm_device *dc_drm = ptr;
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +
>>>> +	component_unbind_all(drm->dev, dc_drm);
>>>> +}
>>>> +
>>>> +static int dc_drm_bind(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +	struct dc_drm_device *dc_drm;
>>>> +	struct drm_device *drm;
>>>> +	int ret;
>>>> +
>>>> +	dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
>>>> +				    base);
>>>> +	if (IS_ERR(dc_drm))
>>>> +		return PTR_ERR(dc_drm);
>>>> +
>>>> +	drm = &dc_drm->base;
>>>> +
>>>> +	ret = component_bind_all(dev, dc_drm);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
>>>> +				       dc_drm);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = dc_kms_init(dc_drm);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = drm_dev_register(drm, 0);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to register drm device: %d\n", ret);
>>>> +		goto err;
>>>> +	}
>>>> +
>>>> +	drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
>>>> +
>>>> +	priv->drm = drm;
>>>> +
>>>> +	return 0;
>>>> +
>>>> +err:
>>>> +	dc_kms_uninit(dc_drm);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static void dc_drm_unbind(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +	struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +	struct drm_crtc *crtc;
>>>> +
>>>> +	priv->drm = NULL;
>>>> +	drm_dev_unplug(drm);
>>>> +	dc_kms_uninit(dc_drm);
>>>> +	drm_atomic_helper_shutdown(drm);
>>>> +
>>>> +	drm_for_each_crtc(crtc, drm)
>>>> +		dc_crtc_disable_at_unbind(crtc);
>>>
>>> There should be no need for that. drm_atomic_helper_shutdown() should
>>> disable all the CRTCs.
>>
>> In case DRM device is unplugged, drm_atomic_helper_shutdown does not
>> effectively disable the CRTCs due to the bypassed logics wrapped by
>> drm_dev_{enter,exit}.  That's why dc_crtc_disable_at_unbind() is called
>> here to effectively disable the CRTCs.
> 
> I see. I haven't faced drm_dev_unplug() earlier. I checked, the "not
> disabled" behaviour is documented and all other drivers don't perform
> any kind of cleanup afterwards. Thus I think it's safe to drop the
> dc_crtc_disable_at_unbind() unless it causes any kind of HW issues.

IMHO, it's a bit safer to set the HWs backing the CRTCs to a known state
at the unbind stage by properly disabling them, in case there is any
unknown SW or HW issue.  Plus, i.MX8qm/qxp SoCs don't provide reset
signals to the Display Controllers(the resets DT property is optional),
otherwise, this disablement function can be dropped(at least for now).

> 
>>
>>>
>>> Also, who is going to do drm_dev_unregister()? I don't see it in the
>>> code.
>>
>> drm_dev_unplug() right above calls drm_dev_unregister().
>>
>>>
>>>> +}
>>>> +
>>>> +static const struct component_master_ops dc_drm_ops = {
>>>> +	.bind = dc_drm_bind,
>>>> +	.unbind = dc_drm_unbind,
>>>> +};
>>>> +
>>>> +static int dc_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct component_match *match = NULL;
>>>> +	struct dc_priv *priv;
>>>> +	int ret;
>>>> +
>>>> +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>>>> +	if (!priv)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
>>>> +	if (IS_ERR(priv->clk_cfg))
>>>> +		return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
>>>> +				     "failed to get cfg clock\n");
>>>> +
>>>> +	dev_set_drvdata(&pdev->dev, priv);
>>>> +
>>>> +	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = devm_pm_runtime_enable(&pdev->dev);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = devm_of_platform_populate(&pdev->dev);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	dc_add_components(&pdev->dev, &match);
>>>> +
>>>> +	ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
>>>> +	if (ret)
>>>> +		return dev_err_probe(&pdev->dev, ret,
>>>> +				     "failed to add component master\n");
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void dc_remove(struct platform_device *pdev)
>>>> +{
>>>> +	component_master_del(&pdev->dev, &dc_drm_ops);
>>>> +}
>>>> +
>>>> +static int dc_runtime_suspend(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	clk_disable_unprepare(priv->clk_cfg);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int dc_runtime_resume(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +	int ret;
>>>> +
>>>> +	ret = clk_prepare_enable(priv->clk_cfg);
>>>> +	if (ret)
>>>> +		dev_err(dev, "failed to enable cfg clock: %d\n", ret);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int dc_suspend(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	return drm_mode_config_helper_suspend(priv->drm);
>>>> +}
>>>> +
>>>> +static int dc_resume(struct device *dev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(dev);
>>>> +
>>>> +	return drm_mode_config_helper_resume(priv->drm);
>>>> +}
>>>> +
>>>> +static void dc_shutdown(struct platform_device *pdev)
>>>> +{
>>>> +	struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
>>>> +
>>>> +	drm_atomic_helper_shutdown(priv->drm);
>>>> +}
>>>> +
>>>> +static const struct dev_pm_ops dc_pm_ops = {
>>>> +	RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
>>>> +	SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
>>>> +};
>>>> +
>>>> +static const struct of_device_id dc_dt_ids[] = {
>>>> +	{ .compatible = "fsl,imx8qxp-dc", },
>>>> +	{ /* sentinel */ }
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
>>>> +
>>>> +static struct platform_driver dc_driver = {
>>>> +	.probe = dc_probe,
>>>> +	.remove = dc_remove,
>>>> +	.shutdown = dc_shutdown,
>>>> +	.driver = {
>>>> +		.name = "imx8-dc",
>>>> +		.of_match_table	= dc_dt_ids,
>>>> +		.pm = pm_sleep_ptr(&dc_pm_ops),
>>>> +	},
>>>> +};
>>>> +
>>>>  static struct platform_driver * const dc_drivers[] = {
>>>>  	&dc_cf_driver,
>>>>  	&dc_de_driver,
>>>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
>>>>  	&dc_lb_driver,
>>>>  	&dc_pe_driver,
>>>>  	&dc_tc_driver,
>>>> +	&dc_driver,
>>>>  };
>>>>  
>>>>  static int __init dc_drm_init(void)
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
>>>> index 3b11f4862c6c..39a771a13933 100644
>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
>>>> @@ -6,19 +6,38 @@
>>>>  #ifndef __DC_DRV_H__
>>>>  #define __DC_DRV_H__
>>>>  
>>>> +#include <linux/container_of.h>
>>>>  #include <linux/platform_device.h>
>>>>  
>>>>  #include <drm/drm_device.h>
>>>> +#include <drm/drm_encoder.h>
>>>>  
>>>>  #include "dc-de.h"
>>>> +#include "dc-kms.h"
>>>>  #include "dc-pe.h"
>>>>  
>>>>  struct dc_drm_device {
>>>>  	struct drm_device base;
>>>> +	struct dc_crtc dc_crtc[DC_DISPLAYS];
>>>> +	struct dc_plane dc_primary[DC_DISPLAYS];
>>>> +	struct drm_encoder encoder[DC_DISPLAYS];
>>>>  	struct dc_de *de[DC_DISPLAYS];
>>>>  	struct dc_pe *pe;
>>>>  };
>>>>  
>>>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
>>>> +{
>>>> +	return container_of(drm, struct dc_drm_device, base);
>>>> +}
>>>> +
>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
>>>> +
>>>> +int dc_kms_init(struct dc_drm_device *dc_drm);
>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
>>>> +
>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
>>>> +
>>>>  extern struct platform_driver dc_cf_driver;
>>>>  extern struct platform_driver dc_ed_driver;
>>>>  extern struct platform_driver dc_de_driver;
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
>>>> new file mode 100644
>>>> index 000000000000..2b18aa37a4a8
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
>>>> @@ -0,0 +1,143 @@
>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>> +/*
>>>> + * Copyright 2024 NXP
>>>> + */
>>>> +
>>>> +#include <linux/of.h>
>>>> +#include <linux/of_graph.h>
>>>> +
>>>> +#include <drm/drm_atomic_helper.h>
>>>> +#include <drm/drm_bridge.h>
>>>> +#include <drm/drm_bridge_connector.h>
>>>> +#include <drm/drm_connector.h>
>>>> +#include <drm/drm_crtc.h>
>>>> +#include <drm/drm_device.h>
>>>> +#include <drm/drm_encoder.h>
>>>> +#include <drm/drm_gem_framebuffer_helper.h>
>>>> +#include <drm/drm_mode_config.h>
>>>> +#include <drm/drm_print.h>
>>>> +#include <drm/drm_probe_helper.h>
>>>> +#include <drm/drm_simple_kms_helper.h>
>>>> +#include <drm/drm_vblank.h>
>>>> +
>>>> +#include "dc-de.h"
>>>> +#include "dc-drv.h"
>>>> +#include "dc-kms.h"
>>>> +
>>>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
>>>> +	.fb_create = drm_gem_fb_create,
>>>> +	.atomic_check = drm_atomic_helper_check,
>>>> +	.atomic_commit = drm_atomic_helper_commit,
>>>> +};
>>>> +
>>>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
>>>> +					int crtc_index)
>>>> +{
>>>> +	struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +	struct drm_crtc *crtc = &dc_crtc->base;
>>>> +	struct drm_connector *connector;
>>>> +	struct device *dev = drm->dev;
>>>> +	struct drm_encoder *encoder;
>>>> +	struct drm_bridge *bridge;
>>>> +	int ret;
>>>> +
>>>> +	bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
>>>> +					0, 0);
>>>> +	if (IS_ERR(bridge)) {
>>>> +		ret = PTR_ERR(bridge);
>>>> +		if (ret == -ENODEV)
>>>> +			return 0;
>>>> +
>>>> +		return dev_err_probe(dev, ret,
>>>> +				     "failed to find bridge for CRTC%u\n",
>>>> +				     crtc->index);
>>>> +	}
>>>> +
>>>> +	encoder = &dc_drm->encoder[crtc_index];
>>>> +	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
>>>> +	if (ret) {
>>>> +		dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
>>>> +			crtc->index, ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	encoder->possible_crtcs = drm_crtc_mask(crtc);
>>>> +
>>>> +	ret = drm_bridge_attach(encoder, bridge, NULL,
>>>> +				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>>>> +	if (ret) {
>>>> +		dev_err(dev,
>>>> +			"failed to attach bridge to encoder for CRTC%u: %d\n",
>>>> +			crtc->index, ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	connector = drm_bridge_connector_init(drm, encoder);
>>>> +	if (IS_ERR(connector)) {
>>>> +		ret = PTR_ERR(connector);
>>>> +		dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
>>>> +			crtc->index, ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = drm_connector_attach_encoder(connector, encoder);
>>>> +	if (ret)
>>>> +		dev_err(dev,
>>>> +			"failed to attach encoder to connector for CRTC%u: %d\n",
>>>> +			crtc->index, ret);
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +int dc_kms_init(struct dc_drm_device *dc_drm)
>>>> +{
>>>> +	struct drm_device *drm = &dc_drm->base;
>>>> +	int ret, i;
>>>> +
>>>> +	ret = drmm_mode_config_init(drm);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	drm->mode_config.min_width = 60;
>>>> +	drm->mode_config.min_height = 60;
>>>> +	drm->mode_config.max_width = 8192;
>>>> +	drm->mode_config.max_height = 8192;
>>>> +	drm->mode_config.funcs = &dc_drm_mode_config_funcs;
>>>> +
>>>> +	drm->vblank_disable_immediate = true;
>>>> +	drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
>>>> +
>>>> +	for (i = 0; i < DC_DISPLAYS; i++) {
>>>> +		ret = dc_crtc_init(dc_drm, i);
>>>> +		if (ret)
>>>> +			return ret;
>>>> +
>>>> +		ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
>>>> +		if (ret)
>>>> +			return ret;
>>>> +	}
>>>> +
>>>> +	for (i = 0; i < DC_DISPLAYS; i++) {
>>>> +		ret = dc_crtc_post_init(dc_drm, i);
>>>
>>> Can you use .late_register for this?
>>
>> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
>> additional userspace interfaces like debugfs interfaces. And, it seems that
>> everyone implementing this uses it to add debugfs interfaces. So, it will
>> kind of abuse it to do CRTC post initialization.
> 
> Why can't they be requested earlier then?

If I request them earlier in dc_crtc_init(), then they cannot be freed by
devm_irq_release() when devm_drm_of_get_bridge() called by
dc_kms_init_encoder_per_crtc() returns -EPROBE_DEFER(which means failing
to find the first DRM bridge for the CRTC).  Why can't they be freed by
devm_irq_release()?  Because they are requested by the devices of ExtDsts
and Display Engines and their drivers are not removed during the probe
deferral dance.  Furthermore, -EPROBE_DEFER won't be returned after
dc_crtc_post_init() since the later called drm_vblank_init() doesn't
return -EPROBE_DEFER anyway, so it's fine to call dc_crtc_post_init() here.

I met the irq free issue on my i.MX8qxp MEK board before, i.e., -EBUSY is
returned when requesting them again, so it's tested.

> 
>>
>>>
>>>> +		if (ret)
>>>> +			return ret;
>>>> +	}
>>>> +
>>>> +	ret = drm_vblank_init(drm, DC_DISPLAYS);
>>>> +	if (ret) {
>>>> +		dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	drm_mode_config_reset(drm);
>>>> +
>>>> +	drm_kms_helper_poll_init(drm);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
>>>> +{
>>>> +	drm_kms_helper_poll_fini(&dc_drm->base);
>>>> +}
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
>>>> new file mode 100644
>>>> index 000000000000..57f6e0c15f57
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
>>>> @@ -0,0 +1,58 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>>> +/*
>>>> + * Copyright 2024 NXP
>>>> + */
>>>> +
>>>> +#ifndef __DC_KMS_H__
>>>> +#define __DC_KMS_H__
>>>> +
>>>> +#include <linux/completion.h>
>>>> +
>>>> +#include <drm/drm_crtc.h>
>>>> +#include <drm/drm_plane.h>
>>>> +#include <drm/drm_vblank.h>
>>>> +
>>>> +#include "dc-de.h"
>>>> +#include "dc-fu.h"
>>>> +#include "dc-pe.h"
>>>> +
>>>> +#define DC_CRTC_IRQS	5
>>>> +
>>>> +struct dc_crtc_irq {
>>>> +	struct dc_crtc *dc_crtc;
>>>> +	unsigned int irq;
>>>> +};
>>>> +
>>>
>>> Please provide some documentation for the structure in the form of the
>>> kerneldoc. E.g. what is the difference between ed_cont and ed_safe?
>>
>> Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the
>> difference between ed_cont(content stream) and ed_safe(safety stream).
>>
>>> The de and fg pointers are global, please don't cache them
>>> unnecessarily.
>>
>> Global? I don't catch your meaning, sorry. To me, it's handy to access de
>> and fg via the two pointers in struct dc_crtc.
> 
> I had to spend some time understanding if they are per-CRTC or if there
> are a single instances of those subdevices. Thus I suggest to access

They are per-CRTC.  One Display Controller contains two Display Engines,
hence two CRTCs.

> them through dc_drm_device. You can ignore this suggestion though.

I'll ignore this.  Thanks anyway.

> 
>>
>>>
>>>> +struct dc_crtc {
>>>> +	struct drm_crtc base;
>>>> +	struct dc_de *de;
>>>> +	struct dc_pe *pe;
>>>> +	struct dc_cf *cf_cont;
>>>> +	struct dc_cf *cf_safe;
>>>> +	struct dc_ed *ed_cont;
>>>> +	struct dc_ed *ed_safe;
>>>> +	struct dc_fg *fg;
>>>> +	unsigned int irq_dec_framecomplete;
>>>> +	unsigned int irq_dec_seqcomplete;
>>>> +	unsigned int irq_dec_shdld;
>>>> +	unsigned int irq_ed_cont_shdld;
>>>> +	unsigned int irq_ed_safe_shdld;
>>>> +	struct completion dec_seqcomplete_done;
>>>> +	struct completion dec_shdld_done;
>>>> +	struct completion ed_safe_shdld_done;
>>>> +	struct completion ed_cont_shdld_done;
>>>> +	struct drm_pending_vblank_event *event;
>>>> +	struct dc_crtc_irq irqs[DC_CRTC_IRQS];
>>>> +};
>>>> +
>>>> +struct dc_plane {
>>>> +	struct drm_plane base;
>>>> +	struct dc_fu *fu;
>>>> +	struct dc_cf *cf;
>>>> +	struct dc_lb *lb;
>>>> +	struct dc_ed *ed;
>>>> +};
>>>> +
>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
>>>> +
>>>> +#endif /* __DC_KMS_H__ */
>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
>>>> new file mode 100644
>>>> index 000000000000..78d0d2cd3451
>>>> --- /dev/null
>>>> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
>>>> @@ -0,0 +1,241 @@
>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>> +/*
>>>> + * Copyright 2024 NXP
>>>> + */
>>>> +
>>>> +#include <linux/container_of.h>
>>>> +
>>>> +#include <drm/drm_atomic.h>
>>>> +#include <drm/drm_atomic_helper.h>
>>>> +#include <drm/drm_atomic_state_helper.h>
>>>> +#include <drm/drm_crtc.h>
>>>> +#include <drm/drm_drv.h>
>>>> +#include <drm/drm_fb_dma_helper.h>
>>>> +#include <drm/drm_fourcc.h>
>>>> +#include <drm/drm_framebuffer.h>
>>>> +#include <drm/drm_gem_atomic_helper.h>
>>>> +#include <drm/drm_plane_helper.h>
>>>> +#include <drm/drm_print.h>
>>>> +
>>>> +#include "dc-drv.h"
>>>> +#include "dc-fu.h"
>>>> +#include "dc-kms.h"
>>>> +
>>>> +#define DC_PLANE_MAX_PITCH	0x10000
>>>> +#define DC_PLANE_MAX_PIX_CNT	8192
>>>> +
>>>> +#define dc_plane_dbg(plane, fmt, ...)					\
>>>> +do {									\
>>>> +	typeof(plane) _plane = (plane);					\
>>>> +	drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,			\
>>>> +		    _plane->base.id, _plane->name, ##__VA_ARGS__);	\
>>>> +} while (0)
>>>> +
>>>> +static const uint32_t dc_plane_formats[] = {
>>>> +	DRM_FORMAT_XRGB8888,
>>>> +};
>>>> +
>>>> +static const struct drm_plane_funcs dc_plane_funcs = {
>>>> +	.update_plane		= drm_atomic_helper_update_plane,
>>>> +	.disable_plane		= drm_atomic_helper_disable_plane,
>>>> +	.destroy		= drm_plane_cleanup,
>>>> +	.reset			= drm_atomic_helper_plane_reset,
>>>> +	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
>>>> +	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
>>>> +};
>>>> +
>>>> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
>>>> +{
>>>> +	return container_of(plane, struct dc_plane, base);
>>>> +}
>>>> +
>>>> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
>>>> +					struct drm_crtc_state *crtc_state)
>>>> +{
>>>> +	if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
>>>> +	    state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
>>>> +	    state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
>>>> +		dc_plane_dbg(state->plane, "no off screen\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
>>>> +{
>>>> +	int src_h = drm_rect_height(&state->src) >> 16;
>>>> +	int src_w = drm_rect_width(&state->src) >> 16;
>>>> +
>>>> +	if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
>>>> +		dc_plane_dbg(state->plane, "invalid source resolution\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int dc_plane_check_fb(struct drm_plane_state *state)
>>>> +{
>>>> +	struct drm_framebuffer *fb = state->fb;
>>>> +	dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
>>>> +
>>>> +	/* base address alignment */
>>>> +	if (baseaddr & 0x3) {
>>>> +		dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	/* pitches[0] range */
>>>> +	if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
>>>> +		dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	/* pitches[0] alignment */
>>>> +	if (fb->pitches[0] & 0x3) {
>>>> +		dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static int
>>>> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_plane_state *plane_state =
>>>> +				drm_atomic_get_new_plane_state(state, plane);
>>>> +	struct drm_crtc_state *crtc_state;
>>>> +	int ret;
>>>> +
>>>> +	/* ok to disable */
>>>> +	if (!plane_state->fb)
>>>> +		return 0;
>>>> +
>>>> +	if (!plane_state->crtc) {
>>>> +		dc_plane_dbg(plane, "no CRTC in plane state\n");
>>>> +		return -EINVAL;
>>>> +	}
>>>> +
>>>> +	crtc_state =
>>>> +		drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
>>>> +	if (WARN_ON(!crtc_state))
>>>> +		return -EINVAL;
>>>> +
>>>> +	ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
>>>> +						  DRM_PLANE_NO_SCALING,
>>>> +						  DRM_PLANE_NO_SCALING,
>>>> +						  true, false);
>>>> +	if (ret) {
>>>> +		dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
>>>> +		return ret;
>>>> +	}
>>>> +
>>>> +	ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	ret = dc_plane_check_max_source_resolution(plane_state);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	return dc_plane_check_fb(plane_state);
>>>> +}
>>>> +
>>>> +static void
>>>> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
>>>> +{
>>>> +	struct drm_plane_state *new_state =
>>>> +				drm_atomic_get_new_plane_state(state, plane);
>>>> +	struct dc_plane *dplane = to_dc_plane(plane);
>>>> +	struct drm_framebuffer *fb = new_state->fb;
>>>> +	const struct dc_fu_ops *fu_ops;
>>>> +	struct dc_lb *lb = dplane->lb;
>>>> +	struct dc_fu *fu = dplane->fu;
>>>> +	dma_addr_t baseaddr;
>>>> +	int src_w, src_h;
>>>> +	int idx;
>>>> +
>>>> +	if (!drm_dev_enter(plane->dev, &idx))
>>>> +		return;
>>>> +
>>>> +	src_w = drm_rect_width(&new_state->src) >> 16;
>>>> +	src_h = drm_rect_height(&new_state->src) >> 16;
>>>> +
>>>> +	baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
>>>> +
>>>> +	fu_ops = dc_fu_get_ops(dplane->fu);
>>>> +
>>>> +	fu_ops->set_layerblend(fu, lb);
>>>> +	fu_ops->set_burstlength(fu, baseaddr);
>>>> +	fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
>>>> +	fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
>>>> +	fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
>>>> +	fu_ops->set_framedimensions(fu, src_w, src_h);
>>>> +	fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
>>>> +	fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
>>>> +
>>>> +	dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
>>>> +
>>>> +	dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
>>>> +	dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
>>>> +	dc_lb_mode(lb, LB_BLEND);
>>>> +	dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
>>>> +	dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
>>>> +
>>>> +	dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
>>>> +
>>>> +	/* set ExtDst's source to LayerBlend */
>>>> +	dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +}
>>>> +
>>>> +static void dc_plane_atomic_disable(struct drm_plane *plane,
>>>> +				    struct drm_atomic_state *state)
>>>> +{
>>>> +	struct dc_plane *dplane = to_dc_plane(plane);
>>>> +	const struct dc_fu_ops *fu_ops;
>>>> +	int idx;
>>>> +
>>>> +	if (!drm_dev_enter(plane->dev, &idx))
>>>> +		return;
>>>> +
>>>> +	/* disable fetchunit in shadow */
>>>> +	fu_ops = dc_fu_get_ops(dplane->fu);
>>>> +	fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
>>>> +
>>>> +	/* set ExtDst's source to ConstFrame */
>>>> +	dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
>>>> +
>>>> +	drm_dev_exit(idx);
>>>> +}
>>>> +
>>>> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
>>>> +	.atomic_check = dc_plane_atomic_check,
>>>> +	.atomic_update = dc_plane_atomic_update,
>>>> +	.atomic_disable = dc_plane_atomic_disable,
>>>> +};
>>>> +
>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
>>>> +{
>>>> +	struct drm_plane *plane = &dc_plane->base;
>>>> +	int ret;
>>>> +
>>>> +	ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
>>>> +				       dc_plane_formats,
>>>> +				       ARRAY_SIZE(dc_plane_formats),
>>>> +				       NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
>>>> +	if (ret)
>>>> +		return ret;
>>>> +
>>>> +	drm_plane_helper_add(plane, &dc_plane_helper_funcs);
>>>> +
>>>> +	dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
>>>> +	dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
>>>> +	dc_plane->lb = dc_drm->pe->lb[plane->index];
>>>> +	dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
>>>> +
>>>> +	return 0;
>>>> +}
>>>> -- 
>>>> 2.34.1
>>>>
>>>
>>
>> -- 
>> Regards,
>> Liu Ying
> 

-- 
Regards,
Liu Ying


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-13  6:07         ` Liu Ying
@ 2024-12-13 13:28           ` Dmitry Baryshkov
  2024-12-16  6:29             ` Liu Ying
  0 siblings, 1 reply; 35+ messages in thread
From: Dmitry Baryshkov @ 2024-12-13 13:28 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On Fri, 13 Dec 2024 at 08:06, Liu Ying <victor.liu@nxp.com> wrote:
>
> On 12/12/2024, Dmitry Baryshkov wrote:
> > On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote:
> >> On 12/10/2024, Dmitry Baryshkov wrote:
> >>> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
> >>>> i.MX8qxp Display Controller(DC) is comprised of three main components that
> >>>> include a blit engine for 2D graphics accelerations, display controller for
> >>>> display output processing, as well as a command sequencer.  Add kernel
> >>>> mode setting support for the display controller part with two CRTCs and
> >>>> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
> >>>> registers of the display controller are accessed without command sequencer
> >>>> involved, instead just by using CPU.  The command sequencer is supposed to
> >>>> be used by the blit engine.
> >>>>
> >>>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> >>>> ---
> >>>> v6:
> >>>> * No change.
> >>>>
> >>>> v5:
> >>>> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
> >>>>
> >>>> v4:
> >>>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
> >>>>   function calls from KMS routine to initialization stage. (Dmitry)
> >>>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
> >>>>   appropriate .h header files or .c source files. (Dmitry)
> >>>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
> >>>> * Drop dc_drm->pe_rpm_count. (Dmitry)
> >>>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
> >>>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
> >>>>   Instead, put it in struct dc_crtc.  (Dmitry)
> >>>> * Call devm_request_irq() to request IRQs, instead of using drmm action.
> >>>>   (Dmitry)
> >>>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
> >>>> * Select DRM_CLIENT_SELECTION due to rebase.
> >>>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
> >>>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
> >>>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
> >>>>   rebase.
> >>>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
> >>>>   register dc_drm_component_unbind_all() action.
> >>>> * Request interrupts in dc_crtc_post_init() after encoder initialization to
> >>>>   make sure next bridge is found first.
> >>>>
> >>>> v3:
> >>>> * No change.
> >>>>
> >>>> v2:
> >>>> * Find next bridge from TCon's port.
> >>>> * Drop drm/drm_module.h include from dc-drv.c.
> >>>>
> >>>>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
> >>>>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
> >>>>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
> >>>>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
> >>>>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
> >>>>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
> >>>>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
> >>>>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
> >>>>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
> >>>>  9 files changed, 1274 insertions(+), 2 deletions(-)
> >>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
> >>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
> >>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
> >>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
> >>>>
> >>>> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
> >>>> index 1fc84c7475de..415993207f2e 100644
> >>>> --- a/drivers/gpu/drm/imx/dc/Kconfig
> >>>> +++ b/drivers/gpu/drm/imx/dc/Kconfig
> >>>> @@ -1,6 +1,11 @@
> >>>>  config DRM_IMX8_DC
> >>>>    tristate "Freescale i.MX8 Display Controller Graphics"
> >>>>    depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
> >>>> +  select DRM_CLIENT_SELECTION
> >>>> +  select DRM_GEM_DMA_HELPER
> >>>> +  select DRM_KMS_HELPER
> >>>> +  select DRM_DISPLAY_HELPER
> >>>> +  select DRM_BRIDGE_CONNECTOR
> >>>>    select GENERIC_IRQ_CHIP
> >>>>    select REGMAP
> >>>>    select REGMAP_MMIO
> >>>> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
> >>>> index 1ce3e8a8db22..b9d33c074984 100644
> >>>> --- a/drivers/gpu/drm/imx/dc/Makefile
> >>>> +++ b/drivers/gpu/drm/imx/dc/Makefile
> >>>> @@ -1,6 +1,7 @@
> >>>>  # SPDX-License-Identifier: GPL-2.0
> >>>>
> >>>> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
> >>>> -              dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
> >>>> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
> >>>> +              dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
> >>>> +              dc-plane.o dc-tc.o
> >>>>
> >>>>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
> >>>> new file mode 100644
> >>>> index 000000000000..fd6daa1807d8
> >>>> --- /dev/null
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
> >>>> @@ -0,0 +1,558 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>> +/*
> >>>> + * Copyright 2024 NXP
> >>>> + */
> >>>> +
> >>>> +#include <linux/completion.h>
> >>>> +#include <linux/container_of.h>
> >>>> +#include <linux/interrupt.h>
> >>>> +#include <linux/irqreturn.h>
> >>>> +#include <linux/pm_runtime.h>
> >>>> +#include <linux/spinlock.h>
> >>>> +
> >>>> +#include <drm/drm_atomic.h>
> >>>> +#include <drm/drm_atomic_helper.h>
> >>>> +#include <drm/drm_atomic_state_helper.h>
> >>>> +#include <drm/drm_crtc.h>
> >>>> +#include <drm/drm_device.h>
> >>>> +#include <drm/drm_drv.h>
> >>>> +#include <drm/drm_modes.h>
> >>>> +#include <drm/drm_modeset_helper_vtables.h>
> >>>> +#include <drm/drm_plane.h>
> >>>> +#include <drm/drm_print.h>
> >>>> +#include <drm/drm_vblank.h>
> >>>> +
> >>>> +#include "dc-de.h"
> >>>> +#include "dc-drv.h"
> >>>> +#include "dc-kms.h"
> >>>> +#include "dc-pe.h"
> >>>> +
> >>>> +#define dc_crtc_dbg(crtc, fmt, ...)                                       \
> >>>> +do {                                                                      \
> >>>> +  typeof(crtc) _crtc = (crtc);                                    \
> >>>
> >>> Use exact type instead of typeof.
> >>
> >> Will do.
> >>
> >>>
> >>>> +  drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,                    \
> >>>> +              _crtc->base.id, _crtc->name, ##__VA_ARGS__);        \
> >>>> +} while (0)
> >>>> +
> >>>> +#define dc_crtc_err(crtc, fmt, ...)                                       \
> >>>> +do {                                                                      \
> >>>> +  typeof(crtc) _crtc = (crtc);                                    \
> >>>> +  drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,                        \
> >>>> +          _crtc->base.id, _crtc->name, ##__VA_ARGS__);            \
> >>>> +} while (0)
> >>>> +
> >>>> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)                            \
> >>>> +do {                                                                      \
> >>>> +  unsigned long ret;                                              \
> >>>> +  ret = wait_for_completion_timeout(&dc_crtc->c, HZ);             \
> >>>> +  if (ret == 0)                                                   \
> >>>> +          dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",      \
> >>>> +                                                  __func__);      \
> >>>> +} while (0)
> >>>> +
> >>>> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)                                   \
> >>>> +do {                                                                      \
> >>>> +  typeof(fg) _fg = (fg);                                          \
> >>>> +  if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {         \
> >>>> +          dc_fg_secondary_clear_channel_status(_fg);              \
> >>>> +          dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",          \
> >>>> +                                                  __func__);      \
> >>>> +  }                                                               \
> >>>> +} while (0)
> >>>> +
> >>>> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)                    \
> >>>> +do {                                                                      \
> >>>> +  if (dc_fg_wait_for_secondary_syncup(fg))                        \
> >>>> +          dc_crtc_err(crtc,                                       \
> >>>> +                  "%s: FrameGen secondary channel isn't syncup\n",\
> >>>> +                                                  __func__);      \
> >>>> +} while (0)
> >>>> +
> >>>> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
> >>>> +{
> >>>> +  return container_of(crtc, struct dc_crtc, base);
> >>>> +}
> >>>> +
> >>>> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +
> >>>> +  return dc_fg_get_frame_index(dc_crtc->fg);
> >>>> +}
> >>>> +
> >>>> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +
> >>>> +  enable_irq(dc_crtc->irq_dec_framecomplete);
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +
> >>>> +  disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
> >>>
> >>> Why is it _nosync?
> >>
> >> Because disable_irq() can only be called from preemptible code according to
> >> it's kerneldoc. If I use disable_irq() here, I get this with
> >> CONFIG_DEBUG_ATOMIC_SLEEP enabled:
> >
> > Please add a one-line comment, like "nosync because of the atomic
> > context"
>
> Will do.
>
> >
> >>
> >> [   50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
> >> [   50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0
> >> [   50.623527] preempt_count: 10003, expected: 0
> >> [   50.627888] RCU nest depth: 0, expected: 0
> >> [   50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407
> >> [   50.642092] Hardware name: Freescale i.MX8QXP MEK (DT)
> >> [   50.647237] Call trace:
> >> [   50.649687]  show_stack+0x18/0x24 (C)
> >> [   50.653369]  dump_stack_lvl+0x80/0xb4
> >> [   50.657043]  dump_stack+0x18/0x24
> >> [   50.660370]  __might_resched+0x114/0x170
> >> [   50.664306]  __might_sleep+0x48/0x98
> >> [   50.667894]  disable_irq+0x24/0x60
> >> [   50.671308]  dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm]
> >> [   50.676912]  drm_vblank_disable_and_save+0xc0/0x108 [drm]
> >> [   50.682533]  vblank_disable_fn+0x78/0x9c [drm]
> >> [   50.687146]  drm_handle_vblank+0x238/0x2e8 [drm]
> >> [   50.691932]  drm_crtc_handle_vblank+0x1c/0x28 [drm]
> >> [   50.696980]  dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm]
> >> [   50.703886]  __handle_irq_event_percpu+0x60/0x14c
> >> [   50.708604]  handle_irq_event+0x4c/0xac
> >> [   50.712443]  handle_level_irq+0xc0/0x1b0
> >> [   50.716379]  generic_handle_irq+0x34/0x4c
> >> [   50.720392]  dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm]
> >> [   50.725727]  generic_handle_domain_irq+0x2c/0x44
> >> [   50.730357]  imx_irqsteer_irq_handler+0xc0/0x1a0
> >> [   50.734987]  generic_handle_domain_irq+0x2c/0x44
> >> [   50.739609]  gic_handle_irq+0x4c/0x114
> >> [   50.743362]  call_on_irq_stack+0x24/0x4c
> >> [   50.747298]  do_interrupt_handler+0x80/0x84
> >> [   50.751494]  el1_interrupt+0x34/0x68
> >> [   50.755082]  el1h_64_irq_handler+0x18/0x24
> >> [   50.759191]  el1h_64_irq+0x6c/0x70
> >> [   50.762597]  default_idle_call+0x28/0x3c (P)
> >> [   50.766879]  default_idle_call+0x24/0x3c (L)
> >> [   50.771163]  do_idle+0x200/0x25c
> >> [   50.774403]  cpu_startup_entry+0x34/0x3c
> >> [   50.778338]  kernel_init+0x0/0x1d8
> >> [   50.781752]  start_kernel+0x5c4/0x70c
> >> [   50.785427]  __primary_switched+0x88/0x90
> >>
> >>>
> >>>> +}
> >>>> +
> >>>> +static irqreturn_t
> >>>> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = dev_id;
> >>>> +  struct drm_crtc *crtc = &dc_crtc->base;
> >>>> +  unsigned long flags;
> >>>> +
> >>>> +  drm_crtc_handle_vblank(crtc);
> >>>> +
> >>>> +  spin_lock_irqsave(&crtc->dev->event_lock, flags);
> >>>> +  if (dc_crtc->event) {
> >>>> +          drm_crtc_send_vblank_event(crtc, dc_crtc->event);
> >>>> +          dc_crtc->event = NULL;
> >>>> +          drm_crtc_vblank_put(crtc);
> >>>> +  }
> >>>> +  spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> >>>> +
> >>>> +  return IRQ_HANDLED;
> >>>> +}
> >>>> +
> >>>> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = dev_id;
> >>>> +
> >>>> +  if (irq == dc_crtc->irq_dec_seqcomplete)
> >>>> +          complete(&dc_crtc->dec_seqcomplete_done);
> >>>> +  else if (irq == dc_crtc->irq_dec_shdld)
> >>>> +          complete(&dc_crtc->dec_shdld_done);
> >>>> +  else if (irq == dc_crtc->irq_ed_cont_shdld)
> >>>> +          complete(&dc_crtc->ed_cont_shdld_done);
> >>>> +  else if (irq == dc_crtc->irq_ed_safe_shdld)
> >>>> +          complete(&dc_crtc->ed_safe_shdld_done);
> >>>
> >>> Is there any reason to have a single multiplex handler instead of having
> >>> 4 separate handlers, each doing one simple thing?
> >>
> >> Just thought that one irq handler is feasible to implement the common
> >> handling logic. It's also ok to use 4 separate handlers with a little
> >> performance improvement. I may change to use 4 separate handlers by
> >> introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco.
> >
> > Just define them one by one, there is no need for a macro.
>
> Will do.
>
> >
> >>
> >>>
> >>>> +
> >>>> +  return IRQ_HANDLED;
> >>>> +}
> >>>> +
> >>>> +static const struct drm_crtc_funcs dc_crtc_funcs = {
> >>>> +  .reset                  = drm_atomic_helper_crtc_reset,
> >>>> +  .destroy                = drm_crtc_cleanup,
> >>>> +  .set_config             = drm_atomic_helper_set_config,
> >>>> +  .page_flip              = drm_atomic_helper_page_flip,
> >>>> +  .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> >>>> +  .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
> >>>> +  .get_vblank_counter     = dc_crtc_get_vblank_counter,
> >>>> +  .enable_vblank          = dc_crtc_enable_vblank,
> >>>> +  .disable_vblank         = dc_crtc_disable_vblank,
> >>>> +  .get_vblank_timestamp   = drm_crtc_vblank_helper_get_vblank_timestamp,
> >>>> +};
> >>>> +
> >>>> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
> >>>> +{
> >>>> +  struct drm_crtc *crtc = crtc_state->crtc;
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +
> >>>> +  spin_lock_irq(&crtc->dev->event_lock);
> >>>> +  if (crtc_state->event) {
> >>>> +          WARN_ON(drm_crtc_vblank_get(crtc));
> >>>> +          WARN_ON(dc_crtc->event);
> >>>> +          dc_crtc->event = crtc_state->event;
> >>>> +          crtc_state->event = NULL;
> >>>> +  }
> >>>> +  spin_unlock_irq(&crtc->dev->event_lock);
> >>>> +}
> >>>> +
> >>>> +static enum drm_mode_status
> >>>> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
> >>>> +{
> >>>> +  return dc_fg_check_clock(dc_crtc->fg, clk_khz);
> >>>> +}
> >>>
> >>> inline
> >>
> >> Will do.
> >>
> >>>
> >>>> +
> >>>> +static enum drm_mode_status
> >>>> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  enum drm_mode_status status;
> >>>> +
> >>>> +  status = dc_crtc_check_clock(dc_crtc, mode->clock);
> >>>> +  if (status != MODE_OK)
> >>>> +          return status;
> >>>> +
> >>>> +  if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
> >>>> +          return MODE_CLOCK_HIGH;
> >>>> +
> >>>> +  return MODE_OK;
> >>>> +}
> >>>> +
> >>>> +static int
> >>>> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_crtc_state *new_crtc_state =
> >>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
> >>>> +  struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  enum drm_mode_status status;
> >>>> +
> >>>> +  status = dc_crtc_check_clock(dc_crtc, adj->clock);
> >>>> +  if (status != MODE_OK)
> >>>> +          return -EINVAL;
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static void
> >>>> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_crtc_state *new_crtc_state =
> >>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  int idx, ret;
> >>>> +
> >>>> +  if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> >>>> +      !new_crtc_state->active)
> >>>> +          return;
> >>>> +
> >>>> +  if (!drm_dev_enter(crtc->dev, &idx))
> >>>> +          return;
> >>>> +
> >>>> +  /* request pixel engine power-on when CRTC starts to be active */
> >>>> +  ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
> >>>> +  if (ret)
> >>>> +          dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
> >>>> +                      ret);
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +}
> >>>> +
> >>>> +static void
> >>>> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_crtc_state *old_crtc_state =
> >>>> +                          drm_atomic_get_old_crtc_state(state, crtc);
> >>>> +  struct drm_crtc_state *new_crtc_state =
> >>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  int idx;
> >>>> +
> >>>> +  if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
> >>>> +      (!old_crtc_state->active && !new_crtc_state->active))
> >>>> +          return;
> >>>> +
> >>>> +  if (!drm_dev_enter(crtc->dev, &idx))
> >>>> +          goto out;
> >>>> +
> >>>> +  enable_irq(dc_crtc->irq_ed_cont_shdld);
> >>>> +
> >>>> +  /* flush plane update out to display */
> >>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> >>>> +
> >>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> >>>> +
> >>>> +  disable_irq(dc_crtc->irq_ed_cont_shdld);
> >>>> +
> >>>> +  DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +
> >>>> +out:
> >>>> +  dc_crtc_queue_state_event(new_crtc_state);
> >>>> +}
> >>>> +
> >>>> +static void
> >>>> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_crtc_state *new_crtc_state =
> >>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
> >>>> +  struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  enum dc_link_id cf_link;
> >>>> +  int idx, ret;
> >>>> +
> >>>> +  dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
> >>>> +
> >>>> +  drm_crtc_vblank_on(crtc);
> >>>> +
> >>>> +  if (!drm_dev_enter(crtc->dev, &idx))
> >>>> +          goto out;
> >>>> +
> >>>> +  /* request display engine power-on when CRTC is enabled */
> >>>> +  ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
> >>>> +  if (ret < 0)
> >>>> +          dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
> >>>> +                      ret);
> >>>> +
> >>>> +  enable_irq(dc_crtc->irq_dec_shdld);
> >>>> +  enable_irq(dc_crtc->irq_ed_cont_shdld);
> >>>> +  enable_irq(dc_crtc->irq_ed_safe_shdld);
> >>>> +
> >>>> +  dc_fg_cfg_videomode(dc_crtc->fg, adj);
> >>>> +
> >>>> +  dc_cf_framedimensions(dc_crtc->cf_cont,
> >>>> +                        adj->crtc_hdisplay, adj->crtc_vdisplay);
> >>>> +  dc_cf_framedimensions(dc_crtc->cf_safe,
> >>>> +                        adj->crtc_hdisplay, adj->crtc_vdisplay);
> >>>> +
> >>>> +  /* constframe in safety stream shows blue frame */
> >>>> +  dc_cf_constantcolor_blue(dc_crtc->cf_safe);
> >>>> +  cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
> >>>> +  dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
> >>>> +
> >>>> +  /* show CRTC background if no plane is enabled */
> >>>> +  if (new_crtc_state->plane_mask == 0) {
> >>>> +          /* constframe in content stream shows black frame */
> >>>> +          dc_cf_constantcolor_black(dc_crtc->cf_cont);
> >>>> +
> >>>> +          cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
> >>>> +          dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
> >>>> +  }
> >>>> +
> >>>> +  dc_fg_enable_clock(dc_crtc->fg);
> >>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
> >>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
> >>>> +  dc_fg_shdtokgen(dc_crtc->fg);
> >>>> +  dc_fg_enable(dc_crtc->fg);
> >>>> +
> >>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
> >>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
> >>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
> >>>> +
> >>>> +  disable_irq(dc_crtc->irq_ed_safe_shdld);
> >>>> +  disable_irq(dc_crtc->irq_ed_cont_shdld);
> >>>> +  disable_irq(dc_crtc->irq_dec_shdld);
> >>>> +
> >>>> +  DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
> >>>> +
> >>>> +  DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +
> >>>> +out:
> >>>> +  dc_crtc_queue_state_event(new_crtc_state);
> >>>> +}
> >>>> +
> >>>> +static void
> >>>> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_crtc_state *new_crtc_state =
> >>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  int idx, ret;
> >>>> +
> >>>> +  if (!drm_dev_enter(crtc->dev, &idx))
> >>>> +          goto out;
> >>>> +
> >>>> +  enable_irq(dc_crtc->irq_dec_seqcomplete);
> >>>> +  dc_fg_disable(dc_crtc->fg);
> >>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
> >>>> +  disable_irq(dc_crtc->irq_dec_seqcomplete);
> >>>> +
> >>>> +  dc_fg_disable_clock(dc_crtc->fg);
> >>>> +
> >>>> +  /* request pixel engine power-off as plane is off too */
> >>>> +  ret = pm_runtime_put(dc_crtc->pe->dev);
> >>>> +  if (ret)
> >>>> +          dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> >>>> +                      ret);
> >>>> +
> >>>> +  /* request display engine power-off when CRTC is disabled */
> >>>> +  ret = pm_runtime_put(dc_crtc->de->dev);
> >>>
> >>> Can this be expressed as a devlink between PE and DE?
> >>
> >> Looking at struct dc_{pe,de}, PE and DE are not dependent with each other,
> >> i.e., no consumer/supplier relationship(note that blit engine in PE can work
> >> by itself without DE) between them. So, it doesn't look right to link the two
> >> devices.
> >
> > Ack
> >
> >>
> >>>
> >>>> +  if (ret < 0)
> >>>> +          dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> >>>> +                      ret);
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +
> >>>> +out:
> >>>> +  drm_crtc_vblank_off(crtc);
> >>>> +
> >>>> +  spin_lock_irq(&crtc->dev->event_lock);
> >>>> +  if (new_crtc_state->event && !new_crtc_state->active) {
> >>>> +          drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
> >>>> +          new_crtc_state->event = NULL;
> >>>> +  }
> >>>> +  spin_unlock_irq(&crtc->dev->event_lock);
> >>>> +}
> >>>> +
> >>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  int ret;
> >>>> +
> >>>> +  if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
> >>>> +          return;
> >>>> +
> >>>> +  dc_fg_disable_clock(dc_crtc->fg);
> >>>> +
> >>>> +  if (pm_runtime_active(dc_crtc->pe->dev)) {
> >>>> +          ret = pm_runtime_put_sync(dc_crtc->pe->dev);
> >>>> +          if (ret)
> >>>> +                  dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
> >>>> +                              ret);
> >>>> +  }
> >>>> +
> >>>> +  ret = pm_runtime_put_sync(dc_crtc->de->dev);
> >>>> +  if (ret < 0)
> >>>> +          dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
> >>>> +                      ret);
> >>>> +}
> >>>> +
> >>>> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
> >>>> +                                   bool in_vblank_irq,
> >>>> +                                   int *vpos, int *hpos,
> >>>> +                                   ktime_t *stime, ktime_t *etime,
> >>>> +                                   const struct drm_display_mode *mode)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
> >>>> +  int vdisplay = mode->crtc_vdisplay;
> >>>> +  int vtotal = mode->crtc_vtotal;
> >>>> +  bool reliable;
> >>>> +  int line;
> >>>> +  int idx;
> >>>> +
> >>>> +  if (stime)
> >>>> +          *stime = ktime_get();
> >>>> +
> >>>> +  if (!drm_dev_enter(crtc->dev, &idx)) {
> >>>> +          reliable = false;
> >>>> +          *vpos = 0;
> >>>> +          *hpos = 0;
> >>>> +          goto out;
> >>>> +  }
> >>>> +
> >>>> +  /* line index starts with 0 for the first active output line */
> >>>> +  line = dc_fg_get_line_index(dc_crtc->fg);
> >>>> +
> >>>> +  if (line < vdisplay)
> >>>> +          /* active scanout area - positive */
> >>>> +          *vpos = line + 1;
> >>>> +  else
> >>>> +          /* inside vblank - negative */
> >>>> +          *vpos = line - (vtotal - 1);
> >>>> +
> >>>> +  *hpos = 0;
> >>>> +
> >>>> +  reliable = true;
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +out:
> >>>> +  if (etime)
> >>>> +          *etime = ktime_get();
> >>>> +
> >>>> +  return reliable;
> >>>> +}
> >>>> +
> >>>> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
> >>>> +  .mode_valid             = dc_crtc_mode_valid,
> >>>> +  .atomic_check           = dc_crtc_atomic_check,
> >>>> +  .atomic_begin           = dc_crtc_atomic_begin,
> >>>> +  .atomic_flush           = dc_crtc_atomic_flush,
> >>>> +  .atomic_enable          = dc_crtc_atomic_enable,
> >>>> +  .atomic_disable         = dc_crtc_atomic_disable,
> >>>> +  .get_scanout_position   = dc_crtc_get_scanout_position,
> >>>> +};
> >>>> +
> >>>> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
> >>>> +{
> >>>> +  struct {
> >>>> +          struct device *dev;
> >>>> +          unsigned int irq;
> >>>> +          irqreturn_t (*irq_handler)(int irq, void *dev_id);
> >>>> +  } irqs[DC_CRTC_IRQS] = {
> >>>> +          {
> >>>> +                  dc_crtc->de->dev,
> >>>> +                  dc_crtc->irq_dec_framecomplete,
> >>>> +                  dc_crtc_dec_framecomplete_irq_handler,
> >>>> +          }, {
> >>>> +                  dc_crtc->de->dev,
> >>>> +                  dc_crtc->irq_dec_seqcomplete,
> >>>> +                  dc_crtc_common_irq_handler,
> >>>> +          }, {
> >>>> +                  dc_crtc->de->dev,
> >>>> +                  dc_crtc->irq_dec_shdld,
> >>>> +                  dc_crtc_common_irq_handler,
> >>>> +          }, {
> >>>> +                  dc_crtc->ed_cont->dev,
> >>>> +                  dc_crtc->irq_ed_cont_shdld,
> >>>> +                  dc_crtc_common_irq_handler,
> >>>> +          }, {
> >>>> +                  dc_crtc->ed_safe->dev,
> >>>> +                  dc_crtc->irq_ed_safe_shdld,
> >>>> +                  dc_crtc_common_irq_handler,
> >>>> +          },
> >>>> +  };
> >>>> +  int i, ret;
> >>>> +
> >>>> +  for (i = 0; i < DC_CRTC_IRQS; i++) {
> >>>> +          struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
> >>>> +
> >>>> +          ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
> >>>> +                                 irqs[i].irq_handler, IRQF_NO_AUTOEN,
> >>>> +                                 dev_name(irqs[i].dev), dc_crtc);
> >>>> +          if (ret) {
> >>>> +                  dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
> >>>> +                          irqs[i].irq, ret);
> >>>> +                  return ret;
> >>>> +          }
> >>>> +
> >>>> +          irq->dc_crtc = dc_crtc;
> >>>> +          irq->irq = irqs[i].irq;
> >>>> +  }
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +  struct dc_de *de = dc_drm->de[crtc_index];
> >>>> +  struct dc_pe *pe = dc_drm->pe;
> >>>> +  struct dc_plane *dc_primary;
> >>>> +  int ret;
> >>>> +
> >>>> +  dc_crtc->de = de;
> >>>> +  dc_crtc->pe = pe;
> >>>> +
> >>>> +  init_completion(&dc_crtc->dec_seqcomplete_done);
> >>>> +  init_completion(&dc_crtc->dec_shdld_done);
> >>>> +  init_completion(&dc_crtc->ed_cont_shdld_done);
> >>>> +  init_completion(&dc_crtc->ed_safe_shdld_done);
> >>>> +
> >>>> +  dc_crtc->cf_cont = pe->cf_cont[crtc_index];
> >>>> +  dc_crtc->cf_safe = pe->cf_safe[crtc_index];
> >>>> +  dc_crtc->ed_cont = pe->ed_cont[crtc_index];
> >>>> +  dc_crtc->ed_safe = pe->ed_safe[crtc_index];
> >>>> +  dc_crtc->fg = de->fg;
> >>>> +
> >>>> +  dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
> >>>> +  dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
> >>>> +  dc_crtc->irq_dec_shdld = de->irq_shdld;
> >>>> +  dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
> >>>> +  dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
> >>>> +
> >>>> +  dc_primary = &dc_drm->dc_primary[crtc_index];
> >>>> +  ret = dc_plane_init(dc_drm, dc_primary);
> >>>> +  if (ret) {
> >>>> +          dev_err(drm->dev,
> >>>> +                  "failed to init primary plane for display engine%u: %d\n",
> >>>> +                  de->id, ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
> >>>> +
> >>>> +  ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
> >>>> +                                  NULL, &dc_crtc_funcs, NULL);
> >>>> +  if (ret)
> >>>> +          dev_err(drm->dev,
> >>>> +                  "failed to add CRTC for display engine%u: %d\n",
> >>>> +                  de->id, ret);
> >>>> +
> >>>> +  return ret;
> >>>> +}
> >>>> +
> >>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +
> >>>> +  return dc_crtc_request_irqs(drm, dc_crtc);
> >>>> +}
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
> >>>> index 17a44362118e..8a7b6c03a222 100644
> >>>> --- a/drivers/gpu/drm/imx/dc/dc-de.h
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
> >>>> @@ -13,6 +13,9 @@
> >>>>
> >>>>  #define DC_DISPLAYS       2
> >>>>
> >>>> +#define DC_FRAMEGEN_MAX_FRAME_INDEX       0x3ffff
> >>>> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ 300000
> >>>> +
> >>>>  struct dc_fg {
> >>>>    struct device *dev;
> >>>>    struct regmap *reg;
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
> >>>> index fd68861f770a..1e4b8afa3eec 100644
> >>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
> >>>> @@ -3,11 +3,254 @@
> >>>>   * Copyright 2024 NXP
> >>>>   */
> >>>>
> >>>> +#include <linux/clk.h>
> >>>> +#include <linux/component.h>
> >>>> +#include <linux/device.h>
> >>>> +#include <linux/dma-mapping.h>
> >>>> +#include <linux/mod_devicetable.h>
> >>>>  #include <linux/module.h>
> >>>> +#include <linux/of.h>
> >>>> +#include <linux/of_platform.h>
> >>>>  #include <linux/platform_device.h>
> >>>> +#include <linux/pm.h>
> >>>> +#include <linux/pm_runtime.h>
> >>>>
> >>>> +#include <drm/drm_atomic_helper.h>
> >>>> +#include <drm/drm_client_setup.h>
> >>>> +#include <drm/drm_crtc.h>
> >>>> +#include <drm/drm_drv.h>
> >>>> +#include <drm/drm_fbdev_dma.h>
> >>>> +#include <drm/drm_fourcc.h>
> >>>> +#include <drm/drm_gem_dma_helper.h>
> >>>> +#include <drm/drm_managed.h>
> >>>> +#include <drm/drm_modeset_helper.h>
> >>>> +#include <drm/drm_of.h>
> >>>> +
> >>>> +#include "dc-de.h"
> >>>>  #include "dc-drv.h"
> >>>>
> >>>> +struct dc_priv {
> >>>> +  struct drm_device *drm;
> >>>> +  struct clk *clk_cfg;
> >>>> +};
> >>>> +
> >>>> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
> >>>> +
> >>>> +static struct drm_driver dc_drm_driver = {
> >>>> +  .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> >>>> +  DRM_GEM_DMA_DRIVER_OPS,
> >>>> +  DRM_FBDEV_DMA_DRIVER_OPS,
> >>>> +  .fops = &dc_drm_driver_fops,
> >>>> +  .name = "imx8-dc",
> >>>> +  .desc = "i.MX8 DC DRM graphics",
> >>>> +  .date = "20240530",
> >>>> +  .major = 1,
> >>>> +  .minor = 0,
> >>>> +  .patchlevel = 0,
> >>>> +};
> >>>> +
> >>>> +static void
> >>>> +dc_add_components(struct device *dev, struct component_match **matchptr)
> >>>> +{
> >>>> +  struct device_node *child, *grandchild;
> >>>> +
> >>>> +  for_each_available_child_of_node(dev->of_node, child) {
> >>>> +          /* The interrupt controller is not a component. */
> >>>> +          if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
> >>>> +                  continue;
> >>>> +
> >>>> +          drm_of_component_match_add(dev, matchptr, component_compare_of,
> >>>> +                                     child);
> >>>> +
> >>>> +          for_each_available_child_of_node(child, grandchild)
> >>>> +                  drm_of_component_match_add(dev, matchptr,
> >>>> +                                             component_compare_of,
> >>>> +                                             grandchild);
> >>>> +  }
> >>>> +}
> >>>> +
> >>>> +static void dc_drm_component_unbind_all(void *ptr)
> >>>> +{
> >>>> +  struct dc_drm_device *dc_drm = ptr;
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +
> >>>> +  component_unbind_all(drm->dev, dc_drm);
> >>>> +}
> >>>> +
> >>>> +static int dc_drm_bind(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +  struct dc_drm_device *dc_drm;
> >>>> +  struct drm_device *drm;
> >>>> +  int ret;
> >>>> +
> >>>> +  dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
> >>>> +                              base);
> >>>> +  if (IS_ERR(dc_drm))
> >>>> +          return PTR_ERR(dc_drm);
> >>>> +
> >>>> +  drm = &dc_drm->base;
> >>>> +
> >>>> +  ret = component_bind_all(dev, dc_drm);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
> >>>> +                                 dc_drm);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = dc_kms_init(dc_drm);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = drm_dev_register(drm, 0);
> >>>> +  if (ret) {
> >>>> +          dev_err(dev, "failed to register drm device: %d\n", ret);
> >>>> +          goto err;
> >>>> +  }
> >>>> +
> >>>> +  drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
> >>>> +
> >>>> +  priv->drm = drm;
> >>>> +
> >>>> +  return 0;
> >>>> +
> >>>> +err:
> >>>> +  dc_kms_uninit(dc_drm);
> >>>> +
> >>>> +  return ret;
> >>>> +}
> >>>> +
> >>>> +static void dc_drm_unbind(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +  struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +  struct drm_crtc *crtc;
> >>>> +
> >>>> +  priv->drm = NULL;
> >>>> +  drm_dev_unplug(drm);
> >>>> +  dc_kms_uninit(dc_drm);
> >>>> +  drm_atomic_helper_shutdown(drm);
> >>>> +
> >>>> +  drm_for_each_crtc(crtc, drm)
> >>>> +          dc_crtc_disable_at_unbind(crtc);
> >>>
> >>> There should be no need for that. drm_atomic_helper_shutdown() should
> >>> disable all the CRTCs.
> >>
> >> In case DRM device is unplugged, drm_atomic_helper_shutdown does not
> >> effectively disable the CRTCs due to the bypassed logics wrapped by
> >> drm_dev_{enter,exit}.  That's why dc_crtc_disable_at_unbind() is called
> >> here to effectively disable the CRTCs.
> >
> > I see. I haven't faced drm_dev_unplug() earlier. I checked, the "not
> > disabled" behaviour is documented and all other drivers don't perform
> > any kind of cleanup afterwards. Thus I think it's safe to drop the
> > dc_crtc_disable_at_unbind() unless it causes any kind of HW issues.
>
> IMHO, it's a bit safer to set the HWs backing the CRTCs to a known state
> at the unbind stage by properly disabling them, in case there is any
> unknown SW or HW issue.  Plus, i.MX8qm/qxp SoCs don't provide reset
> signals to the Display Controllers(the resets DT property is optional),
> otherwise, this disablement function can be dropped(at least for now).

I'd say, a rule of thumb is "Behave similarly" to the existing
drivers. We've had a long-standing TODO item expressed in the DRM
docs: the drm_dev_unplug() vs drm_atomic_helper_shutdown(). I think
your issue should be solved in a generic way. One random idea would be
to have a sepecial drm_dev_unplug() version which is intended for the
normal shutdown path, it should mark the device as unavailable after
disabling the outputs and unregistering it from userspace.

>
> >
> >>
> >>>
> >>> Also, who is going to do drm_dev_unregister()? I don't see it in the
> >>> code.
> >>
> >> drm_dev_unplug() right above calls drm_dev_unregister().
> >>
> >>>
> >>>> +}
> >>>> +
> >>>> +static const struct component_master_ops dc_drm_ops = {
> >>>> +  .bind = dc_drm_bind,
> >>>> +  .unbind = dc_drm_unbind,
> >>>> +};
> >>>> +
> >>>> +static int dc_probe(struct platform_device *pdev)
> >>>> +{
> >>>> +  struct component_match *match = NULL;
> >>>> +  struct dc_priv *priv;
> >>>> +  int ret;
> >>>> +
> >>>> +  priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> >>>> +  if (!priv)
> >>>> +          return -ENOMEM;
> >>>> +
> >>>> +  priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
> >>>> +  if (IS_ERR(priv->clk_cfg))
> >>>> +          return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
> >>>> +                               "failed to get cfg clock\n");
> >>>> +
> >>>> +  dev_set_drvdata(&pdev->dev, priv);
> >>>> +
> >>>> +  ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = devm_pm_runtime_enable(&pdev->dev);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = devm_of_platform_populate(&pdev->dev);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  dc_add_components(&pdev->dev, &match);
> >>>> +
> >>>> +  ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
> >>>> +  if (ret)
> >>>> +          return dev_err_probe(&pdev->dev, ret,
> >>>> +                               "failed to add component master\n");
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static void dc_remove(struct platform_device *pdev)
> >>>> +{
> >>>> +  component_master_del(&pdev->dev, &dc_drm_ops);
> >>>> +}
> >>>> +
> >>>> +static int dc_runtime_suspend(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +
> >>>> +  clk_disable_unprepare(priv->clk_cfg);
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static int dc_runtime_resume(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +  int ret;
> >>>> +
> >>>> +  ret = clk_prepare_enable(priv->clk_cfg);
> >>>> +  if (ret)
> >>>> +          dev_err(dev, "failed to enable cfg clock: %d\n", ret);
> >>>> +
> >>>> +  return ret;
> >>>> +}
> >>>> +
> >>>> +static int dc_suspend(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +
> >>>> +  return drm_mode_config_helper_suspend(priv->drm);
> >>>> +}
> >>>> +
> >>>> +static int dc_resume(struct device *dev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>> +
> >>>> +  return drm_mode_config_helper_resume(priv->drm);
> >>>> +}
> >>>> +
> >>>> +static void dc_shutdown(struct platform_device *pdev)
> >>>> +{
> >>>> +  struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
> >>>> +
> >>>> +  drm_atomic_helper_shutdown(priv->drm);
> >>>> +}
> >>>> +
> >>>> +static const struct dev_pm_ops dc_pm_ops = {
> >>>> +  RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
> >>>> +  SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
> >>>> +};
> >>>> +
> >>>> +static const struct of_device_id dc_dt_ids[] = {
> >>>> +  { .compatible = "fsl,imx8qxp-dc", },
> >>>> +  { /* sentinel */ }
> >>>> +};
> >>>> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
> >>>> +
> >>>> +static struct platform_driver dc_driver = {
> >>>> +  .probe = dc_probe,
> >>>> +  .remove = dc_remove,
> >>>> +  .shutdown = dc_shutdown,
> >>>> +  .driver = {
> >>>> +          .name = "imx8-dc",
> >>>> +          .of_match_table = dc_dt_ids,
> >>>> +          .pm = pm_sleep_ptr(&dc_pm_ops),
> >>>> +  },
> >>>> +};
> >>>> +
> >>>>  static struct platform_driver * const dc_drivers[] = {
> >>>>    &dc_cf_driver,
> >>>>    &dc_de_driver,
> >>>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
> >>>>    &dc_lb_driver,
> >>>>    &dc_pe_driver,
> >>>>    &dc_tc_driver,
> >>>> +  &dc_driver,
> >>>>  };
> >>>>
> >>>>  static int __init dc_drm_init(void)
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>> index 3b11f4862c6c..39a771a13933 100644
> >>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>> @@ -6,19 +6,38 @@
> >>>>  #ifndef __DC_DRV_H__
> >>>>  #define __DC_DRV_H__
> >>>>
> >>>> +#include <linux/container_of.h>
> >>>>  #include <linux/platform_device.h>
> >>>>
> >>>>  #include <drm/drm_device.h>
> >>>> +#include <drm/drm_encoder.h>
> >>>>
> >>>>  #include "dc-de.h"
> >>>> +#include "dc-kms.h"
> >>>>  #include "dc-pe.h"
> >>>>
> >>>>  struct dc_drm_device {
> >>>>    struct drm_device base;
> >>>> +  struct dc_crtc dc_crtc[DC_DISPLAYS];
> >>>> +  struct dc_plane dc_primary[DC_DISPLAYS];
> >>>> +  struct drm_encoder encoder[DC_DISPLAYS];
> >>>>    struct dc_de *de[DC_DISPLAYS];
> >>>>    struct dc_pe *pe;
> >>>>  };
> >>>>
> >>>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
> >>>> +{
> >>>> +  return container_of(drm, struct dc_drm_device, base);
> >>>> +}
> >>>> +
> >>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
> >>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
> >>>> +
> >>>> +int dc_kms_init(struct dc_drm_device *dc_drm);
> >>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
> >>>> +
> >>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
> >>>> +
> >>>>  extern struct platform_driver dc_cf_driver;
> >>>>  extern struct platform_driver dc_ed_driver;
> >>>>  extern struct platform_driver dc_de_driver;
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
> >>>> new file mode 100644
> >>>> index 000000000000..2b18aa37a4a8
> >>>> --- /dev/null
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
> >>>> @@ -0,0 +1,143 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>> +/*
> >>>> + * Copyright 2024 NXP
> >>>> + */
> >>>> +
> >>>> +#include <linux/of.h>
> >>>> +#include <linux/of_graph.h>
> >>>> +
> >>>> +#include <drm/drm_atomic_helper.h>
> >>>> +#include <drm/drm_bridge.h>
> >>>> +#include <drm/drm_bridge_connector.h>
> >>>> +#include <drm/drm_connector.h>
> >>>> +#include <drm/drm_crtc.h>
> >>>> +#include <drm/drm_device.h>
> >>>> +#include <drm/drm_encoder.h>
> >>>> +#include <drm/drm_gem_framebuffer_helper.h>
> >>>> +#include <drm/drm_mode_config.h>
> >>>> +#include <drm/drm_print.h>
> >>>> +#include <drm/drm_probe_helper.h>
> >>>> +#include <drm/drm_simple_kms_helper.h>
> >>>> +#include <drm/drm_vblank.h>
> >>>> +
> >>>> +#include "dc-de.h"
> >>>> +#include "dc-drv.h"
> >>>> +#include "dc-kms.h"
> >>>> +
> >>>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
> >>>> +  .fb_create = drm_gem_fb_create,
> >>>> +  .atomic_check = drm_atomic_helper_check,
> >>>> +  .atomic_commit = drm_atomic_helper_commit,
> >>>> +};
> >>>> +
> >>>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
> >>>> +                                  int crtc_index)
> >>>> +{
> >>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +  struct drm_crtc *crtc = &dc_crtc->base;
> >>>> +  struct drm_connector *connector;
> >>>> +  struct device *dev = drm->dev;
> >>>> +  struct drm_encoder *encoder;
> >>>> +  struct drm_bridge *bridge;
> >>>> +  int ret;
> >>>> +
> >>>> +  bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
> >>>> +                                  0, 0);
> >>>> +  if (IS_ERR(bridge)) {
> >>>> +          ret = PTR_ERR(bridge);
> >>>> +          if (ret == -ENODEV)
> >>>> +                  return 0;
> >>>> +
> >>>> +          return dev_err_probe(dev, ret,
> >>>> +                               "failed to find bridge for CRTC%u\n",
> >>>> +                               crtc->index);
> >>>> +  }
> >>>> +
> >>>> +  encoder = &dc_drm->encoder[crtc_index];
> >>>> +  ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
> >>>> +  if (ret) {
> >>>> +          dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
> >>>> +                  crtc->index, ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  encoder->possible_crtcs = drm_crtc_mask(crtc);
> >>>> +
> >>>> +  ret = drm_bridge_attach(encoder, bridge, NULL,
> >>>> +                          DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> >>>> +  if (ret) {
> >>>> +          dev_err(dev,
> >>>> +                  "failed to attach bridge to encoder for CRTC%u: %d\n",
> >>>> +                  crtc->index, ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  connector = drm_bridge_connector_init(drm, encoder);
> >>>> +  if (IS_ERR(connector)) {
> >>>> +          ret = PTR_ERR(connector);
> >>>> +          dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
> >>>> +                  crtc->index, ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  ret = drm_connector_attach_encoder(connector, encoder);
> >>>> +  if (ret)
> >>>> +          dev_err(dev,
> >>>> +                  "failed to attach encoder to connector for CRTC%u: %d\n",
> >>>> +                  crtc->index, ret);
> >>>> +
> >>>> +  return ret;
> >>>> +}
> >>>> +
> >>>> +int dc_kms_init(struct dc_drm_device *dc_drm)
> >>>> +{
> >>>> +  struct drm_device *drm = &dc_drm->base;
> >>>> +  int ret, i;
> >>>> +
> >>>> +  ret = drmm_mode_config_init(drm);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  drm->mode_config.min_width = 60;
> >>>> +  drm->mode_config.min_height = 60;
> >>>> +  drm->mode_config.max_width = 8192;
> >>>> +  drm->mode_config.max_height = 8192;
> >>>> +  drm->mode_config.funcs = &dc_drm_mode_config_funcs;
> >>>> +
> >>>> +  drm->vblank_disable_immediate = true;
> >>>> +  drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
> >>>> +
> >>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
> >>>> +          ret = dc_crtc_init(dc_drm, i);
> >>>> +          if (ret)
> >>>> +                  return ret;
> >>>> +
> >>>> +          ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
> >>>> +          if (ret)
> >>>> +                  return ret;
> >>>> +  }
> >>>> +
> >>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
> >>>> +          ret = dc_crtc_post_init(dc_drm, i);
> >>>
> >>> Can you use .late_register for this?
> >>
> >> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
> >> additional userspace interfaces like debugfs interfaces. And, it seems that
> >> everyone implementing this uses it to add debugfs interfaces. So, it will
> >> kind of abuse it to do CRTC post initialization.
> >
> > Why can't they be requested earlier then?
>
> If I request them earlier in dc_crtc_init(), then they cannot be freed by
> devm_irq_release() when devm_drm_of_get_bridge() called by
> dc_kms_init_encoder_per_crtc() returns -EPROBE_DEFER(which means failing
> to find the first DRM bridge for the CRTC).  Why can't they be freed by
> devm_irq_release()?  Because they are requested by the devices of ExtDsts
> and Display Engines and their drivers are not removed during the probe
> deferral dance.  Furthermore, -EPROBE_DEFER won't be returned after
> dc_crtc_post_init() since the later called drm_vblank_init() doesn't
> return -EPROBE_DEFER anyway, so it's fine to call dc_crtc_post_init() here.
>
> I met the irq free issue on my i.MX8qxp MEK board before, i.e., -EBUSY is
> returned when requesting them again, so it's tested.

A typical solution is to request all resources before binding the
device as a component. Don't tell me that your interrupt controller is
another component of the DRM device :-)

>
> >
> >>
> >>>
> >>>> +          if (ret)
> >>>> +                  return ret;
> >>>> +  }
> >>>> +
> >>>> +  ret = drm_vblank_init(drm, DC_DISPLAYS);
> >>>> +  if (ret) {
> >>>> +          dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  drm_mode_config_reset(drm);
> >>>> +
> >>>> +  drm_kms_helper_poll_init(drm);
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
> >>>> +{
> >>>> +  drm_kms_helper_poll_fini(&dc_drm->base);
> >>>> +}
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
> >>>> new file mode 100644
> >>>> index 000000000000..57f6e0c15f57
> >>>> --- /dev/null
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
> >>>> @@ -0,0 +1,58 @@
> >>>> +/* SPDX-License-Identifier: GPL-2.0+ */
> >>>> +/*
> >>>> + * Copyright 2024 NXP
> >>>> + */
> >>>> +
> >>>> +#ifndef __DC_KMS_H__
> >>>> +#define __DC_KMS_H__
> >>>> +
> >>>> +#include <linux/completion.h>
> >>>> +
> >>>> +#include <drm/drm_crtc.h>
> >>>> +#include <drm/drm_plane.h>
> >>>> +#include <drm/drm_vblank.h>
> >>>> +
> >>>> +#include "dc-de.h"
> >>>> +#include "dc-fu.h"
> >>>> +#include "dc-pe.h"
> >>>> +
> >>>> +#define DC_CRTC_IRQS      5
> >>>> +
> >>>> +struct dc_crtc_irq {
> >>>> +  struct dc_crtc *dc_crtc;
> >>>> +  unsigned int irq;
> >>>> +};
> >>>> +
> >>>
> >>> Please provide some documentation for the structure in the form of the
> >>> kerneldoc. E.g. what is the difference between ed_cont and ed_safe?
> >>
> >> Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the
> >> difference between ed_cont(content stream) and ed_safe(safety stream).
> >>
> >>> The de and fg pointers are global, please don't cache them
> >>> unnecessarily.
> >>
> >> Global? I don't catch your meaning, sorry. To me, it's handy to access de
> >> and fg via the two pointers in struct dc_crtc.
> >
> > I had to spend some time understanding if they are per-CRTC or if there
> > are a single instances of those subdevices. Thus I suggest to access
>
> They are per-CRTC.  One Display Controller contains two Display Engines,
> hence two CRTCs.

I'm sorry, I probably meant PE here. DE (and consequently FG) are
per-CRTC indeed.

>
> > them through dc_drm_device. You can ignore this suggestion though.
>
> I'll ignore this.  Thanks anyway.
>
> >
> >>
> >>>
> >>>> +struct dc_crtc {
> >>>> +  struct drm_crtc base;
> >>>> +  struct dc_de *de;
> >>>> +  struct dc_pe *pe;
> >>>> +  struct dc_cf *cf_cont;
> >>>> +  struct dc_cf *cf_safe;
> >>>> +  struct dc_ed *ed_cont;
> >>>> +  struct dc_ed *ed_safe;
> >>>> +  struct dc_fg *fg;
> >>>> +  unsigned int irq_dec_framecomplete;
> >>>> +  unsigned int irq_dec_seqcomplete;
> >>>> +  unsigned int irq_dec_shdld;
> >>>> +  unsigned int irq_ed_cont_shdld;
> >>>> +  unsigned int irq_ed_safe_shdld;
> >>>> +  struct completion dec_seqcomplete_done;
> >>>> +  struct completion dec_shdld_done;
> >>>> +  struct completion ed_safe_shdld_done;
> >>>> +  struct completion ed_cont_shdld_done;
> >>>> +  struct drm_pending_vblank_event *event;
> >>>> +  struct dc_crtc_irq irqs[DC_CRTC_IRQS];
> >>>> +};
> >>>> +
> >>>> +struct dc_plane {
> >>>> +  struct drm_plane base;
> >>>> +  struct dc_fu *fu;
> >>>> +  struct dc_cf *cf;
> >>>> +  struct dc_lb *lb;
> >>>> +  struct dc_ed *ed;
> >>>> +};
> >>>> +
> >>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
> >>>> +
> >>>> +#endif /* __DC_KMS_H__ */
> >>>> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
> >>>> new file mode 100644
> >>>> index 000000000000..78d0d2cd3451
> >>>> --- /dev/null
> >>>> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
> >>>> @@ -0,0 +1,241 @@
> >>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>> +/*
> >>>> + * Copyright 2024 NXP
> >>>> + */
> >>>> +
> >>>> +#include <linux/container_of.h>
> >>>> +
> >>>> +#include <drm/drm_atomic.h>
> >>>> +#include <drm/drm_atomic_helper.h>
> >>>> +#include <drm/drm_atomic_state_helper.h>
> >>>> +#include <drm/drm_crtc.h>
> >>>> +#include <drm/drm_drv.h>
> >>>> +#include <drm/drm_fb_dma_helper.h>
> >>>> +#include <drm/drm_fourcc.h>
> >>>> +#include <drm/drm_framebuffer.h>
> >>>> +#include <drm/drm_gem_atomic_helper.h>
> >>>> +#include <drm/drm_plane_helper.h>
> >>>> +#include <drm/drm_print.h>
> >>>> +
> >>>> +#include "dc-drv.h"
> >>>> +#include "dc-fu.h"
> >>>> +#include "dc-kms.h"
> >>>> +
> >>>> +#define DC_PLANE_MAX_PITCH        0x10000
> >>>> +#define DC_PLANE_MAX_PIX_CNT      8192
> >>>> +
> >>>> +#define dc_plane_dbg(plane, fmt, ...)                                     \
> >>>> +do {                                                                      \
> >>>> +  typeof(plane) _plane = (plane);                                 \
> >>>> +  drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,                  \
> >>>> +              _plane->base.id, _plane->name, ##__VA_ARGS__);      \
> >>>> +} while (0)
> >>>> +
> >>>> +static const uint32_t dc_plane_formats[] = {
> >>>> +  DRM_FORMAT_XRGB8888,
> >>>> +};
> >>>> +
> >>>> +static const struct drm_plane_funcs dc_plane_funcs = {
> >>>> +  .update_plane           = drm_atomic_helper_update_plane,
> >>>> +  .disable_plane          = drm_atomic_helper_disable_plane,
> >>>> +  .destroy                = drm_plane_cleanup,
> >>>> +  .reset                  = drm_atomic_helper_plane_reset,
> >>>> +  .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> >>>> +  .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
> >>>> +};
> >>>> +
> >>>> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
> >>>> +{
> >>>> +  return container_of(plane, struct dc_plane, base);
> >>>> +}
> >>>> +
> >>>> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
> >>>> +                                  struct drm_crtc_state *crtc_state)
> >>>> +{
> >>>> +  if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
> >>>> +      state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
> >>>> +      state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
> >>>> +          dc_plane_dbg(state->plane, "no off screen\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
> >>>> +{
> >>>> +  int src_h = drm_rect_height(&state->src) >> 16;
> >>>> +  int src_w = drm_rect_width(&state->src) >> 16;
> >>>> +
> >>>> +  if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
> >>>> +          dc_plane_dbg(state->plane, "invalid source resolution\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static int dc_plane_check_fb(struct drm_plane_state *state)
> >>>> +{
> >>>> +  struct drm_framebuffer *fb = state->fb;
> >>>> +  dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
> >>>> +
> >>>> +  /* base address alignment */
> >>>> +  if (baseaddr & 0x3) {
> >>>> +          dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  /* pitches[0] range */
> >>>> +  if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
> >>>> +          dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  /* pitches[0] alignment */
> >>>> +  if (fb->pitches[0] & 0x3) {
> >>>> +          dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> +
> >>>> +static int
> >>>> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_plane_state *plane_state =
> >>>> +                          drm_atomic_get_new_plane_state(state, plane);
> >>>> +  struct drm_crtc_state *crtc_state;
> >>>> +  int ret;
> >>>> +
> >>>> +  /* ok to disable */
> >>>> +  if (!plane_state->fb)
> >>>> +          return 0;
> >>>> +
> >>>> +  if (!plane_state->crtc) {
> >>>> +          dc_plane_dbg(plane, "no CRTC in plane state\n");
> >>>> +          return -EINVAL;
> >>>> +  }
> >>>> +
> >>>> +  crtc_state =
> >>>> +          drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
> >>>> +  if (WARN_ON(!crtc_state))
> >>>> +          return -EINVAL;
> >>>> +
> >>>> +  ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
> >>>> +                                            DRM_PLANE_NO_SCALING,
> >>>> +                                            DRM_PLANE_NO_SCALING,
> >>>> +                                            true, false);
> >>>> +  if (ret) {
> >>>> +          dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
> >>>> +          return ret;
> >>>> +  }
> >>>> +
> >>>> +  ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  ret = dc_plane_check_max_source_resolution(plane_state);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  return dc_plane_check_fb(plane_state);
> >>>> +}
> >>>> +
> >>>> +static void
> >>>> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct drm_plane_state *new_state =
> >>>> +                          drm_atomic_get_new_plane_state(state, plane);
> >>>> +  struct dc_plane *dplane = to_dc_plane(plane);
> >>>> +  struct drm_framebuffer *fb = new_state->fb;
> >>>> +  const struct dc_fu_ops *fu_ops;
> >>>> +  struct dc_lb *lb = dplane->lb;
> >>>> +  struct dc_fu *fu = dplane->fu;
> >>>> +  dma_addr_t baseaddr;
> >>>> +  int src_w, src_h;
> >>>> +  int idx;
> >>>> +
> >>>> +  if (!drm_dev_enter(plane->dev, &idx))
> >>>> +          return;
> >>>> +
> >>>> +  src_w = drm_rect_width(&new_state->src) >> 16;
> >>>> +  src_h = drm_rect_height(&new_state->src) >> 16;
> >>>> +
> >>>> +  baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
> >>>> +
> >>>> +  fu_ops = dc_fu_get_ops(dplane->fu);
> >>>> +
> >>>> +  fu_ops->set_layerblend(fu, lb);
> >>>> +  fu_ops->set_burstlength(fu, baseaddr);
> >>>> +  fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
> >>>> +  fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
> >>>> +  fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
> >>>> +  fu_ops->set_framedimensions(fu, src_w, src_h);
> >>>> +  fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
> >>>> +  fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
> >>>> +
> >>>> +  dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
> >>>> +
> >>>> +  dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
> >>>> +  dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
> >>>> +  dc_lb_mode(lb, LB_BLEND);
> >>>> +  dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
> >>>> +  dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
> >>>> +
> >>>> +  dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
> >>>> +
> >>>> +  /* set ExtDst's source to LayerBlend */
> >>>> +  dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +}
> >>>> +
> >>>> +static void dc_plane_atomic_disable(struct drm_plane *plane,
> >>>> +                              struct drm_atomic_state *state)
> >>>> +{
> >>>> +  struct dc_plane *dplane = to_dc_plane(plane);
> >>>> +  const struct dc_fu_ops *fu_ops;
> >>>> +  int idx;
> >>>> +
> >>>> +  if (!drm_dev_enter(plane->dev, &idx))
> >>>> +          return;
> >>>> +
> >>>> +  /* disable fetchunit in shadow */
> >>>> +  fu_ops = dc_fu_get_ops(dplane->fu);
> >>>> +  fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
> >>>> +
> >>>> +  /* set ExtDst's source to ConstFrame */
> >>>> +  dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
> >>>> +
> >>>> +  drm_dev_exit(idx);
> >>>> +}
> >>>> +
> >>>> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
> >>>> +  .atomic_check = dc_plane_atomic_check,
> >>>> +  .atomic_update = dc_plane_atomic_update,
> >>>> +  .atomic_disable = dc_plane_atomic_disable,
> >>>> +};
> >>>> +
> >>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
> >>>> +{
> >>>> +  struct drm_plane *plane = &dc_plane->base;
> >>>> +  int ret;
> >>>> +
> >>>> +  ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
> >>>> +                                 dc_plane_formats,
> >>>> +                                 ARRAY_SIZE(dc_plane_formats),
> >>>> +                                 NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
> >>>> +  if (ret)
> >>>> +          return ret;
> >>>> +
> >>>> +  drm_plane_helper_add(plane, &dc_plane_helper_funcs);
> >>>> +
> >>>> +  dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
> >>>> +  dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
> >>>> +  dc_plane->lb = dc_drm->pe->lb[plane->index];
> >>>> +  dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
> >>>> +
> >>>> +  return 0;
> >>>> +}
> >>>> --
> >>>> 2.34.1
> >>>>
> >>>
> >>
> >> --
> >> Regards,
> >> Liu Ying
> >
>
> --
> Regards,
> Liu Ying



-- 
With best wishes
Dmitry


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-13 13:28           ` Dmitry Baryshkov
@ 2024-12-16  6:29             ` Liu Ying
  2024-12-16  9:44               ` Dmitry Baryshkov
  0 siblings, 1 reply; 35+ messages in thread
From: Liu Ying @ 2024-12-16  6:29 UTC (permalink / raw)
  To: Dmitry Baryshkov
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On 12/13/2024, Dmitry Baryshkov wrote:
> On Fri, 13 Dec 2024 at 08:06, Liu Ying <victor.liu@nxp.com> wrote:
>>
>> On 12/12/2024, Dmitry Baryshkov wrote:
>>> On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote:
>>>> On 12/10/2024, Dmitry Baryshkov wrote:
>>>>> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
>>>>>> i.MX8qxp Display Controller(DC) is comprised of three main components that
>>>>>> include a blit engine for 2D graphics accelerations, display controller for
>>>>>> display output processing, as well as a command sequencer.  Add kernel
>>>>>> mode setting support for the display controller part with two CRTCs and
>>>>>> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
>>>>>> registers of the display controller are accessed without command sequencer
>>>>>> involved, instead just by using CPU.  The command sequencer is supposed to
>>>>>> be used by the blit engine.
>>>>>>
>>>>>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
>>>>>> ---
>>>>>> v6:
>>>>>> * No change.
>>>>>>
>>>>>> v5:
>>>>>> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
>>>>>>
>>>>>> v4:
>>>>>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
>>>>>>   function calls from KMS routine to initialization stage. (Dmitry)
>>>>>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
>>>>>>   appropriate .h header files or .c source files. (Dmitry)
>>>>>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
>>>>>> * Drop dc_drm->pe_rpm_count. (Dmitry)
>>>>>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
>>>>>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
>>>>>>   Instead, put it in struct dc_crtc.  (Dmitry)
>>>>>> * Call devm_request_irq() to request IRQs, instead of using drmm action.
>>>>>>   (Dmitry)
>>>>>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
>>>>>> * Select DRM_CLIENT_SELECTION due to rebase.
>>>>>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
>>>>>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
>>>>>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
>>>>>>   rebase.
>>>>>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
>>>>>>   register dc_drm_component_unbind_all() action.
>>>>>> * Request interrupts in dc_crtc_post_init() after encoder initialization to
>>>>>>   make sure next bridge is found first.
>>>>>>
>>>>>> v3:
>>>>>> * No change.
>>>>>>
>>>>>> v2:
>>>>>> * Find next bridge from TCon's port.
>>>>>> * Drop drm/drm_module.h include from dc-drv.c.
>>>>>>
>>>>>>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
>>>>>>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
>>>>>>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
>>>>>>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
>>>>>>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
>>>>>>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
>>>>>>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
>>>>>>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
>>>>>>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
>>>>>>  9 files changed, 1274 insertions(+), 2 deletions(-)
>>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
>>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
>>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
>>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
>>>>>>
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/Kconfig b/drivers/gpu/drm/imx/dc/Kconfig
>>>>>> index 1fc84c7475de..415993207f2e 100644
>>>>>> --- a/drivers/gpu/drm/imx/dc/Kconfig
>>>>>> +++ b/drivers/gpu/drm/imx/dc/Kconfig
>>>>>> @@ -1,6 +1,11 @@
>>>>>>  config DRM_IMX8_DC
>>>>>>    tristate "Freescale i.MX8 Display Controller Graphics"
>>>>>>    depends on DRM && COMMON_CLK && OF && (ARCH_MXC || COMPILE_TEST)
>>>>>> +  select DRM_CLIENT_SELECTION
>>>>>> +  select DRM_GEM_DMA_HELPER
>>>>>> +  select DRM_KMS_HELPER
>>>>>> +  select DRM_DISPLAY_HELPER
>>>>>> +  select DRM_BRIDGE_CONNECTOR
>>>>>>    select GENERIC_IRQ_CHIP
>>>>>>    select REGMAP
>>>>>>    select REGMAP_MMIO
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/Makefile b/drivers/gpu/drm/imx/dc/Makefile
>>>>>> index 1ce3e8a8db22..b9d33c074984 100644
>>>>>> --- a/drivers/gpu/drm/imx/dc/Makefile
>>>>>> +++ b/drivers/gpu/drm/imx/dc/Makefile
>>>>>> @@ -1,6 +1,7 @@
>>>>>>  # SPDX-License-Identifier: GPL-2.0
>>>>>>
>>>>>> -imx8-dc-drm-objs := dc-cf.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o dc-fu.o \
>>>>>> -              dc-fw.o dc-ic.o dc-lb.o dc-pe.o dc-tc.o
>>>>>> +imx8-dc-drm-objs := dc-cf.o dc-crtc.o dc-de.o dc-drv.o dc-ed.o dc-fg.o dc-fl.o \
>>>>>> +              dc-fu.o dc-fw.o dc-ic.o dc-kms.o dc-lb.o dc-pe.o \
>>>>>> +              dc-plane.o dc-tc.o
>>>>>>
>>>>>>  obj-$(CONFIG_DRM_IMX8_DC) += imx8-dc-drm.o
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-crtc.c b/drivers/gpu/drm/imx/dc/dc-crtc.c
>>>>>> new file mode 100644
>>>>>> index 000000000000..fd6daa1807d8
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-crtc.c
>>>>>> @@ -0,0 +1,558 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>>>> +/*
>>>>>> + * Copyright 2024 NXP
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/completion.h>
>>>>>> +#include <linux/container_of.h>
>>>>>> +#include <linux/interrupt.h>
>>>>>> +#include <linux/irqreturn.h>
>>>>>> +#include <linux/pm_runtime.h>
>>>>>> +#include <linux/spinlock.h>
>>>>>> +
>>>>>> +#include <drm/drm_atomic.h>
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_atomic_state_helper.h>
>>>>>> +#include <drm/drm_crtc.h>
>>>>>> +#include <drm/drm_device.h>
>>>>>> +#include <drm/drm_drv.h>
>>>>>> +#include <drm/drm_modes.h>
>>>>>> +#include <drm/drm_modeset_helper_vtables.h>
>>>>>> +#include <drm/drm_plane.h>
>>>>>> +#include <drm/drm_print.h>
>>>>>> +#include <drm/drm_vblank.h>
>>>>>> +
>>>>>> +#include "dc-de.h"
>>>>>> +#include "dc-drv.h"
>>>>>> +#include "dc-kms.h"
>>>>>> +#include "dc-pe.h"
>>>>>> +
>>>>>> +#define dc_crtc_dbg(crtc, fmt, ...)                                       \
>>>>>> +do {                                                                      \
>>>>>> +  typeof(crtc) _crtc = (crtc);                                    \
>>>>>
>>>>> Use exact type instead of typeof.
>>>>
>>>> Will do.
>>>>
>>>>>
>>>>>> +  drm_dbg_kms(_crtc->dev, "[CRTC:%d:%s] " fmt,                    \
>>>>>> +              _crtc->base.id, _crtc->name, ##__VA_ARGS__);        \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +#define dc_crtc_err(crtc, fmt, ...)                                       \
>>>>>> +do {                                                                      \
>>>>>> +  typeof(crtc) _crtc = (crtc);                                    \
>>>>>> +  drm_err(_crtc->dev, "[CRTC:%d:%s] " fmt,                        \
>>>>>> +          _crtc->base.id, _crtc->name, ##__VA_ARGS__);            \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +#define DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(c)                            \
>>>>>> +do {                                                                      \
>>>>>> +  unsigned long ret;                                              \
>>>>>> +  ret = wait_for_completion_timeout(&dc_crtc->c, HZ);             \
>>>>>> +  if (ret == 0)                                                   \
>>>>>> +          dc_crtc_err(crtc, "%s: wait for " #c " timeout\n",      \
>>>>>> +                                                  __func__);      \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +#define DC_CRTC_CHECK_FRAMEGEN_FIFO(fg)                                   \
>>>>>> +do {                                                                      \
>>>>>> +  typeof(fg) _fg = (fg);                                          \
>>>>>> +  if (dc_fg_secondary_requests_to_read_empty_fifo(_fg)) {         \
>>>>>> +          dc_fg_secondary_clear_channel_status(_fg);              \
>>>>>> +          dc_crtc_err(crtc, "%s: FrameGen FIFO empty\n",          \
>>>>>> +                                                  __func__);      \
>>>>>> +  }                                                               \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +#define DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(fg)                    \
>>>>>> +do {                                                                      \
>>>>>> +  if (dc_fg_wait_for_secondary_syncup(fg))                        \
>>>>>> +          dc_crtc_err(crtc,                                       \
>>>>>> +                  "%s: FrameGen secondary channel isn't syncup\n",\
>>>>>> +                                                  __func__);      \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +static inline struct dc_crtc *to_dc_crtc(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +  return container_of(crtc, struct dc_crtc, base);
>>>>>> +}
>>>>>> +
>>>>>> +static u32 dc_crtc_get_vblank_counter(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +
>>>>>> +  return dc_fg_get_frame_index(dc_crtc->fg);
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_crtc_enable_vblank(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +
>>>>>> +  enable_irq(dc_crtc->irq_dec_framecomplete);
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_crtc_disable_vblank(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +
>>>>>> +  disable_irq_nosync(dc_crtc->irq_dec_framecomplete);
>>>>>
>>>>> Why is it _nosync?
>>>>
>>>> Because disable_irq() can only be called from preemptible code according to
>>>> it's kerneldoc. If I use disable_irq() here, I get this with
>>>> CONFIG_DEBUG_ATOMIC_SLEEP enabled:
>>>
>>> Please add a one-line comment, like "nosync because of the atomic
>>> context"
>>
>> Will do.
>>
>>>
>>>>
>>>> [   50.607503] BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
>>>> [   50.615691] in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper/0
>>>> [   50.623527] preempt_count: 10003, expected: 0
>>>> [   50.627888] RCU nest depth: 0, expected: 0
>>>> [   50.631993] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.13.0-rc2-next-20241209-00026-g800cb5b7df74 #1407
>>>> [   50.642092] Hardware name: Freescale i.MX8QXP MEK (DT)
>>>> [   50.647237] Call trace:
>>>> [   50.649687]  show_stack+0x18/0x24 (C)
>>>> [   50.653369]  dump_stack_lvl+0x80/0xb4
>>>> [   50.657043]  dump_stack+0x18/0x24
>>>> [   50.660370]  __might_resched+0x114/0x170
>>>> [   50.664306]  __might_sleep+0x48/0x98
>>>> [   50.667894]  disable_irq+0x24/0x60
>>>> [   50.671308]  dc_crtc_disable_vblank+0x14/0x20 [imx8_dc_drm]
>>>> [   50.676912]  drm_vblank_disable_and_save+0xc0/0x108 [drm]
>>>> [   50.682533]  vblank_disable_fn+0x78/0x9c [drm]
>>>> [   50.687146]  drm_handle_vblank+0x238/0x2e8 [drm]
>>>> [   50.691932]  drm_crtc_handle_vblank+0x1c/0x28 [drm]
>>>> [   50.696980]  dc_crtc_irq_handler_dec_framecomplete+0x1c/0x6c [imx8_dc_drm]
>>>> [   50.703886]  __handle_irq_event_percpu+0x60/0x14c
>>>> [   50.708604]  handle_irq_event+0x4c/0xac
>>>> [   50.712443]  handle_level_irq+0xc0/0x1b0
>>>> [   50.716379]  generic_handle_irq+0x34/0x4c
>>>> [   50.720392]  dc_ic_irq_handler+0x128/0x160 [imx8_dc_drm]
>>>> [   50.725727]  generic_handle_domain_irq+0x2c/0x44
>>>> [   50.730357]  imx_irqsteer_irq_handler+0xc0/0x1a0
>>>> [   50.734987]  generic_handle_domain_irq+0x2c/0x44
>>>> [   50.739609]  gic_handle_irq+0x4c/0x114
>>>> [   50.743362]  call_on_irq_stack+0x24/0x4c
>>>> [   50.747298]  do_interrupt_handler+0x80/0x84
>>>> [   50.751494]  el1_interrupt+0x34/0x68
>>>> [   50.755082]  el1h_64_irq_handler+0x18/0x24
>>>> [   50.759191]  el1h_64_irq+0x6c/0x70
>>>> [   50.762597]  default_idle_call+0x28/0x3c (P)
>>>> [   50.766879]  default_idle_call+0x24/0x3c (L)
>>>> [   50.771163]  do_idle+0x200/0x25c
>>>> [   50.774403]  cpu_startup_entry+0x34/0x3c
>>>> [   50.778338]  kernel_init+0x0/0x1d8
>>>> [   50.781752]  start_kernel+0x5c4/0x70c
>>>> [   50.785427]  __primary_switched+0x88/0x90
>>>>
>>>>>
>>>>>> +}
>>>>>> +
>>>>>> +static irqreturn_t
>>>>>> +dc_crtc_dec_framecomplete_irq_handler(int irq, void *dev_id)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = dev_id;
>>>>>> +  struct drm_crtc *crtc = &dc_crtc->base;
>>>>>> +  unsigned long flags;
>>>>>> +
>>>>>> +  drm_crtc_handle_vblank(crtc);
>>>>>> +
>>>>>> +  spin_lock_irqsave(&crtc->dev->event_lock, flags);
>>>>>> +  if (dc_crtc->event) {
>>>>>> +          drm_crtc_send_vblank_event(crtc, dc_crtc->event);
>>>>>> +          dc_crtc->event = NULL;
>>>>>> +          drm_crtc_vblank_put(crtc);
>>>>>> +  }
>>>>>> +  spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
>>>>>> +
>>>>>> +  return IRQ_HANDLED;
>>>>>> +}
>>>>>> +
>>>>>> +static irqreturn_t dc_crtc_common_irq_handler(int irq, void *dev_id)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = dev_id;
>>>>>> +
>>>>>> +  if (irq == dc_crtc->irq_dec_seqcomplete)
>>>>>> +          complete(&dc_crtc->dec_seqcomplete_done);
>>>>>> +  else if (irq == dc_crtc->irq_dec_shdld)
>>>>>> +          complete(&dc_crtc->dec_shdld_done);
>>>>>> +  else if (irq == dc_crtc->irq_ed_cont_shdld)
>>>>>> +          complete(&dc_crtc->ed_cont_shdld_done);
>>>>>> +  else if (irq == dc_crtc->irq_ed_safe_shdld)
>>>>>> +          complete(&dc_crtc->ed_safe_shdld_done);
>>>>>
>>>>> Is there any reason to have a single multiplex handler instead of having
>>>>> 4 separate handlers, each doing one simple thing?
>>>>
>>>> Just thought that one irq handler is feasible to implement the common
>>>> handling logic. It's also ok to use 4 separate handlers with a little
>>>> performance improvement. I may change to use 4 separate handlers by
>>>> introducing a DEFINE_DC_CRTC_IRQ_HANDLER() marco.
>>>
>>> Just define them one by one, there is no need for a macro.
>>
>> Will do.
>>
>>>
>>>>
>>>>>
>>>>>> +
>>>>>> +  return IRQ_HANDLED;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_funcs dc_crtc_funcs = {
>>>>>> +  .reset                  = drm_atomic_helper_crtc_reset,
>>>>>> +  .destroy                = drm_crtc_cleanup,
>>>>>> +  .set_config             = drm_atomic_helper_set_config,
>>>>>> +  .page_flip              = drm_atomic_helper_page_flip,
>>>>>> +  .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
>>>>>> +  .atomic_destroy_state   = drm_atomic_helper_crtc_destroy_state,
>>>>>> +  .get_vblank_counter     = dc_crtc_get_vblank_counter,
>>>>>> +  .enable_vblank          = dc_crtc_enable_vblank,
>>>>>> +  .disable_vblank         = dc_crtc_disable_vblank,
>>>>>> +  .get_vblank_timestamp   = drm_crtc_vblank_helper_get_vblank_timestamp,
>>>>>> +};
>>>>>> +
>>>>>> +static void dc_crtc_queue_state_event(struct drm_crtc_state *crtc_state)
>>>>>> +{
>>>>>> +  struct drm_crtc *crtc = crtc_state->crtc;
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +
>>>>>> +  spin_lock_irq(&crtc->dev->event_lock);
>>>>>> +  if (crtc_state->event) {
>>>>>> +          WARN_ON(drm_crtc_vblank_get(crtc));
>>>>>> +          WARN_ON(dc_crtc->event);
>>>>>> +          dc_crtc->event = crtc_state->event;
>>>>>> +          crtc_state->event = NULL;
>>>>>> +  }
>>>>>> +  spin_unlock_irq(&crtc->dev->event_lock);
>>>>>> +}
>>>>>> +
>>>>>> +static enum drm_mode_status
>>>>>> +dc_crtc_check_clock(struct dc_crtc *dc_crtc, int clk_khz)
>>>>>> +{
>>>>>> +  return dc_fg_check_clock(dc_crtc->fg, clk_khz);
>>>>>> +}
>>>>>
>>>>> inline
>>>>
>>>> Will do.
>>>>
>>>>>
>>>>>> +
>>>>>> +static enum drm_mode_status
>>>>>> +dc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  enum drm_mode_status status;
>>>>>> +
>>>>>> +  status = dc_crtc_check_clock(dc_crtc, mode->clock);
>>>>>> +  if (status != MODE_OK)
>>>>>> +          return status;
>>>>>> +
>>>>>> +  if (mode->crtc_clock > DC_FRAMEGEN_MAX_CLOCK_KHZ)
>>>>>> +          return MODE_CLOCK_HIGH;
>>>>>> +
>>>>>> +  return MODE_OK;
>>>>>> +}
>>>>>> +
>>>>>> +static int
>>>>>> +dc_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_crtc_state *new_crtc_state =
>>>>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +  struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  enum drm_mode_status status;
>>>>>> +
>>>>>> +  status = dc_crtc_check_clock(dc_crtc, adj->clock);
>>>>>> +  if (status != MODE_OK)
>>>>>> +          return -EINVAL;
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void
>>>>>> +dc_crtc_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_crtc_state *new_crtc_state =
>>>>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  int idx, ret;
>>>>>> +
>>>>>> +  if (!drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>>>>>> +      !new_crtc_state->active)
>>>>>> +          return;
>>>>>> +
>>>>>> +  if (!drm_dev_enter(crtc->dev, &idx))
>>>>>> +          return;
>>>>>> +
>>>>>> +  /* request pixel engine power-on when CRTC starts to be active */
>>>>>> +  ret = pm_runtime_resume_and_get(dc_crtc->pe->dev);
>>>>>> +  if (ret)
>>>>>> +          dc_crtc_err(crtc, "failed to get DC pixel engine RPM: %d\n",
>>>>>> +                      ret);
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static void
>>>>>> +dc_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_crtc_state *old_crtc_state =
>>>>>> +                          drm_atomic_get_old_crtc_state(state, crtc);
>>>>>> +  struct drm_crtc_state *new_crtc_state =
>>>>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  int idx;
>>>>>> +
>>>>>> +  if (drm_atomic_crtc_needs_modeset(new_crtc_state) ||
>>>>>> +      (!old_crtc_state->active && !new_crtc_state->active))
>>>>>> +          return;
>>>>>> +
>>>>>> +  if (!drm_dev_enter(crtc->dev, &idx))
>>>>>> +          goto out;
>>>>>> +
>>>>>> +  enable_irq(dc_crtc->irq_ed_cont_shdld);
>>>>>> +
>>>>>> +  /* flush plane update out to display */
>>>>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>>>>>> +
>>>>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>>>>>> +
>>>>>> +  disable_irq(dc_crtc->irq_ed_cont_shdld);
>>>>>> +
>>>>>> +  DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +
>>>>>> +out:
>>>>>> +  dc_crtc_queue_state_event(new_crtc_state);
>>>>>> +}
>>>>>> +
>>>>>> +static void
>>>>>> +dc_crtc_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_crtc_state *new_crtc_state =
>>>>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +  struct drm_display_mode *adj = &new_crtc_state->adjusted_mode;
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  enum dc_link_id cf_link;
>>>>>> +  int idx, ret;
>>>>>> +
>>>>>> +  dc_crtc_dbg(crtc, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
>>>>>> +
>>>>>> +  drm_crtc_vblank_on(crtc);
>>>>>> +
>>>>>> +  if (!drm_dev_enter(crtc->dev, &idx))
>>>>>> +          goto out;
>>>>>> +
>>>>>> +  /* request display engine power-on when CRTC is enabled */
>>>>>> +  ret = pm_runtime_resume_and_get(dc_crtc->de->dev);
>>>>>> +  if (ret < 0)
>>>>>> +          dc_crtc_err(crtc, "failed to get DC display engine RPM: %d\n",
>>>>>> +                      ret);
>>>>>> +
>>>>>> +  enable_irq(dc_crtc->irq_dec_shdld);
>>>>>> +  enable_irq(dc_crtc->irq_ed_cont_shdld);
>>>>>> +  enable_irq(dc_crtc->irq_ed_safe_shdld);
>>>>>> +
>>>>>> +  dc_fg_cfg_videomode(dc_crtc->fg, adj);
>>>>>> +
>>>>>> +  dc_cf_framedimensions(dc_crtc->cf_cont,
>>>>>> +                        adj->crtc_hdisplay, adj->crtc_vdisplay);
>>>>>> +  dc_cf_framedimensions(dc_crtc->cf_safe,
>>>>>> +                        adj->crtc_hdisplay, adj->crtc_vdisplay);
>>>>>> +
>>>>>> +  /* constframe in safety stream shows blue frame */
>>>>>> +  dc_cf_constantcolor_blue(dc_crtc->cf_safe);
>>>>>> +  cf_link = dc_cf_get_link_id(dc_crtc->cf_safe);
>>>>>> +  dc_ed_pec_src_sel(dc_crtc->ed_safe, cf_link);
>>>>>> +
>>>>>> +  /* show CRTC background if no plane is enabled */
>>>>>> +  if (new_crtc_state->plane_mask == 0) {
>>>>>> +          /* constframe in content stream shows black frame */
>>>>>> +          dc_cf_constantcolor_black(dc_crtc->cf_cont);
>>>>>> +
>>>>>> +          cf_link = dc_cf_get_link_id(dc_crtc->cf_cont);
>>>>>> +          dc_ed_pec_src_sel(dc_crtc->ed_cont, cf_link);
>>>>>> +  }
>>>>>> +
>>>>>> +  dc_fg_enable_clock(dc_crtc->fg);
>>>>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_cont);
>>>>>> +  dc_ed_pec_sync_trigger(dc_crtc->ed_safe);
>>>>>> +  dc_fg_shdtokgen(dc_crtc->fg);
>>>>>> +  dc_fg_enable(dc_crtc->fg);
>>>>>> +
>>>>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_safe_shdld_done);
>>>>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(ed_cont_shdld_done);
>>>>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_shdld_done);
>>>>>> +
>>>>>> +  disable_irq(dc_crtc->irq_ed_safe_shdld);
>>>>>> +  disable_irq(dc_crtc->irq_ed_cont_shdld);
>>>>>> +  disable_irq(dc_crtc->irq_dec_shdld);
>>>>>> +
>>>>>> +  DC_CRTC_WAIT_FOR_FRAMEGEN_SECONDARY_SYNCUP(dc_crtc->fg);
>>>>>> +
>>>>>> +  DC_CRTC_CHECK_FRAMEGEN_FIFO(dc_crtc->fg);
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +
>>>>>> +out:
>>>>>> +  dc_crtc_queue_state_event(new_crtc_state);
>>>>>> +}
>>>>>> +
>>>>>> +static void
>>>>>> +dc_crtc_atomic_disable(struct drm_crtc *crtc, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_crtc_state *new_crtc_state =
>>>>>> +                          drm_atomic_get_new_crtc_state(state, crtc);
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  int idx, ret;
>>>>>> +
>>>>>> +  if (!drm_dev_enter(crtc->dev, &idx))
>>>>>> +          goto out;
>>>>>> +
>>>>>> +  enable_irq(dc_crtc->irq_dec_seqcomplete);
>>>>>> +  dc_fg_disable(dc_crtc->fg);
>>>>>> +  DC_CRTC_WAIT_FOR_COMPLETION_TIMEOUT(dec_seqcomplete_done);
>>>>>> +  disable_irq(dc_crtc->irq_dec_seqcomplete);
>>>>>> +
>>>>>> +  dc_fg_disable_clock(dc_crtc->fg);
>>>>>> +
>>>>>> +  /* request pixel engine power-off as plane is off too */
>>>>>> +  ret = pm_runtime_put(dc_crtc->pe->dev);
>>>>>> +  if (ret)
>>>>>> +          dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>>>>>> +                      ret);
>>>>>> +
>>>>>> +  /* request display engine power-off when CRTC is disabled */
>>>>>> +  ret = pm_runtime_put(dc_crtc->de->dev);
>>>>>
>>>>> Can this be expressed as a devlink between PE and DE?
>>>>
>>>> Looking at struct dc_{pe,de}, PE and DE are not dependent with each other,
>>>> i.e., no consumer/supplier relationship(note that blit engine in PE can work
>>>> by itself without DE) between them. So, it doesn't look right to link the two
>>>> devices.
>>>
>>> Ack
>>>
>>>>
>>>>>
>>>>>> +  if (ret < 0)
>>>>>> +          dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>>>>>> +                      ret);
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +
>>>>>> +out:
>>>>>> +  drm_crtc_vblank_off(crtc);
>>>>>> +
>>>>>> +  spin_lock_irq(&crtc->dev->event_lock);
>>>>>> +  if (new_crtc_state->event && !new_crtc_state->active) {
>>>>>> +          drm_crtc_send_vblank_event(crtc, new_crtc_state->event);
>>>>>> +          new_crtc_state->event = NULL;
>>>>>> +  }
>>>>>> +  spin_unlock_irq(&crtc->dev->event_lock);
>>>>>> +}
>>>>>> +
>>>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  if (!dc_fg_wait_for_frame_index_moving(dc_crtc->fg))
>>>>>> +          return;
>>>>>> +
>>>>>> +  dc_fg_disable_clock(dc_crtc->fg);
>>>>>> +
>>>>>> +  if (pm_runtime_active(dc_crtc->pe->dev)) {
>>>>>> +          ret = pm_runtime_put_sync(dc_crtc->pe->dev);
>>>>>> +          if (ret)
>>>>>> +                  dc_crtc_err(crtc, "failed to put DC pixel engine RPM: %d\n",
>>>>>> +                              ret);
>>>>>> +  }
>>>>>> +
>>>>>> +  ret = pm_runtime_put_sync(dc_crtc->de->dev);
>>>>>> +  if (ret < 0)
>>>>>> +          dc_crtc_err(crtc, "failed to put DC display engine RPM: %d\n",
>>>>>> +                      ret);
>>>>>> +}
>>>>>> +
>>>>>> +static bool dc_crtc_get_scanout_position(struct drm_crtc *crtc,
>>>>>> +                                   bool in_vblank_irq,
>>>>>> +                                   int *vpos, int *hpos,
>>>>>> +                                   ktime_t *stime, ktime_t *etime,
>>>>>> +                                   const struct drm_display_mode *mode)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = to_dc_crtc(crtc);
>>>>>> +  int vdisplay = mode->crtc_vdisplay;
>>>>>> +  int vtotal = mode->crtc_vtotal;
>>>>>> +  bool reliable;
>>>>>> +  int line;
>>>>>> +  int idx;
>>>>>> +
>>>>>> +  if (stime)
>>>>>> +          *stime = ktime_get();
>>>>>> +
>>>>>> +  if (!drm_dev_enter(crtc->dev, &idx)) {
>>>>>> +          reliable = false;
>>>>>> +          *vpos = 0;
>>>>>> +          *hpos = 0;
>>>>>> +          goto out;
>>>>>> +  }
>>>>>> +
>>>>>> +  /* line index starts with 0 for the first active output line */
>>>>>> +  line = dc_fg_get_line_index(dc_crtc->fg);
>>>>>> +
>>>>>> +  if (line < vdisplay)
>>>>>> +          /* active scanout area - positive */
>>>>>> +          *vpos = line + 1;
>>>>>> +  else
>>>>>> +          /* inside vblank - negative */
>>>>>> +          *vpos = line - (vtotal - 1);
>>>>>> +
>>>>>> +  *hpos = 0;
>>>>>> +
>>>>>> +  reliable = true;
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +out:
>>>>>> +  if (etime)
>>>>>> +          *etime = ktime_get();
>>>>>> +
>>>>>> +  return reliable;
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_crtc_helper_funcs dc_helper_funcs = {
>>>>>> +  .mode_valid             = dc_crtc_mode_valid,
>>>>>> +  .atomic_check           = dc_crtc_atomic_check,
>>>>>> +  .atomic_begin           = dc_crtc_atomic_begin,
>>>>>> +  .atomic_flush           = dc_crtc_atomic_flush,
>>>>>> +  .atomic_enable          = dc_crtc_atomic_enable,
>>>>>> +  .atomic_disable         = dc_crtc_atomic_disable,
>>>>>> +  .get_scanout_position   = dc_crtc_get_scanout_position,
>>>>>> +};
>>>>>> +
>>>>>> +static int dc_crtc_request_irqs(struct drm_device *drm, struct dc_crtc *dc_crtc)
>>>>>> +{
>>>>>> +  struct {
>>>>>> +          struct device *dev;
>>>>>> +          unsigned int irq;
>>>>>> +          irqreturn_t (*irq_handler)(int irq, void *dev_id);
>>>>>> +  } irqs[DC_CRTC_IRQS] = {
>>>>>> +          {
>>>>>> +                  dc_crtc->de->dev,
>>>>>> +                  dc_crtc->irq_dec_framecomplete,
>>>>>> +                  dc_crtc_dec_framecomplete_irq_handler,
>>>>>> +          }, {
>>>>>> +                  dc_crtc->de->dev,
>>>>>> +                  dc_crtc->irq_dec_seqcomplete,
>>>>>> +                  dc_crtc_common_irq_handler,
>>>>>> +          }, {
>>>>>> +                  dc_crtc->de->dev,
>>>>>> +                  dc_crtc->irq_dec_shdld,
>>>>>> +                  dc_crtc_common_irq_handler,
>>>>>> +          }, {
>>>>>> +                  dc_crtc->ed_cont->dev,
>>>>>> +                  dc_crtc->irq_ed_cont_shdld,
>>>>>> +                  dc_crtc_common_irq_handler,
>>>>>> +          }, {
>>>>>> +                  dc_crtc->ed_safe->dev,
>>>>>> +                  dc_crtc->irq_ed_safe_shdld,
>>>>>> +                  dc_crtc_common_irq_handler,
>>>>>> +          },
>>>>>> +  };
>>>>>> +  int i, ret;
>>>>>> +
>>>>>> +  for (i = 0; i < DC_CRTC_IRQS; i++) {
>>>>>> +          struct dc_crtc_irq *irq = &dc_crtc->irqs[i];
>>>>>> +
>>>>>> +          ret = devm_request_irq(irqs[i].dev, irqs[i].irq,
>>>>>> +                                 irqs[i].irq_handler, IRQF_NO_AUTOEN,
>>>>>> +                                 dev_name(irqs[i].dev), dc_crtc);
>>>>>> +          if (ret) {
>>>>>> +                  dev_err(irqs[i].dev, "failed to request irq(%u): %d\n",
>>>>>> +                          irqs[i].irq, ret);
>>>>>> +                  return ret;
>>>>>> +          }
>>>>>> +
>>>>>> +          irq->dc_crtc = dc_crtc;
>>>>>> +          irq->irq = irqs[i].irq;
>>>>>> +  }
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +  struct dc_de *de = dc_drm->de[crtc_index];
>>>>>> +  struct dc_pe *pe = dc_drm->pe;
>>>>>> +  struct dc_plane *dc_primary;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  dc_crtc->de = de;
>>>>>> +  dc_crtc->pe = pe;
>>>>>> +
>>>>>> +  init_completion(&dc_crtc->dec_seqcomplete_done);
>>>>>> +  init_completion(&dc_crtc->dec_shdld_done);
>>>>>> +  init_completion(&dc_crtc->ed_cont_shdld_done);
>>>>>> +  init_completion(&dc_crtc->ed_safe_shdld_done);
>>>>>> +
>>>>>> +  dc_crtc->cf_cont = pe->cf_cont[crtc_index];
>>>>>> +  dc_crtc->cf_safe = pe->cf_safe[crtc_index];
>>>>>> +  dc_crtc->ed_cont = pe->ed_cont[crtc_index];
>>>>>> +  dc_crtc->ed_safe = pe->ed_safe[crtc_index];
>>>>>> +  dc_crtc->fg = de->fg;
>>>>>> +
>>>>>> +  dc_crtc->irq_dec_framecomplete = de->irq_framecomplete;
>>>>>> +  dc_crtc->irq_dec_seqcomplete = de->irq_seqcomplete;
>>>>>> +  dc_crtc->irq_dec_shdld = de->irq_shdld;
>>>>>> +  dc_crtc->irq_ed_safe_shdld = dc_crtc->ed_safe->irq_shdld;
>>>>>> +  dc_crtc->irq_ed_cont_shdld = dc_crtc->ed_cont->irq_shdld;
>>>>>> +
>>>>>> +  dc_primary = &dc_drm->dc_primary[crtc_index];
>>>>>> +  ret = dc_plane_init(dc_drm, dc_primary);
>>>>>> +  if (ret) {
>>>>>> +          dev_err(drm->dev,
>>>>>> +                  "failed to init primary plane for display engine%u: %d\n",
>>>>>> +                  de->id, ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  drm_crtc_helper_add(&dc_crtc->base, &dc_helper_funcs);
>>>>>> +
>>>>>> +  ret = drm_crtc_init_with_planes(drm, &dc_crtc->base, &dc_primary->base,
>>>>>> +                                  NULL, &dc_crtc_funcs, NULL);
>>>>>> +  if (ret)
>>>>>> +          dev_err(drm->dev,
>>>>>> +                  "failed to add CRTC for display engine%u: %d\n",
>>>>>> +                  de->id, ret);
>>>>>> +
>>>>>> +  return ret;
>>>>>> +}
>>>>>> +
>>>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +
>>>>>> +  return dc_crtc_request_irqs(drm, dc_crtc);
>>>>>> +}
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-de.h b/drivers/gpu/drm/imx/dc/dc-de.h
>>>>>> index 17a44362118e..8a7b6c03a222 100644
>>>>>> --- a/drivers/gpu/drm/imx/dc/dc-de.h
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-de.h
>>>>>> @@ -13,6 +13,9 @@
>>>>>>
>>>>>>  #define DC_DISPLAYS       2
>>>>>>
>>>>>> +#define DC_FRAMEGEN_MAX_FRAME_INDEX       0x3ffff
>>>>>> +#define DC_FRAMEGEN_MAX_CLOCK_KHZ 300000
>>>>>> +
>>>>>>  struct dc_fg {
>>>>>>    struct device *dev;
>>>>>>    struct regmap *reg;
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.c b/drivers/gpu/drm/imx/dc/dc-drv.c
>>>>>> index fd68861f770a..1e4b8afa3eec 100644
>>>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.c
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.c
>>>>>> @@ -3,11 +3,254 @@
>>>>>>   * Copyright 2024 NXP
>>>>>>   */
>>>>>>
>>>>>> +#include <linux/clk.h>
>>>>>> +#include <linux/component.h>
>>>>>> +#include <linux/device.h>
>>>>>> +#include <linux/dma-mapping.h>
>>>>>> +#include <linux/mod_devicetable.h>
>>>>>>  #include <linux/module.h>
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/of_platform.h>
>>>>>>  #include <linux/platform_device.h>
>>>>>> +#include <linux/pm.h>
>>>>>> +#include <linux/pm_runtime.h>
>>>>>>
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_client_setup.h>
>>>>>> +#include <drm/drm_crtc.h>
>>>>>> +#include <drm/drm_drv.h>
>>>>>> +#include <drm/drm_fbdev_dma.h>
>>>>>> +#include <drm/drm_fourcc.h>
>>>>>> +#include <drm/drm_gem_dma_helper.h>
>>>>>> +#include <drm/drm_managed.h>
>>>>>> +#include <drm/drm_modeset_helper.h>
>>>>>> +#include <drm/drm_of.h>
>>>>>> +
>>>>>> +#include "dc-de.h"
>>>>>>  #include "dc-drv.h"
>>>>>>
>>>>>> +struct dc_priv {
>>>>>> +  struct drm_device *drm;
>>>>>> +  struct clk *clk_cfg;
>>>>>> +};
>>>>>> +
>>>>>> +DEFINE_DRM_GEM_DMA_FOPS(dc_drm_driver_fops);
>>>>>> +
>>>>>> +static struct drm_driver dc_drm_driver = {
>>>>>> +  .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
>>>>>> +  DRM_GEM_DMA_DRIVER_OPS,
>>>>>> +  DRM_FBDEV_DMA_DRIVER_OPS,
>>>>>> +  .fops = &dc_drm_driver_fops,
>>>>>> +  .name = "imx8-dc",
>>>>>> +  .desc = "i.MX8 DC DRM graphics",
>>>>>> +  .date = "20240530",
>>>>>> +  .major = 1,
>>>>>> +  .minor = 0,
>>>>>> +  .patchlevel = 0,
>>>>>> +};
>>>>>> +
>>>>>> +static void
>>>>>> +dc_add_components(struct device *dev, struct component_match **matchptr)
>>>>>> +{
>>>>>> +  struct device_node *child, *grandchild;
>>>>>> +
>>>>>> +  for_each_available_child_of_node(dev->of_node, child) {
>>>>>> +          /* The interrupt controller is not a component. */
>>>>>> +          if (of_device_is_compatible(child, "fsl,imx8qxp-dc-intc"))
>>>>>> +                  continue;
>>>>>> +
>>>>>> +          drm_of_component_match_add(dev, matchptr, component_compare_of,
>>>>>> +                                     child);
>>>>>> +
>>>>>> +          for_each_available_child_of_node(child, grandchild)
>>>>>> +                  drm_of_component_match_add(dev, matchptr,
>>>>>> +                                             component_compare_of,
>>>>>> +                                             grandchild);
>>>>>> +  }
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_drm_component_unbind_all(void *ptr)
>>>>>> +{
>>>>>> +  struct dc_drm_device *dc_drm = ptr;
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +
>>>>>> +  component_unbind_all(drm->dev, dc_drm);
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_drm_bind(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +  struct dc_drm_device *dc_drm;
>>>>>> +  struct drm_device *drm;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  dc_drm = devm_drm_dev_alloc(dev, &dc_drm_driver, struct dc_drm_device,
>>>>>> +                              base);
>>>>>> +  if (IS_ERR(dc_drm))
>>>>>> +          return PTR_ERR(dc_drm);
>>>>>> +
>>>>>> +  drm = &dc_drm->base;
>>>>>> +
>>>>>> +  ret = component_bind_all(dev, dc_drm);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = devm_add_action_or_reset(dev, dc_drm_component_unbind_all,
>>>>>> +                                 dc_drm);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = dc_kms_init(dc_drm);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = drm_dev_register(drm, 0);
>>>>>> +  if (ret) {
>>>>>> +          dev_err(dev, "failed to register drm device: %d\n", ret);
>>>>>> +          goto err;
>>>>>> +  }
>>>>>> +
>>>>>> +  drm_client_setup_with_fourcc(drm, DRM_FORMAT_XRGB8888);
>>>>>> +
>>>>>> +  priv->drm = drm;
>>>>>> +
>>>>>> +  return 0;
>>>>>> +
>>>>>> +err:
>>>>>> +  dc_kms_uninit(dc_drm);
>>>>>> +
>>>>>> +  return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_drm_unbind(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +  struct dc_drm_device *dc_drm = to_dc_drm_device(priv->drm);
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +  struct drm_crtc *crtc;
>>>>>> +
>>>>>> +  priv->drm = NULL;
>>>>>> +  drm_dev_unplug(drm);
>>>>>> +  dc_kms_uninit(dc_drm);
>>>>>> +  drm_atomic_helper_shutdown(drm);
>>>>>> +
>>>>>> +  drm_for_each_crtc(crtc, drm)
>>>>>> +          dc_crtc_disable_at_unbind(crtc);
>>>>>
>>>>> There should be no need for that. drm_atomic_helper_shutdown() should
>>>>> disable all the CRTCs.
>>>>
>>>> In case DRM device is unplugged, drm_atomic_helper_shutdown does not
>>>> effectively disable the CRTCs due to the bypassed logics wrapped by
>>>> drm_dev_{enter,exit}.  That's why dc_crtc_disable_at_unbind() is called
>>>> here to effectively disable the CRTCs.
>>>
>>> I see. I haven't faced drm_dev_unplug() earlier. I checked, the "not
>>> disabled" behaviour is documented and all other drivers don't perform
>>> any kind of cleanup afterwards. Thus I think it's safe to drop the
>>> dc_crtc_disable_at_unbind() unless it causes any kind of HW issues.
>>
>> IMHO, it's a bit safer to set the HWs backing the CRTCs to a known state
>> at the unbind stage by properly disabling them, in case there is any
>> unknown SW or HW issue.  Plus, i.MX8qm/qxp SoCs don't provide reset
>> signals to the Display Controllers(the resets DT property is optional),
>> otherwise, this disablement function can be dropped(at least for now).
> 
> I'd say, a rule of thumb is "Behave similarly" to the existing
> drivers. We've had a long-standing TODO item expressed in the DRM
> docs: the drm_dev_unplug() vs drm_atomic_helper_shutdown(). I think
> your issue should be solved in a generic way. One random idea would be
> to have a sepecial drm_dev_unplug() version which is intended for the
> normal shutdown path, it should mark the device as unavailable after
> disabling the outputs and unregistering it from userspace.

I'll drop dc_crtc_disable_at_unbind() in next version to "behave similarly"
to the existing drivers.

> 
>>
>>>
>>>>
>>>>>
>>>>> Also, who is going to do drm_dev_unregister()? I don't see it in the
>>>>> code.
>>>>
>>>> drm_dev_unplug() right above calls drm_dev_unregister().
>>>>
>>>>>
>>>>>> +}
>>>>>> +
>>>>>> +static const struct component_master_ops dc_drm_ops = {
>>>>>> +  .bind = dc_drm_bind,
>>>>>> +  .unbind = dc_drm_unbind,
>>>>>> +};
>>>>>> +
>>>>>> +static int dc_probe(struct platform_device *pdev)
>>>>>> +{
>>>>>> +  struct component_match *match = NULL;
>>>>>> +  struct dc_priv *priv;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
>>>>>> +  if (!priv)
>>>>>> +          return -ENOMEM;
>>>>>> +
>>>>>> +  priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
>>>>>> +  if (IS_ERR(priv->clk_cfg))
>>>>>> +          return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
>>>>>> +                               "failed to get cfg clock\n");
>>>>>> +
>>>>>> +  dev_set_drvdata(&pdev->dev, priv);
>>>>>> +
>>>>>> +  ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = devm_pm_runtime_enable(&pdev->dev);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = devm_of_platform_populate(&pdev->dev);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  dc_add_components(&pdev->dev, &match);
>>>>>> +
>>>>>> +  ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
>>>>>> +  if (ret)
>>>>>> +          return dev_err_probe(&pdev->dev, ret,
>>>>>> +                               "failed to add component master\n");
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_remove(struct platform_device *pdev)
>>>>>> +{
>>>>>> +  component_master_del(&pdev->dev, &dc_drm_ops);
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_runtime_suspend(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +
>>>>>> +  clk_disable_unprepare(priv->clk_cfg);
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_runtime_resume(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  ret = clk_prepare_enable(priv->clk_cfg);
>>>>>> +  if (ret)
>>>>>> +          dev_err(dev, "failed to enable cfg clock: %d\n", ret);
>>>>>> +
>>>>>> +  return ret;
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_suspend(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +
>>>>>> +  return drm_mode_config_helper_suspend(priv->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_resume(struct device *dev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
>>>>>> +
>>>>>> +  return drm_mode_config_helper_resume(priv->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_shutdown(struct platform_device *pdev)
>>>>>> +{
>>>>>> +  struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
>>>>>> +
>>>>>> +  drm_atomic_helper_shutdown(priv->drm);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct dev_pm_ops dc_pm_ops = {
>>>>>> +  RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
>>>>>> +  SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
>>>>>> +};
>>>>>> +
>>>>>> +static const struct of_device_id dc_dt_ids[] = {
>>>>>> +  { .compatible = "fsl,imx8qxp-dc", },
>>>>>> +  { /* sentinel */ }
>>>>>> +};
>>>>>> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
>>>>>> +
>>>>>> +static struct platform_driver dc_driver = {
>>>>>> +  .probe = dc_probe,
>>>>>> +  .remove = dc_remove,
>>>>>> +  .shutdown = dc_shutdown,
>>>>>> +  .driver = {
>>>>>> +          .name = "imx8-dc",
>>>>>> +          .of_match_table = dc_dt_ids,
>>>>>> +          .pm = pm_sleep_ptr(&dc_pm_ops),
>>>>>> +  },
>>>>>> +};
>>>>>> +
>>>>>>  static struct platform_driver * const dc_drivers[] = {
>>>>>>    &dc_cf_driver,
>>>>>>    &dc_de_driver,
>>>>>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
>>>>>>    &dc_lb_driver,
>>>>>>    &dc_pe_driver,
>>>>>>    &dc_tc_driver,
>>>>>> +  &dc_driver,
>>>>>>  };
>>>>>>
>>>>>>  static int __init dc_drm_init(void)
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
>>>>>> index 3b11f4862c6c..39a771a13933 100644
>>>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
>>>>>> @@ -6,19 +6,38 @@
>>>>>>  #ifndef __DC_DRV_H__
>>>>>>  #define __DC_DRV_H__
>>>>>>
>>>>>> +#include <linux/container_of.h>
>>>>>>  #include <linux/platform_device.h>
>>>>>>
>>>>>>  #include <drm/drm_device.h>
>>>>>> +#include <drm/drm_encoder.h>
>>>>>>
>>>>>>  #include "dc-de.h"
>>>>>> +#include "dc-kms.h"
>>>>>>  #include "dc-pe.h"
>>>>>>
>>>>>>  struct dc_drm_device {
>>>>>>    struct drm_device base;
>>>>>> +  struct dc_crtc dc_crtc[DC_DISPLAYS];
>>>>>> +  struct dc_plane dc_primary[DC_DISPLAYS];
>>>>>> +  struct drm_encoder encoder[DC_DISPLAYS];
>>>>>>    struct dc_de *de[DC_DISPLAYS];
>>>>>>    struct dc_pe *pe;
>>>>>>  };
>>>>>>
>>>>>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
>>>>>> +{
>>>>>> +  return container_of(drm, struct dc_drm_device, base);
>>>>>> +}
>>>>>> +
>>>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
>>>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
>>>>>> +
>>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm);
>>>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
>>>>>> +
>>>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
>>>>>> +
>>>>>>  extern struct platform_driver dc_cf_driver;
>>>>>>  extern struct platform_driver dc_ed_driver;
>>>>>>  extern struct platform_driver dc_de_driver;
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
>>>>>> new file mode 100644
>>>>>> index 000000000000..2b18aa37a4a8
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
>>>>>> @@ -0,0 +1,143 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>>>> +/*
>>>>>> + * Copyright 2024 NXP
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/of.h>
>>>>>> +#include <linux/of_graph.h>
>>>>>> +
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_bridge.h>
>>>>>> +#include <drm/drm_bridge_connector.h>
>>>>>> +#include <drm/drm_connector.h>
>>>>>> +#include <drm/drm_crtc.h>
>>>>>> +#include <drm/drm_device.h>
>>>>>> +#include <drm/drm_encoder.h>
>>>>>> +#include <drm/drm_gem_framebuffer_helper.h>
>>>>>> +#include <drm/drm_mode_config.h>
>>>>>> +#include <drm/drm_print.h>
>>>>>> +#include <drm/drm_probe_helper.h>
>>>>>> +#include <drm/drm_simple_kms_helper.h>
>>>>>> +#include <drm/drm_vblank.h>
>>>>>> +
>>>>>> +#include "dc-de.h"
>>>>>> +#include "dc-drv.h"
>>>>>> +#include "dc-kms.h"
>>>>>> +
>>>>>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
>>>>>> +  .fb_create = drm_gem_fb_create,
>>>>>> +  .atomic_check = drm_atomic_helper_check,
>>>>>> +  .atomic_commit = drm_atomic_helper_commit,
>>>>>> +};
>>>>>> +
>>>>>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
>>>>>> +                                  int crtc_index)
>>>>>> +{
>>>>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +  struct drm_crtc *crtc = &dc_crtc->base;
>>>>>> +  struct drm_connector *connector;
>>>>>> +  struct device *dev = drm->dev;
>>>>>> +  struct drm_encoder *encoder;
>>>>>> +  struct drm_bridge *bridge;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
>>>>>> +                                  0, 0);
>>>>>> +  if (IS_ERR(bridge)) {
>>>>>> +          ret = PTR_ERR(bridge);
>>>>>> +          if (ret == -ENODEV)
>>>>>> +                  return 0;
>>>>>> +
>>>>>> +          return dev_err_probe(dev, ret,
>>>>>> +                               "failed to find bridge for CRTC%u\n",
>>>>>> +                               crtc->index);
>>>>>> +  }
>>>>>> +
>>>>>> +  encoder = &dc_drm->encoder[crtc_index];
>>>>>> +  ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
>>>>>> +  if (ret) {
>>>>>> +          dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
>>>>>> +                  crtc->index, ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  encoder->possible_crtcs = drm_crtc_mask(crtc);
>>>>>> +
>>>>>> +  ret = drm_bridge_attach(encoder, bridge, NULL,
>>>>>> +                          DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>>>>>> +  if (ret) {
>>>>>> +          dev_err(dev,
>>>>>> +                  "failed to attach bridge to encoder for CRTC%u: %d\n",
>>>>>> +                  crtc->index, ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  connector = drm_bridge_connector_init(drm, encoder);
>>>>>> +  if (IS_ERR(connector)) {
>>>>>> +          ret = PTR_ERR(connector);
>>>>>> +          dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
>>>>>> +                  crtc->index, ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  ret = drm_connector_attach_encoder(connector, encoder);
>>>>>> +  if (ret)
>>>>>> +          dev_err(dev,
>>>>>> +                  "failed to attach encoder to connector for CRTC%u: %d\n",
>>>>>> +                  crtc->index, ret);
>>>>>> +
>>>>>> +  return ret;
>>>>>> +}
>>>>>> +
>>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm)
>>>>>> +{
>>>>>> +  struct drm_device *drm = &dc_drm->base;
>>>>>> +  int ret, i;
>>>>>> +
>>>>>> +  ret = drmm_mode_config_init(drm);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  drm->mode_config.min_width = 60;
>>>>>> +  drm->mode_config.min_height = 60;
>>>>>> +  drm->mode_config.max_width = 8192;
>>>>>> +  drm->mode_config.max_height = 8192;
>>>>>> +  drm->mode_config.funcs = &dc_drm_mode_config_funcs;
>>>>>> +
>>>>>> +  drm->vblank_disable_immediate = true;
>>>>>> +  drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
>>>>>> +
>>>>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
>>>>>> +          ret = dc_crtc_init(dc_drm, i);
>>>>>> +          if (ret)
>>>>>> +                  return ret;
>>>>>> +
>>>>>> +          ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
>>>>>> +          if (ret)
>>>>>> +                  return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
>>>>>> +          ret = dc_crtc_post_init(dc_drm, i);
>>>>>
>>>>> Can you use .late_register for this?
>>>>
>>>> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
>>>> additional userspace interfaces like debugfs interfaces. And, it seems that
>>>> everyone implementing this uses it to add debugfs interfaces. So, it will
>>>> kind of abuse it to do CRTC post initialization.
>>>
>>> Why can't they be requested earlier then?
>>
>> If I request them earlier in dc_crtc_init(), then they cannot be freed by
>> devm_irq_release() when devm_drm_of_get_bridge() called by
>> dc_kms_init_encoder_per_crtc() returns -EPROBE_DEFER(which means failing
>> to find the first DRM bridge for the CRTC).  Why can't they be freed by
>> devm_irq_release()?  Because they are requested by the devices of ExtDsts
>> and Display Engines and their drivers are not removed during the probe
>> deferral dance.  Furthermore, -EPROBE_DEFER won't be returned after
>> dc_crtc_post_init() since the later called drm_vblank_init() doesn't
>> return -EPROBE_DEFER anyway, so it's fine to call dc_crtc_post_init() here.
>>
>> I met the irq free issue on my i.MX8qxp MEK board before, i.e., -EBUSY is
>> returned when requesting them again, so it's tested.
> 
> A typical solution is to request all resources before binding the
> device as a component. Don't tell me that your interrupt controller is
> another component of the DRM device :-)

The IRQ handlers are _highly_ related to the CRTC driver(especially the
dc_crtc_dec_framecomplete_irq_handler() where vblank is handled), so maybe
it's more appropriate to request the IRQs and implement the IRQ handlers in
dc-crtc.c instead of doing them in dc-{de,ed}.c. No?

> 
>>
>>>
>>>>
>>>>>
>>>>>> +          if (ret)
>>>>>> +                  return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  ret = drm_vblank_init(drm, DC_DISPLAYS);
>>>>>> +  if (ret) {
>>>>>> +          dev_err(drm->dev, "failed to init vblank support: %d\n", ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  drm_mode_config_reset(drm);
>>>>>> +
>>>>>> +  drm_kms_helper_poll_init(drm);
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm)
>>>>>> +{
>>>>>> +  drm_kms_helper_poll_fini(&dc_drm->base);
>>>>>> +}
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.h b/drivers/gpu/drm/imx/dc/dc-kms.h
>>>>>> new file mode 100644
>>>>>> index 000000000000..57f6e0c15f57
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.h
>>>>>> @@ -0,0 +1,58 @@
>>>>>> +/* SPDX-License-Identifier: GPL-2.0+ */
>>>>>> +/*
>>>>>> + * Copyright 2024 NXP
>>>>>> + */
>>>>>> +
>>>>>> +#ifndef __DC_KMS_H__
>>>>>> +#define __DC_KMS_H__
>>>>>> +
>>>>>> +#include <linux/completion.h>
>>>>>> +
>>>>>> +#include <drm/drm_crtc.h>
>>>>>> +#include <drm/drm_plane.h>
>>>>>> +#include <drm/drm_vblank.h>
>>>>>> +
>>>>>> +#include "dc-de.h"
>>>>>> +#include "dc-fu.h"
>>>>>> +#include "dc-pe.h"
>>>>>> +
>>>>>> +#define DC_CRTC_IRQS      5
>>>>>> +
>>>>>> +struct dc_crtc_irq {
>>>>>> +  struct dc_crtc *dc_crtc;
>>>>>> +  unsigned int irq;
>>>>>> +};
>>>>>> +
>>>>>
>>>>> Please provide some documentation for the structure in the form of the
>>>>> kerneldoc. E.g. what is the difference between ed_cont and ed_safe?
>>>>
>>>> Will add kerneldoc for struct dc_{crtc,plane,drm_device} and tell the
>>>> difference between ed_cont(content stream) and ed_safe(safety stream).
>>>>
>>>>> The de and fg pointers are global, please don't cache them
>>>>> unnecessarily.
>>>>
>>>> Global? I don't catch your meaning, sorry. To me, it's handy to access de
>>>> and fg via the two pointers in struct dc_crtc.
>>>
>>> I had to spend some time understanding if they are per-CRTC or if there
>>> are a single instances of those subdevices. Thus I suggest to access
>>
>> They are per-CRTC.  One Display Controller contains two Display Engines,
>> hence two CRTCs.
> 
> I'm sorry, I probably meant PE here. DE (and consequently FG) are
> per-CRTC indeed.

Alright, I'll drop PE from struct dc_crtc and access it via
struct dc_drm_device in next version.

> 
>>
>>> them through dc_drm_device. You can ignore this suggestion though.
>>
>> I'll ignore this.  Thanks anyway.
>>
>>>
>>>>
>>>>>
>>>>>> +struct dc_crtc {
>>>>>> +  struct drm_crtc base;
>>>>>> +  struct dc_de *de;
>>>>>> +  struct dc_pe *pe;
>>>>>> +  struct dc_cf *cf_cont;
>>>>>> +  struct dc_cf *cf_safe;
>>>>>> +  struct dc_ed *ed_cont;
>>>>>> +  struct dc_ed *ed_safe;
>>>>>> +  struct dc_fg *fg;
>>>>>> +  unsigned int irq_dec_framecomplete;
>>>>>> +  unsigned int irq_dec_seqcomplete;
>>>>>> +  unsigned int irq_dec_shdld;
>>>>>> +  unsigned int irq_ed_cont_shdld;
>>>>>> +  unsigned int irq_ed_safe_shdld;
>>>>>> +  struct completion dec_seqcomplete_done;
>>>>>> +  struct completion dec_shdld_done;
>>>>>> +  struct completion ed_safe_shdld_done;
>>>>>> +  struct completion ed_cont_shdld_done;
>>>>>> +  struct drm_pending_vblank_event *event;
>>>>>> +  struct dc_crtc_irq irqs[DC_CRTC_IRQS];
>>>>>> +};
>>>>>> +
>>>>>> +struct dc_plane {
>>>>>> +  struct drm_plane base;
>>>>>> +  struct dc_fu *fu;
>>>>>> +  struct dc_cf *cf;
>>>>>> +  struct dc_lb *lb;
>>>>>> +  struct dc_ed *ed;
>>>>>> +};
>>>>>> +
>>>>>> +void dc_crtc_disable_at_unbind(struct drm_crtc *crtc);
>>>>>> +
>>>>>> +#endif /* __DC_KMS_H__ */
>>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-plane.c b/drivers/gpu/drm/imx/dc/dc-plane.c
>>>>>> new file mode 100644
>>>>>> index 000000000000..78d0d2cd3451
>>>>>> --- /dev/null
>>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-plane.c
>>>>>> @@ -0,0 +1,241 @@
>>>>>> +// SPDX-License-Identifier: GPL-2.0+
>>>>>> +/*
>>>>>> + * Copyright 2024 NXP
>>>>>> + */
>>>>>> +
>>>>>> +#include <linux/container_of.h>
>>>>>> +
>>>>>> +#include <drm/drm_atomic.h>
>>>>>> +#include <drm/drm_atomic_helper.h>
>>>>>> +#include <drm/drm_atomic_state_helper.h>
>>>>>> +#include <drm/drm_crtc.h>
>>>>>> +#include <drm/drm_drv.h>
>>>>>> +#include <drm/drm_fb_dma_helper.h>
>>>>>> +#include <drm/drm_fourcc.h>
>>>>>> +#include <drm/drm_framebuffer.h>
>>>>>> +#include <drm/drm_gem_atomic_helper.h>
>>>>>> +#include <drm/drm_plane_helper.h>
>>>>>> +#include <drm/drm_print.h>
>>>>>> +
>>>>>> +#include "dc-drv.h"
>>>>>> +#include "dc-fu.h"
>>>>>> +#include "dc-kms.h"
>>>>>> +
>>>>>> +#define DC_PLANE_MAX_PITCH        0x10000
>>>>>> +#define DC_PLANE_MAX_PIX_CNT      8192
>>>>>> +
>>>>>> +#define dc_plane_dbg(plane, fmt, ...)                                     \
>>>>>> +do {                                                                      \
>>>>>> +  typeof(plane) _plane = (plane);                                 \
>>>>>> +  drm_dbg_kms(_plane->dev, "[PLANE:%d:%s] " fmt,                  \
>>>>>> +              _plane->base.id, _plane->name, ##__VA_ARGS__);      \
>>>>>> +} while (0)
>>>>>> +
>>>>>> +static const uint32_t dc_plane_formats[] = {
>>>>>> +  DRM_FORMAT_XRGB8888,
>>>>>> +};
>>>>>> +
>>>>>> +static const struct drm_plane_funcs dc_plane_funcs = {
>>>>>> +  .update_plane           = drm_atomic_helper_update_plane,
>>>>>> +  .disable_plane          = drm_atomic_helper_disable_plane,
>>>>>> +  .destroy                = drm_plane_cleanup,
>>>>>> +  .reset                  = drm_atomic_helper_plane_reset,
>>>>>> +  .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
>>>>>> +  .atomic_destroy_state   = drm_atomic_helper_plane_destroy_state,
>>>>>> +};
>>>>>> +
>>>>>> +static inline struct dc_plane *to_dc_plane(struct drm_plane *plane)
>>>>>> +{
>>>>>> +  return container_of(plane, struct dc_plane, base);
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_plane_check_no_off_screen(struct drm_plane_state *state,
>>>>>> +                                  struct drm_crtc_state *crtc_state)
>>>>>> +{
>>>>>> +  if (state->dst.x1 < 0 || state->dst.y1 < 0 ||
>>>>>> +      state->dst.x2 > crtc_state->adjusted_mode.hdisplay ||
>>>>>> +      state->dst.y2 > crtc_state->adjusted_mode.vdisplay) {
>>>>>> +          dc_plane_dbg(state->plane, "no off screen\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_plane_check_max_source_resolution(struct drm_plane_state *state)
>>>>>> +{
>>>>>> +  int src_h = drm_rect_height(&state->src) >> 16;
>>>>>> +  int src_w = drm_rect_width(&state->src) >> 16;
>>>>>> +
>>>>>> +  if (src_w > DC_PLANE_MAX_PIX_CNT || src_h > DC_PLANE_MAX_PIX_CNT) {
>>>>>> +          dc_plane_dbg(state->plane, "invalid source resolution\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int dc_plane_check_fb(struct drm_plane_state *state)
>>>>>> +{
>>>>>> +  struct drm_framebuffer *fb = state->fb;
>>>>>> +  dma_addr_t baseaddr = drm_fb_dma_get_gem_addr(fb, state, 0);
>>>>>> +
>>>>>> +  /* base address alignment */
>>>>>> +  if (baseaddr & 0x3) {
>>>>>> +          dc_plane_dbg(state->plane, "fb bad baddr alignment\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  /* pitches[0] range */
>>>>>> +  if (fb->pitches[0] > DC_PLANE_MAX_PITCH) {
>>>>>> +          dc_plane_dbg(state->plane, "fb pitches[0] is out of range\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  /* pitches[0] alignment */
>>>>>> +  if (fb->pitches[0] & 0x3) {
>>>>>> +          dc_plane_dbg(state->plane, "fb bad pitches[0] alignment\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> +
>>>>>> +static int
>>>>>> +dc_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_plane_state *plane_state =
>>>>>> +                          drm_atomic_get_new_plane_state(state, plane);
>>>>>> +  struct drm_crtc_state *crtc_state;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  /* ok to disable */
>>>>>> +  if (!plane_state->fb)
>>>>>> +          return 0;
>>>>>> +
>>>>>> +  if (!plane_state->crtc) {
>>>>>> +          dc_plane_dbg(plane, "no CRTC in plane state\n");
>>>>>> +          return -EINVAL;
>>>>>> +  }
>>>>>> +
>>>>>> +  crtc_state =
>>>>>> +          drm_atomic_get_existing_crtc_state(state, plane_state->crtc);
>>>>>> +  if (WARN_ON(!crtc_state))
>>>>>> +          return -EINVAL;
>>>>>> +
>>>>>> +  ret = drm_atomic_helper_check_plane_state(plane_state, crtc_state,
>>>>>> +                                            DRM_PLANE_NO_SCALING,
>>>>>> +                                            DRM_PLANE_NO_SCALING,
>>>>>> +                                            true, false);
>>>>>> +  if (ret) {
>>>>>> +          dc_plane_dbg(plane, "failed to check plane state: %d\n", ret);
>>>>>> +          return ret;
>>>>>> +  }
>>>>>> +
>>>>>> +  ret = dc_plane_check_no_off_screen(plane_state, crtc_state);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  ret = dc_plane_check_max_source_resolution(plane_state);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  return dc_plane_check_fb(plane_state);
>>>>>> +}
>>>>>> +
>>>>>> +static void
>>>>>> +dc_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct drm_plane_state *new_state =
>>>>>> +                          drm_atomic_get_new_plane_state(state, plane);
>>>>>> +  struct dc_plane *dplane = to_dc_plane(plane);
>>>>>> +  struct drm_framebuffer *fb = new_state->fb;
>>>>>> +  const struct dc_fu_ops *fu_ops;
>>>>>> +  struct dc_lb *lb = dplane->lb;
>>>>>> +  struct dc_fu *fu = dplane->fu;
>>>>>> +  dma_addr_t baseaddr;
>>>>>> +  int src_w, src_h;
>>>>>> +  int idx;
>>>>>> +
>>>>>> +  if (!drm_dev_enter(plane->dev, &idx))
>>>>>> +          return;
>>>>>> +
>>>>>> +  src_w = drm_rect_width(&new_state->src) >> 16;
>>>>>> +  src_h = drm_rect_height(&new_state->src) >> 16;
>>>>>> +
>>>>>> +  baseaddr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
>>>>>> +
>>>>>> +  fu_ops = dc_fu_get_ops(dplane->fu);
>>>>>> +
>>>>>> +  fu_ops->set_layerblend(fu, lb);
>>>>>> +  fu_ops->set_burstlength(fu, baseaddr);
>>>>>> +  fu_ops->set_src_stride(fu, DC_FETCHUNIT_FRAC0, fb->pitches[0]);
>>>>>> +  fu_ops->set_src_buf_dimensions(fu, DC_FETCHUNIT_FRAC0, src_w, src_h);
>>>>>> +  fu_ops->set_fmt(fu, DC_FETCHUNIT_FRAC0, fb->format);
>>>>>> +  fu_ops->set_framedimensions(fu, src_w, src_h);
>>>>>> +  fu_ops->set_baseaddress(fu, DC_FETCHUNIT_FRAC0, baseaddr);
>>>>>> +  fu_ops->enable_src_buf(fu, DC_FETCHUNIT_FRAC0);
>>>>>> +
>>>>>> +  dc_plane_dbg(plane, "uses %s\n", fu_ops->get_name(fu));
>>>>>> +
>>>>>> +  dc_lb_pec_dynamic_prim_sel(lb, dc_cf_get_link_id(dplane->cf));
>>>>>> +  dc_lb_pec_dynamic_sec_sel(lb, fu_ops->get_link_id(fu));
>>>>>> +  dc_lb_mode(lb, LB_BLEND);
>>>>>> +  dc_lb_position(lb, new_state->dst.x1, new_state->dst.y1);
>>>>>> +  dc_lb_pec_clken(lb, CLKEN_AUTOMATIC);
>>>>>> +
>>>>>> +  dc_plane_dbg(plane, "uses LayerBlend%u\n", dc_lb_get_id(lb));
>>>>>> +
>>>>>> +  /* set ExtDst's source to LayerBlend */
>>>>>> +  dc_ed_pec_src_sel(dplane->ed, dc_lb_get_link_id(lb));
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static void dc_plane_atomic_disable(struct drm_plane *plane,
>>>>>> +                              struct drm_atomic_state *state)
>>>>>> +{
>>>>>> +  struct dc_plane *dplane = to_dc_plane(plane);
>>>>>> +  const struct dc_fu_ops *fu_ops;
>>>>>> +  int idx;
>>>>>> +
>>>>>> +  if (!drm_dev_enter(plane->dev, &idx))
>>>>>> +          return;
>>>>>> +
>>>>>> +  /* disable fetchunit in shadow */
>>>>>> +  fu_ops = dc_fu_get_ops(dplane->fu);
>>>>>> +  fu_ops->disable_src_buf(dplane->fu, DC_FETCHUNIT_FRAC0);
>>>>>> +
>>>>>> +  /* set ExtDst's source to ConstFrame */
>>>>>> +  dc_ed_pec_src_sel(dplane->ed, dc_cf_get_link_id(dplane->cf));
>>>>>> +
>>>>>> +  drm_dev_exit(idx);
>>>>>> +}
>>>>>> +
>>>>>> +static const struct drm_plane_helper_funcs dc_plane_helper_funcs = {
>>>>>> +  .atomic_check = dc_plane_atomic_check,
>>>>>> +  .atomic_update = dc_plane_atomic_update,
>>>>>> +  .atomic_disable = dc_plane_atomic_disable,
>>>>>> +};
>>>>>> +
>>>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane)
>>>>>> +{
>>>>>> +  struct drm_plane *plane = &dc_plane->base;
>>>>>> +  int ret;
>>>>>> +
>>>>>> +  ret = drm_universal_plane_init(&dc_drm->base, plane, 0, &dc_plane_funcs,
>>>>>> +                                 dc_plane_formats,
>>>>>> +                                 ARRAY_SIZE(dc_plane_formats),
>>>>>> +                                 NULL, DRM_PLANE_TYPE_PRIMARY, NULL);
>>>>>> +  if (ret)
>>>>>> +          return ret;
>>>>>> +
>>>>>> +  drm_plane_helper_add(plane, &dc_plane_helper_funcs);
>>>>>> +
>>>>>> +  dc_plane->fu = dc_drm->pe->fu_disp[plane->index];
>>>>>> +  dc_plane->cf = dc_drm->pe->cf_cont[plane->index];
>>>>>> +  dc_plane->lb = dc_drm->pe->lb[plane->index];
>>>>>> +  dc_plane->ed = dc_drm->pe->ed_cont[plane->index];
>>>>>> +
>>>>>> +  return 0;
>>>>>> +}
>>>>>> --
>>>>>> 2.34.1
>>>>>>
>>>>>
>>>>
>>>> --
>>>> Regards,
>>>> Liu Ying
>>>
>>
>> --
>> Regards,
>> Liu Ying
> 
> 
> 

-- 
Regards,
Liu Ying


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

* Re: [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS
  2024-12-16  6:29             ` Liu Ying
@ 2024-12-16  9:44               ` Dmitry Baryshkov
  0 siblings, 0 replies; 35+ messages in thread
From: Dmitry Baryshkov @ 2024-12-16  9:44 UTC (permalink / raw)
  To: Liu Ying
  Cc: dri-devel, devicetree, imx, linux-arm-kernel, linux-kernel,
	linux-phy, p.zabel, maarten.lankhorst, mripard, tzimmermann,
	airlied, simona, robh, krzk+dt, conor+dt, shawnguo, s.hauer,
	kernel, festevam, tglx, vkoul, kishon, aisheng.dong, agx,
	francesco, frank.li, u.kleine-koenig

On Mon, 16 Dec 2024 at 08:28, Liu Ying <victor.liu@nxp.com> wrote:
>
> On 12/13/2024, Dmitry Baryshkov wrote:
> > On Fri, 13 Dec 2024 at 08:06, Liu Ying <victor.liu@nxp.com> wrote:
> >>
> >> On 12/12/2024, Dmitry Baryshkov wrote:
> >>> On Wed, Dec 11, 2024 at 03:43:20PM +0800, Liu Ying wrote:
> >>>> On 12/10/2024, Dmitry Baryshkov wrote:
> >>>>> On Mon, Dec 09, 2024 at 11:39:16AM +0800, Liu Ying wrote:
> >>>>>> i.MX8qxp Display Controller(DC) is comprised of three main components that
> >>>>>> include a blit engine for 2D graphics accelerations, display controller for
> >>>>>> display output processing, as well as a command sequencer.  Add kernel
> >>>>>> mode setting support for the display controller part with two CRTCs and
> >>>>>> two primary planes(backed by FetchLayer and FetchWarp respectively).  The
> >>>>>> registers of the display controller are accessed without command sequencer
> >>>>>> involved, instead just by using CPU.  The command sequencer is supposed to
> >>>>>> be used by the blit engine.
> >>>>>>
> >>>>>> Signed-off-by: Liu Ying <victor.liu@nxp.com>
> >>>>>> ---
> >>>>>> v6:
> >>>>>> * No change.
> >>>>>>
> >>>>>> v5:
> >>>>>> * Replace .remove_new with .remove in dc-drv.c. (Uwe)
> >>>>>>
> >>>>>> v4:
> >>>>>> * Move dc_fg_displaymode(), dc_fg_panic_displaymode() and dc_lb_blendcontrol()
> >>>>>>   function calls from KMS routine to initialization stage. (Dmitry)
> >>>>>> * Drop dc-crtc.h and dc-plane.h header files and move relevant defines to
> >>>>>>   appropriate .h header files or .c source files. (Dmitry)
> >>>>>> * Drop futile "else" clause from dc_crtc_common_irq_handler(). (Dmitry)
> >>>>>> * Drop dc_drm->pe_rpm_count. (Dmitry)
> >>>>>> * Drop DC_{CRTCS,ENCODERS,PRIMARYS} macros and only use DC_DISPLAYS. (Dmitry)
> >>>>>> * Drop drmm_kcalloc() function call to allocate an array for storing IRQs.
> >>>>>>   Instead, put it in struct dc_crtc.  (Dmitry)
> >>>>>> * Call devm_request_irq() to request IRQs, instead of using drmm action.
> >>>>>>   (Dmitry)
> >>>>>> * Call devm_drm_of_get_bridge() to find the next bridge. (Dmitry)
> >>>>>> * Select DRM_CLIENT_SELECTION due to rebase.
> >>>>>> * Select the missing DRM_DISPLAY_HELPER and DRM_BRIDGE_CONNECTOR.
> >>>>>> * Use DRM_FBDEV_DMA_DRIVER_OPS due to rebase.
> >>>>>> * Replace drm_fbdev_dma_setup() with drm_client_setup_with_fourcc() due to
> >>>>>>   rebase.
> >>>>>> * Replace drmm_add_action_or_reset() with devm_add_action_or_reset() to
> >>>>>>   register dc_drm_component_unbind_all() action.
> >>>>>> * Request interrupts in dc_crtc_post_init() after encoder initialization to
> >>>>>>   make sure next bridge is found first.
> >>>>>>
> >>>>>> v3:
> >>>>>> * No change.
> >>>>>>
> >>>>>> v2:
> >>>>>> * Find next bridge from TCon's port.
> >>>>>> * Drop drm/drm_module.h include from dc-drv.c.
> >>>>>>
> >>>>>>  drivers/gpu/drm/imx/dc/Kconfig    |   5 +
> >>>>>>  drivers/gpu/drm/imx/dc/Makefile   |   5 +-
> >>>>>>  drivers/gpu/drm/imx/dc/dc-crtc.c  | 558 ++++++++++++++++++++++++++++++
> >>>>>>  drivers/gpu/drm/imx/dc/dc-de.h    |   3 +
> >>>>>>  drivers/gpu/drm/imx/dc/dc-drv.c   | 244 +++++++++++++
> >>>>>>  drivers/gpu/drm/imx/dc/dc-drv.h   |  19 +
> >>>>>>  drivers/gpu/drm/imx/dc/dc-kms.c   | 143 ++++++++
> >>>>>>  drivers/gpu/drm/imx/dc/dc-kms.h   |  58 ++++
> >>>>>>  drivers/gpu/drm/imx/dc/dc-plane.c | 241 +++++++++++++
> >>>>>>  9 files changed, 1274 insertions(+), 2 deletions(-)
> >>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-crtc.c
> >>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.c
> >>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-kms.h
> >>>>>>  create mode 100644 drivers/gpu/drm/imx/dc/dc-plane.c
> >>>>>>

> >>>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct component_master_ops dc_drm_ops = {
> >>>>>> +  .bind = dc_drm_bind,
> >>>>>> +  .unbind = dc_drm_unbind,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int dc_probe(struct platform_device *pdev)
> >>>>>> +{
> >>>>>> +  struct component_match *match = NULL;
> >>>>>> +  struct dc_priv *priv;
> >>>>>> +  int ret;
> >>>>>> +
> >>>>>> +  priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
> >>>>>> +  if (!priv)
> >>>>>> +          return -ENOMEM;
> >>>>>> +
> >>>>>> +  priv->clk_cfg = devm_clk_get(&pdev->dev, NULL);
> >>>>>> +  if (IS_ERR(priv->clk_cfg))
> >>>>>> +          return dev_err_probe(&pdev->dev, PTR_ERR(priv->clk_cfg),
> >>>>>> +                               "failed to get cfg clock\n");
> >>>>>> +
> >>>>>> +  dev_set_drvdata(&pdev->dev, priv);
> >>>>>> +
> >>>>>> +  ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> >>>>>> +  if (ret)
> >>>>>> +          return ret;
> >>>>>> +
> >>>>>> +  ret = devm_pm_runtime_enable(&pdev->dev);
> >>>>>> +  if (ret)
> >>>>>> +          return ret;
> >>>>>> +
> >>>>>> +  ret = devm_of_platform_populate(&pdev->dev);
> >>>>>> +  if (ret)
> >>>>>> +          return ret;
> >>>>>> +
> >>>>>> +  dc_add_components(&pdev->dev, &match);
> >>>>>> +
> >>>>>> +  ret = component_master_add_with_match(&pdev->dev, &dc_drm_ops, match);
> >>>>>> +  if (ret)
> >>>>>> +          return dev_err_probe(&pdev->dev, ret,
> >>>>>> +                               "failed to add component master\n");
> >>>>>> +
> >>>>>> +  return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void dc_remove(struct platform_device *pdev)
> >>>>>> +{
> >>>>>> +  component_master_del(&pdev->dev, &dc_drm_ops);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int dc_runtime_suspend(struct device *dev)
> >>>>>> +{
> >>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>>>> +
> >>>>>> +  clk_disable_unprepare(priv->clk_cfg);
> >>>>>> +
> >>>>>> +  return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int dc_runtime_resume(struct device *dev)
> >>>>>> +{
> >>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>>>> +  int ret;
> >>>>>> +
> >>>>>> +  ret = clk_prepare_enable(priv->clk_cfg);
> >>>>>> +  if (ret)
> >>>>>> +          dev_err(dev, "failed to enable cfg clock: %d\n", ret);
> >>>>>> +
> >>>>>> +  return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int dc_suspend(struct device *dev)
> >>>>>> +{
> >>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>>>> +
> >>>>>> +  return drm_mode_config_helper_suspend(priv->drm);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int dc_resume(struct device *dev)
> >>>>>> +{
> >>>>>> +  struct dc_priv *priv = dev_get_drvdata(dev);
> >>>>>> +
> >>>>>> +  return drm_mode_config_helper_resume(priv->drm);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void dc_shutdown(struct platform_device *pdev)
> >>>>>> +{
> >>>>>> +  struct dc_priv *priv = dev_get_drvdata(&pdev->dev);
> >>>>>> +
> >>>>>> +  drm_atomic_helper_shutdown(priv->drm);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct dev_pm_ops dc_pm_ops = {
> >>>>>> +  RUNTIME_PM_OPS(dc_runtime_suspend, dc_runtime_resume, NULL)
> >>>>>> +  SYSTEM_SLEEP_PM_OPS(dc_suspend, dc_resume)
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct of_device_id dc_dt_ids[] = {
> >>>>>> +  { .compatible = "fsl,imx8qxp-dc", },
> >>>>>> +  { /* sentinel */ }
> >>>>>> +};
> >>>>>> +MODULE_DEVICE_TABLE(of, dc_dt_ids);
> >>>>>> +
> >>>>>> +static struct platform_driver dc_driver = {
> >>>>>> +  .probe = dc_probe,
> >>>>>> +  .remove = dc_remove,
> >>>>>> +  .shutdown = dc_shutdown,
> >>>>>> +  .driver = {
> >>>>>> +          .name = "imx8-dc",
> >>>>>> +          .of_match_table = dc_dt_ids,
> >>>>>> +          .pm = pm_sleep_ptr(&dc_pm_ops),
> >>>>>> +  },
> >>>>>> +};
> >>>>>> +
> >>>>>>  static struct platform_driver * const dc_drivers[] = {
> >>>>>>    &dc_cf_driver,
> >>>>>>    &dc_de_driver,
> >>>>>> @@ -19,6 +262,7 @@ static struct platform_driver * const dc_drivers[] = {
> >>>>>>    &dc_lb_driver,
> >>>>>>    &dc_pe_driver,
> >>>>>>    &dc_tc_driver,
> >>>>>> +  &dc_driver,
> >>>>>>  };
> >>>>>>
> >>>>>>  static int __init dc_drm_init(void)
> >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-drv.h b/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>>>> index 3b11f4862c6c..39a771a13933 100644
> >>>>>> --- a/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-drv.h
> >>>>>> @@ -6,19 +6,38 @@
> >>>>>>  #ifndef __DC_DRV_H__
> >>>>>>  #define __DC_DRV_H__
> >>>>>>
> >>>>>> +#include <linux/container_of.h>
> >>>>>>  #include <linux/platform_device.h>
> >>>>>>
> >>>>>>  #include <drm/drm_device.h>
> >>>>>> +#include <drm/drm_encoder.h>
> >>>>>>
> >>>>>>  #include "dc-de.h"
> >>>>>> +#include "dc-kms.h"
> >>>>>>  #include "dc-pe.h"
> >>>>>>
> >>>>>>  struct dc_drm_device {
> >>>>>>    struct drm_device base;
> >>>>>> +  struct dc_crtc dc_crtc[DC_DISPLAYS];
> >>>>>> +  struct dc_plane dc_primary[DC_DISPLAYS];
> >>>>>> +  struct drm_encoder encoder[DC_DISPLAYS];
> >>>>>>    struct dc_de *de[DC_DISPLAYS];
> >>>>>>    struct dc_pe *pe;
> >>>>>>  };
> >>>>>>
> >>>>>> +static inline struct dc_drm_device *to_dc_drm_device(struct drm_device *drm)
> >>>>>> +{
> >>>>>> +  return container_of(drm, struct dc_drm_device, base);
> >>>>>> +}
> >>>>>> +
> >>>>>> +int dc_crtc_init(struct dc_drm_device *dc_drm, int crtc_index);
> >>>>>> +int dc_crtc_post_init(struct dc_drm_device *dc_drm, int crtc_index);
> >>>>>> +
> >>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm);
> >>>>>> +void dc_kms_uninit(struct dc_drm_device *dc_drm);
> >>>>>> +
> >>>>>> +int dc_plane_init(struct dc_drm_device *dc_drm, struct dc_plane *dc_plane);
> >>>>>> +
> >>>>>>  extern struct platform_driver dc_cf_driver;
> >>>>>>  extern struct platform_driver dc_ed_driver;
> >>>>>>  extern struct platform_driver dc_de_driver;
> >>>>>> diff --git a/drivers/gpu/drm/imx/dc/dc-kms.c b/drivers/gpu/drm/imx/dc/dc-kms.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..2b18aa37a4a8
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/gpu/drm/imx/dc/dc-kms.c
> >>>>>> @@ -0,0 +1,143 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0+
> >>>>>> +/*
> >>>>>> + * Copyright 2024 NXP
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/of.h>
> >>>>>> +#include <linux/of_graph.h>
> >>>>>> +
> >>>>>> +#include <drm/drm_atomic_helper.h>
> >>>>>> +#include <drm/drm_bridge.h>
> >>>>>> +#include <drm/drm_bridge_connector.h>
> >>>>>> +#include <drm/drm_connector.h>
> >>>>>> +#include <drm/drm_crtc.h>
> >>>>>> +#include <drm/drm_device.h>
> >>>>>> +#include <drm/drm_encoder.h>
> >>>>>> +#include <drm/drm_gem_framebuffer_helper.h>
> >>>>>> +#include <drm/drm_mode_config.h>
> >>>>>> +#include <drm/drm_print.h>
> >>>>>> +#include <drm/drm_probe_helper.h>
> >>>>>> +#include <drm/drm_simple_kms_helper.h>
> >>>>>> +#include <drm/drm_vblank.h>
> >>>>>> +
> >>>>>> +#include "dc-de.h"
> >>>>>> +#include "dc-drv.h"
> >>>>>> +#include "dc-kms.h"
> >>>>>> +
> >>>>>> +static const struct drm_mode_config_funcs dc_drm_mode_config_funcs = {
> >>>>>> +  .fb_create = drm_gem_fb_create,
> >>>>>> +  .atomic_check = drm_atomic_helper_check,
> >>>>>> +  .atomic_commit = drm_atomic_helper_commit,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int dc_kms_init_encoder_per_crtc(struct dc_drm_device *dc_drm,
> >>>>>> +                                  int crtc_index)
> >>>>>> +{
> >>>>>> +  struct dc_crtc *dc_crtc = &dc_drm->dc_crtc[crtc_index];
> >>>>>> +  struct drm_device *drm = &dc_drm->base;
> >>>>>> +  struct drm_crtc *crtc = &dc_crtc->base;
> >>>>>> +  struct drm_connector *connector;
> >>>>>> +  struct device *dev = drm->dev;
> >>>>>> +  struct drm_encoder *encoder;
> >>>>>> +  struct drm_bridge *bridge;
> >>>>>> +  int ret;
> >>>>>> +
> >>>>>> +  bridge = devm_drm_of_get_bridge(dev, dc_crtc->de->tc->dev->of_node,
> >>>>>> +                                  0, 0);
> >>>>>> +  if (IS_ERR(bridge)) {
> >>>>>> +          ret = PTR_ERR(bridge);
> >>>>>> +          if (ret == -ENODEV)
> >>>>>> +                  return 0;
> >>>>>> +
> >>>>>> +          return dev_err_probe(dev, ret,
> >>>>>> +                               "failed to find bridge for CRTC%u\n",
> >>>>>> +                               crtc->index);
> >>>>>> +  }
> >>>>>> +
> >>>>>> +  encoder = &dc_drm->encoder[crtc_index];
> >>>>>> +  ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_NONE);
> >>>>>> +  if (ret) {
> >>>>>> +          dev_err(dev, "failed to initialize encoder for CRTC%u: %d\n",
> >>>>>> +                  crtc->index, ret);
> >>>>>> +          return ret;
> >>>>>> +  }
> >>>>>> +
> >>>>>> +  encoder->possible_crtcs = drm_crtc_mask(crtc);
> >>>>>> +
> >>>>>> +  ret = drm_bridge_attach(encoder, bridge, NULL,
> >>>>>> +                          DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> >>>>>> +  if (ret) {
> >>>>>> +          dev_err(dev,
> >>>>>> +                  "failed to attach bridge to encoder for CRTC%u: %d\n",
> >>>>>> +                  crtc->index, ret);
> >>>>>> +          return ret;
> >>>>>> +  }
> >>>>>> +
> >>>>>> +  connector = drm_bridge_connector_init(drm, encoder);
> >>>>>> +  if (IS_ERR(connector)) {
> >>>>>> +          ret = PTR_ERR(connector);
> >>>>>> +          dev_err(dev, "failed to init bridge connector for CRTC%u: %d\n",
> >>>>>> +                  crtc->index, ret);
> >>>>>> +          return ret;
> >>>>>> +  }
> >>>>>> +
> >>>>>> +  ret = drm_connector_attach_encoder(connector, encoder);
> >>>>>> +  if (ret)
> >>>>>> +          dev_err(dev,
> >>>>>> +                  "failed to attach encoder to connector for CRTC%u: %d\n",
> >>>>>> +                  crtc->index, ret);
> >>>>>> +
> >>>>>> +  return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +int dc_kms_init(struct dc_drm_device *dc_drm)
> >>>>>> +{
> >>>>>> +  struct drm_device *drm = &dc_drm->base;
> >>>>>> +  int ret, i;
> >>>>>> +
> >>>>>> +  ret = drmm_mode_config_init(drm);
> >>>>>> +  if (ret)
> >>>>>> +          return ret;
> >>>>>> +
> >>>>>> +  drm->mode_config.min_width = 60;
> >>>>>> +  drm->mode_config.min_height = 60;
> >>>>>> +  drm->mode_config.max_width = 8192;
> >>>>>> +  drm->mode_config.max_height = 8192;
> >>>>>> +  drm->mode_config.funcs = &dc_drm_mode_config_funcs;
> >>>>>> +
> >>>>>> +  drm->vblank_disable_immediate = true;
> >>>>>> +  drm->max_vblank_count = DC_FRAMEGEN_MAX_FRAME_INDEX;
> >>>>>> +
> >>>>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
> >>>>>> +          ret = dc_crtc_init(dc_drm, i);
> >>>>>> +          if (ret)
> >>>>>> +                  return ret;
> >>>>>> +
> >>>>>> +          ret = dc_kms_init_encoder_per_crtc(dc_drm, i);
> >>>>>> +          if (ret)
> >>>>>> +                  return ret;
> >>>>>> +  }
> >>>>>> +
> >>>>>> +  for (i = 0; i < DC_DISPLAYS; i++) {
> >>>>>> +          ret = dc_crtc_post_init(dc_drm, i);
> >>>>>
> >>>>> Can you use .late_register for this?
> >>>>
> >>>> Kerneldoc of struct drm_crtc_funcs::late_register says it's used to register
> >>>> additional userspace interfaces like debugfs interfaces. And, it seems that
> >>>> everyone implementing this uses it to add debugfs interfaces. So, it will
> >>>> kind of abuse it to do CRTC post initialization.
> >>>
> >>> Why can't they be requested earlier then?
> >>
> >> If I request them earlier in dc_crtc_init(), then they cannot be freed by
> >> devm_irq_release() when devm_drm_of_get_bridge() called by
> >> dc_kms_init_encoder_per_crtc() returns -EPROBE_DEFER(which means failing
> >> to find the first DRM bridge for the CRTC).  Why can't they be freed by
> >> devm_irq_release()?  Because they are requested by the devices of ExtDsts
> >> and Display Engines and their drivers are not removed during the probe
> >> deferral dance.  Furthermore, -EPROBE_DEFER won't be returned after
> >> dc_crtc_post_init() since the later called drm_vblank_init() doesn't
> >> return -EPROBE_DEFER anyway, so it's fine to call dc_crtc_post_init() here.
> >>
> >> I met the irq free issue on my i.MX8qxp MEK board before, i.e., -EBUSY is
> >> returned when requesting them again, so it's tested.
> >
> > A typical solution is to request all resources before binding the
> > device as a component. Don't tell me that your interrupt controller is
> > another component of the DRM device :-)
>
> The IRQ handlers are _highly_ related to the CRTC driver(especially the
> dc_crtc_dec_framecomplete_irq_handler() where vblank is handled), so maybe
> it's more appropriate to request the IRQs and implement the IRQ handlers in
> dc-crtc.c instead of doing them in dc-{de,ed}.c. No?

And CRTCs don't exist before master_bind(). Ack.

-- 
With best wishes
Dmitry


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

end of thread, other threads:[~2024-12-16  9:45 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-12-09  3:39 [PATCH v6 00/19] Add Freescale i.MX8qxp Display Controller support Liu Ying
2024-12-09  3:39 ` [PATCH v6 01/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller processing units Liu Ying
2024-12-10 22:15   ` Rob Herring
2024-12-11  3:05     ` Liu Ying
2024-12-11 14:55       ` Rob Herring
2024-12-13  3:40         ` Liu Ying
2024-12-09  3:39 ` [PATCH v6 02/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller blit engine Liu Ying
2024-12-09  3:39 ` [PATCH v6 03/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller display engine Liu Ying
2024-12-09  3:39 ` [PATCH v6 04/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
2024-12-09  3:39 ` [PATCH v6 05/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller AXI performance counter Liu Ying
2024-12-09  3:39 ` [PATCH v6 06/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller command sequencer Liu Ying
2024-12-09  3:39 ` [PATCH v6 07/19] dt-bindings: interrupt-controller: Add i.MX8qxp Display Controller interrupt controller Liu Ying
2024-12-09  3:39 ` [PATCH v6 08/19] dt-bindings: display: imx: Add i.MX8qxp Display Controller Liu Ying
2024-12-09  3:39 ` [PATCH v6 09/19] drm/imx: Add i.MX8qxp Display Controller display engine Liu Ying
2024-12-12  8:02   ` Maxime Ripard
2024-12-09  3:39 ` [PATCH v6 10/19] drm/imx: Add i.MX8qxp Display Controller pixel engine Liu Ying
2024-12-12  8:02   ` Maxime Ripard
2024-12-09  3:39 ` [PATCH v6 11/19] drm/imx: Add i.MX8qxp Display Controller interrupt controller Liu Ying
2024-12-12  8:02   ` Maxime Ripard
2024-12-09  3:39 ` [PATCH v6 12/19] drm/imx: Add i.MX8qxp Display Controller KMS Liu Ying
2024-12-09 16:17   ` Dmitry Baryshkov
2024-12-11  7:43     ` Liu Ying
2024-12-11 23:35       ` Dmitry Baryshkov
2024-12-13  6:07         ` Liu Ying
2024-12-13 13:28           ` Dmitry Baryshkov
2024-12-16  6:29             ` Liu Ying
2024-12-16  9:44               ` Dmitry Baryshkov
2024-12-12  8:02   ` Maxime Ripard
2024-12-09  3:39 ` [PATCH v6 13/19] MAINTAINERS: Add maintainer for i.MX8qxp Display Controller Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 14/19] dt-bindings: phy: mixel, mipi-dsi-phy: Allow assigned-clock* properties Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 15/19] dt-bindings: firmware: imx: Add SCU controlled display pixel link nodes Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 16/19] arm64: dts: imx8qxp: Add display controller subsystem Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 17/19] arm64: dts: imx8qxp: Add MIPI-LVDS combo subsystems Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 18/19] arm64: dts: imx8qxp-mek: Enable display controller Liu Ying
2024-12-09  3:39 ` [DO NOT MERGE PATCH v6 19/19] arm64: dts: imx8qxp-mek: Add MX8-DLVDS-LCD1 display module support Liu Ying

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).