linux/drivers/media/radio/radio-zoltrix.c
<<
>>
Prefs
   1/* zoltrix radio plus driver for Linux radio support
   2 * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
   3 *
   4 * BUGS
   5 *  Due to the inconsistency in reading from the signal flags
   6 *  it is difficult to get an accurate tuned signal.
   7 *
   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.
  11 *
  12 *  Some code derived from code by Romolo Manfredini
  13 *                                 romolo@bicnet.it
  14 *
  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
  27 *
  28 * 2006-07-24 - Converted to V4L2 API
  29 *              by Mauro Carvalho Chehab <mchehab@infradead.org>
  30 */
  31
  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 <linux/videodev2.h>    /* kernel radio structs           */
  37#include <linux/mutex.h>
  38#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
  39#include <linux/io.h>           /* outb, outb_p                   */
  40#include <media/v4l2-device.h>
  41#include <media/v4l2-ioctl.h>
  42
  43MODULE_AUTHOR("C.van Schaik");
  44MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
  45MODULE_LICENSE("GPL");
  46
  47#ifndef CONFIG_RADIO_ZOLTRIX_PORT
  48#define CONFIG_RADIO_ZOLTRIX_PORT -1
  49#endif
  50
  51static int io = CONFIG_RADIO_ZOLTRIX_PORT;
  52static int radio_nr = -1;
  53
  54module_param(io, int, 0);
  55MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
  56module_param(radio_nr, int, 0);
  57
  58#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
  59
  60struct zoltrix {
  61        struct v4l2_device v4l2_dev;
  62        struct video_device vdev;
  63        int io;
  64        int curvol;
  65        unsigned long curfreq;
  66        int muted;
  67        unsigned int stereo;
  68        struct mutex lock;
  69};
  70
  71static struct zoltrix zoltrix_card;
  72
  73static int zol_setvol(struct zoltrix *zol, int vol)
  74{
  75        zol->curvol = vol;
  76        if (zol->muted)
  77                return 0;
  78
  79        mutex_lock(&zol->lock);
  80        if (vol == 0) {
  81                outb(0, zol->io);
  82                outb(0, zol->io);
  83                inb(zol->io + 3);    /* Zoltrix needs to be read to confirm */
  84                mutex_unlock(&zol->lock);
  85                return 0;
  86        }
  87
  88        outb(zol->curvol-1, zol->io);
  89        msleep(10);
  90        inb(zol->io + 2);
  91        mutex_unlock(&zol->lock);
  92        return 0;
  93}
  94
  95static void zol_mute(struct zoltrix *zol)
  96{
  97        zol->muted = 1;
  98        mutex_lock(&zol->lock);
  99        outb(0, zol->io);
 100        outb(0, zol->io);
 101        inb(zol->io + 3);            /* Zoltrix needs to be read to confirm */
 102        mutex_unlock(&zol->lock);
 103}
 104
 105static void zol_unmute(struct zoltrix *zol)
 106{
 107        zol->muted = 0;
 108        zol_setvol(zol, zol->curvol);
 109}
 110
 111static int zol_setfreq(struct zoltrix *zol, unsigned long freq)
 112{
 113        /* tunes the radio to the desired frequency */
 114        struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
 115        unsigned long long bitmask, f, m;
 116        unsigned int stereo = zol->stereo;
 117        int i;
 118
 119        if (freq == 0) {
 120                v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n");
 121                return -EINVAL;
 122        }
 123
 124        m = (freq / 160 - 8800) * 2;
 125        f = (unsigned long long)m + 0x4d1c;
 126
 127        bitmask = 0xc480402c10080000ull;
 128        i = 45;
 129
 130        mutex_lock(&zol->lock);
 131
 132        zol->curfreq = freq;
 133
 134        outb(0, zol->io);
 135        outb(0, zol->io);
 136        inb(zol->io + 3);            /* Zoltrix needs to be read to confirm */
 137
 138        outb(0x40, zol->io);
 139        outb(0xc0, zol->io);
 140
 141        bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));
 142        while (i--) {
 143                if ((bitmask & 0x8000000000000000ull) != 0) {
 144                        outb(0x80, zol->io);
 145                        udelay(50);
 146                        outb(0x00, zol->io);
 147                        udelay(50);
 148                        outb(0x80, zol->io);
 149                        udelay(50);
 150                } else {
 151                        outb(0xc0, zol->io);
 152                        udelay(50);
 153                        outb(0x40, zol->io);
 154                        udelay(50);
 155                        outb(0xc0, zol->io);
 156                        udelay(50);
 157                }
 158                bitmask *= 2;
 159        }
 160        /* termination sequence */
 161        outb(0x80, zol->io);
 162        outb(0xc0, zol->io);
 163        outb(0x40, zol->io);
 164        udelay(1000);
 165        inb(zol->io + 2);
 166
 167        udelay(1000);
 168
 169        if (zol->muted) {
 170                outb(0, zol->io);
 171                outb(0, zol->io);
 172                inb(zol->io + 3);
 173                udelay(1000);
 174        }
 175
 176        mutex_unlock(&zol->lock);
 177
 178        if (!zol->muted)
 179                zol_setvol(zol, zol->curvol);
 180        return 0;
 181}
 182
 183/* Get signal strength */
 184static int zol_getsigstr(struct zoltrix *zol)
 185{
 186        int a, b;
 187
 188        mutex_lock(&zol->lock);
 189        outb(0x00, zol->io);         /* This stuff I found to do nothing */
 190        outb(zol->curvol, zol->io);
 191        msleep(20);
 192
 193        a = inb(zol->io);
 194        msleep(10);
 195        b = inb(zol->io);
 196
 197        mutex_unlock(&zol->lock);
 198
 199        if (a != b)
 200                return 0;
 201
 202        /* I found this out by playing with a binary scanner on the card io */
 203        return a == 0xcf || a == 0xdf || a == 0xef;
 204}
 205
 206static int zol_is_stereo(struct zoltrix *zol)
 207{
 208        int x1, x2;
 209
 210        mutex_lock(&zol->lock);
 211
 212        outb(0x00, zol->io);
 213        outb(zol->curvol, zol->io);
 214        msleep(20);
 215
 216        x1 = inb(zol->io);
 217        msleep(10);
 218        x2 = inb(zol->io);
 219
 220        mutex_unlock(&zol->lock);
 221
 222        return x1 == x2 && x1 == 0xcf;
 223}
 224
 225static int vidioc_querycap(struct file *file, void  *priv,
 226                                        struct v4l2_capability *v)
 227{
 228        strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));
 229        strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));
 230        strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
 231        v->version = RADIO_VERSION;
 232        v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
 233        return 0;
 234}
 235
 236static int vidioc_g_tuner(struct file *file, void *priv,
 237                                        struct v4l2_tuner *v)
 238{
 239        struct zoltrix *zol = video_drvdata(file);
 240
 241        if (v->index > 0)
 242                return -EINVAL;
 243
 244        strlcpy(v->name, "FM", sizeof(v->name));
 245        v->type = V4L2_TUNER_RADIO;
 246        v->rangelow = 88 * 16000;
 247        v->rangehigh = 108 * 16000;
 248        v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
 249        v->capability = V4L2_TUNER_CAP_LOW;
 250        if (zol_is_stereo(zol))
 251                v->audmode = V4L2_TUNER_MODE_STEREO;
 252        else
 253                v->audmode = V4L2_TUNER_MODE_MONO;
 254        v->signal = 0xFFFF * zol_getsigstr(zol);
 255        return 0;
 256}
 257
 258static int vidioc_s_tuner(struct file *file, void *priv,
 259                                        struct v4l2_tuner *v)
 260{
 261        return v->index ? -EINVAL : 0;
 262}
 263
 264static int vidioc_s_frequency(struct file *file, void *priv,
 265                                        struct v4l2_frequency *f)
 266{
 267        struct zoltrix *zol = video_drvdata(file);
 268
 269        if (zol_setfreq(zol, f->frequency) != 0)
 270                return -EINVAL;
 271        return 0;
 272}
 273
 274static int vidioc_g_frequency(struct file *file, void *priv,
 275                                        struct v4l2_frequency *f)
 276{
 277        struct zoltrix *zol = video_drvdata(file);
 278
 279        f->type = V4L2_TUNER_RADIO;
 280        f->frequency = zol->curfreq;
 281        return 0;
 282}
 283
 284static int vidioc_queryctrl(struct file *file, void *priv,
 285                                        struct v4l2_queryctrl *qc)
 286{
 287        switch (qc->id) {
 288        case V4L2_CID_AUDIO_MUTE:
 289                return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
 290        case V4L2_CID_AUDIO_VOLUME:
 291                return v4l2_ctrl_query_fill(qc, 0, 65535, 4096, 65535);
 292        }
 293        return -EINVAL;
 294}
 295
 296static int vidioc_g_ctrl(struct file *file, void *priv,
 297                                struct v4l2_control *ctrl)
 298{
 299        struct zoltrix *zol = video_drvdata(file);
 300
 301        switch (ctrl->id) {
 302        case V4L2_CID_AUDIO_MUTE:
 303                ctrl->value = zol->muted;
 304                return 0;
 305        case V4L2_CID_AUDIO_VOLUME:
 306                ctrl->value = zol->curvol * 4096;
 307                return 0;
 308        }
 309        return -EINVAL;
 310}
 311
 312static int vidioc_s_ctrl(struct file *file, void *priv,
 313                                struct v4l2_control *ctrl)
 314{
 315        struct zoltrix *zol = video_drvdata(file);
 316
 317        switch (ctrl->id) {
 318        case V4L2_CID_AUDIO_MUTE:
 319                if (ctrl->value)
 320                        zol_mute(zol);
 321                else {
 322                        zol_unmute(zol);
 323                        zol_setvol(zol, zol->curvol);
 324                }
 325                return 0;
 326        case V4L2_CID_AUDIO_VOLUME:
 327                zol_setvol(zol, ctrl->value / 4096);
 328                return 0;
 329        }
 330        zol->stereo = 1;
 331        if (zol_setfreq(zol, zol->curfreq) != 0)
 332                return -EINVAL;
 333#if 0
 334/* FIXME: Implement stereo/mono switch on V4L2 */
 335        if (v->mode & VIDEO_SOUND_STEREO) {
 336                zol->stereo = 1;
 337                zol_setfreq(zol, zol->curfreq);
 338        }
 339        if (v->mode & VIDEO_SOUND_MONO) {
 340                zol->stereo = 0;
 341                zol_setfreq(zol, zol->curfreq);
 342        }
 343#endif
 344        return -EINVAL;
 345}
 346
 347static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
 348{
 349        *i = 0;
 350        return 0;
 351}
 352
 353static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
 354{
 355        return i ? -EINVAL : 0;
 356}
 357
 358static int vidioc_g_audio(struct file *file, void *priv,
 359                                        struct v4l2_audio *a)
 360{
 361        a->index = 0;
 362        strlcpy(a->name, "Radio", sizeof(a->name));
 363        a->capability = V4L2_AUDCAP_STEREO;
 364        return 0;
 365}
 366
 367static int vidioc_s_audio(struct file *file, void *priv,
 368                                        struct v4l2_audio *a)
 369{
 370        return a->index ? -EINVAL : 0;
 371}
 372
 373static const struct v4l2_file_operations zoltrix_fops =
 374{
 375        .owner          = THIS_MODULE,
 376        .ioctl          = video_ioctl2,
 377};
 378
 379static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = {
 380        .vidioc_querycap    = vidioc_querycap,
 381        .vidioc_g_tuner     = vidioc_g_tuner,
 382        .vidioc_s_tuner     = vidioc_s_tuner,
 383        .vidioc_g_audio     = vidioc_g_audio,
 384        .vidioc_s_audio     = vidioc_s_audio,
 385        .vidioc_g_input     = vidioc_g_input,
 386        .vidioc_s_input     = vidioc_s_input,
 387        .vidioc_g_frequency = vidioc_g_frequency,
 388        .vidioc_s_frequency = vidioc_s_frequency,
 389        .vidioc_queryctrl   = vidioc_queryctrl,
 390        .vidioc_g_ctrl      = vidioc_g_ctrl,
 391        .vidioc_s_ctrl      = vidioc_s_ctrl,
 392};
 393
 394static int __init zoltrix_init(void)
 395{
 396        struct zoltrix *zol = &zoltrix_card;
 397        struct v4l2_device *v4l2_dev = &zol->v4l2_dev;
 398        int res;
 399
 400        strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name));
 401        zol->io = io;
 402        if (zol->io == -1) {
 403                v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n");
 404                return -EINVAL;
 405        }
 406        if (zol->io != 0x20c && zol->io != 0x30c) {
 407                v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n");
 408                return -ENXIO;
 409        }
 410
 411        if (!request_region(zol->io, 2, "zoltrix")) {
 412                v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io);
 413                return -EBUSY;
 414        }
 415
 416        res = v4l2_device_register(NULL, v4l2_dev);
 417        if (res < 0) {
 418                release_region(zol->io, 2);
 419                v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
 420                return res;
 421        }
 422
 423        strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name));
 424        zol->vdev.v4l2_dev = v4l2_dev;
 425        zol->vdev.fops = &zoltrix_fops;
 426        zol->vdev.ioctl_ops = &zoltrix_ioctl_ops;
 427        zol->vdev.release = video_device_release_empty;
 428        video_set_drvdata(&zol->vdev, zol);
 429
 430        if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
 431                v4l2_device_unregister(v4l2_dev);
 432                release_region(zol->io, 2);
 433                return -EINVAL;
 434        }
 435        v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n");
 436
 437        mutex_init(&zol->lock);
 438
 439        /* mute card - prevents noisy bootups */
 440
 441        /* this ensures that the volume is all the way down  */
 442
 443        outb(0, zol->io);
 444        outb(0, zol->io);
 445        msleep(20);
 446        inb(zol->io + 3);
 447
 448        zol->curvol = 0;
 449        zol->stereo = 1;
 450
 451        return 0;
 452}
 453
 454static void __exit zoltrix_exit(void)
 455{
 456        struct zoltrix *zol = &zoltrix_card;
 457
 458        video_unregister_device(&zol->vdev);
 459        v4l2_device_unregister(&zol->v4l2_dev);
 460        release_region(zol->io, 2);
 461}
 462
 463module_init(zoltrix_init);
 464module_exit(zoltrix_exit);
 465
 466