#include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <linux/videodev2.h>
+#include <linux/atmel-isc-media.h>
 
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-device.h>
        (((mbus_code) == MEDIA_BUS_FMT_Y10_1X10) | \
        (((mbus_code) == MEDIA_BUS_FMT_Y8_1X8)))
 
+#define ISC_CTRL_ISC_TO_V4L2(x) ((x) == ISC_WB_O_ZERO_VAL ? 0 : (x))
+#define ISC_CTRL_V4L2_TO_ISC(x) ((x) ? (x) : ISC_WB_O_ZERO_VAL)
+
+static inline void isc_update_v4l2_ctrls(struct isc_device *isc)
+{
+       struct isc_ctrls *ctrls = &isc->ctrls;
+
+       /* In here we set the v4l2 controls w.r.t. our pipeline config */
+       v4l2_ctrl_s_ctrl(isc->r_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_R]);
+       v4l2_ctrl_s_ctrl(isc->b_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_B]);
+       v4l2_ctrl_s_ctrl(isc->gr_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GR]);
+       v4l2_ctrl_s_ctrl(isc->gb_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GB]);
+
+       v4l2_ctrl_s_ctrl(isc->r_off_ctrl,
+                        ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_R]));
+       v4l2_ctrl_s_ctrl(isc->b_off_ctrl,
+                        ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_B]));
+       v4l2_ctrl_s_ctrl(isc->gr_off_ctrl,
+                        ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_GR]));
+       v4l2_ctrl_s_ctrl(isc->gb_off_ctrl,
+                        ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_GB]));
+}
+
 static inline void isc_update_awb_ctrls(struct isc_device *isc)
 {
        struct isc_ctrls *ctrls = &isc->ctrls;
 
+       /* In here we set our actual hw pipeline config */
+
        regmap_write(isc->regmap, ISC_WB_O_RGR,
                     (ISC_WB_O_ZERO_VAL - (ctrls->offset[ISC_HIS_CFG_MODE_R])) |
                     ((ISC_WB_O_ZERO_VAL - ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16));
 
        bay_cfg = isc->config.sd_format->cfa_baycfg;
 
-       if (ctrls->awb == ISC_WB_NONE)
-               isc_reset_awb_ctrls(isc);
-
        regmap_write(regmap, ISC_WB_CFG, bay_cfg);
        isc_update_awb_ctrls(isc);
+       isc_update_v4l2_ctrls(isc);
 
        regmap_write(regmap, ISC_CFA_CFG, bay_cfg | ISC_CFA_CFG_EITPOL);
 
            isc->try_config.sd_format != isc->config.sd_format) {
                isc->ctrls.hist_stat = HIST_INIT;
                isc_reset_awb_ctrls(isc);
+               isc_update_v4l2_ctrls(isc);
        }
        /* make the try configuration active */
        isc->config = isc->try_config;
        ctrls->hist_id = hist_id;
        baysel = isc->config.sd_format->cfa_baycfg << ISC_HIS_CFG_BAYSEL_SHIFT;
 
-       /* if no more auto white balance, reset controls. */
-       if (ctrls->awb == ISC_WB_NONE)
-               isc_reset_awb_ctrls(isc);
-
        pm_runtime_get_sync(isc->dev);
 
        /*
                if (ctrls->awb == ISC_WB_ONETIME) {
                        v4l2_info(&isc->v4l2_dev,
                                  "Completed one time white-balance adjustment.\n");
+                       /* update the v4l2 controls values */
+                       isc_update_v4l2_ctrls(isc);
                        ctrls->awb = ISC_WB_NONE;
                }
        }
        case V4L2_CID_GAMMA:
                ctrls->gamma_index = ctrl->val;
                break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static const struct v4l2_ctrl_ops isc_ctrl_ops = {
+       .s_ctrl = isc_s_ctrl,
+};
+
+static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct isc_device *isc = container_of(ctrl->handler,
+                                            struct isc_device, ctrls.handler);
+       struct isc_ctrls *ctrls = &isc->ctrls;
+
+       if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+               return 0;
+
+       switch (ctrl->id) {
        case V4L2_CID_AUTO_WHITE_BALANCE:
                if (ctrl->val == 1)
                        ctrls->awb = ISC_WB_AUTO;
                if (!isc->config.sd_format)
                        break;
 
-               if (ctrls->hist_stat != HIST_ENABLED)
-                       isc_reset_awb_ctrls(isc);
+               /* configure the controls with new values from v4l2 */
+               if (ctrl->cluster[ISC_CTRL_R_GAIN]->is_new)
+                       ctrls->gain[ISC_HIS_CFG_MODE_R] = isc->r_gain_ctrl->val;
+               if (ctrl->cluster[ISC_CTRL_B_GAIN]->is_new)
+                       ctrls->gain[ISC_HIS_CFG_MODE_B] = isc->b_gain_ctrl->val;
+               if (ctrl->cluster[ISC_CTRL_GR_GAIN]->is_new)
+                       ctrls->gain[ISC_HIS_CFG_MODE_GR] = isc->gr_gain_ctrl->val;
+               if (ctrl->cluster[ISC_CTRL_GB_GAIN]->is_new)
+                       ctrls->gain[ISC_HIS_CFG_MODE_GB] = isc->gb_gain_ctrl->val;
+
+               if (ctrl->cluster[ISC_CTRL_R_OFF]->is_new)
+                       ctrls->offset[ISC_HIS_CFG_MODE_R] =
+                               ISC_CTRL_V4L2_TO_ISC(isc->r_off_ctrl->val);
+               if (ctrl->cluster[ISC_CTRL_B_OFF]->is_new)
+                       ctrls->offset[ISC_HIS_CFG_MODE_B] =
+                               ISC_CTRL_V4L2_TO_ISC(isc->b_off_ctrl->val);
+               if (ctrl->cluster[ISC_CTRL_GR_OFF]->is_new)
+                       ctrls->offset[ISC_HIS_CFG_MODE_GR] =
+                               ISC_CTRL_V4L2_TO_ISC(isc->gr_off_ctrl->val);
+               if (ctrl->cluster[ISC_CTRL_GB_OFF]->is_new)
+                       ctrls->offset[ISC_HIS_CFG_MODE_GB] =
+                               ISC_CTRL_V4L2_TO_ISC(isc->gb_off_ctrl->val);
 
-               if (isc->ctrls.awb == ISC_WB_AUTO &&
+               isc_update_awb_ctrls(isc);
+
+               if (vb2_is_streaming(&isc->vb2_vidq)) {
+                       /*
+                        * If we are streaming, we can update profile to
+                        * have the new settings in place.
+                        */
+                       isc_update_profile(isc);
+               } else {
+                       /*
+                        * The auto cluster will activate automatically this
+                        * control. This has to be deactivated when not
+                        * streaming.
+                        */
+                       v4l2_ctrl_activate(isc->do_wb_ctrl, false);
+               }
+
+               /* if we have autowhitebalance on, start histogram procedure */
+               if (ctrls->awb == ISC_WB_AUTO &&
                    vb2_is_streaming(&isc->vb2_vidq) &&
                    ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code))
                        isc_set_histogram(isc, true);
 
-               break;
-       case V4L2_CID_DO_WHITE_BALANCE:
-               /* if AWB is enabled, do nothing */
-               if (ctrls->awb == ISC_WB_AUTO)
-                       return 0;
+               /*
+                * for one time whitebalance adjustment, check the button,
+                * if it's pressed, perform the one time operation.
+                */
+               if (ctrls->awb == ISC_WB_NONE &&
+                   ctrl->cluster[ISC_CTRL_DO_WB]->is_new &&
+                   !(ctrl->cluster[ISC_CTRL_DO_WB]->flags &
+                   V4L2_CTRL_FLAG_INACTIVE)) {
+                       ctrls->awb = ISC_WB_ONETIME;
+                       isc_set_histogram(isc, true);
+                       v4l2_dbg(1, debug, &isc->v4l2_dev,
+                                "One time white-balance started.\n");
+               }
+               return 0;
+       }
+       return 0;
+}
 
-               ctrls->awb = ISC_WB_ONETIME;
-               isc_set_histogram(isc, true);
-               v4l2_dbg(1, debug, &isc->v4l2_dev,
-                        "One time white-balance started.\n");
+static int isc_g_volatile_awb_ctrl(struct v4l2_ctrl *ctrl)
+{
+       struct isc_device *isc = container_of(ctrl->handler,
+                                            struct isc_device, ctrls.handler);
+       struct isc_ctrls *ctrls = &isc->ctrls;
+
+       switch (ctrl->id) {
+       /* being a cluster, this id will be called for every control */
+       case V4L2_CID_AUTO_WHITE_BALANCE:
+               ctrl->cluster[ISC_CTRL_R_GAIN]->val =
+                                       ctrls->gain[ISC_HIS_CFG_MODE_R];
+               ctrl->cluster[ISC_CTRL_B_GAIN]->val =
+                                       ctrls->gain[ISC_HIS_CFG_MODE_B];
+               ctrl->cluster[ISC_CTRL_GR_GAIN]->val =
+                                       ctrls->gain[ISC_HIS_CFG_MODE_GR];
+               ctrl->cluster[ISC_CTRL_GB_GAIN]->val =
+                                       ctrls->gain[ISC_HIS_CFG_MODE_GB];
+
+               ctrl->cluster[ISC_CTRL_R_OFF]->val =
+                       ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_R]);
+               ctrl->cluster[ISC_CTRL_B_OFF]->val =
+                       ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_B]);
+               ctrl->cluster[ISC_CTRL_GR_OFF]->val =
+                       ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_GR]);
+               ctrl->cluster[ISC_CTRL_GB_OFF]->val =
+                       ISC_CTRL_ISC_TO_V4L2(ctrls->offset[ISC_HIS_CFG_MODE_GB]);
                break;
-       default:
-               return -EINVAL;
        }
-
        return 0;
 }
 
-static const struct v4l2_ctrl_ops isc_ctrl_ops = {
-       .s_ctrl = isc_s_ctrl,
+static const struct v4l2_ctrl_ops isc_awb_ops = {
+       .s_ctrl = isc_s_awb_ctrl,
+       .g_volatile_ctrl = isc_g_volatile_awb_ctrl,
 };
 
+#define ISC_CTRL_OFF(_name, _id, _name_str) \
+       static const struct v4l2_ctrl_config _name = { \
+               .ops = &isc_awb_ops, \
+               .id = _id, \
+               .name = _name_str, \
+               .type = V4L2_CTRL_TYPE_INTEGER, \
+               .flags = V4L2_CTRL_FLAG_SLIDER, \
+               .min = -4095, \
+               .max = 4095, \
+               .step = 1, \
+               .def = 0, \
+       }
+
+ISC_CTRL_OFF(isc_r_off_ctrl, ISC_CID_R_OFFSET, "Red Component Offset");
+ISC_CTRL_OFF(isc_b_off_ctrl, ISC_CID_B_OFFSET, "Blue Component Offset");
+ISC_CTRL_OFF(isc_gr_off_ctrl, ISC_CID_GR_OFFSET, "Green Red Component Offset");
+ISC_CTRL_OFF(isc_gb_off_ctrl, ISC_CID_GB_OFFSET, "Green Blue Component Offset");
+
+#define ISC_CTRL_GAIN(_name, _id, _name_str) \
+       static const struct v4l2_ctrl_config _name = { \
+               .ops = &isc_awb_ops, \
+               .id = _id, \
+               .name = _name_str, \
+               .type = V4L2_CTRL_TYPE_INTEGER, \
+               .flags = V4L2_CTRL_FLAG_SLIDER, \
+               .min = 0, \
+               .max = 8191, \
+               .step = 1, \
+               .def = 512, \
+       }
+
+ISC_CTRL_GAIN(isc_r_gain_ctrl, ISC_CID_R_GAIN, "Red Component Gain");
+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");
+
 static int isc_ctrl_init(struct isc_device *isc)
 {
        const struct v4l2_ctrl_ops *ops = &isc_ctrl_ops;
        ctrls->hist_stat = HIST_INIT;
        isc_reset_awb_ctrls(isc);
 
-       ret = v4l2_ctrl_handler_init(hdl, 5);
+       ret = v4l2_ctrl_handler_init(hdl, 13);
        if (ret < 0)
                return ret;
 
        v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0);
        v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 256);
        v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, GAMMA_MAX, 1, 2);
-       v4l2_ctrl_new_std(hdl, ops, V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
+       isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops,
+                                         V4L2_CID_AUTO_WHITE_BALANCE,
+                                         0, 1, 1, 1);
 
        /* do_white_balance is a button, so min,max,step,default are ignored */
-       isc->do_wb_ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_DO_WHITE_BALANCE,
+       isc->do_wb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops,
+                                           V4L2_CID_DO_WHITE_BALANCE,
                                            0, 0, 0, 0);
 
        if (!isc->do_wb_ctrl) {
 
        v4l2_ctrl_activate(isc->do_wb_ctrl, false);
 
+       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);
+       isc->gr_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_gain_ctrl, NULL);
+       isc->gb_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_gain_ctrl, NULL);
+       isc->r_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_off_ctrl, NULL);
+       isc->b_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_off_ctrl, NULL);
+       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);
+
+       /*
+        * The cluster is in auto mode with autowhitebalance enabled
+        * and manual mode otherwise.
+        */
+       v4l2_ctrl_auto_cluster(10, &isc->awb_ctrl, 0, true);
+
        v4l2_ctrl_handler_setup(hdl);
 
        return 0;
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2019 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Eugen Hristev <eugen.hristev@microchip.com>
+ */
+
+#ifndef __LINUX_ATMEL_ISC_MEDIA_H__
+#define __LINUX_ATMEL_ISC_MEDIA_H__
+
+/*
+ * There are 8 controls available:
+ * 4 gain controls, sliders, for each of the BAYER components: R, B, GR, GB.
+ * These gains are multipliers for each component, in format unsigned 0:4:9 with
+ * a default value of 512 (1.0 multiplier).
+ * 4 offset controls, sliders, for each of the BAYER components: R, B, GR, GB.
+ * These offsets are added/substracted from each component, in format signed
+ * 1:12:0 with a default value of 0 (+/- 0)
+ *
+ * To expose this to userspace, added 8 custom controls, in an auto cluster.
+ *
+ * To summarize the functionality:
+ * The auto cluster switch is the auto white balance control, and it works
+ * like this:
+ * AWB == 1: autowhitebalance is on, the do_white_balance button is inactive,
+ * the gains/offsets are inactive, but volatile and readable.
+ * Thus, the results of the whitebalance algorithm are available to userspace to
+ * read at any time.
+ * AWB == 0: autowhitebalance is off, cluster is in manual mode, user can
+ * configure the gain/offsets directly.
+ * More than that, if the do_white_balance button is
+ * pressed, the driver will perform one-time-adjustment, (preferably with color
+ * checker card) and the userspace can read again the new values.
+ *
+ * With this feature, the userspace can save the coefficients and reinstall them
+ * for example after reboot or reprobing the driver.
+ */
+
+enum atmel_isc_ctrl_id {
+       /* Red component gain control */
+       ISC_CID_R_GAIN = (V4L2_CID_USER_ATMEL_ISC_BASE + 0),
+       /* Blue component gain control */
+       ISC_CID_B_GAIN,
+       /* Green Red component gain control */
+       ISC_CID_GR_GAIN,
+       /* Green Blue gain control */
+       ISC_CID_GB_GAIN,
+       /* Red component offset control */
+       ISC_CID_R_OFFSET,
+       /* Blue component offset control */
+       ISC_CID_B_OFFSET,
+       /* Green Red component offset control */
+       ISC_CID_GR_OFFSET,
+       /* Green Blue component offset control */
+       ISC_CID_GB_OFFSET,
+};
+
+#endif