diff mbox

[31/38] media: imx: Add video switch

Message ID 1465944574-15745-32-git-send-email-steve_longerbeam@mentor.com (mailing list archive)
State New, archived
Headers show

Commit Message

Steve Longerbeam June 14, 2016, 10:49 p.m. UTC
From: Philipp Zabel <p.zabel@pengutronix.de>

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 <s.hauer@pengutronix.de>
Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
---
 drivers/staging/media/imx/capture/Kconfig          |   9 +
 drivers/staging/media/imx/capture/Makefile         |   1 +
 .../staging/media/imx/capture/imx-video-switch.c   | 348 +++++++++++++++++++++
 3 files changed, 358 insertions(+)
 create mode 100644 drivers/staging/media/imx/capture/imx-video-switch.c

Comments

Ian Arkver June 16, 2016, 4:13 p.m. UTC | #1
For me this fails when I try to enable both video muxes (mx6dl, though 
mx6q should be the same).

I get a sysfs duplicate name failure for 34.videomux. I realise passing 
the GPR13 register offset and a bitfield mask as a tuple in the reg 
value of the of_node is handy, but how should we account for multiple 
devices with the same name and address?

A quick and dirty hack would be to have of_get_reg_field do something like

	field->reg = reg_bit_mask[0] & 0xff;

and then use values in the DT that differ in the bits masked off, but 
there must be a nicer way.

Trace below, fyi. This is from the v2 patches posted here, not your v2.1 
tree.

Regards,
IanJ


[    0.096004] ------------[ cut here ]------------
[    0.096035] WARNING: CPU: 0 PID: 1 at 
/home/ian/tx6/yoctomaster/build/tmp/work-shared/tx6u-vid/kernel-source/fs/sysfs/dir.c:31 
sysfs_warn_dup+0x70/0x80
[    0.096046] sysfs: cannot create duplicate filename 
'/devices/soc0/34.videomux'
[    0.096053] Modules linked in:
[    0.096071] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 
4.7.0-rc1-yocto-standard #1
[    0.096079] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[    0.096116] [<80018d8c>] (unwind_backtrace) from [<8001427c>] 
(show_stack+0x18/0x1c)
[    0.096138] [<8001427c>] (show_stack) from [<802c85fc>] 
(dump_stack+0x88/0x9c)
[    0.096157] [<802c85fc>] (dump_stack) from [<80024d90>] 
(__warn+0xf4/0x10c)
[    0.096175] [<80024d90>] (__warn) from [<80024de8>] 
(warn_slowpath_fmt+0x40/0x50)
[    0.096194] [<80024de8>] (warn_slowpath_fmt) from [<80176118>] 
(sysfs_warn_dup+0x70/0x80)
[    0.096212] [<80176118>] (sysfs_warn_dup) from [<80176204>] 
(sysfs_create_dir_ns+0x8c/0x9c)
[    0.096231] [<80176204>] (sysfs_create_dir_ns) from [<802cafd0>] 
(kobject_add_internal+0xc0/0x360)
[    0.096249] [<802cafd0>] (kobject_add_internal) from [<802cb2b8>] 
(kobject_add+0x48/0x98)
[    0.096269] [<802cb2b8>] (kobject_add) from [<803b90e0>] 
(device_add+0xf0/0x5a0)
[    0.096295] [<803b90e0>] (device_add) from [<8051ae20>] 
(of_platform_device_create_pdata+0x8c/0xc4)
[    0.096316] [<8051ae20>] (of_platform_device_create_pdata) from 
[<8051af7c>] (of_platform_bus_create+0x110/0x2a0)
[    0.096333] [<8051af7c>] (of_platform_bus_create) from [<8051b29c>] 
(of_platform_populate+0x64/0xb4)
[    0.096358] [<8051b29c>] (of_platform_populate) from [<808f1a88>] 
(imx6q_init_machine+0x104/0x2b4)
[    0.096377] [<808f1a88>] (imx6q_init_machine) from [<808eba7c>] 
(customize_machine+0x24/0x44)
[    0.096395] [<808eba7c>] (customize_machine) from [<8000980c>] 
(do_one_initcall+0x4c/0x174)
[    0.096414] [<8000980c>] (do_one_initcall) from [<808ead60>] 
(kernel_init_freeable+0x158/0x1e8)
[    0.096435] [<808ead60>] (kernel_init_freeable) from [<8062b4b0>] 
(kernel_init+0x14/0x100)
[    0.096457] [<8062b4b0>] (kernel_init) from [<800104b8>] 
(ret_from_fork+0x14/0x3c)
[    0.096482] ---[ end trace 394e7b4d22c2be44 ]---
[    0.096491] ------------[ cut here ]------------
[    0.096507] WARNING: CPU: 0 PID: 1 at 
/home/ian/tx6/yoctomaster/build/tmp/work-shared/tx6u-vid/kernel-source/lib/kobject.c:240 
kobject_add_internal+0x2e0/0x360
[    0.096518] kobject_add_internal failed for 34.videomux with -EEXIST, 
don't try to register things with the same name in the same directory.
[    0.096525] Modules linked in:
[    0.096539] CPU: 0 PID: 1 Comm: swapper/0 Tainted: G W       
4.7.0-rc1-yocto-standard #1
[    0.096548] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[    0.096570] [<80018d8c>] (unwind_backtrace) from [<8001427c>] 
(show_stack+0x18/0x1c)
[    0.096585] [<8001427c>] (show_stack) from [<802c85fc>] 
(dump_stack+0x88/0x9c)
[    0.096601] [<802c85fc>] (dump_stack) from [<80024d90>] 
(__warn+0xf4/0x10c)
[    0.096616] [<80024d90>] (__warn) from [<80024de8>] 
(warn_slowpath_fmt+0x40/0x50)
[    0.096632] [<80024de8>] (warn_slowpath_fmt) from [<802cb1f0>] 
(kobject_add_internal+0x2e0/0x360)
[    0.096647] [<802cb1f0>] (kobject_add_internal) from [<802cb2b8>] 
(kobject_add+0x48/0x98)
[    0.096664] [<802cb2b8>] (kobject_add) from [<803b90e0>] 
(device_add+0xf0/0x5a0)
[    0.096681] [<803b90e0>] (device_add) from [<8051ae20>] 
(of_platform_device_create_pdata+0x8c/0xc4)
[    0.096700] [<8051ae20>] (of_platform_device_create_pdata) from 
[<8051af7c>] (of_platform_bus_create+0x110/0x2a0)
[    0.096716] [<8051af7c>] (of_platform_bus_create) from [<8051b29c>] 
(of_platform_populate+0x64/0xb4)
[    0.096735] [<8051b29c>] (of_platform_populate) from [<808f1a88>] 
(imx6q_init_machine+0x104/0x2b4)
[    0.096752] [<808f1a88>] (imx6q_init_machine) from [<808eba7c>] 
(customize_machine+0x24/0x44)
[    0.096767] [<808eba7c>] (customize_machine) from [<8000980c>] 
(do_one_initcall+0x4c/0x174)
[    0.096782] [<8000980c>] (do_one_initcall) from [<808ead60>] 
(kernel_init_freeable+0x158/0x1e8)
[    0.096798] [<808ead60>] (kernel_init_freeable) from [<8062b4b0>] 
(kernel_init+0x14/0x100)
[    0.096814] [<8062b4b0>] (kernel_init) from [<800104b8>] 
(ret_from_fork+0x14/0x3c)
[    0.096822] ---[ end trace 394e7b4d22c2be45 ]---


On 14/06/16 23:49, Steve Longerbeam wrote:
> From: Philipp Zabel <p.zabel@pengutronix.de>
>
> 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 <s.hauer@pengutronix.de>
> Signed-off-by: Philipp Zabel <p.zabel@pengutronix.de>
> Signed-off-by: Steve Longerbeam <steve_longerbeam@mentor.com>
> ---
>   drivers/staging/media/imx/capture/Kconfig          |   9 +
>   drivers/staging/media/imx/capture/Makefile         |   1 +
>   .../staging/media/imx/capture/imx-video-switch.c   | 348 +++++++++++++++++++++
>   3 files changed, 358 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..ecd09abe 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 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..0c86679
> --- /dev/null
> +++ b/drivers/staging/media/imx/capture/imx-video-switch.c
> @@ -0,0 +1,348 @@
> +/*
> + * 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 <linux/err.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/syscon.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/of_graph.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/v4l2-of.h>
> +
> +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;
> +	unsigned int 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 (gpio_is_valid(vidsw->gpio))
> +		gpio_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 = 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 = "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");

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Steve Longerbeam June 17, 2016, 8:14 p.m. UTC | #2
On 06/16/2016 09:13 AM, Ian Arkver wrote:
> For me this fails when I try to enable both video muxes (mx6dl, though 
> mx6q should be the same).
>
> I get a sysfs duplicate name failure for 34.videomux. I realise 
> passing the GPR13 register offset and a bitfield mask as a tuple in 
> the reg value of the of_node is handy, but how should we account for 
> multiple devices with the same name and address?
>
> A quick and dirty hack would be to have of_get_reg_field do something 
> like
>
>     field->reg = reg_bit_mask[0] & 0xff;
>
> and then use values in the DT that differ in the bits masked off, but 
> there must be a nicer way.
>
> Trace below, fyi. This is from the v2 patches posted here, not your 
> v2.1 tree.
>

Hi Ian,

Thanks for catching this. I found a simple and clean solution to this
problem: just rename the video-mux node from "videomux" to
"ipu[12]_csi[01]_mux". It makes the subdev names more descriptive
anyway. I've applied that change to my mx6-media-staging-v2.1
branch.

Steve

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/staging/media/imx/capture/Kconfig b/drivers/staging/media/imx/capture/Kconfig
index ac6fce0..ecd09abe 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 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..0c86679
--- /dev/null
+++ b/drivers/staging/media/imx/capture/imx-video-switch.c
@@ -0,0 +1,348 @@ 
+/*
+ * 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 <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/of_graph.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-of.h>
+
+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;
+	unsigned int 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 (gpio_is_valid(vidsw->gpio))
+		gpio_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 = 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 = "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");