Path: blob/master/drivers/media/radio/radio-aimslab.c
15112 views
/* radiotrack (radioreveal) driver for Linux radio support1* (c) 1997 M. Kirkwood2* Converted to V4L2 API by Mauro Carvalho Chehab <[email protected]>3* Converted to new API by Alan Cox <[email protected]>4* Various bugfixes and enhancements by Russell Kroll <[email protected]>5*6* History:7* 1999-02-24 Russell Kroll <[email protected]>8* Fine tuning/VIDEO_TUNER_LOW9* Frequency range expanded to start at 87 MHz10*11* TODO: Allow for more than one of these foolish entities :-)12*13* Notes on the hardware (reverse engineered from other peoples'14* reverse engineering of AIMS' code :-)15*16* Frequency control is done digitally -- ie out(port,encodefreq(95.8));17*18* The signal strength query is unsurprisingly inaccurate. And it seems19* to indicate that (on my card, at least) the frequency setting isn't20* too great. (I have to tune up .025MHz from what the freq should be21* to get a report that the thing is tuned.)22*23* Volume control is (ugh) analogue:24* out(port, start_increasing_volume);25* wait(a_wee_while);26* out(port, stop_changing_the_volume);27*28*/2930#include <linux/module.h> /* Modules */31#include <linux/init.h> /* Initdata */32#include <linux/ioport.h> /* request_region */33#include <linux/delay.h> /* msleep */34#include <linux/videodev2.h> /* kernel radio structs */35#include <linux/version.h> /* for KERNEL_VERSION MACRO */36#include <linux/io.h> /* outb, outb_p */37#include <media/v4l2-device.h>38#include <media/v4l2-ioctl.h>3940MODULE_AUTHOR("M.Kirkwood");41MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");42MODULE_LICENSE("GPL");4344#ifndef CONFIG_RADIO_RTRACK_PORT45#define CONFIG_RADIO_RTRACK_PORT -146#endif4748static int io = CONFIG_RADIO_RTRACK_PORT;49static int radio_nr = -1;5051module_param(io, int, 0);52MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");53module_param(radio_nr, int, 0);5455#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)5657struct rtrack58{59struct v4l2_device v4l2_dev;60struct video_device vdev;61int port;62int curvol;63unsigned long curfreq;64int muted;65int io;66struct mutex lock;67};6869static struct rtrack rtrack_card;7071/* local things */7273static void rt_decvol(struct rtrack *rt)74{75outb(0x58, rt->io); /* volume down + sigstr + on */76msleep(100);77outb(0xd8, rt->io); /* volume steady + sigstr + on */78}7980static void rt_incvol(struct rtrack *rt)81{82outb(0x98, rt->io); /* volume up + sigstr + on */83msleep(100);84outb(0xd8, rt->io); /* volume steady + sigstr + on */85}8687static void rt_mute(struct rtrack *rt)88{89rt->muted = 1;90mutex_lock(&rt->lock);91outb(0xd0, rt->io); /* volume steady, off */92mutex_unlock(&rt->lock);93}9495static int rt_setvol(struct rtrack *rt, int vol)96{97int i;9899mutex_lock(&rt->lock);100101if (vol == rt->curvol) { /* requested volume = current */102if (rt->muted) { /* user is unmuting the card */103rt->muted = 0;104outb(0xd8, rt->io); /* enable card */105}106mutex_unlock(&rt->lock);107return 0;108}109110if (vol == 0) { /* volume = 0 means mute the card */111outb(0x48, rt->io); /* volume down but still "on" */112msleep(2000); /* make sure it's totally down */113outb(0xd0, rt->io); /* volume steady, off */114rt->curvol = 0; /* track the volume state! */115mutex_unlock(&rt->lock);116return 0;117}118119rt->muted = 0;120if (vol > rt->curvol)121for (i = rt->curvol; i < vol; i++)122rt_incvol(rt);123else124for (i = rt->curvol; i > vol; i--)125rt_decvol(rt);126127rt->curvol = vol;128mutex_unlock(&rt->lock);129return 0;130}131132/* the 128+64 on these outb's is to keep the volume stable while tuning133* without them, the volume _will_ creep up with each frequency change134* and bit 4 (+16) is to keep the signal strength meter enabled135*/136137static void send_0_byte(struct rtrack *rt)138{139if (rt->curvol == 0 || rt->muted) {140outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */141outb_p(128+64+16+2+1, rt->io); /* clock */142}143else {144outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */145outb_p(128+64+16+8+2+1, rt->io); /* clock */146}147msleep(1);148}149150static void send_1_byte(struct rtrack *rt)151{152if (rt->curvol == 0 || rt->muted) {153outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */154outb_p(128+64+16+4+2+1, rt->io); /* clock */155}156else {157outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */158outb_p(128+64+16+8+4+2+1, rt->io); /* clock */159}160161msleep(1);162}163164static int rt_setfreq(struct rtrack *rt, unsigned long freq)165{166int i;167168mutex_lock(&rt->lock); /* Stop other ops interfering */169170rt->curfreq = freq;171172/* now uses VIDEO_TUNER_LOW for fine tuning */173174freq += 171200; /* Add 10.7 MHz IF */175freq /= 800; /* Convert to 50 kHz units */176177send_0_byte(rt); /* 0: LSB of frequency */178179for (i = 0; i < 13; i++) /* : frequency bits (1-13) */180if (freq & (1 << i))181send_1_byte(rt);182else183send_0_byte(rt);184185send_0_byte(rt); /* 14: test bit - always 0 */186send_0_byte(rt); /* 15: test bit - always 0 */187188send_0_byte(rt); /* 16: band data 0 - always 0 */189send_0_byte(rt); /* 17: band data 1 - always 0 */190send_0_byte(rt); /* 18: band data 2 - always 0 */191send_0_byte(rt); /* 19: time base - always 0 */192193send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */194send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */195send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */196send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */197198if (rt->curvol == 0 || rt->muted)199outb(0xd0, rt->io); /* volume steady + sigstr */200else201outb(0xd8, rt->io); /* volume steady + sigstr + on */202203mutex_unlock(&rt->lock);204205return 0;206}207208static int rt_getsigstr(struct rtrack *rt)209{210int sig = 1;211212mutex_lock(&rt->lock);213if (inb(rt->io) & 2) /* bit set = no signal present */214sig = 0;215mutex_unlock(&rt->lock);216return sig;217}218219static int vidioc_querycap(struct file *file, void *priv,220struct v4l2_capability *v)221{222strlcpy(v->driver, "radio-aimslab", sizeof(v->driver));223strlcpy(v->card, "RadioTrack", sizeof(v->card));224strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));225v->version = RADIO_VERSION;226v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;227return 0;228}229230static int vidioc_g_tuner(struct file *file, void *priv,231struct v4l2_tuner *v)232{233struct rtrack *rt = video_drvdata(file);234235if (v->index > 0)236return -EINVAL;237238strlcpy(v->name, "FM", sizeof(v->name));239v->type = V4L2_TUNER_RADIO;240v->rangelow = 87 * 16000;241v->rangehigh = 108 * 16000;242v->rxsubchans = V4L2_TUNER_SUB_MONO;243v->capability = V4L2_TUNER_CAP_LOW;244v->audmode = V4L2_TUNER_MODE_MONO;245v->signal = 0xffff * rt_getsigstr(rt);246return 0;247}248249static int vidioc_s_tuner(struct file *file, void *priv,250struct v4l2_tuner *v)251{252return v->index ? -EINVAL : 0;253}254255static int vidioc_s_frequency(struct file *file, void *priv,256struct v4l2_frequency *f)257{258struct rtrack *rt = video_drvdata(file);259260if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)261return -EINVAL;262rt_setfreq(rt, f->frequency);263return 0;264}265266static int vidioc_g_frequency(struct file *file, void *priv,267struct v4l2_frequency *f)268{269struct rtrack *rt = video_drvdata(file);270271if (f->tuner != 0)272return -EINVAL;273f->type = V4L2_TUNER_RADIO;274f->frequency = rt->curfreq;275return 0;276}277278static int vidioc_queryctrl(struct file *file, void *priv,279struct v4l2_queryctrl *qc)280{281switch (qc->id) {282case V4L2_CID_AUDIO_MUTE:283return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);284case V4L2_CID_AUDIO_VOLUME:285return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);286}287return -EINVAL;288}289290static int vidioc_g_ctrl(struct file *file, void *priv,291struct v4l2_control *ctrl)292{293struct rtrack *rt = video_drvdata(file);294295switch (ctrl->id) {296case V4L2_CID_AUDIO_MUTE:297ctrl->value = rt->muted;298return 0;299case V4L2_CID_AUDIO_VOLUME:300ctrl->value = rt->curvol;301return 0;302}303return -EINVAL;304}305306static int vidioc_s_ctrl(struct file *file, void *priv,307struct v4l2_control *ctrl)308{309struct rtrack *rt = video_drvdata(file);310311switch (ctrl->id) {312case V4L2_CID_AUDIO_MUTE:313if (ctrl->value)314rt_mute(rt);315else316rt_setvol(rt, rt->curvol);317return 0;318case V4L2_CID_AUDIO_VOLUME:319rt_setvol(rt, ctrl->value);320return 0;321}322return -EINVAL;323}324325static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)326{327*i = 0;328return 0;329}330331static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)332{333return i ? -EINVAL : 0;334}335336static int vidioc_g_audio(struct file *file, void *priv,337struct v4l2_audio *a)338{339a->index = 0;340strlcpy(a->name, "Radio", sizeof(a->name));341a->capability = V4L2_AUDCAP_STEREO;342return 0;343}344345static int vidioc_s_audio(struct file *file, void *priv,346struct v4l2_audio *a)347{348return a->index ? -EINVAL : 0;349}350351static const struct v4l2_file_operations rtrack_fops = {352.owner = THIS_MODULE,353.unlocked_ioctl = video_ioctl2,354};355356static const struct v4l2_ioctl_ops rtrack_ioctl_ops = {357.vidioc_querycap = vidioc_querycap,358.vidioc_g_tuner = vidioc_g_tuner,359.vidioc_s_tuner = vidioc_s_tuner,360.vidioc_g_audio = vidioc_g_audio,361.vidioc_s_audio = vidioc_s_audio,362.vidioc_g_input = vidioc_g_input,363.vidioc_s_input = vidioc_s_input,364.vidioc_g_frequency = vidioc_g_frequency,365.vidioc_s_frequency = vidioc_s_frequency,366.vidioc_queryctrl = vidioc_queryctrl,367.vidioc_g_ctrl = vidioc_g_ctrl,368.vidioc_s_ctrl = vidioc_s_ctrl,369};370371static int __init rtrack_init(void)372{373struct rtrack *rt = &rtrack_card;374struct v4l2_device *v4l2_dev = &rt->v4l2_dev;375int res;376377strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name));378rt->io = io;379380if (rt->io == -1) {381v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n");382return -EINVAL;383}384385if (!request_region(rt->io, 2, "rtrack")) {386v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io);387return -EBUSY;388}389390res = v4l2_device_register(NULL, v4l2_dev);391if (res < 0) {392release_region(rt->io, 2);393v4l2_err(v4l2_dev, "could not register v4l2_device\n");394return res;395}396397strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name));398rt->vdev.v4l2_dev = v4l2_dev;399rt->vdev.fops = &rtrack_fops;400rt->vdev.ioctl_ops = &rtrack_ioctl_ops;401rt->vdev.release = video_device_release_empty;402video_set_drvdata(&rt->vdev, rt);403404/* Set up the I/O locking */405406mutex_init(&rt->lock);407408/* mute card - prevents noisy bootups */409410/* this ensures that the volume is all the way down */411outb(0x48, rt->io); /* volume down but still "on" */412msleep(2000); /* make sure it's totally down */413outb(0xc0, rt->io); /* steady volume, mute card */414415if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {416v4l2_device_unregister(&rt->v4l2_dev);417release_region(rt->io, 2);418return -EINVAL;419}420v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n");421422return 0;423}424425static void __exit rtrack_exit(void)426{427struct rtrack *rt = &rtrack_card;428429video_unregister_device(&rt->vdev);430v4l2_device_unregister(&rt->v4l2_dev);431release_region(rt->io, 2);432}433434module_init(rtrack_init);435module_exit(rtrack_exit);436437438439