summaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig390
-rw-r--r--drivers/media/radio/Makefile23
-rw-r--r--drivers/media/radio/dsbr100.c547
-rw-r--r--drivers/media/radio/radio-aimslab.c474
-rw-r--r--drivers/media/radio/radio-aztech.c431
-rw-r--r--drivers/media/radio/radio-cadet.c716
-rw-r--r--drivers/media/radio/radio-gemtek-pci.c502
-rw-r--r--drivers/media/radio/radio-gemtek.c656
-rw-r--r--drivers/media/radio/radio-maestro.c476
-rw-r--r--drivers/media/radio/radio-maxiradio.c482
-rw-r--r--drivers/media/radio/radio-mr800.c633
-rw-r--r--drivers/media/radio/radio-rtrack2.c378
-rw-r--r--drivers/media/radio/radio-sf16fmi.c416
-rw-r--r--drivers/media/radio/radio-sf16fmr2.c508
-rw-r--r--drivers/media/radio/radio-si470x.c1770
-rw-r--r--drivers/media/radio/radio-terratec.c448
-rw-r--r--drivers/media/radio/radio-trust.c433
-rw-r--r--drivers/media/radio/radio-typhoon.c491
-rw-r--r--drivers/media/radio/radio-zoltrix.c505
19 files changed, 10279 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
new file mode 100644
index 0000000..5189c4e
--- /dev/null
+++ b/drivers/media/radio/Kconfig
@@ -0,0 +1,390 @@
+#
+# Multimedia Video device configuration
+#
+
+menuconfig RADIO_ADAPTERS
+ bool "Radio Adapters"
+ depends on VIDEO_V4L2
+ default y
+ ---help---
+ Say Y here to enable selecting AM/FM radio adapters.
+
+if RADIO_ADAPTERS && VIDEO_V4L2
+
+config RADIO_CADET
+ tristate "ADS Cadet AM/FM Tuner"
+ depends on ISA && VIDEO_V4L2
+ ---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_V4L2
+ ---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_V4L2
+ ---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_V4L2
+ ---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 (or compatible) support"
+ depends on ISA && VIDEO_V4L2
+ ---help---
+ Choose Y here if you have this FM radio card, and then fill in the
+ I/O port address and settings below. The following cards either have
+ GemTek Radio tuner or are rebranded GemTek Radio cards:
+
+ - Sound Vision 16 Gold with FM Radio
+ - Typhoon Radio card (some models)
+ - Hama 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.
+
+config RADIO_GEMTEK_PORT
+ hex "Fixed I/O port (0x20c, 0x30c, 0x24c, 0x34c, 0c24c or 0x28c)"
+ 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 0x20c, 0x248 or 0x28c.
+ If automatic I/O port probing is enabled this port will be used only
+ in case of automatic probing failure, ie. as a fallback.
+
+config RADIO_GEMTEK_PROBE
+ bool "Automatic I/O port probing"
+ depends on RADIO_GEMTEK=y
+ default y
+ help
+ Say Y here to enable automatic probing for GemTek Radio card. The
+ following ports will be probed: 0x20c, 0x30c, 0x24c, 0x34c, 0x248 and
+ 0x28c.
+
+config RADIO_GEMTEK_PCI
+ tristate "GemTek PCI Radio Card support"
+ depends on VIDEO_V4L2 && 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_V4L2 && 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_V4L2 && PCI
+ ---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_SF16FMI
+ tristate "SF16FMI Radio"
+ depends on ISA && VIDEO_V4L2
+ ---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_V4L2
+ ---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_V4L2
+ ---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_V4L2
+ 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_V4L2
+ ---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_V4L2
+ ---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.
+
+config USB_DSBR
+ tristate "D-Link/GemTek USB FM radio support"
+ depends on USB && VIDEO_V4L2
+ ---help---
+ Say Y here if you want to connect this type of radio to your
+ computer's USB port. Note that the audio is not digital, and
+ you must connect the line out connector to a sound card or a
+ set of speakers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called dsbr100.
+
+config USB_SI470X
+ tristate "Silicon Labs Si470x FM Radio Receiver support"
+ depends on USB && VIDEO_V4L2
+ ---help---
+ This is a driver for USB devices with the Silicon Labs SI470x
+ chip. Currently these devices are known to work:
+ - 10c4:818a: Silicon Labs USB FM Radio Reference Design
+ - 06e1:a155: ADS/Tech FM Radio Receiver (formerly Instant FM Music)
+ - 1b80:d700: KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
+
+ Sound is provided by the ALSA USB Audio/MIDI driver. Therefore
+ if you don't want to use the device solely for RDS receiving,
+ it is recommended to also select SND_USB_AUDIO.
+
+ Please have a look at the documentation, especially on how
+ to redirect the audio stream from the radio to your sound device:
+ Documentation/video4linux/si470x.txt
+
+ Say Y here if you want to connect this type of radio to your
+ computer's USB port.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-si470x.
+
+config USB_MR800
+ tristate "AverMedia MR 800 USB FM radio support"
+ depends on USB && VIDEO_V4L2
+ ---help---
+ Say Y here if you want to connect this type of radio to your
+ computer's USB port. Note that the audio is not digital, and
+ you must connect the line out connector to a sound card or a
+ set of speakers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-mr800.
+
+endif # RADIO_ADAPTERS
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
new file mode 100644
index 0000000..240ec63
--- /dev/null
+++ b/drivers/media/radio/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for the kernel character device drivers.
+#
+
+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_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
+obj-$(CONFIG_USB_DSBR) += dsbr100.o
+obj-$(CONFIG_USB_SI470X) += radio-si470x.o
+obj-$(CONFIG_USB_MR800) += radio-mr800.o
+
+EXTRA_CFLAGS += -Isound
diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c
new file mode 100644
index 0000000..a5ca176
--- /dev/null
+++ b/drivers/media/radio/dsbr100.c
@@ -0,0 +1,547 @@
+/* A driver for the D-Link DSB-R100 USB radio. The R100 plugs
+ into both the USB and an analog audio input, so this thing
+ only deals with initialisation and frequency setting, the
+ audio data has to be handled by a sound driver.
+
+ Major issue: I can't find out where the device reports the signal
+ strength, and indeed the windows software appearantly just looks
+ at the stereo indicator as well. So, scanning will only find
+ stereo stations. Sad, but I can't help it.
+
+ Also, the windows program sends oodles of messages over to the
+ device, and I couldn't figure out their meaning. My suspicion
+ is that they don't have any:-)
+
+ You might find some interesting stuff about this module at
+ http://unimut.fsk.uni-heidelberg.de/unimut/demi/dsbr
+
+ Copyright (c) 2000 Markus Demleitner <msdemlei@cl.uni-heidelberg.de>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ History:
+
+ Version 0.43:
+ Oliver Neukum: avoided DMA coherency issue
+
+ Version 0.42:
+ Converted dsbr100 to use video_ioctl2
+ by Douglas Landgraf <dougsland@gmail.com>
+
+ Version 0.41-ac1:
+ Alan Cox: Some cleanups and fixes
+
+ Version 0.41:
+ Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+
+ Version 0.40:
+ Markus: Updates for 2.6.x kernels, code layout changes, name sanitizing
+
+ Version 0.30:
+ Markus: Updates for 2.5.x kernel and more ISO compliant source
+
+ Version 0.25:
+ PSL and Markus: Cleanup, radio now doesn't stop on device close
+
+ Version 0.24:
+ Markus: Hope I got these silly VIDEO_TUNER_LOW issues finally
+ right. Some minor cleanup, improved standalone compilation
+
+ Version 0.23:
+ Markus: Sign extension bug fixed by declaring transfer_buffer unsigned
+
+ Version 0.22:
+ Markus: Some (brown bag) cleanup in what VIDIOCSTUNER returns,
+ thanks to Mike Cox for pointing the problem out.
+
+ Version 0.21:
+ Markus: Minor cleanup, warnings if something goes wrong, lame attempt
+ to adhere to Documentation/CodingStyle
+
+ Version 0.2:
+ Brad Hards <bradh@dynamite.com.au>: Fixes to make it work as non-module
+ Markus: Copyright clarification
+
+ Version 0.01: Markus: initial release
+
+*/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/usb.h>
+
+/*
+ * Version Information
+ */
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+
+#define DRIVER_VERSION "v0.41"
+#define RADIO_VERSION KERNEL_VERSION(0,4,1)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ }
+};
+
+#define DRIVER_AUTHOR "Markus Demleitner <msdemlei@tucana.harvard.edu>"
+#define DRIVER_DESC "D-Link DSB-R100 USB FM radio driver"
+
+#define DSB100_VENDOR 0x04b4
+#define DSB100_PRODUCT 0x1002
+
+/* Commands the device appears to understand */
+#define DSB100_TUNE 1
+#define DSB100_ONOFF 2
+
+#define TB_LEN 16
+
+/* Frequency limits in MHz -- these are European values. For Japanese
+devices, that would be 76 and 91. */
+#define FREQ_MIN 87.5
+#define FREQ_MAX 108.0
+#define FREQ_MUL 16000
+
+
+static int usb_dsbr100_probe(struct usb_interface *intf,
+ const struct usb_device_id *id);
+static void usb_dsbr100_disconnect(struct usb_interface *intf);
+static int usb_dsbr100_open(struct inode *inode, struct file *file);
+static int usb_dsbr100_close(struct inode *inode, struct file *file);
+
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+
+/* Data for one (physical) device */
+struct dsbr100_device {
+ struct usb_device *usbdev;
+ struct video_device *videodev;
+ u8 *transfer_buffer;
+ int curfreq;
+ int stereo;
+ int users;
+ int removed;
+ int muted;
+};
+
+
+static struct usb_device_id usb_dsbr100_device_table [] = {
+ { USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, usb_dsbr100_device_table);
+
+/* USB subsystem interface */
+static struct usb_driver usb_dsbr100_driver = {
+ .name = "dsbr100",
+ .probe = usb_dsbr100_probe,
+ .disconnect = usb_dsbr100_disconnect,
+ .id_table = usb_dsbr100_device_table,
+};
+
+/* Low-level device interface begins here */
+
+/* switch on radio */
+static int dsbr100_start(struct dsbr100_device *radio)
+{
+ if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ USB_REQ_GET_STATUS,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x00, 0xC7, radio->transfer_buffer, 8, 300) < 0 ||
+ usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ DSB100_ONOFF,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x01, 0x00, radio->transfer_buffer, 8, 300) < 0)
+ return -1;
+ radio->muted=0;
+ return (radio->transfer_buffer)[0];
+}
+
+
+/* switch off radio */
+static int dsbr100_stop(struct dsbr100_device *radio)
+{
+ if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ USB_REQ_GET_STATUS,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x16, 0x1C, radio->transfer_buffer, 8, 300) < 0 ||
+ usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ DSB100_ONOFF,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x00, 0x00, radio->transfer_buffer, 8, 300) < 0)
+ return -1;
+ radio->muted=1;
+ return (radio->transfer_buffer)[0];
+}
+
+/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
+static int dsbr100_setfreq(struct dsbr100_device *radio, int freq)
+{
+ freq = (freq / 16 * 80) / 1000 + 856;
+ if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ DSB100_TUNE,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ (freq >> 8) & 0x00ff, freq & 0xff,
+ radio->transfer_buffer, 8, 300) < 0 ||
+ usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ USB_REQ_GET_STATUS,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x96, 0xB7, radio->transfer_buffer, 8, 300) < 0 ||
+ usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ USB_REQ_GET_STATUS,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x00, 0x24, radio->transfer_buffer, 8, 300) < 0) {
+ radio->stereo = -1;
+ return -1;
+ }
+ radio->stereo = !((radio->transfer_buffer)[0] & 0x01);
+ return (radio->transfer_buffer)[0];
+}
+
+/* return the device status. This is, in effect, just whether it
+sees a stereo signal or not. Pity. */
+static void dsbr100_getstat(struct dsbr100_device *radio)
+{
+ if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ USB_REQ_GET_STATUS,
+ USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+ 0x00 , 0x24, radio->transfer_buffer, 8, 300) < 0)
+ radio->stereo = -1;
+ else
+ radio->stereo = !(radio->transfer_buffer[0] & 0x01);
+}
+
+
+/* USB subsystem interface begins here */
+
+/* handle unplugging of the device, release data structures
+if nothing keeps us from doing it. If something is still
+keeping us busy, the release callback of v4l will take care
+of releasing it. */
+static void usb_dsbr100_disconnect(struct usb_interface *intf)
+{
+ struct dsbr100_device *radio = usb_get_intfdata(intf);
+
+ usb_set_intfdata (intf, NULL);
+ if (radio) {
+ video_unregister_device(radio->videodev);
+ radio->videodev = NULL;
+ if (radio->users) {
+ kfree(radio->transfer_buffer);
+ kfree(radio);
+ } else {
+ radio->removed = 1;
+ }
+ }
+}
+
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "dsbr100", sizeof(v->driver));
+ strlcpy(v->card, "D-Link R-100 USB FM Radio", sizeof(v->card));
+ sprintf(v->bus_info, "USB");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ dsbr100_getstat(radio);
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = FREQ_MIN * FREQ_MUL;
+ v->rangehigh = FREQ_MAX * FREQ_MUL;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ if(radio->stereo)
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xffff; /* We can't get the signal strength */
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ radio->curfreq = f->frequency;
+ if (dsbr100_setfreq(radio, radio->curfreq) == -1)
+ dev_warn(&radio->usbdev->dev, "Set frequency failed\n");
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = radio->curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = radio->muted;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value) {
+ if (dsbr100_stop(radio) == -1) {
+ dev_warn(&radio->usbdev->dev,
+ "Radio did not respond properly\n");
+ return -EBUSY;
+ }
+ } else {
+ if (dsbr100_start(radio) == -1) {
+ dev_warn(&radio->usbdev->dev,
+ "Radio did not respond properly\n");
+ return -EBUSY;
+ }
+ }
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int usb_dsbr100_open(struct inode *inode, struct file *file)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+ int retval;
+
+ lock_kernel();
+ radio->users = 1;
+ radio->muted = 1;
+
+ if (dsbr100_start(radio) < 0) {
+ dev_warn(&radio->usbdev->dev,
+ "Radio did not start up properly\n");
+ radio->users = 0;
+ unlock_kernel();
+ return -EIO;
+ }
+
+ retval = dsbr100_setfreq(radio, radio->curfreq);
+
+ if (retval == -1)
+ printk(KERN_WARNING KBUILD_MODNAME ": Set frequency failed\n");
+
+ unlock_kernel();
+ return 0;
+}
+
+static int usb_dsbr100_close(struct inode *inode, struct file *file)
+{
+ struct dsbr100_device *radio = video_drvdata(file);
+
+ if (!radio)
+ return -ENODEV;
+ radio->users = 0;
+ if (radio->removed) {
+ kfree(radio->transfer_buffer);
+ kfree(radio);
+ }
+ return 0;
+}
+
+/* File system interface */
+static const struct file_operations usb_dsbr100_fops = {
+ .owner = THIS_MODULE,
+ .open = usb_dsbr100_open,
+ .release = usb_dsbr100_close,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops usb_dsbr100_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+/* V4L2 interface */
+static struct video_device dsbr100_videodev_template = {
+ .name = "D-Link DSB-R 100",
+ .fops = &usb_dsbr100_fops,
+ .ioctl_ops = &usb_dsbr100_ioctl_ops,
+ .release = video_device_release,
+};
+
+/* check if the device is present and register with v4l and
+usb if it is */
+static int usb_dsbr100_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct dsbr100_device *radio;
+
+ radio = kmalloc(sizeof(struct dsbr100_device), GFP_KERNEL);
+
+ if (!radio)
+ return -ENOMEM;
+
+ radio->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL);
+
+ if (!(radio->transfer_buffer)) {
+ kfree(radio);
+ return -ENOMEM;
+ }
+ radio->videodev = video_device_alloc();
+
+ if (!(radio->videodev)) {
+ kfree(radio->transfer_buffer);
+ kfree(radio);
+ return -ENOMEM;
+ }
+ memcpy(radio->videodev, &dsbr100_videodev_template,
+ sizeof(dsbr100_videodev_template));
+ radio->removed = 0;
+ radio->users = 0;
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->curfreq = FREQ_MIN * FREQ_MUL;
+ video_set_drvdata(radio->videodev, radio);
+ if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr) < 0) {
+ dev_warn(&intf->dev, "Could not register video device\n");
+ video_device_release(radio->videodev);
+ kfree(radio->transfer_buffer);
+ kfree(radio);
+ return -EIO;
+ }
+ usb_set_intfdata(intf, radio);
+ return 0;
+}
+
+static int __init dsbr100_init(void)
+{
+ int retval = usb_register(&usb_dsbr100_driver);
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
+ DRIVER_DESC "\n");
+ return retval;
+}
+
+static void __exit dsbr100_exit(void)
+{
+ usb_deregister(&usb_dsbr100_driver);
+}
+
+module_init (dsbr100_init);
+module_exit (dsbr100_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/radio/radio-aimslab.c b/drivers/media/radio/radio-aimslab.c
new file mode 100644
index 0000000..9305e95
--- /dev/null
+++ b/drivers/media/radio/radio-aimslab.c
@@ -0,0 +1,474 @@
+/* radiotrack (radioreveal) driver for Linux radio support
+ * (c) 1997 M. Kirkwood
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ * 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> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+#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 mutex lock;
+
+struct rt_device
+{
+ unsigned long in_use;
+ 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/msecs_to_jiffies(1000);
+ 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;
+ mutex_lock(&lock);
+ outb(0xd0, io); /* volume steady, off */
+ mutex_unlock(&lock);
+}
+
+static int rt_setvol(struct rt_device *dev, int vol)
+{
+ int i;
+
+ mutex_lock(&lock);
+
+ if(vol == dev->curvol) { /* requested volume = current */
+ if (dev->muted) { /* user is unmuting the card */
+ dev->muted = 0;
+ outb (0xd8, io); /* enable card */
+ }
+ mutex_unlock(&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! */
+ mutex_unlock(&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;
+ mutex_unlock(&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 */
+
+ mutex_lock(&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 */
+
+ mutex_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 struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-aimslab", sizeof(v->driver));
+ strlcpy(v->card, "RadioTrack", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (87*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xffff*rt_getsigstr(rt);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ rt->curfreq = f->frequency;
+ rt_setfreq(rt, rt->curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = rt->curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = rt->muted;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = rt->curvol * 6554;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ rt_mute(rt);
+ else
+ rt_setvol(rt,rt->curvol);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ rt_setvol(rt,ctrl->value);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio (struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct rt_device rtrack_unit;
+
+static int rtrack_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &rtrack_unit.in_use) ? -EBUSY : 0;
+}
+
+static int rtrack_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &rtrack_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations rtrack_fops = {
+ .owner = THIS_MODULE,
+ .open = rtrack_exclusive_open,
+ .release = rtrack_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops rtrack_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device rtrack_radio = {
+ .name = "RadioTrack radio",
+ .fops = &rtrack_fops,
+ .ioctl_ops = &rtrack_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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;
+ }
+
+ video_set_drvdata(&rtrack_radio, &rtrack_unit);
+
+ if (video_register_device(&rtrack_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "AIMSlab RadioTrack/RadioReveal card driver.\n");
+
+ /* Set up the I/O locking */
+
+ mutex_init(&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 0000000..d784895
--- /dev/null
+++ b/drivers/media/radio/radio-aztech.c
@@ -0,0 +1,431 @@
+/* radio-aztech.c - Aztech radio card driver for Linux 2.2
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ * 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> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+/* 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 mutex lock;
+
+struct az_device
+{
+ unsigned long in_use;
+ 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)
+{
+ mutex_lock(&lock);
+ outb (volconvert(vol), io);
+ mutex_unlock(&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 */
+
+ mutex_lock(&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);
+
+ mutex_unlock(&lock);
+
+ return 0;
+}
+
+static int vidioc_querycap (struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-aztech", sizeof (v->driver));
+ strlcpy(v->card, "Aztech Radio", sizeof (v->card));
+ sprintf(v->bus_info,"ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner (struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct az_device *az = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+
+ v->rangelow=(87*16000);
+ v->rangehigh=(108*16000);
+ v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+ v->capability=V4L2_TUNER_CAP_LOW;
+ if(az_getstereo(az))
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal=0xFFFF*az_getsigstr(az);
+
+ return 0;
+}
+
+
+static int vidioc_s_tuner (struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_g_audio (struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+
+static int vidioc_s_audio (struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_s_frequency (struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct az_device *az = video_drvdata(file);
+
+ az->curfreq = f->frequency;
+ az_setfreq(az, az->curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency (struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct az_device *az = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = az->curfreq;
+
+ return 0;
+}
+
+static int vidioc_queryctrl (struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return (0);
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl (struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct az_device *az = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (az->curvol==0)
+ ctrl->value=1;
+ else
+ ctrl->value=0;
+ return (0);
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value=az->curvol * 6554;
+ return (0);
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl (struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct az_device *az = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value) {
+ az_setvol(az,0);
+ } else {
+ az_setvol(az,az->curvol);
+ }
+ return (0);
+ case V4L2_CID_AUDIO_VOLUME:
+ az_setvol(az,ctrl->value);
+ return (0);
+ }
+ return -EINVAL;
+}
+
+static struct az_device aztech_unit;
+
+static int aztech_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &aztech_unit.in_use) ? -EBUSY : 0;
+}
+
+static int aztech_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &aztech_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations aztech_fops = {
+ .owner = THIS_MODULE,
+ .open = aztech_exclusive_open,
+ .release = aztech_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops aztech_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device aztech_radio = {
+ .name = "Aztech radio",
+ .fops = &aztech_fops,
+ .ioctl_ops = &aztech_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+module_param_named(debug,aztech_radio.debug, int, 0644);
+MODULE_PARM_DESC(debug,"activates debug info");
+
+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;
+ }
+
+ mutex_init(&lock);
+ video_set_drvdata(&aztech_radio, &aztech_unit);
+
+ if (video_register_device(&aztech_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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 0000000..0490a1f
--- /dev/null
+++ b/drivers/media/radio/radio-cadet.c
@@ -0,0 +1,716 @@
+/* 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
+ *
+ * 2006-07-30 Hans J. Koch <koch@hjk-az.de>
+ * Changed API to V4L2
+ */
+
+#include <linux/version.h>
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* V4L2 API defs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/param.h>
+#include <linux/pnp.h>
+
+#define RDS_BUFFER 256
+#define RDS_RX_FLAG 1
+#define MBS_RX_FLAG 2
+
+#define CADET_VERSION KERNEL_VERSION(0,3,3)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+static int io=-1; /* default to isapnp activation */
+static int radio_nr = -1;
+static int users;
+static int curtuner;
+static int tunestat;
+static int sigstrength;
+static wait_queue_head_t read_queue;
+static struct timer_list readtimer;
+static __u8 rdsin, rdsout, rdsstat;
+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_getstereo(void)
+{
+ int ret = V4L2_TUNER_SUB_MONO;
+ if(curtuner != 0) /* Only FM has stereo capability! */
+ return V4L2_TUNER_SUB_MONO;
+
+ spin_lock(&cadet_io_lock);
+ outb(7,io); /* Select tuner control */
+ if( (inb(io+1) & 0x40) == 0)
+ ret = V4L2_TUNER_SUB_STEREO;
+ 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+msecs_to_jiffies(50);
+ 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+msecs_to_jiffies(50);
+ 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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ v->capabilities =
+ V4L2_CAP_TUNER |
+ V4L2_CAP_READWRITE;
+ v->version = CADET_VERSION;
+ strcpy(v->driver, "ADS Cadet");
+ strcpy(v->card, "ADS Cadet");
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ v->type = V4L2_TUNER_RADIO;
+ switch (v->index) {
+ case 0:
+ strcpy(v->name, "FM");
+ v->capability = V4L2_TUNER_CAP_STEREO;
+ v->rangelow = 1400; /* 87.5 MHz */
+ v->rangehigh = 1728; /* 108.0 MHz */
+ v->rxsubchans=cadet_getstereo();
+ switch (v->rxsubchans){
+ case V4L2_TUNER_SUB_MONO:
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ break;
+ case V4L2_TUNER_SUB_STEREO:
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ break;
+ default: ;
+ }
+ break;
+ case 1:
+ strcpy(v->name, "AM");
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->rangelow = 8320; /* 520 kHz */
+ v->rangehigh = 26400; /* 1650 kHz */
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ break;
+ default:
+ return -EINVAL;
+ }
+ v->signal = sigstrength; /* We might need to modify scaling of this */
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if((v->index != 0)&&(v->index != 1))
+ return -EINVAL;
+ curtuner = v->index;
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ f->tuner = curtuner;
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = cadet_getfreq();
+ return 0;
+}
+
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ if (f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+ if((curtuner==0)&&((f->frequency<1400)||(f->frequency>1728)))
+ return -EINVAL;
+ if((curtuner==1)&&((f->frequency<8320)||(f->frequency>26400)))
+ return -EINVAL;
+ cadet_setfreq(f->frequency);
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id){
+ case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
+ ctrl->value = (cadet_getvol() == 0);
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = cadet_getvol();
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id){
+ case V4L2_CID_AUDIO_MUTE: /* TODO: Handle this correctly */
+ if (ctrl->value)
+ cadet_setvol(0);
+ else
+ cadet_setvol(0xffff);
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ cadet_setvol(ctrl->value);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int
+cadet_open(struct inode *inode, struct file *file)
+{
+ users++;
+ if (1 == users) init_waitqueue_head(&read_queue);
+ return 0;
+}
+
+static int
+cadet_release(struct inode *inode, struct file *file)
+{
+ users--;
+ if (0 == users){
+ del_timer_sync(&readtimer);
+ rdsstat=0;
+ }
+ return 0;
+}
+
+static unsigned int
+cadet_poll(struct file *file, struct poll_table_struct *wait)
+{
+ poll_wait(file,&read_queue,wait);
+ if(rdsin != rdsout)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+
+static const struct file_operations cadet_fops = {
+ .owner = THIS_MODULE,
+ .open = cadet_open,
+ .release = cadet_release,
+ .read = cadet_read,
+ .ioctl = video_ioctl2,
+ .poll = cadet_poll,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops cadet_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+static struct video_device cadet_radio = {
+ .name = "Cadet radio",
+ .fops = &cadet_fops,
+ .ioctl_ops = &cadet_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+#ifdef CONFIG_PNP
+
+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,
+};
+
+#else
+static struct pnp_driver cadet_pnp_driver;
+#endif
+
+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")) {
+ 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) < 0) {
+ 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 0000000..e15bee6
--- /dev/null
+++ b/drivers/media/radio/radio-gemtek-pci.c
@@ -0,0 +1,502 @@
+/*
+ ***************************************************************************
+ *
+ * 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
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ *
+ ***************************************************************************
+ */
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/errno.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+#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
+
+struct gemtek_pci_card {
+ struct video_device *videodev;
+
+ u32 iobase;
+ u32 length;
+
+ u32 current_frequency;
+ u8 mute;
+};
+
+static int nr_radio = -1;
+static unsigned long in_use;
+
+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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-gemtek-pci", sizeof(v->driver));
+ strlcpy(v->card, "GemTek PCI Radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct gemtek_pci_card *card = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = GEMTEK_PCI_RANGE_LOW;
+ v->rangehigh = GEMTEK_PCI_RANGE_HIGH;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xffff * gemtek_pci_getsignal(card);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct gemtek_pci_card *card = video_drvdata(file);
+
+ if ( (f->frequency < GEMTEK_PCI_RANGE_LOW) ||
+ (f->frequency > GEMTEK_PCI_RANGE_HIGH) )
+ return -EINVAL;
+ gemtek_pci_setfrequency(card, f->frequency);
+ card->current_frequency = f->frequency;
+ card->mute = false;
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct gemtek_pci_card *card = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = card->current_frequency;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct gemtek_pci_card *card = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = card->mute;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (card->mute)
+ ctrl->value = 0;
+ else
+ ctrl->value = 65535;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct gemtek_pci_card *card = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ gemtek_pci_mute(card);
+ else
+ gemtek_pci_unmute(card);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (ctrl->value)
+ gemtek_pci_unmute(card);
+ else
+ gemtek_pci_mute(card);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+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 int gemtek_pci_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &in_use) ? -EBUSY : 0;
+}
+
+static int gemtek_pci_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &in_use);
+ return 0;
+}
+
+static const struct file_operations gemtek_pci_fops = {
+ .owner = THIS_MODULE,
+ .open = gemtek_pci_exclusive_open,
+ .release = gemtek_pci_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops gemtek_pci_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device vdev_template = {
+ .name = "Gemtek PCI Radio",
+ .fops = &gemtek_pci_fops,
+ .ioctl_ops = &gemtek_pci_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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 = kzalloc( sizeof( struct gemtek_pci_card ), GFP_KERNEL )) == NULL ) {
+ printk( KERN_ERR "gemtek_pci: out of memory\n" );
+ return -ENOMEM;
+ }
+
+ 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_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) < 0) {
+ kfree( devradio );
+ goto err_video;
+ }
+
+ card->videodev = devradio;
+ video_set_drvdata(devradio, card);
+ gemtek_pci_mute( card );
+
+ printk( KERN_INFO "Gemtek PCI Radio (rev. %d) found at 0x%04x-0x%04x.\n",
+ pci_dev->revision, 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_register_driver( &gemtek_pci_driver );
+}
+
+static void __exit gemtek_pci_cleanup_module( void )
+{
+ 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 0000000..d131a5d
--- /dev/null
+++ b/drivers/media/radio/radio-gemtek.c
@@ -0,0 +1,656 @@
+/* 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 :-)
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-common.h>
+#include <linux/spinlock.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,3)
+#define RADIO_BANNER "GemTek Radio card driver: v0.0.3"
+
+/*
+ * Module info.
+ */
+
+MODULE_AUTHOR("Jonas Munsin, Pekka Seppänen <pexu@kapsi.fi>");
+MODULE_DESCRIPTION("A driver for the GemTek Radio card.");
+MODULE_LICENSE("GPL");
+
+/*
+ * Module params.
+ */
+
+#ifndef CONFIG_RADIO_GEMTEK_PORT
+#define CONFIG_RADIO_GEMTEK_PORT -1
+#endif
+#ifndef CONFIG_RADIO_GEMTEK_PROBE
+#define CONFIG_RADIO_GEMTEK_PROBE 1
+#endif
+
+static int io = CONFIG_RADIO_GEMTEK_PORT;
+static int probe = CONFIG_RADIO_GEMTEK_PROBE;
+static int hardmute;
+static int shutdown = 1;
+static int keepmuted = 1;
+static int initmute = 1;
+static int radio_nr = -1;
+static unsigned long in_use;
+
+module_param(io, int, 0444);
+MODULE_PARM_DESC(io, "Force I/O port for the GemTek Radio card if automatic "
+ "probing is disabled or fails. The most common I/O ports are: 0x20c "
+ "0x30c, 0x24c or 0x34c (0x20c, 0x248 and 0x28c have been reported to "
+ "work for the combined sound/radiocard).");
+
+module_param(probe, bool, 0444);
+MODULE_PARM_DESC(probe, "Enable automatic device probing. Note: only the most "
+ "common I/O ports used by the card are probed.");
+
+module_param(hardmute, bool, 0644);
+MODULE_PARM_DESC(hardmute, "Enable `hard muting' by shutting down PLL, may "
+ "reduce static noise.");
+
+module_param(shutdown, bool, 0644);
+MODULE_PARM_DESC(shutdown, "Enable shutting down PLL and muting line when "
+ "module is unloaded.");
+
+module_param(keepmuted, bool, 0644);
+MODULE_PARM_DESC(keepmuted, "Keep card muted even when frequency is changed.");
+
+module_param(initmute, bool, 0444);
+MODULE_PARM_DESC(initmute, "Mute card when module is loaded.");
+
+module_param(radio_nr, int, 0444);
+
+/*
+ * Functions for controlling the card.
+ */
+#define GEMTEK_LOWFREQ (87*16000)
+#define GEMTEK_HIGHFREQ (108*16000)
+
+/*
+ * Frequency calculation constants. Intermediate frequency 10.52 MHz (nominal
+ * value 10.7 MHz), reference divisor 6.39 kHz (nominal 6.25 kHz).
+ */
+#define FSCALE 8
+#define IF_OFFSET ((unsigned int)(10.52 * 16000 * (1<<FSCALE)))
+#define REF_FREQ ((unsigned int)(6.39 * 16 * (1<<FSCALE)))
+
+#define GEMTEK_CK 0x01 /* Clock signal */
+#define GEMTEK_DA 0x02 /* Serial data */
+#define GEMTEK_CE 0x04 /* Chip enable */
+#define GEMTEK_NS 0x08 /* No signal */
+#define GEMTEK_MT 0x10 /* Line mute */
+#define GEMTEK_STDF_3_125_KHZ 0x01 /* Standard frequency 3.125 kHz */
+#define GEMTEK_PLL_OFF 0x07 /* PLL off */
+
+#define BU2614_BUS_SIZE 32 /* BU2614 / BU2614FS bus size */
+
+#define SHORT_DELAY 5 /* usec */
+#define LONG_DELAY 75 /* usec */
+
+struct gemtek_device {
+ unsigned long lastfreq;
+ int muted;
+ u32 bu2614data;
+};
+
+#define BU2614_FREQ_BITS 16 /* D0..D15, Frequency data */
+#define BU2614_PORT_BITS 3 /* P0..P2, Output port control data */
+#define BU2614_VOID_BITS 4 /* unused */
+#define BU2614_FMES_BITS 1 /* CT, Frequency measurement beginning data */
+#define BU2614_STDF_BITS 3 /* R0..R2, Standard frequency data */
+#define BU2614_SWIN_BITS 1 /* S, Switch between FMIN / AMIN */
+#define BU2614_SWAL_BITS 1 /* PS, Swallow counter division (AMIN only)*/
+#define BU2614_VOID2_BITS 1 /* unused */
+#define BU2614_FMUN_BITS 1 /* GT, Frequency measurement time & unlock */
+#define BU2614_TEST_BITS 1 /* TS, Test data is input */
+
+#define BU2614_FREQ_SHIFT 0
+#define BU2614_PORT_SHIFT (BU2614_FREQ_BITS + BU2614_FREQ_SHIFT)
+#define BU2614_VOID_SHIFT (BU2614_PORT_BITS + BU2614_PORT_SHIFT)
+#define BU2614_FMES_SHIFT (BU2614_VOID_BITS + BU2614_VOID_SHIFT)
+#define BU2614_STDF_SHIFT (BU2614_FMES_BITS + BU2614_FMES_SHIFT)
+#define BU2614_SWIN_SHIFT (BU2614_STDF_BITS + BU2614_STDF_SHIFT)
+#define BU2614_SWAL_SHIFT (BU2614_SWIN_BITS + BU2614_SWIN_SHIFT)
+#define BU2614_VOID2_SHIFT (BU2614_SWAL_BITS + BU2614_SWAL_SHIFT)
+#define BU2614_FMUN_SHIFT (BU2614_VOID2_BITS + BU2614_VOID2_SHIFT)
+#define BU2614_TEST_SHIFT (BU2614_FMUN_BITS + BU2614_FMUN_SHIFT)
+
+#define MKMASK(field) (((1<<BU2614_##field##_BITS) - 1) << \
+ BU2614_##field##_SHIFT)
+#define BU2614_PORT_MASK MKMASK(PORT)
+#define BU2614_FREQ_MASK MKMASK(FREQ)
+#define BU2614_VOID_MASK MKMASK(VOID)
+#define BU2614_FMES_MASK MKMASK(FMES)
+#define BU2614_STDF_MASK MKMASK(STDF)
+#define BU2614_SWIN_MASK MKMASK(SWIN)
+#define BU2614_SWAL_MASK MKMASK(SWAL)
+#define BU2614_VOID2_MASK MKMASK(VOID2)
+#define BU2614_FMUN_MASK MKMASK(FMUN)
+#define BU2614_TEST_MASK MKMASK(TEST)
+
+static struct gemtek_device gemtek_unit;
+
+static spinlock_t lock;
+
+/*
+ * Set data which will be sent to BU2614FS.
+ */
+#define gemtek_bu2614_set(dev, field, data) ((dev)->bu2614data = \
+ ((dev)->bu2614data & ~field##_MASK) | ((data) << field##_SHIFT))
+
+/*
+ * Transmit settings to BU2614FS over GemTek IC.
+ */
+static void gemtek_bu2614_transmit(struct gemtek_device *dev)
+{
+ int i, bit, q, mute;
+
+ spin_lock(&lock);
+
+ mute = dev->muted ? GEMTEK_MT : 0x00;
+
+ outb_p(mute | GEMTEK_DA | GEMTEK_CK, io);
+ udelay(SHORT_DELAY);
+ outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, io);
+ udelay(LONG_DELAY);
+
+ for (i = 0, q = dev->bu2614data; i < 32; i++, q >>= 1) {
+ bit = (q & 1) ? GEMTEK_DA : 0;
+ outb_p(mute | GEMTEK_CE | bit, io);
+ udelay(SHORT_DELAY);
+ outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, io);
+ udelay(SHORT_DELAY);
+ }
+
+ outb_p(mute | GEMTEK_DA | GEMTEK_CK, io);
+ udelay(SHORT_DELAY);
+ outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, io);
+ udelay(LONG_DELAY);
+
+ spin_unlock(&lock);
+}
+
+/*
+ * Calculate divisor from FM-frequency for BU2614FS (3.125 KHz STDF expected).
+ */
+static unsigned long gemtek_convfreq(unsigned long freq)
+{
+ return ((freq<<FSCALE) + IF_OFFSET + REF_FREQ/2) / REF_FREQ;
+}
+
+/*
+ * Set FM-frequency.
+ */
+static void gemtek_setfreq(struct gemtek_device *dev, unsigned long freq)
+{
+
+ if (keepmuted && hardmute && dev->muted)
+ return;
+
+ if (freq < GEMTEK_LOWFREQ)
+ freq = GEMTEK_LOWFREQ;
+ else if (freq > GEMTEK_HIGHFREQ)
+ freq = GEMTEK_HIGHFREQ;
+
+ dev->lastfreq = freq;
+ dev->muted = 0;
+
+ gemtek_bu2614_set(dev, BU2614_PORT, 0);
+ gemtek_bu2614_set(dev, BU2614_FMES, 0);
+ gemtek_bu2614_set(dev, BU2614_SWIN, 0); /* FM-mode */
+ gemtek_bu2614_set(dev, BU2614_SWAL, 0);
+ gemtek_bu2614_set(dev, BU2614_FMUN, 1); /* GT bit set */
+ gemtek_bu2614_set(dev, BU2614_TEST, 0);
+
+ gemtek_bu2614_set(dev, BU2614_STDF, GEMTEK_STDF_3_125_KHZ);
+ gemtek_bu2614_set(dev, BU2614_FREQ, gemtek_convfreq(freq));
+
+ gemtek_bu2614_transmit(dev);
+}
+
+/*
+ * Set mute flag.
+ */
+static void gemtek_mute(struct gemtek_device *dev)
+{
+ int i;
+ dev->muted = 1;
+
+ if (hardmute) {
+ /* Turn off PLL, disable data output */
+ gemtek_bu2614_set(dev, BU2614_PORT, 0);
+ gemtek_bu2614_set(dev, BU2614_FMES, 0); /* CT bit off */
+ gemtek_bu2614_set(dev, BU2614_SWIN, 0); /* FM-mode */
+ gemtek_bu2614_set(dev, BU2614_SWAL, 0);
+ gemtek_bu2614_set(dev, BU2614_FMUN, 0); /* GT bit off */
+ gemtek_bu2614_set(dev, BU2614_TEST, 0);
+ gemtek_bu2614_set(dev, BU2614_STDF, GEMTEK_PLL_OFF);
+ gemtek_bu2614_set(dev, BU2614_FREQ, 0);
+ gemtek_bu2614_transmit(dev);
+ } else {
+ spin_lock(&lock);
+
+ /* Read bus contents (CE, CK and DA). */
+ i = inb_p(io);
+ /* Write it back with mute flag set. */
+ outb_p((i >> 5) | GEMTEK_MT, io);
+ udelay(SHORT_DELAY);
+
+ spin_unlock(&lock);
+ }
+}
+
+/*
+ * Unset mute flag.
+ */
+static void gemtek_unmute(struct gemtek_device *dev)
+{
+ int i;
+ dev->muted = 0;
+
+ if (hardmute) {
+ /* Turn PLL back on. */
+ gemtek_setfreq(dev, dev->lastfreq);
+ } else {
+ spin_lock(&lock);
+
+ i = inb_p(io);
+ outb_p(i >> 5, io);
+ udelay(SHORT_DELAY);
+
+ spin_unlock(&lock);
+ }
+}
+
+/*
+ * Get signal strength (= stereo status).
+ */
+static inline int gemtek_getsigstr(void)
+{
+ return inb_p(io) & GEMTEK_NS ? 0 : 1;
+}
+
+/*
+ * Check if requested card acts like GemTek Radio card.
+ */
+static int gemtek_verify(int port)
+{
+ static int verified = -1;
+ int i, q;
+
+ if (verified == port)
+ return 1;
+
+ spin_lock(&lock);
+
+ q = inb_p(port); /* Read bus contents before probing. */
+ /* Try to turn on CE, CK and DA respectively and check if card responds
+ properly. */
+ for (i = 0; i < 3; ++i) {
+ outb_p(1 << i, port);
+ udelay(SHORT_DELAY);
+
+ if ((inb_p(port) & (~GEMTEK_NS)) != (0x17 | (1 << (i + 5)))) {
+ spin_unlock(&lock);
+ return 0;
+ }
+ }
+ outb_p(q >> 5, port); /* Write bus contents back. */
+ udelay(SHORT_DELAY);
+
+ spin_unlock(&lock);
+ verified = port;
+
+ return 1;
+}
+
+/*
+ * Automatic probing for card.
+ */
+static int gemtek_probe(void)
+{
+ int ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c };
+ int i;
+
+ if (!probe) {
+ printk(KERN_INFO "Automatic device probing disabled.\n");
+ return -1;
+ }
+
+ printk(KERN_INFO "Automatic device probing enabled.\n");
+
+ for (i = 0; i < ARRAY_SIZE(ioports); ++i) {
+ printk(KERN_INFO "Trying I/O port 0x%x...\n", ioports[i]);
+
+ if (!request_region(ioports[i], 1, "gemtek-probe")) {
+ printk(KERN_WARNING "I/O port 0x%x busy!\n",
+ ioports[i]);
+ continue;
+ }
+
+ if (gemtek_verify(ioports[i])) {
+ printk(KERN_INFO "Card found from I/O port "
+ "0x%x!\n", ioports[i]);
+
+ release_region(ioports[i], 1);
+
+ io = ioports[i];
+ return io;
+ }
+
+ release_region(ioports[i], 1);
+ }
+
+ printk(KERN_ERR "Automatic probing failed!\n");
+
+ return -1;
+}
+
+/*
+ * Video 4 Linux stuff.
+ */
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ }, {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+static int gemtek_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &in_use) ? -EBUSY : 0;
+}
+
+static int gemtek_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &in_use);
+ return 0;
+}
+
+static const struct file_operations gemtek_fops = {
+ .owner = THIS_MODULE,
+ .open = gemtek_exclusive_open,
+ .release = gemtek_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-gemtek", sizeof(v->driver));
+ strlcpy(v->card, "GemTek", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = GEMTEK_LOWFREQ;
+ v->rangehigh = GEMTEK_HIGHFREQ;
+ v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
+ v->signal = 0xffff * gemtek_getsigstr();
+ if (v->signal) {
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ v->rxsubchans = V4L2_TUNER_SUB_STEREO;
+ } else {
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ }
+
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct gemtek_device *rt = video_drvdata(file);
+
+ gemtek_setfreq(rt, f->frequency);
+
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct gemtek_device *rt = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = rt->lastfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); ++i) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct gemtek_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = rt->muted;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (rt->muted)
+ ctrl->value = 0;
+ else
+ ctrl->value = 65535;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct gemtek_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ gemtek_mute(rt);
+ else
+ gemtek_unmute(rt);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (ctrl->value)
+ gemtek_unmute(rt);
+ else
+ gemtek_mute(rt);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops gemtek_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl
+};
+
+static struct video_device gemtek_radio = {
+ .name = "GemTek Radio card",
+ .fops = &gemtek_fops,
+ .ioctl_ops = &gemtek_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+/*
+ * Initialization / cleanup related stuff.
+ */
+
+/*
+ * Initilize card.
+ */
+static int __init gemtek_init(void)
+{
+ printk(KERN_INFO RADIO_BANNER "\n");
+
+ spin_lock_init(&lock);
+
+ gemtek_probe();
+ if (io) {
+ if (!request_region(io, 1, "gemtek")) {
+ printk(KERN_ERR "I/O port 0x%x already in use.\n", io);
+ return -EBUSY;
+ }
+
+ if (!gemtek_verify(io))
+ printk(KERN_WARNING "Card at I/O port 0x%x does not "
+ "respond properly, check your "
+ "configuration.\n", io);
+ else
+ printk(KERN_INFO "Using I/O port 0x%x.\n", io);
+ } else if (probe) {
+ printk(KERN_ERR "Automatic probing failed and no "
+ "fixed I/O port defined.\n");
+ return -ENODEV;
+ } else {
+ printk(KERN_ERR "Automatic probing disabled but no fixed "
+ "I/O port defined.");
+ return -EINVAL;
+ }
+
+ video_set_drvdata(&gemtek_radio, &gemtek_unit);
+
+ if (video_register_device(&gemtek_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ release_region(io, 1);
+ return -EBUSY;
+ }
+
+ /* Set defaults */
+ gemtek_unit.lastfreq = GEMTEK_LOWFREQ;
+ gemtek_unit.bu2614data = 0;
+
+ if (initmute)
+ gemtek_mute(&gemtek_unit);
+
+ return 0;
+}
+
+/*
+ * Module cleanup
+ */
+static void __exit gemtek_exit(void)
+{
+ if (shutdown) {
+ hardmute = 1; /* Turn off PLL */
+ gemtek_mute(&gemtek_unit);
+ } else {
+ printk(KERN_INFO "Module unloaded but card not muted!\n");
+ }
+
+ video_unregister_device(&gemtek_radio);
+ release_region(io, 1);
+}
+
+module_init(gemtek_init);
+module_exit(gemtek_exit);
diff --git a/drivers/media/radio/radio-maestro.c b/drivers/media/radio/radio-maestro.c
new file mode 100644
index 0000000..4bf4d00
--- /dev/null
+++ b/drivers/media/radio/radio-maestro.c
@@ -0,0 +1,476 @@
+/* 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
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/pci.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,6)
+#define DRIVER_VERSION "0.06"
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ }
+};
+
+#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 unsigned long in_use;
+
+static int maestro_probe(struct pci_dev *pdev, const struct pci_device_id *ent);
+
+static int maestro_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &in_use) ? -EBUSY : 0;
+}
+
+static int maestro_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &in_use);
+ return 0;
+}
+
+static void maestro_remove(struct pci_dev *pdev);
+
+static struct pci_device_id maestro_r_pci_tbl[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ESS1968),
+ .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8,
+ .class_mask = 0xffff00 },
+ { PCI_DEVICE(PCI_VENDOR_ID_ESS, PCI_DEVICE_ID_ESS_ESS1978),
+ .class = PCI_CLASS_MULTIMEDIA_AUDIO << 8,
+ .class_mask = 0xffff00 },
+ { 0 }
+};
+MODULE_DEVICE_TABLE(pci, maestro_r_pci_tbl);
+
+static struct pci_driver maestro_r_driver = {
+ .name = "maestro_radio",
+ .id_table = maestro_r_pci_tbl,
+ .probe = maestro_probe,
+ .remove = __devexit_p(maestro_remove),
+};
+
+static const struct file_operations maestro_fops = {
+ .owner = THIS_MODULE,
+ .open = maestro_exclusive_open,
+ .release = maestro_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+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) */
+};
+
+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 : 1;
+ 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);
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-maestro", sizeof(v->driver));
+ strlcpy(v->card, "Maestro Radio", sizeof(v->card));
+ sprintf(v->bus_info, "PCI");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ (void)radio_bits_get(card);
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = FREQ_LO;
+ v->rangehigh = FREQ_HI;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ if(card->stereo)
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = card->tuned;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ if (f->frequency < FREQ_LO || f->frequency > FREQ_HI)
+ return -EINVAL;
+ radio_bits_set(card, FREQ2BITS(f->frequency));
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = BITS2FREQ(radio_bits_get(card));
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = card->muted;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct radio_device *card = video_drvdata(file);
+ register u16 io = card->io;
+ register u16 omask = inw(io + IO_MASK);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ outw(~STR_WREN, io + IO_MASK);
+ outw((card->muted = ctrl->value ) ?
+ STR_WREN : 0, io);
+ udelay(4);
+ outw(omask, io + IO_MASK);
+ msleep(125);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static u16 __devinit 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 : 1;
+ 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 const struct v4l2_ioctl_ops maestro_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device maestro_radio = {
+ .name = "Maestro radio",
+ .fops = &maestro_fops,
+ .ioctl_ops = &maestro_ioctl_ops,
+ .release = video_device_release,
+};
+
+static int __devinit maestro_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct radio_device *radio_unit;
+ struct video_device *maestro_radio_inst;
+ int retval;
+
+ retval = pci_enable_device(pdev);
+ if (retval) {
+ dev_err(&pdev->dev, "enabling pci device failed!\n");
+ goto err;
+ }
+
+ retval = -ENOMEM;
+
+ radio_unit = kzalloc(sizeof(*radio_unit), GFP_KERNEL);
+ if (radio_unit == NULL) {
+ dev_err(&pdev->dev, "not enough memory\n");
+ goto err;
+ }
+
+ radio_unit->io = pci_resource_start(pdev, 0) + GPIO_DATA;
+
+ maestro_radio_inst = video_device_alloc();
+ if (maestro_radio_inst == NULL) {
+ dev_err(&pdev->dev, "not enough memory\n");
+ goto errfr;
+ }
+
+ memcpy(maestro_radio_inst, &maestro_radio, sizeof(maestro_radio));
+ video_set_drvdata(maestro_radio_inst, radio_unit);
+ pci_set_drvdata(pdev, maestro_radio_inst);
+
+ retval = video_register_device(maestro_radio_inst, VFL_TYPE_RADIO, radio_nr);
+ if (retval) {
+ printk(KERN_ERR "can't register video device!\n");
+ goto errfr1;
+ }
+
+ if (!radio_power_on(radio_unit)) {
+ retval = -EIO;
+ goto errunr;
+ }
+
+ dev_info(&pdev->dev, "version " DRIVER_VERSION " time " __TIME__ " "
+ __DATE__ "\n");
+ dev_info(&pdev->dev, "radio chip initialized\n");
+
+ return 0;
+errunr:
+ video_unregister_device(maestro_radio_inst);
+errfr1:
+ video_device_release(maestro_radio_inst);
+errfr:
+ kfree(radio_unit);
+err:
+ return retval;
+
+}
+
+static void __devexit maestro_remove(struct pci_dev *pdev)
+{
+ struct video_device *vdev = pci_get_drvdata(pdev);
+
+ video_unregister_device(vdev);
+}
+
+static int __init maestro_radio_init(void)
+{
+ int retval = pci_register_driver(&maestro_r_driver);
+
+ if (retval)
+ printk(KERN_ERR "error during registration pci driver\n");
+
+ return retval;
+}
+
+static void __exit maestro_radio_exit(void)
+{
+ pci_unregister_driver(&maestro_r_driver);
+}
+
+module_init(maestro_radio_init);
+module_exit(maestro_radio_exit);
+
+MODULE_AUTHOR("Adam Tlalka, atlka@pg.gda.pl");
+MODULE_DESCRIPTION("Radio driver for the Maestro PCI sound card radio.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c
new file mode 100644
index 0000000..c777a17
--- /dev/null
+++ b/drivers/media/radio/radio-maxiradio.c
@@ -0,0 +1,482 @@
+/*
+ * 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 Sun Feb 4 22:51:27 EET 2001
+ * - tiding up
+ * - removed support for multiple devices as it didn't work anyway
+ *
+ * BUGS:
+ * - card unmutes if you change frequency
+ *
+ * (c) 2006, 2007 by Mauro Carvalho Chehab <mchehab@infradead.org>:
+ * - Conversion to V4L2 API
+ * - Uses video_ioctl2 for parsing and to add debug support
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/mutex.h>
+
+#include <linux/pci.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#define DRIVER_VERSION "0.77"
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,7,7)
+
+static struct video_device maxiradio_radio;
+
+#define dprintk(num, fmt, arg...) \
+ do { \
+ if (maxiradio_radio.debug >= num) \
+ printk(KERN_DEBUG "%s: " fmt, \
+ maxiradio_radio.name, ## arg); } while (0)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ }
+};
+
+#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);
+
+static unsigned long in_use;
+
+#define FREQ_LO 50*16000
+#define FREQ_HI 150*16000
+
+#define FREQ_IF 171200 /* 10.7*16000 */
+#define FREQ_STEP 200 /* 12.5*16 */
+
+/* (x==fmhz*16*1000) -> bits */
+#define FREQ2BITS(x) ((( (unsigned int)(x)+FREQ_IF+(FREQ_STEP<<1)) \
+ /(FREQ_STEP<<2))<<2)
+
+#define BITS2FREQ(x) ((x) * FREQ_STEP - FREQ_IF)
+
+
+static int maxiradio_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &in_use) ? -EBUSY : 0;
+}
+
+static int maxiradio_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &in_use);
+ return 0;
+}
+
+static const struct file_operations maxiradio_fops = {
+ .owner = THIS_MODULE,
+ .open = maxiradio_exclusive_open,
+ .release = maxiradio_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+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 mutex lock;
+} radio_unit = {
+ .muted =1,
+ .freq = FREQ_LO,
+};
+
+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) {
+ dprintk(1, "Radio powered on\n");
+ outb(power, io);
+ } else {
+ dprintk(1, "Radio powered off\n");
+ outb(0,io);
+ }
+}
+
+static void set_freq(__u16 io, __u32 freq)
+{
+ unsigned long int si;
+ int bl;
+ int val = FREQ2BITS(freq);
+
+ /* 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(val & si, io);
+ si >>= 1;
+ }
+
+ dprintk(1, "Radio freq set to %d.%02d MHz\n",
+ freq / 16000,
+ freq % 16000 * 100 / 16000);
+
+ turn_power(io, 1);
+}
+
+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);
+}
+
+
+static int vidioc_querycap (struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-maxiradio", sizeof (v->driver));
+ strlcpy(v->card, "Maxi Radio FM2000 radio", sizeof (v->card));
+ sprintf(v->bus_info,"ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+
+ return 0;
+}
+
+static int vidioc_g_tuner (struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ memset(v,0,sizeof(*v));
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+
+ v->rangelow=FREQ_LO;
+ v->rangehigh=FREQ_HI;
+ v->rxsubchans =V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+ v->capability=V4L2_TUNER_CAP_LOW;
+ if(get_stereo(card->io))
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal=0xffff*get_tune(card->io);
+
+ return 0;
+}
+
+static int vidioc_s_tuner (struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_g_audio (struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "FM");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+
+static int vidioc_s_audio (struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_s_frequency (struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ if (f->frequency < FREQ_LO || f->frequency > FREQ_HI) {
+ dprintk(1, "radio freq (%d.%02d MHz) out of range (%d-%d)\n",
+ f->frequency / 16000,
+ f->frequency % 16000 * 100 / 16000,
+ FREQ_LO / 16000, FREQ_HI / 16000);
+
+ return -EINVAL;
+ }
+
+ card->freq = f->frequency;
+ set_freq(card->io, card->freq);
+ msleep(125);
+
+ return 0;
+}
+
+static int vidioc_g_frequency (struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = card->freq;
+
+ dprintk(4, "radio freq is %d.%02d MHz",
+ f->frequency / 16000,
+ f->frequency % 16000 * 100 / 16000);
+
+ return 0;
+}
+
+static int vidioc_queryctrl (struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
+ return (0);
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl (struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value=card->muted;
+ return (0);
+ }
+
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl (struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct radio_device *card = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ card->muted = ctrl->value;
+ if(card->muted)
+ turn_power(card->io, 0);
+ else
+ set_freq(card->io, card->freq);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static const struct v4l2_ioctl_ops maxiradio_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device maxiradio_radio = {
+ .name = "Maxi Radio FM2000 radio",
+ .fops = &maxiradio_fops,
+ .ioctl_ops = &maxiradio_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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);
+ mutex_init(&radio_unit.lock);
+ video_set_drvdata(&maxiradio_radio, &radio_unit);
+
+ if (video_register_device(&maxiradio_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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_register_driver(&maxiradio_driver);
+}
+
+static void __exit maxiradio_radio_exit(void)
+{
+ pci_unregister_driver(&maxiradio_driver);
+}
+
+module_init(maxiradio_radio_init);
+module_exit(maxiradio_radio_exit);
+
+MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
+MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000 radio.");
+MODULE_LICENSE("GPL");
+
+module_param_named(debug,maxiradio_radio.debug, int, 0644);
+MODULE_PARM_DESC(debug,"activates debug info");
diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c
new file mode 100644
index 0000000..256cbef
--- /dev/null
+++ b/drivers/media/radio/radio-mr800.c
@@ -0,0 +1,633 @@
+/*
+ * A driver for the AverMedia MR 800 USB FM radio. This device plugs
+ * into both the USB and an analog audio input, so this thing
+ * only deals with initialization and frequency setting, the
+ * audio data has to be handled by a sound driver.
+ *
+ * Copyright (c) 2008 Alexey Klimov <klimov.linux@gmail.com>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * Big thanks to authors of dsbr100.c and radio-si470x.c
+ *
+ * When work was looked pretty good, i discover this:
+ * http://av-usbradio.sourceforge.net/index.php
+ * http://sourceforge.net/projects/av-usbradio/
+ * Latest release of theirs project was in 2005.
+ * Probably, this driver could be improved trough using their
+ * achievements (specifications given).
+ * So, we have smth to begin with.
+ *
+ * History:
+ * Version 0.01: First working version.
+ * It's required to blacklist AverMedia USB Radio
+ * in usbhid/hid-quirks.c
+ *
+ * Many things to do:
+ * - Correct power managment of device (suspend & resume)
+ * - Make x86 independance (little-endian and big-endian stuff)
+ * - Add code for scanning and smooth tuning
+ * - Checked and add stereo&mono stuff
+ * - Add code for sensitivity value
+ * - Correct mistakes
+ * - In Japan another FREQ_MIN and FREQ_MAX
+ */
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/usb.h>
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+
+/* driver and module definitions */
+#define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>"
+#define DRIVER_DESC "AverMedia MR 800 USB FM radio driver"
+#define DRIVER_VERSION "0.01"
+#define RADIO_VERSION KERNEL_VERSION(0, 0, 1)
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define USB_AMRADIO_VENDOR 0x07ca
+#define USB_AMRADIO_PRODUCT 0xb800
+
+/* Probably USB_TIMEOUT should be modified in module parameter */
+#define BUFFER_LENGTH 8
+#define USB_TIMEOUT 500
+
+/* Frequency limits in MHz -- these are European values. For Japanese
+devices, that would be 76 and 91. */
+#define FREQ_MIN 87.5
+#define FREQ_MAX 108.0
+#define FREQ_MUL 16000
+
+/* module parameter */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },
+/* HINT: the disabled controls are only here to satify kradio and such apps */
+ { .id = V4L2_CID_AUDIO_VOLUME,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BASS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_LOUDNESS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+};
+
+static int usb_amradio_probe(struct usb_interface *intf,
+ const struct usb_device_id *id);
+static void usb_amradio_disconnect(struct usb_interface *intf);
+static int usb_amradio_open(struct inode *inode, struct file *file);
+static int usb_amradio_close(struct inode *inode, struct file *file);
+static int usb_amradio_suspend(struct usb_interface *intf,
+ pm_message_t message);
+static int usb_amradio_resume(struct usb_interface *intf);
+
+/* Data for one (physical) device */
+struct amradio_device {
+ /* reference to USB and video device */
+ struct usb_device *usbdev;
+ struct video_device *videodev;
+
+ unsigned char *buffer;
+ struct mutex lock; /* buffer locking */
+ int curfreq;
+ int stereo;
+ int users;
+ int removed;
+ int muted;
+};
+
+/* USB Device ID List */
+static struct usb_device_id usb_amradio_device_table[] = {
+ {USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT,
+ USB_CLASS_HID, 0, 0) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, usb_amradio_device_table);
+
+/* USB subsystem interface */
+static struct usb_driver usb_amradio_driver = {
+ .name = "radio-mr800",
+ .probe = usb_amradio_probe,
+ .disconnect = usb_amradio_disconnect,
+ .suspend = usb_amradio_suspend,
+ .resume = usb_amradio_resume,
+ .reset_resume = usb_amradio_resume,
+ .id_table = usb_amradio_device_table,
+ .supports_autosuspend = 1,
+};
+
+/* switch on radio. Send 8 bytes to device. */
+static int amradio_start(struct amradio_device *radio)
+{
+ int retval;
+ int size;
+
+ mutex_lock(&radio->lock);
+
+ radio->buffer[0] = 0x00;
+ radio->buffer[1] = 0x55;
+ radio->buffer[2] = 0xaa;
+ radio->buffer[3] = 0x00;
+ radio->buffer[4] = 0xab;
+ radio->buffer[5] = 0x00;
+ radio->buffer[6] = 0x00;
+ radio->buffer[7] = 0x00;
+
+ retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
+ (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
+
+ if (retval) {
+ mutex_unlock(&radio->lock);
+ return retval;
+ }
+
+ mutex_unlock(&radio->lock);
+
+ radio->muted = 0;
+
+ return retval;
+}
+
+/* switch off radio */
+static int amradio_stop(struct amradio_device *radio)
+{
+ int retval;
+ int size;
+
+ mutex_lock(&radio->lock);
+
+ radio->buffer[0] = 0x00;
+ radio->buffer[1] = 0x55;
+ radio->buffer[2] = 0xaa;
+ radio->buffer[3] = 0x00;
+ radio->buffer[4] = 0xab;
+ radio->buffer[5] = 0x01;
+ radio->buffer[6] = 0x00;
+ radio->buffer[7] = 0x00;
+
+ retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
+ (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
+
+ if (retval) {
+ mutex_unlock(&radio->lock);
+ return retval;
+ }
+
+ mutex_unlock(&radio->lock);
+
+ radio->muted = 1;
+
+ return retval;
+}
+
+/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
+static int amradio_setfreq(struct amradio_device *radio, int freq)
+{
+ int retval;
+ int size;
+ unsigned short freq_send = 0x13 + (freq >> 3) / 25;
+
+ mutex_lock(&radio->lock);
+
+ radio->buffer[0] = 0x00;
+ radio->buffer[1] = 0x55;
+ radio->buffer[2] = 0xaa;
+ radio->buffer[3] = 0x03;
+ radio->buffer[4] = 0xa4;
+ radio->buffer[5] = 0x00;
+ radio->buffer[6] = 0x00;
+ radio->buffer[7] = 0x08;
+
+ retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
+ (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
+
+ if (retval) {
+ mutex_unlock(&radio->lock);
+ return retval;
+ }
+
+ /* frequency is calculated from freq_send and placed in first 2 bytes */
+ radio->buffer[0] = (freq_send >> 8) & 0xff;
+ radio->buffer[1] = freq_send & 0xff;
+ radio->buffer[2] = 0x01;
+ radio->buffer[3] = 0x00;
+ radio->buffer[4] = 0x00;
+ /* 5 and 6 bytes of buffer already = 0x00 */
+ radio->buffer[7] = 0x00;
+
+ retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
+ (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
+
+ if (retval) {
+ mutex_unlock(&radio->lock);
+ return retval;
+ }
+
+ mutex_unlock(&radio->lock);
+
+ radio->stereo = 0;
+
+ return retval;
+}
+
+/* USB subsystem interface begins here */
+
+/* handle unplugging of the device, release data structures
+if nothing keeps us from doing it. If something is still
+keeping us busy, the release callback of v4l will take care
+of releasing it. */
+static void usb_amradio_disconnect(struct usb_interface *intf)
+{
+ struct amradio_device *radio = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+
+ if (radio) {
+ video_unregister_device(radio->videodev);
+ radio->videodev = NULL;
+ if (radio->users) {
+ kfree(radio->buffer);
+ kfree(radio);
+ } else {
+ radio->removed = 1;
+ }
+ }
+}
+
+/* vidioc_querycap - query device capabilities */
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-mr800", sizeof(v->driver));
+ strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card));
+ sprintf(v->bus_info, "USB");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+/* vidioc_g_tuner - get tuner attributes */
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ if (v->index > 0)
+ return -EINVAL;
+
+/* TODO: Add function which look is signal stereo or not
+ * amradio_getstat(radio);
+ */
+ radio->stereo = -1;
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = FREQ_MIN * FREQ_MUL;
+ v->rangehigh = FREQ_MAX * FREQ_MUL;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ if (radio->stereo)
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xffff; /* Can't get the signal strength, sad.. */
+ v->afc = 0; /* Don't know what is this */
+ return 0;
+}
+
+/* vidioc_s_tuner - set tuner attributes */
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+/* vidioc_s_frequency - set tuner radio frequency */
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ radio->curfreq = f->frequency;
+ if (amradio_setfreq(radio, radio->curfreq) < 0)
+ warn("Set frequency failed");
+ return 0;
+}
+
+/* vidioc_g_frequency - get tuner radio frequency */
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = radio->curfreq;
+ return 0;
+}
+
+/* vidioc_queryctrl - enumerate control items */
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+/* vidioc_g_ctrl - get the value of a control */
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = radio->muted;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/* vidioc_s_ctrl - set the value of a control */
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value) {
+ if (amradio_stop(radio) < 0) {
+ warn("amradio_stop() failed");
+ return -1;
+ }
+ } else {
+ if (amradio_start(radio) < 0) {
+ warn("amradio_start() failed");
+ return -1;
+ }
+ }
+ return 0;
+ }
+ return -EINVAL;
+}
+
+/* vidioc_g_audio - get audio attributes */
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+/* vidioc_s_audio - set audio attributes */
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/* vidioc_g_input - get input */
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+/* vidioc_s_input - set input */
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/* open device - amradio_start() and amradio_setfreq() */
+static int usb_amradio_open(struct inode *inode, struct file *file)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ lock_kernel();
+
+ radio->users = 1;
+ radio->muted = 1;
+
+ if (amradio_start(radio) < 0) {
+ warn("Radio did not start up properly");
+ radio->users = 0;
+ unlock_kernel();
+ return -EIO;
+ }
+ if (amradio_setfreq(radio, radio->curfreq) < 0)
+ warn("Set frequency failed");
+
+ unlock_kernel();
+ return 0;
+}
+
+/*close device - free driver structures */
+static int usb_amradio_close(struct inode *inode, struct file *file)
+{
+ struct amradio_device *radio = video_get_drvdata(video_devdata(file));
+
+ if (!radio)
+ return -ENODEV;
+ radio->users = 0;
+ if (radio->removed) {
+ kfree(radio->buffer);
+ kfree(radio);
+ }
+ return 0;
+}
+
+/* Suspend device - stop device. Need to be checked and fixed */
+static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct amradio_device *radio = usb_get_intfdata(intf);
+
+ if (amradio_stop(radio) < 0)
+ warn("amradio_stop() failed");
+
+ info("radio-mr800: Going into suspend..");
+
+ return 0;
+}
+
+/* Resume device - start device. Need to be checked and fixed */
+static int usb_amradio_resume(struct usb_interface *intf)
+{
+ struct amradio_device *radio = usb_get_intfdata(intf);
+
+ if (amradio_start(radio) < 0)
+ warn("amradio_start() failed");
+
+ info("radio-mr800: Coming out of suspend..");
+
+ return 0;
+}
+
+/* File system interface */
+static const struct file_operations usb_amradio_fops = {
+ .owner = THIS_MODULE,
+ .open = usb_amradio_open,
+ .release = usb_amradio_close,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops usb_amradio_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+/* V4L2 interface */
+static struct video_device amradio_videodev_template = {
+ .name = "AverMedia MR 800 USB FM Radio",
+ .fops = &usb_amradio_fops,
+ .ioctl_ops = &usb_amradio_ioctl_ops,
+ .release = video_device_release,
+};
+
+/* check if the device is present and register with v4l and
+usb if it is */
+static int usb_amradio_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct amradio_device *radio;
+
+ radio = kmalloc(sizeof(struct amradio_device), GFP_KERNEL);
+
+ if (!(radio))
+ return -ENOMEM;
+
+ radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
+
+ if (!(radio->buffer)) {
+ kfree(radio);
+ return -ENOMEM;
+ }
+
+ radio->videodev = video_device_alloc();
+
+ if (!(radio->videodev)) {
+ kfree(radio->buffer);
+ kfree(radio);
+ return -ENOMEM;
+ }
+
+ memcpy(radio->videodev, &amradio_videodev_template,
+ sizeof(amradio_videodev_template));
+
+ radio->removed = 0;
+ radio->users = 0;
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->curfreq = 95.16 * FREQ_MUL;
+
+ mutex_init(&radio->lock);
+
+ video_set_drvdata(radio->videodev, radio);
+ if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) {
+ warn("Could not register video device");
+ video_device_release(radio->videodev);
+ kfree(radio->buffer);
+ kfree(radio);
+ return -EIO;
+ }
+
+ usb_set_intfdata(intf, radio);
+ return 0;
+}
+
+static int __init amradio_init(void)
+{
+ int retval = usb_register(&usb_amradio_driver);
+
+ info(DRIVER_VERSION " " DRIVER_DESC);
+ if (retval)
+ err("usb_register failed. Error number %d", retval);
+ return retval;
+}
+
+static void __exit amradio_exit(void)
+{
+ usb_deregister(&usb_amradio_driver);
+}
+
+module_init(amradio_init);
+module_exit(amradio_exit);
+
diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c
new file mode 100644
index 0000000..a670797
--- /dev/null
+++ b/drivers/media/radio/radio-rtrack2.c
@@ -0,0 +1,378 @@
+/* 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 :-)
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/spinlock.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 65535,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+#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
+{
+ unsigned long in_use;
+ 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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-rtrack2", sizeof(v->driver));
+ strlcpy(v->card, "RadioTrack II", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ 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 vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (88*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xFFFF*rt_getsigstr(rt);
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ rt->curfreq = f->frequency;
+ rt_setfreq(rt, rt->curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = rt->curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = rt->muted;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (rt->muted)
+ ctrl->value = 0;
+ else
+ ctrl->value = 65535;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct rt_device *rt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ rt_mute(rt);
+ else
+ rt_unmute(rt);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (ctrl->value)
+ rt_unmute(rt);
+ else
+ rt_mute(rt);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct rt_device rtrack2_unit;
+
+static int rtrack2_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &rtrack2_unit.in_use) ? -EBUSY : 0;
+}
+
+static int rtrack2_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &rtrack2_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations rtrack2_fops = {
+ .owner = THIS_MODULE,
+ .open = rtrack2_exclusive_open,
+ .release = rtrack2_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops rtrack2_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+static struct video_device rtrack2_radio = {
+ .name = "RadioTrack II radio",
+ .fops = &rtrack2_fops,
+ .ioctl_ops = &rtrack2_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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;
+ }
+
+ video_set_drvdata(&rtrack2_radio, &rtrack2_unit);
+
+ spin_lock_init(&lock);
+ if (video_register_device(&rtrack2_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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 0000000..329c90b
--- /dev/null
+++ b/drivers/media/radio/radio-sf16fmi.c
@@ -0,0 +1,416 @@
+/* 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
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/version.h>
+#include <linux/kernel.h> /* __setup */
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/isapnp.h>
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/mutex.h>
+
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ }
+};
+
+struct fmi_device
+{
+ unsigned long in_use;
+ 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 mutex 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)
+{
+ mutex_lock(&lock);
+ outb(0x00, port);
+ mutex_unlock(&lock);
+}
+
+static inline void fmi_unmute(int port)
+{
+ mutex_lock(&lock);
+ outb(0x08, port);
+ mutex_unlock(&lock);
+}
+
+static inline int fmi_setfreq(struct fmi_device *dev)
+{
+ int myport = dev->port;
+ unsigned long freq = dev->curfreq;
+
+ mutex_lock(&lock);
+
+ outbits(16, RSF16_ENCODE(freq), myport);
+ outbits(8, 0xC0, myport);
+ msleep(143); /* was schedule_timeout(HZ/7) */
+ mutex_unlock(&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;
+
+
+ mutex_lock(&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);
+
+ mutex_unlock(&lock);
+ return (res & 2) ? 0 : 0xFFFF;
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-sf16fmi", sizeof(v->driver));
+ strlcpy(v->card, "SF16-FMx radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ int mult;
+ struct fmi_device *fmi = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ mult = (fmi->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000;
+ v->rangelow = RSF16_MINFREQ/mult;
+ v->rangehigh = RSF16_MAXFREQ/mult;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO;
+ v->capability = fmi->flags&V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ v->signal = fmi_getsigstr(fmi);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct fmi_device *fmi = video_drvdata(file);
+
+ if (!(fmi->flags & V4L2_TUNER_CAP_LOW))
+ f->frequency *= 1000;
+ if (f->frequency < RSF16_MINFREQ ||
+ f->frequency > RSF16_MAXFREQ )
+ return -EINVAL;
+ /*rounding in steps of 800 to match th freq
+ that will be used */
+ fmi->curfreq = (f->frequency/800)*800;
+ fmi_setfreq(fmi);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct fmi_device *fmi = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = fmi->curfreq;
+ if (!(fmi->flags & V4L2_TUNER_CAP_LOW))
+ f->frequency /= 1000;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct fmi_device *fmi = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = fmi->curvol;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct fmi_device *fmi = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ fmi_mute(fmi->port);
+ else
+ fmi_unmute(fmi->port);
+ fmi->curvol = ctrl->value;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct fmi_device fmi_unit;
+
+static int fmi_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &fmi_unit.in_use) ? -EBUSY : 0;
+}
+
+static int fmi_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &fmi_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations fmi_fops = {
+ .owner = THIS_MODULE,
+ .open = fmi_exclusive_open,
+ .release = fmi_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops fmi_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device fmi_radio = {
+ .name = "SF16FMx radio",
+ .fops = &fmi_fops,
+ .ioctl_ops = &fmi_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+/* 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 __init 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);
+ pnp_device_detach(dev);
+ return -EBUSY;
+ }
+
+ fmi_unit.port = io;
+ fmi_unit.curvol = 0;
+ fmi_unit.curfreq = 0;
+ fmi_unit.flags = V4L2_TUNER_CAP_LOW;
+ video_set_drvdata(&fmi_radio, &fmi_unit);
+
+ mutex_init(&lock);
+
+ if (video_register_device(&fmi_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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 0000000..b1f47c3
--- /dev/null
+++ b/drivers/media/radio/radio-sf16fmr2.c
@@ -0,0 +1,508 @@
+/* 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)
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/mutex.h>
+
+static struct mutex lock;
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+#define AUD_VOL_INDEX 1
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },
+ [AUD_VOL_INDEX] = {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 15,
+ .step = 1,
+ .default_value = 0,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+#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
+{
+ unsigned long in_use;
+ int port;
+ int curvol; /* 0-15 */
+ 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 useful 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 vol[16] = { 0x021, 0x084, 0x090, 0x104,
+ 0x110, 0x204, 0x210, 0x402,
+ 0x404, 0x408, 0x410, 0x801,
+ 0x802, 0x804, 0x808, 0x810 };
+ int i, a, port = dev->port;
+ int n = vol[dev->curvol & 0x0f];
+
+ if (dev->card_type != 11)
+ return 1;
+
+ 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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-sf16fmr2", sizeof(v->driver));
+ strlcpy(v->card, "SF16-FMR2 radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ int mult;
+ struct fmr2_device *fmr2 = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+
+ mult = (fmr2->flags & V4L2_TUNER_CAP_LOW) ? 1 : 1000;
+ v->rangelow = RSF16_MINFREQ/mult;
+ v->rangehigh = RSF16_MAXFREQ/mult;
+ v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_MODE_STEREO;
+ v->capability = fmr2->flags&V4L2_TUNER_CAP_LOW;
+ v->audmode = fmr2->stereo ? V4L2_TUNER_MODE_STEREO:
+ V4L2_TUNER_MODE_MONO;
+ mutex_lock(&lock);
+ v->signal = fmr2_getsigstr(fmr2);
+ mutex_unlock(&lock);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct fmr2_device *fmr2 = video_drvdata(file);
+
+ if (!(fmr2->flags & V4L2_TUNER_CAP_LOW))
+ f->frequency *= 1000;
+ if (f->frequency < RSF16_MINFREQ ||
+ f->frequency > RSF16_MAXFREQ )
+ return -EINVAL;
+ /*rounding in steps of 200 to match th freq
+ that will be used */
+ fmr2->curfreq = (f->frequency/200)*200;
+
+ /* set card freq (if not muted) */
+ if (fmr2->curvol && !fmr2->mute) {
+ mutex_lock(&lock);
+ fmr2_setfreq(fmr2);
+ mutex_unlock(&lock);
+ }
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct fmr2_device *fmr2 = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = fmr2->curfreq;
+ if (!(fmr2->flags & V4L2_TUNER_CAP_LOW))
+ f->frequency /= 1000;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &radio_qctrl[i], sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct fmr2_device *fmr2 = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = fmr2->mute;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = fmr2->curvol;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct fmr2_device *fmr2 = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ fmr2->mute = ctrl->value;
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ if (ctrl->value > radio_qctrl[AUD_VOL_INDEX].maximum)
+ fmr2->curvol = radio_qctrl[AUD_VOL_INDEX].maximum;
+ else
+ fmr2->curvol = ctrl->value;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+#ifdef DEBUG
+ if (fmr2->curvol && !fmr2->mute)
+ printk(KERN_DEBUG "unmute\n");
+ else
+ printk(KERN_DEBUG "mute\n");
+#endif
+
+ mutex_lock(&lock);
+ if (fmr2->curvol && !fmr2->mute) {
+ fmr2_setvolume(fmr2);
+ /* Set frequency and unmute card */
+ fmr2_setfreq(fmr2);
+ } else
+ fmr2_mute(fmr2->port);
+ mutex_unlock(&lock);
+ return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct fmr2_device fmr2_unit;
+
+static int fmr2_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &fmr2_unit.in_use) ? -EBUSY : 0;
+}
+
+static int fmr2_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &fmr2_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations fmr2_fops = {
+ .owner = THIS_MODULE,
+ .open = fmr2_exclusive_open,
+ .release = fmr2_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops fmr2_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device fmr2_radio = {
+ .name = "SF16FMR2 radio",
+ .fops = &fmr2_fops,
+ .ioctl_ops = &fmr2_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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 = V4L2_TUNER_CAP_LOW;
+ fmr2_unit.card_type = 0;
+ video_set_drvdata(&fmr2_radio, &fmr2_unit);
+
+ mutex_init(&lock);
+
+ if (!request_region(io, 2, "sf16fmr2")) {
+ printk(KERN_ERR "radio-sf16fmr2: request_region failed!\n");
+ return -EBUSY;
+ }
+
+ if (video_register_device(&fmr2_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+
+ printk(KERN_INFO "SF16FMR2 radio card driver at 0x%x.\n", io);
+ /* mute card - prevents noisy bootups */
+ mutex_lock(&lock);
+ fmr2_mute(io);
+ fmr2_product_info(&fmr2_unit);
+ mutex_unlock(&lock);
+ debug_print((KERN_DEBUG "card_type %d\n", fmr2_unit.card_type));
+
+ /* Only card_type == 11 implements volume */
+ if (fmr2_unit.card_type != 11)
+ radio_qctrl[AUD_VOL_INDEX].maximum = 1;
+
+ 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-si470x.c b/drivers/media/radio/radio-si470x.c
new file mode 100644
index 0000000..3e18302
--- /dev/null
+++ b/drivers/media/radio/radio-si470x.c
@@ -0,0 +1,1770 @@
+/*
+ * drivers/media/radio/radio-si470x.c
+ *
+ * Driver for USB radios for the Silicon Labs Si470x FM Radio Receivers:
+ * - Silicon Labs USB FM Radio Reference Design
+ * - ADS/Tech FM Radio Receiver (formerly Instant FM Music) (RDX-155-EF)
+ * - KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
+ *
+ * Copyright (c) 2008 Tobias Lorenz <tobias.lorenz@gmx.net>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * History:
+ * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.0
+ * - First working version
+ * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.1
+ * - Improved error handling, every function now returns errno
+ * - Improved multi user access (start/mute/stop)
+ * - Channel doesn't get lost anymore after start/mute/stop
+ * - RDS support added (polling mode via interrupt EP 1)
+ * - marked default module parameters with *value*
+ * - switched from bit structs to bit masks
+ * - header file cleaned and integrated
+ * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.2
+ * - hex values are now lower case
+ * - commented USB ID for ADS/Tech moved on todo list
+ * - blacklisted si470x in hid-quirks.c
+ * - rds buffer handling functions integrated into *_work, *_read
+ * - rds_command in si470x_poll exchanged against simple retval
+ * - check for firmware version 15
+ * - code order and prototypes still remain the same
+ * - spacing and bottom of band codes remain the same
+ * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.3
+ * - code reordered to avoid function prototypes
+ * - switch/case defaults are now more user-friendly
+ * - unified comment style
+ * - applied all checkpatch.pl v1.12 suggestions
+ * except the warning about the too long lines with bit comments
+ * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
+ * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.4
+ * - avoid poss. locking when doing copy_to_user which may sleep
+ * - RDS is automatically activated on read now
+ * - code cleaned of unnecessary rds_commands
+ * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
+ * (thanks to Guillaume RAMOUSSE)
+ * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.5
+ * - number of seek_retries changed to tune_timeout
+ * - fixed problem with incomplete tune operations by own buffers
+ * - optimization of variables and printf types
+ * - improved error logging
+ * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Oliver Neukum <oliver@neukum.org>
+ * Version 1.0.6
+ * - fixed coverity checker warnings in *_usb_driver_disconnect
+ * - probe()/open() race by correct ordering in probe()
+ * - DMA coherency rules by separate allocation of all buffers
+ * - use of endianness macros
+ * - abuse of spinlock, replaced by mutex
+ * - racy handling of timer in disconnect,
+ * replaced by delayed_work
+ * - racy interruptible_sleep_on(),
+ * replaced with wait_event_interruptible()
+ * - handle signals in read()
+ * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Oliver Neukum <oliver@neukum.org>
+ * Version 1.0.7
+ * - usb autosuspend support
+ * - unplugging fixed
+ * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.8
+ * - hardware frequency seek support
+ * - afc indication
+ * - more safety checks, let si470x_get_freq return errno
+ * - vidioc behavior corrected according to v4l2 spec
+ * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com>
+ * - add support for KWorld USB FM Radio FM700
+ * - blacklisted KWorld radio in hid-core.c and hid-ids.h
+ *
+ * ToDo:
+ * - add firmware download/update support
+ * - RDS support: interrupt mode, instead of polling
+ * - add LED status output (check if that's not already done in firmware)
+ */
+
+
+/* driver definitions */
+#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
+#define DRIVER_NAME "radio-si470x"
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 8)
+#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
+#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
+#define DRIVER_VERSION "1.0.8"
+
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/rds.h>
+#include <asm/unaligned.h>
+
+
+/* USB Device ID List */
+static struct usb_device_id si470x_usb_driver_id_table[] = {
+ /* Silicon Labs USB FM Radio Reference Design */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
+ /* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
+ /* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
+ /* Terminating entry */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
+
+
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0444);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+/* Spacing (kHz) */
+/* 0: 200 kHz (USA, Australia) */
+/* 1: 100 kHz (Europe, Japan) */
+/* 2: 50 kHz */
+static unsigned short space = 2;
+module_param(space, ushort, 0444);
+MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");
+
+/* Bottom of Band (MHz) */
+/* 0: 87.5 - 108 MHz (USA, Europe)*/
+/* 1: 76 - 108 MHz (Japan wide band) */
+/* 2: 76 - 90 MHz (Japan) */
+static unsigned short band = 1;
+module_param(band, ushort, 0444);
+MODULE_PARM_DESC(band, "Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz");
+
+/* De-emphasis */
+/* 0: 75 us (USA) */
+/* 1: 50 us (Europe, Australia, Japan) */
+static unsigned short de = 1;
+module_param(de, ushort, 0444);
+MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");
+
+/* USB timeout */
+static unsigned int usb_timeout = 500;
+module_param(usb_timeout, uint, 0644);
+MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
+
+/* Tune timeout */
+static unsigned int tune_timeout = 3000;
+module_param(tune_timeout, uint, 0644);
+MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
+
+/* Seek timeout */
+static unsigned int seek_timeout = 5000;
+module_param(seek_timeout, uint, 0644);
+MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
+
+/* RDS buffer blocks */
+static unsigned int rds_buf = 100;
+module_param(rds_buf, uint, 0444);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+
+/* RDS maximum block errors */
+static unsigned short max_rds_errors = 1;
+/* 0 means 0 errors requiring correction */
+/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
+/* 2 means 3-5 errors requiring correction */
+/* 3 means 6+ errors or errors in checkword, correction not possible */
+module_param(max_rds_errors, ushort, 0644);
+MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
+
+/* RDS poll frequency */
+static unsigned int rds_poll_time = 40;
+/* 40 is used by the original USBRadio.exe */
+/* 50 is used by radio-cadet */
+/* 75 should be okay */
+/* 80 is the usual RDS receive interval */
+module_param(rds_poll_time, uint, 0644);
+MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*");
+
+
+
+/**************************************************************************
+ * Register Definitions
+ **************************************************************************/
+#define RADIO_REGISTER_SIZE 2 /* 16 register bit width */
+#define RADIO_REGISTER_NUM 16 /* DEVICEID ... RDSD */
+#define RDS_REGISTER_NUM 6 /* STATUSRSSI ... RDSD */
+
+#define DEVICEID 0 /* Device ID */
+#define DEVICEID_PN 0xf000 /* bits 15..12: Part Number */
+#define DEVICEID_MFGID 0x0fff /* bits 11..00: Manufacturer ID */
+
+#define CHIPID 1 /* Chip ID */
+#define CHIPID_REV 0xfc00 /* bits 15..10: Chip Version */
+#define CHIPID_DEV 0x0200 /* bits 09..09: Device */
+#define CHIPID_FIRMWARE 0x01ff /* bits 08..00: Firmware Version */
+
+#define POWERCFG 2 /* Power Configuration */
+#define POWERCFG_DSMUTE 0x8000 /* bits 15..15: Softmute Disable */
+#define POWERCFG_DMUTE 0x4000 /* bits 14..14: Mute Disable */
+#define POWERCFG_MONO 0x2000 /* bits 13..13: Mono Select */
+#define POWERCFG_RDSM 0x0800 /* bits 11..11: RDS Mode (Si4701 only) */
+#define POWERCFG_SKMODE 0x0400 /* bits 10..10: Seek Mode */
+#define POWERCFG_SEEKUP 0x0200 /* bits 09..09: Seek Direction */
+#define POWERCFG_SEEK 0x0100 /* bits 08..08: Seek */
+#define POWERCFG_DISABLE 0x0040 /* bits 06..06: Powerup Disable */
+#define POWERCFG_ENABLE 0x0001 /* bits 00..00: Powerup Enable */
+
+#define CHANNEL 3 /* Channel */
+#define CHANNEL_TUNE 0x8000 /* bits 15..15: Tune */
+#define CHANNEL_CHAN 0x03ff /* bits 09..00: Channel Select */
+
+#define SYSCONFIG1 4 /* System Configuration 1 */
+#define SYSCONFIG1_RDSIEN 0x8000 /* bits 15..15: RDS Interrupt Enable (Si4701 only) */
+#define SYSCONFIG1_STCIEN 0x4000 /* bits 14..14: Seek/Tune Complete Interrupt Enable */
+#define SYSCONFIG1_RDS 0x1000 /* bits 12..12: RDS Enable (Si4701 only) */
+#define SYSCONFIG1_DE 0x0800 /* bits 11..11: De-emphasis (0=75us 1=50us) */
+#define SYSCONFIG1_AGCD 0x0400 /* bits 10..10: AGC Disable */
+#define SYSCONFIG1_BLNDADJ 0x00c0 /* bits 07..06: Stereo/Mono Blend Level Adjustment */
+#define SYSCONFIG1_GPIO3 0x0030 /* bits 05..04: General Purpose I/O 3 */
+#define SYSCONFIG1_GPIO2 0x000c /* bits 03..02: General Purpose I/O 2 */
+#define SYSCONFIG1_GPIO1 0x0003 /* bits 01..00: General Purpose I/O 1 */
+
+#define SYSCONFIG2 5 /* System Configuration 2 */
+#define SYSCONFIG2_SEEKTH 0xff00 /* bits 15..08: RSSI Seek Threshold */
+#define SYSCONFIG2_BAND 0x0080 /* bits 07..06: Band Select */
+#define SYSCONFIG2_SPACE 0x0030 /* bits 05..04: Channel Spacing */
+#define SYSCONFIG2_VOLUME 0x000f /* bits 03..00: Volume */
+
+#define SYSCONFIG3 6 /* System Configuration 3 */
+#define SYSCONFIG3_SMUTER 0xc000 /* bits 15..14: Softmute Attack/Recover Rate */
+#define SYSCONFIG3_SMUTEA 0x3000 /* bits 13..12: Softmute Attenuation */
+#define SYSCONFIG3_SKSNR 0x00f0 /* bits 07..04: Seek SNR Threshold */
+#define SYSCONFIG3_SKCNT 0x000f /* bits 03..00: Seek FM Impulse Detection Threshold */
+
+#define TEST1 7 /* Test 1 */
+#define TEST1_AHIZEN 0x4000 /* bits 14..14: Audio High-Z Enable */
+
+#define TEST2 8 /* Test 2 */
+/* TEST2 only contains reserved bits */
+
+#define BOOTCONFIG 9 /* Boot Configuration */
+/* BOOTCONFIG only contains reserved bits */
+
+#define STATUSRSSI 10 /* Status RSSI */
+#define STATUSRSSI_RDSR 0x8000 /* bits 15..15: RDS Ready (Si4701 only) */
+#define STATUSRSSI_STC 0x4000 /* bits 14..14: Seek/Tune Complete */
+#define STATUSRSSI_SF 0x2000 /* bits 13..13: Seek Fail/Band Limit */
+#define STATUSRSSI_AFCRL 0x1000 /* bits 12..12: AFC Rail */
+#define STATUSRSSI_RDSS 0x0800 /* bits 11..11: RDS Synchronized (Si4701 only) */
+#define STATUSRSSI_BLERA 0x0600 /* bits 10..09: RDS Block A Errors (Si4701 only) */
+#define STATUSRSSI_ST 0x0100 /* bits 08..08: Stereo Indicator */
+#define STATUSRSSI_RSSI 0x00ff /* bits 07..00: RSSI (Received Signal Strength Indicator) */
+
+#define READCHAN 11 /* Read Channel */
+#define READCHAN_BLERB 0xc000 /* bits 15..14: RDS Block D Errors (Si4701 only) */
+#define READCHAN_BLERC 0x3000 /* bits 13..12: RDS Block C Errors (Si4701 only) */
+#define READCHAN_BLERD 0x0c00 /* bits 11..10: RDS Block B Errors (Si4701 only) */
+#define READCHAN_READCHAN 0x03ff /* bits 09..00: Read Channel */
+
+#define RDSA 12 /* RDSA */
+#define RDSA_RDSA 0xffff /* bits 15..00: RDS Block A Data (Si4701 only) */
+
+#define RDSB 13 /* RDSB */
+#define RDSB_RDSB 0xffff /* bits 15..00: RDS Block B Data (Si4701 only) */
+
+#define RDSC 14 /* RDSC */
+#define RDSC_RDSC 0xffff /* bits 15..00: RDS Block C Data (Si4701 only) */
+
+#define RDSD 15 /* RDSD */
+#define RDSD_RDSD 0xffff /* bits 15..00: RDS Block D Data (Si4701 only) */
+
+
+
+/**************************************************************************
+ * USB HID Reports
+ **************************************************************************/
+
+/* Reports 1-16 give direct read/write access to the 16 Si470x registers */
+/* with the (REPORT_ID - 1) corresponding to the register address across USB */
+/* endpoint 0 using GET_REPORT and SET_REPORT */
+#define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1)
+#define REGISTER_REPORT(reg) ((reg) + 1)
+
+/* Report 17 gives direct read/write access to the entire Si470x register */
+/* map across endpoint 0 using GET_REPORT and SET_REPORT */
+#define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
+#define ENTIRE_REPORT 17
+
+/* Report 18 is used to send the lowest 6 Si470x registers up the HID */
+/* interrupt endpoint 1 to Windows every 20 milliseconds for status */
+#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
+#define RDS_REPORT 18
+
+/* Report 19: LED state */
+#define LED_REPORT_SIZE 3
+#define LED_REPORT 19
+
+/* Report 19: stream */
+#define STREAM_REPORT_SIZE 3
+#define STREAM_REPORT 19
+
+/* Report 20: scratch */
+#define SCRATCH_PAGE_SIZE 63
+#define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1)
+#define SCRATCH_REPORT 20
+
+/* Reports 19-22: flash upgrade of the C8051F321 */
+#define WRITE_REPORT 19
+#define FLASH_REPORT 20
+#define CRC_REPORT 21
+#define RESPONSE_REPORT 22
+
+/* Report 23: currently unused, but can accept 60 byte reports on the HID */
+/* interrupt out endpoint 2 every 1 millisecond */
+#define UNUSED_REPORT 23
+
+
+
+/**************************************************************************
+ * Software/Hardware Versions
+ **************************************************************************/
+#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
+#define RADIO_SW_VERSION 7
+#define RADIO_SW_VERSION_CURRENT 15
+#define RADIO_HW_VERSION 1
+
+#define SCRATCH_PAGE_SW_VERSION 1
+#define SCRATCH_PAGE_HW_VERSION 2
+
+
+
+/**************************************************************************
+ * LED State Definitions
+ **************************************************************************/
+#define LED_COMMAND 0x35
+
+#define NO_CHANGE_LED 0x00
+#define ALL_COLOR_LED 0x01 /* streaming state */
+#define BLINK_GREEN_LED 0x02 /* connect state */
+#define BLINK_RED_LED 0x04
+#define BLINK_ORANGE_LED 0x10 /* disconnect state */
+#define SOLID_GREEN_LED 0x20 /* tuning/seeking state */
+#define SOLID_RED_LED 0x40 /* bootload state */
+#define SOLID_ORANGE_LED 0x80
+
+
+
+/**************************************************************************
+ * Stream State Definitions
+ **************************************************************************/
+#define STREAM_COMMAND 0x36
+#define STREAM_VIDPID 0x00
+#define STREAM_AUDIO 0xff
+
+
+
+/**************************************************************************
+ * Bootloader / Flash Commands
+ **************************************************************************/
+
+/* unique id sent to bootloader and required to put into a bootload state */
+#define UNIQUE_BL_ID 0x34
+
+/* mask for the flash data */
+#define FLASH_DATA_MASK 0x55
+
+/* bootloader commands */
+#define GET_SW_VERSION_COMMAND 0x00
+#define SET_PAGE_COMMAND 0x01
+#define ERASE_PAGE_COMMAND 0x02
+#define WRITE_PAGE_COMMAND 0x03
+#define CRC_ON_PAGE_COMMAND 0x04
+#define READ_FLASH_BYTE_COMMAND 0x05
+#define RESET_DEVICE_COMMAND 0x06
+#define GET_HW_VERSION_COMMAND 0x07
+#define BLANK 0xff
+
+/* bootloader command responses */
+#define COMMAND_OK 0x01
+#define COMMAND_FAILED 0x02
+#define COMMAND_PENDING 0x03
+
+/* buffer sizes */
+#define COMMAND_BUFFER_SIZE 4
+#define RESPONSE_BUFFER_SIZE 2
+#define FLASH_BUFFER_SIZE 64
+#define CRC_BUFFER_SIZE 3
+
+
+
+/**************************************************************************
+ * General Driver Definitions
+ **************************************************************************/
+
+/*
+ * si470x_device - private data
+ */
+struct si470x_device {
+ /* reference to USB and video device */
+ struct usb_device *usbdev;
+ struct usb_interface *intf;
+ struct video_device *videodev;
+
+ /* driver management */
+ unsigned int users;
+ unsigned char disconnected;
+ struct mutex disconnect_lock;
+
+ /* Silabs internal registers (0..15) */
+ unsigned short registers[RADIO_REGISTER_NUM];
+
+ /* RDS receive buffer */
+ struct delayed_work work;
+ wait_queue_head_t read_queue;
+ struct mutex lock; /* buffer locking */
+ unsigned char *buffer; /* size is always multiple of three */
+ unsigned int buf_size;
+ unsigned int rd_index;
+ unsigned int wr_index;
+};
+
+
+/*
+ * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW,
+ * 62.5 kHz otherwise.
+ * The tuner is able to have a channel spacing of 50, 100 or 200 kHz.
+ * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW
+ * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000
+ */
+#define FREQ_MUL (1000000 / 62.5)
+
+
+
+/**************************************************************************
+ * General Driver Functions
+ **************************************************************************/
+
+/*
+ * si470x_get_report - receive a HID report
+ */
+static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
+{
+ unsigned char *report = (unsigned char *) buf;
+ int retval;
+
+ retval = usb_control_msg(radio->usbdev,
+ usb_rcvctrlpipe(radio->usbdev, 0),
+ HID_REQ_GET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ report[0], 2,
+ buf, size, usb_timeout);
+
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": si470x_get_report: usb_control_msg returned %d\n",
+ retval);
+ return retval;
+}
+
+
+/*
+ * si470x_set_report - send a HID report
+ */
+static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
+{
+ unsigned char *report = (unsigned char *) buf;
+ int retval;
+
+ retval = usb_control_msg(radio->usbdev,
+ usb_sndctrlpipe(radio->usbdev, 0),
+ HID_REQ_SET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ report[0], 2,
+ buf, size, usb_timeout);
+
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": si470x_set_report: usb_control_msg returned %d\n",
+ retval);
+ return retval;
+}
+
+
+/*
+ * si470x_get_register - read register
+ */
+static int si470x_get_register(struct si470x_device *radio, int regnr)
+{
+ unsigned char buf[REGISTER_REPORT_SIZE];
+ int retval;
+
+ buf[0] = REGISTER_REPORT(regnr);
+
+ retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+
+ if (retval >= 0)
+ radio->registers[regnr] = get_unaligned_be16(&buf[1]);
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+/*
+ * si470x_set_register - write register
+ */
+static int si470x_set_register(struct si470x_device *radio, int regnr)
+{
+ unsigned char buf[REGISTER_REPORT_SIZE];
+ int retval;
+
+ buf[0] = REGISTER_REPORT(regnr);
+ put_unaligned_be16(radio->registers[regnr], &buf[1]);
+
+ retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+/*
+ * si470x_get_all_registers - read entire registers
+ */
+static int si470x_get_all_registers(struct si470x_device *radio)
+{
+ unsigned char buf[ENTIRE_REPORT_SIZE];
+ int retval;
+ unsigned char regnr;
+
+ buf[0] = ENTIRE_REPORT;
+
+ retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+
+ if (retval >= 0)
+ for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
+ radio->registers[regnr] = get_unaligned_be16(
+ &buf[regnr * RADIO_REGISTER_SIZE + 1]);
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+/*
+ * si470x_get_rds_registers - read rds registers
+ */
+static int si470x_get_rds_registers(struct si470x_device *radio)
+{
+ unsigned char buf[RDS_REPORT_SIZE];
+ int retval;
+ int size;
+ unsigned char regnr;
+
+ buf[0] = RDS_REPORT;
+
+ retval = usb_interrupt_msg(radio->usbdev,
+ usb_rcvintpipe(radio->usbdev, 1),
+ (void *) &buf, sizeof(buf), &size, usb_timeout);
+ if (size != sizeof(buf))
+ printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
+ "return size differs: %d != %zu\n", size, sizeof(buf));
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
+ "usb_interrupt_msg returned %d\n", retval);
+
+ if (retval >= 0)
+ for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
+ radio->registers[STATUSRSSI + regnr] =
+ get_unaligned_be16(
+ &buf[regnr * RADIO_REGISTER_SIZE + 1]);
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+/*
+ * si470x_set_chan - set the channel
+ */
+static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
+{
+ int retval;
+ unsigned long timeout;
+ bool timed_out = 0;
+
+ /* start tuning */
+ radio->registers[CHANNEL] &= ~CHANNEL_CHAN;
+ radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
+ retval = si470x_set_register(radio, CHANNEL);
+ if (retval < 0)
+ goto done;
+
+ /* wait till tune operation has completed */
+ timeout = jiffies + msecs_to_jiffies(tune_timeout);
+ do {
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
+ (!timed_out));
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+ printk(KERN_WARNING DRIVER_NAME ": tune does not complete\n");
+ if (timed_out)
+ printk(KERN_WARNING DRIVER_NAME
+ ": tune timed out after %u ms\n", tune_timeout);
+
+stop:
+ /* stop tuning */
+ radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
+ retval = si470x_set_register(radio, CHANNEL);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_get_freq - get the frequency
+ */
+static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
+{
+ unsigned int spacing, band_bottom;
+ unsigned short chan;
+ int retval;
+
+ /* Spacing (kHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
+ /* 0: 200 kHz (USA, Australia) */
+ case 0:
+ spacing = 0.200 * FREQ_MUL; break;
+ /* 1: 100 kHz (Europe, Japan) */
+ case 1:
+ spacing = 0.100 * FREQ_MUL; break;
+ /* 2: 50 kHz */
+ default:
+ spacing = 0.050 * FREQ_MUL; break;
+ };
+
+ /* Bottom of Band (MHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe) */
+ case 0:
+ band_bottom = 87.5 * FREQ_MUL; break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ default:
+ band_bottom = 76 * FREQ_MUL; break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2:
+ band_bottom = 76 * FREQ_MUL; break;
+ };
+
+ /* read channel */
+ retval = si470x_get_register(radio, READCHAN);
+ chan = radio->registers[READCHAN] & READCHAN_READCHAN;
+
+ /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
+ *freq = chan * spacing + band_bottom;
+
+ return retval;
+}
+
+
+/*
+ * si470x_set_freq - set the frequency
+ */
+static int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
+{
+ unsigned int spacing, band_bottom;
+ unsigned short chan;
+
+ /* Spacing (kHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
+ /* 0: 200 kHz (USA, Australia) */
+ case 0:
+ spacing = 0.200 * FREQ_MUL; break;
+ /* 1: 100 kHz (Europe, Japan) */
+ case 1:
+ spacing = 0.100 * FREQ_MUL; break;
+ /* 2: 50 kHz */
+ default:
+ spacing = 0.050 * FREQ_MUL; break;
+ };
+
+ /* Bottom of Band (MHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe) */
+ case 0:
+ band_bottom = 87.5 * FREQ_MUL; break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ default:
+ band_bottom = 76 * FREQ_MUL; break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2:
+ band_bottom = 76 * FREQ_MUL; break;
+ };
+
+ /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
+ chan = (freq - band_bottom) / spacing;
+
+ return si470x_set_chan(radio, chan);
+}
+
+
+/*
+ * si470x_set_seek - set seek
+ */
+static int si470x_set_seek(struct si470x_device *radio,
+ unsigned int wrap_around, unsigned int seek_upward)
+{
+ int retval = 0;
+ unsigned long timeout;
+ bool timed_out = 0;
+
+ /* start seeking */
+ radio->registers[POWERCFG] |= POWERCFG_SEEK;
+ if (wrap_around == 1)
+ radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
+ else
+ radio->registers[POWERCFG] |= POWERCFG_SKMODE;
+ if (seek_upward == 1)
+ radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
+ else
+ radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
+ retval = si470x_set_register(radio, POWERCFG);
+ if (retval < 0)
+ goto done;
+
+ /* wait till seek operation has completed */
+ timeout = jiffies + msecs_to_jiffies(seek_timeout);
+ do {
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
+ (!timed_out));
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+ printk(KERN_WARNING DRIVER_NAME ": seek does not complete\n");
+ if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
+ printk(KERN_WARNING DRIVER_NAME
+ ": seek failed / band limit reached\n");
+ if (timed_out)
+ printk(KERN_WARNING DRIVER_NAME
+ ": seek timed out after %u ms\n", seek_timeout);
+
+stop:
+ /* stop seeking */
+ radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ /* try again, if timed out */
+ if ((retval == 0) && timed_out)
+ retval = -EAGAIN;
+
+ return retval;
+}
+
+
+/*
+ * si470x_start - switch on radio
+ */
+static int si470x_start(struct si470x_device *radio)
+{
+ int retval;
+
+ /* powercfg */
+ radio->registers[POWERCFG] =
+ POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
+ retval = si470x_set_register(radio, POWERCFG);
+ if (retval < 0)
+ goto done;
+
+ /* sysconfig 1 */
+ radio->registers[SYSCONFIG1] = SYSCONFIG1_DE;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ goto done;
+
+ /* sysconfig 2 */
+ radio->registers[SYSCONFIG2] =
+ (0x3f << 8) | /* SEEKTH */
+ ((band << 6) & SYSCONFIG2_BAND) | /* BAND */
+ ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */
+ 15; /* VOLUME (max) */
+ retval = si470x_set_register(radio, SYSCONFIG2);
+ if (retval < 0)
+ goto done;
+
+ /* reset last channel */
+ retval = si470x_set_chan(radio,
+ radio->registers[CHANNEL] & CHANNEL_CHAN);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_stop - switch off radio
+ */
+static int si470x_stop(struct si470x_device *radio)
+{
+ int retval;
+
+ /* sysconfig 1 */
+ radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ goto done;
+
+ /* powercfg */
+ radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
+ /* POWERCFG_ENABLE has to automatically go low */
+ radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_rds_on - switch on rds reception
+ */
+static int si470x_rds_on(struct si470x_device *radio)
+{
+ int retval;
+
+ /* sysconfig 1 */
+ mutex_lock(&radio->lock);
+ radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
+ mutex_unlock(&radio->lock);
+
+ return retval;
+}
+
+
+
+/**************************************************************************
+ * RDS Driver Functions
+ **************************************************************************/
+
+/*
+ * si470x_rds - rds processing function
+ */
+static void si470x_rds(struct si470x_device *radio)
+{
+ unsigned char blocknum;
+ unsigned short bler; /* rds block errors */
+ unsigned short rds;
+ unsigned char tmpbuf[3];
+
+ /* get rds blocks */
+ if (si470x_get_rds_registers(radio) < 0)
+ return;
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
+ /* No RDS group ready */
+ return;
+ }
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
+ /* RDS decoder not synchronized */
+ return;
+ }
+
+ /* copy all four RDS blocks to internal buffer */
+ mutex_lock(&radio->lock);
+ for (blocknum = 0; blocknum < 4; blocknum++) {
+ switch (blocknum) {
+ default:
+ bler = (radio->registers[STATUSRSSI] &
+ STATUSRSSI_BLERA) >> 9;
+ rds = radio->registers[RDSA];
+ break;
+ case 1:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERB) >> 14;
+ rds = radio->registers[RDSB];
+ break;
+ case 2:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERC) >> 12;
+ rds = radio->registers[RDSC];
+ break;
+ case 3:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERD) >> 10;
+ rds = radio->registers[RDSD];
+ break;
+ };
+
+ /* Fill the V4L2 RDS buffer */
+ put_unaligned_le16(rds, &tmpbuf);
+ tmpbuf[2] = blocknum; /* offset name */
+ tmpbuf[2] |= blocknum << 3; /* received offset */
+ if (bler > max_rds_errors)
+ tmpbuf[2] |= 0x80; /* uncorrectable errors */
+ else if (bler > 0)
+ tmpbuf[2] |= 0x40; /* corrected error(s) */
+
+ /* copy RDS block to internal buffer */
+ memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
+ radio->wr_index += 3;
+
+ /* wrap write pointer */
+ if (radio->wr_index >= radio->buf_size)
+ radio->wr_index = 0;
+
+ /* check for overflow */
+ if (radio->wr_index == radio->rd_index) {
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+ }
+ }
+ mutex_unlock(&radio->lock);
+
+ /* wake up read queue */
+ if (radio->wr_index != radio->rd_index)
+ wake_up_interruptible(&radio->read_queue);
+}
+
+
+/*
+ * si470x_work - rds work function
+ */
+static void si470x_work(struct work_struct *work)
+{
+ struct si470x_device *radio = container_of(work, struct si470x_device,
+ work.work);
+
+ /* safety checks */
+ if (radio->disconnected)
+ return;
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
+ return;
+
+ si470x_rds(radio);
+ schedule_delayed_work(&radio->work, msecs_to_jiffies(rds_poll_time));
+}
+
+
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+/*
+ * si470x_fops_read - read RDS data
+ */
+static ssize_t si470x_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+ unsigned int block_count = 0;
+
+ /* switch on rds reception */
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
+ si470x_rds_on(radio);
+ schedule_delayed_work(&radio->work,
+ msecs_to_jiffies(rds_poll_time));
+ }
+
+ /* block if no new data available */
+ while (radio->wr_index == radio->rd_index) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EWOULDBLOCK;
+ goto done;
+ }
+ if (wait_event_interruptible(radio->read_queue,
+ radio->wr_index != radio->rd_index) < 0) {
+ retval = -EINTR;
+ goto done;
+ }
+ }
+
+ /* calculate block count from byte count */
+ count /= 3;
+
+ /* copy RDS block out of internal buffer and to user buffer */
+ mutex_lock(&radio->lock);
+ while (block_count < count) {
+ if (radio->rd_index == radio->wr_index)
+ break;
+
+ /* always transfer rds complete blocks */
+ if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
+ /* retval = -EFAULT; */
+ break;
+
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+
+ /* increment counters */
+ block_count++;
+ buf += 3;
+ retval += 3;
+ }
+ mutex_unlock(&radio->lock);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_fops_poll - poll RDS data
+ */
+static unsigned int si470x_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* switch on rds reception */
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
+ si470x_rds_on(radio);
+ schedule_delayed_work(&radio->work,
+ msecs_to_jiffies(rds_poll_time));
+ }
+
+ poll_wait(file, &radio->read_queue, pts);
+
+ if (radio->rd_index != radio->wr_index)
+ retval = POLLIN | POLLRDNORM;
+
+ return retval;
+}
+
+
+/*
+ * si470x_fops_open - file open
+ */
+static int si470x_fops_open(struct inode *inode, struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval;
+
+ lock_kernel();
+ radio->users++;
+
+ retval = usb_autopm_get_interface(radio->intf);
+ if (retval < 0) {
+ radio->users--;
+ retval = -EIO;
+ goto done;
+ }
+
+ if (radio->users == 1) {
+ retval = si470x_start(radio);
+ if (retval < 0)
+ usb_autopm_put_interface(radio->intf);
+ }
+
+done:
+ unlock_kernel();
+ return retval;
+}
+
+
+/*
+ * si470x_fops_release - file release
+ */
+static int si470x_fops_release(struct inode *inode, struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety check */
+ if (!radio) {
+ retval = -ENODEV;
+ goto done;
+ }
+
+ mutex_lock(&radio->disconnect_lock);
+ radio->users--;
+ if (radio->users == 0) {
+ if (radio->disconnected) {
+ video_unregister_device(radio->videodev);
+ kfree(radio->buffer);
+ kfree(radio);
+ goto unlock;
+ }
+
+ /* stop rds reception */
+ cancel_delayed_work_sync(&radio->work);
+
+ /* cancel read processes */
+ wake_up_interruptible(&radio->read_queue);
+
+ retval = si470x_stop(radio);
+ usb_autopm_put_interface(radio->intf);
+ }
+
+unlock:
+ mutex_unlock(&radio->disconnect_lock);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_fops - file operations interface
+ */
+static const struct file_operations si470x_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = si470x_fops_read,
+ .poll = si470x_fops_poll,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .open = si470x_fops_open,
+ .release = si470x_fops_release,
+};
+
+
+
+/**************************************************************************
+ * Video4Linux Interface
+ **************************************************************************/
+
+/*
+ * si470x_v4l2_queryctrl - query control
+ */
+static struct v4l2_queryctrl si470x_v4l2_queryctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 15,
+ .step = 1,
+ .default_value = 15,
+ },
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ },
+};
+
+
+/*
+ * si470x_vidioc_querycap - query device capabilities
+ */
+static int si470x_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ sprintf(capability->bus_info, "USB");
+ capability->version = DRIVER_KERNEL_VERSION;
+ capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
+ V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+
+ return 0;
+}
+
+
+/*
+ * si470x_vidioc_queryctrl - enumerate control items
+ */
+static int si470x_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ unsigned char i = 0;
+ int retval = -EINVAL;
+
+ /* abort if qc->id is below V4L2_CID_BASE */
+ if (qc->id < V4L2_CID_BASE)
+ goto done;
+
+ /* search video control */
+ for (i = 0; i < ARRAY_SIZE(si470x_v4l2_queryctrl); i++) {
+ if (qc->id == si470x_v4l2_queryctrl[i].id) {
+ memcpy(qc, &(si470x_v4l2_queryctrl[i]), sizeof(*qc));
+ retval = 0; /* found */
+ break;
+ }
+ }
+
+ /* disable unsupported base controls */
+ /* to satisfy kradio and such apps */
+ if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {
+ qc->flags = V4L2_CTRL_FLAG_DISABLED;
+ retval = 0;
+ }
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": query controls failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_ctrl - get the value of a control
+ */
+static int si470x_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = radio->registers[SYSCONFIG2] &
+ SYSCONFIG2_VOLUME;
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = ((radio->registers[POWERCFG] &
+ POWERCFG_DMUTE) == 0) ? 1 : 0;
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": get control failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_ctrl - set the value of a control
+ */
+static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
+ radio->registers[SYSCONFIG2] |= ctrl->value;
+ retval = si470x_set_register(radio, SYSCONFIG2);
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value == 1)
+ radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
+ else
+ radio->registers[POWERCFG] |= POWERCFG_DMUTE;
+ retval = si470x_set_register(radio, POWERCFG);
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set control failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_audio - get audio attributes
+ */
+static int si470x_vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *audio)
+{
+ /* driver constants */
+ audio->index = 0;
+ strcpy(audio->name, "Radio");
+ audio->capability = V4L2_AUDCAP_STEREO;
+ audio->mode = 0;
+
+ return 0;
+}
+
+
+/*
+ * si470x_vidioc_g_tuner - get tuner attributes
+ */
+static int si470x_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+ if (tuner->index != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto done;
+
+ /* driver constants */
+ strcpy(tuner->name, "FM");
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
+
+ /* range limits */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe, default) */
+ default:
+ tuner->rangelow = 87.5 * FREQ_MUL;
+ tuner->rangehigh = 108 * FREQ_MUL;
+ break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ case 1 :
+ tuner->rangelow = 76 * FREQ_MUL;
+ tuner->rangehigh = 108 * FREQ_MUL;
+ break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2 :
+ tuner->rangelow = 76 * FREQ_MUL;
+ tuner->rangehigh = 90 * FREQ_MUL;
+ break;
+ };
+
+ /* stereo indicator == stereo (instead of mono) */
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 1)
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+ else
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+
+ /* mono/stereo selector */
+ if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 1)
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ else
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+
+ /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
+ tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI)
+ * 0x0101;
+
+ /* automatic frequency control: -1: freq to low, 1 freq to high */
+ /* AFCRL does only indicate that freq. differs, not if too low/high */
+ tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": get tuner failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_tuner - set tuner attributes
+ */
+static int si470x_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = -EINVAL;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+ if (tuner->index != 0)
+ goto done;
+
+ /* mono/stereo selector */
+ switch (tuner->audmode) {
+ case V4L2_TUNER_MODE_MONO:
+ radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
+ break;
+ default:
+ goto done;
+ }
+
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set tuner failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_frequency - get tuner or modulator radio frequency
+ */
+static int si470x_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ freq->type = V4L2_TUNER_RADIO;
+ retval = si470x_get_freq(radio, &freq->frequency);
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": get frequency failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_frequency - set tuner or modulator radio frequency
+ */
+static int si470x_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_set_freq(radio, freq->frequency);
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set frequency failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
+ */
+static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ struct v4l2_hw_freq_seek *seek)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ if (radio->disconnected) {
+ retval = -EIO;
+ goto done;
+ }
+ if (seek->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward);
+
+done:
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set hardware frequency seek failed with %d\n",
+ retval);
+ return retval;
+}
+
+
+/*
+ * si470x_ioctl_ops - video device ioctl operations
+ */
+static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
+ .vidioc_querycap = si470x_vidioc_querycap,
+ .vidioc_queryctrl = si470x_vidioc_queryctrl,
+ .vidioc_g_ctrl = si470x_vidioc_g_ctrl,
+ .vidioc_s_ctrl = si470x_vidioc_s_ctrl,
+ .vidioc_g_audio = si470x_vidioc_g_audio,
+ .vidioc_g_tuner = si470x_vidioc_g_tuner,
+ .vidioc_s_tuner = si470x_vidioc_s_tuner,
+ .vidioc_g_frequency = si470x_vidioc_g_frequency,
+ .vidioc_s_frequency = si470x_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
+};
+
+
+/*
+ * si470x_viddev_template - video device interface
+ */
+static struct video_device si470x_viddev_template = {
+ .fops = &si470x_fops,
+ .name = DRIVER_NAME,
+ .release = video_device_release,
+ .ioctl_ops = &si470x_ioctl_ops,
+};
+
+
+
+/**************************************************************************
+ * USB Interface
+ **************************************************************************/
+
+/*
+ * si470x_usb_driver_probe - probe for the device
+ */
+static int si470x_usb_driver_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct si470x_device *radio;
+ int retval = 0;
+
+ /* private data allocation and initialization */
+ radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
+ if (!radio) {
+ retval = -ENOMEM;
+ goto err_initial;
+ }
+ radio->users = 0;
+ radio->disconnected = 0;
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->intf = intf;
+ mutex_init(&radio->disconnect_lock);
+ mutex_init(&radio->lock);
+
+ /* video device allocation and initialization */
+ radio->videodev = video_device_alloc();
+ if (!radio->videodev) {
+ retval = -ENOMEM;
+ goto err_radio;
+ }
+ memcpy(radio->videodev, &si470x_viddev_template,
+ sizeof(si470x_viddev_template));
+ video_set_drvdata(radio->videodev, radio);
+
+ /* show some infos about the specific device */
+ if (si470x_get_all_registers(radio) < 0) {
+ retval = -EIO;
+ goto err_all;
+ }
+ printk(KERN_INFO DRIVER_NAME ": DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
+ radio->registers[DEVICEID], radio->registers[CHIPID]);
+
+ /* check if firmware is current */
+ if ((radio->registers[CHIPID] & CHIPID_FIRMWARE)
+ < RADIO_SW_VERSION_CURRENT) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": This driver is known to work with "
+ "firmware version %hu,\n", RADIO_SW_VERSION_CURRENT);
+ printk(KERN_WARNING DRIVER_NAME
+ ": but the device has firmware version %hu.\n",
+ radio->registers[CHIPID] & CHIPID_FIRMWARE);
+ printk(KERN_WARNING DRIVER_NAME
+ ": If you have some trouble using this driver,\n");
+ printk(KERN_WARNING DRIVER_NAME
+ ": please report to V4L ML at "
+ "video4linux-list@redhat.com\n");
+ }
+
+ /* set initial frequency */
+ si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
+
+ /* rds buffer allocation */
+ radio->buf_size = rds_buf * 3;
+ radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
+ if (!radio->buffer) {
+ retval = -EIO;
+ goto err_all;
+ }
+
+ /* rds buffer configuration */
+ radio->wr_index = 0;
+ radio->rd_index = 0;
+ init_waitqueue_head(&radio->read_queue);
+
+ /* prepare rds work function */
+ INIT_DELAYED_WORK(&radio->work, si470x_work);
+
+ /* register video device */
+ retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
+ if (retval) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": Could not register video device\n");
+ goto err_all;
+ }
+ usb_set_intfdata(intf, radio);
+
+ return 0;
+err_all:
+ video_device_release(radio->videodev);
+ kfree(radio->buffer);
+err_radio:
+ kfree(radio);
+err_initial:
+ return retval;
+}
+
+
+/*
+ * si470x_usb_driver_suspend - suspend the device
+ */
+static int si470x_usb_driver_suspend(struct usb_interface *intf,
+ pm_message_t message)
+{
+ struct si470x_device *radio = usb_get_intfdata(intf);
+
+ printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
+
+ cancel_delayed_work_sync(&radio->work);
+
+ return 0;
+}
+
+
+/*
+ * si470x_usb_driver_resume - resume the device
+ */
+static int si470x_usb_driver_resume(struct usb_interface *intf)
+{
+ struct si470x_device *radio = usb_get_intfdata(intf);
+
+ printk(KERN_INFO DRIVER_NAME ": resuming now...\n");
+
+ mutex_lock(&radio->lock);
+ if (radio->users && radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)
+ schedule_delayed_work(&radio->work,
+ msecs_to_jiffies(rds_poll_time));
+ mutex_unlock(&radio->lock);
+
+ return 0;
+}
+
+
+/*
+ * si470x_usb_driver_disconnect - disconnect the device
+ */
+static void si470x_usb_driver_disconnect(struct usb_interface *intf)
+{
+ struct si470x_device *radio = usb_get_intfdata(intf);
+
+ mutex_lock(&radio->disconnect_lock);
+ radio->disconnected = 1;
+ cancel_delayed_work_sync(&radio->work);
+ usb_set_intfdata(intf, NULL);
+ if (radio->users == 0) {
+ video_unregister_device(radio->videodev);
+ kfree(radio->buffer);
+ kfree(radio);
+ }
+ mutex_unlock(&radio->disconnect_lock);
+}
+
+
+/*
+ * si470x_usb_driver - usb driver interface
+ */
+static struct usb_driver si470x_usb_driver = {
+ .name = DRIVER_NAME,
+ .probe = si470x_usb_driver_probe,
+ .disconnect = si470x_usb_driver_disconnect,
+ .suspend = si470x_usb_driver_suspend,
+ .resume = si470x_usb_driver_resume,
+ .id_table = si470x_usb_driver_id_table,
+ .supports_autosuspend = 1,
+};
+
+
+
+/**************************************************************************
+ * Module Interface
+ **************************************************************************/
+
+/*
+ * si470x_module_init - module init
+ */
+static int __init si470x_module_init(void)
+{
+ printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+ return usb_register(&si470x_usb_driver);
+}
+
+
+/*
+ * si470x_module_exit - module exit
+ */
+static void __exit si470x_module_exit(void)
+{
+ usb_deregister(&si470x_usb_driver);
+}
+
+
+module_init(si470x_module_init);
+module_exit(si470x_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/media/radio/radio-terratec.c b/drivers/media/radio/radio-terratec.c
new file mode 100644
index 0000000..0abb186
--- /dev/null
+++ b/drivers/media/radio/radio-terratec.c
@@ -0,0 +1,448 @@
+/* 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!!
+ *
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/delay.h> /* udelay */
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/spinlock.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 0xff,
+ .step = 1,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+#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
+{
+ unsigned long in_use;
+ 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 */
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-terratec", sizeof(v->driver));
+ strlcpy(v->card, "ActiveRadio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct tt_device *tt = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (87*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xFFFF*tt_getsigstr(tt);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct tt_device *tt = video_drvdata(file);
+
+ tt->curfreq = f->frequency;
+ tt_setfreq(tt, tt->curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct tt_device *tt = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = tt->curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct tt_device *tt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (tt->muted)
+ ctrl->value = 1;
+ else
+ ctrl->value = 0;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = tt->curvol * 6554;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct tt_device *tt = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ tt_mute(tt);
+ else
+ tt_setvol(tt,tt->curvol);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ tt_setvol(tt,ctrl->value);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct tt_device terratec_unit;
+
+static int terratec_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &terratec_unit.in_use) ? -EBUSY : 0;
+}
+
+static int terratec_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &terratec_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations terratec_fops = {
+ .owner = THIS_MODULE,
+ .open = terratec_exclusive_open,
+ .release = terratec_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops terratec_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+static struct video_device terratec_radio = {
+ .name = "TerraTec ActiveRadio",
+ .fops = &terratec_fops,
+ .ioctl_ops = &terratec_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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;
+ }
+
+ video_set_drvdata(&terratec_radio, &terratec_unit);
+
+ spin_lock_init(&lock);
+
+ if (video_register_device(&terratec_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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 0000000..e7b111f
--- /dev/null
+++ b/drivers/media/radio/radio-trust.c
@@ -0,0 +1,433 @@
+/* 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)
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#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/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 2048,
+ .default_value = 65535,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },{
+ .id = V4L2_CID_AUDIO_BASS,
+ .name = "Bass",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 4370,
+ .default_value = 32768,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },{
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .name = "Treble",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 4370,
+ .default_value = 32768,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ },
+};
+
+/* 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;
+static unsigned long in_use;
+
+/* 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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-trust", sizeof(v->driver));
+ strlcpy(v->card, "Trust FM Radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (87.5*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ if (tr_getstereo())
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = tr_getsigstr();
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ curfreq = f->frequency;
+ tr_setfreq(curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = curmute;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = curvol * 2048;
+ return 0;
+ case V4L2_CID_AUDIO_BASS:
+ ctrl->value = curbass * 4370;
+ return 0;
+ case V4L2_CID_AUDIO_TREBLE:
+ ctrl->value = curtreble * 4370;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ tr_setmute(ctrl->value);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ tr_setvol(ctrl->value);
+ return 0;
+ case V4L2_CID_AUDIO_BASS:
+ tr_setbass(ctrl->value);
+ return 0;
+ case V4L2_CID_AUDIO_TREBLE:
+ tr_settreble(ctrl->value);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int trust_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &in_use) ? -EBUSY : 0;
+}
+
+static int trust_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &in_use);
+ return 0;
+}
+
+static const struct file_operations trust_fops = {
+ .owner = THIS_MODULE,
+ .open = trust_exclusive_open,
+ .release = trust_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops trust_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+};
+
+static struct video_device trust_radio = {
+ .name = "Trust FM Radio",
+ .fops = &trust_fops,
+ .ioctl_ops = &trust_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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) < 0) {
+ 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 0000000..952ec35
--- /dev/null
+++ b/drivers/media/radio/radio-typhoon.c
@@ -0,0 +1,491 @@
+/* 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.
+ *
+ * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* request_region */
+#include <linux/proc_fs.h> /* radio card status report */
+#include <linux/seq_file.h>
+#include <asm/io.h> /* outb, outb_p */
+#include <asm/uaccess.h> /* copy to/from user */
+#include <linux/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,1,1)
+#define BANNER "Typhoon Radio Card driver v0.1.1\n"
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 1<<14,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+
+#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 {
+ unsigned long in_use;
+ int iobase;
+ int curvol;
+ int muted;
+ unsigned long curfreq;
+ unsigned long mutefreq;
+ struct mutex 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 void typhoon_setvol_generic(struct typhoon_device *dev, int vol)
+{
+ mutex_lock(&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. */
+ mutex_unlock(&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.
+ *
+ */
+
+ mutex_lock(&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);
+ mutex_unlock(&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 vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-typhoon", sizeof(v->driver));
+ strlcpy(v->card, "Typhoon Radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (87.5*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xFFFF; /* We can't get the signal strength */
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct typhoon_device *typhoon = video_drvdata(file);
+
+ typhoon->curfreq = f->frequency;
+ typhoon_setfreq(typhoon, typhoon->curfreq);
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct typhoon_device *typhoon = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = typhoon->curfreq;
+
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct typhoon_device *typhoon = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = typhoon->muted;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = typhoon->curvol;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl (struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct typhoon_device *typhoon = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ typhoon_mute(typhoon);
+ else
+ typhoon_unmute(typhoon);
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ typhoon_setvol(typhoon, ctrl->value);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct typhoon_device typhoon_unit =
+{
+ .iobase = CONFIG_RADIO_TYPHOON_PORT,
+ .curfreq = CONFIG_RADIO_TYPHOON_MUTEFREQ,
+ .mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ,
+};
+
+static int typhoon_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &typhoon_unit.in_use) ? -EBUSY : 0;
+}
+
+static int typhoon_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &typhoon_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations typhoon_fops = {
+ .owner = THIS_MODULE,
+ .open = typhoon_exclusive_open,
+ .release = typhoon_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops typhoon_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device typhoon_radio = {
+ .name = "Typhoon Radio",
+ .fops = &typhoon_fops,
+ .ioctl_ops = &typhoon_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+#ifdef CONFIG_RADIO_TYPHOON_PROC_FS
+
+static int typhoon_proc_show(struct seq_file *m, void *v)
+{
+ #ifdef MODULE
+ #define MODULEPROCSTRING "Driver loaded as a module"
+ #else
+ #define MODULEPROCSTRING "Driver compiled into kernel"
+ #endif
+
+ seq_puts(m, BANNER);
+ seq_puts(m, "Load type: " MODULEPROCSTRING "\n\n");
+ seq_printf(m, "frequency = %lu kHz\n",
+ typhoon_unit.curfreq >> 4);
+ seq_printf(m, "volume = %d\n", typhoon_unit.curvol);
+ seq_printf(m, "mute = %s\n", typhoon_unit.muted ?
+ "on" : "off");
+ seq_printf(m, "iobase = 0x%x\n", typhoon_unit.iobase);
+ seq_printf(m, "mute frequency = %lu kHz\n",
+ typhoon_unit.mutefreq >> 4);
+ return 0;
+}
+
+static int typhoon_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, typhoon_proc_show, NULL);
+}
+
+static const struct file_operations typhoon_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = typhoon_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+#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;
+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);
+ mutex_init(&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;
+ }
+
+ video_set_drvdata(&typhoon_radio, &typhoon_unit);
+ if (video_register_device(&typhoon_radio, VFL_TYPE_RADIO, radio_nr) < 0) {
+ 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 (!proc_create("driver/radio-typhoon", 0, NULL, &typhoon_proc_fops))
+ 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 0000000..15b10ba
--- /dev/null
+++ b/drivers/media/radio/radio-zoltrix.c
@@ -0,0 +1,505 @@
+/* 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
+ *
+ * 2006-07-24 - Converted to V4L2 API
+ * by Mauro Carvalho Chehab <mchehab@infradead.org>
+ */
+
+#include <linux/module.h> /* Modules */
+#include <linux/init.h> /* Initdata */
+#include <linux/ioport.h> /* 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/videodev2.h> /* kernel radio structs */
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include <linux/version.h> /* for KERNEL_VERSION MACRO */
+#define RADIO_VERSION KERNEL_VERSION(0,0,2)
+
+static struct v4l2_queryctrl radio_qctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 1,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ },{
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 65535,
+ .step = 4096,
+ .default_value = 0xff,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ }
+};
+
+#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 {
+ unsigned long in_use;
+ int port;
+ int curvol;
+ unsigned long curfreq;
+ int muted;
+ unsigned int stereo;
+ struct mutex lock;
+};
+
+static int zol_setvol(struct zol_device *dev, int vol)
+{
+ dev->curvol = vol;
+ if (dev->muted)
+ return 0;
+
+ mutex_lock(&dev->lock);
+ if (vol == 0) {
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ mutex_unlock(&dev->lock);
+ return 0;
+ }
+
+ outb(dev->curvol-1, io);
+ msleep(10);
+ inb(io + 2);
+ mutex_unlock(&dev->lock);
+ return 0;
+}
+
+static void zol_mute(struct zol_device *dev)
+{
+ dev->muted = 1;
+ mutex_lock(&dev->lock);
+ outb(0, io);
+ outb(0, io);
+ inb(io + 3); /* Zoltrix needs to be read to confirm */
+ mutex_unlock(&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) {
+ printk(KERN_WARNING "zoltrix: received zero freq. Failed to set.\n");
+ return -EINVAL;
+ }
+
+ m = (freq / 160 - 8800) * 2;
+ f = (unsigned long long) m + 0x4d1c;
+
+ bitmask = 0xc480402c10080000ull;
+ i = 45;
+
+ mutex_lock(&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);
+ }
+
+ mutex_unlock(&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;
+
+ mutex_lock(&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);
+
+ mutex_unlock(&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;
+
+ mutex_lock(&dev->lock);
+
+ outb(0x00, io);
+ outb(dev->curvol, io);
+ msleep(20);
+
+ x1 = inb(io);
+ msleep(10);
+ x2 = inb(io);
+
+ mutex_unlock(&dev->lock);
+
+ if ((x1 == x2) && (x1 == 0xcf))
+ return 1;
+ return 0;
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ strlcpy(v->driver, "radio-zoltrix", sizeof(v->driver));
+ strlcpy(v->card, "Zoltrix Radio", sizeof(v->card));
+ sprintf(v->bus_info, "ISA");
+ v->version = RADIO_VERSION;
+ v->capabilities = V4L2_CAP_TUNER;
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct zol_device *zol = video_drvdata(file);
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strcpy(v->name, "FM");
+ v->type = V4L2_TUNER_RADIO;
+ v->rangelow = (88*16000);
+ v->rangehigh = (108*16000);
+ v->rxsubchans = V4L2_TUNER_SUB_MONO|V4L2_TUNER_SUB_STEREO;
+ v->capability = V4L2_TUNER_CAP_LOW;
+ if (zol_is_stereo(zol))
+ v->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ v->audmode = V4L2_TUNER_MODE_MONO;
+ v->signal = 0xFFFF*zol_getsigstr(zol);
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ if (v->index > 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct zol_device *zol = video_drvdata(file);
+
+ zol->curfreq = f->frequency;
+ if (zol_setfreq(zol, zol->curfreq) != 0) {
+ printk(KERN_WARNING "zoltrix: Set frequency failed.\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct zol_device *zol = video_drvdata(file);
+
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = zol->curfreq;
+ return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
+ if (qc->id && qc->id == radio_qctrl[i].id) {
+ memcpy(qc, &(radio_qctrl[i]),
+ sizeof(*qc));
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct zol_device *zol = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = zol->muted;
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = zol->curvol * 4096;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct zol_device *zol = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value)
+ zol_mute(zol);
+ else {
+ zol_unmute(zol);
+ zol_setvol(zol,zol->curvol);
+ }
+ return 0;
+ case V4L2_CID_AUDIO_VOLUME:
+ zol_setvol(zol,ctrl->value/4096);
+ return 0;
+ }
+ zol->stereo = 1;
+ if (zol_setfreq(zol, zol->curfreq) != 0) {
+ printk(KERN_WARNING "zoltrix: Set frequency failed.\n");
+ return -EINVAL;
+ }
+#if 0
+/* FIXME: Implement stereo/mono switch on V4L2 */
+ 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);
+ }
+#endif
+ return -EINVAL;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index > 1)
+ return -EINVAL;
+
+ strcpy(a->name, "Radio");
+ a->capability = V4L2_AUDCAP_STEREO;
+ return 0;
+}
+
+static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+ *i = 0;
+ return 0;
+}
+
+static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+ if (i != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *a)
+{
+ if (a->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static struct zol_device zoltrix_unit;
+
+static int zoltrix_exclusive_open(struct inode *inode, struct file *file)
+{
+ return test_and_set_bit(0, &zoltrix_unit.in_use) ? -EBUSY : 0;
+}
+
+static int zoltrix_exclusive_release(struct inode *inode, struct file *file)
+{
+ clear_bit(0, &zoltrix_unit.in_use);
+ return 0;
+}
+
+static const struct file_operations zoltrix_fops =
+{
+ .owner = THIS_MODULE,
+ .open = zoltrix_exclusive_open,
+ .release = zoltrix_exclusive_release,
+ .ioctl = video_ioctl2,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = v4l_compat_ioctl32,
+#endif
+ .llseek = no_llseek,
+};
+
+static const struct v4l2_ioctl_ops zoltrix_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_g_tuner,
+ .vidioc_s_tuner = vidioc_s_tuner,
+ .vidioc_g_audio = vidioc_g_audio,
+ .vidioc_s_audio = vidioc_s_audio,
+ .vidioc_g_input = vidioc_g_input,
+ .vidioc_s_input = vidioc_s_input,
+ .vidioc_g_frequency = vidioc_g_frequency,
+ .vidioc_s_frequency = vidioc_s_frequency,
+ .vidioc_queryctrl = vidioc_queryctrl,
+ .vidioc_g_ctrl = vidioc_g_ctrl,
+ .vidioc_s_ctrl = vidioc_s_ctrl,
+};
+
+static struct video_device zoltrix_radio = {
+ .name = "Zoltrix Radio Plus",
+ .fops = &zoltrix_fops,
+ .ioctl_ops = &zoltrix_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+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;
+ }
+
+ video_set_drvdata(&zoltrix_radio, &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) < 0) {
+ release_region(io, 2);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "Zoltrix Radio Plus card driver.\n");
+
+ mutex_init(&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);
+
OpenPOWER on IntegriCloud