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

From: Gjorgji . Rosikopulos . gjorgji . rosikopulos

Date: Tue Oct 06 2026 - 18:54:07 EST


From: Atanas Filipov <atanas.filipov@xxxxxxxxxxxxxxxx>

Add the module backend used by the SA8775P IFE pixel path. It
configures the default white balance, demosaic, color transform,
and AWB BG blocks.

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

diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile
index 695924e0c03b..2e4c07fda524 100644
--- a/drivers/media/platform/qcom/camss/Makefile
+++ b/drivers/media/platform/qcom/camss/Makefile
@@ -21,6 +21,7 @@ qcom-camss-objs += \
camss-ife-camif-v10x.o \
camss-ife-lite-top-v10x.o \
camss-ife-mod-v10x.o \
+ camss-ife-mod-v41x.o \
camss-ife-params.o \
camss-ife-scale-v10x.o \
camss-ife-scale-v23x.o \
diff --git a/drivers/media/platform/qcom/camss/camss-ife-mod-v41x.c b/drivers/media/platform/qcom/camss/camss-ife-mod-v41x.c
new file mode 100644
index 000000000000..9f4c8ae9667e
--- /dev/null
+++ b/drivers/media/platform/qcom/camss/camss-ife-mod-v41x.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Qualcomm Technologies, Inc.
+ *
+ * IFE pixel-path post-processing modules for the v41x register layout.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/io.h>
+
+#include "camss-ife.h"
+
+/* Module base offsets */
+#define IFE_MOD_V41X_WB_GAIN_OFFSET 0x4000
+#define IFE_MOD_V41X_DEMOSAIC_OFFSET 0x5580
+#define IFE_MOD_V41X_XFORM_OFFSET 0x6200
+#define IFE_MOD_V41X_AWB_BG_OFFSET 0x6800
+
+/* WB gain registers */
+#define IFE_MOD_V41X_WB_GAIN_MODULE_CFG 0x60
+#define IFE_MOD_V41X_WB_GAIN_CFG_0 0x68
+#define IFE_MOD_V41X_WB_GAIN_CFG_1 0x6c
+#define IFE_MOD_V41X_WB_GAIN_CFG_2 0x70
+
+/* Demosaic registers */
+#define IFE_MOD_V41X_DEMOSAIC_MODULE_CFG 0x60
+#define IFE_MOD_V41X_DEMOSAIC_INTERP_COEFF_CFG_0 0x68
+#define IFE_MOD_V41X_DEMOSAIC_INTERP_COEFF_CFG_1 0x6c
+#define IFE_MOD_V41X_DEMOSAIC_INTERP_CLASSIFIER_CFG_0 0x70
+#define IFE_MOD_V41X_DEMOSAIC_INTERP_CLASSIFIER_CFG_1 0x74
+
+/* Color transform (XFORM) registers, 3 channels */
+#define IFE_MOD_V41X_XFORM_MODULE_CFG 0x60
+#define IFE_MOD_V41X_XFORM_CH0_COEFF_CFG_0 0x68
+#define IFE_MOD_V41X_XFORM_CH0_COEFF_CFG_1 0x6c
+#define IFE_MOD_V41X_XFORM_CH0_ADDOFFSET_CFG 0x70
+#define IFE_MOD_V41X_XFORM_CH0_SUBOFFSET_CFG 0x74
+#define IFE_MOD_V41X_XFORM_CH0_CLAMP0_CFG 0x78
+#define IFE_MOD_V41X_XFORM_CH0_CLAMP1_CFG 0x7c
+#define IFE_MOD_V41X_XFORM_CH1_COEFF_CFG_0 0x80
+#define IFE_MOD_V41X_XFORM_CH1_COEFF_CFG_1 0x84
+#define IFE_MOD_V41X_XFORM_CH1_ADDOFFSET_CFG 0x88
+#define IFE_MOD_V41X_XFORM_CH1_SUBOFFSET_CFG 0x8c
+#define IFE_MOD_V41X_XFORM_CH1_CLAMP0_CFG 0x90
+#define IFE_MOD_V41X_XFORM_CH1_CLAMP1_CFG 0x94
+#define IFE_MOD_V41X_XFORM_CH2_COEFF_CFG_0 0x98
+#define IFE_MOD_V41X_XFORM_CH2_COEFF_CFG_1 0x9c
+#define IFE_MOD_V41X_XFORM_CH2_ADDOFFSET_CFG 0xa0
+#define IFE_MOD_V41X_XFORM_CH2_SUBOFFSET_CFG 0xa4
+#define IFE_MOD_V41X_XFORM_CH2_CLAMP0_CFG 0xa8
+#define IFE_MOD_V41X_XFORM_CH2_CLAMP1_CFG 0xac
+
+/* AWB-BG registers */
+#define IFE_MOD_V41X_AWB_BG_MODULE_CFG 0x60
+#define IFE_MOD_V41X_AWB_BG_HORZ_RGN_CFG_0 0x6c
+#define IFE_MOD_V41X_AWB_BG_HORZ_RGN_CFG_1 0x70
+#define IFE_MOD_V41X_AWB_BG_VERT_RGN_CFG_0 0x74
+#define IFE_MOD_V41X_AWB_BG_VERT_RGN_CFG_1 0x78
+#define IFE_MOD_V41X_AWB_BG_RGN_WIDTH_CFG 0x7c
+#define IFE_MOD_V41X_AWB_BG_HI_THRESHOLD_CFG_0 0x80
+#define IFE_MOD_V41X_AWB_BG_LO_THRESHOLD_CFG_0 0x94
+
+/* WB gain bitfields */
+#define IFE_MOD_V41X_WB_GAIN_CFG_EN BIT(0)
+#define IFE_MOD_V41X_WB_GAIN_VALUE GENMASK(31, 17)
+
+/* Demosaic bitfields */
+#define IFE_MOD_V41X_DEMOSAIC_CFG_EN BIT(0)
+#define IFE_MOD_V41X_DEMOSAIC_CFG_DYN_G_CLAMP_EN BIT(14)
+#define IFE_MOD_V41X_DEMOSAIC_CFG_DYN_RB_CLAMP_EN BIT(15)
+
+/* Color transform bitfields */
+#define IFE_MOD_V41X_XFORM_CFG_EN BIT(0)
+#define IFE_MOD_V41X_XFORM_MATRIX_M0 GENMASK(12, 0)
+#define IFE_MOD_V41X_XFORM_MATRIX_M1 GENMASK(28, 16)
+#define IFE_MOD_V41X_XFORM_OFFSET_VALUE GENMASK(31, 17)
+#define IFE_MOD_V41X_XFORM_CLAMP_VALUE GENMASK(31, 18)
+
+/* AWB-BG bitfields */
+#define IFE_MOD_V41X_AWB_BG_MODULE_CFG_EN BIT(0)
+#define IFE_MOD_V41X_AWB_BG_RGN_SAMPLE_PATTERN GENMASK(31, 16)
+#define IFE_MOD_V41X_AWB_BG_HORZ_CFG0_H_NUM GENMASK(21, 16)
+#define IFE_MOD_V41X_AWB_BG_HORZ_CFG1_WIDTH GENMASK(8, 0)
+#define IFE_MOD_V41X_AWB_BG_VERT_CFG0_V_NUM GENMASK(21, 16)
+#define IFE_MOD_V41X_AWB_BG_VERT_CFG1_HEIGHT GENMASK(8, 0)
+#define IFE_MOD_V41X_AWB_BG_RGN_WIDTH_LAST GENMASK(24, 16)
+#define IFE_MOD_V41X_AWB_BG_RGN_WIDTH_FIRST GENMASK(8, 0)
+#define IFE_MOD_V41X_AWB_BG_HI_THRESHOLD GENMASK(31, 8)
+
+/* WB gain default */
+#define IFE_MOD_V41X_WB_GAIN_UNITY 0x400
+
+/* Demosaic defaults */
+#define IFE_MOD_V41X_DEMOSAIC_LAMBDA_G 0x80
+#define IFE_MOD_V41X_DEMOSAIC_LAMBDA_RB 0
+#define IFE_MOD_V41X_DEMOSAIC_A_K_DEFAULT 0x08000000
+#define IFE_MOD_V41X_DEMOSAIC_W_K_DEFAULT 0x66
+
+/* Color transform defaults */
+#define IFE_MOD_V41X_XFORM_MSB_SHIFT 4
+#define IFE_MOD_V41X_XFORM_CHROMA_OFFSET 0x200
+#define IFE_MOD_V41X_XFORM_CLAMP_MAX 0x3ff
+
+/* AWB-BG defaults */
+#define IFE_MOD_V41X_AWB_BG_MAX_H_NUM 64
+#define IFE_MOD_V41X_AWB_BG_MAX_V_NUM 48
+#define IFE_MOD_V41X_AWB_BG_SAMPLE_PATTERN GENMASK(15, 0)
+#define IFE_MOD_V41X_AWB_BG_THRESHOLD 0x3ffff
+#define IFE_MOD_V41X_AWB_BG_NUM_THRESHOLDS 5
+
+static void mod_v41x_wb_gain_config(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_WB_GAIN_OFFSET;
+ u32 value = FIELD_PREP(IFE_MOD_V41X_WB_GAIN_VALUE,
+ IFE_MOD_V41X_WB_GAIN_UNITY);
+
+ writel_relaxed(value, base + IFE_MOD_V41X_WB_GAIN_CFG_0);
+ writel_relaxed(value, base + IFE_MOD_V41X_WB_GAIN_CFG_1);
+ writel_relaxed(value, base + IFE_MOD_V41X_WB_GAIN_CFG_2);
+}
+
+static void mod_v41x_demosaic_config(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_DEMOSAIC_OFFSET;
+
+ writel_relaxed(IFE_MOD_V41X_DEMOSAIC_LAMBDA_RB,
+ base + IFE_MOD_V41X_DEMOSAIC_INTERP_COEFF_CFG_0);
+ writel_relaxed(IFE_MOD_V41X_DEMOSAIC_LAMBDA_G,
+ base + IFE_MOD_V41X_DEMOSAIC_INTERP_COEFF_CFG_1);
+ writel_relaxed(IFE_MOD_V41X_DEMOSAIC_A_K_DEFAULT,
+ base + IFE_MOD_V41X_DEMOSAIC_INTERP_CLASSIFIER_CFG_0);
+ writel_relaxed(IFE_MOD_V41X_DEMOSAIC_W_K_DEFAULT,
+ base + IFE_MOD_V41X_DEMOSAIC_INTERP_CLASSIFIER_CFG_1);
+}
+
+static void mod_v41x_awb_bg_config(struct ife_pp_ctx *ctx,
+ const struct camss_pp_cfg *cfg)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_AWB_BG_OFFSET;
+ unsigned int i;
+ u32 height = (cfg->height / IFE_MOD_V41X_AWB_BG_MAX_V_NUM) & ~3u;
+ u32 width = (cfg->width / IFE_MOD_V41X_AWB_BG_MAX_H_NUM) & ~3u;
+ u32 value;
+
+ value = readl_relaxed(base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+ value &= ~IFE_MOD_V41X_AWB_BG_RGN_SAMPLE_PATTERN;
+ value |= FIELD_PREP(IFE_MOD_V41X_AWB_BG_RGN_SAMPLE_PATTERN,
+ IFE_MOD_V41X_AWB_BG_SAMPLE_PATTERN);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_HORZ_CFG0_H_NUM,
+ IFE_MOD_V41X_AWB_BG_MAX_H_NUM - 1);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_HORZ_RGN_CFG_0);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_HORZ_CFG1_WIDTH, width - 1);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_HORZ_RGN_CFG_1);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_VERT_CFG0_V_NUM,
+ IFE_MOD_V41X_AWB_BG_MAX_V_NUM - 1);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_VERT_RGN_CFG_0);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_VERT_CFG1_HEIGHT, height - 1);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_VERT_RGN_CFG_1);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_RGN_WIDTH_FIRST, width - 1) |
+ FIELD_PREP(IFE_MOD_V41X_AWB_BG_RGN_WIDTH_LAST, width - 1);
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_RGN_WIDTH_CFG);
+
+ value = FIELD_PREP(IFE_MOD_V41X_AWB_BG_HI_THRESHOLD,
+ IFE_MOD_V41X_AWB_BG_THRESHOLD);
+ for (i = 0; i < IFE_MOD_V41X_AWB_BG_NUM_THRESHOLDS; i++) {
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_HI_THRESHOLD_CFG_0 + i * 4);
+ writel_relaxed(0, base + IFE_MOD_V41X_AWB_BG_LO_THRESHOLD_CFG_0 + i * 4);
+ }
+}
+
+static void mod_v41x_awb_bg_enable(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_AWB_BG_OFFSET;
+ u32 value;
+
+ value = readl_relaxed(base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+ value |= IFE_MOD_V41X_AWB_BG_MODULE_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+}
+
+static void mod_v41x_awb_bg_disable(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_AWB_BG_OFFSET;
+ u32 value;
+
+ value = readl_relaxed(base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+ value &= ~IFE_MOD_V41X_AWB_BG_MODULE_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_AWB_BG_MODULE_CFG);
+}
+
+static void mod_v41x_xform_write_coeff(void __iomem *base, u32 offset,
+ u32 m0, u32 m1)
+{
+ u32 value;
+
+ value = FIELD_PREP(IFE_MOD_V41X_XFORM_MATRIX_M0, m0) |
+ FIELD_PREP(IFE_MOD_V41X_XFORM_MATRIX_M1, m1);
+ writel_relaxed(value, base + offset);
+}
+
+static void mod_v41x_xform_write_channel(void __iomem *base, u32 coeff0,
+ u32 coeff1, u32 add_offset,
+ u32 sub_offset, u32 clamp0,
+ u32 clamp1, u32 m0, u32 m1,
+ u32 m2, u32 offset)
+{
+ mod_v41x_xform_write_coeff(base, coeff0, m0, m1);
+ writel_relaxed(FIELD_PREP(IFE_MOD_V41X_XFORM_MATRIX_M0, m2), base + coeff1);
+ writel_relaxed(FIELD_PREP(IFE_MOD_V41X_XFORM_OFFSET_VALUE,
+ offset << IFE_MOD_V41X_XFORM_MSB_SHIFT),
+ base + add_offset);
+ writel_relaxed(0, base + sub_offset);
+ writel_relaxed(FIELD_PREP(IFE_MOD_V41X_XFORM_CLAMP_VALUE, 0), base + clamp0);
+ writel_relaxed(FIELD_PREP(IFE_MOD_V41X_XFORM_CLAMP_VALUE,
+ IFE_MOD_V41X_XFORM_CLAMP_MAX <<
+ IFE_MOD_V41X_XFORM_MSB_SHIFT),
+ base + clamp1);
+}
+
+static void mod_v41x_modules_acquire(struct ife_device *ife, struct ife_pp_ctx *ctx,
+ void __iomem *base)
+{
+ ctx->base = base;
+}
+
+static void mod_v41x_modules_release(struct ife_pp_ctx *ctx)
+{
+ ctx->base = NULL;
+}
+
+static void mod_v41x_modules_config(struct ife_pp_ctx *ctx,
+ const struct camss_pp_cfg *cfg)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_XFORM_OFFSET;
+
+ ctx->in_width = cfg->width;
+ ctx->in_height = cfg->height;
+
+ mod_v41x_wb_gain_config(ctx);
+ mod_v41x_demosaic_config(ctx);
+ mod_v41x_awb_bg_config(ctx, cfg);
+
+ mod_v41x_xform_write_channel(base, IFE_MOD_V41X_XFORM_CH0_COEFF_CFG_0,
+ IFE_MOD_V41X_XFORM_CH0_COEFF_CFG_1,
+ IFE_MOD_V41X_XFORM_CH0_ADDOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH0_SUBOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH0_CLAMP0_CFG,
+ IFE_MOD_V41X_XFORM_CH0_CLAMP1_CFG,
+ 0x259, 0x75, 0x132, 0);
+ mod_v41x_xform_write_channel(base, IFE_MOD_V41X_XFORM_CH1_COEFF_CFG_0,
+ IFE_MOD_V41X_XFORM_CH1_COEFF_CFG_1,
+ IFE_MOD_V41X_XFORM_CH1_ADDOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH1_SUBOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH1_CLAMP0_CFG,
+ IFE_MOD_V41X_XFORM_CH1_CLAMP1_CFG,
+ 0x1eae, 0x1fe, 0x1f54,
+ IFE_MOD_V41X_XFORM_CHROMA_OFFSET);
+ mod_v41x_xform_write_channel(base, IFE_MOD_V41X_XFORM_CH2_COEFF_CFG_0,
+ IFE_MOD_V41X_XFORM_CH2_COEFF_CFG_1,
+ IFE_MOD_V41X_XFORM_CH2_ADDOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH2_SUBOFFSET_CFG,
+ IFE_MOD_V41X_XFORM_CH2_CLAMP0_CFG,
+ IFE_MOD_V41X_XFORM_CH2_CLAMP1_CFG,
+ 0x1e55, 0x1fad, 0x1fe,
+ IFE_MOD_V41X_XFORM_CHROMA_OFFSET);
+}
+
+static void mod_v41x_modules_enable(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_WB_GAIN_OFFSET;
+ u32 value;
+
+ value = readl_relaxed(base + IFE_MOD_V41X_WB_GAIN_MODULE_CFG);
+ value |= IFE_MOD_V41X_WB_GAIN_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_WB_GAIN_MODULE_CFG);
+
+ base = ctx->base + IFE_MOD_V41X_DEMOSAIC_OFFSET;
+ value = readl_relaxed(base + IFE_MOD_V41X_DEMOSAIC_MODULE_CFG);
+ value |= IFE_MOD_V41X_DEMOSAIC_CFG_DYN_G_CLAMP_EN |
+ IFE_MOD_V41X_DEMOSAIC_CFG_DYN_RB_CLAMP_EN |
+ IFE_MOD_V41X_DEMOSAIC_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_DEMOSAIC_MODULE_CFG);
+
+ base = ctx->base + IFE_MOD_V41X_XFORM_OFFSET;
+ value = readl_relaxed(base + IFE_MOD_V41X_XFORM_MODULE_CFG);
+ value |= IFE_MOD_V41X_XFORM_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_XFORM_MODULE_CFG);
+
+ mod_v41x_awb_bg_enable(ctx);
+}
+
+static void mod_v41x_modules_disable(struct ife_pp_ctx *ctx)
+{
+ void __iomem *base = ctx->base + IFE_MOD_V41X_WB_GAIN_OFFSET;
+ u32 value;
+
+ mod_v41x_awb_bg_disable(ctx);
+
+ value = readl_relaxed(base + IFE_MOD_V41X_WB_GAIN_MODULE_CFG);
+ value &= ~IFE_MOD_V41X_WB_GAIN_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_WB_GAIN_MODULE_CFG);
+
+ base = ctx->base + IFE_MOD_V41X_DEMOSAIC_OFFSET;
+ value = readl_relaxed(base + IFE_MOD_V41X_DEMOSAIC_MODULE_CFG);
+ value &= ~(IFE_MOD_V41X_DEMOSAIC_CFG_DYN_G_CLAMP_EN |
+ IFE_MOD_V41X_DEMOSAIC_CFG_DYN_RB_CLAMP_EN |
+ IFE_MOD_V41X_DEMOSAIC_CFG_EN);
+ writel_relaxed(value, base + IFE_MOD_V41X_DEMOSAIC_MODULE_CFG);
+
+ base = ctx->base + IFE_MOD_V41X_XFORM_OFFSET;
+ value = readl_relaxed(base + IFE_MOD_V41X_XFORM_MODULE_CFG);
+ value &= ~IFE_MOD_V41X_XFORM_CFG_EN;
+ writel_relaxed(value, base + IFE_MOD_V41X_XFORM_MODULE_CFG);
+}
+
+const struct camss_mod_ops ife_mod_v41x_ops = {
+ .acquire = mod_v41x_modules_acquire,
+ .release = mod_v41x_modules_release,
+ .config = mod_v41x_modules_config,
+ .enable = mod_v41x_modules_enable,
+ .disable = mod_v41x_modules_disable,
+};
diff --git a/drivers/media/platform/qcom/camss/camss-ife.h b/drivers/media/platform/qcom/camss/camss-ife.h
index 3d35c17fca7f..c38b96076a1f 100644
--- a/drivers/media/platform/qcom/camss/camss-ife.h
+++ b/drivers/media/platform/qcom/camss/camss-ife.h
@@ -403,6 +403,7 @@ 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;
+extern const struct camss_mod_ops ife_mod_v41x_ops;

extern const struct ife_pads_descriptors ife_lite_pads_v20x;
extern const struct ife_pads_descriptors ife_lite_pads_v30x;
--
2.34.1