linux/drivers/media/radio/radio-aimslab.c
<<
>>
Prefs
   1/* radiotrack (radioreveal) driver for Linux radio support
   2 * (c) 1997 M. Kirkwood
   3 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
   4 * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
   5 * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
   6 *
   7 * History:
   8 * 1999-02-24   Russell Kroll <rkroll@exploits.org>
   9 *              Fine tuning/VIDEO_TUNER_LOW
  10 *              Frequency range expanded to start at 87 MHz
  11 *
  12 * TODO: Allow for more than one of these foolish entities :-)
  13 *
  14 * Notes on the hardware (reverse engineered from other peoples'
  15 * reverse engineering of AIMS' code :-)
  16 *
  17 *  Frequency control is done digitally -- ie out(port,encodefreq(95.8));
  18 *
  19 *  The signal strength query is unsurprisingly inaccurate.  And it seems
  20 *  to indicate that (on my card, at least) the frequency setting isn't
  21 *  too great.  (I have to tune up .025MHz from what the freq should be
  22 *  to get a report that the thing is tuned.)
  23 *
  24 *  Volume control is (ugh) analogue:
  25 *   out(port, start_increasing_volume);
  26 *   wait(a_wee_while);
  27 *   out(port, stop_changing_the_volume);
  28 *
  29 */
  30
  31#include <linux/module.h>       /* Modules                      */
  32#include <linux/init.h>         /* Initdata                     */
  33#include <linux/ioport.h>       /* request_region               */
  34#include <linux/delay.h>        /* msleep                       */
  35#include <linux/videodev2.h>    /* kernel radio structs         */
  36#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
  37#include <linux/io.h>           /* outb, outb_p                 */
  38#include <media/v4l2-device.h>
  39#include <media/v4l2-ioctl.h>
  40
  41MODULE_AUTHOR("M.Kirkwood");
  42MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
  43MODULE_LICENSE("GPL");
  44
  45#ifndef CONFIG_RADIO_RTRACK_PORT
  46#define CONFIG_RADIO_RTRACK_PORT -1
  47#endif
  48
  49static int io = CONFIG_RADIO_RTRACK_PORT;
  50static int radio_nr = -1;
  51
  52module_param(io, int, 0);
  53MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");
  54module_param(radio_nr, int, 0);
  55
  56#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)
  57
  58struct rtrack
  59{
  60        struct v4l2_device v4l2_dev;
  61        struct video_device vdev;
  62        int port;
  63        int curvol;
  64        unsigned long curfreq;
  65        int muted;
  66        int io;
  67        struct mutex lock;
  68};
  69
  70static struct rtrack rtrack_card;
  71
  72/* local things */
  73
  74static void rt_decvol(struct rtrack *rt)
  75{
  76        outb(0x58, rt->io);             /* volume down + sigstr + on    */
  77        msleep(100);
  78        outb(0xd8, rt->io);             /* volume steady + sigstr + on  */
  79}
  80
  81static void rt_incvol(struct rtrack *rt)
  82{
  83        outb(0x98, rt->io);             /* volume up + sigstr + on      */
  84        msleep(100);
  85        outb(0xd8, rt->io);             /* volume steady + sigstr + on  */
  86}
  87
  88static void rt_mute(struct rtrack *rt)
  89{
  90        rt->muted = 1;
  91        mutex_lock(&rt->lock);
  92        outb(0xd0, rt->io);             /* volume steady, off           */
  93        mutex_unlock(&rt->lock);
  94}
  95
  96static int rt_setvol(struct rtrack *rt, int vol)
  97{
  98        int i;
  99
 100        mutex_lock(&rt->lock);
 101
 102        if (vol == rt->curvol) {        /* requested volume = current */
 103                if (rt->muted) {        /* user is unmuting the card  */
 104                        rt->muted = 0;
 105                        outb(0xd8, rt->io);     /* enable card */
 106                }
 107                mutex_unlock(&rt->lock);
 108                return 0;
 109        }
 110
 111        if (vol == 0) {                 /* volume = 0 means mute the card */
 112                outb(0x48, rt->io);     /* volume down but still "on"   */
 113                msleep(2000);   /* make sure it's totally down  */
 114                outb(0xd0, rt->io);     /* volume steady, off           */
 115                rt->curvol = 0;         /* track the volume state!      */
 116                mutex_unlock(&rt->lock);
 117                return 0;
 118        }
 119
 120        rt->muted = 0;
 121        if (vol > rt->curvol)
 122                for (i = rt->curvol; i < vol; i++)
 123                        rt_incvol(rt);
 124        else
 125                for (i = rt->curvol; i > vol; i--)
 126                        rt_decvol(rt);
 127
 128        rt->curvol = vol;
 129        mutex_unlock(&rt->lock);
 130        return 0;
 131}
 132
 133/* the 128+64 on these outb's is to keep the volume stable while tuning
 134 * without them, the volume _will_ creep up with each frequency change
 135 * and bit 4 (+16) is to keep the signal strength meter enabled
 136 */
 137
 138static void send_0_byte(struct rtrack *rt)
 139{
 140        if (rt->curvol == 0 || rt->muted) {
 141                outb_p(128+64+16+  1, rt->io);   /* wr-enable + data low */
 142                outb_p(128+64+16+2+1, rt->io);   /* clock */
 143        }
 144        else {
 145                outb_p(128+64+16+8+  1, rt->io);  /* on + wr-enable + data low */
 146                outb_p(128+64+16+8+2+1, rt->io);  /* clock */
 147        }
 148        msleep(1);
 149}
 150
 151static void send_1_byte(struct rtrack *rt)
 152{
 153        if (rt->curvol == 0 || rt->muted) {
 154                outb_p(128+64+16+4  +1, rt->io);   /* wr-enable+data high */
 155                outb_p(128+64+16+4+2+1, rt->io);   /* clock */
 156        }
 157        else {
 158                outb_p(128+64+16+8+4  +1, rt->io); /* on+wr-enable+data high */
 159                outb_p(128+64+16+8+4+2+1, rt->io); /* clock */
 160        }
 161
 162        msleep(1);
 163}
 164
 165static int rt_setfreq(struct rtrack *rt, unsigned long freq)
 166{
 167        int i;
 168
 169        mutex_lock(&rt->lock);                  /* Stop other ops interfering */
 170
 171        rt->curfreq = freq;
 172
 173        /* now uses VIDEO_TUNER_LOW for fine tuning */
 174
 175        freq += 171200;                 /* Add 10.7 MHz IF              */
 176        freq /= 800;                    /* Convert to 50 kHz units      */
 177
 178        send_0_byte(rt);                /*  0: LSB of frequency         */
 179
 180        for (i = 0; i < 13; i++)        /*   : frequency bits (1-13)    */
 181                if (freq & (1 << i))
 182                        send_1_byte(rt);
 183                else
 184                        send_0_byte(rt);
 185
 186        send_0_byte(rt);                /* 14: test bit - always 0    */
 187        send_0_byte(rt);                /* 15: test bit - always 0    */
 188
 189        send_0_byte(rt);                /* 16: band data 0 - always 0 */
 190        send_0_byte(rt);                /* 17: band data 1 - always 0 */
 191        send_0_byte(rt);                /* 18: band data 2 - always 0 */
 192        send_0_byte(rt);                /* 19: time base - always 0   */
 193
 194        send_0_byte(rt);                /* 20: spacing (0 = 25 kHz)   */
 195        send_1_byte(rt);                /* 21: spacing (1 = 25 kHz)   */
 196        send_0_byte(rt);                /* 22: spacing (0 = 25 kHz)   */
 197        send_1_byte(rt);                /* 23: AM/FM (FM = 1, always) */
 198
 199        if (rt->curvol == 0 || rt->muted)
 200                outb(0xd0, rt->io);     /* volume steady + sigstr */
 201        else
 202                outb(0xd8, rt->io);     /* volume steady + sigstr + on */
 203
 204        mutex_unlock(&rt->lock);
 205
 206        return 0;
 207}
 208
 209static int rt_getsigstr(struct rtrack *rt)
 210{
 211        int sig = 1;
 212
 213        mutex_lock(&rt->lock);
 214        if (inb(rt->io) & 2)    /* bit set = no signal present  */
 215                sig = 0;
 216        mutex_unlock(&rt->lock);
 217        return sig;
 218}
 219
 220static int vidioc_querycap(struct file *file, void  *priv,
 221                                        struct v4l2_capability *v)
 222{
 223        strlcpy(v->driver, "radio-aimslab", sizeof(v->driver));
 224        strlcpy(v->card, "RadioTrack", sizeof(v->card));
 225        strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
 226        v->version = RADIO_VERSION;
 227        v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
 228        return 0;
 229}
 230
 231static int vidioc_g_tuner(struct file *file, void *priv,
 232                                        struct v4l2_tuner *v)
 233{
 234        struct rtrack *rt = video_drvdata(file);
 235
 236        if (v->index > 0)
 237                return -EINVAL;
 238
 239        strlcpy(v->name, "FM", sizeof(v->name));
 240        v->type = V4L2_TUNER_RADIO;
 241        v->rangelow = 87 * 16000;
 242        v->rangehigh = 108 * 16000;
 243        v->rxsubchans = V4L2_TUNER_SUB_MONO;
 244        v->capability = V4L2_TUNER_CAP_LOW;
 245        v->audmode = V4L2_TUNER_MODE_MONO;
 246        v->signal = 0xffff * rt_getsigstr(rt);
 247        return 0;
 248}
 249
 250static int vidioc_s_tuner(struct file *file, void *priv,
 251                                        struct v4l2_tuner *v)
 252{
 253        return v->index ? -EINVAL : 0;
 254}
 255
 256static int vidioc_s_frequency(struct file *file, void *priv,
 257                                        struct v4l2_frequency *f)
 258{
 259        struct rtrack *rt = video_drvdata(file);
 260
 261        if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
 262                return -EINVAL;
 263        rt_setfreq(rt, f->frequency);
 264        return 0;
 265}
 266
 267static int vidioc_g_frequency(struct file *file, void *priv,
 268                                        struct v4l2_frequency *f)
 269{
 270        struct rtrack *rt = video_drvdata(file);
 271
 272        if (f->tuner != 0)
 273                return -EINVAL;
 274        f->type = V4L2_TUNER_RADIO;
 275        f->frequency = rt->curfreq;
 276        return 0;
 277}
 278
 279static int vidioc_queryctrl(struct file *file, void *priv,
 280                                        struct v4l2_queryctrl *qc)
 281{
 282        switch (qc->id) {
 283        case V4L2_CID_AUDIO_MUTE:
 284                return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
 285        case V4L2_CID_AUDIO_VOLUME:
 286                return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);
 287        }
 288        return -EINVAL;
 289}
 290
 291static int vidioc_g_ctrl(struct file *file, void *priv,
 292                                        struct v4l2_control *ctrl)
 293{
 294        struct rtrack *rt = video_drvdata(file);
 295
 296        switch (ctrl->id) {
 297        case V4L2_CID_AUDIO_MUTE:
 298                ctrl->value = rt->muted;
 299                return 0;
 300        case V4L2_CID_AUDIO_VOLUME:
 301                ctrl->value = rt->curvol;
 302                return 0;
 303        }
 304        return -EINVAL;
 305}
 306
 307static int vidioc_s_ctrl(struct file *file, void *priv,
 308                                        struct v4l2_control *ctrl)
 309{
 310        struct rtrack *rt = video_drvdata(file);
 311
 312        switch (ctrl->id) {
 313        case V4L2_CID_AUDIO_MUTE:
 314                if (ctrl->value)
 315                        rt_mute(rt);
 316                else
 317                        rt_setvol(rt, rt->curvol);
 318                return 0;
 319        case V4L2_CID_AUDIO_VOLUME:
 320                rt_setvol(rt, ctrl->value);
 321                return 0;
 322        }
 323        return -EINVAL;
 324}
 325
 326static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
 327{
 328        *i = 0;
 329        return 0;
 330}
 331
 332static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
 333{
 334        return i ? -EINVAL : 0;
 335}
 336
 337static int vidioc_g_audio(struct file *file, void *priv,
 338                                        struct v4l2_audio *a)
 339{
 340        a->index = 0;
 341        strlcpy(a->name, "Radio", sizeof(a->name));
 342        a->capability = V4L2_AUDCAP_STEREO;
 343        return 0;
 344}
 345
 346static int vidioc_s_audio(struct file *file, void *priv,
 347                                        struct v4l2_audio *a)
 348{
 349        return a->index ? -EINVAL : 0;
 350}
 351
 352static const struct v4l2_file_operations rtrack_fops = {
 353        .owner          = THIS_MODULE,
 354        .unlocked_ioctl = video_ioctl2,
 355};
 356
 357static const struct v4l2_ioctl_ops rtrack_ioctl_ops = {
 358        .vidioc_querycap    = vidioc_querycap,
 359        .vidioc_g_tuner     = vidioc_g_tuner,
 360        .vidioc_s_tuner     = vidioc_s_tuner,
 361        .vidioc_g_audio     = vidioc_g_audio,
 362        .vidioc_s_audio     = vidioc_s_audio,
 363        .vidioc_g_input     = vidioc_g_input,
 364        .vidioc_s_input     = vidioc_s_input,
 365        .vidioc_g_frequency = vidioc_g_frequency,
 366        .vidioc_s_frequency = vidioc_s_frequency,
 367        .vidioc_queryctrl   = vidioc_queryctrl,
 368        .vidioc_g_ctrl      = vidioc_g_ctrl,
 369        .vidioc_s_ctrl      = vidioc_s_ctrl,
 370};
 371
 372static int __init rtrack_init(void)
 373{
 374        struct rtrack *rt = &rtrack_card;
 375        struct v4l2_device *v4l2_dev = &rt->v4l2_dev;
 376        int res;
 377
 378        strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name));
 379        rt->io = io;
 380
 381        if (rt->io == -1) {
 382                v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n");
 383                return -EINVAL;
 384        }
 385
 386        if (!request_region(rt->io, 2, "rtrack")) {
 387                v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io);
 388                return -EBUSY;
 389        }
 390
 391        res = v4l2_device_register(NULL, v4l2_dev);
 392        if (res < 0) {
 393                release_region(rt->io, 2);
 394                v4l2_err(v4l2_dev, "could not register v4l2_device\n");
 395                return res;
 396        }
 397
 398        strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name));
 399        rt->vdev.v4l2_dev = v4l2_dev;
 400        rt->vdev.fops = &rtrack_fops;
 401        rt->vdev.ioctl_ops = &rtrack_ioctl_ops;
 402        rt->vdev.release = video_device_release_empty;
 403        video_set_drvdata(&rt->vdev, rt);
 404
 405        /* Set up the I/O locking */
 406
 407        mutex_init(&rt->lock);
 408
 409        /* mute card - prevents noisy bootups */
 410
 411        /* this ensures that the volume is all the way down  */
 412        outb(0x48, rt->io);             /* volume down but still "on"   */
 413        msleep(2000);   /* make sure it's totally down  */
 414        outb(0xc0, rt->io);             /* steady volume, mute card     */
 415
 416        if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
 417                v4l2_device_unregister(&rt->v4l2_dev);
 418                release_region(rt->io, 2);
 419                return -EINVAL;
 420        }
 421        v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n");
 422
 423        return 0;
 424}
 425
 426static void __exit rtrack_exit(void)
 427{
 428        struct rtrack *rt = &rtrack_card;
 429
 430        video_unregister_device(&rt->vdev);
 431        v4l2_device_unregister(&rt->v4l2_dev);
 432        release_region(rt->io, 2);
 433}
 434
 435module_init(rtrack_init);
 436module_exit(rtrack_exit);
 437
 438