Path: blob/master/drivers/media/radio/radio-zoltrix.c
15111 views
/* zoltrix radio plus driver for Linux radio support1* (c) 1998 C. van Schaik <[email protected]>2*3* BUGS4* Due to the inconsistency in reading from the signal flags5* it is difficult to get an accurate tuned signal.6*7* It seems that the card is not linear to 0 volume. It cuts off8* at a low volume, and it is not possible (at least I have not found)9* to get fine volume control over the low volume range.10*11* Some code derived from code by Romolo Manfredini12* [email protected]13*14* 1999-05-06 - (C. van Schaik)15* - Make signal strength and stereo scans16* kinder to cpu while in delay17* 1999-01-05 - (C. van Schaik)18* - Changed tuning to 1/160Mhz accuracy19* - Added stereo support20* (card defaults to stereo)21* (can explicitly force mono on the card)22* (can detect if station is in stereo)23* - Added unmute function24* - Reworked ioctl functions25* 2002-07-15 - Fix Stereo typo26*27* 2006-07-24 - Converted to V4L2 API28* by Mauro Carvalho Chehab <[email protected]>29*/3031#include <linux/module.h> /* Modules */32#include <linux/init.h> /* Initdata */33#include <linux/ioport.h> /* request_region */34#include <linux/delay.h> /* udelay, msleep */35#include <linux/videodev2.h> /* kernel radio structs */36#include <linux/mutex.h>37#include <linux/version.h> /* for KERNEL_VERSION MACRO */38#include <linux/io.h> /* outb, outb_p */39#include <media/v4l2-device.h>40#include <media/v4l2-ioctl.h>4142MODULE_AUTHOR("C.van Schaik");43MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");44MODULE_LICENSE("GPL");4546#ifndef CONFIG_RADIO_ZOLTRIX_PORT47#define CONFIG_RADIO_ZOLTRIX_PORT -148#endif4950static int io = CONFIG_RADIO_ZOLTRIX_PORT;51static int radio_nr = -1;5253module_param(io, int, 0);54MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");55module_param(radio_nr, int, 0);5657#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)5859struct zoltrix {60struct v4l2_device v4l2_dev;61struct video_device vdev;62int io;63int curvol;64unsigned long curfreq;65int muted;66unsigned int stereo;67struct mutex lock;68};6970static struct zoltrix zoltrix_card;7172static int zol_setvol(struct zoltrix *zol, int vol)73{74zol->curvol = vol;75if (zol->muted)76return 0;7778mutex_lock(&zol->lock);79if (vol == 0) {80outb(0, zol->io);81outb(0, zol->io);82inb(zol->io + 3); /* Zoltrix needs to be read to confirm */83mutex_unlock(&zol->lock);84return 0;85}8687outb(zol->curvol-1, zol->io);88msleep(10);89inb(zol->io + 2);90mutex_unlock(&zol->lock);91return 0;92}9394static void zol_mute(struct zoltrix *zol)95{96zol->muted = 1;97mutex_lock(&zol->lock);98outb(0, zol->io);99outb(0, zol->io);100inb(zol->io + 3); /* Zoltrix needs to be read to confirm */101mutex_unlock(&zol->lock);102}103104static void zol_unmute(struct zoltrix *zol)105{106zol->muted = 0;107zol_setvol(zol, zol->curvol);108}109110static int zol_setfreq(struct zoltrix *zol, unsigned long freq)111{112/* tunes the radio to the desired frequency */113struct v4l2_device *v4l2_dev = &zol->v4l2_dev;114unsigned long long bitmask, f, m;115unsigned int stereo = zol->stereo;116int i;117118if (freq == 0) {119v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n");120return -EINVAL;121}122123m = (freq / 160 - 8800) * 2;124f = (unsigned long long)m + 0x4d1c;125126bitmask = 0xc480402c10080000ull;127i = 45;128129mutex_lock(&zol->lock);130131zol->curfreq = freq;132133outb(0, zol->io);134outb(0, zol->io);135inb(zol->io + 3); /* Zoltrix needs to be read to confirm */136137outb(0x40, zol->io);138outb(0xc0, zol->io);139140bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));141while (i--) {142if ((bitmask & 0x8000000000000000ull) != 0) {143outb(0x80, zol->io);144udelay(50);145outb(0x00, zol->io);146udelay(50);147outb(0x80, zol->io);148udelay(50);149} else {150outb(0xc0, zol->io);151udelay(50);152outb(0x40, zol->io);153udelay(50);154outb(0xc0, zol->io);155udelay(50);156}157bitmask *= 2;158}159/* termination sequence */160outb(0x80, zol->io);161outb(0xc0, zol->io);162outb(0x40, zol->io);163udelay(1000);164inb(zol->io + 2);165166udelay(1000);167168if (zol->muted) {169outb(0, zol->io);170outb(0, zol->io);171inb(zol->io + 3);172udelay(1000);173}174175mutex_unlock(&zol->lock);176177if (!zol->muted)178zol_setvol(zol, zol->curvol);179return 0;180}181182/* Get signal strength */183static int zol_getsigstr(struct zoltrix *zol)184{185int a, b;186187mutex_lock(&zol->lock);188outb(0x00, zol->io); /* This stuff I found to do nothing */189outb(zol->curvol, zol->io);190msleep(20);191192a = inb(zol->io);193msleep(10);194b = inb(zol->io);195196mutex_unlock(&zol->lock);197198if (a != b)199return 0;200201/* I found this out by playing with a binary scanner on the card io */202return a == 0xcf || a == 0xdf || a == 0xef;203}204205static int zol_is_stereo(struct zoltrix *zol)206{207int x1, x2;208209mutex_lock(&zol->lock);210211outb(0x00, zol->io);212outb(zol->curvol, zol->io);213msleep(20);214215x1 = inb(zol->io);216msleep(10);217x2 = inb(zol->io);218219mutex_unlock(&zol->lock);220221return x1 == x2 && x1 == 0xcf;222}223224static int vidioc_querycap(struct file *file, void *priv,225struct v4l2_capability *v)226{227strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));228strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));229strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));230v->version = RADIO_VERSION;231v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;232return 0;233}234235static int vidioc_g_tuner(struct file *file, void *priv,236struct v4l2_tuner *v)237{238struct zoltrix *zol = video_drvdata(file);239240if (v->index > 0)241return -EINVAL;242243strlcpy(v->name, "FM", sizeof(v->name));244v->type = V4L2_TUNER_RADIO;245v->rangelow = 88 * 16000;246v->rangehigh = 108 * 16000;247v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;248v->capability = V4L2_TUNER_CAP_LOW;249if (zol_is_stereo(zol))250v->audmode = V4L2_TUNER_MODE_STEREO;251else252v->audmode = V4L2_TUNER_MODE_MONO;253v->signal = 0xFFFF * zol_getsigstr(zol);254return 0;255}256257static int vidioc_s_tuner(struct file *file, void *priv,258struct v4l2_tuner *v)259{260return v->index ? -EINVAL : 0;261}262263static int vidioc_s_frequency(struct file *file, void *priv,264struct v4l2_frequency *f)265{266struct zoltrix *zol = video_drvdata(file);267268if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)269return -EINVAL;270if (zol_setfreq(zol, f->frequency) != 0)271return -EINVAL;272return 0;273}274275static int vidioc_g_frequency(struct file *file, void *priv,276struct v4l2_frequency *f)277{278struct zoltrix *zol = video_drvdata(file);279280if (f->tuner != 0)281return -EINVAL;282f->type = V4L2_TUNER_RADIO;283f->frequency = zol->curfreq;284return 0;285}286287static int vidioc_queryctrl(struct file *file, void *priv,288struct v4l2_queryctrl *qc)289{290switch (qc->id) {291case V4L2_CID_AUDIO_MUTE:292return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);293case V4L2_CID_AUDIO_VOLUME:294return v4l2_ctrl_query_fill(qc, 0, 65535, 4096, 65535);295}296return -EINVAL;297}298299static int vidioc_g_ctrl(struct file *file, void *priv,300struct v4l2_control *ctrl)301{302struct zoltrix *zol = video_drvdata(file);303304switch (ctrl->id) {305case V4L2_CID_AUDIO_MUTE:306ctrl->value = zol->muted;307return 0;308case V4L2_CID_AUDIO_VOLUME:309ctrl->value = zol->curvol * 4096;310return 0;311}312return -EINVAL;313}314315static int vidioc_s_ctrl(struct file *file, void *priv,316struct v4l2_control *ctrl)317{318struct zoltrix *zol = video_drvdata(file);319320switch (ctrl->id) {321case V4L2_CID_AUDIO_MUTE:322if (ctrl->value)323zol_mute(zol);324else {325zol_unmute(zol);326zol_setvol(zol, zol->curvol);327}328return 0;329case V4L2_CID_AUDIO_VOLUME:330zol_setvol(zol, ctrl->value / 4096);331return 0;332}333zol->stereo = 1;334if (zol_setfreq(zol, zol->curfreq) != 0)335return -EINVAL;336#if 0337/* FIXME: Implement stereo/mono switch on V4L2 */338if (v->mode & VIDEO_SOUND_STEREO) {339zol->stereo = 1;340zol_setfreq(zol, zol->curfreq);341}342if (v->mode & VIDEO_SOUND_MONO) {343zol->stereo = 0;344zol_setfreq(zol, zol->curfreq);345}346#endif347return -EINVAL;348}349350static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)351{352*i = 0;353return 0;354}355356static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)357{358return i ? -EINVAL : 0;359}360361static int vidioc_g_audio(struct file *file, void *priv,362struct v4l2_audio *a)363{364a->index = 0;365strlcpy(a->name, "Radio", sizeof(a->name));366a->capability = V4L2_AUDCAP_STEREO;367return 0;368}369370static int vidioc_s_audio(struct file *file, void *priv,371struct v4l2_audio *a)372{373return a->index ? -EINVAL : 0;374}375376static const struct v4l2_file_operations zoltrix_fops =377{378.owner = THIS_MODULE,379.unlocked_ioctl = video_ioctl2,380};381382static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = {383.vidioc_querycap = vidioc_querycap,384.vidioc_g_tuner = vidioc_g_tuner,385.vidioc_s_tuner = vidioc_s_tuner,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.vidioc_g_frequency = vidioc_g_frequency,391.vidioc_s_frequency = vidioc_s_frequency,392.vidioc_queryctrl = vidioc_queryctrl,393.vidioc_g_ctrl = vidioc_g_ctrl,394.vidioc_s_ctrl = vidioc_s_ctrl,395};396397static int __init zoltrix_init(void)398{399struct zoltrix *zol = &zoltrix_card;400struct v4l2_device *v4l2_dev = &zol->v4l2_dev;401int res;402403strlcpy(v4l2_dev->name, "zoltrix", sizeof(v4l2_dev->name));404zol->io = io;405if (zol->io == -1) {406v4l2_err(v4l2_dev, "You must set an I/O address with io=0x20c or 0x30c\n");407return -EINVAL;408}409if (zol->io != 0x20c && zol->io != 0x30c) {410v4l2_err(v4l2_dev, "invalid port, try 0x20c or 0x30c\n");411return -ENXIO;412}413414if (!request_region(zol->io, 2, "zoltrix")) {415v4l2_err(v4l2_dev, "port 0x%x already in use\n", zol->io);416return -EBUSY;417}418419res = v4l2_device_register(NULL, v4l2_dev);420if (res < 0) {421release_region(zol->io, 2);422v4l2_err(v4l2_dev, "Could not register v4l2_device\n");423return res;424}425426mutex_init(&zol->lock);427428/* mute card - prevents noisy bootups */429430/* this ensures that the volume is all the way down */431432outb(0, zol->io);433outb(0, zol->io);434msleep(20);435inb(zol->io + 3);436437zol->curvol = 0;438zol->stereo = 1;439440strlcpy(zol->vdev.name, v4l2_dev->name, sizeof(zol->vdev.name));441zol->vdev.v4l2_dev = v4l2_dev;442zol->vdev.fops = &zoltrix_fops;443zol->vdev.ioctl_ops = &zoltrix_ioctl_ops;444zol->vdev.release = video_device_release_empty;445video_set_drvdata(&zol->vdev, zol);446447if (video_register_device(&zol->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {448v4l2_device_unregister(v4l2_dev);449release_region(zol->io, 2);450return -EINVAL;451}452v4l2_info(v4l2_dev, "Zoltrix Radio Plus card driver.\n");453454return 0;455}456457static void __exit zoltrix_exit(void)458{459struct zoltrix *zol = &zoltrix_card;460461video_unregister_device(&zol->vdev);462v4l2_device_unregister(&zol->v4l2_dev);463release_region(zol->io, 2);464}465466module_init(zoltrix_init);467module_exit(zoltrix_exit);468469470471