|
@@ -27,6 +27,23 @@
|
|
|
|
|
|
#include "rcar-vin.h"
|
|
|
|
|
|
+/*
|
|
|
+ * The companion CSI-2 receiver driver (rcar-csi2) is known
|
|
|
+ * and we know it has one source pad (pad 0) and four sink
|
|
|
+ * pads (pad 1-4). So to translate a pad on the remote
|
|
|
+ * CSI-2 receiver to/from the VIN internal channel number simply
|
|
|
+ * subtract/add one from the pad/channel number.
|
|
|
+ */
|
|
|
+#define rvin_group_csi_pad_to_channel(pad) ((pad) - 1)
|
|
|
+#define rvin_group_csi_channel_to_pad(channel) ((channel) + 1)
|
|
|
+
|
|
|
+/*
|
|
|
+ * Not all VINs are created equal, master VINs control the
|
|
|
+ * routing for other VIN's. We can figure out which VIN is
|
|
|
+ * master by looking at a VINs id.
|
|
|
+ */
|
|
|
+#define rvin_group_id_to_master(vin) ((vin) < 4 ? 0 : 4)
|
|
|
+
|
|
|
/* -----------------------------------------------------------------------------
|
|
|
* Gen3 CSI2 Group Allocator
|
|
|
*/
|
|
@@ -409,6 +426,216 @@ static int rvin_digital_graph_init(struct rvin_dev *vin)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+/* -----------------------------------------------------------------------------
|
|
|
+ * Group async notifier
|
|
|
+ */
|
|
|
+
|
|
|
+static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier)
|
|
|
+{
|
|
|
+ struct rvin_dev *vin = notifier_to_vin(notifier);
|
|
|
+ const struct rvin_group_route *route;
|
|
|
+ unsigned int i;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
|
|
|
+ if (ret) {
|
|
|
+ vin_err(vin, "Failed to register subdev nodes\n");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Register all video nodes for the group. */
|
|
|
+ for (i = 0; i < RCAR_VIN_NUM; i++) {
|
|
|
+ if (vin->group->vin[i]) {
|
|
|
+ ret = rvin_v4l2_register(vin->group->vin[i]);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Create all media device links between VINs and CSI-2's. */
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+ for (route = vin->info->routes; route->mask; route++) {
|
|
|
+ struct media_pad *source_pad, *sink_pad;
|
|
|
+ struct media_entity *source, *sink;
|
|
|
+ unsigned int source_idx;
|
|
|
+
|
|
|
+ /* Check that VIN is part of the group. */
|
|
|
+ if (!vin->group->vin[route->vin])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Check that VIN' master is part of the group. */
|
|
|
+ if (!vin->group->vin[rvin_group_id_to_master(route->vin)])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ /* Check that CSI-2 is part of the group. */
|
|
|
+ if (!vin->group->csi[route->csi].subdev)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ source = &vin->group->csi[route->csi].subdev->entity;
|
|
|
+ source_idx = rvin_group_csi_channel_to_pad(route->channel);
|
|
|
+ source_pad = &source->pads[source_idx];
|
|
|
+
|
|
|
+ sink = &vin->group->vin[route->vin]->vdev.entity;
|
|
|
+ sink_pad = &sink->pads[0];
|
|
|
+
|
|
|
+ /* Skip if link already exists. */
|
|
|
+ if (media_entity_find_link(source_pad, sink_pad))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ret = media_create_pad_link(source, source_idx, sink, 0, 0);
|
|
|
+ if (ret) {
|
|
|
+ vin_err(vin, "Error adding link from %s to %s\n",
|
|
|
+ source->name, sink->name);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier,
|
|
|
+ struct v4l2_subdev *subdev,
|
|
|
+ struct v4l2_async_subdev *asd)
|
|
|
+{
|
|
|
+ struct rvin_dev *vin = notifier_to_vin(notifier);
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ for (i = 0; i < RCAR_VIN_NUM; i++)
|
|
|
+ if (vin->group->vin[i])
|
|
|
+ rvin_v4l2_unregister(vin->group->vin[i]);
|
|
|
+
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+
|
|
|
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
|
|
|
+ if (vin->group->csi[i].fwnode != asd->match.fwnode)
|
|
|
+ continue;
|
|
|
+ vin->group->csi[i].subdev = NULL;
|
|
|
+ vin_dbg(vin, "Unbind CSI-2 %s from slot %u\n", subdev->name, i);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier,
|
|
|
+ struct v4l2_subdev *subdev,
|
|
|
+ struct v4l2_async_subdev *asd)
|
|
|
+{
|
|
|
+ struct rvin_dev *vin = notifier_to_vin(notifier);
|
|
|
+ unsigned int i;
|
|
|
+
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+
|
|
|
+ for (i = 0; i < RVIN_CSI_MAX; i++) {
|
|
|
+ if (vin->group->csi[i].fwnode != asd->match.fwnode)
|
|
|
+ continue;
|
|
|
+ vin->group->csi[i].subdev = subdev;
|
|
|
+ vin_dbg(vin, "Bound CSI-2 %s to slot %u\n", subdev->name, i);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct v4l2_async_notifier_operations rvin_group_notify_ops = {
|
|
|
+ .bound = rvin_group_notify_bound,
|
|
|
+ .unbind = rvin_group_notify_unbind,
|
|
|
+ .complete = rvin_group_notify_complete,
|
|
|
+};
|
|
|
+
|
|
|
+static int rvin_mc_parse_of_endpoint(struct device *dev,
|
|
|
+ struct v4l2_fwnode_endpoint *vep,
|
|
|
+ struct v4l2_async_subdev *asd)
|
|
|
+{
|
|
|
+ struct rvin_dev *vin = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ if (vep->base.port != 1 || vep->base.id >= RVIN_CSI_MAX)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!of_device_is_available(to_of_node(asd->match.fwnode))) {
|
|
|
+
|
|
|
+ vin_dbg(vin, "OF device %pOF disabled, ignoring\n",
|
|
|
+ to_of_node(asd->match.fwnode));
|
|
|
+ return -ENOTCONN;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (vin->group->csi[vep->base.id].fwnode) {
|
|
|
+ vin_dbg(vin, "OF device %pOF already handled\n",
|
|
|
+ to_of_node(asd->match.fwnode));
|
|
|
+ return -ENOTCONN;
|
|
|
+ }
|
|
|
+
|
|
|
+ vin->group->csi[vep->base.id].fwnode = asd->match.fwnode;
|
|
|
+
|
|
|
+ vin_dbg(vin, "Add group OF device %pOF to slot %u\n",
|
|
|
+ to_of_node(asd->match.fwnode), vep->base.id);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int rvin_mc_parse_of_graph(struct rvin_dev *vin)
|
|
|
+{
|
|
|
+ unsigned int count = 0;
|
|
|
+ unsigned int i;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+
|
|
|
+ /* If there already is a notifier something has gone wrong, bail out. */
|
|
|
+ if (WARN_ON(vin->group->notifier)) {
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* If not all VIN's are registered don't register the notifier. */
|
|
|
+ for (i = 0; i < RCAR_VIN_NUM; i++)
|
|
|
+ if (vin->group->vin[i])
|
|
|
+ count++;
|
|
|
+
|
|
|
+ if (vin->group->count != count) {
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Have all VIN's look for subdevices. Some subdevices will overlap
|
|
|
+ * but the parser function can handle it, so each subdevice will
|
|
|
+ * only be registered once with the notifier.
|
|
|
+ */
|
|
|
+
|
|
|
+ vin->group->notifier = &vin->notifier;
|
|
|
+
|
|
|
+ for (i = 0; i < RCAR_VIN_NUM; i++) {
|
|
|
+ if (!vin->group->vin[i])
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(
|
|
|
+ vin->group->vin[i]->dev, vin->group->notifier,
|
|
|
+ sizeof(struct v4l2_async_subdev), 1,
|
|
|
+ rvin_mc_parse_of_endpoint);
|
|
|
+ if (ret) {
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+
|
|
|
+ vin->group->notifier->ops = &rvin_group_notify_ops;
|
|
|
+
|
|
|
+ ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier);
|
|
|
+ if (ret < 0) {
|
|
|
+ vin_err(vin, "Notifier registration failed\n");
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
static int rvin_mc_init(struct rvin_dev *vin)
|
|
|
{
|
|
|
int ret;
|
|
@@ -422,7 +649,15 @@ static int rvin_mc_init(struct rvin_dev *vin)
|
|
|
if (ret)
|
|
|
return ret;
|
|
|
|
|
|
- return rvin_group_get(vin);
|
|
|
+ ret = rvin_group_get(vin);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ ret = rvin_mc_parse_of_graph(vin);
|
|
|
+ if (ret)
|
|
|
+ rvin_group_put(vin);
|
|
|
+
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
@@ -542,10 +777,15 @@ static int rcar_vin_remove(struct platform_device *pdev)
|
|
|
v4l2_async_notifier_unregister(&vin->notifier);
|
|
|
v4l2_async_notifier_cleanup(&vin->notifier);
|
|
|
|
|
|
- if (vin->info->use_mc)
|
|
|
+ if (vin->info->use_mc) {
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+ if (vin->group->notifier == &vin->notifier)
|
|
|
+ vin->group->notifier = NULL;
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
rvin_group_put(vin);
|
|
|
- else
|
|
|
+ } else {
|
|
|
v4l2_ctrl_handler_free(&vin->ctrl_handler);
|
|
|
+ }
|
|
|
|
|
|
rvin_dma_unregister(vin);
|
|
|
|