To do a good job taking photos, userland needs to be able to query/set exposure in absolute units. (As scene gets darker, exposure time should be increased to cca 1/100 second -- based on scene type -- then sensitivity should be stepped up to maximum reasonable value, only then time should be increased again). This patch provides neccessary support. Signed-off-by: Pavel Machek --- I have experimental version of sdlcam that can take photos, and this is the most important missing piece. diff --git a/drivers/media/i2c/et8ek8/et8ek8_driver.c b/drivers/media/i2c/et8ek8/et8ek8_driver.c index 256acf73d5ea..82d59691c788 100644 --- a/drivers/media/i2c/et8ek8/et8ek8_driver.c +++ b/drivers/media/i2c/et8ek8/et8ek8_driver.c @@ -50,6 +50,8 @@ struct et8ek8_sensor { struct v4l2_ctrl_handler ctrl_handler; struct v4l2_ctrl *exposure; + struct v4l2_ctrl *exposure_abs; + u32 cur_exposure; struct v4l2_ctrl *pixel_rate; struct et8ek8_reglist *current_reglist; @@ -546,6 +548,65 @@ static int et8ek8_reglist_import(struct i2c_client *client, return 0; } +typedef unsigned int fixpoint8; /* .8 fixed point format. */ + +/* + * Return time of one row in microseconds + * If the sensor is not set to any mode, return zero. + */ +fixpoint8 et8ek8_get_row_time(struct et8ek8_sensor *sensor) +{ + unsigned int clock; /* Pixel clock in Hz>>10 fixed point */ + fixpoint8 rt; /* Row time in .8 fixed point */ + + if (!sensor->current_reglist) + return 0; + + clock = sensor->current_reglist->mode.pixel_clock; + clock = (clock + (1 << 9)) >> 10; + rt = sensor->current_reglist->mode.width * (1000000 >> 2); + rt = (rt + (clock >> 1)) / clock; + + return rt; +} + +/* + * Convert exposure time `us' to rows. Modify `us' to make it to + * correspond to the actual exposure time. + */ +static int et8ek8_exposure_us_to_rows(struct et8ek8_sensor *sensor, u32 *us) +{ + unsigned int rows; /* Exposure value as written to HW (ie. rows) */ + fixpoint8 rt; /* Row time in .8 fixed point */ + + /* Assume that the maximum exposure time is at most ~8 s, + * and the maximum width (with blanking) ~8000 pixels. + * The formula here is in principle as simple as + * rows = exptime / 1e6 / width * pixel_clock + * but to get accurate results while coping with value ranges, + * have to do some fixed point math. + */ + + rt = et8ek8_get_row_time(sensor); + rows = ((*us << 8) + (rt >> 1)) / rt; + + if (rows > sensor->current_reglist->mode.max_exp) + rows = sensor->current_reglist->mode.max_exp; + + /* Set the exposure time to the rounded value */ + *us = (rt * rows + (1 << 7)) >> 8; + + return rows; +} + +/* + * Convert exposure time in rows to microseconds + */ +static int et8ek8_exposure_rows_to_us(struct et8ek8_sensor *sensor, int rows) +{ + return (et8ek8_get_row_time(sensor) * rows + (1 << 7)) >> 8; +} + /* Called to change the V4L2 gain control value. This function * rounds and clamps the given value and updates the V4L2 control value. * If power is on, also updates the sensor analog and digital gains. @@ -637,18 +698,22 @@ static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl) { struct et8ek8_sensor *sensor = container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler); + u32 val = ctrl->val; switch (ctrl->id) { case V4L2_CID_GAIN: return et8ek8_set_gain(sensor, ctrl->val); + case V4L2_CID_EXPOSURE_ABSOLUTE: + val = et8ek8_exposure_us_to_rows(sensor, &val); + /* Fall through */ + case V4L2_CID_EXPOSURE: { - struct i2c_client *client = - v4l2_get_subdevdata(&sensor->subdev); - + struct i2c_client *client = v4l2_get_subdevdata(&sensor->subdev); + sensor->cur_exposure = val; return et8ek8_i2c_write_reg(client, ET8EK8_REG_16BIT, 0x1243, - ctrl->val); + val); } case V4L2_CID_TEST_PATTERN: @@ -662,8 +727,28 @@ static int et8ek8_set_ctrl(struct v4l2_ctrl *ctrl) } } +static int et8ek8_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct et8ek8_sensor *sensor = + container_of(ctrl->handler, struct et8ek8_sensor, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_EXPOSURE_ABSOLUTE: + ctrl->val = et8ek8_exposure_rows_to_us(sensor, sensor->cur_exposure); + return 0; + + case V4L2_CID_EXPOSURE: + ctrl->val = sensor->cur_exposure; + return 0; + + default: + return -EINVAL; + } +} + static const struct v4l2_ctrl_ops et8ek8_ctrl_ops = { .s_ctrl = et8ek8_set_ctrl, + .g_volatile_ctrl = et8ek8_get_ctrl, }; static const char * const et8ek8_test_pattern_menu[] = { @@ -697,6 +782,13 @@ static int et8ek8_init_controls(struct et8ek8_sensor *sensor) v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, V4L2_CID_EXPOSURE, min, max, min, max); + + min = et8ek8_exposure_rows_to_us(sensor, 1); + max = et8ek8_exposure_rows_to_us(sensor, max); + + sensor->exposure_abs = + v4l2_ctrl_new_std(&sensor->ctrl_handler, &et8ek8_ctrl_ops, + V4L2_CID_EXPOSURE_ABSOLUTE, min, max, min, max); } /* V4L2_CID_PIXEL_RATE */ @@ -738,8 +830,12 @@ static void et8ek8_update_controls(struct et8ek8_sensor *sensor) */ pixel_rate = ((mode->pixel_clock + (1 << S) - 1) >> S) + mode->width; pixel_rate = mode->window_width * (pixel_rate - 1) / mode->width; + __v4l2_ctrl_modify_range(sensor->exposure, 1, max, 1, max); + + min = et8ek8_exposure_rows_to_us(sensor, 1); + max = et8ek8_exposure_rows_to_us(sensor, max); + __v4l2_ctrl_modify_range(sensor->exposure_abs, min, max, min, max); - __v4l2_ctrl_modify_range(ctrl, min, max, min, max); __v4l2_ctrl_s_ctrl_int64(sensor->pixel_rate, pixel_rate << S); } -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html