[PATCH v6 09/12] media: microchip-isc: add SAMA7G5 hue and saturation controls
From: Balakrishnan Sambath
Date: Wed Jun 03 2026 - 03:09:55 EST
The CBHS (Contrast/Brightness/Hue/Saturation) block on SAMA7G5
operates in YCbCr space; expose hue and saturation as V4L2 controls.
Hue and saturation act on chroma, so they are active only when the
output format is YUV. The SAMA5D2 has only the CBC block (no
hue/saturation), so the controls are gated on a new has_cbhs flag.
Saturation uses the Q4 fixed-point range 0..127 with default 16
(1.0x) directly matching the CBHS_SAT register field. The control
state is initialised to neutral at probe so the first config_cbc()
write after streaming starts does not produce a grayscale image.
Co-developed-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@xxxxxxxxxxxxx>
Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@xxxxxxxxxxxxx>
Signed-off-by: Balakrishnan Sambath <balakrishnan.s@xxxxxxxxxxxxx>
---
.../media/platform/microchip/microchip-isc-base.c | 70 +++++++++++++++++++++-
.../media/platform/microchip/microchip-isc-regs.h | 11 ++--
drivers/media/platform/microchip/microchip-isc.h | 7 +++
.../platform/microchip/microchip-sama5d2-isc.c | 3 +-
.../platform/microchip/microchip-sama7g5-isc.c | 9 +--
5 files changed, 90 insertions(+), 10 deletions(-)
diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 04187127070d..3a941757906a 100644
--- a/drivers/media/platform/microchip/microchip-isc-base.c
+++ b/drivers/media/platform/microchip/microchip-isc-base.c
@@ -859,6 +859,46 @@ static int isc_try_configure_pipeline(struct isc_device *isc)
return 0;
}
+static bool isc_format_is_yuv(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_cbhs_ctrls() - Activate/deactivate CBHS 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 CBHS 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. Hue and
+ * saturation operate in YCbCr space, so they activate only when the
+ * output format is YUV.
+ */
+static void isc_update_cbhs_ctrls(struct isc_device *isc)
+{
+ bool cbhs_active = isc->config.bits_pipeline & CBHS_ENABLE;
+ bool chroma_active = cbhs_active && isc_format_is_yuv(isc->config.fourcc);
+
+ if (isc->brightness_ctrl)
+ v4l2_ctrl_activate(isc->brightness_ctrl, cbhs_active);
+ if (isc->contrast_ctrl)
+ v4l2_ctrl_activate(isc->contrast_ctrl, cbhs_active);
+ if (isc->hue_ctrl)
+ v4l2_ctrl_activate(isc->hue_ctrl, chroma_active);
+ if (isc->saturation_ctrl)
+ v4l2_ctrl_activate(isc->saturation_ctrl, 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 +942,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_cbhs_ctrls(isc);
dev_dbg(isc->dev, "ISC set_fmt to %.4s @%dx%d\n",
(char *)&f->fmt.pix.pixelformat,
@@ -989,6 +1030,7 @@ static int isc_link_validate(struct media_link *link)
return ret;
isc->config = isc->try_config;
+ isc_update_cbhs_ctrls(isc);
dev_dbg(isc->dev, "New ISC configuration in place\n");
@@ -1457,6 +1499,14 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
case V4L2_CID_CONTRAST:
ctrls->contrast = ctrl->val & ISC_CBC_CONTRAST_MASK;
break;
+ case V4L2_CID_HUE:
+ if (isc->has_cbhs)
+ ctrls->hue = ctrl->val & ISC_CBHS_HUE_MASK;
+ break;
+ case V4L2_CID_SATURATION:
+ if (isc->has_cbhs)
+ ctrls->saturation = ctrl->val & ISC_CBHS_SAT_MASK;
+ break;
case V4L2_CID_GAMMA:
ctrls->gamma_index = ctrl->val;
break;
@@ -1646,7 +1696,24 @@ 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);
+ isc->brightness_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS,
+ -1024, 1023, 1, 0);
+ if (isc->has_cbhs) {
+ /*
+ * CBHS_HUE is a signed 9-bit value in degrees.
+ * CBHS_SAT is Q4 unsigned 7-bit, 16 = 1.0x.
+ * Initialize the kernel-side state to neutral here so the
+ * first config_cbc() call after streaming starts does not
+ * write zero (grayscale) to the hardware.
+ */
+ ctrls->hue = 0;
+ ctrls->saturation = 16;
+ isc->hue_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_HUE,
+ -180, 180, 1, 0);
+ isc->saturation_ctrl = v4l2_ctrl_new_std(hdl, ops,
+ V4L2_CID_SATURATION,
+ 0, 127, 1, 16);
+ }
v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1,
isc->gamma_default);
isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops,
@@ -1665,6 +1732,7 @@ static int isc_ctrl_init(struct isc_device *isc)
}
v4l2_ctrl_activate(isc->do_wb_ctrl, false);
+ isc_update_cbhs_ctrls(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..7f5c2e50e74b 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..127) */
+#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 2282ef7dd596..1ecefe990f00 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -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
@@ -336,6 +338,10 @@ struct isc_device {
struct v4l2_ctrl *b_off_ctrl;
struct v4l2_ctrl *gr_off_ctrl;
struct v4l2_ctrl *gb_off_ctrl;
+ struct v4l2_ctrl *brightness_ctrl;
+ struct v4l2_ctrl *contrast_ctrl;
+ struct v4l2_ctrl *hue_ctrl;
+ struct v4l2_ctrl *saturation_ctrl;
};
#define GAMMA_ENTRIES 64
@@ -343,6 +349,7 @@ struct isc_device {
const u32 (*gamma_table)[GAMMA_ENTRIES];
u32 gamma_max;
u32 gamma_default;
+ 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 9fa8413c74c7..4b8279fba560 100644
--- a/drivers/media/platform/microchip/microchip-sama5d2-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama5d2-isc.c
@@ -264,7 +264,8 @@ static void isc_sama5d2_config_ctrls(struct isc_device *isc,
ctrls->contrast = 256;
- v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 256);
+ isc->contrast_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST,
+ -2048, 2047, 1, 256);
}
static void isc_sama5d2_config_dpc(struct isc_device *isc)
diff --git a/drivers/media/platform/microchip/microchip-sama7g5-isc.c b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
index ac21fe1dade0..e6ccdd465805 100644
--- a/drivers/media/platform/microchip/microchip-sama7g5-isc.c
+++ b/drivers/media/platform/microchip/microchip-sama7g5-isc.c
@@ -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)
@@ -283,7 +282,8 @@ static void isc_sama7g5_config_ctrls(struct isc_device *isc,
ctrls->contrast = 16;
- v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 16);
+ isc->contrast_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST,
+ -2048, 2047, 1, 16);
}
static void isc_sama7g5_config_dpc(struct isc_device *isc)
@@ -463,6 +463,7 @@ static int microchip_xisc_probe(struct platform_device *pdev)
isc->gamma_max = 2;
/* Index 1 in the SAMA7G5 table is gamma 1/2.2 (sRGB). */
isc->gamma_default = 1;
+ isc->has_cbhs = true;
if (of_machine_is_compatible("microchip,sam9x7")) {
isc->max_width = ISC_SAM9X7_MAX_SUPPORT_WIDTH;
--
2.34.1