aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/v4l2-core
diff options
context:
space:
mode:
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>2013-01-08 05:06:31 -0500
committerMauro Carvalho Chehab <mchehab@redhat.com>2013-06-21 15:30:00 -0400
commite9e310491bdbc8c0f33ea0e2ce65eff345a01f71 (patch)
treec1ada792f3d31542ff8038d140de3232035d41a6 /drivers/media/v4l2-core
parent668773b84604926519e2baf444f382f88d799d41 (diff)
[media] V4L2: support asynchronous subdevice registration
Currently bridge device drivers register devices for all subdevices synchronously, typically, during their probing. E.g. if an I2C CMOS sensor is attached to a video bridge device, the bridge driver will create an I2C device and wait for the respective I2C driver to probe. This makes linking of devices straight forward, but this approach cannot be used with intrinsically asynchronous and unordered device registration systems like the Flattened Device Tree. To support such systems this patch adds an asynchronous subdevice registration framework to V4L2. To use it respective (e.g. I2C) subdevice drivers must register themselves with the framework. A bridge driver on the other hand must register notification callbacks, that will be called upon various related events. Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de> Acked-by: Hans Verkuil <hans.verkuil@cisco.com> Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Tested-by: Lad, Prabhakar <prabhakar.csengg@gmail.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media/v4l2-core')
-rw-r--r--drivers/media/v4l2-core/Makefile3
-rw-r--r--drivers/media/v4l2-core/v4l2-async.c280
2 files changed, 282 insertions, 1 deletions
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 628c6307e978..4c33b8d6520c 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -5,7 +5,8 @@
5tuner-objs := tuner-core.o 5tuner-objs := tuner-core.o
6 6
7videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \ 7videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-fh.o \
8 v4l2-event.o v4l2-ctrls.o v4l2-subdev.o v4l2-clk.o 8 v4l2-event.o v4l2-ctrls.o v4l2-subdev.o v4l2-clk.o \
9 v4l2-async.o
9ifeq ($(CONFIG_COMPAT),y) 10ifeq ($(CONFIG_COMPAT),y)
10 videodev-objs += v4l2-compat-ioctl32.o 11 videodev-objs += v4l2-compat-ioctl32.o
11endif 12endif
diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c
new file mode 100644
index 000000000000..c80ffb4ba567
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-async.c
@@ -0,0 +1,280 @@
1/*
2 * V4L2 asynchronous subdevice registration API
3 *
4 * Copyright (C) 2012-2013, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
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 version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#include <linux/device.h>
12#include <linux/err.h>
13#include <linux/i2c.h>
14#include <linux/list.h>
15#include <linux/module.h>
16#include <linux/mutex.h>
17#include <linux/platform_device.h>
18#include <linux/slab.h>
19#include <linux/types.h>
20
21#include <media/v4l2-async.h>
22#include <media/v4l2-device.h>
23#include <media/v4l2-subdev.h>
24
25static bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd)
26{
27 struct i2c_client *client = i2c_verify_client(dev);
28 return client &&
29 asd->bus_type == V4L2_ASYNC_BUS_I2C &&
30 asd->match.i2c.adapter_id == client->adapter->nr &&
31 asd->match.i2c.address == client->addr;
32}
33
34static bool match_platform(struct device *dev, struct v4l2_async_subdev *asd)
35{
36 return asd->bus_type == V4L2_ASYNC_BUS_PLATFORM &&
37 !strcmp(asd->match.platform.name, dev_name(dev));
38}
39
40static LIST_HEAD(subdev_list);
41static LIST_HEAD(notifier_list);
42static DEFINE_MUTEX(list_lock);
43
44static struct v4l2_async_subdev *v4l2_async_belongs(struct v4l2_async_notifier *notifier,
45 struct v4l2_async_subdev_list *asdl)
46{
47 struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl);
48 struct v4l2_async_subdev *asd;
49 bool (*match)(struct device *,
50 struct v4l2_async_subdev *);
51
52 list_for_each_entry(asd, &notifier->waiting, list) {
53 /* bus_type has been verified valid before */
54 switch (asd->bus_type) {
55 case V4L2_ASYNC_BUS_CUSTOM:
56 match = asd->match.custom.match;
57 if (!match)
58 /* Match always */
59 return asd;
60 break;
61 case V4L2_ASYNC_BUS_PLATFORM:
62 match = match_platform;
63 break;
64 case V4L2_ASYNC_BUS_I2C:
65 match = match_i2c;
66 break;
67 default:
68 /* Cannot happen, unless someone breaks us */
69 WARN_ON(true);
70 return NULL;
71 }
72
73 /* match cannot be NULL here */
74 if (match(sd->dev, asd))
75 return asd;
76 }
77
78 return NULL;
79}
80
81static int v4l2_async_test_notify(struct v4l2_async_notifier *notifier,
82 struct v4l2_async_subdev_list *asdl,
83 struct v4l2_async_subdev *asd)
84{
85 struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl);
86 int ret;
87
88 /* Remove from the waiting list */
89 list_del(&asd->list);
90 asdl->asd = asd;
91 asdl->notifier = notifier;
92
93 if (notifier->bound) {
94 ret = notifier->bound(notifier, sd, asd);
95 if (ret < 0)
96 return ret;
97 }
98 /* Move from the global subdevice list to notifier's done */
99 list_move(&asdl->list, &notifier->done);
100
101 ret = v4l2_device_register_subdev(notifier->v4l2_dev, sd);
102 if (ret < 0) {
103 if (notifier->unbind)
104 notifier->unbind(notifier, sd, asd);
105 return ret;
106 }
107
108 if (list_empty(&notifier->waiting) && notifier->complete)
109 return notifier->complete(notifier);
110
111 return 0;
112}
113
114static void v4l2_async_cleanup(struct v4l2_async_subdev_list *asdl)
115{
116 struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl);
117
118 v4l2_device_unregister_subdev(sd);
119 /* Subdevice driver will reprobe and put asdl back onto the list */
120 list_del_init(&asdl->list);
121 asdl->asd = NULL;
122 sd->dev = NULL;
123}
124
125int v4l2_async_notifier_register(struct v4l2_device *v4l2_dev,
126 struct v4l2_async_notifier *notifier)
127{
128 struct v4l2_async_subdev_list *asdl, *tmp;
129 struct v4l2_async_subdev *asd;
130 int i;
131
132 if (!notifier->num_subdevs || notifier->num_subdevs > V4L2_MAX_SUBDEVS)
133 return -EINVAL;
134
135 notifier->v4l2_dev = v4l2_dev;
136 INIT_LIST_HEAD(&notifier->waiting);
137 INIT_LIST_HEAD(&notifier->done);
138
139 for (i = 0; i < notifier->num_subdevs; i++) {
140 asd = notifier->subdev[i];
141
142 switch (asd->bus_type) {
143 case V4L2_ASYNC_BUS_CUSTOM:
144 case V4L2_ASYNC_BUS_PLATFORM:
145 case V4L2_ASYNC_BUS_I2C:
146 break;
147 default:
148 dev_err(notifier->v4l2_dev ? notifier->v4l2_dev->dev : NULL,
149 "Invalid bus-type %u on %p\n",
150 asd->bus_type, asd);
151 return -EINVAL;
152 }
153 list_add_tail(&asd->list, &notifier->waiting);
154 }
155
156 mutex_lock(&list_lock);
157
158 /* Keep also completed notifiers on the list */
159 list_add(&notifier->list, &notifier_list);
160
161 list_for_each_entry_safe(asdl, tmp, &subdev_list, list) {
162 int ret;
163
164 asd = v4l2_async_belongs(notifier, asdl);
165 if (!asd)
166 continue;
167
168 ret = v4l2_async_test_notify(notifier, asdl, asd);
169 if (ret < 0) {
170 mutex_unlock(&list_lock);
171 return ret;
172 }
173 }
174
175 mutex_unlock(&list_lock);
176
177 return 0;
178}
179EXPORT_SYMBOL(v4l2_async_notifier_register);
180
181void v4l2_async_notifier_unregister(struct v4l2_async_notifier *notifier)
182{
183 struct v4l2_async_subdev_list *asdl, *tmp;
184 unsigned int notif_n_subdev = notifier->num_subdevs;
185 unsigned int n_subdev = min(notif_n_subdev, V4L2_MAX_SUBDEVS);
186 struct device *dev[n_subdev];
187 int i = 0;
188
189 mutex_lock(&list_lock);
190
191 list_del(&notifier->list);
192
193 list_for_each_entry_safe(asdl, tmp, &notifier->done, list) {
194 struct v4l2_subdev *sd = v4l2_async_to_subdev(asdl);
195
196 dev[i] = get_device(sd->dev);
197
198 v4l2_async_cleanup(asdl);
199
200 /* If we handled USB devices, we'd have to lock the parent too */
201 device_release_driver(dev[i++]);
202
203 if (notifier->unbind)
204 notifier->unbind(notifier, sd, sd->asdl.asd);
205 }
206
207 mutex_unlock(&list_lock);
208
209 while (i--) {
210 struct device *d = dev[i];
211
212 if (d && device_attach(d) < 0) {
213 const char *name = "(none)";
214 int lock = device_trylock(d);
215
216 if (lock && d->driver)
217 name = d->driver->name;
218 dev_err(d, "Failed to re-probe to %s\n", name);
219 if (lock)
220 device_unlock(d);
221 }
222 put_device(d);
223 }
224 /*
225 * Don't care about the waiting list, it is initialised and populated
226 * upon notifier registration.
227 */
228}
229EXPORT_SYMBOL(v4l2_async_notifier_unregister);
230
231int v4l2_async_register_subdev(struct v4l2_subdev *sd)
232{
233 struct v4l2_async_subdev_list *asdl = &sd->asdl;
234 struct v4l2_async_notifier *notifier;
235
236 mutex_lock(&list_lock);
237
238 INIT_LIST_HEAD(&asdl->list);
239
240 list_for_each_entry(notifier, &notifier_list, list) {
241 struct v4l2_async_subdev *asd = v4l2_async_belongs(notifier, asdl);
242 if (asd) {
243 int ret = v4l2_async_test_notify(notifier, asdl, asd);
244 mutex_unlock(&list_lock);
245 return ret;
246 }
247 }
248
249 /* None matched, wait for hot-plugging */
250 list_add(&asdl->list, &subdev_list);
251
252 mutex_unlock(&list_lock);
253
254 return 0;
255}
256EXPORT_SYMBOL(v4l2_async_register_subdev);
257
258void v4l2_async_unregister_subdev(struct v4l2_subdev *sd)
259{
260 struct v4l2_async_subdev_list *asdl = &sd->asdl;
261 struct v4l2_async_notifier *notifier = asdl->notifier;
262
263 if (!asdl->asd) {
264 if (!list_empty(&asdl->list))
265 v4l2_async_cleanup(asdl);
266 return;
267 }
268
269 mutex_lock(&list_lock);
270
271 list_add(&asdl->asd->list, &notifier->waiting);
272
273 v4l2_async_cleanup(asdl);
274
275 if (notifier->unbind)
276 notifier->unbind(notifier, sd, sd->asdl.asd);
277
278 mutex_unlock(&list_lock);
279}
280EXPORT_SYMBOL(v4l2_async_unregister_subdev);