| /* |
| * video stream multiplexer controlled via mux control |
| * |
| * Copyright (C) 2013 Pengutronix, Sascha Hauer <kernel@pengutronix.de> |
| * Copyright (C) 2016-2017 Pengutronix, Philipp Zabel <kernel@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/err.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/mux/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_mux { |
| struct v4l2_subdev subdev; |
| struct media_pad *pads; |
| struct v4l2_mbus_framefmt *format_mbus; |
| struct mux_control *mux; |
| struct mutex lock; |
| int active; |
| }; |
| |
| static inline struct video_mux *v4l2_subdev_to_video_mux(struct v4l2_subdev *sd) |
| { |
| return container_of(sd, struct video_mux, subdev); |
| } |
| |
| static int video_mux_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_mux *vmux = v4l2_subdev_to_video_mux(sd); |
| int ret = 0; |
| |
| /* |
| * The mux state is determined by the enabled sink pad link. |
| * Enabling or disabling the source pad link has no effect. |
| */ |
| if (local->flags & MEDIA_PAD_FL_SOURCE) |
| 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(&vmux->lock); |
| |
| if (flags & MEDIA_LNK_FL_ENABLED) { |
| if (vmux->active == local->index) |
| goto out; |
| |
| if (vmux->active >= 0) { |
| ret = -EBUSY; |
| goto out; |
| } |
| |
| dev_dbg(sd->dev, "setting %d active\n", local->index); |
| ret = mux_control_try_select(vmux->mux, local->index); |
| if (ret < 0) |
| goto out; |
| vmux->active = local->index; |
| } else { |
| if (vmux->active != local->index) |
| goto out; |
| |
| dev_dbg(sd->dev, "going inactive\n"); |
| mux_control_deselect(vmux->mux); |
| vmux->active = -1; |
| } |
| |
| out: |
| mutex_unlock(&vmux->lock); |
| return ret; |
| } |
| |
| static const struct media_entity_operations video_mux_ops = { |
| .link_setup = video_mux_link_setup, |
| .link_validate = v4l2_subdev_link_validate, |
| }; |
| |
| static int video_mux_s_stream(struct v4l2_subdev *sd, int enable) |
| { |
| struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); |
| struct v4l2_subdev *upstream_sd; |
| struct media_pad *pad; |
| |
| if (vmux->active == -1) { |
| dev_err(sd->dev, "Can not start streaming on inactive mux\n"); |
| return -EINVAL; |
| } |
| |
| pad = media_entity_remote_pad(&sd->entity.pads[vmux->active]); |
| 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_mux_subdev_video_ops = { |
| .s_stream = video_mux_s_stream, |
| }; |
| |
| static struct v4l2_mbus_framefmt * |
| __video_mux_get_pad_format(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| unsigned int pad, u32 which) |
| { |
| struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); |
| |
| switch (which) { |
| case V4L2_SUBDEV_FORMAT_TRY: |
| return v4l2_subdev_get_try_format(sd, cfg, pad); |
| case V4L2_SUBDEV_FORMAT_ACTIVE: |
| return &vmux->format_mbus[pad]; |
| default: |
| return NULL; |
| } |
| } |
| |
| static int video_mux_get_format(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_format *sdformat) |
| { |
| struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); |
| |
| mutex_lock(&vmux->lock); |
| |
| sdformat->format = *__video_mux_get_pad_format(sd, cfg, sdformat->pad, |
| sdformat->which); |
| |
| mutex_unlock(&vmux->lock); |
| |
| return 0; |
| } |
| |
| static int video_mux_set_format(struct v4l2_subdev *sd, |
| struct v4l2_subdev_pad_config *cfg, |
| struct v4l2_subdev_format *sdformat) |
| { |
| struct video_mux *vmux = v4l2_subdev_to_video_mux(sd); |
| struct v4l2_mbus_framefmt *mbusformat; |
| struct media_pad *pad = &vmux->pads[sdformat->pad]; |
| |
| mbusformat = __video_mux_get_pad_format(sd, cfg, sdformat->pad, |
| sdformat->which); |
| if (!mbusformat) |
| return -EINVAL; |
| |
| mutex_lock(&vmux->lock); |
| |
| /* Source pad mirrors active sink pad, no limitations on sink pads */ |
| if ((pad->flags & MEDIA_PAD_FL_SOURCE) && vmux->active >= 0) |
| sdformat->format = vmux->format_mbus[vmux->active]; |
| |
| *mbusformat = sdformat->format; |
| |
| mutex_unlock(&vmux->lock); |
| |
| return 0; |
| } |
| |
| static const struct v4l2_subdev_pad_ops video_mux_pad_ops = { |
| .get_fmt = video_mux_get_format, |
| .set_fmt = video_mux_set_format, |
| }; |
| |
| static const struct v4l2_subdev_ops video_mux_subdev_ops = { |
| .pad = &video_mux_pad_ops, |
| .video = &video_mux_subdev_video_ops, |
| }; |
| |
| static int video_mux_probe(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct device *dev = &pdev->dev; |
| struct device_node *ep; |
| struct video_mux *vmux; |
| unsigned int num_pads = 0; |
| int ret; |
| int i; |
| |
| vmux = devm_kzalloc(dev, sizeof(*vmux), GFP_KERNEL); |
| if (!vmux) |
| return -ENOMEM; |
| |
| platform_set_drvdata(pdev, vmux); |
| |
| v4l2_subdev_init(&vmux->subdev, &video_mux_subdev_ops); |
| snprintf(vmux->subdev.name, sizeof(vmux->subdev.name), "%s", np->name); |
| vmux->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
| vmux->subdev.dev = dev; |
| |
| /* |
| * The largest numbered port is the output port. It determines |
| * total number of pads. |
| */ |
| 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, "Not enough ports %d\n", num_pads); |
| return -EINVAL; |
| } |
| |
| vmux->mux = devm_mux_control_get(dev, NULL); |
| if (IS_ERR(vmux->mux)) { |
| ret = PTR_ERR(vmux->mux); |
| if (ret != -EPROBE_DEFER) |
| dev_err(dev, "Failed to get mux: %d\n", ret); |
| return ret; |
| } |
| |
| mutex_init(&vmux->lock); |
| vmux->active = -1; |
| vmux->pads = devm_kcalloc(dev, num_pads, sizeof(*vmux->pads), |
| GFP_KERNEL); |
| if (!vmux->pads) |
| return -ENOMEM; |
| |
| vmux->format_mbus = devm_kcalloc(dev, num_pads, |
| sizeof(*vmux->format_mbus), |
| GFP_KERNEL); |
| if (!vmux->format_mbus) |
| return -ENOMEM; |
| |
| for (i = 0; i < num_pads - 1; i++) |
| vmux->pads[i].flags = MEDIA_PAD_FL_SINK; |
| vmux->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; |
| |
| vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX; |
| ret = media_entity_pads_init(&vmux->subdev.entity, num_pads, |
| vmux->pads); |
| if (ret < 0) |
| return ret; |
| |
| vmux->subdev.entity.ops = &video_mux_ops; |
| |
| return v4l2_async_register_subdev(&vmux->subdev); |
| } |
| |
| static int video_mux_remove(struct platform_device *pdev) |
| { |
| struct video_mux *vmux = platform_get_drvdata(pdev); |
| struct v4l2_subdev *sd = &vmux->subdev; |
| |
| v4l2_async_unregister_subdev(sd); |
| media_entity_cleanup(&sd->entity); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id video_mux_dt_ids[] = { |
| { .compatible = "video-mux", }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, video_mux_dt_ids); |
| |
| static struct platform_driver video_mux_driver = { |
| .probe = video_mux_probe, |
| .remove = video_mux_remove, |
| .driver = { |
| .of_match_table = video_mux_dt_ids, |
| .name = "video-mux", |
| }, |
| }; |
| |
| module_platform_driver(video_mux_driver); |
| |
| MODULE_DESCRIPTION("video stream multiplexer"); |
| MODULE_AUTHOR("Sascha Hauer, Pengutronix"); |
| MODULE_AUTHOR("Philipp Zabel, Pengutronix"); |
| MODULE_LICENSE("GPL"); |