linux/drivers/media/radio/radio-terratec.c
<<
>>
Prefs
   1/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
   2 * (c) 1999 R. Offermanns (rolf@offermanns.de)
   3 * based on the aimslab radio driver from M. Kirkwood
   4 * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
   5 *
   6 *
   7 * History:
   8 * 1999-05-21   First preview release
   9 *
  10 *  Notes on the hardware:
  11 *  There are two "main" chips on the card:
  12 *  - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
  13 *  - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
  14 *  (you can get the datasheet at the above links)
  15 *
  16 *  Frequency control is done digitally -- ie out(port,encodefreq(95.8));
  17 *  Volume Control is done digitally
  18 *
  19 *  there is a I2C controlled RDS decoder (SAA6588)  onboard, which i would like to support someday
  20 *  (as soon i have understand how to get started :)
  21 *  If you can help me out with that, please contact me!!
  22 *
  23 *
  24 * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
  25 */
  26
  27#include <linux/module.h>       /* Modules                      */
  28#include <linux/init.h>         /* Initdata                     */
  29#include <linux/ioport.h>       /* request_region               */
  30#include <linux/delay.h>        /* udelay                       */
  31#include <asm/io.h>             /* outb, outb_p                 */
  32#include <asm/uaccess.h>        /* copy to/from user            */
  33#include <linux/videodev2.h>    /* kernel radio structs         */
  34#include <media/v4l2-common.h>
  35#include <media/v4l2-ioctl.h>
  36#include <linux/spinlock.h>
  37
  38#include <linux/version.h>      /* for KERNEL_VERSION MACRO     */
  39#define RADIO_VERSION KERNEL_VERSION(0,0,2)
  40
  41static struct v4l2_queryctrl radio_qctrl[] = {
  42        {
  43                .id            = V4L2_CID_AUDIO_MUTE,
  44                .name          = "Mute",
  45                .minimum       = 0,
  46                .maximum       = 1,
  47                .default_value = 1,
  48                .type          = V4L2_CTRL_TYPE_BOOLEAN,
  49        },{
  50                .id            = V4L2_CID_AUDIO_VOLUME,
  51                .name          = "Volume",
  52                .minimum       = 0,
  53                .maximum       = 0xff,
  54                .step          = 1,
  55                .default_value = 0xff,
  56                .type          = V4L2_CTRL_TYPE_INTEGER,
  57        }
  58};
  59
  60#ifndef CONFIG_RADIO_TERRATEC_PORT
  61#define CONFIG_RADIO_TERRATEC_PORT 0x590
  62#endif
  63
  64/**************** this ones are for the terratec *******************/
  65#define BASEPORT        0x590
  66#define VOLPORT         0x591
  67#define WRT_DIS         0x00
  68#define CLK_OFF         0x00
  69#define IIC_DATA        0x01
  70#define IIC_CLK         0x02
  71#define DATA            0x04
  72#define CLK_ON          0x08
  73#define WRT_EN          0x10
  74/*******************************************************************/
  75
  76static int io = CONFIG_RADIO_TERRATEC_PORT;
  77static int radio_nr = -1;
  78static spinlock_t lock;
  79
  80struct tt_device
  81{
  82        unsigned long in_use;
  83        int port;
  84        int curvol;
  85        unsigned long curfreq;
  86        int muted;
  87};
  88
  89
  90/* local things */
  91
  92static void cardWriteVol(int volume)
  93{
  94        int i;
  95        volume = volume+(volume * 32); // change both channels
  96        spin_lock(&lock);
  97        for (i=0;i<8;i++)
  98        {
  99                if (volume & (0x80>>i))
 100                        outb(0x80, VOLPORT);
 101                else outb(0x00, VOLPORT);
 102        }
 103        spin_unlock(&lock);
 104}
 105
 106
 107
 108static void tt_mute(struct tt_device *dev)
 109{
 110        dev->muted = 1;
 111        cardWriteVol(0);
 112}
 113
 114static int tt_setvol(struct tt_device *dev, int vol)
 115{
 116
 117//      printk(KERN_ERR "setvol called, vol = %d\n", vol);
 118
 119        if(vol == dev->curvol) {        /* requested volume = current */
 120                if (dev->muted) {       /* user is unmuting the card  */
 121                        dev->muted = 0;
 122                        cardWriteVol(vol);      /* enable card */
 123                }
 124
 125                return 0;
 126        }
 127
 128        if(vol == 0) {                  /* volume = 0 means mute the card */
 129                cardWriteVol(0);        /* "turn off card" by setting vol to 0 */
 130                dev->curvol = vol;      /* track the volume state!      */
 131                return 0;
 132        }
 133
 134        dev->muted = 0;
 135
 136        cardWriteVol(vol);
 137
 138        dev->curvol = vol;
 139
 140        return 0;
 141
 142}
 143
 144
 145/* this is the worst part in this driver */
 146/* many more or less strange things are going on here, but hey, it works :) */
 147
 148static int tt_setfreq(struct tt_device *dev, unsigned long freq1)
 149{
 150        int freq;
 151        int i;
 152        int p;
 153        int  temp;
 154        long rest;
 155
 156        unsigned char buffer[25];               /* we have to bit shift 25 registers */
 157        freq = freq1/160;                       /* convert the freq. to a nice to handle value */
 158        for(i=24;i>-1;i--)
 159                buffer[i]=0;
 160
 161        rest = freq*10+10700;           /* i once had understood what is going on here */
 162                                        /* maybe some wise guy (friedhelm?) can comment this stuff */
 163        i=13;
 164        p=10;
 165        temp=102400;
 166        while (rest!=0)
 167        {
 168                if (rest%temp  == rest)
 169                        buffer[i] = 0;
 170                else
 171                {
 172                        buffer[i] = 1;
 173                        rest = rest-temp;
 174                }
 175                i--;
 176                p--;
 177                temp = temp/2;
 178        }
 179
 180        spin_lock(&lock);
 181
 182        for (i=24;i>-1;i--)                     /* bit shift the values to the radiocard */
 183        {
 184                if (buffer[i]==1)
 185                {
 186                        outb(WRT_EN|DATA, BASEPORT);
 187                        outb(WRT_EN|DATA|CLK_ON  , BASEPORT);
 188                        outb(WRT_EN|DATA, BASEPORT);
 189                }
 190                else
 191                {
 192                        outb(WRT_EN|0x00, BASEPORT);
 193                        outb(WRT_EN|0x00|CLK_ON  , BASEPORT);
 194                }
 195        }
 196        outb(0x00, BASEPORT);
 197
 198        spin_unlock(&lock);
 199
 200        return 0;
 201}
 202
 203static int tt_getsigstr(struct tt_device *dev)          /* TODO */
 204{
 205        if (inb(io) & 2)        /* bit set = no signal present  */
 206                return 0;
 207        return 1;               /* signal present               */
 208}
 209
 210static int vidioc_querycap(struct file *file, void *priv,
 211                                        struct v4l2_capability *v)
 212{
 213        strlcpy(v->driver, "radio-terratec", sizeof(v->driver));
 214        strlcpy(v->card, "ActiveRadio", sizeof(v->card));
 215        sprintf(v->bus_info, "ISA");
 216        v->version = RADIO_VERSION;
 217        v->capabilities = V4L2_CAP_TUNER;
 218        return 0;
 219}
 220
 221static int vidioc_g_tuner(struct file *file, void *priv,
 222                                        struct v4l2_tuner *v)
 223{
 224        struct tt_device *tt = video_drvdata(file);
 225
 226        if (v->index > 0)
 227                return -EINVAL;
 228
 229        strcpy(v->name, "FM");
 230        v->type = V4L2_TUNER_RADIO;
 231        v->rangelow = (87*16000);
 232        v->rangehigh = (108*16000);
 233        v->rxsubchans = V4L2_TUNER_SUB_MONO;
 234        v->capability = V4L2_TUNER_CAP_LOW;
 235        v->audmode = V4L2_TUNER_MODE_MONO;
 236        v->signal = 0xFFFF*tt_getsigstr(tt);
 237        return 0;
 238}
 239
 240static int vidioc_s_tuner(struct file *file, void *priv,
 241                                        struct v4l2_tuner *v)
 242{
 243        if (v->index > 0)
 244                return -EINVAL;
 245        return 0;
 246}
 247
 248static int vidioc_s_frequency(struct file *file, void *priv,
 249                                        struct v4l2_frequency *f)
 250{
 251        struct tt_device *tt = video_drvdata(file);
 252
 253        tt->curfreq = f->frequency;
 254        tt_setfreq(tt, tt->curfreq);
 255        return 0;
 256}
 257
 258static int vidioc_g_frequency(struct file *file, void *priv,
 259                                        struct v4l2_frequency *f)
 260{
 261        struct tt_device *tt = video_drvdata(file);
 262
 263        f->type = V4L2_TUNER_RADIO;
 264        f->frequency = tt->curfreq;
 265        return 0;
 266}
 267
 268static int vidioc_queryctrl(struct file *file, void *priv,
 269                                        struct v4l2_queryctrl *qc)
 270{
 271        int i;
 272
 273        for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
 274                if (qc->id && qc->id == radio_qctrl[i].id) {
 275                        memcpy(qc, &(radio_qctrl[i]),
 276                                                sizeof(*qc));
 277                        return 0;
 278                }
 279        }
 280        return -EINVAL;
 281}
 282
 283static int vidioc_g_ctrl(struct file *file, void *priv,
 284                                        struct v4l2_control *ctrl)
 285{
 286        struct tt_device *tt = video_drvdata(file);
 287
 288        switch (ctrl->id) {
 289        case V4L2_CID_AUDIO_MUTE:
 290                if (tt->muted)
 291                        ctrl->value = 1;
 292                else
 293                        ctrl->value = 0;
 294                return 0;
 295        case V4L2_CID_AUDIO_VOLUME:
 296                ctrl->value = tt->curvol * 6554;
 297                return 0;
 298        }
 299        return -EINVAL;
 300}
 301
 302static int vidioc_s_ctrl(struct file *file, void *priv,
 303                                        struct v4l2_control *ctrl)
 304{
 305        struct tt_device *tt = video_drvdata(file);
 306
 307        switch (ctrl->id) {
 308        case V4L2_CID_AUDIO_MUTE:
 309                if (ctrl->value)
 310                        tt_mute(tt);
 311                else
 312                        tt_setvol(tt,tt->curvol);
 313                return 0;
 314        case V4L2_CID_AUDIO_VOLUME:
 315                tt_setvol(tt,ctrl->value);
 316                return 0;
 317        }
 318        return -EINVAL;
 319}
 320
 321static int vidioc_g_audio(struct file *file, void *priv,
 322                                        struct v4l2_audio *a)
 323{
 324        if (a->index > 1)
 325                return -EINVAL;
 326
 327        strcpy(a->name, "Radio");
 328        a->capability = V4L2_AUDCAP_STEREO;
 329        return 0;
 330}
 331
 332static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
 333{
 334        *i = 0;
 335        return 0;
 336}
 337
 338static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
 339{
 340        if (i != 0)
 341                return -EINVAL;
 342        return 0;
 343}
 344
 345static int vidioc_s_audio(struct file *file, void *priv,
 346                                        struct v4l2_audio *a)
 347{
 348        if (a->index != 0)
 349                return -EINVAL;
 350        return 0;
 351}
 352
 353static struct tt_device terratec_unit;
 354
 355static int terratec_exclusive_open(struct inode *inode, struct file *file)
 356{
 357        return test_and_set_bit(0, &terratec_unit.in_use) ? -EBUSY : 0;
 358}
 359
 360static int terratec_exclusive_release(struct inode *inode, struct file *file)
 361{
 362        clear_bit(0, &terratec_unit.in_use);
 363        return 0;
 364}
 365
 366static const struct file_operations terratec_fops = {
 367        .owner          = THIS_MODULE,
 368        .open           = terratec_exclusive_open,
 369        .release        = terratec_exclusive_release,
 370        .ioctl          = video_ioctl2,
 371#ifdef CONFIG_COMPAT
 372        .compat_ioctl   = v4l_compat_ioctl32,
 373#endif
 374        .llseek         = no_llseek,
 375};
 376
 377static const struct v4l2_ioctl_ops terratec_ioctl_ops = {
 378        .vidioc_querycap    = vidioc_querycap,
 379        .vidioc_g_tuner     = vidioc_g_tuner,
 380        .vidioc_s_tuner     = vidioc_s_tuner,
 381        .vidioc_g_frequency = vidioc_g_frequency,
 382        .vidioc_s_frequency = vidioc_s_frequency,
 383        .vidioc_queryctrl   = vidioc_queryctrl,
 384        .vidioc_g_ctrl      = vidioc_g_ctrl,
 385        .vidioc_s_ctrl      = vidioc_s_ctrl,
 386        .vidioc_g_audio     = vidioc_g_audio,
 387        .vidioc_s_audio     = vidioc_s_audio,
 388        .vidioc_g_input     = vidioc_g_input,
 389        .vidioc_s_input     = vidioc_s_input,
 390};
 391
 392static struct video_device terratec_radio = {
 393        .name           = "TerraTec ActiveRadio",
 394        .fops           = &terratec_fops,
 395        .ioctl_ops      = &terratec_ioctl_ops,
 396        .release        = video_device_release_empty,
 397};
 398
 399static int __init terratec_init(void)
 400{
 401        if(io==-1)
 402        {
 403                printk(KERN_ERR "You must set an I/O address with io=0x???\n");
 404                return -EINVAL;
 405        }
 406        if (!request_region(io, 2, "terratec"))
 407        {
 408                printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io);
 409                return -EBUSY;
 410        }
 411
 412        video_set_drvdata(&terratec_radio, &terratec_unit);
 413
 414        spin_lock_init(&lock);
 415
 416        if (video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
 417                release_region(io,2);
 418                return -EINVAL;
 419        }
 420
 421        printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n");
 422
 423        /* mute card - prevents noisy bootups */
 424
 425        /* this ensures that the volume is all the way down  */
 426        cardWriteVol(0);
 427        terratec_unit.curvol = 0;
 428
 429        return 0;
 430}
 431
 432MODULE_AUTHOR("R.OFFERMANNS & others");
 433MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
 434MODULE_LICENSE("GPL");
 435module_param(io, int, 0);
 436MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
 437module_param(radio_nr, int, 0);
 438
 439static void __exit terratec_cleanup_module(void)
 440{
 441        video_unregister_device(&terratec_radio);
 442        release_region(io,2);
 443        printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n");
 444}
 445
 446module_init(terratec_init);
 447module_exit(terratec_cleanup_module);
 448
 449