* [PATCH v9 0/9] Add support for i.MX94 DCIF
@ 2026-06-12 11:58 Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 1/9] dt-bindings: display: fsl,ldb: Add i.MX94 LDB Laurentiu Palcu
` (8 more replies)
0 siblings, 9 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Abel Vesa, Peng Fan, Michael Turquette,
Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Andrzej Hajda, Neil Armstrong, Robert Foss,
Laurent Pinchart, Jonas Karlman, Jernej Skrabec, David Airlie,
Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Philipp Zabel, Marek Vasut
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
Hi,
This patch-set adds support for the i.MX94 Display Control Interface.
Also, included in the patch-set is patch that the DCIF driver depends on
for functioning properly:
* 1/9 - 3/9 : add support for i.MX94 to fsl-ldb driver. It also
contains a patch (2/9) from Liu Ying that was already reviewed
and was part of another patch-set ([1]), but was never merged;
Thanks,
Laurentiu
[1] https://lkml.org/lkml/2024/11/14/262
---
Changes in v9:
- Rebased to latest linux-next (next-20260611);
- 2/9: Reworked to store the next bridge in fsl_ldb->bridge.next_bridge
(taking a reference with drm_bridge_get()) instead of adding a new
dedicated field. Removed the r-b tags for this patch since it needs a
fresh review;
- 3/9: Fixed the i.MX94 LDB max_clk_khz limit (165 MHz -> 148.5 MHz) to
match the actual hardware limit. Removed the r-b tags for this patch
since it needs a fresh review;
- 4/9: Added a 'required:' properties list to the binding. Removed
Krzysztof's r-b tag for this patch since it needs a fresh review;
- 5/9: Renamed the CRC source helpers (dcif_crc_source_*) and made them
unconditionally available, dropping the CONFIG_DEBUG_FS stubs;
improved CRC source parsing (auto ROI for the full-frame source,
unsigned params, safer string handling); rebased onto the
drm_atomic_state -> drm_atomic_commit rename in linux-next; reworked
CRC/modeset gating with new has_crc and crtc_pm_enabled flags and
proper PM error handling; switched event_lock to spinlock_irq; removed
a redundant connector-attach call and fixed an error format string;
added new pixel formats and full alpha/blend-mode support;
- 6/9: YAML quoting style fix only, no functional change;
- 7/9: Combined the former "arm64: dts: imx943: Add LVDS/DISPLAY CSR
nodes" prerequisite and the "arm64: dts: imx943: Add display pipeline
nodes" patch into a single new patch targeting the shared imx94.dtsi
instead of imx943.dtsi, removing the dependency on Peng Fan's
not-yet-merged patch;
- 8/9: Renamed the IT6263 bridge node to 'hdmi@4c' and fixed its
reset-gpios polarity to GPIO_ACTIVE_LOW;
- Link to v8: https://lore.kernel.org/r/20260304-dcif-upstreaming-v8-0-bec5c047edd4@oss.nxp.com
Changes in v8:
- Rebased to latest linux-next (next-20260303). Patch 2/9 had a minor
conflict bacause of a patch introduced recently;
- 8/9: Fixed CHECK_DTBS errors reported by Rob's bot due to missing
regulators. Removed the r-b tag for this patch because it needs a
fresh review;
- Link to v7: https://lore.kernel.org/r/20260122-dcif-upstreaming-v7-0-19ea17eb046f@oss.nxp.com
Changes in v7:
- Rebased to latest linux-next;
- Addressed some new checkpatch warnings: kzalloc -> kzalloc_obj;
- Fixed a couple of static check warnings in probe();
- Added Luca's r-b tag for bridge refcounting;
- Link to v6: https://lore.kernel.org/r/20251103-dcif-upstreaming-v6-0-76fcecfda919@oss.nxp.com
Changes in v6:
- 2/9: Collected r-b tag from Francesco;
- 3/9: Removed ch_max_clk_khz variable as suggested by Luca and added
his r-b tag;
- 4/9: Collected r-b tag;
- 5/9: Call drm_bridge_put() automatically in
dcif_crtc_query_output_bus_format() by using a cleanup action (Luca);
- 6/9: Moved allOf: block after required: block (Krzysztof). Collected
r-b tag;
- Link to v5: https://lore.kernel.org/r/20250911-dcif-upstreaming-v5-0-a1e8dab8ae40@oss.nxp.com
Changes in v5:
- 4/9: Removed "bindings for" from the title, changed the port
definition and simplified the example;
- 6/9: Fixed the way 'ldb' child node is declared: declare the
'ldb' child node out of if:then: block and set the property
to false for compatibles other than nxp,imx94-lvds-csr;
- Link to v4: https://lore.kernel.org/r/20250903123332.2569241-1-laurentiu.palcu@oss.nxp.com
Changes in v4:
- Addressed remaining DCIF driver comments from Frank;
- Limit the 'ldb' child node only to CSRs compatible with 'nxp,imx94-lvds-csr'
in the binding file. Since LVDS CSRs are a minority, I chose to
use the if:then: construct instead of if:not:then:;
- Remove the '#address-cells' and '#size-cells' from the ldb node, in
imx94.dtsi, as they're not needed;
- Link to v3: https://lore.kernel.org/r/20250806150521.2174797-1-laurentiu.palcu@oss.nxp.com
Changes in v3:
- Removed the BLK CTL patches and created a separate patch set [2] for them;
- Collected r-b tags for 1/9, 2/9, 3/9 and 9/9;
- Removed the DCIF QoS functionality until I find a better way to
implement it through syscon. QoS functionality will be added in
subsequent patches. Also, used devm_clk_bulk_get_all() and used
dev_err_probe() as suggested;
- Addressed Frank's and Krzysztof's comments on the DCIF bindings;
- Addressed Frank's comments on dtsi and dts files;
- Added a new binding patch, 6/9, for adding 'ldb' optional property to
nxp,imx95-blk-ctl.yaml;
- Link to v2: https://lore.kernel.org/r/20250716081519.3400158-1-laurentiu.palcu@oss.nxp.com
Changes in v2:
- reworked the BLK_CTL patch and split in 2 to make it easier for
review;
- split the dts and dtsi patch in 2 separate ones;
- addressed Frank's comments in DCIF driver;
- addressed Rob's comments for the bindings files;
- addressed a couple of checkpatch issues;
- Link to v1: https://lore.kernel.org/r/20250709122332.2874632-1-laurentiu.palcu@oss.nxp.com
---
Laurentiu Palcu (7):
dt-bindings: display: fsl,ldb: Add i.MX94 LDB
drm/bridge: fsl-ldb: Add support for i.MX94
dt-bindings: display: imx: Add i.MX94 DCIF
dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node
arm64: dts: imx94: Add display pipeline nodes
arm64: dts: imx943-evk: Add display support using IT6263
MAINTAINERS: Add entry for i.MX94 DCIF driver
Liu Ying (1):
drm/bridge: fsl-ldb: Get the next non-panel bridge
Sandor Yu (1):
drm/imx: Add support for i.MX94 DCIF
.../bindings/clock/nxp,imx95-blk-ctl.yaml | 26 +
.../bindings/display/bridge/fsl,ldb.yaml | 2 +
.../bindings/display/imx/nxp,imx94-dcif.yaml | 90 +++
MAINTAINERS | 9 +
arch/arm64/boot/dts/freescale/imx94.dtsi | 82 +++
arch/arm64/boot/dts/freescale/imx943-evk.dts | 86 +++
drivers/gpu/drm/bridge/fsl-ldb.c | 46 +-
drivers/gpu/drm/imx/Kconfig | 1 +
drivers/gpu/drm/imx/Makefile | 1 +
drivers/gpu/drm/imx/dcif/Kconfig | 15 +
drivers/gpu/drm/imx/dcif/Makefile | 5 +
drivers/gpu/drm/imx/dcif/dcif-crc.c | 215 +++++++
drivers/gpu/drm/imx/dcif/dcif-crc.h | 43 ++
drivers/gpu/drm/imx/dcif/dcif-crtc.c | 705 +++++++++++++++++++++
drivers/gpu/drm/imx/dcif/dcif-drv.c | 233 +++++++
drivers/gpu/drm/imx/dcif/dcif-drv.h | 89 +++
drivers/gpu/drm/imx/dcif/dcif-kms.c | 96 +++
drivers/gpu/drm/imx/dcif/dcif-plane.c | 308 +++++++++
drivers/gpu/drm/imx/dcif/dcif-reg.h | 267 ++++++++
19 files changed, 2299 insertions(+), 20 deletions(-)
---
base-commit: e7b907ffb2cd66314df92360e41f7bd5fdaa8182
change-id: 20260602-dcif-upstreaming-fb177f3c9351
Best regards,
--
Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v9 1/9] dt-bindings: display: fsl,ldb: Add i.MX94 LDB
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 2/9] drm/bridge: fsl-ldb: Get the next non-panel bridge Laurentiu Palcu
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Marek Vasut
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
i.MX94 has a single LVDS port and share similar LDB and LVDS control
registers as i.MX8MP and i.MX93.
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml
index 7f380879fffdf..fb70409161fc0 100644
--- a/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml
+++ b/Documentation/devicetree/bindings/display/bridge/fsl,ldb.yaml
@@ -20,6 +20,7 @@ properties:
- fsl,imx6sx-ldb
- fsl,imx8mp-ldb
- fsl,imx93-ldb
+ - fsl,imx94-ldb
clocks:
maxItems: 1
@@ -78,6 +79,7 @@ allOf:
enum:
- fsl,imx6sx-ldb
- fsl,imx93-ldb
+ - fsl,imx94-ldb
then:
properties:
ports:
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 2/9] drm/bridge: fsl-ldb: Get the next non-panel bridge
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 1/9] dt-bindings: display: fsl,ldb: Add i.MX94 LDB Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94 Laurentiu Palcu
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
From: Liu Ying <victor.liu@nxp.com>
The next bridge in bridge chain could be a panel bridge or a non-panel
bridge. Use devm_drm_of_get_bridge() to replace the combination
function calls of of_drm_find_panel() and devm_drm_panel_bridge_add()
to get either a panel bridge or a non-panel bridge, instead of getting
a panel bridge only.
Signed-off-by: Liu Ying <victor.liu@nxp.com>
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
drivers/gpu/drm/bridge/fsl-ldb.c | 31 ++++++++++++-------------------
1 file changed, 12 insertions(+), 19 deletions(-)
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
index 9bfaa3f933709..bd03c36ee696c 100644
--- a/drivers/gpu/drm/bridge/fsl-ldb.c
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -15,7 +15,6 @@
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_of.h>
-#include <drm/drm_panel.h>
#define LDB_CTRL_CH0_ENABLE BIT(0)
#define LDB_CTRL_CH0_DI_SELECT BIT(1)
@@ -86,7 +85,6 @@ static const struct fsl_ldb_devdata fsl_ldb_devdata[] = {
struct fsl_ldb {
struct device *dev;
struct drm_bridge bridge;
- struct drm_bridge *panel_bridge;
struct clk *clk;
struct regmap *regmap;
const struct fsl_ldb_devdata *devdata;
@@ -119,7 +117,7 @@ static int fsl_ldb_attach(struct drm_bridge *bridge,
{
struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
- return drm_bridge_attach(encoder, fsl_ldb->panel_bridge,
+ return drm_bridge_attach(encoder, fsl_ldb->bridge.next_bridge,
bridge, flags);
}
@@ -296,9 +294,8 @@ static const struct drm_bridge_funcs funcs = {
static int fsl_ldb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *panel_node;
struct device_node *remote1, *remote2;
- struct drm_panel *panel;
+ struct drm_bridge *next_bridge;
struct fsl_ldb *fsl_ldb;
int dual_link;
@@ -321,36 +318,32 @@ static int fsl_ldb_probe(struct platform_device *pdev)
if (IS_ERR(fsl_ldb->regmap))
return PTR_ERR(fsl_ldb->regmap);
- /* Locate the remote ports and the panel node */
+ /* Locate the remote ports. */
remote1 = of_graph_get_remote_node(dev->of_node, 1, 0);
remote2 = of_graph_get_remote_node(dev->of_node, 2, 0);
fsl_ldb->ch0_enabled = (remote1 != NULL);
fsl_ldb->ch1_enabled = (remote2 != NULL);
- panel_node = of_node_get(remote1 ? remote1 : remote2);
of_node_put(remote1);
of_node_put(remote2);
- if (!fsl_ldb->ch0_enabled && !fsl_ldb->ch1_enabled) {
- of_node_put(panel_node);
- return dev_err_probe(dev, -ENXIO, "No panel node found");
- }
+ if (!fsl_ldb->ch0_enabled && !fsl_ldb->ch1_enabled)
+ return dev_err_probe(dev, -ENXIO, "No next bridge node found");
dev_dbg(dev, "Using %s\n",
fsl_ldb_is_dual(fsl_ldb) ? "dual-link mode" :
fsl_ldb->ch0_enabled ? "channel 0" : "channel 1");
- panel = of_drm_find_panel(panel_node);
- of_node_put(panel_node);
- if (IS_ERR(panel))
- return PTR_ERR(panel);
-
if (of_property_present(dev->of_node, "nxp,enable-termination-resistor"))
fsl_ldb->use_termination_resistor = true;
- fsl_ldb->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
- if (IS_ERR(fsl_ldb->panel_bridge))
- return PTR_ERR(fsl_ldb->panel_bridge);
+ next_bridge = devm_drm_of_get_bridge(dev, dev->of_node,
+ fsl_ldb->ch0_enabled ? 1 : 2,
+ 0);
+ if (IS_ERR(next_bridge))
+ return dev_err_probe(dev, PTR_ERR(next_bridge),
+ "failed to get next bridge\n");
+ fsl_ldb->bridge.next_bridge = drm_bridge_get(next_bridge);
if (fsl_ldb_is_dual(fsl_ldb)) {
struct device_node *port1, *port2;
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 1/9] dt-bindings: display: fsl,ldb: Add i.MX94 LDB Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 2/9] drm/bridge: fsl-ldb: Get the next non-panel bridge Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 12:09 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 4/9] dt-bindings: display: imx: Add i.MX94 DCIF Laurentiu Palcu
` (5 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Andrzej Hajda, Neil Armstrong,
Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
i.MX94 series LDB controller shares the same LDB and LVDS control
registers as i.MX8MP and i.MX93 but supports a higher maximum clock
frequency.
Add a 'max_clk_khz' member to the fsl_ldb_devdata structure in order to
be able to set different max frequencies for other platforms.
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
drivers/gpu/drm/bridge/fsl-ldb.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
index bd03c36ee696c..b4959f654f2ac 100644
--- a/drivers/gpu/drm/bridge/fsl-ldb.c
+++ b/drivers/gpu/drm/bridge/fsl-ldb.c
@@ -57,6 +57,7 @@ enum fsl_ldb_devtype {
IMX6SX_LDB,
IMX8MP_LDB,
IMX93_LDB,
+ IMX94_LDB,
};
struct fsl_ldb_devdata {
@@ -64,21 +65,31 @@ struct fsl_ldb_devdata {
u32 lvds_ctrl;
bool lvds_en_bit;
bool single_ctrl_reg;
+ u32 max_clk_khz;
};
static const struct fsl_ldb_devdata fsl_ldb_devdata[] = {
[IMX6SX_LDB] = {
.ldb_ctrl = 0x18,
.single_ctrl_reg = true,
+ .max_clk_khz = 80000,
},
[IMX8MP_LDB] = {
.ldb_ctrl = 0x5c,
.lvds_ctrl = 0x128,
+ .max_clk_khz = 80000,
},
[IMX93_LDB] = {
.ldb_ctrl = 0x20,
.lvds_ctrl = 0x24,
.lvds_en_bit = true,
+ .max_clk_khz = 80000,
+ },
+ [IMX94_LDB] = {
+ .ldb_ctrl = 0x04,
+ .lvds_ctrl = 0x08,
+ .lvds_en_bit = true,
+ .max_clk_khz = 148500,
},
};
@@ -274,7 +285,7 @@ fsl_ldb_mode_valid(struct drm_bridge *bridge,
{
struct fsl_ldb *fsl_ldb = to_fsl_ldb(bridge);
- if (mode->clock > (fsl_ldb_is_dual(fsl_ldb) ? 160000 : 80000))
+ if (mode->clock > (fsl_ldb_is_dual(fsl_ldb) ? 2 : 1) * fsl_ldb->devdata->max_clk_khz)
return MODE_CLOCK_HIGH;
return MODE_OK;
@@ -386,6 +397,8 @@ static const struct of_device_id fsl_ldb_match[] = {
.data = &fsl_ldb_devdata[IMX8MP_LDB], },
{ .compatible = "fsl,imx93-ldb",
.data = &fsl_ldb_devdata[IMX93_LDB], },
+ { .compatible = "fsl,imx94-ldb",
+ .data = &fsl_ldb_devdata[IMX94_LDB], },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, fsl_ldb_match);
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 4/9] dt-bindings: display: imx: Add i.MX94 DCIF
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (2 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94 Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 5/9] drm/imx: Add support for " Laurentiu Palcu
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Philipp Zabel, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
DCIF is the i.MX94 Display Controller Interface which is used to
drive a TFT LCD panel or connects to a display interface depending
on the chip configuration.
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
.../bindings/display/imx/nxp,imx94-dcif.yaml | 90 ++++++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/imx/nxp,imx94-dcif.yaml b/Documentation/devicetree/bindings/display/imx/nxp,imx94-dcif.yaml
new file mode 100644
index 0000000000000..8894e87666972
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/imx/nxp,imx94-dcif.yaml
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+# Copyright 2025 NXP
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/imx/nxp,imx94-dcif.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: i.MX94 Display Control Interface (DCIF)
+
+maintainers:
+ - Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
+
+description:
+ The Display Control Interface(DCIF) is a system master that fetches graphics
+ stored in memory and displays them on a TFT LCD panel or connects to a
+ display interface depending on the chip configuration.
+
+properties:
+ compatible:
+ const: nxp,imx94-dcif
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ items:
+ - description: CPU domain 0 (controlled by common registers group).
+ - description: CPU domain 1 (controlled by background layer registers group).
+ - description: CPU domain 2 (controlled by foreground layer registers group).
+
+ interrupt-names:
+ items:
+ - const: common
+ - const: bg_layer
+ - const: fg_layer
+
+ clocks:
+ maxItems: 3
+
+ clock-names:
+ items:
+ - const: apb
+ - const: axi
+ - const: pix
+
+ power-domains:
+ maxItems: 1
+
+ port:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: Display Pixel Interface(DPI) output port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - power-domains
+ - port
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ display-controller@4b120000 {
+ compatible = "nxp,imx94-dcif";
+ reg = <0x4b120000 0x300000>;
+ interrupts = <GIC_SPI 377 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "common", "bg_layer", "fg_layer";
+ clocks = <&scmi_clk 69>, <&scmi_clk 70>, <&dispmix_csr 0>;
+ clock-names = "apb", "axi", "pix";
+ assigned-clocks = <&dispmix_csr 0>;
+ assigned-clock-parents = <&ldb_pll_pixel>;
+ power-domains = <&scmi_devpd 11>;
+ port {
+ dcif_out: endpoint {
+ remote-endpoint = <&ldb_in>;
+ };
+ };
+ };
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 5/9] drm/imx: Add support for i.MX94 DCIF
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (3 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 4/9] dt-bindings: display: imx: Add i.MX94 DCIF Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 12:18 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node Laurentiu Palcu
` (3 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
From: Sandor Yu <sandor.yu@nxp.com>
The i.MX94 Display Control Interface features:
* Up to maximum 3 layers of alpha blending:
- 1 background layer(Layer 0);
- 1 foreground layer(Layer 1);
- A programmable constant color behind the background layer;
* Each layer supports:
- programmable plane size;
- programmable background color;
- embedded alpha and global alpha;
* Data output with CRC checksum for 4 programmable regions;
Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # bridge refcounting
Signed-off-by: Sandor Yu <sandor.yu@nxp.com>
Co-developed-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
drivers/gpu/drm/imx/Kconfig | 1 +
drivers/gpu/drm/imx/Makefile | 1 +
drivers/gpu/drm/imx/dcif/Kconfig | 15 +
drivers/gpu/drm/imx/dcif/Makefile | 5 +
drivers/gpu/drm/imx/dcif/dcif-crc.c | 215 +++++++++++
drivers/gpu/drm/imx/dcif/dcif-crc.h | 43 +++
drivers/gpu/drm/imx/dcif/dcif-crtc.c | 705 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/imx/dcif/dcif-drv.c | 233 +++++++++++
drivers/gpu/drm/imx/dcif/dcif-drv.h | 89 +++++
drivers/gpu/drm/imx/dcif/dcif-kms.c | 96 +++++
drivers/gpu/drm/imx/dcif/dcif-plane.c | 308 +++++++++++++++
drivers/gpu/drm/imx/dcif/dcif-reg.h | 267 +++++++++++++
12 files changed, 1978 insertions(+)
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
index 3e8c6edbc17c2..1b6ced5c60b51 100644
--- a/drivers/gpu/drm/imx/Kconfig
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
source "drivers/gpu/drm/imx/dc/Kconfig"
+source "drivers/gpu/drm/imx/dcif/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 c7b317640d71d..2b9fd85eefaa3 100644
--- a/drivers/gpu/drm/imx/Makefile
+++ b/drivers/gpu/drm/imx/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_IMX8_DC) += dc/
+obj-$(CONFIG_DRM_IMX_DCIF) += dcif/
obj-$(CONFIG_DRM_IMX_DCSS) += dcss/
obj-$(CONFIG_DRM_IMX) += ipuv3/
obj-$(CONFIG_DRM_IMX_LCDC) += lcdc/
diff --git a/drivers/gpu/drm/imx/dcif/Kconfig b/drivers/gpu/drm/imx/dcif/Kconfig
new file mode 100644
index 0000000000000..c33c662721d36
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/Kconfig
@@ -0,0 +1,15 @@
+config DRM_IMX_DCIF
+ tristate "DRM support for NXP i.MX94 DCIF"
+ select DRM_KMS_HELPER
+ select VIDEOMODE_HELPERS
+ select DRM_GEM_DMA_HELPER
+ select DRM_DISPLAY_HELPER
+ select DRM_BRIDGE_CONNECTOR
+ select DRM_CLIENT_SELECTION
+ depends on DRM && OF && ARCH_MXC
+ depends on COMMON_CLK
+ help
+ Enable NXP i.MX94 Display Control Interface(DCIF) support. The DCIF is
+ a system master that fetches graphics stored in memory and displays
+ them on a TFT LCD panel or connects to a display interface depending
+ on the chip configuration.
diff --git a/drivers/gpu/drm/imx/dcif/Makefile b/drivers/gpu/drm/imx/dcif/Makefile
new file mode 100644
index 0000000000000..b429572040f0e
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+
+imx-dcif-drm-objs := dcif-crc.o dcif-crtc.o dcif-drv.o dcif-kms.o dcif-plane.o
+
+obj-$(CONFIG_DRM_IMX_DCIF) += imx-dcif-drm.o
diff --git a/drivers/gpu/drm/imx/dcif/dcif-crc.c b/drivers/gpu/drm/imx/dcif/dcif-crc.c
new file mode 100644
index 0000000000000..dee36e5ca6793
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-crc.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_rect.h>
+
+#include "dcif-crc.h"
+#include "dcif-reg.h"
+
+#define MAX_DCIF_CRC_NUM 4
+
+static int dcif_crc_config(struct dcif_dev *dcif, struct drm_rect *roi, int ncrc)
+{
+ int pos, size;
+
+ if (ncrc >= MAX_DCIF_CRC_NUM)
+ return -EINVAL;
+
+ pos = DCIF_CRC_POS_CRC_HOR_POS(roi->x1) |
+ DCIF_CRC_POS_CRC_VER_POS(roi->y1);
+ size = DCIF_CRC_SIZE_CRC_HOR_SIZE(roi->x2 - roi->x1) |
+ DCIF_CRC_SIZE_CRC_VER_SIZE(roi->y2 - roi->y1);
+
+ regmap_write(dcif->regmap, DCIF_CRC_POS_R(ncrc), pos);
+ regmap_write(dcif->regmap, DCIF_CRC_SIZE_R(ncrc), size);
+
+ regmap_set_bits(dcif->regmap, DCIF_CRC_CTRL,
+ DCIF_CRC_CTRL_CRC_EN(ncrc) | DCIF_CRC_CTRL_CRC_ERR_CNT_RST);
+
+ return 0;
+}
+
+void dcif_crc_source_enable(struct dcif_dev *dcif, enum dcif_crc_source source,
+ struct drm_rect *roi, int ncrc)
+{
+ if (ncrc >= MAX_DCIF_CRC_NUM)
+ return;
+
+ if (source == DCIF_CRC_SRC_NONE)
+ return;
+
+ if (dcif->crc_is_enabled)
+ return;
+
+ dcif_crc_config(dcif, roi, ncrc);
+
+ regmap_set_bits(dcif->regmap, DCIF_CRC_CTRL,
+ DCIF_CRC_CTRL_CRC_MODE | DCIF_CRC_CTRL_CRC_SHADOW_LOAD_EN |
+ DCIF_CRC_CTRL_CRC_TRIG);
+
+ dcif->crc_is_enabled = true;
+}
+
+void dcif_crc_source_disable(struct dcif_dev *dcif, int ncrc)
+{
+ if (!dcif->crc_is_enabled)
+ return;
+
+ if (ncrc >= MAX_DCIF_CRC_NUM)
+ return;
+
+ regmap_clear_bits(dcif->regmap, DCIF_CRC_CTRL, DCIF_CRC_CTRL_CRC_EN(ncrc));
+
+ dcif->crc_is_enabled = false;
+}
+
+/*
+ * Supported modes and source names:
+ * 1) auto mode:
+ * "auto" should be selected as the source name.
+ * The evaluation window is the same to the display region as
+ * indicated by drm_crtc_state->adjusted_mode.
+ *
+ * 2) region of interest(ROI) mode:
+ * "roi:x1,y1,x2,y2" should be selected as the source name.
+ * The region of interest is defined by the inclusive upper left
+ * position at (x1, y1) and the exclusive lower right position
+ * at (x2, y2), see struct drm_rect for the same idea.
+ * The evaluation window is the region of interest.
+ */
+static int dcif_crc_source_parse(struct drm_crtc *crtc, const char *source_name,
+ enum dcif_crc_source *s, struct drm_rect *roi)
+{
+ static const char roi_prefix[] = "roi:";
+
+ if (!source_name) {
+ *s = DCIF_CRC_SRC_NONE;
+ } else if (!strcmp(source_name, "auto")) {
+ struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+
+ roi->x1 = 0;
+ roi->y1 = 0;
+ roi->x2 = mode->hdisplay;
+ roi->y2 = mode->vdisplay;
+
+ *s = DCIF_CRC_SRC_FRAME;
+ } else if (strstarts(source_name, roi_prefix)) {
+ int len = strlen(roi_prefix);
+ unsigned int params[4];
+ char *options, *opt;
+ int i = 0, ret;
+
+ char *buf __free(kfree) = kstrdup(source_name + len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ options = buf;
+
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (i > 3)
+ return -EINVAL;
+
+ ret = kstrtouint(opt, 10, ¶ms[i]);
+ if (ret < 0)
+ return ret;
+
+ i++;
+ }
+
+ if (i != 4)
+ return -EINVAL;
+
+ roi->x1 = params[0];
+ roi->y1 = params[1];
+ roi->x2 = params[2];
+ roi->y2 = params[3];
+
+ if (!drm_rect_visible(roi))
+ return -EINVAL;
+
+ *s = DCIF_CRC_SRC_FRAME_ROI;
+ } else {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int dcif_crc_source_verify(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt)
+{
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ enum dcif_crc_source source;
+ struct drm_rect roi;
+
+ if (dcif_crc_source_parse(crtc, source_name, &source, &roi) < 0) {
+ dev_dbg(dcif->drm.dev, "unknown source %s\n", source_name);
+ return -EINVAL;
+ }
+
+ *values_cnt = 1;
+
+ return 0;
+}
+
+int dcif_crc_source_set(struct drm_crtc *crtc, const char *source_name)
+{
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ struct drm_modeset_acquire_ctx ctx;
+ struct drm_crtc_state *crtc_state;
+ struct drm_atomic_commit *state;
+ struct drm_rect roi = {0, 0, 0, 0};
+ enum dcif_crc_source source;
+ int ret;
+
+ if (dcif_crc_source_parse(crtc, source_name, &source, &roi) < 0) {
+ dev_dbg(dcif->drm.dev, "unknown source %s\n", source_name);
+ return -EINVAL;
+ }
+
+ /* Perform an atomic commit to set the CRC source. */
+ drm_modeset_acquire_init(&ctx, 0);
+
+ state = drm_atomic_commit_alloc(crtc->dev);
+ if (!state) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ state->acquire_ctx = &ctx;
+
+retry:
+ crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (!IS_ERR(crtc_state)) {
+ struct dcif_crtc_state *dcif_crtc_state;
+
+ dcif_crtc_state = to_dcif_crtc_state(crtc_state);
+
+ dcif_crtc_state->crc.source = source;
+ dcif_copy_roi(&roi, &dcif_crtc_state->crc.roi);
+
+ ret = drm_atomic_commit(state);
+ } else {
+ ret = PTR_ERR(crtc_state);
+ }
+
+ if (ret == -EDEADLK) {
+ drm_atomic_commit_clear(state);
+ drm_modeset_backoff(&ctx);
+ goto retry;
+ }
+
+ drm_atomic_commit_put(state);
+
+unlock:
+ drm_modeset_drop_locks(&ctx);
+ drm_modeset_acquire_fini(&ctx);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/imx/dcif/dcif-crc.h b/drivers/gpu/drm/imx/dcif/dcif-crc.h
new file mode 100644
index 0000000000000..6ccb1b7186732
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-crc.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#ifndef __DCIF_CRC_H__
+#define __DCIF_CRC_H__
+
+#include <linux/types.h>
+
+#include "dcif-drv.h"
+
+static inline bool enable_dcif_crc_needed(struct dcif_crtc_state *new_dcstate,
+ struct dcif_crtc_state *old_dcstate)
+{
+ return old_dcstate->crc.source == DCIF_CRC_SRC_NONE &&
+ new_dcstate->crc.source != DCIF_CRC_SRC_NONE;
+}
+
+static inline bool disable_dcif_crc_needed(struct dcif_crtc_state *new_dcstate,
+ struct dcif_crtc_state *old_dcstate)
+{
+ return old_dcstate->crc.source != DCIF_CRC_SRC_NONE &&
+ new_dcstate->crc.source == DCIF_CRC_SRC_NONE;
+}
+
+static inline void dcif_copy_roi(struct drm_rect *from, struct drm_rect *to)
+{
+ to->x1 = from->x1;
+ to->y1 = from->y1;
+ to->x2 = from->x2;
+ to->y2 = from->y2;
+}
+
+int dcif_crc_source_verify(struct drm_crtc *crtc, const char *source_name,
+ size_t *values_cnt);
+int dcif_crc_source_set(struct drm_crtc *crtc, const char *source_name);
+void dcif_crc_source_enable(struct dcif_dev *dcif, enum dcif_crc_source source,
+ struct drm_rect *roi, int ncrc);
+void dcif_crc_source_disable(struct dcif_dev *dcif, int ncrc);
+
+#endif /* __DCIF_CRC_H__ */
diff --git a/drivers/gpu/drm/imx/dcif/dcif-crtc.c b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
new file mode 100644
index 0000000000000..1fa2dfb2e0b98
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
@@ -0,0 +1,705 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/irqreturn.h>
+#include <linux/media-bus-format.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_state_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "dcif-crc.h"
+#include "dcif-drv.h"
+#include "dcif-reg.h"
+
+#define DCIF_MAX_PIXEL_CLOCK 148500000
+
+/* -----------------------------------------------------------------------------
+ * CRTC
+ */
+
+/*
+ * For conversion from YCbCr to RGB, the CSC operates as follows:
+ *
+ * |R| |A1 A2 A3| |Y + D1|
+ * |G| = |B1 B2 B3| * |Cb + D2|
+ * |B| |C1 C2 C3| |Cr + D3|
+ *
+ * The A, B and C coefficients are expressed as signed Q3.8 fixed point values and
+ * the D coefficients as signed Q9.0.
+ */
+static const u32 dcif_yuv2rgb_coeffs[3][2][6] = {
+ [DRM_COLOR_YCBCR_BT601] = {
+ [DRM_COLOR_YCBCR_LIMITED_RANGE] = {
+ /*
+ * BT.601 limited range:
+ *
+ * |R| |1.1644 0.0000 1.5960| |Y - 16 |
+ * |G| = |1.1644 -0.3917 -0.8129| * |Cb - 128|
+ * |B| |1.1644 2.0172 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x12a) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x199) | DCIF_CSC_COEF1_L0_B1(0x12a),
+ DCIF_CSC_COEF2_L0_B2(0x79c) | DCIF_CSC_COEF2_L0_B3(0x730),
+ DCIF_CSC_COEF3_L0_C1(0x12a) | DCIF_CSC_COEF3_L0_C2(0x204),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x1f0),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ [DRM_COLOR_YCBCR_FULL_RANGE] = {
+ /*
+ * BT.601 full range:
+ *
+ * |R| |1.0000 0.0000 1.4020| |Y - 0 |
+ * |G| = |1.0000 -0.3441 -0.7141| * |Cb - 128|
+ * |B| |1.0000 1.7720 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x100) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x167) | DCIF_CSC_COEF1_L0_B1(0x100),
+ DCIF_CSC_COEF2_L0_B2(0x7a8) | DCIF_CSC_COEF2_L0_B3(0x749),
+ DCIF_CSC_COEF3_L0_C1(0x100) | DCIF_CSC_COEF3_L0_C2(0x1c6),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x000),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ },
+ [DRM_COLOR_YCBCR_BT709] = {
+ [DRM_COLOR_YCBCR_LIMITED_RANGE] = {
+ /*
+ * Rec.709 limited range:
+ *
+ * |R| |1.1644 0.0000 1.7927| |Y - 16 |
+ * |G| = |1.1644 -0.2132 -0.5329| * |Cb - 128|
+ * |B| |1.1644 2.1124 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x12a) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x1cb) | DCIF_CSC_COEF1_L0_B1(0x12a),
+ DCIF_CSC_COEF2_L0_B2(0x7c9) | DCIF_CSC_COEF2_L0_B3(0x778),
+ DCIF_CSC_COEF3_L0_C1(0x12a) | DCIF_CSC_COEF3_L0_C2(0x21d),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x1f0),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ [DRM_COLOR_YCBCR_FULL_RANGE] = {
+ /*
+ * Rec.709 full range:
+ *
+ * |R| |1.0000 0.0000 1.5748| |Y - 0 |
+ * |G| = |1.0000 -0.1873 -0.4681| * |Cb - 128|
+ * |B| |1.0000 1.8556 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x100) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x193) | DCIF_CSC_COEF1_L0_B1(0x100),
+ DCIF_CSC_COEF2_L0_B2(0x7d0) | DCIF_CSC_COEF2_L0_B3(0x788),
+ DCIF_CSC_COEF3_L0_C1(0x100) | DCIF_CSC_COEF3_L0_C2(0x1db),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x000),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ },
+ [DRM_COLOR_YCBCR_BT2020] = {
+ [DRM_COLOR_YCBCR_LIMITED_RANGE] = {
+ /*
+ * BT.2020 limited range:
+ *
+ * |R| |1.1644 0.0000 1.6787| |Y - 16 |
+ * |G| = |1.1644 -0.1874 -0.6505| * |Cb - 128|
+ * |B| |1.1644 2.1418 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x12a) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x1ae) | DCIF_CSC_COEF1_L0_B1(0x12a),
+ DCIF_CSC_COEF2_L0_B2(0x7d0) | DCIF_CSC_COEF2_L0_B3(0x759),
+ DCIF_CSC_COEF3_L0_C1(0x12a) | DCIF_CSC_COEF3_L0_C2(0x224),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x1f0),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ [DRM_COLOR_YCBCR_FULL_RANGE] = {
+ /*
+ * BT.2020 full range:
+ *
+ * |R| |1.0000 0.0000 1.4746| |Y - 0 |
+ * |G| = |1.0000 -0.1646 -0.5714| * |Cb - 128|
+ * |B| |1.0000 1.8814 0.0000| |Cr - 128|
+ */
+ DCIF_CSC_COEF0_L0_A1(0x100) | DCIF_CSC_COEF0_L0_A2(0x000),
+ DCIF_CSC_COEF1_L0_A3(0x179) | DCIF_CSC_COEF1_L0_B1(0x100),
+ DCIF_CSC_COEF2_L0_B2(0x7d6) | DCIF_CSC_COEF2_L0_B3(0x76e),
+ DCIF_CSC_COEF3_L0_C1(0x100) | DCIF_CSC_COEF3_L0_C2(0x1e2),
+ DCIF_CSC_COEF4_L0_C3(0x000) | DCIF_CSC_COEF4_L0_D1(0x000),
+ DCIF_CSC_COEF5_L0_D2(0x180) | DCIF_CSC_COEF5_L0_D3(0x180),
+ },
+ },
+};
+
+static enum drm_mode_status dcif_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ if (mode->crtc_clock > DCIF_MAX_PIXEL_CLOCK)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static void dcif_set_formats(struct dcif_dev *dcif, struct drm_plane_state *plane_state,
+ const u32 bus_format)
+{
+ const u32 format = plane_state->fb->format->format;
+ struct drm_device *drm = &dcif->drm;
+ bool in_yuv = false;
+ u32 reg = 0;
+
+ switch (bus_format) {
+ case MEDIA_BUS_FMT_RGB565_1X16:
+ reg |= DCIF_DPI_CTRL_DATA_PATTERN(PATTERN_RGB565);
+ break;
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ reg |= DCIF_DPI_CTRL_DATA_PATTERN(PATTERN_RGB888);
+ break;
+ case MEDIA_BUS_FMT_RBG888_1X24:
+ reg |= DCIF_DPI_CTRL_DATA_PATTERN(PATTERN_RBG888);
+ break;
+ case MEDIA_BUS_FMT_BGR888_1X24:
+ reg |= DCIF_DPI_CTRL_DATA_PATTERN(PATTERN_BGR888);
+ break;
+ case MEDIA_BUS_FMT_GBR888_1X24:
+ reg |= DCIF_DPI_CTRL_DATA_PATTERN(PATTERN_GBR888);
+ break;
+ default:
+ dev_err(drm->dev, "Unknown media bus format 0x%x\n", bus_format);
+ break;
+ }
+
+ regmap_update_bits(dcif->regmap, DCIF_DPI_CTRL, DCIF_DPI_CTRL_DATA_PATTERN_MASK, reg);
+
+ reg = 0;
+ switch (format) {
+ /* RGB Formats */
+ case DRM_FORMAT_RGB565:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_RGB565);
+ break;
+ case DRM_FORMAT_RGB888:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_RGB888);
+ break;
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_ARGB1555);
+ break;
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_ARGB4444:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_ARGB4444);
+ break;
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_ABGR8888);
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_ARGB8888);
+ break;
+
+ /* YUV Formats */
+ case DRM_FORMAT_YUYV:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_YCBCR422) |
+ DCIF_CTRLDESC0_YUV_FORMAT(CTRLDESCL0_YUV_FORMAT_VY2UY1);
+ in_yuv = true;
+ break;
+ case DRM_FORMAT_YVYU:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_YCBCR422) |
+ DCIF_CTRLDESC0_YUV_FORMAT(CTRLDESCL0_YUV_FORMAT_UY2VY1);
+ in_yuv = true;
+ break;
+ case DRM_FORMAT_UYVY:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_YCBCR422) |
+ DCIF_CTRLDESC0_YUV_FORMAT(CTRLDESCL0_YUV_FORMAT_Y2VY1U);
+ in_yuv = true;
+ break;
+ case DRM_FORMAT_VYUY:
+ reg |= DCIF_CTRLDESC0_FORMAT(CTRLDESCL0_FORMAT_YCBCR422) |
+ DCIF_CTRLDESC0_YUV_FORMAT(CTRLDESCL0_YUV_FORMAT_Y2UY1V);
+ in_yuv = true;
+ break;
+
+ default:
+ dev_err(drm->dev, "Unknown pixel format 0x%x\n", format);
+ break;
+ }
+
+ regmap_update_bits(dcif->regmap, DCIF_CTRLDESC0(0),
+ DCIF_CTRLDESC0_FORMAT_MASK | DCIF_CTRLDESC0_YUV_FORMAT_MASK,
+ reg);
+
+ if (in_yuv) {
+ /* Enable CSC YCbCr -> RGB */
+ const u32 *coeffs =
+ dcif_yuv2rgb_coeffs[plane_state->color_encoding][plane_state->color_range];
+
+ regmap_bulk_write(dcif->regmap, DCIF_CSC_COEF0_L0, coeffs, 6);
+
+ regmap_write(dcif->regmap, DCIF_CSC_CTRL_L0,
+ DCIF_CSC_CTRL_L0_CSC_EN |
+ DCIF_CSC_CTRL_L0_CSC_MODE_YCBCR2RGB);
+ } else {
+ regmap_write(dcif->regmap, DCIF_CSC_CTRL_L0, 0);
+ }
+}
+
+static void dcif_set_mode(struct dcif_dev *dcif, u32 bus_flags)
+{
+ struct drm_display_mode *m = &dcif->crtc.state->adjusted_mode;
+ u32 reg = 0;
+
+ if (m->flags & DRM_MODE_FLAG_NHSYNC)
+ reg |= DCIF_DPI_CTRL_HSYNC_POL_LOW;
+ if (m->flags & DRM_MODE_FLAG_NVSYNC)
+ reg |= DCIF_DPI_CTRL_VSYNC_POL_LOW;
+ if (bus_flags & DRM_BUS_FLAG_DE_LOW)
+ reg |= DCIF_DPI_CTRL_DE_POL_LOW;
+ if (bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
+ reg |= DCIF_DPI_CTRL_PCLK_EDGE_FALLING;
+
+ regmap_update_bits(dcif->regmap, DCIF_DPI_CTRL, DCIF_DPI_CTRL_POL_MASK, reg);
+
+ /* config display timings */
+ reg = DCIF_DISP_SIZE_DISP_WIDTH(m->hdisplay) |
+ DCIF_DISP_SIZE_DISP_HEIGHT(m->vdisplay);
+ regmap_write(dcif->regmap, DCIF_DISP_SIZE, reg);
+
+ reg = DCIF_DPI_HSYN_PAR_BP_H(m->htotal - m->hsync_end) |
+ DCIF_DPI_HSYN_PAR_FP_H(m->hsync_start - m->hdisplay);
+ regmap_write(dcif->regmap, DCIF_DPI_HSYN_PAR, reg);
+
+ reg = DCIF_DPI_VSYN_PAR_BP_V(m->vtotal - m->vsync_end) |
+ DCIF_DPI_VSYN_PAR_FP_V(m->vsync_start - m->vdisplay);
+ regmap_write(dcif->regmap, DCIF_DPI_VSYN_PAR, reg);
+
+ reg = DCIF_DPI_VSYN_HSYN_WIDTH_PW_V(m->vsync_end - m->vsync_start) |
+ DCIF_DPI_VSYN_HSYN_WIDTH_PW_H(m->hsync_end - m->hsync_start);
+ regmap_write(dcif->regmap, DCIF_DPI_VSYN_HSYN_WIDTH, reg);
+
+ /* Layer 0 frame size */
+ reg = DCIF_CTRLDESC2_HEIGHT(m->vdisplay) |
+ DCIF_CTRLDESC2_WIDTH(m->hdisplay);
+ regmap_write(dcif->regmap, DCIF_CTRLDESC2(0), reg);
+
+ /*
+ * Configure P_SIZE, T_SIZE and pitch
+ * 1. P_SIZE and T_SIZE should never be less than AXI bus width.
+ * 2. P_SIZE should never be less than T_SIZE.
+ */
+ reg = DCIF_CTRLDESC3_P_SIZE(2) | DCIF_CTRLDESC3_T_SIZE(2) |
+ DCIF_CTRLDESC3_PITCH(dcif->crtc.primary->state->fb->pitches[0]);
+ regmap_write(dcif->regmap, DCIF_CTRLDESC3(0), reg);
+}
+
+static void dcif_enable_plane_panic(struct dcif_dev *dcif)
+{
+ u32 reg;
+
+ /* Set FIFO Panic watermarks, low 1/3, high 2/3. */
+ reg = DCIF_PANIC_THRES_LOW(1 * PANIC0_THRES_MAX / 3) |
+ DCIF_PANIC_THRES_HIGH(2 * PANIC0_THRES_MAX / 3) |
+ DCIF_PANIC_THRES_REQ_EN;
+ regmap_write(dcif->regmap, DCIF_PANIC_THRES(0), reg);
+ regmap_write(dcif->regmap, DCIF_PANIC_THRES(1), reg);
+
+ regmap_set_bits(dcif->regmap, DCIF_IE1(dcif->cpu_domain),
+ DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1);
+}
+
+static void dcif_disable_plane_panic(struct dcif_dev *dcif)
+{
+ regmap_clear_bits(dcif->regmap, DCIF_IE1(dcif->cpu_domain),
+ DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1);
+ regmap_clear_bits(dcif->regmap, DCIF_PANIC_THRES(0), DCIF_PANIC_THRES_REQ_EN);
+ regmap_clear_bits(dcif->regmap, DCIF_PANIC_THRES(1), DCIF_PANIC_THRES_REQ_EN);
+}
+
+static void dcif_enable_controller(struct dcif_dev *dcif)
+{
+ /* Enable Display */
+ regmap_set_bits(dcif->regmap, DCIF_DISP_CTRL, DCIF_DISP_CTRL_DISP_ON);
+
+ /* Enable layer 0 */
+ regmap_set_bits(dcif->regmap, DCIF_CTRLDESC0(0), DCIF_CTRLDESC0_EN);
+}
+
+static void dcif_disable_controller(struct dcif_dev *dcif)
+{
+ u32 reg;
+ int ret;
+
+ /* Disable layer 0 */
+ regmap_clear_bits(dcif->regmap, DCIF_CTRLDESC0(0), DCIF_CTRLDESC0_EN);
+
+ ret = regmap_read_poll_timeout(dcif->regmap, DCIF_CTRLDESC0(0), reg,
+ !(reg & DCIF_CTRLDESC0_EN), 0,
+ 36000); /* Wait ~2 frame times max */
+ if (ret)
+ drm_err(&dcif->drm, "Failed to disable controller!\n");
+
+ /* Disable Display */
+ regmap_clear_bits(dcif->regmap, DCIF_DISP_CTRL, DCIF_DISP_CTRL_DISP_ON);
+}
+
+static void dcif_shadow_load_enable(struct dcif_dev *dcif)
+{
+ regmap_write_bits(dcif->regmap, DCIF_CTRLDESC0(0), DCIF_CTRLDESC0_SHADOW_LOAD_EN,
+ DCIF_CTRLDESC0_SHADOW_LOAD_EN);
+}
+
+static void dcif_reset_block(struct dcif_dev *dcif)
+{
+ regmap_set_bits(dcif->regmap, DCIF_DISP_CTRL, DCIF_DISP_CTRL_SW_RST);
+
+ regmap_clear_bits(dcif->regmap, DCIF_DISP_CTRL, DCIF_DISP_CTRL_SW_RST);
+}
+
+static void dcif_crtc_atomic_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(to_dcif_crtc_state(state));
+}
+
+static void dcif_crtc_reset(struct drm_crtc *crtc)
+{
+ struct dcif_crtc_state *state;
+
+ if (crtc->state)
+ dcif_crtc_atomic_destroy_state(crtc, crtc->state);
+
+ crtc->state = NULL;
+
+ state = kzalloc_obj(*state, GFP_KERNEL);
+ if (state)
+ __drm_atomic_helper_crtc_reset(crtc, &state->base);
+}
+
+static struct drm_crtc_state *dcif_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
+{
+ struct dcif_crtc_state *old = to_dcif_crtc_state(crtc->state);
+ struct dcif_crtc_state *new;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ new = kzalloc_obj(*new, GFP_KERNEL);
+ if (!new)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &new->base);
+
+ new->bus_format = old->bus_format;
+ new->bus_flags = old->bus_flags;
+ new->crc.source = old->crc.source;
+ dcif_copy_roi(&old->crc.roi, &new->crc.roi);
+
+ return &new->base;
+}
+
+static void dcif_crtc_mode_set_nofb(struct drm_crtc_state *crtc_state,
+ struct drm_plane_state *plane_state)
+{
+ struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
+ struct drm_device *drm = crtc_state->crtc->dev;
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc_state->crtc);
+ struct drm_display_mode *m = &crtc_state->adjusted_mode;
+
+ dev_dbg(drm->dev, "Pixel clock: %dkHz\n", m->crtc_clock);
+ dev_dbg(drm->dev, "Bridge bus_flags: 0x%08X\n", dcif_crtc_state->bus_flags);
+ dev_dbg(drm->dev, "Mode flags: 0x%08X\n", m->flags);
+
+ dcif_reset_block(dcif);
+
+ dcif_set_formats(dcif, plane_state, dcif_crtc_state->bus_format);
+
+ dcif_set_mode(dcif, dcif_crtc_state->bus_flags);
+}
+
+static void dcif_crtc_queue_state_event(struct drm_crtc *crtc)
+{
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+
+ scoped_guard(spinlock_irq, &crtc->dev->event_lock) {
+ if (crtc->state->event) {
+ WARN_ON(drm_crtc_vblank_get(crtc));
+ WARN_ON(dcif->event);
+ dcif->event = crtc->state->event;
+ crtc->state->event = NULL;
+ }
+ }
+}
+
+static struct drm_bridge *dcif_crtc_get_bridge(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state)
+{
+ struct drm_connector_state *conn_state;
+ struct drm_encoder *encoder;
+ struct drm_connector *conn;
+ struct drm_bridge *bridge;
+ int i;
+
+ for_each_new_connector_in_state(crtc_state->state, conn, conn_state, i) {
+ if (crtc != conn_state->crtc)
+ continue;
+
+ encoder = conn_state->best_encoder;
+
+ bridge = drm_bridge_chain_get_first_bridge(encoder);
+ if (bridge)
+ return bridge;
+ }
+
+ return NULL;
+}
+
+static void dcif_crtc_query_output_bus_format(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state)
+{
+ struct dcif_crtc_state *dcif_state = to_dcif_crtc_state(crtc_state);
+ struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
+ struct drm_bridge_state *bridge_state;
+
+ dcif_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+ dcif_state->bus_flags = 0;
+
+ bridge = dcif_crtc_get_bridge(crtc, crtc_state);
+ if (!bridge)
+ return;
+
+ bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state, bridge);
+ if (!bridge_state)
+ return;
+
+ dcif_state->bus_format = bridge_state->input_bus_cfg.format;
+ dcif_state->bus_flags = bridge_state->input_bus_cfg.flags;
+}
+
+static int dcif_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ bool enable_primary = crtc_state->plane_mask & drm_plane_mask(crtc->primary);
+ int ret;
+
+ if (crtc_state->active && !enable_primary)
+ return -EINVAL;
+
+ dcif_crtc_query_output_bus_format(crtc, crtc_state);
+
+ if (crtc_state->active_changed && crtc_state->active) {
+ if (!crtc_state->mode_changed) {
+ crtc_state->mode_changed = true;
+ ret = drm_atomic_helper_check_modeset(crtc->dev, state);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void dcif_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
+ struct dcif_crtc_state *old_dcif_crtc_state = to_dcif_crtc_state(old_crtc_state);
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+
+ dcif_shadow_load_enable(dcif);
+
+ if (drm_atomic_crtc_needs_modeset(crtc->state))
+ return;
+
+ if (dcif->has_crc && disable_dcif_crc_needed(dcif_crtc_state,
+ old_dcif_crtc_state))
+ dcif_crc_source_disable(dcif, 0);
+
+ dcif_crtc_queue_state_event(crtc);
+
+ if (dcif->has_crc && enable_dcif_crc_needed(dcif_crtc_state,
+ old_dcif_crtc_state))
+ dcif_crc_source_enable(dcif, dcif_crtc_state->crc.source,
+ &dcif_crtc_state->crc.roi, 0);
+}
+
+static void dcif_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, crtc->primary);
+ struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
+ struct drm_display_mode *adj = &crtc_state->adjusted_mode;
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ struct drm_device *drm = crtc->dev;
+ dma_addr_t baseaddr;
+ int ret;
+
+ dev_dbg(drm->dev, "mode " DRM_MODE_FMT "\n", DRM_MODE_ARG(adj));
+
+ /* enable power when we start to set mode for CRTC */
+ ret = pm_runtime_resume_and_get(drm->dev);
+ if (ret < 0) {
+ drm_err(drm, "failed to resume DCIF, ret = %d\n", ret);
+ return;
+ }
+ dcif->crtc_pm_enabled = true;
+
+ drm_crtc_vblank_on(crtc);
+
+ dcif_crtc_mode_set_nofb(crtc_state, plane_state);
+
+ baseaddr = drm_fb_dma_get_gem_addr(plane_state->fb, plane_state, 0);
+ if (baseaddr)
+ regmap_write(dcif->regmap, DCIF_CTRLDESC4(0), baseaddr);
+
+ dcif_enable_plane_panic(dcif);
+ dcif_enable_controller(dcif);
+
+ dcif_crtc_queue_state_event(crtc);
+
+ if (dcif->has_crc && dcif_crtc_state->crc.source != DCIF_CRC_SRC_NONE)
+ dcif_crc_source_enable(dcif, dcif_crtc_state->crc.source,
+ &dcif_crtc_state->crc.roi, 0);
+}
+
+static void dcif_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+ struct dcif_crtc_state *dcif_crtc_state = to_dcif_crtc_state(crtc_state);
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ struct drm_device *drm = crtc->dev;
+
+ if (dcif->has_crc && dcif_crtc_state->crc.source != DCIF_CRC_SRC_NONE)
+ dcif_crc_source_disable(dcif, 0);
+
+ dcif_disable_controller(dcif);
+ dcif_disable_plane_panic(dcif);
+
+ drm_crtc_vblank_off(crtc);
+
+ if (dcif->crtc_pm_enabled) {
+ dcif->crtc_pm_enabled = false;
+ pm_runtime_put_sync(drm->dev);
+ }
+
+ scoped_guard(spinlock_irq, &crtc->dev->event_lock) {
+ if (crtc->state->event && !crtc->state->active) {
+ drm_crtc_send_vblank_event(crtc, crtc->state->event);
+ crtc->state->event = NULL;
+ }
+ }
+}
+
+static const struct drm_crtc_helper_funcs dcif_crtc_helper_funcs = {
+ .mode_valid = dcif_crtc_mode_valid,
+ .atomic_check = dcif_crtc_atomic_check,
+ .atomic_flush = dcif_crtc_atomic_flush,
+ .atomic_enable = dcif_crtc_atomic_enable,
+ .atomic_disable = dcif_crtc_atomic_disable,
+};
+
+static int dcif_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ int domain = dcif->cpu_domain;
+
+ /* Clear and enable VS_BLANK IRQ */
+ regmap_set_bits(dcif->regmap, DCIF_IS0(domain), DCIF_INT0_VS_BLANK);
+ regmap_set_bits(dcif->regmap, DCIF_IE0(domain), DCIF_INT0_VS_BLANK);
+
+ return 0;
+}
+
+static void dcif_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
+ int domain = dcif->cpu_domain;
+
+ /* Disable and clear VS_BLANK IRQ */
+ regmap_clear_bits(dcif->regmap, DCIF_IE0(domain), DCIF_INT0_VS_BLANK);
+ regmap_clear_bits(dcif->regmap, DCIF_IS0(domain), DCIF_INT0_VS_BLANK);
+}
+
+static const struct drm_crtc_funcs dcif_crtc_funcs = {
+ .reset = dcif_crtc_reset,
+ .destroy = drm_crtc_cleanup,
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_duplicate_state = dcif_crtc_atomic_duplicate_state,
+ .atomic_destroy_state = dcif_crtc_atomic_destroy_state,
+ .enable_vblank = dcif_crtc_enable_vblank,
+ .disable_vblank = dcif_crtc_disable_vblank,
+ .set_crc_source = dcif_crc_source_set,
+ .verify_crc_source = dcif_crc_source_verify,
+};
+
+irqreturn_t dcif_irq_handler(int irq, void *data)
+{
+ struct drm_device *drm = data;
+ struct dcif_dev *dcif = to_dcif_dev(drm);
+ int domain = dcif->cpu_domain;
+ u32 stat0, stat1, crc;
+
+ regmap_read(dcif->regmap, DCIF_IS0(domain), &stat0);
+ regmap_read(dcif->regmap, DCIF_IS1(domain), &stat1);
+ regmap_write(dcif->regmap, DCIF_IS0(domain), stat0);
+ regmap_write(dcif->regmap, DCIF_IS1(domain), stat1);
+
+ if (stat0 & DCIF_INT0_VS_BLANK) {
+ drm_crtc_handle_vblank(&dcif->crtc);
+
+ scoped_guard(spinlock_irqsave, &drm->event_lock) {
+ if (dcif->event) {
+ drm_crtc_send_vblank_event(&dcif->crtc, dcif->event);
+ dcif->event = NULL;
+ drm_crtc_vblank_put(&dcif->crtc);
+ }
+ if (dcif->crc_is_enabled) {
+ regmap_read(dcif->regmap, DCIF_CRC_VAL_R(0), &crc);
+ drm_crtc_add_crc_entry(&dcif->crtc, false, 0, &crc);
+ dev_dbg(drm->dev, "crc=0x%x\n", crc);
+ }
+ }
+ }
+
+ if (stat1 & (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1)) {
+ u32 panic = stat1 & (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1);
+
+ dev_dbg_ratelimited(drm->dev, "FIFO panic on %s\n",
+ panic == (DCIF_INT1_FIFO_PANIC0 | DCIF_INT1_FIFO_PANIC1) ?
+ "layers 0 & 1" : panic == DCIF_INT1_FIFO_PANIC0 ? "layer 0" :
+ "layer 1");
+ }
+
+ return IRQ_HANDLED;
+}
+
+int dcif_crtc_init(struct dcif_dev *dcif)
+{
+ int ret;
+
+ ret = dcif_plane_init(dcif);
+ if (ret)
+ return ret;
+
+ drm_crtc_helper_add(&dcif->crtc, &dcif_crtc_helper_funcs);
+ ret = drm_crtc_init_with_planes(&dcif->drm, &dcif->crtc, &dcif->planes.primary, NULL,
+ &dcif_crtc_funcs, NULL);
+ if (ret) {
+ drm_err(&dcif->drm, "failed to initialize CRTC: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/dcif/dcif-drv.c b/drivers/gpu/drm/imx/dcif/dcif-drv.c
new file mode 100644
index 0000000000000..50ca6461ffb30
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-drv.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_print.h>
+
+#include "dcif-drv.h"
+#include "dcif-reg.h"
+
+#define DCIF_CPU_DOMAIN 0
+
+DEFINE_DRM_GEM_DMA_FOPS(dcif_driver_fops);
+
+static struct drm_driver dcif_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+ DRM_GEM_DMA_DRIVER_OPS,
+ DRM_FBDEV_DMA_DRIVER_OPS,
+ .fops = &dcif_driver_fops,
+ .name = "imx-dcif",
+ .desc = "i.MX DCIF DRM graphics",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+};
+
+static void dcif_read_chip_info(struct dcif_dev *dcif)
+{
+ struct drm_device *drm = &dcif->drm;
+ u32 val, vmin, vmaj;
+ int ret;
+
+ ret = pm_runtime_resume_and_get(drm->dev);
+ if (ret < 0) {
+ drm_err(drm, "failed to resume DCIF: %d\n", ret);
+ return;
+ }
+
+ regmap_read(dcif->regmap, DCIF_VER, &val);
+
+ dcif->has_crc = val & DCIF_FEATURE_CRC;
+
+ vmin = DCIF_VER_GET_MINOR(val);
+ vmaj = DCIF_VER_GET_MAJOR(val);
+ DRM_DEV_DEBUG(drm->dev, "DCIF version is %d.%d\n", vmaj, vmin);
+
+ pm_runtime_put_sync(drm->dev);
+}
+
+static const struct regmap_config dcif_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+ .fast_io = true,
+ .max_register = 0x20250,
+ .cache_type = REGCACHE_NONE,
+ .disable_locking = true,
+};
+
+static int dcif_probe(struct platform_device *pdev)
+{
+ struct dcif_dev *dcif;
+ struct drm_device *drm;
+ int ret;
+ int i;
+
+ dcif = devm_drm_dev_alloc(&pdev->dev, &dcif_driver, struct dcif_dev, drm);
+ if (IS_ERR(dcif))
+ return PTR_ERR(dcif);
+
+ /* CPU 0 domain for interrupt control */
+ dcif->cpu_domain = DCIF_CPU_DOMAIN;
+
+ drm = &dcif->drm;
+ dev_set_drvdata(&pdev->dev, dcif);
+
+ dcif->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(dcif->reg_base))
+ return dev_err_probe(drm->dev, PTR_ERR(dcif->reg_base),
+ "failed to get reg base\n");
+
+ for (i = 0; i < 3; i++) {
+ dcif->irq[i] = platform_get_irq(pdev, i);
+ if (dcif->irq[i] < 0)
+ return dev_err_probe(drm->dev, dcif->irq[i],
+ "failed to get domain%d irq\n", i);
+ }
+
+ dcif->regmap = devm_regmap_init_mmio(drm->dev, dcif->reg_base, &dcif_regmap_config);
+ if (IS_ERR(dcif->regmap))
+ return dev_err_probe(drm->dev, PTR_ERR(dcif->regmap),
+ "failed to init DCIF regmap\n");
+
+ dcif->num_clks = devm_clk_bulk_get_all(drm->dev, &dcif->clks);
+ if (dcif->num_clks < 0)
+ return dev_err_probe(drm->dev, dcif->num_clks,
+ "cannot get required clocks\n");
+
+ dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
+
+ devm_pm_runtime_enable(drm->dev);
+
+ ret = devm_request_irq(drm->dev, dcif->irq[dcif->cpu_domain],
+ dcif_irq_handler, 0, drm->driver->name, drm);
+ if (ret < 0)
+ return dev_err_probe(drm->dev, ret, "failed to install IRQ handler\n");
+
+ dcif_read_chip_info(dcif);
+
+ ret = dcif_kms_prepare(dcif);
+ if (ret)
+ return ret;
+
+ ret = drm_dev_register(drm, 0);
+ if (ret)
+ return dev_err_probe(drm->dev, ret, "failed to register drm device\n");
+
+ drm_client_setup(drm, NULL);
+
+ return 0;
+}
+
+static void dcif_remove(struct platform_device *pdev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(&pdev->dev);
+ struct drm_device *drm = &dcif->drm;
+
+ drm_dev_unregister(drm);
+
+ drm_atomic_helper_shutdown(drm);
+}
+
+static void dcif_shutdown(struct platform_device *pdev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(&pdev->dev);
+ struct drm_device *drm = &dcif->drm;
+
+ drm_atomic_helper_shutdown(drm);
+}
+
+static int dcif_runtime_suspend(struct device *dev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(dcif->num_clks, dcif->clks);
+
+ return 0;
+}
+
+static int dcif_runtime_resume(struct device *dev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(dev);
+ int ret;
+
+ ret = clk_bulk_prepare_enable(dcif->num_clks, dcif->clks);
+ if (ret) {
+ dev_err(dev, "failed to enable clocks: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int dcif_suspend(struct device *dev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(dev);
+ int ret;
+
+ ret = drm_mode_config_helper_suspend(&dcif->drm);
+ if (ret < 0)
+ return ret;
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ return dcif_runtime_suspend(dev);
+}
+
+static int dcif_resume(struct device *dev)
+{
+ struct dcif_dev *dcif = dev_get_drvdata(dev);
+ int ret;
+
+ if (!pm_runtime_suspended(dev)) {
+ ret = dcif_runtime_resume(dev);
+ if (ret < 0)
+ return ret;
+ }
+
+ return drm_mode_config_helper_resume(&dcif->drm);
+}
+
+static const struct dev_pm_ops dcif_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(dcif_suspend, dcif_resume)
+ SET_RUNTIME_PM_OPS(dcif_runtime_suspend, dcif_runtime_resume, NULL)
+};
+
+static const struct of_device_id dcif_dt_ids[] = {
+ { .compatible = "nxp,imx94-dcif", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dcif_dt_ids);
+
+static struct platform_driver dcif_platform_driver = {
+ .probe = dcif_probe,
+ .remove = dcif_remove,
+ .shutdown = dcif_shutdown,
+ .driver = {
+ .name = "imx-dcif-drm",
+ .of_match_table = dcif_dt_ids,
+ .pm = pm_ptr(&dcif_pm_ops),
+ },
+};
+module_platform_driver(dcif_platform_driver);
+
+MODULE_AUTHOR("NXP Semiconductor");
+MODULE_DESCRIPTION("i.MX94 DCIF DRM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/dcif/dcif-drv.h b/drivers/gpu/drm/imx/dcif/dcif-drv.h
new file mode 100644
index 0000000000000..895b2efc51a26
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-drv.h
@@ -0,0 +1,89 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#ifndef __DCIF_DRV_H__
+#define __DCIF_DRV_H__
+
+#include <linux/clk.h>
+#include <linux/irqreturn.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_vblank.h>
+
+#define DCIF_CPU_DOMAINS 3
+
+struct dcif_dev {
+ struct drm_device drm;
+ void __iomem *reg_base;
+
+ struct regmap *regmap;
+ int irq[DCIF_CPU_DOMAINS];
+
+ int num_clks;
+ struct clk_bulk_data *clks;
+
+ struct drm_crtc crtc;
+ struct {
+ struct drm_plane primary;
+ struct drm_plane overlay;
+ } planes;
+ struct drm_encoder encoder;
+
+ struct drm_pending_vblank_event *event;
+
+ /* Implement crc */
+ bool has_crc;
+ bool crc_is_enabled;
+
+ /* Tracks whether atomic_enable obtained a PM runtime reference */
+ bool crtc_pm_enabled;
+
+ /* CPU domain for interrupt control */
+ int cpu_domain;
+};
+
+enum dcif_crc_source {
+ DCIF_CRC_SRC_NONE,
+ DCIF_CRC_SRC_FRAME,
+ DCIF_CRC_SRC_FRAME_ROI,
+};
+
+struct dcif_crc {
+ enum dcif_crc_source source;
+ struct drm_rect roi;
+};
+
+struct dcif_crtc_state {
+ struct drm_crtc_state base;
+ struct dcif_crc crc;
+ u32 bus_format;
+ u32 bus_flags;
+};
+
+static inline struct dcif_dev *to_dcif_dev(struct drm_device *drm_dev)
+{
+ return container_of(drm_dev, struct dcif_dev, drm);
+}
+
+static inline struct dcif_dev *crtc_to_dcif_dev(struct drm_crtc *crtc)
+{
+ return to_dcif_dev(crtc->dev);
+}
+
+static inline struct dcif_crtc_state *to_dcif_crtc_state(struct drm_crtc_state *s)
+{
+ return container_of(s, struct dcif_crtc_state, base);
+}
+
+irqreturn_t dcif_irq_handler(int irq, void *data);
+int dcif_crtc_init(struct dcif_dev *dcif);
+int dcif_plane_init(struct dcif_dev *dcif);
+int dcif_kms_prepare(struct dcif_dev *dcif);
+
+#endif
diff --git a/drivers/gpu/drm/imx/dcif/dcif-kms.c b/drivers/gpu/drm/imx/dcif/dcif-kms.c
new file mode 100644
index 0000000000000..8981434b0a803
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-kms.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "dcif-drv.h"
+
+static int dcif_kms_init(struct dcif_dev *dcif)
+{
+ struct drm_device *drm = &dcif->drm;
+ struct device_node *np = drm->dev->of_node;
+ struct drm_connector *connector;
+ struct drm_bridge *bridge;
+ int ret;
+
+ ret = dcif_crtc_init(dcif);
+ if (ret)
+ return ret;
+
+ bridge = devm_drm_of_get_bridge(drm->dev, np, 0, 0);
+ if (IS_ERR(bridge))
+ return dev_err_probe(drm->dev, PTR_ERR(bridge), "Failed to find bridge\n");
+
+ dcif->encoder.possible_crtcs = drm_crtc_mask(&dcif->crtc);
+ ret = drm_simple_encoder_init(drm, &dcif->encoder, DRM_MODE_ENCODER_NONE);
+ if (ret) {
+ drm_err(drm, "failed to initialize encoder: %d\n", ret);
+ return ret;
+ }
+
+ ret = drm_bridge_attach(&dcif->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ drm_err(drm, "failed to attach bridge to encoder: %d\n", ret);
+ return ret;
+ }
+
+ connector = drm_bridge_connector_init(drm, &dcif->encoder);
+ if (IS_ERR(connector)) {
+ drm_err(drm, "failed to initialize bridge connector: %ld\n", PTR_ERR(connector));
+ return PTR_ERR(connector);
+ }
+
+ return 0;
+}
+
+static const struct drm_mode_config_funcs dcif_mode_config_funcs = {
+ .fb_create = drm_gem_fb_create,
+ .atomic_check = drm_atomic_helper_check,
+ .atomic_commit = drm_atomic_helper_commit,
+};
+
+static const struct drm_mode_config_helper_funcs dcif_mode_config_helpers = {
+ .atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
+};
+
+int dcif_kms_prepare(struct dcif_dev *dcif)
+{
+ struct drm_device *drm = &dcif->drm;
+ int ret;
+
+ ret = drmm_mode_config_init(drm);
+ if (ret)
+ return ret;
+
+ ret = dcif_kms_init(dcif);
+ if (ret)
+ return ret;
+
+ drm->mode_config.min_width = 1;
+ drm->mode_config.min_height = 1;
+ drm->mode_config.max_width = 1920;
+ drm->mode_config.max_height = 1920;
+ drm->mode_config.funcs = &dcif_mode_config_funcs;
+ drm->mode_config.helper_private = &dcif_mode_config_helpers;
+
+ ret = drm_vblank_init(drm, 1);
+ if (ret < 0) {
+ drm_err(drm, "failed to initialize vblank: %d\n", ret);
+ return ret;
+ }
+
+ drm_mode_config_reset(drm);
+
+ drmm_kms_helper_poll_init(drm);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/imx/dcif/dcif-plane.c b/drivers/gpu/drm/imx/dcif/dcif-plane.c
new file mode 100644
index 0000000000000..7b5a68dab6587
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-plane.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2025 NXP
+ */
+
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.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_gem_dma_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_rect.h>
+
+#include "dcif-drv.h"
+#include "dcif-reg.h"
+
+static const u32 dcif_primary_plane_formats[] = {
+ /* RGB */
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+
+ /* Packed YCbCr */
+ DRM_FORMAT_YUYV,
+ DRM_FORMAT_YVYU,
+ DRM_FORMAT_UYVY,
+ DRM_FORMAT_VYUY,
+};
+
+static const u32 dcif_overlay_plane_formats[] = {
+ /* RGB */
+ DRM_FORMAT_RGB565,
+ DRM_FORMAT_RGB888,
+ DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_XRGB1555,
+ DRM_FORMAT_ARGB1555,
+ DRM_FORMAT_XRGB4444,
+ DRM_FORMAT_ARGB4444,
+ DRM_FORMAT_XRGB8888,
+ DRM_FORMAT_ARGB8888,
+};
+
+static inline struct dcif_dev *plane_to_dcif_dev(struct drm_plane *plane)
+{
+ return to_dcif_dev(plane->dev);
+}
+
+static inline dma_addr_t drm_plane_state_to_baseaddr(struct drm_plane_state *state)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct drm_gem_dma_object *dma_obj;
+ unsigned int x = state->src.x1 >> 16;
+ unsigned int y = state->src.y1 >> 16;
+
+ dma_obj = drm_fb_dma_get_gem_obj(fb, 0);
+ if (!dma_obj)
+ return 0;
+
+ return dma_obj->dma_addr + fb->offsets[0] + fb->pitches[0] * y + fb->format->cpp[0] * x;
+}
+
+static int dcif_plane_get_layer_id(struct drm_plane *plane)
+{
+ return (plane->type == DRM_PLANE_TYPE_PRIMARY) ? 0 : 1;
+}
+
+static int dcif_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+ struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
+ struct dcif_dev *dcif = plane_to_dcif_dev(plane);
+ struct drm_framebuffer *fb = new_plane_state->fb;
+ struct drm_framebuffer *old_fb = old_plane_state->fb;
+ struct drm_crtc_state *crtc_state;
+ int ret;
+
+ if (!fb)
+ return 0;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, &dcif->crtc);
+ if (WARN_ON(!crtc_state))
+ return -EINVAL;
+
+ /*
+ * Force CRTC mode change if framebuffer stride or pixel format have changed.
+ */
+ if (plane->type == DRM_PLANE_TYPE_PRIMARY && old_fb &&
+ (fb->pitches[0] != old_fb->pitches[0] || fb->format->format != old_fb->format->format))
+ crtc_state->mode_changed = true;
+
+ ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
+ DRM_PLANE_NO_SCALING,
+ DRM_PLANE_NO_SCALING,
+ true,
+ true);
+ if (ret)
+ return ret;
+
+ if (new_plane_state->fb->format->has_alpha &&
+ new_plane_state->pixel_blend_mode != DRM_MODE_BLEND_PIXEL_NONE &&
+ new_plane_state->alpha != DRM_BLEND_ALPHA_OPAQUE)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void dcif_plane_atomic_update(struct drm_plane *plane, struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
+ struct dcif_dev *dcif = plane_to_dcif_dev(plane);
+ int layer_id = dcif_plane_get_layer_id(plane);
+ struct drm_framebuffer *fb = new_state->fb;
+ u32 crtc_x, crtc_y, crtc_h, crtc_w;
+ u32 layer_fmt = 0, yuv_fmt = 0;
+ dma_addr_t baseaddr;
+ u32 reg;
+
+ if (!fb)
+ return;
+
+ crtc_x = new_state->dst.x1;
+ crtc_y = new_state->dst.y1;
+ crtc_w = drm_rect_width(&new_state->dst);
+ crtc_h = drm_rect_height(&new_state->dst);
+
+ /* visible portion of plane on crtc */
+ regmap_write(dcif->regmap, DCIF_CTRLDESC1(layer_id),
+ DCIF_CTRLDESC1_POSX(crtc_x) | DCIF_CTRLDESC1_POSY(crtc_y));
+ regmap_write(dcif->regmap, DCIF_CTRLDESC2(layer_id),
+ DCIF_CTRLDESC2_WIDTH(crtc_w) | DCIF_CTRLDESC2_HEIGHT(crtc_h));
+
+ /* pitch size */
+ reg = DCIF_CTRLDESC3_P_SIZE(2) | DCIF_CTRLDESC3_T_SIZE(2) |
+ DCIF_CTRLDESC3_PITCH(fb->pitches[0]);
+ regmap_write(dcif->regmap, DCIF_CTRLDESC3(layer_id), reg);
+
+ /* address */
+ baseaddr = drm_plane_state_to_baseaddr(new_state);
+
+ drm_dbg_kms(plane->dev, "[PLANE:%d:%s] fb address %pad, pitch 0x%08x\n",
+ plane->base.id, plane->name, &baseaddr, fb->pitches[0]);
+
+ regmap_write(dcif->regmap, DCIF_CTRLDESC4(layer_id), baseaddr);
+
+ /* Format */
+ switch (fb->format->format) {
+ /* RGB Formats */
+ case DRM_FORMAT_RGB565:
+ layer_fmt = CTRLDESCL0_FORMAT_RGB565;
+ break;
+ case DRM_FORMAT_RGB888:
+ layer_fmt = CTRLDESCL0_FORMAT_RGB888;
+ break;
+ case DRM_FORMAT_XRGB1555:
+ case DRM_FORMAT_ARGB1555:
+ layer_fmt = CTRLDESCL0_FORMAT_ARGB1555;
+ break;
+ case DRM_FORMAT_XRGB4444:
+ case DRM_FORMAT_ARGB4444:
+ layer_fmt = CTRLDESCL0_FORMAT_ARGB4444;
+ break;
+ case DRM_FORMAT_XBGR8888:
+ case DRM_FORMAT_ABGR8888:
+ layer_fmt = CTRLDESCL0_FORMAT_ABGR8888;
+ break;
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ layer_fmt = CTRLDESCL0_FORMAT_ARGB8888;
+ break;
+
+ /* YUV Formats */
+ case DRM_FORMAT_YUYV:
+ layer_fmt = CTRLDESCL0_FORMAT_YCBCR422;
+ yuv_fmt = CTRLDESCL0_YUV_FORMAT_VY2UY1;
+ break;
+ case DRM_FORMAT_YVYU:
+ layer_fmt = CTRLDESCL0_FORMAT_YCBCR422;
+ yuv_fmt = CTRLDESCL0_YUV_FORMAT_UY2VY1;
+ break;
+ case DRM_FORMAT_UYVY:
+ layer_fmt = CTRLDESCL0_FORMAT_YCBCR422;
+ yuv_fmt = CTRLDESCL0_YUV_FORMAT_Y2VY1U;
+ break;
+ case DRM_FORMAT_VYUY:
+ layer_fmt = CTRLDESCL0_FORMAT_YCBCR422;
+ yuv_fmt = CTRLDESCL0_YUV_FORMAT_Y2UY1V;
+ break;
+
+ default:
+ dev_err(dcif->drm.dev, "Unknown pixel format 0x%x\n", fb->format->format);
+ break;
+ }
+
+ reg = DCIF_CTRLDESC0_EN | DCIF_CTRLDESC0_SHADOW_LOAD_EN |
+ DCIF_CTRLDESC0_FORMAT(layer_fmt) | DCIF_CTRLDESC0_YUV_FORMAT(yuv_fmt);
+
+ /* Alpha */
+ if (new_state->pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE ||
+ !new_state->fb->format->has_alpha)
+ reg |= DCIF_CTRLDESC0_GLOBAL_ALPHA(new_state->alpha >> 8) | ALPHA_GLOBAL;
+ else if (new_state->pixel_blend_mode == DRM_MODE_BLEND_COVERAGE)
+ reg |= ALPHA_EMBEDDED;
+ else
+ /*
+ * DCIF does not support premultiplied per-pixel blending but,
+ * since PREMULTI's property presence is mandatory to not break
+ * userspace, we just disable alpha blending for this one.
+ */
+ reg |= DCIF_CTRLDESC0_GLOBAL_ALPHA(255) | ALPHA_GLOBAL;
+
+ regmap_write(dcif->regmap, DCIF_CTRLDESC0(layer_id), reg);
+}
+
+static void dcif_overlay_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct dcif_dev *dcif = plane_to_dcif_dev(plane);
+
+ regmap_update_bits(dcif->regmap, DCIF_CTRLDESC0(1),
+ DCIF_CTRLDESC0_EN | DCIF_CTRLDESC0_SHADOW_LOAD_EN,
+ DCIF_CTRLDESC0_SHADOW_LOAD_EN);
+}
+
+static const struct drm_plane_helper_funcs dcif_primary_plane_helper_funcs = {
+ .prepare_fb = drm_gem_plane_helper_prepare_fb,
+ .atomic_check = dcif_plane_atomic_check,
+ .atomic_update = dcif_plane_atomic_update,
+};
+
+static const struct drm_plane_helper_funcs dcif_overlay_plane_helper_funcs = {
+ .atomic_check = dcif_plane_atomic_check,
+ .atomic_update = dcif_plane_atomic_update,
+ .atomic_disable = dcif_overlay_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs dcif_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,
+};
+
+int dcif_plane_init(struct dcif_dev *dcif)
+{
+ const u32 supported_encodings = BIT(DRM_COLOR_YCBCR_BT601) |
+ BIT(DRM_COLOR_YCBCR_BT709) |
+ BIT(DRM_COLOR_YCBCR_BT2020);
+ const u32 supported_ranges = BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
+ BIT(DRM_COLOR_YCBCR_FULL_RANGE);
+ int ret;
+
+ /* primary plane */
+ drm_plane_helper_add(&dcif->planes.primary, &dcif_primary_plane_helper_funcs);
+ ret = drm_universal_plane_init(&dcif->drm, &dcif->planes.primary, 1, &dcif_plane_funcs,
+ dcif_primary_plane_formats,
+ ARRAY_SIZE(dcif_primary_plane_formats), NULL,
+ DRM_PLANE_TYPE_PRIMARY, NULL);
+ if (ret) {
+ drm_err(&dcif->drm, "failed to initialize primary plane: %d\n", ret);
+ return ret;
+ }
+
+ ret = drm_plane_create_color_properties(&dcif->planes.primary, supported_encodings,
+ supported_ranges, DRM_COLOR_YCBCR_BT601,
+ DRM_COLOR_YCBCR_LIMITED_RANGE);
+ if (ret)
+ return ret;
+
+ ret = drm_plane_create_alpha_property(&dcif->planes.primary);
+ if (ret)
+ return ret;
+
+ /* overlay plane */
+ drm_plane_helper_add(&dcif->planes.overlay, &dcif_overlay_plane_helper_funcs);
+ ret = drm_universal_plane_init(&dcif->drm, &dcif->planes.overlay, 1, &dcif_plane_funcs,
+ dcif_overlay_plane_formats,
+ ARRAY_SIZE(dcif_overlay_plane_formats), NULL,
+ DRM_PLANE_TYPE_OVERLAY, NULL);
+ if (ret) {
+ drm_err(&dcif->drm, "failed to initialize overlay plane: %d\n", ret);
+ return ret;
+ }
+
+ ret = drm_plane_create_alpha_property(&dcif->planes.overlay);
+ if (ret)
+ return ret;
+
+ return drm_plane_create_blend_mode_property(&dcif->planes.overlay,
+ BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE));
+}
diff --git a/drivers/gpu/drm/imx/dcif/dcif-reg.h b/drivers/gpu/drm/imx/dcif/dcif-reg.h
new file mode 100644
index 0000000000000..acf9e3071aa52
--- /dev/null
+++ b/drivers/gpu/drm/imx/dcif/dcif-reg.h
@@ -0,0 +1,267 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright 2025 NXP
+ */
+#ifndef __DCIF_REG_H__
+#define __DCIF_REG_H__
+
+#include <linux/bits.h>
+
+/* Version ID Register */
+#define DCIF_VER 0x0
+#define DCIF_VER_GET_FEATURE(x) FIELD_GET(GENMASK(15, 0), x)
+#define DCIF_VER_GET_MINOR(x) FIELD_GET(GENMASK(23, 16), x)
+#define DCIF_VER_GET_MAJOR(x) FIELD_GET(GENMASK(31, 24), x)
+#define DCIF_FEATURE_CRC BIT(1)
+
+/* Parameter Registers */
+#define DCIF_PAR_0 0x4
+#define DCIF_PAR_0_LAYER_NUM(x) FIELD_PREP(GENMASK(3, 0), x)
+#define DCIF_PAR_0_DOMAIN_NUM(x) FIELD_PREP(GENMASK(5, 4), x)
+#define DCIF_PAR_0_AXI_DATA_WIDTH(x) FIELD_PREP(GENMASK(7, 6), x)
+#define DCIF_PAR_0_CLUT_RAM_NUM(x) FIELD_PREP(GENMASK(11, 8), x)
+#define DCIF_PAR_0_CSC_NUM(x) FIELD_PREP(GENMASK(13, 12), x)
+#define DCIF_PAR_0_CRC_REGION_NUM(x) FIELD_PREP(GENMASK(18, 16), x)
+#define DCIF_PAR_0_BACKUP(x) FIELD_PREP(GENMASK(31, 28), x)
+
+#define DCIF_PAR_1 0x8
+#define DCIF_PAR_1_LAYER0_FIFO_SIZE(x) FIELD_PREP(GENMASK(3, 0), x)
+#define DCIF_PAR_1_LAYER1_FIFO_SIZE(x) FIELD_PREP(GENMASK(7, 4), x)
+
+/* Display Control and Parameter Registers */
+#define DCIF_DISP_CTRL 0x10
+#define DCIF_DISP_CTRL_DISP_ON BIT(0)
+#define DCIF_DISP_CTRL_AXI_RD_HOLD BIT(30)
+#define DCIF_DISP_CTRL_SW_RST BIT(31)
+#define DCIF_DISP_PAR 0x14
+#define DCIF_DISP_PAR_BGND_B(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DCIF_DISP_PAR_BGND_G(x) FIELD_PREP(GENMASK(15, 8), x)
+#define DCIF_DISP_PAR_BGND_R(x) FIELD_PREP(GENMASK(23, 16), x)
+#define DCIF_DISP_SIZE 0x18
+#define DCIF_DISP_SIZE_DISP_WIDTH(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_DISP_SIZE_DISP_HEIGHT(x) FIELD_PREP(GENMASK(27, 16), x)
+
+/* Display Status Registers */
+#define DCIF_DISP_SR0 0x1C
+#define DCIF_DISP_SR0_AXI_RD_PEND(x) FIELD_PREP(GENMASK(4, 0), x)
+#define DCIF_DISP_SR0_DPI_BUSY(x) FIELD_PREP(GENMASK(14, 14), x)
+#define DCIF_DISP_SR0_AXI_RD_BUSY(x) FIELD_PREP(GENMASK(15, 15), x)
+#define DCIF_DISP_SR0_TXFIFO_CNT(x) FIELD_PREP(GENMASK(23, 16), x)
+
+#define DCIF_DISP_SR1 0x20
+#define DCIF_DISP_SR1_H_CNT(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_DISP_SR1_V_CNT(x) FIELD_PREP(GENMASK(27, 16), x)
+
+/* Interrupt Enable and Status Registers, n=0-2*/
+#define DCIF_IE0(n) (0x24 + (n) * 0x10000)
+#define DCIF_IS0(n) (0x28 + (n) * 0x10000)
+#define DCIF_INT0_VSYNC BIT(0)
+#define DCIF_INT0_UNDERRUN BIT(1)
+#define DCIF_INT0_VS_BLANK BIT(2)
+#define DCIF_INT0_HIST_DONE BIT(5)
+#define DCIF_INT0_CRC_ERR BIT(6)
+#define DCIF_INT0_CRC_ERR_SAT BIT(7)
+
+#define DCIF_IE1(n) (0x2C + (n) * 0x10000)
+#define DCIF_IS1(n) (0x30 + (n) * 0x10000)
+#define DCIF_INT1_FIFO_PANIC0 BIT(0)
+#define DCIF_INT1_FIFO_PANIC1 BIT(1)
+#define DCIF_INT1_DMA_ERR0 BIT(8)
+#define DCIF_INT1_DMA_ERR1 BIT(9)
+#define DCIF_INT1_DMA_DONE0 BIT(16)
+#define DCIF_INT1_DMA_DONE1 BIT(17)
+#define DCIF_INT1_FIFO_EMPTY0 BIT(24)
+#define DCIF_INT1_FIFO_EMPTY1 BIT(25)
+
+/* DPI Control and Sync Parameter Registers */
+#define DCIF_DPI_CTRL 0x40
+#define DCIF_DPI_CTRL_HSYNC_POL_LOW BIT(0)
+#define DCIF_DPI_CTRL_VSYNC_POL_LOW BIT(1)
+#define DCIF_DPI_CTRL_DE_POL_LOW BIT(2)
+#define DCIF_DPI_CTRL_PCLK_EDGE_FALLING BIT(3)
+#define DCIF_DPI_CTRL_POL_MASK GENMASK(3, 0)
+#define DCIF_DPI_CTRL_DATA_INV(x) FIELD_PREP(GENMASK(4, 4), x)
+#define DCIF_DPI_CTRL_DEF_BGND_EN(x) FIELD_PREP(GENMASK(5, 5), x)
+#define DCIF_DPI_CTRL_FETCH_OPT(x) FIELD_PREP(GENMASK(9, 8), x)
+#define DCIF_DPI_CTRL_DISP_MODE(x) FIELD_PREP(GENMASK(13, 12), x)
+#define DCIF_DPI_CTRL_DATA_PATTERN_MASK GENMASK(18, 16)
+#define DCIF_DPI_CTRL_DATA_PATTERN(x) FIELD_PREP(GENMASK(18, 16), x)
+#define PATTERN_RGB888 0
+#define PATTERN_RBG888 1
+#define PATTERN_GBR888 2
+#define PATTERN_GRB888 3
+#define PATTERN_BRG888 4
+#define PATTERN_BGR888 5
+#define PATTERN_RGB555 6
+#define PATTERN_RGB565 7
+
+#define DCIF_DPI_HSYN_PAR 0x44
+#define DCIF_DPI_HSYN_PAR_FP_H(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_DPI_HSYN_PAR_BP_H(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_DPI_VSYN_PAR 0x48
+#define DCIF_DPI_VSYN_PAR_FP_V(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_DPI_VSYN_PAR_BP_V(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_DPI_VSYN_HSYN_WIDTH 0x4C
+#define DCIF_DPI_VSYN_HSYN_WIDTH_PW_H(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_DPI_VSYN_HSYN_WIDTH_PW_V(x) FIELD_PREP(GENMASK(27, 16), x)
+
+/* Control Descriptor Registers, n=0-1*/
+#define DCIF_CTRLDESC0(n) (0x10000 + (n) * 0x10000)
+#define DCIF_CTRLDESC0_AB_MODE(x) FIELD_PREP(GENMASK(1, 0), x)
+#define ALPHA_EMBEDDED 0
+#define ALPHA_GLOBAL 1
+#define DCIF_CTRLDESC0_YUV_FORMAT_MASK GENMASK(15, 14)
+#define DCIF_CTRLDESC0_YUV_FORMAT(x) FIELD_PREP(GENMASK(15, 14), x)
+#define CTRLDESCL0_YUV_FORMAT_Y2VY1U 0x0
+#define CTRLDESCL0_YUV_FORMAT_Y2UY1V 0x1
+#define CTRLDESCL0_YUV_FORMAT_VY2UY1 0x2
+#define CTRLDESCL0_YUV_FORMAT_UY2VY1 0x3
+#define DCIF_CTRLDESC0_GLOBAL_ALPHA(x) FIELD_PREP(GENMASK(23, 16), x)
+#define DCIF_CTRLDESC0_FORMAT_MASK GENMASK(27, 24)
+#define DCIF_CTRLDESC0_FORMAT(x) FIELD_PREP(GENMASK(27, 24), x)
+#define CTRLDESCL0_FORMAT_RGB565 0x4
+#define CTRLDESCL0_FORMAT_ARGB1555 0x5
+#define CTRLDESCL0_FORMAT_ARGB4444 0x6
+#define CTRLDESCL0_FORMAT_YCBCR422 0x7
+#define CTRLDESCL0_FORMAT_RGB888 0x8
+#define CTRLDESCL0_FORMAT_ARGB8888 0x9
+#define CTRLDESCL0_FORMAT_ABGR8888 0xa
+#define DCIF_CTRLDESC0_SHADOW_LOAD_EN BIT(30)
+#define DCIF_CTRLDESC0_EN BIT(31)
+
+#define DCIF_CTRLDESC1(n) (0x10004 + (n) * 0x10000)
+#define DCIF_CTRLDESC1_POSX(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_CTRLDESC1_POSY(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_CTRLDESC2(n) (0x10008 + (n) * 0x10000)
+#define DCIF_CTRLDESC2_WIDTH(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_CTRLDESC2_HEIGHT(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_CTRLDESC3(n) (0x1000C + (n) * 0x10000)
+#define DCIF_CTRLDESC3_PITCH(x) FIELD_PREP(GENMASK(15, 0), x)
+#define DCIF_CTRLDESC3_T_SIZE(x) FIELD_PREP(GENMASK(17, 16), x)
+#define DCIF_CTRLDESC3_P_SIZE(x) FIELD_PREP(GENMASK(22, 20), x)
+
+#define DCIF_CTRLDESC4(n) (0x10010 + (n) * 0x10000)
+#define DCIF_CTRLDESC4_ADDR(x) FIELD_PREP(GENMASK(31, 0), x)
+
+#define DCIF_CTRLDESC5(n) (0x10014 + (n) * 0x10000)
+#define DCIF_CTRLDESC6(n) (0x10018 + (n) * 0x10000)
+#define DCIF_CTRLDESC6_BCLR_B(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DCIF_CTRLDESC6_BCLR_G(x) FIELD_PREP(GENMASK(15, 8), x)
+#define DCIF_CTRLDESC6_BCLR_R(x) FIELD_PREP(GENMASK(23, 16), x)
+#define DCIF_CTRLDESC6_BCLR_A(x) FIELD_PREP(GENMASK(31, 24), x)
+
+/* CLUT control Register */
+#define DCIF_CLUT_CTRL 0x1003C
+#define DCIF_CLUT_CTRL_CLUT0_SEL(x) FIELD_PREP(GENMASK(0, 0), x)
+#define DCIF_CLUT_CTRL_CLUT1_SEL(x) FIELD_PREP(GENMASK(3, 3), x)
+#define DCIF_CLUT_CTRL_CLUT_MUX(x) FIELD_PREP(GENMASK(29, 28), x)
+#define DCIF_CLUT_CTRL_CLUT_SHADOW_LOAD_EN(x) FIELD_PREP(GENMASK(31, 31), x)
+
+/* FIFO Panic Threshold Register, n=0-1 */
+#define DCIF_PANIC_THRES(n) (0x10040 + (n) * 0x10000)
+#define DCIF_PANIC_THRES_LOW_MASK GENMASK(11, 0)
+#define DCIF_PANIC_THRES_LOW(x) FIELD_PREP(GENMASK(11, 00), x)
+#define DCIF_PANIC_THRES_HIGH_MASK GENMASK(27, 16)
+#define DCIF_PANIC_THRES_HIGH(x) FIELD_PREP(GENMASK(27, 16), x)
+#define DCIF_PANIC_THRES_REQ_EN BIT(31)
+#define PANIC0_THRES_MAX 511
+
+/* Layer Status Register 0, n=0-1 */
+#define DCIF_LAYER_SR0(n) (0x10044 + (n) * 0x10000)
+#define DCIF_LAYER_SR0_L0_FIFO_CNT_MASK GENMASK(9, 0)
+#define DCIF_LAYER_SR0_L0_FIFO_CNT(x) FIELD_PREP(GENMASK(9, 0), x)
+
+/* Color Space Conversion Control and Coefficient Registers for Layer 0 */
+#define DCIF_CSC_CTRL_L0 0x10050
+#define DCIF_CSC_CTRL_L0_CSC_EN BIT(0)
+#define DCIF_CSC_CTRL_L0_CSC_MODE_YCBCR2RGB BIT(1)
+
+#define DCIF_CSC_COEF0_L0 0x10054
+#define DCIF_CSC_COEF0_L0_A1(x) FIELD_PREP_CONST(GENMASK(10, 0), x)
+#define DCIF_CSC_COEF0_L0_A2(x) FIELD_PREP_CONST(GENMASK(26, 16), x)
+
+#define DCIF_CSC_COEF1_L0 0x10058
+#define DCIF_CSC_COEF1_L0_A3(x) FIELD_PREP_CONST(GENMASK(10, 0), x)
+#define DCIF_CSC_COEF1_L0_B1(x) FIELD_PREP_CONST(GENMASK(26, 16), x)
+
+#define DCIF_CSC_COEF2_L0 0x1005C
+#define DCIF_CSC_COEF2_L0_B2(x) FIELD_PREP_CONST(GENMASK(10, 0), x)
+#define DCIF_CSC_COEF2_L0_B3(x) FIELD_PREP_CONST(GENMASK(26, 16), x)
+
+#define DCIF_CSC_COEF3_L0 0x10060
+#define DCIF_CSC_COEF3_L0_C1(x) FIELD_PREP_CONST(GENMASK(10, 0), x)
+#define DCIF_CSC_COEF3_L0_C2(x) FIELD_PREP_CONST(GENMASK(26, 16), x)
+
+#define DCIF_CSC_COEF4_L0 0x10064
+#define DCIF_CSC_COEF4_L0_C3(x) FIELD_PREP_CONST(GENMASK(10, 0), x)
+#define DCIF_CSC_COEF4_L0_D1(x) FIELD_PREP_CONST(GENMASK(24, 16), x)
+
+#define DCIF_CSC_COEF5_L0 0x10068
+#define DCIF_CSC_COEF5_L0_D2(x) FIELD_PREP_CONST(GENMASK(8, 0), x)
+#define DCIF_CSC_COEF5_L0_D3(x) FIELD_PREP_CONST(GENMASK(24, 16), x)
+
+/* CRC Control, Threshold, and Histogram Coefficient Registers */
+#define DCIF_CRC_CTRL 0x20100
+#define DCIF_CRC_CTRL_CRC_EN(x) (1 << (x))
+#define DCIF_CRC_CTRL_HIST_REGION_SEL(x) FIELD_PREP(GENMASK(17, 16), x)
+#define DCIF_CRC_CTRL_HIST_MODE BIT(21)
+#define DCIF_CRC_CTRL_HIST_TRIG BIT(22)
+#define DCIF_CRC_CTRL_HIST_EN BIT(23)
+#define DCIF_CRC_CTRL_CRC_MODE BIT(28)
+#define DCIF_CRC_CTRL_CRC_TRIG BIT(29)
+#define DCIF_CRC_CTRL_CRC_ERR_CNT_RST BIT(30)
+#define DCIF_CRC_CTRL_CRC_SHADOW_LOAD_EN BIT(31)
+
+#define DCIF_CRC_THRES 0x20104
+#define DCIF_CRC_THRES_CRC_THRESHOLD_MASK GENMASK(31, 0)
+#define DCIF_CRC_THRES_CRC_THRESHOLD(x) FIELD_PREP(GENMASK(31, 0), x)
+
+#define DCIF_CRC_HIST_COEF 0x20108
+#define DCIF_CRC_HIST_COEF_HIST_WB_MASK GENMASK(7, 0)
+#define DCIF_CRC_HIST_COEF_HIST_WB(x) FIELD_PREP(GENMASK(7, 0), x)
+#define DCIF_CRC_HIST_COEF_HIST_WG_MASK GENMASK(15, 8)
+#define DCIF_CRC_HIST_COEF_HIST_WG(x) FIELD_PREP(GENMASK(15, 8), x)
+#define DCIF_CRC_HIST_COEF_HIST_WR_MASK GENMASK(23, 16)
+#define DCIF_CRC_HIST_COEF_HIST_WR(x) FIELD_PREP(GENMASK(23, 16), x)
+
+#define DCIF_CRC_ERR_CNT 0x2010C
+#define DCIF_CRC_ERR_CNT_CRC_ERR_CNT_MASK GENMASK(31, 0)
+#define DCIF_CRC_ERR_CNT_CRC_ERR_CNT(x) FIELD_PREP(GENMASK(31, 0), x)
+
+#define DCIF_CRC_SR 0x20110
+#define DCIF_CRC_SR_HIST_CNT_SAT_MASK BIT(13)
+#define DCIF_CRC_SR_HIST_CNT_SAT(x) FIELD_PREP(GENMASK(13, 13), x)
+#define DCIF_CRC_SR_HIST_SAT_MASK BIT(14)
+#define DCIF_CRC_SR_HIST_SAT(x) FIELD_PREP(GENMASK(14, 14), x)
+#define DCIF_CRC_SR_HIST_BUSY_MASK BIT(15)
+#define DCIF_CRC_SR_HIST_BUSY(x) FIELD_PREP(GENMASK(15, 15), x)
+#define DCIF_CRC_SR_CRC_STATUS_MASK BIT(31)
+#define DCIF_CRC_SR_CRC_STATUS(x) FIELD_PREP(GENMASK(31, 31), x)
+
+#define DCIF_CRC_HIST_CNT_B(n) (0x20114 + (n) * 4)
+#define DCIF_B_BIN_CNT_MASK GENMASK(20, 0)
+#define DCIF_B_BIN_CNT(x) FIELD_PREP(GENMASK(20, 0), x)
+
+/* CRC Region Position, Size, Value, and Expected Value Registers, n=0-3 */
+#define DCIF_CRC_POS_R(n) (0x20214 + (n) * 0x10)
+#define DCIF_CRC_POS_CRC_HOR_POS(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_CRC_POS_CRC_VER_POS(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_CRC_SIZE_R(n) (0x20218 + (n) * 0x10)
+#define DCIF_CRC_SIZE_CRC_HOR_SIZE(x) FIELD_PREP(GENMASK(11, 0), x)
+#define DCIF_CRC_SIZE_CRC_VER_SIZE(x) FIELD_PREP(GENMASK(27, 16), x)
+
+#define DCIF_CRC_VAL_R(n) (0x2021C + (n) * 0x10)
+#define DCIF_CRC_VAL_CRC_VAL_MASK GENMASK(31, 0)
+#define DCIF_CRC_VAL_CRC_VAL(x) FIELD_PREP(GENMASK(31, 0), x)
+
+#define DCIF_CRC_EXP_VAL_R(n) (0x20220 + (n) * 0x10)
+#define DCIF_CRC_EXP_VAL_CRC_EXP_VAL_MASK GENMASK(31, 0)
+#define DCIF_CRC_EXP_VAL_CRC_EXP_VAL(x) FIELD_PREP(GENMASK(31, 0), x)
+
+#endif /* __DCIF_REG_H__ */
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (4 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 5/9] drm/imx: Add support for " Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 12:10 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 7/9] arm64: dts: imx94: Add display pipeline nodes Laurentiu Palcu
` (2 subsequent siblings)
8 siblings, 1 reply; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Abel Vesa, Peng Fan, Michael Turquette,
Stephen Boyd, Brian Masney, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
Since the BLK CTL registers, like the LVDS CSR, can be used to control the
LVDS Display Bridge controllers, add 'ldb' child node to handle
these use cases.
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
.../bindings/clock/nxp,imx95-blk-ctl.yaml | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
index 27403b4c52d62..2b3c762aba1e0 100644
--- a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
+++ b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
@@ -26,6 +26,12 @@ properties:
reg:
maxItems: 1
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 1
+
power-domains:
maxItems: 1
@@ -39,6 +45,11 @@ properties:
ID in its "clocks" phandle cell. See
include/dt-bindings/clock/nxp,imx95-clock.h
+patternProperties:
+ '^ldb@[0-9a-f]+$':
+ type: object
+ $ref: /schemas/display/bridge/fsl,ldb.yaml#
+
required:
- compatible
- reg
@@ -46,6 +57,21 @@ required:
- power-domains
- clocks
+allOf:
+ - if:
+ not:
+ properties:
+ compatible:
+ contains:
+ const: nxp,imx94-lvds-csr
+ then:
+ patternProperties:
+ "^ldb@[0-9a-f]+$": false
+ else:
+ required:
+ - '#address-cells'
+ - '#size-cells'
+
additionalProperties: false
examples:
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 7/9] arm64: dts: imx94: Add display pipeline nodes
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (5 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 8/9] arm64: dts: imx943-evk: Add display support using IT6263 Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 9/9] MAINTAINERS: Add entry for i.MX94 DCIF driver Laurentiu Palcu
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
Add the nodes necessary for the display pipeline on i.MX94:
* LVDS/DISPLAY CSR;
* clock-ldb-pll-div7 needed by DCIF and LDB;
* Display controller interface (DCIF);
* LVDS display bridge (LDB);
Co-developed-by: Peng Fan <peng.fan@nxp.com>
Signed-off-by: Peng Fan <peng.fan@nxp.com>
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
arch/arm64/boot/dts/freescale/imx94.dtsi | 82 ++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx94.dtsi b/arch/arm64/boot/dts/freescale/imx94.dtsi
index a6cb5a6e848b3..95d862682703c 100644
--- a/arch/arm64/boot/dts/freescale/imx94.dtsi
+++ b/arch/arm64/boot/dts/freescale/imx94.dtsi
@@ -3,6 +3,7 @@
* Copyright 2024-2025 NXP
*/
+#include <dt-bindings/clock/nxp,imx94-clock.h>
#include <dt-bindings/dma/fsl-edma.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
@@ -39,6 +40,15 @@ clk_ext1: clock-ext1 {
clock-output-names = "clk_ext1";
};
+ clk_ldb_pll_div7: clock-ldb-pll-div7 {
+ compatible = "fixed-factor-clock";
+ #clock-cells = <0>;
+ clocks = <&scmi_clk IMX94_CLK_LDBPLL>;
+ clock-div = <7>;
+ clock-mult = <1>;
+ clock-output-names = "ldb_pll_div7";
+ };
+
sai1_mclk: clock-sai1-mclk1 {
compatible = "fixed-clock";
#clock-cells = <0>;
@@ -1305,6 +1315,78 @@ wdog4: watchdog@49230000 {
};
};
+ dispmix_csr: syscon@4b010000 {
+ compatible = "nxp,imx94-display-csr", "syscon";
+ reg = <0x0 0x4b010000 0x0 0x10000>;
+ clocks = <&scmi_clk IMX94_CLK_DISPAPB>;
+ #clock-cells = <1>;
+ power-domains = <&scmi_devpd IMX94_PD_DISPLAY>;
+ assigned-clocks = <&scmi_clk IMX94_CLK_DISPAXI>,
+ <&scmi_clk IMX94_CLK_DISPAPB>;
+ assigned-clock-parents = <&scmi_clk IMX94_CLK_SYSPLL1_PFD1>,
+ <&scmi_clk IMX94_CLK_SYSPLL1_PFD1_DIV2>;
+ assigned-clock-rates = <400000000>, <133333333>;
+ };
+
+ lvds_csr: syscon@4b0c0000 {
+ compatible = "nxp,imx94-lvds-csr", "syscon";
+ reg = <0x0 0x4b0c0000 0x0 0x10000>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ clocks = <&scmi_clk IMX94_CLK_DISPAPB>;
+ #clock-cells = <1>;
+ power-domains = <&scmi_devpd IMX94_PD_DISPLAY>;
+
+ ldb: ldb@4 {
+ compatible = "fsl,imx94-ldb";
+ reg = <0x4 0x4>, <0x8 0x4>;
+ reg-names = "ldb", "lvds";
+ clocks = <&lvds_csr IMX94_CLK_DISPMIX_LVDS_CLK_GATE>;
+ clock-names = "ldb";
+ status = "disabled";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ lvds_in: endpoint {
+ remote-endpoint = <&dcif_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ };
+ };
+ };
+ };
+
+ dcif: display-controller@4b120000 {
+ compatible = "nxp,imx94-dcif";
+ reg = <0x0 0x4b120000 0x0 0x300000>;
+ interrupts = <GIC_SPI 377 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 378 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 379 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "common", "bg_layer", "fg_layer";
+ clocks = <&scmi_clk IMX94_CLK_DISPAPB>,
+ <&scmi_clk IMX94_CLK_DISPAXI>,
+ <&dispmix_csr IMX94_CLK_DISPMIX_CLK_SEL>;
+ clock-names = "apb", "axi", "pix";
+ assigned-clocks = <&dispmix_csr IMX94_CLK_DISPMIX_CLK_SEL>;
+ assigned-clock-parents = <&clk_ldb_pll_div7>;
+ power-domains = <&scmi_devpd IMX94_PD_DISPLAY>;
+ status = "disabled";
+
+ port {
+ dcif_out: endpoint {
+ remote-endpoint = <&lvds_in>;
+ };
+ };
+ };
+
hsio_blk_ctl: syscon@4c0100c0 {
compatible = "nxp,imx95-hsio-blk-ctl", "syscon";
reg = <0x0 0x4c0100c0 0x0 0x1>;
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 8/9] arm64: dts: imx943-evk: Add display support using IT6263
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (6 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 7/9] arm64: dts: imx94: Add display pipeline nodes Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 9/9] MAINTAINERS: Add entry for i.MX94 DCIF driver Laurentiu Palcu
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
The ITE IT6263 based NXP LVDS to HDMI converter can be attached to the
i.MX943 EVK board LVDS port using the mini-SAS connector. Since this is
the default configuration for the EVK, add support for it here.
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
---
arch/arm64/boot/dts/freescale/imx943-evk.dts | 86 ++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx943-evk.dts b/arch/arm64/boot/dts/freescale/imx943-evk.dts
index 7cfd424689507..41a2a700a86a5 100644
--- a/arch/arm64/boot/dts/freescale/imx943-evk.dts
+++ b/arch/arm64/boot/dts/freescale/imx943-evk.dts
@@ -77,6 +77,36 @@ dmic: dmic {
#sound-dai-cells = <0>;
};
+ hdmi-connector {
+ compatible = "hdmi-connector";
+ label = "hdmi";
+ type = "a";
+
+ port {
+ hdmi_connector_in: endpoint {
+ remote-endpoint = <&it6263_out>;
+ };
+ };
+ };
+
+ reg_1v8_ext: regulator-1v8-ext {
+ compatible = "regulator-fixed";
+ regulator-name = "1V8_EXT";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
+ reg_3v3_ext: regulator-3v3-ext {
+ compatible = "regulator-fixed";
+ regulator-name = "3V3_EXT";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-boot-on;
+ regulator-always-on;
+ };
+
reg_m2_pwr: regulator-m2-pwr {
compatible = "regulator-fixed";
regulator-name = "M.2-power";
@@ -210,6 +240,10 @@ memory@80000000 {
};
};
+&dcif {
+ status = "okay";
+};
+
&enetc1 {
clocks = <&scmi_clk IMX94_CLK_MAC4>;
clock-names = "ref";
@@ -248,6 +282,21 @@ &flexcan4 {
status = "okay";
};
+&ldb {
+ assigned-clocks = <&scmi_clk IMX94_CLK_LDBPLL_VCO>,
+ <&scmi_clk IMX94_CLK_LDBPLL>;
+ assigned-clock-rates = <4158000000>, <1039500000>;
+ status = "okay";
+
+ ports {
+ port@1 {
+ lvds_out: endpoint {
+ remote-endpoint = <&it6263_in>;
+ };
+ };
+ };
+};
+
&lpi2c3 {
clock-frequency = <400000>;
pinctrl-0 = <&pinctrl_lpi2c3>;
@@ -331,6 +380,43 @@ i2c@3 {
reg = <3>;
#address-cells = <1>;
#size-cells = <0>;
+
+ hdmi@4c {
+ compatible = "ite,it6263";
+ reg = <0x4c>;
+ data-mapping = "jeida-24";
+ reset-gpios = <&pcal6416_i2c3_u171 8 GPIO_ACTIVE_LOW>;
+ ivdd-supply = <®_1v8_ext>;
+ ovdd-supply = <®_3v3_ext>;
+ txavcc18-supply = <®_1v8_ext>;
+ txavcc33-supply = <®_3v3_ext>;
+ pvcc1-supply = <®_1v8_ext>;
+ pvcc2-supply = <®_1v8_ext>;
+ avcc-supply = <®_3v3_ext>;
+ anvdd-supply = <®_1v8_ext>;
+ apvdd-supply = <®_1v8_ext>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+
+ it6263_in: endpoint {
+ remote-endpoint = <&lvds_out>;
+ };
+ };
+
+ port@2 {
+ reg = <2>;
+
+ it6263_out: endpoint {
+ remote-endpoint = <&hdmi_connector_in>;
+ };
+ };
+ };
+ };
};
i2c@4 {
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v9 9/9] MAINTAINERS: Add entry for i.MX94 DCIF driver
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
` (7 preceding siblings ...)
2026-06-12 11:58 ` [PATCH v9 8/9] arm64: dts: imx943-evk: Add display support using IT6263 Laurentiu Palcu
@ 2026-06-12 11:58 ` Laurentiu Palcu
8 siblings, 0 replies; 13+ messages in thread
From: Laurentiu Palcu @ 2026-06-12 11:58 UTC (permalink / raw)
To: Ying Liu, Luca Ceresoli
Cc: Laurentiu Palcu, linux-clk, imx, devicetree, linux-arm-kernel,
linux-kernel, dri-devel
The driver is part of DRM subsystem and is located in
drivers/gpu/drm/imx/dcif.
Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
Reviewed-by: Frank Li <Frank.Li@nxp.com>
---
MAINTAINERS | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index f1caa6e5198b9..e8931231b5b7e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19492,6 +19492,15 @@ S: Maintained
F: Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml
F: drivers/media/platform/nxp/imx-jpeg
+NXP i.MX 94 DCIF DRIVER
+M: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
+L: dri-devel@lists.freedesktop.org
+L: imx@lists.linux.dev
+S: Maintained
+T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F: Documentation/devicetree/bindings/display/imx/nxp,imx94-dcif.yaml
+F: drivers/gpu/drm/imx/dcif/
+
NXP i.MX CLOCK DRIVERS
M: Abel Vesa <abelvesa@kernel.org>
R: Peng Fan <peng.fan@nxp.com>
--
2.51.0
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94
2026-06-12 11:58 ` [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94 Laurentiu Palcu
@ 2026-06-12 12:09 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 12:09 UTC (permalink / raw)
To: Laurentiu Palcu; +Cc: Frank.Li, imx, robh, conor+dt, dri-devel, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Pre-existing use of usleep_range() in an atomic commit callback (fsl_ldb_atomic_enable).
--
commit e14b3dd6d0171a77b5885d6c520dacd31270636e
Author: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
drm/bridge: fsl-ldb: Add support for i.MX94
i.MX94 series LDB controller shares the same LDB and LVDS control
registers as i.MX8MP and i.MX93 but supports a higher maximum clock
frequency.
Add a 'max_clk_khz' member to the fsl_ldb_devdata structure in order to
be able to set different max frequencies for other platforms.
> diff --git a/drivers/gpu/drm/bridge/fsl-ldb.c b/drivers/gpu/drm/bridge/fsl-ldb.c
> index bd03c36ee696c..b4959f654f2ac 100644
> --- a/drivers/gpu/drm/bridge/fsl-ldb.c
> +++ b/drivers/gpu/drm/bridge/fsl-ldb.c
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but I noticed a pre-existing
issue in fsl_ldb_atomic_enable() which executes as part of the atomic commit
modeset phase:
drivers/gpu/drm/bridge/fsl-ldb.c:fsl_ldb_atomic_enable() {
...
if (fsl_ldb->use_termination_resistor)
reg |= LVDS_CTRL_HS_EN;
regmap_write(fsl_ldb->regmap, fsl_ldb->devdata->lvds_ctrl, reg);
/* Wait for VBG to stabilize. */
usleep_range(15, 20);
reg |= (fsl_ldb->ch0_enabled ? LVDS_CTRL_CH0_EN : 0) |
(fsl_ldb->ch1_enabled ? LVDS_CTRL_CH1_EN : 0);
...
}
Are sleeping functions permitted in this context?
Because fsl_ldb_atomic_enable() acts as an encoder atomic enable callback,
it runs in an atomic context where sleeping delays like usleep_range()
are forbidden.
Could this lead to kernel warnings, system instability, or deadlocks?
Should this be replaced with a non-sleeping delay like udelay()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-dcif-upstreaming-v9-0-8d0ff89aa3c5@oss.nxp.com?part=3
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node
2026-06-12 11:58 ` [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node Laurentiu Palcu
@ 2026-06-12 12:10 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 12:10 UTC (permalink / raw)
To: Laurentiu Palcu; +Cc: dri-devel, imx, Frank.Li, robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `allOf` conditional block restricts the `ldb` child node exclusively to `nxp,imx94-lvds-csr`, improperly forbidding it for `nxp,imx95-lvds-csr`.
--
commit 6a3665e114245286584773223a39379615a4d4fc
Author: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node
This commit adds support for the 'ldb' child node to the i.MX95 BLK CTL
bindings. It updates the schema to handle cases where BLK CTL registers
control the LVDS Display Bridge controllers.
> diff --git a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
> --- a/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
> +++ b/Documentation/devicetree/bindings/clock/nxp,imx95-blk-ctl.yaml
[ ... ]
> @@ -46,6 +57,21 @@ required:
> - power-domains
> - clocks
>
> +allOf:
> + - if:
> + not:
> + properties:
> + compatible:
> + contains:
> + const: nxp,imx94-lvds-csr
[Severity: Medium]
Does this restriction improperly forbid the ldb child node on i.MX95 devices?
The clock driver exposes LDB-related clocks for the i.MX95 variant, so it
seems nxp,imx95-lvds-csr is also an LVDS CSR that controls the LVDS Display
Bridge. By omitting nxp,imx95-lvds-csr from this check, will valid i.MX95
device trees defining the ldb child node fail schema validation?
Should this condition check an enum containing both nxp,imx94-lvds-csr and
nxp,imx95-lvds-csr instead?
> + then:
> + patternProperties:
> + "^ldb@[0-9a-f]+$": false
> + else:
> + required:
> + - '#address-cells'
> + - '#size-cells'
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-dcif-upstreaming-v9-0-8d0ff89aa3c5@oss.nxp.com?part=6
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v9 5/9] drm/imx: Add support for i.MX94 DCIF
2026-06-12 11:58 ` [PATCH v9 5/9] drm/imx: Add support for " Laurentiu Palcu
@ 2026-06-12 12:18 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 12:18 UTC (permalink / raw)
To: Laurentiu Palcu; +Cc: robh, conor+dt, imx, devicetree, dri-devel, Frank.Li
Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider:
- [High] Read-modify-write (RMW) operations on Write-1-to-Clear (W1C) interrupt status registers cause lost interrupts.
- [High] Module reference count underflow and use-after-free caused by unbalanced `drm_bridge_put`.
- [High] CRTC `bus_format` and `bus_flags` state is corrupted and permanently lost during plane-only atomic updates.
- [High] Unprotected hardware register accesses in the IRQ handler risk bus errors on runtime suspended devices.
- [High] System resume fails to enable the display because `pm_runtime_resume_and_get` is called while PM transitions are disabled.
- [High] An unhandled pending vblank event in `dcif->event` during CRTC disable causes userspace to hang indefinitely and memory to leak.
- [High] Unlocked access to `crtc->state` in `dcif_crc_source_parse` causes a data race and potential use-after-free.
- [High] Failure of `pm_runtime_resume_and_get` causes the atomic commit to hang permanently.
- [High] Use of sleeping functions and inappropriate lock guards in atomic context paths.
- [High] Ignoring the return value of `drm_crtc_vblank_get()` causes vblank reference count underflow on failure.
- [High] Disabling Layer 0 without triggering `SHADOW_LOAD_EN` causes an abrupt hardware stop and AXI bus lockup risks.
- [High] `drm_crtc_vblank_on()` is called before software reset, permanently breaking vblank interrupts.
- [Medium] Dynamic plane color encoding and color range updates are silently ignored.
- [Medium] Transitions between active CRC sources (e.g., updating ROI coordinates) are ignored.
- [Medium] Data race (TOCTOU) on `dcif->crc_is_enabled` causes garbage CRC reads.
--
commit 4328341d625e1f0fbf8be8ca733a73ea0e76115e
Author: Sandor Yu <sandor.yu@nxp.com>
drm/imx: Add support for i.MX94 DCIF
This commit introduces DRM support for the i.MX94 Display Control
Interface (DCIF). It includes core CRTC, plane, and KMS integration,
along with CRC generation support.
> diff --git a/drivers/gpu/drm/imx/dcif/dcif-crc.h b/drivers/gpu/drm/imx/dcif/dcif-crc.h
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-crc.h
[ ... ]
> +static inline bool enable_dcif_crc_needed(struct dcif_crtc_state *new_dcstate,
> + struct dcif_crtc_state *old_dcstate)
> +{
> + return old_dcstate->crc.source == DCIF_CRC_SRC_NONE &&
> + new_dcstate->crc.source != DCIF_CRC_SRC_NONE;
> +}
[Severity: Medium]
Does this prevent updates when transitioning directly between two active
CRC configurations? If userspace modifies the ROI region while keeping the
source active, both helpers will return false, meaning the hardware might
continue capturing the old ROI coordinates.
> diff --git a/drivers/gpu/drm/imx/dcif/dcif-crc.c b/drivers/gpu/drm/imx/dcif/dcif-crc.c
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-crc.c
[ ... ]
> +void dcif_crc_source_disable(struct dcif_dev *dcif, int ncrc)
> +{
[ ... ]
> + regmap_clear_bits(dcif->regmap, DCIF_CRC_CTRL, DCIF_CRC_CTRL_CRC_EN(ncrc));
> +
> + dcif->crc_is_enabled = false;
> +}
[Severity: Medium]
Is there a data race on the crc_is_enabled flag here? This flag is modified
without holding the event_lock, while the IRQ handler evaluates it with the
lock held. This could allow the module to power off while the IRQ handler
attempts to read the disabled register, generating spurious payload data.
[ ... ]
> +static int dcif_crc_source_parse(struct drm_crtc *crtc, const char *source_name,
> + enum dcif_crc_source *s, struct drm_rect *roi)
> +{
> + static const char roi_prefix[] = "roi:";
> +
> + if (!source_name) {
> + *s = DCIF_CRC_SRC_NONE;
> + } else if (!strcmp(source_name, "auto")) {
> + struct drm_display_mode *mode = &crtc->state->adjusted_mode;
[Severity: High]
Can this unlocked access to the atomic state lead to a use-after-free? This
parser is called before acquiring any connection locks. A concurrent atomic
commit could swap and free the atomic state while this thread is referencing
the adjusted_mode.
> diff --git a/drivers/gpu/drm/imx/dcif/dcif-crtc.c b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-crtc.c
[ ... ]
> +static void dcif_disable_controller(struct dcif_dev *dcif)
> +{
> + u32 reg;
> + int ret;
> +
> + /* Disable layer 0 */
> + regmap_clear_bits(dcif->regmap, DCIF_CTRLDESC0(0), DCIF_CTRLDESC0_EN);
[Severity: High]
Does clearing the enable bit without also triggering the shadow load bit
leave the layer hardware active? The hardware typically ignores the register
change without the shadow load bit, meaning it will continue fetching data
via DMA. Severing the display controller abruptly while DMA is active could
risk AXI bus lockups.
[ ... ]
> +static void dcif_crtc_queue_state_event(struct drm_crtc *crtc)
> +{
> + struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
> +
> + scoped_guard(spinlock_irq, &crtc->dev->event_lock) {
> + if (crtc->state->event) {
> + WARN_ON(drm_crtc_vblank_get(crtc));
> + WARN_ON(dcif->event);
> + dcif->event = crtc->state->event;
[Severity: High]
What happens if drm_crtc_vblank_get returns an error? The code continues to
queue the event without having incremented the vblank reference count. When
the interrupt handler later processes the event and calls the corresponding
put function, it would cause a reference count underflow.
[ ... ]
> +static void dcif_crtc_query_output_bus_format(struct drm_crtc *crtc,
> + struct drm_crtc_state *crtc_state)
> +{
> + struct dcif_crtc_state *dcif_state = to_dcif_crtc_state(crtc_state);
> + struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
[Severity: High]
Will the cleanup attribute here cause a use-after-free? The bridge is
obtained via drm_bridge_chain_get_first_bridge, which does not increment
the module reference count. When the function returns, drm_bridge_put will
decrement a reference that was never acquired, potentially allowing the
bridge module to unload prematurely.
> + struct drm_bridge_state *bridge_state;
> +
> + dcif_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
> + dcif_state->bus_flags = 0;
> +
> + bridge = dcif_crtc_get_bridge(crtc, crtc_state);
> + if (!bridge)
> + return;
[Severity: High]
Could this overwrite destroy the existing bus_format and bus_flags during
a plane-only update? In a plane update, the bridge is not part of the atomic
commit state, causing the bridge state query below to return NULL. Returning
early leaves the state permanently overwritten with default values.
[ ... ]
> +static void dcif_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
[ ... ]
> + /* enable power when we start to set mode for CRTC */
> + ret = pm_runtime_resume_and_get(drm->dev);
> + if (ret < 0) {
> + drm_err(drm, "failed to resume DCIF, ret = %d\n", ret);
> + return;
> + }
[Severity: High]
Is pm_runtime_resume_and_get safe to call here? This is an atomic context
path during non-blocking commits, and this PM function can sleep.
Additionally, if this path executes during system resume, runtime PM
transitions are disabled and it will return an error, leaving the display
blanked.
If it does return early, the pending event in crtc->state->event is never
consumed. The DRM core expects the driver to consume the event, and
abandoning it here will cause the atomic commit to hang indefinitely
waiting for a flip completion.
> + dcif->crtc_pm_enabled = true;
> +
> + drm_crtc_vblank_on(crtc);
> +
> + dcif_crtc_mode_set_nofb(crtc_state, plane_state);
[Severity: High]
Will the software reset performed inside dcif_crtc_mode_set_nofb destroy the
vblank interrupt enablement? Since drm_crtc_vblank_on is called right before
the reset, the hardware state and interrupt enable bits will be cleared,
permanently breaking vblanks.
[ ... ]
> +static void dcif_crtc_atomic_disable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
[ ... ]
> + if (dcif->crtc_pm_enabled) {
> + dcif->crtc_pm_enabled = false;
> + pm_runtime_put_sync(drm->dev);
> + }
> +
> + scoped_guard(spinlock_irq, &crtc->dev->event_lock) {
> + if (crtc->state->event && !crtc->state->active) {
> + drm_crtc_send_vblank_event(crtc, crtc->state->event);
> + crtc->state->event = NULL;
> + }
> + }
> +}
[Severity: High]
Similar to the enable path, pm_runtime_put_sync is a sleeping function
called in an atomic context.
Also, does the use of the spinlock_irq scoped guard here cause problems?
When exiting the scoped guard, it unconditionally re-enables hardware
interrupts, which breaks the caller's atomic execution context.
Finally, if a non-blocking commit queued an event into dcif->event, it
seems to be ignored here. If the hardware interrupts are turned off, the
pending event in dcif->event is stranded and never signaled, leaving
userspace blocked.
[ ... ]
> +static int dcif_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> + struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
> + int domain = dcif->cpu_domain;
> +
> + /* Clear and enable VS_BLANK IRQ */
> + regmap_set_bits(dcif->regmap, DCIF_IS0(domain), DCIF_INT0_VS_BLANK);
> + regmap_set_bits(dcif->regmap, DCIF_IE0(domain), DCIF_INT0_VS_BLANK);
[Severity: High]
Does using a read-modify-write helper on a Write-1-to-Clear register cause
lost interrupts? regmap_set_bits will read the register, OR the new bit, and
write it back, which inadvertently clears any already pending interrupts
before the handler can process them. W1C registers usually need to be
written directly.
[ ... ]
> +static void dcif_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> + struct dcif_dev *dcif = crtc_to_dcif_dev(crtc);
> + int domain = dcif->cpu_domain;
> +
> + /* Disable and clear VS_BLANK IRQ */
> + regmap_clear_bits(dcif->regmap, DCIF_IE0(domain), DCIF_INT0_VS_BLANK);
> + regmap_clear_bits(dcif->regmap, DCIF_IS0(domain), DCIF_INT0_VS_BLANK);
> +}
[Severity: High]
The same applies here. regmap_clear_bits will write a 1 to all pending
interrupts while writing a 0 to the target bit, improperly clearing unrelated
interrupts and failing to clear the target bit.
[ ... ]
> +irqreturn_t dcif_irq_handler(int irq, void *data)
> +{
> + struct drm_device *drm = data;
> + struct dcif_dev *dcif = to_dcif_dev(drm);
> + int domain = dcif->cpu_domain;
> + u32 stat0, stat1, crc;
> +
> + regmap_read(dcif->regmap, DCIF_IS0(domain), &stat0);
[Severity: High]
Is it safe to read hardware registers here without verifying the power
management active state? If the device is in runtime suspend and its clocks
are turned off, a spurious interrupt could trigger this handler, and
accessing unclocked registers can cause bus faults or system lockups. Should
this be protected with pm_runtime_get_if_active?
> diff --git a/drivers/gpu/drm/imx/dcif/dcif-plane.c b/drivers/gpu/drm/imx/dcif/dcif-plane.c
> --- /dev/null
> +++ b/drivers/gpu/drm/imx/dcif/dcif-plane.c
[ ... ]
> +static int dcif_plane_atomic_check(struct drm_plane *plane, struct drm_atomic_commit *state)
> +{
[ ... ]
> + /*
> + * Force CRTC mode change if framebuffer stride or pixel format have changed.
> + */
> + if (plane->type == DRM_PLANE_TYPE_PRIMARY && old_fb &&
> + (fb->pitches[0] != old_fb->pitches[0] || fb->format->format != old_fb->format->format))
> + crtc_state->mode_changed = true;
[Severity: Medium]
Will dynamic updates to plane color encoding or color ranges be silently
ignored? The hardware CSC coefficients are programmed during a full modeset,
but this check does not flag a mode change if only the color properties are
modified.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-dcif-upstreaming-v9-0-8d0ff89aa3c5@oss.nxp.com?part=5
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-06-12 12:18 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12 11:58 [PATCH v9 0/9] Add support for i.MX94 DCIF Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 1/9] dt-bindings: display: fsl,ldb: Add i.MX94 LDB Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 2/9] drm/bridge: fsl-ldb: Get the next non-panel bridge Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 3/9] drm/bridge: fsl-ldb: Add support for i.MX94 Laurentiu Palcu
2026-06-12 12:09 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 4/9] dt-bindings: display: imx: Add i.MX94 DCIF Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 5/9] drm/imx: Add support for " Laurentiu Palcu
2026-06-12 12:18 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 6/9] dt-bindings: clock: nxp,imx95-blk-ctl: Add ldb child node Laurentiu Palcu
2026-06-12 12:10 ` sashiko-bot
2026-06-12 11:58 ` [PATCH v9 7/9] arm64: dts: imx94: Add display pipeline nodes Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 8/9] arm64: dts: imx943-evk: Add display support using IT6263 Laurentiu Palcu
2026-06-12 11:58 ` [PATCH v9 9/9] MAINTAINERS: Add entry for i.MX94 DCIF driver Laurentiu Palcu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox