linux/drivers/media/radio/radio-typhoon.c
<<
>>
Prefs
   1/* Typhoon Radio Card driver for radio support
   2 * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
   3 *
   4 * Notes on the hardware
   5 *
   6 * This card has two output sockets, one for speakers and one for line.
   7 * The speaker output has volume control, but only in four discrete
   8 * steps. The line output has neither volume control nor mute.
   9 *
  10 * The card has auto-stereo according to its manual, although it all
  11 * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
  12 * antenna - I really don't know for sure.
  13 *
  14 * Frequency control is done digitally.
  15 *
  16 * Volume control is done digitally, but there are only four different
  17 * possible values. So you should better always turn the volume up and
  18 * use line control. I got the best results by connecting line output
  19 * to the sound card microphone input. For such a configuration the
  20 * volume control has no effect, since volume control only influences
  21 * the speaker output.
  22 *
  23 * There is no explicit mute/unmute. So I set the radio frequency to a
  24 * value where I do expect just noise and turn the speaker volume down.
  25 * The frequency change is necessary since the card never seems to be
  26 * completely silent.
  27 *
  28 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
  29 */
  30
  31#include <linux/module.h>       /* Modules                        */
  32#include <linux/init.h>         /* Initdata                       */
  33#include <linux/ioport.h>       /* request_region                 */
  34#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
  35#include <linux/videodev2.h>    /* kernel radio structs           */
  36#include <linux/io.h>           /* outb, outb_p                   */
  37#include <media/v4l2-device.h>
  38#include <media/v4l2-ioctl.h>
  39
  40MODULE_AUTHOR("Dr. Henrik Seidel");
  41MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
  42MODULE_LICENSE("GPL");
  43
  44#ifndef CONFIG_RADIO_TYPHOON_PORT
  45#define CONFIG_RADIO_TYPHOON_PORT -1
  46#endif
  47
  48#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
  49#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
  50#endif
  51
  52static int io = CONFIG_RADIO_TYPHOON_PORT;
  53static int radio_nr = -1;
  54
  55module_param(io, int, 0);
  56MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
  57
  58module_param(radio_nr, int, 0);
  59
  60static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;
  61module_param(mutefreq, ulong, 0);
  62MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
  63
  64#define RADIO_VERSION KERNEL_VERSION(0, 1, 1)
  65
  66#define BANNER "Typhoon Radio Card driver v0.1.1\n"
  67
  68struct typhoon {
  69        struct v4l2_device v4l2_dev;
  70        struct video_device vdev;
  71        int io;
  72        int curvol;
  73        int muted;
  74        unsigned long curfreq;
  75        unsigned long mutefreq;
  76        struct mutex lock;
  77};
  78
  79static struct typhoon typhoon_card;
  80
  81static void typhoon_setvol_generic(struct typhoon *dev, int vol)
  82{
  83        mutex_lock(&dev->lock);
  84        vol >>= 14;                             /* Map 16 bit to 2 bit */
  85        vol &= 3;
  86        outb_p(vol / 2, dev->io);               /* Set the volume, high bit. */
  87        outb_p(vol % 2, dev->io + 2);   /* Set the volume, low bit. */
  88        mutex_unlock(&dev->lock);
  89}
  90
  91static int typhoon_setfreq_generic(struct typhoon *dev,
  92                                   unsigned long frequency)
  93{
  94        unsigned long outval;
  95        unsigned long x;
  96
  97        /*
  98         * The frequency transfer curve is not linear. The best fit I could
  99         * get is
 100         *
 101         * outval = -155 + exp((f + 15.55) * 0.057))
 102         *
 103         * where frequency f is in MHz. Since we don't have exp in the kernel,
 104         * I approximate this function by a third order polynomial.
 105         *
 106         */
 107
 108        mutex_lock(&dev->lock);
 109        x = frequency / 160;
 110        outval = (x * x + 2500) / 5000;
 111        outval = (outval * x + 5000) / 10000;
 112        outval -= (10 * x * x + 10433) / 20866;
 113        outval += 4 * x - 11505;
 114
 115        outb_p((outval >> 8) & 0x01, dev->io + 4);
 116        outb_p(outval >> 9, dev->io + 6);
 117        outb_p(outval & 0xff, dev->io + 8);
 118        mutex_unlock(&dev->lock);
 119
 120        return 0;
 121}
 122
 123static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency)
 124{
 125        typhoon_setfreq_generic(dev, frequency);
 126        dev->curfreq = frequency;
 127        return 0;
 128}
 129
 130static void typhoon_mute(struct typhoon *dev)
 131{
 132        if (dev->muted == 1)
 133                return;
 134        typhoon_setvol_generic(dev, 0);
 135        typhoon_setfreq_generic(dev, dev->mutefreq);
 136        dev->muted = 1;
 137}
 138
 139static void typhoon_unmute(struct typhoon *dev)
 140{
 141        if (dev->muted == 0)
 142                return;
 143        typhoon_setfreq_generic(dev, dev->curfreq);
 144        typhoon_setvol_generic(dev, dev->curvol);
 145        dev->muted = 0;
 146}
 147
 148static int typhoon_setvol(struct typhoon *dev, int vol)
 149{
 150        if (dev->muted && vol != 0) {   /* user is unmuting the card */
 151                dev->curvol = vol;
 152                typhoon_unmute(dev);
 153                return 0;
 154        }
 155        if (vol == dev->curvol)         /* requested volume == current */
 156                return 0;
 157
 158        if (vol == 0) {                 /* volume == 0 means mute the card */
 159                typhoon_mute(dev);
 160                dev->curvol = vol;
 161                return 0;
 162        }
 163        typhoon_setvol_generic(dev, vol);
 164        dev->curvol = vol;
 165        return 0;
 166}
 167
 168static int vidioc_querycap(struct file *file, void  *priv,
 169                                        struct v4l2_capability *v)
 170{
 171        strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));
 172        strlcpy(v->card, "Typhoon Radio", sizeof(v->card));
 173        strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
 174        v->version = RADIO_VERSION;
 175        v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
 176        return 0;
 177}
 178
 179static int vidioc_g_tuner(struct file *file, void *priv,
 180                                        struct v4l2_tuner *v)
 181{
 182        if (v->index > 0)
 183                return -EINVAL;
 184
 185        strlcpy(v->name, "FM", sizeof(v->name));
 186        v->type = V4L2_TUNER_RADIO;
 187        v->rangelow = 87.5 * 16000;
 188        v->rangehigh = 108 * 16000;
 189        v->rxsubchans = V4L2_TUNER_SUB_MONO;
 190        v->capability = V4L2_TUNER_CAP_LOW;
 191        v->audmode = V4L2_TUNER_MODE_MONO;
 192        v->signal = 0xFFFF;     /* We can't get the signal strength */
 193        return 0;
 194}
 195
 196static int vidioc_s_tuner(struct file *file, void *priv,
 197                                        struct v4l2_tuner *v)
 198{
 199        return v->index ? -EINVAL : 0;
 200}
 201
 202static int vidioc_g_frequency(struct file *file, void *priv,
 203                                        struct v4l2_frequency *f)
 204{
 205        struct typhoon *dev = video_drvdata(file);
 206
 207        if (f->tuner != 0)
 208                return -EINVAL;
 209        f->type = V4L2_TUNER_RADIO;
 210        f->frequency = dev->curfreq;
 211        return 0;
 212}
 213
 214static int vidioc_s_frequency(struct file *file, void *priv,
 215                                        struct v4l2_frequency *f)
 216{
 217        struct typhoon *dev = video_drvdata(file);
 218
 219        if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
 220                return -EINVAL;
 221        dev->curfreq = f->frequency;
 222        typhoon_setfreq(dev, dev->curfreq);
 223        return 0;
 224}
 225
 226static int vidioc_queryctrl(struct file *file, void *priv,
 227                                        struct v4l2_queryctrl *qc)
 228{
 229        switch (qc->id) {
 230        case V4L2_CID_AUDIO_MUTE:
 231                return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
 232        case V4L2_CID_AUDIO_VOLUME:
 233                return v4l2_ctrl_query_fill(qc, 0, 65535, 16384, 65535);
 234        }
 235        return -EINVAL;
 236}
 237
 238static int vidioc_g_ctrl(struct file *file, void *priv,
 239                                        struct v4l2_control *ctrl)
 240{
 241        struct typhoon *dev = video_drvdata(file);
 242
 243        switch (ctrl->id) {
 244        case V4L2_CID_AUDIO_MUTE:
 245                ctrl->value = dev->muted;
 246                return 0;
 247        case V4L2_CID_AUDIO_VOLUME:
 248                ctrl->value = dev->curvol;
 249                return 0;
 250        }
 251        return -EINVAL;
 252}
 253
 254static int vidioc_s_ctrl (struct file *file, void *priv,
 255                                        struct v4l2_control *ctrl)
 256{
 257        struct typhoon *dev = video_drvdata(file);
 258
 259        switch (ctrl->id) {
 260        case V4L2_CID_AUDIO_MUTE:
 261                if (ctrl->value)
 262                        typhoon_mute(dev);
 263                else
 264                        typhoon_unmute(dev);
 265                return 0;
 266        case V4L2_CID_AUDIO_VOLUME:
 267                typhoon_setvol(dev, ctrl->value);
 268                return 0;
 269        }
 270        return -EINVAL;
 271}
 272
 273static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
 274{
 275        *i = 0;
 276        return 0;
 277}
 278
 279static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
 280{
 281        return i ? -EINVAL : 0;
 282}
 283
 284static int vidioc_g_audio(struct file *file, void *priv,
 285                                        struct v4l2_audio *a)
 286{
 287        a->index = 0;
 288        strlcpy(a->name, "Radio", sizeof(a->name));
 289        a->capability = V4L2_AUDCAP_STEREO;
 290        return 0;
 291}
 292
 293static int vidioc_s_audio(struct file *file, void *priv,
 294                                        struct v4l2_audio *a)
 295{
 296        return a->index ? -EINVAL : 0;
 297}
 298
 299static int vidioc_log_status(struct file *file, void *priv)
 300{
 301        struct typhoon *dev = video_drvdata(file);
 302        struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
 303
 304        v4l2_info(v4l2_dev, BANNER);
 305#ifdef MODULE
 306        v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n");
 307#else
 308        v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n");
 309#endif
 310        v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4);
 311        v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol);
 312        v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ?  "on" : "off");
 313        v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io);
 314        v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4);
 315        return 0;
 316}
 317
 318static const struct v4l2_file_operations typhoon_fops = {
 319        .owner          = THIS_MODULE,
 320        .unlocked_ioctl = video_ioctl2,
 321};
 322
 323static const struct v4l2_ioctl_ops typhoon_ioctl_ops = {
 324        .vidioc_log_status  = vidioc_log_status,
 325        .vidioc_querycap    = vidioc_querycap,
 326        .vidioc_g_tuner     = vidioc_g_tuner,
 327        .vidioc_s_tuner     = vidioc_s_tuner,
 328        .vidioc_g_audio     = vidioc_g_audio,
 329        .vidioc_s_audio     = vidioc_s_audio,
 330        .vidioc_g_input     = vidioc_g_input,
 331        .vidioc_s_input     = vidioc_s_input,
 332        .vidioc_g_frequency = vidioc_g_frequency,
 333        .vidioc_s_frequency = vidioc_s_frequency,
 334        .vidioc_queryctrl   = vidioc_queryctrl,
 335        .vidioc_g_ctrl      = vidioc_g_ctrl,
 336        .vidioc_s_ctrl      = vidioc_s_ctrl,
 337};
 338
 339static int __init typhoon_init(void)
 340{
 341        struct typhoon *dev = &typhoon_card;
 342        struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
 343        int res;
 344
 345        strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name));
 346        dev->io = io;
 347
 348        if (dev->io == -1) {
 349                v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n");
 350                return -EINVAL;
 351        }
 352
 353        if (mutefreq < 87000 || mutefreq > 108500) {
 354                v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n");
 355                v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
 356                return -EINVAL;
 357        }
 358        dev->curfreq = dev->mutefreq = mutefreq << 4;
 359
 360        mutex_init(&dev->lock);
 361        if (!request_region(dev->io, 8, "typhoon")) {
 362                v4l2_err(v4l2_dev, "port 0x%x already in use\n",
 363                       dev->io);
 364                return -EBUSY;
 365        }
 366
 367        res = v4l2_device_register(NULL, v4l2_dev);
 368        if (res < 0) {
 369                release_region(dev->io, 8);
 370                v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
 371                return res;
 372        }
 373        v4l2_info(v4l2_dev, BANNER);
 374
 375        strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
 376        dev->vdev.v4l2_dev = v4l2_dev;
 377        dev->vdev.fops = &typhoon_fops;
 378        dev->vdev.ioctl_ops = &typhoon_ioctl_ops;
 379        dev->vdev.release = video_device_release_empty;
 380        video_set_drvdata(&dev->vdev, dev);
 381
 382        /* mute card - prevents noisy bootups */
 383        typhoon_mute(dev);
 384
 385        if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
 386                v4l2_device_unregister(&dev->v4l2_dev);
 387                release_region(dev->io, 8);
 388                return -EINVAL;
 389        }
 390        v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io);
 391        v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq);
 392
 393        return 0;
 394}
 395
 396static void __exit typhoon_exit(void)
 397{
 398        struct typhoon *dev = &typhoon_card;
 399
 400        video_unregister_device(&dev->vdev);
 401        v4l2_device_unregister(&dev->v4l2_dev);
 402        release_region(dev->io, 8);
 403}
 404
 405module_init(typhoon_init);
 406module_exit(typhoon_exit);
 407
 408