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 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU
Date: Sat, 25 Jul 2026 00:51:17 -0400 [thread overview]
Message-ID: <20260725-k3-display-v1-8-6de34d80e86c@gmail.com> (raw)
In-Reply-To: <20260725-k3-display-v1-0-6de34d80e86c@gmail.com>
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.
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>
---
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 | 259 ++++++++++++++++++++
drivers/gpu/drm/spacemit/spacemit_dmmu.c | 103 ++++++++
drivers/gpu/drm/spacemit/spacemit_dmmu.h | 40 ++++
drivers/gpu/drm/spacemit/spacemit_dpu_reg.h | 108 +++++++++
drivers/gpu/drm/spacemit/spacemit_drm.h | 68 ++++++
9 files changed, 1413 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 000000000000..22d77d56eca1
--- /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 000000000000..abfe34a67a4b
--- /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 000000000000..787d38e9e135
--- /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(cur_cl, 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 000000000000..d5acdbeb3b0e
--- /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 000000000000..27754866bc9a
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/spacemit_crtc.h
@@ -0,0 +1,259 @@
+/* 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,
+ INT_VSYNC_UPDATE,
+};
+
+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.
+ * @mclk_reset: DPU MCLK reset line.
+ * @lcd_reset: LCD-side reset line shared with the bridge.
+ * @esc_reset: Escape-clock reset line.
+ * @aclk_reset: AXI clock reset line.
+ * @dsc_reset: DSC reset line.
+ * @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 reset_control *mclk_reset;
+ struct reset_control *lcd_reset;
+ struct reset_control *esc_reset;
+ struct reset_control *aclk_reset;
+ struct reset_control *dsc_reset;
+
+ 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 cmdlist *cl,
+ 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 000000000000..2ae30dd1a4f2
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/spacemit_dmmu.c
@@ -0,0 +1,103 @@
+// 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;
+ const struct drm_format_info *format = NULL;
+ struct sg_table *sgt = NULL;
+ u32 total_size, offset1, offset2;
+ struct tbu_instance tbu = { };
+ u8 plane_num;
+ u32 val;
+
+ format = fb->format;
+ sgt = drm_gem_shmem_get_pages_sgt(to_drm_gem_shmem_obj(fb->obj[0]));
+ if (IS_ERR(sgt))
+ return PTR_ERR(sgt);
+
+ plane_num = format->num_planes;
+
+ total_size = roundup(fb->obj[0]->size, PAGE_SIZE);
+
+ offset1 = plane_num > 1 ? fb->offsets[1] : total_size;
+ offset2 = plane_num > 2 ? fb->offsets[2] : total_size;
+
+ switch (plane_num) {
+ case 3:
+ tbu.ttb_pa[2] = mmu_tbl->pa + (offset2 >> PAGE_SHIFT) * 4;
+ tbu.tbu_va[2] = TBU_BASE_VA(tbu_id) + offset2;
+ tbu.ttb_size[2] = PAGE_ALIGN(total_size - rounddown(offset2, PAGE_SIZE))
+ >> PAGE_SHIFT;
+ fallthrough;
+ case 2:
+ tbu.ttb_pa[1] = mmu_tbl->pa + (offset1 >> PAGE_SHIFT) * 4;
+ tbu.tbu_va[1] = TBU_BASE_VA(tbu_id) + offset1;
+ tbu.ttb_size[1] = PAGE_ALIGN(offset2 - rounddown(offset1, PAGE_SIZE)) >> PAGE_SHIFT;
+ fallthrough;
+ case 1:
+ tbu.ttb_pa[0] = mmu_tbl->pa;
+ tbu.tbu_va[0] = TBU_BASE_VA(tbu_id);
+ tbu.ttb_size[0] = PAGE_ALIGN(offset1) >> PAGE_SHIFT;
+ fallthrough;
+ default:
+ break;
+ }
+ spacemit_dmmu_fill_pgtable(mmu_tbl, sgt);
+
+ /* enable = 1, fbc_mode = 0 (linear only), plane count in bits [3:2] */
+ val = 0x1 | ((plane_num - 1) << 2);
+ /* 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 000000000000..5aaf1ad77f9f
--- /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 000000000000..a57ed34d38e2
--- /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 000000000000..3f3308920bae
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/spacemit_drm.h
@@ -0,0 +1,68 @@
+/* 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.
+ * @mclk_reset: DPU MCLK reset line.
+ * @lcd_reset: LCD-side reset line shared with the bridge.
+ * @esc_reset: Escape-clock reset line.
+ * @aclk_reset: AXI clock reset line.
+ * @dsc_reset: DSC reset line.
+ * @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 *mclk_reset;
+ struct reset_control *lcd_reset;
+ struct reset_control *esc_reset;
+ struct reset_control *aclk_reset;
+ struct reset_control *dsc_reset;
+ 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_ */
--
2.43.0
next prev 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 ` Cody Kang via B4 Relay [this message]
2026-07-25 5:04 ` [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU 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 ` [PATCH RESEND 10/17] drm/spacemit: add Saturn DPU KMS pipeline Cody Kang via B4 Relay
2026-07-25 5:04 ` 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-8-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