| From 9e584d9de3387588bf455d3c45ec6a092bfa4266 Mon Sep 17 00:00:00 2001 |
| From: David Plowman <david.plowman@raspberrypi.com> |
| Date: Wed, 29 Jan 2020 15:30:53 +0000 |
| Subject: [PATCH] media: ov5647: Add basic support for multiple sensor |
| modes. |
| |
| Specifically: |
| |
| Added a structure ov5647_mode and a list of supported_modes (though no |
| actual new modes as yet). The state object points to the "current mode". |
| |
| ov5647_enum_mbus_code, ov5647_enum_frame_size, ov5647_set_fmt and |
| ov5647_get_fmt all needed upgrading to cope with multiple modes. |
| |
| __sensor_init (which writes all the registers) is now called by |
| ov5647_stream_on (once the mode is known) rather than by |
| ov5647_sensor_power. |
| |
| Signed-off-by: David Plowman <david.plowman@raspberrypi.com> |
| Signed-off-by: Naushir Patuck <naush@raspberrypi.com> |
| --- |
| drivers/media/i2c/ov5647.c | 268 ++++++++++++++++++++++++++++--------- |
| 1 file changed, 202 insertions(+), 66 deletions(-) |
| |
| --- a/drivers/media/i2c/ov5647.c |
| +++ b/drivers/media/i2c/ov5647.c |
| @@ -86,13 +86,17 @@ struct regval_list { |
| u8 data; |
| }; |
| |
| +struct ov5647_mode { |
| + struct v4l2_mbus_framefmt format; |
| + struct regval_list *reg_list; |
| + unsigned int num_regs; |
| +}; |
| + |
| struct ov5647 { |
| struct v4l2_subdev sd; |
| struct media_pad pad; |
| struct mutex lock; |
| - struct v4l2_mbus_framefmt format; |
| - unsigned int width; |
| - unsigned int height; |
| + const struct ov5647_mode *mode; |
| int power_count; |
| struct clk *xclk; |
| struct gpio_desc *pwdn; |
| @@ -207,6 +211,32 @@ static struct regval_list ov5647_640x480 |
| {0x0100, 0x01}, |
| }; |
| |
| +static struct ov5647_mode supported_modes_8bit[] = { |
| + /* |
| + * Original 8-bit VGA mode |
| + * Uncentred crop (top left quarter) from 2x2 binned 1296x972 image. |
| + */ |
| + { |
| + { |
| + .code = MEDIA_BUS_FMT_SBGGR8_1X8, |
| + .colorspace = V4L2_COLORSPACE_SRGB, |
| + .field = V4L2_FIELD_NONE, |
| + .width = 640, |
| + .height = 480 |
| + }, |
| + ov5647_640x480, |
| + ARRAY_SIZE(ov5647_640x480) |
| + }, |
| + /* more modes below here... */ |
| +}; |
| + |
| +static struct ov5647_mode supported_modes_10bit[] = { |
| + /* no 10-bit modes yet */ |
| +}; |
| + |
| +/* Use original 8-bit VGA mode as default. */ |
| +#define OV5647_DEFAULT_MODE (&supported_modes_8bit[0]) |
| + |
| static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val) |
| { |
| int ret; |
| @@ -293,12 +323,55 @@ static int ov5647_set_virtual_channel(st |
| return ov5647_write(sd, OV5647_REG_MIPI_CTRL14, channel_id | (channel << 6)); |
| } |
| |
| +static int __sensor_init(struct v4l2_subdev *sd) |
| +{ |
| + int ret; |
| + u8 resetval, rdval; |
| + struct i2c_client *client = v4l2_get_subdevdata(sd); |
| + struct ov5647 *state = to_state(sd); |
| + |
| + ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval); |
| + if (ret < 0) |
| + return ret; |
| + |
| + ret = ov5647_write_array(sd, state->mode->reg_list, |
| + state->mode->num_regs); |
| + if (ret < 0) { |
| + dev_err(&client->dev, "write sensor default regs error\n"); |
| + return ret; |
| + } |
| + |
| + ret = ov5647_set_virtual_channel(sd, 0); |
| + if (ret < 0) |
| + return ret; |
| + |
| + ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval); |
| + if (ret < 0) |
| + return ret; |
| + |
| + if (!(resetval & 0x01)) { |
| + dev_err(&client->dev, "Device was in SW standby"); |
| + ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01); |
| + if (ret < 0) |
| + return ret; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| static int ov5647_stream_on(struct v4l2_subdev *sd) |
| { |
| + struct i2c_client *client = v4l2_get_subdevdata(sd); |
| struct ov5647 *ov5647 = to_state(sd); |
| u8 val = MIPI_CTRL00_BUS_IDLE; |
| int ret; |
| |
| + ret = __sensor_init(sd); |
| + if (ret < 0) { |
| + dev_err(&client->dev, "sensor_init failed\n"); |
| + return ret; |
| + } |
| + |
| if (ov5647->flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK) |
| val |= MIPI_CTRL00_CLOCK_LANE_GATE | |
| MIPI_CTRL00_LINE_SYNC_ENABLE; |
| @@ -347,44 +420,6 @@ static int set_sw_standby(struct v4l2_su |
| return ov5647_write(sd, OV5647_SW_STANDBY, rdval); |
| } |
| |
| -static int __sensor_init(struct v4l2_subdev *sd) |
| -{ |
| - int ret; |
| - u8 resetval, rdval; |
| - struct i2c_client *client = v4l2_get_subdevdata(sd); |
| - |
| - ret = ov5647_read(sd, OV5647_SW_STANDBY, &rdval); |
| - if (ret < 0) |
| - return ret; |
| - |
| - ret = ov5647_write_array(sd, ov5647_640x480, |
| - ARRAY_SIZE(ov5647_640x480)); |
| - if (ret < 0) { |
| - dev_err(&client->dev, "write sensor default regs error\n"); |
| - return ret; |
| - } |
| - |
| - ret = ov5647_set_virtual_channel(sd, 0); |
| - if (ret < 0) |
| - return ret; |
| - |
| - ret = ov5647_read(sd, OV5647_SW_STANDBY, &resetval); |
| - if (ret < 0) |
| - return ret; |
| - |
| - if (!(resetval & 0x01)) { |
| - dev_err(&client->dev, "Device was in SW standby"); |
| - ret = ov5647_write(sd, OV5647_SW_STANDBY, 0x01); |
| - if (ret < 0) |
| - return ret; |
| - } |
| - |
| - /* |
| - * stream off to make the clock lane into LP-11 state. |
| - */ |
| - return ov5647_stream_off(sd); |
| -} |
| - |
| static int ov5647_sensor_power(struct v4l2_subdev *sd, int on) |
| { |
| int ret = 0; |
| @@ -408,7 +443,7 @@ static int ov5647_sensor_power(struct v4 |
| } |
| |
| ret = ov5647_write_array(sd, sensor_oe_enable_regs, |
| - ARRAY_SIZE(sensor_oe_enable_regs)); |
| + ARRAY_SIZE(sensor_oe_enable_regs)); |
| if (ret < 0) { |
| clk_disable_unprepare(ov5647->xclk); |
| dev_err(&client->dev, |
| @@ -416,7 +451,10 @@ static int ov5647_sensor_power(struct v4 |
| goto out; |
| } |
| |
| - ret = __sensor_init(sd); |
| + /* |
| + * Ensure streaming off to make clock lane go into LP-11 state. |
| + */ |
| + ret = ov5647_stream_off(sd); |
| if (ret < 0) { |
| clk_disable_unprepare(ov5647->xclk); |
| dev_err(&client->dev, |
| @@ -427,7 +465,7 @@ static int ov5647_sensor_power(struct v4 |
| dev_dbg(&client->dev, "OV5647 power off\n"); |
| |
| ret = ov5647_write_array(sd, sensor_oe_disable_regs, |
| - ARRAY_SIZE(sensor_oe_disable_regs)); |
| + ARRAY_SIZE(sensor_oe_disable_regs)); |
| |
| if (ret < 0) |
| dev_dbg(&client->dev, "disable oe failed\n"); |
| @@ -489,10 +527,19 @@ static const struct v4l2_subdev_core_ops |
| |
| static int ov5647_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| + struct ov5647 *state = to_state(sd); |
| + int ret = 0; |
| + |
| + mutex_lock(&state->lock); |
| + |
| if (enable) |
| - return ov5647_stream_on(sd); |
| + ret = ov5647_stream_on(sd); |
| else |
| - return ov5647_stream_off(sd); |
| + ret = ov5647_stream_off(sd); |
| + |
| + mutex_unlock(&state->lock); |
| + |
| + return ret; |
| } |
| |
| static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = { |
| @@ -503,38 +550,127 @@ static int ov5647_enum_mbus_code(struct |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_mbus_code_enum *code) |
| { |
| - if (code->index > 0) |
| + if (code->index == 0 && ARRAY_SIZE(supported_modes_8bit)) |
| + code->code = MEDIA_BUS_FMT_SBGGR8_1X8; |
| + else if (code->index == 0 && ARRAY_SIZE(supported_modes_8bit) == 0 && |
| + ARRAY_SIZE(supported_modes_10bit)) |
| + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; |
| + else if (code->index == 1 && ARRAY_SIZE(supported_modes_8bit) && |
| + ARRAY_SIZE(supported_modes_10bit)) |
| + code->code = MEDIA_BUS_FMT_SBGGR10_1X10; |
| + else |
| return -EINVAL; |
| |
| - code->code = MEDIA_BUS_FMT_SBGGR8_1X8; |
| + return 0; |
| +} |
| + |
| +static int ov5647_enum_frame_size(struct v4l2_subdev *sd, |
| + struct v4l2_subdev_pad_config *cfg, |
| + struct v4l2_subdev_frame_size_enum *fse) |
| +{ |
| + struct ov5647_mode *mode = NULL; |
| + |
| + if (fse->code == MEDIA_BUS_FMT_SBGGR8_1X8) { |
| + if (fse->index >= ARRAY_SIZE(supported_modes_8bit)) |
| + return -EINVAL; |
| + mode = &supported_modes_8bit[fse->index]; |
| + } else if (fse->code == MEDIA_BUS_FMT_SBGGR10_1X10) { |
| + if (fse->index >= ARRAY_SIZE(supported_modes_10bit)) |
| + return -EINVAL; |
| + mode = &supported_modes_10bit[fse->index]; |
| + } else { |
| + return -EINVAL; |
| + } |
| + |
| + fse->min_width = mode->format.width; |
| + fse->max_width = fse->min_width; |
| + fse->min_height = mode->format.height; |
| + fse->max_height = fse->min_height; |
| + |
| + return 0; |
| +} |
| + |
| +static int ov5647_set_fmt(struct v4l2_subdev *sd, |
| + struct v4l2_subdev_pad_config *cfg, |
| + struct v4l2_subdev_format *format) |
| +{ |
| + struct v4l2_mbus_framefmt *fmt = &format->format; |
| + struct ov5647 *state = to_state(sd); |
| + struct v4l2_mbus_framefmt *framefmt; |
| + const struct ov5647_mode *mode_8bit, *mode_10bit, *mode = NULL; |
| + |
| + if (format->pad != 0) |
| + return -EINVAL; |
| + |
| + mutex_lock(&state->lock); |
| + |
| + /* |
| + * Try to respect any given pixel format, otherwise try for a 10-bit |
| + * mode. |
| + */ |
| + mode_8bit = v4l2_find_nearest_size(supported_modes_8bit, |
| + ARRAY_SIZE(supported_modes_8bit), |
| + format.width, format.height, |
| + format->format.width, |
| + format->format.height); |
| + mode_10bit = v4l2_find_nearest_size(supported_modes_10bit, |
| + ARRAY_SIZE(supported_modes_10bit), |
| + format.width, format.height, |
| + format->format.width, |
| + format->format.height); |
| + if (format->format.code == MEDIA_BUS_FMT_SBGGR8_1X8 && mode_8bit) |
| + mode = mode_8bit; |
| + else if (format->format.code == MEDIA_BUS_FMT_SBGGR10_1X10 && |
| + mode_10bit) |
| + mode = mode_10bit; |
| + else if (mode_10bit) |
| + mode = mode_10bit; |
| + else |
| + mode = mode_8bit; |
| + |
| + if (!mode) |
| + return -EINVAL; |
| + |
| + *fmt = mode->format; |
| + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { |
| + framefmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); |
| + *framefmt = format->format; |
| + } else { |
| + state->mode = mode; |
| + } |
| + |
| + mutex_unlock(&state->lock); |
| |
| return 0; |
| } |
| |
| -static int ov5647_set_get_fmt(struct v4l2_subdev *sd, |
| - struct v4l2_subdev_pad_config *cfg, |
| - struct v4l2_subdev_format *format) |
| +static int ov5647_get_fmt(struct v4l2_subdev *sd, |
| + struct v4l2_subdev_pad_config *cfg, |
| + struct v4l2_subdev_format *format) |
| { |
| struct v4l2_mbus_framefmt *fmt = &format->format; |
| + struct ov5647 *state = to_state(sd); |
| |
| if (format->pad != 0) |
| return -EINVAL; |
| |
| - /* Only one format is supported, so return that */ |
| - memset(fmt, 0, sizeof(*fmt)); |
| - fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8; |
| - fmt->colorspace = V4L2_COLORSPACE_SRGB; |
| - fmt->field = V4L2_FIELD_NONE; |
| - fmt->width = 640; |
| - fmt->height = 480; |
| + mutex_lock(&state->lock); |
| + |
| + if (format->which == V4L2_SUBDEV_FORMAT_TRY) |
| + *fmt = *v4l2_subdev_get_try_format(sd, cfg, format->pad); |
| + else |
| + *fmt = state->mode->format; |
| + |
| + mutex_unlock(&state->lock); |
| |
| return 0; |
| } |
| |
| static const struct v4l2_subdev_pad_ops ov5647_subdev_pad_ops = { |
| .enum_mbus_code = ov5647_enum_mbus_code, |
| - .set_fmt = ov5647_set_get_fmt, |
| - .get_fmt = ov5647_set_get_fmt, |
| + .set_fmt = ov5647_set_fmt, |
| + .get_fmt = ov5647_get_fmt, |
| + .enum_frame_size = ov5647_enum_frame_size, |
| }; |
| |
| static const struct v4l2_subdev_ops ov5647_subdev_ops = { |
| @@ -580,18 +716,15 @@ static int ov5647_open(struct v4l2_subde |
| v4l2_subdev_get_try_format(sd, fh->pad, 0); |
| struct v4l2_rect *crop = |
| v4l2_subdev_get_try_crop(sd, fh->pad, 0); |
| + struct ov5647 *state = to_state(sd); |
| |
| crop->left = OV5647_COLUMN_START_DEF; |
| crop->top = OV5647_ROW_START_DEF; |
| crop->width = OV5647_WINDOW_WIDTH_DEF; |
| crop->height = OV5647_WINDOW_HEIGHT_DEF; |
| |
| - format->code = MEDIA_BUS_FMT_SBGGR8_1X8; |
| - |
| - format->width = OV5647_WINDOW_WIDTH_DEF; |
| - format->height = OV5647_WINDOW_HEIGHT_DEF; |
| - format->field = V4L2_FIELD_NONE; |
| - format->colorspace = V4L2_COLORSPACE_SRGB; |
| + /* Set the default format to the same as the sensor. */ |
| + *format = state->mode->format; |
| |
| return 0; |
| } |
| @@ -660,6 +793,9 @@ static int ov5647_probe(struct i2c_clien |
| |
| mutex_init(&sensor->lock); |
| |
| + /* Set the default mode before we init the subdev */ |
| + sensor->mode = OV5647_DEFAULT_MODE; |
| + |
| sd = &sensor->sd; |
| v4l2_i2c_subdev_init(sd, client, &ov5647_subdev_ops); |
| sensor->sd.internal_ops = &ov5647_subdev_internal_ops; |