media: bcm2835-unicam: Rework to not cache the list of active fmts
authorDave Stevenson <dave.stevenson@raspberrypi.org>
Wed, 2 Oct 2019 16:40:38 +0000 (17:40 +0100)
committerPhil Elwell <pelwell@users.noreply.github.com>
Thu, 10 Oct 2019 12:34:04 +0000 (13:34 +0100)
Some sensors will change Bayer order based on H & V flips,
therefore collecting the list of formats at async_bound has
problems.

Enumerate the formats from the sensor every time.

Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.org>
drivers/media/platform/bcm2835/bcm2835-unicam.c

index 7b50775..7c77337 100644 (file)
@@ -363,8 +363,6 @@ struct unicam_device {
        /* Used to store current mbus frame format */
        struct v4l2_mbus_framefmt m_fmt;
 
-       struct unicam_fmt active_fmts[MAX_POSSIBLE_PIX_FMTS];
-       int num_active_fmt;
        unsigned int virtual_channel;
        enum v4l2_mbus_type bus_type;
        /*
@@ -455,48 +453,30 @@ static int find_mbus_depth_by_code(u32 code)
        return 0;
 }
 
-static const struct unicam_fmt *find_format_by_code(struct unicam_device *dev,
-                                                   u32 code)
+static const struct unicam_fmt *find_format_by_code(u32 code)
 {
-       const struct unicam_fmt *fmt;
        unsigned int k;
 
-       for (k = 0; k < dev->num_active_fmt; k++) {
-               fmt = &dev->active_fmts[k];
-               if (fmt->code == code)
-                       return fmt;
+       for (k = 0; k < ARRAY_SIZE(formats); k++) {
+               if (formats[k].code == code)
+                       return &formats[k];
        }
 
        return NULL;
 }
 
-static const struct unicam_fmt *find_format_by_pix(struct unicam_device *dev,
-                                                  u32 pixelformat)
+static const struct unicam_fmt *find_format_by_pix(u32 pixelformat)
 {
-       const struct unicam_fmt *fmt;
        unsigned int k;
 
-       for (k = 0; k < dev->num_active_fmt; k++) {
-               fmt = &dev->active_fmts[k];
-               if (fmt->fourcc == pixelformat)
-                       return fmt;
+       for (k = 0; k < ARRAY_SIZE(formats); k++) {
+               if (formats[k].fourcc == pixelformat)
+                       return &formats[k];
        }
 
        return NULL;
 }
 
-static void dump_active_formats(struct unicam_device *dev)
-{
-       int i;
-
-       for (i = 0; i < dev->num_active_fmt; i++) {
-               unicam_dbg(3, dev, "active_fmt[%d] (%p) is code %04x, fourcc " V4L2_FOURCC_CONV ", depth %d\n",
-                          i, &dev->active_fmts[i], dev->active_fmts[i].code,
-                          V4L2_FOURCC_CONV_ARGS(dev->active_fmts[i].fourcc),
-                          dev->active_fmts[i].depth);
-       }
-}
-
 static inline unsigned int bytes_per_line(u32 width,
                                          const struct unicam_fmt *fmt)
 {
@@ -726,14 +706,40 @@ static int unicam_enum_fmt_vid_cap(struct file *file, void  *priv,
                                   struct v4l2_fmtdesc *f)
 {
        struct unicam_device *dev = video_drvdata(file);
+       struct v4l2_subdev_mbus_code_enum mbus_code;
        const struct unicam_fmt *fmt = NULL;
+       int index = 0;
+       int ret = 0;
+       int i;
 
-       if (f->index >= dev->num_active_fmt)
-               return -EINVAL;
+       /* Loop whilst the sensor driver says it has more formats, but add a
+        * failsafe against a dodgy driver at 128 (more than any sensor will
+        * ever sensibly advertise)
+        */
+       for (i = 0; !ret && i < 128 ; i++) {
+               memset(&mbus_code, 0, sizeof(mbus_code));
+               mbus_code.index = i;
 
-       fmt = &dev->active_fmts[f->index];
+               ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code,
+                                      NULL, &mbus_code);
+               if (ret < 0) {
+                       unicam_dbg(2, dev,
+                                  "subdev->enum_mbus_code idx %d returned %d - index invalid\n",
+                                  i, ret);
+                       return -EINVAL;
+               }
 
-       f->pixelformat = fmt->fourcc;
+               fmt = find_format_by_code(mbus_code.code);
+               if (fmt) {
+                       if (fmt->fourcc) {
+                               if (index == f->index) {
+                                       f->pixelformat = fmt->fourcc;
+                                       break;
+                               }
+                               index++;
+                       }
+               }
+       }
 
        return 0;
 }
@@ -748,6 +754,39 @@ static int unicam_g_fmt_vid_cap(struct file *file, void *priv,
        return 0;
 }
 
+static
+const struct unicam_fmt *get_first_supported_format(struct unicam_device *dev)
+{
+       struct v4l2_subdev_mbus_code_enum mbus_code;
+       const struct unicam_fmt *fmt = NULL;
+       int ret;
+       int j;
+
+       for (j = 0; ret != -EINVAL && ret != -ENOIOCTLCMD; ++j) {
+               memset(&mbus_code, 0, sizeof(mbus_code));
+               mbus_code.index = j;
+               ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, NULL,
+                                      &mbus_code);
+               if (ret < 0) {
+                       unicam_dbg(2, dev,
+                                  "subdev->enum_mbus_code idx %d returned %d - continue\n",
+                                  j, ret);
+                       continue;
+               }
+
+               unicam_dbg(2, dev, "subdev %s: code: %04x idx: %d\n",
+                          dev->sensor->name, mbus_code.code, j);
+
+               fmt = find_format_by_code(mbus_code.code);
+               unicam_dbg(2, dev, "fmt %04x returned as %p, V4L2 FOURCC %04x, csi_dt %02X\n",
+                          mbus_code.code, fmt, fmt ? fmt->fourcc : 0,
+                          fmt ? fmt->csi_dt : 0);
+               if (fmt)
+                       return fmt;
+       }
+
+       return NULL;
+}
 static int unicam_try_fmt_vid_cap(struct file *file, void *priv,
                                  struct v4l2_format *f)
 {
@@ -759,13 +798,15 @@ static int unicam_try_fmt_vid_cap(struct file *file, void *priv,
        struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format;
        int ret;
 
-       fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat);
+       fmt = find_format_by_pix(f->fmt.pix.pixelformat);
        if (!fmt) {
-               unicam_dbg(3, dev, "Fourcc format (0x%08x) not found. Use default of %08X\n",
-                          f->fmt.pix.pixelformat, dev->active_fmts[0].fourcc);
+               /* Pixel format not supported by unicam. Choose the first
+                * supported format, and let the sensor choose something else.
+                */
+               unicam_dbg(3, dev, "Fourcc format (0x%08x) not found. Use first format.\n",
+                          f->fmt.pix.pixelformat);
 
-               /* Just get the first one enumerated */
-               fmt = &dev->active_fmts[0];
+               fmt = &formats[0];
                f->fmt.pix.pixelformat = fmt->fourcc;
        }
 
@@ -785,6 +826,40 @@ static int unicam_try_fmt_vid_cap(struct file *file, void *priv,
                unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n");
 
        v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format);
+       if (mbus_fmt->code != fmt->code) {
+               /* Sensor has returned an alternate format */
+               fmt = find_format_by_code(mbus_fmt->code);
+               if (!fmt) {
+                       /* The alternate format is one unicam can't support.
+                        * Find the first format that is supported by both, and
+                        * then set that.
+                        */
+                       fmt = get_first_supported_format(dev);
+                       mbus_fmt->code = fmt->code;
+
+                       ret = v4l2_subdev_call(dev->sensor, pad, set_fmt,
+                                              dev->sensor_config, &sd_fmt);
+                       if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV)
+                               return ret;
+
+                       if (mbus_fmt->field != V4L2_FIELD_NONE)
+                               unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n");
+
+                       v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format);
+
+                       if (mbus_fmt->code != fmt->code) {
+                               /* We've set a format that the sensor reports
+                                * as being supported, but it refuses to set it.
+                                * Not much else we can do.
+                                * Assume that the sensor driver may accept the
+                                * format when it is set (rather than tried).
+                                */
+                               unicam_err(dev, "Sensor won't accept default format, and Unicam can't support sensor default\n");
+                       }
+               }
+
+               f->fmt.pix.pixelformat = fmt->fourcc;
+       }
 
        return unicam_calc_format_size_bpl(dev, fmt, f);
 }
@@ -805,10 +880,18 @@ static int unicam_s_fmt_vid_cap(struct file *file, void *priv,
        if (ret < 0)
                return ret;
 
-       fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat);
+       fmt = find_format_by_pix(f->fmt.pix.pixelformat);
        if (!fmt) {
-               /* Unknown pixel format - adopt a default */
-               fmt = &dev->active_fmts[0];
+               /* Unknown pixel format - adopt a default.
+                * This shouldn't happen as try_fmt should have resolved any
+                * issues first.
+                */
+               fmt = get_first_supported_format(dev);
+               if (!fmt)
+                       /* It shouldn't be possible to get here with no
+                        * supported formats
+                        */
+                       return -EINVAL;
                f->fmt.pix.pixelformat = fmt->fourcc;
                return -EINVAL;
        }
@@ -944,6 +1027,7 @@ static void unicam_set_packing_config(struct unicam_device *dev)
                        unpack = UNICAM_PUM_NONE;
                        break;
                }
+
                switch (v4l2_depth) {
                case 8:
                        pack = UNICAM_PPM_PACK8;
@@ -1439,7 +1523,7 @@ static int unicam_enum_framesizes(struct file *file, void *priv,
        int ret;
 
        /* check for valid format */
-       fmt = find_format_by_pix(dev, fsize->pixel_format);
+       fmt = find_format_by_pix(fsize->pixel_format);
        if (!fmt) {
                unicam_dbg(3, dev, "Invalid pixel code: %x\n",
                           fsize->pixel_format);
@@ -1478,7 +1562,7 @@ static int unicam_enum_frameintervals(struct file *file, void *priv,
        };
        int ret;
 
-       fmt = find_format_by_pix(dev, fival->pixel_format);
+       fmt = find_format_by_pix(fival->pixel_format);
        if (!fmt)
                return -EINVAL;
 
@@ -1742,27 +1826,6 @@ static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
        .vidioc_unsubscribe_event       = v4l2_event_unsubscribe,
 };
 
-/*
- * Adds an entry to the active_fmts array
- * Returns non-zero if attempting to write off the end of the array.
- */
-static int unicam_add_active_format(struct unicam_device *unicam,
-                                   const struct unicam_fmt *fmt)
-{
-       //Ensure we don't run off the end of the array.
-       if (unicam->num_active_fmt >= MAX_POSSIBLE_PIX_FMTS)
-               return 1;
-
-       unicam->active_fmts[unicam->num_active_fmt] = *fmt;
-       unicam_dbg(2, unicam,
-                  "matched fourcc: " V4L2_FOURCC_CONV ": code: %04x idx: %d\n",
-                  V4L2_FOURCC_CONV_ARGS(fmt->fourcc),
-                  fmt->code, unicam->num_active_fmt);
-       unicam->num_active_fmt++;
-
-       return 0;
-}
-
 static int
 unicam_async_bound(struct v4l2_async_notifier *notifier,
                   struct v4l2_subdev *subdev,
@@ -1770,9 +1833,6 @@ unicam_async_bound(struct v4l2_async_notifier *notifier,
 {
        struct unicam_device *unicam = container_of(notifier->v4l2_dev,
                                               struct unicam_device, v4l2_dev);
-       struct v4l2_subdev_mbus_code_enum mbus_code;
-       int ret = 0;
-       int j;
 
        if (unicam->sensor) {
                unicam_info(unicam, "Rejecting subdev %s (Already set!!)",
@@ -1783,47 +1843,6 @@ unicam_async_bound(struct v4l2_async_notifier *notifier,
        unicam->sensor = subdev;
        unicam_dbg(1, unicam, "Using sensor %s for capture\n", subdev->name);
 
-       /* Enumerate sub device formats and enable all matching local formats */
-       unicam->num_active_fmt = 0;
-       unicam_dbg(2, unicam, "Get supported formats...\n");
-       for (j = 0; ret != -EINVAL && ret != -ENOIOCTLCMD; ++j) {
-               const struct unicam_fmt *fmt = NULL;
-               int k;
-
-               memset(&mbus_code, 0, sizeof(mbus_code));
-               mbus_code.index = j;
-               ret = v4l2_subdev_call(subdev, pad, enum_mbus_code,
-                                      NULL, &mbus_code);
-               if (ret < 0) {
-                       unicam_dbg(2, unicam,
-                                  "subdev->enum_mbus_code idx %d returned %d - continue\n",
-                                  j, ret);
-                       continue;
-               }
-
-               unicam_dbg(2, unicam, "subdev %s: code: %04x idx: %d\n",
-                          subdev->name, mbus_code.code, j);
-
-               for (k = 0; k < ARRAY_SIZE(formats); k++) {
-                       if (mbus_code.code == formats[k].code) {
-                               fmt = &formats[k];
-                               break;
-                       }
-               }
-               unicam_dbg(2, unicam, "fmt %04x returned as %p, V4L2 FOURCC %04x, csi_dt %02X\n",
-                          mbus_code.code, fmt, fmt ? fmt->fourcc : 0,
-                          fmt ? fmt->csi_dt : 0);
-               if (fmt) {
-                       if (unicam_add_active_format(unicam, fmt)) {
-                               unicam_dbg(1, unicam, "Active fmt list truncated\n");
-                               break;
-                       }
-               }
-       }
-       unicam_dbg(2, unicam,
-                  "Done all formats\n");
-       dump_active_formats(unicam);
-
        return 0;
 }
 
@@ -1849,10 +1868,17 @@ static int unicam_probe_complete(struct unicam_device *unicam)
                return ret;
        }
 
-       fmt = find_format_by_code(unicam, mbus_fmt.code);
+       fmt = find_format_by_code(mbus_fmt.code);
        if (!fmt) {
-               /* Default image format not valid. Choose first active fmt. */
-               fmt = &unicam->active_fmts[0];
+               /* Find the first format that the sensor and unicam both
+                * support
+                */
+               fmt = get_first_supported_format(unicam);
+
+               if (!fmt)
+                       /* No compatible formats */
+                       return -EINVAL;
+
                mbus_fmt.code = fmt->code;
                ret = __subdev_set_format(unicam, &mbus_fmt);
                if (ret)