[PATCH v7 09/18] media: rppx1: Add support for Histogram Measurement
From: Jai Luthra
Date: Fri Apr 10 2026 - 05:15:27 EST
From: Niklas Söderlund <niklas.soderlund+renesas@xxxxxxxxxxxx>
Extend the RPPX1 driver to allow setting the Histogram configuration
using the parameter buffer format. 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>
Signed-off-by: Jai Luthra <jai.luthra+renesas@xxxxxxxxxxxxxxxx>
---
.../media/platform/dreamchip/rppx1/rpp_params.c | 4 +
drivers/media/platform/dreamchip/rppx1/rpp_stats.c | 4 +
.../media/platform/dreamchip/rppx1/rppx1_hist.c | 172 +++++++++++++++++++++
3 files changed, 180 insertions(+)
diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c
index 928e4fa196c0e4c0db6a23a89833359645a0941e..788d26f619d1a96d16e9dc499d2763366f70be0a 100644
--- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c
+++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c
@@ -18,6 +18,7 @@ static const struct v4l2_isp_params_block_type_info
rppx1_ext_params_blocks_info[] = {
RPPX1_PARAMS_BLOCK_INFO(AWB_GAIN, awb_gain),
RPPX1_PARAMS_BLOCK_INFO(AWB_MEAS, awb_meas),
+ RPPX1_PARAMS_BLOCK_INFO(HST_MEAS, hst),
RPPX1_PARAMS_BLOCK_INFO(AEC_MEAS, aec),
};
@@ -59,6 +60,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size,
case RPPX1_PARAMS_BLOCK_TYPE_AWB_MEAS:
module = &rpp->post.wbmeas;
break;
+ case RPPX1_PARAMS_BLOCK_TYPE_HST_MEAS:
+ module = &rpp->post.hist;
+ break;
case RPPX1_PARAMS_BLOCK_TYPE_AEC_MEAS:
module = &rpp->pre1.exm;
break;
diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c
index 44edcbc7f2b644d7d569d46707b0b4b94ebe4144..6f3be95026f60252d05370421bb2ec6ea090ce6f 100644
--- a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c
+++ b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c
@@ -16,6 +16,10 @@ void rppx1_stats_fill_isr(struct rppx1 *rpp, u32 isc, void *buf)
if (!rpp_module_call(&rpp->post.wbmeas, fill_stats, &stats->params))
stats->meas_type |= RPPX1_STAT_AWB;
+ if (isc & RPPX1_IRQ_ID_POST_HIST_MEAS)
+ if (!rpp_module_call(&rpp->post.hist, fill_stats, &stats->params))
+ stats->meas_type |= RPPX1_STAT_HIST;
+
if (isc & RPPX1_IRQ_ID_PRE1_EXM)
if (!rpp_module_call(&rpp->pre1.exm, fill_stats, &stats->params))
stats->meas_type |= RPPX1_STAT_AUTOEXP;
diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c b/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c
index cab498ece5a8fac01e9e6c048b786f2aed829dd2..6287f9c401dfc8ee7f1c19acef1a3e82c5036ac7 100644
--- a/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c
+++ b/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c
@@ -25,6 +25,9 @@
#define HIST_LAST_MEAS_LINE_REG 0x0010
#define HIST_SUBSAMPLING_REG 0x0014
+#define HIST_SUBSAMPLING_V_STEPSIZE(x) (((x) & 0x7f) << 24)
+#define HIST_SUBSAMPLING_H_STEP_INC(x) (((x) & 0x1ffff))
+
#define HIST_COEFF_R_REG 0x0018
#define HIST_COEFF_G_REG 0x001c
#define HIST_COEFF_B_REG 0x0020
@@ -71,6 +74,175 @@ static int rppx1_hist_probe(struct rpp_module *mod)
return 0;
}
+#define RPPX1_HIST_WEIGHT(v0, v1, v2, v3) \
+ (((v0) & 0x1f) | (((v1) & 0x1f) << 8) | \
+ (((v2) & 0x1f) << 16) | \
+ (((v3) & 0x1f) << 24))
+
+static int rppx1_hist_fill_params(struct rpp_module *mod,
+ const union rppx1_params_block *block,
+ rppx1_reg_write write, void *priv)
+{
+ const struct rppx1_params_hst_config *cfg = &block->hst;
+ u32 h_offs, v_offs, h_size, v_size;
+ u8 mode, coeff[3];
+
+ /* If the modules is disabled, simply bypass it. */
+ if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) {
+ write(priv, mod->base + HIST_MODE_REG,
+ HIST_MODE_HIST_MODE_DISABLE);
+ return 0;
+ }
+
+ /* Sample after demosaicing. */
+ write(priv, mod->base + HIST_CHANNEL_SEL_REG, 7);
+
+ /*
+ * The RkISP1 histogram_predivider setting controls the pixel spacing
+ * between each sample. On RPPX1 there is greater control as both line
+ * and pixel spacing can be controlled. The RkISP1 stepsize register is
+ * documented as.
+ *
+ * 0, 1, 3: not allowed
+ * 3: process every third input pixel
+ * 4: process every fourth input pixel
+ * 127: process every 127th pixel
+ *
+ * The output bins are 16 bit (FP16.4) so to not overflow a divider
+ * calculated as would be needed.
+ *
+ * count = mode == RGB_COMBINED ? 3 : 1
+ * factor = vsize * hsize * count / 65536
+ *
+ * However the libcamera user of the RkISP documents the setting as
+ * applying to both h and v direction at the same time and calculates
+ * the divider as,
+ *
+ * count = mode == RGB_COMBINED ? 3 : 1
+ * factor = ceil(sqrt(vsize * hsize * count / 65536))
+ *
+ * Real world usage is better then bad documentation, do the same here
+ * and apply the divider in both directions.
+ *
+ * The RPPX1 h-stepping is also configured differently. Internally
+ * there is a 16-bit counter and for each input pixel h_step_inc is
+ * added to it. Every time it overflows the input pixel is sampled.
+ *
+ * h_step_inc = 2**16 => sample every pixel
+ * h_step_inc = 2**15 => sample every other pixel
+ *
+ * Gives us the conversion to RkISP1 parameters of.
+ *
+ * h_step_inc = 65536 / divider
+ */
+ write(priv, mod->base + HIST_SUBSAMPLING_REG,
+ HIST_SUBSAMPLING_V_STEPSIZE(cfg->histogram_predivider) |
+ HIST_SUBSAMPLING_H_STEP_INC(0x10000 / cfg->histogram_predivider));
+
+ /*
+ * Adjust and set measurement window to hardware limitations,
+ * - Offsets must be even.
+ * - Width and height must be divisible by 10.
+ */
+ h_offs = cfg->meas_window.h_offs & 0x1ffe;
+ v_offs = cfg->meas_window.v_offs & 0x1ffe;
+ h_size = cfg->meas_window.h_size - cfg->meas_window.h_size % 10;
+ v_size = cfg->meas_window.v_size - cfg->meas_window.v_size % 10;
+
+ write(priv, mod->base + HIST_H_OFFS_REG, h_offs);
+ write(priv, mod->base + HIST_V_OFFS_REG, v_offs);
+ write(priv, mod->base + HIST_H_SIZE_REG, h_size / 5);
+ write(priv, mod->base + HIST_V_SIZE_REG, v_size / 5);
+
+ /* Set last measurement line for ready interrupt. */
+ write(priv, mod->base + HIST_LAST_MEAS_LINE_REG,
+ v_offs + v_size + 1);
+
+ /* NOTE: Keep the default full sample range. */
+
+ /* Set measurement window weights. */
+ write(priv, mod->base + HIST_WEIGHT_00TO30_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[0], cfg->hist_weight[1],
+ cfg->hist_weight[2], cfg->hist_weight[3]));
+ write(priv, mod->base + HIST_WEIGHT_40TO21_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[4], cfg->hist_weight[5],
+ cfg->hist_weight[6], cfg->hist_weight[7]));
+ write(priv, mod->base + HIST_WEIGHT_31TO12_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[8], cfg->hist_weight[9],
+ cfg->hist_weight[10], cfg->hist_weight[11]));
+ write(priv, mod->base + HIST_WEIGHT_22TO03_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[12], cfg->hist_weight[13],
+ cfg->hist_weight[14], cfg->hist_weight[15]));
+ write(priv, mod->base + HIST_WEIGHT_13TO43_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[16], cfg->hist_weight[17],
+ cfg->hist_weight[18], cfg->hist_weight[19]));
+ write(priv, mod->base + HIST_WEIGHT_04TO34_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[20], cfg->hist_weight[21],
+ cfg->hist_weight[22], cfg->hist_weight[23]));
+ write(priv, mod->base + HIST_WEIGHT_44_REG,
+ RPPX1_HIST_WEIGHT(cfg->hist_weight[24], 0, 0, 0));
+
+ /* Translate RkISP1 modes. */
+ mode = HIST_MODE_HIST_MODE_YRGB;
+ switch (cfg->mode) {
+ case RPPX1_HISTOGRAM_MODE_RGB_COMBINED:
+ /* L = R + G + B */
+ coeff[0] = 0x80;
+ coeff[1] = 0x80;
+ coeff[2] = 0x80;
+ break;
+ case RPPX1_HISTOGRAM_MODE_R_HISTOGRAM:
+ /* L = R */
+ coeff[0] = 0x80;
+ coeff[1] = 0x00;
+ coeff[2] = 0x00;
+ break;
+ case RPPX1_HISTOGRAM_MODE_G_HISTOGRAM:
+ /* L = G */
+ coeff[0] = 0x00;
+ coeff[1] = 0x80;
+ coeff[2] = 0x00;
+ break;
+ case RPPX1_HISTOGRAM_MODE_B_HISTOGRAM:
+ coeff[0] = 0x00;
+ coeff[1] = 0x00;
+ coeff[2] = 0x80;
+ break;
+ case RPPX1_HISTOGRAM_MODE_Y_HISTOGRAM:
+ /* Coefficients for a BT.601 (from datasheet). */
+ coeff[0] = 38;
+ coeff[1] = 75;
+ coeff[2] = 15;
+ break;
+ default:
+ mode = HIST_MODE_HIST_MODE_DISABLE;
+ coeff[0] = 0x00;
+ coeff[1] = 0x00;
+ coeff[2] = 0x00;
+ break;
+ }
+
+ write(priv, mod->base + HIST_MODE_REG, mode);
+ write(priv, mod->base + HIST_COEFF_R_REG, coeff[0]);
+ write(priv, mod->base + HIST_COEFF_G_REG, coeff[1]);
+ write(priv, mod->base + HIST_COEFF_B_REG, coeff[2]);
+
+ write(priv, mod->base + HIST_FORCED_UPDATE_REG, 1);
+
+ return 0;
+}
+
+static int rppx1_hist_fill_stats(struct rpp_module *mod,
+ struct rppx1_stat *stats)
+{
+ for (unsigned int i = 0; i < HIST_BIN_REG_NUM; i++)
+ stats->hist.hist_bins[i] = rpp_module_read(mod, HIST_BIN_REG(i)) & 0xfffff;
+
+ return 0;
+}
+
const struct rpp_module_ops rppx1_hist_ops = {
.probe = rppx1_hist_probe,
+ .fill_params = rppx1_hist_fill_params,
+ .fill_stats = rppx1_hist_fill_stats,
};
--
2.53.0