Hi! > My apologies for the delays in reviewing. Feel free to ping me in the future > if this happens. :-) No problem :-). If you could review the C-code, too... that would be nice. It should be in your inbox somewhere (and I attached it below, without the dts part). > > +Required properties > > +=================== > > + > > +compatible : must contain "video-bus-switch" > > How generic is this? Should we have e.g. nokia,video-bus-switch? And if so, > change the file name accordingly. Generic for "single GPIO controls the switch", AFAICT. But that should be common enough... > > +reg : The interface: > > + 0 - port for image signal processor > > + 1 - port for first camera sensor > > + 2 - port for second camera sensor > > I'd say this must be pretty much specific to the one in N900. You could have > more ports. Or you could say that ports beyond 0 are camera sensors. I guess > this is good enough for now though, it can be changed later on with the > source if a need arises. Well, I'd say that selecting between two sensors is going to be the common case. If someone needs more than two, it will no longer be simple GPIO, so we'll have some fixing to do. > Btw. was it still considered a problem that the endpoint properties for the > sensors can be different? With the g_routing() pad op which is to be added, > the ISP driver (should actually go to a framework somewhere) could parse the > graph and find the proper endpoint there. I don't know about g_routing. I added g_endpoint_config method that passes the configuration, and that seems to work for me. I don't see g_routing in next-20170201 . Is there place to look? Best regards, Pavel --- N900 contains front and back camera, with a switch between the two. This adds support for the switch component, and it is now possible to select between front and back cameras during runtime. Signed-off-by: Sebastian Reichel Signed-off-by: Ivaylo Dimitrov Signed-off-by: Pavel Machek diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ce4a96f..a4b509e 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -91,6 +91,16 @@ config VIDEO_OMAP3_DEBUG ---help--- Enable debug messages on OMAP 3 camera controller driver. +config VIDEO_BUS_SWITCH + tristate "Video Bus switch" + depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API + depends on MEDIA_CONTROLLER + depends on OF + ---help--- + Driver for a GPIO controlled video bus switch, which is used to + connect two camera sensors to the same port a the image signal + processor. + config VIDEO_PXA27x tristate "PXA27x Quick Capture Interface driver" depends on VIDEO_DEV && HAS_DMA diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 40b18d1..8eafc27 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -11,6 +11,8 @@ obj-$(CONFIG_VIDEO_MMP_CAMERA) += marvell-ccic/ obj-$(CONFIG_VIDEO_OMAP3) += omap3isp/ obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o +obj-$(CONFIG_VIDEO_BUS_SWITCH) += video-bus-switch.o + obj-$(CONFIG_VIDEO_VIU) += fsl-viu.o obj-$(CONFIG_VIDEO_VIVID) += vivid/ diff --git a/drivers/media/platform/video-bus-switch.c b/drivers/media/platform/video-bus-switch.c new file mode 100644 index 0000000..6400cfc --- /dev/null +++ b/drivers/media/platform/video-bus-switch.c @@ -0,0 +1,387 @@ +/* + * Generic driver for video bus switches + * + * Copyright (C) 2015 Sebastian Reichel + * Copyright (C) 2016 Pavel Machek + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * TODO: + * isp_subdev_notifier_complete() calls v4l2_device_register_subdev_nodes() + */ + +#define CSI_SWITCH_SUBDEVS 2 +#define CSI_SWITCH_PORTS 3 + +enum vbs_state { + CSI_SWITCH_DISABLED = 0, + CSI_SWITCH_PORT_1 = 1, + CSI_SWITCH_PORT_2 = 2, +}; + +struct vbs_src_pads { + struct media_entity *src; + int src_pad; +}; + +struct vbs_data { + struct gpio_desc *swgpio; + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[CSI_SWITCH_PORTS]; + struct vbs_src_pads src_pads[CSI_SWITCH_PORTS]; + struct v4l2_of_endpoint vep[CSI_SWITCH_PORTS]; + enum vbs_state state; +}; + +struct vbs_async_subdev { + struct v4l2_subdev *sd; + struct v4l2_async_subdev asd; + u8 port; +}; + +static int vbs_of_parse_nodes(struct device *dev, struct vbs_data *pdata) +{ + struct v4l2_async_notifier *notifier = &pdata->notifier; + struct device_node *node = NULL; + + notifier->subdevs = devm_kcalloc(dev, CSI_SWITCH_SUBDEVS, + sizeof(*notifier->subdevs), GFP_KERNEL); + if (!notifier->subdevs) + return -ENOMEM; + + notifier->num_subdevs = 0; + while (notifier->num_subdevs < CSI_SWITCH_SUBDEVS && + (node = of_graph_get_next_endpoint(dev->of_node, node))) { + struct v4l2_of_endpoint vep; + struct vbs_async_subdev *ssd; + + /* skip first port (connected to isp) */ + v4l2_of_parse_endpoint(node, &vep); + if (vep.base.port == 0) { + struct device_node *ispnode; + + ispnode = of_graph_get_remote_port_parent(node); + if (!ispnode) { + dev_warn(dev, "bad remote port parent\n"); + return -EINVAL; + } + + of_node_put(node); + continue; + } + + ssd = devm_kzalloc(dev, sizeof(*ssd), GFP_KERNEL); + if (!ssd) { + of_node_put(node); + return -ENOMEM; + } + + ssd->port = vep.base.port; + + notifier->subdevs[notifier->num_subdevs] = &ssd->asd; + + ssd->asd.match.of.node = of_graph_get_remote_port_parent(node); + of_node_put(node); + if (!ssd->asd.match.of.node) { + dev_warn(dev, "bad remote port parent\n"); + return -EINVAL; + } + + ssd->asd.match_type = V4L2_ASYNC_MATCH_OF; + pdata->vep[notifier->num_subdevs] = vep; + notifier->num_subdevs++; + } + + return notifier->num_subdevs; +} + +static int vbs_registered(struct v4l2_subdev *sd) +{ + struct v4l2_device *v4l2_dev = sd->v4l2_dev; + struct vbs_data *pdata; + int err; + + dev_dbg(sd->dev, "registered, init notifier...\n"); + + pdata = v4l2_get_subdevdata(sd); + + err = v4l2_async_notifier_register(v4l2_dev, &pdata->notifier); + if (err) + return err; + + return 0; +} + +static struct v4l2_subdev *vbs_get_remote_subdev(struct v4l2_subdev *sd) +{ + struct vbs_data *pdata = v4l2_get_subdevdata(sd); + struct media_entity *src; + + if (pdata->state == CSI_SWITCH_DISABLED) + return ERR_PTR(-ENXIO); + + src = pdata->src_pads[pdata->state].src; + + return media_entity_to_v4l2_subdev(src); +} + +static int vbs_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 vbs_data *pdata = v4l2_get_subdevdata(sd); + bool enable = flags & MEDIA_LNK_FL_ENABLED; + + if (local->index > CSI_SWITCH_PORTS - 1) + return -ENXIO; + + /* no configuration needed on source port */ + if (local->index == 0) + return 0; + + if (!enable) { + if (local->index == pdata->state) { + pdata->state = CSI_SWITCH_DISABLED; + + /* Make sure we have both cameras enabled */ + gpiod_set_value(pdata->swgpio, 1); + return 0; + } else { + return -EINVAL; + } + } + + /* there can only be one active sink at the same time */ + if (pdata->state != CSI_SWITCH_DISABLED) + return -EBUSY; + + dev_dbg(sd->dev, "Link setup: going to config %d\n", local->index); + + gpiod_set_value(pdata->swgpio, local->index == CSI_SWITCH_PORT_2); + pdata->state = local->index; + + sd = vbs_get_remote_subdev(sd); + if (IS_ERR(sd)) + return PTR_ERR(sd); + + pdata->subdev.ctrl_handler = sd->ctrl_handler; + + return 0; +} + +static int vbs_subdev_notifier_bound(struct v4l2_async_notifier *async, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct vbs_data *pdata = container_of(async, + struct vbs_data, notifier); + struct vbs_async_subdev *ssd = + container_of(asd, struct vbs_async_subdev, asd); + struct media_entity *sink = &pdata->subdev.entity; + struct media_entity *src = &subdev->entity; + int sink_pad = ssd->port; + int src_pad; + + if (sink_pad >= sink->num_pads) { + dev_err(pdata->subdev.dev, "no sink pad in internal entity!\n"); + return -EINVAL; + } + + for (src_pad = 0; src_pad < subdev->entity.num_pads; src_pad++) { + if (subdev->entity.pads[src_pad].flags & MEDIA_PAD_FL_SOURCE) + break; + } + + if (src_pad >= src->num_pads) { + dev_err(pdata->subdev.dev, "no source pad in external entity\n"); + return -EINVAL; + } + + pdata->src_pads[sink_pad].src = src; + pdata->src_pads[sink_pad].src_pad = src_pad; + ssd->sd = subdev; + + return 0; +} + +static int vbs_subdev_notifier_complete(struct v4l2_async_notifier *async) +{ + struct vbs_data *pdata = container_of(async, struct vbs_data, notifier); + struct media_entity *sink = &pdata->subdev.entity; + int sink_pad; + + for (sink_pad = 1; sink_pad < CSI_SWITCH_PORTS; sink_pad++) { + struct media_entity *src = pdata->src_pads[sink_pad].src; + int src_pad = pdata->src_pads[sink_pad].src_pad; + int err; + + err = media_create_pad_link(src, src_pad, sink, sink_pad, 0); + if (err < 0) + return err; + + dev_dbg(pdata->subdev.dev, "create link: %s[%d] -> %s[%d])\n", + src->name, src_pad, sink->name, sink_pad); + } + + return v4l2_device_register_subdev_nodes(pdata->subdev.v4l2_dev); +} + +static int vbs_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct v4l2_subdev *subdev = vbs_get_remote_subdev(sd); + + if (IS_ERR(subdev)) + return PTR_ERR(subdev); + + return v4l2_subdev_call(subdev, video, s_stream, enable); +} + +static int vbs_g_endpoint_config(struct v4l2_subdev *sd, struct v4l2_of_endpoint *cfg) +{ + struct vbs_data *pdata = v4l2_get_subdevdata(sd); + dev_dbg(sd->dev, "vbs_g_endpoint_config... active port is %d\n", pdata->state); + *cfg = pdata->vep[pdata->state - 1]; + + return 0; +} + + +static const struct v4l2_subdev_internal_ops vbs_internal_ops = { + .registered = &vbs_registered, +}; + +static const struct media_entity_operations vbs_media_ops = { + .link_setup = vbs_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* subdev video operations */ +static const struct v4l2_subdev_video_ops vbs_video_ops = { + .s_stream = vbs_s_stream, + .g_endpoint_config = vbs_g_endpoint_config, +}; + +static const struct v4l2_subdev_ops vbs_ops = { + .video = &vbs_video_ops, +}; + +static int video_bus_switch_probe(struct platform_device *pdev) +{ + struct vbs_data *pdata; + int err = 0; + + /* platform data */ + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_dbg(&pdev->dev, "video-bus-switch: not enough memory\n"); + return -ENOMEM; + } + platform_set_drvdata(pdev, pdata); + + /* switch gpio */ + pdata->swgpio = devm_gpiod_get(&pdev->dev, "switch", GPIOD_OUT_HIGH); + if (IS_ERR(pdata->swgpio)) { + err = PTR_ERR(pdata->swgpio); + dev_err(&pdev->dev, "Failed to request gpio: %d\n", err); + return err; + } + + /* find sub-devices */ + err = vbs_of_parse_nodes(&pdev->dev, pdata); + if (err < 0) { + dev_err(&pdev->dev, "Failed to parse nodes: %d\n", err); + return err; + } + + pdata->state = CSI_SWITCH_DISABLED; + pdata->notifier.bound = vbs_subdev_notifier_bound; + pdata->notifier.complete = vbs_subdev_notifier_complete; + + /* setup subdev */ + pdata->pads[0].flags = MEDIA_PAD_FL_SOURCE; + pdata->pads[1].flags = MEDIA_PAD_FL_SINK; + pdata->pads[2].flags = MEDIA_PAD_FL_SINK; + + v4l2_subdev_init(&pdata->subdev, &vbs_ops); + pdata->subdev.dev = &pdev->dev; + pdata->subdev.owner = pdev->dev.driver->owner; + strncpy(pdata->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE); + v4l2_set_subdevdata(&pdata->subdev, pdata); + pdata->subdev.entity.function = MEDIA_ENT_F_SWITCH; + pdata->subdev.entity.flags |= MEDIA_ENT_F_SWITCH; + pdata->subdev.entity.ops = &vbs_media_ops; + pdata->subdev.internal_ops = &vbs_internal_ops; + err = media_entity_pads_init(&pdata->subdev.entity, CSI_SWITCH_PORTS, + pdata->pads); + if (err < 0) { + dev_err(&pdev->dev, "Failed to init media entity: %d\n", err); + return err; + } + + /* register subdev */ + err = v4l2_async_register_subdev(&pdata->subdev); + if (err < 0) { + dev_err(&pdev->dev, "Failed to register v4l2 subdev: %d\n", err); + media_entity_cleanup(&pdata->subdev.entity); + return err; + } + + dev_info(&pdev->dev, "video-bus-switch registered\n"); + + return 0; +} + +static int video_bus_switch_remove(struct platform_device *pdev) +{ + struct vbs_data *pdata = platform_get_drvdata(pdev); + + v4l2_async_notifier_unregister(&pdata->notifier); + v4l2_async_unregister_subdev(&pdata->subdev); + media_entity_cleanup(&pdata->subdev.entity); + + return 0; +} + +static const struct of_device_id video_bus_switch_of_match[] = { + { .compatible = "video-bus-switch" }, + { }, +}; +MODULE_DEVICE_TABLE(of, video_bus_switch_of_match); + +static struct platform_driver video_bus_switch_driver = { + .driver = { + .name = "video-bus-switch", + .of_match_table = video_bus_switch_of_match, + }, + .probe = video_bus_switch_probe, + .remove = video_bus_switch_remove, +}; + +module_platform_driver(video_bus_switch_driver); + +MODULE_AUTHOR("Sebastian Reichel "); +MODULE_DESCRIPTION("Video Bus Switch"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:video-bus-switch"); diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index cf778c5..448dbb5 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -25,6 +25,7 @@ #include #include #include +#include /* generic v4l2_device notify callback notification values */ #define V4L2_SUBDEV_IR_RX_NOTIFY _IOW('v', 0, u32) @@ -415,6 +416,8 @@ struct v4l2_subdev_video_ops { const struct v4l2_mbus_config *cfg); int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf, unsigned int *size); + int (*g_endpoint_config)(struct v4l2_subdev *sd, + struct v4l2_of_endpoint *cfg); }; /** diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h index 4890787..94648ab 100644 --- a/include/uapi/linux/media.h +++ b/include/uapi/linux/media.h @@ -147,6 +147,7 @@ struct media_device_info { * MEDIA_ENT_F_IF_VID_DECODER and/or MEDIA_ENT_F_IF_AUD_DECODER. */ #define MEDIA_ENT_F_TUNER (MEDIA_ENT_F_OLD_SUBDEV_BASE + 5) +#define MEDIA_ENT_F_SWITCH (MEDIA_ENT_F_OLD_SUBDEV_BASE + 6) #define MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN MEDIA_ENT_F_OLD_SUBDEV_BASE -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html