Path: blob/master/drivers/media/video/cx231xx/cx231xx-dvb.c
17825 views
/*1DVB device driver for cx231xx23Copyright (C) 2008 <srinivasa.deevi at conexant dot com>4Based on em28xx driver56This program is free software; you can redistribute it and/or modify7it under the terms of the GNU General Public License as published by8the Free Software Foundation; either version 2 of the License, or9(at your option) any later version.1011This program is distributed in the hope that it will be useful,12but WITHOUT ANY WARRANTY; without even the implied warranty of13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14GNU General Public License for more details.1516You should have received a copy of the GNU General Public License17along with this program; if not, write to the Free Software18Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.19*/2021#include <linux/kernel.h>22#include <linux/slab.h>23#include <linux/usb.h>2425#include "cx231xx.h"26#include <media/v4l2-common.h>27#include <media/videobuf-vmalloc.h>2829#include "xc5000.h"30#include "s5h1432.h"31#include "tda18271.h"32#include "s5h1411.h"33#include "lgdt3305.h"34#include "mb86a20s.h"3536MODULE_DESCRIPTION("driver for cx231xx based DVB cards");37MODULE_AUTHOR("Srinivasa Deevi <[email protected]>");38MODULE_LICENSE("GPL");3940static unsigned int debug;41module_param(debug, int, 0644);42MODULE_PARM_DESC(debug, "enable debug messages [dvb]");4344DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);4546#define dprintk(level, fmt, arg...) do { \47if (debug >= level) \48printk(KERN_DEBUG "%s/2-dvb: " fmt, dev->name, ## arg); \49} while (0)5051#define CX231XX_DVB_NUM_BUFS 552#define CX231XX_DVB_MAX_PACKETSIZE 56453#define CX231XX_DVB_MAX_PACKETS 645455struct cx231xx_dvb {56struct dvb_frontend *frontend;5758/* feed count management */59struct mutex lock;60int nfeeds;6162/* general boilerplate stuff */63struct dvb_adapter adapter;64struct dvb_demux demux;65struct dmxdev dmxdev;66struct dmx_frontend fe_hw;67struct dmx_frontend fe_mem;68struct dvb_net net;69};7071static 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};8081static 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};8990static struct tda18271_std_map mb86a20s_tda18271_config = {91.dvbt_6 = { .if_freq = 3300, .agc_mode = 3, .std = 4,92.if_lvl = 7, .rfagc_top = 0x37, },93};9495static struct tda18271_config cnxt_rde253s_tunerconfig = {96.std_map = &cnxt_rde253s_tda18271_std_map,97.gate = TDA18271_GATE_ANALOG,98};99100static 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};109static 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};118119static 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};129130static 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};136137static struct tda18271_config hcw_tda18271_config = {138.std_map = &hauppauge_tda18271_std_map,139.gate = TDA18271_GATE_DIGITAL,140};141142static const struct mb86a20s_config pv_mb86a20s_config = {143.demod_address = 0x10,144.is_serial = true,145};146147static 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};152153static inline void print_err_status(struct cx231xx *dev, int packet, int status)154{155char *errmsg = "Unknown";156157switch (status) {158case -ENOENT:159errmsg = "unlinked synchronuously";160break;161case -ECONNRESET:162errmsg = "unlinked asynchronuously";163break;164case -ENOSR:165errmsg = "Buffer error (overrun)";166break;167case -EPIPE:168errmsg = "Stalled (device not responding)";169break;170case -EOVERFLOW:171errmsg = "Babble (bad cable?)";172break;173case -EPROTO:174errmsg = "Bit-stuff error (bad cable?)";175break;176case -EILSEQ:177errmsg = "CRC/Timeout (could be anything)";178break;179case -ETIME:180errmsg = "Device does not respond";181break;182}183if (packet < 0) {184dprintk(1, "URB status %d [%s].\n", status, errmsg);185} else {186dprintk(1, "URB packet %d, status %d [%s].\n",187packet, status, errmsg);188}189}190191static inline int dvb_isoc_copy(struct cx231xx *dev, struct urb *urb)192{193int i;194195if (!dev)196return 0;197198if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))199return 0;200201if (urb->status < 0) {202print_err_status(dev, -1, urb->status);203if (urb->status == -ENOENT)204return 0;205}206207for (i = 0; i < urb->number_of_packets; i++) {208int status = urb->iso_frame_desc[i].status;209210if (status < 0) {211print_err_status(dev, i, status);212if (urb->iso_frame_desc[i].status != -EPROTO)213continue;214}215216dvb_dmx_swfilter(&dev->dvb->demux,217urb->transfer_buffer +218urb->iso_frame_desc[i].offset,219urb->iso_frame_desc[i].actual_length);220}221222return 0;223}224225static inline int dvb_bulk_copy(struct cx231xx *dev, struct urb *urb)226{227if (!dev)228return 0;229230if ((dev->state & DEV_DISCONNECTED) || (dev->state & DEV_MISCONFIGURED))231return 0;232233if (urb->status < 0) {234print_err_status(dev, -1, urb->status);235if (urb->status == -ENOENT)236return 0;237}238239/* Feed the transport payload into the kernel demux */240dvb_dmx_swfilter(&dev->dvb->demux,241urb->transfer_buffer, urb->actual_length);242243return 0;244}245246static int start_streaming(struct cx231xx_dvb *dvb)247{248int rc;249struct cx231xx *dev = dvb->adapter.priv;250251if (dev->USE_ISO) {252cx231xx_info("DVB transfer mode is ISO.\n");253mutex_lock(&dev->i2c_lock);254cx231xx_enable_i2c_port_3(dev, false);255cx231xx_set_alt_setting(dev, INDEX_TS1, 4);256cx231xx_enable_i2c_port_3(dev, true);257mutex_unlock(&dev->i2c_lock);258rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);259if (rc < 0)260return rc;261dev->mode_tv = 1;262return cx231xx_init_isoc(dev, CX231XX_DVB_MAX_PACKETS,263CX231XX_DVB_NUM_BUFS,264dev->ts1_mode.max_pkt_size,265dvb_isoc_copy);266} else {267cx231xx_info("DVB transfer mode is BULK.\n");268cx231xx_set_alt_setting(dev, INDEX_TS1, 0);269rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);270if (rc < 0)271return rc;272dev->mode_tv = 1;273return cx231xx_init_bulk(dev, CX231XX_DVB_MAX_PACKETS,274CX231XX_DVB_NUM_BUFS,275dev->ts1_mode.max_pkt_size,276dvb_bulk_copy);277}278279}280281static int stop_streaming(struct cx231xx_dvb *dvb)282{283struct cx231xx *dev = dvb->adapter.priv;284285if (dev->USE_ISO)286cx231xx_uninit_isoc(dev);287else288cx231xx_uninit_bulk(dev);289290cx231xx_set_mode(dev, CX231XX_SUSPEND);291292return 0;293}294295static int start_feed(struct dvb_demux_feed *feed)296{297struct dvb_demux *demux = feed->demux;298struct cx231xx_dvb *dvb = demux->priv;299int rc, ret;300301if (!demux->dmx.frontend)302return -EINVAL;303304mutex_lock(&dvb->lock);305dvb->nfeeds++;306rc = dvb->nfeeds;307308if (dvb->nfeeds == 1) {309ret = start_streaming(dvb);310if (ret < 0)311rc = ret;312}313314mutex_unlock(&dvb->lock);315return rc;316}317318static int stop_feed(struct dvb_demux_feed *feed)319{320struct dvb_demux *demux = feed->demux;321struct cx231xx_dvb *dvb = demux->priv;322int err = 0;323324mutex_lock(&dvb->lock);325dvb->nfeeds--;326327if (0 == dvb->nfeeds)328err = stop_streaming(dvb);329330mutex_unlock(&dvb->lock);331return err;332}333334/* ------------------------------------------------------------------ */335static int cx231xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)336{337struct cx231xx *dev = fe->dvb->priv;338339if (acquire)340return cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);341else342return cx231xx_set_mode(dev, CX231XX_SUSPEND);343}344345/* ------------------------------------------------------------------ */346347static struct xc5000_config cnxt_rde250_tunerconfig = {348.i2c_address = 0x61,349.if_khz = 4000,350};351static struct xc5000_config cnxt_rdu250_tunerconfig = {352.i2c_address = 0x61,353.if_khz = 3250,354};355356/* ------------------------------------------------------------------ */357#if 0358static int attach_xc5000(u8 addr, struct cx231xx *dev)359{360361struct dvb_frontend *fe;362struct xc5000_config cfg;363364memset(&cfg, 0, sizeof(cfg));365cfg.i2c_adap = &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap;366cfg.i2c_addr = addr;367368if (!dev->dvb->frontend) {369printk(KERN_ERR "%s/2: dvb frontend not attached. "370"Can't attach xc5000\n", dev->name);371return -EINVAL;372}373374fe = dvb_attach(xc5000_attach, dev->dvb->frontend, &cfg);375if (!fe) {376printk(KERN_ERR "%s/2: xc5000 attach failed\n", dev->name);377dvb_frontend_detach(dev->dvb->frontend);378dev->dvb->frontend = NULL;379return -EINVAL;380}381382printk(KERN_INFO "%s/2: xc5000 attached\n", dev->name);383384return 0;385}386#endif387388int cx231xx_set_analog_freq(struct cx231xx *dev, u32 freq)389{390int status = 0;391392if ((dev->dvb != NULL) && (dev->dvb->frontend != NULL)) {393394struct dvb_tuner_ops *dops = &dev->dvb->frontend->ops.tuner_ops;395396if (dops->set_analog_params != NULL) {397struct analog_parameters params;398399params.frequency = freq;400params.std = dev->norm;401params.mode = 0; /* 0- Air; 1 - cable */402/*params.audmode = ; */403404/* Set the analog parameters to set the frequency */405dops->set_analog_params(dev->dvb->frontend, ¶ms);406}407408}409410return status;411}412413int cx231xx_reset_analog_tuner(struct cx231xx *dev)414{415int status = 0;416417if ((dev->dvb != NULL) && (dev->dvb->frontend != NULL)) {418419struct dvb_tuner_ops *dops = &dev->dvb->frontend->ops.tuner_ops;420421if (dops->init != NULL && !dev->xc_fw_load_done) {422423cx231xx_info("Reloading firmware for XC5000\n");424status = dops->init(dev->dvb->frontend);425if (status == 0) {426dev->xc_fw_load_done = 1;427cx231xx_info428("XC5000 firmware download completed\n");429} else {430dev->xc_fw_load_done = 0;431cx231xx_info432("XC5000 firmware download failed !!!\n");433}434}435436}437438return status;439}440441/* ------------------------------------------------------------------ */442443static int register_dvb(struct cx231xx_dvb *dvb,444struct module *module,445struct cx231xx *dev, struct device *device)446{447int result;448449mutex_init(&dvb->lock);450451/* register adapter */452result = dvb_register_adapter(&dvb->adapter, dev->name, module, device,453adapter_nr);454if (result < 0) {455printk(KERN_WARNING456"%s: dvb_register_adapter failed (errno = %d)\n",457dev->name, result);458goto fail_adapter;459}460461/* Ensure all frontends negotiate bus access */462dvb->frontend->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl;463464dvb->adapter.priv = dev;465466/* register frontend */467result = dvb_register_frontend(&dvb->adapter, dvb->frontend);468if (result < 0) {469printk(KERN_WARNING470"%s: dvb_register_frontend failed (errno = %d)\n",471dev->name, result);472goto fail_frontend;473}474475/* register demux stuff */476dvb->demux.dmx.capabilities =477DMX_TS_FILTERING | DMX_SECTION_FILTERING |478DMX_MEMORY_BASED_FILTERING;479dvb->demux.priv = dvb;480dvb->demux.filternum = 256;481dvb->demux.feednum = 256;482dvb->demux.start_feed = start_feed;483dvb->demux.stop_feed = stop_feed;484485result = dvb_dmx_init(&dvb->demux);486if (result < 0) {487printk(KERN_WARNING "%s: dvb_dmx_init failed (errno = %d)\n",488dev->name, result);489goto fail_dmx;490}491492dvb->dmxdev.filternum = 256;493dvb->dmxdev.demux = &dvb->demux.dmx;494dvb->dmxdev.capabilities = 0;495result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);496if (result < 0) {497printk(KERN_WARNING "%s: dvb_dmxdev_init failed (errno = %d)\n",498dev->name, result);499goto fail_dmxdev;500}501502dvb->fe_hw.source = DMX_FRONTEND_0;503result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);504if (result < 0) {505printk(KERN_WARNING506"%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",507dev->name, result);508goto fail_fe_hw;509}510511dvb->fe_mem.source = DMX_MEMORY_FE;512result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);513if (result < 0) {514printk(KERN_WARNING515"%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",516dev->name, result);517goto fail_fe_mem;518}519520result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);521if (result < 0) {522printk(KERN_WARNING523"%s: connect_frontend failed (errno = %d)\n", dev->name,524result);525goto fail_fe_conn;526}527528/* register network adapter */529dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);530return 0;531532fail_fe_conn:533dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);534fail_fe_mem:535dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);536fail_fe_hw:537dvb_dmxdev_release(&dvb->dmxdev);538fail_dmxdev:539dvb_dmx_release(&dvb->demux);540fail_dmx:541dvb_unregister_frontend(dvb->frontend);542fail_frontend:543dvb_frontend_detach(dvb->frontend);544dvb_unregister_adapter(&dvb->adapter);545fail_adapter:546return result;547}548549static void unregister_dvb(struct cx231xx_dvb *dvb)550{551dvb_net_release(&dvb->net);552dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);553dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);554dvb_dmxdev_release(&dvb->dmxdev);555dvb_dmx_release(&dvb->demux);556dvb_unregister_frontend(dvb->frontend);557dvb_frontend_detach(dvb->frontend);558dvb_unregister_adapter(&dvb->adapter);559}560561static int dvb_init(struct cx231xx *dev)562{563int result = 0;564struct cx231xx_dvb *dvb;565566if (!dev->board.has_dvb) {567/* This device does not support the extension */568return 0;569}570571dvb = kzalloc(sizeof(struct cx231xx_dvb), GFP_KERNEL);572573if (dvb == NULL) {574printk(KERN_INFO "cx231xx_dvb: memory allocation failed\n");575return -ENOMEM;576}577dev->dvb = dvb;578dev->cx231xx_set_analog_freq = cx231xx_set_analog_freq;579dev->cx231xx_reset_analog_tuner = cx231xx_reset_analog_tuner;580581mutex_lock(&dev->lock);582cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);583cx231xx_demod_reset(dev);584/* init frontend */585switch (dev->model) {586case CX231XX_BOARD_CNXT_CARRAERA:587case CX231XX_BOARD_CNXT_RDE_250:588589dev->dvb->frontend = dvb_attach(s5h1432_attach,590&dvico_s5h1432_config,591&dev->i2c_bus[dev->board.demod_i2c_master].i2c_adap);592593if (dev->dvb->frontend == NULL) {594printk(DRIVER_NAME595": Failed to attach s5h1432 front end\n");596result = -EINVAL;597goto out_free;598}599600/* define general-purpose callback pointer */601dvb->frontend->callback = cx231xx_tuner_callback;602603if (!dvb_attach(xc5000_attach, dev->dvb->frontend,604&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,605&cnxt_rde250_tunerconfig)) {606result = -EINVAL;607goto out_free;608}609610break;611case CX231XX_BOARD_CNXT_SHELBY:612case CX231XX_BOARD_CNXT_RDU_250:613614dev->dvb->frontend = dvb_attach(s5h1411_attach,615&xc5000_s5h1411_config,616&dev->i2c_bus[dev->board.demod_i2c_master].i2c_adap);617618if (dev->dvb->frontend == NULL) {619printk(DRIVER_NAME620": Failed to attach s5h1411 front end\n");621result = -EINVAL;622goto out_free;623}624625/* define general-purpose callback pointer */626dvb->frontend->callback = cx231xx_tuner_callback;627628if (!dvb_attach(xc5000_attach, dev->dvb->frontend,629&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,630&cnxt_rdu250_tunerconfig)) {631result = -EINVAL;632goto out_free;633}634break;635case CX231XX_BOARD_CNXT_RDE_253S:636637dev->dvb->frontend = dvb_attach(s5h1432_attach,638&dvico_s5h1432_config,639&dev->i2c_bus[dev->board.demod_i2c_master].i2c_adap);640641if (dev->dvb->frontend == NULL) {642printk(DRIVER_NAME643": Failed to attach s5h1432 front end\n");644result = -EINVAL;645goto out_free;646}647648/* define general-purpose callback pointer */649dvb->frontend->callback = cx231xx_tuner_callback;650651if (!dvb_attach(tda18271_attach, dev->dvb->frontend,6520x60, &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,653&cnxt_rde253s_tunerconfig)) {654result = -EINVAL;655goto out_free;656}657break;658case CX231XX_BOARD_CNXT_RDU_253S:659660dev->dvb->frontend = dvb_attach(s5h1411_attach,661&tda18271_s5h1411_config,662&dev->i2c_bus[dev->board.demod_i2c_master].i2c_adap);663664if (dev->dvb->frontend == NULL) {665printk(DRIVER_NAME666": Failed to attach s5h1411 front end\n");667result = -EINVAL;668goto out_free;669}670671/* define general-purpose callback pointer */672dvb->frontend->callback = cx231xx_tuner_callback;673674if (!dvb_attach(tda18271_attach, dev->dvb->frontend,6750x60, &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,676&cnxt_rde253s_tunerconfig)) {677result = -EINVAL;678goto out_free;679}680break;681case CX231XX_BOARD_HAUPPAUGE_EXETER:682683printk(KERN_INFO "%s: looking for tuner / demod on i2c bus: %d\n",684__func__, i2c_adapter_id(&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap));685686dev->dvb->frontend = dvb_attach(lgdt3305_attach,687&hcw_lgdt3305_config,688&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap);689690if (dev->dvb->frontend == NULL) {691printk(DRIVER_NAME692": Failed to attach LG3305 front end\n");693result = -EINVAL;694goto out_free;695}696697/* define general-purpose callback pointer */698dvb->frontend->callback = cx231xx_tuner_callback;699700dvb_attach(tda18271_attach, dev->dvb->frontend,7010x60, &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,702&hcw_tda18271_config);703break;704705case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:706case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID:707708printk(KERN_INFO "%s: looking for demod on i2c bus: %d\n",709__func__, i2c_adapter_id(&dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap));710711dev->dvb->frontend = dvb_attach(mb86a20s_attach,712&pv_mb86a20s_config,713&dev->i2c_bus[dev->board.demod_i2c_master].i2c_adap);714715if (dev->dvb->frontend == NULL) {716printk(DRIVER_NAME717": Failed to attach mb86a20s demod\n");718result = -EINVAL;719goto out_free;720}721722/* define general-purpose callback pointer */723dvb->frontend->callback = cx231xx_tuner_callback;724725dvb_attach(tda18271_attach, dev->dvb->frontend,7260x60, &dev->i2c_bus[dev->board.tuner_i2c_master].i2c_adap,727&pv_tda18271_config);728break;729730default:731printk(KERN_ERR "%s/2: The frontend of your DVB/ATSC card"732" isn't supported yet\n", dev->name);733break;734}735if (NULL == dvb->frontend) {736printk(KERN_ERR737"%s/2: frontend initialization failed\n", dev->name);738result = -EINVAL;739goto out_free;740}741742/* register everything */743result = register_dvb(dvb, THIS_MODULE, dev, &dev->udev->dev);744745if (result < 0)746goto out_free;747748749printk(KERN_INFO "Successfully loaded cx231xx-dvb\n");750751ret:752cx231xx_set_mode(dev, CX231XX_SUSPEND);753mutex_unlock(&dev->lock);754return result;755756out_free:757kfree(dvb);758dev->dvb = NULL;759goto ret;760}761762static int dvb_fini(struct cx231xx *dev)763{764if (!dev->board.has_dvb) {765/* This device does not support the extension */766return 0;767}768769if (dev->dvb) {770unregister_dvb(dev->dvb);771dev->dvb = NULL;772}773774return 0;775}776777static struct cx231xx_ops dvb_ops = {778.id = CX231XX_DVB,779.name = "Cx231xx dvb Extension",780.init = dvb_init,781.fini = dvb_fini,782};783784static int __init cx231xx_dvb_register(void)785{786return cx231xx_register_extension(&dvb_ops);787}788789static void __exit cx231xx_dvb_unregister(void)790{791cx231xx_unregister_extension(&dvb_ops);792}793794module_init(cx231xx_dvb_register);795module_exit(cx231xx_dvb_unregister);796797798