From 91daa793e204e08c93e8c690712dea0c8db84a4e Mon Sep 17 00:00:00 2001 From: Naushir Patuck Date: Wed, 10 Feb 2021 10:50:32 +0000 Subject: [PATCH] media: i2c: imx477: Add very long exposure control to the driver Add support for very long exposures by using the exposure multiplier register. Userland does not need to pass any additional controls to enable long exposures, it simply requests a larger vblank to extend the exposure control range appropriately. Currently, since hblank is fixed, a maximum of approximately 124 seconds of exposure time can be used. In a future change, hblank could also be controlled in userland to give over 200 seconds of exposure time. Signed-off-by: Naushir Patuck --- drivers/media/i2c/imx477.c | 47 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/imx477.c b/drivers/media/i2c/imx477.c index 217780b..7907d51 100644 --- a/drivers/media/i2c/imx477.c +++ b/drivers/media/i2c/imx477.c @@ -44,6 +44,10 @@ #define IMX477_REG_FRAME_LENGTH 0x0340 #define IMX477_FRAME_LENGTH_MAX 0xffdc +/* Long exposure multiplier */ +#define IMX477_LONG_EXP_SHIFT_MAX 7 +#define IMX477_LONG_EXP_SHIFT_REG 0x3100 + /* Exposure control */ #define IMX477_REG_EXPOSURE 0x0202 #define IMX477_EXPOSURE_OFFSET 22 @@ -1097,6 +1101,9 @@ struct imx477 { /* Rewrite common registers on stream on? */ bool common_regs_written; + + /* Current long exposure factor in use. Set through V4L2_CID_VBLANK */ + unsigned int long_exp_shift; }; static inline struct imx477 *to_imx477(struct v4l2_subdev *_sd) @@ -1285,13 +1292,33 @@ static void imx477_adjust_exposure_range(struct imx477 *imx477, /* Honour the VBLANK limits when setting exposure. */ exposure_max = imx477->mode->height + imx477->vblank->val - - IMX477_EXPOSURE_OFFSET; + (IMX477_EXPOSURE_OFFSET << imx477->long_exp_shift); exposure_def = min(exposure_max, imx477->exposure->val); __v4l2_ctrl_modify_range(imx477->exposure, imx477->exposure->minimum, exposure_max, imx477->exposure->step, exposure_def); } +static int imx477_set_frame_length(struct imx477 *imx477, unsigned int val) +{ + int ret = 0; + + imx477->long_exp_shift = 0; + + while (val > IMX477_FRAME_LENGTH_MAX) { + imx477->long_exp_shift++; + val >>= 1; + } + + ret = imx477_write_reg(imx477, IMX477_REG_FRAME_LENGTH, + IMX477_REG_VALUE_16BIT, val); + if (ret) + return ret; + + return imx477_write_reg(imx477, IMX477_LONG_EXP_SHIFT_REG, + IMX477_REG_VALUE_08BIT, imx477->long_exp_shift); +} + static int imx477_set_ctrl(struct v4l2_ctrl *ctrl) { struct imx477 *imx477 = @@ -1299,6 +1326,10 @@ static int imx477_set_ctrl(struct v4l2_ctrl *ctrl) struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd); int ret = 0; + /* + * The VBLANK control may change the limits of usable exposure, so check + * and adjust if necessary. + */ if (ctrl->id == V4L2_CID_VBLANK) imx477_adjust_exposure_range(imx477, ctrl); @@ -1316,7 +1347,8 @@ static int imx477_set_ctrl(struct v4l2_ctrl *ctrl) break; case V4L2_CID_EXPOSURE: ret = imx477_write_reg(imx477, IMX477_REG_EXPOSURE, - IMX477_REG_VALUE_16BIT, ctrl->val); + IMX477_REG_VALUE_16BIT, ctrl->val >> + imx477->long_exp_shift); break; case V4L2_CID_DIGITAL_GAIN: ret = imx477_write_reg(imx477, IMX477_REG_DIGITAL_GAIN, @@ -1350,9 +1382,8 @@ static int imx477_set_ctrl(struct v4l2_ctrl *ctrl) imx477->vflip->val << 1); break; case V4L2_CID_VBLANK: - ret = imx477_write_reg(imx477, IMX477_REG_FRAME_LENGTH, - IMX477_REG_VALUE_16BIT, - imx477->mode->height + ctrl->val); + ret = imx477_set_frame_length(imx477, + imx477->mode->height + ctrl->val); break; default: dev_info(&client->dev, @@ -1521,9 +1552,13 @@ static void imx477_set_framing_limits(struct imx477 *imx477) frm_length_default = imx477_get_frame_length(mode, &mode->timeperframe_default); + /* Default to no long exposure multiplier. */ + imx477->long_exp_shift = 0; + /* Update limits and set FPS to default */ __v4l2_ctrl_modify_range(imx477->vblank, frm_length_min - mode->height, - IMX477_FRAME_LENGTH_MAX - mode->height, + ((1 << IMX477_LONG_EXP_SHIFT_MAX) * + IMX477_FRAME_LENGTH_MAX) - mode->height, 1, frm_length_default - mode->height); /* Setting this will adjust the exposure limits as well. */ -- 2.7.4