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