* [PATCH v2 00/15] media: microchip-isc: fixes and enhancements
[not found] <20251009155251.102472-1-balamanikandan.gunasundar@microchip.com>
@ 2026-05-12 15:43 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 01/15] media: microchip-isc: fix SBGGR10 Bayer pattern Balakrishnan Sambath
` (14 more replies)
0 siblings, 15 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
Bug fixes and feature additions for the Microchip ISC/XISC driver.
Fixes:
- SBGGR10 Bayer pattern was mapped incorrectly (red/blue swap)
- WB register fields corrupted by sign extension
- Race between histogram IRQ and stream stop
- PM runtime reference leak in AWB work handler
Features:
- Driver documentation
- Gamma 1.8/2.4 curves, per-channel gamma LUT
- Hue/saturation controls for SAMA7G5
- Color correction matrix controls
- Grey World AWB with EMA smoothing
Split from v1 per review. Histogram stats follow as Series 2.
Tested on SAMA7G5-EK with IMX219 (RAW10 Bayer capture, AWB, color
controls verified). Build tested with COMPILE_TEST and W=1. All
patches pass checkpatch --strict.
Based on v6.19-rc8 (18f7fcd5e69a).
v1: https://lore.kernel.org/linux-media/20251009155251.102472-1-balamanikandan.gunasundar@microchip.com/
v2:
- Split series (histogram stats moved to Series 2)
- Bug fixes first, then features
- New fixes and features as listed above
- Commit message cleanups
- Rebased on v6.19-rc8
Balakrishnan Sambath (15):
media: microchip-isc: fix SBGGR10 Bayer pattern
media: microchip-isc: mask WB offset and gain register fields
media: microchip-isc: fix race condition on stream stop
media: microchip-isc: fix PM runtime leak in AWB work handler
media: microchip-isc: add driver documentation
media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920
media: microchip-isc: configure DPC and pipeline for SAMA7G5
media: microchip-isc: add gamma 1.8 and 2.4 correction curves
media: microchip-isc: add SAMA7G5 hue and saturation controls
media: microchip-isc: expose color correction matrix as V4L2 controls
media: microchip-isc: add per-channel gamma LUT controls
media: microchip-isc: reset pipeline state on kernel AWB enable
media: microchip-isc: use weighted averages for Grey World AWB
media: microchip-isc: smooth AWB gains with EMA filter
media: microchip-isc: scale DPC black level to sensor bit depth
.../userspace-api/media/drivers/index.rst | 1 +
.../media/drivers/microchip-isc.rst | 71 ++
MAINTAINERS | 1 +
.../platform/microchip/microchip-isc-base.c | 730 ++++++++++++++++--
.../platform/microchip/microchip-isc-regs.h | 11 +-
.../media/platform/microchip/microchip-isc.h | 56 +-
.../microchip/microchip-sama5d2-isc.c | 2 +-
.../microchip/microchip-sama7g5-isc.c | 101 ++-
include/linux/atmel-isc-media.h | 31 +
9 files changed, 896 insertions(+), 108 deletions(-)
create mode 100644 Documentation/userspace-api/media/drivers/microchip-isc.rst
--
2.34.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 01/15] media: microchip-isc: fix SBGGR10 Bayer pattern
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
@ 2026-05-12 15:43 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 02/15] media: microchip-isc: mask WB offset and gain register fields Balakrishnan Sambath
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
stable
SBGGR10 was mapped to ISC_BAY_CFG_RGRG instead of ISC_BAY_CFG_BGBG,
causing red/blue channel swap.
Fixes: 91b4e487b0c6 ("media: microchip: add ISC driver as Microchip ISC")
Cc: stable@vger.kernel.org
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
drivers/media/platform/microchip/microchip-sama7g5-isc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index b0302dfc3278..ca23e8adecbd 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -156,7 +156,7 @@ static struct isc_format sama7g5_formats_list[] = {
.fourcc = V4L2_PIX_FMT_SBGGR10,
.mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
.pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN,
- .cfa_baycfg = ISC_BAY_CFG_RGRG,
+ .cfa_baycfg = ISC_BAY_CFG_BGBG,
},
{
.fourcc = V4L2_PIX_FMT_SGBRG10,
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 02/15] media: microchip-isc: mask WB offset and gain register fields
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 03/15] media: microchip-isc: fix race condition on stream stop Balakrishnan Sambath
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
stable
ISC_WB_O_* and ISC_WB_G_* pack two 13-bit fields per register. Sign
extension from negative offsets corrupts the upper field. Mask both
fields to 13 bits before packing.
Fixes: 3308bec11098 ("at91: isc: integrate pipeline")
Cc: stable@vger.kernel.org
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 21 ++++++++++++-------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index a7cdc743fda7..45b94f1e89d8 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -61,18 +61,23 @@ static inline void isc_update_awb_ctrls(struct isc_device *isc)
/* In here we set our actual hw pipeline config */
+ /*
+ * Mask offset fields to 13 bits. Sign extension of negative s32
+ * values would otherwise corrupt the adjacent field.
+ */
regmap_write(isc->regmap, ISC_WB_O_RGR,
- ((ctrls->offset[ISC_HIS_CFG_MODE_R])) |
- ((ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16));
+ ((u32)ctrls->offset[ISC_HIS_CFG_MODE_R] & GENMASK(12, 0)) |
+ (((u32)ctrls->offset[ISC_HIS_CFG_MODE_GR] & GENMASK(12, 0)) << 16));
regmap_write(isc->regmap, ISC_WB_O_BGB,
- ((ctrls->offset[ISC_HIS_CFG_MODE_B])) |
- ((ctrls->offset[ISC_HIS_CFG_MODE_GB]) << 16));
+ ((u32)ctrls->offset[ISC_HIS_CFG_MODE_B] & GENMASK(12, 0)) |
+ (((u32)ctrls->offset[ISC_HIS_CFG_MODE_GB] & GENMASK(12, 0)) << 16));
+ /* Gains are 13-bit unsigned fields [12:0] and [28:16] */
regmap_write(isc->regmap, ISC_WB_G_RGR,
- ctrls->gain[ISC_HIS_CFG_MODE_R] |
- (ctrls->gain[ISC_HIS_CFG_MODE_GR] << 16));
+ (ctrls->gain[ISC_HIS_CFG_MODE_R] & GENMASK(12, 0)) |
+ ((ctrls->gain[ISC_HIS_CFG_MODE_GR] & GENMASK(12, 0)) << 16));
regmap_write(isc->regmap, ISC_WB_G_BGB,
- ctrls->gain[ISC_HIS_CFG_MODE_B] |
- (ctrls->gain[ISC_HIS_CFG_MODE_GB] << 16));
+ (ctrls->gain[ISC_HIS_CFG_MODE_B] & GENMASK(12, 0)) |
+ ((ctrls->gain[ISC_HIS_CFG_MODE_GB] & GENMASK(12, 0)) << 16));
}
static inline void isc_reset_awb_ctrls(struct isc_device *isc)
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 03/15] media: microchip-isc: fix race condition on stream stop
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 04/15] media: microchip-isc: fix PM runtime leak in AWB work handler Balakrishnan Sambath
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
stable
Disable histogram and drain AWB work queue before releasing DMA
buffers to prevent use-after-free if histogram IRQ fires during
stream stop.
Fixes: 91b4e487b0c6 ("media: microchip: add ISC driver as Microchip ISC")
Cc: stable@vger.kernel.org
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 45b94f1e89d8..b19c5a63b4bd 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -427,6 +427,14 @@ static void isc_stop_streaming(struct vb2_queue *vq)
mutex_unlock(&isc->awb_mutex);
+ /*
+ * Disable the histogram so the ISR stops firing HISREQ, then drain
+ * any work that was already queued before returning. This must happen
+ * after releasing awb_mutex because isc_awb_work also takes it.
+ */
+ isc_set_histogram(isc, false);
+ cancel_work_sync(&isc->awb_work);
+
/* Disable DMA interrupt */
regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE);
@@ -1519,10 +1527,17 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
}
mutex_unlock(&isc->awb_mutex);
- /* if we have autowhitebalance on, start histogram procedure */
+ /*
+ * If AWB auto mode is requested and we are streaming RAW,
+ * start the histogram procedure, but only if it is not
+ * already running. Repeated enable requests would reset
+ * hist_id, preventing the 4-channel Bayer cycle from
+ * completing.
+ */
if (ctrls->awb == ISC_WB_AUTO &&
vb2_is_streaming(&isc->vb2_vidq) &&
- ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
+ ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code) &&
+ ctrls->hist_stat != HIST_ENABLED)
isc_set_histogram(isc, true);
/*
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 04/15] media: microchip-isc: fix PM runtime leak in AWB work handler
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (2 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 05/15] media: microchip-isc: add driver documentation Balakrishnan Sambath
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
stable
Early return when streaming stops skips pm_runtime_put_sync(),
leaking the reference and preventing runtime suspend.
Fixes: 91b4e487b0c6 ("media: microchip: add ISC driver as Microchip ISC")
Cc: stable@vger.kernel.org
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
drivers/media/platform/microchip/microchip-isc-base.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index b19c5a63b4bd..f61a5d5a3e04 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -1429,7 +1429,7 @@ static void isc_awb_work(struct work_struct *w)
/* streaming is not active anymore */
if (isc->stop) {
mutex_unlock(&isc->awb_mutex);
- return;
+ goto out_pm_put;
}
isc_update_profile(isc);
@@ -1440,6 +1440,7 @@ static void isc_awb_work(struct work_struct *w)
if (ctrls->awb)
regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ);
+out_pm_put:
pm_runtime_put_sync(isc->dev);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 05/15] media: microchip-isc: add driver documentation
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (3 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 06/15] media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920 Balakrishnan Sambath
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
Document V4L2 controls and pipeline modes for ISC/XISC camera interface
on SAMA5D2, SAMA7G5, and SAM9X7.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../userspace-api/media/drivers/index.rst | 1 +
.../media/drivers/microchip-isc.rst | 71 +++++++++++++++++++
MAINTAINERS | 1 +
3 files changed, 73 insertions(+)
create mode 100644 Documentation/userspace-api/media/drivers/microchip-isc.rst
diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst
index 02967c9b18d6..65ef6ba3523e 100644
--- a/Documentation/userspace-api/media/drivers/index.rst
+++ b/Documentation/userspace-api/media/drivers/index.rst
@@ -34,6 +34,7 @@ For more details see the file COPYING in the source distribution of Linux.
imx-uapi
mali-c55
max2175
+ microchip-isc
npcm-video
omap3isp-uapi
thp7312
diff --git a/Documentation/userspace-api/media/drivers/microchip-isc.rst b/Documentation/userspace-api/media/drivers/microchip-isc.rst
new file mode 100644
index 000000000000..2a436fd19272
--- /dev/null
+++ b/Documentation/userspace-api/media/drivers/microchip-isc.rst
@@ -0,0 +1,71 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Microchip ISC/XISC Driver
+=========================
+
+The Image Sensor Controller (ISC) on SAMA5D2 and eXtended ISC (XISC) on
+SAMA7G5/SAM9X7 provide camera capture with hardware image processing.
+
+Supported Hardware
+------------------
+
+========== ========== ============== ================ ===============
+SoC Controller Max Resolution Interface Hue/Saturation
+========== ========== ============== ================ ===============
+SAMA5D2 ISC 2592x1944 12-bit parallel No
+SAMA7G5 XISC 3264x2464 12-bit + CSI-2 Yes
+SAM9X7 XISC 2560x1920 12-bit + CSI-2 Yes
+========== ========== ============== ================ ===============
+
+SAM9X7 shares the XISC pipeline with SAMA7G5 but has a smaller internal
+line buffer, limiting horizontal resolution to 2560 pixels.
+
+Controls
+--------
+
+Standard V4L2 controls:
+
+* ``V4L2_CID_BRIGHTNESS``: -1024..1023, default 0
+* ``V4L2_CID_CONTRAST``: -2048..2047, default 256 (1.0x)
+* ``V4L2_CID_GAMMA``: 0..2 selects curve (0=2.4, 1=2.2, 2=1.8)
+* ``V4L2_CID_AUTO_WHITE_BALANCE``: Enable kernel Grey World AWB
+* ``V4L2_CID_DO_WHITE_BALANCE``: Trigger one-shot AWB
+
+SAMA7G5/SAM9X7 add:
+
+* ``V4L2_CID_HUE``: -180..180 degrees
+* ``V4L2_CID_SATURATION``: 0..255, default 16
+
+Custom controls (defined in ``atmel-isc-media.h``):
+
+* ``ISC_CID_R_GAIN``, ``ISC_CID_B_GAIN``, ``ISC_CID_GR_GAIN``,
+ ``ISC_CID_GB_GAIN``: WB gains, 0..8191, Q2.9 (512 = 1.0x)
+* ``ISC_CID_R_OFFSET``, ``ISC_CID_B_OFFSET``, ``ISC_CID_GR_OFFSET``,
+ ``ISC_CID_GB_OFFSET``: WB offsets, -4096..4095
+* ``ISC_CID_CC_RR`` ... ``ISC_CID_CC_BB``: 3x3 color correction matrix,
+ signed Q4.8 (256 = 1.0)
+* ``ISC_CID_CC_OR``, ``ISC_CID_CC_OG``, ``ISC_CID_CC_OB``: RGB offsets
+* ``ISC_CID_GAMMA_R_LUT``, ``ISC_CID_GAMMA_G_LUT``,
+ ``ISC_CID_GAMMA_B_LUT``: Per-channel gamma LUTs, 64-entry arrays
+
+Pipeline
+--------
+
+Pipeline modules: DPC -> WB -> CFA -> CC -> GAM -> CBHS/CBC -> CSC -> SUB
+
+* DPC: Defective Pixel Correction (XISC only), black level subtraction
+ to sensor bit depth, green disparity correction
+* WB: White Balance gains/offsets
+* CFA: Color Filter Array interpolation (demosaic)
+* CC: Color Correction matrix
+* GAM: Gamma correction (preset or per-channel LUT)
+* CBHS: Contrast/Brightness/Hue/Saturation (XISC only)
+* CBC: Contrast/Brightness (ISC only)
+* CSC: Color Space Conversion (RGB to YCbCr)
+* SUB: Chroma subsampling (4:2:2, 4:2:0)
+
+Pipeline usage depends on input and output formats:
+
+* Raw Bayer input, RGB output: DPC, WB, CFA, CC, GAM
+* Raw Bayer input, YUV output: Full pipeline including CSC, CBHS/CBC, SUB
+* Non-RAW input (YUV/RGB sensor): Pipeline bypassed
diff --git a/MAINTAINERS b/MAINTAINERS
index 0efa8cc6775b..fe5c3bb03e60 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17052,6 +17052,7 @@ L: linux-media@vger.kernel.org
S: Supported
F: Documentation/devicetree/bindings/media/atmel,isc.yaml
F: Documentation/devicetree/bindings/media/microchip,xisc.yaml
+F: Documentation/userspace-api/media/drivers/microchip-isc.rst
F: drivers/media/platform/microchip/microchip-isc*
F: drivers/media/platform/microchip/microchip-sama*-isc*
F: drivers/staging/media/deprecated/atmel/atmel-isc*
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 06/15] media: microchip-isc: set SAM9X7 maximum resolution to 2560x1920
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (4 preceding siblings ...)
2026-05-12 15:43 ` [PATCH v2 05/15] media: microchip-isc: add driver documentation Balakrishnan Sambath
@ 2026-05-12 15:43 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 07/15] media: microchip-isc: configure DPC and pipeline for SAMA7G5 Balakrishnan Sambath
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
Balamanikandan Gunasundar
SAM9X7 XISC uses the same image processing pipeline as SAMA7G5 but has
a smaller internal line buffer. The reduced RAM constrains the maximum
horizontal resolution to 2560 pixels (compared to 3264 on SAMA7G5),
resulting in a maximum capture resolution of 2560x1920.
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../media/platform/microchip/microchip-sama7g5-isc.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index ca23e8adecbd..4119cfe12cdf 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -55,6 +55,9 @@
#define ISC_SAMA7G5_MAX_SUPPORT_WIDTH 3264
#define ISC_SAMA7G5_MAX_SUPPORT_HEIGHT 2464
+#define ISC_SAM9X7_MAX_SUPPORT_WIDTH 2560
+#define ISC_SAM9X7_MAX_SUPPORT_HEIGHT 1920
+
#define ISC_SAMA7G5_PIPELINE \
(WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \
CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
@@ -432,8 +435,13 @@ static int microchip_xisc_probe(struct platform_device *pdev)
isc->gamma_table = isc_sama7g5_gamma_table;
isc->gamma_max = 0;
- isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH;
- isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT;
+ if (of_machine_is_compatible("microchip,sam9x7")) {
+ isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH;
+ isc->max_height = ISC_SAM9X7_MAX_SUPPORT_HEIGHT;
+ } else {
+ isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH;
+ isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT;
+ }
isc->config_dpc = isc_sama7g5_config_dpc;
isc->config_csc = isc_sama7g5_config_csc;
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 07/15] media: microchip-isc: configure DPC and pipeline for SAMA7G5
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (5 preceding siblings ...)
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 ` 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
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
Balamanikandan Gunasundar
Enable DPC_GDCENABLE for RGB output. Disable pipeline for raw Bayer
passthrough to provide unmodified sensor data for software ISP.
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
drivers/media/platform/microchip/microchip-isc-base.c | 7 ++-----
drivers/media/platform/microchip/microchip-sama7g5-isc.c | 3 ++-
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index f61a5d5a3e04..23a09ed12946 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -800,7 +800,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
isc->try_config.bits_pipeline = CFA_ENABLE |
WB_ENABLE | GAM_ENABLES | DPC_BLCENABLE |
- CC_ENABLE;
+ DPC_GDCENABLE | CC_ENABLE;
} else {
isc->try_config.bits_pipeline = 0x0;
}
@@ -850,10 +850,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
}
break;
default:
- if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code))
- isc->try_config.bits_pipeline = WB_ENABLE | DPC_BLCENABLE;
- else
- isc->try_config.bits_pipeline = 0x0;
+ isc->try_config.bits_pipeline = 0x0;
}
/* Tune the pipeline to product specific */
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index 4119cfe12cdf..04930aa0f289 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -59,7 +59,8 @@
#define ISC_SAM9X7_MAX_SUPPORT_HEIGHT 1920
#define ISC_SAMA7G5_PIPELINE \
- (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \
+ (DPC_DPCENABLE | DPC_GDCENABLE | DPC_BLCENABLE | \
+ WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \
CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
/* This is a list of the formats that the ISC can *output* */
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 08/15] media: microchip-isc: add gamma 1.8 and 2.4 correction curves
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (6 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 09/15] media: microchip-isc: add SAMA7G5 hue and saturation controls Balakrishnan Sambath
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
Balamanikandan Gunasundar
Add gamma 1.8 and 2.4 curves alongside the existing 2.2 (sRGB).
V4L2_CID_GAMMA selects preset curves: 0=2.4, 1=2.2, 2=1.8.
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 3 +-
.../microchip/microchip-sama7g5-isc.c | 54 ++++++++++++++-----
2 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 23a09ed12946..ae2a0c6ba566 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -1647,8 +1647,7 @@ static int isc_ctrl_init(struct isc_device *isc)
ctrls->brightness = 0;
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0);
- v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1,
- isc->gamma_max);
+ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, 1);
isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops,
V4L2_CID_AUTO_WHITE_BALANCE,
0, 1, 1, 1);
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index 04930aa0f289..8b73b625d92b 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -320,21 +320,47 @@ static void isc_sama7g5_adapt_pipeline(struct isc_device *isc)
isc->try_config.bits_pipeline &= ISC_SAMA7G5_PIPELINE;
}
-/* Gamma table with gamma 1/2.2 */
+/* Gamma tables with gamma values 0.42, 0.45(Default), 0.56 */
static const u32 isc_sama7g5_gamma_table[][GAMMA_ENTRIES] = {
- /* index 0 --> gamma bipartite */
+ /* index 0 --> gamma bipartite 1/2.4(=0.42) */
{
- 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180,
- 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100,
- 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0,
- 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0,
- 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080,
- 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a,
- 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030,
- 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026,
- 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020,
- 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c,
- 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a },
+ 0x940, 0x4b0310, 0x630250, 0x7601d0, 0x840190, 0x910170,
+ 0x9d0150, 0xa80110, 0xb10110, 0xba0110, 0xc300f0, 0xcb00f0,
+ 0xd300e0, 0xda00e0, 0xe100c0, 0xe800c0, 0xee00c0, 0xf400c0,
+ 0xfa00a0, 0x10000a0, 0x10500a0, 0x10b00a0, 0x11000a0, 0x11500a0,
+ 0x11a0080, 0x11f0080, 0x1240080, 0x1290080, 0x12e0080, 0x1330070,
+ 0x1380070, 0x13c0070, 0x1410070, 0x17a0060, 0x1aa0052, 0x1d40046,
+ 0x1f90042, 0x21b003c, 0x23a0038, 0x2570034, 0x2720030, 0x28b002e,
+ 0x2a3002c, 0x2ba002a, 0x2d0002a, 0x2e60028, 0x2fa0026, 0x30e0026,
+ 0x3210024, 0x3330022, 0x3450022, 0x3560020, 0x3670020, 0x3770020,
+ 0x387001e, 0x396001e, 0x3a5001c, 0x3b3001c, 0x3c1001c, 0x3cf001a,
+ 0x3dd001a, 0x3eb0018, 0x3f90018, 0x4070016 },
+ /* index 1 --> gamma bipartite 1/2.2(=0.45) */
+ {
+ 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180,
+ 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100,
+ 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0,
+ 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0,
+ 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080,
+ 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a,
+ 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030,
+ 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026,
+ 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020,
+ 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c,
+ 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a },
+ /* index 2 --> gamma bipartite 1/1.8(=0.56) */
+ {
+ 0xa62, 0x4f0350, 0x680280, 0x7e0200, 0x8d01c0, 0x9a01a0,
+ 0xa50180, 0xb00140, 0xb90140, 0xc20120, 0xcb0120, 0xd30100,
+ 0xdb0100, 0xe300e0, 0xea00e0, 0xf100e0, 0xf700c0, 0xfd00c0,
+ 0x10300c0, 0x10900a0, 0x10e00a0, 0x11400a0, 0x11900a0, 0x11e00a0,
+ 0x12300a0, 0x12800a0, 0x12d0080, 0x1320080, 0x1370080, 0x13c0080,
+ 0x1410080, 0x1460080, 0x14a0070, 0x1830060, 0x1b40052, 0x1df0048,
+ 0x2040042, 0x2250040, 0x2440038, 0x2600036, 0x27b0032, 0x2940030,
+ 0x2ac002e, 0x2c4002c, 0x2da002a, 0x2f0002a, 0x3050028, 0x3190026,
+ 0x32c0026, 0x33e0024, 0x3500024, 0x3610022, 0x3720020, 0x3820020,
+ 0x3920020, 0x3a2001e, 0x3b1001e, 0x3c0001c, 0x3ce001c, 0x3dc001c,
+ 0x3ea001a, 0x3f8001a, 0x4060018, 0x4130018 },
};
static int xisc_parse_dt(struct device *dev, struct isc_device *isc)
@@ -434,7 +460,7 @@ static int microchip_xisc_probe(struct platform_device *pdev)
}
isc->gamma_table = isc_sama7g5_gamma_table;
- isc->gamma_max = 0;
+ isc->gamma_max = 2;
if (of_machine_is_compatible("microchip,sam9x7")) {
isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH;
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 09/15] media: microchip-isc: add SAMA7G5 hue and saturation controls
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (7 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 10/15] media: microchip-isc: expose color correction matrix as V4L2 controls Balakrishnan Sambath
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
Balamanikandan Gunasundar
SAMA7G5 extends CBC with hue and saturation. Add V4L2_CID_HUE and
V4L2_CID_SATURATION controls. Disable CBHS for RGB output since it
operates in YCbCr domain.
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 86 ++++++++++++++++++-
.../platform/microchip/microchip-isc-regs.h | 11 ++-
.../media/platform/microchip/microchip-isc.h | 5 +-
.../microchip/microchip-sama5d2-isc.c | 2 +-
.../microchip/microchip-sama7g5-isc.c | 8 +-
5 files changed, 98 insertions(+), 14 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index ae2a0c6ba566..7e140af51912 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -810,7 +810,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
isc->try_config.bits_pipeline = CFA_ENABLE |
CSC_ENABLE | GAM_ENABLES | WB_ENABLE |
- SUB420_ENABLE | SUB422_ENABLE | CBC_ENABLE |
+ SUB420_ENABLE | SUB422_ENABLE | CBHS_ENABLE |
DPC_BLCENABLE;
} else {
isc->try_config.bits_pipeline = 0x0;
@@ -821,7 +821,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
isc->try_config.bits_pipeline = CFA_ENABLE |
CSC_ENABLE | WB_ENABLE | GAM_ENABLES |
- SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE;
+ SUB422_ENABLE | CBHS_ENABLE | DPC_BLCENABLE;
} else {
isc->try_config.bits_pipeline = 0x0;
}
@@ -833,7 +833,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
isc->try_config.bits_pipeline = CFA_ENABLE |
CSC_ENABLE | WB_ENABLE | GAM_ENABLES |
- SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE;
+ SUB422_ENABLE | CBHS_ENABLE | DPC_BLCENABLE;
} else {
isc->try_config.bits_pipeline = 0x0;
}
@@ -844,7 +844,7 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) {
isc->try_config.bits_pipeline = CFA_ENABLE |
CSC_ENABLE | WB_ENABLE | GAM_ENABLES |
- CBC_ENABLE | DPC_BLCENABLE;
+ CBHS_ENABLE | DPC_BLCENABLE;
} else {
isc->try_config.bits_pipeline = 0x0;
}
@@ -859,6 +859,56 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
return 0;
}
+static bool isc_format_has_chroma(u32 fourcc)
+{
+ switch (fourcc) {
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YUV422P:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/*
+ * isc_update_cbc_ctrl_activity() - Activate/deactivate CBC controls
+ *
+ * Called from isc_set_fmt(), isc_link_validate(), and isc_ctrl_init().
+ * At isc_ctrl_init() time isc->config.bits_pipeline is zero (no format
+ * has been negotiated yet), so all CBC controls are initially marked
+ * inactive. They become active once a format that includes CBHS in the
+ * pipeline is configured via VIDIOC_S_FMT or link validation.
+ */
+static void isc_update_cbc_ctrl_activity(struct isc_device *isc)
+{
+ struct v4l2_ctrl_handler *hdl = &isc->ctrls.handler;
+ struct v4l2_ctrl *brightness;
+ struct v4l2_ctrl *contrast;
+ struct v4l2_ctrl *hue;
+ struct v4l2_ctrl *saturation;
+ bool cbc_active = isc->config.bits_pipeline & CBHS_ENABLE;
+ bool chroma_active = cbc_active && isc_format_has_chroma(isc->config.fourcc);
+
+ brightness = v4l2_ctrl_find(hdl, V4L2_CID_BRIGHTNESS);
+ if (brightness)
+ v4l2_ctrl_activate(brightness, cbc_active);
+
+ contrast = v4l2_ctrl_find(hdl, V4L2_CID_CONTRAST);
+ if (contrast)
+ v4l2_ctrl_activate(contrast, cbc_active);
+
+ hue = v4l2_ctrl_find(hdl, V4L2_CID_HUE);
+ if (hue)
+ v4l2_ctrl_activate(hue, chroma_active);
+
+ saturation = v4l2_ctrl_find(hdl, V4L2_CID_SATURATION);
+ if (saturation)
+ v4l2_ctrl_activate(saturation, chroma_active);
+}
+
static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f)
{
struct v4l2_pix_format *pixfmt = &f->fmt.pix;
@@ -902,6 +952,7 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
/* make the try configuration active */
isc->config = isc->try_config;
isc->fmt = isc->try_fmt;
+ isc_update_cbc_ctrl_activity(isc);
dev_dbg(isc->dev, "ISC set_fmt to %.4s @%dx%d\n",
(char *)&f->fmt.pix.pixelformat,
@@ -989,6 +1040,7 @@ static int isc_link_validate(struct media_link *link)
return ret;
isc->config = isc->try_config;
+ isc_update_cbc_ctrl_activity(isc);
dev_dbg(isc->dev, "New ISC configuration in place\n");
@@ -1453,9 +1505,30 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
switch (ctrl->id) {
case V4L2_CID_BRIGHTNESS:
ctrls->brightness = ctrl->val & ISC_CBC_BRIGHT_MASK;
+ regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, ctrls->brightness);
break;
case V4L2_CID_CONTRAST:
ctrls->contrast = ctrl->val & ISC_CBC_CONTRAST_MASK;
+ regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, ctrls->contrast);
+ break;
+ case V4L2_CID_HUE:
+ if (isc->has_cbhs) {
+ ctrls->hue = ctrl->val & ISC_CBHS_HUE_MASK;
+ regmap_write(regmap, ISC_CBHS_HUE, ctrls->hue);
+ }
+ break;
+ case V4L2_CID_SATURATION:
+ if (isc->has_cbhs) {
+ /*
+ * The ISC CBHS SAT register holds a Q4 fixed-point
+ * coefficient: 0 = grayscale, 16 = 1.0 (no change),
+ * values above 16 boost saturation. The V4L2 range
+ * 0-100 (default 16) maps directly to this hardware
+ * value; no unit conversion is applied.
+ */
+ ctrls->saturation = ctrl->val & ISC_CBHS_SAT_MASK;
+ regmap_write(regmap, ISC_CBHS_SAT, ctrls->saturation);
+ }
break;
case V4L2_CID_GAMMA:
ctrls->gamma_index = ctrl->val;
@@ -1647,6 +1720,10 @@ static int isc_ctrl_init(struct isc_device *isc)
ctrls->brightness = 0;
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0);
+ if (isc->has_cbhs) {
+ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE, -180, 180, 1, 0);
+ v4l2_ctrl_new_std(hdl, ops, V4L2_CID_SATURATION, 0, 100, 1, 16);
+ }
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, 1);
isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops,
V4L2_CID_AUTO_WHITE_BALANCE,
@@ -1664,6 +1741,7 @@ static int isc_ctrl_init(struct isc_device *isc)
}
v4l2_ctrl_activate(isc->do_wb_ctrl, false);
+ isc_update_cbc_ctrl_activity(isc);
isc->r_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_gain_ctrl, NULL);
isc->b_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_gain_ctrl, NULL);
diff --git a/drivers/media/platform/microchip/microchip-isc-regs.h b/drivers/media/platform/microchip/microchip-isc-regs.h
index e77e1d9a1db8..2fd8916abf21 100644
--- a/drivers/media/platform/microchip/microchip-isc-regs.h
+++ b/drivers/media/platform/microchip/microchip-isc-regs.h
@@ -268,10 +268,13 @@
#define ISC_CBC_CONTRAST 0x000003c0
#define ISC_CBC_CONTRAST_MASK GENMASK(11, 0)
-/* Hue Register */
-#define ISC_CBCHS_HUE 0x4e0
-/* Saturation Register */
-#define ISC_CBCHS_SAT 0x4e4
+/* Hue Register: signed 9-bit two's complement, covers -180 to +180 degrees */
+#define ISC_CBHS_HUE 0x4e0
+#define ISC_CBHS_HUE_MASK GENMASK(8, 0)
+
+/* Saturation Register: unsigned Q4 fixed-point (1.0 = 16, V4L2 range 0-100) */
+#define ISC_CBHS_SAT 0x4e4
+#define ISC_CBHS_SAT_MASK GENMASK(6, 0)
/* Offset for SUB422 register specific to sama5d2 product */
#define ISC_SAMA5D2_SUB422_OFFSET 0
diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h
index ad4e98a1dd8f..2c8bcaaa26ea 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -88,7 +88,7 @@ struct isc_format {
#define GAM_RENABLE BIT(9)
#define VHXS_ENABLE BIT(10)
#define CSC_ENABLE BIT(11)
-#define CBC_ENABLE BIT(12)
+#define CBHS_ENABLE BIT(12)
#define SUB422_ENABLE BIT(13)
#define SUB420_ENABLE BIT(14)
@@ -139,6 +139,8 @@ struct isc_ctrls {
u32 brightness;
u32 contrast;
+ u32 hue;
+ u32 saturation;
u8 gamma_index;
#define ISC_WB_NONE 0
#define ISC_WB_AUTO 1
@@ -342,6 +344,7 @@ struct isc_device {
/* pointer to the defined gamma table */
const u32 (*gamma_table)[GAMMA_ENTRIES];
u32 gamma_max;
+ bool has_cbhs;
u32 max_width;
u32 max_height;
diff --git a/drivers/media/platform/microchip/microchip-sama5d2-isc.c b/drivers/media/platform/microchip/microchip-sama5d2-isc.c
index 66d3d7891991..239aac170472 100644
--- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c
@@ -54,7 +54,7 @@
#define ISC_SAMA5D2_PIPELINE \
(WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \
- CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
+ CBHS_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
/* This is a list of the formats that the ISC can *output* */
static const struct isc_format sama5d2_controller_formats[] = {
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index 8b73b625d92b..6705011edc2a 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -61,7 +61,7 @@
#define ISC_SAMA7G5_PIPELINE \
(DPC_DPCENABLE | DPC_GDCENABLE | DPC_BLCENABLE | \
WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \
- CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
+ CBHS_ENABLE | SUB422_ENABLE | SUB420_ENABLE)
/* This is a list of the formats that the ISC can *output* */
static const struct isc_format sama7g5_controller_formats[] = {
@@ -257,9 +257,8 @@ static void isc_sama7g5_config_cbc(struct isc_device *isc)
/* Configure what is set via v4l2 ctrls */
regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, isc->ctrls.brightness);
regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, isc->ctrls.contrast);
- /* Configure Hue and Saturation as neutral midpoint */
- regmap_write(regmap, ISC_CBCHS_HUE, 0);
- regmap_write(regmap, ISC_CBCHS_SAT, (1 << 4));
+ regmap_write(regmap, ISC_CBHS_HUE, isc->ctrls.hue);
+ regmap_write(regmap, ISC_CBHS_SAT, isc->ctrls.saturation);
}
static void isc_sama7g5_config_cc(struct isc_device *isc)
@@ -461,6 +460,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;
if (of_machine_is_compatible("microchip,sam9x7")) {
isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH;
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 10/15] media: microchip-isc: expose color correction matrix as V4L2 controls
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (8 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls Balakrishnan Sambath
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media
Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel,
Balamanikandan Gunasundar
Add custom controls for 3x3 color correction matrix and RGB offsets.
Used by libcamera IPA for sensor color calibration.
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 247 +++++++++++++++++-
.../media/platform/microchip/microchip-isc.h | 23 ++
include/linux/atmel-isc-media.h | 13 +
3 files changed, 281 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 7e140af51912..f78145820e40 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -32,7 +32,7 @@
#include "microchip-isc-regs.h"
#include "microchip-isc.h"
-#define ISC_IS_FORMAT_RAW(mbus_code) \
+#define ISC_IS_FORMAT_RAW(mbus_code) \
(((mbus_code) & 0xf000) == 0x3000)
#define ISC_IS_FORMAT_GREY(mbus_code) \
@@ -55,6 +55,46 @@ static inline void isc_update_v4l2_ctrls(struct isc_device *isc)
v4l2_ctrl_s_ctrl(isc->gb_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_GB]);
}
+static void isc_apply_gamma(struct isc_device *isc);
+
+/* commit CC shadow to hardware; called while ISC is powered */
+static void isc_update_cc_ctrls(struct isc_device *isc)
+{
+ struct isc_ctrls *ctrls = &isc->ctrls;
+ struct regmap *regmap = isc->regmap;
+ u32 m = GENMASK(11, 0);
+
+ if (!ctrls->cc_dirty)
+ return;
+
+ regmap_update_bits(regmap, ISC_CC_RR_RG, m,
+ (u32)ctrls->cc_coeff[0] & m);
+ regmap_update_bits(regmap, ISC_CC_RR_RG, GENMASK(27, 16),
+ ((u32)ctrls->cc_coeff[1] & m) << 16);
+ regmap_update_bits(regmap, ISC_CC_RB_OR, m,
+ (u32)ctrls->cc_coeff[2] & m);
+ regmap_update_bits(regmap, ISC_CC_RB_OR, GENMASK(27, 16),
+ ((u32)ctrls->cc_offset[0] & m) << 16);
+ regmap_update_bits(regmap, ISC_CC_GR_GG, m,
+ (u32)ctrls->cc_coeff[3] & m);
+ regmap_update_bits(regmap, ISC_CC_GR_GG, GENMASK(27, 16),
+ ((u32)ctrls->cc_coeff[4] & m) << 16);
+ regmap_update_bits(regmap, ISC_CC_GB_OG, m,
+ (u32)ctrls->cc_coeff[5] & m);
+ regmap_update_bits(regmap, ISC_CC_GB_OG, GENMASK(27, 16),
+ ((u32)ctrls->cc_offset[1] & m) << 16);
+ regmap_update_bits(regmap, ISC_CC_BR_BG, m,
+ (u32)ctrls->cc_coeff[6] & m);
+ regmap_update_bits(regmap, ISC_CC_BR_BG, GENMASK(27, 16),
+ ((u32)ctrls->cc_coeff[7] & m) << 16);
+ regmap_update_bits(regmap, ISC_CC_BB_OB, m,
+ (u32)ctrls->cc_coeff[8] & m);
+ regmap_update_bits(regmap, ISC_CC_BB_OB, GENMASK(27, 16),
+ ((u32)ctrls->cc_offset[2] & m) << 16);
+
+ ctrls->cc_dirty = false;
+}
+
static inline void isc_update_awb_ctrls(struct isc_device *isc)
{
struct isc_ctrls *ctrls = &isc->ctrls;
@@ -90,6 +130,14 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc)
/* offsets are in 2's complements */
isc->ctrls.offset[c] = 0;
}
+
+ /* identity matrix: diagonal = 1.0 in Q4.8 = 256, off-diagonal = 0 */
+ memset(isc->ctrls.cc_coeff, 0, sizeof(isc->ctrls.cc_coeff));
+ isc->ctrls.cc_coeff[0] = 256; /* RR */
+ isc->ctrls.cc_coeff[4] = 256; /* GG */
+ isc->ctrls.cc_coeff[8] = 256; /* BB */
+ memset(isc->ctrls.cc_offset, 0, sizeof(isc->ctrls.cc_offset));
+ isc->ctrls.cc_dirty = false;
}
static int isc_queue_setup(struct vb2_queue *vq,
@@ -235,7 +283,8 @@ static void isc_set_pipeline(struct isc_device *isc, u32 pipeline)
isc->config_dpc(isc);
isc->config_csc(isc);
isc->config_cbc(isc);
- isc->config_cc(isc);
+ /* use shadow; config_cc() always resets to identity */
+ isc_update_cc_ctrls(isc);
isc->config_gam(isc);
}
@@ -1481,6 +1530,8 @@ static void isc_awb_work(struct work_struct *w)
goto out_pm_put;
}
+ /* write pending CC matrix from shadow to hardware registers */
+ isc_update_cc_ctrls(isc);
isc_update_profile(isc);
mutex_unlock(&isc->awb_mutex);
@@ -1659,6 +1710,161 @@ static int isc_g_volatile_awb_ctrl(struct v4l2_ctrl *ctrl)
return 0;
}
+static int isc_cc_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;
+
+ dev_dbg(isc->dev, "id = 0x%x; val = 0x%x", ctrl->id, ctrl->val);
+
+ /*
+ * CC registers need pm_runtime active for access.
+ * Store to shadow here; isc_update_cc_ctrls() writes to hardware
+ * from isc_awb_work() where ISC is powered.
+ */
+ switch (ctrl->id) {
+ case ISC_CID_CC_RR:
+ ctrls->cc_coeff[0] = ctrl->val;
+ break;
+ case ISC_CID_CC_RG:
+ ctrls->cc_coeff[1] = ctrl->val;
+ break;
+ case ISC_CID_CC_RB:
+ ctrls->cc_coeff[2] = ctrl->val;
+ break;
+ case ISC_CID_CC_OR:
+ ctrls->cc_offset[0] = ctrl->val;
+ break;
+ case ISC_CID_CC_GR:
+ ctrls->cc_coeff[3] = ctrl->val;
+ break;
+ case ISC_CID_CC_GG:
+ ctrls->cc_coeff[4] = ctrl->val;
+ break;
+ case ISC_CID_CC_GB:
+ ctrls->cc_coeff[5] = ctrl->val;
+ break;
+ case ISC_CID_CC_OG:
+ ctrls->cc_offset[1] = ctrl->val;
+ break;
+ case ISC_CID_CC_BR:
+ ctrls->cc_coeff[6] = ctrl->val;
+ break;
+ case ISC_CID_CC_BG:
+ ctrls->cc_coeff[7] = ctrl->val;
+ break;
+ case ISC_CID_CC_BB:
+ ctrls->cc_coeff[8] = ctrl->val;
+ break;
+ case ISC_CID_CC_OB:
+ ctrls->cc_offset[2] = ctrl->val;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ctrls->cc_dirty = true;
+ return 0;
+}
+
+static int isc_cc_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct isc_device *isc = container_of(ctrl->handler,
+ struct isc_device, ctrls.handler);
+ struct regmap *regmap = isc->regmap;
+ unsigned int reg;
+
+ switch (ctrl->id) {
+ case ISC_CID_CC_RR:
+ regmap_read(regmap, ISC_CC_RR_RG, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_RG:
+ regmap_read(regmap, ISC_CC_RR_RG, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_RB:
+ regmap_read(regmap, ISC_CC_RB_OR, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OR:
+ regmap_read(regmap, ISC_CC_RB_OR, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_GR:
+ regmap_read(regmap, ISC_CC_GR_GG, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_GG:
+ regmap_read(regmap, ISC_CC_GR_GG, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_GB:
+ regmap_read(regmap, ISC_CC_GB_OG, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OG:
+ regmap_read(regmap, ISC_CC_GB_OG, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_BR:
+ regmap_read(regmap, ISC_CC_BR_BG, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_BG:
+ regmap_read(regmap, ISC_CC_BR_BG, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_BB:
+ regmap_read(regmap, ISC_CC_BB_OB, ®);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OB:
+ regmap_read(regmap, ISC_CC_BB_OB, ®);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(isc->dev, "id = 0x%x; val = 0x%x", ctrl->id, ctrl->val);
+
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops isc_cc_ops = {
+ .s_ctrl = isc_cc_s_ctrl,
+ .g_volatile_ctrl = isc_cc_g_volatile_ctrl,
+};
+
+#define ISC_CTRL_CC(_name, _id, _name_str, _def) \
+ static const struct v4l2_ctrl_config _name = { \
+ .ops = &isc_cc_ops, \
+ .id = _id, \
+ .name = _name_str, \
+ .type = V4L2_CTRL_TYPE_INTEGER, \
+ .flags = V4L2_CTRL_FLAG_SLIDER | V4L2_CTRL_FLAG_VOLATILE | \
+ V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, \
+ .min = -2048, \
+ .max = 2047, \
+ .step = 1, \
+ .def = _def, \
+ }
+
+ISC_CTRL_CC(isc_cc_rr_ctrl, ISC_CID_CC_RR, "CC RR", 256);
+ISC_CTRL_CC(isc_cc_rg_ctrl, ISC_CID_CC_RG, "CC RG", 0);
+ISC_CTRL_CC(isc_cc_rb_ctrl, ISC_CID_CC_RB, "CC RB", 0);
+ISC_CTRL_CC(isc_cc_or_ctrl, ISC_CID_CC_OR, "CC OR", 0);
+ISC_CTRL_CC(isc_cc_gr_ctrl, ISC_CID_CC_GR, "CC GR", 0);
+ISC_CTRL_CC(isc_cc_gg_ctrl, ISC_CID_CC_GG, "CC GG", 256);
+ISC_CTRL_CC(isc_cc_gb_ctrl, ISC_CID_CC_GB, "CC GB", 0);
+ISC_CTRL_CC(isc_cc_og_ctrl, ISC_CID_CC_OG, "CC OG", 0);
+ISC_CTRL_CC(isc_cc_br_ctrl, ISC_CID_CC_BR, "CC BR", 0);
+ISC_CTRL_CC(isc_cc_bg_ctrl, ISC_CID_CC_BG, "CC BG", 0);
+ISC_CTRL_CC(isc_cc_bb_ctrl, ISC_CID_CC_BB, "CC BB", 256);
+ISC_CTRL_CC(isc_cc_ob_ctrl, ISC_CID_CC_OB, "CC OB", 0);
+
static const struct v4l2_ctrl_ops isc_awb_ops = {
.s_ctrl = isc_s_awb_ctrl,
.g_volatile_ctrl = isc_g_volatile_awb_ctrl,
@@ -1700,6 +1906,29 @@ ISC_CTRL_GAIN(isc_b_gain_ctrl, ISC_CID_B_GAIN, "Blue Component Gain");
ISC_CTRL_GAIN(isc_gr_gain_ctrl, ISC_CID_GR_GAIN, "Green Red Component Gain");
ISC_CTRL_GAIN(isc_gb_gain_ctrl, ISC_CID_GB_GAIN, "Green Blue Component Gain");
+/*
+ * Per-channel gamma LUT controls (64-element U32 arrays, range 0-1023).
+ * Setting any of these activates the custom tone curve and overrides the
+ * preset V4L2_CID_GAMMA curve. One macro expands to a static v4l2_ctrl_config.
+ */
+#define ISC_CTRL_GAMMA_LUT(_name, _id, _name_str) \
+ static const struct v4l2_ctrl_config _name = { \
+ .ops = &isc_ctrl_ops, \
+ .id = _id, \
+ .name = _name_str, \
+ .type = V4L2_CTRL_TYPE_U32, \
+ .flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE, \
+ .dims = { GAMMA_ENTRIES }, \
+ .min = 0, \
+ .max = 1023, \
+ .step = 1, \
+ .def = 0, \
+ }
+
+ISC_CTRL_GAMMA_LUT(isc_gamma_b_lut_ctrl, ISC_CID_GAMMA_B_LUT, "Blue Gamma LUT");
+ISC_CTRL_GAMMA_LUT(isc_gamma_g_lut_ctrl, ISC_CID_GAMMA_G_LUT, "Green Gamma LUT");
+ISC_CTRL_GAMMA_LUT(isc_gamma_r_lut_ctrl, ISC_CID_GAMMA_R_LUT, "Red Gamma LUT");
+
static int isc_ctrl_init(struct isc_device *isc)
{
const struct v4l2_ctrl_ops *ops = &isc_ctrl_ops;
@@ -1752,6 +1981,20 @@ static int isc_ctrl_init(struct isc_device *isc)
isc->gr_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_off_ctrl, NULL);
isc->gb_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_off_ctrl, NULL);
+ /* Color correction control */
+ isc->cc_rr = v4l2_ctrl_new_custom(hdl, &isc_cc_rr_ctrl, NULL);
+ isc->cc_rg = v4l2_ctrl_new_custom(hdl, &isc_cc_rg_ctrl, NULL);
+ isc->cc_rb = v4l2_ctrl_new_custom(hdl, &isc_cc_rb_ctrl, NULL);
+ isc->cc_or = v4l2_ctrl_new_custom(hdl, &isc_cc_or_ctrl, NULL);
+ isc->cc_gr = v4l2_ctrl_new_custom(hdl, &isc_cc_gr_ctrl, NULL);
+ isc->cc_gg = v4l2_ctrl_new_custom(hdl, &isc_cc_gg_ctrl, NULL);
+ isc->cc_gb = v4l2_ctrl_new_custom(hdl, &isc_cc_gb_ctrl, NULL);
+ isc->cc_og = v4l2_ctrl_new_custom(hdl, &isc_cc_og_ctrl, NULL);
+ isc->cc_br = v4l2_ctrl_new_custom(hdl, &isc_cc_br_ctrl, NULL);
+ isc->cc_bg = v4l2_ctrl_new_custom(hdl, &isc_cc_bg_ctrl, NULL);
+ 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);
+
/*
* 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 2c8bcaaa26ea..db651c9f1387 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -134,6 +134,12 @@ enum{
HIST_DISABLED,
};
+#define GAMMA_ENTRIES 64
+
+/* CC matrix coefficients (3x3 row-major) and per-channel offsets */
+#define ISC_CC_COEFF_NUM 9
+#define ISC_CC_OFFSET_NUM 3
+
struct isc_ctrls {
struct v4l2_ctrl_handler handler;
@@ -158,6 +164,11 @@ struct isc_ctrls {
#define HIST_MIN_INDEX 0
#define HIST_MAX_INDEX 1
u32 hist_minmax[HIST_BAYER][2];
+
+ /* 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];
+ bool cc_dirty;
};
#define ISC_PIPE_LINE_NODE_NUM 15
@@ -338,6 +349,18 @@ struct isc_device {
struct v4l2_ctrl *b_off_ctrl;
struct v4l2_ctrl *gr_off_ctrl;
struct v4l2_ctrl *gb_off_ctrl;
+ struct v4l2_ctrl *cc_rr;
+ struct v4l2_ctrl *cc_rg;
+ struct v4l2_ctrl *cc_rb;
+ struct v4l2_ctrl *cc_or;
+ struct v4l2_ctrl *cc_gr;
+ struct v4l2_ctrl *cc_gg;
+ struct v4l2_ctrl *cc_gb;
+ struct v4l2_ctrl *cc_og;
+ struct v4l2_ctrl *cc_br;
+ struct v4l2_ctrl *cc_bg;
+ struct v4l2_ctrl *cc_bb;
+ struct v4l2_ctrl *cc_ob;
};
#define GAMMA_ENTRIES 64
diff --git a/include/linux/atmel-isc-media.h b/include/linux/atmel-isc-media.h
index 79a320fb724e..028d34c8de81 100644
--- a/include/linux/atmel-isc-media.h
+++ b/include/linux/atmel-isc-media.h
@@ -53,6 +53,19 @@ enum atmel_isc_ctrl_id {
ISC_CID_GR_OFFSET,
/* Green Blue component offset control */
ISC_CID_GB_OFFSET,
+ /* Color correction registers */
+ ISC_CID_CC_RR,
+ ISC_CID_CC_RG,
+ ISC_CID_CC_RB,
+ ISC_CID_CC_OR,
+ ISC_CID_CC_GR,
+ ISC_CID_CC_GG,
+ ISC_CID_CC_GB,
+ ISC_CID_CC_OG,
+ ISC_CID_CC_BR,
+ ISC_CID_CC_BG,
+ ISC_CID_CC_BB,
+ ISC_CID_CC_OB,
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (9 preceding siblings ...)
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
2026-05-12 15:43 ` [PATCH v2 12/15] media: microchip-isc: reset pipeline state on kernel AWB enable Balakrishnan Sambath
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
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
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 12/15] media: microchip-isc: reset pipeline state on kernel AWB enable
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (10 preceding siblings ...)
2026-05-12 15:43 ` [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls Balakrishnan Sambath
@ 2026-05-12 15:43 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 13/15] media: microchip-isc: use weighted averages for Grey World AWB Balakrishnan Sambath
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
gamma_lut_override and cc_coeff[] persist across control writes. When
kernel AWB is enabled after userspace has customized the gamma curve or
color correction matrix, the stale settings produce incorrect color.
Clear gamma_lut_override and reset cc_coeff[] to identity when
V4L2_CID_AUTO_WHITE_BALANCE is set to 1.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 3749f473c3c6..e6386f8852e5 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -1730,10 +1730,22 @@ static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
switch (ctrl->id) {
case V4L2_CID_AUTO_WHITE_BALANCE:
- if (ctrl->val == 1)
+ if (ctrl->val == 1) {
ctrls->awb = ISC_WB_AUTO;
- else
+ /*
+ * Reset gamma and CC to defaults when enabling kernel
+ * AWB so it starts with a clean pipeline.
+ */
+ ctrls->gamma_lut_override = false;
+ memset(ctrls->cc_coeff, 0, sizeof(ctrls->cc_coeff));
+ ctrls->cc_coeff[0] = 256; /* RR */
+ ctrls->cc_coeff[4] = 256; /* GG */
+ ctrls->cc_coeff[8] = 256; /* BB */
+ memset(ctrls->cc_offset, 0, sizeof(ctrls->cc_offset));
+ ctrls->cc_dirty = true;
+ } else {
ctrls->awb = ISC_WB_NONE;
+ }
/* configure the controls with new values from v4l2 */
if (ctrl->cluster[ISC_CTRL_R_GAIN]->is_new)
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 13/15] media: microchip-isc: use weighted averages for Grey World AWB
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (11 preceding siblings ...)
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 ` 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
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
Replace pixel counts with intensity-weighted averages. Add 2% outlier
rejection at histogram tails.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 167 +++++++++++++-----
.../media/platform/microchip/microchip-isc.h | 2 +
2 files changed, 125 insertions(+), 44 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index e6386f8852e5..8fcf64708dc8 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -40,6 +40,12 @@
(((mbus_code) == MEDIA_BUS_FMT_Y10_1X10) | \
(((mbus_code) == MEDIA_BUS_FMT_Y8_1X8)))
+/* 4.0 in Q9 fixed-point: cap grey-world correction at 4x. */
+#define ISC_AWB_GW_GAIN_MAX (4u << 9)
+
+/* Outlier rejection: skip darkest/brightest 2% of histogram. */
+#define ISC_AWB_OUTLIER_DIV 50
+
static inline void isc_update_v4l2_ctrls(struct isc_device *isc)
{
struct isc_ctrls *ctrls = &isc->ctrls;
@@ -1404,6 +1410,11 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
u32 *hist_count = &ctrls->hist_count[ctrls->hist_id];
u32 *hist_entry = &ctrls->hist_entry[0];
u32 i;
+ u32 total_pixels;
+ u32 dark_threshold, bright_threshold;
+ u32 cumulative;
+ u64 weighted_sum;
+ u32 pixel_count;
*min = 0;
*max = HIST_ENTRIES;
@@ -1411,44 +1422,103 @@ static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max)
regmap_bulk_read(regmap, ISC_HIS_ENTRY + isc->offsets.his_entry,
hist_entry, HIST_ENTRIES);
- *hist_count = 0;
- /*
- * we deliberately ignore the end of the histogram,
- * the most white pixels
- */
+ /* Calculate total pixels */
+ total_pixels = 0;
+ for (i = 0; i < HIST_ENTRIES; i++)
+ total_pixels += hist_entry[i];
+
+ /* Handle empty histogram case */
+ if (total_pixels == 0) {
+ *hist_count = 0;
+ ctrls->channel_avg[ctrls->hist_id] = 256; /* Default middle value */
+ ctrls->total_pixels[ctrls->hist_id] = 0;
+ *min = 1;
+ *max = HIST_ENTRIES - 1;
+ dev_dbg(isc->dev,
+ "isc wb: no pixels in histogram for channel %u\n",
+ ctrls->hist_id);
+ return;
+ }
+
+ /* Outlier rejection: skip darkest/brightest 2% of histogram */
+ dark_threshold = total_pixels / ISC_AWB_OUTLIER_DIV;
+ bright_threshold = total_pixels / ISC_AWB_OUTLIER_DIV;
+ cumulative = 0;
+
+ /* Find effective minimum (skip dark noise) */
+ *min = 1;
for (i = 1; i < HIST_ENTRIES; i++) {
- if (*hist_entry && !*min)
+ cumulative += hist_entry[i];
+ if (cumulative > dark_threshold) {
*min = i;
- if (*hist_entry)
+ break;
+ }
+ }
+
+ /* Find effective maximum (skip bright saturation) */
+ cumulative = 0;
+ *max = HIST_ENTRIES - 1;
+ for (i = HIST_ENTRIES - 1; i > *min; i--) {
+ cumulative += hist_entry[i];
+ if (cumulative > bright_threshold) {
*max = i;
- *hist_count += i * (*hist_entry++);
+ break;
+ }
}
+ /* Ensure reasonable range */
+ if (*max <= *min) {
+ *min = HIST_ENTRIES / 4;
+ *max = (HIST_ENTRIES * 3) / 4;
+ }
+
+ /* Calculate both pixel count and weighted average for useful range */
+ *hist_count = 0;
+ weighted_sum = 0;
+
+ for (i = *min; i <= *max; i++) {
+ pixel_count = hist_entry[i];
+ *hist_count += pixel_count;
+ weighted_sum += (u64)i * pixel_count;
+ }
+
+ /* Store total useful pixels for this channel */
+ ctrls->total_pixels[ctrls->hist_id] = *hist_count;
+
+ /* Calculate channel average */
+ if (*hist_count > 0)
+ ctrls->channel_avg[ctrls->hist_id] =
+ div64_u64(weighted_sum, *hist_count);
+ else
+ /* Default middle value */
+ ctrls->channel_avg[ctrls->hist_id] = 256;
+
if (!*min)
*min = 1;
- dev_dbg(isc->dev, "isc wb: hist_id %u, hist_count %u",
- ctrls->hist_id, *hist_count);
+ dev_dbg(isc->dev,
+ "isc wb: hist_id %u, avg %u, count %u, range [%u,%u], total %u\n",
+ ctrls->hist_id, ctrls->channel_avg[ctrls->hist_id],
+ *hist_count, *min, *max, total_pixels);
}
static void isc_wb_update(struct isc_ctrls *ctrls)
{
struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls);
- u32 *hist_count = &ctrls->hist_count[0];
u32 c, offset[4];
u64 avg = 0;
- /* We compute two gains, stretch gain and grey world gain */
- u32 s_gain[4], gw_gain[4];
+ u32 gain, gw_gain, s_gain;
+ u32 min_pixels;
+ u32 frame_pixels;
/*
* According to Grey World, we need to set gains for R/B to normalize
* them towards the green channel.
- * Thus we want to keep Green as fixed and adjust only Red/Blue
- * Compute the average of the both green channels first
+ * Thus we want to keep Green as fixed and adjust only Red/Blue.
+ * Compute the average of the both green channels first.
*/
- avg = (u64)hist_count[ISC_HIS_CFG_MODE_GR] +
- (u64)hist_count[ISC_HIS_CFG_MODE_GB];
- avg >>= 1;
+ avg = (ctrls->channel_avg[ISC_HIS_CFG_MODE_GR] +
+ ctrls->channel_avg[ISC_HIS_CFG_MODE_GB]) >> 1;
dev_dbg(isc->dev, "isc wb: green components average %llu\n", avg);
@@ -1456,7 +1526,23 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
if (!avg)
return;
+ /*
+ * Require a minimum pixel count for both black-level offset and
+ * grey-world gain: 1/64 of the frame area, which equals ~6.25% of
+ * one Bayer channel's expected pixel count. This scales with sensor
+ * resolution and prevents noise-dominated histograms (from very small
+ * crops or a nearly-empty frame) from producing wild corrections.
+ * A floor of 64 ensures the guard is non-zero for tiny crops.
+ */
+ frame_pixels = isc->fmt.fmt.pix.width * isc->fmt.fmt.pix.height;
+ min_pixels = frame_pixels ? max(frame_pixels >> 6, 64u) : 64u;
+
for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) {
+ u32 hist_min = ctrls->hist_minmax[c][HIST_MIN_INDEX];
+ u32 hist_max = ctrls->hist_minmax[c][HIST_MAX_INDEX];
+ u32 channel_avg = ctrls->channel_avg[c];
+ u32 total_pixels = ctrls->total_pixels[c];
+
/*
* the color offset is the minimum value of the histogram.
* we stretch this color to the full range by substracting
@@ -1482,40 +1568,33 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
ctrls->offset[c] = -ctrls->offset[c];
/*
- * the stretch gain is the total number of histogram bins
- * divided by the actual range of color component (Max - Min)
- * If we compute gain like this, the actual color component
- * will be stretched to the full histogram.
- * We need to shift 9 bits for precision, we have 9 bits for
- * decimals
+ * Stretch gain: scale the histogram range [hist_min, hist_max]
+ * to the full 512-bin span. Result is in Q9 fixed-point
+ * (1.0 = 512).
*/
- s_gain[c] = (HIST_ENTRIES << 9) /
- (ctrls->hist_minmax[c][HIST_MAX_INDEX] -
- ctrls->hist_minmax[c][HIST_MIN_INDEX] + 1);
+ s_gain = (HIST_ENTRIES << 9) / (hist_max - hist_min + 1);
/*
- * Now we have to compute the gain w.r.t. the average.
- * Add/lose gain to the component towards the average.
- * If it happens that the component is zero, use the
- * fixed point value : 1.0 gain.
+ * Grey-world gain: scale each channel towards the green
+ * average. Require a minimum pixel count so noise-dominated
+ * channels do not produce wild corrections.
*/
- if (hist_count[c])
- gw_gain[c] = div_u64(avg << 9, hist_count[c]);
+ if (channel_avg > 0 && total_pixels >= min_pixels)
+ gw_gain = div64_u64((avg << 9), channel_avg);
else
- gw_gain[c] = 1 << 9;
+ gw_gain = 1 << 9;
- dev_dbg(isc->dev,
- "isc wb: component %d, s_gain %u, gw_gain %u\n",
- c, s_gain[c], gw_gain[c]);
- /* multiply both gains and adjust for decimals */
- ctrls->gain[c] = s_gain[c] * gw_gain[c];
- ctrls->gain[c] >>= 9;
+ /* Cap grey-world correction at 4x to avoid over-amplification. */
+ gw_gain = min_t(u32, gw_gain, ISC_AWB_GW_GAIN_MAX);
- /* make sure we are not out of range */
- ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0));
+ /* Combine stretch and grey-world gains; result stays in Q9. */
+ gain = (s_gain * gw_gain) >> 9;
- dev_dbg(isc->dev, "isc wb: component %d, final gain %u\n",
- c, ctrls->gain[c]);
+ ctrls->gain[c] = clamp_val(gain, 0, GENMASK(12, 0));
+
+ dev_dbg(isc->dev,
+ "isc wb: c=%u black=%u avg=%u s_gain=%u gw_gain=%u gain=%u",
+ c, hist_min, channel_avg, s_gain, gw_gain, gain);
}
}
diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h
index a4f1e6c22e44..44d54404250d 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -164,6 +164,8 @@ struct isc_ctrls {
#define HIST_MIN_INDEX 0
#define HIST_MAX_INDEX 1
u32 hist_minmax[HIST_BAYER][2];
+ u32 channel_avg[HIST_BAYER]; /* Average pixel intensity per channel */
+ u32 total_pixels[HIST_BAYER]; /* Total pixels per channel */
/*
* Custom per-channel gamma LUT (10-bit output values, 64 entries).
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 14/15] media: microchip-isc: smooth AWB gains with EMA filter
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (12 preceding siblings ...)
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 ` Balakrishnan Sambath
2026-05-12 15:43 ` [PATCH v2 15/15] media: microchip-isc: scale DPC black level to sensor bit depth Balakrishnan Sambath
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
Apply exponential moving average (alpha=0.25) to reduce per-frame
flicker from sensor noise.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 19 ++++++++++++++++---
.../media/platform/microchip/microchip-isc.h | 1 +
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 8fcf64708dc8..072386aa2d73 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -134,6 +134,7 @@ static inline void isc_reset_awb_ctrls(struct isc_device *isc)
for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) {
/* gains have a fixed point at 9 decimals */
isc->ctrls.gain[c] = 1 << 9;
+ isc->ctrls.gain_smooth[c] = 1 << 9;
/* offsets are in 2's complements */
isc->ctrls.offset[c] = 0;
}
@@ -1590,11 +1591,23 @@ static void isc_wb_update(struct isc_ctrls *ctrls)
/* Combine stretch and grey-world gains; result stays in Q9. */
gain = (s_gain * gw_gain) >> 9;
- ctrls->gain[c] = clamp_val(gain, 0, GENMASK(12, 0));
+ /*
+ * Smooth gain updates with an exponential weighted average
+ * to suppress per-frame flicker:
+ * smooth[n] = (3 * smooth[n-1] + gain) / 4
+ * Clamp to the hardware register width to prevent unbounded
+ * accumulation under degenerate (near-empty histogram) inputs.
+ */
+ ctrls->gain_smooth[c] = (3 * ctrls->gain_smooth[c] + gain) / 4;
+ ctrls->gain_smooth[c] = min_t(u32, ctrls->gain_smooth[c],
+ GENMASK(12, 0));
+
+ ctrls->gain[c] = ctrls->gain_smooth[c];
dev_dbg(isc->dev,
- "isc wb: c=%u black=%u avg=%u s_gain=%u gw_gain=%u gain=%u",
- c, hist_min, channel_avg, s_gain, gw_gain, gain);
+ "isc wb: c=%u black=%u avg=%u s_gain=%u gw_gain=%u gain=%u smooth=%u\n",
+ c, hist_min, channel_avg, s_gain, gw_gain, gain,
+ ctrls->gain_smooth[c]);
}
}
diff --git a/drivers/media/platform/microchip/microchip-isc.h b/drivers/media/platform/microchip/microchip-isc.h
index 44d54404250d..e558f1a65b33 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -155,6 +155,7 @@ struct isc_ctrls {
/* one for each component : GR, R, GB, B */
u32 gain[HIST_BAYER];
+ u32 gain_smooth[HIST_BAYER];
s32 offset[HIST_BAYER];
u32 hist_entry[HIST_ENTRIES];
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 15/15] media: microchip-isc: scale DPC black level to sensor bit depth
2026-05-12 15:43 ` [PATCH v2 00/15] media: microchip-isc: fixes and enhancements Balakrishnan Sambath
` (13 preceding siblings ...)
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 ` Balakrishnan Sambath
14 siblings, 0 replies; 16+ messages in thread
From: Balakrishnan Sambath @ 2026-05-12 15:43 UTC (permalink / raw)
To: linux-media; +Cc: eugen.hristev, mchehab, hverkuil, nicolas.ferre, linux-kernel
Scale the nominal 10-bit black level (64 counts) to match 8/10/12-bit
sensor bus width.
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@microchip.com>
---
.../microchip/microchip-sama7g5-isc.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index 9110690a49e4..46a721d76453 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -26,6 +26,7 @@
* HIS: Histogram module performs statistic counters on the frames
*/
+#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk-provider.h>
@@ -289,9 +290,25 @@ static void isc_sama7g5_config_dpc(struct isc_device *isc)
{
u32 bay_cfg = isc->config.sd_format->cfa_baycfg;
struct regmap *regmap = isc->regmap;
+ u32 bps, bloff;
+
+ /*
+ * Scale the nominal 10-bit black level offset (64 counts) to the
+ * actual sensor bus width.
+ * ISC_PFE_CFG0_BPS encodes (12 - bit_depth) / 2 in bits[30:28]:
+ * BPS_EIGHT = 4 -> 8-bit -> bloff = 64 >> 2 = 16
+ * BPS_TEN = 2 -> 10-bit -> bloff = 64
+ * BPS_TWELVE = 0 -> 12-bit -> bloff = min(64 << 2, 255) = 255
+ * The BLOFF hardware field is 8-bit so values are clamped to 255.
+ */
+ bps = FIELD_GET(ISC_PFE_CFG0_BPS_MASK, isc->config.sd_format->pfe_cfg0_bps);
+ if (bps >= 2)
+ bloff = 64u >> (bps - 2);
+ else
+ bloff = min(64u << (2 - bps), 255u);
regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BLOFF_MASK,
- (64 << ISC_DPC_CFG_BLOFF_SHIFT));
+ (bloff << ISC_DPC_CFG_BLOFF_SHIFT));
regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BAYCFG_MASK,
(bay_cfg << ISC_DPC_CFG_BAYCFG_SHIFT));
}
--
2.34.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-05-12 15:44 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[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 ` [PATCH v2 11/15] media: microchip-isc: add per-channel gamma LUT controls Balakrishnan Sambath
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox