From patchwork Wed Jul 6 23:06:51 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Steve Longerbeam X-Patchwork-Id: 9217331 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id A5358607D9 for ; Wed, 6 Jul 2016 23:08:00 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 957B728523 for ; Wed, 6 Jul 2016 23:08:00 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 8A1C028526; Wed, 6 Jul 2016 23:08:00 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B50CF28523 for ; Wed, 6 Jul 2016 23:07:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932699AbcGFXHy (ORCPT ); Wed, 6 Jul 2016 19:07:54 -0400 Received: from mail-pa0-f68.google.com ([209.85.220.68]:34299 "EHLO mail-pa0-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932580AbcGFXHp (ORCPT ); Wed, 6 Jul 2016 19:07:45 -0400 Received: by mail-pa0-f68.google.com with SMTP id us13so98056pab.1 for ; Wed, 06 Jul 2016 16:07:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=6IzBz3bBeVWT4naeHydmpXHuPFfS4VF2qv6i8o7OtxE=; b=ui7NWV8DRuLtzYcId9Gy9wr3uaC84rgQmQDsjGy+JEp2KWyTJA5iAGOnwMLqwREj6M ygY6iQLqn+ZqSNIx6pHx8JUh41KKPqBXkPm5Bq/PIoU7lULPpRwvfCPxKpUaqg2Y01TD Hjt0RDVJRkJGBmNcXunndMybW5gNd3gLfNQVoEH3qNEYHJ8HnHlq+mM/Anva8FFWnU/s NuugG8WF8Na7d34Rb6qHWjsAjIpaUv0ab/ClSQmxxnL2lpWl2CglP85zX2tHwt6XFBii GoHMCBPdEbSOQQGyW72PLjT/GIUMWscnx3vn9TRNqWqw+sCAXJrJ9XWJvnII2EvR98t9 chGA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=6IzBz3bBeVWT4naeHydmpXHuPFfS4VF2qv6i8o7OtxE=; b=AyX0i+avaQXJnjnpLPhRGosbfloZXj0/OU+uXxJX25/5ejWLCArV7QHTFu40zQFhj4 q7DAZgPXaXUlbbpQvdBtD7lpZpRA9S0nREIqskuiB9D1IvvV5KajyS8ICECNX+uyHVbk NX2uEd1IUqLA/sie9jpwer5xO+RgklLbi/HpD+1pFakWt43MxJXlTcDTh2gohyQ1kMfN Mhnd9LfdDy8o0GnhSW32VKPARwF9Xf98efHuO3vMXJhcx+/GPZJrQAP1l2HLooW1YXFD rTCqN1bQ4OE8oesTh+m6xarAZ1d8ySbVu/YkR3Yot9sifueDSdBTydQFeFrTJ194ysh7 B/gQ== X-Gm-Message-State: ALyK8tLFdwk9x3rLZlOm0jatxFB2Sicl/6XRWCXI8Gpf9LMNXPhvtJfwnilsWDn7p6JDlg== X-Received: by 10.66.157.8 with SMTP id wi8mr46703629pab.21.1467846464527; Wed, 06 Jul 2016 16:07:44 -0700 (PDT) Received: from mothership.sklembedded.com (c-73-241-2-21.hsd1.ca.comcast.net. [73.241.2.21]) by smtp.googlemail.com with ESMTPSA id c189sm6724125pfg.19.2016.07.06.16.07.43 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 06 Jul 2016 16:07:43 -0700 (PDT) From: Steve Longerbeam X-Google-Original-From: Steve Longerbeam To: linux-media@vger.kernel.org Cc: Philipp Zabel , Sascha Hauer , Steve Longerbeam Subject: [PATCH 21/28] media: imx: Add video switch Date: Wed, 6 Jul 2016 16:06:51 -0700 Message-Id: <1467846418-12913-22-git-send-email-steve_longerbeam@mentor.com> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1467846418-12913-1-git-send-email-steve_longerbeam@mentor.com> References: <1465944574-15745-1-git-send-email-steve_longerbeam@mentor.com> <1467846418-12913-1-git-send-email-steve_longerbeam@mentor.com> Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Philipp Zabel 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 Signed-off-by: Steve Longerbeam --- drivers/staging/media/imx/capture/Kconfig | 9 + drivers/staging/media/imx/capture/Makefile | 1 + .../staging/media/imx/capture/imx-video-switch.c | 347 +++++++++++++++++++++ 3 files changed, 357 insertions(+) create mode 100644 drivers/staging/media/imx/capture/imx-video-switch.c diff --git a/drivers/staging/media/imx/capture/Kconfig b/drivers/staging/media/imx/capture/Kconfig index ac6fce0..1a64011 100644 --- a/drivers/staging/media/imx/capture/Kconfig +++ b/drivers/staging/media/imx/capture/Kconfig @@ -8,4 +8,13 @@ config IMX_MIPI_CSI2 MIPI CSI-2 Receiver driver support. This driver is required for sensor drivers with a MIPI CSI2 interface. +config IMX_VIDEO_SWITCH + tristate "i.MX5/6 Video Bus Multiplexer" + depends on GPIOLIB && VIDEO_IMX_CAMERA + default y + ---help--- + This driver provides support for the i.MX5/6 internal video bus + multiplexer controlled by register bitfields as well as + external multiplexers controller by a GPIO. + endmenu diff --git a/drivers/staging/media/imx/capture/Makefile b/drivers/staging/media/imx/capture/Makefile index 8961a4f..f17b199 100644 --- a/drivers/staging/media/imx/capture/Makefile +++ b/drivers/staging/media/imx/capture/Makefile @@ -4,3 +4,4 @@ imx-camera-objs := imx-camif.o imx-ic-prpenc.o imx-of.o \ obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-camera.o obj-$(CONFIG_VIDEO_IMX_CAMERA) += imx-csi.o obj-$(CONFIG_IMX_MIPI_CSI2) += mipi-csi2.o +obj-$(CONFIG_IMX_VIDEO_SWITCH) += imx-video-switch.o diff --git a/drivers/staging/media/imx/capture/imx-video-switch.c b/drivers/staging/media/imx/capture/imx-video-switch.c new file mode 100644 index 0000000..365616c --- /dev/null +++ b/drivers/staging/media/imx/capture/imx-video-switch.c @@ -0,0 +1,347 @@ +/* + * devicetree probed mediacontrol video multiplexer. + * + * Copyright (C) 2013 Sascha Hauer, Pengutronix + * Copyright (c) 2014-2016 Mentor Graphics Inc. + * + * 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 + +struct vidsw { + struct device *dev; + struct v4l2_subdev subdev; +#ifdef CONFIG_MEDIA_CONTROLLER + struct media_pad *pads; +#endif + struct v4l2_mbus_framefmt *format_mbus; + struct v4l2_of_endpoint *endpoint; + struct regmap_field *field; + struct gpio_desc *gpio; + int output_pad; + int numpads; + int active; +}; + +#define to_vidsw(sd) container_of(sd, struct vidsw, subdev) + +static int vidsw_set_mux(struct vidsw *vidsw, int input_index) +{ + if (vidsw->active >= 0) { + if (vidsw->active == input_index) + return 0; + else + return -EBUSY; + } + + vidsw->active = input_index; + + dev_dbg(vidsw->dev, "setting %d active\n", vidsw->active); + + if (vidsw->field) + regmap_field_write(vidsw->field, vidsw->active); + else if (vidsw->gpio) + gpiod_set_value(vidsw->gpio, vidsw->active); + + return 0; +} + +#ifdef CONFIG_MEDIA_CONTROLLER +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 (!(flags & MEDIA_LNK_FL_ENABLED)) { + if (local->index == vidsw->active) { + dev_dbg(vidsw->dev, "going inactive\n"); + vidsw->active = -1; + } + return 0; + } + + return vidsw_set_mux(vidsw, local->index); +} + +static struct media_entity_operations vidsw_ops = { + .link_setup = vidsw_link_setup, +}; +#endif + +static int vidsw_s_routing(struct v4l2_subdev *sd, u32 input, + u32 output, u32 config) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + + return vidsw_set_mux(vidsw, input); +} + +static int vidsw_async_init(struct vidsw *vidsw, struct device_node *node) +{ + struct v4l2_of_endpoint endpoint; + struct device_node *epnode; + int pad, numpads; +#ifdef CONFIG_MEDIA_CONTROLLER + int ret; +#endif + + numpads = of_get_child_count(node); + if (numpads < 2) { + dev_err(vidsw->dev, "Not enough ports %d\n", numpads); + return -EINVAL; + } + + vidsw->numpads = numpads; + + /* + * the last endpoint must define the mux output pad, + * the rest are the mux input pads. + */ + vidsw->output_pad = numpads - 1; + +#ifdef CONFIG_MEDIA_CONTROLLER + vidsw->pads = devm_kzalloc(vidsw->dev, + numpads * sizeof(*vidsw->pads), + GFP_KERNEL); + if (!vidsw->pads) + return -ENOMEM; +#endif + + vidsw->endpoint = devm_kzalloc(vidsw->dev, + numpads * sizeof(*vidsw->endpoint), + GFP_KERNEL); + if (!vidsw->endpoint) + return -ENOMEM; + + vidsw->format_mbus = devm_kzalloc(vidsw->dev, + numpads * sizeof(*vidsw->format_mbus), + GFP_KERNEL); + if (!vidsw->format_mbus) + return -ENOMEM; + +#ifdef CONFIG_MEDIA_CONTROLLER + vidsw->subdev.entity.ops = &vidsw_ops; + + /* init the pad directions */ + for (pad = 0; pad < vidsw->output_pad; pad++) + vidsw->pads[pad].flags = MEDIA_PAD_FL_SINK; + vidsw->pads[vidsw->output_pad].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&vidsw->subdev.entity, + vidsw->numpads, vidsw->pads); + if (ret < 0) + return ret; +#endif + + epnode = NULL; + for (pad = 0; pad < vidsw->numpads; pad++) { + epnode = of_graph_get_next_endpoint(node, epnode); + if (!epnode) + return -EINVAL; + + v4l2_of_parse_endpoint(epnode, &endpoint); + vidsw->endpoint[pad] = endpoint; + of_node_put(epnode); + } + + 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, + .s_routing = vidsw_s_routing, +}; + +static int vidsw_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + + sdformat->format = vidsw->format_mbus[sdformat->pad]; + + return 0; +} + +static int vidsw_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct vidsw *vidsw = container_of(sd, struct vidsw, subdev); + + if (sdformat->pad >= vidsw->numpads) + return -EINVAL; + + /* Output pad mirrors active input pad, no limitations on input pads */ + if (sdformat->pad == vidsw->output_pad && vidsw->active >= 0) + sdformat->format = vidsw->format_mbus[vidsw->active]; + + if (sdformat->which == V4L2_SUBDEV_FORMAT_TRY) + cfg->try_fmt = sdformat->format; + else + vidsw->format_mbus[sdformat->pad] = sdformat->format; + + 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 reg_bit_mask[2]; + int ret; + + ret = of_property_read_u32_array(node, "reg", reg_bit_mask, 2); + if (ret < 0) + return ret; + + field->reg = reg_bit_mask[0]; + field->lsb = __ffs(reg_bit_mask[1]); + field->msb = __fls(reg_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 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); + v4l2_set_subdevdata(&vidsw->subdev, &pdev->dev); + 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; + + ret = of_get_reg_field(np, &field); + if (ret == 0) { + map = syscon_regmap_lookup_by_phandle(np, "gpr"); + 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 = devm_gpiod_get_optional(&pdev->dev, "mux", + GPIOD_OUT_LOW); + if (IS_ERR(vidsw->gpio)) { + dev_err(&pdev->dev, "request for gpio failed\n"); + return PTR_ERR(vidsw->gpio); + } + + if (!vidsw->gpio) + dev_warn(&pdev->dev, "no control gpio defined\n"); + } + + 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 = "imx-video-mux", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vidsw_dt_ids); + +static struct platform_driver vidsw_driver = { + .probe = vidsw_probe, + .remove = vidsw_remove, + .driver = { + .of_match_table = vidsw_dt_ids, + .name = "imx-video-mux", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(vidsw_driver); + +MODULE_DESCRIPTION("i.MX video stream multiplexer"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix"); +MODULE_LICENSE("GPL");