1 /* zoltrix radio plus driver for Linux radio support
 
   2  * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
 
   5  *  Due to the inconsistency in reading from the signal flags
 
   6  *  it is difficult to get an accurate tuned signal.
 
   8  *  It seems that the card is not linear to 0 volume. It cuts off
 
   9  *  at a low volume, and it is not possible (at least I have not found)
 
  10  *  to get fine volume control over the low volume range.
 
  12  *  Some code derived from code by Romolo Manfredini
 
  15  * 1999-05-06 - (C. van Schaik)
 
  16  *            - Make signal strength and stereo scans
 
  17  *              kinder to cpu while in delay
 
  18  * 1999-01-05 - (C. van Schaik)
 
  19  *            - Changed tuning to 1/160Mhz accuracy
 
  20  *            - Added stereo support
 
  21  *              (card defaults to stereo)
 
  22  *              (can explicitly force mono on the card)
 
  23  *              (can detect if station is in stereo)
 
  24  *            - Added unmute function
 
  25  *            - Reworked ioctl functions
 
  26  * 2002-07-15 - Fix Stereo typo
 
  28  * 2006-07-24 - Converted to V4L2 API
 
  29  *              by Mauro Carvalho Chehab <mchehab@infradead.org>
 
  32 #include <linux/module.h>       /* Modules                        */
 
  33 #include <linux/init.h>         /* Initdata                       */
 
  34 #include <linux/ioport.h>       /* request_region                 */
 
  35 #include <linux/delay.h>        /* udelay, msleep                 */
 
  36 #include <asm/io.h>             /* outb, outb_p                   */
 
  37 #include <asm/uaccess.h>        /* copy to/from user              */
 
  38 #include <linux/videodev2.h>    /* kernel radio structs           */
 
  39 #include <media/v4l2-common.h>
 
  41 #include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
 
  42 #define RADIO_VERSION KERNEL_VERSION(0,0,2)
 
  44 static struct v4l2_queryctrl radio_qctrl[] = {
 
  46                 .id            = V4L2_CID_AUDIO_MUTE,
 
  51                 .type          = V4L2_CTRL_TYPE_BOOLEAN,
 
  53                 .id            = V4L2_CID_AUDIO_VOLUME,
 
  58                 .default_value = 0xff,
 
  59                 .type          = V4L2_CTRL_TYPE_INTEGER,
 
  63 #ifndef CONFIG_RADIO_ZOLTRIX_PORT
 
  64 #define CONFIG_RADIO_ZOLTRIX_PORT -1
 
  67 static int io = CONFIG_RADIO_ZOLTRIX_PORT;
 
  68 static int radio_nr = -1;
 
  73         unsigned long curfreq;
 
  79 static int zol_setvol(struct zol_device *dev, int vol)
 
  85         mutex_lock(&dev->lock);
 
  89                 inb(io + 3);    /* Zoltrix needs to be read to confirm */
 
  90                 mutex_unlock(&dev->lock);
 
  94         outb(dev->curvol-1, io);
 
  97         mutex_unlock(&dev->lock);
 
 101 static void zol_mute(struct zol_device *dev)
 
 104         mutex_lock(&dev->lock);
 
 107         inb(io + 3);            /* Zoltrix needs to be read to confirm */
 
 108         mutex_unlock(&dev->lock);
 
 111 static void zol_unmute(struct zol_device *dev)
 
 114         zol_setvol(dev, dev->curvol);
 
 117 static int zol_setfreq(struct zol_device *dev, unsigned long freq)
 
 119         /* tunes the radio to the desired frequency */
 
 120         unsigned long long bitmask, f, m;
 
 121         unsigned int stereo = dev->stereo;
 
 126         m = (freq / 160 - 8800) * 2;
 
 127         f = (unsigned long long) m + 0x4d1c;
 
 129         bitmask = 0xc480402c10080000ull;
 
 132         mutex_lock(&dev->lock);
 
 136         inb(io + 3);            /* Zoltrix needs to be read to confirm */
 
 141         bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
 
 143                 if ((bitmask & 0x8000000000000000ull) != 0) {
 
 160         /* termination sequence */
 
 177         mutex_unlock(&dev->lock);
 
 181                 zol_setvol(dev, dev->curvol);
 
 186 /* Get signal strength */
 
 188 static int zol_getsigstr(struct zol_device *dev)
 
 192         mutex_lock(&dev->lock);
 
 193         outb(0x00, io);         /* This stuff I found to do nothing */
 
 194         outb(dev->curvol, io);
 
 201         mutex_unlock(&dev->lock);
 
 206         if ((a == 0xcf) || (a == 0xdf)  /* I found this out by playing */
 
 207                 || (a == 0xef))       /* with a binary scanner on the card io */
 
 212 static int zol_is_stereo (struct zol_device *dev)
 
 216         mutex_lock(&dev->lock);
 
 219         outb(dev->curvol, io);
 
 226         mutex_unlock(&dev->lock);
 
 228         if ((x1 == x2) && (x1 == 0xcf))
 
 233 static int zol_do_ioctl(struct inode *inode, struct file *file,
 
 234                         unsigned int cmd, void *arg)
 
 236         struct video_device *dev = video_devdata(file);
 
 237         struct zol_device *zol = dev->priv;
 
 240                 case VIDIOC_QUERYCAP:
 
 242                         struct v4l2_capability *v = arg;
 
 243                         memset(v,0,sizeof(*v));
 
 244                         strlcpy(v->driver, "radio-zoltrix", sizeof (v->driver));
 
 245                         strlcpy(v->card, "Zoltrix Radio", sizeof (v->card));
 
 246                         sprintf(v->bus_info,"ISA");
 
 247                         v->version = RADIO_VERSION;
 
 248                         v->capabilities = V4L2_CAP_TUNER;
 
 254                         struct v4l2_tuner *v = arg;
 
 259                         memset(v,0,sizeof(*v));
 
 260                         strcpy(v->name, "FM");
 
 261                         v->type = V4L2_TUNER_RADIO;
 
 263                         v->rangelow=(88*16000);
 
 264                         v->rangehigh=(108*16000);
 
 265                         v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
 
 266                         v->capability=V4L2_TUNER_CAP_LOW;
 
 267                         if(zol_is_stereo(zol))
 
 268                                 v->audmode = V4L2_TUNER_MODE_STEREO;
 
 270                                 v->audmode = V4L2_TUNER_MODE_MONO;
 
 271                         v->signal=0xFFFF*zol_getsigstr(zol);
 
 277                         struct v4l2_tuner *v = arg;
 
 284                 case VIDIOC_S_FREQUENCY:
 
 286                         struct v4l2_frequency *f = arg;
 
 288                         zol->curfreq = f->frequency;
 
 289                         zol_setfreq(zol, zol->curfreq);
 
 292                 case VIDIOC_G_FREQUENCY:
 
 294                         struct v4l2_frequency *f = arg;
 
 296                         f->type = V4L2_TUNER_RADIO;
 
 297                         f->frequency = zol->curfreq;
 
 301                 case VIDIOC_QUERYCTRL:
 
 303                         struct v4l2_queryctrl *qc = arg;
 
 306                         for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
 
 307                                 if (qc->id && qc->id == radio_qctrl[i].id) {
 
 308                                         memcpy(qc, &(radio_qctrl[i]),
 
 317                         struct v4l2_control *ctrl= arg;
 
 320                                 case V4L2_CID_AUDIO_MUTE:
 
 321                                         ctrl->value=zol->muted;
 
 323                                 case V4L2_CID_AUDIO_VOLUME:
 
 324                                         ctrl->value=zol->curvol * 4096;
 
 331                         struct v4l2_control *ctrl= arg;
 
 334                                 case V4L2_CID_AUDIO_MUTE:
 
 339                                                 zol_setvol(zol,zol->curvol);
 
 342                                 case V4L2_CID_AUDIO_VOLUME:
 
 343                                         zol_setvol(zol,ctrl->value/4096);
 
 347                         zol_setfreq(zol, zol->curfreq);
 
 349 /* FIXME: Implement stereo/mono switch on V4L2 */
 
 350                         if (v->mode & VIDEO_SOUND_STEREO) {
 
 352                                 zol_setfreq(zol, zol->curfreq);
 
 354                         if (v->mode & VIDEO_SOUND_MONO) {
 
 356                                 zol_setfreq(zol, zol->curfreq);
 
 363                         return v4l_compat_translate_ioctl(inode,file,cmd,arg,
 
 368 static int zol_ioctl(struct inode *inode, struct file *file,
 
 369                      unsigned int cmd, unsigned long arg)
 
 371         return video_usercopy(inode, file, cmd, arg, zol_do_ioctl);
 
 374 static struct zol_device zoltrix_unit;
 
 376 static struct file_operations zoltrix_fops =
 
 378         .owner          = THIS_MODULE,
 
 379         .open           = video_exclusive_open,
 
 380         .release        = video_exclusive_release,
 
 382         .compat_ioctl   = v4l_compat_ioctl32,
 
 386 static struct video_device zoltrix_radio =
 
 388         .owner          = THIS_MODULE,
 
 389         .name           = "Zoltrix Radio Plus",
 
 390         .type           = VID_TYPE_TUNER,
 
 392         .fops           = &zoltrix_fops,
 
 395 static int __init zoltrix_init(void)
 
 398                 printk(KERN_ERR "You must set an I/O address with io=0x???\n");
 
 401         if ((io != 0x20c) && (io != 0x30c)) {
 
 402                 printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
 
 406         zoltrix_radio.priv = &zoltrix_unit;
 
 407         if (!request_region(io, 2, "zoltrix")) {
 
 408                 printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
 
 412         if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
 
 414                 release_region(io, 2);
 
 417         printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
 
 419         mutex_init(&zoltrix_unit.lock);
 
 421         /* mute card - prevents noisy bootups */
 
 423         /* this ensures that the volume is all the way down  */
 
 430         zoltrix_unit.curvol = 0;
 
 431         zoltrix_unit.stereo = 1;
 
 436 MODULE_AUTHOR("C.van Schaik");
 
 437 MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
 
 438 MODULE_LICENSE("GPL");
 
 440 module_param(io, int, 0);
 
 441 MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
 
 442 module_param(radio_nr, int, 0);
 
 444 static void __exit zoltrix_cleanup_module(void)
 
 446         video_unregister_device(&zoltrix_radio);
 
 447         release_region(io, 2);
 
 450 module_init(zoltrix_init);
 
 451 module_exit(zoltrix_cleanup_module);