blob: 9727653b76ff64fd7a88a905f460e5c9caeef40f [file] [log] [blame]
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -03001/*
2 DVB device driver for em28xx
3
4 (c) 2008 Mauro Carvalho Chehab <mchehab@infradead.org>
5
Devin Heitmuellerbdfbf952008-04-17 21:38:27 -03006 (c) 2008 Devin Heitmueller <devin.heitmueller@gmail.com>
7 - Fixes for the driver to properly work with HVR-950
Devin Heitmueller4fd305b2008-06-04 13:43:46 -03008 - Fixes for the driver to properly work with Pinnacle PCTV HD Pro Stick
Devin Heitmuellerbdfbf952008-04-17 21:38:27 -03009
Aidan Thornton3421b772008-04-17 21:40:36 -030010 (c) 2008 Aidan Thornton <makosoft@googlemail.com>
11
12 Based on cx88-dvb, saa7134-dvb and videobuf-dvb originally written by:
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -030013 (c) 2004, 2005 Chris Pascoe <c.pascoe@itee.uq.edu.au>
14 (c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation; either version 2 of the License.
19 */
20
21#include <linux/kernel.h>
22#include <linux/usb.h>
23
24#include "em28xx.h"
25#include <media/v4l2-common.h>
26#include <media/videobuf-vmalloc.h>
27
28#include "lgdt330x.h"
Aidan Thornton7e6388a2008-04-17 21:40:03 -030029#include "zl10353.h"
Devin Heitmueller17d9d552008-06-08 10:22:03 -030030#ifdef EM28XX_DRX397XD_SUPPORT
31#include "drx397xD.h"
32#endif
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -030033
34MODULE_DESCRIPTION("driver for em28xx based DVB cards");
35MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>");
36MODULE_LICENSE("GPL");
37
38static unsigned int debug;
39module_param(debug, int, 0644);
40MODULE_PARM_DESC(debug, "enable debug messages [dvb]");
41
42DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
43
44#define dprintk(level, fmt, arg...) do { \
45if (debug >= level) \
Aidan Thornton3421b772008-04-17 21:40:36 -030046 printk(KERN_DEBUG "%s/2-dvb: " fmt, dev->name, ## arg); \
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -030047} while (0)
48
Aidan Thornton3421b772008-04-17 21:40:36 -030049#define EM28XX_DVB_NUM_BUFS 5
50#define EM28XX_DVB_MAX_PACKETSIZE 564
51#define EM28XX_DVB_MAX_PACKETS 64
52
53struct em28xx_dvb {
54 struct dvb_frontend *frontend;
55
56 /* feed count management */
57 struct mutex lock;
58 int nfeeds;
59
60 /* general boilerplate stuff */
61 struct dvb_adapter adapter;
62 struct dvb_demux demux;
63 struct dmxdev dmxdev;
64 struct dmx_frontend fe_hw;
65 struct dmx_frontend fe_mem;
66 struct dvb_net net;
67};
68
69
70static inline void print_err_status(struct em28xx *dev,
71 int packet, int status)
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -030072{
Aidan Thornton3421b772008-04-17 21:40:36 -030073 char *errmsg = "Unknown";
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -030074
Aidan Thornton3421b772008-04-17 21:40:36 -030075 switch (status) {
76 case -ENOENT:
77 errmsg = "unlinked synchronuously";
78 break;
79 case -ECONNRESET:
80 errmsg = "unlinked asynchronuously";
81 break;
82 case -ENOSR:
83 errmsg = "Buffer error (overrun)";
84 break;
85 case -EPIPE:
86 errmsg = "Stalled (device not responding)";
87 break;
88 case -EOVERFLOW:
89 errmsg = "Babble (bad cable?)";
90 break;
91 case -EPROTO:
92 errmsg = "Bit-stuff error (bad cable?)";
93 break;
94 case -EILSEQ:
95 errmsg = "CRC/Timeout (could be anything)";
96 break;
97 case -ETIME:
98 errmsg = "Device does not respond";
99 break;
100 }
101 if (packet < 0) {
102 dprintk(1, "URB status %d [%s].\n", status, errmsg);
103 } else {
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300104 dprintk(1, "URB packet %d, status %d [%s].\n",
105 packet, status, errmsg);
Aidan Thornton3421b772008-04-17 21:40:36 -0300106 }
107}
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300108
Aidan Thornton3421b772008-04-17 21:40:36 -0300109static inline int dvb_isoc_copy(struct em28xx *dev, struct urb *urb)
110{
111 int i;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300112
Aidan Thornton3421b772008-04-17 21:40:36 -0300113 if (!dev)
114 return 0;
115
116 if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))
117 return 0;
118
119 if (urb->status < 0) {
120 print_err_status(dev, -1, urb->status);
121 if (urb->status == -ENOENT)
122 return 0;
123 }
124
125 for (i = 0; i < urb->number_of_packets; i++) {
126 int status = urb->iso_frame_desc[i].status;
127
128 if (status < 0) {
129 print_err_status(dev, i, status);
130 if (urb->iso_frame_desc[i].status != -EPROTO)
131 continue;
132 }
133
134 dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer +
135 urb->iso_frame_desc[i].offset,
136 urb->iso_frame_desc[i].actual_length);
137 }
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300138
139 return 0;
140}
141
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300142static int start_streaming(struct em28xx_dvb *dvb)
143{
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300144 int rc;
Aidan Thornton3421b772008-04-17 21:40:36 -0300145 struct em28xx *dev = dvb->adapter.priv;
146
147 usb_set_interface(dev->udev, 0, 1);
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300148 rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
149 if (rc < 0)
150 return rc;
Aidan Thornton3421b772008-04-17 21:40:36 -0300151
152 return em28xx_init_isoc(dev, EM28XX_DVB_MAX_PACKETS,
153 EM28XX_DVB_NUM_BUFS, EM28XX_DVB_MAX_PACKETSIZE,
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300154 dvb_isoc_copy);
Aidan Thornton3421b772008-04-17 21:40:36 -0300155}
156
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300157static int stop_streaming(struct em28xx_dvb *dvb)
158{
Aidan Thornton3421b772008-04-17 21:40:36 -0300159 struct em28xx *dev = dvb->adapter.priv;
160
161 em28xx_uninit_isoc(dev);
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300162
163 em28xx_set_mode(dev, EM28XX_MODE_UNDEFINED);
164
Aidan Thornton3421b772008-04-17 21:40:36 -0300165 return 0;
166}
167
168static int start_feed(struct dvb_demux_feed *feed)
169{
170 struct dvb_demux *demux = feed->demux;
171 struct em28xx_dvb *dvb = demux->priv;
172 int rc, ret;
173
174 if (!demux->dmx.frontend)
175 return -EINVAL;
176
177 mutex_lock(&dvb->lock);
178 dvb->nfeeds++;
179 rc = dvb->nfeeds;
180
181 if (dvb->nfeeds == 1) {
182 ret = start_streaming(dvb);
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300183 if (ret < 0)
184 rc = ret;
Aidan Thornton3421b772008-04-17 21:40:36 -0300185 }
186
187 mutex_unlock(&dvb->lock);
188 return rc;
189}
190
191static int stop_feed(struct dvb_demux_feed *feed)
192{
193 struct dvb_demux *demux = feed->demux;
194 struct em28xx_dvb *dvb = demux->priv;
195 int err = 0;
196
197 mutex_lock(&dvb->lock);
198 dvb->nfeeds--;
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300199
200 if (0 == dvb->nfeeds)
Aidan Thornton3421b772008-04-17 21:40:36 -0300201 err = stop_streaming(dvb);
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300202
Aidan Thornton3421b772008-04-17 21:40:36 -0300203 mutex_unlock(&dvb->lock);
204 return err;
205}
206
207
Mauro Carvalho Chehabe3569ab2008-04-17 21:49:20 -0300208
209/* ------------------------------------------------------------------ */
210static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
211{
212 struct em28xx *dev = fe->dvb->priv;
213
214 if (acquire)
215 return em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
216 else
217 return em28xx_set_mode(dev, EM28XX_MODE_UNDEFINED);
218}
219
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300220/* ------------------------------------------------------------------ */
221
Mauro Carvalho Chehab227ad4a2008-04-17 21:37:40 -0300222static struct lgdt330x_config em2880_lgdt3303_dev = {
223 .demod_address = 0x0e,
224 .demod_chip = LGDT3303,
225};
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300226
Aidan Thornton7e6388a2008-04-17 21:40:03 -0300227static struct zl10353_config em28xx_zl10353_with_xc3028 = {
228 .demod_address = (0x1e >> 1),
229 .no_tuner = 1,
230 .parallel_ts = 1,
231 .if2 = 45600,
232};
233
Devin Heitmueller17d9d552008-06-08 10:22:03 -0300234#ifdef EM28XX_DRX397XD_SUPPORT
235/* [TODO] djh - not sure yet what the device config needs to contain */
236static struct drx397xD_config em28xx_drx397xD_with_xc3028 = {
237 .demod_address = (0xe0 >> 1),
238};
239#endif
240
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300241/* ------------------------------------------------------------------ */
242
243static int attach_xc3028(u8 addr, struct em28xx *dev)
244{
245 struct dvb_frontend *fe;
Mauro Carvalho Chehab3ca9c092008-04-17 21:37:53 -0300246 struct xc2028_config cfg;
247
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300248 memset(&cfg, 0, sizeof(cfg));
Mauro Carvalho Chehab3ca9c092008-04-17 21:37:53 -0300249 cfg.i2c_adap = &dev->i2c_adap;
250 cfg.i2c_addr = addr;
Mauro Carvalho Chehab3ca9c092008-04-17 21:37:53 -0300251 cfg.callback = em28xx_tuner_callback;
252
Aidan Thornton3421b772008-04-17 21:40:36 -0300253 if (!dev->dvb->frontend) {
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300254 printk(KERN_ERR "%s/2: dvb frontend not attached. "
255 "Can't attach xc3028\n",
256 dev->name);
257 return -EINVAL;
258 }
259
Aidan Thornton3421b772008-04-17 21:40:36 -0300260 fe = dvb_attach(xc2028_attach, dev->dvb->frontend, &cfg);
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300261 if (!fe) {
262 printk(KERN_ERR "%s/2: xc3028 attach failed\n",
263 dev->name);
Aidan Thornton3421b772008-04-17 21:40:36 -0300264 dvb_frontend_detach(dev->dvb->frontend);
Aidan Thornton3421b772008-04-17 21:40:36 -0300265 dev->dvb->frontend = NULL;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300266 return -EINVAL;
267 }
268
269 printk(KERN_INFO "%s/2: xc3028 attached\n", dev->name);
270
271 return 0;
272}
273
Aidan Thornton3421b772008-04-17 21:40:36 -0300274/* ------------------------------------------------------------------ */
275
276int register_dvb(struct em28xx_dvb *dvb,
277 struct module *module,
278 struct em28xx *dev,
279 struct device *device)
280{
281 int result;
282
283 mutex_init(&dvb->lock);
284
285 /* register adapter */
286 result = dvb_register_adapter(&dvb->adapter, dev->name, module, device,
287 adapter_nr);
288 if (result < 0) {
289 printk(KERN_WARNING "%s: dvb_register_adapter failed (errno = %d)\n",
290 dev->name, result);
291 goto fail_adapter;
292 }
Mauro Carvalho Chehabe3569ab2008-04-17 21:49:20 -0300293
294 /* Ensure all frontends negotiate bus access */
295 dvb->frontend->ops.ts_bus_ctrl = em28xx_dvb_bus_ctrl;
296
Aidan Thornton3421b772008-04-17 21:40:36 -0300297 dvb->adapter.priv = dev;
298
299 /* register frontend */
300 result = dvb_register_frontend(&dvb->adapter, dvb->frontend);
301 if (result < 0) {
302 printk(KERN_WARNING "%s: dvb_register_frontend failed (errno = %d)\n",
303 dev->name, result);
304 goto fail_frontend;
305 }
306
307 /* register demux stuff */
308 dvb->demux.dmx.capabilities =
309 DMX_TS_FILTERING | DMX_SECTION_FILTERING |
310 DMX_MEMORY_BASED_FILTERING;
311 dvb->demux.priv = dvb;
312 dvb->demux.filternum = 256;
313 dvb->demux.feednum = 256;
314 dvb->demux.start_feed = start_feed;
315 dvb->demux.stop_feed = stop_feed;
Mauro Carvalho Chehabe3569ab2008-04-17 21:49:20 -0300316
Aidan Thornton3421b772008-04-17 21:40:36 -0300317 result = dvb_dmx_init(&dvb->demux);
318 if (result < 0) {
319 printk(KERN_WARNING "%s: dvb_dmx_init failed (errno = %d)\n",
320 dev->name, result);
321 goto fail_dmx;
322 }
323
324 dvb->dmxdev.filternum = 256;
325 dvb->dmxdev.demux = &dvb->demux.dmx;
326 dvb->dmxdev.capabilities = 0;
327 result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
328 if (result < 0) {
329 printk(KERN_WARNING "%s: dvb_dmxdev_init failed (errno = %d)\n",
330 dev->name, result);
331 goto fail_dmxdev;
332 }
333
334 dvb->fe_hw.source = DMX_FRONTEND_0;
335 result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
336 if (result < 0) {
337 printk(KERN_WARNING "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",
338 dev->name, result);
339 goto fail_fe_hw;
340 }
341
342 dvb->fe_mem.source = DMX_MEMORY_FE;
343 result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
344 if (result < 0) {
345 printk(KERN_WARNING "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",
346 dev->name, result);
347 goto fail_fe_mem;
348 }
349
350 result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
351 if (result < 0) {
352 printk(KERN_WARNING "%s: connect_frontend failed (errno = %d)\n",
353 dev->name, result);
354 goto fail_fe_conn;
355 }
356
357 /* register network adapter */
358 dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
359 return 0;
360
361fail_fe_conn:
362 dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
363fail_fe_mem:
364 dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
365fail_fe_hw:
366 dvb_dmxdev_release(&dvb->dmxdev);
367fail_dmxdev:
368 dvb_dmx_release(&dvb->demux);
369fail_dmx:
370 dvb_unregister_frontend(dvb->frontend);
371fail_frontend:
372 dvb_frontend_detach(dvb->frontend);
373 dvb_unregister_adapter(&dvb->adapter);
374fail_adapter:
375 return result;
376}
377
378static void unregister_dvb(struct em28xx_dvb *dvb)
379{
380 dvb_net_release(&dvb->net);
381 dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
382 dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
383 dvb_dmxdev_release(&dvb->dmxdev);
384 dvb_dmx_release(&dvb->demux);
385 dvb_unregister_frontend(dvb->frontend);
386 dvb_frontend_detach(dvb->frontend);
387 dvb_unregister_adapter(&dvb->adapter);
388}
389
390
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300391static int dvb_init(struct em28xx *dev)
392{
Aidan Thornton3421b772008-04-17 21:40:36 -0300393 int result = 0;
394 struct em28xx_dvb *dvb;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300395
Devin Heitmuellerdf619182008-06-10 12:34:35 -0300396 if (!dev->has_dvb) {
397 /* This device does not support the extension */
398 return 0;
399 }
400
Aidan Thornton3421b772008-04-17 21:40:36 -0300401 dvb = kzalloc(sizeof(struct em28xx_dvb), GFP_KERNEL);
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300402
403 if (dvb == NULL) {
404 printk(KERN_INFO "em28xx_dvb: memory allocation failed\n");
Aidan Thornton3421b772008-04-17 21:40:36 -0300405 return -ENOMEM;
406 }
407 dev->dvb = dvb;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300408
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300409 em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300410 /* init frontend */
411 switch (dev->model) {
Mauro Carvalho Chehab227ad4a2008-04-17 21:37:40 -0300412 case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_950:
Devin Heitmueller4fd305b2008-06-04 13:43:46 -0300413 case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
Aidan Thornton3421b772008-04-17 21:40:36 -0300414 dvb->frontend = dvb_attach(lgdt330x_attach,
415 &em2880_lgdt3303_dev,
416 &dev->i2c_adap);
417 if (attach_xc3028(0x61, dev) < 0) {
418 result = -EINVAL;
419 goto out_free;
420 }
Mauro Carvalho Chehab227ad4a2008-04-17 21:37:40 -0300421 break;
Aidan Thornton7e6388a2008-04-17 21:40:03 -0300422 case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
Aidan Thornton3421b772008-04-17 21:40:36 -0300423 dvb->frontend = dvb_attach(zl10353_attach,
424 &em28xx_zl10353_with_xc3028,
425 &dev->i2c_adap);
426 if (attach_xc3028(0x61, dev) < 0) {
427 result = -EINVAL;
428 goto out_free;
429 }
Aidan Thornton7e6388a2008-04-17 21:40:03 -0300430 break;
Devin Heitmueller17d9d552008-06-08 10:22:03 -0300431 case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
432#ifdef EM28XX_DRX397XD_SUPPORT
433 /* We don't have the config structure properly populated, so
434 this is commented out for now */
435 dvb->frontend = dvb_attach(drx397xD_attach,
436 &em28xx_drx397xD_with_xc3028,
437 &dev->i2c_adap);
438 if (attach_xc3028(0x61, dev) < 0) {
439 result = -EINVAL;
440 goto out_free;
441 }
442 break;
443#endif
reinhard schwab655b8402008-07-26 10:47:00 -0300444 case EM2880_BOARD_TERRATEC_HYBRID_XS:
445 dvb->frontend = dvb_attach(zl10353_attach,
446 &em28xx_zl10353_with_xc3028,
447 &dev->i2c_adap);
448 if (attach_xc3028(0x61, dev) < 0) {
449 result = -EINVAL;
450 goto out_free;
451 }
452 break;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300453 default:
454 printk(KERN_ERR "%s/2: The frontend of your DVB/ATSC card"
455 " isn't supported yet\n",
456 dev->name);
457 break;
458 }
Aidan Thornton3421b772008-04-17 21:40:36 -0300459 if (NULL == dvb->frontend) {
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300460 printk(KERN_ERR
461 "%s/2: frontend initialization failed\n",
462 dev->name);
Aidan Thornton3421b772008-04-17 21:40:36 -0300463 result = -EINVAL;
464 goto out_free;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300465 }
466
467 /* register everything */
Aidan Thornton3421b772008-04-17 21:40:36 -0300468 result = register_dvb(dvb, THIS_MODULE, dev, &dev->udev->dev);
469
Douglas Schilling Landgraf6ea54d92008-04-17 21:41:10 -0300470 if (result < 0)
Aidan Thornton3421b772008-04-17 21:40:36 -0300471 goto out_free;
Aidan Thornton3421b772008-04-17 21:40:36 -0300472
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300473 em28xx_set_mode(dev, EM28XX_MODE_UNDEFINED);
Mauro Carvalho Chehab102a0b02008-04-17 21:40:45 -0300474 printk(KERN_INFO "Successfully loaded em28xx-dvb\n");
Aidan Thornton3421b772008-04-17 21:40:36 -0300475 return 0;
476
477out_free:
Mauro Carvalho Chehabc67ec532008-04-17 21:48:00 -0300478 em28xx_set_mode(dev, EM28XX_MODE_UNDEFINED);
Aidan Thornton3421b772008-04-17 21:40:36 -0300479 kfree(dvb);
480 dev->dvb = NULL;
481 return result;
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300482}
483
484static int dvb_fini(struct em28xx *dev)
485{
Devin Heitmuellerdf619182008-06-10 12:34:35 -0300486 if (!dev->has_dvb) {
487 /* This device does not support the extension */
488 return 0;
489 }
490
Aidan Thornton3421b772008-04-17 21:40:36 -0300491 if (dev->dvb) {
492 unregister_dvb(dev->dvb);
493 dev->dvb = NULL;
494 }
Mauro Carvalho Chehab3aefb792008-04-17 21:36:41 -0300495
496 return 0;
497}
498
499static struct em28xx_ops dvb_ops = {
500 .id = EM28XX_DVB,
501 .name = "Em28xx dvb Extension",
502 .init = dvb_init,
503 .fini = dvb_fini,
504};
505
506static int __init em28xx_dvb_register(void)
507{
508 return em28xx_register_extension(&dvb_ops);
509}
510
511static void __exit em28xx_dvb_unregister(void)
512{
513 em28xx_unregister_extension(&dvb_ops);
514}
515
516module_init(em28xx_dvb_register);
517module_exit(em28xx_dvb_unregister);