Although one could have a working setup with a sensor such as the
MT9V024 connected via LVDS to the deserializer and then via parallel to
the SoC's camera interface even without having a driver for the
deserializer, controlling the deserializer's state is needed when
multiple cameras share the parallel bus. By enabling only one at a time,
a camera can be selected at runtime using mediactl.

This driver will en-/disable the deserializer via GPIOs as needed
depending on the media entity link state.

The current v4l2-compliance doesn't report any warnings or errors.

Signed-off-by: Jan Luebbe <j...@pengutronix.de>
---
 drivers/media/platform/Kconfig       |   7 +
 drivers/media/platform/Makefile      |   2 +
 drivers/media/platform/scan921226h.c | 353 +++++++++++++++++++++++++++
 3 files changed, 362 insertions(+)
 create mode 100644 drivers/media/platform/scan921226h.c

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index c7a1cf8a1b01..f321f895d173 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -384,6 +384,13 @@ config VIDEO_STI_DELTA_DRIVER
 
 endif # VIDEO_STI_DELTA
 
+config VIDEO_SCAN921226H
+       tristate "TI SCAN921226H LVDS deserializer driver"
+       depends on VIDEO_DEV && VIDEO_V4L2
+       help
+             Enables support for the SCAN921226H LVDS deserializer, which is
+             controlled via two GPIOs (output enable and power down).
+
 config VIDEO_SH_VEU
        tristate "SuperH VEU mem2mem video processing driver"
        depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 932515df4477..45f90189a193 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -53,6 +53,8 @@ obj-y                                 += stm32/
 
 obj-y                                  += davinci/
 
+obj-$(CONFIG_VIDEO_SCAN921226H)                += scan921226h.o
+
 obj-$(CONFIG_VIDEO_SH_VOU)             += sh_vou.o
 
 obj-$(CONFIG_SOC_CAMERA)               += soc_camera/
diff --git a/drivers/media/platform/scan921226h.c 
b/drivers/media/platform/scan921226h.c
new file mode 100644
index 000000000000..59fcd55ceaa2
--- /dev/null
+++ b/drivers/media/platform/scan921226h.c
@@ -0,0 +1,353 @@
+/*
+ * TI SCAN921226H deserializer driver
+ *
+ * Copyright (C) 2018 Pengutronix, Jan Luebbe <ker...@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+struct video_des {
+       struct v4l2_subdev subdev;
+       struct media_pad pads[2];
+       struct v4l2_mbus_framefmt format_mbus;
+       struct gpio_desc *npwrdn_gpio;
+       struct gpio_desc *enable_gpio;
+       struct mutex lock;
+       int active;
+};
+
+static inline struct video_des *v4l2_subdev_to_video_des(struct v4l2_subdev 
*sd)
+{
+       return container_of(sd, struct video_des, subdev);
+}
+
+static int video_des_link_setup(struct media_entity *entity,
+                               const struct media_pad *local,
+                               const struct media_pad *remote, u32 flags)
+{
+       struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+       struct video_des *vdes = v4l2_subdev_to_video_des(sd);
+
+       /*
+        * The deserializer state is determined by the enabled source pad link.
+        * Enabling or disabling the sink pad link has no effect.
+        */
+       if (local->flags & MEDIA_PAD_FL_SINK)
+               return 0;
+
+       dev_dbg(sd->dev, "link setup '%s':%d->'%s':%d[%d]",
+               remote->entity->name, remote->index, local->entity->name,
+               local->index, flags & MEDIA_LNK_FL_ENABLED);
+
+       mutex_lock(&vdes->lock);
+
+       if (flags & MEDIA_LNK_FL_ENABLED) {
+               dev_dbg(sd->dev, "going active\n");
+               gpiod_set_value_cansleep(vdes->npwrdn_gpio, 1);
+               udelay(10); /* wait for the PLL to lock */
+               gpiod_set_value_cansleep(vdes->enable_gpio, 1);
+       } else {
+               dev_dbg(sd->dev, "going inactive\n");
+               gpiod_set_value_cansleep(vdes->enable_gpio, 0);
+               gpiod_set_value_cansleep(vdes->npwrdn_gpio, 0);
+       }
+
+       mutex_unlock(&vdes->lock);
+
+       return 0;
+}
+
+static const struct media_entity_operations video_des_ops = {
+       .link_setup = video_des_link_setup,
+       .link_validate = v4l2_subdev_link_validate,
+};
+
+static int video_des_s_stream(struct v4l2_subdev *sd, int enable)
+{
+       struct v4l2_subdev *upstream_sd;
+       struct media_pad *pad;
+
+       pad = media_entity_remote_pad(&sd->entity.pads[0]);
+       if (!pad) {
+               dev_err(sd->dev, "Failed to find remote source pad\n");
+               return -ENOLINK;
+       }
+
+       if (!is_media_entity_v4l2_subdev(pad->entity)) {
+               dev_err(sd->dev, "Upstream entity is not a v4l2 subdev\n");
+               return -ENODEV;
+       }
+
+       upstream_sd = media_entity_to_v4l2_subdev(pad->entity);
+
+       return v4l2_subdev_call(upstream_sd, video, s_stream, enable);
+}
+
+static const struct v4l2_subdev_video_ops video_des_subdev_video_ops = {
+       .s_stream = video_des_s_stream,
+};
+
+static struct v4l2_mbus_framefmt *
+__video_des_get_pad_format(struct v4l2_subdev *sd,
+                          struct v4l2_subdev_pad_config *cfg,
+                          unsigned int pad, u32 which)
+{
+       struct video_des *vdes = v4l2_subdev_to_video_des(sd);
+
+       switch (which) {
+       case V4L2_SUBDEV_FORMAT_TRY:
+               return v4l2_subdev_get_try_format(sd, cfg, pad);
+       case V4L2_SUBDEV_FORMAT_ACTIVE:
+               return &vdes->format_mbus;
+       default:
+               return NULL;
+       }
+}
+
+static int video_des_get_format(struct v4l2_subdev *sd,
+                           struct v4l2_subdev_pad_config *cfg,
+                           struct v4l2_subdev_format *sdformat)
+{
+       struct video_des *vdes = v4l2_subdev_to_video_des(sd);
+
+       mutex_lock(&vdes->lock);
+
+       sdformat->format = *__video_des_get_pad_format(sd, cfg, sdformat->pad,
+                                                      sdformat->which);
+
+       mutex_unlock(&vdes->lock);
+
+       return 0;
+}
+
+static int video_des_set_format(struct v4l2_subdev *sd,
+                           struct v4l2_subdev_pad_config *cfg,
+                           struct v4l2_subdev_format *sdformat)
+{
+       struct video_des *vdes = v4l2_subdev_to_video_des(sd);
+       struct v4l2_mbus_framefmt *mbusformat;
+       struct media_pad *pad = &vdes->pads[sdformat->pad];
+
+       mbusformat = __video_des_get_pad_format(sd, cfg, sdformat->pad,
+                                           sdformat->which);
+       if (!mbusformat)
+               return -EINVAL;
+
+       mutex_lock(&vdes->lock);
+
+       /* Source pad mirrors sink pad, no limitations on sink pads */
+       if ((pad->flags & MEDIA_PAD_FL_SOURCE)) {
+               sdformat->format = vdes->format_mbus;
+       } else {
+               /* any sizes are allowed */
+               v4l_bound_align_image(
+                       &sdformat->format.width, 1, UINT_MAX-1, 0,
+                       &sdformat->format.height, 1, UINT_MAX-1, 0,
+                       0);
+               if (sdformat->format.field == V4L2_FIELD_ANY)
+                       sdformat->format.field = V4L2_FIELD_NONE;
+               switch (sdformat->format.code) {
+               /* only 8 bit formats are supported */
+               case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE:
+               case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE:
+               case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE:
+               case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE:
+               case MEDIA_BUS_FMT_BGR565_2X8_BE:
+               case MEDIA_BUS_FMT_BGR565_2X8_LE:
+               case MEDIA_BUS_FMT_RGB565_2X8_BE:
+               case MEDIA_BUS_FMT_RGB565_2X8_LE:
+               case MEDIA_BUS_FMT_Y8_1X8:
+               case MEDIA_BUS_FMT_UV8_1X8:
+               case MEDIA_BUS_FMT_UYVY8_1_5X8:
+               case MEDIA_BUS_FMT_VYUY8_1_5X8:
+               case MEDIA_BUS_FMT_YUYV8_1_5X8:
+               case MEDIA_BUS_FMT_YVYU8_1_5X8:
+               case MEDIA_BUS_FMT_UYVY8_2X8:
+               case MEDIA_BUS_FMT_VYUY8_2X8:
+               case MEDIA_BUS_FMT_YUYV8_2X8:
+               case MEDIA_BUS_FMT_YVYU8_2X8:
+               case MEDIA_BUS_FMT_SBGGR8_1X8:
+               case MEDIA_BUS_FMT_SGBRG8_1X8:
+               case MEDIA_BUS_FMT_SGRBG8_1X8:
+               case MEDIA_BUS_FMT_SRGGB8_1X8:
+               case MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8:
+               case MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8:
+               case MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8:
+               case MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8:
+               case MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8:
+               case MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8:
+               case MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8:
+               case MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8:
+               case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE:
+               case MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE:
+               case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE:
+               case MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE:
+               case MEDIA_BUS_FMT_JPEG_1X8:
+               case MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8:
+                       break;
+               default:
+                       sdformat->format.code = MEDIA_BUS_FMT_Y8_1X8;
+               }
+       }
+
+       *mbusformat = sdformat->format;
+
+       mutex_unlock(&vdes->lock);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_pad_ops video_des_pad_ops = {
+       .get_fmt = video_des_get_format,
+       .set_fmt = video_des_set_format,
+};
+
+static int video_des_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+       struct video_des *vdes = v4l2_subdev_to_video_des(sd);
+       struct v4l2_mbus_framefmt *format;
+
+       mutex_lock(&vdes->lock);
+
+       format = v4l2_subdev_get_try_format(sd, fh->pad, 0);
+       *format = vdes->format_mbus;
+       format = v4l2_subdev_get_try_format(sd, fh->pad, 1);
+       *format = vdes->format_mbus;
+
+       mutex_unlock(&vdes->lock);
+
+       return 0;
+}
+
+static const struct v4l2_subdev_internal_ops video_des_subdev_internal_ops = {
+       .open = video_des_open,
+};
+
+static const struct v4l2_subdev_ops video_des_subdev_ops = {
+       .pad = &video_des_pad_ops,
+       .video = &video_des_subdev_video_ops,
+};
+
+static int video_des_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct device *dev = &pdev->dev;
+       struct device_node *ep;
+       struct video_des *vdes;
+       unsigned int num_pads = 0;
+       int ret;
+
+       vdes = devm_kzalloc(dev, sizeof(*vdes), GFP_KERNEL);
+       if (!vdes)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, vdes);
+
+       v4l2_subdev_init(&vdes->subdev, &video_des_subdev_ops);
+       snprintf(vdes->subdev.name, sizeof(vdes->subdev.name), "%s", np->name);
+       vdes->subdev.internal_ops = &video_des_subdev_internal_ops;
+       vdes->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+       vdes->subdev.dev = dev;
+
+       /*
+        * We only have two ports: sink and source
+        */
+       for_each_endpoint_of_node(np, ep) {
+               struct of_endpoint endpoint;
+
+               of_graph_parse_endpoint(ep, &endpoint);
+               num_pads = max(num_pads, endpoint.port + 1);
+       }
+
+       if (num_pads != 2) {
+               dev_err(dev, "Wrong number of ports %d (!= 2)\n", num_pads);
+               return -EINVAL;
+       }
+
+       vdes->npwrdn_gpio = devm_gpiod_get(dev, "npwrdn", GPIOD_OUT_LOW);
+       if (IS_ERR(vdes->npwrdn_gpio)) {
+               ret = PTR_ERR(vdes->npwrdn_gpio);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Failed to get npwrdn GPIO: %d\n", ret);
+               return ret;
+       }
+
+       vdes->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+       if (IS_ERR(vdes->enable_gpio)) {
+               ret = PTR_ERR(vdes->enable_gpio);
+               if (ret != -EPROBE_DEFER)
+                       dev_err(dev, "Failed to get enable GPIO: %d\n", ret);
+               return ret;
+       }
+
+       mutex_init(&vdes->lock);
+
+       vdes->pads[0].flags = MEDIA_PAD_FL_SINK;
+       vdes->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+
+       vdes->format_mbus.width = 1;
+       vdes->format_mbus.height = 1;
+       vdes->format_mbus.code = MEDIA_BUS_FMT_Y8_1X8;
+       vdes->format_mbus.field = V4L2_FIELD_NONE;
+
+       vdes->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+       ret = media_entity_pads_init(&vdes->subdev.entity, 2,
+                                    vdes->pads);
+       if (ret < 0)
+               return ret;
+
+       vdes->subdev.entity.ops = &video_des_ops;
+
+       return v4l2_async_register_subdev(&vdes->subdev);
+}
+
+static int video_des_remove(struct platform_device *pdev)
+{
+       struct video_des *vdes = platform_get_drvdata(pdev);
+       struct v4l2_subdev *sd = &vdes->subdev;
+
+       v4l2_async_unregister_subdev(sd);
+       media_entity_cleanup(&sd->entity);
+
+       return 0;
+}
+
+static const struct of_device_id video_des_dt_ids[] = {
+       { .compatible = "ti,scan921226h", },
+       { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, video_des_dt_ids);
+
+static struct platform_driver video_des_driver = {
+       .probe          = video_des_probe,
+       .remove         = video_des_remove,
+       .driver         = {
+               .of_match_table = video_des_dt_ids,
+               .name = "scan921226h",
+       },
+};
+
+module_platform_driver(video_des_driver);
+
+MODULE_DESCRIPTION("SCAN921226H video deserializer");
+MODULE_AUTHOR("Jan Luebbe, Pengutronix");
+MODULE_LICENSE("GPL");
-- 
2.17.0

Reply via email to