media: ov5647: Add basic support for multiple sensor modes.
authorDavid Plowman <david.plowman@raspberrypi.com>
Wed, 29 Jan 2020 15:30:53 +0000 (15:30 +0000)
committerpopcornmix <popcornmix@gmail.com>
Wed, 27 Jan 2021 19:13:09 +0000 (19:13 +0000)
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

index 6ecc018..0d84b76 100644 (file)
@@ -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(struct v4l2_subdev *sd, int channel)
        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_subdev *sd, bool standby)
        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 v4l2_subdev *sd, int on)
                }
 
                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 v4l2_subdev *sd, int on)
                        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 v4l2_subdev *sd, int on)
                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 ov5647_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 v4l2_subdev *sd,
                                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;
+
+       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;
+       }
 
-       code->code = MEDIA_BUS_FMT_SBGGR8_1X8;
+       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_get_fmt(struct v4l2_subdev *sd,
-                             struct v4l2_subdev_pad_config *cfg,
-                             struct v4l2_subdev_format *format)
+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;
 
-       /* 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);
+
+       /*
+        * 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_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;
+
+       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_subdev *sd, struct v4l2_subdev_fh *fh)
                                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_client *client)
 
        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;