Commit 4e52889f authored by Eugen Hristev's avatar Eugen Hristev Committed by Mauro Carvalho Chehab

media: atmel: atmel-isc-base: expose white balance as v4l2 controls

This exposes the white balance configuration of the ISC as v4l2 controls
into userspace.
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.

[hverkuil: fix checkpatch warning]
[hverkuil: minor spacing adjustments in the functionality description]
Signed-off-by: default avatarEugen Hristev <eugen.hristev@microchip.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+huawei@kernel.org>
parent bfaaa20d
......@@ -22,6 +22,7 @@
#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>
......@@ -224,10 +225,35 @@ const u32 isc_gamma_table[GAMMA_MAX + 1][GAMMA_ENTRIES] = {
(((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));
......@@ -662,11 +688,9 @@ static void isc_set_pipeline(struct isc_device *isc, u32 pipeline)
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);
......@@ -1396,6 +1420,7 @@ static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f)
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;
......@@ -1814,10 +1839,6 @@ static void isc_awb_work(struct work_struct *w)
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);
/*
......@@ -1842,6 +1863,8 @@ static void isc_awb_work(struct work_struct *w)
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;
}
}
......@@ -1873,6 +1896,27 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
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;
......@@ -1883,36 +1927,142 @@ static int isc_s_ctrl(struct v4l2_ctrl *ctrl)
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;
......@@ -1923,7 +2073,7 @@ 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, 5);
ret = v4l2_ctrl_handler_init(hdl, 13);
if (ret < 0)
return ret;
......@@ -1933,10 +2083,13 @@ static int isc_ctrl_init(struct isc_device *isc)
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) {
......@@ -1947,6 +2100,21 @@ static int isc_ctrl_init(struct isc_device *isc)
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;
......
......@@ -213,7 +213,6 @@ struct isc_device {
struct fmt_config try_config;
struct isc_ctrls ctrls;
struct v4l2_ctrl *do_wb_ctrl;
struct work_struct awb_work;
struct mutex lock; /* serialize access to file operations */
......@@ -223,6 +222,28 @@ struct isc_device {
struct isc_subdev_entity *current_subdev;
struct list_head subdev_entities;
struct {
#define ISC_CTRL_DO_WB 1
#define ISC_CTRL_R_GAIN 2
#define ISC_CTRL_B_GAIN 3
#define ISC_CTRL_GR_GAIN 4
#define ISC_CTRL_GB_GAIN 5
#define ISC_CTRL_R_OFF 6
#define ISC_CTRL_B_OFF 7
#define ISC_CTRL_GR_OFF 8
#define ISC_CTRL_GB_OFF 9
struct v4l2_ctrl *awb_ctrl;
struct v4l2_ctrl *do_wb_ctrl;
struct v4l2_ctrl *r_gain_ctrl;
struct v4l2_ctrl *b_gain_ctrl;
struct v4l2_ctrl *gr_gain_ctrl;
struct v4l2_ctrl *gb_gain_ctrl;
struct v4l2_ctrl *r_off_ctrl;
struct v4l2_ctrl *b_off_ctrl;
struct v4l2_ctrl *gr_off_ctrl;
struct v4l2_ctrl *gb_off_ctrl;
};
};
#define GAMMA_MAX 2
......
/* 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
......@@ -192,6 +192,12 @@ enum v4l2_colorfx {
* We reserve 16 controls for this driver. */
#define V4L2_CID_USER_IMX_BASE (V4L2_CID_USER_BASE + 0x10b0)
/*
* The base for the atmel isc driver controls.
* We reserve 32 controls for this driver.
*/
#define V4L2_CID_USER_ATMEL_ISC_BASE (V4L2_CID_USER_BASE + 0x10c0)
/* MPEG-class control IDs */
/* The MPEG controls are applicable to all codec controls
* and the 'MPEG' part of the define is historical */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment