mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Thomas Zimmermann <tzimmermann@suse.de>
To: codykang.hk@gmail.com, "David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>,
	"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
	"Maxime Ripard" <mripard@kernel.org>,
	"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>, "Yao Zi" <me@ziyao.cc>,
	"Uwe Kleine-König" <u.kleine-koenig@baylibre.com>,
	"Guodong Xu" <docular.xu@gmail.com>
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
Subject: Re: [PATCH v2 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU
Date: Mon, 14 Sep 2026 16:10:25 +0200	[thread overview]
Message-ID: <0dc12950-68e7-4c30-9138-23539e676e1a@suse.de> (raw)
In-Reply-To: <20260809-k3-display-v2-8-327d7910bf71@gmail.com>

Hi

Am 09.08.26 um 15:14 schrieb Cody Kang via B4 Relay:
> From: Cody Kang <codykang.hk@gmail.com>
>
> Register programming of the Saturn DPU is batched: the driver builds
> per-module register lists in memory and the hardware command-list
> engine replays them at frame boundaries, while scanout buffers are
> mapped through the DPU's private address translation unit (DMMU)
> rather than scanned out by physical address.

I've looked through the code in patch 10. How does a plane interact with 
the hardware? It apparently has its own command list, but there's mostly 
code for the CRTC update.

Best regards
Thomas

>
> Add the shared driver types, the MMIO field accessors, the trace
> events, the command-list builder and the DMMU table code. Some DPU
> configuration registers are write-only, so a read-modify-write would
> corrupt neighbouring fields; byte- and halfword-aligned fields are
> therefore written at their native width instead.
>
> Signed-off-by: Cody Kang <codykang.hk@gmail.com>
>
> ---
> v2:
> - hold the DPU reset lines as one reset_control_bulk_data
>    pipe_resets[5] array in spacemit_drm_private and drop the pointers
>    duplicated into struct spacemit_crtc (Philipp Zabel)
> - drop the INT_VSYNC_UPDATE enum member; it never had an irq_list[]
>    mapping and the register model has no such status bit (Sashiko)
> - drop the unreachable multi-planar arms in spacemit_dmmu_map(); every
>    advertised format is single-planar (Sashiko)
> - drop the unused cmdlist parameter from the crtc_cmdlist op (Sashiko)
> ---
>   drivers/gpu/drm/spacemit/dpu/dpu_saturn.h   |  35 +++
>   drivers/gpu/drm/spacemit/dpu/dpu_trace.h    | 350 ++++++++++++++++++++++++++++
>   drivers/gpu/drm/spacemit/spacemit_cmdlist.c | 304 ++++++++++++++++++++++++
>   drivers/gpu/drm/spacemit/spacemit_cmdlist.h | 146 ++++++++++++
>   drivers/gpu/drm/spacemit/spacemit_crtc.h    | 246 +++++++++++++++++++
>   drivers/gpu/drm/spacemit/spacemit_dmmu.c    |  80 +++++++
>   drivers/gpu/drm/spacemit/spacemit_dmmu.h    |  40 ++++
>   drivers/gpu/drm/spacemit/spacemit_dpu_reg.h | 108 +++++++++
>   drivers/gpu/drm/spacemit/spacemit_drm.h     |  61 +++++
>   9 files changed, 1370 insertions(+)
>
> diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_saturn.h b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.h
> new file mode 100644
> index 0000000000000..22d77d56eca1a
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#ifndef _DPU_SATURN_H_
> +#define _DPU_SATURN_H_
> +
> +#include "../spacemit_crtc.h"
> +
> +struct dpu_format_id {
> +	u32 format;		/* DRM fourcc */
> +	u8 id;
> +	u8 bpp;			/* bits per pixel */
> +};
> +
> +#define SPACEMIT_DPU_INVALID_FORMAT_ID	0xff
> +
> +enum format_features {
> +	FORMAT_RGB  = BIT(0),
> +};
> +
> +struct spacemit_hw_rdma {
> +	u16 formats;
> +};
> +
> +int dpu_parse_clocks(struct device *dev, struct dpu_clk_context *clk_ctx);
> +u8 spacemit_plane_hw_get_format_id(u32 format);
> +u32 saturn_conf_dpuctrl_rdma(struct spacemit_crtc *a_crtc);
> +extern const struct spacemit_hw_device k3_saturn_dpu;
> +void saturn_enable_irq_mask(struct spacemit_crtc *a_crtc, bool enable,
> +			    u32 offset, u32 mask);
> +void spacemit_cfg_rdy_timer_handler(struct timer_list *t);
> +#endif
> diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_trace.h b/drivers/gpu/drm/spacemit/dpu/dpu_trace.h
> new file mode 100644
> index 0000000000000..abfe34a67a4bd
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/dpu/dpu_trace.h
> @@ -0,0 +1,350 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM spacemit_dpu
> +
> +#if !defined(_DPU_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
> +#define _DPU_TRACE_H_
> +
> +#include <linux/stringify.h>
> +#include <linux/types.h>
> +#include <linux/tracepoint.h>
> +
> +#include "dpu_saturn.h"
> +
> +TRACE_EVENT(drm_display_mode_info,
> +	    TP_PROTO(struct drm_display_mode *mode),
> +	TP_ARGS(mode),
> +	TP_STRUCT__entry(
> +		__field(u16, hdisplay)
> +		__field(u16, hbp)
> +		__field(u16, hfp)
> +		__field(u16, hsync)
> +		__field(u16, vdisplay)
> +		__field(u16, vbp)
> +		__field(u16, vfp)
> +		__field(u16, vsync)
> +	),
> +	TP_fast_assign(
> +		__entry->hdisplay = mode->hdisplay;
> +		__entry->hbp = mode->htotal - mode->hsync_end;
> +		__entry->hfp = mode->hsync_start - mode->hdisplay;
> +		__entry->hsync = mode->hsync_end - mode->hsync_start;
> +		__entry->vdisplay = mode->vdisplay;
> +		__entry->vbp = mode->vtotal - mode->vsync_end;
> +		__entry->vfp = mode->vsync_start - mode->vdisplay;
> +		__entry->vsync = mode->vsync_end - mode->vsync_start;
> +	),
> +	TP_printk("drm_display_mode: hdisplay=%d vdisplay=%d hsync=%d vsync=%d hbp=%d hfp=%d vbp=%d vfp=%d",
> +		  __entry->hdisplay, __entry->vdisplay,  __entry->hsync, __entry->vsync,
> +		  __entry->hbp, __entry->hfp,  __entry->vbp, __entry->vfp
> +	)
> +);
> +
> +TRACE_EVENT(dpu_plane_info,
> +	    TP_PROTO(struct drm_plane_state *state, struct drm_framebuffer *fb,
> +		     u32 rdma_id, u32 alpha, u32 rotation),
> +	TP_ARGS(state, fb, rdma_id, alpha, rotation),
> +	TP_STRUCT__entry(
> +			__field(u32, rdma_id)
> +			__field(u32, src_w)
> +			__field(u32, src_h)
> +			__field(u32, src_x)
> +			__field(u32, src_y)
> +			__field(u32, crtc_w)
> +			__field(u32, crtc_h)
> +			__field(u32, crtc_x)
> +			__field(u32, crtc_y)
> +			__field(u32, width)
> +			__field(u32, height)
> +			__field(u32, format)
> +			__field(u32, blend_mode)
> +			__field(u32, alpha)
> +			__field(u32, zpos)
> +			__field(u32, rotation)
> +	),
> +	TP_fast_assign(
> +			__entry->rdma_id = rdma_id;
> +			__entry->src_w = state->src_w >> 16;
> +			__entry->src_h = state->src_h >> 16;
> +			__entry->src_x = state->src_x >> 16;
> +			__entry->src_y = state->src_y >> 16;
> +			__entry->crtc_w = state->crtc_w;
> +			__entry->crtc_h = state->crtc_h;
> +			__entry->crtc_x = state->crtc_x;
> +			__entry->crtc_y = state->crtc_y;
> +			__entry->width = fb->width;
> +			__entry->height = fb->height;
> +			__entry->format = fb->format->format;
> +			__entry->blend_mode = state->pixel_blend_mode;
> +			__entry->alpha = alpha;
> +			__entry->zpos = state->zpos;
> +			__entry->rotation = rotation;
> +	),
> +	TP_printk("rdma_id=%d src: w=%d h=%d x=%d y=%d crtc: w=%d h=%d x=%d y=%d width=%d height=%d fmt=0x%x blend=%d alpha=%d zpos=%d rot=%d",
> +		  __entry->rdma_id, __entry->src_w,
> +		  __entry->src_h, __entry->src_x,
> +		  __entry->src_y, __entry->crtc_w,
> +		  __entry->crtc_h, __entry->crtc_x,
> +		  __entry->crtc_y, __entry->width,
> +		  __entry->height, __entry->format,
> +		  __entry->blend_mode, __entry->alpha,
> +		  __entry->zpos, __entry->rotation)
> +);
> +
> +DECLARE_EVENT_CLASS(dpu_status_template,
> +		    TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status),
> +	TP_STRUCT__entry(
> +		__string(name_str, name)
> +		__field(int, status)
> +	),
> +	TP_fast_assign(
> +		__assign_str(name_str);
> +		__entry->status = status;
> +	),
> +	TP_printk("%s: 0x%x",
> +		  __get_str(name_str), __entry->status)
> +);
> +
> +DEFINE_EVENT(dpu_status_template, dpuctrl,
> +	     TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status)
> +);
> +DEFINE_EVENT(dpu_status_template, spacemit_plane_update_hw_channel,
> +	     TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status)
> +);
> +DEFINE_EVENT(dpu_status_template, saturn_irq_enable,
> +	     TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status)
> +);
> +DEFINE_EVENT(dpu_status_template, saturn_enable_vsync,
> +	     TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status)
> +);
> +DEFINE_EVENT(dpu_status_template, dpu_isr_status,
> +	     TP_PROTO(const char *name, int status),
> +	TP_ARGS(name, status)
> +);
> +
> +DECLARE_EVENT_CLASS(dpu_uint64_t_data_template,
> +		    TP_PROTO(const char *name, u64 data),
> +	TP_ARGS(name, data),
> +	TP_STRUCT__entry(
> +		__string(name_str, name)
> +		__field(u64, data)
> +	),
> +	TP_fast_assign(
> +		__assign_str(name_str);
> +		__entry->data = data;
> +	),
> +	TP_printk("%s: %lld",
> +		  __get_str(name_str), __entry->data)
> +);
> +
> +DEFINE_EVENT(dpu_uint64_t_data_template, u64_data,
> +	     TP_PROTO(const char *name, u64 data),
> +	TP_ARGS(name, data)
> +);
> +
> +TRACE_EVENT(spacemit_dpu_reg_write,
> +	    TP_PROTO(u32 data, u32 base, const char *name),
> +	TP_ARGS(data, base, name),
> +	TP_STRUCT__entry(
> +		__field(u32, data)
> +		__field(u32, base)
> +		__string(name_str, name)
> +	),
> +	TP_fast_assign(
> +		__entry->data = data;
> +		__entry->base = base;
> +		__assign_str(name_str);
> +	),
> +	TP_printk("dpu write 0x%x to addr 0x%x field %s",
> +		  __entry->data, __entry->base, __get_str(name_str))
> +);
> +
> +TRACE_EVENT(spacemit_plane_disable_hw_channel,
> +	    TP_PROTO(u32 layer_id, u32 rdma_id),
> +	TP_ARGS(layer_id, rdma_id),
> +	TP_STRUCT__entry(
> +		__field(u32, layer_id)
> +		__field(u32, rdma_id)
> +	),
> +	TP_fast_assign(
> +		__entry->layer_id = layer_id;
> +		__entry->rdma_id = rdma_id;
> +	),
> +	TP_printk("layer_id:%d rdma_id:%d",
> +		  __entry->layer_id, __entry->rdma_id)
> +);
> +
> +DECLARE_EVENT_CLASS(dpu_func_template,
> +		    TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id),
> +	TP_STRUCT__entry(
> +		__field(u32, dev_id)
> +	),
> +	TP_fast_assign(
> +		__entry->dev_id = dev_id;
> +	),
> +	TP_printk("dev_id=%d", __entry->dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_plane_atomic_check,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_plane_atomic_update,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_plane_atomic_duplicate_state,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_plane_atomic_destroy_state,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_plane_init,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, dpu_enable_clocks,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, dpu_disable_clocks,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, dpu_uninit,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, dpu_isr,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, dpu_run,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_atomic_enable,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_atomic_disable,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_atomic_check,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_atomic_begin,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_atomic_flush,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_enable_vblank,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_disable_vblank,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_run,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_init,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, spacemit_crtc_uninit,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DEFINE_EVENT(dpu_func_template, saturn_ctrl_cfg_ready_timer,
> +	     TP_PROTO(u32 dev_id),
> +	TP_ARGS(dev_id)
> +);
> +DECLARE_EVENT_CLASS(dpu_ctrl_template,
> +		    TP_PROTO(int id, int enable),
> +	TP_ARGS(id, enable),
> +	TP_STRUCT__entry(
> +		__field(u32, id)
> +		__field(u32, enable)
> +	),
> +	TP_fast_assign(
> +		__entry->id = id;
> +		__entry->enable = enable;
> +	),
> +	TP_printk("id=%d enable:%d",
> +		  __entry->id, __entry->enable)
> +);
> +DEFINE_EVENT(dpu_ctrl_template, saturn_ctrl_cfg_ready,
> +	     TP_PROTO(int id, int enable),
> +	TP_ARGS(id, enable)
> +);
> +
> +DEFINE_EVENT(dpu_ctrl_template, saturn_ctrl_sw_start,
> +	     TP_PROTO(int id, int enable),
> +	TP_ARGS(id, enable)
> +);
> +
> +TRACE_EVENT(plat_cmdlist_dump_node,
> +	    TP_PROTO(char *dump),
> +	TP_ARGS(dump),
> +	TP_STRUCT__entry(
> +		__string(dump_str, dump)
> +	),
> +	TP_fast_assign(
> +		__assign_str(dump_str);
> +	),
> +	TP_printk("%s", __get_str(dump_str))
> +);
> +
> +TRACE_EVENT(cmdlist_dump_node,
> +	    TP_PROTO(char *type, int rdma_id, u8 dev_id, int zpos, u16 nod_len),
> +	TP_ARGS(type, rdma_id, dev_id, zpos, nod_len),
> +	TP_STRUCT__entry(
> +		__string(type_str, type)
> +		__field(int, rdma_id)
> +		__field(u8, dev_id)
> +		__field(int, zpos)
> +		__field(u16, nod_len)
> +	),
> +	TP_fast_assign(
> +		__assign_str(type_str);
> +		__entry->rdma_id = rdma_id;
> +		__entry->dev_id = dev_id;
> +		__entry->zpos = zpos;
> +		__entry->nod_len = nod_len;
> +	),
> +	TP_printk("cmdlist[%s]: rdma_id:%d crtc:%d zpos:%d rows:%d",
> +		  __get_str(type_str),
> +		  __entry->rdma_id,
> +		  __entry->dev_id,
> +		  __entry->zpos,
> +		  __entry->nod_len)
> +);
> +#endif /* _DPU_TRACE_H_ */
> +
> +/* This part must be outside protection */
> +#undef TRACE_INCLUDE_FILE
> +#undef TRACE_INCLUDE_PATH
> +#define TRACE_INCLUDE_PATH ../../drivers/gpu/drm/spacemit/dpu/
> +
> +#define TRACE_INCLUDE_FILE dpu_trace
> +#include <trace/define_trace.h>
> diff --git a/drivers/gpu/drm/spacemit/spacemit_cmdlist.c b/drivers/gpu/drm/spacemit/spacemit_cmdlist.c
> new file mode 100644
> index 0000000000000..f4f14eaa12a0f
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_cmdlist.c
> @@ -0,0 +1,304 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/sort.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_plane_helper.h>
> +#include "spacemit_cmdlist.h"
> +#include "spacemit_crtc.h"
> +#include "spacemit_dpu_reg.h"
> +#include "spacemit_drm.h"
> +#include "dpu/dpu_saturn.h"
> +#include "dpu/dpu_trace.h"
> +
> +struct spacemit_plane_state *cl_to_spacemit_pstate(const struct cmdlist *cl)
> +{
> +	return container_of(cl, struct spacemit_plane_state, cl);
> +}
> +
> +struct spacemit_crtc_state *cl_to_spacemit_cstate(const struct cmdlist *cl)
> +{
> +	return container_of(cl, struct spacemit_crtc_state, cl);
> +}
> +
> +struct cmdlist *plane_to_cl(struct drm_plane *plane)
> +{
> +	struct spacemit_plane_state *spacemit_pstate =
> +			to_spacemit_plane_state(plane->state);
> +
> +	return &spacemit_pstate->cl;
> +}
> +
> +struct cmdlist *crtc_to_cl(struct drm_crtc *crtc)
> +{
> +	struct spacemit_crtc_state *spacemit_cstate =
> +			to_spacemit_crtc_state(crtc->state);
> +
> +	return &spacemit_cstate->cl;
> +}
> +
> +static void cmdlist_dump_node(struct cmdlist *cl)
> +{
> +	struct spacemit_drm_private *priv;
> +	struct spacemit_hw_device *hwdev;
> +	struct spacemit_crtc *a_crtc;
> +	char *type;
> +	int zpos = -1, rdma_id = -1;
> +
> +	if (!cl)
> +		return;
> +
> +	if (cl->type == CMDLIST_PLANE) {
> +		struct spacemit_plane_state *spacemit_pstate =
> +				cl_to_spacemit_pstate(cl);
> +
> +		a_crtc = to_spacemit_crtc(spacemit_pstate->state.crtc);
> +		rdma_id = spacemit_pstate->rdma_id;
> +		zpos = (int)(spacemit_pstate->state.zpos);
> +		type = "RDMA";
> +	} else if (cl->type == CMDLIST_CRTC) {
> +		struct spacemit_crtc_state *spacemit_cstate =
> +				cl_to_spacemit_cstate(cl);
> +
> +		a_crtc = to_spacemit_crtc(spacemit_cstate->base.crtc);
> +		type = "CRTC";
> +	} else {
> +		return;
> +	}
> +
> +	trace_cmdlist_dump_node(type, rdma_id, a_crtc->dev_id, zpos,
> +				cl->nod_len);
> +
> +	priv = a_crtc->crtc.dev->dev_private;
> +	hwdev = priv->hwdev;
> +	hwdev->cmdlist_dump_node(cl);
> +}
> +
> +void cmdlist_regs_packing(struct cmdlist *cl, enum cmdlist_mode_type mod,
> +			  struct cmdlist_regs *cl_regs)
> +{
> +	struct spacemit_crtc *a_crtc;
> +	struct spacemit_drm_private *priv;
> +	struct spacemit_hw_device *hwdev;
> +	u32 *reg_base;
> +	int i;
> +	u32 index;
> +
> +	if (!cl_regs)
> +		return;
> +
> +	if (cl->type == CMDLIST_PLANE) {
> +		struct spacemit_plane_state *spacemit_pstate =
> +				cl_to_spacemit_pstate(cl);
> +
> +		a_crtc = to_spacemit_crtc(spacemit_pstate->state.crtc);
> +
> +		drm_dbg(a_crtc->crtc.dev, "rch_id = %d, ch_y = %u\n",
> +			spacemit_pstate->rdma_id,
> +			spacemit_pstate->state.crtc_y);
> +	} else if (cl->type == CMDLIST_CRTC) {
> +		struct spacemit_crtc_state *spacemit_cstate =
> +				cl_to_spacemit_cstate(cl);
> +
> +		a_crtc = to_spacemit_crtc(spacemit_cstate->base.crtc);
> +	} else {
> +		WARN_ONCE(1, "unsupported cmdlist type %d\n", cl->type);
> +		return;
> +	}
> +
> +	priv = a_crtc->crtc.dev->dev_private;
> +	hwdev = priv->hwdev;
> +	reg_base = cl_regs->module;
> +
> +	cl->mode_mask |= mod;
> +
> +	/*
> +	 * Without the atomic_check() buffer the staged register writes vanish.
> +	 */
> +	if (drm_WARN_ON(a_crtc->crtc.dev, !cl->va))
> +		return;
> +
> +	for (i = 0; i < cl_regs->size;) {
> +		u32 value[CMDLIST_ROW_REGS] = {0x0};
> +		u32 offset, strobe = 0;
> +		u8 regs_in_row = 0;
> +
> +		if (cl_regs->flags[i]) {
> +			offset = cl_regs->base + i * sizeof(u32);
> +			for (u8 j = 0; j < CMDLIST_ROW_REGS; j++) {
> +				index = i + j;
> +				if (likely(index < cl_regs->size)) {
> +					if (cl_regs->flags[index]) {
> +						value[j] = reg_base[index];
> +						regs_in_row++;
> +						strobe |= CMDLIST_REG_STROBE(j);
> +					}
> +				} else {
> +					break;
> +				}
> +			}
> +			hwdev->cmdlist_fill_data_row(cl, strobe, offset, value);
> +			/*
> +			 * the row covers the whole window; a shorter stride
> +			 * would revisit covered words as duplicate rows
> +			 */
> +			i += CMDLIST_ROW_REGS;
> +			cl->nod_len++;
> +		} else {
> +			i++;
> +		}
> +	}
> +
> +	drm_dbg(a_crtc->crtc.dev, "row_num = %d\n", cl->nod_len);
> +}
> +
> +void cmdlist_sort_by_group(struct drm_crtc *crtc)
> +{
> +	struct cmdlist *first_cl;
> +	struct cmdlist *last_cl;
> +	struct cmdlist *p;
> +	struct cmdlist *prev;
> +	struct drm_plane *plane;
> +	struct spacemit_crtc_rdma *rdmas =
> +			to_spacemit_crtc_state(crtc->state)->rdmas;
> +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> +
> +	drm_atomic_crtc_for_each_plane(plane, crtc) {
> +		struct spacemit_plane_state *spacemit_pstate =
> +			to_spacemit_plane_state(plane->state);
> +		u32 rdma_id = spacemit_pstate->rdma_id;
> +
> +		first_cl = &spacemit_pstate->cl;
> +		if (!first_cl->va)
> +			continue;
> +
> +		last_cl = first_cl;
> +		last_cl->next = NULL;
> +
> +		rdmas[rdma_id].in_use = true;
> +		if (priv->cmdlist_groups[rdma_id]) {
> +			p = priv->cmdlist_groups[rdma_id];
> +			prev = NULL;
> +			while (p) {
> +				if (cl_to_spacemit_pstate(p)->state.crtc_y <
> +				    spacemit_pstate->state.crtc_y) {
> +					prev = p;
> +					p = p->next;
> +				} else {
> +					break;
> +				}
> +			}
> +			if (!prev) {
> +				priv->cmdlist_groups[rdma_id] = first_cl;
> +				last_cl->next = p;
> +			} else {
> +				prev->next = first_cl;
> +				last_cl->next = p;
> +			}
> +		} else {
> +			priv->cmdlist_groups[rdma_id] = first_cl;
> +		}
> +	}
> +}
> +
> +void cmdlist_atomic_commit(struct drm_crtc *crtc,
> +			   struct drm_crtc_state *old_state)
> +{
> +	int i;
> +	struct cmdlist *cur_cl, *first_cl;
> +	struct spacemit_crtc *a_crtc = NULL;
> +	u32 chy, addrl, addrh;
> +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> +	struct spacemit_hw_device *hwdev = priv->hwdev;
> +	struct spacemit_crtc_rdma *cur_rdmas =
> +			to_spacemit_crtc_state(crtc->state)->rdmas;
> +	struct spacemit_crtc_rdma *old_rdmas =
> +			to_spacemit_crtc_state(old_state)->rdmas;
> +
> +	a_crtc = to_spacemit_crtc(crtc);
> +
> +	for (i = 0; i < hwdev->rdma_nums; i++) {
> +		if (old_rdmas[i].in_use)
> +			hwdev->enable_cmdlist(a_crtc, hwdev, i, false);
> +
> +		if (cur_rdmas[i].in_use) {
> +			drm_dbg(crtc->dev, "cmdlist group = %d\n", i);
> +			cur_cl = priv->cmdlist_groups[i];
> +			first_cl = cur_cl;
> +			while (cur_cl) {
> +				hwdev->cmdlist_fill_conf_row(cur_cl, hwdev,
> +					CMDLIST_CMP_INVALID);
> +				cmdlist_dump_node(cur_cl);
> +				cur_cl = cur_cl->next;
> +			}
> +			chy = cl_to_spacemit_pstate(first_cl)->state.crtc_y;
> +			addrl = (lower_32_bits(priv->cmdlist_groups[i]->pa) &
> +				 CMDLIST_ADDRL_ALIGN_MASK) >> CMDLIST_ADDRL_ALIGN_BITS;
> +
> +			addrh = upper_32_bits(priv->cmdlist_groups[i]->pa);
> +			hwdev->cfg_cmdlist(hwdev, i, chy, addrl, addrh);
> +			hwdev->enable_cmdlist(a_crtc, hwdev, i, true);
> +			priv->cmdlist_groups[i] = NULL;
> +		}
> +	}
> +}
> +
> +void crtc_cmdlist_sort_by_group(struct spacemit_crtc *a_crtc)
> +{
> +	struct cmdlist *first_cl;
> +	struct cmdlist *last_cl;
> +	struct drm_crtc *crtc = &a_crtc->crtc;
> +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> +	struct spacemit_hw_device *hwdev = priv->hwdev;
> +	struct drm_crtc_state *crtc_state = a_crtc->crtc.state;
> +	struct spacemit_crtc_state *spacemit_crtc_state =
> +			to_spacemit_crtc_state(crtc_state);
> +	u8 crtc_id = drm_crtc_index(crtc);
> +
> +	first_cl = &spacemit_crtc_state->cl;
> +	last_cl = first_cl;
> +	last_cl->next = NULL;
> +	priv->cmdlist_groups[hwdev->rdma_nums + crtc_id] = first_cl;
> +}
> +
> +void crtc_cmdlist_atomic_commit(struct spacemit_crtc *a_crtc)
> +{
> +	struct cmdlist *cur_cl;
> +	struct drm_crtc *crtc = &a_crtc->crtc;
> +	struct spacemit_drm_private *priv = crtc->dev->dev_private;
> +	struct spacemit_hw_device *hwdev = priv->hwdev;
> +	u8 crtc_id = drm_crtc_index(crtc);
> +	u8 dev_id = a_crtc->dev_id;
> +
> +	drm_dbg(crtc->dev, "cmdlist group = %d\n",
> +		hwdev->rdma_nums + crtc_id);
> +	cur_cl = priv->cmdlist_groups[hwdev->rdma_nums + crtc_id];
> +	while (cur_cl) {
> +		hwdev->cmdlist_fill_conf_row(cur_cl, hwdev, dev_id);
> +		cmdlist_dump_node(cur_cl);
> +		cur_cl = cur_cl->next;
> +	}
> +	hwdev->crtc_cmdlist(hwdev, priv, crtc_id, dev_id);
> +}
> +
> +/* Called from atomic_check(), where -ENOMEM is still an answer. */
> +int spacemit_cmdlist_alloc(struct device *dev, struct cmdlist *cl)
> +{
> +	if (cl->va)
> +		return 0;
> +
> +	cl->size = PER_CMDLIST_SIZE;
> +	cl->va = dma_alloc_coherent(dev, cl->size, &cl->pa,
> +				    GFP_KERNEL | __GFP_ZERO);
> +	if (!cl->va)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/spacemit/spacemit_cmdlist.h b/drivers/gpu/drm/spacemit/spacemit_cmdlist.h
> new file mode 100644
> index 0000000000000..d5acdbeb3b0e7
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_cmdlist.h
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#ifndef _SATURN_FW_CMDLIST_H_
> +#define _SATURN_FW_CMDLIST_H_
> +
> +#include <linux/stddef.h>
> +#include <linux/types.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +
> +struct spacemit_crtc;
> +struct spacemit_plane_state;
> +
> +#define PER_CMDLIST_SIZE             4096
> +#define CMDLIST_ROW_REGS	(3)
> +#define CMDLIST_CMP_INVALID	(0xff)
> +/* poison for the per-cmdlist compose sentinels, never written to hw */
> +#define CMDLIST_SENTINEL_POISON	0xDEADBEEF
> +
> +/**
> + * enum cmdlist_type - what surface owns a cmdlist instance
> + * @CMDLIST_PLANE: Per-plane RDMA register batches.
> + * @CMDLIST_CRTC: Compose-side batches submitted on every flush.
> + */
> +enum cmdlist_type {
> +	CMDLIST_PLANE = 1,
> +	CMDLIST_CRTC,
> +};
> +
> +/**
> + * struct cmdlist - A DMA-backed batch of register writes replayed at CFG_RDY
> + * @nod_len: Number of register-write rows currently filled.
> + * @size: Capacity of the DMA buffer in bytes.
> + * @mode_mask: cmdlist_mode_type sources; gates which rows are pulled.
> + * @va: Kernel virtual address of the DMA buffer.
> + * @pa: DMA address programmed into the cmdlist base register.
> + * @next: Cmdlists in submission order; the CRTC-level one comes last.
> + * @type: Originating surface; see &enum cmdlist_type.
> + * @rch_start_cmps_y: Compositor-Y start sentinel.
> + * @cmdlist_ch_y_other: Companion sentinel to @rch_start_cmps_y.
> + */
> +struct cmdlist {
> +	u16 nod_len;
> +	u32 size;
> +	u32 mode_mask;
> +	void *va;
> +	dma_addr_t pa;
> +	struct cmdlist *next;
> +	u8 type;
> +	u32 rch_start_cmps_y;
> +	u32 cmdlist_ch_y_other;
> +};
> +
> +enum cmdlist_mode_type {
> +	CMDLIST_MOD_RDMA = BIT(0),
> +	CMDLIST_MOD_COMP = BIT(3),
> +	CMDLIST_MOD_DMMU = BIT(6),
> +};
> +
> +#define CMDLIST_REG_STROBE(index)	(0xf << ((index) * 4))
> +
> +#define CMDLIST_ADDRL_ALIGN_BITS               (4) /* From cmdlist_reg_0[] in CMDLIST_REG */
> +#define CMDLIST_ADDRL_ALIGN_MASK \
> +	((u32)(~(BIT(CMDLIST_ADDRL_ALIGN_BITS) - 1)))
> +/* NULL on failure is safe: every consumer falls back to direct MMIO. */
> +#define alloc_cmdlist_regs(module_name) \
> +({ \
> +	struct cmdlist_regs *cl = kzalloc(sizeof(*cl), GFP_KERNEL); \
> +	if (cl) { \
> +		cl->size = module_name##__NWORDS; \
> +		cl->module = kzalloc(4 * module_name##__NWORDS, GFP_KERNEL); \
> +		cl->flags = kzalloc(module_name##__NWORDS, GFP_KERNEL); \
> +		if (!cl->module || !cl->flags) { \
> +			kfree(cl->module); \
> +			kfree(cl->flags); \
> +			kfree(cl); \
> +			cl = NULL; \
> +		} \
> +	} \
> +	cl; \
> +})
> +
> +#define free_cmdlist_regs(cl_p) \
> +do { \
> +	struct cmdlist_regs *cl = (cl_p); \
> +	if (cl) { \
> +		kfree(cl->module); \
> +		kfree(cl->flags); \
> +		kfree(cl); \
> +	} \
> +} while (0)
> +
> +/*
> + * First touch seeds the word from a live MMIO read: only register blocks that
> + * read back what was written may be routed through cmdlist.
> + */
> +#define cmdlist_write_reg(hwdev, module_name, module_base, field, data, cl_regs) \
> +do { \
> +	struct cmdlist_regs *cl_p = (cl_regs); \
> +	u32 *__words = cl_p->module; \
> +	const unsigned int __w = dpu_field_word(module_name, field); \
> +	if (cl_p->flags[__w] == 0) { \
> +		__words[__w] = readl((hwdev)->base + (module_base) + 4 * __w); \
> +		cl_p->base = (module_base); \
> +		cl_p->flags[__w] = 1; \
> +	} \
> +	__words[__w] &= ~dpu_field_mask(module_name, field); \
> +	__words[__w] |= FIELD_PREP(dpu_field_mask(module_name, field), \
> +				   (u32)(data)); \
> +} while (0)
> +
> +/* Raw whole-word variant (the value32[] call sites); no seeding needed. */
> +#define cmdlist_write_word(module_base, word, data, cl_regs) \
> +do { \
> +	struct cmdlist_regs *cl_p = (cl_regs); \
> +	u32 *__words = cl_p->module; \
> +	cl_p->base = (module_base); \
> +	cl_p->flags[(word)] = 1; \
> +	__words[(word)] = (u32)(data); \
> +} while (0)
> +
> +struct cmdlist_regs {
> +	u32 base;
> +	u16 size;
> +	u32 *module;
> +	u8 *flags;
> +};
> +
> +void cmdlist_regs_packing(struct cmdlist *cl, enum cmdlist_mode_type mod,
> +			  struct cmdlist_regs *cl_regs);
> +void cmdlist_sort_by_group(struct drm_crtc *crtc);
> +void cmdlist_atomic_commit(struct drm_crtc *crtc,
> +			   struct drm_crtc_state *old_state);
> +struct spacemit_plane_state *cl_to_spacemit_pstate(const struct cmdlist *cl);
> +void crtc_cmdlist_sort_by_group(struct spacemit_crtc *a_crtc);
> +void crtc_cmdlist_atomic_commit(struct spacemit_crtc *a_crtc);
> +struct spacemit_crtc_state *cl_to_spacemit_cstate(const struct cmdlist *cl);
> +int spacemit_cmdlist_alloc(struct device *dev, struct cmdlist *cl);
> +struct cmdlist *plane_to_cl(struct drm_plane *plane);
> +struct cmdlist *crtc_to_cl(struct drm_crtc *crtc);
> +
> +#endif /* _SATURN_FW_CMDLIST_H_ */
> diff --git a/drivers/gpu/drm/spacemit/spacemit_crtc.h b/drivers/gpu/drm/spacemit/spacemit_crtc.h
> new file mode 100644
> index 0000000000000..7c03102414d00
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_crtc.h
> @@ -0,0 +1,246 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#ifndef _SPACEMIT_CRTC_H_
> +#define _SPACEMIT_CRTC_H_
> +
> +#include <linux/delay.h>
> +#include <linux/string.h>
> +#include <linux/platform_device.h>
> +#include <linux/device.h>
> +#include <linux/kernel.h>
> +#include <linux/bug.h>
> +#include <linux/regmap.h>
> +#include <video/videomode.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/drm_atomic_uapi.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_vblank.h>
> +#include "spacemit_dpu_ids.h"
> +#include "spacemit_cmdlist.h"
> +#include "spacemit_drm.h"
> +#include <linux/spinlock.h>
> +
> +#define RDMA_INVALID_ID			(~0)
> +
> +#define DPU_MCLK_DEFAULT	307200000
> +#define DPU_AXICLK_DEFAULT	409000000
> +
> +enum spacemit_dpu_irq {
> +	INT_UNDERRUN,
> +	INT_CFG_RDY,
> +	INT_VSYNC,
> +	INT_EOF,
> +	INT_REST,
> +};
> +
> +struct spacemit_crtc_fbcmem {
> +	u32 start;
> +	u32 size;
> +	bool map;
> +};
> +
> +struct spacemit_crtc_rdma {
> +	struct spacemit_crtc_fbcmem fbcmem;
> +	bool in_use;
> +	u32 use_cnt;
> +};
> +
> +struct dpu_mmu_tbl {
> +	u32 size;
> +	void *va;
> +	dma_addr_t pa;
> +};
> +
> +
> +/**
> + * struct spacemit_crtc - Per-instance state for a Saturn DPU CRTC
> + * @dev: Underlying platform device for this DPU instance.
> + * @crtc: Embedded DRM CRTC; reachable via to_spacemit_crtc().
> + * @core: Vtable of DPU pipeline operations, populated at probe.
> + * @mmu_tbl: DPU-local page table mapping planes' buffers into the TBUs.
> + * @dev_id: Vendor-numbered DPU instance ID (0 or 1 on K3).
> + * @irq_online: Online-compose IRQ, kept for synchronize_irq().
> + * @cfg_rdy_timer: Watchdog for a CFG_RDY that never arrives.
> + * @is_1st_f: True until the first frame of a new modeset is transferred.
> + * @first_modeset: True until the first .atomic_enable after probe; that first
> + *		   pass resets the DPU to stop the U-Boot pipeline.
> + * @clk_ctx: Cached clock handles, enabled by dpu_enable_clocks().
> + * @aclk: Requested DPU AXI clock rate (Hz).
> + * @out_format: Output pixel format register cache.
> + * @cl_rdma: Cmdlist register slot for RDMA programming.
> + * @flip_done: Set once the DPU has committed the most recent page flip.
> + * @clocks_on: True while clocks run; MMIO on a gated DPU stalls the bus.
> + * @ur_reported: True once an underrun has been logged since the last CFG_RDY.
> + * @ur_mask_armed: True while clr_int_sts() may still mask the underrun IRQ.
> + */
> +struct spacemit_crtc {
> +	struct device *dev;
> +	struct drm_crtc crtc;
> +	const struct dpu_core_ops *core;
> +	struct dpu_mmu_tbl mmu_tbl;
> +	int dev_id;
> +	int irq_online;
> +	struct timer_list cfg_rdy_timer;
> +
> +	bool is_1st_f;
> +	bool first_modeset;
> +	struct dpu_clk_context clk_ctx;
> +	u32 aclk;
> +	u32 out_format;
> +
> +	struct cmdlist_regs *cl_rdma;
> +
> +	bool flip_done;
> +
> +	bool clocks_on;
> +
> +	bool ur_reported;
> +	bool ur_mask_armed;
> +};
> +
> +static inline struct spacemit_crtc *to_spacemit_crtc(struct drm_crtc *crtc)
> +{
> +	return crtc ? container_of(crtc, struct spacemit_crtc, crtc) : NULL;
> +}
> +
> +struct spacemit_hw_device;
> +struct dpu_core_ops {
> +	int (*init)(struct spacemit_crtc *a_crtc);
> +	void (*uninit)(struct spacemit_crtc *a_crtc);
> +	void (*run)(struct drm_crtc *crtc,
> +		    struct drm_crtc_state *old_state);
> +	void (*disable_vsync)(struct spacemit_crtc *a_crtc);
> +	void (*enable_vsync)(struct spacemit_crtc *a_crtc);
> +	u32 (*online_isr)(struct spacemit_crtc *a_crtc);
> +	int (*enable_clk)(struct spacemit_crtc *a_crtc);
> +	int (*disable_clk)(struct spacemit_crtc *a_crtc);
> +	int (*cal_layer_fbcmem_size)(struct drm_plane *plane,
> +				     struct drm_plane_state *state);
> +	int (*adjust_rdma_fbcmem)(struct spacemit_hw_device *hwdev,
> +				  struct spacemit_crtc_rdma *rdmas);
> +};
> +
> +extern const struct dpu_core_ops dpu_saturn_ops;
> +
> +int spacemit_crtc_run(struct drm_crtc *crtc,
> +		      struct drm_crtc_state *old_state);
> +
> +struct spacemit_plane {
> +	struct drm_plane plane;
> +	struct spacemit_hw_device *hwdev;
> +};
> +
> +/**
> + * struct spacemit_plane_state - Subclassed drm_plane_state for the Saturn DPU
> + * @state: Embedded drm_plane_state; reachable via to_spacemit_plane_state().
> + * @rdma_id: RDMA channel feeding this plane, derived from its zpos.
> + * @format: DPU-internal pixel-format token translated from the fourcc.
> + * @fbcmem_size: Bytes of FBC line buffer reserved for this plane.
> + * @mmu_tbl: Per-plane page table populated by spacemit_dmmu_map().
> + * @cl: Plane-level cmdlist filled during atomic_update.
> + */
> +struct spacemit_plane_state {
> +	struct drm_plane_state state;
> +	u32 rdma_id;
> +	u8 format;
> +	u32 fbcmem_size;
> +	struct dpu_mmu_tbl mmu_tbl;
> +	struct cmdlist cl;
> +};
> +
> +struct spacemit_plane *to_spacemit_plane(struct drm_plane *plane);
> +
> +static inline struct
> +spacemit_plane_state *
> +to_spacemit_plane_state(const struct drm_plane_state *state)
> +{
> +	return container_of(state, struct spacemit_plane_state, state);
> +}
> +
> +/**
> + * struct spacemit_crtc_state - Subclassed drm_crtc_state for the Saturn DPU
> + * @base: Embedded drm_crtc_state; reachable via to_spacemit_crtc_state().
> + * @rdmas: Per-RDMA-channel descriptors, hwdev->rdma_nums entries.
> + * @cl: CRTC-level cmdlist used during atomic_flush.
> + */
> +struct spacemit_crtc_state {
> +	struct drm_crtc_state base;
> +	struct spacemit_crtc_rdma *rdmas;
> +	struct cmdlist cl;
> +};
> +
> +#define to_spacemit_crtc_state(x) \
> +	container_of(x, struct spacemit_crtc_state, base)
> +
> +struct drm_plane *spacemit_plane_init(struct drm_device *drm,
> +				      struct spacemit_crtc *a_crtc);
> +
> +struct spacemit_plane_state;
> +struct spacemit_crtc;
> +struct tbu_instance;
> +struct dpu_mmu_tbl;
> +enum spacemit_dpu_irq;
> +
> +/*
> + * Per-revision Saturn DPU description and operation vtable. One static instance
> + * per DT compatible, selected at probe; reached through spacemit_crtc::core.
> + */
> +struct spacemit_hw_device {
> +	void __iomem *base;
> +	struct regmap *regmap;
> +	u8 plane_nums;
> +	u8 rdma_nums;
> +	u8 crtc_nums;
> +	const struct spacemit_hw_rdma *rdmas;
> +	u8 n_formats;
> +	const struct dpu_format_id *formats;
> +	u8 n_fbcmems;
> +	const u32 *fbcmem_sizes;
> +	void (*enable_vsync)(struct spacemit_crtc *a_crtc,
> +			     struct spacemit_hw_device *hwdev, bool enable);
> +	void (*enable_cfg_irq)(struct spacemit_crtc *a_crtc,
> +			       struct spacemit_hw_device *hwdev, bool enable);
> +	void (*cfg_ready)(struct spacemit_crtc *a_crtc,
> +			  struct spacemit_hw_device *hwdev);
> +	void (*sw_start)(struct spacemit_crtc *a_crtc,
> +			 struct spacemit_hw_device *hwdev);
> +	void (*dpu_init)(struct spacemit_crtc *a_crtc);
> +	void (*irq_enable)(struct spacemit_crtc *a_crtc, bool enable);
> +	void (*plane_update_hw_channel)(struct drm_plane *plane);
> +	void (*plane_disable_hw_channel)(struct drm_plane *plane,
> +					 struct drm_plane_state *old_state);
> +	void (*conf_dpuctrl)(struct drm_crtc *crtc,
> +			     struct drm_crtc_state *old_state);
> +	u32 (*get_cfg_rdy)(struct spacemit_crtc *a_crtc,
> +			   struct spacemit_hw_device *hwdev);
> +	u32 (*get_irq_bit)(enum spacemit_dpu_irq irq_id, int dev_id);
> +	u32 (*get_int_sts)(struct spacemit_hw_device *hwdev, int dev_id);
> +	void (*clr_int_sts)(struct spacemit_crtc *a_crtc, u32 data, int dev_id);
> +	void (*enable_cmdlist)(struct spacemit_crtc *a_crtc,
> +			       struct spacemit_hw_device *hwdev, int id,
> +			       bool enable);
> +	void (*cfg_cmdlist)(struct spacemit_hw_device *hwdev, int id, u32 chy,
> +			    u32 addrl, u32 addrh);
> +	void (*rdma_dmmu)(struct spacemit_hw_device *hwdev, u8 tbu_id,
> +			  struct tbu_instance *tbu, struct drm_framebuffer *fb,
> +			  u32 val, struct cmdlist_regs *cl_rdma,
> +			  struct drm_plane *plane);
> +	int (*get_cl_rdma_buf)(struct spacemit_crtc *a_crtc);
> +	void (*cmdlist_fill_data_row)(struct cmdlist *cl, u32 strobe,
> +				      u32 offset, u32 value[]);
> +	void (*cmdlist_fill_conf_row)(struct cmdlist *cl,
> +				      struct spacemit_hw_device *hwdev,
> +				      u8 dev_id);
> +	void (*crtc_cmdlist)(struct spacemit_hw_device *hwdev,
> +			     struct spacemit_drm_private *priv, u8 crtc_id,
> +			     u8 dev_id);
> +	void (*cmdlist_dump_node)(struct cmdlist *cl);
> +};
> +#endif
> diff --git a/drivers/gpu/drm/spacemit/spacemit_dmmu.c b/drivers/gpu/drm/spacemit/spacemit_dmmu.c
> new file mode 100644
> index 0000000000000..ff4b3378a087c
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_dmmu.c
> @@ -0,0 +1,80 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2023-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#include <linux/stddef.h>
> +#include <linux/gfp.h>
> +#include <linux/dma-mapping.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_shmem_helper.h>
> +
> +#include "spacemit_cmdlist.h"
> +#include "spacemit_dmmu.h"
> +#include "spacemit_drm.h"
> +#include "spacemit_crtc.h"
> +#include "spacemit_dpu_reg.h"
> +
> +static inline void spacemit_dmmu_fill_pgtable(struct dpu_mmu_tbl *tbl,
> +					      struct sg_table *sgt)
> +{
> +	struct sg_dma_page_iter dma_iter;
> +	u32 max_entries = tbl->size / 4;
> +	u32 *ttbr = tbl->va;
> +	u32 i = 0, n = 0, temp = 0;
> +
> +	for_each_sgtable_dma_page(sgt, &dma_iter, 0) {
> +		/*
> +		 * atomic_check sized the table off this same fb; catch a drift
> +		 */
> +		if (WARN_ON_ONCE(n + HW_ALIGN_TTB_NUM >= max_entries))
> +			break;
> +		ttbr[n++] = (sg_page_iter_dma_address(&dma_iter) >> PAGE_SHIFT) & 0x3FFFFFF;
> +	}
> +
> +	if (WARN_ON_ONCE(!n))
> +		return;
> +
> +	/*
> +	 * The silicon over-fetches past the last entry; pad it to avoid a DDR
> +	 * fault.
> +	 */
> +	temp = ttbr[n - 1];
> +	for (i = 0; i < HW_ALIGN_TTB_NUM; i++)
> +		ttbr[n++] = temp;
> +}
> +
> +int spacemit_dmmu_map(struct drm_framebuffer *fb, struct dpu_mmu_tbl *mmu_tbl,
> +		      u8 tbu_id, struct cmdlist_regs *cl_rdma,
> +		 struct drm_plane *plane)
> +{
> +	struct spacemit_drm_private *priv = fb->dev->dev_private;
> +	struct spacemit_hw_device *hwdev = priv->hwdev;
> +	struct sg_table *sgt = NULL;
> +	struct tbu_instance tbu = { };
> +	u32 val;
> +
> +	sgt = drm_gem_shmem_get_pages_sgt(to_drm_gem_shmem_obj(fb->obj[0]));
> +	if (IS_ERR(sgt))
> +		return PTR_ERR(sgt);
> +
> +	/* Every advertised format is single-planar */
> +	tbu.ttb_pa[0] = mmu_tbl->pa;
> +	tbu.tbu_va[0] = TBU_BASE_VA(tbu_id);
> +	tbu.ttb_size[0] = roundup(fb->obj[0]->size, PAGE_SIZE) >> PAGE_SHIFT;
> +
> +	spacemit_dmmu_fill_pgtable(mmu_tbl, sgt);
> +
> +	/*
> +	 * enable = 1, fbc_mode = 0 (linear only)
> +	 * single plane in bits [3:2]
> +	 */
> +	val = 0x1;
> +	/* tbu_burst_limit_en = 1, tlb_fetch_active_en = 1 */
> +	val |= BIT(4) | BIT(5);
> +	val = val | (DPU_QOS_URGENT << 8);
> +	hwdev->rdma_dmmu(hwdev, tbu_id, &tbu, fb, val, cl_rdma, plane);
> +
> +	return 0;
> +}
> diff --git a/drivers/gpu/drm/spacemit/spacemit_dmmu.h b/drivers/gpu/drm/spacemit/spacemit_dmmu.h
> new file mode 100644
> index 0000000000000..5aaf1ad77f9f1
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_dmmu.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#ifndef _SPACEMIT_DMMU_H_
> +#define _SPACEMIT_DMMU_H_
> +
> +#include <linux/types.h>
> +
> +#include <drm/drm_file.h>
> +#include "spacemit_cmdlist.h"
> +
> +#define DPU_QOS_URGENT	4
> +#define DPU_QOS_NORMAL	3
> +#define DPU_QOS_LOW	2
> +/* Worst-case TLB alignment over-fetch at the maximum 4096 src_x. */
> +#define HW_ALIGN_TTB_NUM	60
> +
> +#define RD_OUTS_NUM             16
> +#define RDMA_TIMELIMIT  0xFFFF
> +
> +#define BASE_VA         0x10000000ULL
> +#define VA_STEP_PER_TBU 0x40000000ULL
> +
> +#define TBU_BASE_VA(tbu_id) ((u64)BASE_VA + (u64)VA_STEP_PER_TBU * (tbu_id))
> +
> +struct tbu_instance {
> +	u64 ttb_pa[3];
> +	u64 tbu_va[3];
> +	u32 ttb_size[3];
> +};
> +
> +struct dpu_mmu_tbl;
> +int spacemit_dmmu_map(struct drm_framebuffer *fb, struct dpu_mmu_tbl *mmu_tbl,
> +		      u8 tbu_id, struct cmdlist_regs *cl_rdma,
> +			struct drm_plane *plane);
> +
> +#endif /* _SPACEMIT_DMMU_H_ */
> diff --git a/drivers/gpu/drm/spacemit/spacemit_dpu_reg.h b/drivers/gpu/drm/spacemit/spacemit_dpu_reg.h
> new file mode 100644
> index 0000000000000..a57ed34d38e2f
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_dpu_reg.h
> @@ -0,0 +1,108 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + * Saturn DPU register accessors.
> + */
> +
> +#ifndef _SPACEMIT_DPU_REG_H_
> +#define _SPACEMIT_DPU_REG_H_
> +
> +#include <linux/bitfield.h>
> +#include <linux/io.h>
> +
> +#include "dpu/dpu_trace.h"
> +#include "dpu/saturn_regs/saturn_fields.h"
> +
> +#define dpu_field_word(module_name, field)	(module_name##__##field##_WORD)
> +#define dpu_field_mask(module_name, field)	(module_name##__##field##_MASK)
> +
> +/* MMIO field read: returns the field value shifted down to bit 0. */
> +#define dpu_read_reg(hwdev, module_name, module_base, field)		\
> +	FIELD_GET(dpu_field_mask(module_name, field),			\
> +		  readl((hwdev)->base + (module_base) +			\
> +			4 * dpu_field_word(module_name, field)))
> +
> +/* MMIO raw 32-bit register read/write by word index within the block. */
> +#define dpu_read_word(hwdev, module_base, word)				\
> +	readl((hwdev)->base + (module_base) + 4 * (word))
> +
> +#define dpu_write_reg_word(hwdev, module_base, word, data)		\
> +do {									\
> +	trace_spacemit_dpu_reg_write((u32)(data), (module_base), NULL);	\
> +	writel((u32)(data),						\
> +	       (hwdev)->base + (module_base) + 4 * (word));		\
> +} while (0)
> +
> +/*
> + * Not every DPU config word reads back what was written, so a byte- or
> + * halfword-aligned field is stored at its own width rather than read-modified.
> + */
> +#define __dpu_mmio_field_write(__addr, __mask, data)			\
> +do {									\
> +	if ((__mask) == 0xffU)						\
> +		writeb((u8)((data) & 0xffU), __addr);			\
> +	else if ((__mask) == 0xff00U)					\
> +		writeb((u8)((data) & 0xffU), (__addr) + 1);		\
> +	else if ((__mask) == 0xff0000U)					\
> +		writeb((u8)((data) & 0xffU), (__addr) + 2);		\
> +	else if ((__mask) == 0xff000000U)				\
> +		writeb((u8)((data) & 0xffU), (__addr) + 3);		\
> +	else if ((__mask) == 0xffffU)					\
> +		writew((u16)((data) & 0xffffU), __addr);		\
> +	else if ((__mask) == 0xffff0000U)				\
> +		writew((u16)((data) & 0xffffU), (__addr) + 2);		\
> +	else if ((__mask) == 0xffffffffU)				\
> +		writel((u32)(data), __addr);				\
> +	else								\
> +		writel((readl(__addr) & ~(__mask)) |			\
> +		       FIELD_PREP(__mask, (u32)(data)), __addr);	\
> +} while (0)
> +
> +#define dpu_write_reg(hwdev, module_name, module_base, field, data)	\
> +do {									\
> +	void __iomem *__waddr = (hwdev)->base + (module_base) +	\
> +				4 * dpu_field_word(module_name, field);	\
> +									\
> +	trace_spacemit_dpu_reg_write((u32)(data), (module_base), #field); \
> +	__dpu_mmio_field_write(__waddr,					\
> +			       dpu_field_mask(module_name, field), data); \
> +} while (0)
> +
> +/* Field write into a register array element; the index is a runtime value. */
> +#define dpu_write_reg_arr(hwdev, module_name, module_base, arr, i, field, data) \
> +do {									\
> +	void __iomem *__waddr = (hwdev)->base + (module_base) +	\
> +		4 * module_name##__##arr##__##field##_WORD(i);		\
> +									\
> +	trace_spacemit_dpu_reg_write((u32)(data), (module_base), #field); \
> +	__dpu_mmio_field_write(__waddr,					\
> +			       module_name##__##arr##__##field##_MASK,	\
> +			       data);					\
> +} while (0)
> +
> +/*
> + * Unified CPU/cmdlist field write; a NULL cl_regs selects the direct MMIO path.
> + */
> +#define _dpu_write_reg(hwdev, module_name, module_base, field, data, cl_regs, ...) \
> +do {									\
> +	if ((cl_regs) != NULL)						\
> +		cmdlist_write_reg(hwdev, module_name, module_base,	\
> +				  field, data, cl_regs);		\
> +	else								\
> +		dpu_write_reg(hwdev, module_name, module_base, field,	\
> +			      data);					\
> +} while (0)
> +
> +#define dpu_write(...) _dpu_write_reg(__VA_ARGS__, NULL)
> +
> +/* Unified CPU/cmdlist raw-word write (the value32[] call sites). */
> +#define dpu_write_word(hwdev, module_base, word, data, cl_regs)	\
> +do {									\
> +	if ((cl_regs) != NULL)						\
> +		cmdlist_write_word(module_base, word, data, cl_regs);	\
> +	else								\
> +		dpu_write_reg_word(hwdev, module_base, word, data);	\
> +} while (0)
> +
> +#endif
> diff --git a/drivers/gpu/drm/spacemit/spacemit_drm.h b/drivers/gpu/drm/spacemit/spacemit_drm.h
> new file mode 100644
> index 0000000000000..425f6221a1645
> --- /dev/null
> +++ b/drivers/gpu/drm/spacemit/spacemit_drm.h
> @@ -0,0 +1,61 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
> + *
> + */
> +
> +#ifndef _SPACEMIT_DRM_H_
> +#define _SPACEMIT_DRM_H_
> +
> +#include <drm/drm_print.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_probe_helper.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/clk.h>
> +#include <linux/reset.h>
> +#include "spacemit_dmmu.h"
> +
> +/* The DPU's clock inputs, as named by the binding. */
> +struct dpu_clk_context {
> +	struct clk *pxclk;
> +	struct clk *mclk;
> +	struct clk *escclk;
> +	struct clk *aclk;
> +	struct clk *dscclk;
> +};
> +
> +/**
> + * struct spacemit_drm_private - Top-level driver state for the SpacemiT DRM
> + * card
> + * @ddev: DRM device, created in the component master bind callback; bind-scope,
> + *	  see spacemit_kms in spacemit_crtc.c.
> + * @dev: The DPU platform device; the DPU is the drm subsystem root.
> + * @a_crtc: Per-DPU CRTC state.
> + * @hwdev: Per-revision Saturn DPU description and vtable.
> + * @cmdlist_groups: Pool of per-pipeline cmdlist groups.
> + * @clk_ctx: The DPU's clocks, acquired once at probe.
> + * @pipe_resets: The DPU reset lines, in assert order; bulk-deassert
> + *		 releases them in reverse.
> + * @irq_online: Online-compose IRQ; requested disabled, enabled at bind.
> + */
> +struct spacemit_drm_private {
> +	struct drm_device *ddev;
> +	struct device *dev;
> +	struct spacemit_crtc *a_crtc;
> +	struct spacemit_hw_device *hwdev;
> +	struct cmdlist **cmdlist_groups;
> +
> +	struct dpu_clk_context clk_ctx;
> +	struct reset_control_bulk_data pipe_resets[5];
> +	int irq_online;
> +};
> +
> +extern struct drm_driver spacemit_drm_drv;
> +extern const struct component_master_ops spacemit_drm_master_ops;
> +
> +int spacemit_drm_mode_config_init(struct drm_device *drm);
> +int spacemit_drm_of_component_probe(struct device *dev);
> +
> +#endif /* _SPACEMIT_DRM_H_ */
>

-- 
--
Thomas Zimmermann
Graphics Driver Developer
SUSE Software Solutions Germany GmbH
Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
GF: Jochen Jaser, Andrew McDonald, (HRB 36809, AG Nürnberg)



  reply	other threads:[~2026-09-14 14:11 UTC|newest]

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

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=0dc12950-68e7-4c30-9138-23539e676e1a@suse.de \
    --to=tzimmermann@suse.de \
    --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=docular.xu@gmail.com \
    --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=me@ziyao.cc \
    --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=u.kleine-koenig@baylibre.com \
    --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

all inboxes | Powered by JetHome®