1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
|
Chinese translated version of Documentation/video4linux/v4l2-framework.txt
If you have any comment or update to the content, please contact the
original document maintainer directly. However, if you have a problem
communicating in English you can also ask the Chinese maintainer for
help. Contact the Chinese maintainer if this translation is outdated
or if there is a problem with the translation.
Maintainer: Mauro Carvalho Chehab <mchehab@infradead.org>
Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
---------------------------------------------------------------------
Documentation/video4linux/v4l2-framework.txt 的中文翻译
如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
译存在问题,请联系中文版维护者。
英文版维护者: Mauro Carvalho Chehab <mchehab@infradead.org>
中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
以下为正文
---------------------------------------------------------------------
V4L2 驱动框架概览
==============
本文档描述 V4L2 框架所提供的各种结构和它们之间的关系。
介绍
----
大部分现代 V4L2 设备由多个 IC 组成,在 /dev 下导出多个设备节点,
并同时创建非 V4L2 设备(如 DVB、ALSA、FB、I2C 和红外输入设备)。
由于这种硬件的复杂性,V4L2 驱动也变得非常复杂。
尤其是 V4L2 必须支持 IC 实现音视频的多路复用和编解码,这就更增加了其
复杂性。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动器,但也可
使用其他总线。这些设备称为“子设备”。
长期以来,这个框架仅限于通过 video_device 结构体创建 V4L 设备节点,
并使用 video_buf 处理视频缓冲(注:本文不讨论 video_buf 框架)。
这意味着所有驱动必须自己设置设备实例并连接到子设备。其中一部分要正确地
完成是比较复杂的,使得许多驱动都没有正确地实现。
由于框架的缺失,有很多通用代码都不可重复利用。
因此,这个框架构建所有驱动都需要的基本结构块,而统一的框架将使通用代码
创建成实用函数并在所有驱动中共享变得更加容易。
驱动结构
-------
所有 V4L2 驱动都有如下结构:
1) 每个设备实例的结构体--包含其设备状态。
2) 初始化和控制子设备的方法(如果有)。
3) 创建 V4L2 设备节点 (/dev/videoX、/dev/vbiX 和 /dev/radioX)
并跟踪设备节点的特定数据。
4) 特定文件句柄结构体--包含每个文件句柄的数据。
5) 视频缓冲处理。
以下是它们的初略关系图:
device instances(设备实例)
|
+-sub-device instances(子设备实例)
|
\-V4L2 device nodes(V4L2 设备节点)
|
\-filehandle instances(文件句柄实例)
框架结构
-------
该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备
实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device
结构体保存 V4L2 设备节点的数据;将来 v4l2_fh 结构体将跟踪文件句柄
实例(暂未尚未实现)。
V4L2 框架也可与媒体框架整合(可选的)。如果驱动设置了 v4l2_device
结构体的 mdev 域,子设备和视频节点的入口将自动出现在媒体框架中。
v4l2_device 结构体
----------------
每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。
简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体
嵌入到一个更大的结构体中。
你必须注册这个设备实例:
v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注册操作将会初始化 v4l2_device 结构体。如果 dev->driver_data 域
为 NULL,就将其指向 v4l2_dev。
需要与媒体框架整合的驱动必须手动设置 dev->driver_data,指向包含
v4l2_device 结构体实例的驱动特定设备结构体。这可以在注册 V4L2 设备
实例前通过 dev_set_drvdata() 函数完成。同时必须设置 v4l2_device
结构体的 mdev 域,指向适当的初始化并注册过的 media_device 实例。
如果 v4l2_dev->name 为空,则它将被设置为从 dev 中衍生出的值(为了
更加精确,形式为驱动名后跟 bus_id)。如果你在调用 v4l2_device_register
前已经设置好了,则不会被修改。如果 dev 为 NULL,则你*必须*在调用
v4l2_device_register 前设置 v4l2_dev->name。
你可以基于驱动名和驱动的全局 atomic_t 类型的实例编号,通过
v4l2_device_set_name() 设置 name。这样会生成类似 ivtv0、ivtv1 等
名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:
cx18-0、cx18-1 等。此函数返回实例编号。
第一个 “dev” 参数通常是一个指向 pci_dev、usb_interface 或
platform_device 的指针。很少使其为 NULL,除非是一个ISA设备或者
当一个设备创建了多个 PCI 设备,使得 v4l2_dev 无法与一个特定的父设备
关联。
你也可以提供一个 notify() 回调,使子设备可以调用它实现事件通知。
但这个设置与子设备相关。子设备支持的任何通知必须在
include/media/<subdevice>.h 中定义一个消息头。
注销 v4l2_device 使用如下函数:
v4l2_device_unregister(struct v4l2_device *v4l2_dev);
如果 dev->driver_data 域指向 v4l2_dev,将会被重置为 NULL。注销同时
会自动从设备中注销所有子设备。
如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。
由于 v4l2_device 有一个指向父设备的指针必须被清除,同时标志父设备
已消失,所以必须调用以下函数:
v4l2_device_disconnect(struct v4l2_device *v4l2_dev);
这个函数并*不*注销子设备,因此你依然要调用 v4l2_device_unregister()
函数。如果你的驱动器并非热插拔的,就没有必要调用 v4l2_device_disconnect()。
有时你需要遍历所有被特定驱动注册的设备。这通常发生在多个设备驱动使用
同一个硬件的情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动,
同时 alsa 驱动也使用此硬件。
你可以使用如下例程遍历所有注册的设备:
static int callback(struct device *dev, void *p)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);
/* 测试这个设备是否已经初始化 */
if (v4l2_dev == NULL)
return 0;
...
return 0;
}
int iterate(void *p)
{
struct device_driver *drv;
int err;
/* 在PCI 总线上查找ivtv驱动。
pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */
drv = driver_find("ivtv", &pci_bus_type);
/* 遍历所有的ivtv设备实例 */
err = driver_for_each_device(drv, NULL, p, callback);
put_driver(drv);
return err;
}
有时你需要一个设备实例的运行计数。这个通常用于映射一个设备实例到一个
模块选择数组的索引。
推荐方法如下:
static atomic_t drv_instance = ATOMIC_INIT(0);
static int __devinit drv_probe(struct pci_dev *pdev,
const struct pci_device_id *pci_id)
{
...
state->instance = atomic_inc_return(&drv_instance) - 1;
}
如果你有多个设备节点,对于热插拔设备,知道何时注销 v4l2_device 结构体
就比较困难。为此 v4l2_device 有引用计数支持。当调用 video_register_device
时增加引用计数,而设备节点释放时减小引用计数。当引用计数为零,则
v4l2_device 的release() 回调将被执行。你就可以在此时做最后的清理工作。
如果创建了其他设备节点(比如 ALSA),则你可以通过以下函数手动增减
引用计数:
void v4l2_device_get(struct v4l2_device *v4l2_dev);
或:
int v4l2_device_put(struct v4l2_device *v4l2_dev);
由于引用技术初始化为 1 ,你也需要在 disconnect() 回调(对于 USB 设备)中
调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),否则
引用计数将永远不会为 0 。
v4l2_subdev结构体
------------------
许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责
音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。
这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的
统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。
每个子设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体可以单独
代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态
信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了
内核创建的设备数据。建议使用 v4l2_set_subdevdata() 将这个结构体的
指针保存在 v4l2_subdev 的私有数据域(dev_priv)中。这使得通过 v4l2_subdev
找到实际的低层总线特定设备数据变得容易。
你同时需要一个从低层结构体获取 v4l2_subdev 指针的方法。对于常用的
i2c_client 结构体,i2c_set_clientdata() 函数可用于保存一个 v4l2_subdev
指针;对于其他总线你可能需要使用其他相关函数。
桥驱动中也应保存每个子设备的私有数据,比如一个指向特定桥的各设备私有
数据的指针。为此 v4l2_subdev 结构体提供主机私有数据域(host_priv),
并可通过 v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata()
访问。
从总线桥驱动的视角,驱动加载子设备模块并以某种方式获得 v4l2_subdev
结构体指针。对于 i2c 总线设备相对简单:调用 i2c_get_clientdata()。
对于其他总线也需要做类似的操作。针对 I2C 总线上的子设备辅助函数帮你
完成了大部分复杂的工作。
每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备
不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的
函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以,
函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。
顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动
不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。
这些结构体定义如下:
struct v4l2_subdev_core_ops {
int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);
int (*log_status)(struct v4l2_subdev *sd);
int (*init)(struct v4l2_subdev *sd, u32 val);
...
};
struct v4l2_subdev_tuner_ops {
...
};
struct v4l2_subdev_audio_ops {
...
};
struct v4l2_subdev_video_ops {
...
};
struct v4l2_subdev_pad_ops {
...
};
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_pad_ops *video;
};
其中 core(核心)函数集通常可用于所有子设备,其他类别的实现依赖于
子设备。如视频设备可能不支持音频操作函数,反之亦然。
这样的设置在限制了函数指针数量的同时,还使增加新的操作函数和分类
变得较为容易。
子设备驱动可使用如下函数初始化 v4l2_subdev 结构体:
v4l2_subdev_init(sd, &ops);
然后,你必须用一个唯一的名字初始化 subdev->name,并初始化模块的
owner 域。若使用 i2c 辅助函数,这些都会帮你处理好。
若需同媒体框架整合,你必须调用 media_entity_init() 初始化 v4l2_subdev
结构体中的 media_entity 结构体(entity 域):
struct media_pad *pads = &my_sd->pads;
int err;
err = media_entity_init(&sd->entity, npads, pads, 0);
pads 数组必须预先初始化。无须手动设置 media_entity 的 type 和
name 域,但如有必要,revision 域必须初始化。
当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。
在子设备被注销之后,不要忘记清理 media_entity 结构体:
media_entity_cleanup(&sd->entity);
如果子设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops
替代 v4l2_subdev_video_ops 实现格式相关的功能。
这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接
验证函数。链接验证函数应对管道(两端链接的都是 V4L2 子设备)中的每个
链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。
如果 link_validate 操作没有设置,默认的 v4l2_subdev_link_validate_default()
函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端
都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行
上面提到的检查。
设备(桥)驱动程序必须向 v4l2_device 注册 v4l2_subdev:
int err = v4l2_device_register_subdev(v4l2_dev, sd);
如果子设备模块在它注册前消失,这个操作可能失败。在这个函数成功返回后,
subdev->dev 域就指向了 v4l2_device。
如果 v4l2_device 父设备的 mdev 域为非 NULL 值,则子设备实体将被自动
注册为媒体设备。
注销子设备则可用如下函数:
v4l2_device_unregister_subdev(sd);
此后,子设备模块就可卸载,且 sd->dev == NULL。
注册之设备后,可通过以下方式直接调用其操作函数:
err = sd->ops->core->g_chip_ident(sd, &chip);
但使用如下宏会比较容易且合适:
err = v4l2_subdev_call(sd, core, g_chip_ident, &chip);
这个宏将会做 NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果
subdev->core 或 subdev->core->g_chip_ident 为 NULL,则返回 -ENOIOCTLCMD;
否则将返回 subdev->ops->core->g_chip_ident ops 调用的实际结果。
有时也可能同时调用所有或一系列子设备的某个操作函数:
v4l2_device_call_all(v4l2_dev, 0, core, g_chip_ident, &chip);
任何不支持此操作的子设备都会被跳过,并忽略错误返回值。但如果你需要
检查出错码,则可使用如下函数:
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_chip_ident, &chip);
除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD
外)没有错误发生,则返回 0。
对于以上两个函数的第二个参数为组 ID。如果为 0,则所有子设备都会执行
这个操作。如果为非 0 值,则只有那些组 ID 匹配的子设备才会执行此操作。
在桥驱动注册一个子设备前,可以设置 sd->grp_id 为任何期望值(默认值为
0)。这个值属于桥驱动,且子设备驱动将不会修改和使用它。
组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路板上有多个
音频芯片,每个都有改变音量的能力。但当用户想要改变音量的时候,通常
只有一个会被实际使用。你可以对这样的子设备设置组 ID 为(例如 AUDIO_CONTROLLER)
并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就保证了只有
需要的子设备才会执行这个回调。
如果子设备需要通知它的 v4l2_device 父设备一个事件,可以调用
v4l2_subdev_notify(sd, notification, arg)。这个宏检查是否有一个
notify() 回调被注册,如果没有,返回 -ENODEV。否则返回 notify() 调用
结果。
使用 v4l2_subdev 的好处在于它是一个通用结构体,且不包含任何底层硬件
信息。所有驱动可以包含多个 I2C 总线的子设备,但也有子设备是通过 GPIO
控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于 v4l2
子系统来说就完全透明了。
V4L2 子设备用户空间API
--------------------
除了通过 v4l2_subdev_ops 结构导出的内核 API,V4L2 子设备也可以直接
通过用户空间应用程序来控制。
可以在 /dev 中创建名为 v4l-subdevX 设备节点,以通过其直接访问子设备。
如果子设备支持用户空间直接配置,必须在注册前设置 V4L2_SUBDEV_FL_HAS_DEVNODE
标志。
注册子设备之后, v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes()
函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE 的子设备创建
设备节点。这些设备节点会在子设备注销时自动删除。
这些设备节点处理 V4L2 API 的一个子集。
VIDIOC_QUERYCTRL
VIDIOC_QUERYMENU
VIDIOC_G_CTRL
VIDIOC_S_CTRL
VIDIOC_G_EXT_CTRLS
VIDIOC_S_EXT_CTRLS
VIDIOC_TRY_EXT_CTRLS
这些 ioctls 控制与 V4L2 中定义的一致。他们行为相同,唯一的
不同是他们只处理子设备的控制实现。根据驱动程序,这些控制也
可以通过一个(或多个) V4L2 设备节点访问。
VIDIOC_DQEVENT
VIDIOC_SUBSCRIBE_EVENT
VIDIOC_UNSUBSCRIBE_EVENT
这些 ioctls 事件与 V4L2 中定义的一致。他们行为相同,唯一的
不同是他们只处理子设备产生的事件。根据驱动程序,这些事件也
可以通过一个(或多个) V4L2 设备节点上报。
要使用事件通知的子设备驱动,在注册子设备前必须在 v4l2_subdev::flags
中设置 V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents
中初始化事件队列深度。注册完成后,事件会在 v4l2_subdev::devnode
设备节点中像通常一样被排队。
为正确支持事件机制,poll() 文件操作也应被实现。
私有 ioctls
不在以上列表中的所有 ioctls 会通过 core::ioctl 操作直接传递
给子设备驱动。
I2C 子设备驱动
-------------
由于这些驱动很常见,所以内特提供了特定的辅助函数(v4l2-common.h)让这些
设备的使用更加容易。
添加 v4l2_subdev 支持的推荐方法是让 I2C 驱动将 v4l2_subdev 结构体
嵌入到为每个 I2C 设备实例创建的状态结构体中。而最简单的设备没有状态
结构体,此时可以直接创建一个 v4l2_subdev 结构体。
一个典型的状态结构体如下所示(‘chipname’用芯片名代替):
struct chipname_state {
struct v4l2_subdev sd;
... /* 附加的状态域*/
};
初始化 v4l2_subdev 结构体的方法如下:
v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);
这个函数将填充 v4l2_subdev 结构体中的所有域,并保证 v4l2_subdev 和
i2c_client 都指向彼此。
同时,你也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针
添加一个辅助内联函数。
static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct chipname_state, sd);
}
使用以下函数可以通过 v4l2_subdev 结构体指针获得 i2c_client 结构体
指针:
struct i2c_client *client = v4l2_get_subdevdata(sd);
而以下函数则相反,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体
指针:
struct v4l2_subdev *sd = i2c_get_clientdata(client);
当 remove()函数被调用前,必须保证先调用 v4l2_device_unregister_subdev(sd)。
此操作将会从桥驱动中注销子设备。即使子设备没有注册,调用此函数也是
安全的。
必须这样做的原因是:当桥驱动注销 i2c 适配器时,remove()回调函数
会被那个适配器上的 i2c 设备调用。此后,相应的 v4l2_subdev 结构体
就不存在了,所有它们必须先被注销。在 remove()回调函数中调用
v4l2_device_unregister_subdev(sd),可以保证执行总是正确的。
桥驱动也有一些辅组函数可用:
struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
"module_foo", "chipid", 0x36, NULL);
这个函数会加载给定的模块(如果没有模块需要加载,可以为 NULL),
并用给定的 i2c 适配器结构体指针(i2c_adapter)和 器件地址(chip/address)
作为参数调用 i2c_new_device()。如果一切顺利,则就在 v4l2_device
中注册了子设备。
你也可以利用 v4l2_i2c_new_subdev()的最后一个参数,传递一个可能的
I2C 地址数组,让函数自动探测。这些探测地址只有在前一个参数为 0 的
情况下使用。非零参数意味着你知道准确的 i2c 地址,所以此时无须进行
探测。
如果出错,两个函数都返回 NULL。
注意:传递给 v4l2_i2c_new_subdev()的 chipid 通常与模块名一致。
它允许你指定一个芯片的变体,比如“saa7114”或“saa7115”。一般通过
i2c 驱动自动探测。chipid 的使用是在今后需要深入了解的事情。这个与
i2c 驱动不同,较容易混淆。要知道支持哪些芯片变体,你可以查阅 i2c
驱动代码的 i2c_device_id 表,上面列出了所有可能支持的芯片。
还有两个辅助函数:
v4l2_i2c_new_subdev_cfg:这个函数添加新的 irq 和 platform_data
参数,并有‘addr’和‘probed_addrs’参数:如果 addr 非零,则被使用
(不探测变体),否则 probed_addrs 中的地址将用于自动探测。
例如:以下代码将会探测地址(0x10):
struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
"module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10));
v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其
替代 irq、platform_data 和 add r参数传递给 i2c 驱动。
如果子设备支持 s_config 核心操作,这个操作会在子设备配置好之后以 irq 和
platform_data 为参数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数
同样也会调用 s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。
video_device结构体
-----------------
在 /dev 目录下的实际设备节点根据 video_device 结构体(v4l2-dev.h)
创建。此结构体既可以动态分配也可以嵌入到一个更大的结构体中。
动态分配方法如下:
struct video_device *vdev = video_device_alloc();
if (vdev == NULL)
return -ENOMEM;
vdev->release = video_device_release;
如果将其嵌入到一个大结构体中,则必须自己实现 release()回调。
struct video_device *vdev = &my_vdev->vdev;
vdev->release = my_vdev_release;
release()回调必须被设置,且在最后一个 video_device 用户退出之后
被调用。
默认的 video_device_release()回调只是调用 kfree 来释放之前分配的
内存。
你应该设置这些域:
- v4l2_dev: 设置为 v4l2_device 父设备。
- name: 设置为唯一的描述性设备名。
- fops: 设置为已有的 v4l2_file_operations 结构体。
- ioctl_ops: 如果你使用v4l2_ioctl_ops 来简化 ioctl 的维护
(强烈建议使用,且将来可能变为强制性的!),然后设置你自己的
v4l2_ioctl_ops 结构体.
- lock: 如果你要在驱
|