|
@@ -20,12 +20,174 @@
|
|
|
#include <linux/of_graph.h>
|
|
|
#include <linux/platform_device.h>
|
|
|
#include <linux/pm_runtime.h>
|
|
|
+#include <linux/slab.h>
|
|
|
|
|
|
#include <media/v4l2-async.h>
|
|
|
#include <media/v4l2-fwnode.h>
|
|
|
|
|
|
#include "rcar-vin.h"
|
|
|
|
|
|
+/* -----------------------------------------------------------------------------
|
|
|
+ * Gen3 CSI2 Group Allocator
|
|
|
+ */
|
|
|
+
|
|
|
+/* FIXME: This should if we find a system that supports more
|
|
|
+ * than one group for the whole system be replaced with a linked
|
|
|
+ * list of groups. And eventually all of this should be replaced
|
|
|
+ * with a global device allocator API.
|
|
|
+ *
|
|
|
+ * But for now this works as on all supported systems there will
|
|
|
+ * be only one group for all instances.
|
|
|
+ */
|
|
|
+
|
|
|
+static DEFINE_MUTEX(rvin_group_lock);
|
|
|
+static struct rvin_group *rvin_group_data;
|
|
|
+
|
|
|
+static void rvin_group_cleanup(struct rvin_group *group)
|
|
|
+{
|
|
|
+ media_device_unregister(&group->mdev);
|
|
|
+ media_device_cleanup(&group->mdev);
|
|
|
+ mutex_destroy(&group->lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin)
|
|
|
+{
|
|
|
+ struct media_device *mdev = &group->mdev;
|
|
|
+ const struct of_device_id *match;
|
|
|
+ struct device_node *np;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_init(&group->lock);
|
|
|
+
|
|
|
+ /* Count number of VINs in the system */
|
|
|
+ group->count = 0;
|
|
|
+ for_each_matching_node(np, vin->dev->driver->of_match_table)
|
|
|
+ if (of_device_is_available(np))
|
|
|
+ group->count++;
|
|
|
+
|
|
|
+ vin_dbg(vin, "found %u enabled VIN's in DT", group->count);
|
|
|
+
|
|
|
+ mdev->dev = vin->dev;
|
|
|
+
|
|
|
+ match = of_match_node(vin->dev->driver->of_match_table,
|
|
|
+ vin->dev->of_node);
|
|
|
+
|
|
|
+ strlcpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name));
|
|
|
+ strlcpy(mdev->model, match->compatible, sizeof(mdev->model));
|
|
|
+ snprintf(mdev->bus_info, sizeof(mdev->bus_info), "platform:%s",
|
|
|
+ dev_name(mdev->dev));
|
|
|
+
|
|
|
+ media_device_init(mdev);
|
|
|
+
|
|
|
+ ret = media_device_register(&group->mdev);
|
|
|
+ if (ret)
|
|
|
+ rvin_group_cleanup(group);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void rvin_group_release(struct kref *kref)
|
|
|
+{
|
|
|
+ struct rvin_group *group =
|
|
|
+ container_of(kref, struct rvin_group, refcount);
|
|
|
+
|
|
|
+ mutex_lock(&rvin_group_lock);
|
|
|
+
|
|
|
+ rvin_group_data = NULL;
|
|
|
+
|
|
|
+ rvin_group_cleanup(group);
|
|
|
+
|
|
|
+ kfree(group);
|
|
|
+
|
|
|
+ mutex_unlock(&rvin_group_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static int rvin_group_get(struct rvin_dev *vin)
|
|
|
+{
|
|
|
+ struct rvin_group *group;
|
|
|
+ u32 id;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Make sure VIN id is present and sane */
|
|
|
+ ret = of_property_read_u32(vin->dev->of_node, "renesas,id", &id);
|
|
|
+ if (ret) {
|
|
|
+ vin_err(vin, "%pOF: No renesas,id property found\n",
|
|
|
+ vin->dev->of_node);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (id >= RCAR_VIN_NUM) {
|
|
|
+ vin_err(vin, "%pOF: Invalid renesas,id '%u'\n",
|
|
|
+ vin->dev->of_node, id);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Join or create a VIN group */
|
|
|
+ mutex_lock(&rvin_group_lock);
|
|
|
+ if (rvin_group_data) {
|
|
|
+ group = rvin_group_data;
|
|
|
+ kref_get(&group->refcount);
|
|
|
+ } else {
|
|
|
+ group = kzalloc(sizeof(*group), GFP_KERNEL);
|
|
|
+ if (!group) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto err_group;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = rvin_group_init(group, vin);
|
|
|
+ if (ret) {
|
|
|
+ kfree(group);
|
|
|
+ vin_err(vin, "Failed to initialize group\n");
|
|
|
+ goto err_group;
|
|
|
+ }
|
|
|
+
|
|
|
+ kref_init(&group->refcount);
|
|
|
+
|
|
|
+ rvin_group_data = group;
|
|
|
+ }
|
|
|
+ mutex_unlock(&rvin_group_lock);
|
|
|
+
|
|
|
+ /* Add VIN to group */
|
|
|
+ mutex_lock(&group->lock);
|
|
|
+
|
|
|
+ if (group->vin[id]) {
|
|
|
+ vin_err(vin, "Duplicate renesas,id property value %u\n", id);
|
|
|
+ mutex_unlock(&group->lock);
|
|
|
+ kref_put(&group->refcount, rvin_group_release);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ group->vin[id] = vin;
|
|
|
+
|
|
|
+ vin->id = id;
|
|
|
+ vin->group = group;
|
|
|
+ vin->v4l2_dev.mdev = &group->mdev;
|
|
|
+
|
|
|
+ mutex_unlock(&group->lock);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+err_group:
|
|
|
+ mutex_unlock(&rvin_group_lock);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static void rvin_group_put(struct rvin_dev *vin)
|
|
|
+{
|
|
|
+ mutex_lock(&vin->group->lock);
|
|
|
+
|
|
|
+ vin->group = NULL;
|
|
|
+ vin->v4l2_dev.mdev = NULL;
|
|
|
+
|
|
|
+ if (WARN_ON(vin->group->vin[vin->id] != vin))
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ vin->group->vin[vin->id] = NULL;
|
|
|
+out:
|
|
|
+ mutex_unlock(&vin->group->lock);
|
|
|
+
|
|
|
+ kref_put(&vin->group->refcount, rvin_group_release);
|
|
|
+}
|
|
|
+
|
|
|
/* -----------------------------------------------------------------------------
|
|
|
* Async notifier
|
|
|
*/
|
|
@@ -249,12 +411,18 @@ static int rvin_digital_graph_init(struct rvin_dev *vin)
|
|
|
|
|
|
static int rvin_mc_init(struct rvin_dev *vin)
|
|
|
{
|
|
|
+ int ret;
|
|
|
+
|
|
|
/* All our sources are CSI-2 */
|
|
|
vin->mbus_cfg.type = V4L2_MBUS_CSI2;
|
|
|
vin->mbus_cfg.flags = 0;
|
|
|
|
|
|
vin->pad.flags = MEDIA_PAD_FL_SINK;
|
|
|
- return media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad);
|
|
|
+ ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad);
|
|
|
+ if (ret)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ return rvin_group_get(vin);
|
|
|
}
|
|
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
@@ -374,7 +542,9 @@ 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)
|
|
|
+ rvin_group_put(vin);
|
|
|
+ else
|
|
|
v4l2_ctrl_handler_free(&vin->ctrl_handler);
|
|
|
|
|
|
rvin_dma_unregister(vin);
|