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 09/17] drm/spacemit: add Saturn DPU hardware backend
Date: Sat, 25 Jul 2026 00:51:18 -0400 [thread overview]
Message-ID: <20260725-k3-display-v1-9-6de34d80e86c@gmail.com> (raw)
In-Reply-To: <20260725-k3-display-v1-0-6de34d80e86c@gmail.com>
From: Cody Kang <codykang.hk@gmail.com>
Add the register-level programming of one Saturn instance: clock and
reset bring-up, scene control and timing generator setup, RDMA channel
and composer layer programming, FBC line-buffer budgeting, interrupt
dispatch and the cfg-ready/vsync handshake that paces commits. The
backend is exposed as an ops table and hardware descriptor to be
aggregated by the KMS layer in an upcoming patch.
Signed-off-by: Cody Kang <codykang.hk@gmail.com>
---
drivers/gpu/drm/spacemit/dpu/dpu_saturn.c | 483 +++++++++++
drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c | 896 +++++++++++++++++++++
drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c | 152 ++++
drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.h | 21 +
drivers/gpu/drm/spacemit/dpu/saturn_regs/ops_hee.h | 52 ++
5 files changed, 1604 insertions(+)
diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c
new file mode 100644
index 000000000000..8d5f13bc5373
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/dpu/dpu_saturn.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pm_qos.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/types.h>
+#include <linux/math64.h>
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_gem.h>
+#include "dpu_saturn.h"
+#include "saturn_fbcmem.h"
+#include "../spacemit_cmdlist.h"
+#include "../spacemit_dmmu.h"
+#include "../spacemit_dpu_reg.h"
+#include "../spacemit_drm.h"
+#include <video/display_timing.h>
+#include "../spacemit_dpu_ids.h"
+#include "saturn_regs/ops_hee.h"
+
+#define CREATE_TRACE_POINTS
+#include "dpu_trace.h"
+
+#define TOTAL_RDMA_MEMSIZE (68 * 1024)
+static const struct dpu_format_id primary_fmts[] = {
+ { DRM_FORMAT_XRGB8888, 8, 32 }, /* RDMA_FMT_XRGB_8888 */
+ { DRM_FORMAT_ARGB8888, 4, 32 }, /* RDMA_FMT_ARGB_8888 */
+ { DRM_FORMAT_XBGR8888, 9, 32 }, /* RDMA_FMT_XBGR_8888 */
+ { DRM_FORMAT_ABGR8888, 5, 32 }, /* RDMA_FMT_ABGR_8888 */
+ { DRM_FORMAT_RGBX8888, 10, 32 }, /* RDMA_FMT_RGBX_8888 */
+ { DRM_FORMAT_RGBA8888, 6, 32 }, /* RDMA_FMT_RGBA_8888 */
+ { DRM_FORMAT_BGRX8888, 11, 32 }, /* RDMA_FMT_BGRX_8888 */
+ { DRM_FORMAT_BGRA8888, 7, 32 }, /* RDMA_FMT_BGRA_8888 */
+ { DRM_FORMAT_ABGR2101010, 1, 32 }, /* RDMA_FMT_ABGR_2101010 */
+ { DRM_FORMAT_RGB888, 12, 24 }, /* RDMA_FMT_RGB_888 */
+ { DRM_FORMAT_BGR888, 13, 24 }, /* RDMA_FMT_BGR_888 */
+ { DRM_FORMAT_RGB565, 22, 16 }, /* RDMA_FMT_RGB_565 */
+ { DRM_FORMAT_BGR565, 23, 16 }, /* RDMA_FMT_BGR_565 */
+};
+
+static const struct spacemit_hw_rdma saturn_hee_rdmas[] = {
+ {FORMAT_RGB},
+ {FORMAT_RGB},
+ {FORMAT_RGB},
+ {FORMAT_RGB},
+};
+
+static const u32 saturn_hee_fbcmem_sizes[] = {
+ 89600, /* 87.5k */
+ 15360, /* 16k */
+};
+
+const struct spacemit_hw_device k3_saturn_dpu = {
+ .plane_nums = 1,
+ .crtc_nums = 2,
+ .rdma_nums = ARRAY_SIZE(saturn_hee_rdmas),
+ .rdmas = saturn_hee_rdmas,
+ .n_formats = ARRAY_SIZE(primary_fmts),
+ .formats = primary_fmts,
+ .n_fbcmems = ARRAY_SIZE(saturn_hee_fbcmem_sizes),
+ .fbcmem_sizes = saturn_hee_fbcmem_sizes,
+ .enable_vsync = saturn_hee_enable_vsync,
+ .enable_cfg_irq = saturn_hee_enable_cfg_irq,
+ .cfg_ready = saturn_hee_cfg_ready,
+ .sw_start = saturn_hee_sw_start,
+ .irq_enable = saturn_hee_irq_enable,
+ .dpu_init = saturn_hee_dpu_init,
+ .plane_update_hw_channel = saturn_hee_plane_update_hw_channel,
+ .plane_disable_hw_channel = saturn_hee_plane_disable_hw_channel,
+ .conf_dpuctrl = saturn_hee_conf_dpuctrl,
+ .get_cfg_rdy = saturn_hee_get_cfg_rdy,
+ .get_int_sts = saturn_hee_get_int_sts,
+ .get_irq_bit = saturn_hee_get_irq_bit,
+ .clr_int_sts = saturn_hee_clr_int_sts,
+ .enable_cmdlist = saturn_hee_enable_cmdlist,
+ .cfg_cmdlist = saturn_hee_cfg_cmdlist,
+ .rdma_dmmu = saturn_hee_rdma_dmmu,
+ .get_cl_rdma_buf = saturn_hee_get_cl_rdma_buf,
+ .cmdlist_fill_data_row = saturn_hee_cmdlist_fill_data_row,
+ .cmdlist_fill_conf_row = saturn_hee_cmdlist_fill_conf_row,
+ .crtc_cmdlist = saturn_hee_crtc_cmdlist,
+ .cmdlist_dump_node = saturn_hee_cmdlist_dump_node,
+};
+
+void saturn_enable_irq_mask(struct spacemit_crtc *a_crtc, bool enable,
+ u32 offset, u32 mask)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ if (!hwdev->base) {
+ drm_err(a_crtc->crtc.dev, "hwdev->base is NULL\n");
+ return;
+ }
+
+ if (!a_crtc->clocks_on)
+ return;
+
+ regmap_update_bits(hwdev->regmap, offset, mask, enable ? mask : 0);
+}
+
+int dpu_parse_clocks(struct device *dev, struct dpu_clk_context *clk_ctx)
+{
+ clk_ctx->pxclk = devm_clk_get(dev, "pxclk");
+ if (IS_ERR(clk_ctx->pxclk))
+ return dev_err_probe(dev, PTR_ERR(clk_ctx->pxclk),
+ "failed to get pxclk\n");
+
+ clk_ctx->mclk = devm_clk_get(dev, "mclk");
+ if (IS_ERR(clk_ctx->mclk))
+ return dev_err_probe(dev, PTR_ERR(clk_ctx->mclk),
+ "failed to get mclk\n");
+
+ clk_ctx->escclk = devm_clk_get(dev, "escclk");
+ if (IS_ERR(clk_ctx->escclk))
+ return dev_err_probe(dev, PTR_ERR(clk_ctx->escclk),
+ "failed to get escclk\n");
+
+ clk_ctx->aclk = devm_clk_get(dev, "aclk");
+ if (IS_ERR(clk_ctx->aclk))
+ return dev_err_probe(dev, PTR_ERR(clk_ctx->aclk),
+ "failed to get aclk\n");
+
+ clk_ctx->dscclk = devm_clk_get(dev, "dscclk");
+ if (IS_ERR(clk_ctx->dscclk))
+ return dev_err_probe(dev, PTR_ERR(clk_ctx->dscclk),
+ "failed to get dscclk\n");
+
+ return 0;
+}
+
+static int dpu_enable_clocks(struct spacemit_crtc *a_crtc)
+{
+ struct dpu_clk_context *clk_ctx = &a_crtc->clk_ctx;
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_display_mode *mode = &crtc->mode;
+ u64 clk_val;
+ u64 set_clk_val;
+ int ret;
+
+ ret = clk_prepare_enable(clk_ctx->pxclk);
+ if (ret)
+ goto err;
+ set_clk_val = mode->clock * 1000;
+ if (set_clk_val) {
+ set_clk_val = clk_round_rate(clk_ctx->pxclk, set_clk_val);
+ clk_val = clk_get_rate(clk_ctx->pxclk);
+ if (clk_val != set_clk_val) {
+ clk_set_rate(clk_ctx->pxclk, set_clk_val);
+ drm_dbg(crtc->dev, "pxclk=%lld\n", clk_val);
+ }
+ }
+
+ ret = clk_prepare_enable(clk_ctx->mclk);
+ if (ret)
+ goto err_pxclk;
+ clk_val = clk_get_rate(clk_ctx->mclk);
+ if (clk_val != DPU_MCLK_DEFAULT) {
+ clk_val = clk_round_rate(clk_ctx->mclk, DPU_MCLK_DEFAULT);
+ clk_set_rate(clk_ctx->mclk, clk_val);
+ drm_dbg(crtc->dev, "mclk=%lld\n", clk_val);
+ }
+
+ /* The escape clock only has to run for the "esc" reset to propagate. */
+ ret = clk_prepare_enable(clk_ctx->escclk);
+ if (ret)
+ goto err_mclk;
+
+ ret = clk_prepare_enable(clk_ctx->aclk);
+ if (ret)
+ goto err_escclk;
+ clk_val = clk_get_rate(clk_ctx->aclk);
+ if (clk_val != a_crtc->aclk) {
+ set_clk_val = clk_round_rate(clk_ctx->aclk, a_crtc->aclk);
+ if (set_clk_val != clk_val) {
+ clk_set_rate(clk_ctx->aclk, set_clk_val);
+ drm_dbg(crtc->dev, "aclk %llu -> %llu\n", clk_val,
+ set_clk_val);
+ }
+ }
+
+ ret = clk_prepare_enable(clk_ctx->dscclk);
+ if (ret)
+ goto err_aclk;
+
+ trace_dpu_enable_clocks(a_crtc->dev_id);
+ return 0;
+
+err_aclk:
+ clk_disable_unprepare(clk_ctx->aclk);
+err_escclk:
+ clk_disable_unprepare(clk_ctx->escclk);
+err_mclk:
+ clk_disable_unprepare(clk_ctx->mclk);
+err_pxclk:
+ clk_disable_unprepare(clk_ctx->pxclk);
+err:
+ drm_err(crtc->dev, "failed to enable DPU clocks: %d\n", ret);
+ return ret;
+}
+
+static int dpu_disable_clocks(struct spacemit_crtc *a_crtc)
+{
+ struct dpu_clk_context *clk_ctx = &a_crtc->clk_ctx;
+
+ trace_dpu_disable_clocks(a_crtc->dev_id);
+
+ clk_disable_unprepare(clk_ctx->pxclk);
+ clk_disable_unprepare(clk_ctx->mclk);
+ clk_disable_unprepare(clk_ctx->escclk);
+
+ clk_disable_unprepare(clk_ctx->aclk);
+ clk_disable_unprepare(clk_ctx->dscclk);
+
+ return 0;
+}
+
+u8 spacemit_plane_hw_get_format_id(u32 format)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(primary_fmts); i++) {
+ if (primary_fmts[i].format == format)
+ return primary_fmts[i].id;
+ }
+
+ return SPACEMIT_DPU_INVALID_FORMAT_ID;
+}
+
+static void saturn_enable_vsync(struct spacemit_crtc *a_crtc, bool enable)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ trace_saturn_enable_vsync("vsync", enable);
+ hwdev->enable_vsync(a_crtc, hwdev, enable);
+}
+
+void spacemit_cfg_rdy_timer_handler(struct timer_list *t)
+{
+ struct spacemit_crtc *a_crtc = timer_container_of(a_crtc, t,
+ cfg_rdy_timer);
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct drm_pending_vblank_event *event;
+ unsigned long flags;
+
+ drm_err_ratelimited(drm, "CFG_RDY handshake timeout\n");
+ trace_saturn_ctrl_cfg_ready_timer(a_crtc->dev_id);
+
+ a_crtc->flip_done = true;
+
+ spin_lock_irqsave(&drm->event_lock, flags);
+ event = crtc->state->event;
+ if (event) {
+ crtc->state->event = NULL;
+ drm_crtc_send_vblank_event(crtc, event);
+ }
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+
+ drm_crtc_vblank_put(crtc);
+}
+
+static void saturn_ctrl_cfg_ready(struct spacemit_crtc *a_crtc, bool enable)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ trace_saturn_ctrl_cfg_ready(a_crtc->dev_id, enable);
+ mod_timer(&a_crtc->cfg_rdy_timer, jiffies + msecs_to_jiffies(3000));
+ hwdev->cfg_ready(a_crtc, hwdev);
+}
+
+static void saturn_ctrl_sw_start(struct spacemit_crtc *a_crtc, bool enable)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ trace_saturn_ctrl_sw_start(a_crtc->dev_id, enable);
+
+ hwdev->sw_start(a_crtc, hwdev);
+}
+
+static int dpu_init(struct spacemit_crtc *a_crtc)
+{
+ unsigned int timeout = 1000;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ if (!a_crtc->clocks_on)
+ return 0;
+
+ while (timeout) {
+ if (hwdev->get_cfg_rdy(a_crtc, hwdev) == 0)
+ break;
+ usleep_range(100, 200);
+ timeout--;
+ }
+ if (timeout == 0)
+ drm_err(a_crtc->crtc.dev, "wait for cfg ready timed out\n");
+
+ hwdev->dpu_init(a_crtc);
+
+ return 0;
+}
+
+static void dpu_uninit(struct spacemit_crtc *a_crtc)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ if (!a_crtc->clocks_on)
+ return;
+
+ trace_dpu_uninit(a_crtc->dev_id);
+ hwdev->irq_enable(a_crtc, false);
+}
+
+static inline void dpu_isr_vblank(struct spacemit_crtc *a_crtc, bool *flip)
+{
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_device *drm = crtc->dev;
+ struct drm_pending_vblank_event *event;
+
+ drm_crtc_handle_vblank(crtc);
+
+ if (*flip)
+ return;
+ *flip = true;
+
+ /*
+ * event_lock serialises this against the commit arming
+ * crtc->state->event.
+ */
+ spin_lock(&drm->event_lock);
+ event = crtc->state->event;
+ if (event) {
+ /*
+ * Consume it exactly once, even against a concurrent
+ * commit_hw_done().
+ */
+ crtc->state->event = NULL;
+ drm_crtc_send_vblank_event(crtc, event);
+ }
+ spin_unlock(&drm->event_lock);
+
+ drm_crtc_vblank_put(crtc);
+}
+
+u32 saturn_conf_dpuctrl_rdma(struct spacemit_crtc *a_crtc)
+{
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_plane *plane;
+ u32 rdma_en = 0;
+
+ drm_atomic_crtc_for_each_plane(plane, crtc) {
+ u32 rdma_id = to_spacemit_plane_state(plane->state)->rdma_id;
+
+ if (rdma_id != RDMA_INVALID_ID)
+ rdma_en |= (1 << rdma_id);
+ }
+
+ trace_dpuctrl("rdma_en", rdma_en);
+
+ return rdma_en;
+}
+
+static u32 dpu_online_isr(struct spacemit_crtc *a_crtc)
+{
+ u32 irq_raw, irq_bit, irq_ur_bit;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+ int dev_id = a_crtc->dev_id;
+
+ trace_dpu_isr(a_crtc->dev_id);
+
+ irq_raw = hwdev->get_int_sts(hwdev, dev_id);
+ trace_dpu_isr_status("ONLINE", irq_raw);
+ irq_bit = hwdev->get_irq_bit(INT_UNDERRUN, dev_id);
+ irq_ur_bit = irq_raw & irq_bit;
+ if (irq_ur_bit && !a_crtc->ur_reported) {
+ hwdev->clr_int_sts(a_crtc, irq_ur_bit, dev_id);
+ trace_dpu_isr_status("underrun", irq_ur_bit);
+ drm_err_ratelimited(a_crtc->crtc.dev, "underrun\n");
+ a_crtc->ur_reported = true;
+ }
+ irq_bit = hwdev->get_irq_bit(INT_CFG_RDY, dev_id);
+ if (irq_raw & irq_bit) {
+ hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
+ trace_dpu_isr_status("cfg_rdy_clr", irq_raw & irq_bit);
+ /*
+ * A fired watchdog already sent the event and put this flip's
+ * vblank reference; re-arming the vsync path would put twice.
+ */
+ if (timer_delete(&a_crtc->cfg_rdy_timer))
+ a_crtc->flip_done = false;
+ a_crtc->ur_reported = false;
+ if (hwdev->enable_cfg_irq)
+ hwdev->enable_cfg_irq(a_crtc, hwdev, false);
+ }
+ irq_bit = hwdev->get_irq_bit(INT_VSYNC, dev_id);
+ if (irq_raw & irq_bit) {
+ hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
+ trace_dpu_isr_status("vsync", irq_raw & irq_bit);
+ dpu_isr_vblank(a_crtc, &a_crtc->flip_done);
+ }
+ irq_bit = hwdev->get_irq_bit(INT_REST, dev_id) | irq_ur_bit;
+ if (irq_raw & irq_bit)
+ hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
+
+ irq_bit = hwdev->get_irq_bit(INT_VSYNC_UPDATE, dev_id);
+ if (irq_raw & irq_bit)
+ hwdev->clr_int_sts(a_crtc, irq_bit, dev_id);
+
+ return 0;
+}
+
+static void dpu_run(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ trace_dpu_run(a_crtc->dev_id);
+
+ if (hwdev->conf_dpuctrl)
+ hwdev->conf_dpuctrl(crtc, old_state);
+
+ /* flush all config register writes before CFG_RDY is raised */
+ mb();
+
+ drm_crtc_vblank_get(crtc);
+ if (hwdev->enable_cfg_irq)
+ hwdev->enable_cfg_irq(a_crtc, hwdev, true);
+
+ saturn_ctrl_cfg_ready(a_crtc, true);
+
+ if (unlikely(a_crtc->is_1st_f)) {
+ drm_dbg(a_crtc->crtc.dev, "DPU start\n");
+ a_crtc->is_1st_f = false;
+ saturn_ctrl_sw_start(a_crtc, true);
+ }
+ dma_rmb();
+}
+
+static void dpu_enable_vsync(struct spacemit_crtc *a_crtc)
+{
+ saturn_enable_vsync(a_crtc, true);
+}
+
+static void dpu_disable_vsync(struct spacemit_crtc *a_crtc)
+{
+ saturn_enable_vsync(a_crtc, false);
+}
+
+const struct dpu_core_ops dpu_saturn_ops = {
+ .init = dpu_init,
+ .uninit = dpu_uninit,
+ .run = dpu_run,
+ .online_isr = dpu_online_isr,
+ .enable_clk = dpu_enable_clocks,
+ .disable_clk = dpu_disable_clocks,
+ .enable_vsync = dpu_enable_vsync,
+ .disable_vsync = dpu_disable_vsync,
+ .cal_layer_fbcmem_size = saturn_cal_layer_fbcmem_size,
+ .adjust_rdma_fbcmem = saturn_adjust_rdma_fbcmem,
+};
diff --git a/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c b/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c
new file mode 100644
index 000000000000..c23e4ca36d26
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/dpu/dpu_saturn_hee.c
@@ -0,0 +1,896 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/mfd/syscon.h>
+#include <linux/pm_qos.h>
+#include <linux/regmap.h>
+#include <drm/drm_print.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include "dpu_saturn.h"
+#include "saturn_fbcmem.h"
+#include "../spacemit_cmdlist.h"
+#include "../spacemit_dmmu.h"
+#include "../spacemit_dpu_reg.h"
+#include <video/display_timing.h>
+#include "../spacemit_dpu_ids.h"
+#include "saturn_regs/ops_hee.h"
+#include "saturn_regs/reg_map_hee.h"
+
+#include "dpu_trace.h"
+
+#define SATURN_WRITE_TBU_REGS(hwdev, base, reg_id, cl_tbu) \
+do {\
+ dpu_write(hwdev, MMU_TBU_REG, base, tbu_base_addr##reg_id##_low, \
+ tbu->ttb_pa[reg_id] & 0xFFFFFFFF, cl_tbu); \
+ dpu_write(hwdev, MMU_TBU_REG, base, tbu_base_addr##reg_id##_high, \
+ (tbu->ttb_pa[reg_id] >> 32) & 0xFFF, cl_tbu); \
+ dpu_write(hwdev, MMU_TBU_REG, base, tbu_va##reg_id, \
+ (tbu->tbu_va[reg_id] >> 12), cl_tbu); \
+ dpu_write(hwdev, MMU_TBU_REG, base, \
+ tbu_size##reg_id, tbu->ttb_size[reg_id], cl_tbu); \
+ dpu_write(hwdev, MMU_TBU_REG, base, \
+ vsync_update_en, 1, cl_tbu); \
+} while (0)
+
+#define SATURN_WRITE_RDMA_ADDR_REG(hwdev, reg_id, rdma_id, addr, cl_rdma) \
+do { \
+ if (hwdev) { \
+ dpu_write(hwdev, RDMA_PATH_X_REG, RDMA_BASE_ADDR[rdma_id], \
+ base_addr##reg_id##_low_ly0, (addr) & 0xFFFFFFFF, \
+ cl_rdma); \
+ dpu_write(hwdev, RDMA_PATH_X_REG, RDMA_BASE_ADDR[rdma_id], \
+ base_addr##reg_id##_high_ly0, ((addr) >> 32) & 0xFFF, \
+ cl_rdma); \
+ } \
+} while (0)
+
+static const u32 RDMA_BASE_ADDR[] = {
+ RDMA0_BASE_ADDR,
+ RDMA1_BASE_ADDR,
+ RDMA2_BASE_ADDR,
+ RDMA3_BASE_ADDR,
+};
+
+static const u32 CMP_BASE_ADDR[] = {
+ CMP0_BASE_ADDR,
+ CMP1_BASE_ADDR,
+ CMP2_BASE_ADDR,
+};
+
+static const u32 TMG_BASE_ADDR[] = {
+ 0,
+ TMG0_BASE_ADDR,
+ 0,
+};
+
+static const u32 MMU_TBU_BASE_ADDR_ARRAY[] = {
+ TBU0_ADDR, TBU1_ADDR, TBU2_ADDR, TBU3_ADDR,
+ TBU4_ADDR, TBU5_ADDR, TBU6_ADDR, TBU7_ADDR,
+};
+
+void saturn_hee_enable_vsync(struct spacemit_crtc *a_crtc, struct spacemit_hw_device *hwdev,
+ bool enable)
+{
+ u32 offset = DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_MSK;
+ u32 mask = saturn_hee_get_irq_bit(INT_VSYNC, a_crtc->dev_id);
+
+ saturn_enable_irq_mask(a_crtc, enable, offset, mask);
+}
+
+void saturn_hee_enable_cfg_irq(struct spacemit_crtc *a_crtc, struct spacemit_hw_device *hwdev,
+ bool enable)
+{
+ u32 offset = DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_MSK;
+ u32 mask = saturn_hee_get_irq_bit(INT_CFG_RDY, a_crtc->dev_id);
+
+ saturn_enable_irq_mask(a_crtc, enable, offset, mask);
+}
+
+void saturn_hee_cfg_ready(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev)
+{
+ u32 base = DPU_SCENE_CTL_ADDR(a_crtc->dev_id);
+
+ saturn_enable_irq_mask(a_crtc, true,
+ DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_MSK,
+ saturn_hee_get_irq_bit(INT_UNDERRUN, a_crtc->dev_id));
+ a_crtc->ur_mask_armed = true;
+ dpu_write(hwdev, DPU_CTL_REG, base, both_cfg_rdy, 1);
+}
+
+void saturn_hee_sw_start(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev)
+{
+ u32 base;
+
+ base = DPU_SCENE_CTL_ADDR(a_crtc->dev_id);
+ dpu_write_reg(hwdev, DPU_CTL_REG, base, sw_start, 1);
+}
+
+static void saturn_init_tmg(struct spacemit_crtc *a_crtc)
+{
+ u32 base = 0;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_display_mode *mode = &crtc->mode;
+ u16 vfp, vbp, vsync, hfp, hbp, hsync;
+ void __iomem *tmg_addr;
+ u32 value;
+
+ /* dev_id names the composer type, not the DPU instance: use TMG0. */
+ tmg_addr = hwdev->base + TMG0_BASE_ADDR;
+
+ hsync = mode->hsync_end - mode->hsync_start;
+ hbp = mode->htotal - mode->hsync_end;
+ hfp = mode->hsync_start - mode->hdisplay;
+ vsync = mode->vsync_end - mode->vsync_start;
+ vbp = mode->vtotal - mode->vsync_end;
+ vfp = mode->vsync_start - mode->vdisplay;
+ trace_drm_display_mode_info(mode);
+
+ base = TMG_BASE_ADDR[a_crtc->dev_id];
+ if (base) {
+ dpu_write(hwdev, TMG_REG, base, disp_ready_man_en, 0);
+ dpu_write(hwdev, TMG_REG, base, background_r, 0);
+ dpu_write(hwdev, TMG_REG, base, background_g, 0x0);
+ dpu_write(hwdev, TMG_REG, base, background_b, 0xff);
+ dpu_write(hwdev, TMG_REG, base, eof_1st_ln_dly_num, vfp - 7);
+ dpu_write(hwdev, TMG_REG, base, eof_2nd_ln_dly_num, vfp - 6);
+ dpu_write(hwdev, TMG_REG, base, split_en, 0);
+ dpu_write(hwdev, TMG_REG, base, cmd_screen, 0);
+ dpu_write(hwdev, TMG_REG, base, cmd_wait_en, 0);
+ dpu_write(hwdev, TMG_REG, base, cmd_wait_te, 0);
+ dpu_write(hwdev, TMG_REG, base, sof_pre_ln_num, 0);
+ dpu_write(hwdev, TMG_REG, base, hfp, hfp);
+ dpu_write(hwdev, TMG_REG, base, hbp, hbp);
+ dpu_write(hwdev, TMG_REG, base, vfp, vfp);
+ dpu_write(hwdev, TMG_REG, base, vbp, vbp);
+ dpu_write(hwdev, TMG_REG, base, hsync_width, hsync);
+ dpu_write(hwdev, TMG_REG, base, vsync_width, vsync);
+ dpu_write(hwdev, TMG_REG, base, hsp, 1);
+ dpu_write(hwdev, TMG_REG, base, vsp, 1);
+ dpu_write(hwdev, TMG_REG, base, h_active, mode->hdisplay);
+ dpu_write(hwdev, TMG_REG, base, v_active, mode->vdisplay);
+ dpu_write(hwdev, TMG_REG, base, fm_timing_en, 1);
+ dpu_write(hwdev, TMG_REG, base, user, a_crtc->out_format);
+ }
+
+ value = readl(tmg_addr + 0x14);
+ value &= 0xFFFF0000;
+ value |= vfp;
+ writel(value, tmg_addr + 0x14);
+
+ /* align vsync with hsync */
+ value = readl(tmg_addr + 0x3c);
+ value |= 0x01;
+ writel(value, tmg_addr + 0x3c);
+}
+
+static void saturn_init_regs(struct spacemit_crtc *a_crtc)
+{
+ u32 base = 0;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ base = CMP_BASE_ADDR[a_crtc->dev_id];
+ dpu_write(hwdev, CMPS_X_REG, base, bg_color_b, 0x0);
+ dpu_write(hwdev, CMPS_X_REG, base, bg_color_r, 0x0);
+ dpu_write(hwdev, CMPS_X_REG, base, bg_color_g, 0x0);
+ dpu_write(hwdev, CMPS_X_REG, base, bg_color_a, 0xFF);
+ dpu_write(hwdev, CMPS_X_REG, base, module_enable, 1);
+ dpu_write(hwdev, CMPS_X_REG, base, layer00_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer01_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer02_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer03_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer04_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer05_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer06_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer07_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer08_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer09_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer10_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer11_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer12_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer13_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer14_en, 0);
+ dpu_write(hwdev, CMPS_X_REG, base, layer15_en, 0);
+
+ base = DPU_TOP_BASE_ADDR;
+
+ base = EE_ADDR;
+ dpu_write(hwdev, EE_REG, base, m_benable, 0);
+
+ saturn_init_tmg(a_crtc);
+}
+
+static void saturn_setup_dma_top(struct spacemit_crtc *a_crtc)
+{
+ u32 base = DMA_TOP_BASE_ADDR;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ dpu_write(hwdev, DMA_TOP_REG, base, image_rr_ratio, 0x10);
+ dpu_write(hwdev, DMA_TOP_REG, base, pixel_num_th, 4);
+
+ dpu_write(hwdev, DMA_TOP_REG, base, rdma_timeout_limit, 0xFFFE);
+ dpu_write(hwdev, DMA_TOP_REG, base, wdma_timeout_limit, 0xFFFF);
+}
+
+static void saturn_setup_mmu_top(struct spacemit_crtc *a_crtc)
+{
+ unsigned int rd_outs_num = 0;
+ u32 base = MMU_TOP_BASE_ADDR;
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ rd_outs_num = RD_OUTS_NUM / 2;
+
+ dpu_write(hwdev, MMU_TOP_REG, base, rdma_timelimit, RDMA_TIMELIMIT);
+
+ dpu_write(hwdev, MMU_TOP_REG, base, sram0_tlb_axi_port_sel, 0);
+ dpu_write(hwdev, MMU_TOP_REG, base, sram1_tlb_axi_port_sel, 0);
+ dpu_write(hwdev, MMU_TOP_REG, base, dmac0_rd_outs_num, rd_outs_num);
+ dpu_write(hwdev, MMU_TOP_REG, base, dmac1_rd_outs_num, rd_outs_num);
+}
+
+void saturn_hee_irq_enable(struct spacemit_crtc *a_crtc, bool enable)
+{
+ u32 offset = DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_MSK;
+ u32 mask;
+
+ trace_saturn_irq_enable("irq online", enable);
+ mask = saturn_hee_get_irq_bit(INT_UNDERRUN, a_crtc->dev_id) |
+ saturn_hee_get_irq_bit(INT_CFG_RDY, a_crtc->dev_id);
+ if (enable) {
+ struct spacemit_drm_private *priv =
+ a_crtc->crtc.dev->dev_private;
+
+ /*
+ * Ack first: a surviving status bit would fire as the mask
+ * opens.
+ */
+ regmap_write(priv->hwdev->regmap,
+ DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_STS, mask);
+ }
+ saturn_enable_irq_mask(a_crtc, enable, offset, mask);
+ a_crtc->ur_mask_armed = true;
+}
+
+void saturn_hee_dpu_init(struct spacemit_crtc *a_crtc)
+{
+ saturn_init_regs(a_crtc);
+ saturn_setup_dma_top(a_crtc);
+ saturn_setup_mmu_top(a_crtc);
+ saturn_hee_irq_enable(a_crtc, true);
+}
+
+#define SATURN_WRITE_COMPOSER_LAYER(id_name) \
+do {\
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _colorkey_en, \
+ 0, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _blend_mode, \
+ blend_mode, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _blend_sel, \
+ alpha_sel, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _layer_alpha, \
+ alpha, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _alpha_ratio, \
+ 0, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _area_left, \
+ crtc_x, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _area_top, \
+ crtc_y, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _area_right, \
+ crtc_x + crtc_w - 1, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _area_bottom, \
+ crtc_y + crtc_h - 1, cl_cmp); \
+ if (unlikely(solid_en)) { \
+ dpu_write(hwdev, CMPS_X_REG, base, \
+ layer ## id_name ## _solid_color_r, \
+ solid_r, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, \
+ layer ## id_name ## _solid_color_g, \
+ solid_g, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, \
+ layer ## id_name ## _solid_color_b, \
+ solid_b, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, \
+ layer ## id_name ## _solid_color_a, \
+ solid_a, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _solid_en, \
+ 1, cl_cmp); \
+ } else { \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _solid_en, \
+ 0, cl_cmp); \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _dma_id, \
+ rdma_id, cl_cmp); \
+ } \
+ dpu_write(hwdev, CMPS_X_REG, base, layer ## id_name ## _en, 1, cl_cmp); \
+} while (0)
+
+static void saturn_write_fbcmem_regs(struct spacemit_hw_device *hwdev,
+ struct drm_plane_state *state, u32 rdma_id,
+ u32 base, struct cmdlist_regs *cl_rdma)
+{
+ struct drm_crtc_state *crtc_state = state->crtc->state;
+ const struct spacemit_crtc_rdma *rdmas =
+ to_spacemit_crtc_state(crtc_state)->rdmas;
+ u32 size = rdmas[rdma_id].fbcmem.size;
+ u32 start = rdmas[rdma_id].fbcmem.start;
+ bool map = rdmas[rdma_id].fbcmem.map;
+
+ dpu_write_word(hwdev, base, 31, map << 28 | start << 16 | size,
+ cl_rdma);
+}
+
+
+void saturn_hee_plane_update_hw_channel(struct drm_plane *plane)
+{
+ struct drm_plane_state *state = plane->state;
+ struct spacemit_crtc *a_crtc = to_spacemit_crtc(state->crtc);
+ struct drm_framebuffer *fb = plane->state->fb;
+ struct spacemit_plane_state *spacemit_plane_state =
+ to_spacemit_plane_state(state);
+ struct cmdlist_regs *cl_cmp = NULL;
+ u32 rdma_id = spacemit_plane_state->rdma_id;
+ u8 alpha = state->alpha >> 8;
+ u16 pixel_alpha = state->pixel_blend_mode;
+ u32 src_w, src_h, src_x, src_y;
+ u32 crtc_w, crtc_h, crtc_x, crtc_y;
+ u32 alpha_sel, blend_mode = 0;
+ struct drm_display_mode *mode = &state->crtc->mode;
+
+ u32 base;
+ bool solid_en = false;
+ u32 solid_a, solid_r, solid_g, solid_b;
+ struct spacemit_drm_private *priv = plane->dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+ struct cmdlist_regs *cl_rdma = a_crtc->cl_rdma;
+
+ trace_spacemit_plane_update_hw_channel("rdma_id", rdma_id);
+
+ src_w = state->src_w >> 16;
+ src_h = state->src_h >> 16;
+ src_x = state->src_x >> 16;
+ src_y = state->src_y >> 16;
+
+ crtc_w = state->crtc_w;
+ crtc_h = state->crtc_h;
+ crtc_x = state->crtc_x;
+ crtc_y = state->crtc_y;
+
+ drm_dbg(plane->dev, "crtc_x %u crtc_y %u\n", crtc_x, crtc_y);
+
+ if (rdma_id == RDMA_INVALID_ID)
+ solid_en = true;
+
+ trace_dpu_plane_info(state, fb, rdma_id, alpha, state->rotation);
+
+ /* For solid color both src_w and src_h are 0 */
+ if (!solid_en) {
+ base = RDMA_BASE_ADDR[rdma_id];
+ /* linear scanout only: the FBC decode path is not exposed */
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, layer_mode, 0, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, layer_cmpsr_id,
+ a_crtc->dev_id, cl_rdma);
+ /*
+ * force online-mode routing whatever the bootloader left behind
+ */
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, is_offline, 0, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, is_two_layers, 0,
+ cl_rdma);
+
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, img_width_ly0,
+ fb->width, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, img_height_ly0,
+ fb->height, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, bbox_start_x_ly0, src_x,
+ cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, bbox_start_y_ly0, src_y,
+ cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, bbox_end_x_ly0,
+ src_x + src_w - 1, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, bbox_end_y_ly0,
+ src_y + src_h - 1, cl_rdma);
+
+ saturn_write_fbcmem_regs(hwdev, state, rdma_id, base, cl_rdma);
+
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, rot_mode_ly0, 0,
+ cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, uv_swap, 0, cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, pixel_format,
+ spacemit_plane_state->format, cl_rdma);
+ } else {
+ /* Constant-fill layer: zero-size source, transparent black. */
+ solid_r = 0;
+ solid_g = 0;
+ solid_b = 0;
+ solid_a = 0;
+ }
+
+ switch (pixel_alpha) {
+ case DRM_MODE_BLEND_COVERAGE:
+ blend_mode = 0x0;
+ break;
+ case DRM_MODE_BLEND_PREMULTI:
+ blend_mode = 0x1;
+ break;
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ blend_mode = 0x0;
+ break;
+ default:
+ drm_err(plane->dev, "unsupported blend mode for pixel alpha\n");
+ }
+
+ if (state->fb->format && state->fb->format->has_alpha &&
+ pixel_alpha != DRM_MODE_BLEND_PIXEL_NONE) {
+ if (alpha != 0xff)
+ alpha_sel = 0x2;
+ else
+ alpha_sel = 0x1;
+ } else {
+ blend_mode = 0x0;
+ alpha_sel = 0x0;
+ }
+
+ cl_cmp = alloc_cmdlist_regs(CMPS_X_REG);
+
+ base = CMP_BASE_ADDR[a_crtc->dev_id];
+ dpu_write(hwdev, CMPS_X_REG, base, dst_w, mode->hdisplay, cl_cmp);
+ dpu_write(hwdev, CMPS_X_REG, base, dst_h, mode->vdisplay, cl_cmp);
+
+ SATURN_WRITE_COMPOSER_LAYER(00);
+ cmdlist_regs_packing(crtc_to_cl(plane->state->crtc), CMDLIST_MOD_COMP,
+ cl_cmp);
+ free_cmdlist_regs(cl_cmp);
+}
+
+void saturn_hee_plane_disable_hw_channel(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ u8 dev_id = to_spacemit_crtc(old_state->crtc)->dev_id;
+ u32 base = CMP_BASE_ADDR[dev_id];
+ u32 rdma_id = to_spacemit_plane_state(old_state)->rdma_id;
+ struct spacemit_drm_private *priv = plane->dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+ struct cmdlist_regs *cl_cmp = NULL;
+
+ trace_spacemit_plane_disable_hw_channel(0, rdma_id);
+ cl_cmp = alloc_cmdlist_regs(CMPS_X_REG);
+ dpu_write(hwdev, CMPS_X_REG, base, layer00_en, 0, cl_cmp);
+
+ cmdlist_regs_packing(crtc_to_cl(old_state->crtc), CMDLIST_MOD_COMP,
+ cl_cmp);
+ free_cmdlist_regs(cl_cmp);
+}
+
+void saturn_hee_conf_dpuctrl(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state)
+{
+ u32 rdma_en = 0;
+ u32 base;
+ u32 pp_base = POST_PIPE_ADDR;
+ struct spacemit_drm_private *priv = crtc->dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+ struct spacemit_crtc *a_crtc = to_spacemit_crtc(crtc);
+ struct drm_display_mode *mode = &crtc->mode;
+ struct drm_crtc_state *crtc_state = a_crtc->crtc.state;
+ struct spacemit_crtc_state *spacemit_crtc_state =
+ to_spacemit_crtc_state(crtc_state);
+ struct cmdlist *cl = NULL;
+ struct cmdlist_regs *pp_cl = NULL;
+
+ base = DPU_SCENE_CTL_ADDR(a_crtc->dev_id);
+ rdma_en = saturn_conf_dpuctrl_rdma(a_crtc);
+
+ pp_cl = alloc_cmdlist_regs(POSTPIPE_REG);
+ /*
+ * postpipe should be configured no matter whether pq function is on or
+ * off
+ */
+ dpu_write_word(hwdev, pp_base, 0, 0, pp_cl);
+ dpu_write(hwdev, POSTPIPE_REG, pp_base, m_inwidth, mode->hdisplay,
+ pp_cl);
+ dpu_write(hwdev, POSTPIPE_REG, pp_base, m_inheight, mode->vdisplay,
+ pp_cl);
+
+ cmdlist_regs_packing(crtc_to_cl(crtc), CMDLIST_MOD_COMP, pp_cl);
+ free_cmdlist_regs(pp_cl);
+
+ cmdlist_sort_by_group(crtc);
+ cmdlist_atomic_commit(crtc, old_state);
+
+ dpu_write(hwdev, DPU_CTL_REG, base, nml_scl_en, 0);
+ dpu_write(hwdev, DPU_CTL_REG, base, nml_rch_en, rdma_en);
+ dpu_write(hwdev, DPU_CTL_REG, base, timing_inter0, 8);
+ dpu_write(hwdev, DPU_CTL_REG, base, timing_inter1, 8);
+ dpu_write(hwdev, DPU_CTL_REG, base, video_mod, 1);
+ dpu_write(hwdev, DPU_CTL_REG, base, dbg_mod, 0);
+
+ dpu_write(hwdev, DPU_CTL_REG, base, nml_outctl_en, 0x1);
+ dpu_write(hwdev, DPU_CTL_REG, base, nml_frm_timing_en, 2);
+ cl = &spacemit_crtc_state->cl;
+ if (cl->va) {
+ crtc_cmdlist_sort_by_group(a_crtc);
+ crtc_cmdlist_atomic_commit(a_crtc);
+ }
+}
+
+u32 saturn_hee_get_cfg_rdy(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev)
+{
+ u32 base = DPU_SCENE_CTL_ADDR(a_crtc->dev_id);
+
+ return dpu_read_word(hwdev, base, 2);
+}
+
+u32 saturn_hee_get_int_sts(struct spacemit_hw_device *hwdev, int dev_id)
+{
+ /* Both K3 DPU instances are wired as COMPOSER1 (see master_bind). */
+ if (dev_id != COMPOSER1)
+ return 0;
+
+ return dpu_read_word(hwdev, DPU_INT_BASE_ADDR, 12);
+}
+
+static const struct spacemit_crtc_irq_bitfield {
+ enum spacemit_dpu_irq irq_id;
+ u32 irq_bit;
+} irq_list[] = {
+ [INT_UNDERRUN] = {INT_UNDERRUN, DPU_INT_FRM_TIMING_UNFLOW},
+ [INT_CFG_RDY] = {INT_CFG_RDY, DPU_INT_CFG_RDY_CLR},
+ [INT_VSYNC] = {INT_VSYNC, DPU_INT_FRM_TIMING_VSYNC},
+ [INT_EOF] = {INT_EOF, DPU_INT_FRM_TIMING_EOF},
+ [INT_REST] = {INT_REST, DPU_REST_INT_BITS},
+};
+
+u32 saturn_hee_get_irq_bit(enum spacemit_dpu_irq irq_id, int dev_id)
+{
+ if (irq_id >= ARRAY_SIZE(irq_list))
+ return 0;
+
+ if (dev_id == COMPOSER1 || dev_id == COMPOSER2)
+ return irq_list[irq_id].irq_bit;
+ else
+ return 0;
+}
+
+void saturn_hee_clr_int_sts(struct spacemit_crtc *a_crtc, u32 data, int dev_id)
+{
+ struct spacemit_drm_private *priv = a_crtc->crtc.dev->dev_private;
+ struct spacemit_hw_device *hwdev = priv->hwdev;
+
+ /* Both K3 DPU instances are wired as COMPOSER1 (see master_bind). */
+ if (dev_id != COMPOSER1)
+ return;
+
+ regmap_write(hwdev->regmap, DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_STS,
+ data);
+ if ((saturn_hee_get_irq_bit(INT_UNDERRUN, dev_id) & data) &&
+ a_crtc->ur_mask_armed) {
+ saturn_enable_irq_mask(a_crtc, false,
+ DPU_INT_BASE_ADDR + DPU_ONLINE_IRQ_MSK,
+ saturn_hee_get_irq_bit(INT_UNDERRUN, dev_id));
+ a_crtc->ur_mask_armed = false;
+ }
+}
+
+void saturn_hee_enable_cmdlist(struct spacemit_crtc *a_crtc, struct spacemit_hw_device *hwdev,
+ int id, bool enable)
+{
+ u32 val = 0;
+ u32 base = 0;
+ struct drm_crtc *crtc = &a_crtc->crtc;
+ struct drm_crtc_state *crtc_state = crtc->state;
+ struct spacemit_crtc_state *spacemit_state =
+ to_spacemit_crtc_state(crtc_state);
+ struct spacemit_crtc_rdma *rdmas = spacemit_state->rdmas;
+
+ base = DPU_SCENE_CTL_ADDR(a_crtc->dev_id);
+
+ dpu_write_reg_arr(hwdev, DPU_CTL_TOP_REG, DPU_CTRL_BASE_ADDR,
+ dpu_ctl_top_reg_0, id, cmdlist_rch_en,
+ enable ? 1 : 0);
+ val = dpu_read_reg(hwdev, DPU_CTL_REG, base, nml_rch_vrt_reuse);
+ if (enable && rdmas[id].use_cnt > 1)
+ val |= (1 << id);
+ else
+ val &= ~(1 << id);
+
+ dpu_write(hwdev, DPU_CTL_REG, base, nml_rch_vrt_reuse, val);
+}
+
+void saturn_hee_cfg_cmdlist(struct spacemit_hw_device *hwdev, int id, u32 chy,
+ u32 addrl, u32 addrh)
+{
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR,
+ cmdlist_reg_48, id, cmdlist_ch_y_first, chy);
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR,
+ cmdlist_reg_0, id, cmdlist_ch_start_addrl, addrl);
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR,
+ cmdlist_reg_16, id, cmdlist_ch_start_addrh, addrh);
+}
+
+void saturn_hee_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)
+{
+ u32 base;
+ u8 rdma_id = tbu_id / 2;
+ struct cmdlist_regs *cl_tbu = NULL;
+
+ SATURN_WRITE_RDMA_ADDR_REG(hwdev, 0, rdma_id, tbu->tbu_va[0], cl_rdma);
+ SATURN_WRITE_RDMA_ADDR_REG(hwdev, 1, rdma_id, tbu->tbu_va[1], cl_rdma);
+ SATURN_WRITE_RDMA_ADDR_REG(hwdev, 2, rdma_id, tbu->tbu_va[2], cl_rdma);
+
+ base = RDMA_BASE_ADDR[rdma_id];
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, rdma_stride0_layer0,
+ fb->pitches[0], cl_rdma);
+ dpu_write(hwdev, RDMA_PATH_X_REG, base, rdma_stride1_layer0,
+ fb->pitches[1], cl_rdma);
+
+ cl_tbu = alloc_cmdlist_regs(MMU_TBU_REG);
+ base = MMU_TBU_BASE_ADDR_ARRAY[tbu_id];
+ SATURN_WRITE_TBU_REGS(hwdev, base, 0, cl_tbu);
+ SATURN_WRITE_TBU_REGS(hwdev, base, 1, cl_tbu);
+ SATURN_WRITE_TBU_REGS(hwdev, base, 2, cl_tbu);
+
+ dpu_write_word(hwdev, base, 0, val, cl_tbu);
+ cmdlist_regs_packing(plane_to_cl(plane), CMDLIST_MOD_DMMU, cl_tbu);
+ free_cmdlist_regs(cl_tbu);
+}
+
+static u32 saturn_get_cl_chy_addr(void)
+{
+ return CMDLIST_BASE_ADDR + CMDLIST_CH_Y;
+}
+
+static u32 saturn_get_cl_start_cmps_y_addr(void)
+{
+ return DPU_CTRL_BASE_ADDR + CMDLIST_CH_START_CMPS_Y;
+}
+
+static u32 saturn_get_cl_cfg_rdy_addr(void)
+{
+ return DPU_CTRL_BASE_ADDR + CMDLIST_CFG_READY;
+}
+
+int saturn_hee_get_cl_rdma_buf(struct spacemit_crtc *a_crtc)
+{
+ a_crtc->cl_rdma = alloc_cmdlist_regs(RDMA_PATH_X_REG);
+ if (!a_crtc->cl_rdma)
+ return -ENOMEM;
+ else
+ return 0;
+}
+
+struct cmdlist_header {
+ u64 next_list_addr : 39;
+ /* reserved */
+ u32: 1;
+ u32 nod_len : 16;
+ /* 1: the last cmdlist node, 2: pending node, otherwise 0 */
+ u32 nod_type : 2;
+ u32 next_nod_secu : 1;
+ u64 wait_event_low : 5;
+ u64 wait_event : 59;
+ /* reserved */
+ u32: 5;
+};
+
+struct cmdlist_row {
+ u32 module_cfg_addr : 19;
+ u32 module_cfg_strobe : 12;
+ /* the last row tag = 1, else tag = 0. */
+ u32 row_eof_tag : 1;
+ u32 module_regs[3];
+};
+
+/* DMA stream layout consumed by the cmdlist engine; sizes are load-bearing. */
+static_assert(sizeof(struct cmdlist_header) == 16);
+static_assert(sizeof(struct cmdlist_row) == 16);
+
+#define CL_HEADER_SZ sizeof(struct cmdlist_header)
+#define CL_ROW_SZ sizeof(struct cmdlist_row)
+
+#define CMDLIST_RDMA_CFG_RDY(ch) BIT(ch)
+#define CMDLIST_CMPS_CFG_RDY(ch) BIT((ch) + 20)
+
+#define CMDLIST_WAIT_EVENT_BIT_RDMA_RELOAD BIT(4)
+#define CMDLIST_WAIT_EVENT_BIT_CMPS_RELOAD BIT(6)
+
+void saturn_hee_cmdlist_fill_data_row(struct cmdlist *cl, u32 strobe,
+ u32 offset, u32 value[])
+{
+ struct cmdlist_row *row;
+ u8 i;
+
+ /*
+ * packing counts rows blindly; drop the row rather than the buffer end
+ */
+ if (WARN_ONCE(CL_HEADER_SZ + (cl->nod_len + 1) * CL_ROW_SZ > cl->size,
+ "cmdlist buffer full (rows %u)\n", cl->nod_len))
+ return;
+
+ row = (struct cmdlist_row *)((char *)cl->va + CL_HEADER_SZ) + cl->nod_len;
+ row->module_cfg_addr = offset >> 2;
+ row->module_cfg_strobe = strobe;
+
+ for (i = 0; i < CMDLIST_ROW_REGS; i++)
+ if (strobe & CMDLIST_REG_STROBE(i))
+ row->module_regs[i] = value[i];
+}
+
+void saturn_hee_cmdlist_fill_conf_row(struct cmdlist *cl, struct spacemit_hw_device *hwdev,
+ u8 dev_id)
+{
+ struct cmdlist_header *header;
+ struct cmdlist_row *row;
+ struct spacemit_plane_state *spacemit_pstate = NULL;
+ u8 rch_id = 0;
+ u32 wait_event_bits = 0, cfg_rdy_bits = 0;
+ int cur_crtc_y = -1, cur_crtc_h = -1, next_crtc_y = -1;
+
+ /*
+ * CRTC-level cmdlists live inside a crtc_state; only planes have one.
+ */
+ if (cl->type == CMDLIST_PLANE) {
+ spacemit_pstate = cl_to_spacemit_pstate(cl);
+ rch_id = spacemit_pstate->rdma_id;
+ }
+
+ /* At most three rows are appended below; bail before overrunning. */
+ if (WARN_ONCE(CL_HEADER_SZ + (cl->nod_len + 3) * CL_ROW_SZ > cl->size,
+ "cmdlist buffer full (type %u, rows %u)\n",
+ cl->type, cl->nod_len))
+ return;
+
+ header = (struct cmdlist_header *)(cl->va);
+ row = (struct cmdlist_row *)((char *)header + CL_HEADER_SZ + cl->nod_len * CL_ROW_SZ);
+ if (cl->next) {
+ if (dev_id == CMDLIST_CMP_INVALID) {
+ trace_u64_data("rch_id", rch_id);
+ row->module_cfg_addr = ((saturn_get_cl_chy_addr() + rch_id * 4) >> 2);
+ } else {
+ trace_u64_data("dev_id", dev_id);
+ row->module_cfg_addr = ((saturn_get_cl_chy_addr() + (5 + dev_id) * 4) >> 2);
+ }
+ row->module_cfg_strobe = CMDLIST_REG_STROBE(0);
+ if (cl->cmdlist_ch_y_other != CMDLIST_SENTINEL_POISON)
+ next_crtc_y = cl->cmdlist_ch_y_other;
+ else if (cl->next->type == CMDLIST_PLANE)
+ next_crtc_y = cl_to_spacemit_pstate(cl->next)->state.crtc_y;
+ else
+ next_crtc_y = 0;
+ row->module_regs[0] = next_crtc_y;
+ cl->nod_len++;
+ row++;
+ }
+
+ if (cl->type == CMDLIST_PLANE) {
+ if (cl->rch_start_cmps_y != CMDLIST_SENTINEL_POISON)
+ cur_crtc_y = cl->rch_start_cmps_y;
+ else
+ cur_crtc_y = cl_to_spacemit_pstate(cl)->state.crtc_y;
+ cur_crtc_h = cl_to_spacemit_pstate(cl)->state.crtc_h;
+ /* the cmdlist nodes don't belong to the same plane */
+ if (next_crtc_y != -1 && cl_to_spacemit_pstate(cl->next) != spacemit_pstate &&
+ (cur_crtc_y + cur_crtc_h + 24) > next_crtc_y) {
+ drm_err_ratelimited(spacemit_pstate->state.crtc->dev,
+ "invalid gap: cur_y=%d cur_h=%d next_y=%d\n",
+ cur_crtc_y, cur_crtc_h, next_crtc_y);
+ }
+ row->module_cfg_addr = (saturn_get_cl_start_cmps_y_addr() + rch_id * 4) >> 2;
+ row->module_cfg_strobe = CMDLIST_REG_STROBE(0);
+ row->module_regs[0] =
+ (cur_crtc_y >= 24) ? (cur_crtc_y - 24) : 0;
+ cl->nod_len++;
+ row++;
+ }
+
+ if (cl->mode_mask & CMDLIST_MOD_RDMA) {
+ wait_event_bits |= CMDLIST_WAIT_EVENT_BIT_RDMA_RELOAD;
+ cfg_rdy_bits |= CMDLIST_RDMA_CFG_RDY(rch_id);
+ }
+ if (cl->mode_mask & CMDLIST_MOD_COMP) {
+ wait_event_bits |= CMDLIST_WAIT_EVENT_BIT_CMPS_RELOAD;
+ cfg_rdy_bits |= CMDLIST_CMPS_CFG_RDY(dev_id);
+ }
+ row->module_cfg_addr = (saturn_get_cl_cfg_rdy_addr()) >> 2;
+ row->module_cfg_strobe = CMDLIST_REG_STROBE(0);
+ row->module_regs[0] = cfg_rdy_bits;
+ row->row_eof_tag = 1;
+ cl->nod_len++;
+ row++;
+
+ if (cl->next)
+ header->next_list_addr = cl->next->pa;
+ else
+ header->nod_type = 1;
+ header->nod_len = cl->nod_len + 1; /* include header */
+ header->wait_event_low = wait_event_bits & 0x1F;
+ header->wait_event = wait_event_bits >> 5;
+}
+
+void saturn_hee_crtc_cmdlist(struct cmdlist *cl, struct spacemit_hw_device *hwdev,
+ struct spacemit_drm_private *priv, u8 crtc_id, u8 dev_id)
+{
+ u32 val = 0;
+
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR, cmdlist_reg_48,
+ hwdev->rdma_nums + 2 + crtc_id, cmdlist_ch_y_first,
+ val);
+
+ val = (lower_32_bits(priv->cmdlist_groups[hwdev->rdma_nums + crtc_id]->pa) &
+ CMDLIST_ADDRL_ALIGN_MASK) >> CMDLIST_ADDRL_ALIGN_BITS;
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR, cmdlist_reg_0,
+ hwdev->rdma_nums + 2 + crtc_id,
+ cmdlist_ch_start_addrl, val);
+ val = upper_32_bits(priv->cmdlist_groups[hwdev->rdma_nums + crtc_id]->pa);
+ dpu_write_reg_arr(hwdev, CMDLIST_REG, CMDLIST_BASE_ADDR, cmdlist_reg_16,
+ hwdev->rdma_nums + 2 + crtc_id,
+ cmdlist_ch_start_addrh, val);
+ dpu_write_reg_arr(hwdev, DPU_CTL_TOP_REG, DPU_CTRL_BASE_ADDR,
+ dpu_ctl_top_reg_10, dev_id, cmdlist_cmps_top_en, 1);
+ dpu_write_reg_arr(hwdev, DPU_CTL_TOP_REG, DPU_CTRL_BASE_ADDR,
+ dpu_ctl_top_reg_10, dev_id, cmdlist_cmps_other_en, 1);
+
+ priv->cmdlist_groups[hwdev->rdma_nums + crtc_id] = NULL;
+}
+
+#define DUMP_BUF_LEN 100
+void saturn_hee_cmdlist_dump_node(struct cmdlist *cl)
+{
+ struct cmdlist_header *header = (struct cmdlist_header *)(cl->va);
+ ssize_t ret = 0;
+ char tmp[DUMP_BUF_LEN] = {0x0};
+
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "header: ");
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "next_addr:0x%llx ",
+ (unsigned long long)header->next_list_addr);
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "rows:%d ",
+ header->nod_len);
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "type:%d ",
+ header->nod_type);
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "event_low:0x%llx ",
+ (unsigned long long)header->wait_event_low);
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret, "event:0x%llx ",
+ (unsigned long long)header->wait_event);
+ trace_plat_cmdlist_dump_node(tmp);
+
+ for (int i = 0; i < header->nod_len - 1; i++) {
+ struct cmdlist_row *row = (struct cmdlist_row *)((char *)header + CL_HEADER_SZ) + i;
+ u32 addr = row->module_cfg_addr << 2;
+
+ ret = 0;
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret,
+ "row[%4d:tag:%d]: ", i, row->row_eof_tag);
+
+ for (int j = 0; j < CMDLIST_ROW_REGS; j++) {
+ if (row->module_cfg_strobe & CMDLIST_REG_STROBE(j))
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret,
+ "[%#10x:%#10x]", addr + j * 4,
+ row->module_regs[j]);
+ else
+ ret += scnprintf(tmp + ret, sizeof(tmp) - ret,
+ "%#11x:%#11x", addr + j * 4,
+ row->module_regs[j]);
+ }
+ trace_plat_cmdlist_dump_node(tmp);
+ }
+}
diff --git a/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c
new file mode 100644
index 000000000000..144716ec9b80
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#include <linux/stddef.h>
+#include <linux/export.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_mode.h>
+#include <drm/drm_fourcc.h>
+#include "../spacemit_cmdlist.h"
+#include "saturn_fbcmem.h"
+
+int get_raw_data_plane_rdma_mem_size(u32 drm_4cc_fmt, u32 plane_crop_width,
+ u32 *output_mem_size)
+{
+ u32 ret_mem_size = 0;
+ const struct drm_format_info *info = NULL;
+
+ info = drm_format_info(drm_4cc_fmt);
+ if (info->cpp[0] == 0 || info->num_planes != 1 || info->is_yuv)
+ return -EINVAL;
+
+ /* 64-byte rounding is a dpu hardware request */
+ ret_mem_size = roundup(plane_crop_width * info->cpp[0], 64);
+ ret_mem_size = ret_mem_size / 32;
+
+ if (output_mem_size)
+ *output_mem_size = ret_mem_size;
+
+ return 0;
+}
+
+int saturn_cal_layer_fbcmem_size(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ struct spacemit_plane_state *pstate = to_spacemit_plane_state(state);
+ u32 drm_4cc_fmt = pstate->state.fb->format->format;
+ u32 crop_w = pstate->state.src_w >> 16;
+
+ /* Only linear sizing: the compressed path is not exposed. */
+ return get_raw_data_plane_rdma_mem_size(drm_4cc_fmt, crop_w,
+ &pstate->fbcmem_size);
+}
+
+/* Even RDMA (0, 2, ...): sole owner of the front of its fbc memory bank. */
+static int saturn_fbcmem_place_even(struct spacemit_hw_device *hwdev,
+ struct spacemit_crtc_rdma *rdmas,
+ u32 *fbc_mems_left, u8 index)
+{
+ u32 cur_rdma_fbcmem_size = rdmas[index].fbcmem.size;
+ u32 pri_fbcmem_size = hwdev->fbcmem_sizes[index / 2] / FBCMEM_UNIT;
+
+ if (cur_rdma_fbcmem_size > pri_fbcmem_size)
+ return -ENOSPC;
+
+ /* The preceding odd RDMA may not already share this bank. */
+ if (index > 0 && rdmas[index - 1].fbcmem.map)
+ return -ENOSPC;
+
+ rdmas[index].fbcmem.start = 0;
+ rdmas[index].fbcmem.map = true;
+ fbc_mems_left[index / 2] -= cur_rdma_fbcmem_size;
+
+ return 0;
+}
+
+/* Odd RDMA takes what is left of its own bank, spilling into the next. */
+static int saturn_fbcmem_place_odd(struct spacemit_hw_device *hwdev,
+ struct spacemit_crtc_rdma *rdmas,
+ u32 *fbc_mems_left, u8 index)
+{
+ u32 cur_rdma_fbcmem_size = rdmas[index].fbcmem.size;
+ u32 pri_fbcmem_size = hwdev->fbcmem_sizes[index / 2] / FBCMEM_UNIT;
+ u8 rdma_nums = hwdev->rdma_nums;
+ u8 sec_fbcmem_index;
+ u32 sec_fbcmem_size;
+
+ if (cur_rdma_fbcmem_size <= fbc_mems_left[index / 2]) { /* not share fbc mem */
+ rdmas[index].fbcmem.map = false;
+ rdmas[index].fbcmem.start =
+ pri_fbcmem_size - fbc_mems_left[index / 2];
+ rdmas[index].fbcmem.size = fbc_mems_left[index / 2]; /* use all the mem left */
+ fbc_mems_left[index / 2] = 0;
+ return 0;
+ }
+
+ /* Need to share the next bank's fbc mem. */
+ sec_fbcmem_index = (index / 2 + 1) % (rdma_nums / 2);
+ sec_fbcmem_size = hwdev->fbcmem_sizes[sec_fbcmem_index] / FBCMEM_UNIT;
+ if (cur_rdma_fbcmem_size > fbc_mems_left[index / 2] + sec_fbcmem_size)
+ return -ENOSPC;
+
+ if (index == rdma_nums - 1) { /* last rdma id */
+ /* bank 0 is already in use */
+ if (fbc_mems_left[0] != hwdev->fbcmem_sizes[0] / FBCMEM_UNIT)
+ return -ENOSPC;
+
+ rdmas[index].fbcmem.size =
+ fbc_mems_left[index / 2] + sec_fbcmem_size;
+ cur_rdma_fbcmem_size = rdmas[index].fbcmem.size;
+ }
+ fbc_mems_left[sec_fbcmem_index] -= (cur_rdma_fbcmem_size - fbc_mems_left[index / 2]);
+ rdmas[index].fbcmem.start = pri_fbcmem_size - fbc_mems_left[index / 2];
+ rdmas[index].fbcmem.map = true;
+ fbc_mems_left[index / 2] = 0;
+
+ return 0;
+}
+
+int saturn_adjust_rdma_fbcmem(struct spacemit_hw_device *hwdev,
+ struct spacemit_crtc_rdma *rdmas)
+{
+ int ret = -ENOSPC;
+ u8 index = 0;
+ u32 *fbc_mems_left = NULL;
+ u8 rdma_nums = hwdev->rdma_nums;
+
+ fbc_mems_left = kzalloc_objs(*fbc_mems_left, rdma_nums / 2);
+ if (!fbc_mems_left) {
+ ret = -ENOMEM;
+ goto free;
+ }
+ for (index = 0; index < rdma_nums / 2; index++)
+ fbc_mems_left[index] = hwdev->fbcmem_sizes[index] / FBCMEM_UNIT;
+
+ for (index = 0; index < rdma_nums; index++) {
+ int err;
+
+ if (rdmas[index].fbcmem.size == 0)
+ continue;
+
+ if (index % 2 == 0)
+ err = saturn_fbcmem_place_even(hwdev, rdmas,
+ fbc_mems_left, index);
+ else
+ err = saturn_fbcmem_place_odd(hwdev, rdmas,
+ fbc_mems_left, index);
+ if (err) {
+ ret = err;
+ goto free;
+ }
+ }
+ ret = 0;
+
+free:
+ kfree(fbc_mems_left);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.h b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.h
new file mode 100644
index 000000000000..a4998dbc682f
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/dpu/saturn_fbcmem.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SATURN_FBCMEM_H_
+#define _SATURN_FBCMEM_H_
+
+#include <drm/drm_plane.h>
+#include "../spacemit_crtc.h"
+#include "../spacemit_dpu_reg.h"
+#include "dpu_saturn.h"
+
+#define FBCMEM_UNIT (32) /* fbcmem is 32 bytes per unit */
+
+int get_raw_data_plane_rdma_mem_size(u32 drm_4cc_fmt, u32 plane_crop_width,
+ u32 *output_mem_size);
+
+int saturn_cal_layer_fbcmem_size(struct drm_plane *plane,
+ struct drm_plane_state *state);
+
+int saturn_adjust_rdma_fbcmem(struct spacemit_hw_device *hwdev,
+ struct spacemit_crtc_rdma *rdmas);
+
+#endif
diff --git a/drivers/gpu/drm/spacemit/dpu/saturn_regs/ops_hee.h b/drivers/gpu/drm/spacemit/dpu/saturn_regs/ops_hee.h
new file mode 100644
index 000000000000..7f5f01966377
--- /dev/null
+++ b/drivers/gpu/drm/spacemit/dpu/saturn_regs/ops_hee.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025-2026 SpacemiT Co., Ltd.
+ *
+ */
+
+#ifndef _SATURN_hee_OPS_H_
+#define _SATURN_hee_OPS_H_
+
+#include "../../spacemit_crtc.h"
+
+void saturn_hee_enable_vsync(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev, bool enable);
+void saturn_hee_enable_cfg_irq(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev, bool enable);
+void saturn_hee_cfg_ready(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev);
+void saturn_hee_sw_start(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev);
+void saturn_hee_dpu_init(struct spacemit_crtc *a_crtc);
+void saturn_hee_plane_update_hw_channel(struct drm_plane *plane);
+void saturn_hee_plane_disable_hw_channel(struct drm_plane *plane,
+ struct drm_plane_state *old_state);
+void saturn_hee_irq_enable(struct spacemit_crtc *a_crtc, bool enable);
+void saturn_hee_conf_dpuctrl(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state);
+u32 saturn_hee_get_cfg_rdy(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev);
+u32 saturn_hee_get_int_sts(struct spacemit_hw_device *hwdev, int dev_id);
+u32 saturn_hee_get_irq_bit(enum spacemit_dpu_irq irq_id, int dev_id);
+void saturn_hee_clr_int_sts(struct spacemit_crtc *a_crtc, u32 data, int dev_id);
+void saturn_hee_enable_cmdlist(struct spacemit_crtc *a_crtc,
+ struct spacemit_hw_device *hwdev, int id,
+ bool enable);
+void saturn_hee_cfg_cmdlist(struct spacemit_hw_device *hwdev, int id, u32 chy,
+ u32 addrl, u32 addrh);
+void saturn_hee_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 saturn_hee_get_cl_rdma_buf(struct spacemit_crtc *a_crtc);
+void saturn_hee_cmdlist_fill_data_row(struct cmdlist *cl, u32 strobe,
+ u32 offset, u32 value[]);
+void saturn_hee_cmdlist_fill_conf_row(struct cmdlist *cl,
+ struct spacemit_hw_device *hwdev,
+ u8 dev_id);
+void saturn_hee_crtc_cmdlist(struct cmdlist *cl,
+ struct spacemit_hw_device *hwdev,
+ struct spacemit_drm_private *priv, u8 crtc_id,
+ u8 dev_id);
+void saturn_hee_cmdlist_dump_node(struct cmdlist *cl);
+#endif
--
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 ` [PATCH RESEND 08/17] drm/spacemit: add Saturn DPU core types, cmdlist and display MMU Cody Kang via B4 Relay
2026-07-25 5:04 ` sashiko-bot
2026-07-25 4:51 ` Cody Kang via B4 Relay [this message]
2026-07-25 5:12 ` [PATCH RESEND 09/17] drm/spacemit: add Saturn DPU hardware backend 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-9-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