linux/drivers/media/radio/radio-cadet.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
   3 *
   4 * by Fred Gleason <fredg@wava.com>
   5 * Version 0.3.3
   6 *
   7 * (Loosely) based on code for the Aztech radio card by
   8 *
   9 * Russell Kroll    (rkroll@exploits.org)
  10 * Quay Ly
  11 * Donald Song
  12 * Jason Lewis      (jlewis@twilight.vtc.vsc.edu)
  13 * Scott McGrath    (smcgrath@twilight.vtc.vsc.edu)
  14 * William McGrath  (wmcgrath@twilight.vtc.vsc.edu)
  15 *
  16 * History:
  17 * 2000-04-29   Russell Kroll <rkroll@exploits.org>
  18 *              Added ISAPnP detection for Linux 2.3/2.4
  19 *
  20 * 2001-01-10   Russell Kroll <rkroll@exploits.org>
  21 *              Removed dead CONFIG_RADIO_CADET_PORT code
  22 *              PnP detection on load is now default (no args necessary)
  23 *
  24 * 2002-01-17   Adam Belay <ambx1@neo.rr.com>
  25 *              Updated to latest pnp code
  26 *
  27 * 2003-01-31   Alan Cox <alan@lxorguk.ukuu.org.uk>
  28 *              Cleaned up locking, delay code, general odds and ends
  29 *
  30 * 2006-07-30   Hans J. Koch <koch@hjk-az.de>
  31 *              Changed API to V4L2
  32 */
  33
  34#include <linux/module.h>       /* Modules                      */
  35#include <linux/init.h>         /* Initdata                     */
  36#include <linux/ioport.h>       /* request_region               */
  37#include <linux/delay.h>        /* udelay                       */
  38#include <linux/videodev2.h>    /* V4L2 API defs                */
  39#include <linux/param.h>
  40#include <linux/pnp.h>
  41#include <linux/sched.h>
  42#include <linux/io.h>           /* outb, outb_p                 */
  43#include <media/v4l2-device.h>
  44#include <media/v4l2-ioctl.h>
  45#include <media/v4l2-ctrls.h>
  46#include <media/v4l2-fh.h>
  47#include <media/v4l2-event.h>
  48
  49MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
  50MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
  51MODULE_LICENSE("GPL");
  52MODULE_VERSION("0.3.4");
  53
  54static int io = -1;             /* default to isapnp activation */
  55static int radio_nr = -1;
  56
  57module_param(io, int, 0);
  58MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
  59module_param(radio_nr, int, 0);
  60
  61#define RDS_BUFFER 256
  62#define RDS_RX_FLAG 1
  63#define MBS_RX_FLAG 2
  64
  65struct cadet {
  66        struct v4l2_device v4l2_dev;
  67        struct video_device vdev;
  68        struct v4l2_ctrl_handler ctrl_handler;
  69        int io;
  70        bool is_fm_band;
  71        u32 curfreq;
  72        int tunestat;
  73        int sigstrength;
  74        wait_queue_head_t read_queue;
  75        struct timer_list readtimer;
  76        u8 rdsin, rdsout, rdsstat;
  77        unsigned char rdsbuf[RDS_BUFFER];
  78        struct mutex lock;
  79        int reading;
  80};
  81
  82static struct cadet cadet_card;
  83
  84/*
  85 * Signal Strength Threshold Values
  86 * The V4L API spec does not define any particular unit for the signal
  87 * strength value.  These values are in microvolts of RF at the tuner's input.
  88 */
  89static u16 sigtable[2][4] = {
  90        { 1835, 2621,  4128, 65535 },
  91        { 2185, 4369, 13107, 65535 },
  92};
  93
  94static const struct v4l2_frequency_band bands[] = {
  95        {
  96                .index = 0,
  97                .type = V4L2_TUNER_RADIO,
  98                .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
  99                .rangelow = 8320,      /* 520 kHz */
 100                .rangehigh = 26400,    /* 1650 kHz */
 101                .modulation = V4L2_BAND_MODULATION_AM,
 102        }, {
 103                .index = 1,
 104                .type = V4L2_TUNER_RADIO,
 105                .capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
 106                        V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW |
 107                        V4L2_TUNER_CAP_FREQ_BANDS,
 108                .rangelow = 1400000,   /* 87.5 MHz */
 109                .rangehigh = 1728000,  /* 108.0 MHz */
 110                .modulation = V4L2_BAND_MODULATION_FM,
 111        },
 112};
 113
 114
 115static int cadet_getstereo(struct cadet *dev)
 116{
 117        int ret = V4L2_TUNER_SUB_MONO;
 118
 119        if (!dev->is_fm_band)   /* Only FM has stereo capability! */
 120                return V4L2_TUNER_SUB_MONO;
 121
 122        outb(7, dev->io);          /* Select tuner control */
 123        if ((inb(dev->io + 1) & 0x40) == 0)
 124                ret = V4L2_TUNER_SUB_STEREO;
 125        return ret;
 126}
 127
 128static unsigned cadet_gettune(struct cadet *dev)
 129{
 130        int curvol, i;
 131        unsigned fifo = 0;
 132
 133        /*
 134         * Prepare for read
 135         */
 136
 137        outb(7, dev->io);       /* Select tuner control */
 138        curvol = inb(dev->io + 1); /* Save current volume/mute setting */
 139        outb(0x00, dev->io + 1);  /* Ensure WRITE-ENABLE is LOW */
 140        dev->tunestat = 0xffff;
 141
 142        /*
 143         * Read the shift register
 144         */
 145        for (i = 0; i < 25; i++) {
 146                fifo = (fifo << 1) | ((inb(dev->io + 1) >> 7) & 0x01);
 147                if (i < 24) {
 148                        outb(0x01, dev->io + 1);
 149                        dev->tunestat &= inb(dev->io + 1);
 150                        outb(0x00, dev->io + 1);
 151                }
 152        }
 153
 154        /*
 155         * Restore volume/mute setting
 156         */
 157        outb(curvol, dev->io + 1);
 158        return fifo;
 159}
 160
 161static unsigned cadet_getfreq(struct cadet *dev)
 162{
 163        int i;
 164        unsigned freq = 0, test, fifo = 0;
 165
 166        /*
 167         * Read current tuning
 168         */
 169        fifo = cadet_gettune(dev);
 170
 171        /*
 172         * Convert to actual frequency
 173         */
 174        if (!dev->is_fm_band)    /* AM */
 175                return ((fifo & 0x7fff) - 450) * 16;
 176
 177        test = 12500;
 178        for (i = 0; i < 14; i++) {
 179                if ((fifo & 0x01) != 0)
 180                        freq += test;
 181                test = test << 1;
 182                fifo = fifo >> 1;
 183        }
 184        freq -= 10700000;           /* IF frequency is 10.7 MHz */
 185        freq = (freq * 16) / 1000;   /* Make it 1/16 kHz */
 186        return freq;
 187}
 188
 189static void cadet_settune(struct cadet *dev, unsigned fifo)
 190{
 191        int i;
 192        unsigned test;
 193
 194        outb(7, dev->io);                /* Select tuner control */
 195        /*
 196         * Write the shift register
 197         */
 198        test = 0;
 199        test = (fifo >> 23) & 0x02;      /* Align data for SDO */
 200        test |= 0x1c;                /* SDM=1, SWE=1, SEN=1, SCK=0 */
 201        outb(7, dev->io);                /* Select tuner control */
 202        outb(test, dev->io + 1);           /* Initialize for write */
 203        for (i = 0; i < 25; i++) {
 204                test |= 0x01;              /* Toggle SCK High */
 205                outb(test, dev->io + 1);
 206                test &= 0xfe;              /* Toggle SCK Low */
 207                outb(test, dev->io + 1);
 208                fifo = fifo << 1;            /* Prepare the next bit */
 209                test = 0x1c | ((fifo >> 23) & 0x02);
 210                outb(test, dev->io + 1);
 211        }
 212}
 213
 214static void cadet_setfreq(struct cadet *dev, unsigned freq)
 215{
 216        unsigned fifo;
 217        int i, j, test;
 218        int curvol;
 219
 220        freq = clamp(freq, bands[dev->is_fm_band].rangelow,
 221                           bands[dev->is_fm_band].rangehigh);
 222        dev->curfreq = freq;
 223        /*
 224         * Formulate a fifo command
 225         */
 226        fifo = 0;
 227        if (dev->is_fm_band) {    /* FM */
 228                test = 102400;
 229                freq = freq / 16;       /* Make it kHz */
 230                freq += 10700;               /* IF is 10700 kHz */
 231                for (i = 0; i < 14; i++) {
 232                        fifo = fifo << 1;
 233                        if (freq >= test) {
 234                                fifo |= 0x01;
 235                                freq -= test;
 236                        }
 237                        test = test >> 1;
 238                }
 239        } else {        /* AM */
 240                fifo = (freq / 16) + 450;       /* Make it kHz */
 241                fifo |= 0x100000;               /* Select AM Band */
 242        }
 243
 244        /*
 245         * Save current volume/mute setting
 246         */
 247
 248        outb(7, dev->io);                /* Select tuner control */
 249        curvol = inb(dev->io + 1);
 250
 251        /*
 252         * Tune the card
 253         */
 254        for (j = 3; j > -1; j--) {
 255                cadet_settune(dev, fifo | (j << 16));
 256
 257                outb(7, dev->io);         /* Select tuner control */
 258                outb(curvol, dev->io + 1);
 259
 260                msleep(100);
 261
 262                cadet_gettune(dev);
 263                if ((dev->tunestat & 0x40) == 0) {   /* Tuned */
 264                        dev->sigstrength = sigtable[dev->is_fm_band][j];
 265                        goto reset_rds;
 266                }
 267        }
 268        dev->sigstrength = 0;
 269reset_rds:
 270        outb(3, dev->io);
 271        outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
 272}
 273
 274static bool cadet_has_rds_data(struct cadet *dev)
 275{
 276        bool result;
 277
 278        mutex_lock(&dev->lock);
 279        result = dev->rdsin != dev->rdsout;
 280        mutex_unlock(&dev->lock);
 281        return result;
 282}
 283
 284
 285static void cadet_handler(struct timer_list *t)
 286{
 287        struct cadet *dev = from_timer(dev, t, readtimer);
 288
 289        /* Service the RDS fifo */
 290        if (mutex_trylock(&dev->lock)) {
 291                outb(0x3, dev->io);       /* Select RDS Decoder Control */
 292                if ((inb(dev->io + 1) & 0x20) != 0)
 293                        pr_err("cadet: RDS fifo overflow\n");
 294                outb(0x80, dev->io);      /* Select RDS fifo */
 295
 296                while ((inb(dev->io) & 0x80) != 0) {
 297                        dev->rdsbuf[dev->rdsin] = inb(dev->io + 1);
 298                        if (dev->rdsin + 1 != dev->rdsout)
 299                                dev->rdsin++;
 300                }
 301                mutex_unlock(&dev->lock);
 302        }
 303
 304        /*
 305         * Service pending read
 306         */
 307        if (cadet_has_rds_data(dev))
 308                wake_up_interruptible(&dev->read_queue);
 309
 310        /*
 311         * Clean up and exit
 312         */
 313        dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
 314        add_timer(&dev->readtimer);
 315}
 316
 317static void cadet_start_rds(struct cadet *dev)
 318{
 319        dev->rdsstat = 1;
 320        outb(0x80, dev->io);        /* Select RDS fifo */
 321        timer_setup(&dev->readtimer, cadet_handler, 0);
 322        dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
 323        add_timer(&dev->readtimer);
 324}
 325
 326static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
 327{
 328        struct cadet *dev = video_drvdata(file);
 329        unsigned char readbuf[RDS_BUFFER];
 330        int i = 0;
 331
 332        mutex_lock(&dev->lock);
 333        if (dev->rdsstat == 0)
 334                cadet_start_rds(dev);
 335        mutex_unlock(&dev->lock);
 336
 337        if (!cadet_has_rds_data(dev) && (file->f_flags & O_NONBLOCK))
 338                return -EWOULDBLOCK;
 339        i = wait_event_interruptible(dev->read_queue, cadet_has_rds_data(dev));
 340        if (i)
 341                return i;
 342
 343        mutex_lock(&dev->lock);
 344        while (i < count && dev->rdsin != dev->rdsout)
 345                readbuf[i++] = dev->rdsbuf[dev->rdsout++];
 346        mutex_unlock(&dev->lock);
 347
 348        if (i && copy_to_user(data, readbuf, i))
 349                return -EFAULT;
 350        return i;
 351}
 352
 353
 354static int vidioc_querycap(struct file *file, void *priv,
 355                                struct v4l2_capability *v)
 356{
 357        strscpy(v->driver, "ADS Cadet", sizeof(v->driver));
 358        strscpy(v->card, "ADS Cadet", sizeof(v->card));
 359        strscpy(v->bus_info, "ISA:radio-cadet", sizeof(v->bus_info));
 360        return 0;
 361}
 362
 363static int vidioc_g_tuner(struct file *file, void *priv,
 364                                struct v4l2_tuner *v)
 365{
 366        struct cadet *dev = video_drvdata(file);
 367
 368        if (v->index)
 369                return -EINVAL;
 370        v->type = V4L2_TUNER_RADIO;
 371        strscpy(v->name, "Radio", sizeof(v->name));
 372        v->capability = bands[0].capability | bands[1].capability;
 373        v->rangelow = bands[0].rangelow;           /* 520 kHz (start of AM band) */
 374        v->rangehigh = bands[1].rangehigh;    /* 108.0 MHz (end of FM band) */
 375        if (dev->is_fm_band) {
 376                v->rxsubchans = cadet_getstereo(dev);
 377                outb(3, dev->io);
 378                outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
 379                mdelay(100);
 380                outb(3, dev->io);
 381                if (inb(dev->io + 1) & 0x80)
 382                        v->rxsubchans |= V4L2_TUNER_SUB_RDS;
 383        } else {
 384                v->rangelow = 8320;      /* 520 kHz */
 385                v->rangehigh = 26400;    /* 1650 kHz */
 386                v->rxsubchans = V4L2_TUNER_SUB_MONO;
 387        }
 388        v->audmode = V4L2_TUNER_MODE_STEREO;
 389        v->signal = dev->sigstrength; /* We might need to modify scaling of this */
 390        return 0;
 391}
 392
 393static int vidioc_s_tuner(struct file *file, void *priv,
 394                                const struct v4l2_tuner *v)
 395{
 396        return v->index ? -EINVAL : 0;
 397}
 398
 399static int vidioc_enum_freq_bands(struct file *file, void *priv,
 400                                struct v4l2_frequency_band *band)
 401{
 402        if (band->tuner)
 403                return -EINVAL;
 404        if (band->index >= ARRAY_SIZE(bands))
 405                return -EINVAL;
 406        *band = bands[band->index];
 407        return 0;
 408}
 409
 410static int vidioc_g_frequency(struct file *file, void *priv,
 411                                struct v4l2_frequency *f)
 412{
 413        struct cadet *dev = video_drvdata(file);
 414
 415        if (f->tuner)
 416                return -EINVAL;
 417        f->type = V4L2_TUNER_RADIO;
 418        f->frequency = dev->curfreq;
 419        return 0;
 420}
 421
 422
 423static int vidioc_s_frequency(struct file *file, void *priv,
 424                                const struct v4l2_frequency *f)
 425{
 426        struct cadet *dev = video_drvdata(file);
 427
 428        if (f->tuner)
 429                return -EINVAL;
 430        dev->is_fm_band =
 431                f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2;
 432        cadet_setfreq(dev, f->frequency);
 433        return 0;
 434}
 435
 436static int cadet_s_ctrl(struct v4l2_ctrl *ctrl)
 437{
 438        struct cadet *dev = container_of(ctrl->handler, struct cadet, ctrl_handler);
 439
 440        switch (ctrl->id) {
 441        case V4L2_CID_AUDIO_MUTE:
 442                outb(7, dev->io);                /* Select tuner control */
 443                if (ctrl->val)
 444                        outb(0x00, dev->io + 1);
 445                else
 446                        outb(0x20, dev->io + 1);
 447                return 0;
 448        }
 449        return -EINVAL;
 450}
 451
 452static int cadet_open(struct file *file)
 453{
 454        struct cadet *dev = video_drvdata(file);
 455        int err;
 456
 457        mutex_lock(&dev->lock);
 458        err = v4l2_fh_open(file);
 459        if (err)
 460                goto fail;
 461        if (v4l2_fh_is_singular_file(file))
 462                init_waitqueue_head(&dev->read_queue);
 463fail:
 464        mutex_unlock(&dev->lock);
 465        return err;
 466}
 467
 468static int cadet_release(struct file *file)
 469{
 470        struct cadet *dev = video_drvdata(file);
 471
 472        mutex_lock(&dev->lock);
 473        if (v4l2_fh_is_singular_file(file) && dev->rdsstat) {
 474                del_timer_sync(&dev->readtimer);
 475                dev->rdsstat = 0;
 476        }
 477        v4l2_fh_release(file);
 478        mutex_unlock(&dev->lock);
 479        return 0;
 480}
 481
 482static __poll_t cadet_poll(struct file *file, struct poll_table_struct *wait)
 483{
 484        struct cadet *dev = video_drvdata(file);
 485        __poll_t req_events = poll_requested_events(wait);
 486        __poll_t res = v4l2_ctrl_poll(file, wait);
 487
 488        poll_wait(file, &dev->read_queue, wait);
 489        if (dev->rdsstat == 0 && (req_events & (EPOLLIN | EPOLLRDNORM))) {
 490                mutex_lock(&dev->lock);
 491                if (dev->rdsstat == 0)
 492                        cadet_start_rds(dev);
 493                mutex_unlock(&dev->lock);
 494        }
 495        if (cadet_has_rds_data(dev))
 496                res |= EPOLLIN | EPOLLRDNORM;
 497        return res;
 498}
 499
 500
 501static const struct v4l2_file_operations cadet_fops = {
 502        .owner          = THIS_MODULE,
 503        .open           = cadet_open,
 504        .release        = cadet_release,
 505        .read           = cadet_read,
 506        .unlocked_ioctl = video_ioctl2,
 507        .poll           = cadet_poll,
 508};
 509
 510static const struct v4l2_ioctl_ops cadet_ioctl_ops = {
 511        .vidioc_querycap    = vidioc_querycap,
 512        .vidioc_g_tuner     = vidioc_g_tuner,
 513        .vidioc_s_tuner     = vidioc_s_tuner,
 514        .vidioc_g_frequency = vidioc_g_frequency,
 515        .vidioc_s_frequency = vidioc_s_frequency,
 516        .vidioc_enum_freq_bands = vidioc_enum_freq_bands,
 517        .vidioc_log_status  = v4l2_ctrl_log_status,
 518        .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
 519        .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
 520};
 521
 522static const struct v4l2_ctrl_ops cadet_ctrl_ops = {
 523        .s_ctrl = cadet_s_ctrl,
 524};
 525
 526#ifdef CONFIG_PNP
 527
 528static const struct pnp_device_id cadet_pnp_devices[] = {
 529        /* ADS Cadet AM/FM Radio Card */
 530        {.id = "MSM0c24", .driver_data = 0},
 531        {.id = ""}
 532};
 533
 534MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
 535
 536static int cadet_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
 537{
 538        if (!dev)
 539                return -ENODEV;
 540        /* only support one device */
 541        if (io > 0)
 542                return -EBUSY;
 543
 544        if (!pnp_port_valid(dev, 0))
 545                return -ENODEV;
 546
 547        io = pnp_port_start(dev, 0);
 548
 549        printk(KERN_INFO "radio-cadet: PnP reports device at %#x\n", io);
 550
 551        return io;
 552}
 553
 554static struct pnp_driver cadet_pnp_driver = {
 555        .name           = "radio-cadet",
 556        .id_table       = cadet_pnp_devices,
 557        .probe          = cadet_pnp_probe,
 558        .remove         = NULL,
 559};
 560
 561#else
 562static struct pnp_driver cadet_pnp_driver;
 563#endif
 564
 565static void cadet_probe(struct cadet *dev)
 566{
 567        static int iovals[8] = { 0x330, 0x332, 0x334, 0x336, 0x338, 0x33a, 0x33c, 0x33e };
 568        int i;
 569
 570        for (i = 0; i < 8; i++) {
 571                dev->io = iovals[i];
 572                if (request_region(dev->io, 2, "cadet-probe")) {
 573                        cadet_setfreq(dev, bands[1].rangelow);
 574                        if (cadet_getfreq(dev) == bands[1].rangelow) {
 575                                release_region(dev->io, 2);
 576                                return;
 577                        }
 578                        release_region(dev->io, 2);
 579                }
 580        }
 581        dev->io = -1;
 582}
 583
 584/*
 585 * io should only be set if the user has used something like
 586 * isapnp (the userspace program) to initialize this card for us
 587 */
 588
 589static int __init cadet_init(void)
 590{
 591        struct cadet *dev = &cadet_card;
 592        struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
 593        struct v4l2_ctrl_handler *hdl;
 594        int res = -ENODEV;
 595
 596        strscpy(v4l2_dev->name, "cadet", sizeof(v4l2_dev->name));
 597        mutex_init(&dev->lock);
 598
 599        /* If a probe was requested then probe ISAPnP first (safest) */
 600        if (io < 0)
 601                pnp_register_driver(&cadet_pnp_driver);
 602        dev->io = io;
 603
 604        /* If that fails then probe unsafely if probe is requested */
 605        if (dev->io < 0)
 606                cadet_probe(dev);
 607
 608        /* Else we bail out */
 609        if (dev->io < 0) {
 610#ifdef MODULE
 611                v4l2_err(v4l2_dev, "you must set an I/O address with io=0x330, 0x332, 0x334,\n");
 612                v4l2_err(v4l2_dev, "0x336, 0x338, 0x33a, 0x33c or 0x33e\n");
 613#endif
 614                goto fail;
 615        }
 616        if (!request_region(dev->io, 2, "cadet"))
 617                goto fail;
 618
 619        res = v4l2_device_register(NULL, v4l2_dev);
 620        if (res < 0) {
 621                release_region(dev->io, 2);
 622                v4l2_err(v4l2_dev, "could not register v4l2_device\n");
 623                goto fail;
 624        }
 625
 626        hdl = &dev->ctrl_handler;
 627        v4l2_ctrl_handler_init(hdl, 2);
 628        v4l2_ctrl_new_std(hdl, &cadet_ctrl_ops,
 629                        V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
 630        v4l2_dev->ctrl_handler = hdl;
 631        if (hdl->error) {
 632                res = hdl->error;
 633                v4l2_err(v4l2_dev, "Could not register controls\n");
 634                goto err_hdl;
 635        }
 636
 637        dev->is_fm_band = true;
 638        dev->curfreq = bands[dev->is_fm_band].rangelow;
 639        cadet_setfreq(dev, dev->curfreq);
 640        strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
 641        dev->vdev.v4l2_dev = v4l2_dev;
 642        dev->vdev.fops = &cadet_fops;
 643        dev->vdev.ioctl_ops = &cadet_ioctl_ops;
 644        dev->vdev.release = video_device_release_empty;
 645        dev->vdev.lock = &dev->lock;
 646        dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
 647                                V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE;
 648        video_set_drvdata(&dev->vdev, dev);
 649
 650        res = video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr);
 651        if (res < 0)
 652                goto err_hdl;
 653        v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n", dev->io);
 654        return 0;
 655err_hdl:
 656        v4l2_ctrl_handler_free(hdl);
 657        v4l2_device_unregister(v4l2_dev);
 658        release_region(dev->io, 2);
 659fail:
 660        pnp_unregister_driver(&cadet_pnp_driver);
 661        return res;
 662}
 663
 664static void __exit cadet_exit(void)
 665{
 666        struct cadet *dev = &cadet_card;
 667
 668        video_unregister_device(&dev->vdev);
 669        v4l2_ctrl_handler_free(&dev->ctrl_handler);
 670        v4l2_device_unregister(&dev->v4l2_dev);
 671        outb(7, dev->io);       /* Mute */
 672        outb(0x00, dev->io + 1);
 673        release_region(dev->io, 2);
 674        pnp_unregister_driver(&cadet_pnp_driver);
 675}
 676
 677module_init(cadet_init);
 678module_exit(cadet_exit);
 679
 680