summaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/media/radio
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig354
-rw-r--r--drivers/media/radio/Makefile22
-rw-r--r--drivers/media/radio/miropcm20-radio.c264
-rw-r--r--drivers/media/radio/miropcm20-rds-core.c210
-rw-r--r--drivers/media/radio/miropcm20-rds-core.h19
-rw-r--r--drivers/media/radio/miropcm20-rds.c133
-rw-r--r--drivers/media/radio/radio-aimslab.c368
-rw-r--r--drivers/media/radio/radio-aztech.c315
-rw-r--r--drivers/media/radio/radio-cadet.c620
-rw-r--r--drivers/media/radio/radio-gemtek-pci.c416
-rw-r--r--drivers/media/radio/radio-gemtek.c304
-rw-r--r--drivers/media/radio/radio-maestro.c332
-rw-r--r--drivers/media/radio/radio-maxiradio.c349
-rw-r--r--drivers/media/radio/radio-rtrack2.c266
-rw-r--r--drivers/media/radio/radio-sf16fmi.c328
-rw-r--r--drivers/media/radio/radio-sf16fmr2.c434
-rw-r--r--drivers/media/radio/radio-terratec.c341
-rw-r--r--drivers/media/radio/radio-trust.c320
-rw-r--r--drivers/media/radio/radio-typhoon.c383
-rw-r--r--drivers/media/radio/radio-zoltrix.c385
20 files changed, 6163 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
new file mode 100644
index 00000000000..d318be383de
--- /dev/null
+++ b/drivers/media/radio/Kconfig
@@ -0,0 +1,354 @@
+#
+# Multimedia Video device configuration
+#
+
+menu "Radio Adapters"
+ depends on VIDEO_DEV!=n
+
+config RADIO_CADET
+ tristate "ADS Cadet AM/FM Tuner"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these AM/FM radio cards, and then
+ fill in the port address below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ Further documentation on this driver can be found on the WWW at
+ <http://linux.blackhawke.net/cadet/>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-cadet.
+
+config RADIO_RTRACK
+ tristate "AIMSlab RadioTrack (aka RadioReveal) support"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards, and then fill
+ in the port address below.
+
+ Note that newer AIMSlab RadioTrack cards have a different chipset
+ and are not supported by this driver. For these cards, use the
+ RadioTrack II driver below.
+
+ If you have a GemTeks combined (PnP) sound- and radio card you must
+ use this driver as a module and setup the card with isapnptools.
+ You must also pass the module a suitable io parameter, 0x248 has
+ been reported to be used by these cards.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-aimslab.
+
+config RADIO_RTRACK_PORT
+ hex "RadioTrack i/o port (0x20f or 0x30f)"
+ depends on RADIO_RTRACK=y
+ default "20f"
+ help
+ Enter either 0x30f or 0x20f here. The card default is 0x30f, if you
+ haven't changed the jumper setting on the card.
+
+config RADIO_RTRACK2
+ tristate "AIMSlab RadioTrack II support"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have this FM radio card, and then fill in the
+ port address below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-rtrack2.
+
+config RADIO_RTRACK2_PORT
+ hex "RadioTrack II i/o port (0x20c or 0x30c)"
+ depends on RADIO_RTRACK2=y
+ default "30c"
+ help
+ Enter either 0x30c or 0x20c here. The card default is 0x30c, if you
+ haven't changed the jumper setting on the card.
+
+config RADIO_AZTECH
+ tristate "Aztech/Packard Bell Radio"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards, and then fill
+ in the port address below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-aztech.
+
+config RADIO_AZTECH_PORT
+ hex "Aztech/Packard Bell I/O port (0x350 or 0x358)"
+ depends on RADIO_AZTECH=y
+ default "350"
+ help
+ Enter either 0x350 or 0x358 here. The card default is 0x350, if you
+ haven't changed the setting of jumper JP3 on the card. Removing the
+ jumper sets the card to 0x358.
+
+config RADIO_GEMTEK
+ tristate "GemTek Radio Card support"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have this FM radio card, and then fill in the
+ port address below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-gemtek.
+
+config RADIO_GEMTEK_PORT
+ hex "GemTek i/o port (0x20c, 0x30c, 0x24c or 0x34c)"
+ depends on RADIO_GEMTEK=y
+ default "34c"
+ help
+ Enter either 0x20c, 0x30c, 0x24c or 0x34c here. The card default is
+ 0x34c, if you haven't changed the jumper setting on the card. On
+ Sound Vision 16 Gold PnP with FM Radio (ESS1869+FM Gemtek), the I/O
+ port is 0x28c.
+
+config RADIO_GEMTEK_PCI
+ tristate "GemTek PCI Radio Card support"
+ depends on VIDEO_DEV && PCI
+ ---help---
+ Choose Y here if you have this PCI FM radio card.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-gemtek-pci.
+
+config RADIO_MAXIRADIO
+ tristate "Guillemot MAXI Radio FM 2000 radio"
+ depends on VIDEO_DEV && PCI
+ ---help---
+ Choose Y here if you have this radio card. This card may also be
+ found as Gemtek PCI FM.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-maxiradio.
+
+config RADIO_MAESTRO
+ tristate "Maestro on board radio"
+ depends on VIDEO_DEV
+ ---help---
+ Say Y here to directly support the on-board radio tuner on the
+ Maestro 2 or 2E sound card.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-maestro.
+
+config RADIO_MIROPCM20
+ tristate "miroSOUND PCM20 radio"
+ depends on ISA && VIDEO_DEV && SOUND_ACI_MIXER
+ ---help---
+ Choose Y here if you have this FM radio card. You also need to say Y
+ to "ACI mixer (miroSOUND PCM1-pro/PCM12/PCM20 radio)" (in "Sound")
+ for this to work.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called miropcm20.
+
+config RADIO_MIROPCM20_RDS
+ tristate "miroSOUND PCM20 radio RDS user interface (EXPERIMENTAL)"
+ depends on RADIO_MIROPCM20 && EXPERIMENTAL
+ ---help---
+ Choose Y here if you want to see RDS/RBDS information like
+ RadioText, Programme Service name, Clock Time and date, Programme
+ TYpe and Traffic Announcement/Programme identification. You also
+ need to say Y to "miroSOUND PCM20 radio" and devfs!
+
+ It's not possible to read the raw RDS packets from the device, so
+ the driver cant provide an V4L interface for this. But the
+ availability of RDS is reported over V4L by the basic driver
+ already. Here RDS can be read from files in /dev/v4l/rds.
+
+ To compile this driver as a module, choose M here: the
+ module will be called miropcm20-rds.
+
+config RADIO_SF16FMI
+ tristate "SF16FMI Radio"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards. If you
+ compile the driver into the kernel and your card is not PnP one, you
+ have to add "sf16fm=<io>" to the kernel command line (I/O address is
+ 0x284 or 0x384).
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-sf16fmi.
+
+config RADIO_SF16FMR2
+ tristate "SF16FMR2 Radio"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards.
+
+ In order to control your radio card, you will need to use programs
+ that are compatible with the Video For Linux API. Information on
+ this API and pointers to "v4l" programs may be found on the WWW at
+ <http://roadrunner.swansea.uk.linux.org/v4l.shtml>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-sf16fmr2.
+
+config RADIO_TERRATEC
+ tristate "TerraTec ActiveRadio ISA Standalone"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have this FM radio card, and then fill in the
+ port address below. (TODO)
+
+ Note: This driver is in its early stages. Right now volume and
+ frequency control and muting works at least for me, but
+ unfortunately I have not found anybody who wants to use this card
+ with Linux. So if it is this what YOU are trying to do right now,
+ PLEASE DROP ME A NOTE!! Rolf Offermanns <rolf@offermanns.de>.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-terratec.
+
+config RADIO_TERRATEC_PORT
+ hex "Terratec i/o port (normally 0x590)"
+ depends on RADIO_TERRATEC=y
+ default "590"
+ help
+ Fill in the I/O port of your TerraTec FM radio card. If unsure, go
+ with the default.
+
+config RADIO_TRUST
+ tristate "Trust FM radio card"
+ depends on ISA && VIDEO_DEV
+ help
+ This is a driver for the Trust FM radio cards. Say Y if you have
+ such a card and want to use it under Linux.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-trust.
+
+config RADIO_TRUST_PORT
+ hex "Trust i/o port (usually 0x350 or 0x358)"
+ depends on RADIO_TRUST=y
+ default "350"
+ help
+ Enter the I/O port of your Trust FM radio card. If unsure, try the
+ values "0x350" or "0x358".
+
+config RADIO_TYPHOON
+ tristate "Typhoon Radio (a.k.a. EcoRadio)"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards, and then fill
+ in the port address and the frequency used for muting below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-typhoon.
+
+config RADIO_TYPHOON_PROC_FS
+ bool "Support for /proc/radio-typhoon"
+ depends on PROC_FS && RADIO_TYPHOON
+ help
+ Say Y here if you want the typhoon radio card driver to write
+ status information (frequency, volume, muted, mute frequency,
+ base address) to /proc/radio-typhoon. The file can be viewed with
+ your favorite pager (i.e. use "more /proc/radio-typhoon" or "less
+ /proc/radio-typhoon" or simply "cat /proc/radio-typhoon").
+
+config RADIO_TYPHOON_PORT
+ hex "Typhoon I/O port (0x316 or 0x336)"
+ depends on RADIO_TYPHOON=y
+ default "316"
+ help
+ Enter the I/O port of your Typhoon or EcoRadio radio card.
+
+config RADIO_TYPHOON_MUTEFREQ
+ int "Typhoon frequency set when muting the device (kHz)"
+ depends on RADIO_TYPHOON=y
+ default "87500"
+ help
+ Enter the frequency used for muting the radio. The device is never
+ completely silent. If the volume is just turned down, you can still
+ hear silent voices and music. For that reason, the frequency of the
+ radio device is set to the frequency you can enter here whenever
+ the device is muted. There should be no local radio station at that
+ frequency.
+
+config RADIO_ZOLTRIX
+ tristate "Zoltrix Radio"
+ depends on ISA && VIDEO_DEV
+ ---help---
+ Choose Y here if you have one of these FM radio cards, and then fill
+ in the port address below.
+
+ In order to control your radio card, you will need to use programs
+ 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>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-zoltrix.
+
+config RADIO_ZOLTRIX_PORT
+ hex "ZOLTRIX I/O port (0x20c or 0x30c)"
+ depends on RADIO_ZOLTRIX=y
+ default "20c"
+ help
+ Enter the I/O port of your Zoltrix radio card.
+
+endmenu
+
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
new file mode 100644
index 00000000000..8b351945d06
--- /dev/null
+++ b/drivers/media/radio/Makefile
@@ -0,0 +1,22 @@
+#
+# Makefile for the kernel character device drivers.
+#
+
+miropcm20-objs := miropcm20-rds-core.o miropcm20-radio.o
+
+obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o
+obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o
+obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o
+obj-$(CONFIG_RADIO_SF16FMR2) += radio-sf16fmr2.o
+obj-$(CONFIG_RADIO_CADET) += radio-cadet.o
+obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o
+obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o
+obj-$(CONFIG_RADIO_MAXIRADIO) += radio-maxiradio.o
+obj-$(CONFIG_RADIO_RTRACK) += radio-aimslab.o
+obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o
+obj-$(CONFIG_RADIO_MIROPCM20) += miropcm20.o
+obj-$(CONFIG_RADIO_MIROPCM20_RDS) += miropcm20-rds.o
+obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
+obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o
+obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
+obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o
diff --git a/drivers/media/radio/miropcm20-radio.c b/drivers/media/radio/miropcm20-radio.c
new file mode 100644
index 00000000000..c2ebe8754a9
--- /dev/null
+++ b/drivers/media/radio/miropcm20-radio.c
@@ -0,0 +1,264 @@
+/* Miro PCM20 radio driver for Linux radio support
+ * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
+ * Thanks to Norberto Pellici for the ACI device interface specification
+ * The API part is based on the radiotrack driver by M. Kirkwood
+ * This driver relies on the aci mixer (drivers/sound/aci.c)
+ * Look there for further info...
+ */
+
+/* Revision history:
+ *
+ * 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
+ * 2000-09-05 Robert Siemer <Robert.Siemer@gmx.de>
+ * removed unfinished volume control (maybe adding it later again)
+ * use OSS-mixer; added stereo control
+ */
+
+/* What ever you think about the ACI, version 0x07 is not very well!
+ * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono
+ * conditions... Robert
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/videodev.h>
+#include "../../../sound/oss/aci.h"
+#include "miropcm20-rds-core.h"
+
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+
+struct pcm20_device {
+ unsigned long freq;
+ int muted;
+ int stereo;
+};
+
+
+static int pcm20_mute(struct pcm20_device *dev, unsigned char mute)
+{
+ dev->muted = mute;
+ return aci_write_cmd(ACI_SET_TUNERMUTE, mute);
+}
+
+static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo)
+{
+ dev->stereo = stereo;
+ return aci_write_cmd(ACI_SET_TUNERMONO, !stereo);
+}
+
+static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq)
+{
+ unsigned char freql;
+ unsigned char freqh;
+
+ dev->freq=freq;
+
+ freq /= 160;
+ if (!(aci_version==0x07 || aci_version>=0xb0))
+ freq /= 10; /* I don't know exactly which version
+ * needs this hack */
+ freql = freq & 0xff;
+ freqh = freq >> 8;
+
+ aci_rds_cmd(RDS_RESET, NULL, 0);
+ pcm20_stereo(dev, 1);
+
+ return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh);
+}
+
+static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal)
+{
+ /* okay, check for signal, stereo and rds here... */
+ int i;
+ unsigned char buf;
+
+ if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0)
+ return i;
+ pr_debug("check_sig: 0x%x\n", i);
+ if (i & 0x80) {
+ /* no signal from tuner */
+ *flags=0;
+ *signal=0;
+ return 0;
+ } else
+ *signal=0xffff;
+
+ if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0)
+ return i;
+ if (i & 0x40) {
+ *flags=0;
+ } else {
+ /* stereo */
+ *flags=VIDEO_TUNER_STEREO_ON;
+ /* I can't see stereo, when forced to mono */
+ dev->stereo=1;
+ }
+
+ if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0)
+ return i;
+ if (buf & 1)
+ /* RDS available */
+ *flags|=VIDEO_TUNER_RDS_ON;
+ else
+ return 0;
+
+ if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0)
+ return i;
+ pr_debug("rds-signal: %d\n", buf);
+ if (buf > 15) {
+ printk("miropcm20-radio: RX strengths unexpected high...\n");
+ buf=15;
+ }
+ /* refine signal */
+ if ((*signal=SCALE(15, 0xffff, buf))==0)
+ *signal = 1;
+
+ return 0;
+}
+
+static int pcm20_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct pcm20_device *pcm20 = dev->priv;
+ int i;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ strcpy(v->name, "Miro PCM20");
+ v->channels=1;
+ v->audios=1;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=87*16000;
+ v->rangehigh=108*16000;
+ pcm20_getflags(pcm20, &v->flags, &v->signal);
+ v->flags|=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ strcpy(v->name, "FM");
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = pcm20->freq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ pcm20->freq = *freq;
+ i=pcm20_setfreq(pcm20, pcm20->freq);
+ pr_debug("First view (setfreq): 0x%x\n", i);
+ return i;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags=VIDEO_AUDIO_MUTABLE;
+ if (pcm20->muted)
+ v->flags|=VIDEO_AUDIO_MUTE;
+ v->mode=VIDEO_SOUND_STEREO;
+ if (pcm20->stereo)
+ v->mode|=VIDEO_SOUND_MONO;
+ /* v->step=2048; */
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+
+ pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE));
+ if(v->flags&VIDEO_SOUND_MONO)
+ pcm20_stereo(pcm20, 0);
+ if(v->flags&VIDEO_SOUND_STEREO)
+ pcm20_stereo(pcm20, 1);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int pcm20_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl);
+}
+
+static struct pcm20_device pcm20_unit = {
+ .freq = 87*16000,
+ .muted = 1,
+};
+
+static struct file_operations pcm20_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = pcm20_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device pcm20_radio = {
+ .owner = THIS_MODULE,
+ .name = "Miro PCM 20 radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_RTRACK,
+ .fops = &pcm20_fops,
+ .priv = &pcm20_unit
+};
+
+static int __init pcm20_init(void)
+{
+ if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ goto video_register_device;
+
+ if(attach_aci_rds()<0)
+ goto attach_aci_rds;
+
+ printk(KERN_INFO "Miro PCM20 radio card driver.\n");
+
+ return 0;
+
+ attach_aci_rds:
+ video_unregister_device(&pcm20_radio);
+ video_register_device:
+ return -EINVAL;
+}
+
+MODULE_AUTHOR("Ruurd Reitsma");
+MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
+MODULE_LICENSE("GPL");
+
+static void __exit pcm20_cleanup(void)
+{
+ unload_aci_rds();
+ video_unregister_device(&pcm20_radio);
+}
+
+module_init(pcm20_init);
+module_exit(pcm20_cleanup);
diff --git a/drivers/media/radio/miropcm20-rds-core.c b/drivers/media/radio/miropcm20-rds-core.c
new file mode 100644
index 00000000000..a917a90cb5d
--- /dev/null
+++ b/drivers/media/radio/miropcm20-rds-core.c
@@ -0,0 +1,210 @@
+/*
+ * Many thanks to Fred Seidel <seidel@metabox.de>, the
+ * designer of the RDS decoder hardware. With his help
+ * I was able to code this driver.
+ * Thanks also to Norberto Pellicci, Dominic Mounteney
+ * <DMounteney@pinnaclesys.com> and www.teleauskunft.de
+ * for good hints on finding Fred. It was somewhat hard
+ * to locate him here in Germany... [:
+ *
+ * Revision history:
+ *
+ * 2000-08-09 Robert Siemer <Robert.Siemer@gmx.de>
+ * RDS support for MiroSound PCM20 radio
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include "../../../sound/oss/aci.h"
+#include "miropcm20-rds-core.h"
+
+#define DEBUG 0
+
+static struct semaphore aci_rds_sem;
+
+#define RDS_DATASHIFT 2 /* Bit 2 */
+#define RDS_DATAMASK (1 << RDS_DATASHIFT)
+#define RDS_BUSYMASK 0x10 /* Bit 4 */
+#define RDS_CLOCKMASK 0x08 /* Bit 3 */
+
+#define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1)
+
+
+#if DEBUG
+static void print_matrix(char array[], unsigned int length)
+{
+ int i, j;
+
+ for (i=0; i<length; i++) {
+ printk(KERN_DEBUG "aci-rds: ");
+ for (j=7; j>=0; j--) {
+ printk("%d", (array[i] >> j) & 0x1);
+ }
+ if (i%8 == 0)
+ printk(" byte-border\n");
+ else
+ printk("\n");
+ }
+}
+#endif /* DEBUG */
+
+static int byte2trans(unsigned char byte, unsigned char sendbuffer[], int size)
+{
+ int i;
+
+ if (size != 8)
+ return -1;
+ for (i = 7; i >= 0; i--)
+ sendbuffer[7-i] = (byte & (1 << i)) ? RDS_DATAMASK : 0;
+ sendbuffer[0] |= RDS_CLOCKMASK;
+
+ return 0;
+}
+
+static int rds_waitread(void)
+{
+ unsigned char byte;
+ int i=2000;
+
+ do {
+ byte=inb(RDS_REGISTER);
+ i--;
+ }
+ while ((byte & RDS_BUSYMASK) && i);
+
+ if (i) {
+ #if DEBUG
+ printk(KERN_DEBUG "rds_waitread()");
+ print_matrix(&byte, 1);
+ #endif
+ return (byte);
+ } else {
+ printk(KERN_WARNING "aci-rds: rds_waitread() timeout...\n");
+ return -1;
+ }
+}
+
+/* don't use any ..._nowait() function if you are not sure what you do... */
+
+static inline void rds_rawwrite_nowait(unsigned char byte)
+{
+ #if DEBUG
+ printk(KERN_DEBUG "rds_rawwrite()");
+ print_matrix(&byte, 1);
+ #endif
+ outb(byte, RDS_REGISTER);
+}
+
+static int rds_rawwrite(unsigned char byte)
+{
+ if (rds_waitread() >= 0) {
+ rds_rawwrite_nowait(byte);
+ return 0;
+ } else
+ return -1;
+}
+
+static int rds_write(unsigned char cmd)
+{
+ unsigned char sendbuffer[8];
+ int i;
+
+ if (byte2trans(cmd, sendbuffer, 8) != 0){
+ return -1;
+ } else {
+ for (i=0; i<8; i++) {
+ rds_rawwrite(sendbuffer[i]);
+ }
+ }
+ return 0;
+}
+
+static int rds_readcycle_nowait(void)
+{
+ rds_rawwrite_nowait(0);
+ return rds_waitread();
+}
+
+static int rds_readcycle(void)
+{
+ if (rds_rawwrite(0) < 0)
+ return -1;
+ return rds_waitread();
+}
+
+static int rds_read(unsigned char databuffer[], int datasize)
+{
+ #define READSIZE (8*datasize)
+
+ int i,j;
+
+ if (datasize < 1) /* nothing to read */
+ return 0;
+
+ /* to be able to use rds_readcycle_nowait()
+ I have to waitread() here */
+ if (rds_waitread() < 0)
+ return -1;
+
+ memset(databuffer, 0, datasize);
+
+ for (i=0; i< READSIZE; i++)
+ if((j=rds_readcycle_nowait()) < 0) {
+ return -1;
+ } else {
+ databuffer[i/8]|=(RDS_DATA(j) << (7-(i%8)));
+ }
+
+ return 0;
+}
+
+static int rds_ack(void)
+{
+ int i=rds_readcycle();
+
+ if (i < 0)
+ return -1;
+ if (i & RDS_DATAMASK) {
+ return 0; /* ACK */
+ } else {
+ printk(KERN_DEBUG "aci-rds: NACK\n");
+ return 1; /* NACK */
+ }
+}
+
+int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize)
+{
+ int ret;
+
+ if (down_interruptible(&aci_rds_sem))
+ return -EINTR;
+
+ rds_write(cmd);
+
+ /* RDS_RESET doesn't need further processing */
+ if (cmd!=RDS_RESET && (rds_ack() || rds_read(databuffer, datasize)))
+ ret = -1;
+ else
+ ret = 0;
+
+ up(&aci_rds_sem);
+
+ return ret;
+}
+EXPORT_SYMBOL(aci_rds_cmd);
+
+int __init attach_aci_rds(void)
+{
+ init_MUTEX(&aci_rds_sem);
+ return 0;
+}
+
+void __exit unload_aci_rds(void)
+{
+}
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/radio/miropcm20-rds-core.h b/drivers/media/radio/miropcm20-rds-core.h
new file mode 100644
index 00000000000..aeb5761f046
--- /dev/null
+++ b/drivers/media/radio/miropcm20-rds-core.h
@@ -0,0 +1,19 @@
+#ifndef _MIROPCM20_RDS_CORE_H_
+#define _MIROPCM20_RDS_CORE_H_
+
+extern int aci_rds_cmd(unsigned char cmd, unsigned char databuffer[], int datasize);
+
+#define RDS_STATUS 0x01
+#define RDS_STATIONNAME 0x02
+#define RDS_TEXT 0x03
+#define RDS_ALTFREQ 0x04
+#define RDS_TIMEDATE 0x05
+#define RDS_PI_CODE 0x06
+#define RDS_PTYTATP 0x07
+#define RDS_RESET 0x08
+#define RDS_RXVALUE 0x09
+
+extern void __exit unload_aci_rds(void);
+extern int __init attach_aci_rds(void);
+
+#endif /* _MIROPCM20_RDS_CORE_H_ */
diff --git a/drivers/media/radio/miropcm20-rds.c b/drivers/media/radio/miropcm20-rds.c
new file mode 100644
index 00000000000..df79d5e0aae
--- /dev/null
+++ b/drivers/media/radio/miropcm20-rds.c
@@ -0,0 +1,133 @@
+/* MiroSOUND PCM20 radio rds interface driver
+ * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de>
+ * Thanks to Fred Seidel. See miropcm20-rds-core.c for further information.
+ */
+
+/* Revision history:
+ *
+ * 2001-04-18 Robert Siemer <Robert.Siemer@gmx.de>
+ * separate file for user interface driver
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include "miropcm20-rds-core.h"
+
+static char * text_buffer;
+static int rds_users = 0;
+
+
+static int rds_f_open(struct inode *in, struct file *fi)
+{
+ if (rds_users)
+ return -EBUSY;
+
+ rds_users++;
+ if ((text_buffer=kmalloc(66, GFP_KERNEL)) == 0) {
+ rds_users--;
+ printk(KERN_NOTICE "aci-rds: Out of memory by open()...\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int rds_f_release(struct inode *in, struct file *fi)
+{
+ kfree(text_buffer);
+
+ rds_users--;
+ return 0;
+}
+
+static void print_matrix(char *ch, char out[])
+{
+ int j;
+
+ for (j=7; j>=0; j--) {
+ out[7-j] = ((*ch >> j) & 0x1) + '0';
+ }
+}
+
+static ssize_t rds_f_read(struct file *file, char __user *buffer, size_t length, loff_t *offset)
+{
+// i = sprintf(text_buffer, "length: %d, offset: %d\n", length, *offset);
+
+ char c;
+ char bits[8];
+
+ msleep(2000);
+ aci_rds_cmd(RDS_STATUS, &c, 1);
+ print_matrix(&c, bits);
+ if (copy_to_user(buffer, bits, 8))
+ return -EFAULT;
+
+/* if ((c >> 3) & 1) {
+ aci_rds_cmd(RDS_STATIONNAME, text_buffer+1, 8);
+ text_buffer[0] = ' ' ;
+ text_buffer[9] = '\n';
+ return copy_to_user(buffer+8, text_buffer, 10) ? -EFAULT: 18;
+ }
+*/
+/* if ((c >> 6) & 1) {
+ aci_rds_cmd(RDS_PTYTATP, &c, 1);
+ if ( c & 1)
+ sprintf(text_buffer, " M");
+ else
+ sprintf(text_buffer, " S");
+ if ((c >> 1) & 1)
+ sprintf(text_buffer+2, " TA");
+ else
+ sprintf(text_buffer+2, " --");
+ if ((c >> 7) & 1)
+ sprintf(text_buffer+5, " TP");
+ else
+ sprintf(text_buffer+5, " --");
+ sprintf(text_buffer+8, " %2d\n", (c >> 2) & 0x1f);
+ return copy_to_user(buffer+8, text_buffer, 12) ? -EFAULT: 20;
+ }
+*/
+
+ if ((c >> 4) & 1) {
+ aci_rds_cmd(RDS_TEXT, text_buffer, 65);
+ text_buffer[0] = ' ' ;
+ text_buffer[65] = '\n';
+ return copy_to_user(buffer+8, text_buffer,66) ? -EFAULT : 66+8;
+ } else {
+ put_user('\n', buffer+8);
+ return 9;
+ }
+}
+
+static struct file_operations rds_fops = {
+ .owner = THIS_MODULE,
+ .read = rds_f_read,
+ .open = rds_f_open,
+ .release = rds_f_release
+};
+
+static struct miscdevice rds_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "radiotext",
+ .devfs_name = "v4l/rds/radiotext",
+ .fops = &rds_fops,
+};
+
+static int __init miropcm20_rds_init(void)
+{
+ return misc_register(&rds_miscdev);
+}
+
+static void __exit miropcm20_rds_cleanup(void)
+{
+ misc_deregister(&rds_miscdev);
+}
+
+module_init(miropcm20_rds_init);
+module_exit(miropcm20_rds_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c
new file mode 100644
index 00000000000..8b4ad70dd1b
--- /dev/null
+++ b/drivers/media/radio/radio-aimslab.c
@@ -0,0 +1,368 @@
+/* radiotrack (radioreveal) driver for Linux radio support
+ * (c) 1997 M. Kirkwood
+ * Converted to new API by Alan Cox <Alan.Cox@linux.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'
+ * reverse engineering of AIMS' code :-)
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ *
+ * The signal strength query is unsurprisingly inaccurate. And it seems
+ * to indicate that (on my card, at least) the frequency setting isn't
+ * too great. (I have to tune up .025MHz from what the freq should be
+ * to get a report that the thing is tuned.)
+ *
+ * Volume control is (ugh) analogue:
+ * out(port, start_increasing_volume);
+ * wait(a_wee_while);
+ * out(port, stop_changing_the_volume);
+ *
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_RTRACK_PORT */
+#include <asm/semaphore.h> /* Lock for the I/O */
+
+#ifndef CONFIG_RADIO_RTRACK_PORT
+#define CONFIG_RADIO_RTRACK_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_RTRACK_PORT;
+static int radio_nr = -1;
+static struct semaphore lock;
+
+struct rt_device
+{
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+static void sleep_delay(long n)
+{
+ /* Sleep nicely for 'n' uS */
+ int d=n/(1000000/HZ);
+ if(!d)
+ udelay(n);
+ else
+ msleep(jiffies_to_msecs(d));
+}
+
+static void rt_decvol(void)
+{
+ outb(0x58, io); /* volume down + sigstr + on */
+ sleep_delay(100000);
+ outb(0xd8, io); /* volume steady + sigstr + on */
+}
+
+static void rt_incvol(void)
+{
+ outb(0x98, io); /* volume up + sigstr + on */
+ sleep_delay(100000);
+ outb(0xd8, io); /* volume steady + sigstr + on */
+}
+
+static void rt_mute(struct rt_device *dev)
+{
+ dev->muted = 1;
+ down(&lock);
+ outb(0xd0, io); /* volume steady, off */
+ up(&lock);
+}
+
+static int rt_setvol(struct rt_device *dev, int vol)
+{
+ int i;
+
+ down(&lock);
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ outb (0xd8, io); /* enable card */
+ }
+ up(&lock);
+ return 0;
+ }
+
+ if(vol == 0) { /* volume = 0 means mute the card */
+ outb(0x48, io); /* volume down but still "on" */
+ sleep_delay(2000000); /* make sure it's totally down */
+ outb(0xd0, io); /* volume steady, off */
+ dev->curvol = 0; /* track the volume state! */
+ up(&lock);
+ return 0;
+ }
+
+ dev->muted = 0;
+ if(vol > dev->curvol)
+ for(i = dev->curvol; i < vol; i++)
+ rt_incvol();
+ else
+ for(i = dev->curvol; i > vol; i--)
+ rt_decvol();
+
+ dev->curvol = vol;
+ up(&lock);
+ return 0;
+}
+
+/* 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
+ * and bit 4 (+16) is to keep the signal strength meter enabled
+ */
+
+static void send_0_byte(int port, struct rt_device *dev)
+{
+ if ((dev->curvol == 0) || (dev->muted)) {
+ outb_p(128+64+16+ 1, port); /* wr-enable + data low */
+ outb_p(128+64+16+2+1, port); /* clock */
+ }
+ else {
+ outb_p(128+64+16+8+ 1, port); /* on + wr-enable + data low */
+ outb_p(128+64+16+8+2+1, port); /* clock */
+ }
+ sleep_delay(1000);
+}
+
+static void send_1_byte(int port, struct rt_device *dev)
+{
+ if ((dev->curvol == 0) || (dev->muted)) {
+ outb_p(128+64+16+4 +1, port); /* wr-enable+data high */
+ outb_p(128+64+16+4+2+1, port); /* clock */
+ }
+ else {
+ outb_p(128+64+16+8+4 +1, port); /* on+wr-enable+data high */
+ outb_p(128+64+16+8+4+2+1, port); /* clock */
+ }
+
+ sleep_delay(1000);
+}
+
+static int rt_setfreq(struct rt_device *dev, unsigned long freq)
+{
+ int i;
+
+ /* adapted from radio-aztech.c */
+
+ /* now uses VIDEO_TUNER_LOW for fine tuning */
+
+ freq += 171200; /* Add 10.7 MHz IF */
+ freq /= 800; /* Convert to 50 kHz units */
+
+ down(&lock); /* Stop other ops interfering */
+
+ send_0_byte (io, dev); /* 0: LSB of frequency */
+
+ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
+ if (freq & (1 << i))
+ send_1_byte (io, dev);
+ else
+ send_0_byte (io, dev);
+
+ send_0_byte (io, dev); /* 14: test bit - always 0 */
+ send_0_byte (io, dev); /* 15: test bit - always 0 */
+
+ send_0_byte (io, dev); /* 16: band data 0 - always 0 */
+ send_0_byte (io, dev); /* 17: band data 1 - always 0 */
+ send_0_byte (io, dev); /* 18: band data 2 - always 0 */
+ send_0_byte (io, dev); /* 19: time base - always 0 */
+
+ send_0_byte (io, dev); /* 20: spacing (0 = 25 kHz) */
+ send_1_byte (io, dev); /* 21: spacing (1 = 25 kHz) */
+ send_0_byte (io, dev); /* 22: spacing (0 = 25 kHz) */
+ send_1_byte (io, dev); /* 23: AM/FM (FM = 1, always) */
+
+ if ((dev->curvol == 0) || (dev->muted))
+ outb (0xd0, io); /* volume steady + sigstr */
+ else
+ outb (0xd8, io); /* volume steady + sigstr + on */
+
+ up(&lock);
+
+ return 0;
+}
+
+static int rt_getsigstr(struct rt_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int rt_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct rt_device *rt=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "RadioTrack");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=(87*16000);
+ v->rangehigh=(108*16000);
+ v->flags=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ strcpy(v->name, "FM");
+ v->signal=0xFFFF*rt_getsigstr(rt);
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = rt->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ rt->curfreq = *freq;
+ rt_setfreq(rt, rt->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ v->volume=rt->curvol * 6554;
+ v->step=6554;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ rt_mute(rt);
+ else
+ rt_setvol(rt,v->volume/6554);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int rt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, rt_do_ioctl);
+}
+
+static struct rt_device rtrack_unit;
+
+static struct file_operations rtrack_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = rt_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device rtrack_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "RadioTrack radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_RTRACK,
+ .fops = &rtrack_fops,
+};
+
+static int __init rtrack_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+
+ if (!request_region(io, 2, "rtrack"))
+ {
+ printk(KERN_ERR "rtrack: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ rtrack_radio.priv=&rtrack_unit;
+
+ if(video_register_device(&rtrack_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n");
+
+ /* Set up the I/O locking */
+
+ init_MUTEX(&lock);
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+ outb(0x48, io); /* volume down but still "on" */
+ sleep_delay(2000000); /* make sure it's totally down */
+ outb(0xc0, io); /* steady volume, mute card */
+ rtrack_unit.curvol = 0;
+
+ return 0;
+}
+
+MODULE_AUTHOR("M.Kirkwood");
+MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20f or 0x30f)");
+module_param(radio_nr, int, 0);
+
+static void __exit cleanup_rtrack_module(void)
+{
+ video_unregister_device(&rtrack_radio);
+ release_region(io,2);
+}
+
+module_init(rtrack_init);
+module_exit(cleanup_rtrack_module);
+
diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c
new file mode 100644
index 00000000000..013c835ed91
--- /dev/null
+++ b/drivers/media/radio/radio-aztech.c
@@ -0,0 +1,315 @@
+/* radio-aztech.c - Aztech radio card driver for Linux 2.2
+ *
+ * Adapted to support the Video for Linux API by
+ * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
+ *
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+ * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
+ * along with more information on the card itself.
+ *
+ * History:
+ * 1999-02-24 Russell Kroll <rkroll@exploits.org>
+ * Fine tuning/VIDEO_TUNER_LOW
+ * Range expanded to 87-108 MHz (from 87.9-107.8)
+ *
+ * Notable changes from the original source:
+ * - includes stripped down to the essentials
+ * - for loops used as delays replaced with udelay()
+ * - #defines removed, changed to static values
+ * - tuning structure changed - no more character arrays, other changes
+*/
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_AZTECH_PORT */
+
+/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
+
+#ifndef CONFIG_RADIO_AZTECH_PORT
+#define CONFIG_RADIO_AZTECH_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_AZTECH_PORT;
+static int radio_nr = -1;
+static int radio_wait_time = 1000;
+static struct semaphore lock;
+
+struct az_device
+{
+ int curvol;
+ unsigned long curfreq;
+ int stereo;
+};
+
+static int volconvert(int level)
+{
+ level>>=14; /* Map 16bits down to 2 bit */
+ level&=3;
+
+ /* convert to card-friendly values */
+ switch (level)
+ {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ case 2:
+ return 4;
+ case 3:
+ return 5;
+ }
+ return 0; /* Quieten gcc */
+}
+
+static void send_0_byte (struct az_device *dev)
+{
+ udelay(radio_wait_time);
+ outb_p(2+volconvert(dev->curvol), io);
+ outb_p(64+2+volconvert(dev->curvol), io);
+}
+
+static void send_1_byte (struct az_device *dev)
+{
+ udelay (radio_wait_time);
+ outb_p(128+2+volconvert(dev->curvol), io);
+ outb_p(128+64+2+volconvert(dev->curvol), io);
+}
+
+static int az_setvol(struct az_device *dev, int vol)
+{
+ down(&lock);
+ outb (volconvert(vol), io);
+ up(&lock);
+ return 0;
+}
+
+/* thanks to Michael Dwyer for giving me a dose of clues in
+ * the signal strength department..
+ *
+ * This card has a stereo bit - bit 0 set = mono, not set = stereo
+ * It also has a "signal" bit - bit 1 set = bad signal, not set = good
+ *
+ */
+
+static int az_getsigstr(struct az_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int az_getstereo(struct az_device *dev)
+{
+ if (inb(io) & 1) /* bit set = mono */
+ return 0;
+ return 1; /* stereo */
+}
+
+static int az_setfreq(struct az_device *dev, unsigned long frequency)
+{
+ int i;
+
+ frequency += 171200; /* Add 10.7 MHz IF */
+ frequency /= 800; /* Convert to 50 kHz units */
+
+ down(&lock);
+
+ send_0_byte (dev); /* 0: LSB of frequency */
+
+ for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
+ if (frequency & (1 << i))
+ send_1_byte (dev);
+ else
+ send_0_byte (dev);
+
+ send_0_byte (dev); /* 14: test bit - always 0 */
+ send_0_byte (dev); /* 15: test bit - always 0 */
+ send_0_byte (dev); /* 16: band data 0 - always 0 */
+ if (dev->stereo) /* 17: stereo (1 to enable) */
+ send_1_byte (dev);
+ else
+ send_0_byte (dev);
+
+ send_1_byte (dev); /* 18: band data 1 - unknown */
+ send_0_byte (dev); /* 19: time base - always 0 */
+ send_0_byte (dev); /* 20: spacing (0 = 25 kHz) */
+ send_1_byte (dev); /* 21: spacing (1 = 25 kHz) */
+ send_0_byte (dev); /* 22: spacing (0 = 25 kHz) */
+ send_1_byte (dev); /* 23: AM/FM (FM = 1, always) */
+
+ /* latch frequency */
+
+ udelay (radio_wait_time);
+ outb_p(128+64+volconvert(dev->curvol), io);
+
+ up(&lock);
+
+ return 0;
+}
+
+static int az_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct az_device *az = dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "Aztech Radio");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=(87*16000);
+ v->rangehigh=(108*16000);
+ v->flags=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ v->signal=0xFFFF*az_getsigstr(az);
+ if(az_getstereo(az))
+ v->flags|=VIDEO_TUNER_STEREO_ON;
+ strcpy(v->name, "FM");
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = az->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ az->curfreq = *freq;
+ az_setfreq(az, az->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ if(az->stereo)
+ v->mode=VIDEO_SOUND_STEREO;
+ else
+ v->mode=VIDEO_SOUND_MONO;
+ v->volume=az->curvol;
+ v->step=16384;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ az->curvol=v->volume;
+
+ az->stereo=(v->mode&VIDEO_SOUND_STEREO)?1:0;
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ az_setvol(az,0);
+ else
+ az_setvol(az,az->curvol);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int az_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, az_do_ioctl);
+}
+
+static struct az_device aztech_unit;
+
+static struct file_operations aztech_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = az_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device aztech_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "Aztech radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_AZTECH,
+ .fops = &aztech_fops,
+};
+
+static int __init aztech_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+
+ if (!request_region(io, 2, "aztech"))
+ {
+ printk(KERN_ERR "aztech: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ init_MUTEX(&lock);
+ aztech_radio.priv=&aztech_unit;
+
+ if(video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io,2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "Aztech radio card driver v1.00/19990224 rkroll@exploits.org\n");
+ /* mute card - prevents noisy bootups */
+ outb (0, io);
+ return 0;
+}
+
+MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the Aztech radio card.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the Aztech card (0x350 or 0x358)");
+
+static void __exit aztech_cleanup(void)
+{
+ video_unregister_device(&aztech_radio);
+ release_region(io,2);
+}
+
+module_init(aztech_init);
+module_exit(aztech_cleanup);
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c
new file mode 100644
index 00000000000..53d399b6652
--- /dev/null
+++ b/drivers/media/radio/radio-cadet.c
@@ -0,0 +1,620 @@
+/* radio-cadet.c - A video4linux driver for the ADS Cadet AM/FM Radio Card
+ *
+ * by Fred Gleason <fredg@wava.com>
+ * Version 0.3.3
+ *
+ * (Loosely) based on code for the Aztech radio card by
+ *
+ * Russell Kroll (rkroll@exploits.org)
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+ * History:
+ * 2000-04-29 Russell Kroll <rkroll@exploits.org>
+ * Added ISAPnP detection for Linux 2.3/2.4
+ *
+ * 2001-01-10 Russell Kroll <rkroll@exploits.org>
+ * Removed dead CONFIG_RADIO_CADET_PORT code
+ * PnP detection on load is now default (no args necessary)
+ *
+ * 2002-01-17 Adam Belay <ambx1@neo.rr.com>
+ * Updated to latest pnp code
+ *
+ * 2003-01-31 Alan Cox <alan@redhat.com>
+ * Cleaned up locking, delay code, general odds and ends
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/param.h>
+#include <linux/pnp.h>
+
+#define RDS_BUFFER 256
+
+static int io=-1; /* default to isapnp activation */
+static int radio_nr = -1;
+static int users=0;
+static int curtuner=0;
+static int tunestat=0;
+static int sigstrength=0;
+static wait_queue_head_t read_queue;
+static struct timer_list readtimer;
+static __u8 rdsin=0,rdsout=0,rdsstat=0;
+static unsigned char rdsbuf[RDS_BUFFER];
+static spinlock_t cadet_io_lock;
+
+static int cadet_probe(void);
+
+/*
+ * Signal Strength Threshold Values
+ * The V4L API spec does not define any particular unit for the signal
+ * strength value. These values are in microvolts of RF at the tuner's input.
+ */
+static __u16 sigtable[2][4]={{5,10,30,150},{28,40,63,1000}};
+
+static int cadet_getrds(void)
+{
+ int rdsstat=0;
+
+ spin_lock(&cadet_io_lock);
+ outb(3,io); /* Select Decoder Control/Status */
+ outb(inb(io+1)&0x7f,io+1); /* Reset RDS detection */
+ spin_unlock(&cadet_io_lock);
+
+ msleep(100);
+
+ spin_lock(&cadet_io_lock);
+ outb(3,io); /* Select Decoder Control/Status */
+ if((inb(io+1)&0x80)!=0) {
+ rdsstat|=VIDEO_TUNER_RDS_ON;
+ }
+ if((inb(io+1)&0x10)!=0) {
+ rdsstat|=VIDEO_TUNER_MBS_ON;
+ }
+ spin_unlock(&cadet_io_lock);
+ return rdsstat;
+}
+
+static int cadet_getstereo(void)
+{
+ int ret = 0;
+ if(curtuner != 0) /* Only FM has stereo capability! */
+ return 0;
+
+ spin_lock(&cadet_io_lock);
+ outb(7,io); /* Select tuner control */
+ if( (inb(io+1) & 0x40) == 0)
+ ret = 1;
+ spin_unlock(&cadet_io_lock);
+ return ret;
+}
+
+static unsigned cadet_gettune(void)
+{
+ int curvol,i;
+ unsigned fifo=0;
+
+ /*
+ * Prepare for read
+ */
+
+ spin_lock(&cadet_io_lock);
+
+ outb(7,io); /* Select tuner control */
+ curvol=inb(io+1); /* Save current volume/mute setting */
+ outb(0x00,io+1); /* Ensure WRITE-ENABLE is LOW */
+ tunestat=0xffff;
+
+ /*
+ * Read the shift register
+ */
+ for(i=0;i<25;i++) {
+ fifo=(fifo<<1)|((inb(io+1)>>7)&0x01);
+ if(i<24) {
+ outb(0x01,io+1);
+ tunestat&=inb(io+1);
+ outb(0x00,io+1);
+ }
+ }
+
+ /*
+ * Restore volume/mute setting
+ */
+ outb(curvol,io+1);
+ spin_unlock(&cadet_io_lock);
+
+ return fifo;
+}
+
+static unsigned cadet_getfreq(void)
+{
+ int i;
+ unsigned freq=0,test,fifo=0;
+
+ /*
+ * Read current tuning
+ */
+ fifo=cadet_gettune();
+
+ /*
+ * Convert to actual frequency
+ */
+ if(curtuner==0) { /* FM */
+ test=12500;
+ for(i=0;i<14;i++) {
+ if((fifo&0x01)!=0) {
+ freq+=test;
+ }
+ test=test<<1;
+ fifo=fifo>>1;
+ }
+ freq-=10700000; /* IF frequency is 10.7 MHz */
+ freq=(freq*16)/1000000; /* Make it 1/16 MHz */
+ }
+ if(curtuner==1) { /* AM */
+ freq=((fifo&0x7fff)-2010)*16;
+ }
+
+ return freq;
+}
+
+static void cadet_settune(unsigned fifo)
+{
+ int i;
+ unsigned test;
+
+ spin_lock(&cadet_io_lock);
+
+ outb(7,io); /* Select tuner control */
+ /*
+ * Write the shift register
+ */
+ test=0;
+ test=(fifo>>23)&0x02; /* Align data for SDO */
+ test|=0x1c; /* SDM=1, SWE=1, SEN=1, SCK=0 */
+ outb(7,io); /* Select tuner control */
+ outb(test,io+1); /* Initialize for write */
+ for(i=0;i<25;i++) {
+ test|=0x01; /* Toggle SCK High */
+ outb(test,io+1);
+ test&=0xfe; /* Toggle SCK Low */
+ outb(test,io+1);
+ fifo=fifo<<1; /* Prepare the next bit */
+ test=0x1c|((fifo>>23)&0x02);
+ outb(test,io+1);
+ }
+ spin_unlock(&cadet_io_lock);
+}
+
+static void cadet_setfreq(unsigned freq)
+{
+ unsigned fifo;
+ int i,j,test;
+ int curvol;
+
+ /*
+ * Formulate a fifo command
+ */
+ fifo=0;
+ if(curtuner==0) { /* FM */
+ test=102400;
+ freq=(freq*1000)/16; /* Make it kHz */
+ freq+=10700; /* IF is 10700 kHz */
+ for(i=0;i<14;i++) {
+ fifo=fifo<<1;
+ if(freq>=test) {
+ fifo|=0x01;
+ freq-=test;
+ }
+ test=test>>1;
+ }
+ }
+ if(curtuner==1) { /* AM */
+ fifo=(freq/16)+2010; /* Make it kHz */
+ fifo|=0x100000; /* Select AM Band */
+ }
+
+ /*
+ * Save current volume/mute setting
+ */
+
+ spin_lock(&cadet_io_lock);
+ outb(7,io); /* Select tuner control */
+ curvol=inb(io+1);
+ spin_unlock(&cadet_io_lock);
+
+ /*
+ * Tune the card
+ */
+ for(j=3;j>-1;j--) {
+ cadet_settune(fifo|(j<<16));
+
+ spin_lock(&cadet_io_lock);
+ outb(7,io); /* Select tuner control */
+ outb(curvol,io+1);
+ spin_unlock(&cadet_io_lock);
+
+ msleep(100);
+
+ cadet_gettune();
+ if((tunestat & 0x40) == 0) { /* Tuned */
+ sigstrength=sigtable[curtuner][j];
+ return;
+ }
+ }
+ sigstrength=0;
+}
+
+
+static int cadet_getvol(void)
+{
+ int ret = 0;
+
+ spin_lock(&cadet_io_lock);
+
+ outb(7,io); /* Select tuner control */
+ if((inb(io + 1) & 0x20) != 0)
+ ret = 0xffff;
+
+ spin_unlock(&cadet_io_lock);
+ return ret;
+}
+
+
+static void cadet_setvol(int vol)
+{
+ spin_lock(&cadet_io_lock);
+ outb(7,io); /* Select tuner control */
+ if(vol>0)
+ outb(0x20,io+1);
+ else
+ outb(0x00,io+1);
+ spin_unlock(&cadet_io_lock);
+}
+
+static void cadet_handler(unsigned long data)
+{
+ /*
+ * Service the RDS fifo
+ */
+
+ if(spin_trylock(&cadet_io_lock))
+ {
+ outb(0x3,io); /* Select RDS Decoder Control */
+ if((inb(io+1)&0x20)!=0) {
+ printk(KERN_CRIT "cadet: RDS fifo overflow\n");
+ }
+ outb(0x80,io); /* Select RDS fifo */
+ while((inb(io)&0x80)!=0) {
+ rdsbuf[rdsin]=inb(io+1);
+ if(rdsin==rdsout)
+ printk(KERN_WARNING "cadet: RDS buffer overflow\n");
+ else
+ rdsin++;
+ }
+ spin_unlock(&cadet_io_lock);
+ }
+
+ /*
+ * Service pending read
+ */
+ if( rdsin!=rdsout)
+ wake_up_interruptible(&read_queue);
+
+ /*
+ * Clean up and exit
+ */
+ init_timer(&readtimer);
+ readtimer.function=cadet_handler;
+ readtimer.data=(unsigned long)0;
+ readtimer.expires=jiffies+(HZ/20);
+ add_timer(&readtimer);
+}
+
+
+
+static ssize_t cadet_read(struct file *file, char __user *data,
+ size_t count, loff_t *ppos)
+{
+ int i=0;
+ unsigned char readbuf[RDS_BUFFER];
+
+ if(rdsstat==0) {
+ spin_lock(&cadet_io_lock);
+ rdsstat=1;
+ outb(0x80,io); /* Select RDS fifo */
+ spin_unlock(&cadet_io_lock);
+ init_timer(&readtimer);
+ readtimer.function=cadet_handler;
+ readtimer.data=(unsigned long)0;
+ readtimer.expires=jiffies+(HZ/20);
+ add_timer(&readtimer);
+ }
+ if(rdsin==rdsout) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+ interruptible_sleep_on(&read_queue);
+ }
+ while( i<count && rdsin!=rdsout)
+ readbuf[i++]=rdsbuf[rdsout++];
+
+ if (copy_to_user(data,readbuf,i))
+ return -EFAULT;
+ return i;
+}
+
+
+
+static int cadet_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=2;
+ v->audios=1;
+ strcpy(v->name, "ADS Cadet");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if((v->tuner<0)||(v->tuner>1)) {
+ return -EINVAL;
+ }
+ switch(v->tuner) {
+ case 0:
+ strcpy(v->name,"FM");
+ v->rangelow=1400; /* 87.5 MHz */
+ v->rangehigh=1728; /* 108.0 MHz */
+ v->flags=0;
+ v->mode=0;
+ v->mode|=VIDEO_MODE_AUTO;
+ v->signal=sigstrength;
+ if(cadet_getstereo()==1) {
+ v->flags|=VIDEO_TUNER_STEREO_ON;
+ }
+ v->flags|=cadet_getrds();
+ break;
+ case 1:
+ strcpy(v->name,"AM");
+ v->rangelow=8320; /* 520 kHz */
+ v->rangehigh=26400; /* 1650 kHz */
+ v->flags=0;
+ v->flags|=VIDEO_TUNER_LOW;
+ v->mode=0;
+ v->mode|=VIDEO_MODE_AUTO;
+ v->signal=sigstrength;
+ break;
+ }
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if((v->tuner<0)||(v->tuner>1)) {
+ return -EINVAL;
+ }
+ curtuner=v->tuner;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = cadet_getfreq();
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ if((curtuner==0)&&((*freq<1400)||(*freq>1728))) {
+ return -EINVAL;
+ }
+ if((curtuner==1)&&((*freq<8320)||(*freq>26400))) {
+ return -EINVAL;
+ }
+ cadet_setfreq(*freq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ if(cadet_getstereo()==0) {
+ v->mode=VIDEO_SOUND_MONO;
+ } else {
+ v->mode=VIDEO_SOUND_STEREO;
+ }
+ v->volume=cadet_getvol();
+ v->step=0xffff;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ cadet_setvol(v->volume);
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ cadet_setvol(0);
+ else
+ cadet_setvol(0xffff);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int cadet_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, cadet_do_ioctl);
+}
+
+static int cadet_open(struct inode *inode, struct file *file)
+{
+ if(users)
+ return -EBUSY;
+ users++;
+ init_waitqueue_head(&read_queue);
+ return 0;
+}
+
+static int cadet_release(struct inode *inode, struct file *file)
+{
+ del_timer_sync(&readtimer);
+ rdsstat=0;
+ users--;
+ return 0;
+}
+
+
+static struct file_operations cadet_fops = {
+ .owner = THIS_MODULE,
+ .open = cadet_open,
+ .release = cadet_release,
+ .read = cadet_read,
+ .ioctl = cadet_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device cadet_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "Cadet radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_CADET,
+ .fops = &cadet_fops,
+};
+
+static struct pnp_device_id cadet_pnp_devices[] = {
+ /* ADS Cadet AM/FM Radio Card */
+ {.id = "MSM0c24", .driver_data = 0},
+ {.id = ""}
+};
+
+MODULE_DEVICE_TABLE(pnp, cadet_pnp_devices);
+
+static int cadet_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
+{
+ if (!dev)
+ return -ENODEV;
+ /* only support one device */
+ if (io > 0)
+ return -EBUSY;
+
+ if (!pnp_port_valid(dev, 0)) {
+ return -ENODEV;
+ }
+
+ io = pnp_port_start(dev, 0);
+
+ printk ("radio-cadet: PnP reports device at %#x\n", io);
+
+ return io;
+}
+
+static struct pnp_driver cadet_pnp_driver = {
+ .name = "radio-cadet",
+ .id_table = cadet_pnp_devices,
+ .probe = cadet_pnp_probe,
+ .remove = NULL,
+};
+
+static int cadet_probe(void)
+{
+ static int iovals[8]={0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e};
+ int i;
+
+ for(i=0;i<8;i++) {
+ io=iovals[i];
+ if(request_region(io,2, "cadet-probe")>=0) {
+ cadet_setfreq(1410);
+ if(cadet_getfreq()==1410) {
+ release_region(io, 2);
+ return io;
+ }
+ release_region(io, 2);
+ }
+ }
+ return -1;
+}
+
+/*
+ * io should only be set if the user has used something like
+ * isapnp (the userspace program) to initialize this card for us
+ */
+
+static int __init cadet_init(void)
+{
+ spin_lock_init(&cadet_io_lock);
+
+ /*
+ * If a probe was requested then probe ISAPnP first (safest)
+ */
+ if (io < 0)
+ pnp_register_driver(&cadet_pnp_driver);
+ /*
+ * If that fails then probe unsafely if probe is requested
+ */
+ if(io < 0)
+ io = cadet_probe ();
+
+ /*
+ * Else we bail out
+ */
+
+ if(io < 0) {
+#ifdef MODULE
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+#endif
+ goto fail;
+ }
+ if (!request_region(io,2,"cadet"))
+ goto fail;
+ if(video_register_device(&cadet_radio,VFL_TYPE_RADIO,radio_nr)==-1) {
+ release_region(io,2);
+ goto fail;
+ }
+ printk(KERN_INFO "ADS Cadet Radio Card at 0x%x\n",io);
+ return 0;
+fail:
+ pnp_unregister_driver(&cadet_pnp_driver);
+ return -1;
+}
+
+
+
+MODULE_AUTHOR("Fred Gleason, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the ADS Cadet AM/FM/RDS radio card.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of Cadet card (0x330,0x332,0x334,0x336,0x338,0x33a,0x33c,0x33e)");
+module_param(radio_nr, int, 0);
+
+static void __exit cadet_cleanup_module(void)
+{
+ video_unregister_device(&cadet_radio);
+ release_region(io,2);
+ pnp_unregister_driver(&cadet_pnp_driver);
+}
+
+module_init(cadet_init);
+module_exit(cadet_cleanup_module);
+
diff --git a/drivers/media/radio/radio-gemtek-pci.c b/drivers/media/radio/radio-gemtek-pci.c
new file mode 100644
index 00000000000..630cc786d0a
--- /dev/null
+++ b/drivers/media/radio/radio-gemtek-pci.c
@@ -0,0 +1,416 @@
+/*
+ ***************************************************************************
+ *
+ * radio-gemtek-pci.c - Gemtek PCI Radio driver
+ * (C) 2001 Vladimir Shebordaev <vshebordaev@mail.ru>
+ *
+ ***************************************************************************
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ ***************************************************************************
+ *
+ * Gemtek Corp still silently refuses to release any specifications
+ * of their multimedia devices, so the protocol still has to be
+ * reverse engineered.
+ *
+ * The v4l code was inspired by Jonas Munsin's Gemtek serial line
+ * radio device driver.
+ *
+ * Please, let me know if this piece of code was useful :)
+ *
+ * TODO: multiple device support and portability were not tested
+ *
+ ***************************************************************************
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/videodev.h>
+#include <linux/errno.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifndef PCI_VENDOR_ID_GEMTEK
+#define PCI_VENDOR_ID_GEMTEK 0x5046
+#endif
+
+#ifndef PCI_DEVICE_ID_GEMTEK_PR103
+#define PCI_DEVICE_ID_GEMTEK_PR103 0x1001
+#endif
+
+#ifndef GEMTEK_PCI_RANGE_LOW
+#define GEMTEK_PCI_RANGE_LOW (87*16000)
+#endif
+
+#ifndef GEMTEK_PCI_RANGE_HIGH
+#define GEMTEK_PCI_RANGE_HIGH (108*16000)
+#endif
+
+#ifndef TRUE
+#define TRUE (1)
+#endif
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+struct gemtek_pci_card {
+ struct video_device *videodev;
+
+ u32 iobase;
+ u32 length;
+ u8 chiprev;
+ u16 model;
+
+ u32 current_frequency;
+ u8 mute;
+};
+
+static const char rcsid[] = "$Id: radio-gemtek-pci.c,v 1.1 2001/07/23 08:08:16 ted Exp ted $";
+
+static int nr_radio = -1;
+
+static inline u8 gemtek_pci_out( u16 value, u32 port )
+{
+ outw( value, port );
+
+ return (u8)value;
+}
+
+#define _b0( v ) *((u8 *)&v)
+static void __gemtek_pci_cmd( u16 value, u32 port, u8 *last_byte, int keep )
+{
+ register u8 byte = *last_byte;
+
+ if ( !value ) {
+ if ( !keep )
+ value = (u16)port;
+ byte &= 0xfd;
+ } else
+ byte |= 2;
+
+ _b0( value ) = byte;
+ outw( value, port );
+ byte |= 1;
+ _b0( value ) = byte;
+ outw( value, port );
+ byte &= 0xfe;
+ _b0( value ) = byte;
+ outw( value, port );
+
+ *last_byte = byte;
+}
+
+static inline void gemtek_pci_nil( u32 port, u8 *last_byte )
+{
+ __gemtek_pci_cmd( 0x00, port, last_byte, FALSE );
+}
+
+static inline void gemtek_pci_cmd( u16 cmd, u32 port, u8 *last_byte )
+{
+ __gemtek_pci_cmd( cmd, port, last_byte, TRUE );
+}
+
+static void gemtek_pci_setfrequency( struct gemtek_pci_card *card, unsigned long frequency )
+{
+ register int i;
+ register u32 value = frequency / 200 + 856;
+ register u16 mask = 0x8000;
+ u8 last_byte;
+ u32 port = card->iobase;
+
+ last_byte = gemtek_pci_out( 0x06, port );
+
+ i = 0;
+ do {
+ gemtek_pci_nil( port, &last_byte );
+ i++;
+ } while ( i < 9 );
+
+ i = 0;
+ do {
+ gemtek_pci_cmd( value & mask, port, &last_byte );
+ mask >>= 1;
+ i++;
+ } while ( i < 16 );
+
+ outw( 0x10, port );
+}
+
+
+static inline void gemtek_pci_mute( struct gemtek_pci_card *card )
+{
+ outb( 0x1f, card->iobase );
+ card->mute = TRUE;
+}
+
+static inline void gemtek_pci_unmute( struct gemtek_pci_card *card )
+{
+ if ( card->mute ) {
+ gemtek_pci_setfrequency( card, card->current_frequency );
+ card->mute = FALSE;
+ }
+}
+
+static inline unsigned int gemtek_pci_getsignal( struct gemtek_pci_card *card )
+{
+ return ( inb( card->iobase ) & 0x08 ) ? 0 : 1;
+}
+
+static int gemtek_pci_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct gemtek_pci_card *card = dev->priv;
+
+ switch ( cmd ) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *c = arg;
+
+ memset(c,0,sizeof(*c));
+ c->type = VID_TYPE_TUNER;
+ c->channels = 1;
+ c->audios = 1;
+ strcpy( c->name, "Gemtek PCI Radio" );
+ return 0;
+ }
+
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *t = arg;
+
+ if ( t->tuner )
+ return -EINVAL;
+
+ t->rangelow = GEMTEK_PCI_RANGE_LOW;
+ t->rangehigh = GEMTEK_PCI_RANGE_HIGH;
+ t->flags = VIDEO_TUNER_LOW;
+ t->mode = VIDEO_MODE_AUTO;
+ t->signal = 0xFFFF * gemtek_pci_getsignal( card );
+ strcpy( t->name, "FM" );
+ return 0;
+ }
+
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *t = arg;
+ if ( t->tuner )
+ return -EINVAL;
+ return 0;
+ }
+
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = card->current_frequency;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+
+ if ( (*freq < GEMTEK_PCI_RANGE_LOW) ||
+ (*freq > GEMTEK_PCI_RANGE_HIGH) )
+ return -EINVAL;
+
+ gemtek_pci_setfrequency( card, *freq );
+ card->current_frequency = *freq;
+ card->mute = FALSE;
+
+ return 0;
+ }
+
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *a = arg;
+
+ memset( a, 0, sizeof( *a ) );
+ a->flags |= VIDEO_AUDIO_MUTABLE;
+ a->volume = 1;
+ a->step = 65535;
+ strcpy( a->name, "Radio" );
+ return 0;
+ }
+
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *a = arg;
+
+ if ( a->audio )
+ return -EINVAL;
+
+ if ( a->flags & VIDEO_AUDIO_MUTE )
+ gemtek_pci_mute( card );
+ else
+ gemtek_pci_unmute( card );
+ return 0;
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int gemtek_pci_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, gemtek_pci_do_ioctl);
+}
+
+enum {
+ GEMTEK_PR103
+};
+
+static char *card_names[] __devinitdata = {
+ "GEMTEK_PR103"
+};
+
+static struct pci_device_id gemtek_pci_id[] =
+{
+ { PCI_VENDOR_ID_GEMTEK, PCI_DEVICE_ID_GEMTEK_PR103,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, GEMTEK_PR103 },
+ { 0 }
+};
+
+MODULE_DEVICE_TABLE( pci, gemtek_pci_id );
+
+static int mx = 1;
+
+static struct file_operations gemtek_pci_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = gemtek_pci_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device vdev_template = {
+ .owner = THIS_MODULE,
+ .name = "Gemtek PCI Radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_GEMTEK,
+ .fops = &gemtek_pci_fops,
+};
+
+static int __devinit gemtek_pci_probe( struct pci_dev *pci_dev, const struct pci_device_id *pci_id )
+{
+ struct gemtek_pci_card *card;
+ struct video_device *devradio;
+
+ if ( (card = kmalloc( sizeof( struct gemtek_pci_card ), GFP_KERNEL )) == NULL ) {
+ printk( KERN_ERR "gemtek_pci: out of memory\n" );
+ return -ENOMEM;
+ }
+ memset( card, 0, sizeof( struct gemtek_pci_card ) );
+
+ if ( pci_enable_device( pci_dev ) )
+ goto err_pci;
+
+ card->iobase = pci_resource_start( pci_dev, 0 );
+ card->length = pci_resource_len( pci_dev, 0 );
+
+ if ( request_region( card->iobase, card->length, card_names[pci_id->driver_data] ) == NULL ) {
+ printk( KERN_ERR "gemtek_pci: i/o port already in use\n" );
+ goto err_pci;
+ }
+
+ pci_read_config_byte( pci_dev, PCI_REVISION_ID, &card->chiprev );
+ pci_read_config_word( pci_dev, PCI_SUBSYSTEM_ID, &card->model );
+
+ pci_set_drvdata( pci_dev, card );
+
+ if ( (devradio = kmalloc( sizeof( struct video_device ), GFP_KERNEL )) == NULL ) {
+ printk( KERN_ERR "gemtek_pci: out of memory\n" );
+ goto err_video;
+ }
+ *devradio = vdev_template;
+
+ if ( video_register_device( devradio, VFL_TYPE_RADIO , nr_radio) == -1 ) {
+ kfree( devradio );
+ goto err_video;
+ }
+
+ card->videodev = devradio;
+ devradio->priv = card;
+ gemtek_pci_mute( card );
+
+ printk( KERN_INFO "Gemtek PCI Radio (rev. %d) found at 0x%04x-0x%04x.\n",
+ card->chiprev, card->iobase, card->iobase + card->length - 1 );
+
+ return 0;
+
+err_video:
+ release_region( card->iobase, card->length );
+
+err_pci:
+ kfree( card );
+ return -ENODEV;
+}
+
+static void __devexit gemtek_pci_remove( struct pci_dev *pci_dev )
+{
+ struct gemtek_pci_card *card = pci_get_drvdata( pci_dev );
+
+ video_unregister_device( card->videodev );
+ kfree( card->videodev );
+
+ release_region( card->iobase, card->length );
+
+ if ( mx )
+ gemtek_pci_mute( card );
+
+ kfree( card );
+
+ pci_set_drvdata( pci_dev, NULL );
+}
+
+static struct pci_driver gemtek_pci_driver =
+{
+ .name = "gemtek_pci",
+ .id_table = gemtek_pci_id,
+ .probe = gemtek_pci_probe,
+ .remove = __devexit_p(gemtek_pci_remove),
+};
+
+static int __init gemtek_pci_init_module( void )
+{
+ return pci_module_init( &gemtek_pci_driver );
+}
+
+static void __exit gemtek_pci_cleanup_module( void )
+{
+ return pci_unregister_driver( &gemtek_pci_driver );
+}
+
+MODULE_AUTHOR( "Vladimir Shebordaev <vshebordaev@mail.ru>" );
+MODULE_DESCRIPTION( "The video4linux driver for the Gemtek PCI Radio Card" );
+MODULE_LICENSE("GPL");
+
+module_param(mx, bool, 0);
+MODULE_PARM_DESC( mx, "single digit: 1 - turn off the turner upon module exit (default), 0 - do not" );
+module_param(nr_radio, int, 0);
+MODULE_PARM_DESC( nr_radio, "video4linux device number to use");
+
+module_init( gemtek_pci_init_module );
+module_exit( gemtek_pci_cleanup_module );
+
diff --git a/drivers/media/radio/radio-gemtek.c b/drivers/media/radio/radio-gemtek.c
new file mode 100644
index 00000000000..202bfe6819b
--- /dev/null
+++ b/drivers/media/radio/radio-gemtek.c
@@ -0,0 +1,304 @@
+/* GemTek radio card driver for Linux (C) 1998 Jonas Munsin <jmunsin@iki.fi>
+ *
+ * GemTek hasn't released any specs on the card, so the protocol had to
+ * be reverse engineered with dosemu.
+ *
+ * Besides the protocol changes, this is mostly a copy of:
+ *
+ * RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
+ *
+ * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
+ * Converted to new API by Alan Cox <Alan.Cox@linux.org>
+ * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
+ *
+ * TODO: Allow for more than one of these foolish entities :-)
+ *
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_GEMTEK_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_GEMTEK_PORT
+#define CONFIG_RADIO_GEMTEK_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_GEMTEK_PORT;
+static int radio_nr = -1;
+static spinlock_t lock;
+
+struct gemtek_device
+{
+ int port;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+/* the correct way to mute the gemtek may be to write the last written
+ * frequency || 0x10, but just writing 0x10 once seems to do it as well
+ */
+static void gemtek_mute(struct gemtek_device *dev)
+{
+ if(dev->muted)
+ return;
+ spin_lock(&lock);
+ outb(0x10, io);
+ spin_unlock(&lock);
+ dev->muted = 1;
+}
+
+static void gemtek_unmute(struct gemtek_device *dev)
+{
+ if(dev->muted == 0)
+ return;
+ spin_lock(&lock);
+ outb(0x20, io);
+ spin_unlock(&lock);
+ dev->muted = 0;
+}
+
+static void zero(void)
+{
+ outb_p(0x04, io);
+ udelay(5);
+ outb_p(0x05, io);
+ udelay(5);
+}
+
+static void one(void)
+{
+ outb_p(0x06, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+}
+
+static int gemtek_setfreq(struct gemtek_device *dev, unsigned long freq)
+{
+ int i;
+
+/* freq = 78.25*((float)freq/16000.0 + 10.52); */
+
+ freq /= 16;
+ freq += 10520;
+ freq *= 7825;
+ freq /= 100000;
+
+ spin_lock(&lock);
+
+ /* 2 start bits */
+ outb_p(0x03, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+
+ /* 28 frequency bits (lsb first) */
+ for (i = 0; i < 14; i++)
+ if (freq & (1 << i))
+ one();
+ else
+ zero();
+ /* 36 unknown bits */
+ for (i = 0; i < 11; i++)
+ zero();
+ one();
+ for (i = 0; i < 4; i++)
+ zero();
+ one();
+ zero();
+
+ /* 2 end bits */
+ outb_p(0x03, io);
+ udelay(5);
+ outb_p(0x07, io);
+ udelay(5);
+
+ spin_unlock(&lock);
+
+ return 0;
+}
+
+static int gemtek_getsigstr(struct gemtek_device *dev)
+{
+ spin_lock(&lock);
+ inb(io);
+ udelay(5);
+ spin_unlock(&lock);
+ if (inb(io) & 8) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int gemtek_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct gemtek_device *rt=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "GemTek");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=87*16000;
+ v->rangehigh=108*16000;
+ v->flags=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ v->signal=0xFFFF*gemtek_getsigstr(rt);
+ strcpy(v->name, "FM");
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = rt->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ rt->curfreq = *freq;
+ /* needs to be called twice in order for getsigstr to work */
+ gemtek_setfreq(rt, rt->curfreq);
+ gemtek_setfreq(rt, rt->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags|=VIDEO_AUDIO_MUTABLE;
+ v->volume=1;
+ v->step=65535;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ gemtek_mute(rt);
+ else
+ gemtek_unmute(rt);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int gemtek_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, gemtek_do_ioctl);
+}
+
+static struct gemtek_device gemtek_unit;
+
+static struct file_operations gemtek_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = gemtek_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device gemtek_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "GemTek radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_GEMTEK,
+ .fops = &gemtek_fops,
+};
+
+static int __init gemtek_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x20c, io=0x30c, io=0x24c or io=0x34c (io=0x020c or io=0x248 for the combined sound/radiocard)\n");
+ return -EINVAL;
+ }
+
+ if (!request_region(io, 4, "gemtek"))
+ {
+ printk(KERN_ERR "gemtek: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ gemtek_radio.priv=&gemtek_unit;
+
+ if(video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io, 4);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "GemTek Radio Card driver.\n");
+
+ spin_lock_init(&lock);
+
+ /* this is _maybe_ unnecessary */
+ outb(0x01, io);
+
+ /* mute card - prevents noisy bootups */
+ gemtek_unit.muted = 0;
+ gemtek_mute(&gemtek_unit);
+
+ return 0;
+}
+
+MODULE_AUTHOR("Jonas Munsin");
+MODULE_DESCRIPTION("A driver for the GemTek Radio Card");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the GemTek card (0x20c, 0x30c, 0x24c or 0x34c (0x20c or 0x248 have been reported to work for the combined sound/radiocard)).");
+module_param(radio_nr, int, 0);
+
+static void __exit gemtek_cleanup(void)
+{
+ video_unregister_device(&gemtek_radio);
+ release_region(io,4);
+}
+
+module_init(gemtek_init);
+module_exit(gemtek_cleanup);
+
+/*
+ Local variables:
+ compile-command: "gcc -c -DMODVERSIONS -D__KERNEL__ -DMODULE -O6 -Wall -Wstrict-prototypes -I /home/blp/tmp/linux-2.1.111-rtrack/include radio-rtrack2.c"
+ End:
+*/
diff --git a/drivers/media/radio/radio-maestro.c b/drivers/media/radio/radio-maestro.c
new file mode 100644
index 00000000000..e62147e4ed1
--- /dev/null
+++ b/drivers/media/radio/radio-maestro.c
@@ -0,0 +1,332 @@
+/* Maestro PCI sound card radio driver for Linux support
+ * (c) 2000 A. Tlalka, atlka@pg.gda.pl
+ * Notes on the hardware
+ *
+ * + Frequency control is done digitally
+ * + No volume control - only mute/unmute - you have to use Aux line volume
+ * control on Maestro card to set the volume
+ * + Radio status (tuned/not_tuned and stereo/mono) is valid some time after
+ * frequency setting (>100ms) and only when the radio is unmuted.
+ * version 0.02
+ * + io port is automatically detected - only the first radio is used
+ * version 0.03
+ * + thread access locking additions
+ * version 0.04
+ * + code improvements
+ * + VIDEO_TUNER_LOW is permanent
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include <linux/pci.h>
+#include <linux/videodev.h>
+
+#define DRIVER_VERSION "0.04"
+
+#define PCI_VENDOR_ESS 0x125D
+#define PCI_DEVICE_ID_ESS_ESS1968 0x1968 /* Maestro 2 */
+#define PCI_DEVICE_ID_ESS_ESS1978 0x1978 /* Maestro 2E */
+
+#define GPIO_DATA 0x60 /* port offset from ESS_IO_BASE */
+
+#define IO_MASK 4 /* mask register offset from GPIO_DATA
+ bits 1=unmask write to given bit */
+#define IO_DIR 8 /* direction register offset from GPIO_DATA
+ bits 0/1=read/write direction */
+
+#define GPIO6 0x0040 /* mask bits for GPIO lines */
+#define GPIO7 0x0080
+#define GPIO8 0x0100
+#define GPIO9 0x0200
+
+#define STR_DATA GPIO6 /* radio TEA5757 pins and GPIO bits */
+#define STR_CLK GPIO7
+#define STR_WREN GPIO8
+#define STR_MOST GPIO9
+
+#define FREQ_LO 50*16000
+#define FREQ_HI 150*16000
+
+#define FREQ_IF 171200 /* 10.7*16000 */
+#define FREQ_STEP 200 /* 12.5*16 */
+
+#define FREQ2BITS(x) ((((unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\
+ /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */
+
+#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)
+
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+
+static int radio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static struct file_operations maestro_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = radio_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device maestro_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "Maestro radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_SF16MI,
+ .fops = &maestro_fops,
+};
+
+static struct radio_device
+{
+ __u16 io, /* base of Maestro card radio io (GPIO_DATA)*/
+ muted, /* VIDEO_AUDIO_MUTE */
+ stereo, /* VIDEO_TUNER_STEREO_ON */
+ tuned; /* signal strength (0 or 0xffff) */
+ struct semaphore lock;
+} radio_unit = {0, 0, 0, 0, };
+
+static __u32 radio_bits_get(struct radio_device *dev)
+{
+ register __u16 io=dev->io, l, rdata;
+ register __u32 data=0;
+ __u16 omask;
+ omask = inw(io + IO_MASK);
+ outw(~(STR_CLK | STR_WREN), io + IO_MASK);
+ outw(0, io);
+ udelay(16);
+
+ for (l=24;l--;) {
+ outw(STR_CLK, io); /* HI state */
+ udelay(2);
+ if(!l)
+ dev->tuned = inw(io) & STR_MOST ? 0 : 0xffff;
+ outw(0, io); /* LO state */
+ udelay(2);
+ data <<= 1; /* shift data */
+ rdata = inw(io);
+ if(!l)
+ dev->stereo = rdata & STR_MOST ?
+ 0 : VIDEO_TUNER_STEREO_ON;
+ else
+ if(rdata & STR_DATA)
+ data++;
+ udelay(2);
+ }
+ if(dev->muted)
+ outw(STR_WREN, io);
+ udelay(4);
+ outw(omask, io + IO_MASK);
+ return data & 0x3ffe;
+}
+
+static void radio_bits_set(struct radio_device *dev, __u32 data)
+{
+ register __u16 io=dev->io, l, bits;
+ __u16 omask, odir;
+ omask = inw(io + IO_MASK);
+ odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN);
+ outw(odir | STR_DATA, io + IO_DIR);
+ outw(~(STR_DATA | STR_CLK | STR_WREN), io + IO_MASK);
+ udelay(16);
+ for (l=25;l;l--) {
+ bits = ((data >> 18) & STR_DATA) | STR_WREN ;
+ data <<= 1; /* shift data */
+ outw(bits, io); /* start strobe */
+ udelay(2);
+ outw(bits | STR_CLK, io); /* HI level */
+ udelay(2);
+ outw(bits, io); /* LO level */
+ udelay(4);
+ }
+ if(!dev->muted)
+ outw(0, io);
+ udelay(4);
+ outw(omask, io + IO_MASK);
+ outw(odir, io + IO_DIR);
+ msleep(125);
+}
+
+inline static int radio_function(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct radio_device *card=dev->priv;
+
+ switch(cmd) {
+ case VIDIOCGCAP: {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "Maestro radio");
+ v->type=VID_TYPE_TUNER;
+ v->channels=v->audios=1;
+ return 0;
+ }
+ case VIDIOCGTUNER: {
+ struct video_tuner *v = arg;
+ if(v->tuner)
+ return -EINVAL;
+ (void)radio_bits_get(card);
+ v->flags = VIDEO_TUNER_LOW | card->stereo;
+ v->signal = card->tuned;
+ strcpy(v->name, "FM");
+ v->rangelow = FREQ_LO;
+ v->rangehigh = FREQ_HI;
+ v->mode = VIDEO_MODE_AUTO;
+ return 0;
+ }
+ case VIDIOCSTUNER: {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGFREQ: {
+ unsigned long *freq = arg;
+ *freq = BITS2FREQ(radio_bits_get(card));
+ return 0;
+ }
+ case VIDIOCSFREQ: {
+ unsigned long *freq = arg;
+ if (*freq<FREQ_LO || *freq>FREQ_HI )
+ return -EINVAL;
+ radio_bits_set(card, FREQ2BITS(*freq));
+ return 0;
+ }
+ case VIDIOCGAUDIO: {
+ struct video_audio *v = arg;
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "Radio");
+ v->flags=VIDEO_AUDIO_MUTABLE | card->muted;
+ v->mode=VIDEO_SOUND_STEREO;
+ return 0;
+ }
+ case VIDIOCSAUDIO: {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ {
+ register __u16 io=card->io;
+ register __u16 omask = inw(io + IO_MASK);
+ outw(~STR_WREN, io + IO_MASK);
+ outw((card->muted = v->flags & VIDEO_AUDIO_MUTE)
+ ? STR_WREN : 0, io);
+ udelay(4);
+ outw(omask, io + IO_MASK);
+ msleep(125);
+ return 0;
+ }
+ }
+ case VIDIOCGUNIT: {
+ struct video_unit *v = arg;
+ v->video=VIDEO_NO_UNIT;
+ v->vbi=VIDEO_NO_UNIT;
+ v->radio=dev->minor;
+ v->audio=0;
+ v->teletext=VIDEO_NO_UNIT;
+ return 0;
+ }
+ default: return -ENOIOCTLCMD;
+ }
+}
+
+static int radio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct radio_device *card=dev->priv;
+ int ret;
+
+ down(&card->lock);
+ ret = video_usercopy(inode, file, cmd, arg, radio_function);
+ up(&card->lock);
+ return ret;
+}
+
+static __u16 radio_install(struct pci_dev *pcidev);
+
+MODULE_AUTHOR("Adam Tlalka, atlka@pg.gda.pl");
+MODULE_DESCRIPTION("Radio driver for the Maestro PCI sound card radio.");
+MODULE_LICENSE("GPL");
+
+static void __exit maestro_radio_exit(void)
+{
+ video_unregister_device(&maestro_radio);
+}
+
+static int __init maestro_radio_init(void)
+{
+ register __u16 found=0;
+ struct pci_dev *pcidev = NULL;
+ while(!found && (pcidev = pci_find_device(PCI_VENDOR_ESS,
+ PCI_DEVICE_ID_ESS_ESS1968,
+ pcidev)))
+ found |= radio_install(pcidev);
+ while(!found && (pcidev = pci_find_device(PCI_VENDOR_ESS,
+ PCI_DEVICE_ID_ESS_ESS1978,
+ pcidev)))
+ found |= radio_install(pcidev);
+ if(!found) {
+ printk(KERN_INFO "radio-maestro: no devices found.\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+module_init(maestro_radio_init);
+module_exit(maestro_radio_exit);
+
+inline static __u16 radio_power_on(struct radio_device *dev)
+{
+ register __u16 io=dev->io;
+ register __u32 ofreq;
+ __u16 omask, odir;
+ omask = inw(io + IO_MASK);
+ odir = (inw(io + IO_DIR) & ~STR_DATA) | (STR_CLK | STR_WREN);
+ outw(odir & ~STR_WREN, io + IO_DIR);
+ dev->muted = inw(io) & STR_WREN ? 0 : VIDEO_AUDIO_MUTE;
+ outw(odir, io + IO_DIR);
+ outw(~(STR_WREN | STR_CLK), io + IO_MASK);
+ outw(dev->muted ? 0 : STR_WREN, io);
+ udelay(16);
+ outw(omask, io + IO_MASK);
+ ofreq = radio_bits_get(dev);
+ if((ofreq<FREQ2BITS(FREQ_LO)) || (ofreq>FREQ2BITS(FREQ_HI)))
+ ofreq = FREQ2BITS(FREQ_LO);
+ radio_bits_set(dev, ofreq);
+ return (ofreq == radio_bits_get(dev));
+}
+
+static __u16 radio_install(struct pci_dev *pcidev)
+{
+ if(((pcidev->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO)
+ return 0;
+
+ radio_unit.io = pcidev->resource[0].start + GPIO_DATA;
+ maestro_radio.priv = &radio_unit;
+ init_MUTEX(&radio_unit.lock);
+
+ if(radio_power_on(&radio_unit)) {
+ if(video_register_device(&maestro_radio, VFL_TYPE_RADIO, radio_nr)==-1) {
+ printk("radio-maestro: can't register device!");
+ return 0;
+ }
+ printk(KERN_INFO "radio-maestro: version "
+ DRIVER_VERSION
+ " time "
+ __TIME__ " "
+ __DATE__
+ "\n");
+ printk(KERN_INFO "radio-maestro: radio chip initialized\n");
+ return 1;
+ } else
+ return 0;
+}
+
diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c
new file mode 100644
index 00000000000..5b748a48ce7
--- /dev/null
+++ b/drivers/media/radio/radio-maxiradio.c
@@ -0,0 +1,349 @@
+/*
+ * Guillemot Maxi Radio FM 2000 PCI radio card driver for Linux
+ * (C) 2001 Dimitromanolakis Apostolos <apdim@grecian.net>
+ *
+ * Based in the radio Maestro PCI driver. Actually it uses the same chip
+ * for radio but different pci controller.
+ *
+ * I didn't have any specs I reversed engineered the protocol from
+ * the windows driver (radio.dll).
+ *
+ * The card uses the TEA5757 chip that includes a search function but it
+ * is useless as I haven't found any way to read back the frequency. If
+ * anybody does please mail me.
+ *
+ * For the pdf file see:
+ * http://www.semiconductors.philips.com/pip/TEA5757H/V1
+ *
+ *
+ * CHANGES:
+ * 0.75b
+ * - better pci interface thanks to Francois Romieu <romieu@cogenit.fr>
+ *
+ * 0.75
+ * - tiding up
+ * - removed support for multiple devices as it didn't work anyway
+ *
+ * BUGS:
+ * - card unmutes if you change frequency
+ *
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include <linux/pci.h>
+#include <linux/videodev.h>
+
+/* version 0.75 Sun Feb 4 22:51:27 EET 2001 */
+#define DRIVER_VERSION "0.75"
+
+#ifndef PCI_VENDOR_ID_GUILLEMOT
+#define PCI_VENDOR_ID_GUILLEMOT 0x5046
+#endif
+
+#ifndef PCI_DEVICE_ID_GUILLEMOT
+#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
+#endif
+
+
+/* TEA5757 pin mappings */
+static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16 ;
+
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+
+
+#define FREQ_LO 50*16000
+#define FREQ_HI 150*16000
+
+#define FREQ_IF 171200 /* 10.7*16000 */
+#define FREQ_STEP 200 /* 12.5*16 */
+
+#define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1))\
+ /(FREQ_STEP<<2))<<2) /* (x==fmhz*16*1000) -> bits */
+
+#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)
+
+
+static int radio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+static struct file_operations maxiradio_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = radio_ioctl,
+ .llseek = no_llseek,
+};
+static struct video_device maxiradio_radio =
+{
+ .owner = THIS_MODULE,
+ .name = "Maxi Radio FM2000 radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_SF16MI,
+ .fops = &maxiradio_fops,
+};
+
+static struct radio_device
+{
+ __u16 io, /* base of radio io */
+ muted, /* VIDEO_AUDIO_MUTE */
+ stereo, /* VIDEO_TUNER_STEREO_ON */
+ tuned; /* signal strength (0 or 0xffff) */
+
+ unsigned long freq;
+
+ struct semaphore lock;
+} radio_unit = {0, 0, 0, 0, };
+
+
+static void outbit(unsigned long bit, __u16 io)
+{
+ if(bit != 0)
+ {
+ outb( power|wren|data ,io); udelay(4);
+ outb( power|wren|data|clk ,io); udelay(4);
+ outb( power|wren|data ,io); udelay(4);
+ }
+ else
+ {
+ outb( power|wren ,io); udelay(4);
+ outb( power|wren|clk ,io); udelay(4);
+ outb( power|wren ,io); udelay(4);
+ }
+}
+
+static void turn_power(__u16 io, int p)
+{
+ if(p != 0) outb(power, io); else outb(0,io);
+}
+
+
+static void set_freq(__u16 io, __u32 data)
+{
+ unsigned long int si;
+ int bl;
+
+ /* TEA5757 shift register bits (see pdf) */
+
+ outbit(0,io); // 24 search
+ outbit(1,io); // 23 search up/down
+
+ outbit(0,io); // 22 stereo/mono
+
+ outbit(0,io); // 21 band
+ outbit(0,io); // 20 band (only 00=FM works I think)
+
+ outbit(0,io); // 19 port ?
+ outbit(0,io); // 18 port ?
+
+ outbit(0,io); // 17 search level
+ outbit(0,io); // 16 search level
+
+ si = 0x8000;
+ for(bl = 1; bl <= 16 ; bl++) { outbit(data & si,io); si >>=1; }
+
+ outb(power,io);
+}
+
+static int get_stereo(__u16 io)
+{
+ outb(power,io); udelay(4);
+ return !(inb(io) & mo_st);
+}
+
+static int get_tune(__u16 io)
+{
+ outb(power+clk,io); udelay(4);
+ return !(inb(io) & mo_st);
+}
+
+
+inline static int radio_function(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct radio_device *card=dev->priv;
+
+ switch(cmd) {
+ case VIDIOCGCAP: {
+ struct video_capability *v = arg;
+
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "Maxi Radio FM2000 radio");
+ v->type=VID_TYPE_TUNER;
+ v->channels=v->audios=1;
+ return 0;
+ }
+ case VIDIOCGTUNER: {
+ struct video_tuner *v = arg;
+
+ if(v->tuner)
+ return -EINVAL;
+
+ card->stereo = 0xffff * get_stereo(card->io);
+ card->tuned = 0xffff * get_tune(card->io);
+
+ v->flags = VIDEO_TUNER_LOW | card->stereo;
+ v->signal = card->tuned;
+
+ strcpy(v->name, "FM");
+
+ v->rangelow = FREQ_LO;
+ v->rangehigh = FREQ_HI;
+ v->mode = VIDEO_MODE_AUTO;
+
+ return 0;
+ }
+ case VIDIOCSTUNER: {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGFREQ: {
+ unsigned long *freq = arg;
+
+ *freq = card->freq;
+ return 0;
+ }
+ case VIDIOCSFREQ: {
+ unsigned long *freq = arg;
+
+ if (*freq < FREQ_LO || *freq > FREQ_HI)
+ return -EINVAL;
+ card->freq = *freq;
+ set_freq(card->io, FREQ2BITS(card->freq));
+ msleep(125);
+ return 0;
+ }
+ case VIDIOCGAUDIO: {
+ struct video_audio *v = arg;
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "Radio");
+ v->flags=VIDEO_AUDIO_MUTABLE | card->muted;
+ v->mode=VIDEO_SOUND_STEREO;
+ return 0;
+ }
+
+ case VIDIOCSAUDIO: {
+ struct video_audio *v = arg;
+
+ if(v->audio)
+ return -EINVAL;
+ card->muted = v->flags & VIDEO_AUDIO_MUTE;
+ if(card->muted)
+ turn_power(card->io, 0);
+ else
+ set_freq(card->io, FREQ2BITS(card->freq));
+ return 0;
+ }
+ case VIDIOCGUNIT: {
+ struct video_unit *v = arg;
+
+ v->video=VIDEO_NO_UNIT;
+ v->vbi=VIDEO_NO_UNIT;
+ v->radio=dev->minor;
+ v->audio=0;
+ v->teletext=VIDEO_NO_UNIT;
+ return 0;
+ }
+ default: return -ENOIOCTLCMD;
+ }
+}
+
+static int radio_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct radio_device *card=dev->priv;
+ int ret;
+
+ down(&card->lock);
+ ret = video_usercopy(inode, file, cmd, arg, radio_function);
+ up(&card->lock);
+ return ret;
+}
+
+MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
+MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");
+MODULE_LICENSE("GPL");
+
+
+static int __devinit maxiradio_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ if(!request_region(pci_resource_start(pdev, 0),
+ pci_resource_len(pdev, 0), "Maxi Radio FM 2000")) {
+ printk(KERN_ERR "radio-maxiradio: can't reserve I/O ports\n");
+ goto err_out;
+ }
+
+ if (pci_enable_device(pdev))
+ goto err_out_free_region;
+
+ radio_unit.io = pci_resource_start(pdev, 0);
+ init_MUTEX(&radio_unit.lock);
+ maxiradio_radio.priv = &radio_unit;
+
+ if(video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr)==-1) {
+ printk("radio-maxiradio: can't register device!");
+ goto err_out_free_region;
+ }
+
+ printk(KERN_INFO "radio-maxiradio: version "
+ DRIVER_VERSION
+ " time "
+ __TIME__ " "
+ __DATE__
+ "\n");
+
+ printk(KERN_INFO "radio-maxiradio: found Guillemot MAXI Radio device (io = 0x%x)\n",
+ radio_unit.io);
+ return 0;
+
+err_out_free_region:
+ release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
+err_out:
+ return -ENODEV;
+}
+
+static void __devexit maxiradio_remove_one(struct pci_dev *pdev)
+{
+ video_unregister_device(&maxiradio_radio);
+ release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
+}
+
+static struct pci_device_id maxiradio_pci_tbl[] = {
+ { PCI_VENDOR_ID_GUILLEMOT, PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO,
+ PCI_ANY_ID, PCI_ANY_ID, },
+ { 0,}
+};
+
+MODULE_DEVICE_TABLE(pci, maxiradio_pci_tbl);
+
+static struct pci_driver maxiradio_driver = {
+ .name = "radio-maxiradio",
+ .id_table = maxiradio_pci_tbl,
+ .probe = maxiradio_init_one,
+ .remove = __devexit_p(maxiradio_remove_one),
+};
+
+static int __init maxiradio_radio_init(void)
+{
+ return pci_module_init(&maxiradio_driver);
+}
+
+static void __exit maxiradio_radio_exit(void)
+{
+ pci_unregister_driver(&maxiradio_driver);
+}
+
+module_init(maxiradio_radio_init);
+module_exit(maxiradio_radio_exit);
diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c
new file mode 100644
index 00000000000..c00245d4d24
--- /dev/null
+++ b/drivers/media/radio/radio-rtrack2.c
@@ -0,0 +1,266 @@
+/* RadioTrack II driver for Linux radio support (C) 1998 Ben Pfaff
+ *
+ * Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
+ * Converted to new API by Alan Cox <Alan.Cox@linux.org>
+ * Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
+ *
+ * TODO: Allow for more than one of these foolish entities :-)
+ *
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_RTRACK2_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_RTRACK2_PORT
+#define CONFIG_RADIO_RTRACK2_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_RTRACK2_PORT;
+static int radio_nr = -1;
+static spinlock_t lock;
+
+struct rt_device
+{
+ int port;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+static void rt_mute(struct rt_device *dev)
+{
+ if(dev->muted)
+ return;
+ spin_lock(&lock);
+ outb(1, io);
+ spin_unlock(&lock);
+ dev->muted = 1;
+}
+
+static void rt_unmute(struct rt_device *dev)
+{
+ if(dev->muted == 0)
+ return;
+ spin_lock(&lock);
+ outb(0, io);
+ spin_unlock(&lock);
+ dev->muted = 0;
+}
+
+static void zero(void)
+{
+ outb_p(1, io);
+ outb_p(3, io);
+ outb_p(1, io);
+}
+
+static void one(void)
+{
+ outb_p(5, io);
+ outb_p(7, io);
+ outb_p(5, io);
+}
+
+static int rt_setfreq(struct rt_device *dev, unsigned long freq)
+{
+ int i;
+
+ freq = freq / 200 + 856;
+
+ spin_lock(&lock);
+
+ outb_p(0xc8, io);
+ outb_p(0xc9, io);
+ outb_p(0xc9, io);
+
+ for (i = 0; i < 10; i++)
+ zero ();
+
+ for (i = 14; i >= 0; i--)
+ if (freq & (1 << i))
+ one ();
+ else
+ zero ();
+
+ outb_p(0xc8, io);
+ if (!dev->muted)
+ outb_p(0, io);
+
+ spin_unlock(&lock);
+ return 0;
+}
+
+static int rt_getsigstr(struct rt_device *dev)
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+static int rt_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct rt_device *rt=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "RadioTrack II");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=88*16000;
+ v->rangehigh=108*16000;
+ v->flags=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ v->signal=0xFFFF*rt_getsigstr(rt);
+ strcpy(v->name, "FM");
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = rt->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ rt->curfreq = *freq;
+ rt_setfreq(rt, rt->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags|=VIDEO_AUDIO_MUTABLE;
+ v->volume=1;
+ v->step=65535;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ rt_mute(rt);
+ else
+ rt_unmute(rt);
+
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int rt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, rt_do_ioctl);
+}
+
+static struct rt_device rtrack2_unit;
+
+static struct file_operations rtrack2_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = rt_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device rtrack2_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "RadioTrack II radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_RTRACK2,
+ .fops = &rtrack2_fops,
+};
+
+static int __init rtrack2_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x20c or io=0x30c\n");
+ return -EINVAL;
+ }
+ if (!request_region(io, 4, "rtrack2"))
+ {
+ printk(KERN_ERR "rtrack2: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ rtrack2_radio.priv=&rtrack2_unit;
+
+ spin_lock_init(&lock);
+ if(video_register_device(&rtrack2_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io, 4);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "AIMSlab Radiotrack II card driver.\n");
+
+ /* mute card - prevents noisy bootups */
+ outb(1, io);
+ rtrack2_unit.muted = 1;
+
+ return 0;
+}
+
+MODULE_AUTHOR("Ben Pfaff");
+MODULE_DESCRIPTION("A driver for the RadioTrack II radio card.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the RadioTrack card (0x20c or 0x30c)");
+module_param(radio_nr, int, 0);
+
+static void __exit rtrack2_cleanup_module(void)
+{
+ video_unregister_device(&rtrack2_radio);
+ release_region(io,4);
+}
+
+module_init(rtrack2_init);
+module_exit(rtrack2_cleanup_module);
+
+/*
+ Local variables:
+ compile-command: "mmake"
+ End:
+*/
diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c
new file mode 100644
index 00000000000..3a464a09221
--- /dev/null
+++ b/drivers/media/radio/radio-sf16fmi.c
@@ -0,0 +1,328 @@
+/* SF16FMI radio driver for Linux radio support
+ * heavily based on rtrack driver...
+ * (c) 1997 M. Kirkwood
+ * (c) 1998 Petr Vandrovec, vandrove@vc.cvut.cz
+ *
+ * Fitted to new interface by Alan Cox <alan.cox@linux.org>
+ * Made working and cleaned up functions <mikael.hedin@irf.se>
+ * Support for ISAPnP by Ladislav Michl <ladis@psi.cz>
+ *
+ * Notes on the hardware
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ * No volume control - only mute/unmute - you have to use line volume
+ * control on SB-part of SF16FMI
+ *
+ */
+
+#include <linux/kernel.h> /* __setup */
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/isapnp.h>
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <asm/semaphore.h>
+
+struct fmi_device
+{
+ int port;
+ int curvol; /* 1 or 0 */
+ unsigned long curfreq; /* freq in kHz */
+ __u32 flags;
+};
+
+static int io = -1;
+static int radio_nr = -1;
+static struct pnp_dev *dev = NULL;
+static struct semaphore lock;
+
+/* freq is in 1/16 kHz to internal number, hw precision is 50 kHz */
+/* It is only useful to give freq in intervall of 800 (=0.05Mhz),
+ * other bits will be truncated, e.g 92.7400016 -> 92.7, but
+ * 92.7400017 -> 92.75
+ */
+#define RSF16_ENCODE(x) ((x)/800+214)
+#define RSF16_MINFREQ 87*16000
+#define RSF16_MAXFREQ 108*16000
+
+static void outbits(int bits, unsigned int data, int port)
+{
+ while(bits--) {
+ if(data & 1) {
+ outb(5, port);
+ udelay(6);
+ outb(7, port);
+ udelay(6);
+ } else {
+ outb(1, port);
+ udelay(6);
+ outb(3, port);
+ udelay(6);
+ }
+ data>>=1;
+ }
+}
+
+static inline void fmi_mute(int port)
+{
+ down(&lock);
+ outb(0x00, port);
+ up(&lock);
+}
+
+static inline void fmi_unmute(int port)
+{
+ down(&lock);
+ outb(0x08, port);
+ up(&lock);
+}
+
+static inline int fmi_setfreq(struct fmi_device *dev)
+{
+ int myport = dev->port;
+ unsigned long freq = dev->curfreq;
+
+ down(&lock);
+
+ outbits(16, RSF16_ENCODE(freq), myport);
+ outbits(8, 0xC0, myport);
+ msleep(143); /* was schedule_timeout(HZ/7) */
+ up(&lock);
+ if (dev->curvol) fmi_unmute(myport);
+ return 0;
+}
+
+static inline int fmi_getsigstr(struct fmi_device *dev)
+{
+ int val;
+ int res;
+ int myport = dev->port;
+
+
+ down(&lock);
+ val = dev->curvol ? 0x08 : 0x00; /* unmute/mute */
+ outb(val, myport);
+ outb(val | 0x10, myport);
+ msleep(143); /* was schedule_timeout(HZ/7) */
+ res = (int)inb(myport+1);
+ outb(val, myport);
+
+ up(&lock);
+ return (res & 2) ? 0 : 0xFFFF;
+}
+
+static int fmi_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct fmi_device *fmi=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "SF16-FMx radio");
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ int mult;
+
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ strcpy(v->name, "FM");
+ mult = (fmi->flags & VIDEO_TUNER_LOW) ? 1 : 1000;
+ v->rangelow = RSF16_MINFREQ/mult;
+ v->rangehigh = RSF16_MAXFREQ/mult;
+ v->flags=fmi->flags;
+ v->mode=VIDEO_MODE_AUTO;
+ v->signal = fmi_getsigstr(fmi);
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ fmi->flags = v->flags & VIDEO_TUNER_LOW;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = fmi->curfreq;
+ if (!(fmi->flags & VIDEO_TUNER_LOW))
+ *freq /= 1000;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ if (!(fmi->flags & VIDEO_TUNER_LOW))
+ *freq *= 1000;
+ if (*freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ )
+ return -EINVAL;
+ /*rounding in steps of 800 to match th freq
+ that will be used */
+ fmi->curfreq = (*freq/800)*800;
+ fmi_setfreq(fmi);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0,sizeof(*v));
+ v->flags=( (!fmi->curvol)*VIDEO_AUDIO_MUTE | VIDEO_AUDIO_MUTABLE);
+ strcpy(v->name, "Radio");
+ v->mode=VIDEO_SOUND_STEREO;
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ fmi->curvol= v->flags&VIDEO_AUDIO_MUTE ? 0 : 1;
+ fmi->curvol ?
+ fmi_unmute(fmi->port) : fmi_mute(fmi->port);
+ return 0;
+ }
+ case VIDIOCGUNIT:
+ {
+ struct video_unit *v = arg;
+ v->video=VIDEO_NO_UNIT;
+ v->vbi=VIDEO_NO_UNIT;
+ v->radio=dev->minor;
+ v->audio=0; /* How do we find out this??? */
+ v->teletext=VIDEO_NO_UNIT;
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int fmi_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, fmi_do_ioctl);
+}
+
+static struct fmi_device fmi_unit;
+
+static struct file_operations fmi_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = fmi_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device fmi_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "SF16FMx radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_SF16MI,
+ .fops = &fmi_fops,
+};
+
+/* ladis: this is my card. does any other types exist? */
+static struct isapnp_device_id id_table[] __devinitdata = {
+ { ISAPNP_ANY_ID, ISAPNP_ANY_ID,
+ ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0},
+ { ISAPNP_CARD_END, },
+};
+
+MODULE_DEVICE_TABLE(isapnp, id_table);
+
+static int isapnp_fmi_probe(void)
+{
+ int i = 0;
+
+ while (id_table[i].card_vendor != 0 && dev == NULL) {
+ dev = pnp_find_dev(NULL, id_table[i].vendor,
+ id_table[i].function, NULL);
+ i++;
+ }
+
+ if (!dev)
+ return -ENODEV;
+ if (pnp_device_attach(dev) < 0)
+ return -EAGAIN;
+ if (pnp_activate_dev(dev) < 0) {
+ printk ("radio-sf16fmi: PnP configure failed (out of resources?)\n");
+ pnp_device_detach(dev);
+ return -ENOMEM;
+ }
+ if (!pnp_port_valid(dev, 0)) {
+ pnp_device_detach(dev);
+ return -ENODEV;
+ }
+
+ i = pnp_port_start(dev, 0);
+ printk ("radio-sf16fmi: PnP reports card at %#x\n", i);
+
+ return i;
+}
+
+static int __init fmi_init(void)
+{
+ if (io < 0)
+ io = isapnp_fmi_probe();
+ if (io < 0) {
+ printk(KERN_ERR "radio-sf16fmi: No PnP card found.\n");
+ return io;
+ }
+ if (!request_region(io, 2, "radio-sf16fmi")) {
+ printk(KERN_ERR "radio-sf16fmi: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ fmi_unit.port = io;
+ fmi_unit.curvol = 0;
+ fmi_unit.curfreq = 0;
+ fmi_unit.flags = VIDEO_TUNER_LOW;
+ fmi_radio.priv = &fmi_unit;
+
+ init_MUTEX(&lock);
+
+ if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) == -1) {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "SF16FMx radio card driver at 0x%x\n", io);
+ /* mute card - prevents noisy bootups */
+ fmi_mute(io);
+ return 0;
+}
+
+MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood");
+MODULE_DESCRIPTION("A driver for the SF16MI radio.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the SF16MI card (0x284 or 0x384)");
+module_param(radio_nr, int, 0);
+
+static void __exit fmi_cleanup_module(void)
+{
+ video_unregister_device(&fmi_radio);
+ release_region(io, 2);
+ if (dev)
+ pnp_device_detach(dev);
+}
+
+module_init(fmi_init);
+module_exit(fmi_cleanup_module);
diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c
new file mode 100644
index 00000000000..0732efda6a9
--- /dev/null
+++ b/drivers/media/radio/radio-sf16fmr2.c
@@ -0,0 +1,434 @@
+/* SF16FMR2 radio driver for Linux radio support
+ * heavily based on fmi driver...
+ * (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com
+ *
+ * Notes on the hardware
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ * No volume control - only mute/unmute - you have to use line volume
+ *
+ * For read stereo/mono you must wait 0.1 sec after set frequency and
+ * card unmuted so I set frequency on unmute
+ * Signal handling seem to work only on autoscanning (not implemented)
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <asm/semaphore.h>
+
+static struct semaphore lock;
+
+#undef DEBUG
+//#define DEBUG 1
+
+#ifdef DEBUG
+# define debug_print(s) printk s
+#else
+# define debug_print(s)
+#endif
+
+/* this should be static vars for module size */
+struct fmr2_device
+{
+ int port;
+ int curvol; /* 0-65535, if not volume 0 or 65535 */
+ int mute;
+ int stereo; /* card is producing stereo audio */
+ unsigned long curfreq; /* freq in kHz */
+ int card_type;
+ __u32 flags;
+};
+
+static int io = 0x384;
+static int radio_nr = -1;
+
+/* hw precision is 12.5 kHz
+ * It is only usefull to give freq in intervall of 200 (=0.0125Mhz),
+ * other bits will be truncated
+ */
+#define RSF16_ENCODE(x) ((x)/200+856)
+#define RSF16_MINFREQ 87*16000
+#define RSF16_MAXFREQ 108*16000
+
+static inline void wait(int n,int port)
+{
+ for (;n;--n) inb(port);
+}
+
+static void outbits(int bits, unsigned int data, int nWait, int port)
+{
+ int bit;
+ for(;--bits>=0;) {
+ bit = (data>>bits) & 1;
+ outb(bit,port);
+ wait(nWait,port);
+ outb(bit|2,port);
+ wait(nWait,port);
+ outb(bit,port);
+ wait(nWait,port);
+ }
+}
+
+static inline void fmr2_mute(int port)
+{
+ outb(0x00, port);
+ wait(4,port);
+}
+
+static inline void fmr2_unmute(int port)
+{
+ outb(0x04, port);
+ wait(4,port);
+}
+
+static inline int fmr2_stereo_mode(int port)
+{
+ int n = inb(port);
+ outb(6,port);
+ inb(port);
+ n = ((n>>3)&1)^1;
+ debug_print((KERN_DEBUG "stereo: %d\n", n));
+ return n;
+}
+
+static int fmr2_product_info(struct fmr2_device *dev)
+{
+ int n = inb(dev->port);
+ n &= 0xC1;
+ if (n == 0)
+ {
+ /* this should support volume set */
+ dev->card_type = 12;
+ return 0;
+ }
+ /* not volume (mine is 11) */
+ dev->card_type = (n==128)?11:0;
+ return n;
+}
+
+static inline int fmr2_getsigstr(struct fmr2_device *dev)
+{
+ /* !!! work only if scanning freq */
+ int port = dev->port, res = 0xffff;
+ outb(5,port);
+ wait(4,port);
+ if (!(inb(port)&1)) res = 0;
+ debug_print((KERN_DEBUG "signal: %d\n", res));
+ return res;
+}
+
+/* set frequency and unmute card */
+static int fmr2_setfreq(struct fmr2_device *dev)
+{
+ int port = dev->port;
+ unsigned long freq = dev->curfreq;
+
+ fmr2_mute(port);
+
+ /* 0x42 for mono output
+ * 0x102 forward scanning
+ * 0x182 scansione avanti
+ */
+ outbits(9,0x2,3,port);
+ outbits(16,RSF16_ENCODE(freq),2,port);
+
+ fmr2_unmute(port);
+
+ /* wait 0.11 sec */
+ msleep(110);
+
+ /* NOTE if mute this stop radio
+ you must set freq on unmute */
+ dev->stereo = fmr2_stereo_mode(port);
+ return 0;
+}
+
+/* !!! not tested, in my card this does't work !!! */
+static int fmr2_setvolume(struct fmr2_device *dev)
+{
+ int i,a,n, port = dev->port;
+
+ if (dev->card_type != 11) return 1;
+
+ switch( (dev->curvol+(1<<11)) >> 12 )
+ {
+ case 0: case 1: n = 0x21; break;
+ case 2: n = 0x84; break;
+ case 3: n = 0x90; break;
+ case 4: n = 0x104; break;
+ case 5: n = 0x110; break;
+ case 6: n = 0x204; break;
+ case 7: n = 0x210; break;
+ case 8: n = 0x402; break;
+ case 9: n = 0x404; break;
+ default:
+ case 10: n = 0x408; break;
+ case 11: n = 0x410; break;
+ case 12: n = 0x801; break;
+ case 13: n = 0x802; break;
+ case 14: n = 0x804; break;
+ case 15: n = 0x808; break;
+ case 16: n = 0x810; break;
+ }
+ for(i=12;--i>=0;)
+ {
+ a = ((n >> i) & 1) << 6; /* if (a=0) a= 0; else a= 0x40; */
+ outb(a|4, port);
+ wait(4,port);
+ outb(a|0x24, port);
+ wait(4,port);
+ outb(a|4, port);
+ wait(4,port);
+ }
+ for(i=6;--i>=0;)
+ {
+ a = ((0x18 >> i) & 1) << 6;
+ outb(a|4, port);
+ wait(4,port);
+ outb(a|0x24, port);
+ wait(4,port);
+ outb(a|4, port);
+ wait(4,port);
+ }
+ wait(4,port);
+ outb(0x14, port);
+
+ return 0;
+}
+
+static int fmr2_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct fmr2_device *fmr2 = dev->priv;
+ debug_print((KERN_DEBUG "freq %ld flags %d vol %d mute %d "
+ "stereo %d type %d\n",
+ fmr2->curfreq, fmr2->flags, fmr2->curvol, fmr2->mute,
+ fmr2->stereo, fmr2->card_type));
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "SF16-FMR2 radio");
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ int mult;
+
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ strcpy(v->name, "FM");
+ mult = (fmr2->flags & VIDEO_TUNER_LOW) ? 1 : 1000;
+ v->rangelow = RSF16_MINFREQ/mult;
+ v->rangehigh = RSF16_MAXFREQ/mult;
+ v->flags = fmr2->flags | VIDEO_AUDIO_MUTABLE;
+ if (fmr2->mute)
+ v->flags |= VIDEO_AUDIO_MUTE;
+ v->mode=VIDEO_MODE_AUTO;
+ down(&lock);
+ v->signal = fmr2_getsigstr(fmr2);
+ up(&lock);
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if (v->tuner!=0)
+ return -EINVAL;
+ fmr2->flags = v->flags & VIDEO_TUNER_LOW;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = fmr2->curfreq;
+ if (!(fmr2->flags & VIDEO_TUNER_LOW))
+ *freq /= 1000;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ if (!(fmr2->flags & VIDEO_TUNER_LOW))
+ *freq *= 1000;
+ if ( *freq < RSF16_MINFREQ || *freq > RSF16_MAXFREQ )
+ return -EINVAL;
+ /* rounding in steps of 200 to match th freq
+ * that will be used
+ */
+ fmr2->curfreq = (*freq/200)*200;
+
+ /* set card freq (if not muted) */
+ if (fmr2->curvol && !fmr2->mute)
+ {
+ down(&lock);
+ fmr2_setfreq(fmr2);
+ up(&lock);
+ }
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0,sizeof(*v));
+ /* !!! do not return VIDEO_AUDIO_MUTE */
+ v->flags = VIDEO_AUDIO_MUTABLE;
+ strcpy(v->name, "Radio");
+ /* get current stereo mode */
+ v->mode = fmr2->stereo ? VIDEO_SOUND_STEREO: VIDEO_SOUND_MONO;
+ /* volume supported ? */
+ if (fmr2->card_type == 11)
+ {
+ v->flags |= VIDEO_AUDIO_VOLUME;
+ v->step = 1 << 12;
+ v->volume = fmr2->curvol;
+ }
+ debug_print((KERN_DEBUG "Get flags %d vol %d\n", v->flags, v->volume));
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ debug_print((KERN_DEBUG "Set flags %d vol %d\n", v->flags, v->volume));
+ /* set volume */
+ if (v->flags & VIDEO_AUDIO_VOLUME)
+ fmr2->curvol = v->volume; /* !!! set with precision */
+ if (fmr2->card_type != 11) fmr2->curvol = 65535;
+ fmr2->mute = 0;
+ if (v->flags & VIDEO_AUDIO_MUTE)
+ fmr2->mute = 1;
+#ifdef DEBUG
+ if (fmr2->curvol && !fmr2->mute)
+ printk(KERN_DEBUG "unmute\n");
+ else
+ printk(KERN_DEBUG "mute\n");
+#endif
+ down(&lock);
+ if (fmr2->curvol && !fmr2->mute)
+ {
+ fmr2_setvolume(fmr2);
+ fmr2_setfreq(fmr2);
+ }
+ else fmr2_mute(fmr2->port);
+ up(&lock);
+ return 0;
+ }
+ case VIDIOCGUNIT:
+ {
+ struct video_unit *v = arg;
+ v->video=VIDEO_NO_UNIT;
+ v->vbi=VIDEO_NO_UNIT;
+ v->radio=dev->minor;
+ v->audio=0; /* How do we find out this??? */
+ v->teletext=VIDEO_NO_UNIT;
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int fmr2_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+ {
+ return video_usercopy(inode, file, cmd, arg, fmr2_do_ioctl);
+}
+
+static struct fmr2_device fmr2_unit;
+
+static struct file_operations fmr2_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = fmr2_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device fmr2_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "SF16FMR2 radio",
+ . type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_SF16FMR2,
+ .fops = &fmr2_fops,
+};
+
+static int __init fmr2_init(void)
+{
+ fmr2_unit.port = io;
+ fmr2_unit.curvol = 0;
+ fmr2_unit.mute = 0;
+ fmr2_unit.curfreq = 0;
+ fmr2_unit.stereo = 1;
+ fmr2_unit.flags = VIDEO_TUNER_LOW;
+ fmr2_unit.card_type = 0;
+ fmr2_radio.priv = &fmr2_unit;
+
+ init_MUTEX(&lock);
+
+ if (request_region(io, 2, "sf16fmr2"))
+ {
+ printk(KERN_ERR "fmr2: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ if(video_register_device(&fmr2_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "SF16FMR2 radio card driver at 0x%x.\n", io);
+ debug_print((KERN_DEBUG "Mute %d Low %d\n",VIDEO_AUDIO_MUTE,VIDEO_TUNER_LOW));
+ /* mute card - prevents noisy bootups */
+ down(&lock);
+ fmr2_mute(io);
+ fmr2_product_info(&fmr2_unit);
+ up(&lock);
+ debug_print((KERN_DEBUG "card_type %d\n", fmr2_unit.card_type));
+ return 0;
+}
+
+MODULE_AUTHOR("Ziglio Frediano, freddy77@angelfire.com");
+MODULE_DESCRIPTION("A driver for the SF16FMR2 radio.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the SF16FMR2 card (should be 0x384, if do not work try 0x284)");
+module_param(radio_nr, int, 0);
+
+static void __exit fmr2_cleanup_module(void)
+{
+ video_unregister_device(&fmr2_radio);
+ release_region(io,2);
+}
+
+module_init(fmr2_init);
+module_exit(fmr2_cleanup_module);
+
+#ifndef MODULE
+
+static int __init fmr2_setup_io(char *str)
+{
+ get_option(&str, &io);
+ return 1;
+}
+
+__setup("sf16fmr2=", fmr2_setup_io);
+
+#endif
diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c
new file mode 100644
index 00000000000..248d67fde03
--- /dev/null
+++ b/drivers/media/radio/radio-terratec.c
@@ -0,0 +1,341 @@
+/* Terratec ActiveRadio ISA Standalone card driver for Linux radio support
+ * (c) 1999 R. Offermanns (rolf@offermanns.de)
+ * based on the aimslab radio driver from M. Kirkwood
+ * many thanks to Michael Becker and Friedhelm Birth (from TerraTec)
+ *
+ *
+ * History:
+ * 1999-05-21 First preview release
+ *
+ * Notes on the hardware:
+ * There are two "main" chips on the card:
+ * - Philips OM5610 (http://www-us.semiconductors.philips.com/acrobat/datasheets/OM5610_2.pdf)
+ * - Philips SAA6588 (http://www-us.semiconductors.philips.com/acrobat/datasheets/SAA6588_1.pdf)
+ * (you can get the datasheet at the above links)
+ *
+ * Frequency control is done digitally -- ie out(port,encodefreq(95.8));
+ * Volume Control is done digitally
+ *
+ * there is a I2C controlled RDS decoder (SAA6588) onboard, which i would like to support someday
+ * (as soon i have understand how to get started :)
+ * If you can help me out with that, please contact me!!
+ *
+ *
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_TERRATEC_PORT */
+#include <linux/spinlock.h>
+
+#ifndef CONFIG_RADIO_TERRATEC_PORT
+#define CONFIG_RADIO_TERRATEC_PORT 0x590
+#endif
+
+/**************** this ones are for the terratec *******************/
+#define BASEPORT 0x590
+#define VOLPORT 0x591
+#define WRT_DIS 0x00
+#define CLK_OFF 0x00
+#define IIC_DATA 0x01
+#define IIC_CLK 0x02
+#define DATA 0x04
+#define CLK_ON 0x08
+#define WRT_EN 0x10
+/*******************************************************************/
+
+static int io = CONFIG_RADIO_TERRATEC_PORT;
+static int radio_nr = -1;
+static spinlock_t lock;
+
+struct tt_device
+{
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+};
+
+
+/* local things */
+
+static void cardWriteVol(int volume)
+{
+ int i;
+ volume = volume+(volume * 32); // change both channels
+ spin_lock(&lock);
+ for (i=0;i<8;i++)
+ {
+ if (volume & (0x80>>i))
+ outb(0x80, VOLPORT);
+ else outb(0x00, VOLPORT);
+ }
+ spin_unlock(&lock);
+}
+
+
+
+static void tt_mute(struct tt_device *dev)
+{
+ dev->muted = 1;
+ cardWriteVol(0);
+}
+
+static int tt_setvol(struct tt_device *dev, int vol)
+{
+
+// printk(KERN_ERR "setvol called, vol = %d\n", vol);
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ cardWriteVol(vol); /* enable card */
+ }
+
+ return 0;
+ }
+
+ if(vol == 0) { /* volume = 0 means mute the card */
+ cardWriteVol(0); /* "turn off card" by setting vol to 0 */
+ dev->curvol = vol; /* track the volume state! */
+ return 0;
+ }
+
+ dev->muted = 0;
+
+ cardWriteVol(vol);
+
+ dev->curvol = vol;
+
+ return 0;
+
+}
+
+
+/* this is the worst part in this driver */
+/* many more or less strange things are going on here, but hey, it works :) */
+
+static int tt_setfreq(struct tt_device *dev, unsigned long freq1)
+{
+ int freq;
+ int i;
+ int p;
+ int temp;
+ long rest;
+
+ unsigned char buffer[25]; /* we have to bit shift 25 registers */
+ freq = freq1/160; /* convert the freq. to a nice to handle value */
+ for(i=24;i>-1;i--)
+ buffer[i]=0;
+
+ rest = freq*10+10700; /* i once had understood what is going on here */
+ /* maybe some wise guy (friedhelm?) can comment this stuff */
+ i=13;
+ p=10;
+ temp=102400;
+ while (rest!=0)
+ {
+ if (rest%temp == rest)
+ buffer[i] = 0;
+ else
+ {
+ buffer[i] = 1;
+ rest = rest-temp;
+ }
+ i--;
+ p--;
+ temp = temp/2;
+ }
+
+ spin_lock(&lock);
+
+ for (i=24;i>-1;i--) /* bit shift the values to the radiocard */
+ {
+ if (buffer[i]==1)
+ {
+ outb(WRT_EN|DATA, BASEPORT);
+ outb(WRT_EN|DATA|CLK_ON , BASEPORT);
+ outb(WRT_EN|DATA, BASEPORT);
+ }
+ else
+ {
+ outb(WRT_EN|0x00, BASEPORT);
+ outb(WRT_EN|0x00|CLK_ON , BASEPORT);
+ }
+ }
+ outb(0x00, BASEPORT);
+
+ spin_unlock(&lock);
+
+ return 0;
+}
+
+static int tt_getsigstr(struct tt_device *dev) /* TODO */
+{
+ if (inb(io) & 2) /* bit set = no signal present */
+ return 0;
+ return 1; /* signal present */
+}
+
+
+/* implement the video4linux api */
+
+static int tt_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct tt_device *tt=dev->priv;
+
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "ActiveRadio");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow=(87*16000);
+ v->rangehigh=(108*16000);
+ v->flags=VIDEO_TUNER_LOW;
+ v->mode=VIDEO_MODE_AUTO;
+ strcpy(v->name, "FM");
+ v->signal=0xFFFF*tt_getsigstr(tt);
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = tt->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ tt->curfreq = *freq;
+ tt_setfreq(tt, tt->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v,0, sizeof(*v));
+ v->flags|=VIDEO_AUDIO_MUTABLE|VIDEO_AUDIO_VOLUME;
+ v->volume=tt->curvol * 6554;
+ v->step=6554;
+ strcpy(v->name, "Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if(v->audio)
+ return -EINVAL;
+ if(v->flags&VIDEO_AUDIO_MUTE)
+ tt_mute(tt);
+ else
+ tt_setvol(tt,v->volume/6554);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int tt_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, tt_do_ioctl);
+}
+
+static struct tt_device terratec_unit;
+
+static struct file_operations terratec_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = tt_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device terratec_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "TerraTec ActiveRadio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_TERRATEC,
+ .fops = &terratec_fops,
+};
+
+static int __init terratec_init(void)
+{
+ if(io==-1)
+ {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if (!request_region(io, 2, "terratec"))
+ {
+ printk(KERN_ERR "TerraTec: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ terratec_radio.priv=&terratec_unit;
+
+ spin_lock_init(&lock);
+
+ if(video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io,2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver.\n");
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+ cardWriteVol(0);
+ terratec_unit.curvol = 0;
+
+ return 0;
+}
+
+MODULE_AUTHOR("R.OFFERMANNS & others");
+MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
+MODULE_LICENSE("GPL");
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the TerraTec ActiveRadio card (0x590 or 0x591)");
+module_param(radio_nr, int, 0);
+
+static void __exit terratec_cleanup_module(void)
+{
+ video_unregister_device(&terratec_radio);
+ release_region(io,2);
+ printk(KERN_INFO "TERRATEC ActivRadio Standalone card driver unloaded.\n");
+}
+
+module_init(terratec_init);
+module_exit(terratec_cleanup_module);
+
diff --git a/drivers/media/radio/radio-trust.c b/drivers/media/radio/radio-trust.c
new file mode 100644
index 00000000000..b300bedf7c7
--- /dev/null
+++ b/drivers/media/radio/radio-trust.c
@@ -0,0 +1,320 @@
+/* radio-trust.c - Trust FM Radio card driver for Linux 2.2
+ * by Eric Lammerts <eric@scintilla.utwente.nl>
+ *
+ * Based on radio-aztech.c. Original notes:
+ *
+ * Adapted to support the Video for Linux API by
+ * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by:
+ *
+ * Quay Ly
+ * Donald Song
+ * Jason Lewis (jlewis@twilight.vtc.vsc.edu)
+ * Scott McGrath (smcgrath@twilight.vtc.vsc.edu)
+ * William McGrath (wmcgrath@twilight.vtc.vsc.edu)
+ *
+ * The basis for this code may be found at http://bigbang.vtc.vsc.edu/fmradio/
+ */
+
+#include <stdarg.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/videodev.h>
+#include <linux/config.h> /* CONFIG_RADIO_TRUST_PORT */
+
+/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
+
+#ifndef CONFIG_RADIO_TRUST_PORT
+#define CONFIG_RADIO_TRUST_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_TRUST_PORT;
+static int radio_nr = -1;
+static int ioval = 0xf;
+static __u16 curvol;
+static __u16 curbass;
+static __u16 curtreble;
+static unsigned long curfreq;
+static int curstereo;
+static int curmute;
+
+/* i2c addresses */
+#define TDA7318_ADDR 0x88
+#define TSA6060T_ADDR 0xc4
+
+#define TR_DELAY do { inb(io); inb(io); inb(io); } while(0)
+#define TR_SET_SCL outb(ioval |= 2, io)
+#define TR_CLR_SCL outb(ioval &= 0xfd, io)
+#define TR_SET_SDA outb(ioval |= 1, io)
+#define TR_CLR_SDA outb(ioval &= 0xfe, io)
+
+static void write_i2c(int n, ...)
+{
+ unsigned char val, mask;
+ va_list args;
+
+ va_start(args, n);
+
+ /* start condition */
+ TR_SET_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SDA;
+ TR_CLR_SCL;
+ TR_DELAY;
+
+ for(; n; n--) {
+ val = va_arg(args, unsigned);
+ for(mask = 0x80; mask; mask >>= 1) {
+ if(val & mask)
+ TR_SET_SDA;
+ else
+ TR_CLR_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SCL;
+ TR_DELAY;
+ }
+ /* acknowledge bit */
+ TR_SET_SDA;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_CLR_SCL;
+ TR_DELAY;
+ }
+
+ /* stop condition */
+ TR_CLR_SDA;
+ TR_DELAY;
+ TR_SET_SCL;
+ TR_DELAY;
+ TR_SET_SDA;
+ TR_DELAY;
+
+ va_end(args);
+}
+
+static void tr_setvol(__u16 vol)
+{
+ curvol = vol / 2048;
+ write_i2c(2, TDA7318_ADDR, curvol ^ 0x1f);
+}
+
+static int basstreble2chip[15] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8
+};
+
+static void tr_setbass(__u16 bass)
+{
+ curbass = bass / 4370;
+ write_i2c(2, TDA7318_ADDR, 0x60 | basstreble2chip[curbass]);
+}
+
+static void tr_settreble(__u16 treble)
+{
+ curtreble = treble / 4370;
+ write_i2c(2, TDA7318_ADDR, 0x70 | basstreble2chip[curtreble]);
+}
+
+static void tr_setstereo(int stereo)
+{
+ curstereo = !!stereo;
+ ioval = (ioval & 0xfb) | (!curstereo << 2);
+ outb(ioval, io);
+}
+
+static void tr_setmute(int mute)
+{
+ curmute = !!mute;
+ ioval = (ioval & 0xf7) | (curmute << 3);
+ outb(ioval, io);
+}
+
+static int tr_getsigstr(void)
+{
+ int i, v;
+
+ for(i = 0, v = 0; i < 100; i++) v |= inb(io);
+ return (v & 1)? 0 : 0xffff;
+}
+
+static int tr_getstereo(void)
+{
+ /* don't know how to determine it, just return the setting */
+ return curstereo;
+}
+
+static void tr_setfreq(unsigned long f)
+{
+ f /= 160; /* Convert to 10 kHz units */
+ f += 1070; /* Add 10.7 MHz IF */
+
+ write_i2c(5, TSA6060T_ADDR, (f << 1) | 1, f >> 7, 0x60 | ((f >> 15) & 1), 0);
+}
+
+static int tr_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ switch(cmd)
+ {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+
+ memset(v,0,sizeof(*v));
+ v->type=VID_TYPE_TUNER;
+ v->channels=1;
+ v->audios=1;
+ strcpy(v->name, "Trust FM Radio");
+
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+
+ if(v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+
+ v->rangelow = 87500 * 16;
+ v->rangehigh = 108000 * 16;
+ v->flags = VIDEO_TUNER_LOW;
+ v->mode = VIDEO_MODE_AUTO;
+
+ v->signal = tr_getsigstr();
+ if(tr_getstereo())
+ v->flags |= VIDEO_TUNER_STEREO_ON;
+
+ strcpy(v->name, "FM");
+
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if(v->tuner != 0)
+ return -EINVAL;
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ tr_setfreq(*freq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+
+ memset(v,0, sizeof(*v));
+ v->flags = VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS | VIDEO_AUDIO_TREBLE;
+ v->mode = curstereo? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
+ v->volume = curvol * 2048;
+ v->step = 2048;
+ v->bass = curbass * 4370;
+ v->treble = curtreble * 4370;
+
+ strcpy(v->name, "Trust FM Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+
+ if(v->audio)
+ return -EINVAL;
+ tr_setvol(v->volume);
+ tr_setbass(v->bass);
+ tr_settreble(v->treble);
+ tr_setstereo(v->mode & VIDEO_SOUND_STEREO);
+ tr_setmute(v->flags & VIDEO_AUDIO_MUTE);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int tr_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, tr_do_ioctl);
+}
+
+static struct file_operations trust_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = tr_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device trust_radio=
+{
+ .owner = THIS_MODULE,
+ .name = "Trust FM Radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_TRUST,
+ .fops = &trust_fops,
+};
+
+static int __init trust_init(void)
+{
+ if(io == -1) {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if(!request_region(io, 2, "Trust FM Radio")) {
+ printk(KERN_ERR "trust: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+ if(video_register_device(&trust_radio, VFL_TYPE_RADIO, radio_nr)==-1)
+ {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "Trust FM Radio card driver v1.0.\n");
+
+ write_i2c(2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */
+ write_i2c(2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */
+
+ tr_setvol(0x8000);
+ tr_setbass(0x8000);
+ tr_settreble(0x8000);
+ tr_setstereo(1);
+
+ /* mute card - prevents noisy bootups */
+ tr_setmute(1);
+
+ return 0;
+}
+
+MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
+MODULE_DESCRIPTION("A driver for the Trust FM Radio card.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the Trust FM Radio card (0x350 or 0x358)");
+module_param(radio_nr, int, 0);
+
+static void __exit cleanup_trust_module(void)
+{
+ video_unregister_device(&trust_radio);
+ release_region(io, 2);
+}
+
+module_init(trust_init);
+module_exit(cleanup_trust_module);
diff --git a/drivers/media/radio/radio-typhoon.c b/drivers/media/radio/radio-typhoon.c
new file mode 100644
index 00000000000..d7da901ebe9
--- /dev/null
+++ b/drivers/media/radio/radio-typhoon.c
@@ -0,0 +1,383 @@
+/* Typhoon Radio Card driver for radio support
+ * (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
+ *
+ * Card manufacturer:
+ * http://194.18.155.92/idc/prod2.idc?nr=50753&lang=e
+ *
+ * Notes on the hardware
+ *
+ * This card has two output sockets, one for speakers and one for line.
+ * The speaker output has volume control, but only in four discrete
+ * steps. The line output has neither volume control nor mute.
+ *
+ * The card has auto-stereo according to its manual, although it all
+ * sounds mono to me (even with the Win/DOS drivers). Maybe it's my
+ * antenna - I really don't know for sure.
+ *
+ * Frequency control is done digitally.
+ *
+ * Volume control is done digitally, but there are only four different
+ * possible values. So you should better always turn the volume up and
+ * use line control. I got the best results by connecting line output
+ * to the sound card microphone input. For such a configuration the
+ * volume control has no effect, since volume control only influences
+ * the speaker output.
+ *
+ * There is no explicit mute/unmute. So I set the radio frequency to a
+ * value where I do expect just noise and turn the speaker volume down.
+ * The frequency change is necessary since the card never seems to be
+ * completely silent.
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/proc_fs.h> /* radio card status report */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_TYPHOON_* */
+
+#define BANNER "Typhoon Radio Card driver v0.1\n"
+
+#ifndef CONFIG_RADIO_TYPHOON_PORT
+#define CONFIG_RADIO_TYPHOON_PORT -1
+#endif
+
+#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
+#define CONFIG_RADIO_TYPHOON_MUTEFREQ 0
+#endif
+
+#ifndef CONFIG_PROC_FS
+#undef CONFIG_RADIO_TYPHOON_PROC_FS
+#endif
+
+struct typhoon_device {
+ int users;
+ int iobase;
+ int curvol;
+ int muted;
+ unsigned long curfreq;
+ unsigned long mutefreq;
+ struct semaphore lock;
+};
+
+static void typhoon_setvol_generic(struct typhoon_device *dev, int vol);
+static int typhoon_setfreq_generic(struct typhoon_device *dev,
+ unsigned long frequency);
+static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency);
+static void typhoon_mute(struct typhoon_device *dev);
+static void typhoon_unmute(struct typhoon_device *dev);
+static int typhoon_setvol(struct typhoon_device *dev, int vol);
+static int typhoon_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+static int typhoon_get_info(char *buf, char **start, off_t offset, int len);
+#endif
+
+static void typhoon_setvol_generic(struct typhoon_device *dev, int vol)
+{
+ down(&dev->lock);
+ vol >>= 14; /* Map 16 bit to 2 bit */
+ vol &= 3;
+ outb_p(vol / 2, dev->iobase); /* Set the volume, high bit. */
+ outb_p(vol % 2, dev->iobase + 2); /* Set the volume, low bit. */
+ up(&dev->lock);
+}
+
+static int typhoon_setfreq_generic(struct typhoon_device *dev,
+ unsigned long frequency)
+{
+ unsigned long outval;
+ unsigned long x;
+
+ /*
+ * The frequency transfer curve is not linear. The best fit I could
+ * get is
+ *
+ * outval = -155 + exp((f + 15.55) * 0.057))
+ *
+ * where frequency f is in MHz. Since we don't have exp in the kernel,
+ * I approximate this function by a third order polynomial.
+ *
+ */
+
+ down(&dev->lock);
+ x = frequency / 160;
+ outval = (x * x + 2500) / 5000;
+ outval = (outval * x + 5000) / 10000;
+ outval -= (10 * x * x + 10433) / 20866;
+ outval += 4 * x - 11505;
+
+ outb_p((outval >> 8) & 0x01, dev->iobase + 4);
+ outb_p(outval >> 9, dev->iobase + 6);
+ outb_p(outval & 0xff, dev->iobase + 8);
+ up(&dev->lock);
+
+ return 0;
+}
+
+static int typhoon_setfreq(struct typhoon_device *dev, unsigned long frequency)
+{
+ typhoon_setfreq_generic(dev, frequency);
+ dev->curfreq = frequency;
+ return 0;
+}
+
+static void typhoon_mute(struct typhoon_device *dev)
+{
+ if (dev->muted == 1)
+ return;
+ typhoon_setvol_generic(dev, 0);
+ typhoon_setfreq_generic(dev, dev->mutefreq);
+ dev->muted = 1;
+}
+
+static void typhoon_unmute(struct typhoon_device *dev)
+{
+ if (dev->muted == 0)
+ return;
+ typhoon_setfreq_generic(dev, dev->curfreq);
+ typhoon_setvol_generic(dev, dev->curvol);
+ dev->muted = 0;
+}
+
+static int typhoon_setvol(struct typhoon_device *dev, int vol)
+{
+ if (dev->muted && vol != 0) { /* user is unmuting the card */
+ dev->curvol = vol;
+ typhoon_unmute(dev);
+ return 0;
+ }
+ if (vol == dev->curvol) /* requested volume == current */
+ return 0;
+
+ if (vol == 0) { /* volume == 0 means mute the card */
+ typhoon_mute(dev);
+ dev->curvol = vol;
+ return 0;
+ }
+ typhoon_setvol_generic(dev, vol);
+ dev->curvol = vol;
+ return 0;
+}
+
+
+static int typhoon_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct typhoon_device *typhoon = dev->priv;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+ memset(v,0,sizeof(*v));
+ v->type = VID_TYPE_TUNER;
+ v->channels = 1;
+ v->audios = 1;
+ strcpy(v->name, "Typhoon Radio");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if (v->tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v->rangelow = 875 * 1600;
+ v->rangehigh = 1080 * 1600;
+ v->flags = VIDEO_TUNER_LOW;
+ v->mode = VIDEO_MODE_AUTO;
+ v->signal = 0xFFFF; /* We can't get the signal strength */
+ strcpy(v->name, "FM");
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if (v->tuner != 0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = typhoon->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ typhoon->curfreq = *freq;
+ typhoon_setfreq(typhoon, typhoon->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v, 0, sizeof(*v));
+ v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+ v->mode |= VIDEO_SOUND_MONO;
+ v->volume = typhoon->curvol;
+ v->step = 1 << 14;
+ strcpy(v->name, "Typhoon Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if (v->audio)
+ return -EINVAL;
+ if (v->flags & VIDEO_AUDIO_MUTE)
+ typhoon_mute(typhoon);
+ else
+ typhoon_unmute(typhoon);
+ if (v->flags & VIDEO_AUDIO_VOLUME)
+ typhoon_setvol(typhoon, v->volume);
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int typhoon_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, typhoon_do_ioctl);
+}
+
+static struct typhoon_device typhoon_unit =
+{
+ .iobase = CONFIG_RADIO_TYPHOON_PORT,
+ .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ,
+ .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ,
+};
+
+static struct file_operations typhoon_fops = {
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = typhoon_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device typhoon_radio =
+{
+ .owner = THIS_MODULE,
+ .name = "Typhoon Radio",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_TYPHOON,
+ .fops = &typhoon_fops,
+};
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+
+static int typhoon_get_info(char *buf, char **start, off_t offset, int len)
+{
+ char *out = buf;
+
+ #ifdef MODULE
+ #define MODULEPROCSTRING "Driver loaded as a module"
+ #else
+ #define MODULEPROCSTRING "Driver compiled into kernel"
+ #endif
+
+ /* output must be kept under PAGE_SIZE */
+ out += sprintf(out, BANNER);
+ out += sprintf(out, "Load type: " MODULEPROCSTRING "\n\n");
+ out += sprintf(out, "frequency = %lu kHz\n",
+ typhoon_unit.curfreq >> 4);
+ out += sprintf(out, "volume = %d\n", typhoon_unit.curvol);
+ out += sprintf(out, "mute = %s\n", typhoon_unit.muted ?
+ "on" : "off");
+ out += sprintf(out, "iobase = 0x%x\n", typhoon_unit.iobase);
+ out += sprintf(out, "mute frequency = %lu kHz\n",
+ typhoon_unit.mutefreq >> 4);
+ return out - buf;
+}
+
+#endif /* CONFIG_RADIO_TYPHOON_PROC_FS */
+
+MODULE_AUTHOR("Dr. Henrik Seidel");
+MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
+MODULE_LICENSE("GPL");
+
+static int io = -1;
+static int radio_nr = -1;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the Typhoon card (0x316 or 0x336)");
+module_param(radio_nr, int, 0);
+
+#ifdef MODULE
+static unsigned long mutefreq = 0;
+module_param(mutefreq, ulong, 0);
+MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
+#endif
+
+static int __init typhoon_init(void)
+{
+#ifdef MODULE
+ if (io == -1) {
+ printk(KERN_ERR "radio-typhoon: You must set an I/O address with io=0x316 or io=0x336\n");
+ return -EINVAL;
+ }
+ typhoon_unit.iobase = io;
+
+ if (mutefreq < 87000 || mutefreq > 108500) {
+ printk(KERN_ERR "radio-typhoon: You must set a frequency (in kHz) used when muting the card,\n");
+ printk(KERN_ERR "radio-typhoon: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108500)\n");
+ return -EINVAL;
+ }
+ typhoon_unit.mutefreq = mutefreq;
+#endif /* MODULE */
+
+ printk(KERN_INFO BANNER);
+ init_MUTEX(&typhoon_unit.lock);
+ io = typhoon_unit.iobase;
+ if (!request_region(io, 8, "typhoon")) {
+ printk(KERN_ERR "radio-typhoon: port 0x%x already in use\n",
+ typhoon_unit.iobase);
+ return -EBUSY;
+ }
+
+ typhoon_radio.priv = &typhoon_unit;
+ if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) == -1)
+ {
+ release_region(io, 8);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "radio-typhoon: port 0x%x.\n", typhoon_unit.iobase);
+ printk(KERN_INFO "radio-typhoon: mute frequency is %lu kHz.\n",
+ typhoon_unit.mutefreq);
+ typhoon_unit.mutefreq <<= 4;
+
+ /* mute card - prevents noisy bootups */
+ typhoon_mute(&typhoon_unit);
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+ if (!create_proc_info_entry("driver/radio-typhoon", 0, NULL,
+ typhoon_get_info))
+ printk(KERN_ERR "radio-typhoon: registering /proc/driver/radio-typhoon failed\n");
+#endif
+
+ return 0;
+}
+
+static void __exit typhoon_cleanup_module(void)
+{
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+ remove_proc_entry("driver/radio-typhoon", NULL);
+#endif
+
+ video_unregister_device(&typhoon_radio);
+ release_region(io, 8);
+}
+
+module_init(typhoon_init);
+module_exit(typhoon_cleanup_module);
+
diff --git a/drivers/media/radio/radio-zoltrix.c b/drivers/media/radio/radio-zoltrix.c
new file mode 100644
index 00000000000..342f92df4ab
--- /dev/null
+++ b/drivers/media/radio/radio-zoltrix.c
@@ -0,0 +1,385 @@
+/* zoltrix radio plus driver for Linux radio support
+ * (c) 1998 C. van Schaik <carl@leg.uct.ac.za>
+ *
+ * BUGS
+ * Due to the inconsistency in reading from the signal flags
+ * it is difficult to get an accurate tuned signal.
+ *
+ * It seems that the card is not linear to 0 volume. It cuts off
+ * at a low volume, and it is not possible (at least I have not found)
+ * to get fine volume control over the low volume range.
+ *
+ * Some code derived from code by Romolo Manfredini
+ * romolo@bicnet.it
+ *
+ * 1999-05-06 - (C. van Schaik)
+ * - Make signal strength and stereo scans
+ * kinder to cpu while in delay
+ * 1999-01-05 - (C. van Schaik)
+ * - Changed tuning to 1/160Mhz accuracy
+ * - Added stereo support
+ * (card defaults to stereo)
+ * (can explicitly force mono on the card)
+ * (can detect if station is in stereo)
+ * - Added unmute function
+ * - Reworked ioctl functions
+ * 2002-07-15 - Fix Stereo typo
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* check_region, request_region */
+#include <linux/delay.h> /* udelay, msleep */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev.h> /* kernel radio structs */
+#include <linux/config.h> /* CONFIG_RADIO_ZOLTRIX_PORT */
+
+#ifndef CONFIG_RADIO_ZOLTRIX_PORT
+#define CONFIG_RADIO_ZOLTRIX_PORT -1
+#endif
+
+static int io = CONFIG_RADIO_ZOLTRIX_PORT;
+static int radio_nr = -1;
+
+struct zol_device {
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+ unsigned int stereo;
+ struct semaphore lock;
+};
+
+static int zol_setvol(struct zol_device *dev, int vol)
+{
+ dev->curvol = vol;
+ if (dev->muted)
+ return 0;
+
+ down(&dev->lock);
+ if (vol == 0) {
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ up(&dev->lock);
+ return 0;
+ }
+
+ outb(dev->curvol-1, io);
+ msleep(10);
+ inb(io + 2);
+ up(&dev->lock);
+ return 0;
+}
+
+static void zol_mute(struct zol_device *dev)
+{
+ dev->muted = 1;
+ down(&dev->lock);
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ up(&dev->lock);
+}
+
+static void zol_unmute(struct zol_device *dev)
+{
+ dev->muted = 0;
+ zol_setvol(dev, dev->curvol);
+}
+
+static int zol_setfreq(struct zol_device *dev, unsigned long freq)
+{
+ /* tunes the radio to the desired frequency */
+ unsigned long long bitmask, f, m;
+ unsigned int stereo = dev->stereo;
+ int i;
+
+ if (freq == 0)
+ return 1;
+ m = (freq / 160 - 8800) * 2;
+ f = (unsigned long long) m + 0x4d1c;
+
+ bitmask = 0xc480402c10080000ull;
+ i = 45;
+
+ down(&dev->lock);
+
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+
+ outb(0x40, io);
+ outb(0xc0, io);
+
+ bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ ( stereo << 31));
+ while (i--) {
+ if ((bitmask & 0x8000000000000000ull) != 0) {
+ outb(0x80, io);
+ udelay(50);
+ outb(0x00, io);
+ udelay(50);
+ outb(0x80, io);
+ udelay(50);
+ } else {
+ outb(0xc0, io);
+ udelay(50);
+ outb(0x40, io);
+ udelay(50);
+ outb(0xc0, io);
+ udelay(50);
+ }
+ bitmask *= 2;
+ }
+ /* termination sequence */
+ outb(0x80, io);
+ outb(0xc0, io);
+ outb(0x40, io);
+ udelay(1000);
+ inb(io+2);
+
+ udelay(1000);
+
+ if (dev->muted)
+ {
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3);
+ udelay(1000);
+ }
+
+ up(&dev->lock);
+
+ if(!dev->muted)
+ {
+ zol_setvol(dev, dev->curvol);
+ }
+ return 0;
+}
+
+/* Get signal strength */
+
+static int zol_getsigstr(struct zol_device *dev)
+{
+ int a, b;
+
+ down(&dev->lock);
+ outb(0x00, io); /* This stuff I found to do nothing */
+ outb(dev->curvol, io);
+ msleep(20);
+
+ a = inb(io);
+ msleep(10);
+ b = inb(io);
+
+ up(&dev->lock);
+
+ if (a != b)
+ return (0);
+
+ if ((a == 0xcf) || (a == 0xdf) /* I found this out by playing */
+ || (a == 0xef)) /* with a binary scanner on the card io */
+ return (1);
+ return (0);
+}
+
+static int zol_is_stereo (struct zol_device *dev)
+{
+ int x1, x2;
+
+ down(&dev->lock);
+
+ outb(0x00, io);
+ outb(dev->curvol, io);
+ msleep(20);
+
+ x1 = inb(io);
+ msleep(10);
+ x2 = inb(io);
+
+ up(&dev->lock);
+
+ if ((x1 == x2) && (x1 == 0xcf))
+ return 1;
+ return 0;
+}
+
+static int zol_do_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, void *arg)
+{
+ struct video_device *dev = video_devdata(file);
+ struct zol_device *zol = dev->priv;
+
+ switch (cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability *v = arg;
+
+ memset(v,0,sizeof(*v));
+ v->type = VID_TYPE_TUNER;
+ v->channels = 1 + zol->stereo;
+ v->audios = 1;
+ strcpy(v->name, "Zoltrix Radio");
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner *v = arg;
+ if (v->tuner)
+ return -EINVAL;
+ strcpy(v->name, "FM");
+ v->rangelow = (int) (88.0 * 16000);
+ v->rangehigh = (int) (108.0 * 16000);
+ v->flags = zol_is_stereo(zol)
+ ? VIDEO_TUNER_STEREO_ON : 0;
+ v->flags |= VIDEO_TUNER_LOW;
+ v->mode = VIDEO_MODE_AUTO;
+ v->signal = 0xFFFF * zol_getsigstr(zol);
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner *v = arg;
+ if (v->tuner != 0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ {
+ unsigned long *freq = arg;
+ *freq = zol->curfreq;
+ return 0;
+ }
+ case VIDIOCSFREQ:
+ {
+ unsigned long *freq = arg;
+ zol->curfreq = *freq;
+ zol_setfreq(zol, zol->curfreq);
+ return 0;
+ }
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *v = arg;
+ memset(v, 0, sizeof(*v));
+ v->flags |= VIDEO_AUDIO_MUTABLE | VIDEO_AUDIO_VOLUME;
+ v->mode |= zol_is_stereo(zol)
+ ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO;
+ v->volume = zol->curvol * 4096;
+ v->step = 4096;
+ strcpy(v->name, "Zoltrix Radio");
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *v = arg;
+ if (v->audio)
+ return -EINVAL;
+
+ if (v->flags & VIDEO_AUDIO_MUTE)
+ zol_mute(zol);
+ else {
+ zol_unmute(zol);
+ zol_setvol(zol, v->volume / 4096);
+ }
+
+ if (v->mode & VIDEO_SOUND_STEREO) {
+ zol->stereo = 1;
+ zol_setfreq(zol, zol->curfreq);
+ }
+ if (v->mode & VIDEO_SOUND_MONO) {
+ zol->stereo = 0;
+ zol_setfreq(zol, zol->curfreq);
+ }
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static int zol_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return video_usercopy(inode, file, cmd, arg, zol_do_ioctl);
+}
+
+static struct zol_device zoltrix_unit;
+
+static struct file_operations zoltrix_fops =
+{
+ .owner = THIS_MODULE,
+ .open = video_exclusive_open,
+ .release = video_exclusive_release,
+ .ioctl = zol_ioctl,
+ .llseek = no_llseek,
+};
+
+static struct video_device zoltrix_radio =
+{
+ .owner = THIS_MODULE,
+ .name = "Zoltrix Radio Plus",
+ .type = VID_TYPE_TUNER,
+ .hardware = VID_HARDWARE_ZOLTRIX,
+ .fops = &zoltrix_fops,
+};
+
+static int __init zoltrix_init(void)
+{
+ if (io == -1) {
+ printk(KERN_ERR "You must set an I/O address with io=0x???\n");
+ return -EINVAL;
+ }
+ if ((io != 0x20c) && (io != 0x30c)) {
+ printk(KERN_ERR "zoltrix: invalid port, try 0x20c or 0x30c\n");
+ return -ENXIO;
+ }
+
+ zoltrix_radio.priv = &zoltrix_unit;
+ if (!request_region(io, 2, "zoltrix")) {
+ printk(KERN_ERR "zoltrix: port 0x%x already in use\n", io);
+ return -EBUSY;
+ }
+
+ if (video_register_device(&zoltrix_radio, VFL_TYPE_RADIO, radio_nr) == -1)
+ {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
+
+ init_MUTEX(&zoltrix_unit.lock);
+
+ /* mute card - prevents noisy bootups */
+
+ /* this ensures that the volume is all the way down */
+
+ outb(0, io);
+ outb(0, io);
+ msleep(20);
+ inb(io + 3);
+
+ zoltrix_unit.curvol = 0;
+ zoltrix_unit.stereo = 1;
+
+ return 0;
+}
+
+MODULE_AUTHOR("C.van Schaik");
+MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
+MODULE_LICENSE("GPL");
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "I/O address of the Zoltrix Radio Plus (0x20c or 0x30c)");
+module_param(radio_nr, int, 0);
+
+static void __exit zoltrix_cleanup_module(void)
+{
+ video_unregister_device(&zoltrix_radio);
+ release_region(io, 2);
+}
+
+module_init(zoltrix_init);
+module_exit(zoltrix_cleanup_module);
+