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