Commit | Line | Data |
---|---|---|
e0d3bafd SD |
1 | /* |
2 | DVB device driver for cx231xx | |
3 | ||
4 | Copyright (C) 2008 <srinivasa.deevi at conexant dot com> | |
b9255176 | 5 | Based on em28xx driver |
e0d3bafd SD |
6 | |
7 | This program is free software; you can redistribute it and/or modify | |
8 | it under the terms of the GNU General Public License as published by | |
9 | the Free Software Foundation; either version 2 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | This program is distributed in the hope that it will be useful, | |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with this program; if not, write to the Free Software | |
19 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
20 | */ | |
21 | ||
589dadf2 | 22 | #include "cx231xx.h" |
e0d3bafd | 23 | #include <linux/kernel.h> |
5a0e3ad6 | 24 | #include <linux/slab.h> |
e0d3bafd | 25 | |
e0d3bafd SD |
26 | #include <media/v4l2-common.h> |
27 | #include <media/videobuf-vmalloc.h> | |
6168309a | 28 | #include <media/tuner.h> |
e0d3bafd SD |
29 | |
30 | #include "xc5000.h" | |
64fbf444 PB |
31 | #include "s5h1432.h" |
32 | #include "tda18271.h" | |
33 | #include "s5h1411.h" | |
1a50fdde | 34 | #include "lgdt3305.h" |
dd2e7dd2 | 35 | #include "si2165.h" |
ede676c7 | 36 | #include "mb86a20s.h" |
9e49f7c3 | 37 | #include "si2157.h" |
809abdbf | 38 | #include "lgdt3306a.h" |
e0d3bafd | 39 | |
e0d3bafd SD |
40 | MODULE_DESCRIPTION("driver for cx231xx based DVB cards"); |
41 | MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>"); | |
42 | MODULE_LICENSE("GPL"); | |
43 | ||
44 | static unsigned int debug; | |
45 | module_param(debug, int, 0644); | |
46 | MODULE_PARM_DESC(debug, "enable debug messages [dvb]"); | |
47 | ||
48 | DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); | |
49 | ||
e0d3bafd SD |
50 | #define CX231XX_DVB_NUM_BUFS 5 |
51 | #define CX231XX_DVB_MAX_PACKETSIZE 564 | |
52 | #define CX231XX_DVB_MAX_PACKETS 64 | |
53 | ||
54 | struct cx231xx_dvb { | |
84b5dbf3 | 55 | struct dvb_frontend *frontend; |
e0d3bafd SD |
56 | |
57 | /* feed count management */ | |
84b5dbf3 MCC |
58 | struct mutex lock; |
59 | int nfeeds; | |
e0d3bafd SD |
60 | |
61 | /* general boilerplate stuff */ | |
84b5dbf3 MCC |
62 | struct dvb_adapter adapter; |
63 | struct dvb_demux demux; | |
64 | struct dmxdev dmxdev; | |
65 | struct dmx_frontend fe_hw; | |
66 | struct dmx_frontend fe_mem; | |
67 | struct dvb_net net; | |
6d3debaf | 68 | struct i2c_client *i2c_client_tuner; |
e0d3bafd SD |
69 | }; |
70 | ||
64fbf444 PB |
71 | static struct s5h1432_config dvico_s5h1432_config = { |
72 | .output_mode = S5H1432_SERIAL_OUTPUT, | |
73 | .gpio = S5H1432_GPIO_ON, | |
74 | .qam_if = S5H1432_IF_4000, | |
75 | .vsb_if = S5H1432_IF_4000, | |
76 | .inversion = S5H1432_INVERSION_OFF, | |
77 | .status_mode = S5H1432_DEMODLOCKING, | |
78 | .mpeg_timing = S5H1432_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, | |
79 | }; | |
80 | ||
81 | static struct tda18271_std_map cnxt_rde253s_tda18271_std_map = { | |
82 | .dvbt_6 = { .if_freq = 4000, .agc_mode = 3, .std = 4, | |
83 | .if_lvl = 1, .rfagc_top = 0x37, }, | |
84 | .dvbt_7 = { .if_freq = 4000, .agc_mode = 3, .std = 5, | |
85 | .if_lvl = 1, .rfagc_top = 0x37, }, | |
86 | .dvbt_8 = { .if_freq = 4000, .agc_mode = 3, .std = 6, | |
87 | .if_lvl = 1, .rfagc_top = 0x37, }, | |
88 | }; | |
89 | ||
ede676c7 | 90 | static struct tda18271_std_map mb86a20s_tda18271_config = { |
84c09d72 MCC |
91 | .dvbt_6 = { .if_freq = 4000, .agc_mode = 3, .std = 4, |
92 | .if_lvl = 0, .rfagc_top = 0x37, }, | |
ede676c7 MCC |
93 | }; |
94 | ||
64fbf444 PB |
95 | static struct tda18271_config cnxt_rde253s_tunerconfig = { |
96 | .std_map = &cnxt_rde253s_tda18271_std_map, | |
97 | .gate = TDA18271_GATE_ANALOG, | |
98 | }; | |
99 | ||
100 | static struct s5h1411_config tda18271_s5h1411_config = { | |
101 | .output_mode = S5H1411_SERIAL_OUTPUT, | |
102 | .gpio = S5H1411_GPIO_OFF, | |
103 | .vsb_if = S5H1411_IF_3250, | |
104 | .qam_if = S5H1411_IF_4000, | |
105 | .inversion = S5H1411_INVERSION_ON, | |
106 | .status_mode = S5H1411_DEMODLOCKING, | |
107 | .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, | |
108 | }; | |
109 | static struct s5h1411_config xc5000_s5h1411_config = { | |
110 | .output_mode = S5H1411_SERIAL_OUTPUT, | |
111 | .gpio = S5H1411_GPIO_OFF, | |
112 | .vsb_if = S5H1411_IF_3250, | |
113 | .qam_if = S5H1411_IF_3250, | |
114 | .inversion = S5H1411_INVERSION_OFF, | |
115 | .status_mode = S5H1411_DEMODLOCKING, | |
116 | .mpeg_timing = S5H1411_MPEGTIMING_CONTINOUS_NONINVERTING_CLOCK, | |
117 | }; | |
1a50fdde MK |
118 | |
119 | static struct lgdt3305_config hcw_lgdt3305_config = { | |
120 | .i2c_addr = 0x0e, | |
121 | .mpeg_mode = LGDT3305_MPEG_SERIAL, | |
122 | .tpclk_edge = LGDT3305_TPCLK_FALLING_EDGE, | |
123 | .tpvalid_polarity = LGDT3305_TP_VALID_HIGH, | |
124 | .deny_i2c_rptr = 1, | |
125 | .spectral_inversion = 1, | |
126 | .qam_if_khz = 4000, | |
127 | .vsb_if_khz = 3250, | |
128 | }; | |
129 | ||
130 | static struct tda18271_std_map hauppauge_tda18271_std_map = { | |
131 | .atsc_6 = { .if_freq = 3250, .agc_mode = 3, .std = 4, | |
132 | .if_lvl = 1, .rfagc_top = 0x58, }, | |
133 | .qam_6 = { .if_freq = 4000, .agc_mode = 3, .std = 5, | |
134 | .if_lvl = 1, .rfagc_top = 0x58, }, | |
135 | }; | |
136 | ||
137 | static struct tda18271_config hcw_tda18271_config = { | |
138 | .std_map = &hauppauge_tda18271_std_map, | |
139 | .gate = TDA18271_GATE_DIGITAL, | |
140 | }; | |
141 | ||
ede676c7 MCC |
142 | static const struct mb86a20s_config pv_mb86a20s_config = { |
143 | .demod_address = 0x10, | |
7572f9c5 | 144 | .is_serial = true, |
ede676c7 MCC |
145 | }; |
146 | ||
147 | static struct tda18271_config pv_tda18271_config = { | |
148 | .std_map = &mb86a20s_tda18271_config, | |
149 | .gate = TDA18271_GATE_DIGITAL, | |
150 | .small_i2c = TDA18271_03_BYTE_CHUNK_INIT, | |
151 | }; | |
152 | ||
dd2e7dd2 MS |
153 | static const struct si2165_config hauppauge_930C_HD_1113xx_si2165_config = { |
154 | .i2c_addr = 0x64, | |
155 | .chip_mode = SI2165_MODE_PLL_XTAL, | |
156 | .ref_freq_Hz = 16000000, | |
157 | }; | |
158 | ||
9e49f7c3 MS |
159 | static const struct si2165_config pctv_quatro_stick_1114xx_si2165_config = { |
160 | .i2c_addr = 0x64, | |
161 | .chip_mode = SI2165_MODE_PLL_EXT, | |
162 | .ref_freq_Hz = 24000000, | |
163 | }; | |
164 | ||
809abdbf OS |
165 | static struct lgdt3306a_config hauppauge_955q_lgdt3306a_config = { |
166 | .i2c_addr = 0x59, | |
167 | .qam_if_khz = 4000, | |
168 | .vsb_if_khz = 3250, | |
169 | .deny_i2c_rptr = 1, | |
170 | .spectral_inversion = 1, | |
171 | .mpeg_mode = LGDT3306A_MPEG_SERIAL, | |
172 | .tpclk_edge = LGDT3306A_TPCLK_RISING_EDGE, | |
173 | .tpvalid_polarity = LGDT3306A_TP_VALID_HIGH, | |
174 | .xtalMHz = 25, | |
175 | }; | |
176 | ||
84b5dbf3 | 177 | static inline void print_err_status(struct cx231xx *dev, int packet, int status) |
e0d3bafd SD |
178 | { |
179 | char *errmsg = "Unknown"; | |
180 | ||
181 | switch (status) { | |
182 | case -ENOENT: | |
183 | errmsg = "unlinked synchronuously"; | |
184 | break; | |
185 | case -ECONNRESET: | |
186 | errmsg = "unlinked asynchronuously"; | |
187 | break; | |
188 | case -ENOSR: | |
189 | errmsg = "Buffer error (overrun)"; | |
190 | break; | |
191 | case -EPIPE: | |
192 | errmsg = "Stalled (device not responding)"; | |
193 | break; | |
194 | case -EOVERFLOW: | |
195 | errmsg = "Babble (bad cable?)"; | |
196 | break; | |
197 | case -EPROTO: | |
198 | errmsg = "Bit-stuff error (bad cable?)"; | |
199 | break; | |
200 | case -EILSEQ: | |
201 | errmsg = "CRC/Timeout (could be anything)"; | |
202 | break; | |
203 | case -ETIME: | |
204 | errmsg = "Device does not respond"; | |
205 | break; | |
206 | } | |
207 | if (packet < 0) { | |
336fea92 | 208 | dev_dbg(dev->dev, |
3b795d01 | 209 | "URB status %d [%s].\n", status, errmsg); |
e0d3bafd | 210 | } else { |
336fea92 | 211 | dev_dbg(dev->dev, |
3b795d01 | 212 | "URB packet %d, status %d [%s].\n", |
e0d3bafd SD |
213 | packet, status, errmsg); |
214 | } | |
215 | } | |
216 | ||
217 | static inline int dvb_isoc_copy(struct cx231xx *dev, struct urb *urb) | |
218 | { | |
219 | int i; | |
220 | ||
221 | if (!dev) | |
222 | return 0; | |
223 | ||
990862a2 | 224 | if (dev->state & DEV_DISCONNECTED) |
e0d3bafd SD |
225 | return 0; |
226 | ||
227 | if (urb->status < 0) { | |
228 | print_err_status(dev, -1, urb->status); | |
229 | if (urb->status == -ENOENT) | |
230 | return 0; | |
231 | } | |
232 | ||
233 | for (i = 0; i < urb->number_of_packets; i++) { | |
234 | int status = urb->iso_frame_desc[i].status; | |
235 | ||
236 | if (status < 0) { | |
237 | print_err_status(dev, i, status); | |
238 | if (urb->iso_frame_desc[i].status != -EPROTO) | |
239 | continue; | |
240 | } | |
241 | ||
64fbf444 PB |
242 | dvb_dmx_swfilter(&dev->dvb->demux, |
243 | urb->transfer_buffer + | |
244 | urb->iso_frame_desc[i].offset, | |
245 | urb->iso_frame_desc[i].actual_length); | |
e0d3bafd SD |
246 | } |
247 | ||
248 | return 0; | |
249 | } | |
250 | ||
64fbf444 PB |
251 | static inline int dvb_bulk_copy(struct cx231xx *dev, struct urb *urb) |
252 | { | |
64fbf444 PB |
253 | if (!dev) |
254 | return 0; | |
255 | ||
990862a2 | 256 | if (dev->state & DEV_DISCONNECTED) |
64fbf444 PB |
257 | return 0; |
258 | ||
259 | if (urb->status < 0) { | |
260 | print_err_status(dev, -1, urb->status); | |
261 | if (urb->status == -ENOENT) | |
262 | return 0; | |
263 | } | |
264 | ||
265 | /* Feed the transport payload into the kernel demux */ | |
266 | dvb_dmx_swfilter(&dev->dvb->demux, | |
267 | urb->transfer_buffer, urb->actual_length); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
e0d3bafd SD |
272 | static int start_streaming(struct cx231xx_dvb *dvb) |
273 | { | |
274 | int rc; | |
275 | struct cx231xx *dev = dvb->adapter.priv; | |
276 | ||
64fbf444 | 277 | if (dev->USE_ISO) { |
336fea92 | 278 | dev_dbg(dev->dev, "DVB transfer mode is ISO.\n"); |
64fbf444 | 279 | cx231xx_set_alt_setting(dev, INDEX_TS1, 4); |
64fbf444 PB |
280 | rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); |
281 | if (rc < 0) | |
282 | return rc; | |
283 | dev->mode_tv = 1; | |
284 | return cx231xx_init_isoc(dev, CX231XX_DVB_MAX_PACKETS, | |
285 | CX231XX_DVB_NUM_BUFS, | |
286 | dev->ts1_mode.max_pkt_size, | |
287 | dvb_isoc_copy); | |
288 | } else { | |
336fea92 | 289 | dev_dbg(dev->dev, "DVB transfer mode is BULK.\n"); |
64fbf444 PB |
290 | cx231xx_set_alt_setting(dev, INDEX_TS1, 0); |
291 | rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); | |
292 | if (rc < 0) | |
293 | return rc; | |
294 | dev->mode_tv = 1; | |
295 | return cx231xx_init_bulk(dev, CX231XX_DVB_MAX_PACKETS, | |
296 | CX231XX_DVB_NUM_BUFS, | |
297 | dev->ts1_mode.max_pkt_size, | |
298 | dvb_bulk_copy); | |
299 | } | |
e0d3bafd | 300 | |
e0d3bafd SD |
301 | } |
302 | ||
303 | static int stop_streaming(struct cx231xx_dvb *dvb) | |
304 | { | |
305 | struct cx231xx *dev = dvb->adapter.priv; | |
306 | ||
64fbf444 PB |
307 | if (dev->USE_ISO) |
308 | cx231xx_uninit_isoc(dev); | |
309 | else | |
310 | cx231xx_uninit_bulk(dev); | |
e0d3bafd SD |
311 | |
312 | cx231xx_set_mode(dev, CX231XX_SUSPEND); | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
317 | static int start_feed(struct dvb_demux_feed *feed) | |
318 | { | |
84b5dbf3 | 319 | struct dvb_demux *demux = feed->demux; |
e0d3bafd SD |
320 | struct cx231xx_dvb *dvb = demux->priv; |
321 | int rc, ret; | |
322 | ||
323 | if (!demux->dmx.frontend) | |
324 | return -EINVAL; | |
325 | ||
326 | mutex_lock(&dvb->lock); | |
327 | dvb->nfeeds++; | |
328 | rc = dvb->nfeeds; | |
329 | ||
330 | if (dvb->nfeeds == 1) { | |
331 | ret = start_streaming(dvb); | |
332 | if (ret < 0) | |
333 | rc = ret; | |
334 | } | |
335 | ||
336 | mutex_unlock(&dvb->lock); | |
337 | return rc; | |
338 | } | |
339 | ||
340 | static int stop_feed(struct dvb_demux_feed *feed) | |
341 | { | |
84b5dbf3 | 342 | struct dvb_demux *demux = feed->demux; |
e0d3bafd SD |
343 | struct cx231xx_dvb *dvb = demux->priv; |
344 | int err = 0; | |
345 | ||
346 | mutex_lock(&dvb->lock); | |
347 | dvb->nfeeds--; | |
348 | ||
349 | if (0 == dvb->nfeeds) | |
350 | err = stop_streaming(dvb); | |
351 | ||
352 | mutex_unlock(&dvb->lock); | |
353 | return err; | |
354 | } | |
355 | ||
e0d3bafd SD |
356 | /* ------------------------------------------------------------------ */ |
357 | static int cx231xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire) | |
358 | { | |
359 | struct cx231xx *dev = fe->dvb->priv; | |
360 | ||
361 | if (acquire) | |
362 | return cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); | |
363 | else | |
364 | return cx231xx_set_mode(dev, CX231XX_SUSPEND); | |
365 | } | |
366 | ||
367 | /* ------------------------------------------------------------------ */ | |
368 | ||
e0d3bafd | 369 | static struct xc5000_config cnxt_rde250_tunerconfig = { |
84b5dbf3 | 370 | .i2c_address = 0x61, |
64fbf444 PB |
371 | .if_khz = 4000, |
372 | }; | |
373 | static struct xc5000_config cnxt_rdu250_tunerconfig = { | |
374 | .i2c_address = 0x61, | |
375 | .if_khz = 3250, | |
e0d3bafd SD |
376 | }; |
377 | ||
e0d3bafd SD |
378 | /* ------------------------------------------------------------------ */ |
379 | #if 0 | |
380 | static int attach_xc5000(u8 addr, struct cx231xx *dev) | |
381 | { | |
382 | ||
383 | struct dvb_frontend *fe; | |
384 | struct xc5000_config cfg; | |
385 | ||
386 | memset(&cfg, 0, sizeof(cfg)); | |
c3c3f1ae | 387 | cfg.i2c_adap = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master); |
84b5dbf3 | 388 | cfg.i2c_addr = addr; |
e0d3bafd SD |
389 | |
390 | if (!dev->dvb->frontend) { | |
336fea92 | 391 | dev_err(dev->dev, "%s/2: dvb frontend not attached. " |
84b5dbf3 | 392 | "Can't attach xc5000\n", dev->name); |
e0d3bafd SD |
393 | return -EINVAL; |
394 | } | |
395 | ||
396 | fe = dvb_attach(xc5000_attach, dev->dvb->frontend, &cfg); | |
397 | if (!fe) { | |
336fea92 | 398 | dev_err(dev->dev, "%s/2: xc5000 attach failed\n", dev->name); |
e0d3bafd SD |
399 | dvb_frontend_detach(dev->dvb->frontend); |
400 | dev->dvb->frontend = NULL; | |
401 | return -EINVAL; | |
402 | } | |
403 | ||
336fea92 | 404 | dev_info(dev->dev, "%s/2: xc5000 attached\n", dev->name); |
e0d3bafd SD |
405 | |
406 | return 0; | |
407 | } | |
408 | #endif | |
409 | ||
84b5dbf3 | 410 | int cx231xx_set_analog_freq(struct cx231xx *dev, u32 freq) |
e0d3bafd | 411 | { |
84b5dbf3 | 412 | if ((dev->dvb != NULL) && (dev->dvb->frontend != NULL)) { |
e0d3bafd | 413 | |
84b5dbf3 | 414 | struct dvb_tuner_ops *dops = &dev->dvb->frontend->ops.tuner_ops; |
e0d3bafd | 415 | |
84b5dbf3 MCC |
416 | if (dops->set_analog_params != NULL) { |
417 | struct analog_parameters params; | |
e0d3bafd | 418 | |
84b5dbf3 MCC |
419 | params.frequency = freq; |
420 | params.std = dev->norm; | |
421 | params.mode = 0; /* 0- Air; 1 - cable */ | |
422 | /*params.audmode = ; */ | |
e0d3bafd | 423 | |
84b5dbf3 | 424 | /* Set the analog parameters to set the frequency */ |
84b5dbf3 | 425 | dops->set_analog_params(dev->dvb->frontend, ¶ms); |
e0d3bafd SD |
426 | } |
427 | ||
84b5dbf3 MCC |
428 | } |
429 | ||
ea21f702 | 430 | return 0; |
e0d3bafd SD |
431 | } |
432 | ||
433 | int cx231xx_reset_analog_tuner(struct cx231xx *dev) | |
434 | { | |
84b5dbf3 | 435 | int status = 0; |
e0d3bafd | 436 | |
84b5dbf3 | 437 | if ((dev->dvb != NULL) && (dev->dvb->frontend != NULL)) { |
e0d3bafd | 438 | |
84b5dbf3 | 439 | struct dvb_tuner_ops *dops = &dev->dvb->frontend->ops.tuner_ops; |
e0d3bafd | 440 | |
84b5dbf3 | 441 | if (dops->init != NULL && !dev->xc_fw_load_done) { |
e0d3bafd | 442 | |
336fea92 | 443 | dev_dbg(dev->dev, |
b7085c08 | 444 | "Reloading firmware for XC5000\n"); |
84b5dbf3 MCC |
445 | status = dops->init(dev->dvb->frontend); |
446 | if (status == 0) { | |
447 | dev->xc_fw_load_done = 1; | |
336fea92 | 448 | dev_dbg(dev->dev, |
b7085c08 | 449 | "XC5000 firmware download completed\n"); |
84b5dbf3 MCC |
450 | } else { |
451 | dev->xc_fw_load_done = 0; | |
336fea92 | 452 | dev_dbg(dev->dev, |
b7085c08 | 453 | "XC5000 firmware download failed !!!\n"); |
e0d3bafd | 454 | } |
e0d3bafd SD |
455 | } |
456 | ||
84b5dbf3 MCC |
457 | } |
458 | ||
e0d3bafd SD |
459 | return status; |
460 | } | |
461 | ||
e0d3bafd SD |
462 | /* ------------------------------------------------------------------ */ |
463 | ||
464 | static int register_dvb(struct cx231xx_dvb *dvb, | |
84b5dbf3 MCC |
465 | struct module *module, |
466 | struct cx231xx *dev, struct device *device) | |
e0d3bafd SD |
467 | { |
468 | int result; | |
469 | ||
470 | mutex_init(&dvb->lock); | |
471 | ||
1d058bdc | 472 | |
e0d3bafd SD |
473 | /* register adapter */ |
474 | result = dvb_register_adapter(&dvb->adapter, dev->name, module, device, | |
475 | adapter_nr); | |
476 | if (result < 0) { | |
336fea92 | 477 | dev_warn(dev->dev, |
84b5dbf3 | 478 | "%s: dvb_register_adapter failed (errno = %d)\n", |
e0d3bafd SD |
479 | dev->name, result); |
480 | goto fail_adapter; | |
481 | } | |
89a2c1d6 | 482 | dvb_register_media_controller(&dvb->adapter, dev->media_dev); |
e0d3bafd SD |
483 | |
484 | /* Ensure all frontends negotiate bus access */ | |
485 | dvb->frontend->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl; | |
486 | ||
487 | dvb->adapter.priv = dev; | |
488 | ||
489 | /* register frontend */ | |
490 | result = dvb_register_frontend(&dvb->adapter, dvb->frontend); | |
491 | if (result < 0) { | |
336fea92 | 492 | dev_warn(dev->dev, |
84b5dbf3 | 493 | "%s: dvb_register_frontend failed (errno = %d)\n", |
e0d3bafd SD |
494 | dev->name, result); |
495 | goto fail_frontend; | |
496 | } | |
497 | ||
498 | /* register demux stuff */ | |
499 | dvb->demux.dmx.capabilities = | |
84b5dbf3 MCC |
500 | DMX_TS_FILTERING | DMX_SECTION_FILTERING | |
501 | DMX_MEMORY_BASED_FILTERING; | |
502 | dvb->demux.priv = dvb; | |
503 | dvb->demux.filternum = 256; | |
504 | dvb->demux.feednum = 256; | |
e0d3bafd | 505 | dvb->demux.start_feed = start_feed; |
84b5dbf3 | 506 | dvb->demux.stop_feed = stop_feed; |
e0d3bafd SD |
507 | |
508 | result = dvb_dmx_init(&dvb->demux); | |
509 | if (result < 0) { | |
336fea92 | 510 | dev_warn(dev->dev, |
3b795d01 | 511 | "%s: dvb_dmx_init failed (errno = %d)\n", |
e0d3bafd SD |
512 | dev->name, result); |
513 | goto fail_dmx; | |
514 | } | |
515 | ||
84b5dbf3 MCC |
516 | dvb->dmxdev.filternum = 256; |
517 | dvb->dmxdev.demux = &dvb->demux.dmx; | |
e0d3bafd SD |
518 | dvb->dmxdev.capabilities = 0; |
519 | result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter); | |
520 | if (result < 0) { | |
336fea92 | 521 | dev_warn(dev->dev, |
3b795d01 MCC |
522 | "%s: dvb_dmxdev_init failed (errno = %d)\n", |
523 | dev->name, result); | |
e0d3bafd SD |
524 | goto fail_dmxdev; |
525 | } | |
526 | ||
527 | dvb->fe_hw.source = DMX_FRONTEND_0; | |
528 | result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw); | |
529 | if (result < 0) { | |
336fea92 | 530 | dev_warn(dev->dev, |
84b5dbf3 | 531 | "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n", |
e0d3bafd SD |
532 | dev->name, result); |
533 | goto fail_fe_hw; | |
534 | } | |
535 | ||
536 | dvb->fe_mem.source = DMX_MEMORY_FE; | |
537 | result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem); | |
538 | if (result < 0) { | |
336fea92 | 539 | dev_warn(dev->dev, |
3b795d01 MCC |
540 | "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n", |
541 | dev->name, result); | |
e0d3bafd SD |
542 | goto fail_fe_mem; |
543 | } | |
544 | ||
545 | result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw); | |
546 | if (result < 0) { | |
336fea92 | 547 | dev_warn(dev->dev, |
3b795d01 MCC |
548 | "%s: connect_frontend failed (errno = %d)\n", |
549 | dev->name, result); | |
e0d3bafd SD |
550 | goto fail_fe_conn; |
551 | } | |
552 | ||
553 | /* register network adapter */ | |
554 | dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx); | |
6168309a MCC |
555 | result = dvb_create_media_graph(&dvb->adapter, |
556 | dev->tuner_type == TUNER_ABSENT); | |
0d3ab841 MCC |
557 | if (result < 0) |
558 | goto fail_create_graph; | |
480884b6 | 559 | |
e0d3bafd SD |
560 | return 0; |
561 | ||
0d3ab841 MCC |
562 | fail_create_graph: |
563 | dvb_net_release(&dvb->net); | |
b9255176 | 564 | fail_fe_conn: |
e0d3bafd | 565 | dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); |
b9255176 | 566 | fail_fe_mem: |
e0d3bafd | 567 | dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); |
b9255176 | 568 | fail_fe_hw: |
e0d3bafd | 569 | dvb_dmxdev_release(&dvb->dmxdev); |
b9255176 | 570 | fail_dmxdev: |
e0d3bafd | 571 | dvb_dmx_release(&dvb->demux); |
b9255176 | 572 | fail_dmx: |
e0d3bafd | 573 | dvb_unregister_frontend(dvb->frontend); |
b9255176 | 574 | fail_frontend: |
e0d3bafd SD |
575 | dvb_frontend_detach(dvb->frontend); |
576 | dvb_unregister_adapter(&dvb->adapter); | |
b9255176 | 577 | fail_adapter: |
e0d3bafd SD |
578 | return result; |
579 | } | |
580 | ||
581 | static void unregister_dvb(struct cx231xx_dvb *dvb) | |
582 | { | |
6d3debaf | 583 | struct i2c_client *client; |
e0d3bafd SD |
584 | dvb_net_release(&dvb->net); |
585 | dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem); | |
586 | dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw); | |
587 | dvb_dmxdev_release(&dvb->dmxdev); | |
588 | dvb_dmx_release(&dvb->demux); | |
6d3debaf MS |
589 | client = dvb->i2c_client_tuner; |
590 | /* remove I2C tuner */ | |
591 | if (client) { | |
592 | module_put(client->dev.driver->owner); | |
593 | i2c_unregister_device(client); | |
594 | } | |
e0d3bafd SD |
595 | dvb_unregister_frontend(dvb->frontend); |
596 | dvb_frontend_detach(dvb->frontend); | |
597 | dvb_unregister_adapter(&dvb->adapter); | |
598 | } | |
599 | ||
e0d3bafd SD |
600 | static int dvb_init(struct cx231xx *dev) |
601 | { | |
602 | int result = 0; | |
603 | struct cx231xx_dvb *dvb; | |
c3c3f1ae MS |
604 | struct i2c_adapter *tuner_i2c; |
605 | struct i2c_adapter *demod_i2c; | |
e0d3bafd SD |
606 | |
607 | if (!dev->board.has_dvb) { | |
608 | /* This device does not support the extension */ | |
609 | return 0; | |
610 | } | |
611 | ||
612 | dvb = kzalloc(sizeof(struct cx231xx_dvb), GFP_KERNEL); | |
613 | ||
614 | if (dvb == NULL) { | |
336fea92 | 615 | dev_info(dev->dev, |
3b795d01 | 616 | "cx231xx_dvb: memory allocation failed\n"); |
e0d3bafd SD |
617 | return -ENOMEM; |
618 | } | |
619 | dev->dvb = dvb; | |
620 | dev->cx231xx_set_analog_freq = cx231xx_set_analog_freq; | |
84b5dbf3 | 621 | dev->cx231xx_reset_analog_tuner = cx231xx_reset_analog_tuner; |
e0d3bafd | 622 | |
c3c3f1ae MS |
623 | tuner_i2c = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master); |
624 | demod_i2c = cx231xx_get_i2c_adap(dev, dev->board.demod_i2c_master); | |
761f6cf6 | 625 | mutex_lock(&dev->lock); |
e0d3bafd | 626 | cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE); |
64fbf444 | 627 | cx231xx_demod_reset(dev); |
e0d3bafd SD |
628 | /* init frontend */ |
629 | switch (dev->model) { | |
64fbf444 | 630 | case CX231XX_BOARD_CNXT_CARRAERA: |
84b5dbf3 MCC |
631 | case CX231XX_BOARD_CNXT_RDE_250: |
632 | ||
64fbf444 PB |
633 | dev->dvb->frontend = dvb_attach(s5h1432_attach, |
634 | &dvico_s5h1432_config, | |
c3c3f1ae | 635 | demod_i2c); |
84b5dbf3 MCC |
636 | |
637 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 638 | dev_err(dev->dev, |
3b795d01 | 639 | "Failed to attach s5h1432 front end\n"); |
84b5dbf3 MCC |
640 | result = -EINVAL; |
641 | goto out_free; | |
642 | } | |
643 | ||
644 | /* define general-purpose callback pointer */ | |
645 | dvb->frontend->callback = cx231xx_tuner_callback; | |
646 | ||
d5abcc78 | 647 | if (!dvb_attach(xc5000_attach, dev->dvb->frontend, |
c3c3f1ae | 648 | tuner_i2c, |
d5abcc78 | 649 | &cnxt_rde250_tunerconfig)) { |
84b5dbf3 MCC |
650 | result = -EINVAL; |
651 | goto out_free; | |
652 | } | |
653 | ||
654 | break; | |
64fbf444 | 655 | case CX231XX_BOARD_CNXT_SHELBY: |
84b5dbf3 MCC |
656 | case CX231XX_BOARD_CNXT_RDU_250: |
657 | ||
64fbf444 PB |
658 | dev->dvb->frontend = dvb_attach(s5h1411_attach, |
659 | &xc5000_s5h1411_config, | |
c3c3f1ae | 660 | demod_i2c); |
84b5dbf3 MCC |
661 | |
662 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 663 | dev_err(dev->dev, |
3b795d01 | 664 | "Failed to attach s5h1411 front end\n"); |
84b5dbf3 MCC |
665 | result = -EINVAL; |
666 | goto out_free; | |
667 | } | |
668 | ||
669 | /* define general-purpose callback pointer */ | |
670 | dvb->frontend->callback = cx231xx_tuner_callback; | |
671 | ||
d5abcc78 | 672 | if (!dvb_attach(xc5000_attach, dev->dvb->frontend, |
c3c3f1ae | 673 | tuner_i2c, |
64fbf444 PB |
674 | &cnxt_rdu250_tunerconfig)) { |
675 | result = -EINVAL; | |
676 | goto out_free; | |
677 | } | |
678 | break; | |
679 | case CX231XX_BOARD_CNXT_RDE_253S: | |
680 | ||
681 | dev->dvb->frontend = dvb_attach(s5h1432_attach, | |
682 | &dvico_s5h1432_config, | |
c3c3f1ae | 683 | demod_i2c); |
64fbf444 PB |
684 | |
685 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 686 | dev_err(dev->dev, |
3b795d01 | 687 | "Failed to attach s5h1432 front end\n"); |
64fbf444 PB |
688 | result = -EINVAL; |
689 | goto out_free; | |
690 | } | |
691 | ||
692 | /* define general-purpose callback pointer */ | |
693 | dvb->frontend->callback = cx231xx_tuner_callback; | |
694 | ||
695 | if (!dvb_attach(tda18271_attach, dev->dvb->frontend, | |
c3c3f1ae | 696 | 0x60, tuner_i2c, |
64fbf444 PB |
697 | &cnxt_rde253s_tunerconfig)) { |
698 | result = -EINVAL; | |
699 | goto out_free; | |
700 | } | |
701 | break; | |
702 | case CX231XX_BOARD_CNXT_RDU_253S: | |
8b1255a2 | 703 | case CX231XX_BOARD_KWORLD_UB445_USB_HYBRID: |
64fbf444 PB |
704 | |
705 | dev->dvb->frontend = dvb_attach(s5h1411_attach, | |
706 | &tda18271_s5h1411_config, | |
c3c3f1ae | 707 | demod_i2c); |
64fbf444 PB |
708 | |
709 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 710 | dev_err(dev->dev, |
3b795d01 | 711 | "Failed to attach s5h1411 front end\n"); |
64fbf444 PB |
712 | result = -EINVAL; |
713 | goto out_free; | |
714 | } | |
715 | ||
716 | /* define general-purpose callback pointer */ | |
717 | dvb->frontend->callback = cx231xx_tuner_callback; | |
718 | ||
719 | if (!dvb_attach(tda18271_attach, dev->dvb->frontend, | |
c3c3f1ae | 720 | 0x60, tuner_i2c, |
64fbf444 | 721 | &cnxt_rde253s_tunerconfig)) { |
84b5dbf3 MCC |
722 | result = -EINVAL; |
723 | goto out_free; | |
724 | } | |
725 | break; | |
1a50fdde MK |
726 | case CX231XX_BOARD_HAUPPAUGE_EXETER: |
727 | ||
336fea92 | 728 | dev_info(dev->dev, |
3b795d01 | 729 | "%s: looking for tuner / demod on i2c bus: %d\n", |
c3c3f1ae | 730 | __func__, i2c_adapter_id(tuner_i2c)); |
1a50fdde MK |
731 | |
732 | dev->dvb->frontend = dvb_attach(lgdt3305_attach, | |
733 | &hcw_lgdt3305_config, | |
599bedb7 | 734 | demod_i2c); |
1a50fdde MK |
735 | |
736 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 737 | dev_err(dev->dev, |
3b795d01 | 738 | "Failed to attach LG3305 front end\n"); |
1a50fdde MK |
739 | result = -EINVAL; |
740 | goto out_free; | |
741 | } | |
742 | ||
743 | /* define general-purpose callback pointer */ | |
744 | dvb->frontend->callback = cx231xx_tuner_callback; | |
745 | ||
746 | dvb_attach(tda18271_attach, dev->dvb->frontend, | |
c3c3f1ae | 747 | 0x60, tuner_i2c, |
1a50fdde MK |
748 | &hcw_tda18271_config); |
749 | break; | |
750 | ||
dd2e7dd2 MS |
751 | case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx: |
752 | ||
753 | dev->dvb->frontend = dvb_attach(si2165_attach, | |
754 | &hauppauge_930C_HD_1113xx_si2165_config, | |
599bedb7 | 755 | demod_i2c |
dd2e7dd2 MS |
756 | ); |
757 | ||
758 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 759 | dev_err(dev->dev, |
3b795d01 | 760 | "Failed to attach SI2165 front end\n"); |
dd2e7dd2 MS |
761 | result = -EINVAL; |
762 | goto out_free; | |
763 | } | |
764 | ||
3f9280a8 | 765 | dev->dvb->frontend->ops.i2c_gate_ctrl = NULL; |
dd2e7dd2 MS |
766 | |
767 | /* define general-purpose callback pointer */ | |
768 | dvb->frontend->callback = cx231xx_tuner_callback; | |
769 | ||
770 | dvb_attach(tda18271_attach, dev->dvb->frontend, | |
771 | 0x60, | |
c3c3f1ae | 772 | tuner_i2c, |
dd2e7dd2 MS |
773 | &hcw_tda18271_config); |
774 | ||
775 | dev->cx231xx_reset_analog_tuner = NULL; | |
776 | break; | |
777 | ||
9e49f7c3 MS |
778 | case CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx: |
779 | { | |
780 | struct i2c_client *client; | |
781 | struct i2c_board_info info; | |
782 | struct si2157_config si2157_config; | |
783 | ||
784 | memset(&info, 0, sizeof(struct i2c_board_info)); | |
785 | ||
786 | dev->dvb->frontend = dvb_attach(si2165_attach, | |
787 | &pctv_quatro_stick_1114xx_si2165_config, | |
599bedb7 | 788 | demod_i2c |
9e49f7c3 MS |
789 | ); |
790 | ||
791 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 792 | dev_err(dev->dev, |
3b795d01 | 793 | "Failed to attach SI2165 front end\n"); |
9e49f7c3 MS |
794 | result = -EINVAL; |
795 | goto out_free; | |
796 | } | |
797 | ||
3f9280a8 | 798 | dev->dvb->frontend->ops.i2c_gate_ctrl = NULL; |
9e49f7c3 MS |
799 | |
800 | /* define general-purpose callback pointer */ | |
801 | dvb->frontend->callback = cx231xx_tuner_callback; | |
802 | ||
803 | /* attach tuner */ | |
804 | memset(&si2157_config, 0, sizeof(si2157_config)); | |
805 | si2157_config.fe = dev->dvb->frontend; | |
133bc4e4 MCC |
806 | #ifdef CONFIG_MEDIA_CONTROLLER_DVB |
807 | si2157_config.mdev = dev->media_dev; | |
808 | #endif | |
ee3c3e46 | 809 | si2157_config.if_port = 1; |
9e49f7c3 MS |
810 | si2157_config.inversion = true; |
811 | strlcpy(info.type, "si2157", I2C_NAME_SIZE); | |
812 | info.addr = 0x60; | |
813 | info.platform_data = &si2157_config; | |
814 | request_module("si2157"); | |
815 | ||
816 | client = i2c_new_device( | |
c3c3f1ae | 817 | tuner_i2c, |
9e49f7c3 MS |
818 | &info); |
819 | if (client == NULL || client->dev.driver == NULL) { | |
820 | dvb_frontend_detach(dev->dvb->frontend); | |
821 | result = -ENODEV; | |
822 | goto out_free; | |
823 | } | |
824 | ||
825 | if (!try_module_get(client->dev.driver->owner)) { | |
826 | i2c_unregister_device(client); | |
827 | dvb_frontend_detach(dev->dvb->frontend); | |
828 | result = -ENODEV; | |
829 | goto out_free; | |
830 | } | |
831 | ||
832 | dev->cx231xx_reset_analog_tuner = NULL; | |
833 | ||
834 | dev->dvb->i2c_client_tuner = client; | |
835 | break; | |
836 | } | |
809abdbf OS |
837 | case CX231XX_BOARD_HAUPPAUGE_955Q: |
838 | { | |
839 | struct i2c_client *client; | |
840 | struct i2c_board_info info; | |
841 | struct si2157_config si2157_config; | |
842 | ||
843 | memset(&info, 0, sizeof(struct i2c_board_info)); | |
844 | ||
845 | dev->dvb->frontend = dvb_attach(lgdt3306a_attach, | |
846 | &hauppauge_955q_lgdt3306a_config, | |
599bedb7 | 847 | demod_i2c |
809abdbf OS |
848 | ); |
849 | ||
850 | if (dev->dvb->frontend == NULL) { | |
851 | dev_err(dev->dev, | |
852 | "Failed to attach LGDT3306A frontend.\n"); | |
853 | result = -EINVAL; | |
854 | goto out_free; | |
855 | } | |
856 | ||
857 | dev->dvb->frontend->ops.i2c_gate_ctrl = NULL; | |
858 | ||
859 | /* define general-purpose callback pointer */ | |
860 | dvb->frontend->callback = cx231xx_tuner_callback; | |
861 | ||
862 | /* attach tuner */ | |
863 | memset(&si2157_config, 0, sizeof(si2157_config)); | |
864 | si2157_config.fe = dev->dvb->frontend; | |
133bc4e4 MCC |
865 | #ifdef CONFIG_MEDIA_CONTROLLER_DVB |
866 | si2157_config.mdev = dev->media_dev; | |
867 | #endif | |
ee3c3e46 | 868 | si2157_config.if_port = 1; |
809abdbf OS |
869 | si2157_config.inversion = true; |
870 | strlcpy(info.type, "si2157", I2C_NAME_SIZE); | |
871 | info.addr = 0x60; | |
872 | info.platform_data = &si2157_config; | |
873 | request_module("si2157"); | |
874 | ||
875 | client = i2c_new_device( | |
876 | tuner_i2c, | |
877 | &info); | |
878 | if (client == NULL || client->dev.driver == NULL) { | |
879 | dvb_frontend_detach(dev->dvb->frontend); | |
880 | result = -ENODEV; | |
881 | goto out_free; | |
882 | } | |
883 | ||
884 | if (!try_module_get(client->dev.driver->owner)) { | |
885 | i2c_unregister_device(client); | |
886 | dvb_frontend_detach(dev->dvb->frontend); | |
887 | result = -ENODEV; | |
888 | goto out_free; | |
889 | } | |
890 | ||
891 | dev->cx231xx_reset_analog_tuner = NULL; | |
9e49f7c3 | 892 | |
809abdbf OS |
893 | dev->dvb->i2c_client_tuner = client; |
894 | break; | |
895 | } | |
ede676c7 | 896 | case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID: |
eeaaf817 | 897 | case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID: |
ede676c7 | 898 | |
336fea92 | 899 | dev_info(dev->dev, |
3b795d01 MCC |
900 | "%s: looking for demod on i2c bus: %d\n", |
901 | __func__, i2c_adapter_id(tuner_i2c)); | |
ede676c7 MCC |
902 | |
903 | dev->dvb->frontend = dvb_attach(mb86a20s_attach, | |
904 | &pv_mb86a20s_config, | |
c3c3f1ae | 905 | demod_i2c); |
ede676c7 MCC |
906 | |
907 | if (dev->dvb->frontend == NULL) { | |
336fea92 | 908 | dev_err(dev->dev, |
3b795d01 | 909 | "Failed to attach mb86a20s demod\n"); |
ede676c7 MCC |
910 | result = -EINVAL; |
911 | goto out_free; | |
912 | } | |
913 | ||
914 | /* define general-purpose callback pointer */ | |
915 | dvb->frontend->callback = cx231xx_tuner_callback; | |
916 | ||
917 | dvb_attach(tda18271_attach, dev->dvb->frontend, | |
c3c3f1ae | 918 | 0x60, tuner_i2c, |
ede676c7 MCC |
919 | &pv_tda18271_config); |
920 | break; | |
e0d3bafd SD |
921 | |
922 | default: | |
336fea92 | 923 | dev_err(dev->dev, |
3b795d01 MCC |
924 | "%s/2: The frontend of your DVB/ATSC card isn't supported yet\n", |
925 | dev->name); | |
e0d3bafd SD |
926 | break; |
927 | } | |
928 | if (NULL == dvb->frontend) { | |
336fea92 | 929 | dev_err(dev->dev, |
84b5dbf3 | 930 | "%s/2: frontend initialization failed\n", dev->name); |
e0d3bafd SD |
931 | result = -EINVAL; |
932 | goto out_free; | |
933 | } | |
934 | ||
e0d3bafd | 935 | /* register everything */ |
336fea92 | 936 | result = register_dvb(dvb, THIS_MODULE, dev, dev->dev); |
e0d3bafd SD |
937 | |
938 | if (result < 0) | |
939 | goto out_free; | |
940 | ||
761f6cf6 | 941 | |
336fea92 | 942 | dev_info(dev->dev, "Successfully loaded cx231xx-dvb\n"); |
e0d3bafd | 943 | |
761f6cf6 | 944 | ret: |
e0d3bafd | 945 | cx231xx_set_mode(dev, CX231XX_SUSPEND); |
761f6cf6 DH |
946 | mutex_unlock(&dev->lock); |
947 | return result; | |
948 | ||
949 | out_free: | |
e0d3bafd SD |
950 | kfree(dvb); |
951 | dev->dvb = NULL; | |
761f6cf6 | 952 | goto ret; |
e0d3bafd SD |
953 | } |
954 | ||
955 | static int dvb_fini(struct cx231xx *dev) | |
956 | { | |
957 | if (!dev->board.has_dvb) { | |
958 | /* This device does not support the extension */ | |
959 | return 0; | |
960 | } | |
961 | ||
962 | if (dev->dvb) { | |
963 | unregister_dvb(dev->dvb); | |
964 | dev->dvb = NULL; | |
965 | } | |
966 | ||
967 | return 0; | |
968 | } | |
969 | ||
970 | static struct cx231xx_ops dvb_ops = { | |
84b5dbf3 | 971 | .id = CX231XX_DVB, |
e0d3bafd SD |
972 | .name = "Cx231xx dvb Extension", |
973 | .init = dvb_init, | |
974 | .fini = dvb_fini, | |
975 | }; | |
976 | ||
977 | static int __init cx231xx_dvb_register(void) | |
978 | { | |
979 | return cx231xx_register_extension(&dvb_ops); | |
980 | } | |
981 | ||
982 | static void __exit cx231xx_dvb_unregister(void) | |
983 | { | |
984 | cx231xx_unregister_extension(&dvb_ops); | |
985 | } | |
986 | ||
987 | module_init(cx231xx_dvb_register); | |
988 | module_exit(cx231xx_dvb_unregister); |