From patchwork Thu Jun 12 17:06:38 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Philipp Zabel X-Patchwork-Id: 4343881 Return-Path: X-Original-To: patchwork-linux-media@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 027979F3B4 for ; Thu, 12 Jun 2014 17:07:49 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id A752620340 for ; Thu, 12 Jun 2014 17:07:47 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 6B09520351 for ; Thu, 12 Jun 2014 17:07:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756312AbaFLRG7 (ORCPT ); Thu, 12 Jun 2014 13:06:59 -0400 Received: from metis.ext.pengutronix.de ([92.198.50.35]:33049 "EHLO metis.ext.pengutronix.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756270AbaFLRGr (ORCPT ); Thu, 12 Jun 2014 13:06:47 -0400 Received: from dude.hi.pengutronix.de ([10.1.0.7] helo=dude.pengutronix.de) by metis.ext.pengutronix.de with esmtp (Exim 4.72) (envelope-from ) id 1Wv8Sa-0000jm-S1; Thu, 12 Jun 2014 19:06:44 +0200 From: Philipp Zabel To: linux-media@vger.kernel.org Cc: Steve Longerbeam , Philipp Zabel , Sascha Hauer Subject: [RFC PATCH 24/26] [media] imx: Add video switch Date: Thu, 12 Jun 2014 19:06:38 +0200 Message-Id: <1402592800-2925-25-git-send-email-p.zabel@pengutronix.de> X-Mailer: git-send-email 2.0.0.rc2 In-Reply-To: <1402592800-2925-1-git-send-email-p.zabel@pengutronix.de> References: <1402592800-2925-1-git-send-email-p.zabel@pengutronix.de> X-SA-Exim-Connect-IP: 10.1.0.7 X-SA-Exim-Mail-From: p.zabel@pengutronix.de X-SA-Exim-Scanned: No (on metis.ext.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-media@vger.kernel.org Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Spam-Status: No, score=-7.5 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This driver can handle SoC internal and extern video bus multiplexers, controlled either by register bit fields or by GPIO. Signed-off-by: Sascha Hauer Signed-off-by: Philipp Zabel --- drivers/media/platform/imx/Kconfig | 8 + drivers/media/platform/imx/Makefile | 1 + drivers/media/platform/imx/imx-video-switch.c | 347 ++++++++++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 drivers/media/platform/imx/imx-video-switch.c diff --git a/drivers/media/platform/imx/Kconfig b/drivers/media/platform/imx/Kconfig index 8431441..ffced1c 100644 --- a/drivers/media/platform/imx/Kconfig +++ b/drivers/media/platform/imx/Kconfig @@ -11,6 +11,14 @@ config MEDIA_IMX_IPU This driver provides core support for the i.MX IPUv3 contained in i.MX5 and i.MX6 SoCs. +config MEDIA_IMX_VIDEO_SWITCH + tristate "i.MX Video Bus Multiplexer" + depends on VIDEO_V4L2_SUBDEV_API + help + This driver provides support for SoC internal video bus multiplexers + controlled by register bitfields as well as external multiplexers + controller by a GPIO. + config VIDEO_IMX_IPU_COMMON tristate diff --git a/drivers/media/platform/imx/Makefile b/drivers/media/platform/imx/Makefile index 49d8fab..d972674 100644 --- a/drivers/media/platform/imx/Makefile +++ b/drivers/media/platform/imx/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_MEDIA_IMX) += imx-media.o +obj-$(CONFIG_MEDIA_IMX_VIDEO_SWITCH) += imx-video-switch.o obj-$(CONFIG_VIDEO_IMX_IPU_COMMON) += imx-ipu.o obj-$(CONFIG_VIDEO_IMX_IPU_CSI) += imx-ipuv3-csi.o obj-$(CONFIG_VIDEO_IMX_IPU_SCALER) += imx-ipu-scaler.o diff --git a/drivers/media/platform/imx/imx-video-switch.c b/drivers/media/platform/imx/imx-video-switch.c new file mode 100644 index 0000000..7a5f64f --- /dev/null +++ b/drivers/media/platform/imx/imx-video-switch.c @@ -0,0 +1,347 @@ +/* + * devicetree probed mediacontrol multiplexer. + * + * Copyright (C) 2013 Sascha Hauer, Pengutronix + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include /* for ipu_media_entity_create_link */ + +enum { + PAD_INPUT0, + PAD_INPUT1, + PAD_OUTPUT, + PAD_NUM, +}; + +struct vidsw { + struct device *dev; + struct v4l2_subdev subdev; + struct media_pad pads[PAD_NUM]; + struct v4l2_mbus_framefmt format_mbus[PAD_NUM]; + struct v4l2_of_endpoint endpoint[PAD_NUM - 1]; + struct regmap_field *field; + unsigned int gpio; + int streaming; + int active; +}; + +#define to_vidsw(sd) container_of(sd, struct vidsw, subdev) + +static int vidsw_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 vidsw *vidsw = to_vidsw(sd); + + dev_dbg(vidsw->dev, "link setup %s -> %s", remote->entity->name, + local->entity->name); + + if (vidsw->streaming) + return -EBUSY; + + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + if (local->index == vidsw->active) { + dev_dbg(vidsw->dev, "going inactive\n"); + vidsw->active = -1; + } + return 0; + } + + if (vidsw->active >= 0) { + if (vidsw->active == local->index) + return 0; + else + return -EBUSY; + } + + vidsw->active = local->index; + + dev_dbg(vidsw->dev, "setting %d active\n", vidsw->active); + + if (vidsw->field) + regmap_field_write(vidsw->field, vidsw->active); + else if (gpio_is_valid(vidsw->gpio)) + gpio_set_value(vidsw->gpio, vidsw->active); + + return 0; +} + +static struct media_entity_operations vidsw_ops = { + .link_setup = vidsw_link_setup, +}; + +static int vidsw_async_init(struct vidsw *vidsw, struct device_node *node) +{ + struct v4l2_of_endpoint endpoint; + struct device_node *rp, *port; + u32 portno; + int numports; + int ret; + + numports = PAD_NUM; + + vidsw->pads[PAD_INPUT0].flags = MEDIA_PAD_FL_SINK; + vidsw->pads[PAD_INPUT1].flags = MEDIA_PAD_FL_SINK; + vidsw->pads[PAD_OUTPUT].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_init(&vidsw->subdev.entity, numports, vidsw->pads, 0); + if (ret < 0) + return ret; + + vidsw->subdev.entity.ops = &vidsw_ops; + + rp = NULL; + + while (1) { + rp = of_graph_get_next_endpoint(node, rp); + if (!rp) + break; + + port = of_get_parent(rp); + if (!port) + return -EINVAL; + + portno = 0; + of_property_read_u32(port, "reg", &portno); + + v4l2_of_parse_endpoint(rp, &endpoint); + of_node_put(rp); + + if (portno > 1) + break; + + vidsw->endpoint[portno] = endpoint; + + /* FIXME */ + ipu_media_entity_create_link(&vidsw->subdev, portno, rp, 0); + } + + return 0; +} + +static int vidsw_registered(struct v4l2_subdev *sd) +{ + return 0; +} + +int vidsw_g_mbus_config(struct v4l2_subdev *sd, struct v4l2_mbus_config *cfg) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + + dev_dbg(vidsw->dev, "reporting configration %d\n", vidsw->active); + + /* Mirror the input side on the output side */ + cfg->type = vidsw->endpoint[vidsw->active].bus_type; + if (cfg->type == V4L2_MBUS_PARALLEL || cfg->type == V4L2_MBUS_BT656) + cfg->flags = vidsw->endpoint[vidsw->active].bus.parallel.flags; + + return 0; +} + +static const struct v4l2_subdev_video_ops vidsw_subdev_video_ops = { + .g_mbus_config = vidsw_g_mbus_config, +}; + +static struct v4l2_mbus_framefmt * +__vidsw_get_pad_format(struct vidsw *vidsw, struct v4l2_subdev_fh *fh, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(fh, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &vidsw->format_mbus[pad]; + default: + return NULL; + } +} + +static int vidsw_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *sdformat) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + + sdformat->format = *__vidsw_get_pad_format(vidsw, fh, sdformat->pad, + sdformat->which); + return 0; +} + +static int vidsw_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh, + struct v4l2_subdev_format *sdformat) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + struct v4l2_mbus_framefmt *mbusformat; + + if (sdformat->pad >= PAD_NUM) + return -EINVAL; + + mbusformat = __vidsw_get_pad_format(vidsw, fh, sdformat->pad, + sdformat->which); + if (!mbusformat) + return -EINVAL; + + /* Output pad mirrors active input pad, no limitations on input pads */ + if (sdformat->pad == PAD_OUTPUT && vidsw->active >= 0) + *mbusformat = vidsw->format_mbus[vidsw->active]; + else + *mbusformat = sdformat->format; + + sdformat->format = *mbusformat; + + return 0; +} + +static struct v4l2_subdev_pad_ops vidsw_pad_ops = { + .get_fmt = vidsw_get_format, + .set_fmt = vidsw_set_format, +}; + +static struct v4l2_subdev_ops vidsw_subdev_ops = { + .pad = &vidsw_pad_ops, + .video = &vidsw_subdev_video_ops, +}; + +static struct v4l2_subdev_internal_ops vidsw_internal_ops = { + .registered = vidsw_registered, +}; + +static int of_get_reg_field(struct device_node *node, struct reg_field *field) +{ + u32 bit_mask; + int ret; + + ret = of_property_read_u32(node, "reg", &field->reg); + if (ret < 0) + return ret; + + ret = of_property_read_u32(node, "bit-mask", &bit_mask); + if (ret < 0) + return ret; + + ret = of_property_read_u32(node, "bit-shift", &field->lsb); + if (ret < 0) + return ret; + + field->msb = field->lsb + ffs(bit_mask) - 1; + + return 0; +} + +static int vidsw_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct reg_field field; + struct vidsw *vidsw; + struct regmap *map; + int num_pads; + int ret; + + vidsw = devm_kzalloc(&pdev->dev, sizeof(*vidsw), GFP_KERNEL); + if (!vidsw) + return -ENOMEM; + + platform_set_drvdata(pdev, vidsw); + + v4l2_subdev_init(&vidsw->subdev, &vidsw_subdev_ops); + vidsw->subdev.internal_ops = &vidsw_internal_ops; + snprintf(vidsw->subdev.name, sizeof(vidsw->subdev.name), "%s", + np->name); + vidsw->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + vidsw->subdev.dev = &pdev->dev; + vidsw->dev = &pdev->dev; + vidsw->active = -1; + + num_pads = of_get_child_count(np); + if (num_pads < 2) { + dev_err(&pdev->dev, "Not enough ports %d\n", num_pads); + return -EINVAL; + } + + ret = of_get_reg_field(np, &field); + if (ret == 0) { + map = syscon_node_to_regmap(np->parent); + if (!map) { + dev_err(&pdev->dev, "Failed to get syscon register map\n"); + return PTR_ERR(map); + } + + vidsw->field = devm_regmap_field_alloc(&pdev->dev, map, field); + if (IS_ERR(vidsw->field)) { + dev_err(&pdev->dev, "Failed to allocate regmap field\n"); + return PTR_ERR(vidsw->field); + } + } else { + vidsw->gpio = of_get_named_gpio_flags(np, "gpios", 0, + NULL); + ret = gpio_request_one(vidsw->gpio, + GPIOF_OUT_INIT_LOW, np->name); + if (ret < 0) { + dev_warn(&pdev->dev, + "could not request control gpio %d: %d\n", + vidsw->gpio, ret); + vidsw->gpio = -1; + } + } + + ret = vidsw_async_init(vidsw, np); + if (ret) + return ret; + + ret = v4l2_async_register_subdev(&vidsw->subdev); + if (ret) + return ret; + + return 0; +} + +static int vidsw_remove(struct platform_device *pdev) +{ + /* FIXME */ + + return -EBUSY; +} + +static const struct of_device_id vidsw_dt_ids[] = { + { .compatible = "video-multiplexer", }, + { /* sentinel */ } +}; + +static struct platform_driver vidsw_driver = { + .probe = vidsw_probe, + .remove = vidsw_remove, + .driver = { + .of_match_table = vidsw_dt_ids, + .name = "video-multiplexer", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(vidsw_driver); + +MODULE_DESCRIPTION("i.MX video stream multiplexer"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL");