Path: blob/master/drivers/media/radio/radio-cadet.c
15111 views
/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card1*2* by Fred Gleason <[email protected]>3* Version 0.3.34*5* (Loosely) based on code for the Aztech radio card by6*7* Russell Kroll ([email protected])8* Quay Ly9* Donald Song10* Jason Lewis ([email protected])11* Scott McGrath ([email protected])12* William McGrath ([email protected])13*14* History:15* 2000-04-29 Russell Kroll <[email protected]>16* Added ISAPnP detection for Linux 2.3/2.417*18* 2001-01-10 Russell Kroll <[email protected]>19* Removed dead CONFIG_RADIO_CADET_PORT code20* PnP detection on load is now default (no args necessary)21*22* 2002-01-17 Adam Belay <[email protected]>23* Updated to latest pnp code24*25* 2003-01-31 Alan Cox <[email protected]>26* Cleaned up locking, delay code, general odds and ends27*28* 2006-07-30 Hans J. Koch <[email protected]>29* Changed API to V4L230*/3132#include <linux/version.h>33#include <linux/module.h> /* Modules */34#include <linux/init.h> /* Initdata */35#include <linux/ioport.h> /* request_region */36#include <linux/delay.h> /* udelay */37#include <linux/videodev2.h> /* V4L2 API defs */38#include <linux/param.h>39#include <linux/pnp.h>40#include <linux/sched.h>41#include <linux/io.h> /* outb, outb_p */42#include <media/v4l2-device.h>43#include <media/v4l2-ioctl.h>4445MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");46MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");47MODULE_LICENSE("GPL");4849static int io = -1; /* default to isapnp activation */50static int radio_nr = -1;5152module_param(io, int, 0);53MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");54module_param(radio_nr, int, 0);5556#define CADET_VERSION KERNEL_VERSION(0, 3, 3)5758#define RDS_BUFFER 25659#define RDS_RX_FLAG 160#define MBS_RX_FLAG 26162struct cadet {63struct v4l2_device v4l2_dev;64struct video_device vdev;65int io;66int users;67int curtuner;68int tunestat;69int sigstrength;70wait_queue_head_t read_queue;71struct timer_list readtimer;72__u8 rdsin, rdsout, rdsstat;73unsigned char rdsbuf[RDS_BUFFER];74struct mutex lock;75int reading;76};7778static struct cadet cadet_card;7980/*81* Signal Strength Threshold Values82* The V4L API spec does not define any particular unit for the signal83* strength value. These values are in microvolts of RF at the tuner's input.84*/85static __u16 sigtable[2][4] = {86{ 5, 10, 30, 150 },87{ 28, 40, 63, 1000 }88};899091static int cadet_getstereo(struct cadet *dev)92{93int ret = V4L2_TUNER_SUB_MONO;9495if (dev->curtuner != 0) /* Only FM has stereo capability! */96return V4L2_TUNER_SUB_MONO;9798mutex_lock(&dev->lock);99outb(7, dev->io); /* Select tuner control */100if ((inb(dev->io + 1) & 0x40) == 0)101ret = V4L2_TUNER_SUB_STEREO;102mutex_unlock(&dev->lock);103return ret;104}105106static unsigned cadet_gettune(struct cadet *dev)107{108int curvol, i;109unsigned fifo = 0;110111/*112* Prepare for read113*/114115mutex_lock(&dev->lock);116117outb(7, dev->io); /* Select tuner control */118curvol = inb(dev->io + 1); /* Save current volume/mute setting */119outb(0x00, dev->io + 1); /* Ensure WRITE-ENABLE is LOW */120dev->tunestat = 0xffff;121122/*123* Read the shift register124*/125for (i = 0; i < 25; i++) {126fifo = (fifo << 1) | ((inb(dev->io + 1) >> 7) & 0x01);127if (i < 24) {128outb(0x01, dev->io + 1);129dev->tunestat &= inb(dev->io + 1);130outb(0x00, dev->io + 1);131}132}133134/*135* Restore volume/mute setting136*/137outb(curvol, dev->io + 1);138mutex_unlock(&dev->lock);139140return fifo;141}142143static unsigned cadet_getfreq(struct cadet *dev)144{145int i;146unsigned freq = 0, test, fifo = 0;147148/*149* Read current tuning150*/151fifo = cadet_gettune(dev);152153/*154* Convert to actual frequency155*/156if (dev->curtuner == 0) { /* FM */157test = 12500;158for (i = 0; i < 14; i++) {159if ((fifo & 0x01) != 0)160freq += test;161test = test << 1;162fifo = fifo >> 1;163}164freq -= 10700000; /* IF frequency is 10.7 MHz */165freq = (freq * 16) / 1000000; /* Make it 1/16 MHz */166}167if (dev->curtuner == 1) /* AM */168freq = ((fifo & 0x7fff) - 2010) * 16;169170return freq;171}172173static void cadet_settune(struct cadet *dev, unsigned fifo)174{175int i;176unsigned test;177178mutex_lock(&dev->lock);179180outb(7, dev->io); /* Select tuner control */181/*182* Write the shift register183*/184test = 0;185test = (fifo >> 23) & 0x02; /* Align data for SDO */186test |= 0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */187outb(7, dev->io); /* Select tuner control */188outb(test, dev->io + 1); /* Initialize for write */189for (i = 0; i < 25; i++) {190test |= 0x01; /* Toggle SCK High */191outb(test, dev->io + 1);192test &= 0xfe; /* Toggle SCK Low */193outb(test, dev->io + 1);194fifo = fifo << 1; /* Prepare the next bit */195test = 0x1c | ((fifo >> 23) & 0x02);196outb(test, dev->io + 1);197}198mutex_unlock(&dev->lock);199}200201static void cadet_setfreq(struct cadet *dev, unsigned freq)202{203unsigned fifo;204int i, j, test;205int curvol;206207/*208* Formulate a fifo command209*/210fifo = 0;211if (dev->curtuner == 0) { /* FM */212test = 102400;213freq = (freq * 1000) / 16; /* Make it kHz */214freq += 10700; /* IF is 10700 kHz */215for (i = 0; i < 14; i++) {216fifo = fifo << 1;217if (freq >= test) {218fifo |= 0x01;219freq -= test;220}221test = test >> 1;222}223}224if (dev->curtuner == 1) { /* AM */225fifo = (freq / 16) + 2010; /* Make it kHz */226fifo |= 0x100000; /* Select AM Band */227}228229/*230* Save current volume/mute setting231*/232233mutex_lock(&dev->lock);234outb(7, dev->io); /* Select tuner control */235curvol = inb(dev->io + 1);236mutex_unlock(&dev->lock);237238/*239* Tune the card240*/241for (j = 3; j > -1; j--) {242cadet_settune(dev, fifo | (j << 16));243244mutex_lock(&dev->lock);245outb(7, dev->io); /* Select tuner control */246outb(curvol, dev->io + 1);247mutex_unlock(&dev->lock);248249msleep(100);250251cadet_gettune(dev);252if ((dev->tunestat & 0x40) == 0) { /* Tuned */253dev->sigstrength = sigtable[dev->curtuner][j];254return;255}256}257dev->sigstrength = 0;258}259260261static int cadet_getvol(struct cadet *dev)262{263int ret = 0;264265mutex_lock(&dev->lock);266267outb(7, dev->io); /* Select tuner control */268if ((inb(dev->io + 1) & 0x20) != 0)269ret = 0xffff;270271mutex_unlock(&dev->lock);272return ret;273}274275276static void cadet_setvol(struct cadet *dev, int vol)277{278mutex_lock(&dev->lock);279outb(7, dev->io); /* Select tuner control */280if (vol > 0)281outb(0x20, dev->io + 1);282else283outb(0x00, dev->io + 1);284mutex_unlock(&dev->lock);285}286287static void cadet_handler(unsigned long data)288{289struct cadet *dev = (void *)data;290291/* Service the RDS fifo */292if (mutex_trylock(&dev->lock)) {293outb(0x3, dev->io); /* Select RDS Decoder Control */294if ((inb(dev->io + 1) & 0x20) != 0)295printk(KERN_CRIT "cadet: RDS fifo overflow\n");296outb(0x80, dev->io); /* Select RDS fifo */297while ((inb(dev->io) & 0x80) != 0) {298dev->rdsbuf[dev->rdsin] = inb(dev->io + 1);299if (dev->rdsin == dev->rdsout)300printk(KERN_WARNING "cadet: RDS buffer overflow\n");301else302dev->rdsin++;303}304mutex_unlock(&dev->lock);305}306307/*308* Service pending read309*/310if (dev->rdsin != dev->rdsout)311wake_up_interruptible(&dev->read_queue);312313/*314* Clean up and exit315*/316init_timer(&dev->readtimer);317dev->readtimer.function = cadet_handler;318dev->readtimer.data = (unsigned long)0;319dev->readtimer.expires = jiffies + msecs_to_jiffies(50);320add_timer(&dev->readtimer);321}322323324static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)325{326struct cadet *dev = video_drvdata(file);327unsigned char readbuf[RDS_BUFFER];328int i = 0;329330mutex_lock(&dev->lock);331if (dev->rdsstat == 0) {332dev->rdsstat = 1;333outb(0x80, dev->io); /* Select RDS fifo */334init_timer(&dev->readtimer);335dev->readtimer.function = cadet_handler;336dev->readtimer.data = (unsigned long)dev;337dev->readtimer.expires = jiffies + msecs_to_jiffies(50);338add_timer(&dev->readtimer);339}340if (dev->rdsin == dev->rdsout) {341mutex_unlock(&dev->lock);342if (file->f_flags & O_NONBLOCK)343return -EWOULDBLOCK;344interruptible_sleep_on(&dev->read_queue);345mutex_lock(&dev->lock);346}347while (i < count && dev->rdsin != dev->rdsout)348readbuf[i++] = dev->rdsbuf[dev->rdsout++];349mutex_unlock(&dev->lock);350351if (copy_to_user(data, readbuf, i))352return -EFAULT;353return i;354}355356357static int vidioc_querycap(struct file *file, void *priv,358struct v4l2_capability *v)359{360strlcpy(v->driver, "ADS Cadet", sizeof(v->driver));361strlcpy(v->card, "ADS Cadet", sizeof(v->card));362strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));363v->version = CADET_VERSION;364v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO |365V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE;366return 0;367}368369static int vidioc_g_tuner(struct file *file, void *priv,370struct v4l2_tuner *v)371{372struct cadet *dev = video_drvdata(file);373374v->type = V4L2_TUNER_RADIO;375switch (v->index) {376case 0:377strlcpy(v->name, "FM", sizeof(v->name));378v->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |379V4L2_TUNER_CAP_RDS_BLOCK_IO;380v->rangelow = 1400; /* 87.5 MHz */381v->rangehigh = 1728; /* 108.0 MHz */382v->rxsubchans = cadet_getstereo(dev);383switch (v->rxsubchans) {384case V4L2_TUNER_SUB_MONO:385v->audmode = V4L2_TUNER_MODE_MONO;386break;387case V4L2_TUNER_SUB_STEREO:388v->audmode = V4L2_TUNER_MODE_STEREO;389break;390default:391break;392}393v->rxsubchans |= V4L2_TUNER_SUB_RDS;394break;395case 1:396strlcpy(v->name, "AM", sizeof(v->name));397v->capability = V4L2_TUNER_CAP_LOW;398v->rangelow = 8320; /* 520 kHz */399v->rangehigh = 26400; /* 1650 kHz */400v->rxsubchans = V4L2_TUNER_SUB_MONO;401v->audmode = V4L2_TUNER_MODE_MONO;402break;403default:404return -EINVAL;405}406v->signal = dev->sigstrength; /* We might need to modify scaling of this */407return 0;408}409410static int vidioc_s_tuner(struct file *file, void *priv,411struct v4l2_tuner *v)412{413struct cadet *dev = video_drvdata(file);414415if (v->index != 0 && v->index != 1)416return -EINVAL;417dev->curtuner = v->index;418return 0;419}420421static int vidioc_g_frequency(struct file *file, void *priv,422struct v4l2_frequency *f)423{424struct cadet *dev = video_drvdata(file);425426f->tuner = dev->curtuner;427f->type = V4L2_TUNER_RADIO;428f->frequency = cadet_getfreq(dev);429return 0;430}431432433static int vidioc_s_frequency(struct file *file, void *priv,434struct v4l2_frequency *f)435{436struct cadet *dev = video_drvdata(file);437438if (f->type != V4L2_TUNER_RADIO)439return -EINVAL;440if (dev->curtuner == 0 && (f->frequency < 1400 || f->frequency > 1728))441return -EINVAL;442if (dev->curtuner == 1 && (f->frequency < 8320 || f->frequency > 26400))443return -EINVAL;444cadet_setfreq(dev, f->frequency);445return 0;446}447448static int vidioc_queryctrl(struct file *file, void *priv,449struct v4l2_queryctrl *qc)450{451switch (qc->id) {452case V4L2_CID_AUDIO_MUTE:453return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);454case V4L2_CID_AUDIO_VOLUME:455return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);456}457return -EINVAL;458}459460static int vidioc_g_ctrl(struct file *file, void *priv,461struct v4l2_control *ctrl)462{463struct cadet *dev = video_drvdata(file);464465switch (ctrl->id) {466case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */467ctrl->value = (cadet_getvol(dev) == 0);468break;469case V4L2_CID_AUDIO_VOLUME:470ctrl->value = cadet_getvol(dev);471break;472default:473return -EINVAL;474}475return 0;476}477478static int vidioc_s_ctrl(struct file *file, void *priv,479struct v4l2_control *ctrl)480{481struct cadet *dev = video_drvdata(file);482483switch (ctrl->id){484case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */485if (ctrl->value)486cadet_setvol(dev, 0);487else488cadet_setvol(dev, 0xffff);489break;490case V4L2_CID_AUDIO_VOLUME:491cadet_setvol(dev, ctrl->value);492break;493default:494return -EINVAL;495}496return 0;497}498499static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)500{501*i = 0;502return 0;503}504505static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)506{507return i ? -EINVAL : 0;508}509510static int vidioc_g_audio(struct file *file, void *priv,511struct v4l2_audio *a)512{513a->index = 0;514strlcpy(a->name, "Radio", sizeof(a->name));515a->capability = V4L2_AUDCAP_STEREO;516return 0;517}518519static int vidioc_s_audio(struct file *file, void *priv,520struct v4l2_audio *a)521{522return a->index ? -EINVAL : 0;523}524525static int cadet_open(struct file *file)526{527struct cadet *dev = video_drvdata(file);528529mutex_lock(&dev->lock);530dev->users++;531if (1 == dev->users)532init_waitqueue_head(&dev->read_queue);533mutex_unlock(&dev->lock);534return 0;535}536537static int cadet_release(struct file *file)538{539struct cadet *dev = video_drvdata(file);540541mutex_lock(&dev->lock);542dev->users--;543if (0 == dev->users) {544del_timer_sync(&dev->readtimer);545dev->rdsstat = 0;546}547mutex_unlock(&dev->lock);548return 0;549}550551static unsigned int cadet_poll(struct file *file, struct poll_table_struct *wait)552{553struct cadet *dev = video_drvdata(file);554555poll_wait(file, &dev->read_queue, wait);556if (dev->rdsin != dev->rdsout)557return POLLIN | POLLRDNORM;558return 0;559}560561562static const struct v4l2_file_operations cadet_fops = {563.owner = THIS_MODULE,564.open = cadet_open,565.release = cadet_release,566.read = cadet_read,567.unlocked_ioctl = video_ioctl2,568.poll = cadet_poll,569};570571static const struct v4l2_ioctl_ops cadet_ioctl_ops = {572.vidioc_querycap = vidioc_querycap,573.vidioc_g_tuner = vidioc_g_tuner,574.vidioc_s_tuner = vidioc_s_tuner,575.vidioc_g_frequency = vidioc_g_frequency,576.vidioc_s_frequency = vidioc_s_frequency,577.vidioc_queryctrl = vidioc_queryctrl,578.vidioc_g_ctrl = vidioc_g_ctrl,579.vidioc_s_ctrl = vidioc_s_ctrl,580.vidioc_g_audio = vidioc_g_audio,581.vidioc_s_audio = vidioc_s_audio,582.vidioc_g_input = vidioc_g_input,583.vidioc_s_input = vidioc_s_input,584};585586#ifdef CONFIG_PNP587588static struct pnp_device_id cadet_pnp_devices[] = {589/* ADS Cadet AM/FM Radio Card */590{.id = "MSM0c24", .driver_data = 0},591{.id = ""}592};593594MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);595596static int cadet_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)597{598if (!dev)599return -ENODEV;600/* only support one device */601if (io > 0)602return -EBUSY;603604if (!pnp_port_valid(dev, 0))605return -ENODEV;606607io = pnp_port_start(dev, 0);608609printk(KERN_INFO "radio-cadet: PnP reports device at %#x\n", io);610611return io;612}613614static struct pnp_driver cadet_pnp_driver = {615.name = "radio-cadet",616.id_table = cadet_pnp_devices,617.probe = cadet_pnp_probe,618.remove = NULL,619};620621#else622static struct pnp_driver cadet_pnp_driver;623#endif624625static void cadet_probe(struct cadet *dev)626{627static int iovals[8] = { 0x330, 0x332, 0x334, 0x336, 0x338, 0x33a, 0x33c, 0x33e };628int i;629630for (i = 0; i < 8; i++) {631dev->io = iovals[i];632if (request_region(dev->io, 2, "cadet-probe")) {633cadet_setfreq(dev, 1410);634if (cadet_getfreq(dev) == 1410) {635release_region(dev->io, 2);636return;637}638release_region(dev->io, 2);639}640}641dev->io = -1;642}643644/*645* io should only be set if the user has used something like646* isapnp (the userspace program) to initialize this card for us647*/648649static int __init cadet_init(void)650{651struct cadet *dev = &cadet_card;652struct v4l2_device *v4l2_dev = &dev->v4l2_dev;653int res;654655strlcpy(v4l2_dev->name, "cadet", sizeof(v4l2_dev->name));656mutex_init(&dev->lock);657658/* If a probe was requested then probe ISAPnP first (safest) */659if (io < 0)660pnp_register_driver(&cadet_pnp_driver);661dev->io = io;662663/* If that fails then probe unsafely if probe is requested */664if (dev->io < 0)665cadet_probe(dev);666667/* Else we bail out */668if (dev->io < 0) {669#ifdef MODULE670v4l2_err(v4l2_dev, "you must set an I/O address with io=0x330, 0x332, 0x334,\n");671v4l2_err(v4l2_dev, "0x336, 0x338, 0x33a, 0x33c or 0x33e\n");672#endif673goto fail;674}675if (!request_region(dev->io, 2, "cadet"))676goto fail;677678res = v4l2_device_register(NULL, v4l2_dev);679if (res < 0) {680release_region(dev->io, 2);681v4l2_err(v4l2_dev, "could not register v4l2_device\n");682goto fail;683}684685strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));686dev->vdev.v4l2_dev = v4l2_dev;687dev->vdev.fops = &cadet_fops;688dev->vdev.ioctl_ops = &cadet_ioctl_ops;689dev->vdev.release = video_device_release_empty;690video_set_drvdata(&dev->vdev, dev);691692if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {693v4l2_device_unregister(v4l2_dev);694release_region(dev->io, 2);695goto fail;696}697v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n", dev->io);698return 0;699fail:700pnp_unregister_driver(&cadet_pnp_driver);701return -ENODEV;702}703704static void __exit cadet_exit(void)705{706struct cadet *dev = &cadet_card;707708video_unregister_device(&dev->vdev);709v4l2_device_unregister(&dev->v4l2_dev);710release_region(dev->io, 2);711pnp_unregister_driver(&cadet_pnp_driver);712}713714module_init(cadet_init);715module_exit(cadet_exit);716717718719