Path: blob/master/drivers/media/radio/radio-aztech.c
15111 views
/* radio-aztech.c - Aztech radio card driver for Linux 2.21*2* Converted to V4L2 API by Mauro Carvalho Chehab <[email protected]>3* Adapted to support the Video for Linux API by4* Russell Kroll <[email protected]>. Based on original tuner code by:5*6* Quay Ly7* Donald Song8* Jason Lewis ([email protected])9* Scott McGrath ([email protected])10* William McGrath ([email protected])11*12* The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/13* along with more information on the card itself.14*15* History:16* 1999-02-24 Russell Kroll <[email protected]>17* Fine tuning/VIDEO_TUNER_LOW18* Range expanded to 87-108 MHz (from 87.9-107.8)19*20* Notable changes from the original source:21* - includes stripped down to the essentials22* - for loops used as delays replaced with udelay()23* - #defines removed, changed to static values24* - tuning structure changed - no more character arrays, other changes25*/2627#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 <linux/videodev2.h> /* kernel radio structs */32#include <linux/version.h> /* for KERNEL_VERSION MACRO */33#include <linux/io.h> /* outb, outb_p */34#include <media/v4l2-device.h>35#include <media/v4l2-ioctl.h>3637MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");38MODULE_DESCRIPTION("A driver for the Aztech radio card.");39MODULE_LICENSE("GPL");4041/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */4243#ifndef CONFIG_RADIO_AZTECH_PORT44#define CONFIG_RADIO_AZTECH_PORT -145#endif4647static int io = CONFIG_RADIO_AZTECH_PORT;48static int radio_nr = -1;49static int radio_wait_time = 1000;5051module_param(io, int, 0);52module_param(radio_nr, int, 0);53MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");5455#define RADIO_VERSION KERNEL_VERSION(0, 0, 2)5657struct aztech58{59struct v4l2_device v4l2_dev;60struct video_device vdev;61int io;62int curvol;63unsigned long curfreq;64int stereo;65struct mutex lock;66};6768static struct aztech aztech_card;6970static int volconvert(int level)71{72level >>= 14; /* Map 16bits down to 2 bit */73level &= 3;7475/* convert to card-friendly values */76switch (level) {77case 0:78return 0;79case 1:80return 1;81case 2:82return 4;83case 3:84return 5;85}86return 0; /* Quieten gcc */87}8889static void send_0_byte(struct aztech *az)90{91udelay(radio_wait_time);92outb_p(2 + volconvert(az->curvol), az->io);93outb_p(64 + 2 + volconvert(az->curvol), az->io);94}9596static void send_1_byte(struct aztech *az)97{98udelay (radio_wait_time);99outb_p(128 + 2 + volconvert(az->curvol), az->io);100outb_p(128 + 64 + 2 + volconvert(az->curvol), az->io);101}102103static int az_setvol(struct aztech *az, int vol)104{105mutex_lock(&az->lock);106outb(volconvert(vol), az->io);107mutex_unlock(&az->lock);108return 0;109}110111/* thanks to Michael Dwyer for giving me a dose of clues in112* the signal strength department..113*114* This card has a stereo bit - bit 0 set = mono, not set = stereo115* It also has a "signal" bit - bit 1 set = bad signal, not set = good116*117*/118119static int az_getsigstr(struct aztech *az)120{121int sig = 1;122123mutex_lock(&az->lock);124if (inb(az->io) & 2) /* bit set = no signal present */125sig = 0;126mutex_unlock(&az->lock);127return sig;128}129130static int az_getstereo(struct aztech *az)131{132int stereo = 1;133134mutex_lock(&az->lock);135if (inb(az->io) & 1) /* bit set = mono */136stereo = 0;137mutex_unlock(&az->lock);138return stereo;139}140141static int az_setfreq(struct aztech *az, unsigned long frequency)142{143int i;144145mutex_lock(&az->lock);146147az->curfreq = frequency;148frequency += 171200; /* Add 10.7 MHz IF */149frequency /= 800; /* Convert to 50 kHz units */150151send_0_byte(az); /* 0: LSB of frequency */152153for (i = 0; i < 13; i++) /* : frequency bits (1-13) */154if (frequency & (1 << i))155send_1_byte(az);156else157send_0_byte(az);158159send_0_byte(az); /* 14: test bit - always 0 */160send_0_byte(az); /* 15: test bit - always 0 */161send_0_byte(az); /* 16: band data 0 - always 0 */162if (az->stereo) /* 17: stereo (1 to enable) */163send_1_byte(az);164else165send_0_byte(az);166167send_1_byte(az); /* 18: band data 1 - unknown */168send_0_byte(az); /* 19: time base - always 0 */169send_0_byte(az); /* 20: spacing (0 = 25 kHz) */170send_1_byte(az); /* 21: spacing (1 = 25 kHz) */171send_0_byte(az); /* 22: spacing (0 = 25 kHz) */172send_1_byte(az); /* 23: AM/FM (FM = 1, always) */173174/* latch frequency */175176udelay(radio_wait_time);177outb_p(128 + 64 + volconvert(az->curvol), az->io);178179mutex_unlock(&az->lock);180181return 0;182}183184static int vidioc_querycap(struct file *file, void *priv,185struct v4l2_capability *v)186{187strlcpy(v->driver, "radio-aztech", sizeof(v->driver));188strlcpy(v->card, "Aztech Radio", sizeof(v->card));189strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));190v->version = RADIO_VERSION;191v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;192return 0;193}194195static int vidioc_g_tuner(struct file *file, void *priv,196struct v4l2_tuner *v)197{198struct aztech *az = video_drvdata(file);199200if (v->index > 0)201return -EINVAL;202203strlcpy(v->name, "FM", sizeof(v->name));204v->type = V4L2_TUNER_RADIO;205206v->rangelow = 87 * 16000;207v->rangehigh = 108 * 16000;208v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;209v->capability = V4L2_TUNER_CAP_LOW;210if (az_getstereo(az))211v->audmode = V4L2_TUNER_MODE_STEREO;212else213v->audmode = V4L2_TUNER_MODE_MONO;214v->signal = 0xFFFF * az_getsigstr(az);215216return 0;217}218219static int vidioc_s_tuner(struct file *file, void *priv,220struct v4l2_tuner *v)221{222return v->index ? -EINVAL : 0;223}224225static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)226{227*i = 0;228return 0;229}230231static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)232{233return i ? -EINVAL : 0;234}235236static int vidioc_g_audio(struct file *file, void *priv,237struct v4l2_audio *a)238{239a->index = 0;240strlcpy(a->name, "Radio", sizeof(a->name));241a->capability = V4L2_AUDCAP_STEREO;242return 0;243}244245static int vidioc_s_audio(struct file *file, void *priv,246struct v4l2_audio *a)247{248return a->index ? -EINVAL : 0;249}250251static int vidioc_s_frequency(struct file *file, void *priv,252struct v4l2_frequency *f)253{254struct aztech *az = video_drvdata(file);255256if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)257return -EINVAL;258az_setfreq(az, f->frequency);259return 0;260}261262static int vidioc_g_frequency(struct file *file, void *priv,263struct v4l2_frequency *f)264{265struct aztech *az = video_drvdata(file);266267if (f->tuner != 0)268return -EINVAL;269f->type = V4L2_TUNER_RADIO;270f->frequency = az->curfreq;271return 0;272}273274static int vidioc_queryctrl(struct file *file, void *priv,275struct v4l2_queryctrl *qc)276{277switch (qc->id) {278case V4L2_CID_AUDIO_MUTE:279return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);280case V4L2_CID_AUDIO_VOLUME:281return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);282}283return -EINVAL;284}285286static int vidioc_g_ctrl(struct file *file, void *priv,287struct v4l2_control *ctrl)288{289struct aztech *az = video_drvdata(file);290291switch (ctrl->id) {292case V4L2_CID_AUDIO_MUTE:293if (az->curvol == 0)294ctrl->value = 1;295else296ctrl->value = 0;297return 0;298case V4L2_CID_AUDIO_VOLUME:299ctrl->value = az->curvol * 6554;300return 0;301}302return -EINVAL;303}304305static int vidioc_s_ctrl(struct file *file, void *priv,306struct v4l2_control *ctrl)307{308struct aztech *az = video_drvdata(file);309310switch (ctrl->id) {311case V4L2_CID_AUDIO_MUTE:312if (ctrl->value)313az_setvol(az, 0);314else315az_setvol(az, az->curvol);316return 0;317case V4L2_CID_AUDIO_VOLUME:318az_setvol(az, ctrl->value);319return 0;320}321return -EINVAL;322}323324static const struct v4l2_file_operations aztech_fops = {325.owner = THIS_MODULE,326.unlocked_ioctl = video_ioctl2,327};328329static const struct v4l2_ioctl_ops aztech_ioctl_ops = {330.vidioc_querycap = vidioc_querycap,331.vidioc_g_tuner = vidioc_g_tuner,332.vidioc_s_tuner = vidioc_s_tuner,333.vidioc_g_audio = vidioc_g_audio,334.vidioc_s_audio = vidioc_s_audio,335.vidioc_g_input = vidioc_g_input,336.vidioc_s_input = vidioc_s_input,337.vidioc_g_frequency = vidioc_g_frequency,338.vidioc_s_frequency = vidioc_s_frequency,339.vidioc_queryctrl = vidioc_queryctrl,340.vidioc_g_ctrl = vidioc_g_ctrl,341.vidioc_s_ctrl = vidioc_s_ctrl,342};343344static int __init aztech_init(void)345{346struct aztech *az = &aztech_card;347struct v4l2_device *v4l2_dev = &az->v4l2_dev;348int res;349350strlcpy(v4l2_dev->name, "aztech", sizeof(v4l2_dev->name));351az->io = io;352353if (az->io == -1) {354v4l2_err(v4l2_dev, "you must set an I/O address with io=0x350 or 0x358\n");355return -EINVAL;356}357358if (!request_region(az->io, 2, "aztech")) {359v4l2_err(v4l2_dev, "port 0x%x already in use\n", az->io);360return -EBUSY;361}362363res = v4l2_device_register(NULL, v4l2_dev);364if (res < 0) {365release_region(az->io, 2);366v4l2_err(v4l2_dev, "Could not register v4l2_device\n");367return res;368}369370mutex_init(&az->lock);371strlcpy(az->vdev.name, v4l2_dev->name, sizeof(az->vdev.name));372az->vdev.v4l2_dev = v4l2_dev;373az->vdev.fops = &aztech_fops;374az->vdev.ioctl_ops = &aztech_ioctl_ops;375az->vdev.release = video_device_release_empty;376video_set_drvdata(&az->vdev, az);377/* mute card - prevents noisy bootups */378outb(0, az->io);379380if (video_register_device(&az->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {381v4l2_device_unregister(v4l2_dev);382release_region(az->io, 2);383return -EINVAL;384}385386v4l2_info(v4l2_dev, "Aztech radio card driver v1.00/19990224 [email protected]\n");387return 0;388}389390static void __exit aztech_exit(void)391{392struct aztech *az = &aztech_card;393394video_unregister_device(&az->vdev);395v4l2_device_unregister(&az->v4l2_dev);396release_region(az->io, 2);397}398399module_init(aztech_init);400module_exit(aztech_exit);401402403