Path: blob/master/drivers/media/radio/radio-typhoon.c
15112 views
/* Typhoon Radio Card driver for radio support1* (c) 1999 Dr. Henrik Seidel <[email protected]>2*3* Notes on the hardware4*5* This card has two output sockets, one for speakers and one for line.6* The speaker output has volume control, but only in four discrete7* steps. The line output has neither volume control nor mute.8*9* The card has auto-stereo according to its manual, although it all10* sounds mono to me (even with the Win/DOS drivers). Maybe it's my11* antenna - I really don't know for sure.12*13* Frequency control is done digitally.14*15* Volume control is done digitally, but there are only four different16* possible values. So you should better always turn the volume up and17* use line control. I got the best results by connecting line output18* to the sound card microphone input. For such a configuration the19* volume control has no effect, since volume control only influences20* the speaker output.21*22* There is no explicit mute/unmute. So I set the radio frequency to a23* value where I do expect just noise and turn the speaker volume down.24* The frequency change is necessary since the card never seems to be25* completely silent.26*27* Converted to V4L2 API by Mauro Carvalho Chehab <[email protected]>28*/2930#include <linux/module.h> /* Modules */31#include <linux/init.h> /* Initdata */32#include <linux/ioport.h> /* request_region */33#include <linux/version.h> /* for KERNEL_VERSION MACRO */34#include <linux/videodev2.h> /* kernel radio structs */35#include <linux/io.h> /* outb, outb_p */36#include <media/v4l2-device.h>37#include <media/v4l2-ioctl.h>3839MODULE_AUTHOR("Dr. Henrik Seidel");40MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");41MODULE_LICENSE("GPL");4243#ifndef CONFIG_RADIO_TYPHOON_PORT44#define CONFIG_RADIO_TYPHOON_PORT -145#endif4647#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ48#define CONFIG_RADIO_TYPHOON_MUTEFREQ 049#endif5051static int io = CONFIG_RADIO_TYPHOON_PORT;52static int radio_nr = -1;5354module_param(io, int, 0);55MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");5657module_param(radio_nr, int, 0);5859static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;60module_param(mutefreq, ulong, 0);61MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");6263#define RADIO_VERSION KERNEL_VERSION(0, 1, 1)6465#define BANNER "Typhoon Radio Card driver v0.1.1\n"6667struct typhoon {68struct v4l2_device v4l2_dev;69struct video_device vdev;70int io;71int curvol;72int muted;73unsigned long curfreq;74unsigned long mutefreq;75struct mutex lock;76};7778static struct typhoon typhoon_card;7980static void typhoon_setvol_generic(struct typhoon *dev, int vol)81{82mutex_lock(&dev->lock);83vol >>= 14; /* Map 16 bit to 2 bit */84vol &= 3;85outb_p(vol / 2, dev->io); /* Set the volume, high bit. */86outb_p(vol % 2, dev->io + 2); /* Set the volume, low bit. */87mutex_unlock(&dev->lock);88}8990static int typhoon_setfreq_generic(struct typhoon *dev,91unsigned long frequency)92{93unsigned long outval;94unsigned long x;9596/*97* The frequency transfer curve is not linear. The best fit I could98* get is99*100* outval = -155 + exp((f + 15.55) * 0.057))101*102* where frequency f is in MHz. Since we don't have exp in the kernel,103* I approximate this function by a third order polynomial.104*105*/106107mutex_lock(&dev->lock);108x = frequency / 160;109outval = (x * x + 2500) / 5000;110outval = (outval * x + 5000) / 10000;111outval -= (10 * x * x + 10433) / 20866;112outval += 4 * x - 11505;113114outb_p((outval >> 8) & 0x01, dev->io + 4);115outb_p(outval >> 9, dev->io + 6);116outb_p(outval & 0xff, dev->io + 8);117mutex_unlock(&dev->lock);118119return 0;120}121122static int typhoon_setfreq(struct typhoon *dev, unsigned long frequency)123{124typhoon_setfreq_generic(dev, frequency);125dev->curfreq = frequency;126return 0;127}128129static void typhoon_mute(struct typhoon *dev)130{131if (dev->muted == 1)132return;133typhoon_setvol_generic(dev, 0);134typhoon_setfreq_generic(dev, dev->mutefreq);135dev->muted = 1;136}137138static void typhoon_unmute(struct typhoon *dev)139{140if (dev->muted == 0)141return;142typhoon_setfreq_generic(dev, dev->curfreq);143typhoon_setvol_generic(dev, dev->curvol);144dev->muted = 0;145}146147static int typhoon_setvol(struct typhoon *dev, int vol)148{149if (dev->muted && vol != 0) { /* user is unmuting the card */150dev->curvol = vol;151typhoon_unmute(dev);152return 0;153}154if (vol == dev->curvol) /* requested volume == current */155return 0;156157if (vol == 0) { /* volume == 0 means mute the card */158typhoon_mute(dev);159dev->curvol = vol;160return 0;161}162typhoon_setvol_generic(dev, vol);163dev->curvol = vol;164return 0;165}166167static int vidioc_querycap(struct file *file, void *priv,168struct v4l2_capability *v)169{170strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));171strlcpy(v->card, "Typhoon Radio", sizeof(v->card));172strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));173v->version = RADIO_VERSION;174v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;175return 0;176}177178static int vidioc_g_tuner(struct file *file, void *priv,179struct v4l2_tuner *v)180{181if (v->index > 0)182return -EINVAL;183184strlcpy(v->name, "FM", sizeof(v->name));185v->type = V4L2_TUNER_RADIO;186v->rangelow = 87.5 * 16000;187v->rangehigh = 108 * 16000;188v->rxsubchans = V4L2_TUNER_SUB_MONO;189v->capability = V4L2_TUNER_CAP_LOW;190v->audmode = V4L2_TUNER_MODE_MONO;191v->signal = 0xFFFF; /* We can't get the signal strength */192return 0;193}194195static int vidioc_s_tuner(struct file *file, void *priv,196struct v4l2_tuner *v)197{198return v->index ? -EINVAL : 0;199}200201static int vidioc_g_frequency(struct file *file, void *priv,202struct v4l2_frequency *f)203{204struct typhoon *dev = video_drvdata(file);205206if (f->tuner != 0)207return -EINVAL;208f->type = V4L2_TUNER_RADIO;209f->frequency = dev->curfreq;210return 0;211}212213static int vidioc_s_frequency(struct file *file, void *priv,214struct v4l2_frequency *f)215{216struct typhoon *dev = video_drvdata(file);217218if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)219return -EINVAL;220dev->curfreq = f->frequency;221typhoon_setfreq(dev, dev->curfreq);222return 0;223}224225static int vidioc_queryctrl(struct file *file, void *priv,226struct v4l2_queryctrl *qc)227{228switch (qc->id) {229case V4L2_CID_AUDIO_MUTE:230return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);231case V4L2_CID_AUDIO_VOLUME:232return v4l2_ctrl_query_fill(qc, 0, 65535, 16384, 65535);233}234return -EINVAL;235}236237static int vidioc_g_ctrl(struct file *file, void *priv,238struct v4l2_control *ctrl)239{240struct typhoon *dev = video_drvdata(file);241242switch (ctrl->id) {243case V4L2_CID_AUDIO_MUTE:244ctrl->value = dev->muted;245return 0;246case V4L2_CID_AUDIO_VOLUME:247ctrl->value = dev->curvol;248return 0;249}250return -EINVAL;251}252253static int vidioc_s_ctrl (struct file *file, void *priv,254struct v4l2_control *ctrl)255{256struct typhoon *dev = video_drvdata(file);257258switch (ctrl->id) {259case V4L2_CID_AUDIO_MUTE:260if (ctrl->value)261typhoon_mute(dev);262else263typhoon_unmute(dev);264return 0;265case V4L2_CID_AUDIO_VOLUME:266typhoon_setvol(dev, ctrl->value);267return 0;268}269return -EINVAL;270}271272static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)273{274*i = 0;275return 0;276}277278static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)279{280return i ? -EINVAL : 0;281}282283static int vidioc_g_audio(struct file *file, void *priv,284struct v4l2_audio *a)285{286a->index = 0;287strlcpy(a->name, "Radio", sizeof(a->name));288a->capability = V4L2_AUDCAP_STEREO;289return 0;290}291292static int vidioc_s_audio(struct file *file, void *priv,293struct v4l2_audio *a)294{295return a->index ? -EINVAL : 0;296}297298static int vidioc_log_status(struct file *file, void *priv)299{300struct typhoon *dev = video_drvdata(file);301struct v4l2_device *v4l2_dev = &dev->v4l2_dev;302303v4l2_info(v4l2_dev, BANNER);304#ifdef MODULE305v4l2_info(v4l2_dev, "Load type: Driver loaded as a module\n\n");306#else307v4l2_info(v4l2_dev, "Load type: Driver compiled into kernel\n\n");308#endif309v4l2_info(v4l2_dev, "frequency = %lu kHz\n", dev->curfreq >> 4);310v4l2_info(v4l2_dev, "volume = %d\n", dev->curvol);311v4l2_info(v4l2_dev, "mute = %s\n", dev->muted ? "on" : "off");312v4l2_info(v4l2_dev, "io = 0x%x\n", dev->io);313v4l2_info(v4l2_dev, "mute frequency = %lu kHz\n", dev->mutefreq >> 4);314return 0;315}316317static const struct v4l2_file_operations typhoon_fops = {318.owner = THIS_MODULE,319.unlocked_ioctl = video_ioctl2,320};321322static const struct v4l2_ioctl_ops typhoon_ioctl_ops = {323.vidioc_log_status = vidioc_log_status,324.vidioc_querycap = vidioc_querycap,325.vidioc_g_tuner = vidioc_g_tuner,326.vidioc_s_tuner = vidioc_s_tuner,327.vidioc_g_audio = vidioc_g_audio,328.vidioc_s_audio = vidioc_s_audio,329.vidioc_g_input = vidioc_g_input,330.vidioc_s_input = vidioc_s_input,331.vidioc_g_frequency = vidioc_g_frequency,332.vidioc_s_frequency = vidioc_s_frequency,333.vidioc_queryctrl = vidioc_queryctrl,334.vidioc_g_ctrl = vidioc_g_ctrl,335.vidioc_s_ctrl = vidioc_s_ctrl,336};337338static int __init typhoon_init(void)339{340struct typhoon *dev = &typhoon_card;341struct v4l2_device *v4l2_dev = &dev->v4l2_dev;342int res;343344strlcpy(v4l2_dev->name, "typhoon", sizeof(v4l2_dev->name));345dev->io = io;346347if (dev->io == -1) {348v4l2_err(v4l2_dev, "You must set an I/O address with io=0x316 or io=0x336\n");349return -EINVAL;350}351352if (mutefreq < 87000 || mutefreq > 108500) {353v4l2_err(v4l2_dev, "You must set a frequency (in kHz) used when muting the card,\n");354v4l2_err(v4l2_dev, "e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");355return -EINVAL;356}357dev->curfreq = dev->mutefreq = mutefreq << 4;358359mutex_init(&dev->lock);360if (!request_region(dev->io, 8, "typhoon")) {361v4l2_err(v4l2_dev, "port 0x%x already in use\n",362dev->io);363return -EBUSY;364}365366res = v4l2_device_register(NULL, v4l2_dev);367if (res < 0) {368release_region(dev->io, 8);369v4l2_err(v4l2_dev, "Could not register v4l2_device\n");370return res;371}372v4l2_info(v4l2_dev, BANNER);373374strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));375dev->vdev.v4l2_dev = v4l2_dev;376dev->vdev.fops = &typhoon_fops;377dev->vdev.ioctl_ops = &typhoon_ioctl_ops;378dev->vdev.release = video_device_release_empty;379video_set_drvdata(&dev->vdev, dev);380381/* mute card - prevents noisy bootups */382typhoon_mute(dev);383384if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {385v4l2_device_unregister(&dev->v4l2_dev);386release_region(dev->io, 8);387return -EINVAL;388}389v4l2_info(v4l2_dev, "port 0x%x.\n", dev->io);390v4l2_info(v4l2_dev, "mute frequency is %lu kHz.\n", mutefreq);391392return 0;393}394395static void __exit typhoon_exit(void)396{397struct typhoon *dev = &typhoon_card;398399video_unregister_device(&dev->vdev);400v4l2_device_unregister(&dev->v4l2_dev);401release_region(dev->io, 8);402}403404module_init(typhoon_init);405module_exit(typhoon_exit);406407408409