15bc3cb74SMauro Carvalho Chehab /*
25bc3cb74SMauro Carvalho Chehab     V4L2 device support.
35bc3cb74SMauro Carvalho Chehab 
45bc3cb74SMauro Carvalho Chehab     Copyright (C) 2008  Hans Verkuil <hverkuil@xs4all.nl>
55bc3cb74SMauro Carvalho Chehab 
65bc3cb74SMauro Carvalho Chehab     This program is free software; you can redistribute it and/or modify
75bc3cb74SMauro Carvalho Chehab     it under the terms of the GNU General Public License as published by
85bc3cb74SMauro Carvalho Chehab     the Free Software Foundation; either version 2 of the License, or
95bc3cb74SMauro Carvalho Chehab     (at your option) any later version.
105bc3cb74SMauro Carvalho Chehab 
115bc3cb74SMauro Carvalho Chehab     This program is distributed in the hope that it will be useful,
125bc3cb74SMauro Carvalho Chehab     but WITHOUT ANY WARRANTY; without even the implied warranty of
135bc3cb74SMauro Carvalho Chehab     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
145bc3cb74SMauro Carvalho Chehab     GNU General Public License for more details.
155bc3cb74SMauro Carvalho Chehab 
165bc3cb74SMauro Carvalho Chehab     You should have received a copy of the GNU General Public License
175bc3cb74SMauro Carvalho Chehab     along with this program; if not, write to the Free Software
185bc3cb74SMauro Carvalho Chehab     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
195bc3cb74SMauro Carvalho Chehab  */
205bc3cb74SMauro Carvalho Chehab 
215bc3cb74SMauro Carvalho Chehab #include <linux/types.h>
225bc3cb74SMauro Carvalho Chehab #include <linux/ioctl.h>
235bc3cb74SMauro Carvalho Chehab #include <linux/module.h>
245bc3cb74SMauro Carvalho Chehab #include <linux/i2c.h>
255bc3cb74SMauro Carvalho Chehab #include <linux/slab.h>
265bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_SPI)
275bc3cb74SMauro Carvalho Chehab #include <linux/spi/spi.h>
285bc3cb74SMauro Carvalho Chehab #endif
295bc3cb74SMauro Carvalho Chehab #include <linux/videodev2.h>
305bc3cb74SMauro Carvalho Chehab #include <media/v4l2-device.h>
315bc3cb74SMauro Carvalho Chehab #include <media/v4l2-ctrls.h>
325bc3cb74SMauro Carvalho Chehab 
335bc3cb74SMauro Carvalho Chehab int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
345bc3cb74SMauro Carvalho Chehab {
355bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev == NULL)
365bc3cb74SMauro Carvalho Chehab 		return -EINVAL;
375bc3cb74SMauro Carvalho Chehab 
385bc3cb74SMauro Carvalho Chehab 	INIT_LIST_HEAD(&v4l2_dev->subdevs);
395bc3cb74SMauro Carvalho Chehab 	spin_lock_init(&v4l2_dev->lock);
405bc3cb74SMauro Carvalho Chehab 	v4l2_prio_init(&v4l2_dev->prio);
415bc3cb74SMauro Carvalho Chehab 	kref_init(&v4l2_dev->ref);
425bc3cb74SMauro Carvalho Chehab 	get_device(dev);
435bc3cb74SMauro Carvalho Chehab 	v4l2_dev->dev = dev;
445bc3cb74SMauro Carvalho Chehab 	if (dev == NULL) {
455bc3cb74SMauro Carvalho Chehab 		/* If dev == NULL, then name must be filled in by the caller */
469e882e3bSHans Verkuil 		if (WARN_ON(!v4l2_dev->name[0]))
479e882e3bSHans Verkuil 			return -EINVAL;
485bc3cb74SMauro Carvalho Chehab 		return 0;
495bc3cb74SMauro Carvalho Chehab 	}
505bc3cb74SMauro Carvalho Chehab 
515bc3cb74SMauro Carvalho Chehab 	/* Set name to driver name + device name if it is empty. */
525bc3cb74SMauro Carvalho Chehab 	if (!v4l2_dev->name[0])
535bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
545bc3cb74SMauro Carvalho Chehab 			dev->driver->name, dev_name(dev));
555bc3cb74SMauro Carvalho Chehab 	if (!dev_get_drvdata(dev))
565bc3cb74SMauro Carvalho Chehab 		dev_set_drvdata(dev, v4l2_dev);
575bc3cb74SMauro Carvalho Chehab 	return 0;
585bc3cb74SMauro Carvalho Chehab }
595bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_register);
605bc3cb74SMauro Carvalho Chehab 
615bc3cb74SMauro Carvalho Chehab static void v4l2_device_release(struct kref *ref)
625bc3cb74SMauro Carvalho Chehab {
635bc3cb74SMauro Carvalho Chehab 	struct v4l2_device *v4l2_dev =
645bc3cb74SMauro Carvalho Chehab 		container_of(ref, struct v4l2_device, ref);
655bc3cb74SMauro Carvalho Chehab 
665bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->release)
675bc3cb74SMauro Carvalho Chehab 		v4l2_dev->release(v4l2_dev);
685bc3cb74SMauro Carvalho Chehab }
695bc3cb74SMauro Carvalho Chehab 
705bc3cb74SMauro Carvalho Chehab int v4l2_device_put(struct v4l2_device *v4l2_dev)
715bc3cb74SMauro Carvalho Chehab {
725bc3cb74SMauro Carvalho Chehab 	return kref_put(&v4l2_dev->ref, v4l2_device_release);
735bc3cb74SMauro Carvalho Chehab }
745bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_put);
755bc3cb74SMauro Carvalho Chehab 
765bc3cb74SMauro Carvalho Chehab int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename,
775bc3cb74SMauro Carvalho Chehab 						atomic_t *instance)
785bc3cb74SMauro Carvalho Chehab {
795bc3cb74SMauro Carvalho Chehab 	int num = atomic_inc_return(instance) - 1;
805bc3cb74SMauro Carvalho Chehab 	int len = strlen(basename);
815bc3cb74SMauro Carvalho Chehab 
825bc3cb74SMauro Carvalho Chehab 	if (basename[len - 1] >= '0' && basename[len - 1] <= '9')
835bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
845bc3cb74SMauro Carvalho Chehab 				"%s-%d", basename, num);
855bc3cb74SMauro Carvalho Chehab 	else
865bc3cb74SMauro Carvalho Chehab 		snprintf(v4l2_dev->name, sizeof(v4l2_dev->name),
875bc3cb74SMauro Carvalho Chehab 				"%s%d", basename, num);
885bc3cb74SMauro Carvalho Chehab 	return num;
895bc3cb74SMauro Carvalho Chehab }
905bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_set_name);
915bc3cb74SMauro Carvalho Chehab 
925bc3cb74SMauro Carvalho Chehab void v4l2_device_disconnect(struct v4l2_device *v4l2_dev)
935bc3cb74SMauro Carvalho Chehab {
945bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->dev == NULL)
955bc3cb74SMauro Carvalho Chehab 		return;
965bc3cb74SMauro Carvalho Chehab 
975bc3cb74SMauro Carvalho Chehab 	if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev)
985bc3cb74SMauro Carvalho Chehab 		dev_set_drvdata(v4l2_dev->dev, NULL);
995bc3cb74SMauro Carvalho Chehab 	put_device(v4l2_dev->dev);
1005bc3cb74SMauro Carvalho Chehab 	v4l2_dev->dev = NULL;
1015bc3cb74SMauro Carvalho Chehab }
1025bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_disconnect);
1035bc3cb74SMauro Carvalho Chehab 
1045bc3cb74SMauro Carvalho Chehab void v4l2_device_unregister(struct v4l2_device *v4l2_dev)
1055bc3cb74SMauro Carvalho Chehab {
1065bc3cb74SMauro Carvalho Chehab 	struct v4l2_subdev *sd, *next;
1075bc3cb74SMauro Carvalho Chehab 
1089e882e3bSHans Verkuil 	/* Just return if v4l2_dev is NULL or if it was already
1099e882e3bSHans Verkuil 	 * unregistered before. */
1109e882e3bSHans Verkuil 	if (v4l2_dev == NULL || !v4l2_dev->name[0])
1115bc3cb74SMauro Carvalho Chehab 		return;
1125bc3cb74SMauro Carvalho Chehab 	v4l2_device_disconnect(v4l2_dev);
1135bc3cb74SMauro Carvalho Chehab 
1145bc3cb74SMauro Carvalho Chehab 	/* Unregister subdevs */
1155bc3cb74SMauro Carvalho Chehab 	list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) {
1165bc3cb74SMauro Carvalho Chehab 		v4l2_device_unregister_subdev(sd);
1177b34be71SPeter Senna Tschudin #if IS_ENABLED(CONFIG_I2C)
1185bc3cb74SMauro Carvalho Chehab 		if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) {
1195bc3cb74SMauro Carvalho Chehab 			struct i2c_client *client = v4l2_get_subdevdata(sd);
1205bc3cb74SMauro Carvalho Chehab 
1210d51ebd3STommi Franttila 			/*
1220d51ebd3STommi Franttila 			 * We need to unregister the i2c client
1230d51ebd3STommi Franttila 			 * explicitly. We cannot rely on
1240d51ebd3STommi Franttila 			 * i2c_del_adapter to always unregister
1250d51ebd3STommi Franttila 			 * clients for us, since if the i2c bus is a
1260d51ebd3STommi Franttila 			 * platform bus, then it is never deleted.
1270d51ebd3STommi Franttila 			 *
1280d51ebd3STommi Franttila 			 * Device tree or ACPI based devices must not
1290d51ebd3STommi Franttila 			 * be unregistered as they have not been
1300d51ebd3STommi Franttila 			 * registered by us, and would not be
1310d51ebd3STommi Franttila 			 * re-created by just probing the V4L2 driver.
1320d51ebd3STommi Franttila 			 */
1330d51ebd3STommi Franttila 			if (client &&
1340d51ebd3STommi Franttila 			    !client->dev.of_node && !client->dev.fwnode)
1355bc3cb74SMauro Carvalho Chehab 				i2c_unregister_device(client);
1365bc3cb74SMauro Carvalho Chehab 			continue;
1375bc3cb74SMauro Carvalho Chehab 		}
1385bc3cb74SMauro Carvalho Chehab #endif
1395bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_SPI)
1405bc3cb74SMauro Carvalho Chehab 		if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) {
1415bc3cb74SMauro Carvalho Chehab 			struct spi_device *spi = v4l2_get_subdevdata(sd);
1425bc3cb74SMauro Carvalho Chehab 
1430d51ebd3STommi Franttila 			if (spi && !spi->dev.of_node && !spi->dev.fwnode)
1445bc3cb74SMauro Carvalho Chehab 				spi_unregister_device(spi);
1455bc3cb74SMauro Carvalho Chehab 			continue;
1465bc3cb74SMauro Carvalho Chehab 		}
1475bc3cb74SMauro Carvalho Chehab #endif
1485bc3cb74SMauro Carvalho Chehab 	}
1499e882e3bSHans Verkuil 	/* Mark as unregistered, thus preventing duplicate unregistrations */
1509e882e3bSHans Verkuil 	v4l2_dev->name[0] = '\0';
1515bc3cb74SMauro Carvalho Chehab }
1525bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_unregister);
1535bc3cb74SMauro Carvalho Chehab 
1545bc3cb74SMauro Carvalho Chehab int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
1555bc3cb74SMauro Carvalho Chehab 				struct v4l2_subdev *sd)
1565bc3cb74SMauro Carvalho Chehab {
1575bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
1585bc3cb74SMauro Carvalho Chehab 	struct media_entity *entity = &sd->entity;
1595bc3cb74SMauro Carvalho Chehab #endif
1605bc3cb74SMauro Carvalho Chehab 	int err;
1615bc3cb74SMauro Carvalho Chehab 
1625bc3cb74SMauro Carvalho Chehab 	/* Check for valid input */
1635bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev == NULL || sd == NULL || !sd->name[0])
1645bc3cb74SMauro Carvalho Chehab 		return -EINVAL;
1655bc3cb74SMauro Carvalho Chehab 
1665bc3cb74SMauro Carvalho Chehab 	/* Warn if we apparently re-register a subdev */
1675bc3cb74SMauro Carvalho Chehab 	WARN_ON(sd->v4l2_dev != NULL);
1685bc3cb74SMauro Carvalho Chehab 
169b2a06aecSSakari Ailus 	/*
170b2a06aecSSakari Ailus 	 * The reason to acquire the module here is to avoid unloading
171b2a06aecSSakari Ailus 	 * a module of sub-device which is registered to a media
172b2a06aecSSakari Ailus 	 * device. To make it possible to unload modules for media
173b2a06aecSSakari Ailus 	 * devices that also register sub-devices, do not
174b2a06aecSSakari Ailus 	 * try_module_get() such sub-device owners.
175b2a06aecSSakari Ailus 	 */
176b2a06aecSSakari Ailus 	sd->owner_v4l2_dev = v4l2_dev->dev && v4l2_dev->dev->driver &&
177b2a06aecSSakari Ailus 		sd->owner == v4l2_dev->dev->driver->owner;
178b2a06aecSSakari Ailus 
179b2a06aecSSakari Ailus 	if (!sd->owner_v4l2_dev && !try_module_get(sd->owner))
1805bc3cb74SMauro Carvalho Chehab 		return -ENODEV;
1815bc3cb74SMauro Carvalho Chehab 
1825bc3cb74SMauro Carvalho Chehab 	sd->v4l2_dev = v4l2_dev;
1835bc3cb74SMauro Carvalho Chehab 	if (sd->internal_ops && sd->internal_ops->registered) {
1845bc3cb74SMauro Carvalho Chehab 		err = sd->internal_ops->registered(sd);
185317efce9SLaurent Pinchart 		if (err)
186317efce9SLaurent Pinchart 			goto error_module;
1875bc3cb74SMauro Carvalho Chehab 	}
1885bc3cb74SMauro Carvalho Chehab 
1895bc3cb74SMauro Carvalho Chehab 	/* This just returns 0 if either of the two args is NULL */
19034a6b7d0SHans Verkuil 	err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler, NULL);
191317efce9SLaurent Pinchart 	if (err)
192317efce9SLaurent Pinchart 		goto error_unregister;
1935bc3cb74SMauro Carvalho Chehab 
1945bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
1955bc3cb74SMauro Carvalho Chehab 	/* Register the entity. */
1965bc3cb74SMauro Carvalho Chehab 	if (v4l2_dev->mdev) {
1975bc3cb74SMauro Carvalho Chehab 		err = media_device_register_entity(v4l2_dev->mdev, entity);
198317efce9SLaurent Pinchart 		if (err < 0)
199317efce9SLaurent Pinchart 			goto error_unregister;
2005bc3cb74SMauro Carvalho Chehab 	}
2015bc3cb74SMauro Carvalho Chehab #endif
2025bc3cb74SMauro Carvalho Chehab 
2035bc3cb74SMauro Carvalho Chehab 	spin_lock(&v4l2_dev->lock);
2045bc3cb74SMauro Carvalho Chehab 	list_add_tail(&sd->list, &v4l2_dev->subdevs);
2055bc3cb74SMauro Carvalho Chehab 	spin_unlock(&v4l2_dev->lock);
2065bc3cb74SMauro Carvalho Chehab 
2075bc3cb74SMauro Carvalho Chehab 	return 0;
208317efce9SLaurent Pinchart 
209317efce9SLaurent Pinchart error_unregister:
210317efce9SLaurent Pinchart 	if (sd->internal_ops && sd->internal_ops->unregistered)
211317efce9SLaurent Pinchart 		sd->internal_ops->unregistered(sd);
212317efce9SLaurent Pinchart error_module:
213b2a06aecSSakari Ailus 	if (!sd->owner_v4l2_dev)
214317efce9SLaurent Pinchart 		module_put(sd->owner);
215317efce9SLaurent Pinchart 	sd->v4l2_dev = NULL;
216317efce9SLaurent Pinchart 	return err;
2175bc3cb74SMauro Carvalho Chehab }
2185bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_register_subdev);
2195bc3cb74SMauro Carvalho Chehab 
2205bc3cb74SMauro Carvalho Chehab static void v4l2_device_release_subdev_node(struct video_device *vdev)
2215bc3cb74SMauro Carvalho Chehab {
2225bc3cb74SMauro Carvalho Chehab 	struct v4l2_subdev *sd = video_get_drvdata(vdev);
2235bc3cb74SMauro Carvalho Chehab 	sd->devnode = NULL;
2245bc3cb74SMauro Carvalho Chehab 	kfree(vdev);
2255bc3cb74SMauro Carvalho Chehab }
2265bc3cb74SMauro Carvalho Chehab 
2275bc3cb74SMauro Carvalho Chehab int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
2285bc3cb74SMauro Carvalho Chehab {
2295bc3cb74SMauro Carvalho Chehab 	struct video_device *vdev;
2305bc3cb74SMauro Carvalho Chehab 	struct v4l2_subdev *sd;
2315bc3cb74SMauro Carvalho Chehab 	int err;
2325bc3cb74SMauro Carvalho Chehab 
2335bc3cb74SMauro Carvalho Chehab 	/* Register a device node for every subdev marked with the
2345bc3cb74SMauro Carvalho Chehab 	 * V4L2_SUBDEV_FL_HAS_DEVNODE flag.
2355bc3cb74SMauro Carvalho Chehab 	 */
2365bc3cb74SMauro Carvalho Chehab 	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
2375bc3cb74SMauro Carvalho Chehab 		if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
2385bc3cb74SMauro Carvalho Chehab 			continue;
2395bc3cb74SMauro Carvalho Chehab 
2405bc3cb74SMauro Carvalho Chehab 		vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
2415bc3cb74SMauro Carvalho Chehab 		if (!vdev) {
2425bc3cb74SMauro Carvalho Chehab 			err = -ENOMEM;
2435bc3cb74SMauro Carvalho Chehab 			goto clean_up;
2445bc3cb74SMauro Carvalho Chehab 		}
2455bc3cb74SMauro Carvalho Chehab 
2465bc3cb74SMauro Carvalho Chehab 		video_set_drvdata(vdev, sd);
2475bc3cb74SMauro Carvalho Chehab 		strlcpy(vdev->name, sd->name, sizeof(vdev->name));
2485bc3cb74SMauro Carvalho Chehab 		vdev->v4l2_dev = v4l2_dev;
2495bc3cb74SMauro Carvalho Chehab 		vdev->fops = &v4l2_subdev_fops;
2505bc3cb74SMauro Carvalho Chehab 		vdev->release = v4l2_device_release_subdev_node;
2515bc3cb74SMauro Carvalho Chehab 		vdev->ctrl_handler = sd->ctrl_handler;
2525bc3cb74SMauro Carvalho Chehab 		err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
2535bc3cb74SMauro Carvalho Chehab 					      sd->owner);
2545bc3cb74SMauro Carvalho Chehab 		if (err < 0) {
2555bc3cb74SMauro Carvalho Chehab 			kfree(vdev);
2565bc3cb74SMauro Carvalho Chehab 			goto clean_up;
2575bc3cb74SMauro Carvalho Chehab 		}
2585bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
259e31a0ba7SMauro Carvalho Chehab 		sd->entity.info.dev.major = VIDEO_MAJOR;
260e31a0ba7SMauro Carvalho Chehab 		sd->entity.info.dev.minor = vdev->minor;
261d9c21e3eSMauro Carvalho Chehab 
262d9c21e3eSMauro Carvalho Chehab 		/* Interface is created by __video_register_device() */
263d9c21e3eSMauro Carvalho Chehab 		if (vdev->v4l2_dev->mdev) {
264d9c21e3eSMauro Carvalho Chehab 			struct media_link *link;
265d9c21e3eSMauro Carvalho Chehab 
266d9c21e3eSMauro Carvalho Chehab 			link = media_create_intf_link(&sd->entity,
267d9c21e3eSMauro Carvalho Chehab 						      &vdev->intf_devnode->intf,
268d9c21e3eSMauro Carvalho Chehab 						      0);
269d9c21e3eSMauro Carvalho Chehab 			if (!link)
270d9c21e3eSMauro Carvalho Chehab 				goto clean_up;
271d9c21e3eSMauro Carvalho Chehab 		}
2725bc3cb74SMauro Carvalho Chehab #endif
2735bc3cb74SMauro Carvalho Chehab 		sd->devnode = vdev;
2745bc3cb74SMauro Carvalho Chehab 	}
2755bc3cb74SMauro Carvalho Chehab 	return 0;
2765bc3cb74SMauro Carvalho Chehab 
2775bc3cb74SMauro Carvalho Chehab clean_up:
2785bc3cb74SMauro Carvalho Chehab 	list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
2795bc3cb74SMauro Carvalho Chehab 		if (!sd->devnode)
2805bc3cb74SMauro Carvalho Chehab 			break;
2815bc3cb74SMauro Carvalho Chehab 		video_unregister_device(sd->devnode);
2825bc3cb74SMauro Carvalho Chehab 	}
2835bc3cb74SMauro Carvalho Chehab 
2845bc3cb74SMauro Carvalho Chehab 	return err;
2855bc3cb74SMauro Carvalho Chehab }
2865bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes);
2875bc3cb74SMauro Carvalho Chehab 
2885bc3cb74SMauro Carvalho Chehab void v4l2_device_unregister_subdev(struct v4l2_subdev *sd)
2895bc3cb74SMauro Carvalho Chehab {
2905bc3cb74SMauro Carvalho Chehab 	struct v4l2_device *v4l2_dev;
2915bc3cb74SMauro Carvalho Chehab 
2925bc3cb74SMauro Carvalho Chehab 	/* return if it isn't registered */
2935bc3cb74SMauro Carvalho Chehab 	if (sd == NULL || sd->v4l2_dev == NULL)
2945bc3cb74SMauro Carvalho Chehab 		return;
2955bc3cb74SMauro Carvalho Chehab 
2965bc3cb74SMauro Carvalho Chehab 	v4l2_dev = sd->v4l2_dev;
2975bc3cb74SMauro Carvalho Chehab 
2985bc3cb74SMauro Carvalho Chehab 	spin_lock(&v4l2_dev->lock);
2995bc3cb74SMauro Carvalho Chehab 	list_del(&sd->list);
3005bc3cb74SMauro Carvalho Chehab 	spin_unlock(&v4l2_dev->lock);
3015bc3cb74SMauro Carvalho Chehab 
3025bc3cb74SMauro Carvalho Chehab 	if (sd->internal_ops && sd->internal_ops->unregistered)
3035bc3cb74SMauro Carvalho Chehab 		sd->internal_ops->unregistered(sd);
3045bc3cb74SMauro Carvalho Chehab 	sd->v4l2_dev = NULL;
3055bc3cb74SMauro Carvalho Chehab 
3065bc3cb74SMauro Carvalho Chehab #if defined(CONFIG_MEDIA_CONTROLLER)
307c2efd3e6SSylwester Nawrocki 	if (v4l2_dev->mdev) {
308d9c21e3eSMauro Carvalho Chehab 		/*
309d9c21e3eSMauro Carvalho Chehab 		 * No need to explicitly remove links, as both pads and
310d9c21e3eSMauro Carvalho Chehab 		 * links are removed by the function below, in the right order
311d9c21e3eSMauro Carvalho Chehab 		 */
3125bc3cb74SMauro Carvalho Chehab 		media_device_unregister_entity(&sd->entity);
313c2efd3e6SSylwester Nawrocki 	}
3145bc3cb74SMauro Carvalho Chehab #endif
3155bc3cb74SMauro Carvalho Chehab 	video_unregister_device(sd->devnode);
316b2a06aecSSakari Ailus 	if (!sd->owner_v4l2_dev)
3175bc3cb74SMauro Carvalho Chehab 		module_put(sd->owner);
3185bc3cb74SMauro Carvalho Chehab }
3195bc3cb74SMauro Carvalho Chehab EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev);
320