From ad531f3d8b37521248508d5084b0ba480a27ef6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Wed, 8 Oct 2025 15:39:26 +0200 Subject: [PATCH 01/71] dma-buf: rework stub fence initialisation v2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of doing this on the first call of the function just initialize the stub fence during kernel load. This has the clear advantage of lower overhead and also doesn't rely on the ops to not be NULL any more. v2: use correct signal function Signed-off-by: Christian König Reviewed-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20251031134442.113648-3-christian.koenig@amd.com --- drivers/dma-buf/dma-fence.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/drivers/dma-buf/dma-fence.c b/drivers/dma-buf/dma-fence.c index 3f78c56b58dc..35e241041c10 100644 --- a/drivers/dma-buf/dma-fence.c +++ b/drivers/dma-buf/dma-fence.c @@ -121,29 +121,27 @@ static const struct dma_fence_ops dma_fence_stub_ops = { .get_timeline_name = dma_fence_stub_get_name, }; +static int __init dma_fence_init_stub(void) +{ + dma_fence_init(&dma_fence_stub, &dma_fence_stub_ops, + &dma_fence_stub_lock, 0, 0); + + set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, + &dma_fence_stub.flags); + + dma_fence_signal(&dma_fence_stub); + return 0; +} +subsys_initcall(dma_fence_init_stub); + /** * dma_fence_get_stub - return a signaled fence * - * Return a stub fence which is already signaled. The fence's - * timestamp corresponds to the first time after boot this - * function is called. + * Return a stub fence which is already signaled. The fence's timestamp + * corresponds to the initialisation time of the linux kernel. */ struct dma_fence *dma_fence_get_stub(void) { - spin_lock(&dma_fence_stub_lock); - if (!dma_fence_stub.ops) { - dma_fence_init(&dma_fence_stub, - &dma_fence_stub_ops, - &dma_fence_stub_lock, - 0, 0); - - set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT, - &dma_fence_stub.flags); - - dma_fence_signal_locked(&dma_fence_stub); - } - spin_unlock(&dma_fence_stub_lock); - return dma_fence_get(&dma_fence_stub); } EXPORT_SYMBOL(dma_fence_get_stub); From 3a0ff7b98af4a5de1b995dfb57e65843f9b7b628 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Tue, 4 Nov 2025 10:53:39 -0800 Subject: [PATCH 02/71] accel/amdxdna: Support preemption requests The driver checks the firmware version during initialization.If preemption is supported, the driver configures preemption accordingly and handles userspace preemption requests. Otherwise, the driver returns an error for userspace preemption requests. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251104185340.897560-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_message.c | 95 +++++++++++++++++++++++++ drivers/accel/amdxdna/aie2_msg_priv.h | 3 + drivers/accel/amdxdna/aie2_pci.c | 63 ++++++++++++++++ drivers/accel/amdxdna/aie2_pci.h | 8 +++ drivers/accel/amdxdna/amdxdna_ctx.h | 17 +++++ drivers/accel/amdxdna/amdxdna_pci_drv.c | 3 +- drivers/accel/amdxdna/npu4_regs.c | 4 ++ include/uapi/drm/amdxdna_accel.h | 16 ++++- 8 files changed, 207 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_message.c b/drivers/accel/amdxdna/aie2_message.c index 69cdce9ff208..d493bb1c3360 100644 --- a/drivers/accel/amdxdna/aie2_message.c +++ b/drivers/accel/amdxdna/aie2_message.c @@ -210,6 +210,14 @@ int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwct hwctx->fw_ctx_id = resp.context_id; WARN_ONCE(hwctx->fw_ctx_id == -1, "Unexpected context id"); + if (ndev->force_preempt_enabled) { + ret = aie2_runtime_cfg(ndev, AIE2_RT_CFG_FORCE_PREEMPT, &hwctx->fw_ctx_id); + if (ret) { + XDNA_ERR(xdna, "failed to enable force preempt %d", ret); + return ret; + } + } + cq_pair = &resp.cq_pair[0]; x2i.mb_head_ptr_reg = AIE2_MBOX_OFF(ndev, cq_pair->x2i_q.head_addr); x2i.mb_tail_ptr_reg = AIE2_MBOX_OFF(ndev, cq_pair->x2i_q.tail_addr); @@ -601,6 +609,11 @@ aie2_cmdlist_fill_dpu(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size) return 0; } +static int aie2_cmdlist_unsupp(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size) +{ + return -EOPNOTSUPP; +} + static u32 aie2_get_chain_msg_op(u32 cmd_op) { switch (cmd_op) { @@ -621,6 +634,8 @@ static struct aie2_exec_msg_ops legacy_exec_message_ops = { .init_chain_req = aie2_init_exec_chain_req, .fill_cf_slot = aie2_cmdlist_fill_cf, .fill_dpu_slot = aie2_cmdlist_fill_dpu, + .fill_preempt_slot = aie2_cmdlist_unsupp, + .fill_elf_slot = aie2_cmdlist_unsupp, .get_chain_msg_op = aie2_get_chain_msg_op, }; @@ -680,6 +695,74 @@ aie2_cmdlist_fill_npu_dpu(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *si return 0; } +static int +aie2_cmdlist_fill_npu_preempt(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size) +{ + struct cmd_chain_slot_npu *npu_slot = slot; + struct amdxdna_cmd_preempt_data *pd; + u32 cmd_len; + u32 arg_sz; + + pd = amdxdna_cmd_get_payload(cmd_bo, &cmd_len); + arg_sz = cmd_len - sizeof(*pd); + if (cmd_len < sizeof(*pd) || arg_sz > MAX_NPU_ARGS_SIZE) + return -EINVAL; + + if (*size < sizeof(*npu_slot) + arg_sz) + return -EINVAL; + + npu_slot->cu_idx = amdxdna_cmd_get_cu_idx(cmd_bo); + if (npu_slot->cu_idx == INVALID_CU_IDX) + return -EINVAL; + + memset(npu_slot, 0, sizeof(*npu_slot)); + npu_slot->type = EXEC_NPU_TYPE_PREEMPT; + npu_slot->inst_buf_addr = pd->inst_buf; + npu_slot->save_buf_addr = pd->save_buf; + npu_slot->restore_buf_addr = pd->restore_buf; + npu_slot->inst_size = pd->inst_size; + npu_slot->save_size = pd->save_size; + npu_slot->restore_size = pd->restore_size; + npu_slot->inst_prop_cnt = pd->inst_prop_cnt; + npu_slot->arg_cnt = arg_sz / sizeof(u32); + memcpy(npu_slot->args, pd->prop_args, arg_sz); + + *size = sizeof(*npu_slot) + arg_sz; + return 0; +} + +static int +aie2_cmdlist_fill_npu_elf(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size) +{ + struct cmd_chain_slot_npu *npu_slot = slot; + struct amdxdna_cmd_preempt_data *pd; + u32 cmd_len; + u32 arg_sz; + + pd = amdxdna_cmd_get_payload(cmd_bo, &cmd_len); + arg_sz = cmd_len - sizeof(*pd); + if (cmd_len < sizeof(*pd) || arg_sz > MAX_NPU_ARGS_SIZE) + return -EINVAL; + + if (*size < sizeof(*npu_slot) + arg_sz) + return -EINVAL; + + memset(npu_slot, 0, sizeof(*npu_slot)); + npu_slot->type = EXEC_NPU_TYPE_ELF; + npu_slot->inst_buf_addr = pd->inst_buf; + npu_slot->save_buf_addr = pd->save_buf; + npu_slot->restore_buf_addr = pd->restore_buf; + npu_slot->inst_size = pd->inst_size; + npu_slot->save_size = pd->save_size; + npu_slot->restore_size = pd->restore_size; + npu_slot->inst_prop_cnt = pd->inst_prop_cnt; + npu_slot->arg_cnt = 1; + npu_slot->args[0] = AIE2_EXEC_BUFFER_KERNEL_OP_TXN; + + *size = struct_size(npu_slot, args, npu_slot->arg_cnt); + return 0; +} + static u32 aie2_get_npu_chain_msg_op(u32 cmd_op) { return MSG_OP_CHAIN_EXEC_NPU; @@ -691,6 +774,8 @@ static struct aie2_exec_msg_ops npu_exec_message_ops = { .init_chain_req = aie2_init_npu_chain_req, .fill_cf_slot = aie2_cmdlist_fill_npu_cf, .fill_dpu_slot = aie2_cmdlist_fill_npu_dpu, + .fill_preempt_slot = aie2_cmdlist_fill_npu_preempt, + .fill_elf_slot = aie2_cmdlist_fill_npu_elf, .get_chain_msg_op = aie2_get_npu_chain_msg_op, }; @@ -749,6 +834,16 @@ aie2_cmdlist_fill_slot(void *slot, struct amdxdna_gem_obj *cmd_abo, case ERT_START_NPU: ret = EXEC_MSG_OPS(xdna)->fill_dpu_slot(cmd_abo, slot, size); break; + case ERT_START_NPU_PREEMPT: + if (!AIE2_FEATURE_ON(xdna->dev_handle, AIE2_PREEMPT)) + return -EOPNOTSUPP; + ret = EXEC_MSG_OPS(xdna)->fill_preempt_slot(cmd_abo, slot, size); + break; + case ERT_START_NPU_PREEMPT_ELF: + if (!AIE2_FEATURE_ON(xdna->dev_handle, AIE2_PREEMPT)) + return -EOPNOTSUPP; + ret = EXEC_MSG_OPS(xdna)->fill_elf_slot(cmd_abo, slot, size); + break; default: XDNA_INFO(xdna, "Unsupported op %d", op); ret = -EOPNOTSUPP; diff --git a/drivers/accel/amdxdna/aie2_msg_priv.h b/drivers/accel/amdxdna/aie2_msg_priv.h index 947daa63f064..1c957a6298d3 100644 --- a/drivers/accel/amdxdna/aie2_msg_priv.h +++ b/drivers/accel/amdxdna/aie2_msg_priv.h @@ -176,6 +176,8 @@ struct exec_dpu_req { enum exec_npu_type { EXEC_NPU_TYPE_NON_ELF = 0x1, EXEC_NPU_TYPE_PARTIAL_ELF = 0x2, + EXEC_NPU_TYPE_PREEMPT = 0x3, + EXEC_NPU_TYPE_ELF = 0x4, }; union exec_req { @@ -372,6 +374,7 @@ struct cmd_chain_slot_dpu { }; #define MAX_NPU_ARGS_SIZE (26 * sizeof(__u32)) +#define AIE2_EXEC_BUFFER_KERNEL_OP_TXN 3 struct cmd_chain_slot_npu { enum exec_npu_type type; u64 inst_buf_addr; diff --git a/drivers/accel/amdxdna/aie2_pci.c b/drivers/accel/amdxdna/aie2_pci.c index d7ccbdaf47f5..ceef1c502e9e 100644 --- a/drivers/accel/amdxdna/aie2_pci.c +++ b/drivers/accel/amdxdna/aie2_pci.c @@ -183,6 +183,10 @@ int aie2_runtime_cfg(struct amdxdna_dev_hdl *ndev, if (cfg->category != category) continue; + if (cfg->feature_mask && + bitmap_subset(&cfg->feature_mask, &ndev->feature_mask, AIE2_FEATURE_MAX)) + continue; + value = val ? *val : cfg->value; ret = aie2_set_runtime_cfg(ndev, cfg->type, value); if (ret) { @@ -932,6 +936,25 @@ static int aie2_get_telemetry(struct amdxdna_client *client, return 0; } +static int aie2_get_preempt_state(struct amdxdna_client *client, + struct amdxdna_drm_get_info *args) +{ + struct amdxdna_drm_attribute_state state = {}; + struct amdxdna_dev *xdna = client->xdna; + struct amdxdna_dev_hdl *ndev; + + ndev = xdna->dev_handle; + if (args->param == DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE) + state.state = ndev->force_preempt_enabled; + else if (args->param == DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE) + state.state = ndev->frame_boundary_preempt; + + if (copy_to_user(u64_to_user_ptr(args->buffer), &state, sizeof(state))) + return -EFAULT; + + return 0; +} + static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_info *args) { struct amdxdna_dev *xdna = client->xdna; @@ -972,6 +995,10 @@ static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_i case DRM_AMDXDNA_QUERY_RESOURCE_INFO: ret = aie2_query_resource_info(client, args); break; + case DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE: + case DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE: + ret = aie2_get_preempt_state(client, args); + break; default: XDNA_ERR(xdna, "Not supported request parameter %u", args->param); ret = -EOPNOTSUPP; @@ -1078,6 +1105,38 @@ static int aie2_set_power_mode(struct amdxdna_client *client, return aie2_pm_set_mode(xdna->dev_handle, power_mode); } +static int aie2_set_preempt_state(struct amdxdna_client *client, + struct amdxdna_drm_set_state *args) +{ + struct amdxdna_dev_hdl *ndev = client->xdna->dev_handle; + struct amdxdna_drm_attribute_state state; + u32 val; + int ret; + + if (copy_from_user(&state, u64_to_user_ptr(args->buffer), sizeof(state))) + return -EFAULT; + + if (state.state > 1) + return -EINVAL; + + if (XDNA_MBZ_DBG(client->xdna, state.pad, sizeof(state.pad))) + return -EINVAL; + + if (args->param == DRM_AMDXDNA_SET_FORCE_PREEMPT) { + ndev->force_preempt_enabled = state.state; + } else if (args->param == DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT) { + val = state.state; + ret = aie2_runtime_cfg(ndev, AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT, + &val); + if (ret) + return ret; + + ndev->frame_boundary_preempt = state.state; + } + + return 0; +} + static int aie2_set_state(struct amdxdna_client *client, struct amdxdna_drm_set_state *args) { @@ -1095,6 +1154,10 @@ static int aie2_set_state(struct amdxdna_client *client, case DRM_AMDXDNA_SET_POWER_MODE: ret = aie2_set_power_mode(client, args); break; + case DRM_AMDXDNA_SET_FORCE_PREEMPT: + case DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT: + ret = aie2_set_preempt_state(client, args); + break; default: XDNA_ERR(xdna, "Not supported request parameter %u", args->param); ret = -EOPNOTSUPP; diff --git a/drivers/accel/amdxdna/aie2_pci.h b/drivers/accel/amdxdna/aie2_pci.h index 9793cd1e0c55..a5f9c42155d1 100644 --- a/drivers/accel/amdxdna/aie2_pci.h +++ b/drivers/accel/amdxdna/aie2_pci.h @@ -110,12 +110,15 @@ struct aie_metadata { enum rt_config_category { AIE2_RT_CFG_INIT, AIE2_RT_CFG_CLK_GATING, + AIE2_RT_CFG_FORCE_PREEMPT, + AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT, }; struct rt_config { u32 type; u32 value; u32 category; + unsigned long feature_mask; }; struct dpm_clk_freq { @@ -164,6 +167,8 @@ struct aie2_exec_msg_ops { void (*init_chain_req)(void *req, u64 slot_addr, size_t size, u32 cmd_cnt); int (*fill_cf_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size); int (*fill_dpu_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size); + int (*fill_preempt_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size); + int (*fill_elf_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size); u32 (*get_chain_msg_op)(u32 cmd_op); }; @@ -197,6 +202,8 @@ struct amdxdna_dev_hdl { u32 hclk_freq; u32 max_tops; u32 curr_tops; + u32 force_preempt_enabled; + u32 frame_boundary_preempt; /* Mailbox and the management channel */ struct mailbox *mbox; @@ -223,6 +230,7 @@ struct aie2_hw_ops { enum aie2_fw_feature { AIE2_NPU_COMMAND, + AIE2_PREEMPT, AIE2_FEATURE_MAX }; diff --git a/drivers/accel/amdxdna/amdxdna_ctx.h b/drivers/accel/amdxdna/amdxdna_ctx.h index d02fb32499fa..b6151244d64f 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.h +++ b/drivers/accel/amdxdna/amdxdna_ctx.h @@ -16,6 +16,8 @@ enum ert_cmd_opcode { ERT_START_CU = 0, ERT_CMD_CHAIN = 19, ERT_START_NPU = 20, + ERT_START_NPU_PREEMPT = 21, + ERT_START_NPU_PREEMPT_ELF = 22, ERT_INVALID_CMD = ~0U, }; @@ -55,6 +57,21 @@ struct amdxdna_cmd_chain { u64 data[] __counted_by(command_count); }; +/* + * Interpretation of the beginning of data payload for ERT_START_NPU_PREEMPT in + * amdxdna_cmd. The rest of the payload in amdxdna_cmd is regular kernel args. + */ +struct amdxdna_cmd_preempt_data { + u64 inst_buf; /* instruction buffer address */ + u64 save_buf; /* save buffer address */ + u64 restore_buf; /* restore buffer address */ + u32 inst_size; /* size of instruction buffer in bytes */ + u32 save_size; /* size of save buffer in bytes */ + u32 restore_size; /* size of restore buffer in bytes */ + u32 inst_prop_cnt; /* properties count */ + u32 prop_args[]; /* properties and regular kernel arguments */ +}; + /* Exec buffer command header format */ #define AMDXDNA_CMD_STATE GENMASK(3, 0) #define AMDXDNA_CMD_EXTRA_CU_MASK GENMASK(11, 10) diff --git a/drivers/accel/amdxdna/amdxdna_pci_drv.c b/drivers/accel/amdxdna/amdxdna_pci_drv.c index 7590265d4485..1973ab67721b 100644 --- a/drivers/accel/amdxdna/amdxdna_pci_drv.c +++ b/drivers/accel/amdxdna/amdxdna_pci_drv.c @@ -31,9 +31,10 @@ MODULE_FIRMWARE("amdnpu/17f0_20/npu.sbin"); * 0.3: Support firmware debug buffer * 0.4: Support getting resource information * 0.5: Support getting telemetry data + * 0.6: Support preemption */ #define AMDXDNA_DRIVER_MAJOR 0 -#define AMDXDNA_DRIVER_MINOR 5 +#define AMDXDNA_DRIVER_MINOR 6 /* * Bind the driver base on (vendor_id, device_id) pair and later use the diff --git a/drivers/accel/amdxdna/npu4_regs.c b/drivers/accel/amdxdna/npu4_regs.c index d90777275a9f..986a5f28ba24 100644 --- a/drivers/accel/amdxdna/npu4_regs.c +++ b/drivers/accel/amdxdna/npu4_regs.c @@ -64,10 +64,13 @@ const struct rt_config npu4_default_rt_cfg[] = { { 5, 1, AIE2_RT_CFG_INIT }, /* PDI APP LOAD MODE */ { 10, 1, AIE2_RT_CFG_INIT }, /* DEBUG BUF */ + { 14, 0, AIE2_RT_CFG_INIT, BIT_U64(AIE2_PREEMPT) }, /* Frame boundary preemption */ { 1, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */ { 2, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */ { 3, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */ { 4, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */ + { 13, 0, AIE2_RT_CFG_FORCE_PREEMPT }, + { 14, 0, AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT }, { 0 }, }; @@ -85,6 +88,7 @@ const struct dpm_clk_freq npu4_dpm_clk_table[] = { const struct aie2_fw_feature_tbl npu4_fw_feature_table[] = { { .feature = AIE2_NPU_COMMAND, .min_minor = 15 }, + { .feature = AIE2_PREEMPT, .min_minor = 12 }, { 0 } }; diff --git a/include/uapi/drm/amdxdna_accel.h b/include/uapi/drm/amdxdna_accel.h index 8ad254bc35a5..62c917fd4f7b 100644 --- a/include/uapi/drm/amdxdna_accel.h +++ b/include/uapi/drm/amdxdna_accel.h @@ -443,7 +443,9 @@ enum amdxdna_drm_get_param { DRM_AMDXDNA_QUERY_FIRMWARE_VERSION = 8, DRM_AMDXDNA_GET_POWER_MODE, DRM_AMDXDNA_QUERY_TELEMETRY, - DRM_AMDXDNA_QUERY_RESOURCE_INFO = 12, + DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE, + DRM_AMDXDNA_QUERY_RESOURCE_INFO, + DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE, }; /** @@ -462,6 +464,16 @@ struct amdxdna_drm_get_resource_info { __u64 npu_task_curr; }; +/** + * struct amdxdna_drm_attribute_state - State of an attribute + */ +struct amdxdna_drm_attribute_state { + /** @state: enabled or disabled */ + __u8 state; + /** @pad: MBZ */ + __u8 pad[7]; +}; + /** * struct amdxdna_drm_query_telemetry_header - Telemetry data header */ @@ -613,6 +625,8 @@ enum amdxdna_drm_set_param { DRM_AMDXDNA_SET_POWER_MODE, DRM_AMDXDNA_WRITE_AIE_MEM, DRM_AMDXDNA_WRITE_AIE_REG, + DRM_AMDXDNA_SET_FORCE_PREEMPT, + DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT, }; /** From 3301ef0a72b4b43a65914747150c076f52b39344 Mon Sep 17 00:00:00 2001 From: Youssef Samir Date: Wed, 29 Oct 2025 11:18:12 -0700 Subject: [PATCH 03/71] accel/qaic: Add support for PM callbacks Add initial support for suspend and hibernation PM callbacks to QAIC. The device can be suspended any time in which the data path is not busy as queued I/O operations are lost on suspension and cannot be resumed after suspend. Signed-off-by: Youssef Samir Reviewed-by: Carl Vanderlip Signed-off-by: Zack McKevitt Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251029181808.1216466-1-zachary.mckevitt@oss.qualcomm.com --- drivers/accel/qaic/qaic.h | 2 + drivers/accel/qaic/qaic_drv.c | 89 ++++++++++++++++++++++++++++++ drivers/accel/qaic/qaic_timesync.c | 9 +++ drivers/accel/qaic/qaic_timesync.h | 3 + 4 files changed, 103 insertions(+) diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index 820d133236dd..2bfc4ce203c5 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -161,6 +161,8 @@ struct qaic_device { struct mhi_device *qts_ch; /* Work queue for tasks related to MHI "QAIC_TIMESYNC" channel */ struct workqueue_struct *qts_wq; + /* MHI "QAIC_TIMESYNC_PERIODIC" channel device */ + struct mhi_device *mqts_ch; /* Head of list of page allocated by MHI bootlog device */ struct list_head bootlog; /* MHI bootlog channel device */ diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index e162f4b8a262..1b5dc717a6c7 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -660,6 +660,92 @@ static const struct pci_error_handlers qaic_pci_err_handler = { .reset_done = qaic_pci_reset_done, }; +static bool qaic_is_under_reset(struct qaic_device *qdev) +{ + int rcu_id; + bool ret; + + rcu_id = srcu_read_lock(&qdev->dev_lock); + ret = qdev->dev_state != QAIC_ONLINE; + srcu_read_unlock(&qdev->dev_lock, rcu_id); + return ret; +} + +static bool qaic_data_path_busy(struct qaic_device *qdev) +{ + bool ret = false; + int dev_rcu_id; + int i; + + dev_rcu_id = srcu_read_lock(&qdev->dev_lock); + if (qdev->dev_state != QAIC_ONLINE) { + srcu_read_unlock(&qdev->dev_lock, dev_rcu_id); + return false; + } + for (i = 0; i < qdev->num_dbc; i++) { + struct dma_bridge_chan *dbc = &qdev->dbc[i]; + unsigned long flags; + int ch_rcu_id; + + ch_rcu_id = srcu_read_lock(&dbc->ch_lock); + if (!dbc->usr || !dbc->in_use) { + srcu_read_unlock(&dbc->ch_lock, ch_rcu_id); + continue; + } + spin_lock_irqsave(&dbc->xfer_lock, flags); + ret = !list_empty(&dbc->xfer_list); + spin_unlock_irqrestore(&dbc->xfer_lock, flags); + srcu_read_unlock(&dbc->ch_lock, ch_rcu_id); + if (ret) + break; + } + srcu_read_unlock(&qdev->dev_lock, dev_rcu_id); + return ret; +} + +static int qaic_pm_suspend(struct device *dev) +{ + struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev)); + + dev_dbg(dev, "Suspending..\n"); + if (qaic_data_path_busy(qdev)) { + dev_dbg(dev, "Device's datapath is busy. Aborting suspend..\n"); + return -EBUSY; + } + if (qaic_is_under_reset(qdev)) { + dev_dbg(dev, "Device is under reset. Aborting suspend..\n"); + return -EBUSY; + } + qaic_mqts_ch_stop_timer(qdev->mqts_ch); + qaic_pci_reset_prepare(qdev->pdev); + pci_save_state(qdev->pdev); + pci_disable_device(qdev->pdev); + pci_set_power_state(qdev->pdev, PCI_D3hot); + return 0; +} + +static int qaic_pm_resume(struct device *dev) +{ + struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev)); + int ret; + + dev_dbg(dev, "Resuming..\n"); + pci_set_power_state(qdev->pdev, PCI_D0); + pci_restore_state(qdev->pdev); + ret = pci_enable_device(qdev->pdev); + if (ret) { + dev_err(dev, "pci_enable_device failed on resume %d\n", ret); + return ret; + } + pci_set_master(qdev->pdev); + qaic_pci_reset_done(qdev->pdev); + return 0; +} + +static const struct dev_pm_ops qaic_pm_ops = { + SYSTEM_SLEEP_PM_OPS(qaic_pm_suspend, qaic_pm_resume) +}; + static struct pci_driver qaic_pci_driver = { .name = QAIC_NAME, .id_table = qaic_ids, @@ -667,6 +753,9 @@ static struct pci_driver qaic_pci_driver = { .remove = qaic_pci_remove, .shutdown = qaic_pci_shutdown, .err_handler = &qaic_pci_err_handler, + .driver = { + .pm = pm_sleep_ptr(&qaic_pm_ops), + }, }; static int __init qaic_init(void) diff --git a/drivers/accel/qaic/qaic_timesync.c b/drivers/accel/qaic/qaic_timesync.c index 3fac540f8e03..8af2475f4f36 100644 --- a/drivers/accel/qaic/qaic_timesync.c +++ b/drivers/accel/qaic/qaic_timesync.c @@ -171,6 +171,13 @@ static void qaic_timesync_timer(struct timer_list *t) dev_err(mqtsdev->dev, "%s mod_timer error:%d\n", __func__, ret); } +void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev) +{ + struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev); + + timer_delete_sync(&mqtsdev->timer); +} + static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id) { struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev)); @@ -206,6 +213,7 @@ static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_devi timer->expires = jiffies + msecs_to_jiffies(timesync_delay_ms); add_timer(timer); dev_set_drvdata(&mhi_dev->dev, mqtsdev); + qdev->mqts_ch = mhi_dev; return 0; @@ -221,6 +229,7 @@ static void qaic_timesync_remove(struct mhi_device *mhi_dev) { struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev); + mqtsdev->qdev->mqts_ch = NULL; timer_delete_sync(&mqtsdev->timer); mhi_unprepare_from_transfer(mqtsdev->mhi_dev); kfree(mqtsdev->sync_msg); diff --git a/drivers/accel/qaic/qaic_timesync.h b/drivers/accel/qaic/qaic_timesync.h index 851b7acd43bb..77b9c2b55057 100644 --- a/drivers/accel/qaic/qaic_timesync.h +++ b/drivers/accel/qaic/qaic_timesync.h @@ -6,6 +6,9 @@ #ifndef __QAIC_TIMESYNC_H__ #define __QAIC_TIMESYNC_H__ +#include + int qaic_timesync_init(void); void qaic_timesync_deinit(void); +void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev); #endif /* __QAIC_TIMESYNC_H__ */ From b6fa6100cec0287856ec1b363b0a962a9be90e6c Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Oct 2025 22:41:51 -0700 Subject: [PATCH 04/71] drm/panfrost: fix UAPI kernel-doc warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix all kernel-doc warnings in include/uapi/drm/panfrost_drm.h. This mostly means modifying existing comments to conform to kernel-doc format, but there also some additions of missing kernel-doc comments and changing non-kernel-doc comments to use "/*" to begin them. Warning: panfrost_drm.h:83 struct member 'jc' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'in_syncs' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'in_sync_count' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'out_sync' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'bo_handles' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'bo_handle_count' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'requirements' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'jm_ctx_handle' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:83 struct member 'pad' not described in 'drm_panfrost_submit' Warning: panfrost_drm.h:116 Incorrect use of kernel-doc format: * Returned offset for the BO in the GPU address space. This offset Warning: panfrost_drm.h:124 struct member 'size' not described in 'drm_panfrost_create_bo' Warning: panfrost_drm.h:124 struct member 'flags' not described in 'drm_panfrost_create_bo' Warning: panfrost_drm.h:124 struct member 'handle' not described in 'drm_panfrost_create_bo' Warning: panfrost_drm.h:124 struct member 'pad' not described in 'drm_panfrost_create_bo' Warning: panfrost_drm.h:124 struct member 'nonzero' not described in 'drm_panfrost_create_bo' Warning: panfrost_drm.h:143 struct member 'handle' not described in 'drm_panfrost_mmap_bo' Warning: panfrost_drm.h:143 struct member 'flags' not described in 'drm_panfrost_mmap_bo' Warning: panfrost_drm.h:143 struct member 'offset' not described in 'drm_panfrost_mmap_bo' Signed-off-by: Randy Dunlap Reviewed-by: Steven Price Reviewed-by: Adrián Larumbe Signed-off-by: Steven Price Link: https://patch.msgid.link/20251031054152.1406764-1-rdunlap@infradead.org --- include/uapi/drm/panfrost_drm.h | 118 ++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/include/uapi/drm/panfrost_drm.h b/include/uapi/drm/panfrost_drm.h index e8b47c9f6976..1956431bb391 100644 --- a/include/uapi/drm/panfrost_drm.h +++ b/include/uapi/drm/panfrost_drm.h @@ -54,32 +54,46 @@ extern "C" { * This asks the kernel to have the GPU execute a render command list. */ struct drm_panfrost_submit { - - /** Address to GPU mapping of job descriptor */ + /** + * @jc: Address to GPU mapping of job descriptor + */ __u64 jc; - - /** An optional array of sync objects to wait on before starting this job. */ + /** + * @in_syncs: An optional array of sync objects to wait on + * before starting this job. + */ __u64 in_syncs; - - /** Number of sync objects to wait on before starting this job. */ + /** + * @in_sync_count: Number of sync objects to wait on before + * starting this job. + */ __u32 in_sync_count; - - /** An optional sync object to place the completion fence in. */ + /** + * @out_sync: An optional sync object to place the completion fence in. + */ __u32 out_sync; - - /** Pointer to a u32 array of the BOs that are referenced by the job. */ + /** + * @bo_handles: Pointer to a u32 array of the BOs that are + * referenced by the job. + */ __u64 bo_handles; - - /** Number of BO handles passed in (size is that times 4). */ + /** + * @bo_handle_count: Number of BO handles passed in (size is + * that times 4). + */ __u32 bo_handle_count; - - /** A combination of PANFROST_JD_REQ_* */ + /** + * @requirements: A combination of PANFROST_JD_REQ_* + */ __u32 requirements; - - /** JM context handle. Zero if you want to use the default context. */ + /** + * @jm_ctx_handle: JM context handle. Zero if you want to use the + * default context. + */ __u32 jm_ctx_handle; - - /** Padding field. MBZ. */ + /** + * @pad: Padding field. Must be zero. + */ __u32 pad; }; @@ -92,9 +106,18 @@ struct drm_panfrost_submit { * completed. */ struct drm_panfrost_wait_bo { + /** + * @handle: Handle for the object to wait for. + */ __u32 handle; + /** + * @pad: Padding, must be zero-filled. + */ __u32 pad; - __s64 timeout_ns; /* absolute */ + /** + * @timeout_ns: absolute number of nanoseconds to wait. + */ + __s64 timeout_ns; }; /* Valid flags to pass to drm_panfrost_create_bo */ @@ -107,16 +130,26 @@ struct drm_panfrost_wait_bo { * The flags argument is a bit mask of PANFROST_BO_* flags. */ struct drm_panfrost_create_bo { + /** + * @size: size of shmem/BO area to create (bytes) + */ __u32 size; + /** + * @flags: see PANFROST_BO_* flags + */ __u32 flags; - /** Returned GEM handle for the BO. */ + /** + * @handle: Returned GEM handle for the BO. + */ __u32 handle; - /* Pad, must be zero-filled. */ + /** + * @pad: Padding, must be zero-filled. + */ __u32 pad; /** - * Returned offset for the BO in the GPU address space. This offset - * is private to the DRM fd and is valid for the lifetime of the GEM - * handle. + * @offset: Returned offset for the BO in the GPU address space. + * This offset is private to the DRM fd and is valid for the + * lifetime of the GEM handle. * * This offset value will always be nonzero, since various HW * units treat 0 specially. @@ -136,10 +169,17 @@ struct drm_panfrost_create_bo { * used in a future extension. */ struct drm_panfrost_mmap_bo { - /** Handle for the object being mapped. */ + /** + * @handle: Handle for the object being mapped. + */ __u32 handle; + /** + * @flags: currently not used (should be zero) + */ __u32 flags; - /** offset into the drm node to use for subsequent mmap call. */ + /** + * @offset: offset into the drm node to use for subsequent mmap call. + */ __u64 offset; }; @@ -196,7 +236,7 @@ struct drm_panfrost_get_param { __u64 value; }; -/** +/* * Returns the offset for the BO in the GPU address space for this DRM fd. * This is the same value returned by drm_panfrost_create_bo, if that was called * from this DRM fd. @@ -244,12 +284,14 @@ struct drm_panfrost_madvise { * struct drm_panfrost_set_label_bo - ioctl argument for labelling Panfrost BOs. */ struct drm_panfrost_set_label_bo { - /** @handle: Handle of the buffer object to label. */ + /** + * @handle: Handle of the buffer object to label. + */ __u32 handle; - - /** @pad: MBZ. */ + /** + * @pad: Must be zero. + */ __u32 pad; - /** * @label: User pointer to a NUL-terminated string * @@ -330,10 +372,13 @@ enum drm_panfrost_jm_ctx_priority { }; struct drm_panfrost_jm_ctx_create { - /** @handle: Handle of the created JM context */ + /** + * @handle: Handle of the created JM context + */ __u32 handle; - - /** @priority: Context priority (see enum drm_panfrost_jm_ctx_priority). */ + /** + * @priority: Context priority (see enum drm_panfrost_jm_ctx_priority). + */ __u32 priority; }; @@ -344,8 +389,9 @@ struct drm_panfrost_jm_ctx_destroy { * Must be a valid context handle returned by DRM_IOCTL_PANTHOR_JM_CTX_CREATE. */ __u32 handle; - - /** @pad: Padding field, MBZ. */ + /** + * @pad: Padding field, must be zero. + */ __u32 pad; }; From 3ca8b2668ca5a410c23a7a7932cc406ebbb89ab2 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Thu, 6 Nov 2025 07:52:17 +0700 Subject: [PATCH 05/71] drm/ttm: Fix @alloc_flags description MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stephen Rothwell reports htmldocs warnings when merging drm-misc tree: Documentation/gpu/drm-mm:40: include/drm/ttm/ttm_device.h:225: ERROR: Unknown target name: "ttm_allocation". [docutils] Documentation/gpu/drm-mm:43: drivers/gpu/drm/ttm/ttm_device.c:202: ERROR: Unknown target name: "ttm_allocation". [docutils] Documentation/gpu/drm-mm:73: include/drm/ttm/ttm_pool.h:68: ERROR: Unknown target name: "ttm_allocation_pool". [docutils] Documentation/gpu/drm-mm:76: drivers/gpu/drm/ttm/ttm_pool.c:1070: ERROR: Unknown target name: "ttm_allocation_pool". [docutils] Fix these by adding missing wildcard on TTM_ALLOCATION_* and TTM_ALLOCATION_POOL_* in @alloc_flags description. Fixes: 0af5b6a8f8dd ("drm/ttm: Replace multiple booleans with flags in pool init") Fixes: 77e19f8d3297 ("drm/ttm: Replace multiple booleans with flags in device init") Fixes: 402b3a865090 ("drm/ttm: Add an allocation flag to propagate -ENOSPC on OOM") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/linux-next/20251105161838.55b962a3@canb.auug.org.au/ Signed-off-by: Bagas Sanjaya Acked-by: Tvrtko Ursulin Reviewed-by: Christian König Signed-off-by: Tvrtko Ursulin Link: https://lore.kernel.org/r/20251106005217.14026-1-bagasdotme@gmail.com --- drivers/gpu/drm/ttm/ttm_device.c | 2 +- drivers/gpu/drm/ttm/ttm_pool.c | 2 +- include/drm/ttm/ttm_device.h | 2 +- include/drm/ttm/ttm_pool.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/ttm/ttm_device.c b/drivers/gpu/drm/ttm/ttm_device.c index 5c10e5fbf43b..9a51afaf0749 100644 --- a/drivers/gpu/drm/ttm/ttm_device.c +++ b/drivers/gpu/drm/ttm/ttm_device.c @@ -199,7 +199,7 @@ EXPORT_SYMBOL(ttm_device_swapout); * @dev: The core kernel device pointer for DMA mappings and allocations. * @mapping: The address space to use for this bo. * @vma_manager: A pointer to a vma manager. - * @alloc_flags: TTM_ALLOCATION_ flags. + * @alloc_flags: TTM_ALLOCATION_* flags. * * Initializes a struct ttm_device: * Returns: diff --git a/drivers/gpu/drm/ttm/ttm_pool.c b/drivers/gpu/drm/ttm/ttm_pool.c index 97e9ce505cf6..18b6db015619 100644 --- a/drivers/gpu/drm/ttm/ttm_pool.c +++ b/drivers/gpu/drm/ttm/ttm_pool.c @@ -1067,7 +1067,7 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt, * @pool: the pool to initialize * @dev: device for DMA allocations and mappings * @nid: NUMA node to use for allocations - * @alloc_flags: TTM_ALLOCATION_POOL_ flags + * @alloc_flags: TTM_ALLOCATION_POOL_* flags * * Initialize the pool and its pool types. */ diff --git a/include/drm/ttm/ttm_device.h b/include/drm/ttm/ttm_device.h index d016360e5ceb..5618aef462f2 100644 --- a/include/drm/ttm/ttm_device.h +++ b/include/drm/ttm/ttm_device.h @@ -221,7 +221,7 @@ struct ttm_device { struct list_head device_list; /** - * @alloc_flags: TTM_ALLOCATION_ flags. + * @alloc_flags: TTM_ALLOCATION_* flags. */ unsigned int alloc_flags; diff --git a/include/drm/ttm/ttm_pool.h b/include/drm/ttm/ttm_pool.h index 67c72de913bb..233581670e78 100644 --- a/include/drm/ttm/ttm_pool.h +++ b/include/drm/ttm/ttm_pool.h @@ -64,7 +64,7 @@ struct ttm_pool_type { * * @dev: the device we allocate pages for * @nid: which numa node to use - * @alloc_flags: TTM_ALLOCATION_POOL_ flags + * @alloc_flags: TTM_ALLOCATION_POOL_* flags * @caching: pools for each caching/order */ struct ttm_pool { From f23e40e378a0858da26e8d5a6f09f82ecd95e247 Mon Sep 17 00:00:00 2001 From: Ajye Huang Date: Sat, 1 Nov 2025 12:00:43 +0800 Subject: [PATCH 06/71] drm/edid: add 6 bpc quirk to the Sharp LQ116M1JW10 The Sharp LQ116M1JW105 reports that it supports 8 bpc modes, but it will happen display noise in some videos. So, limit it to 6 bpc modes. Signed-off-by: Ajye Huang Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patch.msgid.link/20251101040043.3768848-1-ajye_huang@compal.corp-partner.google.com --- drivers/gpu/drm/drm_edid.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index e2e85345aa9a..26bb7710a462 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -250,6 +250,9 @@ static const struct edid_quirk { EDID_QUIRK('S', 'V', 'R', 0x1019, BIT(EDID_QUIRK_NON_DESKTOP)), EDID_QUIRK('A', 'U', 'O', 0x1111, BIT(EDID_QUIRK_NON_DESKTOP)), + /* LQ116M1JW10 displays noise when 8 bpc, but display fine as 6 bpc */ + EDID_QUIRK('S', 'H', 'P', 0x154c, BIT(EDID_QUIRK_FORCE_6BPC)), + /* * @drm_edid_internal_quirk entries end here, following with the * @drm_edid_quirk entries. From dea9f84776b96a703f504631ebe9fea07bd2c181 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Wed, 5 Nov 2025 11:41:40 -0800 Subject: [PATCH 07/71] accel/amdxdna: Fix dma_fence leak when job is canceled Currently, dma_fence_put(job->fence) is called in job notification callback. However, if a job is canceled, the notification callback is never invoked, leading to a memory leak. Move dma_fence_put(job->fence) to the job cleanup function to ensure the fence is always released. Fixes: aac243092b70 ("accel/amdxdna: Add command execution") Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251105194140.1004314-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_ctx.c | 1 - drivers/accel/amdxdna/amdxdna_ctx.c | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index b78c47ed0d34..bdc90fe8a47e 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -189,7 +189,6 @@ aie2_sched_notify(struct amdxdna_sched_job *job) up(&job->hwctx->priv->job_sem); job->job_done = true; - dma_fence_put(fence); mmput_async(job->mm); aie2_job_put(job); } diff --git a/drivers/accel/amdxdna/amdxdna_ctx.c b/drivers/accel/amdxdna/amdxdna_ctx.c index 878cc955f56d..d17aef89a0ad 100644 --- a/drivers/accel/amdxdna/amdxdna_ctx.c +++ b/drivers/accel/amdxdna/amdxdna_ctx.c @@ -422,6 +422,7 @@ void amdxdna_sched_job_cleanup(struct amdxdna_sched_job *job) trace_amdxdna_debug_point(job->hwctx->name, job->seq, "job release"); amdxdna_arg_bos_put(job); amdxdna_gem_put_obj(job->cmd_bo); + dma_fence_put(job->fence); } int amdxdna_cmd_submit(struct amdxdna_client *client, From 83646541d197fe732b6fb7503557e4989f9e3551 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 6 Nov 2025 16:01:21 +0100 Subject: [PATCH 08/71] drm/sched: Replace use of system_wq with system_percpu_wq In the general workqueue implementation, if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. For more details see the Link tag below. This continues the effort to refactor worqueue APIs, which has begun with the change introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") Use the successor of system_wq, system_percpu_wq, for the scheduler's default timeout_wq. system_wq will be removed in a few release cycles. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Philipp Stanner Link: https://patch.msgid.link/20251106150121.256367-1-marco.crivellari@suse.com --- drivers/gpu/drm/scheduler/sched_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index 2463ced2427a..1d4f1b822e7b 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -1320,7 +1320,7 @@ int drm_sched_init(struct drm_gpu_scheduler *sched, const struct drm_sched_init_ sched->name = args->name; sched->timeout = args->timeout; sched->hang_limit = args->hang_limit; - sched->timeout_wq = args->timeout_wq ? args->timeout_wq : system_wq; + sched->timeout_wq = args->timeout_wq ? args->timeout_wq : system_percpu_wq; sched->score = args->score ? args->score : &sched->_score; sched->dev = args->dev; From 3946d3ba99342f3b9996e621f05e7003d4308171 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Thu, 6 Nov 2025 08:31:58 +0100 Subject: [PATCH 09/71] drm/vblank: Fix kernel docs for vblank timer Fix documentation for drm_crtc_vblank_start_timer(), which referred to drm_crtc_vblank_cancel_timer(). Signed-off-by: Thomas Zimmermann Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/dri-devel/20251106152201.6f248c09@canb.auug.org.au/ Fixes: 74afeb812850 ("drm/vblank: Add vblank timer") Cc: Thomas Zimmermann Cc: Louis Chauvet Cc: Javier Martinez Canillas Cc: David Airlie Cc: Simona Vetter Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: dri-devel@lists.freedesktop.org Reviewed-by: Louis Chauvet Link: https://patch.msgid.link/20251106073207.11192-1-tzimmermann@suse.de --- drivers/gpu/drm/drm_vblank.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c index 61e211fd3c9c..451ec9620226 100644 --- a/drivers/gpu/drm/drm_vblank.c +++ b/drivers/gpu/drm/drm_vblank.c @@ -2258,7 +2258,7 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc) EXPORT_SYMBOL(drm_crtc_vblank_start_timer); /** - * drm_crtc_vblank_start_timer - Cancels the given CRTC's vblank timer + * drm_crtc_vblank_cancel_timer - Cancels the given CRTC's vblank timer * @crtc: the CRTC * * Drivers should call this function from their CRTC's disable_vblank From f050da08a4edfcccb92bbc93eafb9723286a5ce2 Mon Sep 17 00:00:00 2001 From: Chintan Patel Date: Mon, 27 Oct 2025 20:43:37 -0700 Subject: [PATCH 10/71] drm/vblank: Increase timeout in drm_wait_one_vblank() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, wait_event_timeout() in drm_wait_one_vblank() uses a 100ms timeout. Under heavy scheduling pressure or rare delayed vblank handling, this can trigger WARNs unnecessarily. Increase the timeout to 1000ms to reduce spurious WARNs, while still catching genuine issues. Reported-by: syzbot+147ba789658184f0ce04@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=147ba789658184f0ce04 Tested-by: syzbot+147ba789658184f0ce04@syzkaller.appspotmail.com Signed-off-by: Chintan Patel v2: - Dropped unnecessary in-code comment (suggested by Thomas Zimmermann) - Removed else branch, only log timeout case v3: - Replaced drm_dbg_kms()/manual logging with drm_err() (suggested by Ville Syrjälä) - Removed unnecessary curr = drm_vblank_count() (suggested by Thomas Zimmermann) - Fixed commit message wording ("invalid userspace calls" → "delayed vblank handling") v4: - Keep the original drm_WARN() to catch genuine kernel issues - Increased timeout from 100ms → 1000ms to reduce spurious WARNs (suggested by Thomas Zimmermann) Reviewed-by: Thomas Zimmermann Signed-off-by: Thomas Zimmermann Link: https://patch.msgid.link/20251028034337.6341-1-chintanlike@gmail.com --- drivers/gpu/drm/drm_vblank.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_vblank.c b/drivers/gpu/drm/drm_vblank.c index 451ec9620226..32d013c5c8fc 100644 --- a/drivers/gpu/drm/drm_vblank.c +++ b/drivers/gpu/drm/drm_vblank.c @@ -1315,7 +1315,7 @@ void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe) ret = wait_event_timeout(vblank->queue, last != drm_vblank_count(dev, pipe), - msecs_to_jiffies(100)); + msecs_to_jiffies(1000)); drm_WARN(dev, ret == 0, "vblank wait timed out on crtc %i\n", pipe); From 9942d36a73c2d46c52fdd6f37cf698f3cb09ce5c Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 4 Nov 2025 11:36:04 +0100 Subject: [PATCH 11/71] drm/vmwgfx: Set surface-framebuffer GEM objects Set struct drm_framebuffer.obj[0] to the allocated GEM buffer object for surface framebuffers. Avoids a NULL-pointer deref in the client's vmap helpers. [ 22.640191] Console: switching to colour frame buffer device 160x50 [ 22.641788] Oops: general protection fault, probably for non-canonical address 0xdffffc000000001f: 0000 [#1] SMP KASAN NOPTI [ 22.641795] KASAN: null-ptr-deref in range [0x00000000000000f8-0x00000000000000ff] [...] [ 22.641809] Hardware name: VMware, Inc. VMware20,1/440BX Desktop Reference Platform, BIOS VMW201.00V.24928539.B64.2508260915 08/26/2025 [ 22.641812] Workqueue: events drm_fb_helper_damage_work [ 22.641824] RIP: 0010:drm_gem_lock+0x25/0x50 [ 22.641831] Code: 90 90 90 90 90 f3 0f 1e fa 0f 1f 44 00 00 48 b8 00 00 00 00 00 fc ff df 53 48 89 fb 48 81 c7 f8 00 00 00 48 89 fa 48 c1 ea 03 <80> 3c 02 00 75 0f 48 8b bb f8 00 00 00 31 f6 5b e9 16 2e 15 01 e8 [...] [ 22.641889] Call Trace: [ 22.641891] [ 22.641894] drm_client_buffer_vmap_local+0x78/0x140 [ 22.641903] drm_fbdev_ttm_helper_fb_dirty+0x20c/0x510 [drm_ttm_helper] [ 22.641913] ? __pfx_drm_fbdev_ttm_helper_fb_dirty+0x10/0x10 [drm_ttm_helper] [ 22.641918] ? __raw_spin_lock_irqsave+0x8c/0xf0 [ 22.641924] ? __pfx___raw_spin_lock_irqsave+0x10/0x10 [ 22.641928] ? __pfx_mutex_lock+0x10/0x10 [ 22.641936] drm_fb_helper_fb_dirty+0x29a/0x5e0 [ 22.641942] ? __pfx_drm_fb_helper_fb_dirty+0x10/0x10 [...] Signed-off-by: Thomas Zimmermann Fixes: ea39f2e66e61 ("drm/client: Deprecate struct drm_client_buffer.gem") Reported-by: Ian Forbes Closes: https://lore.kernel.org/dri-devel/CAO6MGtjg8PiRiSLomJQRBduTBSC0WkqX67tEZwA9qwOgRzchpw@mail.gmail.com/ Cc: Thomas Zimmermann Cc: Jocelyn Falempe Cc: Maarten Lankhorst Cc: Maxime Ripard Cc: David Airlie Cc: Simona Vetter Cc: dri-devel@lists.freedesktop.org Tested-by: Ian Forbes Reviewed-by: Jocelyn Falempe Reviewed-by: Zack Rusin Link: https://patch.msgid.link/20251104103611.167821-1-tzimmermann@suse.de --- drivers/gpu/drm/vmwgfx/vmwgfx_kms.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c index 54ea1b513950..d32ce1cb579e 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_kms.c @@ -553,6 +553,9 @@ static int vmw_kms_new_framebuffer_surface(struct vmw_private *dev_priv, memcpy(&vfbs->uo, uo, sizeof(vfbs->uo)); vmw_user_object_ref(&vfbs->uo); + if (vfbs->uo.buffer) + vfbs->base.base.obj[0] = &vfbs->uo.buffer->tbo.base; + *out = &vfbs->base; ret = drm_framebuffer_init(dev, &vfbs->base.base, From 1750e652a996382a8608ec70167c3719d0556bda Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Thu, 6 Nov 2025 10:05:21 -0800 Subject: [PATCH 12/71] accel/amdxdna: Treat power-off failure as unrecoverable error Failing to set power off indicates an unrecoverable hardware or firmware error. Update the driver to treat such a failure as a fatal condition and stop further operations that depend on successful power state transition. This prevents undefined behavior when the hardware remains in an unexpected state after a failed power-off attempt. Reviewed-by: Mario Limonciello (AMD) Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251106180521.1095218-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_smu.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/accel/amdxdna/aie2_smu.c b/drivers/accel/amdxdna/aie2_smu.c index 11c0e9e7b03a..bd94ee96c2bc 100644 --- a/drivers/accel/amdxdna/aie2_smu.c +++ b/drivers/accel/amdxdna/aie2_smu.c @@ -147,6 +147,16 @@ int aie2_smu_init(struct amdxdna_dev_hdl *ndev) { int ret; + /* + * Failing to set power off indicates an unrecoverable hardware or + * firmware error. + */ + ret = aie2_smu_exec(ndev, AIE2_SMU_POWER_OFF, 0, NULL); + if (ret) { + XDNA_ERR(ndev->xdna, "Access power failed, ret %d", ret); + return ret; + } + ret = aie2_smu_exec(ndev, AIE2_SMU_POWER_ON, 0, NULL); if (ret) { XDNA_ERR(ndev->xdna, "Power on failed, ret %d", ret); From f286066ed9df38637eb6c12fb2856f1e0b9731d4 Mon Sep 17 00:00:00 2001 From: Pranjal Ramajor Asha Kanojiya Date: Fri, 31 Oct 2025 10:41:00 -0700 Subject: [PATCH 13/71] accel/qaic: Add DMA Bridge Channel(DBC) sysfs and uevents Expose sysfs files for each DBC representing the current state of that DBC. For example, sysfs for DBC ID 0 and accel minor number 0 looks like this, /sys/class/accel/accel0/dbc0_state Following are the states and their corresponding values, DBC_STATE_IDLE (0) DBC_STATE_ASSIGNED (1) DBC_STATE_BEFORE_SHUTDOWN (2) DBC_STATE_AFTER_SHUTDOWN (3) DBC_STATE_BEFORE_POWER_UP (4) DBC_STATE_AFTER_POWER_UP (5) Signed-off-by: Pranjal Ramajor Asha Kanojiya Signed-off-by: Youssef Samir Signed-off-by: Zack McKevitt Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251031174059.2814445-2-zachary.mckevitt@oss.qualcomm.com --- Documentation/ABI/stable/sysfs-driver-qaic | 16 +++ drivers/accel/qaic/Makefile | 1 + drivers/accel/qaic/qaic.h | 25 +++++ drivers/accel/qaic/qaic_control.c | 2 + drivers/accel/qaic/qaic_drv.c | 8 ++ drivers/accel/qaic/qaic_sysfs.c | 109 +++++++++++++++++++++ 6 files changed, 161 insertions(+) create mode 100644 Documentation/ABI/stable/sysfs-driver-qaic create mode 100644 drivers/accel/qaic/qaic_sysfs.c diff --git a/Documentation/ABI/stable/sysfs-driver-qaic b/Documentation/ABI/stable/sysfs-driver-qaic new file mode 100644 index 000000000000..e5876935e62b --- /dev/null +++ b/Documentation/ABI/stable/sysfs-driver-qaic @@ -0,0 +1,16 @@ +What: /sys/bus/pci/drivers/qaic/XXXX:XX:XX.X/accel/accel/dbc_state +Date: October 2025 +KernelVersion: 6.19 +Contact: Jeff Hugo +Description: Represents the current state of DMA Bridge channel (DBC). Below are the possible + states, + IDLE (0) - DBC is free and can be activated + ASSIGNED (1) - DBC is activated and a workload is running on device + BEFORE_SHUTDOWN (2) - Sub-system associated with this workload has crashed and + it will shutdown soon + AFTER_SHUTDOWN (3) - Sub-system associated with this workload has crashed and + it has shutdown + BEFORE_POWER_UP (4) - Sub-system associated with this workload is shutdown and + it will be powered up soon + AFTER_POWER_UP (5) - Sub-system associated with this workload is now powered up +Users: Any userspace application or clients interested in DBC state. diff --git a/drivers/accel/qaic/Makefile b/drivers/accel/qaic/Makefile index 1106b876f737..8f6746e5f03a 100644 --- a/drivers/accel/qaic/Makefile +++ b/drivers/accel/qaic/Makefile @@ -11,6 +11,7 @@ qaic-y := \ qaic_data.o \ qaic_drv.o \ qaic_ras.o \ + qaic_sysfs.o \ qaic_timesync.o \ sahara.o diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index 2bfc4ce203c5..cbaec577c457 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -47,6 +47,22 @@ enum __packed dev_states { QAIC_ONLINE, }; +enum dbc_states { + /* DBC is free and can be activated */ + DBC_STATE_IDLE, + /* DBC is activated and a workload is running on device */ + DBC_STATE_ASSIGNED, + /* Sub-system associated with this workload has crashed and it will shutdown soon */ + DBC_STATE_BEFORE_SHUTDOWN, + /* Sub-system associated with this workload has crashed and it has shutdown */ + DBC_STATE_AFTER_SHUTDOWN, + /* Sub-system associated with this workload is shutdown and it will be powered up soon */ + DBC_STATE_BEFORE_POWER_UP, + /* Sub-system associated with this workload is now powered up */ + DBC_STATE_AFTER_POWER_UP, + DBC_STATE_MAX, +}; + extern bool datapath_polling; struct qaic_user { @@ -114,6 +130,8 @@ struct dma_bridge_chan { unsigned int irq; /* Polling work item to simulate interrupts */ struct work_struct poll_work; + /* Represents various states of this DBC from enum dbc_states */ + unsigned int state; }; struct qaic_device { @@ -197,6 +215,8 @@ struct qaic_drm_device { struct list_head users; /* Synchronizes access to users list */ struct mutex users_mutex; + /* Pointer to array of DBC sysfs attributes */ + void *sysfs_attrs; }; struct qaic_bo { @@ -321,4 +341,9 @@ int qaic_perf_stats_bo_ioctl(struct drm_device *dev, void *data, struct drm_file int qaic_detach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); void irq_polling_work(struct work_struct *work); +/* qaic_sysfs.c */ +int qaic_sysfs_init(struct qaic_drm_device *qddev); +void qaic_sysfs_remove(struct qaic_drm_device *qddev); +void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state); + #endif /* _QAIC_H_ */ diff --git a/drivers/accel/qaic/qaic_control.c b/drivers/accel/qaic/qaic_control.c index 49b6e75ef82a..428d8f65bff3 100644 --- a/drivers/accel/qaic/qaic_control.c +++ b/drivers/accel/qaic/qaic_control.c @@ -310,6 +310,7 @@ static void save_dbc_buf(struct qaic_device *qdev, struct ioctl_resources *resou enable_dbc(qdev, dbc_id, usr); qdev->dbc[dbc_id].in_use = true; resources->buf = NULL; + set_dbc_state(qdev, dbc_id, DBC_STATE_ASSIGNED); } } @@ -923,6 +924,7 @@ static int decode_deactivate(struct qaic_device *qdev, void *trans, u32 *msg_len } release_dbc(qdev, dbc_id); + set_dbc_state(qdev, dbc_id, DBC_STATE_IDLE); *msg_len += sizeof(*in_trans); return 0; diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index 1b5dc717a6c7..a9fcb9782d27 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -270,6 +270,13 @@ static int qaic_create_drm_device(struct qaic_device *qdev, s32 partition_id) return ret; } + ret = qaic_sysfs_init(qddev); + if (ret) { + drm_dev_unregister(drm); + pci_dbg(qdev->pdev, "qaic_sysfs_init failed %d\n", ret); + return ret; + } + qaic_debugfs_init(qddev); return ret; @@ -281,6 +288,7 @@ static void qaic_destroy_drm_device(struct qaic_device *qdev, s32 partition_id) struct drm_device *drm = to_drm(qddev); struct qaic_user *usr; + qaic_sysfs_remove(qddev); drm_dev_unregister(drm); qddev->partition_id = 0; /* diff --git a/drivers/accel/qaic/qaic_sysfs.c b/drivers/accel/qaic/qaic_sysfs.c new file mode 100644 index 000000000000..e0afb0ffb589 --- /dev/null +++ b/drivers/accel/qaic/qaic_sysfs.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* Copyright (c) 2020-2025, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "qaic.h" + +#define NAME_LEN 14 + +struct dbc_attribute { + struct device_attribute dev_attr; + u32 dbc_id; + char name[NAME_LEN]; +}; + +static ssize_t dbc_state_show(struct device *dev, struct device_attribute *a, char *buf) +{ + struct dbc_attribute *dbc_attr = container_of(a, struct dbc_attribute, dev_attr); + struct drm_minor *minor = dev_get_drvdata(dev); + struct qaic_device *qdev; + + qdev = to_qaic_device(minor->dev); + return sysfs_emit(buf, "%d\n", qdev->dbc[dbc_attr->dbc_id].state); +} + +void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state) +{ + struct device *kdev = to_accel_kdev(qdev->qddev); + char *envp[3] = {}; + char state_str[16]; + char id_str[12]; + + envp[0] = id_str; + envp[1] = state_str; + + if (state >= DBC_STATE_MAX) + return; + if (dbc_id >= qdev->num_dbc) + return; + if (state == qdev->dbc[dbc_id].state) + return; + + scnprintf(id_str, ARRAY_SIZE(id_str), "DBC_ID=%d", dbc_id); + scnprintf(state_str, ARRAY_SIZE(state_str), "DBC_STATE=%d", state); + + qdev->dbc[dbc_id].state = state; + kobject_uevent_env(&kdev->kobj, KOBJ_CHANGE, envp); +} + +int qaic_sysfs_init(struct qaic_drm_device *qddev) +{ + struct device *kdev = to_accel_kdev(qddev); + struct drm_device *drm = to_drm(qddev); + u32 num_dbc = qddev->qdev->num_dbc; + struct dbc_attribute *dbc_attrs; + int i, ret; + + dbc_attrs = drmm_kcalloc(drm, num_dbc, sizeof(*dbc_attrs), GFP_KERNEL); + if (!dbc_attrs) + return -ENOMEM; + + for (i = 0; i < num_dbc; ++i) { + struct dbc_attribute *dbc_attr = &dbc_attrs[i]; + + sysfs_attr_init(&dbc_attr->dev_attr.attr); + dbc_attr->dbc_id = i; + scnprintf(dbc_attr->name, NAME_LEN, "dbc%d_state", i); + dbc_attr->dev_attr.attr.name = dbc_attr->name; + dbc_attr->dev_attr.attr.mode = 0444; + dbc_attr->dev_attr.show = dbc_state_show; + ret = sysfs_create_file(&kdev->kobj, &dbc_attr->dev_attr.attr); + if (ret) { + int j; + + for (j = 0; j < i; ++j) { + dbc_attr = &dbc_attrs[j]; + sysfs_remove_file(&kdev->kobj, &dbc_attr->dev_attr.attr); + } + drmm_kfree(drm, dbc_attrs); + return ret; + } + } + + qddev->sysfs_attrs = dbc_attrs; + return 0; +} + +void qaic_sysfs_remove(struct qaic_drm_device *qddev) +{ + struct dbc_attribute *dbc_attrs = qddev->sysfs_attrs; + struct device *kdev = to_accel_kdev(qddev); + u32 num_dbc = qddev->qdev->num_dbc; + int i; + + if (!dbc_attrs) + return; + + qddev->sysfs_attrs = NULL; + for (i = 0; i < num_dbc; ++i) + sysfs_remove_file(&kdev->kobj, &dbc_attrs[i].dev_attr.attr); + drmm_kfree(to_drm(qddev), dbc_attrs); +} From 9675093acea04c7b51ac3a646c7d0ba376b000d6 Mon Sep 17 00:00:00 2001 From: Jeffrey Hugo Date: Fri, 31 Oct 2025 10:41:02 -0700 Subject: [PATCH 14/71] accel/qaic: Implement basic SSR handling Subsystem restart (SSR) for a qaic device means that a NSP has crashed, and will be restarted. However the restart process will lose any state associated with activation, so the user will need to do some recovery. While SSR has the provision to collect a crash dump, this patch does not implement support for it. Co-developed-by: Jeffrey Hugo Signed-off-by: Jeffrey Hugo Co-developed-by: Pranjal Ramajor Asha Kanojiya Signed-off-by: Pranjal Ramajor Asha Kanojiya Co-developed-by: Troy Hanson Signed-off-by: Troy Hanson Co-developed-by: Aswin Venkatesan Signed-off-by: Aswin Venkatesan Signed-off-by: Jeffrey Hugo Signed-off-by: Youssef Samir Signed-off-by: Zack McKevitt Reviewed-by: Jeff Hugo [jhugo: Fix minor checkpatch whitespace issues] Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251031174059.2814445-3-zachary.mckevitt@oss.qualcomm.com --- Documentation/accel/qaic/aic100.rst | 24 ++- drivers/accel/qaic/Makefile | 1 + drivers/accel/qaic/qaic.h | 9 + drivers/accel/qaic/qaic_data.c | 64 ++++++- drivers/accel/qaic/qaic_drv.c | 17 ++ drivers/accel/qaic/qaic_ssr.c | 259 ++++++++++++++++++++++++++++ drivers/accel/qaic/qaic_ssr.h | 16 ++ 7 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 drivers/accel/qaic/qaic_ssr.c create mode 100644 drivers/accel/qaic/qaic_ssr.h diff --git a/Documentation/accel/qaic/aic100.rst b/Documentation/accel/qaic/aic100.rst index 273da6192fb3..3b287c3987d2 100644 --- a/Documentation/accel/qaic/aic100.rst +++ b/Documentation/accel/qaic/aic100.rst @@ -487,8 +487,8 @@ one user crashes, the fallout of that should be limited to that workload and not impact other workloads. SSR accomplishes this. If a particular workload crashes, QSM notifies the host via the QAIC_SSR MHI -channel. This notification identifies the workload by it's assigned DBC. A -multi-stage recovery process is then used to cleanup both sides, and get the +channel. This notification identifies the workload by its assigned DBC. A +multi-stage recovery process is then used to cleanup both sides, and gets the DBC/NSPs into a working state. When SSR occurs, any state in the workload is lost. Any inputs that were in @@ -496,6 +496,26 @@ process, or queued by not yet serviced, are lost. The loaded artifacts will remain in on-card DDR, but the host will need to re-activate the workload if it desires to recover the workload. +When SSR occurs for a specific NSP, the assigned DBC goes through the +following state transactions in order: +DBC_STATE_BEFORE_SHUTDOWN + Indicates that the affected NSP was found in an unrecoverable error + condition. +DBC_STATE_AFTER_SHUTDOWN + Indicates that the NSP is under reset. +DBC_STATE_BEFORE_POWER_UP + Indicates that the NSP's debug information has been collected, and is + ready to be collected by the host (if desired). At that stage the NSP + is restarted by QSM. +DBC_STATE_AFTER_POWER_UP + Indicates that the NSP has been restarted, fully operational and is + in idle state. + +SSR also has an optional crashdump collection feature. If enabled, the host can +collect the memory dump for the crashed NSP and dump it to the user space via +the dev_coredump subsystem. The host can also decline the crashdump collection +request from the device. + Reliability, Accessibility, Serviceability (RAS) ================================================ diff --git a/drivers/accel/qaic/Makefile b/drivers/accel/qaic/Makefile index 8f6746e5f03a..71f727b74da3 100644 --- a/drivers/accel/qaic/Makefile +++ b/drivers/accel/qaic/Makefile @@ -11,6 +11,7 @@ qaic-y := \ qaic_data.o \ qaic_drv.o \ qaic_ras.o \ + qaic_ssr.o \ qaic_sysfs.o \ qaic_timesync.o \ sahara.o diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index cbaec577c457..b9ae1c256c7a 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -21,6 +21,7 @@ #define QAIC_DBC_BASE SZ_128K #define QAIC_DBC_SIZE SZ_4K +#define QAIC_SSR_DBC_SENTINEL U32_MAX /* No ongoing SSR sentinel */ #define QAIC_NO_PARTITION -1 @@ -197,6 +198,12 @@ struct qaic_device { unsigned int ue_count; /* Un-correctable non-fatal error count */ unsigned int ue_nf_count; + /* MHI SSR channel device */ + struct mhi_device *ssr_ch; + /* Work queue for tasks related to MHI SSR device */ + struct workqueue_struct *ssr_wq; + /* DBC which is under SSR. Sentinel U32_MAX would mean that no SSR in progress */ + u32 ssr_dbc; }; struct qaic_drm_device { @@ -340,6 +347,8 @@ int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file int qaic_perf_stats_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int qaic_detach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); void irq_polling_work(struct work_struct *work); +void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id); +void qaic_dbc_exit_ssr(struct qaic_device *qdev); /* qaic_sysfs.c */ int qaic_sysfs_init(struct qaic_drm_device *qddev); diff --git a/drivers/accel/qaic/qaic_data.c b/drivers/accel/qaic/qaic_data.c index fa723a2bdfa9..50a2c3482fd1 100644 --- a/drivers/accel/qaic/qaic_data.c +++ b/drivers/accel/qaic/qaic_data.c @@ -1047,6 +1047,11 @@ int qaic_attach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_fi goto unlock_ch_srcu; } + if (dbc->id == qdev->ssr_dbc) { + ret = -EPIPE; + goto unlock_ch_srcu; + } + ret = qaic_prepare_bo(qdev, bo, &args->hdr); if (ret) goto unlock_ch_srcu; @@ -1370,6 +1375,11 @@ static int __qaic_execute_bo_ioctl(struct drm_device *dev, void *data, struct dr goto release_ch_rcu; } + if (dbc->id == qdev->ssr_dbc) { + ret = -EPIPE; + goto release_ch_rcu; + } + ret = mutex_lock_interruptible(&dbc->req_lock); if (ret) goto release_ch_rcu; @@ -1722,6 +1732,11 @@ int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file goto unlock_ch_srcu; } + if (dbc->id == qdev->ssr_dbc) { + ret = -EPIPE; + goto unlock_ch_srcu; + } + obj = drm_gem_object_lookup(file_priv, args->handle); if (!obj) { ret = -ENOENT; @@ -1742,6 +1757,9 @@ int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file if (!dbc->usr) ret = -EPERM; + if (dbc->id == qdev->ssr_dbc) + ret = -EPIPE; + put_obj: drm_gem_object_put(obj); unlock_ch_srcu: @@ -1945,6 +1963,17 @@ static void empty_xfer_list(struct qaic_device *qdev, struct dma_bridge_chan *db spin_unlock_irqrestore(&dbc->xfer_lock, flags); } +static void sync_empty_xfer_list(struct qaic_device *qdev, struct dma_bridge_chan *dbc) +{ + empty_xfer_list(qdev, dbc); + synchronize_srcu(&dbc->ch_lock); + /* + * Threads holding channel lock, may add more elements in the xfer_list. + * Flush out these elements from xfer_list. + */ + empty_xfer_list(qdev, dbc); +} + int disable_dbc(struct qaic_device *qdev, u32 dbc_id, struct qaic_user *usr) { if (!qdev->dbc[dbc_id].usr || qdev->dbc[dbc_id].usr->handle != usr->handle) @@ -1973,13 +2002,7 @@ void wakeup_dbc(struct qaic_device *qdev, u32 dbc_id) struct dma_bridge_chan *dbc = &qdev->dbc[dbc_id]; dbc->usr = NULL; - empty_xfer_list(qdev, dbc); - synchronize_srcu(&dbc->ch_lock); - /* - * Threads holding channel lock, may add more elements in the xfer_list. - * Flush out these elements from xfer_list. - */ - empty_xfer_list(qdev, dbc); + sync_empty_xfer_list(qdev, dbc); } void release_dbc(struct qaic_device *qdev, u32 dbc_id) @@ -2020,3 +2043,30 @@ void qaic_data_get_fifo_info(struct dma_bridge_chan *dbc, u32 *head, u32 *tail) *head = readl(dbc->dbc_base + REQHP_OFF); *tail = readl(dbc->dbc_base + REQTP_OFF); } + +/* + * qaic_dbc_enter_ssr - Prepare to enter in sub system reset(SSR) for given DBC ID. + * @qdev: qaic device handle + * @dbc_id: ID of the DBC which will enter SSR + * + * The device will automatically deactivate the workload as not + * all errors can be silently recovered. The user will be + * notified and will need to decide the required recovery + * action to take. + */ +void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id) +{ + qdev->ssr_dbc = dbc_id; + release_dbc(qdev, dbc_id); +} + +/* + * qaic_dbc_exit_ssr - Prepare to exit from sub system reset(SSR) for given DBC ID. + * @qdev: qaic device handle + * + * The DBC returns to an operational state and begins accepting work after exiting SSR. + */ +void qaic_dbc_exit_ssr(struct qaic_device *qdev) +{ + qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL; +} diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index a9fcb9782d27..4d9f4f149b3d 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -30,6 +30,7 @@ #include "qaic.h" #include "qaic_debugfs.h" #include "qaic_ras.h" +#include "qaic_ssr.h" #include "qaic_timesync.h" #include "sahara.h" @@ -390,6 +391,7 @@ void qaic_dev_reset_clean_local_state(struct qaic_device *qdev) qaic_notify_reset(qdev); /* start tearing things down */ + qaic_clean_up_ssr(qdev); for (i = 0; i < qdev->num_dbc; ++i) release_dbc(qdev, i); } @@ -439,11 +441,18 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev, qdev->qts_wq = qaicm_wq_init(drm, "qaic_ts"); if (IS_ERR(qdev->qts_wq)) return NULL; + qdev->ssr_wq = qaicm_wq_init(drm, "qaic_ssr"); + if (IS_ERR(qdev->ssr_wq)) + return NULL; ret = qaicm_srcu_init(drm, &qdev->dev_lock); if (ret) return NULL; + ret = qaic_ssr_init(qdev); + if (ret) + pci_info(pdev, "QAIC SSR crashdump collection not supported.\n"); + qdev->qddev = qddev; qdev->pdev = pdev; qddev->qdev = qdev; @@ -799,9 +808,16 @@ static int __init qaic_init(void) ret = qaic_ras_register(); if (ret) pr_debug("qaic: qaic_ras_register failed %d\n", ret); + ret = qaic_ssr_register(); + if (ret) { + pr_debug("qaic: qaic_ssr_register failed %d\n", ret); + goto free_bootlog; + } return 0; +free_bootlog: + qaic_bootlog_unregister(); free_mhi: mhi_driver_unregister(&qaic_mhi_driver); free_pci: @@ -827,6 +843,7 @@ static void __exit qaic_exit(void) * reinitializing the link_up state after the cleanup is done. */ link_up = true; + qaic_ssr_unregister(); qaic_ras_unregister(); qaic_bootlog_unregister(); qaic_timesync_deinit(); diff --git a/drivers/accel/qaic/qaic_ssr.c b/drivers/accel/qaic/qaic_ssr.c new file mode 100644 index 000000000000..e9c7fe0573dd --- /dev/null +++ b/drivers/accel/qaic/qaic_ssr.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "qaic.h" +#include "qaic_ssr.h" + +#define SSR_RESP_MSG_SZ 32 + +#define DEBUG_TRANSFER_INFO BIT(0) +#define DEBUG_TRANSFER_INFO_RSP BIT(1) +#define MEMORY_READ BIT(2) +#define MEMORY_READ_RSP BIT(3) +#define DEBUG_TRANSFER_DONE BIT(4) +#define DEBUG_TRANSFER_DONE_RSP BIT(5) +#define SSR_EVENT BIT(8) +#define SSR_EVENT_RSP BIT(9) + +#define SSR_EVENT_NACK BIT(0) +#define BEFORE_SHUTDOWN BIT(1) +#define AFTER_SHUTDOWN BIT(2) +#define BEFORE_POWER_UP BIT(3) +#define AFTER_POWER_UP BIT(4) + +struct _ssr_hdr { + __le32 cmd; + __le32 len; + __le32 dbc_id; +}; + +struct ssr_hdr { + u32 cmd; + u32 len; + u32 dbc_id; +}; + +struct ssr_debug_transfer_info_rsp { + struct _ssr_hdr hdr; + __le32 ret; +} __packed; + +struct ssr_event { + struct ssr_hdr hdr; + u32 event; +} __packed; + +struct ssr_event_rsp { + struct _ssr_hdr hdr; + __le32 event; +} __packed; + +struct ssr_resp { + /* Work struct to schedule work coming on QAIC_SSR channel */ + struct work_struct work; + /* Root struct of device, used to access device resources */ + struct qaic_device *qdev; + /* Buffer used by MHI for transfer requests */ + u8 data[] __aligned(8); +}; + +void qaic_clean_up_ssr(struct qaic_device *qdev) +{ + qaic_dbc_exit_ssr(qdev); +} + +static void ssr_worker(struct work_struct *work) +{ + struct ssr_resp *resp = container_of(work, struct ssr_resp, work); + struct ssr_hdr *hdr = (struct ssr_hdr *)resp->data; + struct ssr_debug_transfer_info_rsp *debug_rsp; + struct qaic_device *qdev = resp->qdev; + struct ssr_event_rsp *event_rsp; + struct dma_bridge_chan *dbc; + struct ssr_event *event; + u32 ssr_event_ack; + int ret; + + le32_to_cpus(&hdr->cmd); + le32_to_cpus(&hdr->len); + le32_to_cpus(&hdr->dbc_id); + + if (hdr->len > SSR_RESP_MSG_SZ) + goto out; + + if (hdr->dbc_id >= qdev->num_dbc) + goto out; + + dbc = &qdev->dbc[hdr->dbc_id]; + + switch (hdr->cmd) { + case DEBUG_TRANSFER_INFO: + /* Decline crash dump request from the device */ + debug_rsp = kmalloc(sizeof(*debug_rsp), GFP_KERNEL); + if (!debug_rsp) + break; + + debug_rsp->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_INFO_RSP); + debug_rsp->hdr.len = cpu_to_le32(sizeof(*debug_rsp)); + debug_rsp->hdr.dbc_id = cpu_to_le32(event->hdr.dbc_id); + debug_rsp->ret = cpu_to_le32(1); + + ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, + debug_rsp, sizeof(*debug_rsp), MHI_EOT); + if (ret) { + pci_warn(qdev->pdev, "Could not send DEBUG_TRANSFER_INFO_RSP %d\n", ret); + kfree(debug_rsp); + } + return; + case SSR_EVENT: + event = (struct ssr_event *)hdr; + le32_to_cpus(&event->event); + ssr_event_ack = event->event; + + switch (event->event) { + case BEFORE_SHUTDOWN: + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_SHUTDOWN); + qaic_dbc_enter_ssr(qdev, hdr->dbc_id); + break; + case AFTER_SHUTDOWN: + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_SHUTDOWN); + break; + case BEFORE_POWER_UP: + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_POWER_UP); + break; + case AFTER_POWER_UP: + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_POWER_UP); + break; + default: + break; + } + + event_rsp = kmalloc(sizeof(*event_rsp), GFP_KERNEL); + if (!event_rsp) + break; + + event_rsp->hdr.cmd = cpu_to_le32(SSR_EVENT_RSP); + event_rsp->hdr.len = cpu_to_le32(sizeof(*event_rsp)); + event_rsp->hdr.dbc_id = cpu_to_le32(hdr->dbc_id); + event_rsp->event = cpu_to_le32(ssr_event_ack); + + ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, event_rsp, sizeof(*event_rsp), + MHI_EOT); + if (ret) + kfree(event_rsp); + + if (event->event == AFTER_POWER_UP) { + qaic_dbc_exit_ssr(qdev); + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_IDLE); + } + + break; + default: + break; + } + +out: + ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT); + if (ret) + kfree(resp); +} + +static int qaic_ssr_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id) +{ + struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev)); + struct ssr_resp *resp; + int ret; + + ret = mhi_prepare_for_transfer(mhi_dev); + if (ret) + return ret; + + resp = kzalloc(sizeof(*resp) + SSR_RESP_MSG_SZ, GFP_KERNEL); + if (!resp) { + mhi_unprepare_from_transfer(mhi_dev); + return -ENOMEM; + } + + resp->qdev = qdev; + INIT_WORK(&resp->work, ssr_worker); + + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT); + if (ret) { + kfree(resp); + mhi_unprepare_from_transfer(mhi_dev); + return ret; + } + + dev_set_drvdata(&mhi_dev->dev, qdev); + qdev->ssr_ch = mhi_dev; + + return 0; +} + +static void qaic_ssr_mhi_remove(struct mhi_device *mhi_dev) +{ + struct qaic_device *qdev; + + qdev = dev_get_drvdata(&mhi_dev->dev); + mhi_unprepare_from_transfer(qdev->ssr_ch); + qdev->ssr_ch = NULL; +} + +static void qaic_ssr_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result) +{ + kfree(mhi_result->buf_addr); +} + +static void qaic_ssr_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result) +{ + struct ssr_resp *resp = container_of(mhi_result->buf_addr, struct ssr_resp, data); + struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev); + + if (mhi_result->transaction_status) { + kfree(resp); + return; + } + queue_work(qdev->ssr_wq, &resp->work); +} + +static const struct mhi_device_id qaic_ssr_mhi_match_table[] = { + { .chan = "QAIC_SSR", }, + {}, +}; + +static struct mhi_driver qaic_ssr_mhi_driver = { + .id_table = qaic_ssr_mhi_match_table, + .remove = qaic_ssr_mhi_remove, + .probe = qaic_ssr_mhi_probe, + .ul_xfer_cb = qaic_ssr_mhi_ul_xfer_cb, + .dl_xfer_cb = qaic_ssr_mhi_dl_xfer_cb, + .driver = { + .name = "qaic_ssr", + }, +}; + +int qaic_ssr_init(struct qaic_device *qdev) +{ + qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL; + return 0; +} + +int qaic_ssr_register(void) +{ + return mhi_driver_register(&qaic_ssr_mhi_driver); +} + +void qaic_ssr_unregister(void) +{ + mhi_driver_unregister(&qaic_ssr_mhi_driver); +} diff --git a/drivers/accel/qaic/qaic_ssr.h b/drivers/accel/qaic/qaic_ssr.h new file mode 100644 index 000000000000..02cd9ee1a483 --- /dev/null +++ b/drivers/accel/qaic/qaic_ssr.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2021, 2024 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __QAIC_SSR_H__ +#define __QAIC_SSR_H__ + +struct qaic_device; + +int qaic_ssr_register(void); +void qaic_ssr_unregister(void); +void qaic_clean_up_ssr(struct qaic_device *qdev); +int qaic_ssr_init(struct qaic_device *qdev); +#endif /* __QAIC_SSR_H__ */ From 6bc1fe6c748caaf926eaf14c8a262f4c65aead9f Mon Sep 17 00:00:00 2001 From: Pranjal Ramajor Asha Kanojiya Date: Fri, 31 Oct 2025 10:41:04 -0700 Subject: [PATCH 15/71] accel/qaic: Collect crashdump from SSR channel After subsystem of the device has crashed it sends a message with command DEBUG_TRANSFER_INFO to kernel(host). Send ACK for that message and then prepare to collect the ramdump of the subsystem Steps of crashdump collection is as follows, 1) Device sends DEBUG_TRANSFER_INFO message indicating that device wants to send crashdump. 2) Send an acknowledgment to that message either ACK or NACK. a) NACK will inform the device that host will not download the crashdump b) ACK will inform the device that host will download the crashdump 3) Along with the DEBUG_TRANSFER_INFO we receive a table base address and its length, use that to download that table from device. a) This table is meta data of the crashdump and not the actual crashdump. 4) After we respond as ACK for message received on step 1) we start downloading the table. Use series of MEMORY_READ/MEMORY_READ_RSP SSR commands to download the entire table. 5) Each entry in the table represents a segment of crashdump. Once the table downloading is complete, iterate through each entry of table and download each crashdump segment(same as table itself). Table entry contains the memory base address and length along with other info. 6) After the entire crashdump is downloaded send DEBUG_TRANSFER_DONE which marks that host is terminating the crashdump transfer. This message can be send in both success or error case. 7) After receiving DEBUG_TRANSFER_DONE_RSP hand over the crashdump to dev_coredumpv() and free all the necessary memory. Co-developed-by: Jeffrey Hugo Signed-off-by: Jeffrey Hugo Co-developed-by: Pranjal Ramajor Asha Kanojiya Signed-off-by: Pranjal Ramajor Asha Kanojiya Signed-off-by: Pranjal Ramajor Asha Kanojiya Signed-off-by: Youssef Samir Signed-off-by: Zack McKevitt Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251031174059.2814445-4-zachary.mckevitt@oss.qualcomm.com --- drivers/accel/qaic/Kconfig | 1 + drivers/accel/qaic/qaic.h | 2 + drivers/accel/qaic/qaic_drv.c | 2 +- drivers/accel/qaic/qaic_ssr.c | 588 +++++++++++++++++++++++++++++++++- drivers/accel/qaic/qaic_ssr.h | 3 +- 5 files changed, 578 insertions(+), 18 deletions(-) diff --git a/drivers/accel/qaic/Kconfig b/drivers/accel/qaic/Kconfig index 5e405a19c157..116e42d152ca 100644 --- a/drivers/accel/qaic/Kconfig +++ b/drivers/accel/qaic/Kconfig @@ -9,6 +9,7 @@ config DRM_ACCEL_QAIC depends on PCI && HAS_IOMEM depends on MHI_BUS select CRC32 + select WANT_DEV_COREDUMP help Enables driver for Qualcomm's Cloud AI accelerator PCIe cards that are designed to accelerate Deep Learning inference workloads. diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index b9ae1c256c7a..4a371ddc0a55 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -202,6 +202,8 @@ struct qaic_device { struct mhi_device *ssr_ch; /* Work queue for tasks related to MHI SSR device */ struct workqueue_struct *ssr_wq; + /* Buffer to collect SSR crashdump via SSR MHI channel */ + void *ssr_mhi_buf; /* DBC which is under SSR. Sentinel U32_MAX would mean that no SSR in progress */ u32 ssr_dbc; }; diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index 4d9f4f149b3d..68f5b5db8b44 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -449,7 +449,7 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev, if (ret) return NULL; - ret = qaic_ssr_init(qdev); + ret = qaic_ssr_init(qdev, drm); if (ret) pci_info(pdev, "QAIC SSR crashdump collection not supported.\n"); diff --git a/drivers/accel/qaic/qaic_ssr.c b/drivers/accel/qaic/qaic_ssr.c index e9c7fe0573dd..9b662d690371 100644 --- a/drivers/accel/qaic/qaic_ssr.c +++ b/drivers/accel/qaic/qaic_ssr.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,9 @@ #include "qaic_ssr.h" #define SSR_RESP_MSG_SZ 32 +#define SSR_MHI_BUF_SIZE SZ_64K +#define SSR_MEM_READ_DATA_SIZE ((u64)SSR_MHI_BUF_SIZE - sizeof(struct ssr_crashdump)) +#define SSR_MEM_READ_CHUNK_SIZE ((u64)SSR_MEM_READ_DATA_SIZE - sizeof(struct ssr_memory_read_rsp)) #define DEBUG_TRANSFER_INFO BIT(0) #define DEBUG_TRANSFER_INFO_RSP BIT(1) @@ -31,6 +35,19 @@ #define BEFORE_POWER_UP BIT(3) #define AFTER_POWER_UP BIT(4) +struct debug_info_table { + /* Save preferences. Default is mandatory */ + u64 save_perf; + /* Base address of the debug region */ + u64 mem_base; + /* Size of debug region in bytes */ + u64 len; + /* Description */ + char desc[20]; + /* Filename of debug region */ + char filename[20]; +}; + struct _ssr_hdr { __le32 cmd; __le32 len; @@ -43,11 +60,41 @@ struct ssr_hdr { u32 dbc_id; }; +struct ssr_debug_transfer_info { + struct ssr_hdr hdr; + u32 resv; + u64 tbl_addr; + u64 tbl_len; +} __packed; + struct ssr_debug_transfer_info_rsp { struct _ssr_hdr hdr; __le32 ret; } __packed; +struct ssr_memory_read { + struct _ssr_hdr hdr; + __le32 resv; + __le64 addr; + __le64 len; +} __packed; + +struct ssr_memory_read_rsp { + struct _ssr_hdr hdr; + __le32 resv; + u8 data[]; +} __packed; + +struct ssr_debug_transfer_done { + struct _ssr_hdr hdr; + __le32 resv; +} __packed; + +struct ssr_debug_transfer_done_rsp { + struct _ssr_hdr hdr; + __le32 ret; +} __packed; + struct ssr_event { struct ssr_hdr hdr; u32 event; @@ -67,17 +114,453 @@ struct ssr_resp { u8 data[] __aligned(8); }; +/* SSR crashdump book keeping structure */ +struct ssr_dump_info { + /* DBC associated with this SSR crashdump */ + struct dma_bridge_chan *dbc; + /* + * It will be used when we complete the crashdump download and switch + * to waiting on SSR events + */ + struct ssr_resp *resp; + /* MEMORY READ request MHI buffer.*/ + struct ssr_memory_read *read_buf_req; + /* TRUE: ->read_buf_req is queued for MHI transaction. FALSE: Otherwise */ + bool read_buf_req_queued; + /* Address of table in host */ + void *tbl_addr; + /* Total size of table */ + u64 tbl_len; + /* Offset of table(->tbl_addr) where the new chunk will be dumped */ + u64 tbl_off; + /* Address of table in device/target */ + u64 tbl_addr_dev; + /* Ptr to the entire dump */ + void *dump_addr; + /* Entire crashdump size */ + u64 dump_sz; + /* Offset of crashdump(->dump_addr) where the new chunk will be dumped */ + u64 dump_off; + /* Points to the table entry we are currently downloading */ + struct debug_info_table *tbl_ent; + /* Offset in the current table entry(->tbl_ent) for next chuck */ + u64 tbl_ent_off; +}; + +struct ssr_crashdump { + /* + * Points to a book keeping struct maintained by MHI SSR device while + * downloading a SSR crashdump. It is NULL when crashdump downloading + * not in progress. + */ + struct ssr_dump_info *dump_info; + /* Work struct to schedule work coming on QAIC_SSR channel */ + struct work_struct work; + /* Root struct of device, used to access device resources */ + struct qaic_device *qdev; + /* Buffer used by MHI for transfer requests */ + u8 data[]; +}; + +#define QAIC_SSR_DUMP_V1_MAGIC 0x1234567890abcdef +#define QAIC_SSR_DUMP_V1_VER 1 +struct dump_file_meta { + u64 magic; + u64 version; + u64 size; /* Total size of the entire dump */ + u64 tbl_len; /* Length of the table in byte */ +}; + +/* + * Layout of crashdump + * +------------------------------------------+ + * | Crashdump Meta structure | + * | type: struct dump_file_meta | + * +------------------------------------------+ + * | Crashdump Table | + * | type: array of struct debug_info_table | + * | | + * | | + * | | + * +------------------------------------------+ + * | Crashdump | + * | | + * | | + * | | + * | | + * | | + * +------------------------------------------+ + */ + +static void free_ssr_dump_info(struct ssr_crashdump *ssr_crash) +{ + struct ssr_dump_info *dump_info = ssr_crash->dump_info; + + ssr_crash->dump_info = NULL; + if (!dump_info) + return; + if (!dump_info->read_buf_req_queued) + kfree(dump_info->read_buf_req); + vfree(dump_info->tbl_addr); + vfree(dump_info->dump_addr); + kfree(dump_info); +} + void qaic_clean_up_ssr(struct qaic_device *qdev) { + struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf; + + if (!ssr_crash) + return; + qaic_dbc_exit_ssr(qdev); + free_ssr_dump_info(ssr_crash); +} + +static int alloc_dump(struct ssr_dump_info *dump_info) +{ + struct debug_info_table *tbl_ent = dump_info->tbl_addr; + struct dump_file_meta *dump_meta; + u64 tbl_sz_lp = 0; + u64 dump_size = 0; + + while (tbl_sz_lp < dump_info->tbl_len) { + le64_to_cpus(&tbl_ent->save_perf); + le64_to_cpus(&tbl_ent->mem_base); + le64_to_cpus(&tbl_ent->len); + + if (tbl_ent->len == 0) + return -EINVAL; + + dump_size += tbl_ent->len; + tbl_ent++; + tbl_sz_lp += sizeof(*tbl_ent); + } + + dump_info->dump_sz = dump_size + dump_info->tbl_len + sizeof(*dump_meta); + dump_info->dump_addr = vzalloc(dump_info->dump_sz); + if (!dump_info->dump_addr) + return -ENOMEM; + + /* Copy crashdump meta and table */ + dump_meta = dump_info->dump_addr; + dump_meta->magic = QAIC_SSR_DUMP_V1_MAGIC; + dump_meta->version = QAIC_SSR_DUMP_V1_VER; + dump_meta->size = dump_info->dump_sz; + dump_meta->tbl_len = dump_info->tbl_len; + memcpy(dump_info->dump_addr + sizeof(*dump_meta), dump_info->tbl_addr, dump_info->tbl_len); + /* Offset by crashdump meta and table (copied above) */ + dump_info->dump_off = dump_info->tbl_len + sizeof(*dump_meta); + + return 0; +} + +static int send_xfer_done(struct qaic_device *qdev, void *resp, u32 dbc_id) +{ + struct ssr_debug_transfer_done *xfer_done; + int ret; + + xfer_done = kmalloc(sizeof(*xfer_done), GFP_KERNEL); + if (!xfer_done) { + ret = -ENOMEM; + goto out; + } + + ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp, SSR_RESP_MSG_SZ, MHI_EOT); + if (ret) + goto free_xfer_done; + + xfer_done->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_DONE); + xfer_done->hdr.len = cpu_to_le32(sizeof(*xfer_done)); + xfer_done->hdr.dbc_id = cpu_to_le32(dbc_id); + + ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, xfer_done, sizeof(*xfer_done), MHI_EOT); + if (ret) + goto free_xfer_done; + + return 0; + +free_xfer_done: + kfree(xfer_done); +out: + return ret; +} + +static int mem_read_req(struct qaic_device *qdev, u64 dest_addr, u64 dest_len) +{ + struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf; + struct ssr_memory_read *read_buf_req; + struct ssr_dump_info *dump_info; + int ret; + + dump_info = ssr_crash->dump_info; + ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, ssr_crash->data, SSR_MEM_READ_DATA_SIZE, + MHI_EOT); + if (ret) + goto out; + + read_buf_req = dump_info->read_buf_req; + read_buf_req->hdr.cmd = cpu_to_le32(MEMORY_READ); + read_buf_req->hdr.len = cpu_to_le32(sizeof(*read_buf_req)); + read_buf_req->hdr.dbc_id = cpu_to_le32(qdev->ssr_dbc); + read_buf_req->addr = cpu_to_le64(dest_addr); + read_buf_req->len = cpu_to_le64(dest_len); + + ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, read_buf_req, sizeof(*read_buf_req), + MHI_EOT); + if (!ret) + dump_info->read_buf_req_queued = true; + +out: + return ret; +} + +static int ssr_copy_table(struct ssr_dump_info *dump_info, void *data, u64 len) +{ + if (len > dump_info->tbl_len - dump_info->tbl_off) + return -EINVAL; + + memcpy(dump_info->tbl_addr + dump_info->tbl_off, data, len); + dump_info->tbl_off += len; + + /* Entire table has been downloaded, alloc dump memory */ + if (dump_info->tbl_off == dump_info->tbl_len) { + dump_info->tbl_ent = dump_info->tbl_addr; + return alloc_dump(dump_info); + } + + return 0; +} + +static int ssr_copy_dump(struct ssr_dump_info *dump_info, void *data, u64 len) +{ + struct debug_info_table *tbl_ent; + + tbl_ent = dump_info->tbl_ent; + + if (len > tbl_ent->len - dump_info->tbl_ent_off) + return -EINVAL; + + memcpy(dump_info->dump_addr + dump_info->dump_off, data, len); + dump_info->dump_off += len; + dump_info->tbl_ent_off += len; + + /* + * Current segment (a entry in table) of the crashdump is complete, + * move to next one + */ + if (tbl_ent->len == dump_info->tbl_ent_off) { + dump_info->tbl_ent++; + dump_info->tbl_ent_off = 0; + } + + return 0; +} + +static void ssr_dump_worker(struct work_struct *work) +{ + struct ssr_crashdump *ssr_crash = container_of(work, struct ssr_crashdump, work); + struct qaic_device *qdev = ssr_crash->qdev; + struct ssr_memory_read_rsp *mem_rd_resp; + struct debug_info_table *tbl_ent; + struct ssr_dump_info *dump_info; + u64 dest_addr, dest_len; + struct _ssr_hdr *_hdr; + struct ssr_hdr hdr; + u64 data_len; + int ret; + + mem_rd_resp = (struct ssr_memory_read_rsp *)ssr_crash->data; + _hdr = &mem_rd_resp->hdr; + hdr.cmd = le32_to_cpu(_hdr->cmd); + hdr.len = le32_to_cpu(_hdr->len); + hdr.dbc_id = le32_to_cpu(_hdr->dbc_id); + + if (hdr.dbc_id != qdev->ssr_dbc) + goto reset_device; + + dump_info = ssr_crash->dump_info; + if (!dump_info) + goto reset_device; + + if (hdr.cmd != MEMORY_READ_RSP) + goto free_dump_info; + + if (hdr.len > SSR_MEM_READ_DATA_SIZE) + goto free_dump_info; + + data_len = hdr.len - sizeof(*mem_rd_resp); + + if (dump_info->tbl_off < dump_info->tbl_len) /* Chunk belongs to table */ + ret = ssr_copy_table(dump_info, mem_rd_resp->data, data_len); + else /* Chunk belongs to crashdump */ + ret = ssr_copy_dump(dump_info, mem_rd_resp->data, data_len); + + if (ret) + goto free_dump_info; + + if (dump_info->tbl_off < dump_info->tbl_len) { + /* Continue downloading table */ + dest_addr = dump_info->tbl_addr_dev + dump_info->tbl_off; + dest_len = min(SSR_MEM_READ_CHUNK_SIZE, dump_info->tbl_len - dump_info->tbl_off); + ret = mem_read_req(qdev, dest_addr, dest_len); + } else if (dump_info->dump_off < dump_info->dump_sz) { + /* Continue downloading crashdump */ + tbl_ent = dump_info->tbl_ent; + dest_addr = tbl_ent->mem_base + dump_info->tbl_ent_off; + dest_len = min(SSR_MEM_READ_CHUNK_SIZE, tbl_ent->len - dump_info->tbl_ent_off); + ret = mem_read_req(qdev, dest_addr, dest_len); + } else { + /* Crashdump download complete */ + ret = send_xfer_done(qdev, dump_info->resp->data, hdr.dbc_id); + } + + /* Most likely a MHI xfer has failed */ + if (ret) + goto free_dump_info; + + return; + +free_dump_info: + /* Free the allocated memory */ + free_ssr_dump_info(ssr_crash); +reset_device: + /* + * After subsystem crashes in device crashdump collection begins but + * something went wrong while collecting crashdump, now instead of + * handling this error we just reset the device as the best effort has + * been made + */ + mhi_soc_reset(qdev->mhi_cntrl); +} + +static struct ssr_dump_info *alloc_dump_info(struct qaic_device *qdev, + struct ssr_debug_transfer_info *debug_info) +{ + struct ssr_dump_info *dump_info; + int ret; + + le64_to_cpus(&debug_info->tbl_len); + le64_to_cpus(&debug_info->tbl_addr); + + if (debug_info->tbl_len == 0 || + debug_info->tbl_len % sizeof(struct debug_info_table) != 0) { + ret = -EINVAL; + goto out; + } + + /* Allocate SSR crashdump book keeping structure */ + dump_info = kzalloc(sizeof(*dump_info), GFP_KERNEL); + if (!dump_info) { + ret = -ENOMEM; + goto out; + } + + /* Buffer used to send MEMORY READ request to device via MHI */ + dump_info->read_buf_req = kzalloc(sizeof(*dump_info->read_buf_req), GFP_KERNEL); + if (!dump_info->read_buf_req) { + ret = -ENOMEM; + goto free_dump_info; + } + + /* Crashdump meta table buffer */ + dump_info->tbl_addr = vzalloc(debug_info->tbl_len); + if (!dump_info->tbl_addr) { + ret = -ENOMEM; + goto free_read_buf_req; + } + + dump_info->tbl_addr_dev = debug_info->tbl_addr; + dump_info->tbl_len = debug_info->tbl_len; + + return dump_info; + +free_read_buf_req: + kfree(dump_info->read_buf_req); +free_dump_info: + kfree(dump_info); +out: + return ERR_PTR(ret); +} + +static int dbg_xfer_info_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc, + struct ssr_debug_transfer_info *debug_info) +{ + struct ssr_debug_transfer_info_rsp *debug_rsp; + struct ssr_crashdump *ssr_crash = NULL; + int ret = 0, ret2; + + debug_rsp = kmalloc(sizeof(*debug_rsp), GFP_KERNEL); + if (!debug_rsp) + return -ENOMEM; + + if (!qdev->ssr_mhi_buf) { + ret = -ENOMEM; + goto send_rsp; + } + + if (dbc->state != DBC_STATE_BEFORE_POWER_UP) { + ret = -EINVAL; + goto send_rsp; + } + + ssr_crash = qdev->ssr_mhi_buf; + ssr_crash->dump_info = alloc_dump_info(qdev, debug_info); + if (IS_ERR(ssr_crash->dump_info)) { + ret = PTR_ERR(ssr_crash->dump_info); + ssr_crash->dump_info = NULL; + } + +send_rsp: + debug_rsp->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_INFO_RSP); + debug_rsp->hdr.len = cpu_to_le32(sizeof(*debug_rsp)); + debug_rsp->hdr.dbc_id = cpu_to_le32(dbc->id); + /* + * 0 = Return an ACK confirming the host is ready to download crashdump + * 1 = Return an NACK confirming the host is not ready to download crashdump + */ + debug_rsp->ret = cpu_to_le32(ret ? 1 : 0); + + ret2 = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, debug_rsp, sizeof(*debug_rsp), MHI_EOT); + if (ret2) { + free_ssr_dump_info(ssr_crash); + kfree(debug_rsp); + return ret2; + } + + return ret; +} + +static void dbg_xfer_done_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc, + struct ssr_debug_transfer_done_rsp *xfer_rsp) +{ + struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf; + u32 status = le32_to_cpu(xfer_rsp->ret); + struct device *dev = &qdev->pdev->dev; + struct ssr_dump_info *dump_info; + + dump_info = ssr_crash->dump_info; + if (!dump_info) + return; + + if (status) { + free_ssr_dump_info(ssr_crash); + return; + } + + dev_coredumpv(dev, dump_info->dump_addr, dump_info->dump_sz, GFP_KERNEL); + /* dev_coredumpv will free dump_info->dump_addr */ + dump_info->dump_addr = NULL; + free_ssr_dump_info(ssr_crash); } static void ssr_worker(struct work_struct *work) { struct ssr_resp *resp = container_of(work, struct ssr_resp, work); struct ssr_hdr *hdr = (struct ssr_hdr *)resp->data; - struct ssr_debug_transfer_info_rsp *debug_rsp; + struct ssr_dump_info *dump_info = NULL; struct qaic_device *qdev = resp->qdev; + struct ssr_crashdump *ssr_crash; struct ssr_event_rsp *event_rsp; struct dma_bridge_chan *dbc; struct ssr_event *event; @@ -98,27 +581,34 @@ static void ssr_worker(struct work_struct *work) switch (hdr->cmd) { case DEBUG_TRANSFER_INFO: - /* Decline crash dump request from the device */ - debug_rsp = kmalloc(sizeof(*debug_rsp), GFP_KERNEL); - if (!debug_rsp) + ret = dbg_xfer_info_rsp(qdev, dbc, (struct ssr_debug_transfer_info *)resp->data); + if (ret) break; - debug_rsp->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_INFO_RSP); - debug_rsp->hdr.len = cpu_to_le32(sizeof(*debug_rsp)); - debug_rsp->hdr.dbc_id = cpu_to_le32(event->hdr.dbc_id); - debug_rsp->ret = cpu_to_le32(1); + ssr_crash = qdev->ssr_mhi_buf; + dump_info = ssr_crash->dump_info; + dump_info->dbc = dbc; + dump_info->resp = resp; - ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, - debug_rsp, sizeof(*debug_rsp), MHI_EOT); + /* Start by downloading debug table */ + ret = mem_read_req(qdev, dump_info->tbl_addr_dev, + min(dump_info->tbl_len, SSR_MEM_READ_CHUNK_SIZE)); if (ret) { - pci_warn(qdev->pdev, "Could not send DEBUG_TRANSFER_INFO_RSP %d\n", ret); - kfree(debug_rsp); + free_ssr_dump_info(ssr_crash); + break; } + + /* + * Till now everything went fine, which means that we will be + * collecting crashdump chunk by chunk. Do not queue a response + * buffer for SSR cmds till the crashdump is complete. + */ return; case SSR_EVENT: event = (struct ssr_event *)hdr; le32_to_cpus(&event->event); ssr_event_ack = event->event; + ssr_crash = qdev->ssr_mhi_buf; switch (event->event) { case BEFORE_SHUTDOWN: @@ -132,6 +622,18 @@ static void ssr_worker(struct work_struct *work) set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_POWER_UP); break; case AFTER_POWER_UP: + /* + * If dump info is a non NULL value it means that we + * have received this SSR event while downloading a + * crashdump for this DBC is still in progress. NACK + * the SSR event + */ + if (ssr_crash && ssr_crash->dump_info) { + free_ssr_dump_info(ssr_crash); + ssr_event_ack = SSR_EVENT_NACK; + break; + } + set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_POWER_UP); break; default: @@ -152,11 +654,14 @@ static void ssr_worker(struct work_struct *work) if (ret) kfree(event_rsp); - if (event->event == AFTER_POWER_UP) { + if (event->event == AFTER_POWER_UP && ssr_event_ack != SSR_EVENT_NACK) { qaic_dbc_exit_ssr(qdev); set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_IDLE); } + break; + case DEBUG_TRANSFER_DONE_RSP: + dbg_xfer_done_rsp(qdev, dbc, (struct ssr_debug_transfer_done_rsp *)hdr); break; default: break; @@ -211,6 +716,31 @@ static void qaic_ssr_mhi_remove(struct mhi_device *mhi_dev) static void qaic_ssr_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result) { + struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev); + struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf; + struct _ssr_hdr *hdr = mhi_result->buf_addr; + struct ssr_dump_info *dump_info; + + if (mhi_result->transaction_status) { + kfree(mhi_result->buf_addr); + return; + } + + /* + * MEMORY READ is used to download crashdump. And crashdump is + * downloaded chunk by chunk in a series of MEMORY READ SSR commands. + * Hence to avoid too many kmalloc() and kfree() of the same MEMORY READ + * request buffer, we allocate only one such buffer and free it only + * once. + */ + if (le32_to_cpu(hdr->cmd) == MEMORY_READ) { + dump_info = ssr_crash->dump_info; + if (dump_info) { + dump_info->read_buf_req_queued = false; + return; + } + } + kfree(mhi_result->buf_addr); } @@ -218,12 +748,23 @@ static void qaic_ssr_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_resul { struct ssr_resp *resp = container_of(mhi_result->buf_addr, struct ssr_resp, data); struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev); + struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf; + bool memory_read_rsp = false; + + if (ssr_crash && ssr_crash->data == mhi_result->buf_addr) + memory_read_rsp = true; if (mhi_result->transaction_status) { - kfree(resp); + /* Do not free SSR crashdump buffer as it allocated via managed APIs */ + if (!memory_read_rsp) + kfree(resp); return; } - queue_work(qdev->ssr_wq, &resp->work); + + if (memory_read_rsp) + queue_work(qdev->ssr_wq, &ssr_crash->work); + else + queue_work(qdev->ssr_wq, &resp->work); } static const struct mhi_device_id qaic_ssr_mhi_match_table[] = { @@ -242,9 +783,24 @@ static struct mhi_driver qaic_ssr_mhi_driver = { }, }; -int qaic_ssr_init(struct qaic_device *qdev) +int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm) { + struct ssr_crashdump *ssr_crash; + qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL; + + /* + * Device requests only one SSR at a time. So allocating only one + * buffer to download crashdump is good enough. + */ + ssr_crash = drmm_kzalloc(drm, SSR_MHI_BUF_SIZE, GFP_KERNEL); + if (!ssr_crash) + return -ENOMEM; + + ssr_crash->qdev = qdev; + INIT_WORK(&ssr_crash->work, ssr_dump_worker); + qdev->ssr_mhi_buf = ssr_crash; + return 0; } diff --git a/drivers/accel/qaic/qaic_ssr.h b/drivers/accel/qaic/qaic_ssr.h index 02cd9ee1a483..97ccff305750 100644 --- a/drivers/accel/qaic/qaic_ssr.h +++ b/drivers/accel/qaic/qaic_ssr.h @@ -7,10 +7,11 @@ #ifndef __QAIC_SSR_H__ #define __QAIC_SSR_H__ +struct drm_device; struct qaic_device; int qaic_ssr_register(void); void qaic_ssr_unregister(void); void qaic_clean_up_ssr(struct qaic_device *qdev); -int qaic_ssr_init(struct qaic_device *qdev); +int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm); #endif /* __QAIC_SSR_H__ */ From a2b0c33e9423cd06133304e2f81c713849059b10 Mon Sep 17 00:00:00 2001 From: Zack McKevitt Date: Fri, 31 Oct 2025 12:25:12 -0700 Subject: [PATCH 16/71] accel/qaic: Add qaic_ prefix to irq_polling_work Rename irq_polling_work to qaic_irq_polling_work to reduce ambiguity and avoid potential naming conflicts in the future. Signed-off-by: Zack McKevitt Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251031192511.3179130-1-zachary.mckevitt@oss.qualcomm.com --- drivers/accel/qaic/qaic.h | 2 +- drivers/accel/qaic/qaic_data.c | 2 +- drivers/accel/qaic/qaic_drv.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index 4a371ddc0a55..fa7a8155658c 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -348,7 +348,7 @@ int qaic_partial_execute_bo_ioctl(struct drm_device *dev, void *data, struct drm int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int qaic_perf_stats_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); int qaic_detach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv); -void irq_polling_work(struct work_struct *work); +void qaic_irq_polling_work(struct work_struct *work); void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id); void qaic_dbc_exit_ssr(struct qaic_device *qdev); diff --git a/drivers/accel/qaic/qaic_data.c b/drivers/accel/qaic/qaic_data.c index 50a2c3482fd1..60cb4d65d48e 100644 --- a/drivers/accel/qaic/qaic_data.c +++ b/drivers/accel/qaic/qaic_data.c @@ -1514,7 +1514,7 @@ irqreturn_t dbc_irq_handler(int irq, void *data) return IRQ_WAKE_THREAD; } -void irq_polling_work(struct work_struct *work) +void qaic_irq_polling_work(struct work_struct *work) { struct dma_bridge_chan *dbc = container_of(work, struct dma_bridge_chan, poll_work); unsigned long flags; diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index 68f5b5db8b44..4c70bd949d53 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -562,7 +562,7 @@ static int init_msi(struct qaic_device *qdev, struct pci_dev *pdev) qdev->dbc[i].irq = pci_irq_vector(pdev, qdev->single_msi ? 0 : i + 1); if (!qdev->single_msi) disable_irq_nosync(qdev->dbc[i].irq); - INIT_WORK(&qdev->dbc[i].poll_work, irq_polling_work); + INIT_WORK(&qdev->dbc[i].poll_work, qaic_irq_polling_work); } } From e96a7dc6de5f6c2cac8cc919584c95ae52cf1f9a Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Thu, 23 Oct 2025 22:33:50 +0100 Subject: [PATCH 17/71] dt-bindings: display: renesas,rzg2l-du: Add support for RZ/V2N SoC Document support for the DU IP found on the Renesas RZ/V2N (R9A09G056) SoC. The DU IP is functionally identical to that on the RZ/V2H(P) SoC, so no driver changes are needed. The existing `renesas,r9a09g057-du` compatible will be used as a fallback for the RZ/V2N SoC. Signed-off-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Biju Das Link: https://patch.msgid.link/20251023213350.681602-1-prabhakar.mahadev-lad.rj@bp.renesas.com --- .../devicetree/bindings/display/renesas,rzg2l-du.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml b/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml index 1e32d14b6edb..2cc66dcef870 100644 --- a/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml +++ b/Documentation/devicetree/bindings/display/renesas,rzg2l-du.yaml @@ -25,6 +25,9 @@ properties: - enum: - renesas,r9a07g054-du # RZ/V2L - const: renesas,r9a07g044-du # RZ/G2L fallback + - items: + - const: renesas,r9a09g056-du # RZ/V2N + - const: renesas,r9a09g057-du # RZ/V2H(P) fallback reg: maxItems: 1 From 6b83815bb114cbf9de5e6a71bc20015be8b17c9d Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:04:55 +0200 Subject: [PATCH 18/71] drm/vblank: use drm_crtc_vblank_crtc() in workers We have drm_crtc_vblank_crtc() to get the struct drm_vblank_crtc pointer for a crtc. Use it instead of poking at dev->vblank[] directly. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/f046701a10340c1dcaecb1b52e41dcf2236fded1.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/drm_vblank_work.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_vblank_work.c b/drivers/gpu/drm/drm_vblank_work.c index e4e1873f0e1e..70f0199251ea 100644 --- a/drivers/gpu/drm/drm_vblank_work.c +++ b/drivers/gpu/drm/drm_vblank_work.c @@ -244,7 +244,7 @@ EXPORT_SYMBOL(drm_vblank_work_flush); void drm_vblank_work_flush_all(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; - struct drm_vblank_crtc *vblank = &dev->vblank[drm_crtc_index(crtc)]; + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); spin_lock_irq(&dev->event_lock); wait_event_lock_irq(vblank->work_wait_queue, From 0830e122ce579d5c22461c5770c9586096fa3c60 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:04:58 +0200 Subject: [PATCH 19/71] drm/tidss: use drm_crtc_vblank_crtc() We have drm_crtc_vblank_crtc() to get the struct drm_vblank_crtc pointer for a crtc. Use it instead of poking at dev->vblank[] directly. Cc: Jyri Sarha Cc: Tomi Valkeinen Acked-by: Jyri Sarha Link: https://patch.msgid.link/ffd5ebe03391b3c01e616c0c844a4b8ddecede36.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/tidss/tidss_crtc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_crtc.c b/drivers/gpu/drm/tidss/tidss_crtc.c index 411b1a25e29c..8f81eb560b9e 100644 --- a/drivers/gpu/drm/tidss/tidss_crtc.c +++ b/drivers/gpu/drm/tidss/tidss_crtc.c @@ -248,8 +248,7 @@ static void tidss_crtc_atomic_enable(struct drm_crtc *crtc, dispc_vp_enable(tidss->dispc, tcrtc->hw_videoport); if (crtc->state->event) { - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &ddev->vblank[pipe]; + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); vblank->time = ktime_get(); From 44ea44ad7256964b5ace233cf69e589e29beeee7 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:04:59 +0200 Subject: [PATCH 20/71] drm/vmwgfx: use drm_crtc_vblank_crtc() We have drm_crtc_vblank_crtc() to get the struct drm_vblank_crtc pointer for a crtc. Use it instead of poking at dev->vblank[] directly. Cc: Zack Rusin Cc: Broadcom internal kernel review list Reviewed-by: Ian Forbes Link: https://patch.msgid.link/5157c2e927676aad75348855cf7b6745cba90003.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c index aec774fa4d7b..5abd7f5ad2db 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_vkms.c @@ -247,9 +247,8 @@ vmw_vkms_get_vblank_timestamp(struct drm_crtc *crtc, { struct drm_device *dev = crtc->dev; struct vmw_private *vmw = vmw_priv(dev); - unsigned int pipe = crtc->index; struct vmw_display_unit *du = vmw_crtc_to_du(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); if (!vmw->vkms_enabled) return false; @@ -281,8 +280,7 @@ vmw_vkms_enable_vblank(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct vmw_private *vmw = vmw_priv(dev); - unsigned int pipe = drm_crtc_index(crtc); - struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); struct vmw_display_unit *du = vmw_crtc_to_du(crtc); if (!vmw->vkms_enabled) From d930ffa5d6e8867a290db9c6aad1c62731aeb2c3 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:05:00 +0200 Subject: [PATCH 21/71] drm/gma500: use drm_crtc_vblank_crtc() We have drm_crtc_vblank_crtc() to get the struct drm_vblank_crtc pointer for a crtc. Use it instead of poking at dev->vblank[] directly. However, we also need to get the crtc to start with. We could use drm_crtc_from_index(), but refactor to use drm_for_each_crtc() instead. This is all a bit tedious, and perhaps the driver shouldn't be poking at vblank->enabled directly in the first place. But at least hide away the dev->vblank[] access in drm_vblank.c where it belongs. Cc: Patrik Jakobsson Acked-by: Patrik Jakobsson Link: https://patch.msgid.link/27b2c6772c68120d0d5ec28477db0d993743e955.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/gma500/psb_irq.c | 36 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/gma500/psb_irq.c b/drivers/gpu/drm/gma500/psb_irq.c index c224c7ff353c..3a946b472064 100644 --- a/drivers/gpu/drm/gma500/psb_irq.c +++ b/drivers/gpu/drm/gma500/psb_irq.c @@ -250,6 +250,7 @@ static irqreturn_t gma_irq_handler(int irq, void *arg) void gma_irq_preinstall(struct drm_device *dev) { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct drm_crtc *crtc; unsigned long irqflags; spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); @@ -260,10 +261,15 @@ void gma_irq_preinstall(struct drm_device *dev) PSB_WSGX32(0x00000000, PSB_CR_EVENT_HOST_ENABLE); PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE); - if (dev->vblank[0].enabled) - dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG; - if (dev->vblank[1].enabled) - dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG; + drm_for_each_crtc(crtc, dev) { + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); + + if (vblank->enabled) { + u32 mask = drm_crtc_index(crtc) ? _PSB_VSYNC_PIPEB_FLAG : + _PSB_VSYNC_PIPEA_FLAG; + dev_priv->vdc_irq_mask |= mask; + } + } /* Revisit this area - want per device masks ? */ if (dev_priv->ops->hotplug) @@ -278,8 +284,8 @@ void gma_irq_preinstall(struct drm_device *dev) void gma_irq_postinstall(struct drm_device *dev) { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); + struct drm_crtc *crtc; unsigned long irqflags; - unsigned int i; spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags); @@ -292,11 +298,13 @@ void gma_irq_postinstall(struct drm_device *dev) PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R); PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); - for (i = 0; i < dev->num_crtcs; ++i) { - if (dev->vblank[i].enabled) - gma_enable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE); + drm_for_each_crtc(crtc, dev) { + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); + + if (vblank->enabled) + gma_enable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE); else - gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE); + gma_disable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE); } if (dev_priv->ops->hotplug_enable) @@ -337,8 +345,8 @@ void gma_irq_uninstall(struct drm_device *dev) { struct drm_psb_private *dev_priv = to_drm_psb_private(dev); struct pci_dev *pdev = to_pci_dev(dev->dev); + struct drm_crtc *crtc; unsigned long irqflags; - unsigned int i; if (!dev_priv->irq_enabled) return; @@ -350,9 +358,11 @@ void gma_irq_uninstall(struct drm_device *dev) PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM); - for (i = 0; i < dev->num_crtcs; ++i) { - if (dev->vblank[i].enabled) - gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE); + drm_for_each_crtc(crtc, dev) { + struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc); + + if (vblank->enabled) + gma_disable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE); } dev_priv->vdc_irq_mask &= _PSB_IRQ_SGX_FLAG | From 23ab3cb314d945255e9734b509acf8cd21f6eee0 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:04:56 +0200 Subject: [PATCH 22/71] drm/atomic: use drm_crtc_vblank_waitqueue() We have drm_crtc_vblank_waitqueue() to get the wait_queue_head_t pointer for a vblank. Use it instead of poking at dev->vblank[] directly. Due to the macro maze of wait_event_timeout() that uses the address-of operator on the argument, we have to pass it in with the indirection operator. Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/1097348197acea9110da8baebbbc189890d01660.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/drm_atomic_helper.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c index 5a473a274ff0..e641fcf8c568 100644 --- a/drivers/gpu/drm/drm_atomic_helper.c +++ b/drivers/gpu/drm/drm_atomic_helper.c @@ -1831,10 +1831,12 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev, } for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) { + wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc); + if (!(crtc_mask & drm_crtc_mask(crtc))) continue; - ret = wait_event_timeout(dev->vblank[i].queue, + ret = wait_event_timeout(*queue, state->crtcs[i].last_vblank_count != drm_crtc_vblank_count(crtc), msecs_to_jiffies(100)); From 95eacb81d0d98775c9eb71dc13e6ef24110766b6 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 7 Nov 2025 13:04:57 +0200 Subject: [PATCH 23/71] drm/msm: use drm_crtc_vblank_waitqueue() We have drm_crtc_vblank_waitqueue() to get the wait_queue_head_t pointer for a vblank. Use it instead of poking at dev->vblank[] directly. Due to the macro maze of wait_event_timeout() that uses the address-of operator on the argument, we have to pass it in with the indirection operator. Cc: Rob Clark Cc: Dmitry Baryshkov Cc: Abhinav Kumar Cc: Jessica Zhang Cc: Sean Paul Cc: Marijn Suijten Cc: linux-arm-msm@vger.kernel.org Cc: freedreno@lists.freedesktop.org Acked-by: Dmitry Baryshkov Link: https://patch.msgid.link/5917fd537f4a775a1c135a68f294df3917980943.1762513240.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c | 3 ++- drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c index da53ca88251e..e8066f9fd534 100644 --- a/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c @@ -527,13 +527,14 @@ static void mdp4_crtc_wait_for_flush_done(struct drm_crtc *crtc) struct drm_device *dev = crtc->dev; struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); struct mdp4_kms *mdp4_kms = get_kms(crtc); + wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc); int ret; ret = drm_crtc_vblank_get(crtc); if (ret) return; - ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue, + ret = wait_event_timeout(*queue, !(mdp4_read(mdp4_kms, REG_MDP4_OVERLAY_FLUSH) & mdp4_crtc->flushed_mask), msecs_to_jiffies(50)); diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c index 4c4900a7beda..373ae7d9bf01 100644 --- a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c @@ -1234,6 +1234,7 @@ static void mdp5_crtc_wait_for_flush_done(struct drm_crtc *crtc) struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); struct mdp5_ctl *ctl = mdp5_cstate->ctl; + wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc); int ret; /* Should not call this function if crtc is disabled. */ @@ -1244,7 +1245,7 @@ static void mdp5_crtc_wait_for_flush_done(struct drm_crtc *crtc) if (ret) return; - ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue, + ret = wait_event_timeout(*queue, ((mdp5_ctl_get_commit_status(ctl) & mdp5_crtc->flushed_mask) == 0), msecs_to_jiffies(50)); From 3dd0be7a301bb0adcca4f7021cfd2e366ca90a0e Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Thu, 30 Oct 2025 10:14:11 +0100 Subject: [PATCH 24/71] drm/ast: Handle framebuffer from dma-buf In the atomic update callback, ast should call drm_gem_fb_begin_cpu_access() to make sure it can read the framebuffer from the CPU, otherwise the data might not be there due to cache, and synchronization. Tested on a Lenovo SE100, while rendering on the ArrowLake GPU with i915 driver, and using ast for display. Suggested-by: Thomas Zimmermann Reviewed-by: Thomas Zimmermann Link: https://patch.msgid.link/20251030091627.340780-1-jfalempe@redhat.com Signed-off-by: Jocelyn Falempe --- drivers/gpu/drm/ast/ast_mode.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/ast/ast_mode.c b/drivers/gpu/drm/ast/ast_mode.c index de7b6294ce40..cd08990a10f9 100644 --- a/drivers/gpu/drm/ast/ast_mode.c +++ b/drivers/gpu/drm/ast/ast_mode.c @@ -557,9 +557,14 @@ static void ast_primary_plane_helper_atomic_update(struct drm_plane *plane, ast_set_vbios_color_reg(ast, fb->format, ast_crtc_state->vmode); } - drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); - drm_atomic_for_each_plane_damage(&iter, &damage) { - ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage); + /* if the buffer comes from another device */ + if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE) == 0) { + drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state); + drm_atomic_for_each_plane_damage(&iter, &damage) { + ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage); + } + + drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE); } /* From 4492d54d59872bb72e119ff9f77969ab4d8a0e6b Mon Sep 17 00:00:00 2001 From: Akash Goel Date: Tue, 21 Oct 2025 09:10:42 +0100 Subject: [PATCH 25/71] drm/panthor: Fix potential memleak of vma structure This commit addresses a memleak issue of panthor_vma (or drm_gpuva) structure in Panthor driver, that can happen if the GPU page table update operation to map the pages fail. The issue is very unlikely to occur in practice. v2: Add panthor_vm_op_ctx_return_vma() helper (Boris) v3: Add WARN_ON_ONCE (Boris) Fixes: 647810ec2476 ("drm/panthor: Add the MMU/VM logical block") Signed-off-by: Akash Goel Reviewed-by: Boris Brezillon Reviewed-by: Steven Price Signed-off-by: Steven Price Link: https://patch.msgid.link/20251021081042.1377406-1-akash.goel@arm.com --- drivers/gpu/drm/panthor/panthor_mmu.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c index 58fead90533a..6a41dfd7aaf3 100644 --- a/drivers/gpu/drm/panthor/panthor_mmu.c +++ b/drivers/gpu/drm/panthor/panthor_mmu.c @@ -1147,6 +1147,20 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx, } } +static void +panthor_vm_op_ctx_return_vma(struct panthor_vm_op_ctx *op_ctx, + struct panthor_vma *vma) +{ + for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) { + if (!op_ctx->preallocated_vmas[i]) { + op_ctx->preallocated_vmas[i] = vma; + return; + } + } + + WARN_ON_ONCE(1); +} + static struct panthor_vma * panthor_vm_op_ctx_get_vma(struct panthor_vm_op_ctx *op_ctx) { @@ -2082,8 +2096,10 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv) ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags), op_ctx->map.sgt, op->map.gem.offset, op->map.va.range); - if (ret) + if (ret) { + panthor_vm_op_ctx_return_vma(op_ctx, vma); return ret; + } /* Ref owned by the mapping now, clear the obj field so we don't release the * pinning/obj ref behind GPUVA's back. From 6d61e37833ee151fc649212ccec490df19e8cde3 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Mon, 10 Nov 2025 10:59:52 +0700 Subject: [PATCH 26/71] accel/qaic: Separate DBC_STATE_* definition list Stephen Rothwell reports htmldocs warnings when merging drm-misc tree: Documentation/accel/qaic/aic100.rst:502: ERROR: Unexpected indentation. [docutils] Documentation/accel/qaic/aic100.rst:504: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils] Fix these by separating DBC_STATE_* definition list from preceding paragraph. Fixes: 9675093acea04c ("accel/qaic: Implement basic SSR handling") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/linux-next/20251110132401.200d88bd@canb.auug.org.au/ Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251110035952.25778-3-bagasdotme@gmail.com --- Documentation/accel/qaic/aic100.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/accel/qaic/aic100.rst b/Documentation/accel/qaic/aic100.rst index 3b287c3987d2..41331cf580b1 100644 --- a/Documentation/accel/qaic/aic100.rst +++ b/Documentation/accel/qaic/aic100.rst @@ -498,6 +498,7 @@ it desires to recover the workload. When SSR occurs for a specific NSP, the assigned DBC goes through the following state transactions in order: + DBC_STATE_BEFORE_SHUTDOWN Indicates that the affected NSP was found in an unrecoverable error condition. From be4cd2a13a31496c7fb9e46a244c4391b8b7cf31 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Mon, 10 Nov 2025 10:59:53 +0700 Subject: [PATCH 27/71] accel/qaic: Format DBC states table in sysfs ABI documentation Stephen Rothwell reports htmldocs warnings when merging drm-misc tree: Documentation/ABI/stable/sysfs-driver-qaic:1: ERROR: Unexpected indentation. [docutils] Documentation/ABI/stable/sysfs-driver-qaic:1: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils] Documentation/ABI/stable/sysfs-driver-qaic:1: WARNING: Definition list ends without a blank line; unexpected unindent. [docutils] These are caused by DMA Bridge channel (DBC) states list in sysfs ABI docs. Format it as a table to fix them. Fixes: f286066ed9df38 ("accel/qaic: Add DMA Bridge Channel(DBC) sysfs and uevents") Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/linux-next/20251110135038.29e96051@canb.auug.org.au/ Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Reviewed-by: Jeff Hugo Signed-off-by: Jeff Hugo Link: https://patch.msgid.link/20251110035952.25778-4-bagasdotme@gmail.com --- Documentation/ABI/stable/sysfs-driver-qaic | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-driver-qaic b/Documentation/ABI/stable/sysfs-driver-qaic index e5876935e62b..c767a93342b3 100644 --- a/Documentation/ABI/stable/sysfs-driver-qaic +++ b/Documentation/ABI/stable/sysfs-driver-qaic @@ -3,14 +3,17 @@ Date: October 2025 KernelVersion: 6.19 Contact: Jeff Hugo Description: Represents the current state of DMA Bridge channel (DBC). Below are the possible - states, - IDLE (0) - DBC is free and can be activated - ASSIGNED (1) - DBC is activated and a workload is running on device - BEFORE_SHUTDOWN (2) - Sub-system associated with this workload has crashed and + states: + + =================== ========================================================== + IDLE (0) DBC is free and can be activated + ASSIGNED (1) DBC is activated and a workload is running on device + BEFORE_SHUTDOWN (2) Sub-system associated with this workload has crashed and it will shutdown soon - AFTER_SHUTDOWN (3) - Sub-system associated with this workload has crashed and + AFTER_SHUTDOWN (3) Sub-system associated with this workload has crashed and it has shutdown - BEFORE_POWER_UP (4) - Sub-system associated with this workload is shutdown and + BEFORE_POWER_UP (4) Sub-system associated with this workload is shutdown and it will be powered up soon - AFTER_POWER_UP (5) - Sub-system associated with this workload is now powered up + AFTER_POWER_UP (5) Sub-system associated with this workload is now powered up + =================== ========================================================== Users: Any userspace application or clients interested in DBC state. From 63cc028484abb7c2c61118f492b95083a2913b12 Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Thu, 6 Nov 2025 11:10:51 +0100 Subject: [PATCH 28/71] accel/ivpu: Add fdinfo support for memory statistics Implement DRM fdinfo interface to expose memory usage statistics for NPU device file descriptors. Exclude unpinned and imported buffers from resident memory calculations to provide accurate memory usage reporting. Reviewed-by: Jeff Hugo Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20251106101052.1050348-2-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 6 ++++++ drivers/accel/ivpu/ivpu_gem.c | 12 ++++++++++++ drivers/accel/ivpu/ivpu_gem.h | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index b305effcf003..ce7dbd473059 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -455,6 +455,9 @@ int ivpu_shutdown(struct ivpu_device *vdev) static const struct file_operations ivpu_fops = { .owner = THIS_MODULE, DRM_ACCEL_FOPS, +#if CONFIG_PROC_FS + .show_fdinfo = drm_show_fdinfo, +#endif }; static const struct drm_driver driver = { @@ -469,6 +472,9 @@ static const struct drm_driver driver = { .ioctls = ivpu_drm_ioctls, .num_ioctls = ARRAY_SIZE(ivpu_drm_ioctls), .fops = &ivpu_fops, +#if CONFIG_PROC_FS + .show_fdinfo = drm_show_memory_stats, +#endif .name = DRIVER_NAME, .desc = DRIVER_DESC, diff --git a/drivers/accel/ivpu/ivpu_gem.c b/drivers/accel/ivpu/ivpu_gem.c index 74b12c7e6caf..ece68f570b7e 100644 --- a/drivers/accel/ivpu/ivpu_gem.c +++ b/drivers/accel/ivpu/ivpu_gem.c @@ -333,6 +333,17 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj) drm_gem_shmem_free(&bo->base); } +static enum drm_gem_object_status ivpu_gem_status(struct drm_gem_object *obj) +{ + struct ivpu_bo *bo = to_ivpu_bo(obj); + enum drm_gem_object_status status = 0; + + if (ivpu_bo_is_resident(bo)) + status |= DRM_GEM_OBJECT_RESIDENT; + + return status; +} + static const struct drm_gem_object_funcs ivpu_gem_funcs = { .free = ivpu_gem_bo_free, .open = ivpu_gem_bo_open, @@ -343,6 +354,7 @@ static const struct drm_gem_object_funcs ivpu_gem_funcs = { .vmap = drm_gem_shmem_object_vmap, .vunmap = drm_gem_shmem_object_vunmap, .mmap = drm_gem_shmem_object_mmap, + .status = ivpu_gem_status, .vm_ops = &drm_gem_shmem_vm_ops, }; diff --git a/drivers/accel/ivpu/ivpu_gem.h b/drivers/accel/ivpu/ivpu_gem.h index 2dcd7eba9cb7..0c3350f22b55 100644 --- a/drivers/accel/ivpu/ivpu_gem.h +++ b/drivers/accel/ivpu/ivpu_gem.h @@ -82,6 +82,11 @@ static inline bool ivpu_bo_is_read_only(struct ivpu_bo *bo) return bo->flags & DRM_IVPU_BO_READ_ONLY; } +static inline bool ivpu_bo_is_resident(struct ivpu_bo *bo) +{ + return !!bo->base.pages; +} + static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u32 vpu_addr) { if (vpu_addr < bo->vpu_addr) From 5ce6778a31b4b1468211b75aa84166c5f2db4c3c Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Thu, 6 Nov 2025 11:10:52 +0100 Subject: [PATCH 29/71] accel/ivpu: Count only resident buffers in memory utilization Do not count buffer objects that have no backing pages, including imported buffers where pages are set by VM faults triggered by userspace or pinned by other drivers. Instead, return information about actual memory used by the NPU. Counting imported buffers results in incorrect calculations when the same pages are counted multiple times, giving overly high results. Fixes: 7bfc9fa99580 ("accel/ivpu: Expose NPU memory utilization info in sysfs") Reviewed-by: Jeff Hugo Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20251106101052.1050348-3-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_sysfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/accel/ivpu/ivpu_sysfs.c b/drivers/accel/ivpu/ivpu_sysfs.c index 268ab7744a8b..d250a10caca9 100644 --- a/drivers/accel/ivpu/ivpu_sysfs.c +++ b/drivers/accel/ivpu/ivpu_sysfs.c @@ -63,7 +63,8 @@ npu_memory_utilization_show(struct device *dev, struct device_attribute *attr, c mutex_lock(&vdev->bo_list_lock); list_for_each_entry(bo, &vdev->bo_list, bo_list_node) - total_npu_memory += bo->base.base.size; + if (ivpu_bo_is_resident(bo)) + total_npu_memory += ivpu_bo_size(bo); mutex_unlock(&vdev->bo_list_lock); return sysfs_emit(buf, "%lld\n", total_npu_memory); From ccb7725df5bb3f1ad9606aa12d13688bad4deeab Mon Sep 17 00:00:00 2001 From: Karol Wachowski Date: Wed, 12 Nov 2025 08:19:11 +0100 Subject: [PATCH 30/71] accel/ivpu: Fix warning due to undefined CONFIG_PROC_FS Change #if to #ifdef CONFIG_PROC_FS to fix warning reported by test robot: drivers/accel/ivpu/ivpu_drv.c:458:5: warning: "CONFIG_PROC_FS" is not defined, evaluates to 0 [-Wundef] Fixes: 63cc028484ab ("accel/ivpu: Add fdinfo support for memory statistics") Reviewed-by: Maciej Falkowski Reviewed-by: Andrzej.Kacprowski@linux.intel.com Signed-off-by: Karol Wachowski Link: https://patch.msgid.link/20251112071911.1136934-1-karol.wachowski@linux.intel.com --- drivers/accel/ivpu/ivpu_drv.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/accel/ivpu/ivpu_drv.c b/drivers/accel/ivpu/ivpu_drv.c index ce7dbd473059..3d6fccdefdd6 100644 --- a/drivers/accel/ivpu/ivpu_drv.c +++ b/drivers/accel/ivpu/ivpu_drv.c @@ -455,7 +455,7 @@ int ivpu_shutdown(struct ivpu_device *vdev) static const struct file_operations ivpu_fops = { .owner = THIS_MODULE, DRM_ACCEL_FOPS, -#if CONFIG_PROC_FS +#ifdef CONFIG_PROC_FS .show_fdinfo = drm_show_fdinfo, #endif }; @@ -472,7 +472,7 @@ static const struct drm_driver driver = { .ioctls = ivpu_drm_ioctls, .num_ioctls = ARRAY_SIZE(ivpu_drm_ioctls), .fops = &ivpu_fops, -#if CONFIG_PROC_FS +#ifdef CONFIG_PROC_FS .show_fdinfo = drm_show_memory_stats, #endif From 4520911eeee0f07241758379d6202156fe4fe65c Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:13 +0100 Subject: [PATCH 31/71] drm/sun4i: mixer: Fix up DE33 channel macros Properly define macros. Till now raw numbers and inappropriate macro was used. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-2-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index a1c1cbccc654..b5badfa2c997 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -39,6 +39,9 @@ #define DE3_CH_BASE 0x1000 #define DE3_CH_SIZE 0x0800 +#define DE33_CH_BASE 0x1000 +#define DE33_CH_SIZE 0x20000 + #define SUN8I_MIXER_BLEND_PIPE_CTL(base) ((base) + 0) #define SUN8I_MIXER_BLEND_ATTR_FCOLOR(base, x) ((base) + 0x4 + 0x10 * (x)) #define SUN8I_MIXER_BLEND_ATTR_INSIZE(base, x) ((base) + 0x8 + 0x10 * (x)) @@ -242,7 +245,7 @@ static inline u32 sun8i_channel_base(struct sun8i_mixer *mixer, int channel) { if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - return mixer->cfg->map[channel] * 0x20000 + DE2_CH_SIZE; + return DE33_CH_BASE + mixer->cfg->map[channel] * DE33_CH_SIZE; else if (mixer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_CH_BASE + channel * DE3_CH_SIZE; else From f863fff123570a908fd03c8638ad9f29495fc847 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:14 +0100 Subject: [PATCH 32/71] drm/sun4i: mixer: Remove ccsc cfg for >= DE3 Those engine versions don't need ccsc argument, since CSC units are located on different position and for each layer. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-3-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index e2a532e11183..a0382a970e8e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -791,7 +791,6 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = { }; static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE3, .mod_rate = 600000000, .scaler_mask = 0xf, @@ -801,7 +800,6 @@ static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = { }; static const struct sun8i_mixer_cfg sun50i_h616_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE33, .mod_rate = 600000000, .scaler_mask = 0xf, From 558716d5586ae9e02847d8426ae8048701e5e913 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:15 +0100 Subject: [PATCH 33/71] drm/sun4i: de2: Initialize layer fields earlier drm_universal_plane_init() can already call some callbacks, like format_mod_supported, during initialization. Because of that, fields should be initialized beforehand. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-4-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 9 +++++---- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 6108dda1e414..12c138165c35 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -304,6 +304,11 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, if (!layer) return ERR_PTR(-ENOMEM); + layer->mixer = mixer; + layer->type = SUN8I_LAYER_TYPE_UI; + layer->channel = channel; + layer->overlay = 0; + if (index == 0) type = DRM_PLANE_TYPE_PRIMARY; @@ -334,10 +339,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, } drm_plane_helper_add(&layer->plane, &sun8i_ui_layer_helper_funcs); - layer->mixer = mixer; - layer->type = SUN8I_LAYER_TYPE_UI; - layer->channel = channel; - layer->overlay = 0; return layer; } diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index de2fe1942840..2eec23a2c0d4 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -485,6 +485,11 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, if (!layer) return ERR_PTR(-ENOMEM); + layer->mixer = mixer; + layer->type = SUN8I_LAYER_TYPE_VI; + layer->channel = index; + layer->overlay = 0; + if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { formats = sun8i_vi_layer_de3_formats; format_count = ARRAY_SIZE(sun8i_vi_layer_de3_formats); @@ -543,10 +548,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, } drm_plane_helper_add(&layer->plane, &sun8i_vi_layer_helper_funcs); - layer->mixer = mixer; - layer->type = SUN8I_LAYER_TYPE_VI; - layer->channel = index; - layer->overlay = 0; return layer; } From f841ea7e3f7ea6409ea34235dd4b4390e368b620 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:16 +0100 Subject: [PATCH 34/71] drm/sun4i: ui_layer: Move check from update to check callback DRM requires that all checks are done in atomic_check callback. Move one check from atomic_commit to atomic_check callback. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-5-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 12c138165c35..75394b56d75f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -135,16 +135,11 @@ static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, struct drm_plane_state *state = plane->state; const struct drm_format_info *fmt; u32 val, ch_base, hw_fmt; - int ret; ch_base = sun8i_channel_base(mixer, channel); fmt = state->fb->format; - ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); - if (ret || fmt->is_yuv) { - DRM_DEBUG_DRIVER("Invalid format\n"); - return -EINVAL; - } + sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); val = hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; regmap_update_bits(mixer->engine.regs, @@ -202,7 +197,9 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, struct sun8i_layer *layer = plane_to_sun8i_layer(plane); struct drm_crtc *crtc = new_plane_state->crtc; struct drm_crtc_state *crtc_state; - int min_scale, max_scale; + const struct drm_format_info *fmt; + int min_scale, max_scale, ret; + u32 hw_fmt; if (!crtc) return 0; @@ -211,6 +208,13 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; + fmt = new_plane_state->fb->format; + ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); + if (ret || fmt->is_yuv) { + DRM_DEBUG_DRIVER("Invalid plane format\n"); + return -EINVAL; + } + min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; From b12c28bb15d374b89908c1cecc9251a631149c37 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:17 +0100 Subject: [PATCH 35/71] drm/sun4i: vi_layer: Move check from update to check callback DRM requires that all check are done in atomic_check callback. Move one check from atomic_commit to atomic_check callback. Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Reviewed-by: Chen-Yu Tsai Link: https://patch.msgid.link/20251104180942.61538-6-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 2eec23a2c0d4..bec92a3f31f9 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -218,16 +218,11 @@ static int sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, struct drm_plane_state *state = plane->state; u32 val, ch_base, csc_mode, hw_fmt; const struct drm_format_info *fmt; - int ret; ch_base = sun8i_channel_base(mixer, channel); fmt = state->fb->format; - ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); - if (ret) { - DRM_DEBUG_DRIVER("Invalid format\n"); - return ret; - } + sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET; regmap_update_bits(mixer->engine.regs, @@ -323,7 +318,9 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, struct sun8i_layer *layer = plane_to_sun8i_layer(plane); struct drm_crtc *crtc = new_plane_state->crtc; struct drm_crtc_state *crtc_state; - int min_scale, max_scale; + const struct drm_format_info *fmt; + int min_scale, max_scale, ret; + u32 hw_fmt; if (!crtc) return 0; @@ -332,6 +329,13 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, if (WARN_ON(!crtc_state)) return -EINVAL; + fmt = new_plane_state->fb->format; + ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); + if (ret) { + DRM_DEBUG_DRIVER("Invalid plane format\n"); + return ret; + } + min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; From 1fbf862685fa6e7c0673d7aa0939ed71adcabbce Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:18 +0100 Subject: [PATCH 36/71] drm/sun4i: layers: Make atomic commit functions void Functions called by atomic_commit callback should not fail. None of them actually returns error, so make them void. No functional change. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-7-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 20 +++++++------------- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 20 +++++++------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 75394b56d75f..3c86e890b06c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -47,9 +47,9 @@ static void sun8i_ui_layer_update_alpha(struct sun8i_mixer *mixer, int channel, mask, val); } -static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane, - unsigned int zpos) +static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane, + unsigned int zpos) { struct drm_plane_state *state = plane->state; u32 src_w, src_h, dst_w, dst_h; @@ -125,12 +125,10 @@ static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, regmap_write(bld_regs, SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), outsize); - - return 0; } -static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; const struct drm_format_info *fmt; @@ -145,12 +143,10 @@ static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); - - return 0; } -static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; struct drm_framebuffer *fb = state->fb; @@ -185,8 +181,6 @@ static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, regmap_write(mixer->engine.regs, SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, overlay), lower_32_bits(dma_addr)); - - return 0; } static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index bec92a3f31f9..02e8ef1b137c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -49,9 +49,9 @@ static void sun8i_vi_layer_update_alpha(struct sun8i_mixer *mixer, int channel, } } -static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane, - unsigned int zpos) +static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane, + unsigned int zpos) { struct drm_plane_state *state = plane->state; const struct drm_format_info *format = state->fb->format; @@ -192,8 +192,6 @@ static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, regmap_write(bld_regs, SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), outsize); - - return 0; } static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format) @@ -212,8 +210,8 @@ static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format) } } -static int sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; u32 val, ch_base, csc_mode, hw_fmt; @@ -247,12 +245,10 @@ static int sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, regmap_update_bits(mixer->engine.regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE, val); - - return 0; } -static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; struct drm_framebuffer *fb = state->fb; @@ -306,8 +302,6 @@ static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, overlay, i), lower_32_bits(dma_addr)); } - - return 0; } static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, From 06e644c7586c265956489581282ee40743d8c7b7 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:19 +0100 Subject: [PATCH 37/71] drm/sun4i: Move blender config from layers to mixer With upcoming DE33 support, layer management must be decoupled from other operations like blender configuration. There are two reasons: - DE33 will have separate driver for planes and thus it will be harder to manage different register spaces - Architecturaly it's better to split access by modules. Blender is now exclusively managed by mixer. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-8-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 17 ++++++++++++++--- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 22 +++------------------- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 22 +++------------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index a0382a970e8e..c06c11137d31 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -284,8 +284,8 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, drm_for_each_plane(plane, state->dev) { struct sun8i_layer *layer = plane_to_sun8i_layer(plane); + int w, h, x, y, zpos; bool enable; - int zpos; if (!(plane->possible_crtcs & drm_crtc_mask(crtc)) || layer->mixer != mixer) continue; @@ -296,10 +296,14 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, enable = plane_state->crtc && plane_state->visible; zpos = plane_state->normalized_zpos; + x = plane_state->dst.x1; + y = plane_state->dst.y1; + w = drm_rect_width(&plane_state->dst); + h = drm_rect_height(&plane_state->dst); - DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d\n", + DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d x=%d y=%d w=%d h=%d\n", plane->base.id, layer->channel, layer->overlay, - enable, zpos); + enable, zpos, x, y, w, h); /* * We always update the layer enable bit, because it can clear @@ -313,6 +317,13 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, /* Route layer to pipe based on zpos */ route |= layer->channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); pipe_en |= SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos); + + regmap_write(bld_regs, + SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos), + SUN8I_MIXER_COORD(x, y)); + regmap_write(bld_regs, + SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), + SUN8I_MIXER_SIZE(w, h)); } regmap_write(bld_regs, SUN8I_MIXER_BLEND_ROUTE(bld_base), route); diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 3c86e890b06c..bf9cce262745 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -48,21 +48,17 @@ static void sun8i_ui_layer_update_alpha(struct sun8i_mixer *mixer, int channel, } static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane, - unsigned int zpos) + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; u32 src_w, src_h, dst_w, dst_h; - struct regmap *bld_regs; - u32 bld_base, ch_base; u32 outsize, insize; u32 hphase, vphase; + u32 ch_base; DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n", channel, overlay); - bld_base = sun8i_blender_base(mixer); - bld_regs = sun8i_blender_regmap(mixer); ch_base = sun8i_channel_base(mixer, channel); src_w = drm_rect_width(&state->src) >> 16; @@ -114,17 +110,6 @@ static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, else sun8i_ui_scaler_enable(mixer, channel, false); } - - /* Set base coordinates */ - DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n", - state->dst.x1, state->dst.y1); - DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h); - regmap_write(bld_regs, - SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos), - SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1)); - regmap_write(bld_regs, - SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), - outsize); } static void sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, @@ -230,14 +215,13 @@ static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); struct sun8i_layer *layer = plane_to_sun8i_layer(plane); - unsigned int zpos = new_state->normalized_zpos; struct sun8i_mixer *mixer = layer->mixer; if (!new_state->crtc || !new_state->visible) return; sun8i_ui_layer_update_coord(mixer, layer->channel, - layer->overlay, plane, zpos); + layer->overlay, plane); sun8i_ui_layer_update_alpha(mixer, layer->channel, layer->overlay, plane); sun8i_ui_layer_update_formats(mixer, layer->channel, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 02e8ef1b137c..25d4e1710c1a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -50,25 +50,21 @@ static void sun8i_vi_layer_update_alpha(struct sun8i_mixer *mixer, int channel, } static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane, - unsigned int zpos) + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; const struct drm_format_info *format = state->fb->format; u32 src_w, src_h, dst_w, dst_h; - struct regmap *bld_regs; - u32 bld_base, ch_base; u32 outsize, insize; u32 hphase, vphase; u32 hn = 0, hm = 0; u32 vn = 0, vm = 0; bool subsampled; + u32 ch_base; DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n", channel, overlay); - bld_base = sun8i_blender_base(mixer); - bld_regs = sun8i_blender_regmap(mixer); ch_base = sun8i_channel_base(mixer, channel); src_w = drm_rect_width(&state->src) >> 16; @@ -181,17 +177,6 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, SUN8I_MIXER_CHAN_VI_VDS_UV(ch_base), SUN8I_MIXER_CHAN_VI_DS_N(vn) | SUN8I_MIXER_CHAN_VI_DS_M(vm)); - - /* Set base coordinates */ - DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n", - state->dst.x1, state->dst.y1); - DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h); - regmap_write(bld_regs, - SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos), - SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1)); - regmap_write(bld_regs, - SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos), - outsize); } static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format) @@ -350,14 +335,13 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); struct sun8i_layer *layer = plane_to_sun8i_layer(plane); - unsigned int zpos = new_state->normalized_zpos; struct sun8i_mixer *mixer = layer->mixer; if (!new_state->crtc || !new_state->visible) return; sun8i_vi_layer_update_coord(mixer, layer->channel, - layer->overlay, plane, zpos); + layer->overlay, plane); sun8i_vi_layer_update_alpha(mixer, layer->channel, layer->overlay, plane); sun8i_vi_layer_update_formats(mixer, layer->channel, From 1251b20a41cb0ad0b0c1080554b62d36ddb5eee4 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:20 +0100 Subject: [PATCH 38/71] drm/sun4i: ui layer: Write attributes in one go It turns out that none of the UI channel registers were meant to be read. Mostly it works fine but sometimes it returns incorrect values. Rework UI layer code to write all registers in one go to avoid reads. This rework will also allow proper code separation. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-9-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 50 +++++++++----------------- 1 file changed, 16 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index bf9cce262745..3a20be01ed4f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -26,25 +26,27 @@ #include "sun8i_ui_scaler.h" #include "sun8i_vi_scaler.h" -static void sun8i_ui_layer_update_alpha(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_ui_layer_update_attributes(struct sun8i_mixer *mixer, + int channel, int overlay, + struct drm_plane *plane) { - u32 mask, val, ch_base; + struct drm_plane_state *state = plane->state; + const struct drm_format_info *fmt; + u32 val, ch_base, hw_fmt; ch_base = sun8i_channel_base(mixer, channel); + fmt = state->fb->format; + sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); - mask = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK | - SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK; - - val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(plane->state->alpha >> 8); - - val |= (plane->state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? + val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(state->alpha >> 8); + val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_PIXEL : SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_COMBINED; + val |= hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; + val |= SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), - mask, val); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), val); } static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, @@ -112,24 +114,6 @@ static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, } } -static void sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) -{ - struct drm_plane_state *state = plane->state; - const struct drm_format_info *fmt; - u32 val, ch_base, hw_fmt; - - ch_base = sun8i_channel_base(mixer, channel); - - fmt = state->fb->format; - sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); - - val = hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), - SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val); -} - static void sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, int overlay, struct drm_plane *plane) { @@ -220,12 +204,10 @@ static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, if (!new_state->crtc || !new_state->visible) return; + sun8i_ui_layer_update_attributes(mixer, layer->channel, + layer->overlay, plane); sun8i_ui_layer_update_coord(mixer, layer->channel, layer->overlay, plane); - sun8i_ui_layer_update_alpha(mixer, layer->channel, - layer->overlay, plane); - sun8i_ui_layer_update_formats(mixer, layer->channel, - layer->overlay, plane); sun8i_ui_layer_update_buffer(mixer, layer->channel, layer->overlay, plane); } From 6d166e222fe94a7329f1b164904e272081fff913 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:21 +0100 Subject: [PATCH 39/71] drm/sun4i: vi layer: Write attributes in one go It turns out that none of the VI channel registers were meant to be read. Mostly it works fine but sometimes it returns incorrect values. Rework VI layer code to write all registers in one go to avoid reads. This rework will also allow proper code separation. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-10-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 71 ++++++++++---------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 25d4e1710c1a..18f2df30f40e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -19,34 +19,35 @@ #include "sun8i_vi_layer.h" #include "sun8i_vi_scaler.h" -static void sun8i_vi_layer_update_alpha(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_attributes(struct sun8i_mixer *mixer, + int channel, int overlay, + struct drm_plane *plane) { - u32 mask, val, ch_base; + struct drm_plane_state *state = plane->state; + const struct drm_format_info *fmt; + u32 val, ch_base, hw_fmt; ch_base = sun8i_channel_base(mixer, channel); + fmt = state->fb->format; + sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); + val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET; + if (!fmt->is_yuv) + val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE; + val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN; if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { - mask = SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MASK | - SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_MASK; - val = SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA - (plane->state->alpha >> 8); - - val |= (plane->state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? + val |= SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA(state->alpha >> 8); + val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL : SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED; - - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, - overlay), - mask, val); } else if (mixer->cfg->vi_num == 1) { - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, - SUN8I_MIXER_FCC_GLOBAL_ALPHA_MASK, - SUN8I_MIXER_FCC_GLOBAL_ALPHA - (plane->state->alpha >> 8)); + regmap_write(mixer->engine.regs, + SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, + SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); } + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), val); } static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, @@ -195,23 +196,14 @@ static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format) } } -static void sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_colors(struct sun8i_mixer *mixer, int channel, + int overlay, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - u32 val, ch_base, csc_mode, hw_fmt; const struct drm_format_info *fmt; - - ch_base = sun8i_channel_base(mixer, channel); + u32 csc_mode; fmt = state->fb->format; - sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); - - val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET; - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), - SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_MASK, val); - csc_mode = sun8i_vi_layer_get_csc_mode(fmt); if (csc_mode != SUN8I_CSC_MODE_OFF) { sun8i_csc_set_ccsc_coefficients(mixer, channel, csc_mode, @@ -221,15 +213,6 @@ static void sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel } else { sun8i_csc_enable_ccsc(mixer, channel, false); } - - if (!fmt->is_yuv) - val = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE; - else - val = 0; - - regmap_update_bits(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), - SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE, val); } static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, @@ -340,12 +323,12 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, if (!new_state->crtc || !new_state->visible) return; + sun8i_vi_layer_update_attributes(mixer, layer->channel, + layer->overlay, plane); sun8i_vi_layer_update_coord(mixer, layer->channel, layer->overlay, plane); - sun8i_vi_layer_update_alpha(mixer, layer->channel, - layer->overlay, plane); - sun8i_vi_layer_update_formats(mixer, layer->channel, - layer->overlay, plane); + sun8i_vi_layer_update_colors(mixer, layer->channel, + layer->overlay, plane); sun8i_vi_layer_update_buffer(mixer, layer->channel, layer->overlay, plane); } From fc9b4e3b78d9f03e84154f3331722c470266bc1f Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:22 +0100 Subject: [PATCH 40/71] drm/sun4i: mixer: Move layer enabling to atomic_update Enable or disable layer only in layer atomic update callback. Doing so will enable having separate layer driver later for DE33. There is no fear that enable bit would be set incorrectly, as all read-modify-write sequences for that register are now eliminated. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-11-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 24 ------------------------ drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 13 ++++++++++++- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 13 ++++++++++++- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index c06c11137d31..091ea109713b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -251,24 +251,6 @@ int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format) return -EINVAL; } -static void sun8i_layer_enable(struct sun8i_layer *layer, bool enable) -{ - u32 ch_base = sun8i_channel_base(layer->mixer, layer->channel); - u32 val, reg, mask; - - if (layer->type == SUN8I_LAYER_TYPE_UI) { - val = enable ? SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN : 0; - mask = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; - reg = SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay); - } else { - val = enable ? SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN : 0; - mask = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN; - reg = SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay); - } - - regmap_update_bits(layer->mixer->engine.regs, reg, mask, val); -} - static void sun8i_mixer_commit(struct sunxi_engine *engine, struct drm_crtc *crtc, struct drm_atomic_state *state) @@ -305,12 +287,6 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, plane->base.id, layer->channel, layer->overlay, enable, zpos, x, y, w, h); - /* - * We always update the layer enable bit, because it can clear - * spontaneously for unknown reasons. - */ - sun8i_layer_enable(layer, enable); - if (!enable) continue; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 3a20be01ed4f..939a704917a6 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -26,6 +26,15 @@ #include "sun8i_ui_scaler.h" #include "sun8i_vi_scaler.h" +static void sun8i_ui_layer_disable(struct sun8i_mixer *mixer, + int channel, int overlay) +{ + u32 ch_base = sun8i_channel_base(mixer, channel); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), 0); +} + static void sun8i_ui_layer_update_attributes(struct sun8i_mixer *mixer, int channel, int overlay, struct drm_plane *plane) @@ -201,8 +210,10 @@ static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, struct sun8i_layer *layer = plane_to_sun8i_layer(plane); struct sun8i_mixer *mixer = layer->mixer; - if (!new_state->crtc || !new_state->visible) + if (!new_state->crtc || !new_state->visible) { + sun8i_ui_layer_disable(mixer, layer->channel, layer->overlay); return; + } sun8i_ui_layer_update_attributes(mixer, layer->channel, layer->overlay, plane); diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 18f2df30f40e..03e55de3850e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -19,6 +19,15 @@ #include "sun8i_vi_layer.h" #include "sun8i_vi_scaler.h" +static void sun8i_vi_layer_disable(struct sun8i_mixer *mixer, + int channel, int overlay) +{ + u32 ch_base = sun8i_channel_base(mixer, channel); + + regmap_write(mixer->engine.regs, + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), 0); +} + static void sun8i_vi_layer_update_attributes(struct sun8i_mixer *mixer, int channel, int overlay, struct drm_plane *plane) @@ -320,8 +329,10 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, struct sun8i_layer *layer = plane_to_sun8i_layer(plane); struct sun8i_mixer *mixer = layer->mixer; - if (!new_state->crtc || !new_state->visible) + if (!new_state->crtc || !new_state->visible) { + sun8i_vi_layer_disable(mixer, layer->channel, layer->overlay); return; + } sun8i_vi_layer_update_attributes(mixer, layer->channel, layer->overlay, plane); From 870e3cf48c56c99d4f1d1a8401b8579464c78131 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:23 +0100 Subject: [PATCH 41/71] drm/sun4i: de2/de3: Simplify CSC config interface Merging both function into one lets this one decide on it's own if CSC should be enabled or not. Currently heuristics for that is pretty simple - enable it for YUV formats and disable for RGB. DE3 and newer allows YUV pipeline, which will be easier to implement these way. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-12-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_csc.c | 89 ++++++++++---------------- drivers/gpu/drm/sun4i/sun8i_csc.h | 9 ++- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 11 +--- 3 files changed, 40 insertions(+), 69 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c index c100d29b1a89..cf0c5121661b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.c +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -107,23 +107,28 @@ static const u32 yuv2rgb_de3[2][3][12] = { }, }; -static void sun8i_csc_set_coefficients(struct regmap *map, u32 base, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range) +static void sun8i_csc_setup(struct regmap *map, u32 base, + enum sun8i_csc_mode mode, + enum drm_color_encoding encoding, + enum drm_color_range range) { + u32 base_reg, val; const u32 *table; - u32 base_reg; int i; table = yuv2rgb[range][encoding]; switch (mode) { + case SUN8I_CSC_MODE_OFF: + val = 0; + break; case SUN8I_CSC_MODE_YUV2RGB: + val = SUN8I_CSC_CTRL_EN; base_reg = SUN8I_CSC_COEFF(base, 0); regmap_bulk_write(map, base_reg, table, 12); break; case SUN8I_CSC_MODE_YVU2RGB: + val = SUN8I_CSC_CTRL_EN; for (i = 0; i < 12; i++) { if ((i & 3) == 1) base_reg = SUN8I_CSC_COEFF(base, i + 1); @@ -135,28 +140,37 @@ static void sun8i_csc_set_coefficients(struct regmap *map, u32 base, } break; default: + val = 0; DRM_WARN("Wrong CSC mode specified.\n"); return; } + + regmap_write(map, SUN8I_CSC_CTRL(base), val); } -static void sun8i_de3_ccsc_set_coefficients(struct regmap *map, int layer, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range) +static void sun8i_de3_ccsc_setup(struct regmap *map, int layer, + enum sun8i_csc_mode mode, + enum drm_color_encoding encoding, + enum drm_color_range range) { + u32 addr, val, mask; const u32 *table; - u32 addr; int i; + mask = SUN50I_MIXER_BLEND_CSC_CTL_EN(layer); table = yuv2rgb_de3[range][encoding]; switch (mode) { + case SUN8I_CSC_MODE_OFF: + val = 0; + break; case SUN8I_CSC_MODE_YUV2RGB: + val = mask; addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE, layer, 0); regmap_bulk_write(map, addr, table, 12); break; case SUN8I_CSC_MODE_YVU2RGB: + val = mask; for (i = 0; i < 12; i++) { if ((i & 3) == 1) addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE, @@ -173,67 +187,30 @@ static void sun8i_de3_ccsc_set_coefficients(struct regmap *map, int layer, } break; default: + val = 0; DRM_WARN("Wrong CSC mode specified.\n"); return; } -} - -static void sun8i_csc_enable(struct regmap *map, u32 base, bool enable) -{ - u32 val; - - if (enable) - val = SUN8I_CSC_CTRL_EN; - else - val = 0; - - regmap_update_bits(map, SUN8I_CSC_CTRL(base), SUN8I_CSC_CTRL_EN, val); -} - -static void sun8i_de3_ccsc_enable(struct regmap *map, int layer, bool enable) -{ - u32 val, mask; - - mask = SUN50I_MIXER_BLEND_CSC_CTL_EN(layer); - - if (enable) - val = mask; - else - val = 0; regmap_update_bits(map, SUN50I_MIXER_BLEND_CSC_CTL(DE3_BLD_BASE), mask, val); } -void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range) +void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, + enum sun8i_csc_mode mode, + enum drm_color_encoding encoding, + enum drm_color_range range) { u32 base; if (mixer->cfg->de_type == SUN8I_MIXER_DE3) { - sun8i_de3_ccsc_set_coefficients(mixer->engine.regs, layer, - mode, encoding, range); + sun8i_de3_ccsc_setup(mixer->engine.regs, layer, + mode, encoding, range); return; } base = ccsc_base[mixer->cfg->ccsc][layer]; - sun8i_csc_set_coefficients(mixer->engine.regs, base, - mode, encoding, range); -} - -void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable) -{ - u32 base; - - if (mixer->cfg->de_type == SUN8I_MIXER_DE3) { - sun8i_de3_ccsc_enable(mixer->engine.regs, layer, enable); - return; - } - - base = ccsc_base[mixer->cfg->ccsc][layer]; - - sun8i_csc_enable(mixer->engine.regs, base, enable); + sun8i_csc_setup(mixer->engine.regs, base, + mode, encoding, range); } diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.h b/drivers/gpu/drm/sun4i/sun8i_csc.h index 828b86fd0cab..27b6807fc786 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.h +++ b/drivers/gpu/drm/sun4i/sun8i_csc.h @@ -28,10 +28,9 @@ enum sun8i_csc_mode { SUN8I_CSC_MODE_YVU2RGB, }; -void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range); -void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable); +void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, + enum sun8i_csc_mode mode, + enum drm_color_encoding encoding, + enum drm_color_range range); #endif diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 03e55de3850e..0f21da8fe38f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -214,14 +214,9 @@ static void sun8i_vi_layer_update_colors(struct sun8i_mixer *mixer, int channel, fmt = state->fb->format; csc_mode = sun8i_vi_layer_get_csc_mode(fmt); - if (csc_mode != SUN8I_CSC_MODE_OFF) { - sun8i_csc_set_ccsc_coefficients(mixer, channel, csc_mode, - state->color_encoding, - state->color_range); - sun8i_csc_enable_ccsc(mixer, channel, true); - } else { - sun8i_csc_enable_ccsc(mixer, channel, false); - } + sun8i_csc_config(mixer, channel, csc_mode, + state->color_encoding, + state->color_range); } static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, From a7febbd455ff0a68a874024b4674430493e1bea0 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:24 +0100 Subject: [PATCH 42/71] drm/sun4i: csc: Simplify arguments with taking plane state Taking plane state directly reduces number of arguments, avoids copying values and allows making additional decisions. For example, when plane is disabled, CSC should be turned off. This is also cleanup for later patches which will move call to another place. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-13-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_csc.c | 44 ++++++++++++++++++++++---- drivers/gpu/drm/sun4i/sun8i_csc.h | 11 ++----- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 33 +------------------ 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c index cf0c5121661b..ac7b62adc7df 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.c +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -3,11 +3,20 @@ * Copyright (C) Jernej Skrabec */ +#include +#include +#include #include #include "sun8i_csc.h" #include "sun8i_mixer.h" +enum sun8i_csc_mode { + SUN8I_CSC_MODE_OFF, + SUN8I_CSC_MODE_YUV2RGB, + SUN8I_CSC_MODE_YVU2RGB, +}; + static const u32 ccsc_base[][2] = { [CCSC_MIXER0_LAYOUT] = {CCSC00_OFFSET, CCSC01_OFFSET}, [CCSC_MIXER1_LAYOUT] = {CCSC10_OFFSET, CCSC11_OFFSET}, @@ -196,21 +205,44 @@ static void sun8i_de3_ccsc_setup(struct regmap *map, int layer, mask, val); } -void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range) +static u32 sun8i_csc_get_mode(struct drm_plane_state *state) { + const struct drm_format_info *format; + + if (!state->crtc || !state->visible) + return SUN8I_CSC_MODE_OFF; + + format = state->fb->format; + if (!format->is_yuv) + return SUN8I_CSC_MODE_OFF; + + switch (format->format) { + case DRM_FORMAT_YVU411: + case DRM_FORMAT_YVU420: + case DRM_FORMAT_YVU422: + case DRM_FORMAT_YVU444: + return SUN8I_CSC_MODE_YVU2RGB; + default: + return SUN8I_CSC_MODE_YUV2RGB; + } +} + +void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, + struct drm_plane_state *state) +{ + u32 mode = sun8i_csc_get_mode(state); u32 base; if (mixer->cfg->de_type == SUN8I_MIXER_DE3) { sun8i_de3_ccsc_setup(mixer->engine.regs, layer, - mode, encoding, range); + mode, state->color_encoding, + state->color_range); return; } base = ccsc_base[mixer->cfg->ccsc][layer]; sun8i_csc_setup(mixer->engine.regs, base, - mode, encoding, range); + mode, state->color_encoding, + state->color_range); } diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.h b/drivers/gpu/drm/sun4i/sun8i_csc.h index 27b6807fc786..ce921521aaca 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.h +++ b/drivers/gpu/drm/sun4i/sun8i_csc.h @@ -8,6 +8,7 @@ #include +struct drm_plane_state; struct sun8i_mixer; /* VI channel CSC units offsets */ @@ -22,15 +23,7 @@ struct sun8i_mixer; #define SUN8I_CSC_CTRL_EN BIT(0) -enum sun8i_csc_mode { - SUN8I_CSC_MODE_OFF, - SUN8I_CSC_MODE_YUV2RGB, - SUN8I_CSC_MODE_YVU2RGB, -}; - void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, - enum sun8i_csc_mode mode, - enum drm_color_encoding encoding, - enum drm_color_range range); + struct drm_plane_state *state); #endif diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 0f21da8fe38f..511a599f3112 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -189,36 +189,6 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, SUN8I_MIXER_CHAN_VI_DS_M(vm)); } -static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format) -{ - if (!format->is_yuv) - return SUN8I_CSC_MODE_OFF; - - switch (format->format) { - case DRM_FORMAT_YVU411: - case DRM_FORMAT_YVU420: - case DRM_FORMAT_YVU422: - case DRM_FORMAT_YVU444: - return SUN8I_CSC_MODE_YVU2RGB; - default: - return SUN8I_CSC_MODE_YUV2RGB; - } -} - -static void sun8i_vi_layer_update_colors(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) -{ - struct drm_plane_state *state = plane->state; - const struct drm_format_info *fmt; - u32 csc_mode; - - fmt = state->fb->format; - csc_mode = sun8i_vi_layer_get_csc_mode(fmt); - sun8i_csc_config(mixer, channel, csc_mode, - state->color_encoding, - state->color_range); -} - static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, int overlay, struct drm_plane *plane) { @@ -333,8 +303,7 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, layer->overlay, plane); sun8i_vi_layer_update_coord(mixer, layer->channel, layer->overlay, plane); - sun8i_vi_layer_update_colors(mixer, layer->channel, - layer->overlay, plane); + sun8i_csc_config(mixer, layer->channel, new_state); sun8i_vi_layer_update_buffer(mixer, layer->channel, layer->overlay, plane); } From feea4205efb2455b7bfaae87abec34d90ce539bc Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:25 +0100 Subject: [PATCH 43/71] drm/sun4i: de2/de3: Move plane type determination to mixer Plane type determination logic inside layer init functions doesn't allow index register to be repurposed to plane sequence, which it almost is. So move out the logic to mixer, which allows further rework for DE33 support. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-14-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 15 +++++++++++++-- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 5 +---- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 1 + drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 5 +---- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 1 + 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 091ea109713b..cf6202099844 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -316,6 +316,7 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, { struct drm_plane **planes; struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); + enum drm_plane_type type; int i; planes = devm_kcalloc(drm->dev, @@ -327,7 +328,12 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, for (i = 0; i < mixer->cfg->vi_num; i++) { struct sun8i_layer *layer; - layer = sun8i_vi_layer_init_one(drm, mixer, i); + if (i == 0 && !mixer->cfg->ui_num) + type = DRM_PLANE_TYPE_PRIMARY; + else + type = DRM_PLANE_TYPE_OVERLAY; + + layer = sun8i_vi_layer_init_one(drm, mixer, type, i); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize overlay plane\n"); @@ -340,7 +346,12 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, for (i = 0; i < mixer->cfg->ui_num; i++) { struct sun8i_layer *layer; - layer = sun8i_ui_layer_init_one(drm, mixer, i); + if (i == 0) + type = DRM_PLANE_TYPE_PRIMARY; + else + type = DRM_PLANE_TYPE_OVERLAY; + + layer = sun8i_ui_layer_init_one(drm, mixer, type, i); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 939a704917a6..c7bbdb37c2d7 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -267,9 +267,9 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, + enum drm_plane_type type, int index) { - enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; int channel = mixer->cfg->vi_num + index; struct sun8i_layer *layer; unsigned int plane_cnt; @@ -284,9 +284,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, layer->channel = channel; layer->overlay = 0; - if (index == 0) - type = DRM_PLANE_TYPE_PRIMARY; - /* possible crtcs are set later */ ret = drm_universal_plane_init(drm, &layer->plane, 0, &sun8i_ui_layer_funcs, diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index 83892f6ff211..7745aec32d76 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -51,5 +51,6 @@ struct sun8i_layer; struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, + enum drm_plane_type type, int index); #endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 511a599f3112..118b7e33bddb 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -412,9 +412,9 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, + enum drm_plane_type type, int index) { - enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY; u32 supported_encodings, supported_ranges; unsigned int plane_cnt, format_count; struct sun8i_layer *layer; @@ -438,9 +438,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, format_count = ARRAY_SIZE(sun8i_vi_layer_formats); } - if (!mixer->cfg->ui_num && index == 0) - type = DRM_PLANE_TYPE_PRIMARY; - /* possible crtcs are set later */ ret = drm_universal_plane_init(drm, &layer->plane, 0, &sun8i_vi_layer_funcs, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index 655440cdc78f..fc22b9a6bd8d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -56,5 +56,6 @@ struct sun8i_layer; struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, + enum drm_plane_type type, int index); #endif /* _SUN8I_VI_LAYER_H_ */ From 32ca21b97f2f1e0f0e9a22de0642530b70339fdd Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:26 +0100 Subject: [PATCH 44/71] drm/sun4i: ui_layer: Change index meaning In the pursuit of making UI/VI layer code independent of DE version, change meaning of UI index to index of the plane within mixer. DE33 can split amount of VI and UI planes between multiple mixer in whatever way it deems acceptable, so simple calculation VI num + UI index won't be meaningful anymore. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-15-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 5 +++-- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index cf6202099844..e15c59e0c52b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -344,6 +344,7 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, } for (i = 0; i < mixer->cfg->ui_num; i++) { + unsigned int index = mixer->cfg->vi_num + i; struct sun8i_layer *layer; if (i == 0) @@ -351,14 +352,14 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; - layer = sun8i_ui_layer_init_one(drm, mixer, type, i); + layer = sun8i_ui_layer_init_one(drm, mixer, type, index); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); return ERR_CAST(layer); } - planes[mixer->cfg->vi_num + i] = &layer->plane; + planes[index] = &layer->plane; } return planes; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index c7bbdb37c2d7..67a59a477542 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -270,7 +270,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, enum drm_plane_type type, int index) { - int channel = mixer->cfg->vi_num + index; struct sun8i_layer *layer; unsigned int plane_cnt; int ret; @@ -281,7 +280,7 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, layer->mixer = mixer; layer->type = SUN8I_LAYER_TYPE_UI; - layer->channel = channel; + layer->channel = index; layer->overlay = 0; /* possible crtcs are set later */ @@ -303,7 +302,7 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - ret = drm_plane_create_zpos_property(&layer->plane, channel, + ret = drm_plane_create_zpos_property(&layer->plane, index, 0, plane_cnt - 1); if (ret) { dev_err(drm->dev, "Couldn't add zpos property\n"); From 4fa45b04a47d6cb91add07797a683e51f88bff68 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:27 +0100 Subject: [PATCH 45/71] drm/sun4i: layer: move num of planes calc out of layer code With DE33, number of planes no longer depends on mixer because layers are shared between all mixers. Get this value via parameter, so DE specific code can fill in proper value. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-16-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 10 +++++----- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 6 ++---- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 3 ++- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 7 +++---- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 3 ++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index e15c59e0c52b..6cd91595abf2 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -316,12 +316,11 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, { struct drm_plane **planes; struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); + int plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; enum drm_plane_type type; int i; - planes = devm_kcalloc(drm->dev, - mixer->cfg->vi_num + mixer->cfg->ui_num + 1, - sizeof(*planes), GFP_KERNEL); + planes = devm_kcalloc(drm->dev, plane_cnt, sizeof(*planes), GFP_KERNEL); if (!planes) return ERR_PTR(-ENOMEM); @@ -333,7 +332,7 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; - layer = sun8i_vi_layer_init_one(drm, mixer, type, i); + layer = sun8i_vi_layer_init_one(drm, mixer, type, i, plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize overlay plane\n"); @@ -352,7 +351,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; - layer = sun8i_ui_layer_init_one(drm, mixer, type, index); + layer = sun8i_ui_layer_init_one(drm, mixer, type, index, + plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 67a59a477542..e5f73060e3f1 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -268,10 +268,10 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, - int index) + int index, + int plane_cnt) { struct sun8i_layer *layer; - unsigned int plane_cnt; int ret; layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL); @@ -294,8 +294,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; - ret = drm_plane_create_alpha_property(&layer->plane); if (ret) { dev_err(drm->dev, "Couldn't add alpha property\n"); diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index 7745aec32d76..0613b34d36e0 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -52,5 +52,6 @@ struct sun8i_layer; struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, - int index); + int index, + int plane_cnt); #endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 118b7e33bddb..fdb9320a71e5 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -413,10 +413,11 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, - int index) + int index, + int plane_cnt) { u32 supported_encodings, supported_ranges; - unsigned int plane_cnt, format_count; + unsigned int format_count; struct sun8i_layer *layer; const u32 *formats; int ret; @@ -449,8 +450,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; - if (mixer->cfg->vi_num == 1 || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { ret = drm_plane_create_alpha_property(&layer->plane); if (ret) { diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index fc22b9a6bd8d..a568e1db1e19 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -57,5 +57,6 @@ struct sun8i_layer; struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, - int index); + int index, + int plane_cnt); #endif /* _SUN8I_VI_LAYER_H_ */ From 7d126c6e80e4a488aa4c1839d14b4ccea560f47b Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:28 +0100 Subject: [PATCH 46/71] drm/sun4i: ui_layer: use layer struct instead of multiple args This change is equally a cleanup (less arguments) and preparation for DE33 separate plane driver. It will introduce additional register space. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-17-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 62 +++++++++++++------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index e5f73060e3f1..29b555132b19 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -26,24 +26,24 @@ #include "sun8i_ui_scaler.h" #include "sun8i_vi_scaler.h" -static void sun8i_ui_layer_disable(struct sun8i_mixer *mixer, - int channel, int overlay) +static void sun8i_ui_layer_disable(struct sun8i_layer *layer) { - u32 ch_base = sun8i_channel_base(mixer, channel); + struct sun8i_mixer *mixer = layer->mixer; + u32 ch_base = sun8i_channel_base(mixer, layer->channel); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), 0); + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), 0); } -static void sun8i_ui_layer_update_attributes(struct sun8i_mixer *mixer, - int channel, int overlay, +static void sun8i_ui_layer_update_attributes(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun8i_mixer *mixer = layer->mixer; const struct drm_format_info *fmt; u32 val, ch_base, hw_fmt; - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); fmt = state->fb->format; sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); @@ -55,22 +55,23 @@ static void sun8i_ui_layer_update_attributes(struct sun8i_mixer *mixer, val |= SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay), val); + SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), val); } -static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, + struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun8i_mixer *mixer = layer->mixer; u32 src_w, src_h, dst_w, dst_h; u32 outsize, insize; u32 hphase, vphase; u32 ch_base; DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n", - channel, overlay); + layer->channel, layer->overlay); - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); src_w = drm_rect_width(&state->src) >> 16; src_h = drm_rect_height(&state->src) >> 16; @@ -88,7 +89,7 @@ static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, state->src.x1 >> 16, state->src.y1 >> 16); DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, overlay), + SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, layer->overlay), insize); regmap_write(mixer->engine.regs, SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch_base), @@ -103,37 +104,38 @@ static void sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel, vscale = state->src_h / state->crtc_h; if (mixer->cfg->de_type == SUN8I_MIXER_DE33) { - sun8i_vi_scaler_setup(mixer, channel, src_w, src_h, + sun8i_vi_scaler_setup(mixer, layer->channel, src_w, src_h, dst_w, dst_h, hscale, vscale, hphase, vphase, state->fb->format); - sun8i_vi_scaler_enable(mixer, channel, true); + sun8i_vi_scaler_enable(mixer, layer->channel, true); } else { - sun8i_ui_scaler_setup(mixer, channel, src_w, src_h, + sun8i_ui_scaler_setup(mixer, layer->channel, src_w, src_h, dst_w, dst_h, hscale, vscale, hphase, vphase); - sun8i_ui_scaler_enable(mixer, channel, true); + sun8i_ui_scaler_enable(mixer, layer->channel, true); } } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - sun8i_vi_scaler_enable(mixer, channel, false); + sun8i_vi_scaler_enable(mixer, layer->channel, false); else - sun8i_ui_scaler_enable(mixer, channel, false); + sun8i_ui_scaler_enable(mixer, layer->channel, false); } } -static void sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_ui_layer_update_buffer(struct sun8i_layer *layer, + struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun8i_mixer *mixer = layer->mixer; struct drm_framebuffer *fb = state->fb; struct drm_gem_dma_object *gem; dma_addr_t dma_addr; u32 ch_base; int bpp; - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); /* Get the physical address of the buffer in memory */ gem = drm_fb_dma_get_gem_obj(fb, 0); @@ -151,13 +153,13 @@ static void sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel, /* Set the line width */ DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, overlay), + SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, layer->overlay), fb->pitches[0]); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &dma_addr); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, overlay), + SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, layer->overlay), lower_32_bits(dma_addr)); } @@ -208,19 +210,15 @@ static void sun8i_ui_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); struct sun8i_layer *layer = plane_to_sun8i_layer(plane); - struct sun8i_mixer *mixer = layer->mixer; if (!new_state->crtc || !new_state->visible) { - sun8i_ui_layer_disable(mixer, layer->channel, layer->overlay); + sun8i_ui_layer_disable(layer); return; } - sun8i_ui_layer_update_attributes(mixer, layer->channel, - layer->overlay, plane); - sun8i_ui_layer_update_coord(mixer, layer->channel, - layer->overlay, plane); - sun8i_ui_layer_update_buffer(mixer, layer->channel, - layer->overlay, plane); + sun8i_ui_layer_update_attributes(layer, plane); + sun8i_ui_layer_update_coord(layer, plane); + sun8i_ui_layer_update_buffer(layer, plane); } static const struct drm_plane_helper_funcs sun8i_ui_layer_helper_funcs = { From f10a80105efaccd6dc5a19039958ef3061a0fba3 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:29 +0100 Subject: [PATCH 47/71] drm/sun4i: vi_layer: use layer struct instead of multiple args This change is equally a cleanup (less arguments) and preparation for DE33 separate plane driver. It will introduce additional register space. No functional changes. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-18-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 58 +++++++++++++------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index fdb9320a71e5..9ab4306458c6 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -19,24 +19,24 @@ #include "sun8i_vi_layer.h" #include "sun8i_vi_scaler.h" -static void sun8i_vi_layer_disable(struct sun8i_mixer *mixer, - int channel, int overlay) +static void sun8i_vi_layer_disable(struct sun8i_layer *layer) { - u32 ch_base = sun8i_channel_base(mixer, channel); + struct sun8i_mixer *mixer = layer->mixer; + u32 ch_base = sun8i_channel_base(mixer, layer->channel); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), 0); + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), 0); } -static void sun8i_vi_layer_update_attributes(struct sun8i_mixer *mixer, - int channel, int overlay, +static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun8i_mixer *mixer = layer->mixer; const struct drm_format_info *fmt; u32 val, ch_base, hw_fmt; - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); fmt = state->fb->format; sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); @@ -56,14 +56,15 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_mixer *mixer, } regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay), val); + SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val); } -static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, + struct drm_plane *plane) { struct drm_plane_state *state = plane->state; const struct drm_format_info *format = state->fb->format; + struct sun8i_mixer *mixer = layer->mixer; u32 src_w, src_h, dst_w, dst_h; u32 outsize, insize; u32 hphase, vphase; @@ -73,9 +74,9 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, u32 ch_base; DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n", - channel, overlay); + layer->channel, layer->overlay); - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); src_w = drm_rect_width(&state->src) >> 16; src_h = drm_rect_height(&state->src) >> 16; @@ -113,7 +114,7 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, (state->src.y1 >> 16) & ~(format->vsub - 1)); DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); regmap_write(mixer->engine.regs, - SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, overlay), + SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, layer->overlay), insize); regmap_write(mixer->engine.regs, SUN8I_MIXER_CHAN_VI_OVL_SIZE(ch_base), @@ -162,13 +163,13 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, hscale = (src_w << 16) / dst_w; vscale = (src_h << 16) / dst_h; - sun8i_vi_scaler_setup(mixer, channel, src_w, src_h, dst_w, + sun8i_vi_scaler_setup(mixer, layer->channel, src_w, src_h, dst_w, dst_h, hscale, vscale, hphase, vphase, format); - sun8i_vi_scaler_enable(mixer, channel, true); + sun8i_vi_scaler_enable(mixer, layer->channel, true); } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); - sun8i_vi_scaler_enable(mixer, channel, false); + sun8i_vi_scaler_enable(mixer, layer->channel, false); } regmap_write(mixer->engine.regs, @@ -189,10 +190,11 @@ static void sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel, SUN8I_MIXER_CHAN_VI_DS_M(vm)); } -static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, - int overlay, struct drm_plane *plane) +static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer, + struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun8i_mixer *mixer = layer->mixer; struct drm_framebuffer *fb = state->fb; const struct drm_format_info *format = fb->format; struct drm_gem_dma_object *gem; @@ -201,7 +203,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, u32 ch_base; int i; - ch_base = sun8i_channel_base(mixer, channel); + ch_base = sun8i_channel_base(mixer, layer->channel); /* Adjust x and y to be dividable by subsampling factor */ src_x = (state->src.x1 >> 16) & ~(format->hsub - 1); @@ -233,7 +235,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, i + 1, fb->pitches[i]); regmap_write(mixer->engine.regs, SUN8I_MIXER_CHAN_VI_LAYER_PITCH(ch_base, - overlay, i), + layer->overlay, i), fb->pitches[i]); DRM_DEBUG_DRIVER("Setting %d. buffer address to %pad\n", @@ -241,7 +243,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel, regmap_write(mixer->engine.regs, SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(ch_base, - overlay, i), + layer->overlay, i), lower_32_bits(dma_addr)); } } @@ -292,20 +294,16 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane); struct sun8i_layer *layer = plane_to_sun8i_layer(plane); - struct sun8i_mixer *mixer = layer->mixer; if (!new_state->crtc || !new_state->visible) { - sun8i_vi_layer_disable(mixer, layer->channel, layer->overlay); + sun8i_vi_layer_disable(layer); return; } - sun8i_vi_layer_update_attributes(mixer, layer->channel, - layer->overlay, plane); - sun8i_vi_layer_update_coord(mixer, layer->channel, - layer->overlay, plane); - sun8i_csc_config(mixer, layer->channel, new_state); - sun8i_vi_layer_update_buffer(mixer, layer->channel, - layer->overlay, plane); + sun8i_vi_layer_update_attributes(layer, plane); + sun8i_vi_layer_update_coord(layer, plane); + sun8i_csc_config(layer->mixer, layer->channel, new_state); + sun8i_vi_layer_update_buffer(layer, plane); } static const struct drm_plane_helper_funcs sun8i_vi_layer_helper_funcs = { From 1ebec02bba7a594ca641673a85c0eb942836b02c Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:30 +0100 Subject: [PATCH 48/71] drm/sun4i: ui_scaler: use layer instead of mixer for args Layer related peripherals should take layer struct as a input. This looks cleaner and also necessary for proper DE33 support later. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-19-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 9 ++++----- drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 14 ++++++++------ drivers/gpu/drm/sun4i/sun8i_ui_scaler.h | 4 ++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 29b555132b19..0d243bdf57a9 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -110,17 +110,16 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, state->fb->format); sun8i_vi_scaler_enable(mixer, layer->channel, true); } else { - sun8i_ui_scaler_setup(mixer, layer->channel, src_w, src_h, - dst_w, dst_h, hscale, vscale, - hphase, vphase); - sun8i_ui_scaler_enable(mixer, layer->channel, true); + sun8i_ui_scaler_setup(layer, src_w, src_h, dst_w, dst_h, + hscale, vscale, hphase, vphase); + sun8i_ui_scaler_enable(layer, true); } } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); if (mixer->cfg->de_type == SUN8I_MIXER_DE33) sun8i_vi_scaler_enable(mixer, layer->channel, false); else - sun8i_ui_scaler_enable(mixer, layer->channel, false); + sun8i_ui_scaler_enable(layer, false); } } diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index 8b7a58e27517..fcd72c4fd49a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -127,14 +127,15 @@ static int sun8i_ui_scaler_coef_index(unsigned int step) } } -void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) +void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable) { + struct sun8i_mixer *mixer = layer->mixer; u32 val, base; - if (WARN_ON(layer < mixer->cfg->vi_num)) + if (WARN_ON(layer->channel < mixer->cfg->vi_num)) return; - base = sun8i_ui_scaler_base(mixer, layer); + base = sun8i_ui_scaler_base(mixer, layer->channel); if (enable) val = SUN8I_SCALER_GSU_CTRL_EN | @@ -145,18 +146,19 @@ void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) regmap_write(mixer->engine.regs, SUN8I_SCALER_GSU_CTRL(base), val); } -void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer, +void sun8i_ui_scaler_setup(struct sun8i_layer *layer, u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, u32 hscale, u32 vscale, u32 hphase, u32 vphase) { + struct sun8i_mixer *mixer = layer->mixer; u32 insize, outsize; int i, offset; u32 base; - if (WARN_ON(layer < mixer->cfg->vi_num)) + if (WARN_ON(layer->channel < mixer->cfg->vi_num)) return; - base = sun8i_ui_scaler_base(mixer, layer); + base = sun8i_ui_scaler_base(mixer, layer->channel); hphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; vphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h index 1ef4bd6f2718..872d88a58e7e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.h @@ -35,8 +35,8 @@ #define SUN8I_SCALER_GSU_CTRL_EN BIT(0) #define SUN8I_SCALER_GSU_CTRL_COEFF_RDY BIT(4) -void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable); -void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer, +void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable); +void sun8i_ui_scaler_setup(struct sun8i_layer *layer, u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, u32 hscale, u32 vscale, u32 hphase, u32 vphase); From f54c353cffa4997f67103cb9b045594755f204b0 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:31 +0100 Subject: [PATCH 49/71] drm/sun4i: vi_scaler: use layer instead of mixer for args Layer related peripherals should take layer struct as a input. This looks cleaner and also necessary for proper DE33 support later. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-20-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 9 ++++----- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 9 ++++----- drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 10 ++++++---- drivers/gpu/drm/sun4i/sun8i_vi_scaler.h | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 0d243bdf57a9..5651a6290f98 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -104,11 +104,10 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, vscale = state->src_h / state->crtc_h; if (mixer->cfg->de_type == SUN8I_MIXER_DE33) { - sun8i_vi_scaler_setup(mixer, layer->channel, src_w, src_h, - dst_w, dst_h, hscale, vscale, - hphase, vphase, + sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h, + hscale, vscale, hphase, vphase, state->fb->format); - sun8i_vi_scaler_enable(mixer, layer->channel, true); + sun8i_vi_scaler_enable(layer, true); } else { sun8i_ui_scaler_setup(layer, src_w, src_h, dst_w, dst_h, hscale, vscale, hphase, vphase); @@ -117,7 +116,7 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - sun8i_vi_scaler_enable(mixer, layer->channel, false); + sun8i_vi_scaler_enable(layer, false); else sun8i_ui_scaler_enable(layer, false); } diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 9ab4306458c6..dd24f9eee685 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -163,13 +163,12 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, hscale = (src_w << 16) / dst_w; vscale = (src_h << 16) / dst_h; - sun8i_vi_scaler_setup(mixer, layer->channel, src_w, src_h, dst_w, - dst_h, hscale, vscale, hphase, vphase, - format); - sun8i_vi_scaler_enable(mixer, layer->channel, true); + sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h, + hscale, vscale, hphase, vphase, format); + sun8i_vi_scaler_enable(layer, true); } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); - sun8i_vi_scaler_enable(mixer, layer->channel, false); + sun8i_vi_scaler_enable(layer, false); } regmap_write(mixer->engine.regs, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index 82df6244af88..a76677a1649f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -909,11 +909,12 @@ static void sun8i_vi_scaler_set_coeff(struct regmap *map, u32 base, } } -void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) +void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable) { + struct sun8i_mixer *mixer = layer->mixer; u32 val, base; - base = sun8i_vi_scaler_base(mixer, layer); + base = sun8i_vi_scaler_base(mixer, layer->channel); if (enable) val = SUN8I_SCALER_VSU_CTRL_EN | @@ -925,16 +926,17 @@ void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable) SUN8I_SCALER_VSU_CTRL(base), val); } -void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer, +void sun8i_vi_scaler_setup(struct sun8i_layer *layer, u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, u32 hscale, u32 vscale, u32 hphase, u32 vphase, const struct drm_format_info *format) { + struct sun8i_mixer *mixer = layer->mixer; u32 chphase, cvphase; u32 insize, outsize; u32 base; - base = sun8i_vi_scaler_base(mixer, layer); + base = sun8i_vi_scaler_base(mixer, layer->channel); hphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; vphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h index 68f6593b369a..73eecc4d1b1d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h @@ -69,8 +69,8 @@ #define SUN50I_SCALER_VSU_ANGLE_SHIFT(x) (((x) << 16) & 0xF) #define SUN50I_SCALER_VSU_ANGLE_OFFSET(x) ((x) & 0xFF) -void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable); -void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer, +void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable); +void sun8i_vi_scaler_setup(struct sun8i_layer *layer, u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, u32 hscale, u32 vscale, u32 hphase, u32 vphase, const struct drm_format_info *format); From 37aba59f5fd4389bac39fcff40baafa672a238c8 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:32 +0100 Subject: [PATCH 50/71] drm/sun4i: layers: Make regmap for layers configurable Till DE33, there were no reason to decouple registers from mixer. However, with future new plane driver, this will be necessary. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-21-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 7 ++++-- drivers/gpu/drm/sun4i/sun8i_mixer.h | 1 + drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 12 ++++++---- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 1 + drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 16 ++++++------- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 22 ++++++++++-------- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 1 + drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 31 ++++++++++++------------- 8 files changed, 50 insertions(+), 41 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 6cd91595abf2..f8af959e5d66 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -332,7 +332,9 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; - layer = sun8i_vi_layer_init_one(drm, mixer, type, i, plane_cnt); + layer = sun8i_vi_layer_init_one(drm, mixer, type, + mixer->engine.regs, i, + plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize overlay plane\n"); @@ -351,7 +353,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; - layer = sun8i_ui_layer_init_one(drm, mixer, type, index, + layer = sun8i_ui_layer_init_one(drm, mixer, type, + mixer->engine.regs, index, plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index b5badfa2c997..2e3689008b50 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -214,6 +214,7 @@ struct sun8i_layer { int type; int channel; int overlay; + struct regmap *regs; }; static inline struct sun8i_layer * diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 5651a6290f98..a038c1706803 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -54,7 +54,7 @@ static void sun8i_ui_layer_update_attributes(struct sun8i_layer *layer, val |= hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET; val |= SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN; - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), val); } @@ -88,10 +88,10 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, DRM_DEBUG_DRIVER("Layer source offset X: %d Y: %d\n", state->src.x1 >> 16, state->src.y1 >> 16); DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, layer->overlay), insize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch_base), insize); @@ -150,13 +150,13 @@ static void sun8i_ui_layer_update_buffer(struct sun8i_layer *layer, /* Set the line width */ DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, layer->overlay), fb->pitches[0]); DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &dma_addr); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, layer->overlay), lower_32_bits(dma_addr)); } @@ -264,6 +264,7 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, + struct regmap *regs, int index, int plane_cnt) { @@ -278,6 +279,7 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, layer->type = SUN8I_LAYER_TYPE_UI; layer->channel = index; layer->overlay = 0; + layer->regs = regs; /* possible crtcs are set later */ ret = drm_universal_plane_init(drm, &layer->plane, 0, diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index 0613b34d36e0..e0b2cfa02749 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -52,6 +52,7 @@ struct sun8i_layer; struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, + struct regmap *regs, int index, int plane_cnt); #endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index fcd72c4fd49a..2fc54dc20307 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -143,7 +143,7 @@ void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable) else val = 0; - regmap_write(mixer->engine.regs, SUN8I_SCALER_GSU_CTRL(base), val); + regmap_write(layer->regs, SUN8I_SCALER_GSU_CTRL(base), val); } void sun8i_ui_scaler_setup(struct sun8i_layer *layer, @@ -168,22 +168,22 @@ void sun8i_ui_scaler_setup(struct sun8i_layer *layer, insize = SUN8I_UI_SCALER_SIZE(src_w, src_h); outsize = SUN8I_UI_SCALER_SIZE(dst_w, dst_h); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_OUTSIZE(base), outsize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_INSIZE(base), insize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_HSTEP(base), hscale); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_VSTEP(base), vscale); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_HPHASE(base), hphase); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_VPHASE(base), vphase); offset = sun8i_ui_scaler_coef_index(hscale) * SUN8I_UI_SCALER_COEFF_COUNT; for (i = 0; i < SUN8I_UI_SCALER_COEFF_COUNT; i++) - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_GSU_HCOEFF(base, i), lan2coefftab16[offset + i]); } diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index dd24f9eee685..3e414d7fe236 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -50,12 +50,12 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL : SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED; } else if (mixer->cfg->vi_num == 1) { - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); } - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val); } @@ -113,10 +113,10 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, (state->src.x1 >> 16) & ~(format->hsub - 1), (state->src.y1 >> 16) & ~(format->vsub - 1)); DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, layer->overlay), insize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_OVL_SIZE(ch_base), insize); @@ -171,19 +171,19 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, sun8i_vi_scaler_enable(layer, false); } - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_HDS_Y(ch_base), SUN8I_MIXER_CHAN_VI_DS_N(hn) | SUN8I_MIXER_CHAN_VI_DS_M(hm)); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_HDS_UV(ch_base), SUN8I_MIXER_CHAN_VI_DS_N(hn) | SUN8I_MIXER_CHAN_VI_DS_M(hm)); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_VDS_Y(ch_base), SUN8I_MIXER_CHAN_VI_DS_N(vn) | SUN8I_MIXER_CHAN_VI_DS_M(vm)); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_VDS_UV(ch_base), SUN8I_MIXER_CHAN_VI_DS_N(vn) | SUN8I_MIXER_CHAN_VI_DS_M(vm)); @@ -232,7 +232,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer, /* Set the line width */ DRM_DEBUG_DRIVER("Layer %d. line width: %d bytes\n", i + 1, fb->pitches[i]); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_PITCH(ch_base, layer->overlay, i), fb->pitches[i]); @@ -240,7 +240,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer, DRM_DEBUG_DRIVER("Setting %d. buffer address to %pad\n", i + 1, &dma_addr); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(ch_base, layer->overlay, i), lower_32_bits(dma_addr)); @@ -410,6 +410,7 @@ static const uint64_t sun8i_layer_modifiers[] = { struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, + struct regmap *regs, int index, int plane_cnt) { @@ -427,6 +428,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, layer->type = SUN8I_LAYER_TYPE_VI; layer->channel = index; layer->overlay = 0; + layer->regs = regs; if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { formats = sun8i_vi_layer_de3_formats; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index a568e1db1e19..70766d752fa6 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -57,6 +57,7 @@ struct sun8i_layer; struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, + struct regmap *regs, int index, int plane_cnt); #endif /* _SUN8I_VI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index a76677a1649f..0e308feb492a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -911,10 +911,9 @@ static void sun8i_vi_scaler_set_coeff(struct regmap *map, u32 base, void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable) { - struct sun8i_mixer *mixer = layer->mixer; u32 val, base; - base = sun8i_vi_scaler_base(mixer, layer->channel); + base = sun8i_vi_scaler_base(layer->mixer, layer->channel); if (enable) val = SUN8I_SCALER_VSU_CTRL_EN | @@ -922,7 +921,7 @@ void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable) else val = 0; - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CTRL(base), val); } @@ -968,36 +967,36 @@ void sun8i_vi_scaler_setup(struct sun8i_layer *layer, else val = SUN50I_SCALER_VSU_SCALE_MODE_NORMAL; - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN50I_SCALER_VSU_SCALE_MODE(base), val); } - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_OUTSIZE(base), outsize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_YINSIZE(base), insize); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_YHSTEP(base), hscale); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_YVSTEP(base), vscale); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_YHPHASE(base), hphase); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_YVPHASE(base), vphase); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CINSIZE(base), SUN8I_VI_SCALER_SIZE(src_w / format->hsub, src_h / format->vsub)); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CHSTEP(base), hscale / format->hsub); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CVSTEP(base), vscale / format->vsub); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CHPHASE(base), chphase); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_SCALER_VSU_CVPHASE(base), cvphase); - sun8i_vi_scaler_set_coeff(mixer->engine.regs, base, + sun8i_vi_scaler_set_coeff(layer->regs, base, hscale, vscale, format); } From 0bc7d54dcc0b3f5ed47e0decce4acfea9611476e Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:33 +0100 Subject: [PATCH 51/71] drm/sun4i: csc: use layer arg instead of mixer Layer will be more universal, due to DE33 support. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-22-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_csc.c | 10 +++++----- drivers/gpu/drm/sun4i/sun8i_csc.h | 4 ++-- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c index ac7b62adc7df..c371e94b95bd 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.c +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -227,22 +227,22 @@ static u32 sun8i_csc_get_mode(struct drm_plane_state *state) } } -void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, +void sun8i_csc_config(struct sun8i_layer *layer, struct drm_plane_state *state) { u32 mode = sun8i_csc_get_mode(state); u32 base; - if (mixer->cfg->de_type == SUN8I_MIXER_DE3) { - sun8i_de3_ccsc_setup(mixer->engine.regs, layer, + if (layer->mixer->cfg->de_type == SUN8I_MIXER_DE3) { + sun8i_de3_ccsc_setup(layer->regs, layer->channel, mode, state->color_encoding, state->color_range); return; } - base = ccsc_base[mixer->cfg->ccsc][layer]; + base = ccsc_base[layer->mixer->cfg->ccsc][layer->channel]; - sun8i_csc_setup(mixer->engine.regs, base, + sun8i_csc_setup(layer->regs, base, mode, state->color_encoding, state->color_range); } diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.h b/drivers/gpu/drm/sun4i/sun8i_csc.h index ce921521aaca..2a4b79599610 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.h +++ b/drivers/gpu/drm/sun4i/sun8i_csc.h @@ -9,7 +9,7 @@ #include struct drm_plane_state; -struct sun8i_mixer; +struct sun8i_layer; /* VI channel CSC units offsets */ #define CCSC00_OFFSET 0xAA050 @@ -23,7 +23,7 @@ struct sun8i_mixer; #define SUN8I_CSC_CTRL_EN BIT(0) -void sun8i_csc_config(struct sun8i_mixer *mixer, int layer, +void sun8i_csc_config(struct sun8i_layer *layer, struct drm_plane_state *state); #endif diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 3e414d7fe236..78df7836099f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -301,7 +301,7 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane, sun8i_vi_layer_update_attributes(layer, plane); sun8i_vi_layer_update_coord(layer, plane); - sun8i_csc_config(layer->mixer, layer->channel, new_state); + sun8i_csc_config(layer, new_state); sun8i_vi_layer_update_buffer(layer, plane); } From 515441734c2bde512405aff29b89c3d35d8ba7d4 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:34 +0100 Subject: [PATCH 52/71] drm/sun4i: layers: add physical index arg This avoids plane mapping in layers code, which allows future refactoring, when layer code will move away from accessing mixer structure. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-23-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 17 +++++++++++++---- drivers/gpu/drm/sun4i/sun8i_mixer.h | 3 ++- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 5 +++-- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 2 +- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 5 +++-- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 2 +- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index f8af959e5d66..ceb9bd920151 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -284,14 +284,14 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, h = drm_rect_height(&plane_state->dst); DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d x=%d y=%d w=%d h=%d\n", - plane->base.id, layer->channel, layer->overlay, + plane->base.id, layer->index, layer->overlay, enable, zpos, x, y, w, h); if (!enable) continue; /* Route layer to pipe based on zpos */ - route |= layer->channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); + route |= layer->index << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos); pipe_en |= SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos); regmap_write(bld_regs, @@ -318,6 +318,7 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine); int plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num; enum drm_plane_type type; + unsigned int phy_index; int i; planes = devm_kcalloc(drm->dev, plane_cnt, sizeof(*planes), GFP_KERNEL); @@ -332,9 +333,13 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; + phy_index = i; + if (mixer->cfg->de_type == SUN8I_MIXER_DE33) + phy_index = mixer->cfg->map[i]; + layer = sun8i_vi_layer_init_one(drm, mixer, type, mixer->engine.regs, i, - plane_cnt); + phy_index, plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize overlay plane\n"); @@ -353,9 +358,13 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, else type = DRM_PLANE_TYPE_OVERLAY; + phy_index = index; + if (mixer->cfg->de_type == SUN8I_MIXER_DE33) + phy_index = mixer->cfg->map[index]; + layer = sun8i_ui_layer_init_one(drm, mixer, type, mixer->engine.regs, index, - plane_cnt); + phy_index, plane_cnt); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index 2e3689008b50..d14188cdfab3 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -212,6 +212,7 @@ struct sun8i_layer { struct drm_plane plane; struct sun8i_mixer *mixer; int type; + int index; int channel; int overlay; struct regmap *regs; @@ -246,7 +247,7 @@ static inline u32 sun8i_channel_base(struct sun8i_mixer *mixer, int channel) { if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - return DE33_CH_BASE + mixer->cfg->map[channel] * DE33_CH_SIZE; + return DE33_CH_BASE + channel * DE33_CH_SIZE; else if (mixer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_CH_BASE + channel * DE3_CH_SIZE; else diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index a038c1706803..2bbd00f595f7 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -265,7 +265,7 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, - int index, + int index, int phy_index, int plane_cnt) { struct sun8i_layer *layer; @@ -277,7 +277,8 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, layer->mixer = mixer; layer->type = SUN8I_LAYER_TYPE_UI; - layer->channel = index; + layer->index = index; + layer->channel = phy_index; layer->overlay = 0; layer->regs = regs; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index e0b2cfa02749..9383c3364df3 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -53,6 +53,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, - int index, + int index, int phy_index, int plane_cnt); #endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 78df7836099f..ce4b270e503b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -411,7 +411,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, - int index, + int index, int phy_index, int plane_cnt) { u32 supported_encodings, supported_ranges; @@ -426,7 +426,8 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, layer->mixer = mixer; layer->type = SUN8I_LAYER_TYPE_VI; - layer->channel = index; + layer->index = index; + layer->channel = phy_index; layer->overlay = 0; layer->regs = regs; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index 70766d752fa6..89d0c32e63cf 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -58,6 +58,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, - int index, + int index, int phy_index, int plane_cnt); #endif /* _SUN8I_VI_LAYER_H_ */ From c0cbdda9abd957c2392fb7b77bb3af16fb7f760a Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:35 +0100 Subject: [PATCH 53/71] drm/sun4i: vi_scaler: Update DE33 base calculation Now that channel base calculation is straightforward, let's update VI scaler base calculation to be simpler. At the same time, also introduce macro to avoid magic numbers. Note, reason why current magic value and new macro value isn't the same is because sun8i_channel_base() already introduces offset to channel registers. Previous value is just the difference to VI scaler registers. However, new code calculates scaler base from channel base. This is also easier to understand when looking into BSP driver. Macro value can be easily found whereas old diff value was not. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-24-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 3 ++- drivers/gpu/drm/sun4i/sun8i_vi_scaler.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index 0e308feb492a..fe0bb1de6f08 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -836,7 +836,8 @@ static const u32 bicubic4coefftab32[480] = { static u32 sun8i_vi_scaler_base(struct sun8i_mixer *mixer, int channel) { if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - return sun8i_channel_base(mixer, channel) + 0x3000; + return DE33_VI_SCALER_UNIT_BASE + + DE33_CH_SIZE * channel; else if (mixer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_VI_SCALER_UNIT_BASE + DE3_VI_SCALER_UNIT_SIZE * channel; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h index 73eecc4d1b1d..245fe2f431c3 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.h @@ -18,6 +18,8 @@ #define DE3_VI_SCALER_UNIT_BASE 0x20000 #define DE3_VI_SCALER_UNIT_SIZE 0x08000 +#define DE33_VI_SCALER_UNIT_BASE 0x4000 + /* this two macros assumes 16 fractional bits which is standard in DRM */ #define SUN8I_VI_SCALER_SCALE_MIN 1 #define SUN8I_VI_SCALER_SCALE_MAX ((1UL << 20) - 1) From 7907cf11406bd1e1a311765a09634f73454bbe3f Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:36 +0100 Subject: [PATCH 54/71] drm/sun4i: mixer: Convert heuristics to quirk Determination if FCC unit can be used for VI layer alpha depends on number of VI channels. This info won't be available anymore in future to VI layer driver because of DE33 way of allocating planes from same pool to different mixers. While order is slightly changed, it doesn't affect anything due to double buffering of registers. New order keeps related registers together and quirk separate. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-25-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 9 +++++++++ drivers/gpu/drm/sun4i/sun8i_mixer.h | 3 +++ drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 12 +++++++----- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index ceb9bd920151..86361564189c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -708,6 +708,7 @@ static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { .de_type = SUN8I_MIXER_DE2, .scaler_mask = 0xf, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; @@ -717,6 +718,7 @@ static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = { .de_type = SUN8I_MIXER_DE2, .scaler_mask = 0x3, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; @@ -727,6 +729,7 @@ static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = { .mod_rate = 432000000, .scaler_mask = 0xf, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; @@ -737,6 +740,7 @@ static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = { .mod_rate = 297000000, .scaler_mask = 0xf, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; @@ -747,6 +751,7 @@ static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = { .mod_rate = 297000000, .scaler_mask = 0x3, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; @@ -767,6 +772,7 @@ static const struct sun8i_mixer_cfg sun20i_d1_mixer0_cfg = { .mod_rate = 297000000, .scaler_mask = 0x3, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; @@ -777,6 +783,7 @@ static const struct sun8i_mixer_cfg sun20i_d1_mixer1_cfg = { .mod_rate = 297000000, .scaler_mask = 0x1, .scanline_yuv = 1024, + .de2_fcc_alpha = 1, .ui_num = 0, .vi_num = 1, }; @@ -787,6 +794,7 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = { .mod_rate = 297000000, .scaler_mask = 0xf, .scanline_yuv = 4096, + .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; @@ -797,6 +805,7 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = { .mod_rate = 297000000, .scaler_mask = 0x3, .scanline_yuv = 2048, + .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index d14188cdfab3..def07afd37e1 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -176,6 +176,8 @@ enum sun8i_mixer_type { * a functional block. * @de_type: sun8i_mixer_type enum representing the display engine generation. * @scaline_yuv: size of a scanline for VI scaler for YUV formats. + * @de2_fcc_alpha: use FCC for missing DE2 VI alpha capability + * Most DE2 cores has FCC. If number of VI planes is one, enable this. * @map: channel map for DE variants processing YUV separately (DE33) */ struct sun8i_mixer_cfg { @@ -186,6 +188,7 @@ struct sun8i_mixer_cfg { unsigned long mod_rate; unsigned int de_type; unsigned int scanline_yuv; + unsigned int de2_fcc_alpha : 1; unsigned int map[6]; }; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index ce4b270e503b..8e0117440664 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -49,14 +49,16 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL : SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED; - } else if (mixer->cfg->vi_num == 1) { - regmap_write(layer->regs, - SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, - SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); } regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val); + + if (mixer->cfg->de2_fcc_alpha) { + regmap_write(layer->regs, + SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, + SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); + } } static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, @@ -450,7 +452,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - if (mixer->cfg->vi_num == 1 || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (mixer->cfg->de2_fcc_alpha || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { ret = drm_plane_create_alpha_property(&layer->plane); if (ret) { dev_err(drm->dev, "Couldn't add alpha property\n"); From a23e3402f65d1784e30b0b6654f5146905457a45 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:37 +0100 Subject: [PATCH 55/71] drm/sun4i: ui_scaler: drop sanity checks They can't be triggered if mixer configuration is properly specified in quirks. Additionally, number of VI channels won't be available in future due to rework for DE33 support. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-26-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index 2fc54dc20307..c0947ccf675b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -132,9 +132,6 @@ void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable) struct sun8i_mixer *mixer = layer->mixer; u32 val, base; - if (WARN_ON(layer->channel < mixer->cfg->vi_num)) - return; - base = sun8i_ui_scaler_base(mixer, layer->channel); if (enable) @@ -155,9 +152,6 @@ void sun8i_ui_scaler_setup(struct sun8i_layer *layer, int i, offset; u32 base; - if (WARN_ON(layer->channel < mixer->cfg->vi_num)) - return; - base = sun8i_ui_scaler_base(mixer, layer->channel); hphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; From 5a96ae801f0ac39f85491d81b4f02e1a613107cd Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:38 +0100 Subject: [PATCH 56/71] drm/sun4i: mixer: Add quirk for number of VI scalers On DE2 and DE3, UI scalers are located right after VI scalers. So in order to calculate proper UI scaler base address, number of VI scalers must be known. In practice, it is same as number of VI channels, but it doesn't need to be. Let's make a quirk for this number. Code for configuring channels and associated functions won't have access to vi_num quirk anymore after rework for independent planes. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-27-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 11 +++++++++++ drivers/gpu/drm/sun4i/sun8i_mixer.h | 2 ++ drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 10 +++++----- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 86361564189c..446af7026259 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -706,6 +706,7 @@ static void sun8i_mixer_remove(struct platform_device *pdev) static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, .scaler_mask = 0xf, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -716,6 +717,7 @@ static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = { .ccsc = CCSC_MIXER1_LAYOUT, .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, .scaler_mask = 0x3, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -727,6 +729,7 @@ static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = { .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 432000000, + .vi_scaler_num = 1, .scaler_mask = 0xf, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -738,6 +741,7 @@ static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = { .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0xf, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -749,6 +753,7 @@ static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = { .ccsc = CCSC_MIXER1_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0x3, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -760,6 +765,7 @@ static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { .de_type = SUN8I_MIXER_DE2, .vi_num = 2, .ui_num = 1, + .vi_scaler_num = 2, .scaler_mask = 0x3, .scanline_yuv = 2048, .ccsc = CCSC_MIXER0_LAYOUT, @@ -770,6 +776,7 @@ static const struct sun8i_mixer_cfg sun20i_d1_mixer0_cfg = { .ccsc = CCSC_D1_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0x3, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -781,6 +788,7 @@ static const struct sun8i_mixer_cfg sun20i_d1_mixer1_cfg = { .ccsc = CCSC_MIXER1_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0x1, .scanline_yuv = 1024, .de2_fcc_alpha = 1, @@ -792,6 +800,7 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = { .ccsc = CCSC_MIXER0_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0xf, .scanline_yuv = 4096, .de2_fcc_alpha = 1, @@ -803,6 +812,7 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = { .ccsc = CCSC_MIXER1_LAYOUT, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, + .vi_scaler_num = 1, .scaler_mask = 0x3, .scanline_yuv = 2048, .de2_fcc_alpha = 1, @@ -813,6 +823,7 @@ static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = { static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = { .de_type = SUN8I_MIXER_DE3, .mod_rate = 600000000, + .vi_scaler_num = 1, .scaler_mask = 0xf, .scanline_yuv = 4096, .ui_num = 3, diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index def07afd37e1..8c2e8005fc5b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -167,6 +167,7 @@ enum sun8i_mixer_type { * struct sun8i_mixer_cfg - mixer HW configuration * @vi_num: number of VI channels * @ui_num: number of UI channels + * @vi_scaler_num: Number of VI scalers. Used on DE2 and DE3. * @scaler_mask: bitmask which tells which channel supports scaling * First, scaler supports for VI channels is defined and after that, scaler * support for UI channels. For example, if mixer has 2 VI channels without @@ -183,6 +184,7 @@ enum sun8i_mixer_type { struct sun8i_mixer_cfg { int vi_num; int ui_num; + unsigned int vi_scaler_num; int scaler_mask; int ccsc; unsigned long mod_rate; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index c0947ccf675b..0ba1482688d7 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -91,16 +91,16 @@ static const u32 lan2coefftab16[240] = { static u32 sun8i_ui_scaler_base(struct sun8i_mixer *mixer, int channel) { - int vi_num = mixer->cfg->vi_num; + int offset = mixer->cfg->vi_scaler_num; if (mixer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_VI_SCALER_UNIT_BASE + - DE3_VI_SCALER_UNIT_SIZE * vi_num + - DE3_UI_SCALER_UNIT_SIZE * (channel - vi_num); + DE3_VI_SCALER_UNIT_SIZE * offset + + DE3_UI_SCALER_UNIT_SIZE * (channel - offset); else return DE2_VI_SCALER_UNIT_BASE + - DE2_VI_SCALER_UNIT_SIZE * vi_num + - DE2_UI_SCALER_UNIT_SIZE * (channel - vi_num); + DE2_VI_SCALER_UNIT_SIZE * offset + + DE2_UI_SCALER_UNIT_SIZE * (channel - offset); } static int sun8i_ui_scaler_coef_index(unsigned int step) From d1fe2639425c121f811f73fd948dea2477ad9a19 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:39 +0100 Subject: [PATCH 57/71] drm/sun4i: mixer: split out layer config Later special plane only driver for DE33 will provide separate configuration. This change will also help layer driver migrate away from mixer structure. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-28-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_csc.c | 2 +- drivers/gpu/drm/sun4i/sun8i_mixer.c | 152 +++++++++++++++--------- drivers/gpu/drm/sun4i/sun8i_mixer.h | 33 +++-- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 2 +- drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 2 +- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 8 +- 6 files changed, 123 insertions(+), 76 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c index c371e94b95bd..30779db2f9b2 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.c +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -240,7 +240,7 @@ void sun8i_csc_config(struct sun8i_layer *layer, return; } - base = ccsc_base[layer->mixer->cfg->ccsc][layer->channel]; + base = ccsc_base[layer->mixer->cfg->lay_cfg.ccsc][layer->channel]; sun8i_csc_setup(layer->regs, base, mode, state->color_encoding, diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 446af7026259..b515204c815e 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -704,137 +704,173 @@ static void sun8i_mixer_remove(struct platform_device *pdev) } static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0xf, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, - .vi_scaler_num = 1, - .scaler_mask = 0xf, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = { - .ccsc = CCSC_MIXER1_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER1_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0x3, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, - .vi_scaler_num = 1, - .scaler_mask = 0x3, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0xf, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 432000000, - .vi_scaler_num = 1, - .scaler_mask = 0xf, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0xf, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0xf, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = { - .ccsc = CCSC_MIXER1_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER1_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0x3, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0x3, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = { - .de_type = SUN8I_MIXER_DE2, - .vi_num = 2, - .ui_num = 1, - .vi_scaler_num = 2, - .scaler_mask = 0x3, - .scanline_yuv = 2048, - .ccsc = CCSC_MIXER0_LAYOUT, - .mod_rate = 150000000, + .lay_cfg = { + .ccsc = CCSC_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 2, + .scaler_mask = 0x3, + .scanline_yuv = 2048, + }, + .de_type = SUN8I_MIXER_DE2, + .mod_rate = 150000000, + .vi_num = 2, + .ui_num = 1, }; static const struct sun8i_mixer_cfg sun20i_d1_mixer0_cfg = { - .ccsc = CCSC_D1_MIXER0_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_D1_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0x3, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0x3, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun20i_d1_mixer1_cfg = { - .ccsc = CCSC_MIXER1_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER1_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0x1, + .scanline_yuv = 1024, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0x1, - .scanline_yuv = 1024, - .de2_fcc_alpha = 1, .ui_num = 0, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = { - .ccsc = CCSC_MIXER0_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER0_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0xf, + .scanline_yuv = 4096, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0xf, - .scanline_yuv = 4096, - .de2_fcc_alpha = 1, .ui_num = 3, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = { - .ccsc = CCSC_MIXER1_LAYOUT, + .lay_cfg = { + .ccsc = CCSC_MIXER1_LAYOUT, + .de_type = SUN8I_MIXER_DE2, + .vi_scaler_num = 1, + .scaler_mask = 0x3, + .scanline_yuv = 2048, + .de2_fcc_alpha = 1, + }, .de_type = SUN8I_MIXER_DE2, .mod_rate = 297000000, - .vi_scaler_num = 1, - .scaler_mask = 0x3, - .scanline_yuv = 2048, - .de2_fcc_alpha = 1, .ui_num = 1, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = { + .lay_cfg = { + .de_type = SUN8I_MIXER_DE3, + .vi_scaler_num = 1, + .scaler_mask = 0xf, + .scanline_yuv = 4096, + }, .de_type = SUN8I_MIXER_DE3, .mod_rate = 600000000, - .vi_scaler_num = 1, - .scaler_mask = 0xf, - .scanline_yuv = 4096, .ui_num = 3, .vi_num = 1, }; static const struct sun8i_mixer_cfg sun50i_h616_mixer0_cfg = { + .lay_cfg = { + .de_type = SUN8I_MIXER_DE33, + .scaler_mask = 0xf, + .scanline_yuv = 4096, + }, .de_type = SUN8I_MIXER_DE33, .mod_rate = 600000000, - .scaler_mask = 0xf, - .scanline_yuv = 4096, .ui_num = 3, .vi_num = 1, .map = {0, 6, 7, 8}, diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index 8c2e8005fc5b..5b6068755ad1 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -164,34 +164,45 @@ enum sun8i_mixer_type { }; /** - * struct sun8i_mixer_cfg - mixer HW configuration - * @vi_num: number of VI channels - * @ui_num: number of UI channels + * struct sun8i_layer_cfg - layer configuration * @vi_scaler_num: Number of VI scalers. Used on DE2 and DE3. * @scaler_mask: bitmask which tells which channel supports scaling * First, scaler supports for VI channels is defined and after that, scaler * support for UI channels. For example, if mixer has 2 VI channels without * scaler and 2 UI channels with scaler, bitmask would be 0xC. * @ccsc: select set of CCSC base addresses from the enumeration above. - * @mod_rate: module clock rate that needs to be set in order to have - * a functional block. * @de_type: sun8i_mixer_type enum representing the display engine generation. * @scaline_yuv: size of a scanline for VI scaler for YUV formats. * @de2_fcc_alpha: use FCC for missing DE2 VI alpha capability * Most DE2 cores has FCC. If number of VI planes is one, enable this. - * @map: channel map for DE variants processing YUV separately (DE33) */ -struct sun8i_mixer_cfg { - int vi_num; - int ui_num; +struct sun8i_layer_cfg { unsigned int vi_scaler_num; int scaler_mask; int ccsc; - unsigned long mod_rate; unsigned int de_type; unsigned int scanline_yuv; unsigned int de2_fcc_alpha : 1; - unsigned int map[6]; +}; + +/** + * struct sun8i_mixer_cfg - mixer HW configuration + * @lay_cfg: layer configuration + * @vi_num: number of VI channels + * @ui_num: number of UI channels + * @de_type: sun8i_mixer_type enum representing the display engine generation. + * @mod_rate: module clock rate that needs to be set in order to have + * a functional block. + * @map: channel map for DE variants processing YUV separately (DE33) + */ + +struct sun8i_mixer_cfg { + struct sun8i_layer_cfg lay_cfg; + int vi_num; + int ui_num; + unsigned int de_type; + unsigned long mod_rate; + unsigned int map[6]; }; struct sun8i_mixer { diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index 2bbd00f595f7..cd19e40aefdc 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -190,7 +190,7 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; - if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) { + if (layer->mixer->cfg->lay_cfg.scaler_mask & BIT(layer->channel)) { min_scale = SUN8I_UI_SCALER_SCALE_MIN; max_scale = SUN8I_UI_SCALER_SCALE_MAX; } diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index 0ba1482688d7..4d06c366de7f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -91,7 +91,7 @@ static const u32 lan2coefftab16[240] = { static u32 sun8i_ui_scaler_base(struct sun8i_mixer *mixer, int channel) { - int offset = mixer->cfg->vi_scaler_num; + int offset = mixer->cfg->lay_cfg.vi_scaler_num; if (mixer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_VI_SCALER_UNIT_BASE + diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 8e0117440664..84ca7a37f14c 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -54,7 +54,7 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val); - if (mixer->cfg->de2_fcc_alpha) { + if (mixer->cfg->lay_cfg.de2_fcc_alpha) { regmap_write(layer->regs, SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); @@ -153,7 +153,7 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, } /* it seems that every RGB scaler has buffer for 2048 pixels */ - scanline = subsampled ? mixer->cfg->scanline_yuv : 2048; + scanline = subsampled ? mixer->cfg->lay_cfg.scanline_yuv : 2048; if (src_w > scanline) { DRM_DEBUG_DRIVER("Using horizontal coarse scaling\n"); @@ -278,7 +278,7 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; - if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) { + if (layer->mixer->cfg->lay_cfg.scaler_mask & BIT(layer->channel)) { min_scale = SUN8I_VI_SCALER_SCALE_MIN; max_scale = SUN8I_VI_SCALER_SCALE_MAX; } @@ -452,7 +452,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - if (mixer->cfg->de2_fcc_alpha || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (mixer->cfg->lay_cfg.de2_fcc_alpha || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { ret = drm_plane_create_alpha_property(&layer->plane); if (ret) { dev_err(drm->dev, "Couldn't add alpha property\n"); From bb1d2ddee5c45017e6b6e50c9eda02cb2bd600b6 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:40 +0100 Subject: [PATCH 58/71] drm/sun4i: layer: replace mixer with layer struct This allows to almost completely decouple layer code from mixer. This is important for DE33. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-29-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_csc.c | 4 ++-- drivers/gpu/drm/sun4i/sun8i_mixer.c | 6 +++-- drivers/gpu/drm/sun4i/sun8i_mixer.h | 27 ++++++++++----------- drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 24 +++++++++---------- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 3 ++- drivers/gpu/drm/sun4i/sun8i_ui_scaler.c | 16 ++++++------- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 31 ++++++++++++------------- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 3 ++- drivers/gpu/drm/sun4i/sun8i_vi_scaler.c | 19 +++++++-------- 9 files changed, 66 insertions(+), 67 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_csc.c b/drivers/gpu/drm/sun4i/sun8i_csc.c index 30779db2f9b2..ce81c12f511d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_csc.c +++ b/drivers/gpu/drm/sun4i/sun8i_csc.c @@ -233,14 +233,14 @@ void sun8i_csc_config(struct sun8i_layer *layer, u32 mode = sun8i_csc_get_mode(state); u32 base; - if (layer->mixer->cfg->de_type == SUN8I_MIXER_DE3) { + if (layer->cfg->de_type == SUN8I_MIXER_DE3) { sun8i_de3_ccsc_setup(layer->regs, layer->channel, mode, state->color_encoding, state->color_range); return; } - base = ccsc_base[layer->mixer->cfg->lay_cfg.ccsc][layer->channel]; + base = ccsc_base[layer->cfg->ccsc][layer->channel]; sun8i_csc_setup(layer->regs, base, mode, state->color_encoding, diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index b515204c815e..5fd5781d4b13 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -339,7 +339,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, layer = sun8i_vi_layer_init_one(drm, mixer, type, mixer->engine.regs, i, - phy_index, plane_cnt); + phy_index, plane_cnt, + &mixer->cfg->lay_cfg); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize overlay plane\n"); @@ -364,7 +365,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, layer = sun8i_ui_layer_init_one(drm, mixer, type, mixer->engine.regs, index, - phy_index, plane_cnt); + phy_index, plane_cnt, + &mixer->cfg->lay_cfg); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", i ? "overlay" : "primary"); diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index 5b6068755ad1..3948023e095b 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -225,13 +225,14 @@ enum { }; struct sun8i_layer { - struct drm_plane plane; - struct sun8i_mixer *mixer; - int type; - int index; - int channel; - int overlay; - struct regmap *regs; + struct drm_plane plane; + struct sun8i_mixer *mixer; + int type; + int index; + int channel; + int overlay; + struct regmap *regs; + const struct sun8i_layer_cfg *cfg; }; static inline struct sun8i_layer * @@ -260,14 +261,14 @@ sun8i_blender_regmap(struct sun8i_mixer *mixer) } static inline u32 -sun8i_channel_base(struct sun8i_mixer *mixer, int channel) +sun8i_channel_base(struct sun8i_layer *layer) { - if (mixer->cfg->de_type == SUN8I_MIXER_DE33) - return DE33_CH_BASE + channel * DE33_CH_SIZE; - else if (mixer->cfg->de_type == SUN8I_MIXER_DE3) - return DE3_CH_BASE + channel * DE3_CH_SIZE; + if (layer->cfg->de_type == SUN8I_MIXER_DE33) + return DE33_CH_BASE + layer->channel * DE33_CH_SIZE; + else if (layer->cfg->de_type == SUN8I_MIXER_DE3) + return DE3_CH_BASE + layer->channel * DE3_CH_SIZE; else - return DE2_CH_BASE + channel * DE2_CH_SIZE; + return DE2_CH_BASE + layer->channel * DE2_CH_SIZE; } int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format); diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index cd19e40aefdc..a8a67241c822 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -28,10 +28,9 @@ static void sun8i_ui_layer_disable(struct sun8i_layer *layer) { - struct sun8i_mixer *mixer = layer->mixer; - u32 ch_base = sun8i_channel_base(mixer, layer->channel); + u32 ch_base = sun8i_channel_base(layer); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), 0); } @@ -39,11 +38,10 @@ static void sun8i_ui_layer_update_attributes(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct sun8i_mixer *mixer = layer->mixer; const struct drm_format_info *fmt; u32 val, ch_base, hw_fmt; - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); fmt = state->fb->format; sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); @@ -62,7 +60,6 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct sun8i_mixer *mixer = layer->mixer; u32 src_w, src_h, dst_w, dst_h; u32 outsize, insize; u32 hphase, vphase; @@ -71,7 +68,7 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n", layer->channel, layer->overlay); - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); src_w = drm_rect_width(&state->src) >> 16; src_h = drm_rect_height(&state->src) >> 16; @@ -103,7 +100,7 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, hscale = state->src_w / state->crtc_w; vscale = state->src_h / state->crtc_h; - if (mixer->cfg->de_type == SUN8I_MIXER_DE33) { + if (layer->cfg->de_type == SUN8I_MIXER_DE33) { sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h, hscale, vscale, hphase, vphase, state->fb->format); @@ -115,7 +112,7 @@ static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer, } } else { DRM_DEBUG_DRIVER("HW scaling is not needed\n"); - if (mixer->cfg->de_type == SUN8I_MIXER_DE33) + if (layer->cfg->de_type == SUN8I_MIXER_DE33) sun8i_vi_scaler_enable(layer, false); else sun8i_ui_scaler_enable(layer, false); @@ -126,14 +123,13 @@ static void sun8i_ui_layer_update_buffer(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct sun8i_mixer *mixer = layer->mixer; struct drm_framebuffer *fb = state->fb; struct drm_gem_dma_object *gem; dma_addr_t dma_addr; u32 ch_base; int bpp; - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); /* Get the physical address of the buffer in memory */ gem = drm_fb_dma_get_gem_obj(fb, 0); @@ -190,7 +186,7 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane, min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; - if (layer->mixer->cfg->lay_cfg.scaler_mask & BIT(layer->channel)) { + if (layer->cfg->scaler_mask & BIT(layer->channel)) { min_scale = SUN8I_UI_SCALER_SCALE_MIN; max_scale = SUN8I_UI_SCALER_SCALE_MAX; } @@ -266,7 +262,8 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, - int plane_cnt) + int plane_cnt, + const struct sun8i_layer_cfg *cfg) { struct sun8i_layer *layer; int ret; @@ -281,6 +278,7 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, layer->channel = phy_index; layer->overlay = 0; layer->regs = regs; + layer->cfg = cfg; /* possible crtcs are set later */ ret = drm_universal_plane_init(drm, &layer->plane, 0, diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index 9383c3364df3..c357b39999ff 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -54,5 +54,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, - int plane_cnt); + int plane_cnt, + const struct sun8i_layer_cfg *cfg); #endif /* _SUN8I_UI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c index 4d06c366de7f..a178da8f532a 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_scaler.c @@ -89,18 +89,18 @@ static const u32 lan2coefftab16[240] = { 0x0b1c1603, 0x0d1c1502, 0x0e1d1401, 0x0f1d1301, }; -static u32 sun8i_ui_scaler_base(struct sun8i_mixer *mixer, int channel) +static u32 sun8i_ui_scaler_base(struct sun8i_layer *layer) { - int offset = mixer->cfg->lay_cfg.vi_scaler_num; + int offset = layer->cfg->vi_scaler_num; - if (mixer->cfg->de_type == SUN8I_MIXER_DE3) + if (layer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_VI_SCALER_UNIT_BASE + DE3_VI_SCALER_UNIT_SIZE * offset + - DE3_UI_SCALER_UNIT_SIZE * (channel - offset); + DE3_UI_SCALER_UNIT_SIZE * (layer->channel - offset); else return DE2_VI_SCALER_UNIT_BASE + DE2_VI_SCALER_UNIT_SIZE * offset + - DE2_UI_SCALER_UNIT_SIZE * (channel - offset); + DE2_UI_SCALER_UNIT_SIZE * (layer->channel - offset); } static int sun8i_ui_scaler_coef_index(unsigned int step) @@ -129,10 +129,9 @@ static int sun8i_ui_scaler_coef_index(unsigned int step) void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable) { - struct sun8i_mixer *mixer = layer->mixer; u32 val, base; - base = sun8i_ui_scaler_base(mixer, layer->channel); + base = sun8i_ui_scaler_base(layer); if (enable) val = SUN8I_SCALER_GSU_CTRL_EN | @@ -147,12 +146,11 @@ void sun8i_ui_scaler_setup(struct sun8i_layer *layer, u32 src_w, u32 src_h, u32 dst_w, u32 dst_h, u32 hscale, u32 vscale, u32 hphase, u32 vphase) { - struct sun8i_mixer *mixer = layer->mixer; u32 insize, outsize; int i, offset; u32 base; - base = sun8i_ui_scaler_base(mixer, layer->channel); + base = sun8i_ui_scaler_base(layer); hphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; vphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 84ca7a37f14c..5f91011425bf 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -21,10 +21,9 @@ static void sun8i_vi_layer_disable(struct sun8i_layer *layer) { - struct sun8i_mixer *mixer = layer->mixer; - u32 ch_base = sun8i_channel_base(mixer, layer->channel); + u32 ch_base = sun8i_channel_base(layer); - regmap_write(mixer->engine.regs, + regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), 0); } @@ -32,11 +31,10 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct sun8i_mixer *mixer = layer->mixer; const struct drm_format_info *fmt; u32 val, ch_base, hw_fmt; - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); fmt = state->fb->format; sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt); @@ -44,7 +42,7 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, if (!fmt->is_yuv) val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE; val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN; - if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (layer->cfg->de_type >= SUN8I_MIXER_DE3) { val |= SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA(state->alpha >> 8); val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ? SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL : @@ -54,7 +52,7 @@ static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer, regmap_write(layer->regs, SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val); - if (mixer->cfg->lay_cfg.de2_fcc_alpha) { + if (layer->cfg->de2_fcc_alpha) { regmap_write(layer->regs, SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG, SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8)); @@ -78,7 +76,7 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n", layer->channel, layer->overlay); - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); src_w = drm_rect_width(&state->src) >> 16; src_h = drm_rect_height(&state->src) >> 16; @@ -153,7 +151,7 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, } /* it seems that every RGB scaler has buffer for 2048 pixels */ - scanline = subsampled ? mixer->cfg->lay_cfg.scanline_yuv : 2048; + scanline = subsampled ? layer->cfg->scanline_yuv : 2048; if (src_w > scanline) { DRM_DEBUG_DRIVER("Using horizontal coarse scaling\n"); @@ -195,7 +193,6 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; - struct sun8i_mixer *mixer = layer->mixer; struct drm_framebuffer *fb = state->fb; const struct drm_format_info *format = fb->format; struct drm_gem_dma_object *gem; @@ -204,7 +201,7 @@ static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer, u32 ch_base; int i; - ch_base = sun8i_channel_base(mixer, layer->channel); + ch_base = sun8i_channel_base(layer); /* Adjust x and y to be dividable by subsampling factor */ src_x = (state->src.x1 >> 16) & ~(format->hsub - 1); @@ -278,7 +275,7 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane, min_scale = DRM_PLANE_NO_SCALING; max_scale = DRM_PLANE_NO_SCALING; - if (layer->mixer->cfg->lay_cfg.scaler_mask & BIT(layer->channel)) { + if (layer->cfg->scaler_mask & BIT(layer->channel)) { min_scale = SUN8I_VI_SCALER_SCALE_MIN; max_scale = SUN8I_VI_SCALER_SCALE_MAX; } @@ -414,7 +411,8 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, - int plane_cnt) + int plane_cnt, + const struct sun8i_layer_cfg *cfg) { u32 supported_encodings, supported_ranges; unsigned int format_count; @@ -432,8 +430,9 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, layer->channel = phy_index; layer->overlay = 0; layer->regs = regs; + layer->cfg = cfg; - if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (layer->cfg->de_type >= SUN8I_MIXER_DE3) { formats = sun8i_vi_layer_de3_formats; format_count = ARRAY_SIZE(sun8i_vi_layer_de3_formats); } else { @@ -452,7 +451,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, return ERR_PTR(ret); } - if (mixer->cfg->lay_cfg.de2_fcc_alpha || mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (layer->cfg->de2_fcc_alpha || layer->cfg->de_type >= SUN8I_MIXER_DE3) { ret = drm_plane_create_alpha_property(&layer->plane); if (ret) { dev_err(drm->dev, "Couldn't add alpha property\n"); @@ -469,7 +468,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, supported_encodings = BIT(DRM_COLOR_YCBCR_BT601) | BIT(DRM_COLOR_YCBCR_BT709); - if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) + if (layer->cfg->de_type >= SUN8I_MIXER_DE3) supported_encodings |= BIT(DRM_COLOR_YCBCR_BT2020); supported_ranges = BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) | diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index 89d0c32e63cf..6ec68baa2409 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -59,5 +59,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, - int plane_cnt); + int plane_cnt, + const struct sun8i_layer_cfg *cfg); #endif /* _SUN8I_VI_LAYER_H_ */ diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c index fe0bb1de6f08..3dec4eeb1ba2 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_scaler.c @@ -833,17 +833,17 @@ static const u32 bicubic4coefftab32[480] = { 0x1012110d, 0x1012110d, 0x1013110c, 0x1013110c, }; -static u32 sun8i_vi_scaler_base(struct sun8i_mixer *mixer, int channel) +static u32 sun8i_vi_scaler_base(struct sun8i_layer *layer) { - if (mixer->cfg->de_type == SUN8I_MIXER_DE33) + if (layer->cfg->de_type == SUN8I_MIXER_DE33) return DE33_VI_SCALER_UNIT_BASE + - DE33_CH_SIZE * channel; - else if (mixer->cfg->de_type == SUN8I_MIXER_DE3) + DE33_CH_SIZE * layer->channel; + else if (layer->cfg->de_type == SUN8I_MIXER_DE3) return DE3_VI_SCALER_UNIT_BASE + - DE3_VI_SCALER_UNIT_SIZE * channel; + DE3_VI_SCALER_UNIT_SIZE * layer->channel; else return DE2_VI_SCALER_UNIT_BASE + - DE2_VI_SCALER_UNIT_SIZE * channel; + DE2_VI_SCALER_UNIT_SIZE * layer->channel; } static int sun8i_vi_scaler_coef_index(unsigned int step) @@ -914,7 +914,7 @@ void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable) { u32 val, base; - base = sun8i_vi_scaler_base(layer->mixer, layer->channel); + base = sun8i_vi_scaler_base(layer); if (enable) val = SUN8I_SCALER_VSU_CTRL_EN | @@ -931,12 +931,11 @@ void sun8i_vi_scaler_setup(struct sun8i_layer *layer, u32 hscale, u32 vscale, u32 hphase, u32 vphase, const struct drm_format_info *format) { - struct sun8i_mixer *mixer = layer->mixer; u32 chphase, cvphase; u32 insize, outsize; u32 base; - base = sun8i_vi_scaler_base(mixer, layer->channel); + base = sun8i_vi_scaler_base(layer); hphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; vphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16; @@ -960,7 +959,7 @@ void sun8i_vi_scaler_setup(struct sun8i_layer *layer, cvphase = vphase; } - if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) { + if (layer->cfg->de_type >= SUN8I_MIXER_DE3) { u32 val; if (format->hsub == 1 && format->vsub == 1) From 345bca734c577e27fd609077d41dbda1576f2dbf Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:41 +0100 Subject: [PATCH 59/71] drm/sun4i: vi_scaler: Find mixer from crtc With "floating" planes in DE33, mixer can't be stored in layer structure anymore. Find mixer using currently bound crtc. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-30-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index 5f91011425bf..da0d9167d328 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -14,6 +14,7 @@ #include #include +#include "sun4i_crtc.h" #include "sun8i_csc.h" #include "sun8i_mixer.h" #include "sun8i_vi_layer.h" @@ -63,8 +64,9 @@ static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer, struct drm_plane *plane) { struct drm_plane_state *state = plane->state; + struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(state->crtc); + struct sun8i_mixer *mixer = engine_to_sun8i_mixer(scrtc->engine); const struct drm_format_info *format = state->fb->format; - struct sun8i_mixer *mixer = layer->mixer; u32 src_w, src_h, dst_w, dst_h; u32 outsize, insize; u32 hphase, vphase; From 54c33a4fcf155bade9e270527670432f56e309ec Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Tue, 4 Nov 2025 19:09:42 +0100 Subject: [PATCH 60/71] drm/sun4i: Nuke mixer pointer from layer code It's not used anymore, so remove it. This allows trully independent layer state from mixer. Reviewed-by: Chen-Yu Tsai Tested-by: Ryan Walklin Signed-off-by: Jernej Skrabec Link: https://patch.msgid.link/20251104180942.61538-31-jernej.skrabec@gmail.com Signed-off-by: Chen-Yu Tsai --- drivers/gpu/drm/sun4i/sun8i_mixer.c | 12 +++++------- drivers/gpu/drm/sun4i/sun8i_mixer.h | 1 - drivers/gpu/drm/sun4i/sun8i_ui_layer.c | 2 -- drivers/gpu/drm/sun4i/sun8i_ui_layer.h | 1 - drivers/gpu/drm/sun4i/sun8i_vi_layer.c | 2 -- drivers/gpu/drm/sun4i/sun8i_vi_layer.h | 1 - 6 files changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.c b/drivers/gpu/drm/sun4i/sun8i_mixer.c index 5fd5781d4b13..ce9c155bfad7 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.c +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.c @@ -269,7 +269,7 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine, int w, h, x, y, zpos; bool enable; - if (!(plane->possible_crtcs & drm_crtc_mask(crtc)) || layer->mixer != mixer) + if (!(plane->possible_crtcs & drm_crtc_mask(crtc))) continue; plane_state = drm_atomic_get_new_plane_state(state, plane); @@ -337,9 +337,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, if (mixer->cfg->de_type == SUN8I_MIXER_DE33) phy_index = mixer->cfg->map[i]; - layer = sun8i_vi_layer_init_one(drm, mixer, type, - mixer->engine.regs, i, - phy_index, plane_cnt, + layer = sun8i_vi_layer_init_one(drm, type, mixer->engine.regs, + i, phy_index, plane_cnt, &mixer->cfg->lay_cfg); if (IS_ERR(layer)) { dev_err(drm->dev, @@ -363,9 +362,8 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm, if (mixer->cfg->de_type == SUN8I_MIXER_DE33) phy_index = mixer->cfg->map[index]; - layer = sun8i_ui_layer_init_one(drm, mixer, type, - mixer->engine.regs, index, - phy_index, plane_cnt, + layer = sun8i_ui_layer_init_one(drm, type, mixer->engine.regs, + index, phy_index, plane_cnt, &mixer->cfg->lay_cfg); if (IS_ERR(layer)) { dev_err(drm->dev, "Couldn't initialize %s plane\n", diff --git a/drivers/gpu/drm/sun4i/sun8i_mixer.h b/drivers/gpu/drm/sun4i/sun8i_mixer.h index 3948023e095b..e2f83301aae8 100644 --- a/drivers/gpu/drm/sun4i/sun8i_mixer.h +++ b/drivers/gpu/drm/sun4i/sun8i_mixer.h @@ -226,7 +226,6 @@ enum { struct sun8i_layer { struct drm_plane plane; - struct sun8i_mixer *mixer; int type; int index; int channel; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c index a8a67241c822..f08f6da55dd0 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.c @@ -258,7 +258,6 @@ static const uint64_t sun8i_layer_modifiers[] = { }; struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, - struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, @@ -272,7 +271,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, if (!layer) return ERR_PTR(-ENOMEM); - layer->mixer = mixer; layer->type = SUN8I_LAYER_TYPE_UI; layer->index = index; layer->channel = phy_index; diff --git a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h index c357b39999ff..1581ffc6d4e5 100644 --- a/drivers/gpu/drm/sun4i/sun8i_ui_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_ui_layer.h @@ -50,7 +50,6 @@ struct sun8i_mixer; struct sun8i_layer; struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm, - struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c index da0d9167d328..ca3ab59e108d 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.c +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.c @@ -409,7 +409,6 @@ static const uint64_t sun8i_layer_modifiers[] = { }; struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, - struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, @@ -426,7 +425,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, if (!layer) return ERR_PTR(-ENOMEM); - layer->mixer = mixer; layer->type = SUN8I_LAYER_TYPE_VI; layer->index = index; layer->channel = phy_index; diff --git a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h index 6ec68baa2409..29cc5573691f 100644 --- a/drivers/gpu/drm/sun4i/sun8i_vi_layer.h +++ b/drivers/gpu/drm/sun4i/sun8i_vi_layer.h @@ -55,7 +55,6 @@ struct sun8i_mixer; struct sun8i_layer; struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm, - struct sun8i_mixer *mixer, enum drm_plane_type type, struct regmap *regs, int index, int phy_index, From 527e132573dfa793818a536b18eec49598a6f6f5 Mon Sep 17 00:00:00 2001 From: Jayesh Choudhary Date: Tue, 4 Nov 2025 20:44:21 +0530 Subject: [PATCH 61/71] drm/tidss: Remove max_pclk_khz and min_pclk_khz from tidss display features The TIDSS hardware does not have independent maximum or minimum pixel clock limits for each video port. Instead, these limits are determined by the SoC's clock architecture. Previously, this constraint was modeled using the 'max_pclk_khz' and 'min_pclk_khz' fields in 'dispc_features', but this approach is static and does not account for the dynamic behavior of PLLs. This patch removes the 'max_pclk_khz' and 'min_pclk_khz' fields from 'dispc_features'. The correct way to check if a requested mode's pixel clock is supported is by using 'clk_round_rate()' in the 'mode_valid()' hook. If the best frequency match for the mode clock falls within the supported tolerance, it is approved. TIDSS supports a 5% pixel clock tolerance, which is now reflected in the validation logic. This change allows existing DSS-compatible drivers to be reused across SoCs that only differ in their pixel clock characteristics. The validation uses 'clk_round_rate()' for each mode, which may introduce additional delay (about 3.5 ms for 30 modes), but this is generally negligible. Users desiring faster validation may bypass these calls selectively, for example, checking only the highest resolution mode, as shown here[1]. [1]: https://lore.kernel.org/all/20250704094851.182131-3-j-choudhary@ti.com/ Tested-by: Michael Walle Reviewed-by: Devarsh Thakkar Reviewed-by: Tomi Valkeinen Signed-off-by: Jayesh Choudhary Signed-off-by: Swamil Jain Link: https://patch.msgid.link/20251104151422.307162-2-s-jain1@ti.com [Tomi: dropped 'inline' from check_pixel_clock] Signed-off-by: Tomi Valkeinen --- drivers/gpu/drm/tidss/tidss_dispc.c | 86 +++++++++++------------------ drivers/gpu/drm/tidss/tidss_dispc.h | 3 - 2 files changed, 31 insertions(+), 58 deletions(-) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index d8e1a1bcd660..644b68185f67 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -58,12 +58,6 @@ static const u16 tidss_k2g_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_k2g_feats = { - .min_pclk_khz = 4375, - - .max_pclk_khz = { - [DISPC_VP_DPI] = 150000, - }, - /* * XXX According TRM the RGB input buffer width up to 2560 should * work on 3 taps, but in practice it only works up to 1280. @@ -146,11 +140,6 @@ static const u16 tidss_am65x_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_am65x_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - [DISPC_VP_OLDI_AM65X] = 165000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -246,11 +235,6 @@ static const u16 tidss_j721e_common_regs[DISPC_COMMON_REG_TABLE_LEN] = { }; const struct dispc_features dispc_j721e_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 170000, - [DISPC_VP_INTERNAL] = 600000, - }, - .scaling = { .in_width_max_5tap_rgb = 2048, .in_width_max_3tap_rgb = 4096, @@ -317,11 +301,6 @@ const struct dispc_features dispc_j721e_feats = { }; const struct dispc_features dispc_am625_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - [DISPC_VP_INTERNAL] = 170000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -378,15 +357,6 @@ const struct dispc_features dispc_am625_feats = { }; const struct dispc_features dispc_am62a7_feats = { - /* - * if the code reaches dispc_mode_valid with VP1, - * it should return MODE_BAD. - */ - .max_pclk_khz = { - [DISPC_VP_TIED_OFF] = 0, - [DISPC_VP_DPI] = 165000, - }, - .scaling = { .in_width_max_5tap_rgb = 1280, .in_width_max_3tap_rgb = 2560, @@ -443,10 +413,6 @@ const struct dispc_features dispc_am62a7_feats = { }; const struct dispc_features dispc_am62l_feats = { - .max_pclk_khz = { - [DISPC_VP_DPI] = 165000, - }, - .subrev = DISPC_AM62L, .common = "common", @@ -1324,33 +1290,54 @@ static void dispc_vp_set_default_color(struct dispc_device *dispc, DISPC_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff); } +/* + * Calculate the percentage difference between the requested pixel clock rate + * and the effective rate resulting from calculating the clock divider value. + */ +unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate) +{ + int r = rate / 100, rr = real_rate / 100; + + return (unsigned int)(abs(((rr - r) * 100) / r)); +} + +static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport, + unsigned long clock) +{ + unsigned long round_clock; + + round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock); + /* + * To keep the check consistent with dispc_vp_set_clk_rate(), we + * use the same 5% check here. + */ + if (dispc_pclk_diff(clock, round_clock) > 5) + return -EINVAL; + + return 0; +} + enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc, u32 hw_videoport, const struct drm_display_mode *mode) { u32 hsw, hfp, hbp, vsw, vfp, vbp; enum dispc_vp_bus_type bus_type; - int max_pclk; bus_type = dispc->feat->vp_bus_type[hw_videoport]; - max_pclk = dispc->feat->max_pclk_khz[bus_type]; - - if (WARN_ON(max_pclk == 0)) + if (WARN_ON(bus_type == DISPC_VP_TIED_OFF)) return MODE_BAD; - if (mode->clock < dispc->feat->min_pclk_khz) - return MODE_CLOCK_LOW; - - if (mode->clock > max_pclk) - return MODE_CLOCK_HIGH; - if (mode->hdisplay > 4096) return MODE_BAD; if (mode->vdisplay > 4096) return MODE_BAD; + if (check_pixel_clock(dispc, hw_videoport, mode->clock * 1000)) + return MODE_CLOCK_RANGE; + /* TODO: add interlace support */ if (mode->flags & DRM_MODE_FLAG_INTERLACE) return MODE_NO_INTERLACE; @@ -1414,17 +1401,6 @@ void dispc_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport) clk_disable_unprepare(dispc->vp_clk[hw_videoport]); } -/* - * Calculate the percentage difference between the requested pixel clock rate - * and the effective rate resulting from calculating the clock divider value. - */ -unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate) -{ - int r = rate / 100, rr = real_rate / 100; - - return (unsigned int)(abs(((rr - r) * 100) / r)); -} - int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport, unsigned long rate) { diff --git a/drivers/gpu/drm/tidss/tidss_dispc.h b/drivers/gpu/drm/tidss/tidss_dispc.h index f38493a70122..739d211d0018 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.h +++ b/drivers/gpu/drm/tidss/tidss_dispc.h @@ -77,9 +77,6 @@ enum dispc_dss_subrevision { }; struct dispc_features { - int min_pclk_khz; - int max_pclk_khz[DISPC_VP_MAX_BUS_TYPE]; - struct dispc_features_scaling scaling; enum dispc_dss_subrevision subrev; From 86db652fc22f5674ffe3b1f7c91c397c69d26d94 Mon Sep 17 00:00:00 2001 From: Jayesh Choudhary Date: Tue, 4 Nov 2025 20:44:22 +0530 Subject: [PATCH 62/71] drm/tidss: Move OLDI mode validation to OLDI bridge mode_valid hook After integrating OLDI support[0], it is necessary to identify which VP instances use OLDI, since the OLDI driver owns the video port clock (as a serial clock). Clock operations on these VPs must be delegated to the OLDI driver, not handled by the TIDSS driver. This issue also emerged in upstream discussions when DSI-related clock management was attempted in the TIDSS driver[1]. To address this, add an 'is_ext_vp_clk' array to the 'tidss_device' structure, marking a VP as 'true' during 'tidss_oldi_init()' and as 'false' during 'tidss_oldi_deinit()'. TIDSS then uses 'is_ext_vp_clk' to skip clock validation checks in 'dispc_vp_mode_valid()' for VPs under OLDI control. Since OLDI uses the DSS VP clock directly as a serial interface and manages its own rate, mode validation should be implemented in the OLDI bridge's 'mode_valid' hook. This patch adds that logic, ensuring proper delegation and avoiding spurious clock handling in the TIDSS driver. [0]: https://lore.kernel.org/all/20250528122544.817829-1-aradhya.bhatia@linux.dev/ [1]: https://lore.kernel.org/all/DA6TT575Z82D.3MPK8HG5GRL8U@kernel.org/ Fixes: 7246e0929945 ("drm/tidss: Add OLDI bridge support") Tested-by: Michael Walle Reviewed-by: Devarsh Thakkar Reviewed-by: Tomi Valkeinen Signed-off-by: Jayesh Choudhary Signed-off-by: Swamil Jain Link: https://patch.msgid.link/20251104151422.307162-3-s-jain1@ti.com Signed-off-by: Tomi Valkeinen Link: https://patch.msgid.link/ffd5ebe03391b3c01e616c0c844a4b8ddecede36.1762513240.git.jani.nikula@intel.com --- drivers/gpu/drm/tidss/tidss_dispc.c | 7 +++++++ drivers/gpu/drm/tidss/tidss_drv.h | 2 ++ drivers/gpu/drm/tidss/tidss_oldi.c | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/drivers/gpu/drm/tidss/tidss_dispc.c b/drivers/gpu/drm/tidss/tidss_dispc.c index 644b68185f67..58d5eb033bdb 100644 --- a/drivers/gpu/drm/tidss/tidss_dispc.c +++ b/drivers/gpu/drm/tidss/tidss_dispc.c @@ -1306,6 +1306,13 @@ static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport, { unsigned long round_clock; + /* + * For VP's with external clocking, clock operations must be + * delegated to respective driver, so we skip the check here. + */ + if (dispc->tidss->is_ext_vp_clk[hw_videoport]) + return 0; + round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock); /* * To keep the check consistent with dispc_vp_set_clk_rate(), we diff --git a/drivers/gpu/drm/tidss/tidss_drv.h b/drivers/gpu/drm/tidss/tidss_drv.h index 84454a4855d1..e1c1f41d8b4b 100644 --- a/drivers/gpu/drm/tidss/tidss_drv.h +++ b/drivers/gpu/drm/tidss/tidss_drv.h @@ -24,6 +24,8 @@ struct tidss_device { const struct dispc_features *feat; struct dispc_device *dispc; + bool is_ext_vp_clk[TIDSS_MAX_PORTS]; + unsigned int num_crtcs; struct drm_crtc *crtcs[TIDSS_MAX_PORTS]; diff --git a/drivers/gpu/drm/tidss/tidss_oldi.c b/drivers/gpu/drm/tidss/tidss_oldi.c index 7688251beba2..17c535bfa057 100644 --- a/drivers/gpu/drm/tidss/tidss_oldi.c +++ b/drivers/gpu/drm/tidss/tidss_oldi.c @@ -309,6 +309,25 @@ static u32 *tidss_oldi_atomic_get_input_bus_fmts(struct drm_bridge *bridge, return input_fmts; } +static enum drm_mode_status +tidss_oldi_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct tidss_oldi *oldi = drm_bridge_to_tidss_oldi(bridge); + unsigned long round_clock; + + round_clock = clk_round_rate(oldi->serial, mode->clock * 7 * 1000); + /* + * To keep the check consistent with dispc_vp_set_clk_rate(), + * we use the same 5% check here. + */ + if (dispc_pclk_diff(mode->clock * 7 * 1000, round_clock) > 5) + return -EINVAL; + + return 0; +} + static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = { .attach = tidss_oldi_bridge_attach, .atomic_pre_enable = tidss_oldi_atomic_pre_enable, @@ -317,6 +336,7 @@ static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, .atomic_reset = drm_atomic_helper_bridge_reset, + .mode_valid = tidss_oldi_mode_valid, }; static int get_oldi_mode(struct device_node *oldi_tx, int *companion_instance) @@ -430,6 +450,7 @@ void tidss_oldi_deinit(struct tidss_device *tidss) for (int i = 0; i < tidss->num_oldis; i++) { if (tidss->oldis[i]) { drm_bridge_remove(&tidss->oldis[i]->bridge); + tidss->is_ext_vp_clk[tidss->oldis[i]->parent_vp] = false; tidss->oldis[i] = NULL; } } @@ -580,6 +601,7 @@ int tidss_oldi_init(struct tidss_device *tidss) oldi->bridge.timings = &default_tidss_oldi_timings; tidss->oldis[tidss->num_oldis++] = oldi; + tidss->is_ext_vp_clk[oldi->parent_vp] = true; oldi->tidss = tidss; drm_bridge_add(&oldi->bridge); From 6d13495325930d6a5ccf53e150b6c3f61f4b9a63 Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Mon, 10 Nov 2025 16:32:56 +0100 Subject: [PATCH 63/71] drm/nouveau/uvmm: Prepare for larger pages Currently memory allocated by VM_BIND uAPI can only have a granuality matching PAGE_SIZE (4KiB in common case) To have a better memory management and to allow big (64KiB) and huge (2MiB) pages later in the series, we are now passing the page shift all around the internals of UVMM. Signed-off-by: Mary Guillemard Co-developed-by: Mohamed Ahmed Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Reviewed-by: James Jones Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251110-nouveau-compv6-v6-1-83b05475f57c@mary.zone --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 46 ++++++++++++++++---------- drivers/gpu/drm/nouveau/nouveau_uvmm.h | 1 + 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index 79eefdfd08a2..2cd0835b05e8 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -107,34 +107,34 @@ nouveau_uvmm_vmm_sparse_unref(struct nouveau_uvmm *uvmm, static int nouveau_uvmm_vmm_get(struct nouveau_uvmm *uvmm, - u64 addr, u64 range) + u64 addr, u64 range, u8 page_shift) { struct nvif_vmm *vmm = &uvmm->vmm.vmm; - return nvif_vmm_raw_get(vmm, addr, range, PAGE_SHIFT); + return nvif_vmm_raw_get(vmm, addr, range, page_shift); } static int nouveau_uvmm_vmm_put(struct nouveau_uvmm *uvmm, - u64 addr, u64 range) + u64 addr, u64 range, u8 page_shift) { struct nvif_vmm *vmm = &uvmm->vmm.vmm; - return nvif_vmm_raw_put(vmm, addr, range, PAGE_SHIFT); + return nvif_vmm_raw_put(vmm, addr, range, page_shift); } static int nouveau_uvmm_vmm_unmap(struct nouveau_uvmm *uvmm, - u64 addr, u64 range, bool sparse) + u64 addr, u64 range, u8 page_shift, bool sparse) { struct nvif_vmm *vmm = &uvmm->vmm.vmm; - return nvif_vmm_raw_unmap(vmm, addr, range, PAGE_SHIFT, sparse); + return nvif_vmm_raw_unmap(vmm, addr, range, page_shift, sparse); } static int nouveau_uvmm_vmm_map(struct nouveau_uvmm *uvmm, - u64 addr, u64 range, + u64 addr, u64 range, u8 page_shift, u64 bo_offset, u8 kind, struct nouveau_mem *mem) { @@ -163,7 +163,7 @@ nouveau_uvmm_vmm_map(struct nouveau_uvmm *uvmm, return -ENOSYS; } - return nvif_vmm_raw_map(vmm, addr, range, PAGE_SHIFT, + return nvif_vmm_raw_map(vmm, addr, range, page_shift, &args, argc, &mem->mem, bo_offset); } @@ -182,8 +182,9 @@ nouveau_uvma_vmm_put(struct nouveau_uvma *uvma) { u64 addr = uvma->va.va.addr; u64 range = uvma->va.va.range; + u8 page_shift = uvma->page_shift; - return nouveau_uvmm_vmm_put(to_uvmm(uvma), addr, range); + return nouveau_uvmm_vmm_put(to_uvmm(uvma), addr, range, page_shift); } static int @@ -193,9 +194,11 @@ nouveau_uvma_map(struct nouveau_uvma *uvma, u64 addr = uvma->va.va.addr; u64 offset = uvma->va.gem.offset; u64 range = uvma->va.va.range; + u8 page_shift = uvma->page_shift; return nouveau_uvmm_vmm_map(to_uvmm(uvma), addr, range, - offset, uvma->kind, mem); + page_shift, offset, uvma->kind, + mem); } static int @@ -203,12 +206,13 @@ nouveau_uvma_unmap(struct nouveau_uvma *uvma) { u64 addr = uvma->va.va.addr; u64 range = uvma->va.va.range; + u8 page_shift = uvma->page_shift; bool sparse = !!uvma->region; if (drm_gpuva_invalidated(&uvma->va)) return 0; - return nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, sparse); + return nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, page_shift, sparse); } static int @@ -501,7 +505,8 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm, if (vmm_get_range) nouveau_uvmm_vmm_put(uvmm, vmm_get_start, - vmm_get_range); + vmm_get_range, + PAGE_SHIFT); break; } case DRM_GPUVA_OP_REMAP: { @@ -528,6 +533,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm, u64 ustart = va->va.addr; u64 urange = va->va.range; u64 uend = ustart + urange; + u8 page_shift = uvma_from_va(va)->page_shift; /* Nothing to do for mappings we merge with. */ if (uend == vmm_get_start || @@ -538,7 +544,8 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm, u64 vmm_get_range = ustart - vmm_get_start; nouveau_uvmm_vmm_put(uvmm, vmm_get_start, - vmm_get_range); + vmm_get_range, + page_shift); } vmm_get_start = uend; break; @@ -592,6 +599,7 @@ op_map_prepare(struct nouveau_uvmm *uvmm, uvma->region = args->region; uvma->kind = args->kind; + uvma->page_shift = PAGE_SHIFT; drm_gpuva_map(&uvmm->base, &uvma->va, op); @@ -633,7 +641,8 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm, if (vmm_get_range) { ret = nouveau_uvmm_vmm_get(uvmm, vmm_get_start, - vmm_get_range); + vmm_get_range, + new->map->page_shift); if (ret) { op_map_prepare_unwind(new->map); goto unwind; @@ -689,6 +698,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm, u64 ustart = va->va.addr; u64 urange = va->va.range; u64 uend = ustart + urange; + u8 page_shift = uvma_from_va(va)->page_shift; op_unmap_prepare(u); @@ -704,7 +714,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm, u64 vmm_get_range = ustart - vmm_get_start; ret = nouveau_uvmm_vmm_get(uvmm, vmm_get_start, - vmm_get_range); + vmm_get_range, page_shift); if (ret) { op_unmap_prepare_unwind(va); goto unwind; @@ -799,10 +809,11 @@ op_unmap_range(struct drm_gpuva_op_unmap *u, u64 addr, u64 range) { struct nouveau_uvma *uvma = uvma_from_va(u->va); + u8 page_shift = uvma->page_shift; bool sparse = !!uvma->region; if (!drm_gpuva_invalidated(u->va)) - nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, sparse); + nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, page_shift, sparse); } static void @@ -882,6 +893,7 @@ nouveau_uvmm_sm_cleanup(struct nouveau_uvmm *uvmm, struct drm_gpuva_op_map *n = r->next; struct drm_gpuva *va = r->unmap->va; struct nouveau_uvma *uvma = uvma_from_va(va); + u8 page_shift = uvma->page_shift; if (unmap) { u64 addr = va->va.addr; @@ -893,7 +905,7 @@ nouveau_uvmm_sm_cleanup(struct nouveau_uvmm *uvmm, if (n) end = n->va.addr; - nouveau_uvmm_vmm_put(uvmm, addr, end - addr); + nouveau_uvmm_vmm_put(uvmm, addr, end - addr, page_shift); } nouveau_uvma_gem_put(uvma); diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.h b/drivers/gpu/drm/nouveau/nouveau_uvmm.h index 9d3c348581eb..51925711ae90 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.h +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.h @@ -33,6 +33,7 @@ struct nouveau_uvma { struct nouveau_uvma_region *region; u8 kind; + u8 page_shift; }; #define uvmm_from_gpuvm(x) container_of((x), struct nouveau_uvmm, base) From c488a94e7e143b0a6c107e5c7effacca9004ca1a Mon Sep 17 00:00:00 2001 From: Mary Guillemard Date: Mon, 10 Nov 2025 16:32:57 +0100 Subject: [PATCH 64/71] drm/nouveau/uvmm: Allow larger pages Now that everything in UVMM knows about the variable page shift, we can select larger values. The proposed approach relies on nouveau_bo::page unless if it would cause alignment issues (in which case we fall back to searching for an appropriate shift) Signed-off-by: Mary Guillemard Co-developed-by: Mohamed Ahmed Signed-off-by: Mohamed Ahmed Reviewed-by: Lyude Paul Reviewed-by: James Jones Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251110-nouveau-compv6-v6-2-83b05475f57c@mary.zone --- drivers/gpu/drm/nouveau/nouveau_uvmm.c | 60 +++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_uvmm.c b/drivers/gpu/drm/nouveau/nouveau_uvmm.c index 2cd0835b05e8..f10809115c56 100644 --- a/drivers/gpu/drm/nouveau/nouveau_uvmm.c +++ b/drivers/gpu/drm/nouveau/nouveau_uvmm.c @@ -454,6 +454,62 @@ op_unmap_prepare_unwind(struct drm_gpuva *va) drm_gpuva_insert(va->vm, va); } +static bool +op_map_aligned_to_page_shift(const struct drm_gpuva_op_map *op, u8 page_shift) +{ + u64 non_page_bits = (1ULL << page_shift) - 1; + + return (op->va.addr & non_page_bits) == 0 && + (op->va.range & non_page_bits) == 0 && + (op->gem.offset & non_page_bits) == 0; +} + +static u8 +select_page_shift(struct nouveau_uvmm *uvmm, struct drm_gpuva_op_map *op) +{ + struct nouveau_bo *nvbo = nouveau_gem_object(op->gem.obj); + + /* nouveau_bo_fixup_align() guarantees that the page size will be aligned + * for most cases, but it can't handle cases where userspace allocates with + * a size and then binds with a smaller granularity. So in order to avoid + * breaking old userspace, we need to ensure that the VA is actually + * aligned before using it, and if it isn't, then we downgrade to the first + * granularity that will fit, which is optimal from a correctness and + * performance perspective. + */ + if (op_map_aligned_to_page_shift(op, nvbo->page)) + return nvbo->page; + + struct nouveau_mem *mem = nouveau_mem(nvbo->bo.resource); + struct nvif_vmm *vmm = &uvmm->vmm.vmm; + int i; + + /* If the given granularity doesn't fit, let's find one that will fit. */ + for (i = 0; i < vmm->page_nr; i++) { + /* Ignore anything that is bigger or identical to the BO preference. */ + if (vmm->page[i].shift >= nvbo->page) + continue; + + /* Skip incompatible domains. */ + if ((mem->mem.type & NVIF_MEM_VRAM) && !vmm->page[i].vram) + continue; + if ((mem->mem.type & NVIF_MEM_HOST) && + (!vmm->page[i].host || vmm->page[i].shift > PAGE_SHIFT)) + continue; + + /* If it fits, return the proposed shift. */ + if (op_map_aligned_to_page_shift(op, vmm->page[i].shift)) + return vmm->page[i].shift; + } + + /* If we get here then nothing can reconcile the requirements. This should never + * happen. + */ + drm_WARN_ONCE(op->gem.obj->dev, 1, "Could not find an appropriate page size.\n"); + + return PAGE_SHIFT; +} + static void nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm, struct nouveau_uvma_prealloc *new, @@ -506,7 +562,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm, if (vmm_get_range) nouveau_uvmm_vmm_put(uvmm, vmm_get_start, vmm_get_range, - PAGE_SHIFT); + select_page_shift(uvmm, &op->map)); break; } case DRM_GPUVA_OP_REMAP: { @@ -599,7 +655,7 @@ op_map_prepare(struct nouveau_uvmm *uvmm, uvma->region = args->region; uvma->kind = args->kind; - uvma->page_shift = PAGE_SHIFT; + uvma->page_shift = select_page_shift(uvmm, op); drm_gpuva_map(&uvmm->base, &uvma->va, op); From a79d3845f91928505b056e0a1e3f345aa88c12ac Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Mon, 10 Nov 2025 16:32:58 +0100 Subject: [PATCH 65/71] drm/nouveau/mmu/gp100: Remove unused/broken support for compression From GP100 onwards it's not possible to initialise comptag RAM without PMU firmware, which nouveau has no support for. As such, this code is essentially a no-op and will always revert to the equivalent non-compressed kind due to comptag allocation failure. It's also broken for the needs of VM_BIND/Vulkan. Remove the code entirely to make way for supporting compression on GPUs that support GSM-RM. Signed-off-by: Ben Skeggs Signed-off-by: Mohamed Ahmed Signed-off-by: Mary Guillemard Reviewed-by: Lyude Paul Reviewed-by: James Jones Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251110-nouveau-compv6-v6-3-83b05475f57c@mary.zone --- .../drm/nouveau/nvkm/subdev/mmu/vmmgp100.c | 39 ++----------------- .../drm/nouveau/nvkm/subdev/mmu/vmmgp10b.c | 4 +- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c index 851fd847a2a9..ecff1096a1bb 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c @@ -21,9 +21,7 @@ */ #include "vmm.h" -#include #include -#include #include #include @@ -117,8 +115,6 @@ gp100_vmm_pgt_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, { u64 data = (addr >> 4) | map->type; - map->type += ptes * map->ctag; - while (ptes--) { VMM_WO064(pt, vmm, ptei++ * 8, data); data += map->next; @@ -142,7 +138,6 @@ gp100_vmm_pgt_dma(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, while (ptes--) { const u64 data = (*map->dma++ >> 4) | map->type; VMM_WO064(pt, vmm, ptei++ * 8, data); - map->type += map->ctag; } nvkm_done(pt->memory); return; @@ -200,8 +195,6 @@ gp100_vmm_pd0_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, { u64 data = (addr >> 4) | map->type; - map->type += ptes * map->ctag; - while (ptes--) { VMM_WO128(pt, vmm, ptei++ * 0x10, data, 0ULL); data += map->next; @@ -411,8 +404,6 @@ gp100_vmm_valid(struct nvkm_vmm *vmm, void *argv, u32 argc, struct gp100_vmm_map_vn vn; struct gp100_vmm_map_v0 v0; } *args = argv; - struct nvkm_device *device = vmm->mmu->subdev.device; - struct nvkm_memory *memory = map->memory; u8 kind, kind_inv, priv, ro, vol; int kindn, aper, ret = -ENOSYS; const u8 *kindm; @@ -450,30 +441,8 @@ gp100_vmm_valid(struct nvkm_vmm *vmm, void *argv, u32 argc, } if (kindm[kind] != kind) { - u64 tags = nvkm_memory_size(memory) >> 16; - if (aper != 0 || !(page->type & NVKM_VMM_PAGE_COMP)) { - VMM_DEBUG(vmm, "comp %d %02x", aper, page->type); - return -EINVAL; - } - - if (!map->no_comp) { - ret = nvkm_memory_tags_get(memory, device, tags, - nvkm_ltc_tags_clear, - &map->tags); - if (ret) { - VMM_DEBUG(vmm, "comp %d", ret); - return ret; - } - } - - if (!map->no_comp && map->tags->mn) { - tags = map->tags->mn->offset + (map->offset >> 16); - map->ctag |= ((1ULL << page->shift) >> 16) << 36; - map->type |= tags << 36; - map->next |= map->ctag; - } else { - kind = kindm[kind]; - } + /* Revert to non-compressed kind. */ + kind = kindm[kind]; } map->type |= BIT(0); @@ -592,8 +561,8 @@ gp100_vmm = { { 47, &gp100_vmm_desc_16[4], NVKM_VMM_PAGE_Sxxx }, { 38, &gp100_vmm_desc_16[3], NVKM_VMM_PAGE_Sxxx }, { 29, &gp100_vmm_desc_16[2], NVKM_VMM_PAGE_Sxxx }, - { 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SVxC }, - { 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SVxC }, + { 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SVxx }, + { 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SVxx }, { 12, &gp100_vmm_desc_12[0], NVKM_VMM_PAGE_SVHx }, {} } diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp10b.c b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp10b.c index e081239afe58..5791d134962b 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp10b.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp10b.c @@ -34,8 +34,8 @@ gp10b_vmm = { { 47, &gp100_vmm_desc_16[4], NVKM_VMM_PAGE_Sxxx }, { 38, &gp100_vmm_desc_16[3], NVKM_VMM_PAGE_Sxxx }, { 29, &gp100_vmm_desc_16[2], NVKM_VMM_PAGE_Sxxx }, - { 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SxHC }, - { 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SxHC }, + { 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SxHx }, + { 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SxHx }, { 12, &gp100_vmm_desc_12[0], NVKM_VMM_PAGE_SxHx }, {} } From 0ee6a72bb0347fd191fb61715efbc2b518be0926 Mon Sep 17 00:00:00 2001 From: Ben Skeggs Date: Mon, 10 Nov 2025 16:32:59 +0100 Subject: [PATCH 66/71] drm/nouveau/mmu/tu102: Add support for compressed kinds Allow compressed PTE kinds to be written into PTEs when GSP-RM is present, rather than reverting to their non-compressed versions. Signed-off-by: Ben Skeggs Signed-off-by: Mohamed Ahmed Signed-off-by: Mary Guillemard Reviewed-by: Lyude Paul Reviewed-by: James Jones Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251110-nouveau-compv6-v6-4-83b05475f57c@mary.zone --- .../drm/nouveau/nvkm/subdev/mmu/vmmgp100.c | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c index ecff1096a1bb..ed15a4475181 100644 --- a/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c +++ b/drivers/gpu/drm/nouveau/nvkm/subdev/mmu/vmmgp100.c @@ -109,12 +109,34 @@ gp100_vmm_pgt_pfn(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, nvkm_done(pt->memory); } +static inline u64 +gp100_vmm_comptag_nr(u64 size) +{ + return size >> 16; /* One comptag per 64KiB VRAM. */ +} + +static inline u64 +gp100_vmm_pte_comptagline_base(u64 addr) +{ + /* RM allocates enough comptags for all of VRAM, so use a 1:1 mapping. */ + return (1 + gp100_vmm_comptag_nr(addr)) << 36; /* NV_MMU_VER2_PTE_COMPTAGLINE */ +} + +static inline u64 +gp100_vmm_pte_comptagline_incr(u32 page_size) +{ + return gp100_vmm_comptag_nr(page_size) << 36; /* NV_MMU_VER2_PTE_COMPTAGLINE */ +} + static inline void gp100_vmm_pgt_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, u32 ptei, u32 ptes, struct nvkm_vmm_map *map, u64 addr) { u64 data = (addr >> 4) | map->type; + if (map->ctag) + data |= gp100_vmm_pte_comptagline_base(addr); + while (ptes--) { VMM_WO064(pt, vmm, ptei++ * 8, data); data += map->next; @@ -195,6 +217,9 @@ gp100_vmm_pd0_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt, { u64 data = (addr >> 4) | map->type; + if (map->ctag) + data |= gp100_vmm_pte_comptagline_base(addr); + while (ptes--) { VMM_WO128(pt, vmm, ptei++ * 0x10, data, 0ULL); data += map->next; @@ -440,9 +465,26 @@ gp100_vmm_valid(struct nvkm_vmm *vmm, void *argv, u32 argc, return -EINVAL; } + /* Handle compression. */ if (kindm[kind] != kind) { - /* Revert to non-compressed kind. */ - kind = kindm[kind]; + struct nvkm_device *device = vmm->mmu->subdev.device; + + /* Compression is only supported when using GSP-RM, as + * PMU firmware is required in order to initialise the + * compbit backing store. + */ + if (nvkm_gsp_rm(device->gsp)) { + /* Turing GPUs require PTE_COMPTAGLINE to be filled, + * in addition to specifying a compressed kind. + */ + if (device->card_type < GA100) { + map->ctag = gp100_vmm_pte_comptagline_incr(1 << map->page->shift); + map->next |= map->ctag; + } + } else { + /* Revert to non-compressed kind. */ + kind = kindm[kind]; + } } map->type |= BIT(0); From 85ce566b362447fa4a2dd9d124c46fbfd04465be Mon Sep 17 00:00:00 2001 From: Mohamed Ahmed Date: Mon, 10 Nov 2025 16:33:00 +0100 Subject: [PATCH 67/71] drm/nouveau/drm: Bump the driver version to 1.4.1 to report new features The HW can only do compression on large and huge pages, and enabling it on 4K pages leads to a MMU fault. Compression also needs kernel support for handling the compressed kinds and managing the compression tags. This increments the nouveau version number which allows NVK to enable it only when the kernel actually supports both features and avoid breaking the system if a newer mesa version is paired with an older kernel version. For the associated userspace MR, please see !36450: https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36450 Signed-off-by: Mohamed Ahmed Signed-off-by: Mary Guillemard Reviewed-by: Lyude Paul Reviewed-by: James Jones Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251110-nouveau-compv6-v6-5-83b05475f57c@mary.zone --- drivers/gpu/drm/nouveau/nouveau_drv.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/nouveau_drv.h b/drivers/gpu/drm/nouveau/nouveau_drv.h index 84a275b06295..0e409414f44d 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drv.h +++ b/drivers/gpu/drm/nouveau/nouveau_drv.h @@ -10,7 +10,7 @@ #define DRIVER_MAJOR 1 #define DRIVER_MINOR 4 -#define DRIVER_PATCHLEVEL 0 +#define DRIVER_PATCHLEVEL 1 /* * 1.1.1: @@ -35,6 +35,8 @@ * programs that get directly linked with NVKM. * 1.3.1: * - implemented limited ABI16/NVIF interop + * 1.4.1: + * - add variable page sizes and compression for Turing+ */ #include From de0d6e19d2ef33ba34be2467ffdf3595da5f5326 Mon Sep 17 00:00:00 2001 From: Timur Tabi Date: Fri, 10 Oct 2025 17:39:57 -0500 Subject: [PATCH 68/71] drm/nouveau: fully define nvfw_hs_load_header_v2 Add the missing fields of the nvfw_hs_load_header_v2 struct, so that the struct matches the actual contents of the firmware images. nvfw_hs_load_header_v2 is a struct that defines a header for some firmware images used by Nouveau. The current structure definition is incomplete; it omits the last two fields because they are unused. To maintain consistency between Nouveau, OpenRM, and Nova, and to make it easier to support possible future images, we should fully define the struct. Also add a __counted_by tag for the flex array. Signed-off-by: Timur Tabi Reviewed-by: Lyude Paul Signed-off-by: Lyude Paul Link: https://patch.msgid.link/20251010223957.1078525-1-ttabi@nvidia.com --- drivers/gpu/drm/nouveau/include/nvfw/hs.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/nouveau/include/nvfw/hs.h b/drivers/gpu/drm/nouveau/include/nvfw/hs.h index 8b58b668fc0c..c78ab11ec3ac 100644 --- a/drivers/gpu/drm/nouveau/include/nvfw/hs.h +++ b/drivers/gpu/drm/nouveau/include/nvfw/hs.h @@ -52,7 +52,9 @@ struct nvfw_hs_load_header_v2 { struct { u32 offset; u32 size; - } app[]; + u32 data_offset; + u32 data_size; + } app[] __counted_by(num_apps); }; const struct nvfw_hs_load_header_v2 *nvfw_hs_load_header_v2(struct nvkm_subdev *, const void *); From 0709abaf67345b3a8966198fc0957fcc361b065f Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Tue, 4 Nov 2025 16:38:05 +0100 Subject: [PATCH 69/71] drm/imx/ipuv3: Fix dumb-buffer allocation for non-RGB formats Align pitch to multiples of 8 pixels for bpp values that do not map to RGB formats. The call to drm_driver_color_mode_format() fails with DRM_INVALID_FORMAT in these cases. Fall back to manually computing the pitch alignment from which drm_mode_size_dumb() can compute the correct pitch. Fixes userspace that allocates dumb buffers for YUV formats, where bpp equals 12. A common example is the IGT kms_getfb test. v2: - ignore width in calculation Suggested-by: Dmitry Baryshkov Signed-off-by: Thomas Zimmermann Fixes: b1d0e470f881 ("drm/imx/ipuv3: Compute dumb-buffer sizes with drm_mode_size_dumb()") Cc: Thomas Zimmermann Cc: Philipp Zabel Cc: Shawn Guo Cc: Sascha Hauer Cc: Pengutronix Kernel Team Cc: Fabio Estevam Cc: dri-devel@lists.freedesktop.org Cc: imx@lists.linux.dev Cc: linux-arm-kernel@lists.infradead.org Tested-by: Philipp Zabel Reviewed-by: Philipp Zabel Link: https://patch.msgid.link/20251104153832.189666-1-tzimmermann@suse.de --- drivers/gpu/drm/imx/ipuv3/imx-drm-core.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/imx/ipuv3/imx-drm-core.c b/drivers/gpu/drm/imx/ipuv3/imx-drm-core.c index 465b5a6ad5bb..eddb471119c6 100644 --- a/drivers/gpu/drm/imx/ipuv3/imx-drm-core.c +++ b/drivers/gpu/drm/imx/ipuv3/imx-drm-core.c @@ -144,7 +144,6 @@ static int imx_drm_dumb_create(struct drm_file *file_priv, struct drm_mode_create_dumb *args) { u32 fourcc; - const struct drm_format_info *info; u64 pitch_align; int ret; @@ -156,12 +155,15 @@ static int imx_drm_dumb_create(struct drm_file *file_priv, * the allocated buffer. */ fourcc = drm_driver_color_mode_format(drm, args->bpp); - if (fourcc == DRM_FORMAT_INVALID) - return -EINVAL; - info = drm_format_info(fourcc); - if (!info) - return -EINVAL; - pitch_align = drm_format_info_min_pitch(info, 0, SZ_8); + if (fourcc != DRM_FORMAT_INVALID) { + const struct drm_format_info *info = drm_format_info(fourcc); + + if (!info) + return -EINVAL; + pitch_align = drm_format_info_min_pitch(info, 0, 8); + } else { + pitch_align = DIV_ROUND_UP(args->bpp, SZ_8) * 8; + } if (!pitch_align || pitch_align > U32_MAX) return -EINVAL; ret = drm_mode_size_dumb(drm, args, pitch_align, 0); From 6ff9385c07aa311f01f87307e6256231be7d8675 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 7 Nov 2025 10:11:15 -0800 Subject: [PATCH 70/71] accel/amdxdna: Clear mailbox interrupt register during channel creation The mailbox interrupt register is not always cleared when a mailbox channel is created. This can leave stale interrupt states from previous operations. Fix this by explicitly clearing the interrupt register in the mailbox channel creation function. Fixes: b87f920b9344 ("accel/amdxdna: Support hardware mailbox") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251107181115.1293158-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/amdxdna_mailbox.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/accel/amdxdna/amdxdna_mailbox.c b/drivers/accel/amdxdna/amdxdna_mailbox.c index 24258dcc18eb..858df97cd3fb 100644 --- a/drivers/accel/amdxdna/amdxdna_mailbox.c +++ b/drivers/accel/amdxdna/amdxdna_mailbox.c @@ -516,6 +516,7 @@ xdna_mailbox_create_channel(struct mailbox *mb, } mb_chann->bad_state = false; + mailbox_reg_write(mb_chann, mb_chann->iohub_int_addr, 0); MB_DBG(mb_chann, "Mailbox channel created (irq: %d)", mb_chann->msix_irq); return mb_chann; From ca2583412306ceda9304a7c4302fd9efbf43e963 Mon Sep 17 00:00:00 2001 From: Lizhi Hou Date: Fri, 7 Nov 2025 10:10:50 -0800 Subject: [PATCH 71/71] accel/amdxdna: Fix deadlock between context destroy and job timeout Hardware context destroy function holds dev_lock while waiting for all jobs to complete. The timeout job also needs to acquire dev_lock, this leads to a deadlock. Fix the issue by temporarily releasing dev_lock before waiting for all jobs to finish, and reacquiring it afterward. Fixes: 4fd6ca90fc7f ("accel/amdxdna: Refactor hardware context destroy routine") Reviewed-by: Maciej Falkowski Signed-off-by: Lizhi Hou Link: https://patch.msgid.link/20251107181050.1293125-1-lizhi.hou@amd.com --- drivers/accel/amdxdna/aie2_ctx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/accel/amdxdna/aie2_ctx.c b/drivers/accel/amdxdna/aie2_ctx.c index bdc90fe8a47e..42d876a427c5 100644 --- a/drivers/accel/amdxdna/aie2_ctx.c +++ b/drivers/accel/amdxdna/aie2_ctx.c @@ -690,17 +690,19 @@ void aie2_hwctx_fini(struct amdxdna_hwctx *hwctx) xdna = hwctx->client->xdna; XDNA_DBG(xdna, "%s sequence number %lld", hwctx->name, hwctx->priv->seq); - drm_sched_entity_destroy(&hwctx->priv->entity); - aie2_hwctx_wait_for_idle(hwctx); /* Request fw to destroy hwctx and cancel the rest pending requests */ aie2_release_resource(hwctx); + mutex_unlock(&xdna->dev_lock); + drm_sched_entity_destroy(&hwctx->priv->entity); + /* Wait for all submitted jobs to be completed or canceled */ wait_event(hwctx->priv->job_free_wq, atomic64_read(&hwctx->job_submit_cnt) == atomic64_read(&hwctx->job_free_cnt)); + mutex_lock(&xdna->dev_lock); drm_sched_fini(&hwctx->priv->sched); aie2_ctx_syncobj_destroy(hwctx);