[PATCH 29/45] media: qcom: camss: sa8775p: add IFE top v4.1x

From: Gjorgji . Rosikopulos . gjorgji . rosikopulos

Date: Tue Oct 06 2026 - 18:58:08 EST


From: Atanas Filipov <atanas.filipov@xxxxxxxxxxxxxxxx>

Add the IFE top v4.1x engine used by the SA8775P full pixel path.

Signed-off-by: Atanas Filipov <atanas.filipov@xxxxxxxxxxxxxxxx>
---
drivers/media/platform/qcom/camss/Makefile | 1 +
.../platform/qcom/camss/camss-ife-top-v41x.c | 384 ++++++++++++++++++
drivers/media/platform/qcom/camss/camss-ife.h | 1 +
3 files changed, 386 insertions(+)
create mode 100644 drivers/media/platform/qcom/camss/camss-ife-top-v41x.c

diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index e28b7477dab1..479ab17f3bdb 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -24,6 +24,7 @@ qcom-camss-objs += \
camss-ife-scale-v10x.o \
camss-ife-scale-v23x.o \
camss-ife-top-v20x.o \
+ camss-ife-top-v41x.o \
camss-ife-pads.o \
camss-ispif.o \
camss-tpg.o \
diff --git a/drivers/media/platform/qcom/camss/camss-ife-top-v41x.c b/drivers/media/platform/qcom/camss/camss-ife-top-v41x.c
new file mode 100644
index 000000000000..b600dd009268
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-ife-top-v41x.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Qualcomm Technologies, Inc.
+ */
+
+#include "camss-ife.h"
+#include "camss-ife-scale-v23x.h"
+#include "camss.h"
+
+#define IFE_V41X_TOP_OFFS 0x0000
+
+/* DS M/N scaler module base offsets from IFE TOP base */
+#define IFE_V41X_VID_DS_MN_Y_OFFSET 0x7f80
+#define IFE_V41X_DISP_DS_MN_Y_OFFSET 0x6780
+#define IFE_V41X_VID_DS_MN_C_OFFSET 0x8700
+#define IFE_V41X_DISP_DS_MN_C_OFFSET 0x6a00
+#define IFE_V41X_DISP_POST_MN_Y_OFFSET 0x6c80
+#define IFE_V41X_DISP_POST_MN_C_OFFSET 0x6e00
+
+#define IFE_V41X_MOD_VER_GEN GENMASK(31, 28)
+#define IFE_V41X_MOD_VER_REV GENMASK(27, 16)
+#define IFE_V41X_MOD_VER_STEP GENMASK(15, 0)
+#define IFE_V41X_MOD_GEN(val) FIELD_GET(IFE_V41X_MOD_VER_GEN, val)
+#define IFE_V41X_MOD_REV(val) FIELD_GET(IFE_V41X_MOD_VER_REV, val)
+#define IFE_V41X_MOD_STEP(val) FIELD_GET(IFE_V41X_MOD_VER_STEP, val)
+
+/* CSID-level reset and register-update (via csid_base) */
+#define IFE_V41X_CSID_RST_CMD 0x00000010
+#define IFE_V41X_CSID_RST_CMD_SW_RST BIT(0)
+#define IFE_V41X_CSID_RUP_AUP_CMD 0x00000018
+#define IFE_V41X_CSID_RUP_IPP0 BIT(0)
+#define IFE_V41X_CSID_RUP_RDI0 BIT(4)
+#define IFE_V41X_CSID_RUP_RDI1 BIT(5)
+#define IFE_V41X_CSID_RUP_RDI2 BIT(6)
+#define IFE_V41X_CSID_AUP_IPP0 BIT(16)
+#define IFE_V41X_CSID_AUP_RDI0 BIT(20)
+#define IFE_V41X_CSID_AUP_RDI1 BIT(21)
+#define IFE_V41X_CSID_AUP_RDI2 BIT(22)
+
+/* TOP functional IRQ registers */
+#define IFE_V41X_FUNC_IRQ_CMD 0x000000a4
+#define IFE_V41X_FUNC_IRQ_CMD_CLEAR BIT(0)
+#define IFE_V41X_FUNC_IRQ_MASK_0 0x000000a8
+#define IFE_V41X_FUNC_IRQ_MASK_1 0x000000ac
+#define IFE_V41X_FUNC_IRQ_CLEAR_0 0x000000b0
+#define IFE_V41X_FUNC_IRQ_CLEAR_1 0x000000b4
+#define IFE_V41X_FUNC_IRQ_STATUS_0 0x000000b8
+#define IFE_V41X_FUNC_IRQ_STATUS_1 0x000000bc
+
+/* IRQ_STATUS_0 bits */
+#define IFE_V41X_IRQ0_DIAG_VIOLATION BIT(15)
+#define IFE_V41X_IRQ0_DSP_ERROR_VIOLATION BIT(14)
+#define IFE_V41X_IRQ0_DSP_RX_FIFO_OVERFLOW BIT(13)
+#define IFE_V41X_IRQ0_DSP_TX_FIFO_OVERFLOW BIT(12)
+#define IFE_V41X_IRQ0_PIXEL_PIPE_VIOLATION BIT(4)
+#define IFE_V41X_IRQ0_BUSWR BIT(0)
+#define IFE_V41X_IRQ0_ERRORS_MASK \
+ (IFE_V41X_IRQ0_DIAG_VIOLATION | \
+ IFE_V41X_IRQ0_DSP_ERROR_VIOLATION | \
+ IFE_V41X_IRQ0_DSP_RX_FIFO_OVERFLOW | \
+ IFE_V41X_IRQ0_DSP_TX_FIFO_OVERFLOW | \
+ IFE_V41X_IRQ0_PIXEL_PIPE_VIOLATION)
+
+/* IRQ_STATUS_1 bits */
+#define IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH1 BIT(17)
+#define IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH0 BIT(16)
+#define IFE_V41X_IRQ1_RDI2_EOF BIT(13)
+#define IFE_V41X_IRQ1_RDI2_SOF BIT(12)
+#define IFE_V41X_IRQ1_RDI1_EOF BIT(11)
+#define IFE_V41X_IRQ1_RDI1_SOF BIT(10)
+#define IFE_V41X_IRQ1_RDI0_EOF BIT(9)
+#define IFE_V41X_IRQ1_RDI0_SOF BIT(8)
+#define IFE_V41X_IRQ1_PIXEL_PIPE_EOF BIT(1)
+#define IFE_V41X_IRQ1_PIXEL_PIPE_SOF BIT(0)
+#define IFE_V41X_IRQ1_RDI0 \
+ (IFE_V41X_IRQ1_RDI0_SOF | IFE_V41X_IRQ1_RDI0_EOF)
+#define IFE_V41X_IRQ1_RDI1 \
+ (IFE_V41X_IRQ1_RDI1_SOF | IFE_V41X_IRQ1_RDI1_EOF)
+#define IFE_V41X_IRQ1_RDI2 \
+ (IFE_V41X_IRQ1_RDI2_SOF | IFE_V41X_IRQ1_RDI2_EOF)
+#define IFE_V41X_IRQ1_PP \
+ (IFE_V41X_IRQ1_PIXEL_PIPE_SOF | \
+ IFE_V41X_IRQ1_PIXEL_PIPE_EOF | \
+ IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH0 | \
+ IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH1)
+
+/* WM indices for PP output paths */
+#define IFE_V41X_PP_VID_Y_WM 0
+#define IFE_V41X_PP_VID_C_WM 1
+#define IFE_V41X_PP_DISP_Y_WM 3
+#define IFE_V41X_PP_DISP_C_WM 4
+
+#define IFE_V41X_HW_VERSION 0x00000000
+
+#define IFE_V41X_CORE_CFG_0 0x00000024
+#define IFE_V41X_CORE_CFG_0_VID_OUT_OUTPUT_PACKER_EN BIT(10)
+#define IFE_V41X_CORE_CFG_0_DISP_DS4_R2PD_DISABLE BIT(21)
+#define IFE_V41X_CORE_CFG_0_DISP_DS16_R2PD_DISABLE BIT(22)
+
+static int ife_top_v41x_acquire(struct ife_device *ife, void __iomem *base,
+ void __iomem *csid_base)
+{
+ struct ife_top_ctx *ctx = &ife->top_ctx;
+
+ if (!csid_base)
+ return -EINVAL;
+
+ ctx->base = base + IFE_V41X_TOP_OFFS;
+ ctx->csid_base = csid_base;
+ ctx->dev = ife->camss->dev;
+
+ return 0;
+}
+
+static void ife_top_v41x_release(struct ife_top_ctx *ctx)
+{
+ ctx->base = NULL;
+ ctx->csid_base = NULL;
+}
+
+static u32 ife_top_v41x_version(struct ife_top_ctx *ctx)
+{
+ struct camss_hw_version hw_ver;
+ u32 reg = readl_relaxed(ctx->base + IFE_V41X_HW_VERSION);
+
+ hw_ver.ids.major = IFE_V41X_MOD_GEN(reg);
+ hw_ver.ids.minor = IFE_V41X_MOD_REV(reg);
+ hw_ver.ids.steps = IFE_V41X_MOD_STEP(reg);
+
+ return hw_ver.hw_version;
+}
+
+static void ife_top_v41x_reset(struct ife_top_ctx *ctx)
+{
+ void __iomem *top = ctx->base;
+ void __iomem *csid = ctx->csid_base;
+
+ writel_relaxed(~0, top + IFE_V41X_FUNC_IRQ_CLEAR_0);
+ writel_relaxed(~0, top + IFE_V41X_FUNC_IRQ_CLEAR_1);
+ writel_relaxed(IFE_V41X_FUNC_IRQ_CMD_CLEAR, top + IFE_V41X_FUNC_IRQ_CMD);
+ writel_relaxed(IFE_V41X_CSID_RST_CMD_SW_RST, csid + IFE_V41X_CSID_RST_CMD);
+}
+
+static void ife_top_v41x_reg_update(struct ife_top_ctx *ctx,
+ const struct ife_pad_desc *dsc)
+{
+ void __iomem *csid = ctx->csid_base;
+ u32 rval;
+
+ switch (dsc->hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_PP:
+ rval = IFE_V41X_CSID_RUP_IPP0 | IFE_V41X_CSID_AUP_IPP0;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ rval = IFE_V41X_CSID_RUP_RDI0 | IFE_V41X_CSID_AUP_RDI0;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ rval = IFE_V41X_CSID_RUP_RDI1 | IFE_V41X_CSID_AUP_RDI1;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ rval = IFE_V41X_CSID_RUP_RDI2 | IFE_V41X_CSID_AUP_RDI2;
+ break;
+ default:
+ dev_warn(ctx->dev, "Invalid RUP pipe: 0x%x\n", dsc->hw_pipe);
+ return;
+ }
+
+ writel_relaxed(rval, csid + IFE_V41X_CSID_RUP_AUP_CMD);
+}
+
+static int ife_top_v41x_pipe_config(struct ife_top_ctx *ctx,
+ const struct ife_pad_desc *desc,
+ const struct camss_ife_pipeline_cfg *pipeline_cfg,
+ const struct camss_ife_wm_config *wm_cfg)
+{
+ enum camss_ife_hw_pipe hw_pipe = desc->hw_pipe;
+
+ switch (hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ break;
+ case CAMSS_IFE_HW_PIPE_PP:
+ if (wm_cfg[Y_PLANE].wm_id == IFE_V41X_PP_VID_Y_WM &&
+ wm_cfg[C_PLANE].wm_id == IFE_V41X_PP_VID_C_WM) {
+ camif_v23x_scale_config(ctx->base + IFE_V41X_VID_DS_MN_Y_OFFSET,
+ pipeline_cfg, &wm_cfg[Y_PLANE]);
+ camif_v23x_scale_config(ctx->base + IFE_V41X_VID_DS_MN_C_OFFSET,
+ pipeline_cfg, &wm_cfg[C_PLANE]);
+ }
+
+ if (wm_cfg[Y_PLANE].wm_id == IFE_V41X_PP_DISP_Y_WM &&
+ wm_cfg[C_PLANE].wm_id == IFE_V41X_PP_DISP_C_WM) {
+ camif_v23x_scale_config(ctx->base + IFE_V41X_DISP_DS_MN_Y_OFFSET,
+ pipeline_cfg, &wm_cfg[Y_PLANE]);
+ camif_v23x_scale_config(ctx->base + IFE_V41X_DISP_DS_MN_C_OFFSET,
+ pipeline_cfg, &wm_cfg[C_PLANE]);
+ camif_v23x_post_mn_config(ctx->base + IFE_V41X_DISP_POST_MN_Y_OFFSET,
+ &wm_cfg[Y_PLANE], false);
+ camif_v23x_post_mn_config(ctx->base + IFE_V41X_DISP_POST_MN_C_OFFSET,
+ &wm_cfg[C_PLANE], true);
+ }
+ break;
+ default:
+ dev_err(ctx->dev, "Invalid HW pipeline identifier: 0x%x\n", hw_pipe);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ife_top_v41x_pipe_enable(struct ife_top_ctx *ctx,
+ const struct ife_pad_desc *desc)
+{
+ enum camss_ife_hw_pipe hw_pipe = desc->hw_pipe;
+
+ switch (hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ break;
+ case CAMSS_IFE_HW_PIPE_PP:
+ camif_v23x_scale_enable(ctx->base + IFE_V41X_VID_DS_MN_Y_OFFSET);
+ camif_v23x_scale_enable(ctx->base + IFE_V41X_VID_DS_MN_C_OFFSET);
+ writel_relaxed(IFE_V41X_CORE_CFG_0_VID_OUT_OUTPUT_PACKER_EN |
+ IFE_V41X_CORE_CFG_0_DISP_DS4_R2PD_DISABLE |
+ IFE_V41X_CORE_CFG_0_DISP_DS16_R2PD_DISABLE,
+ ctx->base + IFE_V41X_CORE_CFG_0);
+ camif_v23x_scale_enable(ctx->base + IFE_V41X_DISP_DS_MN_Y_OFFSET);
+ camif_v23x_scale_enable(ctx->base + IFE_V41X_DISP_DS_MN_C_OFFSET);
+ camif_v23x_post_mn_enable(ctx->base + IFE_V41X_DISP_POST_MN_Y_OFFSET);
+ camif_v23x_post_mn_enable(ctx->base + IFE_V41X_DISP_POST_MN_C_OFFSET);
+ break;
+ default:
+ dev_err(ctx->dev, "Invalid HW pipeline identifier: 0x%x\n", hw_pipe);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ife_top_v41x_pipe_disable(struct ife_top_ctx *ctx,
+ const struct ife_pad_desc *desc)
+{
+ enum camss_ife_hw_pipe hw_pipe = desc->hw_pipe;
+
+ switch (hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ break;
+ case CAMSS_IFE_HW_PIPE_PP:
+ camif_v23x_scale_disable(ctx->base + IFE_V41X_VID_DS_MN_Y_OFFSET);
+ camif_v23x_scale_disable(ctx->base + IFE_V41X_VID_DS_MN_C_OFFSET);
+ writel_relaxed(0, ctx->base + IFE_V41X_CORE_CFG_0);
+ camif_v23x_scale_disable(ctx->base + IFE_V41X_DISP_DS_MN_Y_OFFSET);
+ camif_v23x_scale_disable(ctx->base + IFE_V41X_DISP_DS_MN_C_OFFSET);
+ camif_v23x_post_mn_disable(ctx->base + IFE_V41X_DISP_POST_MN_Y_OFFSET);
+ camif_v23x_post_mn_disable(ctx->base + IFE_V41X_DISP_POST_MN_C_OFFSET);
+ break;
+ default:
+ dev_err(ctx->dev, "Invalid HW pipeline identifier: 0x%x\n", hw_pipe);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void ife_top_v41x_prepare(struct ife_top_ctx *ctx)
+{
+ void __iomem *top = ctx->base;
+ u32 rval;
+
+ writel_relaxed(~0, top + IFE_V41X_FUNC_IRQ_CLEAR_0);
+ writel_relaxed(~0, top + IFE_V41X_FUNC_IRQ_CLEAR_1);
+ writel_relaxed(IFE_V41X_FUNC_IRQ_CMD_CLEAR, top + IFE_V41X_FUNC_IRQ_CMD);
+
+ rval = IFE_V41X_IRQ0_BUSWR | IFE_V41X_IRQ0_ERRORS_MASK;
+ writel_relaxed(rval, top + IFE_V41X_FUNC_IRQ_MASK_0);
+}
+
+static void ife_top_v41x_enable(struct ife_top_ctx *ctx, enum camss_ife_hw_pipe hw_pipe)
+{
+ void __iomem *top = ctx->base;
+ u32 rval = readl_relaxed(top + IFE_V41X_FUNC_IRQ_MASK_1);
+
+ switch (hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_PP:
+ rval |= IFE_V41X_IRQ1_PP;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ rval |= IFE_V41X_IRQ1_RDI0;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ rval |= IFE_V41X_IRQ1_RDI1;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ rval |= IFE_V41X_IRQ1_RDI2;
+ break;
+ default:
+ return;
+ }
+
+ writel_relaxed(rval, top + IFE_V41X_FUNC_IRQ_MASK_1);
+}
+
+static void ife_top_v41x_disable(struct ife_top_ctx *ctx, enum camss_ife_hw_pipe hw_pipe)
+{
+ void __iomem *top = ctx->base;
+ u32 rval = readl_relaxed(top + IFE_V41X_FUNC_IRQ_MASK_1);
+
+ switch (hw_pipe) {
+ case CAMSS_IFE_HW_PIPE_PP:
+ rval &= ~IFE_V41X_IRQ1_PP;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI0:
+ rval &= ~IFE_V41X_IRQ1_RDI0;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI1:
+ rval &= ~IFE_V41X_IRQ1_RDI1;
+ break;
+ case CAMSS_IFE_HW_PIPE_RDI2:
+ rval &= ~IFE_V41X_IRQ1_RDI2;
+ break;
+ default:
+ return;
+ }
+
+ writel_relaxed(rval, top + IFE_V41X_FUNC_IRQ_MASK_1);
+}
+
+static void ife_top_v41x_irq_status(struct ife_top_ctx *ctx, u32 isr_stat[IFE_TOP_STATUS_MAX])
+{
+ void __iomem *top = ctx->base;
+ u32 stat0;
+ u32 stat1;
+
+ stat0 = readl_relaxed(top + IFE_V41X_FUNC_IRQ_STATUS_0);
+ stat1 = readl_relaxed(top + IFE_V41X_FUNC_IRQ_STATUS_1);
+
+ writel_relaxed(stat0, top + IFE_V41X_FUNC_IRQ_CLEAR_0);
+ writel_relaxed(stat1, top + IFE_V41X_FUNC_IRQ_CLEAR_1);
+ writel_relaxed(IFE_V41X_FUNC_IRQ_CMD_CLEAR, top + IFE_V41X_FUNC_IRQ_CMD);
+
+ isr_stat[IFE_TOP_STATUS_ID0] = 0;
+ isr_stat[IFE_TOP_STATUS_ID1] = 0;
+ isr_stat[IFE_TOP_STATUS_ID2] = stat0 & IFE_V41X_IRQ0_ERRORS_MASK;
+
+ if (stat0 & IFE_V41X_IRQ0_BUSWR)
+ isr_stat[IFE_TOP_STATUS_ID0] |= IFE_HW_TOP_IRQ0_BUSWR;
+
+ if (stat1 & IFE_V41X_IRQ1_RDI0_SOF)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_RDI0_SOF;
+
+ if (stat1 & IFE_V41X_IRQ1_RDI1_SOF)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_RDI1_SOF;
+
+ if (stat1 & IFE_V41X_IRQ1_RDI2_SOF)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_RDI2_SOF;
+
+ if (stat1 & IFE_V41X_IRQ1_PIXEL_PIPE_SOF)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_PP_SOF;
+
+ if (stat1 & IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH0)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_PP_EPOCH0;
+
+ if (stat1 & IFE_V41X_IRQ1_PIXEL_PIPE_EPOCH1)
+ isr_stat[IFE_TOP_STATUS_ID1] |= IFE_HW_TOP_IRQ1_PP_EPOCH1;
+}
+
+const struct ife_hw_top_ops ife_top_v41x_ops = {
+ .acquire = ife_top_v41x_acquire,
+ .release = ife_top_v41x_release,
+ .version = ife_top_v41x_version,
+ .prepare = ife_top_v41x_prepare,
+ .enable = ife_top_v41x_enable,
+ .disable = ife_top_v41x_disable,
+ .pipe_config = ife_top_v41x_pipe_config,
+ .pipe_enable = ife_top_v41x_pipe_enable,
+ .pipe_disable = ife_top_v41x_pipe_disable,
+ .reg_update = ife_top_v41x_reg_update,
+ .reset = ife_top_v41x_reset,
+ .irq_status = ife_top_v41x_irq_status,
+};
diff --git a/drivers/media/platform/qcom/camss/camss-ife.h b/drivers/media/platform/qcom/camss/camss-ife.h
index efbb1e75d550..31d9c83767a5 100644
--- a/drivers/media/platform/qcom/camss/camss-ife.h
+++ b/drivers/media/platform/qcom/camss/camss-ife.h
@@ -396,6 +396,7 @@ camss_ife_pipe_for_stream_id(int nstreams,

extern const struct ife_pads_descriptors ife_pads_v20x;
extern const struct ife_hw_top_ops ife_top_20x_ops;
+extern const struct ife_hw_top_ops ife_top_v41x_ops;
extern const struct ife_hw_buswr_ops ife_buswr_20x_ops;
extern const struct ife_hw_buswr_ops ife_buswr_31x_ops;
extern const struct camss_mod_ops ife_mod_v10x_ops;
--
2.34.1