11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
25bc3cb74SMauro Carvalho Chehab /*
35bc3cb74SMauro Carvalho Chehab     V4L2 device support.
45bc3cb74SMauro Carvalho Chehab 
55bc3cb74SMauro Carvalho Chehab     Copyright (C) 2008  Hans Verkuil <hverkuil@xs4all.nl>
65bc3cb74SMauro Carvalho Chehab 
75bc3cb74SMauro Carvalho Chehab  */
85bc3cb74SMauro Carvalho Chehab 
95bc3cb74SMauro Carvalho Chehab #include <linux/types.h>
105bc3cb74SMauro Carvalho Chehab #include <linux/ioctl.h>
115bc3cb74SMauro Carvalho Chehab #include <linux/module.h>
125bc3cb74SMauro Carvalho Chehab #include <linux/slab.h>
135bc3cb74SMauro Carvalho Chehab #include <linux/videodev2.h>
145bc3cb74SMauro Carvalho Chehab #include <media/v4l2-device.h>
155bc3cb74SMauro Carvalho Chehab #include <media/v4l2-ctrls.h>
165bc3cb74SMauro Carvalho Chehab 
v4l2_device_register(struct device * dev,struct v4l2_device * v4l2_dev)175bc3cb74SMauro Carvalho Chehab int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
185bc3cb74SMauro Carvalho Chehab {
195bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev == NULL)
205bc3cb74SMauro Carvalho Chehab 		return -EINVAL;
215bc3cb74SMauro Carvalho Chehab 
225bc3cb74SMauro Carvalho Chehab 	INIT_LIST_HEAD(&v4l2_dev->subdevs);
235bc3cb74SMauro Carvalho Chehab 	spin_lock_init(&v4l2_dev->lock);
245bc3cb74SMauro Carvalho Chehab 	v4l2_prio_init(&v4l2_dev->prio);
255bc3cb74SMauro Carvalho Chehab 	kref_init(&v4l2_dev->ref);
265bc3cb74SMauro Carvalho Chehab 	get_device(dev);
275bc3cb74SMauro Carvalho Chehab 	v4l2_dev->dev = dev;
285bc3cb74SMauro Carvalho Chehab 	if (dev == NULL) {
295bc3cb74SMauro Carvalho Chehab 		/* If dev == NULL, then name must be filled in by the caller */
309e882e3bSHans Verkuil 		if (WARN_ON(!v4l2_dev->name[0]))
319e882e3bSHans Verkuil 			return -EINVAL;
325bc3cb74SMauro Carvalho Chehab 		return 0;
335bc3cb74SMauro Carvalho Chehab 	}
345bc3cb74SMauro Carvalho Chehab 
355bc3cb74SMauro Carvalho Chehab 	/* Set name to driver name + device name if it is empty. */
365bc3cb74SMauro Carvalho Chehab 	if (!v4l2_dev->name[0])
375bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
385bc3cb74SMauro Carvalho Chehab 			dev->driver->name, dev_name(dev));
395bc3cb74SMauro Carvalho Chehab 	if (!dev_get_drvdata(dev))
405bc3cb74SMauro Carvalho Chehab 		dev_set_drvdata(dev, v4l2_dev);
415bc3cb74SMauro Carvalho Chehab 	return 0;
425bc3cb74SMauro Carvalho Chehab }
435bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_register);
445bc3cb74SMauro Carvalho Chehab 
v4l2_device_release(struct kref * ref)455bc3cb74SMauro Carvalho Chehab static void v4l2_device_release(struct kref *ref)
465bc3cb74SMauro Carvalho Chehab {
475bc3cb74SMauro Carvalho Chehab 	struct v4l2_device *v4l2_dev =
485bc3cb74SMauro Carvalho Chehab 		container_of(ref, struct v4l2_device, ref);
495bc3cb74SMauro Carvalho Chehab 
505bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->release)
515bc3cb74SMauro Carvalho Chehab 		v4l2_dev->release(v4l2_dev);
525bc3cb74SMauro Carvalho Chehab }
535bc3cb74SMauro Carvalho Chehab 
v4l2_device_put(struct v4l2_device * v4l2_dev)545bc3cb74SMauro Carvalho Chehab int v4l2_device_put(struct v4l2_device *v4l2_dev)
555bc3cb74SMauro Carvalho Chehab {
565bc3cb74SMauro Carvalho Chehab 	return kref_put(&v4l2_dev->ref, v4l2_device_release);
575bc3cb74SMauro Carvalho Chehab }
585bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_put);
595bc3cb74SMauro Carvalho Chehab 
v4l2_device_set_name(struct v4l2_device * v4l2_dev,const char * basename,atomic_t * instance)605bc3cb74SMauro Carvalho Chehab int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
615bc3cb74SMauro Carvalho Chehab 						atomic_t *instance)
625bc3cb74SMauro Carvalho Chehab {
635bc3cb74SMauro Carvalho Chehab 	int num = atomic_inc_return(instance) - 1;
645bc3cb74SMauro Carvalho Chehab 	int len = strlen(basename);
655bc3cb74SMauro Carvalho Chehab 
665bc3cb74SMauro Carvalho Chehab 	if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
675bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
685bc3cb74SMauro Carvalho Chehab 				"%s-%d", basename, num);
695bc3cb74SMauro Carvalho Chehab 	else
705bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
715bc3cb74SMauro Carvalho Chehab 				"%s%d", basename, num);
725bc3cb74SMauro Carvalho Chehab 	return num;
735bc3cb74SMauro Carvalho Chehab }
745bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_set_name);
755bc3cb74SMauro Carvalho Chehab 
v4l2_device_disconnect(struct v4l2_device * v4l2_dev)765bc3cb74SMauro Carvalho Chehab void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
775bc3cb74SMauro Carvalho Chehab {
785bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->dev == NULL)
795bc3cb74SMauro Carvalho Chehab 		return;
805bc3cb74SMauro Carvalho Chehab 
815bc3cb74SMauro Carvalho Chehab 	if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
825bc3cb74SMauro Carvalho Chehab 		dev_set_drvdata(v4l2_dev->dev, NULL);
835bc3cb74SMauro Carvalho Chehab 	put_device(v4l2_dev->dev);
845bc3cb74SMauro Carvalho Chehab 	v4l2_dev->dev = NULL;
855bc3cb74SMauro Carvalho Chehab }
865bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
875bc3cb74SMauro Carvalho Chehab 
v4l2_device_unregister(struct v4l2_device * v4l2_dev)885bc3cb74SMauro Carvalho Chehab void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
895bc3cb74SMauro Carvalho Chehab {
905bc3cb74SMauro Carvalho Chehab 	struct v4l2_subdev *sd, *next;
915bc3cb74SMauro Carvalho Chehab 
929e882e3bSHans Verkuil 	/* Just return if v4l2_dev is NULL or if it was already
939e882e3bSHans Verkuil 	 * unregistered before. */
949e882e3bSHans Verkuil 	if (v4l2_dev == NULL || !v4l2_dev->name[0])
955bc3cb74SMauro Carvalho Chehab 		return;
965bc3cb74SMauro Carvalho Chehab 	v4l2_device_disconnect(v4l2_dev);
975bc3cb74SMauro Carvalho Chehab 
985bc3cb74SMauro Carvalho Chehab 	/* Unregister subdevs */
995bc3cb74SMauro Carvalho Chehab 	list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
1005bc3cb74SMauro Carvalho Chehab 		v4l2_device_unregister_subdev(sd);
10151ff392cSEzequiel Garcia 		if (sd->flags & V4L2_SUBDEV_FL_IS_I2C)
10251ff392cSEzequiel Garcia 			v4l2_i2c_subdev_unregister(sd);
103a9cff393SEzequiel Garcia 		else if (sd->flags & V4L2_SUBDEV_FL_IS_SPI)
104a9cff393SEzequiel Garcia 			v4l2_spi_subdev_unregister(sd);
1055bc3cb74SMauro Carvalho Chehab 	}
1069e882e3bSHans Verkuil 	/* Mark as unregistered, thus preventing duplicate unregistrations */
1079e882e3bSHans Verkuil 	v4l2_dev->name[0] = '\0';
1085bc3cb74SMauro Carvalho Chehab }
1095bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_unregister);
1105bc3cb74SMauro Carvalho Chehab 
v4l2_device_register_subdev(struct v4l2_device * v4l2_dev,struct v4l2_subdev * sd)1115bc3cb74SMauro Carvalho Chehab int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
1125bc3cb74SMauro Carvalho Chehab 				struct v4l2_subdev *sd)
1135bc3cb74SMauro Carvalho Chehab {
1145bc3cb74SMauro Carvalho Chehab 	int err;
1155bc3cb74SMauro Carvalho Chehab 
1165bc3cb74SMauro Carvalho Chehab 	/* Check for valid input */
117fc490717SSakari Ailus 	if (!v4l2_dev || !sd || sd->v4l2_dev || !sd->name[0])
1185bc3cb74SMauro Carvalho Chehab 		return -EINVAL;
1195bc3cb74SMauro Carvalho Chehab 
120b2a06aecSSakari Ailus 	/*
121b2a06aecSSakari Ailus 	 * The reason to acquire the module here is to avoid unloading
122b2a06aecSSakari Ailus 	 * a module of sub-device which is registered to a media
123b2a06aecSSakari Ailus 	 * device. To make it possible to unload modules for media
124b2a06aecSSakari Ailus 	 * devices that also register sub-devices, do not
125b2a06aecSSakari Ailus 	 * try_module_get() such sub-device owners.
126b2a06aecSSakari Ailus 	 */
127b2a06aecSSakari Ailus 	sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
128b2a06aecSSakari Ailus 		sd->owner == v4l2_dev->dev->driver->owner;
129b2a06aecSSakari Ailus 
130b2a06aecSSakari Ailus 	if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
1315bc3cb74SMauro Carvalho Chehab 		return -ENODEV;
1325bc3cb74SMauro Carvalho Chehab 
1335bc3cb74SMauro Carvalho Chehab 	sd->v4l2_dev = v4l2_dev;
1345bc3cb74SMauro Carvalho Chehab 	/* This just returns 0 if either of the two args is NULL */
135da1b1aeaSHans Verkuil 	err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler,
136da1b1aeaSHans Verkuil 				    NULL, true);
137317efce9SLaurent Pinchart 	if (err)
138bdf5c198SSakari Ailus 		goto error_module;
1395bc3cb74SMauro Carvalho Chehab 
1405bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
1415bc3cb74SMauro Carvalho Chehab 	/* Register the entity. */
1425bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->mdev) {
143aead0ffbSEugen Hristev 		err = media_device_register_entity(v4l2_dev->mdev, &sd->entity);
144317efce9SLaurent Pinchart 		if (err < 0)
145bdf5c198SSakari Ailus 			goto error_module;
1465bc3cb74SMauro Carvalho Chehab 	}
1475bc3cb74SMauro Carvalho Chehab #endif
1485bc3cb74SMauro Carvalho Chehab 
149bdf5c198SSakari Ailus 	if (sd->internal_ops && sd->internal_ops->registered) {
150bdf5c198SSakari Ailus 		err = sd->internal_ops->registered(sd);
151bdf5c198SSakari Ailus 		if (err)
152bdf5c198SSakari Ailus 			goto error_unregister;
153bdf5c198SSakari Ailus 	}
154bdf5c198SSakari Ailus 
1555bc3cb74SMauro Carvalho Chehab 	spin_lock(&v4l2_dev->lock);
1565bc3cb74SMauro Carvalho Chehab 	list_add_tail(&sd->list, &v4l2_dev->subdevs);
1575bc3cb74SMauro Carvalho Chehab 	spin_unlock(&v4l2_dev->lock);
1585bc3cb74SMauro Carvalho Chehab 
1595bc3cb74SMauro Carvalho Chehab 	return 0;
160317efce9SLaurent Pinchart 
161317efce9SLaurent Pinchart error_unregister:
162bdf5c198SSakari Ailus #if defined(CONFIG_MEDIA_CONTROLLER)
163aead0ffbSEugen Hristev 	media_device_unregister_entity(&sd->entity);
164bdf5c198SSakari Ailus #endif
165317efce9SLaurent Pinchart error_module:
166b2a06aecSSakari Ailus 	if (!sd->owner_v4l2_dev)
167317efce9SLaurent Pinchart 		module_put(sd->owner);
168317efce9SLaurent Pinchart 	sd->v4l2_dev = NULL;
169317efce9SLaurent Pinchart 	return err;
1705bc3cb74SMauro Carvalho Chehab }
1715bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
1725bc3cb74SMauro Carvalho Chehab 
v4l2_subdev_release(struct v4l2_subdev * sd)1730e43734dSHans Verkuil static void v4l2_subdev_release(struct v4l2_subdev *sd)
1740e43734dSHans Verkuil {
1750e43734dSHans Verkuil 	struct module *owner = !sd->owner_v4l2_dev ? sd->owner : NULL;
1760e43734dSHans Verkuil 
1770e43734dSHans Verkuil 	if (sd->internal_ops && sd->internal_ops->release)
1780e43734dSHans Verkuil 		sd->internal_ops->release(sd);
1796990570fSDafna Hirschfeld 	sd->devnode = NULL;
1800e43734dSHans Verkuil 	module_put(owner);
1810e43734dSHans Verkuil }
1820e43734dSHans Verkuil 
v4l2_device_release_subdev_node(struct video_device * vdev)1835bc3cb74SMauro Carvalho Chehab static void v4l2_device_release_subdev_node(struct video_device *vdev)
1845bc3cb74SMauro Carvalho Chehab {
1850e43734dSHans Verkuil 	v4l2_subdev_release(video_get_drvdata(vdev));
1865bc3cb74SMauro Carvalho Chehab 	kfree(vdev);
1875bc3cb74SMauro Carvalho Chehab }
1885bc3cb74SMauro Carvalho Chehab 
__v4l2_device_register_subdev_nodes(struct v4l2_device * v4l2_dev,bool read_only)189f75c431eSJacopo Mondi int __v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev,
190f75c431eSJacopo Mondi 					bool read_only)
1915bc3cb74SMauro Carvalho Chehab {
1925bc3cb74SMauro Carvalho Chehab 	struct video_device *vdev;
1935bc3cb74SMauro Carvalho Chehab 	struct v4l2_subdev *sd;
1945bc3cb74SMauro Carvalho Chehab 	int err;
1955bc3cb74SMauro Carvalho Chehab 
1965bc3cb74SMauro Carvalho Chehab 	/* Register a device node for every subdev marked with the
1975bc3cb74SMauro Carvalho Chehab 	 * V4L2_SUBDEV_FL_HAS_DEVNODE flag.
1985bc3cb74SMauro Carvalho Chehab 	 */
1995bc3cb74SMauro Carvalho Chehab 	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
2005bc3cb74SMauro Carvalho Chehab 		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
2015bc3cb74SMauro Carvalho Chehab 			continue;
2025bc3cb74SMauro Carvalho Chehab 
203db0f4691SSebastian Reichel 		if (sd->devnode)
204db0f4691SSebastian Reichel 			continue;
205db0f4691SSebastian Reichel 
2065bc3cb74SMauro Carvalho Chehab 		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
2075bc3cb74SMauro Carvalho Chehab 		if (!vdev) {
2085bc3cb74SMauro Carvalho Chehab 			err = -ENOMEM;
2095bc3cb74SMauro Carvalho Chehab 			goto clean_up;
2105bc3cb74SMauro Carvalho Chehab 		}
2115bc3cb74SMauro Carvalho Chehab 
2125bc3cb74SMauro Carvalho Chehab 		video_set_drvdata(vdev, sd);
213c0decac1SMauro Carvalho Chehab 		strscpy(vdev->name, sd->name, sizeof(vdev->name));
214ee494cf3STomasz Figa 		vdev->dev_parent = sd->dev;
2155bc3cb74SMauro Carvalho Chehab 		vdev->v4l2_dev = v4l2_dev;
2165bc3cb74SMauro Carvalho Chehab 		vdev->fops = &v4l2_subdev_fops;
2175bc3cb74SMauro Carvalho Chehab 		vdev->release = v4l2_device_release_subdev_node;
2185bc3cb74SMauro Carvalho Chehab 		vdev->ctrl_handler = sd->ctrl_handler;
219f75c431eSJacopo Mondi 		if (read_only)
220f75c431eSJacopo Mondi 			set_bit(V4L2_FL_SUBDEV_RO_DEVNODE, &vdev->flags);
221*989bea48SSakari Ailus 		sd->devnode = vdev;
2225bc3cb74SMauro Carvalho Chehab 		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
2235bc3cb74SMauro Carvalho Chehab 					      sd->owner);
2245bc3cb74SMauro Carvalho Chehab 		if (err < 0) {
225*989bea48SSakari Ailus 			sd->devnode = NULL;
2265bc3cb74SMauro Carvalho Chehab 			kfree(vdev);
2275bc3cb74SMauro Carvalho Chehab 			goto clean_up;
2285bc3cb74SMauro Carvalho Chehab 		}
2295bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
230e31a0ba7SMauro Carvalho Chehab 		sd->entity.info.dev.major = VIDEO_MAJOR;
231e31a0ba7SMauro Carvalho Chehab 		sd->entity.info.dev.minor = vdev->minor;
232d9c21e3eSMauro Carvalho Chehab 
233d9c21e3eSMauro Carvalho Chehab 		/* Interface is created by __video_register_device() */
234d9c21e3eSMauro Carvalho Chehab 		if (vdev->v4l2_dev->mdev) {
235d9c21e3eSMauro Carvalho Chehab 			struct media_link *link;
236d9c21e3eSMauro Carvalho Chehab 
237d9c21e3eSMauro Carvalho Chehab 			link = media_create_intf_link(&sd->entity,
238d9c21e3eSMauro Carvalho Chehab 						      &vdev->intf_devnode->intf,
2394d1e4545SHans Verkuil 						      MEDIA_LNK_FL_ENABLED |
2404d1e4545SHans Verkuil 						      MEDIA_LNK_FL_IMMUTABLE);
2411630b832SDan Carpenter 			if (!link) {
2421630b832SDan Carpenter 				err = -ENOMEM;
243d9c21e3eSMauro Carvalho Chehab 				goto clean_up;
244d9c21e3eSMauro Carvalho Chehab 			}
2451630b832SDan Carpenter 		}
2465bc3cb74SMauro Carvalho Chehab #endif
2475bc3cb74SMauro Carvalho Chehab 	}
2485bc3cb74SMauro Carvalho Chehab 	return 0;
2495bc3cb74SMauro Carvalho Chehab 
2505bc3cb74SMauro Carvalho Chehab clean_up:
2515bc3cb74SMauro Carvalho Chehab 	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
2525bc3cb74SMauro Carvalho Chehab 		if (!sd->devnode)
2535bc3cb74SMauro Carvalho Chehab 			break;
2545bc3cb74SMauro Carvalho Chehab 		video_unregister_device(sd->devnode);
2555bc3cb74SMauro Carvalho Chehab 	}
2565bc3cb74SMauro Carvalho Chehab 
2575bc3cb74SMauro Carvalho Chehab 	return err;
2585bc3cb74SMauro Carvalho Chehab }
259f75c431eSJacopo Mondi EXPORT_SYMBOL_GPL(__v4l2_device_register_subdev_nodes);
2605bc3cb74SMauro Carvalho Chehab 
v4l2_device_unregister_subdev(struct v4l2_subdev * sd)2615bc3cb74SMauro Carvalho Chehab void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
2625bc3cb74SMauro Carvalho Chehab {
2635bc3cb74SMauro Carvalho Chehab 	struct v4l2_device *v4l2_dev;
2645bc3cb74SMauro Carvalho Chehab 
2655bc3cb74SMauro Carvalho Chehab 	/* return if it isn't registered */
2665bc3cb74SMauro Carvalho Chehab 	if (sd == NULL || sd->v4l2_dev == NULL)
2675bc3cb74SMauro Carvalho Chehab 		return;
2685bc3cb74SMauro Carvalho Chehab 
2695bc3cb74SMauro Carvalho Chehab 	v4l2_dev = sd->v4l2_dev;
2705bc3cb74SMauro Carvalho Chehab 
2715bc3cb74SMauro Carvalho Chehab 	spin_lock(&v4l2_dev->lock);
2725bc3cb74SMauro Carvalho Chehab 	list_del(&sd->list);
2735bc3cb74SMauro Carvalho Chehab 	spin_unlock(&v4l2_dev->lock);
2745bc3cb74SMauro Carvalho Chehab 
2755bc3cb74SMauro Carvalho Chehab 	if (sd->internal_ops && sd->internal_ops->unregistered)
2765bc3cb74SMauro Carvalho Chehab 		sd->internal_ops->unregistered(sd);
2775bc3cb74SMauro Carvalho Chehab 	sd->v4l2_dev = NULL;
2785bc3cb74SMauro Carvalho Chehab 
2795bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
280c2efd3e6SSylwester Nawrocki 	if (v4l2_dev->mdev) {
281d9c21e3eSMauro Carvalho Chehab 		/*
282d9c21e3eSMauro Carvalho Chehab 		 * No need to explicitly remove links, as both pads and
283d9c21e3eSMauro Carvalho Chehab 		 * links are removed by the function below, in the right order
284d9c21e3eSMauro Carvalho Chehab 		 */
2855bc3cb74SMauro Carvalho Chehab 		media_device_unregister_entity(&sd->entity);
286c2efd3e6SSylwester Nawrocki 	}
2875bc3cb74SMauro Carvalho Chehab #endif
2880e43734dSHans Verkuil 	if (sd->devnode)
2895bc3cb74SMauro Carvalho Chehab 		video_unregister_device(sd->devnode);
2900e43734dSHans Verkuil 	else
2910e43734dSHans Verkuil 		v4l2_subdev_release(sd);
2925bc3cb74SMauro Carvalho Chehab }
2935bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
294