diff options
Diffstat (limited to 'drivers/media/v4l2-core/v4l2-device.c')
-rw-r--r-- | drivers/media/v4l2-core/v4l2-device.c | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/drivers/media/v4l2-core/v4l2-device.c b/drivers/media/v4l2-core/v4l2-device.c new file mode 100644 index 000000000000..1f203b85a637 --- /dev/null +++ b/drivers/media/v4l2-core/v4l2-device.c | |||
@@ -0,0 +1,280 @@ | |||
1 | /* | ||
2 | V4L2 device support. | ||
3 | |||
4 | Copyright (C) 2008 Hans Verkuil <hverkuil@xs4all.nl> | ||
5 | |||
6 | This program is free software; you can redistribute it and/or modify | ||
7 | it under the terms of the GNU General Public License as published by | ||
8 | the Free Software Foundation; either version 2 of the License, or | ||
9 | (at your option) any later version. | ||
10 | |||
11 | This program is distributed in the hope that it will be useful, | ||
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | GNU General Public License for more details. | ||
15 | |||
16 | You should have received a copy of the GNU General Public License | ||
17 | along with this program; if not, write to the Free Software | ||
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
19 | */ | ||
20 | |||
21 | #include <linux/types.h> | ||
22 | #include <linux/ioctl.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/i2c.h> | ||
25 | #include <linux/slab.h> | ||
26 | #if defined(CONFIG_SPI) | ||
27 | #include <linux/spi/spi.h> | ||
28 | #endif | ||
29 | #include <linux/videodev2.h> | ||
30 | #include <media/v4l2-device.h> | ||
31 | #include <media/v4l2-ctrls.h> | ||
32 | |||
33 | int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev) | ||
34 | { | ||
35 | if (v4l2_dev == NULL) | ||
36 | return -EINVAL; | ||
37 | |||
38 | INIT_LIST_HEAD(&v4l2_dev->subdevs); | ||
39 | spin_lock_init(&v4l2_dev->lock); | ||
40 | mutex_init(&v4l2_dev->ioctl_lock); | ||
41 | v4l2_prio_init(&v4l2_dev->prio); | ||
42 | kref_init(&v4l2_dev->ref); | ||
43 | get_device(dev); | ||
44 | v4l2_dev->dev = dev; | ||
45 | if (dev == NULL) { | ||
46 | /* If dev == NULL, then name must be filled in by the caller */ | ||
47 | WARN_ON(!v4l2_dev->name[0]); | ||
48 | return 0; | ||
49 | } | ||
50 | |||
51 | /* Set name to driver name + device name if it is empty. */ | ||
52 | if (!v4l2_dev->name[0]) | ||
53 | snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s", | ||
54 | dev->driver->name, dev_name(dev)); | ||
55 | if (!dev_get_drvdata(dev)) | ||
56 | dev_set_drvdata(dev, v4l2_dev); | ||
57 | return 0; | ||
58 | } | ||
59 | EXPORT_SYMBOL_GPL(v4l2_device_register); | ||
60 | |||
61 | static void v4l2_device_release(struct kref *ref) | ||
62 | { | ||
63 | struct v4l2_device *v4l2_dev = | ||
64 | container_of(ref, struct v4l2_device, ref); | ||
65 | |||
66 | if (v4l2_dev->release) | ||
67 | v4l2_dev->release(v4l2_dev); | ||
68 | } | ||
69 | |||
70 | int v4l2_device_put(struct v4l2_device *v4l2_dev) | ||
71 | { | ||
72 | return kref_put(&v4l2_dev->ref, v4l2_device_release); | ||
73 | } | ||
74 | EXPORT_SYMBOL_GPL(v4l2_device_put); | ||
75 | |||
76 | int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, | ||
77 | atomic_t *instance) | ||
78 | { | ||
79 | int num = atomic_inc_return(instance) - 1; | ||
80 | int len = strlen(basename); | ||
81 | |||
82 | if (basename[len - 1] >= '0' && basename[len - 1] <= '9') | ||
83 | snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), | ||
84 | "%s-%d", basename, num); | ||
85 | else | ||
86 | snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), | ||
87 | "%s%d", basename, num); | ||
88 | return num; | ||
89 | } | ||
90 | EXPORT_SYMBOL_GPL(v4l2_device_set_name); | ||
91 | |||
92 | void v4l2_device_disconnect(struct v4l2_device *v4l2_dev) | ||
93 | { | ||
94 | if (v4l2_dev->dev == NULL) | ||
95 | return; | ||
96 | |||
97 | if (dev_get_drvdata(v4l2_dev->dev) == v4l2_dev) | ||
98 | dev_set_drvdata(v4l2_dev->dev, NULL); | ||
99 | put_device(v4l2_dev->dev); | ||
100 | v4l2_dev->dev = NULL; | ||
101 | } | ||
102 | EXPORT_SYMBOL_GPL(v4l2_device_disconnect); | ||
103 | |||
104 | void v4l2_device_unregister(struct v4l2_device *v4l2_dev) | ||
105 | { | ||
106 | struct v4l2_subdev *sd, *next; | ||
107 | |||
108 | if (v4l2_dev == NULL) | ||
109 | return; | ||
110 | v4l2_device_disconnect(v4l2_dev); | ||
111 | |||
112 | /* Unregister subdevs */ | ||
113 | list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) { | ||
114 | v4l2_device_unregister_subdev(sd); | ||
115 | #if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) | ||
116 | if (sd->flags & V4L2_SUBDEV_FL_IS_I2C) { | ||
117 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
118 | |||
119 | /* We need to unregister the i2c client explicitly. | ||
120 | We cannot rely on i2c_del_adapter to always | ||
121 | unregister clients for us, since if the i2c bus | ||
122 | is a platform bus, then it is never deleted. */ | ||
123 | if (client) | ||
124 | i2c_unregister_device(client); | ||
125 | continue; | ||
126 | } | ||
127 | #endif | ||
128 | #if defined(CONFIG_SPI) | ||
129 | if (sd->flags & V4L2_SUBDEV_FL_IS_SPI) { | ||
130 | struct spi_device *spi = v4l2_get_subdevdata(sd); | ||
131 | |||
132 | if (spi) | ||
133 | spi_unregister_device(spi); | ||
134 | continue; | ||
135 | } | ||
136 | #endif | ||
137 | } | ||
138 | } | ||
139 | EXPORT_SYMBOL_GPL(v4l2_device_unregister); | ||
140 | |||
141 | int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, | ||
142 | struct v4l2_subdev *sd) | ||
143 | { | ||
144 | #if defined(CONFIG_MEDIA_CONTROLLER) | ||
145 | struct media_entity *entity = &sd->entity; | ||
146 | #endif | ||
147 | int err; | ||
148 | |||
149 | /* Check for valid input */ | ||
150 | if (v4l2_dev == NULL || sd == NULL || !sd->name[0]) | ||
151 | return -EINVAL; | ||
152 | |||
153 | /* Warn if we apparently re-register a subdev */ | ||
154 | WARN_ON(sd->v4l2_dev != NULL); | ||
155 | |||
156 | if (!try_module_get(sd->owner)) | ||
157 | return -ENODEV; | ||
158 | |||
159 | sd->v4l2_dev = v4l2_dev; | ||
160 | if (sd->internal_ops && sd->internal_ops->registered) { | ||
161 | err = sd->internal_ops->registered(sd); | ||
162 | if (err) { | ||
163 | module_put(sd->owner); | ||
164 | return err; | ||
165 | } | ||
166 | } | ||
167 | |||
168 | /* This just returns 0 if either of the two args is NULL */ | ||
169 | err = v4l2_ctrl_add_handler(v4l2_dev->ctrl_handler, sd->ctrl_handler); | ||
170 | if (err) { | ||
171 | if (sd->internal_ops && sd->internal_ops->unregistered) | ||
172 | sd->internal_ops->unregistered(sd); | ||
173 | module_put(sd->owner); | ||
174 | return err; | ||
175 | } | ||
176 | |||
177 | #if defined(CONFIG_MEDIA_CONTROLLER) | ||
178 | /* Register the entity. */ | ||
179 | if (v4l2_dev->mdev) { | ||
180 | err = media_device_register_entity(v4l2_dev->mdev, entity); | ||
181 | if (err < 0) { | ||
182 | if (sd->internal_ops && sd->internal_ops->unregistered) | ||
183 | sd->internal_ops->unregistered(sd); | ||
184 | module_put(sd->owner); | ||
185 | return err; | ||
186 | } | ||
187 | } | ||
188 | #endif | ||
189 | |||
190 | spin_lock(&v4l2_dev->lock); | ||
191 | list_add_tail(&sd->list, &v4l2_dev->subdevs); | ||
192 | spin_unlock(&v4l2_dev->lock); | ||
193 | |||
194 | return 0; | ||
195 | } | ||
196 | EXPORT_SYMBOL_GPL(v4l2_device_register_subdev); | ||
197 | |||
198 | static void v4l2_device_release_subdev_node(struct video_device *vdev) | ||
199 | { | ||
200 | struct v4l2_subdev *sd = video_get_drvdata(vdev); | ||
201 | sd->devnode = NULL; | ||
202 | kfree(vdev); | ||
203 | } | ||
204 | |||
205 | int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev) | ||
206 | { | ||
207 | struct video_device *vdev; | ||
208 | struct v4l2_subdev *sd; | ||
209 | int err; | ||
210 | |||
211 | /* Register a device node for every subdev marked with the | ||
212 | * V4L2_SUBDEV_FL_HAS_DEVNODE flag. | ||
213 | */ | ||
214 | list_for_each_entry(sd, &v4l2_dev->subdevs, list) { | ||
215 | if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE)) | ||
216 | continue; | ||
217 | |||
218 | vdev = kzalloc(sizeof(*vdev), GFP_KERNEL); | ||
219 | if (!vdev) { | ||
220 | err = -ENOMEM; | ||
221 | goto clean_up; | ||
222 | } | ||
223 | |||
224 | video_set_drvdata(vdev, sd); | ||
225 | strlcpy(vdev->name, sd->name, sizeof(vdev->name)); | ||
226 | vdev->v4l2_dev = v4l2_dev; | ||
227 | vdev->fops = &v4l2_subdev_fops; | ||
228 | vdev->release = v4l2_device_release_subdev_node; | ||
229 | vdev->ctrl_handler = sd->ctrl_handler; | ||
230 | err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, | ||
231 | sd->owner); | ||
232 | if (err < 0) { | ||
233 | kfree(vdev); | ||
234 | goto clean_up; | ||
235 | } | ||
236 | #if defined(CONFIG_MEDIA_CONTROLLER) | ||
237 | sd->entity.info.v4l.major = VIDEO_MAJOR; | ||
238 | sd->entity.info.v4l.minor = vdev->minor; | ||
239 | #endif | ||
240 | sd->devnode = vdev; | ||
241 | } | ||
242 | return 0; | ||
243 | |||
244 | clean_up: | ||
245 | list_for_each_entry(sd, &v4l2_dev->subdevs, list) { | ||
246 | if (!sd->devnode) | ||
247 | break; | ||
248 | video_unregister_device(sd->devnode); | ||
249 | } | ||
250 | |||
251 | return err; | ||
252 | } | ||
253 | EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes); | ||
254 | |||
255 | void v4l2_device_unregister_subdev(struct v4l2_subdev *sd) | ||
256 | { | ||
257 | struct v4l2_device *v4l2_dev; | ||
258 | |||
259 | /* return if it isn't registered */ | ||
260 | if (sd == NULL || sd->v4l2_dev == NULL) | ||
261 | return; | ||
262 | |||
263 | v4l2_dev = sd->v4l2_dev; | ||
264 | |||
265 | spin_lock(&v4l2_dev->lock); | ||
266 | list_del(&sd->list); | ||
267 | spin_unlock(&v4l2_dev->lock); | ||
268 | |||
269 | if (sd->internal_ops && sd->internal_ops->unregistered) | ||
270 | sd->internal_ops->unregistered(sd); | ||
271 | sd->v4l2_dev = NULL; | ||
272 | |||
273 | #if defined(CONFIG_MEDIA_CONTROLLER) | ||
274 | if (v4l2_dev->mdev) | ||
275 | media_device_unregister_entity(&sd->entity); | ||
276 | #endif | ||
277 | video_unregister_device(sd->devnode); | ||
278 | module_put(sd->owner); | ||
279 | } | ||
280 | EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev); | ||