From: Balakrishnan Sambath <balakrishnan.s@microchip.com>
To: <linux-media@vger.kernel.org>
Cc: <eugen.hristev@linaro.org>, <mchehab@kernel.org>,
<hverkuil@kernel.org>, <nicolas.ferre@microchip.com>,
<linux-kernel@vger.kernel.org>
Subject: [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls
Date: Tue, 12 May 2026 21:13:35 +0530 [thread overview]
Message-ID: <20260512154339.210444-12-balakrishnan.s@microchip.com> (raw)
In-Reply-To: <20260512154339.210444-1-balakrishnan.s@microchip.com>
Add 64-entry gamma LUT controls for R/G/B channels. Setting any LUT
overrides V4L2_CID_GAMMA; writing V4L2_CID_GAMMA restores presets.
Supports SAMA7G5 bipartite encoding.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 148 +++++++++++++++++-
.../media/platform/microchip/microchip-isc.h | 25 ++-
.../microchip/microchip-sama7g5-isc.c | 1 +
include/linux/atmel-isc-media.h | 18 +++
4 files changed, 184 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index f78145820e40..3749f473c3c6 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -12,6 +12,7 @@
#include <linux/interrupt.h>
#include <linux/math64.h>
#include <linux/module.h>
+#include <linux/string.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
@@ -250,12 +251,83 @@ static void isc_start_dma(struct isc_device *isc)
spin_unlock(&isc->awb_lock);
}
-static void isc_set_pipeline(struct isc_device *isc, u32 pipeline)
+/**
+ * isc_lut_to_hw() - Convert a 64-entry 10-bit LUT to ISC register format
+ * @lut: 64-element array of 10-bit output values (0-1023); element i
+ * is the desired output for input range [i*16 .. (i+1)*16 - 1].
+ * @hw: 64-element output array to receive the packed hardware values.
+ * @bipartite: true for SAMA7G5 (ISC_GAM_CTRL_BIPART active): delta is stored
+ * as a Q9 per-step increment (multiply by 32 = 512/16).
+ * false for SAMA5D2: delta is a plain per-segment increment.
+ *
+ * Each hardware register word packs two fields:
+ * bits[31:16] = 10-bit output value at the start of segment i
+ * bits[15:0] = interpolation delta
+ *
+ * In bipartite mode the hardware uses the delta to linearly interpolate
+ * across all 16 input steps within the segment (Q9 fixed-point: delta/512
+ * per step). In non-bipartite mode the hardware applies a constant output
+ * across the whole segment.
+ */
+static void isc_lut_to_hw(const u32 *lut, u32 *hw, bool bipartite)
+{
+ unsigned int i;
+ u32 cur, next, delta;
+
+ for (i = 0; i < GAMMA_ENTRIES; i++) {
+ cur = lut[i];
+ next = (i < GAMMA_ENTRIES - 1) ? lut[i + 1] : 1023;
+
+ /*
+ * Bipartite (SAMA7G5): delta = per-step increment in Q9
+ * = (next - cur) * 512 / 16 = (next - cur) * 32
+ * Non-bipartite (SAMA5D2): delta = per-segment increment
+ * = (next - cur)
+ */
+ delta = bipartite ? (next - cur) * 32 : (next - cur);
+
+ hw[i] = (cur << 16) | (delta & 0xffff);
+ }
+}
+
+/**
+ * isc_apply_gamma() - Write gamma LUT registers from current ctrls state
+ * @isc: ISC device
+ *
+ * Converts ctrls->gamma_lut_{b,g,r}[] to hardware format via isc_lut_to_hw()
+ * and writes all three ISC_GAM_*ENTRY register banks. Falls back to the
+ * preset gamma_table when gamma_lut_override is false.
+ *
+ * Must be called before isc_update_profile() whenever the gamma curve changes
+ * mid-stream, since isc_update_profile() only commits whatever is already in
+ * the registers to the active pipeline shadow.
+ */
+static void isc_apply_gamma(struct isc_device *isc)
{
struct regmap *regmap = isc->regmap;
struct isc_ctrls *ctrls = &isc->ctrls;
- u32 val, bay_cfg;
const u32 *gamma;
+ u32 hw[GAMMA_ENTRIES];
+
+ if (ctrls->gamma_lut_override) {
+ isc_lut_to_hw(ctrls->gamma_lut_b, hw, isc->gamma_bipartite);
+ regmap_bulk_write(regmap, ISC_GAM_BENTRY, hw, GAMMA_ENTRIES);
+ isc_lut_to_hw(ctrls->gamma_lut_g, hw, isc->gamma_bipartite);
+ regmap_bulk_write(regmap, ISC_GAM_GENTRY, hw, GAMMA_ENTRIES);
+ isc_lut_to_hw(ctrls->gamma_lut_r, hw, isc->gamma_bipartite);
+ regmap_bulk_write(regmap, ISC_GAM_RENTRY, hw, GAMMA_ENTRIES);
+ } else {
+ gamma = &isc->gamma_table[ctrls->gamma_index][0];
+ regmap_bulk_write(regmap, ISC_GAM_BENTRY, gamma, GAMMA_ENTRIES);
+ regmap_bulk_write(regmap, ISC_GAM_GENTRY, gamma, GAMMA_ENTRIES);
+ regmap_bulk_write(regmap, ISC_GAM_RENTRY, gamma, GAMMA_ENTRIES);
+ }
+}
+
+static void isc_set_pipeline(struct isc_device *isc, u32 pipeline)
+{
+ struct regmap *regmap = isc->regmap;
+ u32 val, bay_cfg;
unsigned int i;
/* WB-->CFA-->CC-->GAM-->CSC-->CBC-->SUB422-->SUB420 */
@@ -275,10 +347,7 @@ static void isc_set_pipeline(struct isc_device *isc, u32 pipeline)
regmap_write(regmap, ISC_CFA_CFG, bay_cfg | ISC_CFA_CFG_EITPOL);
- gamma = &isc->gamma_table[ctrls->gamma_index][0];
- regmap_bulk_write(regmap, ISC_GAM_BENTRY, gamma, GAMMA_ENTRIES);
- regmap_bulk_write(regmap, ISC_GAM_GENTRY, gamma, GAMMA_ENTRIES);
- regmap_bulk_write(regmap, ISC_GAM_RENTRY, gamma, GAMMA_ENTRIES);
+ isc_apply_gamma(isc);
isc->config_dpc(isc);
isc->config_csc(isc);
@@ -1544,11 +1613,35 @@ static void isc_awb_work(struct work_struct *w)
pm_runtime_put_sync(isc->dev);
}
+/*
+ * isc_update_gamma_lut_override() - Evaluate gamma LUT override flag
+ *
+ * Activates the per-channel LUT override only when at least one entry
+ * across any channel is non-zero. An all-zero write (including the
+ * default initialisation from v4l2_ctrl_handler_setup) disables the
+ * override so the built-in gamma table remains active.
+ */
+static void isc_update_gamma_lut_override(struct isc_ctrls *ctrls)
+{
+ unsigned int i;
+ bool any_nonzero = false;
+
+ for (i = 0; i < GAMMA_ENTRIES && !any_nonzero; i++) {
+ if (ctrls->gamma_lut_b[i] ||
+ ctrls->gamma_lut_g[i] ||
+ ctrls->gamma_lut_r[i])
+ any_nonzero = true;
+ }
+ ctrls->gamma_lut_override = any_nonzero;
+}
+
static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct isc_device *isc = container_of(ctrl->handler,
struct isc_device, ctrls.handler);
struct isc_ctrls *ctrls = &isc->ctrls;
+ struct regmap *regmap = isc->regmap;
+ bool apply_gamma = false;
if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
return 0;
@@ -1583,11 +1676,42 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
break;
case V4L2_CID_GAMMA:
ctrls->gamma_index = ctrl->val;
+ ctrls->gamma_lut_override = false;
+ apply_gamma = true;
+ break;
+ case ISC_CID_GAMMA_B_LUT:
+ memcpy(ctrls->gamma_lut_b, ctrl->p_new.p_u32,
+ GAMMA_ENTRIES * sizeof(u32));
+ isc_update_gamma_lut_override(ctrls);
+ apply_gamma = true;
+ break;
+ case ISC_CID_GAMMA_G_LUT:
+ memcpy(ctrls->gamma_lut_g, ctrl->p_new.p_u32,
+ GAMMA_ENTRIES * sizeof(u32));
+ isc_update_gamma_lut_override(ctrls);
+ apply_gamma = true;
+ break;
+ case ISC_CID_GAMMA_R_LUT:
+ memcpy(ctrls->gamma_lut_r, ctrl->p_new.p_u32,
+ GAMMA_ENTRIES * sizeof(u32));
+ isc_update_gamma_lut_override(ctrls);
+ apply_gamma = true;
break;
default:
return -EINVAL;
}
+ /*
+ * isc_apply_gamma() writes gamma LUT registers; it must be called
+ * under awb_mutex so it does not race with isc_awb_work() which also
+ * calls isc_update_profile() under the same lock.
+ */
+ mutex_lock(&isc->awb_mutex);
+ if (apply_gamma)
+ isc_apply_gamma(isc);
+ isc_update_profile(isc);
+ mutex_unlock(&isc->awb_mutex);
+
return 0;
}
@@ -1939,7 +2063,12 @@ static int isc_ctrl_init(struct isc_device *isc)
ctrls->hist_stat = HIST_INIT;
isc_reset_awb_ctrls(isc);
- ret = v4l2_ctrl_handler_init(hdl, 13);
+ /*
+ * 30 controls maximum (SAMA7G5 with CBHS):
+ * contrast(1) + brightness(1) + hue+saturation(2) + gamma(1) +
+ * awb+do_wb(2) + 4×gain + 4×offset + 12×CC + 3×gamma_LUT
+ */
+ ret = v4l2_ctrl_handler_init(hdl, 30);
if (ret < 0)
return ret;
@@ -1995,6 +2124,11 @@ static int isc_ctrl_init(struct isc_device *isc)
isc->cc_bb = v4l2_ctrl_new_custom(hdl, &isc_cc_bb_ctrl, NULL);
isc->cc_ob = v4l2_ctrl_new_custom(hdl, &isc_cc_ob_ctrl, NULL);
+ /* Per-channel gamma LUT controls */
+ isc->gamma_b_lut_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gamma_b_lut_ctrl, NULL);
+ isc->gamma_g_lut_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gamma_g_lut_ctrl, NULL);
+ isc->gamma_r_lut_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gamma_r_lut_ctrl, NULL);
+
/*
* The cluster is in auto mode with autowhitebalance enabled
* and manual mode otherwise.
diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h
index db651c9f1387..a4f1e6c22e44 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -165,6 +165,17 @@ struct isc_ctrls {
#define HIST_MAX_INDEX 1
u32 hist_minmax[HIST_BAYER][2];
+ /*
+ * Custom per-channel gamma LUT (10-bit output values, 64 entries).
+ * Set via ISC_CID_GAMMA_{R,G,B}_LUT controls. When gamma_lut_override
+ * is true these arrays are converted to hardware format at pipeline
+ * start-up, overriding the preset curve from gamma_index.
+ */
+ u32 gamma_lut_r[GAMMA_ENTRIES];
+ u32 gamma_lut_g[GAMMA_ENTRIES];
+ u32 gamma_lut_b[GAMMA_ENTRIES];
+ bool gamma_lut_override;
+
/* CC matrix shadow; committed from isc_set_pipeline() and isc_awb_work() */
s32 cc_coeff[ISC_CC_COEFF_NUM];
s32 cc_offset[ISC_CC_OFFSET_NUM];
@@ -363,12 +374,24 @@ struct isc_device {
struct v4l2_ctrl *cc_ob;
};
-#define GAMMA_ENTRIES 64
/* pointer to the defined gamma table */
const u32 (*gamma_table)[GAMMA_ENTRIES];
u32 gamma_max;
bool has_cbhs;
+ /*
+ * When true the GAM block operates in bipartite piecewise-linear
+ * interpolation mode (ISC_GAM_CTRL_BIPART set). The LUT-to-hardware
+ * conversion uses a Q9 per-step delta; without bipartite mode the
+ * delta is a plain per-segment increment.
+ */
+ bool gamma_bipartite;
+
+ /* V4L2 ctrl handles for the per-channel gamma LUT override */
+ struct v4l2_ctrl *gamma_b_lut_ctrl;
+ struct v4l2_ctrl *gamma_g_lut_ctrl;
+ struct v4l2_ctrl *gamma_r_lut_ctrl;
+
u32 max_width;
u32 max_height;
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index 6705011edc2a..9110690a49e4 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -461,6 +461,7 @@ static int microchip_xisc_probe(struct platform_device *pdev)
isc->gamma_table = isc_sama7g5_gamma_table;
isc->gamma_max = 2;
isc->has_cbhs = true;
+ isc->gamma_bipartite = true;
if (of_machine_is_compatible("microchip,sam9x7")) {
isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH;
diff --git a/include/linux/atmel-isc-media.h b/include/linux/atmel-isc-media.h
index 028d34c8de81..459e96170d9e 100644
--- a/include/linux/atmel-isc-media.h
+++ b/include/linux/atmel-isc-media.h
@@ -66,6 +66,24 @@ enum atmel_isc_ctrl_id {
ISC_CID_CC_BG,
ISC_CID_CC_BB,
ISC_CID_CC_OB,
+
+ /*
+ * Per-channel gamma LUT override controls.
+ *
+ * Each control is a 64-element U32 array. Element i holds the
+ * desired 10-bit output value (0-1023) for sensor input values in
+ * the range [i*16 .. (i+1)*16 - 1]. The driver converts the
+ * simple linear array to the hardware piecewise-linear (bipartite)
+ * register format internally.
+ *
+ * Setting any of these controls activates the custom LUT for all
+ * three channels and overrides the preset curve selected by
+ * V4L2_CID_GAMMA. Writing V4L2_CID_GAMMA deactivates the custom
+ * LUT and restores the selected preset curve.
+ */
+ ISC_CID_GAMMA_B_LUT,
+ ISC_CID_GAMMA_G_LUT,
+ ISC_CID_GAMMA_R_LUT,
};
#endif
--
2.34.1
next prev parent reply other threads:[~2026-05-12 15:44 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20251009155251.102472-1-balamanikandan.gunasundar@microchip.com>
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 01/15] media: microchip-isc: fix SBGGR10 Bayer pattern Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 02/15] media: microchip-isc: mask WB offset and gain register fields Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 03/15] media: microchip-isc: fix race condition on stream stop Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 04/15] media: microchip-isc: fix PM runtime leak in AWB work handler Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 05/15] media: microchip-isc: add driver documentation Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 06/15] media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920 Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 07/15] media: microchip-isc: configure DPC and pipeline for SAMA7G5 Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 08/15] media: microchip-isc: add gamma 1.8 and 2.4 correction curves Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 09/15] media: microchip-isc: add SAMA7G5 hue and saturation controls Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 10/15] media: microchip-isc: expose color correction matrix as V4L2 controls Balakrishnan Sambath
2026-05-12 15:43 ` Balakrishnan Sambath [this message]
2026-05-12 15:43 ` [PATCH v2 12/15] media: microchip-isc: reset pipeline state on kernel AWB enable Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 13/15] media: microchip-isc: use weighted averages for Grey World AWB Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 14/15] media: microchip-isc: smooth AWB gains with EMA filter Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 15/15] media: microchip-isc: scale DPC black level to sensor bit depth Balakrishnan Sambath
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260512154339.210444-12-balakrishnan.s@microchip.com \
--to=balakrishnan.s@microchip.com \
--cc=eugen.hristev@linaro.org \
--cc=hverkuil@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=mchehab@kernel.org \
--cc=nicolas.ferre@microchip.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox