[PATCH v9 09/13] media: rppx1: bls: Add support for black level compensation

From: Niklas Söderlund

Date: Sat May 16 2026 - 17:18:29 EST


Extend the RPPX1 driver to allow setting the black level measurement
and gain configuration. It uses the RPPX1 framework for parameters and
its writer abstraction to allow the user to control how, and when,
configuration is applied to the RPPX1.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@xxxxxxxxxxxx>
Co-developed-by: Jai Luthra <jai.luthra+renesas@xxxxxxxxxxxxxxxx>
Signed-off-by: Jai Luthra <jai.luthra+renesas@xxxxxxxxxxxxxxxx>
Co-developed-by: Jacopo Mondi <jacopo.mondi+renesas@xxxxxxxxxxxxxxxx>
Signed-off-by: Jacopo Mondi <jacopo.mondi+renesas@xxxxxxxxxxxxxxxx>
---
.../platform/dreamchip/rppx1/rpp_module.h | 1 +
.../platform/dreamchip/rppx1/rpp_params.c | 5 +
.../platform/dreamchip/rppx1/rppx1_bls.c | 97 ++++++++++++++++
.../uapi/linux/media/dreamchip/rppx1-config.h | 105 +++++++++++++++++-
4 files changed, 207 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h
index b134d140fe22..5e20fcdcbcc4 100644
--- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h
+++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h
@@ -47,6 +47,7 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value);

union rppx1_params_block {
struct v4l2_isp_block_header header;
+ struct rppx1_bls_params bls;
struct rppx1_awbg_params awbg;
struct rppx1_hist_params hist;
struct rppx1_exm_params exm;
diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c
index 975ce3a42fb5..831cf7ca154c 100644
--- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c
+++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c
@@ -17,6 +17,8 @@

static const struct v4l2_isp_params_block_type_info
rppx1_ext_params_blocks_info[] = {
+ RPPX1_PARAMS_BLOCK_INFO(BLS_PRE1, bls),
+ RPPX1_PARAMS_BLOCK_INFO(BLS_PRE2, bls),
RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg),
RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg),
RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist),
@@ -58,6 +60,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size,
block_offset += block->header.size;

switch (block->header.type) {
+ case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1:
+ module = &rpp->pre1.bls;
+ break;
case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1:
module = &rpp->pre1.awbg;
break;
diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c
index 882a9a819229..01a61db279bf 100644
--- a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c
+++ b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c
@@ -6,6 +6,7 @@
*/

#include "rpp_module.h"
+#include "rppx1.h"

#define BLS_VERSION_REG 0x0000

@@ -32,6 +33,8 @@
#define BLS_B_MEASURED_REG 0x0040
#define BLS_C_MEASURED_REG 0x0044
#define BLS_D_MEASURED_REG 0x0048
+#define BLS_PRE1_FIXED_MASK GENMASK(24, 0)
+#define BLS_PRE2_FIXED_MASK GENMASK(12, 0)

static int rppx1_bls_probe(struct rpp_module *mod)
{
@@ -55,6 +58,100 @@ static int rppx1_bls_probe(struct rpp_module *mod)
return 0;
}

+static void
+rppx1_bls_swap_regs(struct rpp_module *mod, const u32 input[4], u32 output[4])
+{
+ static const unsigned int swap[4][4] = {
+ [RPP_RGGB] = { 0, 1, 2, 3 },
+ [RPP_GRBG] = { 1, 0, 3, 2 },
+ [RPP_GBRG] = { 2, 3, 0, 1 },
+ [RPP_BGGR] = { 3, 2, 1, 0 },
+ };
+
+ /* Swap to pattern used in our path, PRE1 or PRE2. */
+ struct rpp_module *acq = mod == &mod->rpp->pre1.bls ?
+ &mod->rpp->pre1.acq : &mod->rpp->pre2.bls;
+ enum rpp_raw_pattern pattern = acq->info.acq.raw_pattern;
+
+ for (unsigned int i = 0; i < 4; ++i)
+ output[i] = input[swap[pattern][i]];
+}
+
+static int
+rppx1_bls_fill_params(struct rpp_module *mod,
+ const union rppx1_params_block *block,
+ rppx1_reg_write write, void *priv)
+{
+ const struct rppx1_bls_params *cfg = &block->bls;
+
+ /* If the modules is disabled, simply bypass it. */
+ if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+ write(priv, mod->base + BLS_CTRL_REG, 0);
+ return 0;
+ }
+
+ u32 ctrl = BLS_CTRL_BLS_EN;
+
+ if (cfg->mode == RPPX1_BLS_MODE_FIXED) {
+ static const u32 regs[] = {
+ BLS_A_FIXED_REG,
+ BLS_B_FIXED_REG,
+ BLS_C_FIXED_REG,
+ BLS_D_FIXED_REG,
+ };
+ u32 swapped[4];
+
+ rppx1_bls_swap_regs(mod, regs, swapped);
+
+ /*
+ * The PRE1 pipe fixed values are 24-bits + 1 sign bit, while
+ * the PRE2 pipe values are 12-bits + 1 sign bit.
+ */
+ u32 mask;
+
+ switch (cfg->header.type) {
+ case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1:
+ mask = BLS_PRE1_FIXED_MASK;
+ break;
+ case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2:
+ mask = BLS_PRE2_FIXED_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ write(priv, mod->base + swapped[0], cfg->fixed.a & mask);
+ write(priv, mod->base + swapped[1], cfg->fixed.b & mask);
+ write(priv, mod->base + swapped[2], cfg->fixed.c & mask);
+ write(priv, mod->base + swapped[3], cfg->fixed.d & mask);
+ } else {
+ write(priv, mod->base + BLS_SAMPLES_REG, cfg->samples);
+
+ if (cfg->en_windows & RPPX1_BLS_WIN_EN_WIN1) {
+ write(priv, mod->base + BLS_H1_START_REG, cfg->window1.h_offs);
+ write(priv, mod->base + BLS_H1_STOP_REG, cfg->window1.h_size);
+ write(priv, mod->base + BLS_V1_START_REG, cfg->window1.v_offs);
+ write(priv, mod->base + BLS_V1_STOP_REG, cfg->window1.v_size);
+ ctrl |= BLS_CTRL_BLS_WIN1;
+ }
+
+ if (cfg->en_windows & RPPX1_BLS_WIN_EN_WIN2) {
+ write(priv, mod->base + BLS_H2_START_REG, cfg->window2.h_offs);
+ write(priv, mod->base + BLS_H2_STOP_REG, cfg->window2.h_size);
+ write(priv, mod->base + BLS_V2_START_REG, cfg->window2.v_offs);
+ write(priv, mod->base + BLS_V2_STOP_REG, cfg->window2.v_size);
+ ctrl |= BLS_CTRL_BLS_WIN2;
+ }
+
+ ctrl |= BLS_CTRL_BLS_MODE_MEASURED;
+ }
+
+ write(priv, mod->base + BLS_CTRL_REG, ctrl);
+
+ return 0;
+}
+
const struct rpp_module_ops rppx1_bls_ops = {
.probe = rppx1_bls_probe,
+ .fill_params = rppx1_bls_fill_params,
};
diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h
index 50aba160c6cd..dbbd6223f33c 100644
--- a/include/uapi/linux/media/dreamchip/rppx1-config.h
+++ b/include/uapi/linux/media/dreamchip/rppx1-config.h
@@ -86,6 +86,8 @@ enum rppx1_meas_chan {
* @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1: PRE1 pipe Histogram Measurement
* @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2: PRE2 pipe Histogram Measurement
* @RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: POST pipe Histogram Measurement
+ * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: PRE1 pipe Black Level Subtraction
+ * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: PRE2 pipe Black Level Subtraction
*/
enum rppx1_params_block_type {
RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST,
@@ -97,6 +99,8 @@ enum rppx1_params_block_type {
RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1,
RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2,
RPPX1_PARAMS_BLOCK_TYPE_HIST_POST,
+ RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1,
+ RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2,
};

/**
@@ -321,6 +325,103 @@ struct rppx1_hist_params {
__u8 sample_shift;
};

+/**
+ * struct rppx1_bls_fixed - BLS fixed subtraction values
+ *
+ * Fixed black level values subtracted from sensor data per Bayer channel.
+ * Negative values result in addition.
+ *
+ * The PRE1 pipe BLS module operates on a 24-bits input data and fixed black
+ * levels are stored as a signed 2's complement representation ranging from
+ * -2^24 to 2^24-1.
+ *
+ * The PRE2 pipe BLS module operates on a 12-bits input data and fixed black
+ * levels are stored as a signed 2's complement representation ranging from
+ * -2^12 to 2^12-1.
+ *
+ * Userspace is expected to provide fixed black level values with a bit-depth
+ * matching the one of pipe in use.
+ *
+ * These subtraction values are matched with the sensor native Bayer components
+ * ordering according to the cropping configuration on the input port.
+ *
+ * @a: subtraction value for channel A
+ * @b: subtraction value for channel B
+ * @c: subtraction value for channel C
+ * @d: subtraction value for channel D
+ */
+struct rppx1_bls_fixed {
+ __u32 a;
+ __u32 b;
+ __u32 c;
+ __u32 d;
+};
+
+/**
+ * enum rppx1_bls_mode - BLS subtraction mode
+ *
+ * Select if subtracted black level come from fixed or measured values.
+ *
+ * @RPPX1_BLS_MODE_FIXED: subtract fixed values
+ * @RPPX1_BLS_MODE_MEAS: subtract measured values
+ */
+enum rppx1_bls_mode {
+ RPPX1_BLS_MODE_FIXED,
+ RPPX1_BLS_MODE_MEAS,
+};
+
+/**
+ * enum rppx1_bls_win_en: BLS measurement configuration
+ *
+ * Select the measurement window to use for measured black level values.
+ *
+ * @RPPX1_BLS_WIN_EN_OFF: disable measurement
+ * @RPPX1_BLS_WIN_EN_WIN1: Enable measurement from window 1
+ * @RPPX1_BLS_WIN_EN_WIN2: enable measurement from window 2
+ * @RPPX1_BLS_WIN_EN_WIN12: enable measurement from window 1 and window 2
+ */
+enum rppx1_bls_win_en {
+ RPPX1_BLS_WIN_EN_OFF,
+ RPPX1_BLS_WIN_EN_WIN1,
+ RPPX1_BLS_WIN_EN_WIN2,
+ RPPX1_BLS_WIN_EN_WIN12,
+};
+
+/**
+ * struct rppx1_bls_params - RPP-X1 Black Level Subtraction Module
+ *
+ * The RPP-X1 Black Level Subtraction module is available on the PRE1 and PRE2
+ * pre-fusion pipes. Userspace selects which pipe to operate by setting the
+ * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or
+ * RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2.
+ *
+ * The BLS module operates on fixed or measured data according to the setting of
+ * the @mode field. When RPPX1_BLS_MODE_FIXED is used userspace shall provide
+ * the per-channel black levels in @fixed. When RPPX1_BLS_MODE_MEAS is used
+ * userspace shall configure the measurement windows @window1 and optionally
+ * @window2 to select the optically black pixels region in the input frame. The
+ * @samples fields controls how many measure samples are used for averaging the
+ * measured black levels.
+ *
+ * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or
+ * type == RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2)
+ * @mode: BLS subtraction mode (see enum rppx1_bls_mode)
+ * @en_windows: BLS measurement mode (see rppx1_bls_win_en)
+ * @samples: log2 of the number of measured pixels per Bayer position
+ * @window1: BLS measurement window 1 (14 bits)
+ * @window2: BLS measurement window 2 (14 bits)
+ * @fixed: fixed subtraction values (see enum rppx1_bls_fixed)
+ */
+struct rppx1_bls_params {
+ struct v4l2_isp_params_block_header header;
+ __u8 mode;
+ __u8 en_windows;
+ __u8 samples;
+ struct rppx1_window window1;
+ struct rppx1_window window2;
+ struct rppx1_bls_fixed fixed;
+};
+
/**
* RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks
*
@@ -336,7 +437,9 @@ struct rppx1_hist_params {
sizeof(struct rppx1_exm_params) + \
sizeof(struct rppx1_hist_params) + \
sizeof(struct rppx1_hist_params) + \
- sizeof(struct rppx1_hist_params))
+ sizeof(struct rppx1_hist_params) + \
+ sizeof(struct rppx1_bls_params) + \
+ sizeof(struct rppx1_bls_params))

/* ---------------------------------------------------------------------------
* Statistics Structures
--
2.54.0