[PATCH 18/18] media: microchip-isc: expose color correction registers as v4l2 controls

Balamanikandan Gunasundar
2025-10-09T15:55:53Z

Enable the Color correction registers as v4l2 controls. Applications such
as libcamera can read and write the elements of color correction matrix
through the standard v4l2 control API.

Signed-off-by: Balamanikandan Gunasundar <balamanikandan.gunasundar@microchip.com>
---
.../platform/microchip/microchip-isc-base.c | 175 +++++++++++++++++-
.../media/platform/microchip/microchip-isc.h | 12 ++
include/linux/atmel-isc-media.h | 13 ++
3 files changed, 199 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/microchip/microchip-isc-base.c b/drivers/media/platform/microchip/microchip-isc-base.c
index 6c937a20fce0..e679dc02dc9f 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) \
@@ -1677,6 +1677,165 @@ 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 regmap *regmap = isc->regmap;
+
+ dev_dbg(isc->dev, "id = 0x%x; val = 0x%x", ctrl->id, ctrl->val);
+
+ switch (ctrl->id) {
+ case ISC_CID_CC_RR:
+ regmap_update_bits(regmap, ISC_CC_RR_RG, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_RG:
+ regmap_update_bits(regmap, ISC_CC_RR_RG, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ case ISC_CID_CC_RB:
+ regmap_update_bits(regmap, ISC_CC_RB_OR, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_OR:
+ regmap_update_bits(regmap, ISC_CC_RB_OR, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ case ISC_CID_CC_GR:
+ regmap_update_bits(regmap, ISC_CC_GR_GG, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_GG:
+ regmap_update_bits(regmap, ISC_CC_GR_GG, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ case ISC_CID_CC_GB:
+ regmap_update_bits(regmap, ISC_CC_GB_OG, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_OG:
+ regmap_update_bits(regmap, ISC_CC_GB_OG, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ case ISC_CID_CC_BR:
+ regmap_update_bits(regmap, ISC_CC_BR_BG, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_BG:
+ regmap_update_bits(regmap, ISC_CC_BR_BG, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ case ISC_CID_CC_BB:
+ regmap_update_bits(regmap, ISC_CC_BB_OB, GENMASK(11, 0), ctrl->val);
+ break;
+ case ISC_CID_CC_OB:
+ regmap_update_bits(regmap, ISC_CC_BB_OB, GENMASK(27, 16),
+ (ctrl->val & GENMASK(11, 0)) << 16);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ 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, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_RG:
+ regmap_read(regmap, ISC_CC_RR_RG, &reg);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_RB:
+ regmap_read(regmap, ISC_CC_RB_OR, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OR:
+ regmap_read(regmap, ISC_CC_RB_OR, &reg);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_GR:
+ regmap_read(regmap, ISC_CC_GR_GG, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_GG:
+ regmap_read(regmap, ISC_CC_GR_GG, &reg);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_GB:
+ regmap_read(regmap, ISC_CC_GB_OG, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OG:
+ regmap_read(regmap, ISC_CC_GB_OG, &reg);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_BR:
+ regmap_read(regmap, ISC_CC_BR_BG, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_BG:
+ regmap_read(regmap, ISC_CC_BR_BG, &reg);
+ ctrl->val = sign_extend32((reg & GENMASK(27, 16)) >> 16, 11);
+ break;
+ case ISC_CID_CC_BB:
+ regmap_read(regmap, ISC_CC_BB_OB, &reg);
+ ctrl->val = sign_extend32(reg & GENMASK(11, 0), 11);
+ break;
+ case ISC_CID_CC_OB:
+ regmap_read(regmap, ISC_CC_BB_OB, &reg);
+ 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) \
+ 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, \
+ .min = -2048, \
+ .max = 2047, \
+ .step = 1, \
+ .def = 0, \
+ }
+
+ISC_CTRL_CC(isc_cc_rr_ctrl, ISC_CID_CC_RR, "CC RR");
+ISC_CTRL_CC(isc_cc_rg_ctrl, ISC_CID_CC_RG, "CC RG");
+
+ISC_CTRL_CC(isc_cc_rb_ctrl, ISC_CID_CC_RB, "CC RB");
+ISC_CTRL_CC(isc_cc_or_ctrl, ISC_CID_CC_OR, "CC OR");
+
+ISC_CTRL_CC(isc_cc_gr_ctrl, ISC_CID_CC_GR, "CC GR");
+ISC_CTRL_CC(isc_cc_gg_ctrl, ISC_CID_CC_GG, "CC GG");
+
+ISC_CTRL_CC(isc_cc_gb_ctrl, ISC_CID_CC_GB, "CC GB");
+ISC_CTRL_CC(isc_cc_og_ctrl, ISC_CID_CC_OG, "CC OG");
+
+ISC_CTRL_CC(isc_cc_br_ctrl, ISC_CID_CC_BR, "CC BR");
+ISC_CTRL_CC(isc_cc_bg_ctrl, ISC_CID_CC_BG, "CC BG");
+
+ISC_CTRL_CC(isc_cc_bb_ctrl, ISC_CID_CC_BB, "CC BB");
+ISC_CTRL_CC(isc_cc_ob_ctrl, ISC_CID_CC_OB, "CC OB");
+
static const struct v4l2_ctrl_ops isc_awb_ops = {
.s_ctrl = isc_s_awb_ctrl,
.g_volatile_ctrl = isc_g_volatile_awb_ctrl,
@@ -1767,6 +1926,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 fcb20669ef69..aaa6c4b653d4 100644
--- a/drivers/media/platform/microchip/microchip-isc.h
+++ b/drivers/media/platform/microchip/microchip-isc.h
@@ -357,6 +357,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;
};

/* Statistics device */
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