[PATCH 09/17] drm/spacemit: add Saturn DPU hardware backend
Cody Kang <[email protected]> Fri, 24 Jul 2026 23:16:41 -0400
| Newsgroups | dev.linux.lists.spacemit,org.freedesktop.lists.dri-devel,org.infradead.lists.linux-phy,org.infradead.lists.linux-riscv,org.kernel.vger.linux-clk,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- 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