Devicetree
 help / color / mirror / Atom feed
From: Cody Kang via B4 Relay <devnull+codykang.hk.gmail.com@kernel.org>
To: David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
	 Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
	 Maxime Ripard <mripard@kernel.org>,
	Thomas Zimmermann <tzimmermann@suse.de>,
	 Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>, Yixun Lan <dlan@kernel.org>,
	 Vinod Koul <vkoul@kernel.org>,
	Neil Armstrong <neil.armstrong@linaro.org>,
	 Haylen Chu <heylenay@4d2.org>,
	Michael Turquette <mturquette@baylibre.com>,
	 Stephen Boyd <sboyd@kernel.org>,
	Brian Masney <bmasney@redhat.com>,
	 Philipp Zabel <p.zabel@pengutronix.de>,
	Paul Walmsley <pjw@kernel.org>,
	 Palmer Dabbelt <palmer@dabbelt.com>,
	Albert Ou <aou@eecs.berkeley.edu>,
	 Alexandre Ghiti <alex@ghiti.fr>
Cc: dri-devel@lists.freedesktop.org, linux-riscv@lists.infradead.org,
	 devicetree@vger.kernel.org, spacemit@lists.linux.dev,
	 linux-kernel@vger.kernel.org, linux-phy@lists.infradead.org,
	 linux-clk@vger.kernel.org, Cody Kang <codykang.hk@gmail.com>
Subject: [PATCH RESEND 10/17] drm/spacemit: add Saturn DPU KMS pipeline
Date: Sat, 25 Jul 2026 00:51:19 -0400	[thread overview]
Message-ID: <20260725-k3-display-v1-10-6de34d80e86c@gmail.com> (raw)
In-Reply-To: <20260725-k3-display-v1-0-6de34d80e86c@gmail.com>

From: Cody Kang <codykang.hk@gmail.com>

Add the atomic KMS implementation on top of the hardware backend: one
CRTC and one primary plane per DPU instance. atomic_check validates
the plane rectangle 1:1 against the mode and sizes the per-channel FBC
line buffer; atomic commit builds the cmdlist batches, maps the
framebuffer through the DMMU and arms the cfg-ready handshake, with
vblank events driven from the DPU interrupt.

Signed-off-by: Cody Kang <codykang.hk@gmail.com>
---
 drivers/gpu/drm/spacemit/spacemit_crtc.c   | 815 +++++++++++++++++++++++++++++
 drivers/gpu/drm/spacemit/spacemit_planes.c | 376 +++++++++++++
 2 files changed, 1191 insertions(+)

diff --git a/drivers/gpu/drm/spacemit/spacemit_crtc.c b/drivers/gpu/drm/spacemit/spacemit_crtc.c
new file mode 100644
index 000000000000..b75ff6320501
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/spacemit_crtc.c
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#include <drm/drm_print.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/clients/drm_client_setup.h>
+#include <linux/aperture.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/component.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/pm_qos.h>
+#include <linux/property.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/trace_events.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include "spacemit_dpu_ids.h"
+#include <linux/reset.h>
+#include "spacemit_cmdlist.h"
+#include "spacemit_dmmu.h"
+#include "spacemit_drm.h"
+#include "spacemit_crtc.h"
+#include "dpu/dpu_saturn.h"
+#include "dpu/dpu_trace.h"
+
+static int spacemit_crtc_init(struct spacemit_crtc *a_crtc);
+static int spacemit_crtc_uninit(struct spacemit_crtc *a_crtc);
+static int dpu_pm_suspend(struct device *dev);
+static int dpu_pm_resume(struct device *dev);
+static void dpu_reset_assert(struct device *dev, const char *name,
+			     struct reset_control *rstc);
+
+static void saturn_count_rdma_use(struct drm_crtc_state *crtc_state)
+{
+	struct drm_plane *plane;
+	struct spacemit_crtc_state *spacemit_state =
+			to_spacemit_crtc_state(crtc_state);
+	struct spacemit_crtc_rdma *rdmas = spacemit_state->rdmas;
+	u32 rdma_id = 0;
+	const struct drm_plane_state *pstate;
+
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		rdma_id = to_spacemit_plane_state(pstate)->rdma_id;
+		if (rdma_id != RDMA_INVALID_ID)
+			rdmas[rdma_id].use_cnt++;
+	}
+}
+
+static int spacemit_crtc_atomic_check_fbmem(struct drm_crtc *crtc,
+					    struct drm_crtc_state *crtc_state)
+{
+	struct spacemit_crtc_rdma *rdmas =
+			to_spacemit_crtc_state(crtc_state)->rdmas;
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+	struct spacemit_drm_private *priv = crtc->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	const struct drm_plane_state *pstate;
+	struct drm_plane *plane;
+
+	drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+		u32 rdma_id = to_spacemit_plane_state(pstate)->rdma_id;
+		u32 layer_fbcmem_size =
+				to_spacemit_plane_state(pstate)->fbcmem_size;
+
+		if (rdma_id != RDMA_INVALID_ID)
+			rdmas[rdma_id].fbcmem.size = max(layer_fbcmem_size,
+				rdmas[rdma_id].fbcmem.size);
+	}
+
+	return a_crtc->core->adjust_rdma_fbcmem(hwdev, rdmas);
+}
+
+static void spacemit_crtc_atomic_enable(struct drm_crtc *crtc,
+					struct drm_atomic_commit *old_state)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+	int idx;
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		return;
+
+	trace_spacemit_crtc_atomic_enable(a_crtc->dev_id);
+
+	/*
+	 * U-Boot leaves its boot-logo pipeline running. Hard-stop it with the
+	 * resets alone; the clocks are not ours to gate until we have enabled
+	 * them.
+	 */
+	if (unlikely(a_crtc->first_modeset)) {
+		dpu_reset_assert(a_crtc->dev, "lcd", a_crtc->lcd_reset);
+		dpu_reset_assert(a_crtc->dev, "esc", a_crtc->esc_reset);
+		dpu_reset_assert(a_crtc->dev, "mclk", a_crtc->mclk_reset);
+		dpu_reset_assert(a_crtc->dev, "aclk", a_crtc->aclk_reset);
+		dpu_reset_assert(a_crtc->dev, "dsc", a_crtc->dsc_reset);
+		usleep_range(10000, 20000);
+		a_crtc->first_modeset = false;
+	}
+
+	if (!a_crtc->clocks_on) {
+		a_crtc->clocks_on = true;
+		dpu_pm_resume(a_crtc->dev);
+
+		/*
+		 * The DPU must settle after reset deassert or CFG_RDY races
+		 * the flush.
+		 */
+		usleep_range(10000, 20000);
+	}
+
+	drm_crtc_vblank_on(&a_crtc->crtc);
+
+	spacemit_crtc_init(a_crtc);
+
+	drm_dev_exit(idx);
+}
+
+static void spacemit_crtc_atomic_disable(struct drm_crtc *crtc,
+					 struct drm_atomic_commit *old_state)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+	struct drm_device *drm = a_crtc->crtc.dev;
+	int idx;
+
+	if (!drm_dev_enter(drm, &idx))
+		return;
+
+	trace_spacemit_crtc_atomic_disable(a_crtc->dev_id);
+
+	spacemit_crtc_uninit(a_crtc);
+
+	drm_crtc_vblank_off(&a_crtc->crtc);
+
+	/*
+	 * MMIO on a clock-gated DPU stalls the bus, so an in-flight handler
+	 * must finish its status reads before dpu_pm_suspend() gates the
+	 * clocks.
+	 */
+	if (a_crtc->irq_online > 0)
+		synchronize_irq(a_crtc->irq_online);
+
+	/*
+	 * Nothing else cancels the watchdog; it would fire against freed state.
+	 */
+	timer_delete_sync(&a_crtc->cfg_rdy_timer);
+
+	/* Close the ISR's MMIO gate before the clocks actually stop. */
+	a_crtc->clocks_on = false;
+	dpu_pm_suspend(a_crtc->dev);
+
+	spin_lock_irq(&drm->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+	}
+	spin_unlock_irq(&drm->event_lock);
+
+	drm_dev_exit(idx);
+}
+
+static int spacemit_crtc_atomic_check(struct drm_crtc *crtc,
+				      struct drm_atomic_commit *atomic_state)
+{
+	struct drm_crtc_state *state =
+			drm_atomic_get_new_crtc_state(atomic_state, crtc);
+	struct spacemit_crtc_state *cstate = to_spacemit_crtc_state(state);
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+	int ret = 0;
+
+	trace_spacemit_crtc_atomic_check(a_crtc->dev_id);
+
+	ret = spacemit_crtc_atomic_check_fbmem(crtc, state);
+	if (ret) {
+		drm_dbg_kms(crtc->dev,
+			    "failed to satisfy fbcmem size for all rdmas\n");
+		return ret;
+	}
+
+	saturn_count_rdma_use(state);
+
+	ret = spacemit_cmdlist_alloc(a_crtc->dev, &cstate->cl);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void spacemit_crtc_atomic_begin(struct drm_crtc *crtc,
+				       struct drm_atomic_commit *state)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+
+	trace_spacemit_crtc_atomic_begin(a_crtc->dev_id);
+}
+
+static void spacemit_crtc_atomic_flush(struct drm_crtc *crtc,
+				       struct drm_atomic_commit *state)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
+		crtc);
+	int idx;
+
+	if (!drm_dev_enter(crtc->dev, &idx))
+		return;
+
+	trace_spacemit_crtc_atomic_flush(a_crtc->dev_id);
+
+	spacemit_crtc_run(crtc, old_state);
+
+	drm_dev_exit(idx);
+}
+
+static struct drm_crtc_state *
+spacemit_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct spacemit_crtc_state *state;
+	struct spacemit_drm_private *priv = crtc->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	u8 n_rdma;
+
+	if (WARN_ON(!crtc->state))
+		return NULL;
+
+	state = kzalloc_obj(*state);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
+
+	n_rdma = hwdev->rdma_nums;
+	state->rdmas = kzalloc_objs(*state->rdmas, n_rdma);
+	if (!state->rdmas) {
+		kfree(state);
+		return NULL;
+	}
+
+	state->cl.type = CMDLIST_CRTC;
+
+	return &state->base;
+}
+
+static void spacemit_crtc_destroy_state(struct drm_crtc *crtc,
+					struct drm_crtc_state *state)
+{
+	struct spacemit_crtc_state *spacemit_state = NULL;
+	struct spacemit_crtc *a_crtc = NULL;
+
+	if (state) {
+		spacemit_state = to_spacemit_crtc_state(state);
+		__drm_atomic_helper_crtc_destroy_state(state);
+		a_crtc = to_spacemit_crtc(crtc);
+		if (spacemit_state->cl.va)
+			dma_free_coherent(a_crtc->dev,
+					  spacemit_state->cl.size,
+					  spacemit_state->cl.va,
+					  spacemit_state->cl.pa);
+		kfree(spacemit_state->rdmas);
+		kfree(spacemit_state);
+	}
+}
+
+static void spacemit_crtc_reset(struct drm_crtc *crtc)
+{
+	struct spacemit_drm_private *priv = crtc->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	struct spacemit_crtc_state *state;
+
+	if (crtc->state)
+		spacemit_crtc_destroy_state(crtc, crtc->state);
+
+	state = kzalloc_obj(*state);
+	if (!state) {
+		__drm_atomic_helper_crtc_reset(crtc, NULL);
+		return;
+	}
+
+	state->rdmas = kzalloc_objs(*state->rdmas, hwdev->rdma_nums);
+	if (!state->rdmas) {
+		kfree(state);
+		__drm_atomic_helper_crtc_reset(crtc, NULL);
+		return;
+	}
+
+	__drm_atomic_helper_crtc_reset(crtc, &state->base);
+}
+
+static int spacemit_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+
+	trace_spacemit_crtc_enable_vblank(a_crtc->dev_id);
+
+	if (a_crtc->core && a_crtc->core->enable_vsync)
+		a_crtc->core->enable_vsync(a_crtc);
+
+	return 0;
+}
+
+static void spacemit_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+
+	trace_spacemit_crtc_disable_vblank(a_crtc->dev_id);
+
+	if (a_crtc->core && a_crtc->core->disable_vsync)
+		a_crtc->core->disable_vsync(a_crtc);
+}
+
+static const struct drm_crtc_helper_funcs spacemit_crtc_helper_funcs = {
+	.atomic_check = spacemit_crtc_atomic_check,
+	.atomic_begin = spacemit_crtc_atomic_begin,
+	.atomic_flush = spacemit_crtc_atomic_flush,
+	.atomic_enable = spacemit_crtc_atomic_enable,
+	.atomic_disable = spacemit_crtc_atomic_disable,
+};
+
+static const struct drm_crtc_funcs spacemit_crtc_funcs = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = spacemit_crtc_reset,
+	.atomic_duplicate_state = spacemit_crtc_duplicate_state,
+	.atomic_destroy_state = spacemit_crtc_destroy_state,
+	.enable_vblank = spacemit_crtc_enable_vblank,
+	.disable_vblank = spacemit_crtc_disable_vblank,
+};
+
+static void spacemit_crtc_put_port(struct drm_device *drm, void *port)
+{
+	of_node_put(port);
+}
+
+static int spacemit_crtc_bind_init(struct drm_device *drm, struct drm_crtc *crtc,
+				   struct drm_plane *primary, struct device_node *port)
+{
+	int err;
+
+	/*
+	 * drm_of_find_possible_crtcs() walks crtc->port for the life of the
+	 * drm_device.
+	 */
+	crtc->port = of_node_get(port);
+	err = drmm_add_action_or_reset(drm, spacemit_crtc_put_port, port);
+	if (err)
+		return err;
+
+	err = drm_crtc_init_with_planes(drm, crtc, primary, NULL,
+					&spacemit_crtc_funcs, NULL);
+	if (err) {
+		drm_err(drm, "failed to init crtc\n");
+		return err;
+	}
+
+	drm_mode_crtc_set_gamma_size(crtc, 256);
+
+	drm_crtc_helper_add(crtc, &spacemit_crtc_helper_funcs);
+
+	return 0;
+}
+
+int spacemit_crtc_run(struct drm_crtc *crtc,
+		      struct drm_crtc_state *old_state)
+{
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+
+	trace_spacemit_crtc_run(a_crtc->dev_id);
+
+	if (a_crtc->core && a_crtc->core->run)
+		a_crtc->core->run(crtc, old_state);
+
+	return 0;
+}
+
+static int spacemit_crtc_init(struct spacemit_crtc *a_crtc)
+{
+	trace_spacemit_crtc_init(a_crtc->dev_id);
+
+	if (a_crtc->core && a_crtc->core->init)
+		a_crtc->core->init(a_crtc);
+
+	a_crtc->is_1st_f = true;
+
+	return 0;
+}
+
+static int spacemit_crtc_uninit(struct spacemit_crtc *a_crtc)
+{
+	trace_spacemit_crtc_uninit(a_crtc->dev_id);
+
+	if (a_crtc->core && a_crtc->core->uninit)
+		a_crtc->core->uninit(a_crtc);
+
+	return 0;
+}
+
+static irqreturn_t spacemit_dpu_isr(int irq, void *data)
+{
+	struct spacemit_drm_private *priv = data;
+	struct spacemit_crtc *a_crtc = priv->a_crtc;
+	int idx;
+
+	if (!a_crtc)
+		return IRQ_NONE;
+
+	if (!drm_dev_enter(a_crtc->crtc.dev, &idx))
+		return IRQ_NONE;
+
+	/* Reading a clock-gated DPU's status registers stalls the bus. */
+	if (unlikely(!a_crtc->clocks_on)) {
+		drm_dev_exit(idx);
+		return IRQ_NONE;
+	}
+
+	if (a_crtc->core && a_crtc->core->online_isr)
+		a_crtc->core->online_isr(a_crtc);
+
+	drm_dev_exit(idx);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Acquire at probe, not at bind: the aggregate can rebind without this device
+ * detaching, and the exclusive resets would not be handed out a second time.
+ */
+static int spacemit_dpu_hw_resources_init(struct spacemit_drm_private *priv,
+					  struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	ret = dpu_parse_clocks(dev, &priv->clk_ctx);
+	if (ret)
+		return ret;
+
+	priv->mclk_reset = devm_reset_control_get_optional_exclusive(dev,
+								     "mclk");
+	if (IS_ERR(priv->mclk_reset))
+		return dev_err_probe(dev, PTR_ERR(priv->mclk_reset),
+				     "failed to get mclk reset\n");
+	priv->esc_reset = devm_reset_control_get_optional_exclusive(dev, "esc");
+	if (IS_ERR(priv->esc_reset))
+		return dev_err_probe(dev, PTR_ERR(priv->esc_reset),
+				     "failed to get esc reset\n");
+	priv->lcd_reset = devm_reset_control_get_optional_exclusive(dev, "lcd");
+	if (IS_ERR(priv->lcd_reset))
+		return dev_err_probe(dev, PTR_ERR(priv->lcd_reset),
+				     "failed to get lcd reset\n");
+	priv->aclk_reset = devm_reset_control_get_optional_exclusive(dev,
+								     "aclk");
+	if (IS_ERR(priv->aclk_reset))
+		return dev_err_probe(dev, PTR_ERR(priv->aclk_reset),
+				     "failed to get aclk reset\n");
+	priv->dsc_reset = devm_reset_control_get_optional_exclusive(dev, "dsc");
+	if (IS_ERR(priv->dsc_reset))
+		return dev_err_probe(dev, PTR_ERR(priv->dsc_reset),
+				     "failed to get dsc reset\n");
+
+	priv->irq_online = platform_get_irq_byname(pdev, "online");
+	if (priv->irq_online < 0)
+		return priv->irq_online;
+
+	/*
+	 * Keep the line disabled until bind: the handler needs bind-created
+	 * state, and a bootloader-left display can raise an interrupt during
+	 * probe.
+	 */
+	ret = devm_request_irq(dev, priv->irq_online, spacemit_dpu_isr,
+			       IRQF_NO_AUTOEN, "DPU_ONLINE", priv);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to request online IRQ\n");
+
+	return 0;
+}
+
+static const struct regmap_config spacemit_dpu_regmap_config = {
+	.name		= "dpu",
+	.reg_bits	= 32,
+	.val_bits	= 32,
+	.reg_stride	= 4,
+	.max_register	= 0x53ffc,	/* 0x54000-byte DPU MMIO from binding */
+};
+
+/* Bind-scope wrapper so devm_drm_dev_alloc has a struct to embed into. */
+struct spacemit_kms {
+	struct drm_device drm;
+};
+
+static int spacemit_drm_master_bind(struct device *dev)
+{
+	struct spacemit_drm_private *priv = dev_get_drvdata(dev);
+	struct device_node *np = dev->of_node;
+	struct device_node *ports = NULL, *port = NULL;
+	struct spacemit_crtc *a_crtc;
+	struct drm_plane *plane;
+	struct spacemit_kms *kms;
+	struct drm_device *drm;
+	int ret;
+
+	ret = aperture_remove_all_conflicting_devices(spacemit_drm_drv.name);
+	if (ret) {
+		dev_err(dev, "failed to remove existing framebuffers: %d\n",
+			ret);
+		return ret;
+	}
+
+	kms = devm_drm_dev_alloc(dev, &spacemit_drm_drv,
+				 struct spacemit_kms, drm);
+	if (IS_ERR(kms))
+		return PTR_ERR(kms);
+	drm = &kms->drm;
+	priv->ddev = drm;
+	drm->dev_private = priv;
+
+	ret = spacemit_drm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	a_crtc = drmm_kzalloc(drm, sizeof(*a_crtc), GFP_KERNEL);
+	if (!a_crtc) {
+		ret = -ENOMEM;
+		goto err_dc;
+	}
+	a_crtc->dev = dev;
+	a_crtc->first_modeset = true;
+	priv->a_crtc = a_crtc;
+
+	a_crtc->clk_ctx = priv->clk_ctx;
+	a_crtc->aclk = DPU_AXICLK_DEFAULT;
+	a_crtc->mclk_reset = priv->mclk_reset;
+	a_crtc->esc_reset = priv->esc_reset;
+	a_crtc->lcd_reset = priv->lcd_reset;
+	a_crtc->aclk_reset = priv->aclk_reset;
+	a_crtc->dsc_reset = priv->dsc_reset;
+	a_crtc->irq_online = priv->irq_online;
+
+	/* Both K3 DPU instances are COMPOSER1 in the Saturn IRQ tables. */
+	a_crtc->dev_id = COMPOSER1;
+
+	/* The K3 pipeline always hands RGB888 to the DP/eDP encoder. */
+	a_crtc->out_format = OUTFMT_RGB888;
+
+	a_crtc->core = &dpu_saturn_ops;
+
+	timer_setup(&a_crtc->cfg_rdy_timer, spacemit_cfg_rdy_timer_handler, 0);
+
+	plane = spacemit_plane_init(drm, a_crtc);
+	if (IS_ERR(plane)) {
+		ret = PTR_ERR(plane);
+		goto err_dc;
+	}
+
+	ports = of_get_child_by_name(np, "ports");
+	if (!ports) {
+		dev_err(dev, "CRTC %pOF has no ports node\n", np);
+		ret = -EINVAL;
+		goto err_dc;
+	}
+
+	port = of_get_child_by_name(ports, "port");
+	if (!port) {
+		dev_err(dev, "CRTC %pOF has no port@X node\n", np);
+		ret = -EINVAL;
+		goto err_ports_put;
+	}
+
+	ret = spacemit_crtc_bind_init(drm, &a_crtc->crtc, plane, port);
+	of_node_put(port);
+	of_node_put(ports);
+	port = NULL;
+	ports = NULL;
+	if (ret)
+		goto err_dc;
+
+	/*
+	 * component_bind_all() unbinds what it bound before returning an error.
+	 */
+	ret = component_bind_all(dev, drm);
+	if (ret) {
+		dev_err(dev, "failed to bind all components\n");
+		goto err_dc;
+	}
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret) {
+		dev_err(dev, "failed to initialize vblank\n");
+		goto err_unbind_all;
+	}
+
+	drm_mode_config_reset(drm);
+	drmm_kms_helper_poll_init(drm);
+	drm_helper_hpd_irq_event(drm);
+
+	enable_irq(priv->irq_online);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret < 0)
+		goto err_disable_irq;
+	drm_client_setup(drm, NULL);
+
+	return 0;
+
+err_disable_irq:
+	disable_irq(priv->irq_online);
+
+err_unbind_all:
+	component_unbind_all(dev, drm);
+	goto err_dc;
+err_ports_put:
+	of_node_put(port);
+	of_node_put(ports);
+err_dc:
+	/*
+	 * .shutdown tests priv->ddev; never leave it on a half-initialised
+	 * device.
+	 */
+	priv->ddev = NULL;
+	priv->a_crtc = NULL;
+	return ret;
+}
+
+static void spacemit_drm_master_unbind(struct device *dev)
+{
+	struct spacemit_drm_private *priv = dev_get_drvdata(dev);
+	struct drm_device *drm = priv->ddev;
+
+	disable_irq(priv->irq_online);
+
+	drm_dev_unregister(drm);
+	drm_atomic_helper_shutdown(drm);
+	component_unbind_all(dev, drm);
+
+	/* .shutdown and a later re-bind must not touch the stale instance. */
+	priv->ddev = NULL;
+	priv->a_crtc = NULL;
+}
+
+const struct component_master_ops spacemit_drm_master_ops = {
+	.bind = spacemit_drm_master_bind,
+	.unbind = spacemit_drm_master_unbind,
+};
+
+static int spacemit_dpu_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	const struct spacemit_hw_device *hwdev_template;
+	struct spacemit_drm_private *priv;
+	struct device_node *np = dev->of_node;
+	int ret;
+
+	if (!np) {
+		dev_err(dev, "no OF node for the DPU\n");
+		return -ENODEV;
+	}
+
+	ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->dev = dev;
+	dev_set_drvdata(dev, priv);
+
+	/* The match data is shared const; the MMIO mapping is per-instance. */
+	hwdev_template = device_get_match_data(dev);
+	if (!hwdev_template)
+		return -ENODEV;
+	priv->hwdev = devm_kmemdup(dev, hwdev_template,
+				   sizeof(*hwdev_template), GFP_KERNEL);
+	if (!priv->hwdev)
+		return -ENOMEM;
+
+	priv->cmdlist_groups = devm_kcalloc(dev,
+		priv->hwdev->rdma_nums + priv->hwdev->crtc_nums,
+		sizeof(*priv->cmdlist_groups), GFP_KERNEL);
+	if (!priv->cmdlist_groups)
+		return -ENOMEM;
+
+	priv->hwdev->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(priv->hwdev->base))
+		return PTR_ERR(priv->hwdev->base);
+
+	priv->hwdev->regmap = devm_regmap_init_mmio(dev, priv->hwdev->base,
+		&spacemit_dpu_regmap_config);
+	if (IS_ERR(priv->hwdev->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->hwdev->regmap),
+				     "failed to init DPU regmap\n");
+
+	ret = spacemit_dpu_hw_resources_init(priv, pdev);
+	if (ret)
+		return ret;
+
+	return spacemit_drm_of_component_probe(dev);
+}
+
+static void spacemit_dpu_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &spacemit_drm_master_ops);
+}
+
+static void spacemit_dpu_shutdown(struct platform_device *pdev)
+{
+	struct spacemit_drm_private *priv = dev_get_drvdata(&pdev->dev);
+	struct drm_device *drm = priv->ddev;
+
+	if (!drm) {
+		dev_warn(&pdev->dev,
+			 "drm device is not available, no shutdown\n");
+		return;
+	}
+
+	drm_atomic_helper_shutdown(drm);
+}
+
+/*
+ * The assert and deassert orders are not mirror images: the hardware wants
+ * these.
+ */
+static void dpu_reset_assert(struct device *dev, const char *name,
+			     struct reset_control *rstc)
+{
+	int ret = reset_control_assert(rstc);
+
+	if (ret)
+		dev_warn(dev, "failed to assert %s reset: %d\n", name, ret);
+}
+
+static void dpu_reset_deassert(struct device *dev, const char *name,
+			       struct reset_control *rstc)
+{
+	int ret = reset_control_deassert(rstc);
+
+	if (ret)
+		dev_warn(dev, "failed to deassert %s reset: %d\n", name, ret);
+}
+
+static int dpu_pm_suspend(struct device *dev)
+{
+	struct spacemit_drm_private *priv = dev_get_drvdata(dev);
+	struct spacemit_crtc *a_crtc = priv->a_crtc;
+
+	/*
+	 * Assert before gating: a reset asserted into an already-gated block
+	 * has no clock edges to propagate on and leaves the register file
+	 * untouched.
+	 */
+	dpu_reset_assert(dev, "lcd", a_crtc->lcd_reset);
+	dpu_reset_assert(dev, "esc", a_crtc->esc_reset);
+	dpu_reset_assert(dev, "mclk", a_crtc->mclk_reset);
+	dpu_reset_assert(dev, "aclk", a_crtc->aclk_reset);
+	dpu_reset_assert(dev, "dsc", a_crtc->dsc_reset);
+	usleep_range(10000, 20000);
+
+	if (a_crtc->core && a_crtc->core->disable_clk)
+		a_crtc->core->disable_clk(a_crtc);
+
+	return 0;
+}
+
+static int dpu_pm_resume(struct device *dev)
+{
+	struct spacemit_drm_private *priv = dev_get_drvdata(dev);
+	struct spacemit_crtc *a_crtc = priv->a_crtc;
+
+	dpu_reset_deassert(dev, "mclk", a_crtc->mclk_reset);
+	dpu_reset_deassert(dev, "esc", a_crtc->esc_reset);
+	dpu_reset_deassert(dev, "lcd", a_crtc->lcd_reset);
+	dpu_reset_deassert(dev, "aclk", a_crtc->aclk_reset);
+	dpu_reset_deassert(dev, "dsc", a_crtc->dsc_reset);
+
+	if (a_crtc->core && a_crtc->core->enable_clk)
+		a_crtc->core->enable_clk(a_crtc);
+
+	return 0;
+}
+
+static const struct of_device_id dpu_match_table[] = {
+	{
+		.compatible = "spacemit,k3-saturn-dpu",
+		.data = &k3_saturn_dpu,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, dpu_match_table);
+
+static struct platform_driver spacemit_dpu_driver = {
+	.probe = spacemit_dpu_probe,
+	.remove = spacemit_dpu_remove,
+	.shutdown = spacemit_dpu_shutdown,
+	.driver = {
+		.name = "spacemit-dpu-drv",
+		.of_match_table = dpu_match_table,
+		.suppress_bind_attrs = true,
+	},
+};
+module_platform_driver(spacemit_dpu_driver);
+
+MODULE_DESCRIPTION("SpacemiT DRM KMS master driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/spacemit/spacemit_planes.c b/drivers/gpu/drm/spacemit/spacemit_planes.c
new file mode 100644
index 000000000000..3442013b0c37
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/spacemit_planes.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_color_mgmt.h>
+#include <drm/drm_gem.h>
+#include <linux/component.h>
+#include <linux/dma-mapping.h>
+#include "spacemit_cmdlist.h"
+#include "spacemit_dmmu.h"
+#include "spacemit_drm.h"
+#include "dpu/dpu_saturn.h"
+#include "dpu/dpu_trace.h"
+#include "dpu/saturn_fbcmem.h"
+
+/* The AFBC decoder exists but no modifier has been validated on hardware. */
+static const u64 supported_format_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+struct spacemit_plane *to_spacemit_plane(struct drm_plane *plane)
+{
+	return container_of(plane, struct spacemit_plane, plane);
+}
+
+static int spacemit_plane_atomic_check(struct drm_plane *plane,
+				       struct drm_atomic_commit *atomic_state)
+{
+	struct drm_plane_state *state =
+			drm_atomic_get_new_plane_state(atomic_state, plane);
+	struct drm_framebuffer *fb = state->fb;
+	u32 src_w, src_h, src_x, src_y;
+	u32 crtc_w, crtc_h;
+	struct spacemit_plane_state *cur_state = to_spacemit_plane_state(state);
+	u32 cur_rdma_id = cur_state->rdma_id;
+	struct spacemit_drm_private *priv = plane->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	struct spacemit_crtc *a_crtc = NULL;
+
+	struct drm_crtc_state *crtc_state = NULL;
+	int ret;
+
+	if (state->crtc)
+		crtc_state = drm_atomic_get_new_crtc_state(atomic_state,
+							   state->crtc);
+
+	ret = drm_atomic_helper_check_plane_state(state, crtc_state,
+						  DRM_PLANE_NO_SCALING,
+						  DRM_PLANE_NO_SCALING,
+						  true, false);
+	if (ret)
+		return ret;
+
+	if (!state->visible)
+		return 0;
+
+	a_crtc = to_spacemit_crtc(state->crtc);
+	trace_spacemit_plane_atomic_check(a_crtc->dev_id);
+
+	src_x = state->src_x >> 16;
+	src_y = state->src_y >> 16;
+	src_w = state->src_w >> 16;
+	src_h = state->src_h >> 16;
+	crtc_w = state->crtc_w;
+	crtc_h = state->crtc_h;
+
+	/* The programming paths use the raw rectangles, so refuse to clip. */
+	if ((state->src.x1 >> 16) != src_x || (state->src.y1 >> 16) != src_y ||
+	    (drm_rect_width(&state->src) >> 16) != src_w ||
+	    (drm_rect_height(&state->src) >> 16) != src_h ||
+	    state->dst.x1 != state->crtc_x || state->dst.y1 != state->crtc_y ||
+	    drm_rect_width(&state->dst) != crtc_w ||
+	    drm_rect_height(&state->dst) != crtc_h) {
+		drm_dbg_kms(plane->dev,
+			    "clipped plane rectangles not supported\n");
+		return -EINVAL;
+	}
+
+	/* One-dimensional sources are invalid (only 0x0 = constant fill) */
+	if ((src_w == 0) != (src_h == 0)) {
+		drm_dbg_kms(plane->dev, "unsupported src_w %d src_h %d\n",
+			    src_w, src_h);
+		return -EINVAL;
+	}
+
+	if (src_w == 0 && src_h == 0)
+		cur_rdma_id = RDMA_INVALID_ID;	/* constant-fill layer, no RDMA */
+	else if (cur_rdma_id == RDMA_INVALID_ID)
+		cur_rdma_id = state->zpos;	/* first commit: bind channel by zpos */
+	cur_state->rdma_id = cur_rdma_id;
+
+	if (cur_rdma_id != RDMA_INVALID_ID) {
+		if (cur_rdma_id >= hwdev->rdma_nums) {
+			drm_dbg_kms(plane->dev, "invalid rdma id %d\n",
+				    cur_rdma_id);
+			return -EINVAL;
+		}
+
+		if (a_crtc->core->cal_layer_fbcmem_size(plane, state)) {
+			drm_dbg_kms(plane->dev,
+				    "plane %d: invalid fbcmem size\n",
+				    state->zpos);
+			return -EINVAL;
+		}
+
+		cur_state->mmu_tbl.size =
+			((PAGE_ALIGN(fb->obj[0]->size) >> PAGE_SHIFT) +
+			 HW_ALIGN_TTB_NUM) * 4;
+		cur_state->mmu_tbl.va =
+			dma_alloc_coherent(a_crtc->dev, cur_state->mmu_tbl.size,
+					   &cur_state->mmu_tbl.pa,
+					   GFP_KERNEL | __GFP_ZERO);
+		if (!cur_state->mmu_tbl.va)
+			return -ENOMEM;
+	}
+
+	/*
+	 * The commit cannot fail, so take the DMA buffer while -ENOMEM is an
+	 * answer.
+	 */
+	ret = spacemit_cmdlist_alloc(a_crtc->dev, &cur_state->cl);
+	if (ret)
+		return ret;
+
+	cur_state->format = spacemit_plane_hw_get_format_id(fb->format->format);
+	if (cur_state->format == SPACEMIT_DPU_INVALID_FORMAT_ID) {
+		drm_dbg_kms(plane->dev, "unsupported format %p4cc\n",
+			    &fb->format->format);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void spacemit_plane_atomic_update(struct drm_plane *plane,
+					 struct drm_atomic_commit *state)
+{
+	int ret = 0;
+	struct spacemit_crtc *a_crtc = to_spacemit_crtc(plane->state->crtc);
+	struct spacemit_plane_state *spacemit_pstate =
+			to_spacemit_plane_state(plane->state);
+	struct spacemit_drm_private *priv = plane->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	u32 rdma_id = spacemit_pstate->rdma_id;
+	struct cmdlist *cl = NULL;
+	int idx;
+
+	if (!drm_dev_enter(plane->dev, &idx))
+		return;
+
+	trace_spacemit_plane_atomic_update(a_crtc->dev_id);
+
+	hwdev->get_cl_rdma_buf(a_crtc);
+
+	hwdev->plane_update_hw_channel(plane);
+
+	/* Solid-fill layers have no RDMA and no page table */
+	if (rdma_id < hwdev->rdma_nums && spacemit_pstate->mmu_tbl.va) {
+		u8 tbu_id = rdma_id * 2;
+
+		ret = spacemit_dmmu_map(plane->state->fb,
+					&spacemit_pstate->mmu_tbl, tbu_id,
+					a_crtc->cl_rdma, plane);
+		if (!ret)
+			cmdlist_regs_packing(plane_to_cl(plane),
+					     CMDLIST_MOD_RDMA, a_crtc->cl_rdma);
+		else
+			drm_err(plane->dev, "failed to map plane: %d\n", ret);
+	}
+
+	cl = &spacemit_pstate->cl;
+	cl->rch_start_cmps_y = CMDLIST_SENTINEL_POISON;
+	cl->cmdlist_ch_y_other = CMDLIST_SENTINEL_POISON;
+	free_cmdlist_regs(a_crtc->cl_rdma);
+
+	drm_dev_exit(idx);
+}
+
+static void spacemit_plane_atomic_disable(struct drm_plane *plane,
+					  struct drm_atomic_commit *state)
+{
+	struct drm_plane_state *old_state =
+			drm_atomic_get_old_plane_state(state, plane);
+	struct spacemit_drm_private *priv = plane->dev->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	int idx;
+
+	if (!drm_dev_enter(plane->dev, &idx))
+		return;
+
+	hwdev->plane_disable_hw_channel(plane, old_state);
+
+	drm_dev_exit(idx);
+}
+
+static void spacemit_plane_reset(struct drm_plane *plane)
+{
+	struct spacemit_plane_state *s;
+
+	if (plane->state) {
+		s = to_spacemit_plane_state(plane->state);
+		__drm_atomic_helper_plane_destroy_state(plane->state);
+		kfree(s);
+		plane->state = NULL;
+	}
+
+	s = kzalloc(sizeof(*s), GFP_KERNEL);
+	if (s) {
+		__drm_atomic_helper_plane_reset(plane, &s->state);
+		s->rdma_id = RDMA_INVALID_ID;
+	}
+}
+
+static struct drm_plane_state *
+spacemit_plane_atomic_duplicate_state(struct drm_plane *plane)
+{
+	struct spacemit_plane_state *s;
+	struct spacemit_plane_state *old_state =
+			to_spacemit_plane_state(plane->state);
+	struct spacemit_crtc *a_crtc = NULL;
+
+	if (plane->crtc) {
+		a_crtc = to_spacemit_crtc(plane->crtc);
+		trace_spacemit_plane_atomic_duplicate_state(a_crtc->dev_id);
+	}
+
+	s = kzalloc(sizeof(*s), GFP_KERNEL);
+	if (!s)
+		return NULL;
+
+	__drm_atomic_helper_plane_duplicate_state(plane, &s->state);
+
+	WARN_ON(s->state.plane != plane);
+
+	s->cl.type = CMDLIST_PLANE;
+
+	s->rdma_id = old_state->rdma_id;
+	s->format = old_state->format;
+	s->fbcmem_size = 0;
+
+	return &s->state;
+}
+
+static void spacemit_plane_atomic_destroy_state(struct drm_plane *plane,
+						struct drm_plane_state *state)
+{
+	struct spacemit_plane_state *spacemit_pstate =
+			to_spacemit_plane_state(state);
+	struct spacemit_crtc *a_crtc = NULL;
+
+	if (state->crtc) {
+		a_crtc = to_spacemit_crtc(state->crtc);
+
+		if (spacemit_pstate->mmu_tbl.va)
+			dma_free_coherent(a_crtc->dev,
+					  spacemit_pstate->mmu_tbl.size,
+					  spacemit_pstate->mmu_tbl.va,
+					  spacemit_pstate->mmu_tbl.pa);
+
+		if (spacemit_pstate->cl.va)
+			dma_free_coherent(a_crtc->dev,
+					  spacemit_pstate->cl.size,
+					  spacemit_pstate->cl.va,
+					  spacemit_pstate->cl.pa);
+		trace_spacemit_plane_atomic_destroy_state(a_crtc->dev_id);
+	}
+	__drm_atomic_helper_plane_destroy_state(state);
+
+	kfree(to_spacemit_plane_state(state));
+}
+
+static void spacemit_plane_create_properties(struct spacemit_plane *p)
+{
+	unsigned int support_modes = BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+		BIT(DRM_MODE_BLEND_PREMULTI) |
+		BIT(DRM_MODE_BLEND_COVERAGE);
+
+	drm_plane_create_alpha_property(&p->plane);
+
+	drm_plane_create_blend_mode_property(&p->plane, support_modes);
+}
+
+static const struct drm_plane_helper_funcs spacemit_plane_helper_funcs = {
+	.atomic_check = spacemit_plane_atomic_check,
+	.atomic_update = spacemit_plane_atomic_update,
+	.atomic_disable = spacemit_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs spacemit_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane	= drm_atomic_helper_disable_plane,
+	.destroy = drm_plane_cleanup,
+	.reset = spacemit_plane_reset,
+	.atomic_duplicate_state = spacemit_plane_atomic_duplicate_state,
+	.atomic_destroy_state = spacemit_plane_atomic_destroy_state,
+};
+
+struct drm_plane *spacemit_plane_init(struct drm_device *drm,
+				      struct spacemit_crtc *a_crtc)
+{
+	struct spacemit_plane *p = NULL;
+	int err, j;
+	u32 *formats;
+	struct spacemit_drm_private *priv = drm->dev_private;
+	struct spacemit_hw_device *hwdev = priv->hwdev;
+	u8 n_formats = hwdev->n_formats;
+	u8 n_fbcmems = hwdev->n_fbcmems;
+	u8 n_rdmas = hwdev->rdma_nums;
+	u32 plane_crtc_mask;
+
+	trace_spacemit_plane_init(a_crtc->dev_id);
+	if (n_fbcmems * 2 != n_rdmas) {
+		drm_err(drm, "mismatched rdma/fbcmem counts: %d vs %d\n",
+			n_rdmas, n_fbcmems);
+		return ERR_PTR(-EINVAL);
+	}
+
+	for (j = 0; j < n_fbcmems; j++) {
+		if (hwdev->fbcmem_sizes[j] < FBCMEM_UNIT) {
+			drm_err(drm, "invalid fbcmem_sizes[%d] = %u\n",
+				j, hwdev->fbcmem_sizes[j]);
+			return ERR_PTR(-EINVAL);
+		}
+	}
+
+	formats = kcalloc(n_formats, sizeof(*formats), GFP_KERNEL);
+	if (!formats)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * The CRTC is created right after this, so its index is the current
+	 * count.
+	 */
+	plane_crtc_mask = 1 << drm->mode_config.num_crtc;
+
+	/* drmm, not devm: an open DRM fd outlives the platform unbind. */
+	p = drmm_kzalloc(drm, sizeof(*p), GFP_KERNEL);
+	if (!p) {
+		kfree(formats);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	for (j = 0; j < n_formats; j++)
+		formats[j] = hwdev->formats[j].format;
+
+	err = drm_universal_plane_init(drm, &p->plane, plane_crtc_mask,
+				       &spacemit_plane_funcs, formats,
+				       n_formats, supported_format_modifiers,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	kfree(formats);
+	if (err) {
+		drm_err(drm, "failed to init primary plane\n");
+		return ERR_PTR(err);
+	}
+
+	drm_plane_helper_add(&p->plane, &spacemit_plane_helper_funcs);
+
+	spacemit_plane_create_properties(p);
+
+	p->hwdev = hwdev;
+
+	return &p->plane;
+}

-- 
2.43.0



  parent reply	other threads:[~2026-07-25  4:51 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-25  4:51 [PATCH RESEND 00/17] drm/spacemit: add SpacemiT K3 display support Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 01/17] dt-bindings: display: spacemit: add K3 Saturn DPU controller Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 02/17] dt-bindings: phy: add SpacemiT K3 Innosilicon DP PHY Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 03/17] dt-bindings: display: spacemit: add K3 Innosilicon DP/eDP controller Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 04/17] dt-bindings: soc: spacemit: allow eDP/DP PHY PLL pixel clocks on K3 APMU Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 05/17] phy: spacemit: add Innosilicon DP TX PHY driver Cody Kang via B4 Relay
2026-07-25  5:02   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 06/17] clk: spacemit: k3: parent eDP/DP pixel clock to the PHY PLL Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 07/17] drm/spacemit: add Saturn DPU register model Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU Cody Kang via B4 Relay
2026-07-25  5:04   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 09/17] drm/spacemit: add Saturn DPU hardware backend Cody Kang via B4 Relay
2026-07-25  5:12   ` sashiko-bot
2026-07-25  4:51 ` Cody Kang via B4 Relay [this message]
2026-07-25  5:04   ` [PATCH RESEND 10/17] drm/spacemit: add Saturn DPU KMS pipeline sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 11/17] drm/spacemit: add Saturn DPU DRM device driver Cody Kang via B4 Relay
2026-07-25  5:05   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 12/17] drm/spacemit: add Innosilicon DP/eDP controller bridge driver Cody Kang via B4 Relay
2026-07-25  5:04   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 13/17] MAINTAINERS: add SpacemiT K3 display driver entry Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 14/17] riscv: dts: spacemit: k3: add display nodes Cody Kang via B4 Relay
2026-07-25  5:08   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 15/17] riscv: dts: spacemit: k3-pico-itx: enable the DisplayPort output Cody Kang via B4 Relay
2026-07-25  5:12   ` sashiko-bot
2026-07-25  4:51 ` [PATCH RESEND 16/17] riscv: dts: spacemit: k3-com260-ifx: " Cody Kang via B4 Relay
2026-07-25  4:51 ` [PATCH RESEND 17/17] riscv: defconfig: spacemit: k3: enable display driver Cody Kang via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260725-k3-display-v1-10-6de34d80e86c@gmail.com \
    --to=devnull+codykang.hk.gmail.com@kernel.org \
    --cc=airlied@gmail.com \
    --cc=alex@ghiti.fr \
    --cc=aou@eecs.berkeley.edu \
    --cc=bmasney@redhat.com \
    --cc=codykang.hk@gmail.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlan@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=heylenay@4d2.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-phy@lists.infradead.org \
    --cc=linux-riscv@lists.infradead.org \
    --cc=maarten.lankhorst@linux.intel.com \
    --cc=mripard@kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=neil.armstrong@linaro.org \
    --cc=p.zabel@pengutronix.de \
    --cc=palmer@dabbelt.com \
    --cc=pjw@kernel.org \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    --cc=simona@ffwll.ch \
    --cc=spacemit@lists.linux.dev \
    --cc=tzimmermann@suse.de \
    --cc=vkoul@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox