Commit cc3c6df1 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab

[media] radio-aimslab: Convert to radio-isa

Tested with actual hardware and the Keene USB FM Transmitter.

Improved the volume handling delays through trial and error.
Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 137c579c
...@@ -202,6 +202,7 @@ config RADIO_CADET ...@@ -202,6 +202,7 @@ config RADIO_CADET
config RADIO_RTRACK config RADIO_RTRACK
tristate "AIMSlab RadioTrack (aka RadioReveal) support" tristate "AIMSlab RadioTrack (aka RadioReveal) support"
depends on ISA && VIDEO_V4L2 depends on ISA && VIDEO_V4L2
select RADIO_ISA
---help--- ---help---
Choose Y here if you have one of these FM radio cards, and then fill Choose Y here if you have one of these FM radio cards, and then fill
in the port address below. in the port address below.
...@@ -215,11 +216,7 @@ config RADIO_RTRACK ...@@ -215,11 +216,7 @@ config RADIO_RTRACK
You must also pass the module a suitable io parameter, 0x248 has You must also pass the module a suitable io parameter, 0x248 has
been reported to be used by these cards. been reported to be used by these cards.
In order to control your radio card, you will need to use programs More information is contained in the file
that are compatible with the Video For Linux API. Information on
this API and pointers to "v4l" programs may be found at
<file:Documentation/video4linux/API.html>. More information is
contained in the file
<file:Documentation/video4linux/radiotrack.txt>. <file:Documentation/video4linux/radiotrack.txt>.
To compile this driver as a module, choose M here: the To compile this driver as a module, choose M here: the
...@@ -228,7 +225,7 @@ config RADIO_RTRACK ...@@ -228,7 +225,7 @@ config RADIO_RTRACK
config RADIO_RTRACK_PORT config RADIO_RTRACK_PORT
hex "RadioTrack i/o port (0x20f or 0x30f)" hex "RadioTrack i/o port (0x20f or 0x30f)"
depends on RADIO_RTRACK=y depends on RADIO_RTRACK=y
default "20f" default "30f"
help help
Enter either 0x30f or 0x20f here. The card default is 0x30f, if you Enter either 0x30f or 0x20f here. The card default is 0x30f, if you
haven't changed the jumper setting on the card. haven't changed the jumper setting on the card.
......
/* radiotrack (radioreveal) driver for Linux radio support /*
* (c) 1997 M. Kirkwood * AimsLab RadioTrack (aka RadioVeveal) driver
*
* Copyright 1997 M. Kirkwood
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org> * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk> * Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org> * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
* *
* History:
* 1999-02-24 Russell Kroll <rkroll@exploits.org>
* Fine tuning/VIDEO_TUNER_LOW
* Frequency range expanded to start at 87 MHz
*
* TODO: Allow for more than one of these foolish entities :-)
*
* Notes on the hardware (reverse engineered from other peoples' * Notes on the hardware (reverse engineered from other peoples'
* reverse engineering of AIMS' code :-) * reverse engineering of AIMS' code :-)
* *
...@@ -26,6 +23,7 @@ ...@@ -26,6 +23,7 @@
* wait(a_wee_while); * wait(a_wee_while);
* out(port, stop_changing_the_volume); * out(port, stop_changing_the_volume);
* *
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/ */
#include <linux/module.h> /* Modules */ #include <linux/module.h> /* Modules */
...@@ -36,399 +34,176 @@ ...@@ -36,399 +34,176 @@
#include <linux/io.h> /* outb, outb_p */ #include <linux/io.h> /* outb, outb_p */
#include <media/v4l2-device.h> #include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h> #include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
MODULE_AUTHOR("M.Kirkwood"); MODULE_AUTHOR("M. Kirkwood");
MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card."); MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3"); MODULE_VERSION("1.0.0");
#ifndef CONFIG_RADIO_RTRACK_PORT #ifndef CONFIG_RADIO_RTRACK_PORT
#define CONFIG_RADIO_RTRACK_PORT -1 #define CONFIG_RADIO_RTRACK_PORT -1
#endif #endif
static int io = CONFIG_RADIO_RTRACK_PORT; #define RTRACK_MAX 2
static int radio_nr = -1;
module_param(io, int, 0); static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT,
MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)"); [1 ... (RTRACK_MAX - 1)] = -1 };
module_param(radio_nr, int, 0); static int radio_nr[RTRACK_MAX] = { [0 ... (RTRACK_MAX - 1)] = -1 };
struct rtrack module_param_array(io, int, NULL, 0444);
{ MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)");
struct v4l2_device v4l2_dev; module_param_array(radio_nr, int, NULL, 0444);
struct video_device vdev; MODULE_PARM_DESC(radio_nr, "Radio device numbers");
int port;
struct rtrack {
struct radio_isa_card isa;
int curvol; int curvol;
unsigned long curfreq;
int muted;
int io;
struct mutex lock;
}; };
static struct rtrack rtrack_card; static struct radio_isa_card *rtrack_alloc(void)
/* local things */
static void rt_decvol(struct rtrack *rt)
{
outb(0x58, rt->io); /* volume down + sigstr + on */
msleep(100);
outb(0xd8, rt->io); /* volume steady + sigstr + on */
}
static void rt_incvol(struct rtrack *rt)
{
outb(0x98, rt->io); /* volume up + sigstr + on */
msleep(100);
outb(0xd8, rt->io); /* volume steady + sigstr + on */
}
static void rt_mute(struct rtrack *rt)
{ {
rt->muted = 1; struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL);
mutex_lock(&rt->lock);
outb(0xd0, rt->io); /* volume steady, off */
mutex_unlock(&rt->lock);
}
static int rt_setvol(struct rtrack *rt, int vol)
{
int i;
mutex_lock(&rt->lock);
if (vol == rt->curvol) { /* requested volume = current */
if (rt->muted) { /* user is unmuting the card */
rt->muted = 0;
outb(0xd8, rt->io); /* enable card */
}
mutex_unlock(&rt->lock);
return 0;
}
if (vol == 0) { /* volume = 0 means mute the card */
outb(0x48, rt->io); /* volume down but still "on" */
msleep(2000); /* make sure it's totally down */
outb(0xd0, rt->io); /* volume steady, off */
rt->curvol = 0; /* track the volume state! */
mutex_unlock(&rt->lock);
return 0;
}
rt->muted = 0; if (rt)
if (vol > rt->curvol) rt->curvol = 0xff;
for (i = rt->curvol; i < vol; i++) return rt ? &rt->isa : NULL;
rt_incvol(rt);
else
for (i = rt->curvol; i > vol; i--)
rt_decvol(rt);
rt->curvol = vol;
mutex_unlock(&rt->lock);
return 0;
} }
/* the 128+64 on these outb's is to keep the volume stable while tuning /* The 128+64 on these outb's is to keep the volume stable while tuning.
* without them, the volume _will_ creep up with each frequency change * Without them, the volume _will_ creep up with each frequency change
* and bit 4 (+16) is to keep the signal strength meter enabled * and bit 4 (+16) is to keep the signal strength meter enabled.
*/ */
static void send_0_byte(struct rtrack *rt) static void send_0_byte(struct radio_isa_card *isa, int on)
{ {
if (rt->curvol == 0 || rt->muted) { outb_p(128+64+16+on+1, isa->io); /* wr-enable + data low */
outb_p(128+64+16+ 1, rt->io); /* wr-enable + data low */ outb_p(128+64+16+on+2+1, isa->io); /* clock */
outb_p(128+64+16+2+1, rt->io); /* clock */
}
else {
outb_p(128+64+16+8+ 1, rt->io); /* on + wr-enable + data low */
outb_p(128+64+16+8+2+1, rt->io); /* clock */
}
msleep(1); msleep(1);
} }
static void send_1_byte(struct rtrack *rt) static void send_1_byte(struct radio_isa_card *isa, int on)
{ {
if (rt->curvol == 0 || rt->muted) { outb_p(128+64+16+on+4+1, isa->io); /* wr-enable+data high */
outb_p(128+64+16+4 +1, rt->io); /* wr-enable+data high */ outb_p(128+64+16+on+4+2+1, isa->io); /* clock */
outb_p(128+64+16+4+2+1, rt->io); /* clock */
}
else {
outb_p(128+64+16+8+4 +1, rt->io); /* on+wr-enable+data high */
outb_p(128+64+16+8+4+2+1, rt->io); /* clock */
}
msleep(1); msleep(1);
} }
static int rt_setfreq(struct rtrack *rt, unsigned long freq) static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq)
{ {
int on = v4l2_ctrl_g_ctrl(isa->mute) ? 0 : 8;
int i; int i;
mutex_lock(&rt->lock); /* Stop other ops interfering */
rt->curfreq = freq;
/* now uses VIDEO_TUNER_LOW for fine tuning */
freq += 171200; /* Add 10.7 MHz IF */ freq += 171200; /* Add 10.7 MHz IF */
freq /= 800; /* Convert to 50 kHz units */ freq /= 800; /* Convert to 50 kHz units */
send_0_byte(rt); /* 0: LSB of frequency */ send_0_byte(isa, on); /* 0: LSB of frequency */
for (i = 0; i < 13; i++) /* : frequency bits (1-13) */ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
if (freq & (1 << i)) if (freq & (1 << i))
send_1_byte(rt); send_1_byte(isa, on);
else else
send_0_byte(rt); send_0_byte(isa, on);
send_0_byte(rt); /* 14: test bit - always 0 */
send_0_byte(rt); /* 15: test bit - always 0 */
send_0_byte(rt); /* 16: band data 0 - always 0 */ send_0_byte(isa, on); /* 14: test bit - always 0 */
send_0_byte(rt); /* 17: band data 1 - always 0 */ send_0_byte(isa, on); /* 15: test bit - always 0 */
send_0_byte(rt); /* 18: band data 2 - always 0 */
send_0_byte(rt); /* 19: time base - always 0 */
send_0_byte(rt); /* 20: spacing (0 = 25 kHz) */ send_0_byte(isa, on); /* 16: band data 0 - always 0 */
send_1_byte(rt); /* 21: spacing (1 = 25 kHz) */ send_0_byte(isa, on); /* 17: band data 1 - always 0 */
send_0_byte(rt); /* 22: spacing (0 = 25 kHz) */ send_0_byte(isa, on); /* 18: band data 2 - always 0 */
send_1_byte(rt); /* 23: AM/FM (FM = 1, always) */ send_0_byte(isa, on); /* 19: time base - always 0 */
if (rt->curvol == 0 || rt->muted) send_0_byte(isa, on); /* 20: spacing (0 = 25 kHz) */
outb(0xd0, rt->io); /* volume steady + sigstr */ send_1_byte(isa, on); /* 21: spacing (1 = 25 kHz) */
else send_0_byte(isa, on); /* 22: spacing (0 = 25 kHz) */
outb(0xd8, rt->io); /* volume steady + sigstr + on */ send_1_byte(isa, on); /* 23: AM/FM (FM = 1, always) */
mutex_unlock(&rt->lock);
outb(0xd0 + on, isa->io); /* volume steady + sigstr */
return 0; return 0;
} }
static int rt_getsigstr(struct rtrack *rt) static u32 rtrack_g_signal(struct radio_isa_card *isa)
{ {
int sig = 1; /* bit set = no signal present */
return 0xffff * !(inb(isa->io) & 2);
mutex_lock(&rt->lock);
if (inb(rt->io) & 2) /* bit set = no signal present */
sig = 0;
mutex_unlock(&rt->lock);
return sig;
} }
static int vidioc_querycap(struct file *file, void *priv, static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
struct v4l2_capability *v)
{ {
strlcpy(v->driver, "radio-aimslab", sizeof(v->driver)); struct rtrack *rt = container_of(isa, struct rtrack, isa);
strlcpy(v->card, "RadioTrack", sizeof(v->card)); int curvol = rt->curvol;
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct rtrack *rt = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name)); if (mute) {
v->type = V4L2_TUNER_RADIO; outb(0xd0, isa->io); /* volume steady + sigstr + off */
v->rangelow = 87 * 16000;
v->rangehigh = 108 * 16000;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_MONO;
v->signal = 0xffff * rt_getsigstr(rt);
return 0; return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack *rt = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
rt_setfreq(rt, f->frequency);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct rtrack *rt = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = rt->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
case V4L2_CID_AUDIO_VOLUME:
return v4l2_ctrl_query_fill(qc, 0, 0xff, 1, 0xff);
} }
return -EINVAL; if (vol == 0) { /* volume = 0 means mute the card */
} outb(0x48, isa->io); /* volume down but still "on" */
msleep(curvol * 3); /* make sure it's totally down */
static int vidioc_g_ctrl(struct file *file, void *priv, } else if (curvol < vol) {
struct v4l2_control *ctrl) outb(0x98, isa->io); /* volume up + sigstr + on */
{ for (; curvol < vol; curvol++)
struct rtrack *rt = video_drvdata(file); udelay(3000);
} else if (curvol > vol) {
switch (ctrl->id) { outb(0x58, isa->io); /* volume down + sigstr + on */
case V4L2_CID_AUDIO_MUTE: for (; curvol > vol; curvol--)
ctrl->value = rt->muted; udelay(3000);
return 0;
case V4L2_CID_AUDIO_VOLUME:
ctrl->value = rt->curvol;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rtrack *rt = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
rt_mute(rt);
else
rt_setvol(rt, rt->curvol);
return 0;
case V4L2_CID_AUDIO_VOLUME:
rt_setvol(rt, ctrl->value);
return 0;
} }
return -EINVAL; outb(0xd8, isa->io); /* volume steady + sigstr + on */
} rt->curvol = vol;
static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
{
*i = 0;
return 0; return 0;
} }
static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) /* Mute card - prevents noisy bootups */
static int rtrack_initialize(struct radio_isa_card *isa)
{ {
return i ? -EINVAL : 0; /* this ensures that the volume is all the way up */
} outb(0x90, isa->io); /* volume up but still "on" */
msleep(3000); /* make sure it's totally up */
static int vidioc_g_audio(struct file *file, void *priv, outb(0xc0, isa->io); /* steady volume, mute card */
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0; return 0;
} }
static int vidioc_s_audio(struct file *file, void *priv, static const struct radio_isa_ops rtrack_ops = {
struct v4l2_audio *a) .alloc = rtrack_alloc,
{ .init = rtrack_initialize,
return a->index ? -EINVAL : 0; .s_mute_volume = rtrack_s_mute_volume,
} .s_frequency = rtrack_s_frequency,
.g_signal = rtrack_g_signal,
static const struct v4l2_file_operations rtrack_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
}; };
static const struct v4l2_ioctl_ops rtrack_ioctl_ops = { static const int rtrack_ioports[] = { 0x20f, 0x30f };
.vidioc_querycap = vidioc_querycap,
.vidioc_g_tuner = vidioc_g_tuner, static struct radio_isa_driver rtrack_driver = {
.vidioc_s_tuner = vidioc_s_tuner, .driver = {
.vidioc_g_audio = vidioc_g_audio, .match = radio_isa_match,
.vidioc_s_audio = vidioc_s_audio, .probe = radio_isa_probe,
.vidioc_g_input = vidioc_g_input, .remove = radio_isa_remove,
.vidioc_s_input = vidioc_s_input, .driver = {
.vidioc_g_frequency = vidioc_g_frequency, .name = "radio-aimslab",
.vidioc_s_frequency = vidioc_s_frequency, },
.vidioc_queryctrl = vidioc_queryctrl, },
.vidioc_g_ctrl = vidioc_g_ctrl, .io_params = io,
.vidioc_s_ctrl = vidioc_s_ctrl, .radio_nr_params = radio_nr,
.io_ports = rtrack_ioports,
.num_of_io_ports = ARRAY_SIZE(rtrack_ioports),
.region_size = 2,
.card = "AIMSlab RadioTrack/RadioReveal",
.ops = &rtrack_ops,
.has_stereo = true,
.max_volume = 0xff,
}; };
static int __init rtrack_init(void) static int __init rtrack_init(void)
{ {
struct rtrack *rt = &rtrack_card; return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX);
struct v4l2_device *v4l2_dev = &rt->v4l2_dev;
int res;
strlcpy(v4l2_dev->name, "rtrack", sizeof(v4l2_dev->name));
rt->io = io;
if (rt->io == -1) {
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x20f or 0x30f\n");
return -EINVAL;
}
if (!request_region(rt->io, 2, "rtrack")) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", rt->io);
return -EBUSY;
}
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(rt->io, 2);
v4l2_err(v4l2_dev, "could not register v4l2_device\n");
return res;
}
strlcpy(rt->vdev.name, v4l2_dev->name, sizeof(rt->vdev.name));
rt->vdev.v4l2_dev = v4l2_dev;
rt->vdev.fops = &rtrack_fops;
rt->vdev.ioctl_ops = &rtrack_ioctl_ops;
rt->vdev.release = video_device_release_empty;
video_set_drvdata(&rt->vdev, rt);
/* Set up the I/O locking */
mutex_init(&rt->lock);
/* mute card - prevents noisy bootups */
/* this ensures that the volume is all the way down */
outb(0x48, rt->io); /* volume down but still "on" */
msleep(2000); /* make sure it's totally down */
outb(0xc0, rt->io); /* steady volume, mute card */
if (video_register_device(&rt->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(&rt->v4l2_dev);
release_region(rt->io, 2);
return -EINVAL;
}
v4l2_info(v4l2_dev, "AIMSlab RadioTrack/RadioReveal card driver.\n");
return 0;
} }
static void __exit rtrack_exit(void) static void __exit rtrack_exit(void)
{ {
struct rtrack *rt = &rtrack_card; isa_unregister_driver(&rtrack_driver.driver);
video_unregister_device(&rt->vdev);
v4l2_device_unregister(&rt->v4l2_dev);
release_region(rt->io, 2);
} }
module_init(rtrack_init); module_init(rtrack_init);
module_exit(rtrack_exit); module_exit(rtrack_exit);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment