Initial commit; kernel source import

This commit is contained in:
Nathan
2025-04-06 23:50:55 -05:00
commit 25c6d769f4
45093 changed files with 18199410 additions and 0 deletions

498
drivers/media/radio/Kconfig Normal file
View File

@@ -0,0 +1,498 @@
#
# Multimedia Video device configuration
#
menuconfig RADIO_ADAPTERS
bool "Radio Adapters"
depends on VIDEO_V4L2
depends on MEDIA_RADIO_SUPPORT
default y
---help---
Say Y here to enable selecting AM/FM radio adapters.
if RADIO_ADAPTERS && VIDEO_V4L2
config RADIO_SI470X
bool "Silicon Labs Si470x FM Radio Receiver support"
depends on VIDEO_V4L2
source "drivers/media/radio/si470x/Kconfig"
config RADIO_SI476X
tristate "Silicon Laboratories Si476x I2C FM Radio"
depends on I2C && VIDEO_V4L2
depends on MFD_SI476X_CORE
depends on SND_SOC
select SND_SOC_SI476X
---help---
Choose Y here if you have this FM radio chip.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux 2 API. Information on
this API and pointers to "v4l2" 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-si476x.
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.
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 RADIO_MAXIRADIO
tristate "Guillemot MAXI Radio FM 2000 radio"
depends on VIDEO_V4L2 && PCI && SND
---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_SHARK
tristate "Griffin radioSHARK USB radio receiver"
depends on USB && SND
---help---
Choose Y here if you have this radio receiver.
There are 2 versions of this device, this driver is for version 1,
which is white.
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-shark.
config RADIO_SHARK2
tristate "Griffin radioSHARK2 USB radio receiver"
depends on USB
---help---
Choose Y here if you have this radio receiver.
There are 2 versions of this device, this driver is for version 2,
which is black.
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-shark2.
config I2C_SI4713
tristate "I2C driver for Silicon Labs Si4713 device"
depends on I2C && VIDEO_V4L2
---help---
Say Y here if you want support to Si4713 I2C device.
This device driver supports only i2c bus.
To compile this driver as a module, choose M here: the
module will be called si4713.
config RADIO_SI4713
tristate "Silicon Labs Si4713 FM Radio Transmitter support"
depends on I2C && VIDEO_V4L2
select I2C_SI4713
---help---
Say Y here if you want support to Si4713 FM Radio Transmitter.
This device can transmit audio through FM. It can transmit
RDS and RBDS signals as well. This module is the v4l2 radio
interface for the i2c driver of this device.
To compile this driver as a module, choose M here: the
module will be called radio-si4713.
config USB_KEENE
tristate "Keene FM Transmitter USB support"
depends on USB && VIDEO_V4L2
---help---
Say Y here if you want to connect this type of FM transmitter
to your computer's USB port.
To compile this driver as a module, choose M here: the
module will be called radio-keene.
config USB_MA901
tristate "Masterkit MA901 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 or headphones.
To compile this driver as a module, choose M here: the
module will be called radio-ma901.
config RADIO_TEA5764
tristate "TEA5764 I2C FM radio support"
depends on I2C && VIDEO_V4L2
---help---
Say Y here if you want to use the TEA5764 FM chip found in
EZX phones. This FM chip is present in EZX phones from Motorola,
connected to internal pxa I2C bus.
To compile this driver as a module, choose M here: the
module will be called radio-tea5764.
config RADIO_TEA5764_XTAL
bool "TEA5764 crystal reference"
depends on RADIO_TEA5764=y
default y
help
Say Y here if TEA5764 have a 32768 Hz crystal in circuit, say N
here if TEA5764 reference frequency is connected in FREQIN.
config RADIO_SAA7706H
tristate "SAA7706H Car Radio DSP"
depends on I2C && VIDEO_V4L2
---help---
Say Y here if you want to use the SAA7706H Car radio Digital
Signal Processor, found for instance on the Russellville development
board. On the russellville the device is connected to internal
timberdale I2C bus.
To compile this driver as a module, choose M here: the
module will be called SAA7706H.
config RADIO_TEF6862
tristate "TEF6862 Car Radio Enhanced Selectivity Tuner"
depends on I2C && VIDEO_V4L2
---help---
Say Y here if you want to use the TEF6862 Car Radio Enhanced
Selectivity Tuner, found for instance on the Russellville development
board. On the russellville the device is connected to internal
timberdale I2C bus.
To compile this driver as a module, choose M here: the
module will be called TEF6862.
config RADIO_TIMBERDALE
tristate "Enable the Timberdale radio driver"
depends on MFD_TIMBERDALE && VIDEO_V4L2
depends on I2C # for RADIO_SAA7706H
select RADIO_TEF6862
select RADIO_SAA7706H
---help---
This is a kind of umbrella driver for the Radio Tuner and DSP
found behind the Timberdale FPGA on the Russellville board.
Enabling this driver will automatically select the DSP and tuner.
config RADIO_WL1273
tristate "Texas Instruments WL1273 I2C FM Radio"
depends on I2C && VIDEO_V4L2 && GENERIC_HARDIRQS
select MFD_CORE
select MFD_WL1273_CORE
select FW_LOADER
---help---
Choose Y here if you have this FM radio chip.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux 2 API. Information on
this API and pointers to "v4l2" 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-wl1273.
# TI's ST based wl128x FM radio
source "drivers/media/radio/wl128x/Kconfig"
#
# ISA drivers configuration
#
menuconfig V4L_RADIO_ISA_DRIVERS
bool "ISA radio devices"
depends on ISA
default n
---help---
Say Y here to enable support for these ISA drivers.
if V4L_RADIO_ISA_DRIVERS
config RADIO_ISA
depends on ISA
tristate
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.
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
select RADIO_ISA
---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.
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 "30f"
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
select RADIO_ISA
---help---
Choose Y here if you have this FM radio card, and then fill in the
port address below.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
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
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address below.
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
select RADIO_ISA
---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
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, 0x248 or 0x28c)"
depends on RADIO_GEMTEK=y
default "34c"
help
Enter either 0x20c, 0x30c, 0x24c, 0x34c, 0x248 or 0x28c 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_MIROPCM20
tristate "miroSOUND PCM20 radio"
depends on ISA && ISA_DMA_API && VIDEO_V4L2 && SND
select SND_ISA
select SND_MIRO
---help---
Choose Y here if you have this FM radio card. You also need to enable
the ALSA sound system. This choice automatically selects the ALSA
sound card driver "Miro miroSOUND PCM1pro/PCM12/PCM20radio" as this
is required for the radio-miropcm20.
To compile this driver as a module, choose M here: the
module will be called radio-miropcm20.
config RADIO_SF16FMI
tristate "SF16-FMI/SF16-FMP/SF16-FMD Radio"
depends on ISA && VIDEO_V4L2
---help---
Choose Y here if you have one of these FM radio cards.
To compile this driver as a module, choose M here: the
module will be called radio-sf16fmi.
config RADIO_SF16FMR2
tristate "SF16-FMR2/SF16-FMD2 Radio"
depends on ISA && VIDEO_V4L2 && SND
---help---
Choose Y here if you have one of these FM radio cards.
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
select RADIO_ISA
---help---
Choose Y here if you have this FM radio card.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called radio-terratec.
config RADIO_TRUST
tristate "Trust FM radio card"
depends on ISA && VIDEO_V4L2
select RADIO_ISA
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.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
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
select RADIO_ISA
---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.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
To compile this driver as a module, choose M here: the
module will be called 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
select RADIO_ISA
---help---
Choose Y here if you have one of these FM radio cards, and then fill
in the port address below.
Note: this driver hasn't been tested since a long time due to lack
of hardware. If you have this hardware, then please contact the
linux-media mailinglist.
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.
endif # V4L_RADIO_ISA_DRIVERS
endif # RADIO_ADAPTERS

View File

@@ -0,0 +1,38 @@
#
# Makefile for the kernel character device drivers.
#
obj-$(CONFIG_RADIO_ISA) += radio-isa.o
obj-$(CONFIG_RADIO_AZTECH) += radio-aztech.o
obj-$(CONFIG_RADIO_RTRACK2) += radio-rtrack2.o
obj-$(CONFIG_RADIO_SF16FMI) += radio-sf16fmi.o
obj-$(CONFIG_RADIO_SF16FMR2) += radio-sf16fmr2.o
obj-$(CONFIG_RADIO_CADET) += radio-cadet.o
obj-$(CONFIG_RADIO_TYPHOON) += radio-typhoon.o
obj-$(CONFIG_RADIO_TERRATEC) += radio-terratec.o
obj-$(CONFIG_RADIO_MAXIRADIO) += radio-maxiradio.o
obj-$(CONFIG_RADIO_SHARK) += radio-shark.o
obj-$(CONFIG_RADIO_SHARK2) += shark2.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_TRUST) += radio-trust.o
obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o
obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o
obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o
obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o
obj-$(CONFIG_USB_DSBR) += dsbr100.o
obj-$(CONFIG_RADIO_SI470X) += si470x/
obj-$(CONFIG_USB_MR800) += radio-mr800.o
obj-$(CONFIG_USB_KEENE) += radio-keene.o
obj-$(CONFIG_USB_MA901) += radio-ma901.o
obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o
obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o
obj-$(CONFIG_RADIO_WL128X) += wl128x/
shark2-objs := radio-shark2.o radio-tea5777.o
ccflags-y += -Isound

View File

@@ -0,0 +1,434 @@
/* A driver for the D-Link DSB-R100 USB radio and Gemtek USB Radio 21.
* The device 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
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*
* 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
*/
#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 <linux/usb.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
/*
* Version Information
*/
MODULE_AUTHOR("Markus Demleitner <msdemlei@tucana.harvard.edu>");
MODULE_DESCRIPTION("D-Link DSB-R100 USB FM radio driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.1.0");
#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
#define v4l2_dev_to_radio(d) container_of(d, struct dsbr100_device, v4l2_dev)
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;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
u8 *transfer_buffer;
struct mutex v4l2_lock;
int curfreq;
bool stereo;
bool muted;
};
/* Low-level device interface begins here */
/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
static int dsbr100_setfreq(struct dsbr100_device *radio, unsigned freq)
{
unsigned f = (freq / 16 * 80) / 1000 + 856;
int retval = 0;
if (!radio->muted) {
retval = usb_control_msg(radio->usbdev,
usb_rcvctrlpipe(radio->usbdev, 0),
DSB100_TUNE,
USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
(f >> 8) & 0x00ff, f & 0xff,
radio->transfer_buffer, 8, 300);
if (retval >= 0)
mdelay(1);
}
if (retval >= 0) {
radio->curfreq = freq;
return 0;
}
dev_err(&radio->usbdev->dev,
"%s - usb_control_msg returned %i, request %i\n",
__func__, retval, DSB100_TUNE);
return retval;
}
/* switch on radio */
static int dsbr100_start(struct dsbr100_device *radio)
{
int retval = 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);
if (retval >= 0)
return dsbr100_setfreq(radio, radio->curfreq);
dev_err(&radio->usbdev->dev,
"%s - usb_control_msg returned %i, request %i\n",
__func__, retval, DSB100_ONOFF);
return retval;
}
/* switch off radio */
static int dsbr100_stop(struct dsbr100_device *radio)
{
int retval = 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);
if (retval >= 0)
return 0;
dev_err(&radio->usbdev->dev,
"%s - usb_control_msg returned %i, request %i\n",
__func__, retval, DSB100_ONOFF);
return retval;
}
/* 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)
{
int retval = 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);
if (retval < 0) {
radio->stereo = false;
dev_err(&radio->usbdev->dev,
"%s - usb_control_msg returned %i, request %i\n",
__func__, retval, USB_REQ_GET_STATUS);
} else {
radio->stereo = !(radio->transfer_buffer[0] & 0x01);
}
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct dsbr100_device *radio = video_drvdata(file);
strlcpy(v->driver, "dsbr100", sizeof(v->driver));
strlcpy(v->card, "D-Link R-100 USB FM Radio", sizeof(v->card));
usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
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 = radio->stereo ? V4L2_TUNER_SUB_STEREO :
V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
v->audmode = V4L2_TUNER_MODE_STEREO;
v->signal = radio->stereo ? 0xffff : 0; /* We can't get the signal strength */
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct dsbr100_device *radio = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
return dsbr100_setfreq(radio, clamp_t(unsigned, f->frequency,
FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL));
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct dsbr100_device *radio = video_drvdata(file);
if (f->tuner)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = radio->curfreq;
return 0;
}
static int usb_dsbr100_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct dsbr100_device *radio =
container_of(ctrl->handler, struct dsbr100_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
radio->muted = ctrl->val;
return radio->muted ? dsbr100_stop(radio) : dsbr100_start(radio);
}
return -EINVAL;
}
/* USB subsystem interface begins here */
/*
* Handle unplugging of the device.
* We call video_unregister_device in any case.
* The last function called in this procedure is
* usb_dsbr100_video_device_release
*/
static void usb_dsbr100_disconnect(struct usb_interface *intf)
{
struct dsbr100_device *radio = usb_get_intfdata(intf);
mutex_lock(&radio->v4l2_lock);
/*
* Disconnect is also called on unload, and in that case we need to
* mute the device. This call will silently fail if it is called
* after a physical disconnect.
*/
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);
usb_set_intfdata(intf, NULL);
video_unregister_device(&radio->videodev);
v4l2_device_disconnect(&radio->v4l2_dev);
mutex_unlock(&radio->v4l2_lock);
v4l2_device_put(&radio->v4l2_dev);
}
/* Suspend device - stop device. */
static int usb_dsbr100_suspend(struct usb_interface *intf, pm_message_t message)
{
struct dsbr100_device *radio = usb_get_intfdata(intf);
mutex_lock(&radio->v4l2_lock);
if (!radio->muted && dsbr100_stop(radio) < 0)
dev_warn(&intf->dev, "dsbr100_stop failed\n");
mutex_unlock(&radio->v4l2_lock);
dev_info(&intf->dev, "going into suspend..\n");
return 0;
}
/* Resume device - start device. */
static int usb_dsbr100_resume(struct usb_interface *intf)
{
struct dsbr100_device *radio = usb_get_intfdata(intf);
mutex_lock(&radio->v4l2_lock);
if (!radio->muted && dsbr100_start(radio) < 0)
dev_warn(&intf->dev, "dsbr100_start failed\n");
mutex_unlock(&radio->v4l2_lock);
dev_info(&intf->dev, "coming out of suspend..\n");
return 0;
}
/* free data structures */
static void usb_dsbr100_release(struct v4l2_device *v4l2_dev)
{
struct dsbr100_device *radio = v4l2_dev_to_radio(v4l2_dev);
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->transfer_buffer);
kfree(radio);
}
static const struct v4l2_ctrl_ops usb_dsbr100_ctrl_ops = {
.s_ctrl = usb_dsbr100_s_ctrl,
};
/* File system interface */
static const struct v4l2_file_operations usb_dsbr100_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
};
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_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/* 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;
struct v4l2_device *v4l2_dev;
int retval;
radio = kzalloc(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;
}
v4l2_dev = &radio->v4l2_dev;
v4l2_dev->release = usb_dsbr100_release;
retval = v4l2_device_register(&intf->dev, v4l2_dev);
if (retval < 0) {
v4l2_err(v4l2_dev, "couldn't register v4l2_device\n");
goto err_reg_dev;
}
v4l2_ctrl_handler_init(&radio->hdl, 1);
v4l2_ctrl_new_std(&radio->hdl, &usb_dsbr100_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
if (radio->hdl.error) {
retval = radio->hdl.error;
v4l2_err(v4l2_dev, "couldn't register control\n");
goto err_reg_ctrl;
}
mutex_init(&radio->v4l2_lock);
strlcpy(radio->videodev.name, v4l2_dev->name, sizeof(radio->videodev.name));
radio->videodev.v4l2_dev = v4l2_dev;
radio->videodev.fops = &usb_dsbr100_fops;
radio->videodev.ioctl_ops = &usb_dsbr100_ioctl_ops;
radio->videodev.release = video_device_release_empty;
radio->videodev.lock = &radio->v4l2_lock;
radio->videodev.ctrl_handler = &radio->hdl;
set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags);
radio->usbdev = interface_to_usbdev(intf);
radio->curfreq = FREQ_MIN * FREQ_MUL;
radio->muted = true;
video_set_drvdata(&radio->videodev, radio);
usb_set_intfdata(intf, radio);
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr);
if (retval == 0)
return 0;
v4l2_err(v4l2_dev, "couldn't register video device\n");
err_reg_ctrl:
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(v4l2_dev);
err_reg_dev:
kfree(radio->transfer_buffer);
kfree(radio);
return retval;
}
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,
.suspend = usb_dsbr100_suspend,
.resume = usb_dsbr100_resume,
.reset_resume = usb_dsbr100_resume,
};
module_usb_driver(usb_dsbr100_driver);

View File

@@ -0,0 +1,43 @@
#ifndef __LM7000_H
#define __LM7000_H
/* Sanyo LM7000 tuner chip control
*
* Copyright 2012 Ondrej Zary <linux@rainbow-software.org>
* based on radio-aimslab.c by M. Kirkwood
* and radio-sf16fmi.c by M. Kirkwood and Petr Vandrovec
*/
#define LM7000_DATA (1 << 0)
#define LM7000_CLK (1 << 1)
#define LM7000_CE (1 << 2)
#define LM7000_FM_100 (0 << 20)
#define LM7000_FM_50 (1 << 20)
#define LM7000_FM_25 (2 << 20)
#define LM7000_BIT_FM (1 << 23)
static inline void lm7000_set_freq(u32 freq, void *handle,
void (*set_pins)(void *handle, u8 pins))
{
int i;
u8 data;
u32 val;
freq += 171200; /* Add 10.7 MHz IF */
freq /= 400; /* Convert to 25 kHz units */
val = freq | LM7000_FM_25 | LM7000_BIT_FM;
/* write the 24-bit register, starting with LSB */
for (i = 0; i < 24; i++) {
data = val & (1 << i) ? LM7000_DATA : 0;
set_pins(handle, data | LM7000_CE);
udelay(2);
set_pins(handle, data | LM7000_CE | LM7000_CLK);
udelay(2);
set_pins(handle, data | LM7000_CE);
udelay(2);
}
set_pins(handle, 0);
}
#endif /* __LM7000_H */

View File

@@ -0,0 +1,194 @@
/*
* AimsLab RadioTrack (aka RadioVeveal) driver
*
* Copyright 1997 M. Kirkwood
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* 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);
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
#include <linux/init.h> /* Initdata */
#include <linux/ioport.h> /* request_region */
#include <linux/delay.h> /* msleep */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
#include "lm7000.h"
MODULE_AUTHOR("M. Kirkwood");
MODULE_DESCRIPTION("A driver for the RadioTrack/RadioReveal radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");
#ifndef CONFIG_RADIO_RTRACK_PORT
#define CONFIG_RADIO_RTRACK_PORT -1
#endif
#define RTRACK_MAX 2
static int io[RTRACK_MAX] = { [0] = CONFIG_RADIO_RTRACK_PORT,
[1 ... (RTRACK_MAX - 1)] = -1 };
static int radio_nr[RTRACK_MAX] = { [0 ... (RTRACK_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct rtrack {
struct radio_isa_card isa;
int curvol;
};
static struct radio_isa_card *rtrack_alloc(void)
{
struct rtrack *rt = kzalloc(sizeof(struct rtrack), GFP_KERNEL);
if (rt)
rt->curvol = 0xff;
return rt ? &rt->isa : NULL;
}
#define AIMS_BIT_TUN_CE (1 << 0)
#define AIMS_BIT_TUN_CLK (1 << 1)
#define AIMS_BIT_TUN_DATA (1 << 2)
#define AIMS_BIT_VOL_CE (1 << 3)
#define AIMS_BIT_TUN_STRQ (1 << 4)
/* bit 5 is not connected */
#define AIMS_BIT_VOL_UP (1 << 6) /* active low */
#define AIMS_BIT_VOL_DN (1 << 7) /* active low */
static void rtrack_set_pins(void *handle, u8 pins)
{
struct radio_isa_card *isa = handle;
struct rtrack *rt = container_of(isa, struct rtrack, isa);
u8 bits = AIMS_BIT_VOL_DN | AIMS_BIT_VOL_UP | AIMS_BIT_TUN_STRQ;
if (!v4l2_ctrl_g_ctrl(rt->isa.mute))
bits |= AIMS_BIT_VOL_CE;
if (pins & LM7000_DATA)
bits |= AIMS_BIT_TUN_DATA;
if (pins & LM7000_CLK)
bits |= AIMS_BIT_TUN_CLK;
if (pins & LM7000_CE)
bits |= AIMS_BIT_TUN_CE;
outb_p(bits, rt->isa.io);
}
static int rtrack_s_frequency(struct radio_isa_card *isa, u32 freq)
{
lm7000_set_freq(freq, isa, rtrack_set_pins);
return 0;
}
static u32 rtrack_g_signal(struct radio_isa_card *isa)
{
/* bit set = no signal present */
return 0xffff * !(inb(isa->io) & 2);
}
static int rtrack_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct rtrack *rt = container_of(isa, struct rtrack, isa);
int curvol = rt->curvol;
if (mute) {
outb(0xd0, isa->io); /* volume steady + sigstr + off */
return 0;
}
if (vol == 0) { /* volume = 0 means mute the card */
outb(0x48, isa->io); /* volume down but still "on" */
msleep(curvol * 3); /* make sure it's totally down */
} else if (curvol < vol) {
outb(0x98, isa->io); /* volume up + sigstr + on */
for (; curvol < vol; curvol++)
udelay(3000);
} else if (curvol > vol) {
outb(0x58, isa->io); /* volume down + sigstr + on */
for (; curvol > vol; curvol--)
udelay(3000);
}
outb(0xd8, isa->io); /* volume steady + sigstr + on */
rt->curvol = vol;
return 0;
}
/* Mute card - prevents noisy bootups */
static int rtrack_initialize(struct radio_isa_card *isa)
{
/* this ensures that the volume is all the way up */
outb(0x90, isa->io); /* volume up but still "on" */
msleep(3000); /* make sure it's totally up */
outb(0xc0, isa->io); /* steady volume, mute card */
return 0;
}
static const struct radio_isa_ops rtrack_ops = {
.alloc = rtrack_alloc,
.init = rtrack_initialize,
.s_mute_volume = rtrack_s_mute_volume,
.s_frequency = rtrack_s_frequency,
.g_signal = rtrack_g_signal,
};
static const int rtrack_ioports[] = { 0x20f, 0x30f };
static struct radio_isa_driver rtrack_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-aimslab",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = rtrack_ioports,
.num_of_io_ports = ARRAY_SIZE(rtrack_ioports),
.region_size = 2,
.card = "AIMSlab RadioTrack/RadioReveal",
.ops = &rtrack_ops,
.has_stereo = true,
.max_volume = 0xff,
};
static int __init rtrack_init(void)
{
return isa_register_driver(&rtrack_driver.driver, RTRACK_MAX);
}
static void __exit rtrack_exit(void)
{
isa_unregister_driver(&rtrack_driver.driver);
}
module_init(rtrack_init);
module_exit(rtrack_exit);

View File

@@ -0,0 +1,186 @@
/*
* radio-aztech.c - Aztech radio card driver
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@xs4all.nl>
* 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)
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#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 <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
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_VERSION("1.0.0");
/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
#ifndef CONFIG_RADIO_AZTECH_PORT
#define CONFIG_RADIO_AZTECH_PORT -1
#endif
#define AZTECH_MAX 2
static int io[AZTECH_MAX] = { [0] = CONFIG_RADIO_AZTECH_PORT,
[1 ... (AZTECH_MAX - 1)] = -1 };
static int radio_nr[AZTECH_MAX] = { [0 ... (AZTECH_MAX - 1)] = -1 };
static const int radio_wait_time = 1000;
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Aztech card (0x350 or 0x358)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct aztech {
struct radio_isa_card isa;
int curvol;
};
static void send_0_byte(struct aztech *az)
{
udelay(radio_wait_time);
outb_p(2 + az->curvol, az->isa.io);
outb_p(64 + 2 + az->curvol, az->isa.io);
}
static void send_1_byte(struct aztech *az)
{
udelay(radio_wait_time);
outb_p(128 + 2 + az->curvol, az->isa.io);
outb_p(128 + 64 + 2 + az->curvol, az->isa.io);
}
static struct radio_isa_card *aztech_alloc(void)
{
struct aztech *az = kzalloc(sizeof(*az), GFP_KERNEL);
return az ? &az->isa : NULL;
}
static int aztech_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct aztech *az = container_of(isa, struct aztech, isa);
int i;
freq += 171200; /* Add 10.7 MHz IF */
freq /= 800; /* Convert to 50 kHz units */
send_0_byte(az); /* 0: LSB of frequency */
for (i = 0; i < 13; i++) /* : frequency bits (1-13) */
if (freq & (1 << i))
send_1_byte(az);
else
send_0_byte(az);
send_0_byte(az); /* 14: test bit - always 0 */
send_0_byte(az); /* 15: test bit - always 0 */
send_0_byte(az); /* 16: band data 0 - always 0 */
if (isa->stereo) /* 17: stereo (1 to enable) */
send_1_byte(az);
else
send_0_byte(az);
send_1_byte(az); /* 18: band data 1 - unknown */
send_0_byte(az); /* 19: time base - always 0 */
send_0_byte(az); /* 20: spacing (0 = 25 kHz) */
send_1_byte(az); /* 21: spacing (1 = 25 kHz) */
send_0_byte(az); /* 22: spacing (0 = 25 kHz) */
send_1_byte(az); /* 23: AM/FM (FM = 1, always) */
/* latch frequency */
udelay(radio_wait_time);
outb_p(128 + 64 + az->curvol, az->isa.io);
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
*/
static u32 aztech_g_rxsubchans(struct radio_isa_card *isa)
{
if (inb(isa->io) & 1)
return V4L2_TUNER_SUB_MONO;
return V4L2_TUNER_SUB_STEREO;
}
static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo)
{
return aztech_s_frequency(isa, isa->freq);
}
static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct aztech *az = container_of(isa, struct aztech, isa);
if (mute)
vol = 0;
az->curvol = (vol & 1) + ((vol & 2) << 1);
outb(az->curvol, isa->io);
return 0;
}
static const struct radio_isa_ops aztech_ops = {
.alloc = aztech_alloc,
.s_mute_volume = aztech_s_mute_volume,
.s_frequency = aztech_s_frequency,
.s_stereo = aztech_s_stereo,
.g_rxsubchans = aztech_g_rxsubchans,
};
static const int aztech_ioports[] = { 0x350, 0x358 };
static struct radio_isa_driver aztech_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-aztech",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = aztech_ioports,
.num_of_io_ports = ARRAY_SIZE(aztech_ioports),
.region_size = 2,
.card = "Aztech Radio",
.ops = &aztech_ops,
.has_stereo = true,
.max_volume = 3,
};
static int __init aztech_init(void)
{
return isa_register_driver(&aztech_driver.driver, AZTECH_MAX);
}
static void __exit aztech_exit(void)
{
isa_unregister_driver(&aztech_driver.driver);
}
module_init(aztech_init);
module_exit(aztech_exit);

View File

@@ -0,0 +1,677 @@
/* 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@lxorguk.ukuu.org.uk>
* 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/module.h> /* Modules */
#include <linux/init.h> /* Initdata */
#include <linux/ioport.h> /* request_region */
#include <linux/delay.h> /* udelay */
#include <linux/videodev2.h> /* V4L2 API defs */
#include <linux/param.h>
#include <linux/pnp.h>
#include <linux/sched.h>
#include <linux/io.h> /* outb, outb_p */
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
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_VERSION("0.3.4");
static int io = -1; /* default to isapnp activation */
static int radio_nr = -1;
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);
#define RDS_BUFFER 256
#define RDS_RX_FLAG 1
#define MBS_RX_FLAG 2
struct cadet {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct v4l2_ctrl_handler ctrl_handler;
int io;
bool is_fm_band;
u32 curfreq;
int tunestat;
int sigstrength;
wait_queue_head_t read_queue;
struct timer_list readtimer;
u8 rdsin, rdsout, rdsstat;
unsigned char rdsbuf[RDS_BUFFER];
struct mutex lock;
int reading;
};
static struct cadet cadet_card;
/*
* 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] = {
{ 1835, 2621, 4128, 65535 },
{ 2185, 4369, 13107, 65535 },
};
static const struct v4l2_frequency_band bands[] = {
{
.index = 0,
.type = V4L2_TUNER_RADIO,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 8320, /* 520 kHz */
.rangehigh = 26400, /* 1650 kHz */
.modulation = V4L2_BAND_MODULATION_AM,
}, {
.index = 1,
.type = V4L2_TUNER_RADIO,
.capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
V4L2_TUNER_CAP_RDS_BLOCK_IO | V4L2_TUNER_CAP_LOW |
V4L2_TUNER_CAP_FREQ_BANDS,
.rangelow = 1400000, /* 87.5 MHz */
.rangehigh = 1728000, /* 108.0 MHz */
.modulation = V4L2_BAND_MODULATION_FM,
},
};
static int cadet_getstereo(struct cadet *dev)
{
int ret = V4L2_TUNER_SUB_MONO;
if (!dev->is_fm_band) /* Only FM has stereo capability! */
return V4L2_TUNER_SUB_MONO;
outb(7, dev->io); /* Select tuner control */
if ((inb(dev->io + 1) & 0x40) == 0)
ret = V4L2_TUNER_SUB_STEREO;
return ret;
}
static unsigned cadet_gettune(struct cadet *dev)
{
int curvol, i;
unsigned fifo = 0;
/*
* Prepare for read
*/
outb(7, dev->io); /* Select tuner control */
curvol = inb(dev->io + 1); /* Save current volume/mute setting */
outb(0x00, dev->io + 1); /* Ensure WRITE-ENABLE is LOW */
dev->tunestat = 0xffff;
/*
* Read the shift register
*/
for (i = 0; i < 25; i++) {
fifo = (fifo << 1) | ((inb(dev->io + 1) >> 7) & 0x01);
if (i < 24) {
outb(0x01, dev->io + 1);
dev->tunestat &= inb(dev->io + 1);
outb(0x00, dev->io + 1);
}
}
/*
* Restore volume/mute setting
*/
outb(curvol, dev->io + 1);
return fifo;
}
static unsigned cadet_getfreq(struct cadet *dev)
{
int i;
unsigned freq = 0, test, fifo = 0;
/*
* Read current tuning
*/
fifo = cadet_gettune(dev);
/*
* Convert to actual frequency
*/
if (!dev->is_fm_band) /* AM */
return ((fifo & 0x7fff) - 450) * 16;
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) / 1000; /* Make it 1/16 kHz */
return freq;
}
static void cadet_settune(struct cadet *dev, unsigned fifo)
{
int i;
unsigned test;
outb(7, dev->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, dev->io); /* Select tuner control */
outb(test, dev->io + 1); /* Initialize for write */
for (i = 0; i < 25; i++) {
test |= 0x01; /* Toggle SCK High */
outb(test, dev->io + 1);
test &= 0xfe; /* Toggle SCK Low */
outb(test, dev->io + 1);
fifo = fifo << 1; /* Prepare the next bit */
test = 0x1c | ((fifo >> 23) & 0x02);
outb(test, dev->io + 1);
}
}
static void cadet_setfreq(struct cadet *dev, unsigned freq)
{
unsigned fifo;
int i, j, test;
int curvol;
freq = clamp(freq, bands[dev->is_fm_band].rangelow,
bands[dev->is_fm_band].rangehigh);
dev->curfreq = freq;
/*
* Formulate a fifo command
*/
fifo = 0;
if (dev->is_fm_band) { /* FM */
test = 102400;
freq = freq / 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;
}
} else { /* AM */
fifo = (freq / 16) + 450; /* Make it kHz */
fifo |= 0x100000; /* Select AM Band */
}
/*
* Save current volume/mute setting
*/
outb(7, dev->io); /* Select tuner control */
curvol = inb(dev->io + 1);
/*
* Tune the card
*/
for (j = 3; j > -1; j--) {
cadet_settune(dev, fifo | (j << 16));
outb(7, dev->io); /* Select tuner control */
outb(curvol, dev->io + 1);
msleep(100);
cadet_gettune(dev);
if ((dev->tunestat & 0x40) == 0) { /* Tuned */
dev->sigstrength = sigtable[dev->is_fm_band][j];
goto reset_rds;
}
}
dev->sigstrength = 0;
reset_rds:
outb(3, dev->io);
outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
}
static void cadet_handler(unsigned long data)
{
struct cadet *dev = (void *)data;
/* Service the RDS fifo */
if (mutex_trylock(&dev->lock)) {
outb(0x3, dev->io); /* Select RDS Decoder Control */
if ((inb(dev->io + 1) & 0x20) != 0)
printk(KERN_CRIT "cadet: RDS fifo overflow\n");
outb(0x80, dev->io); /* Select RDS fifo */
while ((inb(dev->io) & 0x80) != 0) {
dev->rdsbuf[dev->rdsin] = inb(dev->io + 1);
if (dev->rdsin + 1 == dev->rdsout)
printk(KERN_WARNING "cadet: RDS buffer overflow\n");
else
dev->rdsin++;
}
mutex_unlock(&dev->lock);
}
/*
* Service pending read
*/
if (dev->rdsin != dev->rdsout)
wake_up_interruptible(&dev->read_queue);
/*
* Clean up and exit
*/
init_timer(&dev->readtimer);
dev->readtimer.function = cadet_handler;
dev->readtimer.data = data;
dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
add_timer(&dev->readtimer);
}
static void cadet_start_rds(struct cadet *dev)
{
dev->rdsstat = 1;
outb(0x80, dev->io); /* Select RDS fifo */
init_timer(&dev->readtimer);
dev->readtimer.function = cadet_handler;
dev->readtimer.data = (unsigned long)dev;
dev->readtimer.expires = jiffies + msecs_to_jiffies(50);
add_timer(&dev->readtimer);
}
static ssize_t cadet_read(struct file *file, char __user *data, size_t count, loff_t *ppos)
{
struct cadet *dev = video_drvdata(file);
unsigned char readbuf[RDS_BUFFER];
int i = 0;
mutex_lock(&dev->lock);
if (dev->rdsstat == 0)
cadet_start_rds(dev);
if (dev->rdsin == dev->rdsout) {
if (file->f_flags & O_NONBLOCK) {
i = -EWOULDBLOCK;
goto unlock;
}
mutex_unlock(&dev->lock);
interruptible_sleep_on(&dev->read_queue);
mutex_lock(&dev->lock);
}
while (i < count && dev->rdsin != dev->rdsout)
readbuf[i++] = dev->rdsbuf[dev->rdsout++];
if (i && copy_to_user(data, readbuf, i))
i = -EFAULT;
unlock:
mutex_unlock(&dev->lock);
return i;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, "ADS Cadet", sizeof(v->driver));
strlcpy(v->card, "ADS Cadet", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct cadet *dev = video_drvdata(file);
if (v->index)
return -EINVAL;
v->type = V4L2_TUNER_RADIO;
strlcpy(v->name, "Radio", sizeof(v->name));
v->capability = bands[0].capability | bands[1].capability;
v->rangelow = bands[0].rangelow; /* 520 kHz (start of AM band) */
v->rangehigh = bands[1].rangehigh; /* 108.0 MHz (end of FM band) */
if (dev->is_fm_band) {
v->rxsubchans = cadet_getstereo(dev);
outb(3, dev->io);
outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
mdelay(100);
outb(3, dev->io);
if (inb(dev->io + 1) & 0x80)
v->rxsubchans |= V4L2_TUNER_SUB_RDS;
} else {
v->rangelow = 8320; /* 520 kHz */
v->rangehigh = 26400; /* 1650 kHz */
v->rxsubchans = V4L2_TUNER_SUB_MONO;
}
v->audmode = V4L2_TUNER_MODE_STEREO;
v->signal = dev->sigstrength; /* We might need to modify scaling of this */
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_enum_freq_bands(struct file *file, void *priv,
struct v4l2_frequency_band *band)
{
if (band->tuner)
return -EINVAL;
if (band->index >= ARRAY_SIZE(bands))
return -EINVAL;
*band = bands[band->index];
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct cadet *dev = video_drvdata(file);
if (f->tuner)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = dev->curfreq;
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct cadet *dev = video_drvdata(file);
if (f->tuner)
return -EINVAL;
dev->is_fm_band =
f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2;
cadet_setfreq(dev, f->frequency);
return 0;
}
static int cadet_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct cadet *dev = container_of(ctrl->handler, struct cadet, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
outb(7, dev->io); /* Select tuner control */
if (ctrl->val)
outb(0x00, dev->io + 1);
else
outb(0x20, dev->io + 1);
return 0;
}
return -EINVAL;
}
static int cadet_open(struct file *file)
{
struct cadet *dev = video_drvdata(file);
int err;
mutex_lock(&dev->lock);
err = v4l2_fh_open(file);
if (err)
goto fail;
if (v4l2_fh_is_singular_file(file))
init_waitqueue_head(&dev->read_queue);
fail:
mutex_unlock(&dev->lock);
return err;
}
static int cadet_release(struct file *file)
{
struct cadet *dev = video_drvdata(file);
mutex_lock(&dev->lock);
if (v4l2_fh_is_singular_file(file) && dev->rdsstat) {
del_timer_sync(&dev->readtimer);
dev->rdsstat = 0;
}
v4l2_fh_release(file);
mutex_unlock(&dev->lock);
return 0;
}
static unsigned int cadet_poll(struct file *file, struct poll_table_struct *wait)
{
struct cadet *dev = video_drvdata(file);
unsigned long req_events = poll_requested_events(wait);
unsigned int res = v4l2_ctrl_poll(file, wait);
poll_wait(file, &dev->read_queue, wait);
if (dev->rdsstat == 0 && (req_events & (POLLIN | POLLRDNORM))) {
mutex_lock(&dev->lock);
if (dev->rdsstat == 0)
cadet_start_rds(dev);
mutex_unlock(&dev->lock);
}
if (dev->rdsin != dev->rdsout)
res |= POLLIN | POLLRDNORM;
return res;
}
static const struct v4l2_file_operations cadet_fops = {
.owner = THIS_MODULE,
.open = cadet_open,
.release = cadet_release,
.read = cadet_read,
.unlocked_ioctl = video_ioctl2,
.poll = cadet_poll,
};
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_enum_freq_bands = vidioc_enum_freq_bands,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct v4l2_ctrl_ops cadet_ctrl_ops = {
.s_ctrl = cadet_s_ctrl,
};
#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(KERN_INFO "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 void cadet_probe(struct cadet *dev)
{
static int iovals[8] = { 0x330, 0x332, 0x334, 0x336, 0x338, 0x33a, 0x33c, 0x33e };
int i;
for (i = 0; i < 8; i++) {
dev->io = iovals[i];
if (request_region(dev->io, 2, "cadet-probe")) {
cadet_setfreq(dev, bands[1].rangelow);
if (cadet_getfreq(dev) == bands[1].rangelow) {
release_region(dev->io, 2);
return;
}
release_region(dev->io, 2);
}
}
dev->io = -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)
{
struct cadet *dev = &cadet_card;
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
struct v4l2_ctrl_handler *hdl;
int res = -ENODEV;
strlcpy(v4l2_dev->name, "cadet", sizeof(v4l2_dev->name));
mutex_init(&dev->lock);
/* If a probe was requested then probe ISAPnP first (safest) */
if (io < 0)
pnp_register_driver(&cadet_pnp_driver);
dev->io = io;
/* If that fails then probe unsafely if probe is requested */
if (dev->io < 0)
cadet_probe(dev);
/* Else we bail out */
if (dev->io < 0) {
#ifdef MODULE
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x330, 0x332, 0x334,\n");
v4l2_err(v4l2_dev, "0x336, 0x338, 0x33a, 0x33c or 0x33e\n");
#endif
goto fail;
}
if (!request_region(dev->io, 2, "cadet"))
goto fail;
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(dev->io, 2);
v4l2_err(v4l2_dev, "could not register v4l2_device\n");
goto fail;
}
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 2);
v4l2_ctrl_new_std(hdl, &cadet_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_dev->ctrl_handler = hdl;
if (hdl->error) {
res = hdl->error;
v4l2_err(v4l2_dev, "Could not register controls\n");
goto err_hdl;
}
dev->is_fm_band = true;
dev->curfreq = bands[dev->is_fm_band].rangelow;
cadet_setfreq(dev, dev->curfreq);
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
dev->vdev.v4l2_dev = v4l2_dev;
dev->vdev.fops = &cadet_fops;
dev->vdev.ioctl_ops = &cadet_ioctl_ops;
dev->vdev.release = video_device_release_empty;
dev->vdev.lock = &dev->lock;
set_bit(V4L2_FL_USE_FH_PRIO, &dev->vdev.flags);
video_set_drvdata(&dev->vdev, dev);
res = video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr);
if (res < 0)
goto err_hdl;
v4l2_info(v4l2_dev, "ADS Cadet Radio Card at 0x%x\n", dev->io);
return 0;
err_hdl:
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(v4l2_dev);
release_region(dev->io, 2);
fail:
pnp_unregister_driver(&cadet_pnp_driver);
return res;
}
static void __exit cadet_exit(void)
{
struct cadet *dev = &cadet_card;
video_unregister_device(&dev->vdev);
v4l2_ctrl_handler_free(&dev->ctrl_handler);
v4l2_device_unregister(&dev->v4l2_dev);
outb(7, dev->io); /* Mute */
outb(0x00, dev->io + 1);
release_region(dev->io, 2);
pnp_unregister_driver(&cadet_pnp_driver);
}
module_init(cadet_init);
module_exit(cadet_exit);

View File

@@ -0,0 +1,343 @@
/*
* GemTek radio card driver
*
* Copyright 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@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*
* Note: this card seems to swap the left and right audio channels!
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#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 <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/pnp.h>
#include <linux/slab.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include "radio-isa.h"
/*
* 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_VERSION("1.0.0");
/*
* 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
#define GEMTEK_MAX 4
static bool probe = CONFIG_RADIO_GEMTEK_PROBE;
static bool hardmute;
static int io[GEMTEK_MAX] = { [0] = CONFIG_RADIO_GEMTEK_PORT,
[1 ... (GEMTEK_MAX - 1)] = -1 };
static int radio_nr[GEMTEK_MAX] = { [0 ... (GEMTEK_MAX - 1)] = -1 };
module_param(probe, bool, 0444);
MODULE_PARM_DESC(probe, "Enable automatic device probing.");
module_param(hardmute, bool, 0644);
MODULE_PARM_DESC(hardmute, "Enable 'hard muting' by shutting down PLL, may "
"reduce static noise.");
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "Force I/O ports 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_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
/*
* 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 {
struct radio_isa_card isa;
bool 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)
/*
* 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 *gt)
{
struct radio_isa_card *isa = &gt->isa;
int i, bit, q, mute;
mute = gt->muted ? GEMTEK_MT : 0x00;
outb_p(mute | GEMTEK_CE | GEMTEK_DA | GEMTEK_CK, isa->io);
udelay(LONG_DELAY);
for (i = 0, q = gt->bu2614data; i < 32; i++, q >>= 1) {
bit = (q & 1) ? GEMTEK_DA : 0;
outb_p(mute | GEMTEK_CE | bit, isa->io);
udelay(SHORT_DELAY);
outb_p(mute | GEMTEK_CE | bit | GEMTEK_CK, isa->io);
udelay(SHORT_DELAY);
}
outb_p(mute | GEMTEK_DA | GEMTEK_CK, isa->io);
udelay(SHORT_DELAY);
}
/*
* 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;
}
static struct radio_isa_card *gemtek_alloc(void)
{
struct gemtek *gt = kzalloc(sizeof(*gt), GFP_KERNEL);
if (gt)
gt->muted = true;
return gt ? &gt->isa : NULL;
}
/*
* Set FM-frequency.
*/
static int gemtek_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct gemtek *gt = container_of(isa, struct gemtek, isa);
if (hardmute && gt->muted)
return 0;
gemtek_bu2614_set(gt, BU2614_PORT, 0);
gemtek_bu2614_set(gt, BU2614_FMES, 0);
gemtek_bu2614_set(gt, BU2614_SWIN, 0); /* FM-mode */
gemtek_bu2614_set(gt, BU2614_SWAL, 0);
gemtek_bu2614_set(gt, BU2614_FMUN, 1); /* GT bit set */
gemtek_bu2614_set(gt, BU2614_TEST, 0);
gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_STDF_3_125_KHZ);
gemtek_bu2614_set(gt, BU2614_FREQ, gemtek_convfreq(freq));
gemtek_bu2614_transmit(gt);
return 0;
}
/*
* Set mute flag.
*/
static int gemtek_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct gemtek *gt = container_of(isa, struct gemtek, isa);
int i;
gt->muted = mute;
if (hardmute) {
if (!mute)
return gemtek_s_frequency(isa, isa->freq);
/* Turn off PLL, disable data output */
gemtek_bu2614_set(gt, BU2614_PORT, 0);
gemtek_bu2614_set(gt, BU2614_FMES, 0); /* CT bit off */
gemtek_bu2614_set(gt, BU2614_SWIN, 0); /* FM-mode */
gemtek_bu2614_set(gt, BU2614_SWAL, 0);
gemtek_bu2614_set(gt, BU2614_FMUN, 0); /* GT bit off */
gemtek_bu2614_set(gt, BU2614_TEST, 0);
gemtek_bu2614_set(gt, BU2614_STDF, GEMTEK_PLL_OFF);
gemtek_bu2614_set(gt, BU2614_FREQ, 0);
gemtek_bu2614_transmit(gt);
return 0;
}
/* Read bus contents (CE, CK and DA). */
i = inb_p(isa->io);
/* Write it back with mute flag set. */
outb_p((i >> 5) | (mute ? GEMTEK_MT : 0), isa->io);
udelay(SHORT_DELAY);
return 0;
}
static u32 gemtek_g_rxsubchans(struct radio_isa_card *isa)
{
if (inb_p(isa->io) & GEMTEK_NS)
return V4L2_TUNER_SUB_MONO;
return V4L2_TUNER_SUB_STEREO;
}
/*
* Check if requested card acts like GemTek Radio card.
*/
static bool gemtek_probe(struct radio_isa_card *isa, int io)
{
int i, q;
q = inb_p(io); /* 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, io);
udelay(SHORT_DELAY);
if ((inb_p(io) & ~GEMTEK_NS) != (0x17 | (1 << (i + 5))))
return false;
}
outb_p(q >> 5, io); /* Write bus contents back. */
udelay(SHORT_DELAY);
return true;
}
static const struct radio_isa_ops gemtek_ops = {
.alloc = gemtek_alloc,
.probe = gemtek_probe,
.s_mute_volume = gemtek_s_mute_volume,
.s_frequency = gemtek_s_frequency,
.g_rxsubchans = gemtek_g_rxsubchans,
};
static const int gemtek_ioports[] = { 0x20c, 0x30c, 0x24c, 0x34c, 0x248, 0x28c };
#ifdef CONFIG_PNP
static struct pnp_device_id gemtek_pnp_devices[] = {
/* AOpen FX-3D/Pro Radio */
{.id = "ADS7183", .driver_data = 0},
{.id = ""}
};
MODULE_DEVICE_TABLE(pnp, gemtek_pnp_devices);
#endif
static struct radio_isa_driver gemtek_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-gemtek",
},
},
#ifdef CONFIG_PNP
.pnp_driver = {
.name = "radio-gemtek",
.id_table = gemtek_pnp_devices,
.probe = radio_isa_pnp_probe,
.remove = radio_isa_pnp_remove,
},
#endif
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = gemtek_ioports,
.num_of_io_ports = ARRAY_SIZE(gemtek_ioports),
.region_size = 1,
.card = "GemTek Radio",
.ops = &gemtek_ops,
.has_stereo = true,
};
static int __init gemtek_init(void)
{
gemtek_driver.probe = probe;
#ifdef CONFIG_PNP
pnp_register_driver(&gemtek_driver.pnp_driver);
#endif
return isa_register_driver(&gemtek_driver.driver, GEMTEK_MAX);
}
static void __exit gemtek_exit(void)
{
hardmute = 1; /* Turn off PLL */
#ifdef CONFIG_PNP
pnp_unregister_driver(&gemtek_driver.pnp_driver);
#endif
isa_unregister_driver(&gemtek_driver.driver);
}
module_init(gemtek_init);
module_exit(gemtek_exit);

View File

@@ -0,0 +1,394 @@
/*
* Framework for ISA radio drivers.
* This takes care of all the V4L2 scaffolding, allowing the ISA drivers
* to concentrate on the actual hardware operation.
*
* Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include "radio-isa.h"
MODULE_AUTHOR("Hans Verkuil");
MODULE_DESCRIPTION("A framework for ISA radio drivers.");
MODULE_LICENSE("GPL");
#define FREQ_LOW (87U * 16000U)
#define FREQ_HIGH (108U * 16000U)
static int radio_isa_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct radio_isa_card *isa = video_drvdata(file);
strlcpy(v->driver, isa->drv->driver.driver.name, sizeof(v->driver));
strlcpy(v->card, isa->drv->card, sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name);
v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int radio_isa_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct radio_isa_card *isa = video_drvdata(file);
const struct radio_isa_ops *ops = isa->drv->ops;
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = FREQ_LOW;
v->rangehigh = FREQ_HIGH;
v->capability = V4L2_TUNER_CAP_LOW;
if (isa->drv->has_stereo)
v->capability |= V4L2_TUNER_CAP_STEREO;
if (ops->g_rxsubchans)
v->rxsubchans = ops->g_rxsubchans(isa);
else
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->audmode = isa->stereo ? V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
if (ops->g_signal)
v->signal = ops->g_signal(isa);
else
v->signal = (v->rxsubchans & V4L2_TUNER_SUB_STEREO) ?
0xffff : 0;
return 0;
}
static int radio_isa_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct radio_isa_card *isa = video_drvdata(file);
const struct radio_isa_ops *ops = isa->drv->ops;
if (v->index)
return -EINVAL;
if (ops->s_stereo) {
isa->stereo = (v->audmode == V4L2_TUNER_MODE_STEREO);
return ops->s_stereo(isa, isa->stereo);
}
return 0;
}
static int radio_isa_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct radio_isa_card *isa = video_drvdata(file);
u32 freq = f->frequency;
int res;
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
freq = clamp(freq, FREQ_LOW, FREQ_HIGH);
res = isa->drv->ops->s_frequency(isa, freq);
if (res == 0)
isa->freq = freq;
return res;
}
static int radio_isa_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct radio_isa_card *isa = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = isa->freq;
return 0;
}
static int radio_isa_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct radio_isa_card *isa =
container_of(ctrl->handler, struct radio_isa_card, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
return isa->drv->ops->s_mute_volume(isa, ctrl->val,
isa->volume ? isa->volume->val : 0);
}
return -EINVAL;
}
static int radio_isa_log_status(struct file *file, void *priv)
{
struct radio_isa_card *isa = video_drvdata(file);
v4l2_info(&isa->v4l2_dev, "I/O Port = 0x%03x\n", isa->io);
v4l2_ctrl_handler_log_status(&isa->hdl, isa->v4l2_dev.name);
return 0;
}
static const struct v4l2_ctrl_ops radio_isa_ctrl_ops = {
.s_ctrl = radio_isa_s_ctrl,
};
static const struct v4l2_file_operations radio_isa_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops radio_isa_ioctl_ops = {
.vidioc_querycap = radio_isa_querycap,
.vidioc_g_tuner = radio_isa_g_tuner,
.vidioc_s_tuner = radio_isa_s_tuner,
.vidioc_g_frequency = radio_isa_g_frequency,
.vidioc_s_frequency = radio_isa_s_frequency,
.vidioc_log_status = radio_isa_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
int radio_isa_match(struct device *pdev, unsigned int dev)
{
struct radio_isa_driver *drv = pdev->platform_data;
return drv->probe || drv->io_params[dev] >= 0;
}
EXPORT_SYMBOL_GPL(radio_isa_match);
static bool radio_isa_valid_io(const struct radio_isa_driver *drv, int io)
{
int i;
for (i = 0; i < drv->num_of_io_ports; i++)
if (drv->io_ports[i] == io)
return true;
return false;
}
static struct radio_isa_card *radio_isa_alloc(struct radio_isa_driver *drv,
struct device *pdev)
{
struct v4l2_device *v4l2_dev;
struct radio_isa_card *isa = drv->ops->alloc();
if (!isa)
return NULL;
dev_set_drvdata(pdev, isa);
isa->drv = drv;
v4l2_dev = &isa->v4l2_dev;
strlcpy(v4l2_dev->name, dev_name(pdev), sizeof(v4l2_dev->name));
return isa;
}
static int radio_isa_common_probe(struct radio_isa_card *isa,
struct device *pdev,
int radio_nr, unsigned region_size)
{
const struct radio_isa_driver *drv = isa->drv;
const struct radio_isa_ops *ops = drv->ops;
struct v4l2_device *v4l2_dev = &isa->v4l2_dev;
int res;
if (!request_region(isa->io, region_size, v4l2_dev->name)) {
v4l2_err(v4l2_dev, "port 0x%x already in use\n", isa->io);
kfree(isa);
return -EBUSY;
}
res = v4l2_device_register(pdev, v4l2_dev);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
goto err_dev_reg;
}
v4l2_ctrl_handler_init(&isa->hdl, 1);
isa->mute = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
if (drv->max_volume)
isa->volume = v4l2_ctrl_new_std(&isa->hdl, &radio_isa_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, drv->max_volume, 1,
drv->max_volume);
v4l2_dev->ctrl_handler = &isa->hdl;
if (isa->hdl.error) {
res = isa->hdl.error;
v4l2_err(v4l2_dev, "Could not register controls\n");
goto err_hdl;
}
if (drv->max_volume)
v4l2_ctrl_cluster(2, &isa->mute);
v4l2_dev->ctrl_handler = &isa->hdl;
mutex_init(&isa->lock);
isa->vdev.lock = &isa->lock;
strlcpy(isa->vdev.name, v4l2_dev->name, sizeof(isa->vdev.name));
isa->vdev.v4l2_dev = v4l2_dev;
isa->vdev.fops = &radio_isa_fops;
isa->vdev.ioctl_ops = &radio_isa_ioctl_ops;
isa->vdev.release = video_device_release_empty;
set_bit(V4L2_FL_USE_FH_PRIO, &isa->vdev.flags);
video_set_drvdata(&isa->vdev, isa);
isa->freq = FREQ_LOW;
isa->stereo = drv->has_stereo;
if (ops->init)
res = ops->init(isa);
if (!res)
res = v4l2_ctrl_handler_setup(&isa->hdl);
if (!res)
res = ops->s_frequency(isa, isa->freq);
if (!res && ops->s_stereo)
res = ops->s_stereo(isa, isa->stereo);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not setup card\n");
goto err_hdl;
}
res = video_register_device(&isa->vdev, VFL_TYPE_RADIO, radio_nr);
if (res < 0) {
v4l2_err(v4l2_dev, "Could not register device node\n");
goto err_hdl;
}
v4l2_info(v4l2_dev, "Initialized radio card %s on port 0x%03x\n",
drv->card, isa->io);
return 0;
err_hdl:
v4l2_ctrl_handler_free(&isa->hdl);
err_dev_reg:
release_region(isa->io, region_size);
kfree(isa);
return res;
}
static int radio_isa_common_remove(struct radio_isa_card *isa,
unsigned region_size)
{
const struct radio_isa_ops *ops = isa->drv->ops;
ops->s_mute_volume(isa, true, isa->volume ? isa->volume->cur.val : 0);
video_unregister_device(&isa->vdev);
v4l2_ctrl_handler_free(&isa->hdl);
v4l2_device_unregister(&isa->v4l2_dev);
release_region(isa->io, region_size);
v4l2_info(&isa->v4l2_dev, "Removed radio card %s\n", isa->drv->card);
kfree(isa);
return 0;
}
int radio_isa_probe(struct device *pdev, unsigned int dev)
{
struct radio_isa_driver *drv = pdev->platform_data;
const struct radio_isa_ops *ops = drv->ops;
struct v4l2_device *v4l2_dev;
struct radio_isa_card *isa;
isa = radio_isa_alloc(drv, pdev);
if (!isa)
return -ENOMEM;
isa->io = drv->io_params[dev];
v4l2_dev = &isa->v4l2_dev;
if (drv->probe && ops->probe) {
int i;
for (i = 0; i < drv->num_of_io_ports; ++i) {
int io = drv->io_ports[i];
if (request_region(io, drv->region_size, v4l2_dev->name)) {
bool found = ops->probe(isa, io);
release_region(io, drv->region_size);
if (found) {
isa->io = io;
break;
}
}
}
}
if (!radio_isa_valid_io(drv, isa->io)) {
int i;
if (isa->io < 0)
return -ENODEV;
v4l2_err(v4l2_dev, "you must set an I/O address with io=0x%03x",
drv->io_ports[0]);
for (i = 1; i < drv->num_of_io_ports; i++)
printk(KERN_CONT "/0x%03x", drv->io_ports[i]);
printk(KERN_CONT ".\n");
kfree(isa);
return -EINVAL;
}
return radio_isa_common_probe(isa, pdev, drv->radio_nr_params[dev],
drv->region_size);
}
EXPORT_SYMBOL_GPL(radio_isa_probe);
int radio_isa_remove(struct device *pdev, unsigned int dev)
{
struct radio_isa_card *isa = dev_get_drvdata(pdev);
return radio_isa_common_remove(isa, isa->drv->region_size);
}
EXPORT_SYMBOL_GPL(radio_isa_remove);
#ifdef CONFIG_PNP
int radio_isa_pnp_probe(struct pnp_dev *dev, const struct pnp_device_id *dev_id)
{
struct pnp_driver *pnp_drv = to_pnp_driver(dev->dev.driver);
struct radio_isa_driver *drv = container_of(pnp_drv,
struct radio_isa_driver, pnp_driver);
struct radio_isa_card *isa;
if (!pnp_port_valid(dev, 0))
return -ENODEV;
isa = radio_isa_alloc(drv, &dev->dev);
if (!isa)
return -ENOMEM;
isa->io = pnp_port_start(dev, 0);
return radio_isa_common_probe(isa, &dev->dev, drv->radio_nr_params[0],
pnp_port_len(dev, 0));
}
EXPORT_SYMBOL_GPL(radio_isa_pnp_probe);
void radio_isa_pnp_remove(struct pnp_dev *dev)
{
struct radio_isa_card *isa = dev_get_drvdata(&dev->dev);
radio_isa_common_remove(isa, pnp_port_len(dev, 0));
}
EXPORT_SYMBOL_GPL(radio_isa_pnp_remove);
#endif

View File

@@ -0,0 +1,114 @@
/*
* Framework for ISA radio drivers.
* This takes care of all the V4L2 scaffolding, allowing the ISA drivers
* to concentrate on the actual hardware operation.
*
* Copyright (C) 2012 Hans Verkuil <hans.verkuil@cisco.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef _RADIO_ISA_H_
#define _RADIO_ISA_H_
#include <linux/isa.h>
#include <linux/pnp.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
struct radio_isa_driver;
struct radio_isa_ops;
/* Core structure for radio ISA cards */
struct radio_isa_card {
const struct radio_isa_driver *drv;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
struct video_device vdev;
struct mutex lock;
const struct radio_isa_ops *ops;
struct { /* mute/volume cluster */
struct v4l2_ctrl *mute;
struct v4l2_ctrl *volume;
};
/* I/O port */
int io;
/* Card is in stereo audio mode */
bool stereo;
/* Current frequency */
u32 freq;
};
struct radio_isa_ops {
/* Allocate and initialize a radio_isa_card struct */
struct radio_isa_card *(*alloc)(void);
/* Probe whether a card is present at the given port */
bool (*probe)(struct radio_isa_card *isa, int io);
/* Special card initialization can be done here, this is called after
* the standard controls are registered, but before they are setup,
* thus allowing drivers to add their own controls here. */
int (*init)(struct radio_isa_card *isa);
/* Set mute and volume. */
int (*s_mute_volume)(struct radio_isa_card *isa, bool mute, int volume);
/* Set frequency */
int (*s_frequency)(struct radio_isa_card *isa, u32 freq);
/* Set stereo/mono audio mode */
int (*s_stereo)(struct radio_isa_card *isa, bool stereo);
/* Get rxsubchans value for VIDIOC_G_TUNER */
u32 (*g_rxsubchans)(struct radio_isa_card *isa);
/* Get the signal strength for VIDIOC_G_TUNER */
u32 (*g_signal)(struct radio_isa_card *isa);
};
/* Top level structure needed to instantiate the cards */
struct radio_isa_driver {
struct isa_driver driver;
#ifdef CONFIG_PNP
struct pnp_driver pnp_driver;
#endif
const struct radio_isa_ops *ops;
/* The module_param_array with the specified I/O ports */
int *io_params;
/* The module_param_array with the radio_nr values */
int *radio_nr_params;
/* Whether we should probe for possible cards */
bool probe;
/* The list of possible I/O ports */
const int *io_ports;
/* The size of that list */
int num_of_io_ports;
/* The region size to request */
unsigned region_size;
/* The name of the card */
const char *card;
/* Card can capture stereo audio */
bool has_stereo;
/* The maximum volume for the volume control. If 0, then there
is no volume control possible. */
int max_volume;
};
int radio_isa_match(struct device *pdev, unsigned int dev);
int radio_isa_probe(struct device *pdev, unsigned int dev);
int radio_isa_remove(struct device *pdev, unsigned int dev);
#ifdef CONFIG_PNP
int radio_isa_pnp_probe(struct pnp_dev *dev,
const struct pnp_device_id *dev_id);
void radio_isa_pnp_remove(struct pnp_dev *dev);
#endif
#endif

View File

@@ -0,0 +1,434 @@
/*
* Copyright (c) 2012 Hans Verkuil <hverkuil@xs4all.nl>
*
* 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
*/
/* 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-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <linux/usb.h>
#include <linux/mutex.h>
/* driver and module definitions */
MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
MODULE_DESCRIPTION("Keene FM Transmitter driver");
MODULE_LICENSE("GPL");
/* Actually, it advertises itself as a Logitech */
#define USB_KEENE_VENDOR 0x046d
#define USB_KEENE_PRODUCT 0x0a0e
/* Probably USB_TIMEOUT should be modified in module parameter */
#define BUFFER_LENGTH 8
#define USB_TIMEOUT 500
/* Frequency limits in MHz */
#define FREQ_MIN 76U
#define FREQ_MAX 108U
#define FREQ_MUL 16000U
/* USB Device ID List */
static struct usb_device_id usb_keene_device_table[] = {
{USB_DEVICE_AND_INTERFACE_INFO(USB_KEENE_VENDOR, USB_KEENE_PRODUCT,
USB_CLASS_HID, 0, 0) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_keene_device_table);
struct keene_device {
struct usb_device *usbdev;
struct usb_interface *intf;
struct video_device vdev;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
struct mutex lock;
u8 *buffer;
unsigned curfreq;
u8 tx;
u8 pa;
bool stereo;
bool muted;
bool preemph_75_us;
};
static inline struct keene_device *to_keene_dev(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct keene_device, v4l2_dev);
}
/* Set frequency (if non-0), PA, mute and turn on/off the FM transmitter. */
static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play)
{
unsigned short freq_send = freq ? (freq - 76 * 16000) / 800 : 0;
int ret;
radio->buffer[0] = 0x00;
radio->buffer[1] = 0x50;
radio->buffer[2] = (freq_send >> 8) & 0xff;
radio->buffer[3] = freq_send & 0xff;
radio->buffer[4] = radio->pa;
/* If bit 4 is set, then tune to the frequency.
If bit 3 is set, then unmute; if bit 2 is set, then mute.
If bit 1 is set, then enter idle mode; if bit 0 is set,
then enter transit mode.
*/
radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) |
(freq ? 0x10 : 0);
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (ret < 0) {
dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret);
return ret;
}
if (freq)
radio->curfreq = freq;
return 0;
}
/* Set TX, stereo and preemphasis mode (50 us vs 75 us). */
static int keene_cmd_set(struct keene_device *radio)
{
int ret;
radio->buffer[0] = 0x00;
radio->buffer[1] = 0x51;
radio->buffer[2] = radio->tx;
/* If bit 0 is set, then transmit mono, otherwise stereo.
If bit 2 is set, then enable 75 us preemphasis, otherwise
it is 50 us. */
radio->buffer[3] = (!radio->stereo) | (radio->preemph_75_us ? 4 : 0);
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x200, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (ret < 0) {
dev_warn(&radio->vdev.dev, "%s failed (%d)\n", __func__, ret);
return ret;
}
return 0;
}
/* Handle unplugging the device.
* We call video_unregister_device in any case.
* The last function called in this procedure is
* usb_keene_device_release.
*/
static void usb_keene_disconnect(struct usb_interface *intf)
{
struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf));
mutex_lock(&radio->lock);
usb_set_intfdata(intf, NULL);
video_unregister_device(&radio->vdev);
v4l2_device_disconnect(&radio->v4l2_dev);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
static int usb_keene_suspend(struct usb_interface *intf, pm_message_t message)
{
struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf));
return keene_cmd_main(radio, 0, false);
}
static int usb_keene_resume(struct usb_interface *intf)
{
struct keene_device *radio = to_keene_dev(usb_get_intfdata(intf));
mdelay(50);
keene_cmd_set(radio);
keene_cmd_main(radio, radio->curfreq, true);
return 0;
}
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct keene_device *radio = video_drvdata(file);
strlcpy(v->driver, "radio-keene", sizeof(v->driver));
strlcpy(v->card, "Keene FM Transmitter", sizeof(v->card));
usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_MODULATOR;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_g_modulator(struct file *file, void *priv,
struct v4l2_modulator *v)
{
struct keene_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->rangelow = FREQ_MIN * FREQ_MUL;
v->rangehigh = FREQ_MAX * FREQ_MUL;
v->txsubchans = radio->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
return 0;
}
static int vidioc_s_modulator(struct file *file, void *priv,
const struct v4l2_modulator *v)
{
struct keene_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
radio->stereo = (v->txsubchans == V4L2_TUNER_SUB_STEREO);
return keene_cmd_set(radio);
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct keene_device *radio = video_drvdata(file);
unsigned freq = f->frequency;
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
return keene_cmd_main(radio, freq, true);
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct keene_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = radio->curfreq;
return 0;
}
static int keene_s_ctrl(struct v4l2_ctrl *ctrl)
{
static const u8 db2tx[] = {
/* -15, -12, -9, -6, -3, 0 dB */
0x03, 0x13, 0x02, 0x12, 0x22, 0x32,
/* 3, 6, 9, 12, 15, 18 dB */
0x21, 0x31, 0x20, 0x30, 0x40, 0x50
};
struct keene_device *radio =
container_of(ctrl->handler, struct keene_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
radio->muted = ctrl->val;
return keene_cmd_main(radio, 0, true);
case V4L2_CID_TUNE_POWER_LEVEL:
/* To go from dBuV to the register value we apply the
following formula: */
radio->pa = (ctrl->val - 71) * 100 / 62;
return keene_cmd_main(radio, 0, true);
case V4L2_CID_TUNE_PREEMPHASIS:
radio->preemph_75_us = ctrl->val == V4L2_PREEMPHASIS_75_uS;
return keene_cmd_set(radio);
case V4L2_CID_AUDIO_COMPRESSION_GAIN:
radio->tx = db2tx[(ctrl->val - ctrl->minimum) / ctrl->step];
return keene_cmd_set(radio);
}
return -EINVAL;
}
/* File system interface */
static const struct v4l2_file_operations usb_keene_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ctrl_ops keene_ctrl_ops = {
.s_ctrl = keene_s_ctrl,
};
static const struct v4l2_ioctl_ops usb_keene_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_g_modulator = vidioc_g_modulator,
.vidioc_s_modulator = vidioc_s_modulator,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static void usb_keene_video_device_release(struct v4l2_device *v4l2_dev)
{
struct keene_device *radio = to_keene_dev(v4l2_dev);
/* free rest memory */
v4l2_ctrl_handler_free(&radio->hdl);
kfree(radio->buffer);
kfree(radio);
}
/* check if the device is present and register with v4l and usb if it is */
static int usb_keene_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct keene_device *radio;
struct v4l2_ctrl_handler *hdl;
int retval = 0;
/*
* The Keene FM transmitter USB device has the same USB ID as
* the Logitech AudioHub Speaker, but it should ignore the hid.
* Check if the name is that of the Keene device.
* If not, then someone connected the AudioHub and we shouldn't
* attempt to handle this driver.
* For reference: the product name of the AudioHub is
* "AudioHub Speaker".
*/
if (dev->product && strcmp(dev->product, "B-LINK USB Audio "))
return -ENODEV;
radio = kzalloc(sizeof(struct keene_device), GFP_KERNEL);
if (radio)
radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
if (!radio || !radio->buffer) {
dev_err(&intf->dev, "kmalloc for keene_device failed\n");
kfree(radio);
retval = -ENOMEM;
goto err;
}
hdl = &radio->hdl;
v4l2_ctrl_handler_init(hdl, 4);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_MUTE,
0, 1, 1, 0);
v4l2_ctrl_new_std_menu(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_PREEMPHASIS,
V4L2_PREEMPHASIS_75_uS, 1, V4L2_PREEMPHASIS_50_uS);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_TUNE_POWER_LEVEL,
84, 118, 1, 118);
v4l2_ctrl_new_std(hdl, &keene_ctrl_ops, V4L2_CID_AUDIO_COMPRESSION_GAIN,
-15, 18, 3, 0);
radio->pa = 118;
radio->tx = 0x32;
radio->stereo = true;
radio->curfreq = 95.16 * FREQ_MUL;
if (hdl->error) {
retval = hdl->error;
v4l2_ctrl_handler_free(hdl);
goto err_v4l2;
}
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_v4l2;
}
mutex_init(&radio->lock);
radio->v4l2_dev.ctrl_handler = hdl;
radio->v4l2_dev.release = usb_keene_video_device_release;
strlcpy(radio->vdev.name, radio->v4l2_dev.name,
sizeof(radio->vdev.name));
radio->vdev.v4l2_dev = &radio->v4l2_dev;
radio->vdev.fops = &usb_keene_fops;
radio->vdev.ioctl_ops = &usb_keene_ioctl_ops;
radio->vdev.lock = &radio->lock;
radio->vdev.release = video_device_release_empty;
radio->vdev.vfl_dir = VFL_DIR_TX;
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
usb_set_intfdata(intf, &radio->v4l2_dev);
video_set_drvdata(&radio->vdev, radio);
set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1);
if (retval < 0) {
dev_err(&intf->dev, "could not register video device\n");
goto err_vdev;
}
v4l2_ctrl_handler_setup(hdl);
dev_info(&intf->dev, "V4L2 device registered as %s\n",
video_device_node_name(&radio->vdev));
return 0;
err_vdev:
v4l2_device_unregister(&radio->v4l2_dev);
err_v4l2:
kfree(radio->buffer);
kfree(radio);
err:
return retval;
}
/* USB subsystem interface */
static struct usb_driver usb_keene_driver = {
.name = "radio-keene",
.probe = usb_keene_probe,
.disconnect = usb_keene_disconnect,
.id_table = usb_keene_device_table,
.suspend = usb_keene_suspend,
.resume = usb_keene_resume,
.reset_resume = usb_keene_resume,
};
static int __init keene_init(void)
{
int retval = usb_register(&usb_keene_driver);
if (retval)
pr_err(KBUILD_MODNAME
": usb_register failed. Error number %d\n", retval);
return retval;
}
static void __exit keene_exit(void)
{
usb_deregister(&usb_keene_driver);
}
module_init(keene_init);
module_exit(keene_exit);

View File

@@ -0,0 +1,471 @@
/*
* Driver for the MasterKit MA901 USB FM radio. This device plugs
* into the USB port and an analog audio input or headphones, so this thing
* only deals with initialization, frequency setting, volume.
*
* Copyright (c) 2012 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
*/
#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-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <linux/usb.h>
#include <linux/mutex.h>
#define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>"
#define DRIVER_DESC "Masterkit MA901 USB FM radio driver"
#define DRIVER_VERSION "0.0.1"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
#define USB_MA901_VENDOR 0x16c0
#define USB_MA901_PRODUCT 0x05df
/* dev_warn macro with driver name */
#define MA901_DRIVER_NAME "radio-ma901"
#define ma901radio_dev_warn(dev, fmt, arg...) \
dev_warn(dev, MA901_DRIVER_NAME " - " fmt, ##arg)
#define ma901radio_dev_err(dev, fmt, arg...) \
dev_err(dev, MA901_DRIVER_NAME " - " fmt, ##arg)
/* Probably USB_TIMEOUT should be modified in module parameter */
#define BUFFER_LENGTH 8
#define USB_TIMEOUT 500
#define FREQ_MIN 87.5
#define FREQ_MAX 108.0
#define FREQ_MUL 16000
#define MA901_VOLUME_MAX 16
#define MA901_VOLUME_MIN 0
/* Commands that device should understand
* List isn't full and will be updated with implementation of new functions
*/
#define MA901_RADIO_SET_FREQ 0x03
#define MA901_RADIO_SET_VOLUME 0x04
#define MA901_RADIO_SET_MONO_STEREO 0x05
/* Comfortable defines for ma901radio_set_stereo */
#define MA901_WANT_STEREO 0x50
#define MA901_WANT_MONO 0xd0
/* module parameter */
static int radio_nr = -1;
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "Radio file number");
/* Data for one (physical) device */
struct ma901radio_device {
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
struct video_device vdev;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
u8 *buffer;
struct mutex lock; /* buffer locking */
int curfreq;
u16 volume;
int stereo;
bool muted;
};
static inline struct ma901radio_device *to_ma901radio_dev(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct ma901radio_device, v4l2_dev);
}
/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
static int ma901radio_set_freq(struct ma901radio_device *radio, int freq)
{
unsigned int freq_send = 0x300 + (freq >> 5) / 25;
int retval;
radio->buffer[0] = 0x0a;
radio->buffer[1] = MA901_RADIO_SET_FREQ;
radio->buffer[2] = ((freq_send >> 8) & 0xff) + 0x80;
radio->buffer[3] = freq_send & 0xff;
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x0300, 0,
radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (retval < 0)
return retval;
radio->curfreq = freq;
return 0;
}
static int ma901radio_set_volume(struct ma901radio_device *radio, u16 vol_to_set)
{
int retval;
radio->buffer[0] = 0x0a;
radio->buffer[1] = MA901_RADIO_SET_VOLUME;
radio->buffer[2] = 0xc2;
radio->buffer[3] = vol_to_set + 0x20;
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x0300, 0,
radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (retval < 0)
return retval;
radio->volume = vol_to_set;
return retval;
}
static int ma901_set_stereo(struct ma901radio_device *radio, u8 stereo)
{
int retval;
radio->buffer[0] = 0x0a;
radio->buffer[1] = MA901_RADIO_SET_MONO_STEREO;
radio->buffer[2] = stereo;
radio->buffer[3] = 0x00;
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
radio->buffer[7] = 0x00;
retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
9, 0x21, 0x0300, 0,
radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
if (retval < 0)
return retval;
if (stereo == MA901_WANT_STEREO)
radio->stereo = V4L2_TUNER_MODE_STEREO;
else
radio->stereo = V4L2_TUNER_MODE_MONO;
return retval;
}
/* Handle unplugging the device.
* We call video_unregister_device in any case.
* The last function called in this procedure is
* usb_ma901radio_device_release.
*/
static void usb_ma901radio_disconnect(struct usb_interface *intf)
{
struct ma901radio_device *radio = to_ma901radio_dev(usb_get_intfdata(intf));
mutex_lock(&radio->lock);
video_unregister_device(&radio->vdev);
usb_set_intfdata(intf, NULL);
v4l2_device_disconnect(&radio->v4l2_dev);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
/* vidioc_querycap - query device capabilities */
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct ma901radio_device *radio = video_drvdata(file);
strlcpy(v->driver, "radio-ma901", sizeof(v->driver));
strlcpy(v->card, "Masterkit MA901 USB FM Radio", sizeof(v->card));
usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
/* vidioc_g_tuner - get tuner attributes */
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct ma901radio_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
v->signal = 0;
/* TODO: the same words like in _probe() goes here.
* When receiving of stats will be implemented then we can call
* ma901radio_get_stat().
* retval = ma901radio_get_stat(radio, &is_stereo, &v->signal);
*/
strcpy(v->name, "FM");
v->type = V4L2_TUNER_RADIO;
v->rangelow = FREQ_MIN * FREQ_MUL;
v->rangehigh = FREQ_MAX * FREQ_MUL;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
/* v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO; */
v->audmode = radio->stereo ?
V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
return 0;
}
/* vidioc_s_tuner - set tuner attributes */
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct ma901radio_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
/* mono/stereo selector */
switch (v->audmode) {
case V4L2_TUNER_MODE_MONO:
return ma901_set_stereo(radio, MA901_WANT_MONO);
default:
return ma901_set_stereo(radio, MA901_WANT_STEREO);
}
}
/* vidioc_s_frequency - set tuner radio frequency */
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct ma901radio_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
return ma901radio_set_freq(radio, clamp_t(unsigned, f->frequency,
FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL));
}
/* vidioc_g_frequency - get tuner radio frequency */
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct ma901radio_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->frequency = radio->curfreq;
return 0;
}
static int usb_ma901radio_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct ma901radio_device *radio =
container_of(ctrl->handler, struct ma901radio_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME: /* set volume */
return ma901radio_set_volume(radio, (u16)ctrl->val);
}
return -EINVAL;
}
/* TODO: Should we really need to implement suspend and resume functions?
* Radio has it's own memory and will continue playing if power is present
* on usb port and on resume it will start to play again based on freq, volume
* values in device memory.
*/
static int usb_ma901radio_suspend(struct usb_interface *intf, pm_message_t message)
{
return 0;
}
static int usb_ma901radio_resume(struct usb_interface *intf)
{
return 0;
}
static const struct v4l2_ctrl_ops usb_ma901radio_ctrl_ops = {
.s_ctrl = usb_ma901radio_s_ctrl,
};
/* File system interface */
static const struct v4l2_file_operations usb_ma901radio_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops usb_ma901radio_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_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static void usb_ma901radio_release(struct v4l2_device *v4l2_dev)
{
struct ma901radio_device *radio = to_ma901radio_dev(v4l2_dev);
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->buffer);
kfree(radio);
}
/* check if the device is present and register with v4l and usb if it is */
static int usb_ma901radio_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct ma901radio_device *radio;
int retval = 0;
/* Masterkit MA901 usb radio has the same USB ID as many others
* Atmel V-USB devices. Let's make additional checks to be sure
* that this is our device.
*/
if (dev->product && dev->manufacturer &&
(strncmp(dev->product, "MA901", 5) != 0
|| strncmp(dev->manufacturer, "www.masterkit.ru", 16) != 0))
return -ENODEV;
radio = kzalloc(sizeof(struct ma901radio_device), GFP_KERNEL);
if (!radio) {
dev_err(&intf->dev, "kzalloc for ma901radio_device failed\n");
retval = -ENOMEM;
goto err;
}
radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
if (!radio->buffer) {
dev_err(&intf->dev, "kmalloc for radio->buffer failed\n");
retval = -ENOMEM;
goto err_nobuf;
}
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_v4l2;
}
v4l2_ctrl_handler_init(&radio->hdl, 1);
/* TODO:It looks like this radio doesn't have mute/unmute control
* and windows program just emulate it using volume control.
* Let's plan to do the same in this driver.
*
* v4l2_ctrl_new_std(&radio->hdl, &usb_ma901radio_ctrl_ops,
* V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
*/
v4l2_ctrl_new_std(&radio->hdl, &usb_ma901radio_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, MA901_VOLUME_MIN,
MA901_VOLUME_MAX, 1, MA901_VOLUME_MAX);
if (radio->hdl.error) {
retval = radio->hdl.error;
dev_err(&intf->dev, "couldn't register control\n");
goto err_ctrl;
}
mutex_init(&radio->lock);
radio->v4l2_dev.ctrl_handler = &radio->hdl;
radio->v4l2_dev.release = usb_ma901radio_release;
strlcpy(radio->vdev.name, radio->v4l2_dev.name,
sizeof(radio->vdev.name));
radio->vdev.v4l2_dev = &radio->v4l2_dev;
radio->vdev.fops = &usb_ma901radio_fops;
radio->vdev.ioctl_ops = &usb_ma901radio_ioctl_ops;
radio->vdev.release = video_device_release_empty;
radio->vdev.lock = &radio->lock;
set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
usb_set_intfdata(intf, &radio->v4l2_dev);
radio->curfreq = 95.21 * FREQ_MUL;
video_set_drvdata(&radio->vdev, radio);
/* TODO: we can get some statistics (freq, volume) from device
* but it's not implemented yet. After insertion in usb-port radio
* setups frequency and starts playing without any initialization.
* So we don't call usb_ma901radio_init/get_stat() here.
* retval = usb_ma901radio_init(radio);
*/
retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO,
radio_nr);
if (retval < 0) {
dev_err(&intf->dev, "could not register video device\n");
goto err_vdev;
}
return 0;
err_vdev:
v4l2_ctrl_handler_free(&radio->hdl);
err_ctrl:
v4l2_device_unregister(&radio->v4l2_dev);
err_v4l2:
kfree(radio->buffer);
err_nobuf:
kfree(radio);
err:
return retval;
}
/* USB Device ID List */
static struct usb_device_id usb_ma901radio_device_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(USB_MA901_VENDOR, USB_MA901_PRODUCT,
USB_CLASS_HID, 0, 0) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, usb_ma901radio_device_table);
/* USB subsystem interface */
static struct usb_driver usb_ma901radio_driver = {
.name = MA901_DRIVER_NAME,
.probe = usb_ma901radio_probe,
.disconnect = usb_ma901radio_disconnect,
.suspend = usb_ma901radio_suspend,
.resume = usb_ma901radio_resume,
.reset_resume = usb_ma901radio_resume,
.id_table = usb_ma901radio_device_table,
};
module_usb_driver(usb_ma901radio_driver);

View File

@@ -0,0 +1,214 @@
/*
* 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.nxp.com/acrobat_download2/expired_datasheets/TEA5757_5759_3.pdf
*
*
* 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 <linux/mutex.h>
#include <linux/pci.h>
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <sound/tea575x-tuner.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
MODULE_AUTHOR("Dimitromanolakis Apostolos, apdim@grecian.net");
MODULE_DESCRIPTION("Radio driver for the Guillemot Maxi Radio FM2000.");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");
static int radio_nr = -1;
module_param(radio_nr, int, 0644);
MODULE_PARM_DESC(radio_nr, "Radio device number");
/* TEA5757 pin mappings */
static const int clk = 1, data = 2, wren = 4, mo_st = 8, power = 16;
static atomic_t maxiradio_instance = ATOMIC_INIT(0);
#define PCI_VENDOR_ID_GUILLEMOT 0x5046
#define PCI_DEVICE_ID_GUILLEMOT_MAXIRADIO 0x1001
struct maxiradio
{
struct snd_tea575x tea;
struct v4l2_device v4l2_dev;
struct pci_dev *pdev;
u16 io; /* base of radio io */
};
static inline struct maxiradio *to_maxiradio(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct maxiradio, v4l2_dev);
}
static void maxiradio_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
{
struct maxiradio *dev = tea->private_data;
u8 bits = 0;
bits |= (pins & TEA575X_DATA) ? data : 0;
bits |= (pins & TEA575X_CLK) ? clk : 0;
bits |= (pins & TEA575X_WREN) ? wren : 0;
bits |= power;
outb(bits, dev->io);
}
/* Note: this card cannot read out the data of the shift registers,
only the mono/stereo pin works. */
static u8 maxiradio_tea575x_get_pins(struct snd_tea575x *tea)
{
struct maxiradio *dev = tea->private_data;
u8 bits = inb(dev->io);
return ((bits & data) ? TEA575X_DATA : 0) |
((bits & mo_st) ? TEA575X_MOST : 0);
}
static void maxiradio_tea575x_set_direction(struct snd_tea575x *tea, bool output)
{
}
static struct snd_tea575x_ops maxiradio_tea_ops = {
.set_pins = maxiradio_tea575x_set_pins,
.get_pins = maxiradio_tea575x_get_pins,
.set_direction = maxiradio_tea575x_set_direction,
};
static int maxiradio_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
struct maxiradio *dev;
struct v4l2_device *v4l2_dev;
int retval = -ENOMEM;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (dev == NULL) {
dev_err(&pdev->dev, "not enough memory\n");
return -ENOMEM;
}
v4l2_dev = &dev->v4l2_dev;
v4l2_device_set_name(v4l2_dev, "maxiradio", &maxiradio_instance);
retval = v4l2_device_register(&pdev->dev, v4l2_dev);
if (retval < 0) {
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
goto errfr;
}
dev->tea.private_data = dev;
dev->tea.ops = &maxiradio_tea_ops;
/* The data pin cannot be read. This may be a hardware limitation, or
we just don't know how to read it. */
dev->tea.cannot_read_data = true;
dev->tea.v4l2_dev = v4l2_dev;
dev->tea.radio_nr = radio_nr;
strlcpy(dev->tea.card, "Maxi Radio FM2000", sizeof(dev->tea.card));
snprintf(dev->tea.bus_info, sizeof(dev->tea.bus_info),
"PCI:%s", pci_name(pdev));
retval = -ENODEV;
if (!request_region(pci_resource_start(pdev, 0),
pci_resource_len(pdev, 0), v4l2_dev->name)) {
dev_err(&pdev->dev, "can't reserve I/O ports\n");
goto err_hdl;
}
if (pci_enable_device(pdev))
goto err_out_free_region;
dev->io = pci_resource_start(pdev, 0);
if (snd_tea575x_init(&dev->tea, THIS_MODULE)) {
printk(KERN_ERR "radio-maxiradio: Unable to detect TEA575x tuner\n");
goto err_out_free_region;
}
return 0;
err_out_free_region:
release_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0));
err_hdl:
v4l2_device_unregister(v4l2_dev);
errfr:
kfree(dev);
return retval;
}
static void maxiradio_remove(struct pci_dev *pdev)
{
struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev);
struct maxiradio *dev = to_maxiradio(v4l2_dev);
snd_tea575x_exit(&dev->tea);
/* Turn off power */
outb(0, dev->io);
v4l2_device_unregister(v4l2_dev);
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_probe,
.remove = maxiradio_remove,
};
static int __init maxiradio_init(void)
{
return pci_register_driver(&maxiradio_driver);
}
static void __exit maxiradio_exit(void)
{
pci_unregister_driver(&maxiradio_driver);
}
module_init(maxiradio_init);
module_exit(maxiradio_exit);

View File

@@ -0,0 +1,245 @@
/* Miro PCM20 radio driver for Linux radio support
* (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
* Thanks to Norberto Pellici for the ACI device interface specification
* The API part is based on the radiotrack driver by M. Kirkwood
* This driver relies on the aci mixer provided by the snd-miro
* ALSA driver.
* Look there for further info...
*/
/* What ever you think about the ACI, version 0x07 is not very well!
* I can't get frequency, 'tuner status', 'tuner flags' or mute/mono
* conditions... Robert
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/videodev2.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <sound/aci.h>
static int radio_nr = -1;
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)");
struct pcm20 {
struct v4l2_device v4l2_dev;
struct video_device vdev;
struct v4l2_ctrl_handler ctrl_handler;
unsigned long freq;
u32 audmode;
struct snd_miro_aci *aci;
struct mutex lock;
};
static struct pcm20 pcm20_card = {
.freq = 87 * 16000,
.audmode = V4L2_TUNER_MODE_STEREO,
};
static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq)
{
unsigned char freql;
unsigned char freqh;
struct snd_miro_aci *aci = dev->aci;
freq /= 160;
if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0))
freq /= 10; /* I don't know exactly which version
* needs this hack */
freql = freq & 0xff;
freqh = freq >> 8;
return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh);
}
static const struct v4l2_file_operations pcm20_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.poll = v4l2_ctrl_poll,
.release = v4l2_fh_release,
.unlocked_ioctl = video_ioctl2,
};
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct pcm20 *dev = video_drvdata(file);
strlcpy(v->driver, "Miro PCM20", sizeof(v->driver));
strlcpy(v->card, "Miro PCM20", sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name);
v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct pcm20 *dev = video_drvdata(file);
int res;
if (v->index)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = 87*16000;
v->rangehigh = 108*16000;
res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1);
v->signal = (res & 0x80) ? 0 : 0xffff;
/* Note: stereo detection does not work if the audio is muted,
it will default to mono in that case. */
res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1);
v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO :
V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
v->audmode = dev->audmode;
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct pcm20 *dev = video_drvdata(file);
if (v->index)
return -EINVAL;
if (v->audmode > V4L2_TUNER_MODE_STEREO)
dev->audmode = V4L2_TUNER_MODE_STEREO;
else
dev->audmode = v->audmode;
snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
dev->audmode == V4L2_TUNER_MODE_MONO, -1);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct pcm20 *dev = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = dev->freq;
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct pcm20 *dev = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U);
pcm20_setfreq(dev, dev->freq);
return 0;
}
static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1);
return 0;
}
return -EINVAL;
}
static const struct v4l2_ioctl_ops pcm20_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_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct v4l2_ctrl_ops pcm20_ctrl_ops = {
.s_ctrl = pcm20_s_ctrl,
};
static int __init pcm20_init(void)
{
struct pcm20 *dev = &pcm20_card;
struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
struct v4l2_ctrl_handler *hdl;
int res;
dev->aci = snd_aci_get_aci();
if (dev->aci == NULL) {
v4l2_err(v4l2_dev,
"you must load the snd-miro driver first!\n");
return -ENODEV;
}
strlcpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name));
mutex_init(&dev->lock);
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
v4l2_err(v4l2_dev, "could not register v4l2_device\n");
return -EINVAL;
}
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 1);
v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_dev->ctrl_handler = hdl;
if (hdl->error) {
res = hdl->error;
v4l2_err(v4l2_dev, "Could not register control\n");
goto err_hdl;
}
strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
dev->vdev.v4l2_dev = v4l2_dev;
dev->vdev.fops = &pcm20_fops;
dev->vdev.ioctl_ops = &pcm20_ioctl_ops;
dev->vdev.release = video_device_release_empty;
dev->vdev.lock = &dev->lock;
set_bit(V4L2_FL_USE_FH_PRIO, &dev->vdev.flags);
video_set_drvdata(&dev->vdev, dev);
snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
dev->audmode == V4L2_TUNER_MODE_MONO, -1);
pcm20_setfreq(dev, dev->freq);
if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0)
goto err_hdl;
v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n");
return 0;
err_hdl:
v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(v4l2_dev);
return -EINVAL;
}
MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt");
MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
MODULE_LICENSE("GPL");
static void __exit pcm20_cleanup(void)
{
struct pcm20 *dev = &pcm20_card;
video_unregister_device(&dev->vdev);
snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1);
v4l2_ctrl_handler_free(&dev->ctrl_handler);
v4l2_device_unregister(&dev->v4l2_dev);
}
module_init(pcm20_init);
module_exit(pcm20_cleanup);

View File

@@ -0,0 +1,614 @@
/*
* 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 and contributors 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 through using their
* achievements (specifications given).
* Also, Faidon Liambotis <paravoid@debian.org> wrote nice driver for this radio
* in 2007. He allowed to use his driver to improve current mr800 radio driver.
* http://kerneltrap.org/mailarchive/linux-usb-devel/2007/10/11/342492
*
* Version 0.01: First working version.
* It's required to blacklist AverMedia USB Radio
* in usbhid/hid-quirks.c
* Version 0.10: A lot of cleanups and fixes: unpluging the device,
* few mutex locks were added, codinstyle issues, etc.
* Added stereo support. Thanks to
* Douglas Schilling Landgraf <dougsland@gmail.com> and
* David Ellingsworth <david@identd.dyndns.org>
* for discussion, help and support.
* Version 0.11: Converted to v4l2_device.
*
* Many things to do:
* - Correct power management of device (suspend & resume)
* - Add code for scanning and smooth tuning
* - 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-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <linux/usb.h>
#include <linux/mutex.h>
/* 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.1.2"
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
#define USB_AMRADIO_VENDOR 0x07ca
#define USB_AMRADIO_PRODUCT 0xb800
/* dev_warn macro with driver name */
#define MR800_DRIVER_NAME "radio-mr800"
#define amradio_dev_warn(dev, fmt, arg...) \
dev_warn(dev, MR800_DRIVER_NAME " - " fmt, ##arg)
#define amradio_dev_err(dev, fmt, arg...) \
dev_err(dev, MR800_DRIVER_NAME " - " fmt, ##arg)
/* 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
/*
* Commands that device should understand
* List isn't full and will be updated with implementation of new functions
*/
#define AMRADIO_SET_FREQ 0xa4
#define AMRADIO_GET_READY_FLAG 0xa5
#define AMRADIO_GET_SIGNAL 0xa7
#define AMRADIO_GET_FREQ 0xa8
#define AMRADIO_SET_SEARCH_UP 0xa9
#define AMRADIO_SET_SEARCH_DOWN 0xaa
#define AMRADIO_SET_MUTE 0xab
#define AMRADIO_SET_RIGHT_MUTE 0xac
#define AMRADIO_SET_LEFT_MUTE 0xad
#define AMRADIO_SET_MONO 0xae
#define AMRADIO_SET_SEARCH_LVL 0xb0
#define AMRADIO_STOP_SEARCH 0xb1
/* Comfortable defines for amradio_set_stereo */
#define WANT_STEREO 0x00
#define WANT_MONO 0x01
/* module parameter */
static int radio_nr = -1;
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "Radio Nr");
/* Data for one (physical) device */
struct amradio_device {
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
struct video_device vdev;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler hdl;
u8 *buffer;
struct mutex lock; /* buffer locking */
int curfreq;
int stereo;
int muted;
};
static inline struct amradio_device *to_amradio_dev(struct v4l2_device *v4l2_dev)
{
return container_of(v4l2_dev, struct amradio_device, v4l2_dev);
}
static int amradio_send_cmd(struct amradio_device *radio, u8 cmd, u8 arg,
u8 *extra, u8 extralen, bool reply)
{
int retval;
int size;
radio->buffer[0] = 0x00;
radio->buffer[1] = 0x55;
radio->buffer[2] = 0xaa;
radio->buffer[3] = extralen;
radio->buffer[4] = cmd;
radio->buffer[5] = arg;
radio->buffer[6] = 0x00;
radio->buffer[7] = extra || reply ? 8 : 0;
retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT);
if (retval < 0 || size != BUFFER_LENGTH) {
if (video_is_registered(&radio->vdev))
amradio_dev_warn(&radio->vdev.dev,
"cmd %02x failed\n", cmd);
return retval ? retval : -EIO;
}
if (!extra && !reply)
return 0;
if (extra) {
memcpy(radio->buffer, extra, extralen);
memset(radio->buffer + extralen, 0, 8 - extralen);
retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT);
} else {
memset(radio->buffer, 0, 8);
retval = usb_bulk_msg(radio->usbdev, usb_rcvbulkpipe(radio->usbdev, 0x81),
radio->buffer, BUFFER_LENGTH, &size, USB_TIMEOUT);
}
if (retval == 0 && size == BUFFER_LENGTH)
return 0;
if (video_is_registered(&radio->vdev) && cmd != AMRADIO_GET_READY_FLAG)
amradio_dev_warn(&radio->vdev.dev, "follow-up to cmd %02x failed\n", cmd);
return retval ? retval : -EIO;
}
/* switch on/off the radio. Send 8 bytes to device */
static int amradio_set_mute(struct amradio_device *radio, bool mute)
{
int ret = amradio_send_cmd(radio,
AMRADIO_SET_MUTE, mute, NULL, 0, false);
if (!ret)
radio->muted = mute;
return ret;
}
/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
static int amradio_set_freq(struct amradio_device *radio, int freq)
{
unsigned short freq_send;
u8 buf[3];
int retval;
/* we need to be sure that frequency isn't out of range */
freq = clamp_t(unsigned, freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
freq_send = 0x10 + (freq >> 3) / 25;
/* frequency is calculated from freq_send and placed in first 2 bytes */
buf[0] = (freq_send >> 8) & 0xff;
buf[1] = freq_send & 0xff;
buf[2] = 0x01;
retval = amradio_send_cmd(radio, AMRADIO_SET_FREQ, 0, buf, 3, false);
if (retval)
return retval;
radio->curfreq = freq;
msleep(40);
return 0;
}
static int amradio_set_stereo(struct amradio_device *radio, bool stereo)
{
int ret = amradio_send_cmd(radio,
AMRADIO_SET_MONO, !stereo, NULL, 0, false);
if (!ret)
radio->stereo = stereo;
return ret;
}
static int amradio_get_stat(struct amradio_device *radio, bool *is_stereo, u32 *signal)
{
int ret = amradio_send_cmd(radio,
AMRADIO_GET_SIGNAL, 0, NULL, 0, true);
if (ret)
return ret;
*is_stereo = radio->buffer[2] >> 7;
*signal = (radio->buffer[3] & 0xf0) << 8;
return 0;
}
/* Handle unplugging the device.
* We call video_unregister_device in any case.
* The last function called in this procedure is
* usb_amradio_device_release.
*/
static void usb_amradio_disconnect(struct usb_interface *intf)
{
struct amradio_device *radio = to_amradio_dev(usb_get_intfdata(intf));
mutex_lock(&radio->lock);
video_unregister_device(&radio->vdev);
amradio_set_mute(radio, true);
usb_set_intfdata(intf, NULL);
v4l2_device_disconnect(&radio->v4l2_dev);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
/* vidioc_querycap - query device capabilities */
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct amradio_device *radio = video_drvdata(file);
strlcpy(v->driver, "radio-mr800", sizeof(v->driver));
strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card));
usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_RADIO | V4L2_CAP_TUNER |
V4L2_CAP_HW_FREQ_SEEK;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
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_drvdata(file);
bool is_stereo = false;
int retval;
if (v->index > 0)
return -EINVAL;
v->signal = 0;
retval = amradio_get_stat(radio, &is_stereo, &v->signal);
if (retval)
return retval;
strcpy(v->name, "FM");
v->type = V4L2_TUNER_RADIO;
v->rangelow = FREQ_MIN * FREQ_MUL;
v->rangehigh = FREQ_MAX * FREQ_MUL;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_HWSEEK_WRAP;
v->rxsubchans = is_stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
v->audmode = radio->stereo ?
V4L2_TUNER_MODE_STEREO : V4L2_TUNER_MODE_MONO;
return 0;
}
/* vidioc_s_tuner - set tuner attributes */
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct amradio_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
/* mono/stereo selector */
switch (v->audmode) {
case V4L2_TUNER_MODE_MONO:
return amradio_set_stereo(radio, WANT_MONO);
default:
return amradio_set_stereo(radio, WANT_STEREO);
}
}
/* vidioc_s_frequency - set tuner radio frequency */
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct amradio_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
return amradio_set_freq(radio, f->frequency);
}
/* 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_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = radio->curfreq;
return 0;
}
static int vidioc_s_hw_freq_seek(struct file *file, void *priv,
const struct v4l2_hw_freq_seek *seek)
{
static u8 buf[8] = {
0x3d, 0x32, 0x0f, 0x08, 0x3d, 0x32, 0x0f, 0x08
};
struct amradio_device *radio = video_drvdata(file);
unsigned long timeout;
int retval;
if (seek->tuner != 0 || !seek->wrap_around)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
retval = amradio_send_cmd(radio,
AMRADIO_SET_SEARCH_LVL, 0, buf, 8, false);
if (retval)
return retval;
amradio_set_freq(radio, radio->curfreq);
retval = amradio_send_cmd(radio,
seek->seek_upward ? AMRADIO_SET_SEARCH_UP : AMRADIO_SET_SEARCH_DOWN,
0, NULL, 0, false);
if (retval)
return retval;
timeout = jiffies + msecs_to_jiffies(30000);
for (;;) {
if (time_after(jiffies, timeout)) {
retval = -ENODATA;
break;
}
if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {
retval = -ERESTARTSYS;
break;
}
retval = amradio_send_cmd(radio, AMRADIO_GET_READY_FLAG,
0, NULL, 0, true);
if (retval)
continue;
amradio_send_cmd(radio, AMRADIO_GET_FREQ, 0, NULL, 0, true);
if (radio->buffer[1] || radio->buffer[2]) {
/* To check: sometimes radio->curfreq is set to out of range value */
radio->curfreq = (radio->buffer[1] << 8) | radio->buffer[2];
radio->curfreq = (radio->curfreq - 0x10) * 200;
amradio_send_cmd(radio, AMRADIO_STOP_SEARCH,
0, NULL, 0, false);
amradio_set_freq(radio, radio->curfreq);
retval = 0;
break;
}
}
amradio_send_cmd(radio, AMRADIO_STOP_SEARCH, 0, NULL, 0, false);
amradio_set_freq(radio, radio->curfreq);
return retval;
}
static int usb_amradio_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct amradio_device *radio =
container_of(ctrl->handler, struct amradio_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
return amradio_set_mute(radio, ctrl->val);
}
return -EINVAL;
}
static int usb_amradio_init(struct amradio_device *radio)
{
int retval;
retval = amradio_set_mute(radio, true);
if (retval)
goto out_err;
retval = amradio_set_stereo(radio, true);
if (retval)
goto out_err;
retval = amradio_set_freq(radio, radio->curfreq);
if (retval)
goto out_err;
return 0;
out_err:
amradio_dev_err(&radio->vdev.dev, "initialization failed\n");
return retval;
}
/* 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 = to_amradio_dev(usb_get_intfdata(intf));
mutex_lock(&radio->lock);
if (!radio->muted) {
amradio_set_mute(radio, true);
radio->muted = false;
}
mutex_unlock(&radio->lock);
dev_info(&intf->dev, "going into suspend..\n");
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 = to_amradio_dev(usb_get_intfdata(intf));
mutex_lock(&radio->lock);
amradio_set_stereo(radio, radio->stereo);
amradio_set_freq(radio, radio->curfreq);
if (!radio->muted)
amradio_set_mute(radio, false);
mutex_unlock(&radio->lock);
dev_info(&intf->dev, "coming out of suspend..\n");
return 0;
}
static const struct v4l2_ctrl_ops usb_amradio_ctrl_ops = {
.s_ctrl = usb_amradio_s_ctrl,
};
/* File system interface */
static const struct v4l2_file_operations usb_amradio_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
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_s_hw_freq_seek = vidioc_s_hw_freq_seek,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static void usb_amradio_release(struct v4l2_device *v4l2_dev)
{
struct amradio_device *radio = to_amradio_dev(v4l2_dev);
/* free rest memory */
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->buffer);
kfree(radio);
}
/* 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;
int retval = 0;
radio = kzalloc(sizeof(struct amradio_device), GFP_KERNEL);
if (!radio) {
dev_err(&intf->dev, "kmalloc for amradio_device failed\n");
retval = -ENOMEM;
goto err;
}
radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
if (!radio->buffer) {
dev_err(&intf->dev, "kmalloc for radio->buffer failed\n");
retval = -ENOMEM;
goto err_nobuf;
}
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_v4l2;
}
v4l2_ctrl_handler_init(&radio->hdl, 1);
v4l2_ctrl_new_std(&radio->hdl, &usb_amradio_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
if (radio->hdl.error) {
retval = radio->hdl.error;
dev_err(&intf->dev, "couldn't register control\n");
goto err_ctrl;
}
mutex_init(&radio->lock);
radio->v4l2_dev.ctrl_handler = &radio->hdl;
radio->v4l2_dev.release = usb_amradio_release;
strlcpy(radio->vdev.name, radio->v4l2_dev.name,
sizeof(radio->vdev.name));
radio->vdev.v4l2_dev = &radio->v4l2_dev;
radio->vdev.fops = &usb_amradio_fops;
radio->vdev.ioctl_ops = &usb_amradio_ioctl_ops;
radio->vdev.release = video_device_release_empty;
radio->vdev.lock = &radio->lock;
set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
usb_set_intfdata(intf, &radio->v4l2_dev);
radio->curfreq = 95.16 * FREQ_MUL;
video_set_drvdata(&radio->vdev, radio);
retval = usb_amradio_init(radio);
if (retval)
goto err_vdev;
retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO,
radio_nr);
if (retval < 0) {
dev_err(&intf->dev, "could not register video device\n");
goto err_vdev;
}
return 0;
err_vdev:
v4l2_ctrl_handler_free(&radio->hdl);
err_ctrl:
v4l2_device_unregister(&radio->v4l2_dev);
err_v4l2:
kfree(radio->buffer);
err_nobuf:
kfree(radio);
err:
return retval;
}
/* 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 = MR800_DRIVER_NAME,
.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,
};
module_usb_driver(usb_amradio_driver);

View File

@@ -0,0 +1,141 @@
/*
* RadioTrack II driver
* Copyright 1998 Ben Pfaff
*
* Based on RadioTrack I/RadioReveal (C) 1997 M. Kirkwood
* Converted to new API by Alan Cox <alan@lxorguk.ukuu.org.uk>
* Various bugfixes and enhancements by Russell Kroll <rkroll@exploits.org>
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*
* Fully tested with actual hardware and the v4l2-compliance tool.
*/
#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 <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("Ben Pfaff");
MODULE_DESCRIPTION("A driver for the RadioTrack II radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_RTRACK2_PORT
#define CONFIG_RADIO_RTRACK2_PORT -1
#endif
#define RTRACK2_MAX 2
static int io[RTRACK2_MAX] = { [0] = CONFIG_RADIO_RTRACK2_PORT,
[1 ... (RTRACK2_MAX - 1)] = -1 };
static int radio_nr[RTRACK2_MAX] = { [0 ... (RTRACK2_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the RadioTrack card (0x20f or 0x30f)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
static struct radio_isa_card *rtrack2_alloc(void)
{
return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
}
static void zero(struct radio_isa_card *isa)
{
outb_p(1, isa->io);
outb_p(3, isa->io);
outb_p(1, isa->io);
}
static void one(struct radio_isa_card *isa)
{
outb_p(5, isa->io);
outb_p(7, isa->io);
outb_p(5, isa->io);
}
static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq)
{
int i;
freq = freq / 200 + 856;
outb_p(0xc8, isa->io);
outb_p(0xc9, isa->io);
outb_p(0xc9, isa->io);
for (i = 0; i < 10; i++)
zero(isa);
for (i = 14; i >= 0; i--)
if (freq & (1 << i))
one(isa);
else
zero(isa);
outb_p(0xc8, isa->io);
outb_p(v4l2_ctrl_g_ctrl(isa->mute), isa->io);
return 0;
}
static u32 rtrack2_g_signal(struct radio_isa_card *isa)
{
/* bit set = no signal present */
return (inb(isa->io) & 2) ? 0 : 0xffff;
}
static int rtrack2_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
outb(mute, isa->io);
return 0;
}
static const struct radio_isa_ops rtrack2_ops = {
.alloc = rtrack2_alloc,
.s_mute_volume = rtrack2_s_mute_volume,
.s_frequency = rtrack2_s_frequency,
.g_signal = rtrack2_g_signal,
};
static const int rtrack2_ioports[] = { 0x20f, 0x30f };
static struct radio_isa_driver rtrack2_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-rtrack2",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = rtrack2_ioports,
.num_of_io_ports = ARRAY_SIZE(rtrack2_ioports),
.region_size = 4,
.card = "AIMSlab RadioTrack II",
.ops = &rtrack2_ops,
.has_stereo = true,
};
static int __init rtrack2_init(void)
{
return isa_register_driver(&rtrack2_driver.driver, RTRACK2_MAX);
}
static void __exit rtrack2_exit(void)
{
isa_unregister_driver(&rtrack2_driver.driver);
}
module_init(rtrack2_init);
module_exit(rtrack2_exit);

View File

@@ -0,0 +1,402 @@
/* SF16-FMI, SF16-FMP and SF16-FMD 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@lxorguk.ukuu.org.uk>
* 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 SF16-FMI/SF16-FMP/SF16-FMD
*
* Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
*/
#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/isapnp.h>
#include <linux/mutex.h>
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "lm7000.h"
MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood");
MODULE_DESCRIPTION("A driver for the SF16-FMI, SF16-FMP and SF16-FMD radio.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.0.3");
static int io = -1;
static int radio_nr = -1;
module_param(io, int, 0);
MODULE_PARM_DESC(io, "I/O address of the SF16-FMI/SF16-FMP/SF16-FMD card (0x284 or 0x384)");
module_param(radio_nr, int, 0);
struct fmi
{
struct v4l2_device v4l2_dev;
struct video_device vdev;
int io;
bool mute;
unsigned long curfreq; /* freq in kHz */
struct mutex lock;
};
static struct fmi fmi_card;
static struct pnp_dev *dev;
bool pnp_attached;
#define RSF16_MINFREQ (87 * 16000)
#define RSF16_MAXFREQ (108 * 16000)
#define FMI_BIT_TUN_CE (1 << 0)
#define FMI_BIT_TUN_CLK (1 << 1)
#define FMI_BIT_TUN_DATA (1 << 2)
#define FMI_BIT_VOL_SW (1 << 3)
#define FMI_BIT_TUN_STRQ (1 << 4)
static void fmi_set_pins(void *handle, u8 pins)
{
struct fmi *fmi = handle;
u8 bits = FMI_BIT_TUN_STRQ;
if (!fmi->mute)
bits |= FMI_BIT_VOL_SW;
if (pins & LM7000_DATA)
bits |= FMI_BIT_TUN_DATA;
if (pins & LM7000_CLK)
bits |= FMI_BIT_TUN_CLK;
if (pins & LM7000_CE)
bits |= FMI_BIT_TUN_CE;
mutex_lock(&fmi->lock);
outb_p(bits, fmi->io);
mutex_unlock(&fmi->lock);
}
static inline void fmi_mute(struct fmi *fmi)
{
mutex_lock(&fmi->lock);
outb(0x00, fmi->io);
mutex_unlock(&fmi->lock);
}
static inline void fmi_unmute(struct fmi *fmi)
{
mutex_lock(&fmi->lock);
outb(0x08, fmi->io);
mutex_unlock(&fmi->lock);
}
static inline int fmi_getsigstr(struct fmi *fmi)
{
int val;
int res;
mutex_lock(&fmi->lock);
val = fmi->mute ? 0x00 : 0x08; /* mute/unmute */
outb(val, fmi->io);
outb(val | 0x10, fmi->io);
msleep(143); /* was schedule_timeout(HZ/7) */
res = (int)inb(fmi->io + 1);
outb(val, fmi->io);
mutex_unlock(&fmi->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-FMI/FMP/FMD radio", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct fmi *fmi = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = RSF16_MINFREQ;
v->rangehigh = RSF16_MAXFREQ;
v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
v->capability = V4L2_TUNER_CAP_STEREO | 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,
const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct fmi *fmi = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
if (f->frequency < RSF16_MINFREQ ||
f->frequency > RSF16_MAXFREQ)
return -EINVAL;
/* rounding in steps of 800 to match the freq
that will be used */
lm7000_set_freq((f->frequency / 800) * 800, fmi, fmi_set_pins);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct fmi *fmi = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = fmi->curfreq;
return 0;
}
static int vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
}
return -EINVAL;
}
static int vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct fmi *fmi = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = fmi->mute;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct fmi *fmi = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
fmi_mute(fmi);
else
fmi_unmute(fmi);
fmi->mute = ctrl->value;
return 0;
}
return -EINVAL;
}
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)
{
return i ? -EINVAL : 0;
}
static int vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int vidioc_s_audio(struct file *file, void *priv,
const struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static const struct v4l2_file_operations fmi_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
};
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,
};
/* ladis: this is my card. does any other types exist? */
static struct isapnp_device_id id_table[] = {
/* SF16-FMI */
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad10), 0},
/* SF16-FMD */
{ ISAPNP_ANY_ID, ISAPNP_ANY_ID,
ISAPNP_VENDOR('M','F','R'), ISAPNP_FUNCTION(0xad12), 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(KERN_ERR "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(KERN_INFO "radio-sf16fmi: PnP reports card at %#x\n", i);
return i;
}
static int __init fmi_init(void)
{
struct fmi *fmi = &fmi_card;
struct v4l2_device *v4l2_dev = &fmi->v4l2_dev;
int res, i;
int probe_ports[] = { 0, 0x284, 0x384 };
if (io < 0) {
for (i = 0; i < ARRAY_SIZE(probe_ports); i++) {
io = probe_ports[i];
if (io == 0) {
io = isapnp_fmi_probe();
if (io < 0)
continue;
pnp_attached = 1;
}
if (!request_region(io, 2, "radio-sf16fmi")) {
if (pnp_attached)
pnp_device_detach(dev);
io = -1;
continue;
}
if (pnp_attached ||
((inb(io) & 0xf9) == 0xf9 && (inb(io) & 0x4) == 0))
break;
release_region(io, 2);
io = -1;
}
} else {
if (!request_region(io, 2, "radio-sf16fmi")) {
printk(KERN_ERR "radio-sf16fmi: port %#x already in use\n", io);
return -EBUSY;
}
if (inb(io) == 0xff) {
printk(KERN_ERR "radio-sf16fmi: card not present at %#x\n", io);
release_region(io, 2);
return -ENODEV;
}
}
if (io < 0) {
printk(KERN_ERR "radio-sf16fmi: no cards found\n");
return -ENODEV;
}
strlcpy(v4l2_dev->name, "sf16fmi", sizeof(v4l2_dev->name));
fmi->io = io;
res = v4l2_device_register(NULL, v4l2_dev);
if (res < 0) {
release_region(fmi->io, 2);
if (pnp_attached)
pnp_device_detach(dev);
v4l2_err(v4l2_dev, "Could not register v4l2_device\n");
return res;
}
strlcpy(fmi->vdev.name, v4l2_dev->name, sizeof(fmi->vdev.name));
fmi->vdev.v4l2_dev = v4l2_dev;
fmi->vdev.fops = &fmi_fops;
fmi->vdev.ioctl_ops = &fmi_ioctl_ops;
fmi->vdev.release = video_device_release_empty;
video_set_drvdata(&fmi->vdev, fmi);
mutex_init(&fmi->lock);
/* mute card - prevents noisy bootups */
fmi_mute(fmi);
if (video_register_device(&fmi->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
v4l2_device_unregister(v4l2_dev);
release_region(fmi->io, 2);
if (pnp_attached)
pnp_device_detach(dev);
return -EINVAL;
}
v4l2_info(v4l2_dev, "card driver at 0x%x\n", fmi->io);
return 0;
}
static void __exit fmi_exit(void)
{
struct fmi *fmi = &fmi_card;
video_unregister_device(&fmi->vdev);
v4l2_device_unregister(&fmi->v4l2_dev);
release_region(fmi->io, 2);
if (dev && pnp_attached)
pnp_device_detach(dev);
}
module_init(fmi_init);
module_exit(fmi_exit);

View File

@@ -0,0 +1,347 @@
/* SF16-FMR2 and SF16-FMD2 radio driver for Linux
* Copyright (c) 2011 Ondrej Zary
*
* Original driver was (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com
* but almost nothing remained here after conversion to generic TEA575x
* implementation
*/
#include <linux/delay.h>
#include <linux/module.h> /* Modules */
#include <linux/init.h> /* Initdata */
#include <linux/slab.h>
#include <linux/ioport.h> /* request_region */
#include <linux/io.h> /* outb, outb_p */
#include <linux/isa.h>
#include <linux/pnp.h>
#include <sound/tea575x-tuner.h>
MODULE_AUTHOR("Ondrej Zary");
MODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver");
MODULE_LICENSE("GPL");
/* these cards can only use two different ports (0x384 and 0x284) */
#define FMR2_MAX 2
static int radio_nr[FMR2_MAX] = { [0 ... (FMR2_MAX - 1)] = -1 };
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct fmr2 {
int io;
struct v4l2_device v4l2_dev;
struct snd_tea575x tea;
struct v4l2_ctrl *volume;
struct v4l2_ctrl *balance;
bool is_fmd2;
};
static int num_fmr2_cards;
static struct fmr2 *fmr2_cards[FMR2_MAX];
static bool isa_registered;
static bool pnp_registered;
/* the port is hardwired on SF16-FMR2 */
#define FMR2_PORT 0x384
/* TEA575x tuner pins */
#define STR_DATA (1 << 0)
#define STR_CLK (1 << 1)
#define STR_WREN (1 << 2)
#define STR_MOST (1 << 3)
/* PT2254A/TC9154A volume control pins */
#define PT_ST (1 << 4)
#define PT_CK (1 << 5)
#define PT_DATA (1 << 6)
/* volume control presence pin */
#define FMR2_HASVOL (1 << 7)
static void fmr2_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
{
struct fmr2 *fmr2 = tea->private_data;
u8 bits = 0;
bits |= (pins & TEA575X_DATA) ? STR_DATA : 0;
bits |= (pins & TEA575X_CLK) ? STR_CLK : 0;
/* WRITE_ENABLE is inverted, DATA must be high during read */
bits |= (pins & TEA575X_WREN) ? 0 : STR_WREN | STR_DATA;
outb(bits, fmr2->io);
}
static u8 fmr2_tea575x_get_pins(struct snd_tea575x *tea)
{
struct fmr2 *fmr2 = tea->private_data;
u8 bits = inb(fmr2->io);
return (bits & STR_DATA) ? TEA575X_DATA : 0 |
(bits & STR_MOST) ? TEA575X_MOST : 0;
}
static void fmr2_tea575x_set_direction(struct snd_tea575x *tea, bool output)
{
}
static struct snd_tea575x_ops fmr2_tea_ops = {
.set_pins = fmr2_tea575x_set_pins,
.get_pins = fmr2_tea575x_get_pins,
.set_direction = fmr2_tea575x_set_direction,
};
/* TC9154A/PT2254A volume control */
/* 18-bit shift register bit definitions */
#define TC9154A_ATT_MAJ_0DB (1 << 0)
#define TC9154A_ATT_MAJ_10DB (1 << 1)
#define TC9154A_ATT_MAJ_20DB (1 << 2)
#define TC9154A_ATT_MAJ_30DB (1 << 3)
#define TC9154A_ATT_MAJ_40DB (1 << 4)
#define TC9154A_ATT_MAJ_50DB (1 << 5)
#define TC9154A_ATT_MAJ_60DB (1 << 6)
#define TC9154A_ATT_MIN_0DB (1 << 7)
#define TC9154A_ATT_MIN_2DB (1 << 8)
#define TC9154A_ATT_MIN_4DB (1 << 9)
#define TC9154A_ATT_MIN_6DB (1 << 10)
#define TC9154A_ATT_MIN_8DB (1 << 11)
/* bit 12 is ignored */
#define TC9154A_CHANNEL_LEFT (1 << 13)
#define TC9154A_CHANNEL_RIGHT (1 << 14)
/* bits 15, 16, 17 must be 0 */
#define TC9154A_ATT_MAJ(x) (1 << x)
#define TC9154A_ATT_MIN(x) (1 << (7 + x))
static void tc9154a_set_pins(struct fmr2 *fmr2, u8 pins)
{
if (!fmr2->tea.mute)
pins |= STR_WREN;
outb(pins, fmr2->io);
}
static void tc9154a_set_attenuation(struct fmr2 *fmr2, int att, u32 channel)
{
int i;
u32 reg;
u8 bit;
reg = TC9154A_ATT_MAJ(att / 10) | TC9154A_ATT_MIN((att % 10) / 2);
reg |= channel;
/* write 18-bit shift register, LSB first */
for (i = 0; i < 18; i++) {
bit = reg & (1 << i) ? PT_DATA : 0;
tc9154a_set_pins(fmr2, bit);
udelay(5);
tc9154a_set_pins(fmr2, bit | PT_CK);
udelay(5);
tc9154a_set_pins(fmr2, bit);
}
/* latch register data */
udelay(5);
tc9154a_set_pins(fmr2, PT_ST);
udelay(5);
tc9154a_set_pins(fmr2, 0);
}
static int fmr2_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
struct fmr2 *fmr2 = tea->private_data;
int volume, balance, left, right;
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
volume = ctrl->val;
balance = fmr2->balance->cur.val;
break;
case V4L2_CID_AUDIO_BALANCE:
balance = ctrl->val;
volume = fmr2->volume->cur.val;
break;
default:
return -EINVAL;
}
left = right = volume;
if (balance < 0)
right = max(0, right + balance);
if (balance > 0)
left = max(0, left - balance);
tc9154a_set_attenuation(fmr2, abs(left - 68), TC9154A_CHANNEL_LEFT);
tc9154a_set_attenuation(fmr2, abs(right - 68), TC9154A_CHANNEL_RIGHT);
return 0;
}
static const struct v4l2_ctrl_ops fmr2_ctrl_ops = {
.s_ctrl = fmr2_s_ctrl,
};
static int fmr2_tea_ext_init(struct snd_tea575x *tea)
{
struct fmr2 *fmr2 = tea->private_data;
/* FMR2 can have volume control, FMD2 can't (uses SB16 mixer) */
if (!fmr2->is_fmd2 && inb(fmr2->io) & FMR2_HASVOL) {
fmr2->volume = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 68, 2, 56);
fmr2->balance = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_BALANCE, -68, 68, 2, 0);
if (tea->ctrl_handler.error) {
printk(KERN_ERR "radio-sf16fmr2: can't initialize controls\n");
return tea->ctrl_handler.error;
}
}
return 0;
}
static struct pnp_device_id fmr2_pnp_ids[] = {
{ .id = "MFRad13" }, /* tuner subdevice of SF16-FMD2 */
{ .id = "" }
};
MODULE_DEVICE_TABLE(pnp, fmr2_pnp_ids);
static int fmr2_probe(struct fmr2 *fmr2, struct device *pdev, int io)
{
int err, i;
char *card_name = fmr2->is_fmd2 ? "SF16-FMD2" : "SF16-FMR2";
/* avoid errors if a card was already registered at given port */
for (i = 0; i < num_fmr2_cards; i++)
if (io == fmr2_cards[i]->io)
return -EBUSY;
strlcpy(fmr2->v4l2_dev.name, "radio-sf16fmr2",
sizeof(fmr2->v4l2_dev.name)),
fmr2->io = io;
if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) {
printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io);
return -EBUSY;
}
dev_set_drvdata(pdev, fmr2);
err = v4l2_device_register(pdev, &fmr2->v4l2_dev);
if (err < 0) {
v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n");
release_region(fmr2->io, 2);
return err;
}
fmr2->tea.v4l2_dev = &fmr2->v4l2_dev;
fmr2->tea.private_data = fmr2;
fmr2->tea.radio_nr = radio_nr[num_fmr2_cards];
fmr2->tea.ops = &fmr2_tea_ops;
fmr2->tea.ext_init = fmr2_tea_ext_init;
strlcpy(fmr2->tea.card, card_name, sizeof(fmr2->tea.card));
snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "%s:%s",
fmr2->is_fmd2 ? "PnP" : "ISA", dev_name(pdev));
if (snd_tea575x_init(&fmr2->tea, THIS_MODULE)) {
printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n");
release_region(fmr2->io, 2);
return -ENODEV;
}
printk(KERN_INFO "radio-sf16fmr2: %s radio card at 0x%x.\n",
card_name, fmr2->io);
return 0;
}
static int fmr2_isa_match(struct device *pdev, unsigned int ndev)
{
struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
if (!fmr2)
return 0;
if (fmr2_probe(fmr2, pdev, FMR2_PORT)) {
kfree(fmr2);
return 0;
}
dev_set_drvdata(pdev, fmr2);
fmr2_cards[num_fmr2_cards++] = fmr2;
return 1;
}
static int fmr2_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id)
{
int ret;
struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
if (!fmr2)
return -ENOMEM;
fmr2->is_fmd2 = true;
ret = fmr2_probe(fmr2, &pdev->dev, pnp_port_start(pdev, 0));
if (ret) {
kfree(fmr2);
return ret;
}
pnp_set_drvdata(pdev, fmr2);
fmr2_cards[num_fmr2_cards++] = fmr2;
return 0;
}
static void fmr2_remove(struct fmr2 *fmr2)
{
snd_tea575x_exit(&fmr2->tea);
release_region(fmr2->io, 2);
v4l2_device_unregister(&fmr2->v4l2_dev);
kfree(fmr2);
}
static int fmr2_isa_remove(struct device *pdev, unsigned int ndev)
{
fmr2_remove(dev_get_drvdata(pdev));
dev_set_drvdata(pdev, NULL);
return 0;
}
static void fmr2_pnp_remove(struct pnp_dev *pdev)
{
fmr2_remove(pnp_get_drvdata(pdev));
pnp_set_drvdata(pdev, NULL);
}
struct isa_driver fmr2_isa_driver = {
.match = fmr2_isa_match,
.remove = fmr2_isa_remove,
.driver = {
.name = "radio-sf16fmr2",
},
};
struct pnp_driver fmr2_pnp_driver = {
.name = "radio-sf16fmr2",
.id_table = fmr2_pnp_ids,
.probe = fmr2_pnp_probe,
.remove = fmr2_pnp_remove,
};
static int __init fmr2_init(void)
{
int ret;
ret = pnp_register_driver(&fmr2_pnp_driver);
if (!ret)
pnp_registered = true;
ret = isa_register_driver(&fmr2_isa_driver, 1);
if (!ret)
isa_registered = true;
return (pnp_registered || isa_registered) ? 0 : ret;
}
static void __exit fmr2_exit(void)
{
if (pnp_registered)
pnp_unregister_driver(&fmr2_pnp_driver);
if (isa_registered)
isa_unregister_driver(&fmr2_isa_driver);
}
module_init(fmr2_init);
module_exit(fmr2_exit);

View File

@@ -0,0 +1,423 @@
/*
* Linux V4L2 radio driver for the Griffin radioSHARK USB radio receiver
*
* Note the radioSHARK offers the audio through a regular USB audio device,
* this driver only handles the tuning.
*
* The info necessary to drive the shark was taken from the small userspace
* shark.c program by Michael Rolig, which he kindly placed in the Public
* Domain.
*
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.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
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <media/v4l2-device.h>
#include <sound/tea575x-tuner.h>
#if defined(CONFIG_LEDS_CLASS) || \
(defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK_MODULE))
#define SHARK_USE_LEDS 1
#endif
/*
* Version Information
*/
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("Griffin radioSHARK, USB radio receiver driver");
MODULE_LICENSE("GPL");
#define SHARK_IN_EP 0x83
#define SHARK_OUT_EP 0x05
#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */
#define TEA575X_BIT_BAND_MASK (3<<20)
#define TEA575X_BIT_BAND_FM (0<<20)
#define TB_LEN 6
#define DRV_NAME "radioshark"
#define v4l2_dev_to_shark(d) container_of(d, struct shark_device, v4l2_dev)
/* Note BLUE_IS_PULSE comes after NO_LEDS as it is a status bit, not a LED */
enum { BLUE_LED, BLUE_PULSE_LED, RED_LED, NO_LEDS, BLUE_IS_PULSE };
struct shark_device {
struct usb_device *usbdev;
struct v4l2_device v4l2_dev;
struct snd_tea575x tea;
#ifdef SHARK_USE_LEDS
struct work_struct led_work;
struct led_classdev leds[NO_LEDS];
char led_names[NO_LEDS][32];
atomic_t brightness[NO_LEDS];
unsigned long brightness_new;
#endif
u8 *transfer_buffer;
u32 last_val;
};
static atomic_t shark_instance = ATOMIC_INIT(0);
static void shark_write_val(struct snd_tea575x *tea, u32 val)
{
struct shark_device *shark = tea->private_data;
int i, res, actual_len;
/* Avoid unnecessary (slow) USB transfers */
if (shark->last_val == val)
return;
memset(shark->transfer_buffer, 0, TB_LEN);
shark->transfer_buffer[0] = 0xc0; /* Write shift register command */
for (i = 0; i < 4; i++)
shark->transfer_buffer[i] |= (val >> (24 - i * 8)) & 0xff;
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev, SHARK_OUT_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res >= 0)
shark->last_val = val;
else
v4l2_err(&shark->v4l2_dev, "set-freq error: %d\n", res);
}
static u32 shark_read_val(struct snd_tea575x *tea)
{
struct shark_device *shark = tea->private_data;
int i, res, actual_len;
u32 val = 0;
memset(shark->transfer_buffer, 0, TB_LEN);
shark->transfer_buffer[0] = 0x80;
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev, SHARK_OUT_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0) {
v4l2_err(&shark->v4l2_dev, "request-status error: %d\n", res);
return shark->last_val;
}
res = usb_interrupt_msg(shark->usbdev,
usb_rcvintpipe(shark->usbdev, SHARK_IN_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0) {
v4l2_err(&shark->v4l2_dev, "get-status error: %d\n", res);
return shark->last_val;
}
for (i = 0; i < 4; i++)
val |= shark->transfer_buffer[i] << (24 - i * 8);
shark->last_val = val;
/*
* The shark does not allow actually reading the stereo / mono pin :(
* So assume that when we're tuned to an FM station and mono has not
* been requested, that we're receiving stereo.
*/
if (((val & TEA575X_BIT_BAND_MASK) == TEA575X_BIT_BAND_FM) &&
!(val & TEA575X_BIT_MONO))
shark->tea.stereo = true;
else
shark->tea.stereo = false;
return val;
}
static struct snd_tea575x_ops shark_tea_ops = {
.write_val = shark_write_val,
.read_val = shark_read_val,
};
#ifdef SHARK_USE_LEDS
static void shark_led_work(struct work_struct *work)
{
struct shark_device *shark =
container_of(work, struct shark_device, led_work);
int i, res, brightness, actual_len;
for (i = 0; i < 3; i++) {
if (!test_and_clear_bit(i, &shark->brightness_new))
continue;
brightness = atomic_read(&shark->brightness[i]);
memset(shark->transfer_buffer, 0, TB_LEN);
if (i != RED_LED) {
shark->transfer_buffer[0] = 0xA0 + i;
shark->transfer_buffer[1] = brightness;
} else
shark->transfer_buffer[0] = brightness ? 0xA9 : 0xA8;
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev, 0x05),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0)
v4l2_err(&shark->v4l2_dev, "set LED %s error: %d\n",
shark->led_names[i], res);
}
}
static void shark_led_set_blue(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct shark_device *shark =
container_of(led_cdev, struct shark_device, leds[BLUE_LED]);
atomic_set(&shark->brightness[BLUE_LED], value);
set_bit(BLUE_LED, &shark->brightness_new);
clear_bit(BLUE_IS_PULSE, &shark->brightness_new);
schedule_work(&shark->led_work);
}
static void shark_led_set_blue_pulse(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct shark_device *shark = container_of(led_cdev,
struct shark_device, leds[BLUE_PULSE_LED]);
atomic_set(&shark->brightness[BLUE_PULSE_LED], 256 - value);
set_bit(BLUE_PULSE_LED, &shark->brightness_new);
set_bit(BLUE_IS_PULSE, &shark->brightness_new);
schedule_work(&shark->led_work);
}
static void shark_led_set_red(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct shark_device *shark =
container_of(led_cdev, struct shark_device, leds[RED_LED]);
atomic_set(&shark->brightness[RED_LED], value);
set_bit(RED_LED, &shark->brightness_new);
schedule_work(&shark->led_work);
}
static const struct led_classdev shark_led_templates[NO_LEDS] = {
[BLUE_LED] = {
.name = "%s:blue:",
.brightness = LED_OFF,
.max_brightness = 127,
.brightness_set = shark_led_set_blue,
},
[BLUE_PULSE_LED] = {
.name = "%s:blue-pulse:",
.brightness = LED_OFF,
.max_brightness = 255,
.brightness_set = shark_led_set_blue_pulse,
},
[RED_LED] = {
.name = "%s:red:",
.brightness = LED_OFF,
.max_brightness = 1,
.brightness_set = shark_led_set_red,
},
};
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
int i, retval;
atomic_set(&shark->brightness[BLUE_LED], 127);
INIT_WORK(&shark->led_work, shark_led_work);
for (i = 0; i < NO_LEDS; i++) {
shark->leds[i] = shark_led_templates[i];
snprintf(shark->led_names[i], sizeof(shark->led_names[0]),
shark->leds[i].name, shark->v4l2_dev.name);
shark->leds[i].name = shark->led_names[i];
retval = led_classdev_register(dev, &shark->leds[i]);
if (retval) {
v4l2_err(&shark->v4l2_dev,
"couldn't register led: %s\n",
shark->led_names[i]);
return retval;
}
}
return 0;
}
static void shark_unregister_leds(struct shark_device *shark)
{
int i;
for (i = 0; i < NO_LEDS; i++)
led_classdev_unregister(&shark->leds[i]);
cancel_work_sync(&shark->led_work);
}
static void shark_resume_leds(struct shark_device *shark)
{
if (test_bit(BLUE_IS_PULSE, &shark->brightness_new))
set_bit(BLUE_PULSE_LED, &shark->brightness_new);
else
set_bit(BLUE_LED, &shark->brightness_new);
set_bit(RED_LED, &shark->brightness_new);
schedule_work(&shark->led_work);
}
#else
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
v4l2_warn(&shark->v4l2_dev,
"CONFIG_LEDS_CLASS not enabled, LED support disabled\n");
return 0;
}
static inline void shark_unregister_leds(struct shark_device *shark) { }
static inline void shark_resume_leds(struct shark_device *shark) { }
#endif
static void usb_shark_disconnect(struct usb_interface *intf)
{
struct v4l2_device *v4l2_dev = usb_get_intfdata(intf);
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
mutex_lock(&shark->tea.mutex);
v4l2_device_disconnect(&shark->v4l2_dev);
snd_tea575x_exit(&shark->tea);
mutex_unlock(&shark->tea.mutex);
shark_unregister_leds(shark);
v4l2_device_put(&shark->v4l2_dev);
}
static void usb_shark_release(struct v4l2_device *v4l2_dev)
{
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
v4l2_device_unregister(&shark->v4l2_dev);
kfree(shark->transfer_buffer);
kfree(shark);
}
static int usb_shark_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct shark_device *shark;
int retval = -ENOMEM;
shark = kzalloc(sizeof(struct shark_device), GFP_KERNEL);
if (!shark)
return retval;
shark->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL);
if (!shark->transfer_buffer)
goto err_alloc_buffer;
v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance);
retval = shark_register_leds(shark, &intf->dev);
if (retval)
goto err_reg_leds;
shark->v4l2_dev.release = usb_shark_release;
retval = v4l2_device_register(&intf->dev, &shark->v4l2_dev);
if (retval) {
v4l2_err(&shark->v4l2_dev, "couldn't register v4l2_device\n");
goto err_reg_dev;
}
shark->usbdev = interface_to_usbdev(intf);
shark->tea.v4l2_dev = &shark->v4l2_dev;
shark->tea.private_data = shark;
shark->tea.radio_nr = -1;
shark->tea.ops = &shark_tea_ops;
shark->tea.cannot_mute = true;
shark->tea.has_am = true;
strlcpy(shark->tea.card, "Griffin radioSHARK",
sizeof(shark->tea.card));
usb_make_path(shark->usbdev, shark->tea.bus_info,
sizeof(shark->tea.bus_info));
retval = snd_tea575x_init(&shark->tea, THIS_MODULE);
if (retval) {
v4l2_err(&shark->v4l2_dev, "couldn't init tea5757\n");
goto err_init_tea;
}
return 0;
err_init_tea:
v4l2_device_unregister(&shark->v4l2_dev);
err_reg_dev:
shark_unregister_leds(shark);
err_reg_leds:
kfree(shark->transfer_buffer);
err_alloc_buffer:
kfree(shark);
return retval;
}
#ifdef CONFIG_PM
static int usb_shark_suspend(struct usb_interface *intf, pm_message_t message)
{
return 0;
}
static int usb_shark_resume(struct usb_interface *intf)
{
struct v4l2_device *v4l2_dev = usb_get_intfdata(intf);
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
mutex_lock(&shark->tea.mutex);
snd_tea575x_set_freq(&shark->tea);
mutex_unlock(&shark->tea.mutex);
shark_resume_leds(shark);
return 0;
}
#endif
/* Specify the bcdDevice value, as the radioSHARK and radioSHARK2 share ids */
static struct usb_device_id usb_shark_device_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = 0x077d,
.idProduct = 0x627a,
.bcdDevice_lo = 0x0001,
.bcdDevice_hi = 0x0001,
.bInterfaceClass = 3,
},
{ }
};
MODULE_DEVICE_TABLE(usb, usb_shark_device_table);
static struct usb_driver usb_shark_driver = {
.name = DRV_NAME,
.probe = usb_shark_probe,
.disconnect = usb_shark_disconnect,
.id_table = usb_shark_device_table,
#ifdef CONFIG_PM
.suspend = usb_shark_suspend,
.resume = usb_shark_resume,
.reset_resume = usb_shark_resume,
#endif
};
module_usb_driver(usb_shark_driver);

View File

@@ -0,0 +1,389 @@
/*
* Linux V4L2 radio driver for the Griffin radioSHARK2 USB radio receiver
*
* Note the radioSHARK2 offers the audio through a regular USB audio device,
* this driver only handles the tuning.
*
* The info necessary to drive the shark2 was taken from the small userspace
* shark2.c program by Hisaaki Shibata, which he kindly placed in the Public
* Domain.
*
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.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
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <media/v4l2-device.h>
#include "radio-tea5777.h"
#if defined(CONFIG_LEDS_CLASS) || \
(defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK2_MODULE))
#define SHARK_USE_LEDS 1
#endif
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
MODULE_DESCRIPTION("Griffin radioSHARK2, USB radio receiver driver");
MODULE_LICENSE("GPL");
static int debug;
module_param(debug, int, 0);
MODULE_PARM_DESC(debug, "Debug level (0-1)");
#define SHARK_IN_EP 0x83
#define SHARK_OUT_EP 0x05
#define TB_LEN 7
#define DRV_NAME "radioshark2"
#define v4l2_dev_to_shark(d) container_of(d, struct shark_device, v4l2_dev)
enum { BLUE_LED, RED_LED, NO_LEDS };
struct shark_device {
struct usb_device *usbdev;
struct v4l2_device v4l2_dev;
struct radio_tea5777 tea;
#ifdef SHARK_USE_LEDS
struct work_struct led_work;
struct led_classdev leds[NO_LEDS];
char led_names[NO_LEDS][32];
atomic_t brightness[NO_LEDS];
unsigned long brightness_new;
#endif
u8 *transfer_buffer;
};
static atomic_t shark_instance = ATOMIC_INIT(0);
static int shark_write_reg(struct radio_tea5777 *tea, u64 reg)
{
struct shark_device *shark = tea->private_data;
int i, res, actual_len;
memset(shark->transfer_buffer, 0, TB_LEN);
shark->transfer_buffer[0] = 0x81; /* Write register command */
for (i = 0; i < 6; i++)
shark->transfer_buffer[i + 1] = (reg >> (40 - i * 8)) & 0xff;
v4l2_dbg(1, debug, tea->v4l2_dev, "shark2-write: %*ph\n",
7, shark->transfer_buffer);
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev, SHARK_OUT_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0) {
v4l2_err(tea->v4l2_dev, "write error: %d\n", res);
return res;
}
return 0;
}
static int shark_read_reg(struct radio_tea5777 *tea, u32 *reg_ret)
{
struct shark_device *shark = tea->private_data;
int i, res, actual_len;
u32 reg = 0;
memset(shark->transfer_buffer, 0, TB_LEN);
shark->transfer_buffer[0] = 0x82;
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev, SHARK_OUT_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0) {
v4l2_err(tea->v4l2_dev, "request-read error: %d\n", res);
return res;
}
res = usb_interrupt_msg(shark->usbdev,
usb_rcvintpipe(shark->usbdev, SHARK_IN_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0) {
v4l2_err(tea->v4l2_dev, "read error: %d\n", res);
return res;
}
for (i = 0; i < 3; i++)
reg |= shark->transfer_buffer[i] << (16 - i * 8);
v4l2_dbg(1, debug, tea->v4l2_dev, "shark2-read: %*ph\n",
3, shark->transfer_buffer);
*reg_ret = reg;
return 0;
}
static struct radio_tea5777_ops shark_tea_ops = {
.write_reg = shark_write_reg,
.read_reg = shark_read_reg,
};
#ifdef SHARK_USE_LEDS
static void shark_led_work(struct work_struct *work)
{
struct shark_device *shark =
container_of(work, struct shark_device, led_work);
int i, res, brightness, actual_len;
for (i = 0; i < 2; i++) {
if (!test_and_clear_bit(i, &shark->brightness_new))
continue;
brightness = atomic_read(&shark->brightness[i]);
memset(shark->transfer_buffer, 0, TB_LEN);
shark->transfer_buffer[0] = 0x83 + i;
shark->transfer_buffer[1] = brightness;
res = usb_interrupt_msg(shark->usbdev,
usb_sndintpipe(shark->usbdev,
SHARK_OUT_EP),
shark->transfer_buffer, TB_LEN,
&actual_len, 1000);
if (res < 0)
v4l2_err(&shark->v4l2_dev, "set LED %s error: %d\n",
shark->led_names[i], res);
}
}
static void shark_led_set_blue(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct shark_device *shark =
container_of(led_cdev, struct shark_device, leds[BLUE_LED]);
atomic_set(&shark->brightness[BLUE_LED], value);
set_bit(BLUE_LED, &shark->brightness_new);
schedule_work(&shark->led_work);
}
static void shark_led_set_red(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct shark_device *shark =
container_of(led_cdev, struct shark_device, leds[RED_LED]);
atomic_set(&shark->brightness[RED_LED], value);
set_bit(RED_LED, &shark->brightness_new);
schedule_work(&shark->led_work);
}
static const struct led_classdev shark_led_templates[NO_LEDS] = {
[BLUE_LED] = {
.name = "%s:blue:",
.brightness = LED_OFF,
.max_brightness = 127,
.brightness_set = shark_led_set_blue,
},
[RED_LED] = {
.name = "%s:red:",
.brightness = LED_OFF,
.max_brightness = 1,
.brightness_set = shark_led_set_red,
},
};
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
int i, retval;
atomic_set(&shark->brightness[BLUE_LED], 127);
INIT_WORK(&shark->led_work, shark_led_work);
for (i = 0; i < NO_LEDS; i++) {
shark->leds[i] = shark_led_templates[i];
snprintf(shark->led_names[i], sizeof(shark->led_names[0]),
shark->leds[i].name, shark->v4l2_dev.name);
shark->leds[i].name = shark->led_names[i];
retval = led_classdev_register(dev, &shark->leds[i]);
if (retval) {
v4l2_err(&shark->v4l2_dev,
"couldn't register led: %s\n",
shark->led_names[i]);
return retval;
}
}
return 0;
}
static void shark_unregister_leds(struct shark_device *shark)
{
int i;
for (i = 0; i < NO_LEDS; i++)
led_classdev_unregister(&shark->leds[i]);
cancel_work_sync(&shark->led_work);
}
static void shark_resume_leds(struct shark_device *shark)
{
int i;
for (i = 0; i < NO_LEDS; i++)
set_bit(i, &shark->brightness_new);
schedule_work(&shark->led_work);
}
#else
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
v4l2_warn(&shark->v4l2_dev,
"CONFIG_LEDS_CLASS not enabled, LED support disabled\n");
return 0;
}
static inline void shark_unregister_leds(struct shark_device *shark) { }
static inline void shark_resume_leds(struct shark_device *shark) { }
#endif
static void usb_shark_disconnect(struct usb_interface *intf)
{
struct v4l2_device *v4l2_dev = usb_get_intfdata(intf);
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
mutex_lock(&shark->tea.mutex);
v4l2_device_disconnect(&shark->v4l2_dev);
radio_tea5777_exit(&shark->tea);
mutex_unlock(&shark->tea.mutex);
shark_unregister_leds(shark);
v4l2_device_put(&shark->v4l2_dev);
}
static void usb_shark_release(struct v4l2_device *v4l2_dev)
{
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
v4l2_device_unregister(&shark->v4l2_dev);
kfree(shark->transfer_buffer);
kfree(shark);
}
static int usb_shark_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct shark_device *shark;
int retval = -ENOMEM;
shark = kzalloc(sizeof(struct shark_device), GFP_KERNEL);
if (!shark)
return retval;
shark->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL);
if (!shark->transfer_buffer)
goto err_alloc_buffer;
v4l2_device_set_name(&shark->v4l2_dev, DRV_NAME, &shark_instance);
retval = shark_register_leds(shark, &intf->dev);
if (retval)
goto err_reg_leds;
shark->v4l2_dev.release = usb_shark_release;
retval = v4l2_device_register(&intf->dev, &shark->v4l2_dev);
if (retval) {
v4l2_err(&shark->v4l2_dev, "couldn't register v4l2_device\n");
goto err_reg_dev;
}
shark->usbdev = interface_to_usbdev(intf);
shark->tea.v4l2_dev = &shark->v4l2_dev;
shark->tea.private_data = shark;
shark->tea.ops = &shark_tea_ops;
shark->tea.has_am = true;
shark->tea.write_before_read = true;
strlcpy(shark->tea.card, "Griffin radioSHARK2",
sizeof(shark->tea.card));
usb_make_path(shark->usbdev, shark->tea.bus_info,
sizeof(shark->tea.bus_info));
retval = radio_tea5777_init(&shark->tea, THIS_MODULE);
if (retval) {
v4l2_err(&shark->v4l2_dev, "couldn't init tea5777\n");
goto err_init_tea;
}
return 0;
err_init_tea:
v4l2_device_unregister(&shark->v4l2_dev);
err_reg_dev:
shark_unregister_leds(shark);
err_reg_leds:
kfree(shark->transfer_buffer);
err_alloc_buffer:
kfree(shark);
return retval;
}
#ifdef CONFIG_PM
static int usb_shark_suspend(struct usb_interface *intf, pm_message_t message)
{
return 0;
}
static int usb_shark_resume(struct usb_interface *intf)
{
struct v4l2_device *v4l2_dev = usb_get_intfdata(intf);
struct shark_device *shark = v4l2_dev_to_shark(v4l2_dev);
int ret;
mutex_lock(&shark->tea.mutex);
ret = radio_tea5777_set_freq(&shark->tea);
mutex_unlock(&shark->tea.mutex);
shark_resume_leds(shark);
return ret;
}
#endif
/* Specify the bcdDevice value, as the radioSHARK and radioSHARK2 share ids */
static struct usb_device_id usb_shark_device_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = 0x077d,
.idProduct = 0x627a,
.bcdDevice_lo = 0x0010,
.bcdDevice_hi = 0x0010,
.bInterfaceClass = 3,
},
{ }
};
MODULE_DEVICE_TABLE(usb, usb_shark_device_table);
static struct usb_driver usb_shark_driver = {
.name = DRV_NAME,
.probe = usb_shark_probe,
.disconnect = usb_shark_disconnect,
.id_table = usb_shark_device_table,
#ifdef CONFIG_PM
.suspend = usb_shark_suspend,
.resume = usb_shark_resume,
.reset_resume = usb_shark_resume,
#endif
};
module_usb_driver(usb_shark_driver);

View File

@@ -0,0 +1,246 @@
/*
* drivers/media/radio/radio-si4713.c
*
* Platform Driver for Silicon Labs Si4713 FM Radio Transmitter:
*
* Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
* Contact: Eduardo Valentin <eduardo.valentin@nokia.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
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/videodev2.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/radio-si4713.h>
/* module parameters */
static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr,
"Minor number for radio device (-1 ==> auto assign)");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter");
MODULE_VERSION("0.0.1");
MODULE_ALIAS("platform:radio-si4713");
/* Driver state struct */
struct radio_si4713_device {
struct v4l2_device v4l2_dev;
struct video_device radio_dev;
struct mutex lock;
};
/* radio_si4713_fops - file operations interface */
static const struct v4l2_file_operations radio_si4713_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
/* Note: locking is done at the subdev level in the i2c driver. */
.unlocked_ioctl = video_ioctl2,
};
/* Video4Linux Interface */
/* radio_si4713_querycap - query device capabilities */
static int radio_si4713_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver));
strlcpy(capability->card, "Silicon Labs Si4713 Modulator",
sizeof(capability->card));
strlcpy(capability->bus_info, "platform:radio-si4713",
sizeof(capability->bus_info));
capability->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;
capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
/*
* v4l2 ioctl call backs.
* we are just a wrapper for v4l2_sub_devs.
*/
static inline struct v4l2_device *get_v4l2_dev(struct file *file)
{
return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev;
}
static int radio_si4713_g_modulator(struct file *file, void *p,
struct v4l2_modulator *vm)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
g_modulator, vm);
}
static int radio_si4713_s_modulator(struct file *file, void *p,
const struct v4l2_modulator *vm)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
s_modulator, vm);
}
static int radio_si4713_g_frequency(struct file *file, void *p,
struct v4l2_frequency *vf)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
g_frequency, vf);
}
static int radio_si4713_s_frequency(struct file *file, void *p,
const struct v4l2_frequency *vf)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
s_frequency, vf);
}
static long radio_si4713_default(struct file *file, void *p,
bool valid_prio, unsigned int cmd, void *arg)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
ioctl, cmd, arg);
}
static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
.vidioc_querycap = radio_si4713_querycap,
.vidioc_g_modulator = radio_si4713_g_modulator,
.vidioc_s_modulator = radio_si4713_s_modulator,
.vidioc_g_frequency = radio_si4713_g_frequency,
.vidioc_s_frequency = radio_si4713_s_frequency,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_default = radio_si4713_default,
};
/* radio_si4713_vdev_template - video device interface */
static struct video_device radio_si4713_vdev_template = {
.fops = &radio_si4713_fops,
.name = "radio-si4713",
.release = video_device_release_empty,
.ioctl_ops = &radio_si4713_ioctl_ops,
.vfl_dir = VFL_DIR_TX,
};
/* Platform driver interface */
/* radio_si4713_pdriver_probe - probe for the device */
static int radio_si4713_pdriver_probe(struct platform_device *pdev)
{
struct radio_si4713_platform_data *pdata = pdev->dev.platform_data;
struct radio_si4713_device *rsdev;
struct i2c_adapter *adapter;
struct v4l2_subdev *sd;
int rval = 0;
if (!pdata) {
dev_err(&pdev->dev, "Cannot proceed without platform data.\n");
rval = -EINVAL;
goto exit;
}
rsdev = devm_kzalloc(&pdev->dev, sizeof(*rsdev), GFP_KERNEL);
if (!rsdev) {
dev_err(&pdev->dev, "Failed to alloc video device.\n");
rval = -ENOMEM;
goto exit;
}
mutex_init(&rsdev->lock);
rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);
if (rval) {
dev_err(&pdev->dev, "Failed to register v4l2 device.\n");
goto exit;
}
adapter = i2c_get_adapter(pdata->i2c_bus);
if (!adapter) {
dev_err(&pdev->dev, "Cannot get i2c adapter %d\n",
pdata->i2c_bus);
rval = -ENODEV;
goto unregister_v4l2_dev;
}
sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter,
pdata->subdev_board_info, NULL);
if (!sd) {
dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");
rval = -ENODEV;
goto put_adapter;
}
rsdev->radio_dev = radio_si4713_vdev_template;
rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev;
rsdev->radio_dev.ctrl_handler = sd->ctrl_handler;
set_bit(V4L2_FL_USE_FH_PRIO, &rsdev->radio_dev.flags);
/* Serialize all access to the si4713 */
rsdev->radio_dev.lock = &rsdev->lock;
video_set_drvdata(&rsdev->radio_dev, rsdev);
if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) {
dev_err(&pdev->dev, "Could not register video device.\n");
rval = -EIO;
goto put_adapter;
}
dev_info(&pdev->dev, "New device successfully probed\n");
goto exit;
put_adapter:
i2c_put_adapter(adapter);
unregister_v4l2_dev:
v4l2_device_unregister(&rsdev->v4l2_dev);
exit:
return rval;
}
/* radio_si4713_pdriver_remove - remove the device */
static int radio_si4713_pdriver_remove(struct platform_device *pdev)
{
struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next,
struct v4l2_subdev, list);
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct radio_si4713_device *rsdev;
rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev);
video_unregister_device(&rsdev->radio_dev);
i2c_put_adapter(client->adapter);
v4l2_device_unregister(&rsdev->v4l2_dev);
return 0;
}
static struct platform_driver radio_si4713_pdriver = {
.driver = {
.name = "radio-si4713",
.owner = THIS_MODULE,
},
.probe = radio_si4713_pdriver_probe,
.remove = radio_si4713_pdriver_remove,
};
module_platform_driver(radio_si4713_pdriver);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,595 @@
/*
* driver/media/radio/radio-tea5764.c
*
* Driver for TEA5764 radio chip for linux 2.6.
* This driver is for TEA5764 chip from NXP, used in EZX phones from Motorola.
* The I2C protocol is used for communicate with chip.
*
* Based in radio-tea5761.c Copyright (C) 2005 Nokia Corporation
*
* Copyright (c) 2008 Fabio Belavenuto <belavenuto@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
*
* History:
* 2008-12-06 Fabio Belavenuto <belavenuto@gmail.com>
* initial code
*
* TODO:
* add platform_data support for IRQs platform dependencies
* add RDS support
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/init.h> /* Initdata */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/i2c.h> /* I2C */
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#define DRIVER_VERSION "0.0.2"
#define DRIVER_AUTHOR "Fabio Belavenuto <belavenuto@gmail.com>"
#define DRIVER_DESC "A driver for the TEA5764 radio chip for EZX Phones."
#define PINFO(format, ...)\
printk(KERN_INFO KBUILD_MODNAME ": "\
DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
#define PWARN(format, ...)\
printk(KERN_WARNING KBUILD_MODNAME ": "\
DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
# define PDEBUG(format, ...)\
printk(KERN_DEBUG KBUILD_MODNAME ": "\
DRIVER_VERSION ": " format "\n", ## __VA_ARGS__)
/* Frequency limits in MHz -- these are European values. For Japanese
devices, that would be 76000 and 91000. */
#define FREQ_MIN 87500
#define FREQ_MAX 108000
#define FREQ_MUL 16
/* TEA5764 registers */
#define TEA5764_MANID 0x002b
#define TEA5764_CHIPID 0x5764
#define TEA5764_INTREG_BLMSK 0x0001
#define TEA5764_INTREG_FRRMSK 0x0002
#define TEA5764_INTREG_LEVMSK 0x0008
#define TEA5764_INTREG_IFMSK 0x0010
#define TEA5764_INTREG_BLMFLAG 0x0100
#define TEA5764_INTREG_FRRFLAG 0x0200
#define TEA5764_INTREG_LEVFLAG 0x0800
#define TEA5764_INTREG_IFFLAG 0x1000
#define TEA5764_FRQSET_SUD 0x8000
#define TEA5764_FRQSET_SM 0x4000
#define TEA5764_TNCTRL_PUPD1 0x8000
#define TEA5764_TNCTRL_PUPD0 0x4000
#define TEA5764_TNCTRL_BLIM 0x2000
#define TEA5764_TNCTRL_SWPM 0x1000
#define TEA5764_TNCTRL_IFCTC 0x0800
#define TEA5764_TNCTRL_AFM 0x0400
#define TEA5764_TNCTRL_SMUTE 0x0200
#define TEA5764_TNCTRL_SNC 0x0100
#define TEA5764_TNCTRL_MU 0x0080
#define TEA5764_TNCTRL_SSL1 0x0040
#define TEA5764_TNCTRL_SSL0 0x0020
#define TEA5764_TNCTRL_HLSI 0x0010
#define TEA5764_TNCTRL_MST 0x0008
#define TEA5764_TNCTRL_SWP 0x0004
#define TEA5764_TNCTRL_DTC 0x0002
#define TEA5764_TNCTRL_AHLSI 0x0001
#define TEA5764_TUNCHK_LEVEL(x) (((x) & 0x00F0) >> 4)
#define TEA5764_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9)
#define TEA5764_TUNCHK_TUNTO 0x0100
#define TEA5764_TUNCHK_LD 0x0008
#define TEA5764_TUNCHK_STEREO 0x0004
#define TEA5764_TESTREG_TRIGFR 0x0800
struct tea5764_regs {
u16 intreg; /* INTFLAG & INTMSK */
u16 frqset; /* FRQSETMSB & FRQSETLSB */
u16 tnctrl; /* TNCTRL1 & TNCTRL2 */
u16 frqchk; /* FRQCHKMSB & FRQCHKLSB */
u16 tunchk; /* IFCHK & LEVCHK */
u16 testreg; /* TESTBITS & TESTMODE */
u16 rdsstat; /* RDSSTAT1 & RDSSTAT2 */
u16 rdslb; /* RDSLBMSB & RDSLBLSB */
u16 rdspb; /* RDSPBMSB & RDSPBLSB */
u16 rdsbc; /* RDSBBC & RDSGBC */
u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */
u16 rdsbbl; /* PAUSEDET & RDSBBL */
u16 manid; /* MANID1 & MANID2 */
u16 chipid; /* CHIPID1 & CHIPID2 */
} __attribute__ ((packed));
struct tea5764_write_regs {
u8 intreg; /* INTMSK */
u16 frqset; /* FRQSETMSB & FRQSETLSB */
u16 tnctrl; /* TNCTRL1 & TNCTRL2 */
u16 testreg; /* TESTBITS & TESTMODE */
u16 rdsctrl; /* RDSCTRL1 & RDSCTRL2 */
u16 rdsbbl; /* PAUSEDET & RDSBBL */
} __attribute__ ((packed));
#ifdef CONFIG_RADIO_TEA5764_XTAL
#define RADIO_TEA5764_XTAL 1
#else
#define RADIO_TEA5764_XTAL 0
#endif
static int radio_nr = -1;
static int use_xtal = RADIO_TEA5764_XTAL;
struct tea5764_device {
struct i2c_client *i2c_client;
struct video_device *videodev;
struct tea5764_regs regs;
struct mutex mutex;
};
/* I2C code related */
static int tea5764_i2c_read(struct tea5764_device *radio)
{
int i;
u16 *p = (u16 *) &radio->regs;
struct i2c_msg msgs[1] = {
{ .addr = radio->i2c_client->addr,
.flags = I2C_M_RD,
.len = sizeof(radio->regs),
.buf = (void *)&radio->regs
},
};
if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)
return -EIO;
for (i = 0; i < sizeof(struct tea5764_regs) / sizeof(u16); i++)
p[i] = __be16_to_cpu(p[i]);
return 0;
}
static int tea5764_i2c_write(struct tea5764_device *radio)
{
struct tea5764_write_regs wr;
struct tea5764_regs *r = &radio->regs;
struct i2c_msg msgs[1] = {
{
.addr = radio->i2c_client->addr,
.len = sizeof(wr),
.buf = (void *)&wr
},
};
wr.intreg = r->intreg & 0xff;
wr.frqset = __cpu_to_be16(r->frqset);
wr.tnctrl = __cpu_to_be16(r->tnctrl);
wr.testreg = __cpu_to_be16(r->testreg);
wr.rdsctrl = __cpu_to_be16(r->rdsctrl);
wr.rdsbbl = __cpu_to_be16(r->rdsbbl);
if (i2c_transfer(radio->i2c_client->adapter, msgs, 1) != 1)
return -EIO;
return 0;
}
/* V4L2 code related */
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,
}
};
static void tea5764_power_up(struct tea5764_device *radio)
{
struct tea5764_regs *r = &radio->regs;
if (!(r->tnctrl & TEA5764_TNCTRL_PUPD0)) {
r->tnctrl &= ~(TEA5764_TNCTRL_AFM | TEA5764_TNCTRL_MU |
TEA5764_TNCTRL_HLSI);
if (!use_xtal)
r->testreg |= TEA5764_TESTREG_TRIGFR;
else
r->testreg &= ~TEA5764_TESTREG_TRIGFR;
r->tnctrl |= TEA5764_TNCTRL_PUPD0;
tea5764_i2c_write(radio);
}
}
static void tea5764_power_down(struct tea5764_device *radio)
{
struct tea5764_regs *r = &radio->regs;
if (r->tnctrl & TEA5764_TNCTRL_PUPD0) {
r->tnctrl &= ~TEA5764_TNCTRL_PUPD0;
tea5764_i2c_write(radio);
}
}
static void tea5764_set_freq(struct tea5764_device *radio, int freq)
{
struct tea5764_regs *r = &radio->regs;
/* formula: (freq [+ or -] 225000) / 8192 */
if (r->tnctrl & TEA5764_TNCTRL_HLSI)
r->frqset = (freq + 225000) / 8192;
else
r->frqset = (freq - 225000) / 8192;
}
static int tea5764_get_freq(struct tea5764_device *radio)
{
struct tea5764_regs *r = &radio->regs;
if (r->tnctrl & TEA5764_TNCTRL_HLSI)
return (r->frqchk * 8192) - 225000;
else
return (r->frqchk * 8192) + 225000;
}
/* tune an frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
static void tea5764_tune(struct tea5764_device *radio, int freq)
{
tea5764_set_freq(radio, freq);
if (tea5764_i2c_write(radio))
PWARN("Could not set frequency!");
}
static void tea5764_set_audout_mode(struct tea5764_device *radio, int audmode)
{
struct tea5764_regs *r = &radio->regs;
int tnctrl = r->tnctrl;
if (audmode == V4L2_TUNER_MODE_MONO)
r->tnctrl |= TEA5764_TNCTRL_MST;
else
r->tnctrl &= ~TEA5764_TNCTRL_MST;
if (tnctrl != r->tnctrl)
tea5764_i2c_write(radio);
}
static int tea5764_get_audout_mode(struct tea5764_device *radio)
{
struct tea5764_regs *r = &radio->regs;
if (r->tnctrl & TEA5764_TNCTRL_MST)
return V4L2_TUNER_MODE_MONO;
else
return V4L2_TUNER_MODE_STEREO;
}
static void tea5764_mute(struct tea5764_device *radio, int on)
{
struct tea5764_regs *r = &radio->regs;
int tnctrl = r->tnctrl;
if (on)
r->tnctrl |= TEA5764_TNCTRL_MU;
else
r->tnctrl &= ~TEA5764_TNCTRL_MU;
if (tnctrl != r->tnctrl)
tea5764_i2c_write(radio);
}
static int tea5764_is_muted(struct tea5764_device *radio)
{
return radio->regs.tnctrl & TEA5764_TNCTRL_MU;
}
/* V4L2 vidioc */
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct tea5764_device *radio = video_drvdata(file);
struct video_device *dev = radio->videodev;
strlcpy(v->driver, dev->dev.driver->name, sizeof(v->driver));
strlcpy(v->card, dev->name, sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info),
"I2C:%s", dev_name(&dev->dev));
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct tea5764_device *radio = video_drvdata(file);
struct tea5764_regs *r = &radio->regs;
if (v->index > 0)
return -EINVAL;
memset(v, 0, sizeof(*v));
strcpy(v->name, "FM");
v->type = V4L2_TUNER_RADIO;
tea5764_i2c_read(radio);
v->rangelow = FREQ_MIN * FREQ_MUL;
v->rangehigh = FREQ_MAX * FREQ_MUL;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
if (r->tunchk & TEA5764_TUNCHK_STEREO)
v->rxsubchans = V4L2_TUNER_SUB_STEREO;
else
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->audmode = tea5764_get_audout_mode(radio);
v->signal = TEA5764_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;
v->afc = TEA5764_TUNCHK_IFCNT(r->tunchk);
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct tea5764_device *radio = video_drvdata(file);
if (v->index > 0)
return -EINVAL;
tea5764_set_audout_mode(radio, v->audmode);
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct tea5764_device *radio = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
if (f->frequency == 0) {
/* We special case this as a power down control. */
tea5764_power_down(radio);
}
if (f->frequency < (FREQ_MIN * FREQ_MUL))
return -EINVAL;
if (f->frequency > (FREQ_MAX * FREQ_MUL))
return -EINVAL;
tea5764_power_up(radio);
tea5764_tune(radio, (f->frequency * 125) / 2);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct tea5764_device *radio = video_drvdata(file);
struct tea5764_regs *r = &radio->regs;
if (f->tuner != 0)
return -EINVAL;
tea5764_i2c_read(radio);
memset(f, 0, sizeof(*f));
f->type = V4L2_TUNER_RADIO;
if (r->tnctrl & TEA5764_TNCTRL_PUPD0)
f->frequency = (tea5764_get_freq(radio) * 2) / 125;
else
f->frequency = 0;
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 tea5764_device *radio = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
tea5764_i2c_read(radio);
ctrl->value = tea5764_is_muted(radio) ? 1 : 0;
return 0;
}
return -EINVAL;
}
static int vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct tea5764_device *radio = video_drvdata(file);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
tea5764_mute(radio, ctrl->value);
return 0;
}
return -EINVAL;
}
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_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_s_audio(struct file *file, void *priv,
const struct v4l2_audio *a)
{
if (a->index != 0)
return -EINVAL;
return 0;
}
/* File system interface */
static const struct v4l2_file_operations tea5764_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops tea5764_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,
};
/* V4L2 interface */
static struct video_device tea5764_radio_template = {
.name = "TEA5764 FM-Radio",
.fops = &tea5764_fops,
.ioctl_ops = &tea5764_ioctl_ops,
.release = video_device_release,
};
/* I2C probe: check if the device exists and register with v4l if it is */
static int tea5764_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tea5764_device *radio;
struct tea5764_regs *r;
int ret;
PDEBUG("probe");
radio = kzalloc(sizeof(struct tea5764_device), GFP_KERNEL);
if (!radio)
return -ENOMEM;
mutex_init(&radio->mutex);
radio->i2c_client = client;
ret = tea5764_i2c_read(radio);
if (ret)
goto errfr;
r = &radio->regs;
PDEBUG("chipid = %04X, manid = %04X", r->chipid, r->manid);
if (r->chipid != TEA5764_CHIPID ||
(r->manid & 0x0fff) != TEA5764_MANID) {
PWARN("This chip is not a TEA5764!");
ret = -EINVAL;
goto errfr;
}
radio->videodev = video_device_alloc();
if (!(radio->videodev)) {
ret = -ENOMEM;
goto errfr;
}
memcpy(radio->videodev, &tea5764_radio_template,
sizeof(tea5764_radio_template));
i2c_set_clientdata(client, radio);
video_set_drvdata(radio->videodev, radio);
radio->videodev->lock = &radio->mutex;
/* initialize and power off the chip */
tea5764_i2c_read(radio);
tea5764_set_audout_mode(radio, V4L2_TUNER_MODE_STEREO);
tea5764_mute(radio, 1);
tea5764_power_down(radio);
ret = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
if (ret < 0) {
PWARN("Could not register video device!");
goto errrel;
}
PINFO("registered.");
return 0;
errrel:
video_device_release(radio->videodev);
errfr:
kfree(radio);
return ret;
}
static int tea5764_i2c_remove(struct i2c_client *client)
{
struct tea5764_device *radio = i2c_get_clientdata(client);
PDEBUG("remove");
if (radio) {
tea5764_power_down(radio);
video_unregister_device(radio->videodev);
kfree(radio);
}
return 0;
}
/* I2C subsystem interface */
static const struct i2c_device_id tea5764_id[] = {
{ "radio-tea5764", 0 },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(i2c, tea5764_id);
static struct i2c_driver tea5764_i2c_driver = {
.driver = {
.name = "radio-tea5764",
.owner = THIS_MODULE,
},
.probe = tea5764_i2c_probe,
.remove = tea5764_i2c_remove,
.id_table = tea5764_id,
};
module_i2c_driver(tea5764_i2c_driver);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
module_param(use_xtal, int, 0);
MODULE_PARM_DESC(use_xtal, "Chip have a xtal connected in board");
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "video4linux device number to use");

View File

@@ -0,0 +1,603 @@
/*
* v4l2 driver for TEA5777 Philips AM/FM radio tuner chips
*
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
*
* Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips:
*
* Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
*
* 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
*
*/
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include "radio-tea5777.h"
MODULE_AUTHOR("Hans de Goede <perex@perex.cz>");
MODULE_DESCRIPTION("Routines for control of TEA5777 Philips AM/FM radio tuner chips");
MODULE_LICENSE("GPL");
#define TEA5777_FM_IF 150 /* kHz */
#define TEA5777_FM_FREQ_STEP 50 /* kHz */
#define TEA5777_AM_IF 21 /* kHz */
#define TEA5777_AM_FREQ_STEP 1 /* kHz */
/* Write reg, common bits */
#define TEA5777_W_MUTE_MASK (1LL << 47)
#define TEA5777_W_MUTE_SHIFT 47
#define TEA5777_W_AM_FM_MASK (1LL << 46)
#define TEA5777_W_AM_FM_SHIFT 46
#define TEA5777_W_STB_MASK (1LL << 45)
#define TEA5777_W_STB_SHIFT 45
#define TEA5777_W_IFCE_MASK (1LL << 29)
#define TEA5777_W_IFCE_SHIFT 29
#define TEA5777_W_IFW_MASK (1LL << 28)
#define TEA5777_W_IFW_SHIFT 28
#define TEA5777_W_HILO_MASK (1LL << 27)
#define TEA5777_W_HILO_SHIFT 27
#define TEA5777_W_DBUS_MASK (1LL << 26)
#define TEA5777_W_DBUS_SHIFT 26
#define TEA5777_W_INTEXT_MASK (1LL << 24)
#define TEA5777_W_INTEXT_SHIFT 24
#define TEA5777_W_P1_MASK (1LL << 23)
#define TEA5777_W_P1_SHIFT 23
#define TEA5777_W_P0_MASK (1LL << 22)
#define TEA5777_W_P0_SHIFT 22
#define TEA5777_W_PEN1_MASK (1LL << 21)
#define TEA5777_W_PEN1_SHIFT 21
#define TEA5777_W_PEN0_MASK (1LL << 20)
#define TEA5777_W_PEN0_SHIFT 20
#define TEA5777_W_CHP0_MASK (1LL << 18)
#define TEA5777_W_CHP0_SHIFT 18
#define TEA5777_W_DEEM_MASK (1LL << 17)
#define TEA5777_W_DEEM_SHIFT 17
#define TEA5777_W_SEARCH_MASK (1LL << 7)
#define TEA5777_W_SEARCH_SHIFT 7
#define TEA5777_W_PROGBLIM_MASK (1LL << 6)
#define TEA5777_W_PROGBLIM_SHIFT 6
#define TEA5777_W_UPDWN_MASK (1LL << 5)
#define TEA5777_W_UPDWN_SHIFT 5
#define TEA5777_W_SLEV_MASK (3LL << 3)
#define TEA5777_W_SLEV_SHIFT 3
/* Write reg, FM specific bits */
#define TEA5777_W_FM_PLL_MASK (0x1fffLL << 32)
#define TEA5777_W_FM_PLL_SHIFT 32
#define TEA5777_W_FM_FREF_MASK (0x03LL << 30)
#define TEA5777_W_FM_FREF_SHIFT 30
#define TEA5777_W_FM_FREF_VALUE 0LL /* 50k steps, 150k IF */
#define TEA5777_W_FM_FORCEMONO_MASK (1LL << 15)
#define TEA5777_W_FM_FORCEMONO_SHIFT 15
#define TEA5777_W_FM_SDSOFF_MASK (1LL << 14)
#define TEA5777_W_FM_SDSOFF_SHIFT 14
#define TEA5777_W_FM_DOFF_MASK (1LL << 13)
#define TEA5777_W_FM_DOFF_SHIFT 13
#define TEA5777_W_FM_STEP_MASK (3LL << 1)
#define TEA5777_W_FM_STEP_SHIFT 1
/* Write reg, AM specific bits */
#define TEA5777_W_AM_PLL_MASK (0x7ffLL << 34)
#define TEA5777_W_AM_PLL_SHIFT 34
#define TEA5777_W_AM_AGCRF_MASK (1LL << 33)
#define TEA5777_W_AM_AGCRF_SHIFT 33
#define TEA5777_W_AM_AGCIF_MASK (1LL << 32)
#define TEA5777_W_AM_AGCIF_SHIFT 32
#define TEA5777_W_AM_MWLW_MASK (1LL << 31)
#define TEA5777_W_AM_MWLW_SHIFT 31
#define TEA5777_W_AM_LW 0LL
#define TEA5777_W_AM_MW 1LL
#define TEA5777_W_AM_LNA_MASK (1LL << 30)
#define TEA5777_W_AM_LNA_SHIFT 30
#define TEA5777_W_AM_PEAK_MASK (1LL << 25)
#define TEA5777_W_AM_PEAK_SHIFT 25
#define TEA5777_W_AM_RFB_MASK (1LL << 16)
#define TEA5777_W_AM_RFB_SHIFT 16
#define TEA5777_W_AM_CALLIGN_MASK (1LL << 15)
#define TEA5777_W_AM_CALLIGN_SHIFT 15
#define TEA5777_W_AM_CBANK_MASK (0x7fLL << 8)
#define TEA5777_W_AM_CBANK_SHIFT 8
#define TEA5777_W_AM_DELAY_MASK (1LL << 2)
#define TEA5777_W_AM_DELAY_SHIFT 2
#define TEA5777_W_AM_STEP_MASK (1LL << 1)
#define TEA5777_W_AM_STEP_SHIFT 1
/* Read reg, common bits */
#define TEA5777_R_LEVEL_MASK (0x0f << 17)
#define TEA5777_R_LEVEL_SHIFT 17
#define TEA5777_R_SFOUND_MASK (0x01 << 16)
#define TEA5777_R_SFOUND_SHIFT 16
#define TEA5777_R_BLIM_MASK (0x01 << 15)
#define TEA5777_R_BLIM_SHIFT 15
/* Read reg, FM specific bits */
#define TEA5777_R_FM_STEREO_MASK (0x01 << 21)
#define TEA5777_R_FM_STEREO_SHIFT 21
#define TEA5777_R_FM_PLL_MASK 0x1fff
#define TEA5777_R_FM_PLL_SHIFT 0
enum { BAND_FM, BAND_AM };
static const struct v4l2_frequency_band bands[] = {
{
.type = V4L2_TUNER_RADIO,
.index = 0,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
.rangelow = 76000 * 16,
.rangehigh = 108000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
{
.type = V4L2_TUNER_RADIO,
.index = 1,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_PROG_LIM,
.rangelow = 530 * 16,
.rangehigh = 1710 * 16,
.modulation = V4L2_BAND_MODULATION_AM,
},
};
static u32 tea5777_freq_to_v4l2_freq(struct radio_tea5777 *tea, u32 freq)
{
switch (tea->band) {
case BAND_FM:
return (freq * TEA5777_FM_FREQ_STEP + TEA5777_FM_IF) * 16;
case BAND_AM:
return (freq * TEA5777_AM_FREQ_STEP + TEA5777_AM_IF) * 16;
}
return 0; /* Never reached */
}
int radio_tea5777_set_freq(struct radio_tea5777 *tea)
{
u32 freq;
int res;
freq = clamp(tea->freq, bands[tea->band].rangelow,
bands[tea->band].rangehigh);
freq = (freq + 8) / 16; /* to kHz */
switch (tea->band) {
case BAND_FM:
tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
freq = (freq - TEA5777_FM_IF) / TEA5777_FM_FREQ_STEP;
tea->write_reg &= ~TEA5777_W_FM_PLL_MASK;
tea->write_reg |= (u64)freq << TEA5777_W_FM_PLL_SHIFT;
tea->write_reg &= ~TEA5777_W_FM_FREF_MASK;
tea->write_reg |= TEA5777_W_FM_FREF_VALUE <<
TEA5777_W_FM_FREF_SHIFT;
tea->write_reg &= ~TEA5777_W_FM_FORCEMONO_MASK;
if (tea->audmode == V4L2_TUNER_MODE_MONO)
tea->write_reg |= 1LL << TEA5777_W_FM_FORCEMONO_SHIFT;
break;
case BAND_AM:
tea->write_reg &= ~TEA5777_W_AM_FM_MASK;
tea->write_reg |= (1LL << TEA5777_W_AM_FM_SHIFT);
freq = (freq - TEA5777_AM_IF) / TEA5777_AM_FREQ_STEP;
tea->write_reg &= ~TEA5777_W_AM_PLL_MASK;
tea->write_reg |= (u64)freq << TEA5777_W_AM_PLL_SHIFT;
tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
tea->write_reg &= ~TEA5777_W_AM_AGCRF_MASK;
tea->write_reg &= ~TEA5777_W_AM_MWLW_MASK;
tea->write_reg |= TEA5777_W_AM_MW << TEA5777_W_AM_MWLW_SHIFT;
tea->write_reg &= ~TEA5777_W_AM_LNA_MASK;
tea->write_reg |= 1LL << TEA5777_W_AM_LNA_SHIFT;
tea->write_reg &= ~TEA5777_W_AM_PEAK_MASK;
tea->write_reg |= 1LL << TEA5777_W_AM_PEAK_SHIFT;
tea->write_reg &= ~TEA5777_W_AM_CALLIGN_MASK;
break;
}
res = tea->ops->write_reg(tea, tea->write_reg);
if (res)
return res;
tea->needs_write = false;
tea->read_reg = -1;
tea->freq = tea5777_freq_to_v4l2_freq(tea, freq);
return 0;
}
static int radio_tea5777_update_read_reg(struct radio_tea5777 *tea, int wait)
{
int res;
if (tea->read_reg != -1)
return 0;
if (tea->write_before_read && tea->needs_write) {
res = radio_tea5777_set_freq(tea);
if (res)
return res;
}
if (wait) {
if (schedule_timeout_interruptible(msecs_to_jiffies(wait)))
return -ERESTARTSYS;
}
res = tea->ops->read_reg(tea, &tea->read_reg);
if (res)
return res;
tea->needs_write = true;
return 0;
}
/*
* Linux Video interface
*/
static int vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
struct radio_tea5777 *tea = video_drvdata(file);
strlcpy(v->driver, tea->v4l2_dev->name, sizeof(v->driver));
strlcpy(v->card, tea->card, sizeof(v->card));
strlcat(v->card, " TEA5777", sizeof(v->card));
strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
v->device_caps |= V4L2_CAP_HW_FREQ_SEEK;
v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int vidioc_enum_freq_bands(struct file *file, void *priv,
struct v4l2_frequency_band *band)
{
struct radio_tea5777 *tea = video_drvdata(file);
if (band->tuner != 0 || band->index >= ARRAY_SIZE(bands) ||
(!tea->has_am && band->index == BAND_AM))
return -EINVAL;
*band = bands[band->index];
return 0;
}
static int vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct radio_tea5777 *tea = video_drvdata(file);
int res;
if (v->index > 0)
return -EINVAL;
res = radio_tea5777_update_read_reg(tea, 0);
if (res)
return res;
memset(v, 0, sizeof(*v));
if (tea->has_am)
strlcpy(v->name, "AM/FM", sizeof(v->name));
else
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
v->rangelow = tea->has_am ? bands[BAND_AM].rangelow :
bands[BAND_FM].rangelow;
v->rangehigh = bands[BAND_FM].rangehigh;
if (tea->band == BAND_FM &&
(tea->read_reg & TEA5777_R_FM_STEREO_MASK))
v->rxsubchans = V4L2_TUNER_SUB_STEREO;
else
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->audmode = tea->audmode;
/* shift - 12 to convert 4-bits (0-15) scale to 16-bits (0-65535) */
v->signal = (tea->read_reg & TEA5777_R_LEVEL_MASK) >>
(TEA5777_R_LEVEL_SHIFT - 12);
/* Invalidate read_reg, so that next call we return up2date signal */
tea->read_reg = -1;
return 0;
}
static int vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct radio_tea5777 *tea = video_drvdata(file);
u32 orig_audmode = tea->audmode;
if (v->index)
return -EINVAL;
tea->audmode = v->audmode;
if (tea->audmode > V4L2_TUNER_MODE_STEREO)
tea->audmode = V4L2_TUNER_MODE_STEREO;
if (tea->audmode != orig_audmode && tea->band == BAND_FM)
return radio_tea5777_set_freq(tea);
return 0;
}
static int vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct radio_tea5777 *tea = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = tea->freq;
return 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct radio_tea5777 *tea = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
if (tea->has_am && f->frequency < (20000 * 16))
tea->band = BAND_AM;
else
tea->band = BAND_FM;
tea->freq = f->frequency;
return radio_tea5777_set_freq(tea);
}
static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
const struct v4l2_hw_freq_seek *a)
{
struct radio_tea5777 *tea = video_drvdata(file);
unsigned long timeout;
u32 rangelow = a->rangelow;
u32 rangehigh = a->rangehigh;
int i, res, spacing;
u32 orig_freq;
if (a->tuner || a->wrap_around)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
if (rangelow || rangehigh) {
for (i = 0; i < ARRAY_SIZE(bands); i++) {
if (i == BAND_AM && !tea->has_am)
continue;
if (bands[i].rangelow >= rangelow &&
bands[i].rangehigh <= rangehigh)
break;
}
if (i == ARRAY_SIZE(bands))
return -EINVAL; /* No matching band found */
tea->band = i;
if (tea->freq < rangelow || tea->freq > rangehigh) {
tea->freq = clamp(tea->freq, rangelow,
rangehigh);
res = radio_tea5777_set_freq(tea);
if (res)
return res;
}
} else {
rangelow = bands[tea->band].rangelow;
rangehigh = bands[tea->band].rangehigh;
}
spacing = (tea->band == BAND_AM) ? (5 * 16) : (200 * 16); /* kHz */
orig_freq = tea->freq;
tea->write_reg |= TEA5777_W_PROGBLIM_MASK;
if (tea->seek_rangelow != rangelow) {
tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
tea->freq = rangelow;
res = radio_tea5777_set_freq(tea);
if (res)
goto leave;
tea->seek_rangelow = rangelow;
}
if (tea->seek_rangehigh != rangehigh) {
tea->write_reg |= TEA5777_W_UPDWN_MASK;
tea->freq = rangehigh;
res = radio_tea5777_set_freq(tea);
if (res)
goto leave;
tea->seek_rangehigh = rangehigh;
}
tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
tea->write_reg |= TEA5777_W_SEARCH_MASK;
if (a->seek_upward) {
tea->write_reg |= TEA5777_W_UPDWN_MASK;
tea->freq = orig_freq + spacing;
} else {
tea->write_reg &= ~TEA5777_W_UPDWN_MASK;
tea->freq = orig_freq - spacing;
}
res = radio_tea5777_set_freq(tea);
if (res)
goto leave;
timeout = jiffies + msecs_to_jiffies(5000);
for (;;) {
if (time_after(jiffies, timeout)) {
res = -ENODATA;
break;
}
res = radio_tea5777_update_read_reg(tea, 100);
if (res)
break;
/*
* Note we use tea->freq to track how far we've searched sofar
* this is necessary to ensure we continue seeking at the right
* point, in the write_before_read case.
*/
tea->freq = (tea->read_reg & TEA5777_R_FM_PLL_MASK);
tea->freq = tea5777_freq_to_v4l2_freq(tea, tea->freq);
if ((tea->read_reg & TEA5777_R_SFOUND_MASK)) {
tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
return 0;
}
if (tea->read_reg & TEA5777_R_BLIM_MASK) {
res = -ENODATA;
break;
}
/* Force read_reg update */
tea->read_reg = -1;
}
leave:
tea->write_reg &= ~TEA5777_W_PROGBLIM_MASK;
tea->write_reg &= ~TEA5777_W_SEARCH_MASK;
tea->freq = orig_freq;
radio_tea5777_set_freq(tea);
return res;
}
static int tea575x_s_ctrl(struct v4l2_ctrl *c)
{
struct radio_tea5777 *tea =
container_of(c->handler, struct radio_tea5777, ctrl_handler);
switch (c->id) {
case V4L2_CID_AUDIO_MUTE:
if (c->val)
tea->write_reg |= TEA5777_W_MUTE_MASK;
else
tea->write_reg &= ~TEA5777_W_MUTE_MASK;
return radio_tea5777_set_freq(tea);
}
return -EINVAL;
}
static const struct v4l2_file_operations tea575x_fops = {
.unlocked_ioctl = video_ioctl2,
.open = v4l2_fh_open,
.release = v4l2_fh_release,
.poll = v4l2_ctrl_poll,
};
static const struct v4l2_ioctl_ops tea575x_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_s_hw_freq_seek = vidioc_s_hw_freq_seek,
.vidioc_enum_freq_bands = vidioc_enum_freq_bands,
.vidioc_log_status = v4l2_ctrl_log_status,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct video_device tea575x_radio = {
.ioctl_ops = &tea575x_ioctl_ops,
.release = video_device_release_empty,
};
static const struct v4l2_ctrl_ops tea575x_ctrl_ops = {
.s_ctrl = tea575x_s_ctrl,
};
int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner)
{
int res;
tea->write_reg = (1LL << TEA5777_W_IFCE_SHIFT) |
(1LL << TEA5777_W_IFW_SHIFT) |
(1LL << TEA5777_W_INTEXT_SHIFT) |
(1LL << TEA5777_W_CHP0_SHIFT) |
(1LL << TEA5777_W_SLEV_SHIFT);
tea->freq = 90500 * 16; /* 90.5Mhz default */
tea->audmode = V4L2_TUNER_MODE_STEREO;
res = radio_tea5777_set_freq(tea);
if (res) {
v4l2_err(tea->v4l2_dev, "can't set initial freq (%d)\n", res);
return res;
}
tea->vd = tea575x_radio;
video_set_drvdata(&tea->vd, tea);
mutex_init(&tea->mutex);
strlcpy(tea->vd.name, tea->v4l2_dev->name, sizeof(tea->vd.name));
tea->vd.lock = &tea->mutex;
tea->vd.v4l2_dev = tea->v4l2_dev;
tea->fops = tea575x_fops;
tea->fops.owner = owner;
tea->vd.fops = &tea->fops;
set_bit(V4L2_FL_USE_FH_PRIO, &tea->vd.flags);
tea->vd.ctrl_handler = &tea->ctrl_handler;
v4l2_ctrl_handler_init(&tea->ctrl_handler, 1);
v4l2_ctrl_new_std(&tea->ctrl_handler, &tea575x_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
res = tea->ctrl_handler.error;
if (res) {
v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
v4l2_ctrl_handler_free(&tea->ctrl_handler);
return res;
}
v4l2_ctrl_handler_setup(&tea->ctrl_handler);
res = video_register_device(&tea->vd, VFL_TYPE_RADIO, -1);
if (res) {
v4l2_err(tea->v4l2_dev, "can't register video device!\n");
v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
return res;
}
return 0;
}
EXPORT_SYMBOL_GPL(radio_tea5777_init);
void radio_tea5777_exit(struct radio_tea5777 *tea)
{
video_unregister_device(&tea->vd);
v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
}
EXPORT_SYMBOL_GPL(radio_tea5777_exit);

View File

@@ -0,0 +1,90 @@
#ifndef __RADIO_TEA5777_H
#define __RADIO_TEA5777_H
/*
* v4l2 driver for TEA5777 Philips AM/FM radio tuner chips
*
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
*
* Based on the ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips:
*
* Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.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
*
*/
#include <linux/videodev2.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#define TEA575X_FMIF 10700
#define TEA575X_AMIF 450
struct radio_tea5777;
struct radio_tea5777_ops {
/*
* Write the 6 bytes large write register of the tea5777
*
* val represents the 6 write registers, with byte 1 from the
* datasheet being the most significant byte (so byte 5 of the u64),
* and byte 6 from the datasheet being the least significant byte.
*
* returns 0 on success.
*/
int (*write_reg)(struct radio_tea5777 *tea, u64 val);
/*
* Read the 3 bytes large read register of the tea5777
*
* The read value gets returned in val, akin to write_reg, byte 1 from
* the datasheet is stored as the most significant byte (so byte 2 of
* the u32), and byte 3 from the datasheet gets stored as the least
* significant byte (iow byte 0 of the u32).
*
* returns 0 on success.
*/
int (*read_reg)(struct radio_tea5777 *tea, u32 *val);
};
struct radio_tea5777 {
struct v4l2_device *v4l2_dev;
struct v4l2_file_operations fops;
struct video_device vd; /* video device */
bool has_am; /* Device can tune to AM freqs */
bool write_before_read; /* must write before read quirk */
bool needs_write; /* for write before read quirk */
u32 band; /* current band */
u32 freq; /* current frequency */
u32 audmode; /* last set audmode */
u32 seek_rangelow; /* current hwseek limits */
u32 seek_rangehigh;
u32 read_reg;
u64 write_reg;
struct mutex mutex;
struct radio_tea5777_ops *ops;
void *private_data;
u8 card[32];
u8 bus_info[32];
struct v4l2_ctrl_handler ctrl_handler;
};
int radio_tea5777_init(struct radio_tea5777 *tea, struct module *owner);
void radio_tea5777_exit(struct radio_tea5777 *tea);
int radio_tea5777_set_freq(struct radio_tea5777 *tea);
#endif /* __RADIO_TEA5777_H */

View File

@@ -0,0 +1,170 @@
/* 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
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
* 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/videodev2.h> /* kernel radio structs */
#include <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("R. Offermans & others");
MODULE_DESCRIPTION("A driver for the TerraTec ActiveRadio Standalone radio card.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1.99");
/* Note: there seems to be only one possible port (0x590), but without
hardware this is hard to verify. For now, this is the only one we will
support. */
static int io = 0x590;
static int radio_nr = -1;
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device number");
#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 struct radio_isa_card *terratec_alloc(void)
{
return kzalloc(sizeof(struct radio_isa_card), GFP_KERNEL);
}
static int terratec_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
int i;
if (mute)
vol = 0;
vol = vol + (vol * 32); /* change both channels */
for (i = 0; i < 8; i++) {
if (vol & (0x80 >> i))
outb(0x80, isa->io + 1);
else
outb(0x00, isa->io + 1);
}
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 terratec_s_frequency(struct radio_isa_card *isa, u32 freq)
{
int i;
int p;
int temp;
long rest;
unsigned char buffer[25]; /* we have to bit shift 25 registers */
freq = freq / 160; /* convert the freq. to a nice to handle value */
memset(buffer, 0, sizeof(buffer));
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;
}
for (i = 24; i > -1; i--) { /* bit shift the values to the radiocard */
if (buffer[i] == 1) {
outb(WRT_EN | DATA, isa->io);
outb(WRT_EN | DATA | CLK_ON, isa->io);
outb(WRT_EN | DATA, isa->io);
} else {
outb(WRT_EN | 0x00, isa->io);
outb(WRT_EN | 0x00 | CLK_ON, isa->io);
}
}
outb(0x00, isa->io);
return 0;
}
static u32 terratec_g_signal(struct radio_isa_card *isa)
{
/* bit set = no signal present */
return (inb(isa->io) & 2) ? 0 : 0xffff;
}
static const struct radio_isa_ops terratec_ops = {
.alloc = terratec_alloc,
.s_mute_volume = terratec_s_mute_volume,
.s_frequency = terratec_s_frequency,
.g_signal = terratec_g_signal,
};
static const int terratec_ioports[] = { 0x590 };
static struct radio_isa_driver terratec_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-terratec",
},
},
.io_params = &io,
.radio_nr_params = &radio_nr,
.io_ports = terratec_ioports,
.num_of_io_ports = ARRAY_SIZE(terratec_ioports),
.region_size = 2,
.card = "TerraTec ActiveRadio",
.ops = &terratec_ops,
.has_stereo = true,
.max_volume = 10,
};
static int __init terratec_init(void)
{
return isa_register_driver(&terratec_driver.driver, 1);
}
static void __exit terratec_exit(void)
{
isa_unregister_driver(&terratec_driver.driver);
}
module_init(terratec_init);
module_exit(terratec_exit);

View File

@@ -0,0 +1,231 @@
/*
* radio-timb.c Timberdale FPGA Radio driver
* Copyright (c) 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include <linux/io.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <media/timb_radio.h>
#define DRIVER_NAME "timb-radio"
struct timbradio {
struct timb_radio_platform_data pdata;
struct v4l2_subdev *sd_tuner;
struct v4l2_subdev *sd_dsp;
struct video_device video_dev;
struct v4l2_device v4l2_dev;
struct mutex lock;
};
static int timbradio_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *v)
{
strlcpy(v->driver, DRIVER_NAME, sizeof(v->driver));
strlcpy(v->card, "Timberdale Radio", sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "platform:"DRIVER_NAME);
v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
return 0;
}
static int timbradio_vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_tuner, tuner, g_tuner, v);
}
static int timbradio_vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *v)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_tuner, tuner, s_tuner, v);
}
static int timbradio_vidioc_g_input(struct file *filp, void *priv,
unsigned int *i)
{
*i = 0;
return 0;
}
static int timbradio_vidioc_s_input(struct file *filp, void *priv,
unsigned int i)
{
return i ? -EINVAL : 0;
}
static int timbradio_vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *a)
{
a->index = 0;
strlcpy(a->name, "Radio", sizeof(a->name));
a->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int timbradio_vidioc_s_audio(struct file *file, void *priv,
const struct v4l2_audio *a)
{
return a->index ? -EINVAL : 0;
}
static int timbradio_vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *f)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f);
}
static int timbradio_vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *f)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_tuner, tuner, g_frequency, f);
}
static int timbradio_vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_dsp, core, queryctrl, qc);
}
static int timbradio_vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_dsp, core, g_ctrl, ctrl);
}
static int timbradio_vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_dsp, core, s_ctrl, ctrl);
}
static const struct v4l2_ioctl_ops timbradio_ioctl_ops = {
.vidioc_querycap = timbradio_vidioc_querycap,
.vidioc_g_tuner = timbradio_vidioc_g_tuner,
.vidioc_s_tuner = timbradio_vidioc_s_tuner,
.vidioc_g_frequency = timbradio_vidioc_g_frequency,
.vidioc_s_frequency = timbradio_vidioc_s_frequency,
.vidioc_g_input = timbradio_vidioc_g_input,
.vidioc_s_input = timbradio_vidioc_s_input,
.vidioc_g_audio = timbradio_vidioc_g_audio,
.vidioc_s_audio = timbradio_vidioc_s_audio,
.vidioc_queryctrl = timbradio_vidioc_queryctrl,
.vidioc_g_ctrl = timbradio_vidioc_g_ctrl,
.vidioc_s_ctrl = timbradio_vidioc_s_ctrl
};
static const struct v4l2_file_operations timbradio_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
};
static int timbradio_probe(struct platform_device *pdev)
{
struct timb_radio_platform_data *pdata = pdev->dev.platform_data;
struct timbradio *tr;
int err;
if (!pdata) {
dev_err(&pdev->dev, "Platform data missing\n");
err = -EINVAL;
goto err;
}
tr = devm_kzalloc(&pdev->dev, sizeof(*tr), GFP_KERNEL);
if (!tr) {
err = -ENOMEM;
goto err;
}
tr->pdata = *pdata;
mutex_init(&tr->lock);
strlcpy(tr->video_dev.name, "Timberdale Radio",
sizeof(tr->video_dev.name));
tr->video_dev.fops = &timbradio_fops;
tr->video_dev.ioctl_ops = &timbradio_ioctl_ops;
tr->video_dev.release = video_device_release_empty;
tr->video_dev.minor = -1;
tr->video_dev.lock = &tr->lock;
strlcpy(tr->v4l2_dev.name, DRIVER_NAME, sizeof(tr->v4l2_dev.name));
err = v4l2_device_register(NULL, &tr->v4l2_dev);
if (err)
goto err;
tr->video_dev.v4l2_dev = &tr->v4l2_dev;
err = video_register_device(&tr->video_dev, VFL_TYPE_RADIO, -1);
if (err) {
dev_err(&pdev->dev, "Error reg video\n");
goto err_video_req;
}
video_set_drvdata(&tr->video_dev, tr);
platform_set_drvdata(pdev, tr);
return 0;
err_video_req:
video_device_release_empty(&tr->video_dev);
v4l2_device_unregister(&tr->v4l2_dev);
err:
dev_err(&pdev->dev, "Failed to register: %d\n", err);
return err;
}
static int timbradio_remove(struct platform_device *pdev)
{
struct timbradio *tr = platform_get_drvdata(pdev);
video_unregister_device(&tr->video_dev);
video_device_release_empty(&tr->video_dev);
v4l2_device_unregister(&tr->v4l2_dev);
return 0;
}
static struct platform_driver timbradio_platform_driver = {
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
.probe = timbradio_probe,
.remove = timbradio_remove,
};
module_platform_driver(timbradio_platform_driver);
MODULE_DESCRIPTION("Timberdale Radio driver");
MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("0.0.2");
MODULE_ALIAS("platform:"DRIVER_NAME);

View File

@@ -0,0 +1,243 @@
/* 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 <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
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_VERSION("0.1.99");
/* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */
#ifndef CONFIG_RADIO_TRUST_PORT
#define CONFIG_RADIO_TRUST_PORT -1
#endif
#define TRUST_MAX 2
static int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT,
[1 ... (TRUST_MAX - 1)] = -1 };
static int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct trust {
struct radio_isa_card isa;
int ioval;
};
static struct radio_isa_card *trust_alloc(void)
{
struct trust *tr = kzalloc(sizeof(*tr), GFP_KERNEL);
return tr ? &tr->isa : NULL;
}
/* i2c addresses */
#define TDA7318_ADDR 0x88
#define TSA6060T_ADDR 0xc4
#define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0)
#define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io)
#define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io)
#define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io)
#define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io)
static void write_i2c(struct trust *tr, 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 int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct trust *tr = container_of(isa, struct trust, isa);
tr->ioval = (tr->ioval & 0xf7) | (mute << 3);
outb(tr->ioval, isa->io);
write_i2c(tr, 2, TDA7318_ADDR, vol ^ 0x1f);
return 0;
}
static int trust_s_stereo(struct radio_isa_card *isa, bool stereo)
{
struct trust *tr = container_of(isa, struct trust, isa);
tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2);
outb(tr->ioval, isa->io);
return 0;
}
static u32 trust_g_signal(struct radio_isa_card *isa)
{
int i, v;
for (i = 0, v = 0; i < 100; i++)
v |= inb(isa->io);
return (v & 1) ? 0 : 0xffff;
}
static int trust_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct trust *tr = container_of(isa, struct trust, isa);
freq /= 160; /* Convert to 10 kHz units */
freq += 1070; /* Add 10.7 MHz IF */
write_i2c(tr, 5, TSA6060T_ADDR, (freq << 1) | 1,
freq >> 7, 0x60 | ((freq >> 15) & 1), 0);
return 0;
}
static int basstreble2chip[15] = {
0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8
};
static int trust_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct radio_isa_card *isa =
container_of(ctrl->handler, struct radio_isa_card, hdl);
struct trust *tr = container_of(isa, struct trust, isa);
switch (ctrl->id) {
case V4L2_CID_AUDIO_BASS:
write_i2c(tr, 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]);
return 0;
case V4L2_CID_AUDIO_TREBLE:
write_i2c(tr, 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]);
return 0;
}
return -EINVAL;
}
static const struct v4l2_ctrl_ops trust_ctrl_ops = {
.s_ctrl = trust_s_ctrl,
};
static int trust_initialize(struct radio_isa_card *isa)
{
struct trust *tr = container_of(isa, struct trust, isa);
tr->ioval = 0xf;
write_i2c(tr, 2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */
write_i2c(tr, 2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */
v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
V4L2_CID_AUDIO_BASS, 0, 15, 1, 8);
v4l2_ctrl_new_std(&isa->hdl, &trust_ctrl_ops,
V4L2_CID_AUDIO_TREBLE, 0, 15, 1, 8);
return isa->hdl.error;
}
static const struct radio_isa_ops trust_ops = {
.init = trust_initialize,
.alloc = trust_alloc,
.s_mute_volume = trust_s_mute_volume,
.s_frequency = trust_s_frequency,
.s_stereo = trust_s_stereo,
.g_signal = trust_g_signal,
};
static const int trust_ioports[] = { 0x350, 0x358 };
static struct radio_isa_driver trust_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-trust",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = trust_ioports,
.num_of_io_ports = ARRAY_SIZE(trust_ioports),
.region_size = 2,
.card = "Trust FM Radio",
.ops = &trust_ops,
.has_stereo = true,
.max_volume = 31,
};
static int __init trust_init(void)
{
return isa_register_driver(&trust_driver.driver, TRUST_MAX);
}
static void __exit trust_exit(void)
{
isa_unregister_driver(&trust_driver.driver);
}
module_init(trust_init);
module_exit(trust_exit);

View File

@@ -0,0 +1,180 @@
/* Typhoon Radio Card driver for radio support
* (c) 1999 Dr. Henrik Seidel <Henrik.Seidel@gmx.de>
*
* 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/videodev2.h> /* kernel radio structs */
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
#define DRIVER_VERSION "0.1.2"
MODULE_AUTHOR("Dr. Henrik Seidel");
MODULE_DESCRIPTION("A driver for the Typhoon radio card (a.k.a. EcoRadio).");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_TYPHOON_PORT
#define CONFIG_RADIO_TYPHOON_PORT -1
#endif
#ifndef CONFIG_RADIO_TYPHOON_MUTEFREQ
#define CONFIG_RADIO_TYPHOON_MUTEFREQ 87000
#endif
#define TYPHOON_MAX 2
static int io[TYPHOON_MAX] = { [0] = CONFIG_RADIO_TYPHOON_PORT,
[1 ... (TYPHOON_MAX - 1)] = -1 };
static int radio_nr[TYPHOON_MAX] = { [0 ... (TYPHOON_MAX - 1)] = -1 };
static unsigned long mutefreq = CONFIG_RADIO_TYPHOON_MUTEFREQ;
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Typhoon card (0x316 or 0x336)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
module_param(mutefreq, ulong, 0);
MODULE_PARM_DESC(mutefreq, "Frequency used when muting the card (in kHz)");
struct typhoon {
struct radio_isa_card isa;
int muted;
};
static struct radio_isa_card *typhoon_alloc(void)
{
struct typhoon *ty = kzalloc(sizeof(*ty), GFP_KERNEL);
return ty ? &ty->isa : NULL;
}
static int typhoon_s_frequency(struct radio_isa_card *isa, u32 freq)
{
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.
*
*/
x = freq / 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, isa->io + 4);
outb_p(outval >> 9, isa->io + 6);
outb_p(outval & 0xff, isa->io + 8);
return 0;
}
static int typhoon_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct typhoon *ty = container_of(isa, struct typhoon, isa);
if (mute)
vol = 0;
vol >>= 14; /* Map 16 bit to 2 bit */
vol &= 3;
outb_p(vol / 2, isa->io); /* Set the volume, high bit. */
outb_p(vol % 2, isa->io + 2); /* Set the volume, low bit. */
if (vol == 0 && !ty->muted) {
ty->muted = true;
return typhoon_s_frequency(isa, mutefreq << 4);
}
if (vol && ty->muted) {
ty->muted = false;
return typhoon_s_frequency(isa, isa->freq);
}
return 0;
}
static const struct radio_isa_ops typhoon_ops = {
.alloc = typhoon_alloc,
.s_mute_volume = typhoon_s_mute_volume,
.s_frequency = typhoon_s_frequency,
};
static const int typhoon_ioports[] = { 0x316, 0x336 };
static struct radio_isa_driver typhoon_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-typhoon",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = typhoon_ioports,
.num_of_io_ports = ARRAY_SIZE(typhoon_ioports),
.region_size = 8,
.card = "Typhoon Radio",
.ops = &typhoon_ops,
.has_stereo = true,
.max_volume = 3,
};
static int __init typhoon_init(void)
{
if (mutefreq < 87000 || mutefreq > 108000) {
printk(KERN_ERR "%s: You must set a frequency (in kHz) used when muting the card,\n",
typhoon_driver.driver.driver.name);
printk(KERN_ERR "%s: e.g. with \"mutefreq=87500\" (87000 <= mutefreq <= 108000)\n",
typhoon_driver.driver.driver.name);
return -ENODEV;
}
return isa_register_driver(&typhoon_driver.driver, TYPHOON_MAX);
}
static void __exit typhoon_exit(void)
{
isa_unregister_driver(&typhoon_driver.driver);
}
module_init(typhoon_init);
module_exit(typhoon_exit);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,248 @@
/*
* Zoltrix Radio Plus driver
* Copyright 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>
*
* Converted to the radio-isa framework by Hans Verkuil <hans.verkuil@cisco.com>
*
* Note that this is the driver for the Zoltrix Radio Plus.
* This driver does not work for the Zoltrix Radio Plus 108 or the
* Zoltrix Radio Plus for Windows.
*
* Fully tested with the Keene USB FM Transmitter and the v4l2-compliance tool.
*/
#include <linux/module.h> /* Modules */
#include <linux/init.h> /* Initdata */
#include <linux/ioport.h> /* request_region */
#include <linux/delay.h> /* udelay, msleep */
#include <linux/videodev2.h> /* kernel radio structs */
#include <linux/mutex.h>
#include <linux/io.h> /* outb, outb_p */
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include "radio-isa.h"
MODULE_AUTHOR("C. van Schaik");
MODULE_DESCRIPTION("A driver for the Zoltrix Radio Plus.");
MODULE_LICENSE("GPL");
MODULE_VERSION("0.1.99");
#ifndef CONFIG_RADIO_ZOLTRIX_PORT
#define CONFIG_RADIO_ZOLTRIX_PORT -1
#endif
#define ZOLTRIX_MAX 2
static int io[ZOLTRIX_MAX] = { [0] = CONFIG_RADIO_ZOLTRIX_PORT,
[1 ... (ZOLTRIX_MAX - 1)] = -1 };
static int radio_nr[ZOLTRIX_MAX] = { [0 ... (ZOLTRIX_MAX - 1)] = -1 };
module_param_array(io, int, NULL, 0444);
MODULE_PARM_DESC(io, "I/O addresses of the Zoltrix Radio Plus card (0x20c or 0x30c)");
module_param_array(radio_nr, int, NULL, 0444);
MODULE_PARM_DESC(radio_nr, "Radio device numbers");
struct zoltrix {
struct radio_isa_card isa;
int curvol;
bool muted;
};
static struct radio_isa_card *zoltrix_alloc(void)
{
struct zoltrix *zol = kzalloc(sizeof(*zol), GFP_KERNEL);
return zol ? &zol->isa : NULL;
}
static int zoltrix_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
zol->curvol = vol;
zol->muted = mute;
if (mute || vol == 0) {
outb(0, isa->io);
outb(0, isa->io);
inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
return 0;
}
outb(vol - 1, isa->io);
msleep(10);
inb(isa->io + 2);
return 0;
}
/* tunes the radio to the desired frequency */
static int zoltrix_s_frequency(struct radio_isa_card *isa, u32 freq)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
struct v4l2_device *v4l2_dev = &isa->v4l2_dev;
unsigned long long bitmask, f, m;
bool stereo = isa->stereo;
int i;
if (freq == 0) {
v4l2_warn(v4l2_dev, "cannot set a frequency of 0.\n");
return -EINVAL;
}
m = (freq / 160 - 8800) * 2;
f = (unsigned long long)m + 0x4d1c;
bitmask = 0xc480402c10080000ull;
i = 45;
outb(0, isa->io);
outb(0, isa->io);
inb(isa->io + 3); /* Zoltrix needs to be read to confirm */
outb(0x40, isa->io);
outb(0xc0, isa->io);
bitmask = (bitmask ^ ((f & 0xff) << 47) ^ ((f & 0xff00) << 30) ^ (stereo << 31));
while (i--) {
if ((bitmask & 0x8000000000000000ull) != 0) {
outb(0x80, isa->io);
udelay(50);
outb(0x00, isa->io);
udelay(50);
outb(0x80, isa->io);
udelay(50);
} else {
outb(0xc0, isa->io);
udelay(50);
outb(0x40, isa->io);
udelay(50);
outb(0xc0, isa->io);
udelay(50);
}
bitmask *= 2;
}
/* termination sequence */
outb(0x80, isa->io);
outb(0xc0, isa->io);
outb(0x40, isa->io);
udelay(1000);
inb(isa->io + 2);
udelay(1000);
return zoltrix_s_mute_volume(isa, zol->muted, zol->curvol);
}
/* Get signal strength */
static u32 zoltrix_g_rxsubchans(struct radio_isa_card *isa)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
int a, b;
outb(0x00, isa->io); /* This stuff I found to do nothing */
outb(zol->curvol, isa->io);
msleep(20);
a = inb(isa->io);
msleep(10);
b = inb(isa->io);
return (a == b && a == 0xcf) ?
V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
}
static u32 zoltrix_g_signal(struct radio_isa_card *isa)
{
struct zoltrix *zol = container_of(isa, struct zoltrix, isa);
int a, b;
outb(0x00, isa->io); /* This stuff I found to do nothing */
outb(zol->curvol, isa->io);
msleep(20);
a = inb(isa->io);
msleep(10);
b = inb(isa->io);
if (a != b)
return 0;
/* I found this out by playing with a binary scanner on the card io */
return (a == 0xcf || a == 0xdf || a == 0xef) ? 0xffff : 0;
}
static int zoltrix_s_stereo(struct radio_isa_card *isa, bool stereo)
{
return zoltrix_s_frequency(isa, isa->freq);
}
static const struct radio_isa_ops zoltrix_ops = {
.alloc = zoltrix_alloc,
.s_mute_volume = zoltrix_s_mute_volume,
.s_frequency = zoltrix_s_frequency,
.s_stereo = zoltrix_s_stereo,
.g_rxsubchans = zoltrix_g_rxsubchans,
.g_signal = zoltrix_g_signal,
};
static const int zoltrix_ioports[] = { 0x20c, 0x30c };
static struct radio_isa_driver zoltrix_driver = {
.driver = {
.match = radio_isa_match,
.probe = radio_isa_probe,
.remove = radio_isa_remove,
.driver = {
.name = "radio-zoltrix",
},
},
.io_params = io,
.radio_nr_params = radio_nr,
.io_ports = zoltrix_ioports,
.num_of_io_ports = ARRAY_SIZE(zoltrix_ioports),
.region_size = 2,
.card = "Zoltrix Radio Plus",
.ops = &zoltrix_ops,
.has_stereo = true,
.max_volume = 15,
};
static int __init zoltrix_init(void)
{
return isa_register_driver(&zoltrix_driver.driver, ZOLTRIX_MAX);
}
static void __exit zoltrix_exit(void)
{
isa_unregister_driver(&zoltrix_driver.driver);
}
module_init(zoltrix_init);
module_exit(zoltrix_exit);

View File

@@ -0,0 +1,452 @@
/*
* saa7706.c Philips SAA7706H Car Radio DSP driver
* Copyright (c) 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
#include <media/v4l2-chip-ident.h>
#define DRIVER_NAME "saa7706h"
/* the I2C memory map looks like this
$1C00 - $FFFF Not Used
$2200 - $3FFF Reserved YRAM (DSP2) space
$2000 - $21FF YRAM (DSP2)
$1FF0 - $1FFF Hardware Registers
$1280 - $1FEF Reserved XRAM (DSP2) space
$1000 - $127F XRAM (DSP2)
$0FFF DSP CONTROL
$0A00 - $0FFE Reserved
$0980 - $09FF Reserved YRAM (DSP1) space
$0800 - $097F YRAM (DSP1)
$0200 - $07FF Not Used
$0180 - $01FF Reserved XRAM (DSP1) space
$0000 - $017F XRAM (DSP1)
*/
#define SAA7706H_REG_CTRL 0x0fff
#define SAA7706H_CTRL_BYP_PLL 0x0001
#define SAA7706H_CTRL_PLL_DIV_MASK 0x003e
#define SAA7706H_CTRL_PLL3_62975MHZ 0x003e
#define SAA7706H_CTRL_DSP_TURBO 0x0040
#define SAA7706H_CTRL_PC_RESET_DSP1 0x0080
#define SAA7706H_CTRL_PC_RESET_DSP2 0x0100
#define SAA7706H_CTRL_DSP1_ROM_EN_MASK 0x0600
#define SAA7706H_CTRL_DSP1_FUNC_PROM 0x0000
#define SAA7706H_CTRL_DSP2_ROM_EN_MASK 0x1800
#define SAA7706H_CTRL_DSP2_FUNC_PROM 0x0000
#define SAA7706H_CTRL_DIG_SIL_INTERPOL 0x8000
#define SAA7706H_REG_EVALUATION 0x1ff0
#define SAA7706H_EVAL_DISABLE_CHARGE_PUMP 0x000001
#define SAA7706H_EVAL_DCS_CLOCK 0x000002
#define SAA7706H_EVAL_GNDRC1_ENABLE 0x000004
#define SAA7706H_EVAL_GNDRC2_ENABLE 0x000008
#define SAA7706H_REG_CL_GEN1 0x1ff3
#define SAA7706H_CL_GEN1_MIN_LOOPGAIN_MASK 0x00000f
#define SAA7706H_CL_GEN1_LOOPGAIN_MASK 0x0000f0
#define SAA7706H_CL_GEN1_COARSE_RATION 0xffff00
#define SAA7706H_REG_CL_GEN2 0x1ff4
#define SAA7706H_CL_GEN2_WSEDGE_FALLING 0x000001
#define SAA7706H_CL_GEN2_STOP_VCO 0x000002
#define SAA7706H_CL_GEN2_FRERUN 0x000004
#define SAA7706H_CL_GEN2_ADAPTIVE 0x000008
#define SAA7706H_CL_GEN2_FINE_RATIO_MASK 0x0ffff0
#define SAA7706H_REG_CL_GEN4 0x1ff6
#define SAA7706H_CL_GEN4_BYPASS_PLL1 0x001000
#define SAA7706H_CL_GEN4_PLL1_DIV_MASK 0x03e000
#define SAA7706H_CL_GEN4_DSP1_TURBO 0x040000
#define SAA7706H_REG_SEL 0x1ff7
#define SAA7706H_SEL_DSP2_SRCA_MASK 0x000007
#define SAA7706H_SEL_DSP2_FMTA_MASK 0x000031
#define SAA7706H_SEL_DSP2_SRCB_MASK 0x0001c0
#define SAA7706H_SEL_DSP2_FMTB_MASK 0x000e00
#define SAA7706H_SEL_DSP1_SRC_MASK 0x003000
#define SAA7706H_SEL_DSP1_FMT_MASK 0x01c003
#define SAA7706H_SEL_SPDIF2 0x020000
#define SAA7706H_SEL_HOST_IO_FMT_MASK 0x1c0000
#define SAA7706H_SEL_EN_HOST_IO 0x200000
#define SAA7706H_REG_IAC 0x1ff8
#define SAA7706H_REG_CLK_SET 0x1ff9
#define SAA7706H_REG_CLK_COEFF 0x1ffa
#define SAA7706H_REG_INPUT_SENS 0x1ffb
#define SAA7706H_INPUT_SENS_RDS_VOL_MASK 0x0003f
#define SAA7706H_INPUT_SENS_FM_VOL_MASK 0x00fc0
#define SAA7706H_INPUT_SENS_FM_MPX 0x01000
#define SAA7706H_INPUT_SENS_OFF_FILTER_A_EN 0x02000
#define SAA7706H_INPUT_SENS_OFF_FILTER_B_EN 0x04000
#define SAA7706H_REG_PHONE_NAV_AUDIO 0x1ffc
#define SAA7706H_REG_IO_CONF_DSP2 0x1ffd
#define SAA7706H_REG_STATUS_DSP2 0x1ffe
#define SAA7706H_REG_PC_DSP2 0x1fff
#define SAA7706H_DSP1_MOD0 0x0800
#define SAA7706H_DSP1_ROM_VER 0x097f
#define SAA7706H_DSP2_MPTR0 0x1000
#define SAA7706H_DSP1_MODPNTR 0x0000
#define SAA7706H_DSP2_XMEM_CONTLLCW 0x113e
#define SAA7706H_DSP2_XMEM_BUSAMP 0x114a
#define SAA7706H_DSP2_XMEM_FDACPNTR 0x11f9
#define SAA7706H_DSP2_XMEM_IIS1PNTR 0x11fb
#define SAA7706H_DSP2_YMEM_PVGA 0x212a
#define SAA7706H_DSP2_YMEM_PVAT1 0x212b
#define SAA7706H_DSP2_YMEM_PVAT 0x212c
#define SAA7706H_DSP2_YMEM_ROM_VER 0x21ff
#define SUPPORTED_DSP1_ROM_VER 0x667
struct saa7706h_state {
struct v4l2_subdev sd;
unsigned muted;
};
static inline struct saa7706h_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct saa7706h_state, sd);
}
static int saa7706h_i2c_send(struct i2c_client *client, const u8 *data, int len)
{
int err = i2c_master_send(client, data, len);
if (err == len)
return 0;
return err > 0 ? -EIO : err;
}
static int saa7706h_i2c_transfer(struct i2c_client *client,
struct i2c_msg *msgs, int num)
{
int err = i2c_transfer(client->adapter, msgs, num);
if (err == num)
return 0;
return err > 0 ? -EIO : err;
}
static int saa7706h_set_reg24(struct v4l2_subdev *sd, u16 reg, u32 val)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
u8 buf[5];
int pos = 0;
buf[pos++] = reg >> 8;
buf[pos++] = reg;
buf[pos++] = val >> 16;
buf[pos++] = val >> 8;
buf[pos++] = val;
return saa7706h_i2c_send(client, buf, pos);
}
static int saa7706h_set_reg24_err(struct v4l2_subdev *sd, u16 reg, u32 val,
int *err)
{
return *err ? *err : saa7706h_set_reg24(sd, reg, val);
}
static int saa7706h_set_reg16(struct v4l2_subdev *sd, u16 reg, u16 val)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
u8 buf[4];
int pos = 0;
buf[pos++] = reg >> 8;
buf[pos++] = reg;
buf[pos++] = val >> 8;
buf[pos++] = val;
return saa7706h_i2c_send(client, buf, pos);
}
static int saa7706h_set_reg16_err(struct v4l2_subdev *sd, u16 reg, u16 val,
int *err)
{
return *err ? *err : saa7706h_set_reg16(sd, reg, val);
}
static int saa7706h_get_reg16(struct v4l2_subdev *sd, u16 reg)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
u8 buf[2];
int err;
u8 regaddr[] = {reg >> 8, reg};
struct i2c_msg msg[] = {
{
.addr = client->addr,
.len = sizeof(regaddr),
.buf = regaddr
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = sizeof(buf),
.buf = buf
}
};
err = saa7706h_i2c_transfer(client, msg, ARRAY_SIZE(msg));
if (err)
return err;
return buf[0] << 8 | buf[1];
}
static int saa7706h_unmute(struct v4l2_subdev *sd)
{
struct saa7706h_state *state = to_state(sd);
int err = 0;
err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
SAA7706H_CTRL_PC_RESET_DSP2, &err);
/* newer versions of the chip requires a small sleep after reset */
msleep(1);
err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
SAA7706H_CTRL_PLL3_62975MHZ, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_EVALUATION, 0, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN1, 0x040022, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN2,
SAA7706H_CL_GEN2_WSEDGE_FALLING, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN4, 0x024080, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_SEL, 0x200080, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IAC, 0xf4caed, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_SET, 0x124334, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_COEFF, 0x004a1a,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_INPUT_SENS, 0x0071c7,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PHONE_NAV_AUDIO,
0x0e22ff, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IO_CONF_DSP2, 0x001ff8,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_STATUS_DSP2, 0x080003,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PC_DSP2, 0x000004, &err);
err = saa7706h_set_reg16_err(sd, SAA7706H_DSP1_MOD0, 0x0c6c, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_MPTR0, 0x000b4b, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x000600, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x0000c0, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000819,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x00085a,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_BUSAMP, 0x7fffff,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_FDACPNTR, 0x2000cb,
&err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_IIS1PNTR, 0x2000cb,
&err);
err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVGA, 0x0f80, &err);
err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT1, 0x0800,
&err);
err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT, 0x0800, &err);
err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000905,
&err);
if (!err)
state->muted = 0;
return err;
}
static int saa7706h_mute(struct v4l2_subdev *sd)
{
struct saa7706h_state *state = to_state(sd);
int err;
err = saa7706h_set_reg16(sd, SAA7706H_REG_CTRL,
SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
SAA7706H_CTRL_PC_RESET_DSP2);
if (!err)
state->muted = 1;
return err;
}
static int saa7706h_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
{
switch (qc->id) {
case V4L2_CID_AUDIO_MUTE:
return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
}
return -EINVAL;
}
static int saa7706h_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
{
struct saa7706h_state *state = to_state(sd);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
ctrl->value = state->muted;
return 0;
}
return -EINVAL;
}
static int saa7706h_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
{
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
if (ctrl->value)
return saa7706h_mute(sd);
return saa7706h_unmute(sd);
}
return -EINVAL;
}
static int saa7706h_g_chip_ident(struct v4l2_subdev *sd,
struct v4l2_dbg_chip_ident *chip)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_SAA7706H, 0);
}
static const struct v4l2_subdev_core_ops saa7706h_core_ops = {
.g_chip_ident = saa7706h_g_chip_ident,
.queryctrl = saa7706h_queryctrl,
.g_ctrl = saa7706h_g_ctrl,
.s_ctrl = saa7706h_s_ctrl,
};
static const struct v4l2_subdev_ops saa7706h_ops = {
.core = &saa7706h_core_ops,
};
/*
* Generic i2c probe
* concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
*/
static int saa7706h_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct saa7706h_state *state;
struct v4l2_subdev *sd;
int err;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
v4l_info(client, "chip found @ 0x%02x (%s)\n",
client->addr << 1, client->adapter->name);
state = kzalloc(sizeof(struct saa7706h_state), GFP_KERNEL);
if (state == NULL)
return -ENOMEM;
sd = &state->sd;
v4l2_i2c_subdev_init(sd, client, &saa7706h_ops);
/* check the rom versions */
err = saa7706h_get_reg16(sd, SAA7706H_DSP1_ROM_VER);
if (err < 0)
goto err;
if (err != SUPPORTED_DSP1_ROM_VER)
v4l2_warn(sd, "Unknown DSP1 ROM code version: 0x%x\n", err);
state->muted = 1;
/* startup in a muted state */
err = saa7706h_mute(sd);
if (err)
goto err;
return 0;
err:
v4l2_device_unregister_subdev(sd);
kfree(to_state(sd));
printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err);
return err;
}
static int saa7706h_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
saa7706h_mute(sd);
v4l2_device_unregister_subdev(sd);
kfree(to_state(sd));
return 0;
}
static const struct i2c_device_id saa7706h_id[] = {
{DRIVER_NAME, 0},
{},
};
MODULE_DEVICE_TABLE(i2c, saa7706h_id);
static struct i2c_driver saa7706h_driver = {
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
},
.probe = saa7706h_probe,
.remove = saa7706h_remove,
.id_table = saa7706h_id,
};
module_i2c_driver(saa7706h_driver);
MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver");
MODULE_AUTHOR("Mocean Laboratories");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,37 @@
config USB_SI470X
tristate "Silicon Labs Si470x FM Radio Receiver support with USB"
depends on USB && RADIO_SI470X
---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)
- 10c5:819a: Sanei Electric FM USB Radio (aka DealExtreme.com PCear)
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-usb-si470x.
config I2C_SI470X
tristate "Silicon Labs Si470x FM Radio Receiver support with I2C"
depends on I2C && RADIO_SI470X && !USB_SI470X
---help---
This is a driver for I2C devices with the Silicon Labs SI470x
chip.
Say Y here if you want to connect this type of radio to your
computer's I2C port.
To compile this driver as a module, choose M here: the
module will be called radio-i2c-si470x.

View File

@@ -0,0 +1,9 @@
#
# Makefile for radios with Silicon Labs Si470x FM Radio Receivers
#
radio-usb-si470x-objs := radio-si470x-usb.o radio-si470x-common.o
radio-i2c-si470x-objs := radio-si470x-i2c.o radio-si470x-common.o
obj-$(CONFIG_USB_SI470X) += radio-usb-si470x.o
obj-$(CONFIG_I2C_SI470X) += radio-i2c-si470x.o

View File

@@ -0,0 +1,759 @@
/*
* drivers/media/radio/si470x/radio-si470x-common.c
*
* Driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.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
*/
/*
* 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
* 2008-12-03 Mark Lord <mlord@pobox.com>
* - add support for DealExtreme USB Radio
* 2009-01-31 Bob Ross <pigiron@gmx.com>
* - correction of stereo detection/setting
* - correction of signal strength indicator scaling
* 2009-01-31 Rick Bronson <rick@efn.org>
* Tobias Lorenz <tobias.lorenz@gmx.net>
* - add LED status output
* - get HW/SW version from scratchpad
* 2009-06-16 Edouard Lafargue <edouard@lafargue.name>
* Version 1.0.10
* - add support for interrupt mode for RDS endpoint,
* instead of polling.
* Improves RDS reception significantly
*/
/* kernel includes */
#include "radio-si470x.h"
/**************************************************************************
* Module Parameters
**************************************************************************/
/* 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*");
/* 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*");
/* 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*");
static const struct v4l2_frequency_band bands[] = {
{
.type = V4L2_TUNER_RADIO,
.index = 0,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 87500 * 16,
.rangehigh = 108000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
{
.type = V4L2_TUNER_RADIO,
.index = 1,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 76000 * 16,
.rangehigh = 108000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
{
.type = V4L2_TUNER_RADIO,
.index = 2,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 76000 * 16,
.rangehigh = 90000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
};
/**************************************************************************
* Generic Functions
**************************************************************************/
/*
* si470x_set_band - set the band
*/
static int si470x_set_band(struct si470x_device *radio, int band)
{
if (radio->band == band)
return 0;
radio->band = band;
radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND;
radio->registers[SYSCONFIG2] |= radio->band << 6;
return si470x_set_register(radio, SYSCONFIG2);
}
/*
* si470x_set_chan - set the channel
*/
static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
{
int retval;
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 */
INIT_COMPLETION(radio->completion);
retval = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(tune_timeout));
if (!retval)
timed_out = true;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
dev_warn(&radio->videodev.dev, "tune does not complete\n");
if (timed_out)
dev_warn(&radio->videodev.dev,
"tune timed out after %u ms\n", tune_timeout);
/* stop tuning */
radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
retval = si470x_set_register(radio, CHANNEL);
done:
return retval;
}
/*
* si470x_get_step - get channel spacing
*/
static unsigned int si470x_get_step(struct si470x_device *radio)
{
/* Spacing (kHz) */
switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
/* 0: 200 kHz (USA, Australia) */
case 0:
return 200 * 16;
/* 1: 100 kHz (Europe, Japan) */
case 1:
return 100 * 16;
/* 2: 50 kHz */
default:
return 50 * 16;
};
}
/*
* si470x_get_freq - get the frequency
*/
static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
{
int chan, retval;
/* 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 * si470x_get_step(radio) + bands[radio->band].rangelow;
return retval;
}
/*
* si470x_set_freq - set the frequency
*/
int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
{
unsigned short chan;
freq = clamp(freq, bands[radio->band].rangelow,
bands[radio->band].rangehigh);
/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio);
return si470x_set_chan(radio, chan);
}
/*
* si470x_set_seek - set seek
*/
static int si470x_set_seek(struct si470x_device *radio,
const struct v4l2_hw_freq_seek *seek)
{
int band, retval;
unsigned int freq;
bool timed_out = 0;
/* set band */
if (seek->rangelow || seek->rangehigh) {
for (band = 0; band < ARRAY_SIZE(bands); band++) {
if (bands[band].rangelow == seek->rangelow &&
bands[band].rangehigh == seek->rangehigh)
break;
}
if (band == ARRAY_SIZE(bands))
return -EINVAL; /* No matching band found */
} else
band = 1; /* If nothing is specified seek 76 - 108 Mhz */
if (radio->band != band) {
retval = si470x_get_freq(radio, &freq);
if (retval)
return retval;
retval = si470x_set_band(radio, band);
if (retval)
return retval;
retval = si470x_set_freq(radio, freq);
if (retval)
return retval;
}
/* start seeking */
radio->registers[POWERCFG] |= POWERCFG_SEEK;
if (seek->wrap_around)
radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
else
radio->registers[POWERCFG] |= POWERCFG_SKMODE;
if (seek->seek_upward)
radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
else
radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
retval = si470x_set_register(radio, POWERCFG);
if (retval < 0)
return retval;
/* wait till tune operation has completed */
INIT_COMPLETION(radio->completion);
retval = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(seek_timeout));
if (!retval)
timed_out = true;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
dev_warn(&radio->videodev.dev, "seek does not complete\n");
if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
dev_warn(&radio->videodev.dev,
"seek failed / band limit reached\n");
/* stop seeking */
radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
retval = si470x_set_register(radio, POWERCFG);
/* try again, if timed out */
if (retval == 0 && timed_out)
return -ENODATA;
return retval;
}
/*
* si470x_start - switch on radio
*/
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] =
(de << 11) & SYSCONFIG1_DE; /* DE*/
retval = si470x_set_register(radio, SYSCONFIG1);
if (retval < 0)
goto done;
/* sysconfig 2 */
radio->registers[SYSCONFIG2] =
(0x1f << 8) | /* SEEKTH */
((radio->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
*/
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 */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
retval = si470x_set_register(radio, SYSCONFIG1);
if (retval < 0)
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
return retval;
}
/**************************************************************************
* 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);
/* 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 */
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;
}
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);
unsigned long req_events = poll_requested_events(pts);
int retval = v4l2_ctrl_poll(file, pts);
if (req_events & (POLLIN | POLLRDNORM)) {
/* switch on rds reception */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
si470x_rds_on(radio);
poll_wait(file, &radio->read_queue, pts);
if (radio->rd_index != radio->wr_index)
retval |= POLLIN | POLLRDNORM;
}
return retval;
}
/*
* si470x_fops - file operations interface
*/
static const struct v4l2_file_operations si470x_fops = {
.owner = THIS_MODULE,
.read = si470x_fops_read,
.poll = si470x_fops_poll,
.unlocked_ioctl = video_ioctl2,
.open = si470x_fops_open,
.release = si470x_fops_release,
};
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
static int si470x_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct si470x_device *radio =
container_of(ctrl->handler, struct si470x_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
radio->registers[SYSCONFIG2] |= ctrl->val;
return si470x_set_register(radio, SYSCONFIG2);
case V4L2_CID_AUDIO_MUTE:
if (ctrl->val)
radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
else
radio->registers[POWERCFG] |= POWERCFG_DMUTE;
return si470x_set_register(radio, POWERCFG);
default:
return -EINVAL;
}
}
/*
* 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;
if (tuner->index != 0)
return -EINVAL;
if (!radio->status_rssi_auto_update) {
retval = si470x_get_register(radio, STATUSRSSI);
if (retval < 0)
return retval;
}
/* driver constants */
strcpy(tuner->name, "FM");
tuner->type = V4L2_TUNER_RADIO;
tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP;
tuner->rangelow = 76 * FREQ_MUL;
tuner->rangehigh = 108 * FREQ_MUL;
/* stereo indicator == stereo (instead of mono) */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)
tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
else
tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
/* If there is a reliable method of detecting an RDS channel,
then this code should check for that before setting this
RDS subchannel. */
tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
/* mono/stereo selector */
if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
tuner->audmode = V4L2_TUNER_MODE_STEREO;
else
tuner->audmode = V4L2_TUNER_MODE_MONO;
/* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
/* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */
tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);
/* the ideal factor is 0xffff/75 = 873,8 */
tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);
if (tuner->signal > 0xffff)
tuner->signal = 0xffff;
/* 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;
return retval;
}
/*
* si470x_vidioc_s_tuner - set tuner attributes
*/
static int si470x_vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *tuner)
{
struct si470x_device *radio = video_drvdata(file);
if (tuner->index != 0)
return -EINVAL;
/* mono/stereo selector */
switch (tuner->audmode) {
case V4L2_TUNER_MODE_MONO:
radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
break;
case V4L2_TUNER_MODE_STEREO:
default:
radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
break;
}
return si470x_set_register(radio, POWERCFG);
}
/*
* 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);
if (freq->tuner != 0)
return -EINVAL;
freq->type = V4L2_TUNER_RADIO;
return si470x_get_freq(radio, &freq->frequency);
}
/*
* si470x_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int si470x_vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *freq)
{
struct si470x_device *radio = video_drvdata(file);
int retval;
if (freq->tuner != 0)
return -EINVAL;
if (freq->frequency < bands[radio->band].rangelow ||
freq->frequency > bands[radio->band].rangehigh) {
/* Switch to band 1 which covers everything we support */
retval = si470x_set_band(radio, 1);
if (retval)
return retval;
}
return si470x_set_freq(radio, freq->frequency);
}
/*
* si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
*/
static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
const struct v4l2_hw_freq_seek *seek)
{
struct si470x_device *radio = video_drvdata(file);
if (seek->tuner != 0)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
return si470x_set_seek(radio, seek);
}
/*
* si470x_vidioc_enum_freq_bands - enumerate supported bands
*/
static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv,
struct v4l2_frequency_band *band)
{
if (band->tuner != 0)
return -EINVAL;
if (band->index >= ARRAY_SIZE(bands))
return -EINVAL;
*band = bands[band->index];
return 0;
}
const struct v4l2_ctrl_ops si470x_ctrl_ops = {
.s_ctrl = si470x_s_ctrl,
};
/*
* si470x_ioctl_ops - video device ioctl operations
*/
static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
.vidioc_querycap = si470x_vidioc_querycap,
.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,
.vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/*
* si470x_viddev_template - video device interface
*/
struct video_device si470x_viddev_template = {
.fops = &si470x_fops,
.name = DRIVER_NAME,
.release = video_device_release_empty,
.ioctl_ops = &si470x_ioctl_ops,
};

View File

@@ -0,0 +1,526 @@
/*
* drivers/media/radio/si470x/radio-si470x-i2c.c
*
* I2C driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Samsung Electronics Co.Ltd
* Author: Joonyoung Shim <jy0922.shim@samsung.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
*/
/* driver definitions */
#define DRIVER_AUTHOR "Joonyoung Shim <jy0922.shim@samsung.com>";
#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
#define DRIVER_DESC "I2C radio driver for Si470x FM Radio Receivers"
#define DRIVER_VERSION "1.0.2"
/* kernel includes */
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include "radio-si470x.h"
/* I2C Device ID List */
static const struct i2c_device_id si470x_i2c_id[] = {
/* Generic Entry */
{ "si470x", 0 },
/* Terminating entry */
{ }
};
MODULE_DEVICE_TABLE(i2c, si470x_i2c_id);
/**************************************************************************
* Module Parameters
**************************************************************************/
/* Radio Nr */
static int radio_nr = -1;
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio Nr");
/* 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*");
/**************************************************************************
* I2C Definitions
**************************************************************************/
/* Write starts with the upper byte of register 0x02 */
#define WRITE_REG_NUM 8
#define WRITE_INDEX(i) (i + 0x02)
/* Read starts with the upper byte of register 0x0a */
#define READ_REG_NUM RADIO_REGISTER_NUM
#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
/**************************************************************************
* General Driver Functions - REGISTERs
**************************************************************************/
/*
* si470x_get_register - read register
*/
int si470x_get_register(struct si470x_device *radio, int regnr)
{
u16 buf[READ_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.flags = I2C_M_RD,
.len = sizeof(u16) * READ_REG_NUM,
.buf = (void *)buf
},
};
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
radio->registers[regnr] = __be16_to_cpu(buf[READ_INDEX(regnr)]);
return 0;
}
/*
* si470x_set_register - write register
*/
int si470x_set_register(struct si470x_device *radio, int regnr)
{
int i;
u16 buf[WRITE_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.len = sizeof(u16) * WRITE_REG_NUM,
.buf = (void *)buf
},
};
for (i = 0; i < WRITE_REG_NUM; i++)
buf[i] = __cpu_to_be16(radio->registers[WRITE_INDEX(i)]);
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
return 0;
}
/**************************************************************************
* General Driver Functions - ENTIRE REGISTERS
**************************************************************************/
/*
* si470x_get_all_registers - read entire registers
*/
static int si470x_get_all_registers(struct si470x_device *radio)
{
int i;
u16 buf[READ_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.flags = I2C_M_RD,
.len = sizeof(u16) * READ_REG_NUM,
.buf = (void *)buf
},
};
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
for (i = 0; i < READ_REG_NUM; i++)
radio->registers[i] = __be16_to_cpu(buf[READ_INDEX(i)]);
return 0;
}
/**************************************************************************
* File Operations Interface
**************************************************************************/
/*
* si470x_fops_open - file open
*/
int si470x_fops_open(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
int retval = v4l2_fh_open(file);
if (retval)
return retval;
if (v4l2_fh_is_singular_file(file)) {
/* start radio */
retval = si470x_start(radio);
if (retval < 0)
goto done;
/* enable RDS / STC interrupt */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN;
radio->registers[SYSCONFIG1] |= SYSCONFIG1_STCIEN;
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;
radio->registers[SYSCONFIG1] |= 0x1 << 2;
retval = si470x_set_register(radio, SYSCONFIG1);
}
done:
if (retval)
v4l2_fh_release(file);
return retval;
}
/*
* si470x_fops_release - file release
*/
int si470x_fops_release(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
if (v4l2_fh_is_singular_file(file))
/* stop radio */
si470x_stop(radio);
return v4l2_fh_release(file);
}
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
/*
* si470x_vidioc_querycap - query device capabilities
*/
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));
capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE |
V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
/**************************************************************************
* I2C Interface
**************************************************************************/
/*
* si470x_i2c_interrupt - interrupt handler
*/
static irqreturn_t si470x_i2c_interrupt(int irq, void *dev_id)
{
struct si470x_device *radio = dev_id;
unsigned char regnr;
unsigned char blocknum;
unsigned short bler; /* rds block errors */
unsigned short rds;
unsigned char tmpbuf[3];
int retval = 0;
/* check Seek/Tune Complete */
retval = si470x_get_register(radio, STATUSRSSI);
if (retval < 0)
goto end;
if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
complete(&radio->completion);
/* safety checks */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
goto end;
/* Update RDS registers */
for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) {
retval = si470x_get_register(radio, STATUSRSSI + regnr);
if (retval < 0)
goto end;
}
/* get rds blocks */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0)
/* No RDS group ready, better luck next time */
goto end;
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;
}
}
if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue);
end:
return IRQ_HANDLED;
}
/*
* si470x_i2c_probe - probe for the device
*/
static int si470x_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct si470x_device *radio;
int retval = 0;
unsigned char version_warning = 0;
/* private data allocation and initialization */
radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
if (!radio) {
retval = -ENOMEM;
goto err_initial;
}
radio->client = client;
radio->band = 1; /* Default to 76 - 108 MHz */
mutex_init(&radio->lock);
init_completion(&radio->completion);
/* video device initialization */
radio->videodev = si470x_viddev_template;
video_set_drvdata(&radio->videodev, radio);
/* power up : need 110ms */
radio->registers[POWERCFG] = POWERCFG_ENABLE;
if (si470x_set_register(radio, POWERCFG) < 0) {
retval = -EIO;
goto err_radio;
}
msleep(110);
/* get device and chip versions */
if (si470x_get_all_registers(radio) < 0) {
retval = -EIO;
goto err_radio;
}
dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
radio->registers[DEVICEID], radio->registers[CHIPID]);
if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
dev_warn(&client->dev,
"This driver is known to work with "
"firmware version %hu,\n", RADIO_FW_VERSION);
dev_warn(&client->dev,
"but the device has firmware version %hu.\n",
radio->registers[CHIPID] & CHIPID_FIRMWARE);
version_warning = 1;
}
/* give out version warning */
if (version_warning == 1) {
dev_warn(&client->dev,
"If you have some trouble using this driver,\n");
dev_warn(&client->dev,
"please report to V4L ML at "
"linux-media@vger.kernel.org\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_radio;
}
/* rds buffer configuration */
radio->wr_index = 0;
radio->rd_index = 0;
init_waitqueue_head(&radio->read_queue);
retval = request_threaded_irq(client->irq, NULL, si470x_i2c_interrupt,
IRQF_TRIGGER_FALLING, DRIVER_NAME, radio);
if (retval) {
dev_err(&client->dev, "Failed to register interrupt\n");
goto err_rds;
}
/* register video device */
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
radio_nr);
if (retval) {
dev_warn(&client->dev, "Could not register video device\n");
goto err_all;
}
i2c_set_clientdata(client, radio);
return 0;
err_all:
free_irq(client->irq, radio);
err_rds:
kfree(radio->buffer);
err_radio:
kfree(radio);
err_initial:
return retval;
}
/*
* si470x_i2c_remove - remove the device
*/
static int si470x_i2c_remove(struct i2c_client *client)
{
struct si470x_device *radio = i2c_get_clientdata(client);
free_irq(client->irq, radio);
video_unregister_device(&radio->videodev);
kfree(radio);
return 0;
}
#ifdef CONFIG_PM
/*
* si470x_i2c_suspend - suspend the device
*/
static int si470x_i2c_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct si470x_device *radio = i2c_get_clientdata(client);
/* power down */
radio->registers[POWERCFG] |= POWERCFG_DISABLE;
if (si470x_set_register(radio, POWERCFG) < 0)
return -EIO;
return 0;
}
/*
* si470x_i2c_resume - resume the device
*/
static int si470x_i2c_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct si470x_device *radio = i2c_get_clientdata(client);
/* power up : need 110ms */
radio->registers[POWERCFG] |= POWERCFG_ENABLE;
if (si470x_set_register(radio, POWERCFG) < 0)
return -EIO;
msleep(110);
return 0;
}
static SIMPLE_DEV_PM_OPS(si470x_i2c_pm, si470x_i2c_suspend, si470x_i2c_resume);
#endif
/*
* si470x_i2c_driver - i2c driver interface
*/
static struct i2c_driver si470x_i2c_driver = {
.driver = {
.name = "si470x",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &si470x_i2c_pm,
#endif
},
.probe = si470x_i2c_probe,
.remove = si470x_i2c_remove,
.id_table = si470x_i2c_id,
};
module_i2c_driver(si470x_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);

View File

@@ -0,0 +1,849 @@
/*
* drivers/media/radio/si470x/radio-si470x-usb.c
*
* USB driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 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
*/
/*
* ToDo:
* - add firmware download/update support
*/
/* driver definitions */
#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
#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.10"
/* kernel includes */
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/slab.h>
#include "radio-si470x.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) },
/* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
/* Axentia ALERT FM USB Receiver */
{ USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, 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");
/* USB timeout */
static unsigned int usb_timeout = 500;
module_param(usb_timeout, uint, 0644);
MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
/* 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*");
/**************************************************************************
* 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_SIZE 4
#define WRITE_REPORT 19
#define FLASH_REPORT_SIZE 64
#define FLASH_REPORT 20
#define CRC_REPORT_SIZE 3
#define CRC_REPORT 21
#define RESPONSE_REPORT_SIZE 2
#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 from Scratch Page
**************************************************************************/
#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
#define RADIO_SW_VERSION 1
#define RADIO_HW_VERSION 1
/**************************************************************************
* 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
/**************************************************************************
* General Driver Functions - REGISTER_REPORTs
**************************************************************************/
/*
* 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)
dev_warn(&radio->intf->dev,
"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)
dev_warn(&radio->intf->dev,
"si470x_set_report: usb_control_msg returned %d\n",
retval);
return retval;
}
/*
* si470x_get_register - read register
*/
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
*/
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;
}
/**************************************************************************
* General Driver Functions - ENTIRE_REPORT
**************************************************************************/
/*
* 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;
}
/**************************************************************************
* General Driver Functions - LED_REPORT
**************************************************************************/
/*
* si470x_set_led_state - sets the led state
*/
static int si470x_set_led_state(struct si470x_device *radio,
unsigned char led_state)
{
unsigned char buf[LED_REPORT_SIZE];
int retval;
buf[0] = LED_REPORT;
buf[1] = LED_COMMAND;
buf[2] = led_state;
retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* General Driver Functions - SCRATCH_REPORT
**************************************************************************/
/*
* si470x_get_scratch_versions - gets the scratch page and version infos
*/
static int si470x_get_scratch_page_versions(struct si470x_device *radio)
{
unsigned char buf[SCRATCH_REPORT_SIZE];
int retval;
buf[0] = SCRATCH_REPORT;
retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
if (retval < 0)
dev_warn(&radio->intf->dev, "si470x_get_scratch: "
"si470x_get_report returned %d\n", retval);
else {
radio->software_version = buf[1];
radio->hardware_version = buf[2];
}
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* RDS Driver Functions
**************************************************************************/
/*
* si470x_int_in_callback - rds callback and processing function
*
* TODO: do we need to use mutex locks in some sections?
*/
static void si470x_int_in_callback(struct urb *urb)
{
struct si470x_device *radio = urb->context;
int retval;
unsigned char regnr;
unsigned char blocknum;
unsigned short bler; /* rds block errors */
unsigned short rds;
unsigned char tmpbuf[3];
if (urb->status) {
if (urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN) {
return;
} else {
dev_warn(&radio->intf->dev,
"non-zero urb status (%d)\n", urb->status);
goto resubmit; /* Maybe we can recover. */
}
}
/* Sometimes the device returns len 0 packets */
if (urb->actual_length != RDS_REPORT_SIZE)
goto resubmit;
radio->registers[STATUSRSSI] =
get_unaligned_be16(&radio->int_in_buffer[1]);
if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
complete(&radio->completion);
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) {
/* Update RDS registers with URB data */
for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++)
radio->registers[STATUSRSSI + regnr] =
get_unaligned_be16(&radio->int_in_buffer[
regnr * RADIO_REGISTER_SIZE + 1]);
/* get rds blocks */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
/* No RDS group ready, better luck next time */
goto resubmit;
}
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
/* RDS decoder not synchronized */
goto resubmit;
}
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;
}
}
if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue);
}
resubmit:
/* Resubmit if we're still running. */
if (radio->int_in_running && radio->usbdev) {
retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
if (retval) {
dev_warn(&radio->intf->dev,
"resubmitting urb failed (%d)", retval);
radio->int_in_running = 0;
}
}
radio->status_rssi_auto_update = radio->int_in_running;
}
int si470x_fops_open(struct file *file)
{
return v4l2_fh_open(file);
}
int si470x_fops_release(struct file *file)
{
return v4l2_fh_release(file);
}
static void si470x_usb_release(struct v4l2_device *v4l2_dev)
{
struct si470x_device *radio =
container_of(v4l2_dev, struct si470x_device, v4l2_dev);
usb_free_urb(radio->int_in_urb);
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->int_in_buffer);
kfree(radio->buffer);
kfree(radio);
}
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
/*
* si470x_vidioc_querycap - query device capabilities
*/
int si470x_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
struct si470x_device *radio = video_drvdata(file);
strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
usb_make_path(radio->usbdev, capability->bus_info,
sizeof(capability->bus_info));
capability->device_caps = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE |
V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
capability->capabilities = capability->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
static int si470x_start_usb(struct si470x_device *radio)
{
int retval;
/* initialize interrupt urb */
usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
usb_rcvintpipe(radio->usbdev,
radio->int_in_endpoint->bEndpointAddress),
radio->int_in_buffer,
le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
si470x_int_in_callback,
radio,
radio->int_in_endpoint->bInterval);
radio->int_in_running = 1;
mb();
retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
if (retval) {
dev_info(&radio->intf->dev,
"submitting int urb failed (%d)\n", retval);
radio->int_in_running = 0;
}
radio->status_rssi_auto_update = radio->int_in_running;
/* start radio */
retval = si470x_start(radio);
if (retval < 0)
return retval;
v4l2_ctrl_handler_setup(&radio->hdl);
return retval;
}
/**************************************************************************
* 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;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i, int_end_size, retval = 0;
unsigned char version_warning = 0;
/* private data allocation and initialization */
radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
if (!radio) {
retval = -ENOMEM;
goto err_initial;
}
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
radio->band = 1; /* Default to 76 - 108 MHz */
mutex_init(&radio->lock);
init_completion(&radio->completion);
iface_desc = intf->cur_altsetting;
/* Set up interrupt endpoint information. */
for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
endpoint = &iface_desc->endpoint[i].desc;
if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
USB_DIR_IN) && ((endpoint->bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))
radio->int_in_endpoint = endpoint;
}
if (!radio->int_in_endpoint) {
dev_info(&intf->dev, "could not find interrupt in endpoint\n");
retval = -EIO;
goto err_radio;
}
int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
if (!radio->int_in_buffer) {
dev_info(&intf->dev, "could not allocate int_in_buffer");
retval = -ENOMEM;
goto err_radio;
}
radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!radio->int_in_urb) {
dev_info(&intf->dev, "could not allocate int_in_urb");
retval = -ENOMEM;
goto err_intbuffer;
}
radio->v4l2_dev.release = si470x_usb_release;
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_urb;
}
v4l2_ctrl_handler_init(&radio->hdl, 2);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
if (radio->hdl.error) {
retval = radio->hdl.error;
dev_err(&intf->dev, "couldn't register control\n");
goto err_dev;
}
radio->videodev = si470x_viddev_template;
radio->videodev.ctrl_handler = &radio->hdl;
radio->videodev.lock = &radio->lock;
radio->videodev.v4l2_dev = &radio->v4l2_dev;
radio->videodev.release = video_device_release_empty;
set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags);
video_set_drvdata(&radio->videodev, radio);
/* get device and chip versions */
if (si470x_get_all_registers(radio) < 0) {
retval = -EIO;
goto err_ctrl;
}
dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
radio->registers[DEVICEID], radio->registers[CHIPID]);
if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
dev_warn(&intf->dev,
"This driver is known to work with "
"firmware version %hu,\n", RADIO_FW_VERSION);
dev_warn(&intf->dev,
"but the device has firmware version %hu.\n",
radio->registers[CHIPID] & CHIPID_FIRMWARE);
version_warning = 1;
}
/* get software and hardware versions */
if (si470x_get_scratch_page_versions(radio) < 0) {
retval = -EIO;
goto err_ctrl;
}
dev_info(&intf->dev, "software version %d, hardware version %d\n",
radio->software_version, radio->hardware_version);
if (radio->software_version < RADIO_SW_VERSION) {
dev_warn(&intf->dev,
"This driver is known to work with "
"software version %hu,\n", RADIO_SW_VERSION);
dev_warn(&intf->dev,
"but the device has software version %hu.\n",
radio->software_version);
version_warning = 1;
}
if (radio->hardware_version < RADIO_HW_VERSION) {
dev_warn(&intf->dev,
"This driver is known to work with "
"hardware version %hu,\n", RADIO_HW_VERSION);
dev_warn(&intf->dev,
"but the device has hardware version %hu.\n",
radio->hardware_version);
version_warning = 1;
}
/* give out version warning */
if (version_warning == 1) {
dev_warn(&intf->dev,
"If you have some trouble using this driver,\n");
dev_warn(&intf->dev,
"please report to V4L ML at "
"linux-media@vger.kernel.org\n");
}
/* set led to connect state */
si470x_set_led_state(radio, BLINK_GREEN_LED);
/* rds buffer allocation */
radio->buf_size = rds_buf * 3;
radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
if (!radio->buffer) {
retval = -EIO;
goto err_ctrl;
}
/* rds buffer configuration */
radio->wr_index = 0;
radio->rd_index = 0;
init_waitqueue_head(&radio->read_queue);
usb_set_intfdata(intf, radio);
/* start radio */
retval = si470x_start_usb(radio);
if (retval < 0)
goto err_all;
/* set initial frequency */
si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
/* register video device */
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
radio_nr);
if (retval) {
dev_err(&intf->dev, "Could not register video device\n");
goto err_all;
}
return 0;
err_all:
kfree(radio->buffer);
err_ctrl:
v4l2_ctrl_handler_free(&radio->hdl);
err_dev:
v4l2_device_unregister(&radio->v4l2_dev);
err_urb:
usb_free_urb(radio->int_in_urb);
err_intbuffer:
kfree(radio->int_in_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);
dev_info(&intf->dev, "suspending now...\n");
/* shutdown interrupt handler */
if (radio->int_in_running) {
radio->int_in_running = 0;
if (radio->int_in_urb)
usb_kill_urb(radio->int_in_urb);
}
/* cancel read processes */
wake_up_interruptible(&radio->read_queue);
/* stop radio */
si470x_stop(radio);
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);
int ret;
dev_info(&intf->dev, "resuming now...\n");
/* start radio */
ret = si470x_start_usb(radio);
if (ret == 0)
v4l2_ctrl_handler_setup(&radio->hdl);
return ret;
}
/*
* 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->lock);
v4l2_device_disconnect(&radio->v4l2_dev);
video_unregister_device(&radio->videodev);
usb_set_intfdata(intf, NULL);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
/*
* si470x_usb_driver - usb driver interface
*
* A note on suspend/resume: this driver had only empty suspend/resume
* functions, and when I tried to test suspend/resume it always disconnected
* instead of resuming (using my ADS InstantFM stick). So I've decided to
* remove these callbacks until someone else with better hardware can
* implement and test this.
*/
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,
.reset_resume = si470x_usb_driver_resume,
.id_table = si470x_usb_driver_id_table,
};
module_usb_driver(si470x_usb_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);

View File

@@ -0,0 +1,226 @@
/*
* drivers/media/radio/si470x/radio-si470x.h
*
* Driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 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
*/
/* driver definitions */
#define DRIVER_NAME "radio-si470x"
/* kernel includes */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/videodev2.h>
#include <linux/mutex.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/v4l2-device.h>
#include <asm/unaligned.h>
/**************************************************************************
* 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 0x00c0 /* 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) */
/**************************************************************************
* General Driver Definitions
**************************************************************************/
/*
* si470x_device - private data
*/
struct si470x_device {
struct v4l2_device v4l2_dev;
struct video_device videodev;
struct v4l2_ctrl_handler hdl;
int band;
/* Silabs internal registers (0..15) */
unsigned short registers[RADIO_REGISTER_NUM];
/* RDS receive buffer */
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;
struct completion completion;
bool status_rssi_auto_update; /* Does RSSI get updated automatic? */
#if IS_ENABLED(CONFIG_USB_SI470X)
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
/* Interrupt endpoint handling */
char *int_in_buffer;
struct usb_endpoint_descriptor *int_in_endpoint;
struct urb *int_in_urb;
int int_in_running;
/* scratch page */
unsigned char software_version;
unsigned char hardware_version;
#endif
#if IS_ENABLED(CONFIG_I2C_SI470X)
struct i2c_client *client;
#endif
};
/**************************************************************************
* Firmware Versions
**************************************************************************/
#define RADIO_FW_VERSION 12
/**************************************************************************
* Frequency Multiplicator
**************************************************************************/
/*
* 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)
/**************************************************************************
* Common Functions
**************************************************************************/
extern struct video_device si470x_viddev_template;
extern const struct v4l2_ctrl_ops si470x_ctrl_ops;
int si470x_get_register(struct si470x_device *radio, int regnr);
int si470x_set_register(struct si470x_device *radio, int regnr);
int si470x_disconnect_check(struct si470x_device *radio);
int si470x_set_freq(struct si470x_device *radio, unsigned int freq);
int si470x_start(struct si470x_device *radio);
int si470x_stop(struct si470x_device *radio);
int si470x_fops_open(struct file *file);
int si470x_fops_release(struct file *file);
int si470x_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,238 @@
/*
* drivers/media/radio/si4713-i2c.h
*
* Property and commands definitions for Si4713 radio transmitter chip.
*
* Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
* Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*
*/
#ifndef SI4713_I2C_H
#define SI4713_I2C_H
#include <media/v4l2-subdev.h>
#include <media/v4l2-ctrls.h>
#include <media/si4713.h>
#define SI4713_PRODUCT_NUMBER 0x0D
/* Command Timeouts */
#define DEFAULT_TIMEOUT 500
#define TIMEOUT_SET_PROPERTY 20
#define TIMEOUT_TX_TUNE_POWER 30000
#define TIMEOUT_TX_TUNE 110000
#define TIMEOUT_POWER_UP 200000
/*
* Command and its arguments definitions
*/
#define SI4713_PWUP_CTSIEN (1<<7)
#define SI4713_PWUP_GPO2OEN (1<<6)
#define SI4713_PWUP_PATCH (1<<5)
#define SI4713_PWUP_XOSCEN (1<<4)
#define SI4713_PWUP_FUNC_TX 0x02
#define SI4713_PWUP_FUNC_PATCH 0x0F
#define SI4713_PWUP_OPMOD_ANALOG 0x50
#define SI4713_PWUP_OPMOD_DIGITAL 0x0F
#define SI4713_PWUP_NARGS 2
#define SI4713_PWUP_NRESP 1
#define SI4713_CMD_POWER_UP 0x01
#define SI4713_GETREV_NRESP 9
#define SI4713_CMD_GET_REV 0x10
#define SI4713_PWDN_NRESP 1
#define SI4713_CMD_POWER_DOWN 0x11
#define SI4713_SET_PROP_NARGS 5
#define SI4713_SET_PROP_NRESP 1
#define SI4713_CMD_SET_PROPERTY 0x12
#define SI4713_GET_PROP_NARGS 3
#define SI4713_GET_PROP_NRESP 4
#define SI4713_CMD_GET_PROPERTY 0x13
#define SI4713_GET_STATUS_NRESP 1
#define SI4713_CMD_GET_INT_STATUS 0x14
#define SI4713_CMD_PATCH_ARGS 0x15
#define SI4713_CMD_PATCH_DATA 0x16
#define SI4713_MAX_FREQ 10800
#define SI4713_MIN_FREQ 7600
#define SI4713_TXFREQ_NARGS 3
#define SI4713_TXFREQ_NRESP 1
#define SI4713_CMD_TX_TUNE_FREQ 0x30
#define SI4713_MAX_POWER 120
#define SI4713_MIN_POWER 88
#define SI4713_MAX_ANTCAP 191
#define SI4713_MIN_ANTCAP 0
#define SI4713_TXPWR_NARGS 4
#define SI4713_TXPWR_NRESP 1
#define SI4713_CMD_TX_TUNE_POWER 0x31
#define SI4713_TXMEA_NARGS 4
#define SI4713_TXMEA_NRESP 1
#define SI4713_CMD_TX_TUNE_MEASURE 0x32
#define SI4713_INTACK_MASK 0x01
#define SI4713_TXSTATUS_NARGS 1
#define SI4713_TXSTATUS_NRESP 8
#define SI4713_CMD_TX_TUNE_STATUS 0x33
#define SI4713_OVERMOD_BIT (1 << 2)
#define SI4713_IALH_BIT (1 << 1)
#define SI4713_IALL_BIT (1 << 0)
#define SI4713_ASQSTATUS_NARGS 1
#define SI4713_ASQSTATUS_NRESP 5
#define SI4713_CMD_TX_ASQ_STATUS 0x34
#define SI4713_RDSBUFF_MODE_MASK 0x87
#define SI4713_RDSBUFF_NARGS 7
#define SI4713_RDSBUFF_NRESP 6
#define SI4713_CMD_TX_RDS_BUFF 0x35
#define SI4713_RDSPS_PSID_MASK 0x1F
#define SI4713_RDSPS_NARGS 5
#define SI4713_RDSPS_NRESP 1
#define SI4713_CMD_TX_RDS_PS 0x36
#define SI4713_CMD_GPO_CTL 0x80
#define SI4713_CMD_GPO_SET 0x81
/*
* Bits from status response
*/
#define SI4713_CTS (1<<7)
#define SI4713_ERR (1<<6)
#define SI4713_RDS_INT (1<<2)
#define SI4713_ASQ_INT (1<<1)
#define SI4713_STC_INT (1<<0)
/*
* Property definitions
*/
#define SI4713_GPO_IEN 0x0001
#define SI4713_DIG_INPUT_FORMAT 0x0101
#define SI4713_DIG_INPUT_SAMPLE_RATE 0x0103
#define SI4713_REFCLK_FREQ 0x0201
#define SI4713_REFCLK_PRESCALE 0x0202
#define SI4713_TX_COMPONENT_ENABLE 0x2100
#define SI4713_TX_AUDIO_DEVIATION 0x2101
#define SI4713_TX_PILOT_DEVIATION 0x2102
#define SI4713_TX_RDS_DEVIATION 0x2103
#define SI4713_TX_LINE_INPUT_LEVEL 0x2104
#define SI4713_TX_LINE_INPUT_MUTE 0x2105
#define SI4713_TX_PREEMPHASIS 0x2106
#define SI4713_TX_PILOT_FREQUENCY 0x2107
#define SI4713_TX_ACOMP_ENABLE 0x2200
#define SI4713_TX_ACOMP_THRESHOLD 0x2201
#define SI4713_TX_ACOMP_ATTACK_TIME 0x2202
#define SI4713_TX_ACOMP_RELEASE_TIME 0x2203
#define SI4713_TX_ACOMP_GAIN 0x2204
#define SI4713_TX_LIMITER_RELEASE_TIME 0x2205
#define SI4713_TX_ASQ_INTERRUPT_SOURCE 0x2300
#define SI4713_TX_ASQ_LEVEL_LOW 0x2301
#define SI4713_TX_ASQ_DURATION_LOW 0x2302
#define SI4713_TX_ASQ_LEVEL_HIGH 0x2303
#define SI4713_TX_ASQ_DURATION_HIGH 0x2304
#define SI4713_TX_RDS_INTERRUPT_SOURCE 0x2C00
#define SI4713_TX_RDS_PI 0x2C01
#define SI4713_TX_RDS_PS_MIX 0x2C02
#define SI4713_TX_RDS_PS_MISC 0x2C03
#define SI4713_TX_RDS_PS_REPEAT_COUNT 0x2C04
#define SI4713_TX_RDS_PS_MESSAGE_COUNT 0x2C05
#define SI4713_TX_RDS_PS_AF 0x2C06
#define SI4713_TX_RDS_FIFO_SIZE 0x2C07
#define PREEMPHASIS_USA 75
#define PREEMPHASIS_EU 50
#define PREEMPHASIS_DISABLED 0
#define FMPE_USA 0x00
#define FMPE_EU 0x01
#define FMPE_DISABLED 0x02
#define POWER_UP 0x01
#define POWER_DOWN 0x00
#define MAX_RDS_PTY 31
#define MAX_RDS_DEVIATION 90000
/*
* PSNAME is known to be defined as 8 character sized (RDS Spec).
* However, there is receivers which scroll PSNAME 8xN sized.
*/
#define MAX_RDS_PS_NAME 96
/*
* MAX_RDS_RADIO_TEXT is known to be defined as 32 (2A group) or 64 (2B group)
* character sized (RDS Spec).
* However, there is receivers which scroll them as well.
*/
#define MAX_RDS_RADIO_TEXT 384
#define MAX_LIMITER_RELEASE_TIME 102390
#define MAX_LIMITER_DEVIATION 90000
#define MAX_PILOT_DEVIATION 90000
#define MAX_PILOT_FREQUENCY 19000
#define MAX_ACOMP_RELEASE_TIME 1000000
#define MAX_ACOMP_ATTACK_TIME 5000
#define MAX_ACOMP_THRESHOLD 0
#define MIN_ACOMP_THRESHOLD (-40)
#define MAX_ACOMP_GAIN 20
#define SI4713_NUM_SUPPLIES 2
/*
* si4713_device - private data
*/
struct si4713_device {
/* v4l2_subdev and i2c reference (v4l2_subdev priv data) */
struct v4l2_subdev sd;
struct v4l2_ctrl_handler ctrl_handler;
/* private data structures */
struct { /* si4713 control cluster */
/* This is one big cluster since the mute control
* powers off the device and after unmuting again all
* controls need to be set at once. The only way of doing
* that is by making it one big cluster. */
struct v4l2_ctrl *mute;
struct v4l2_ctrl *rds_ps_name;
struct v4l2_ctrl *rds_radio_text;
struct v4l2_ctrl *rds_pi;
struct v4l2_ctrl *rds_deviation;
struct v4l2_ctrl *rds_pty;
struct v4l2_ctrl *compression_enabled;
struct v4l2_ctrl *compression_threshold;
struct v4l2_ctrl *compression_gain;
struct v4l2_ctrl *compression_attack_time;
struct v4l2_ctrl *compression_release_time;
struct v4l2_ctrl *pilot_tone_enabled;
struct v4l2_ctrl *pilot_tone_freq;
struct v4l2_ctrl *pilot_tone_deviation;
struct v4l2_ctrl *limiter_enabled;
struct v4l2_ctrl *limiter_deviation;
struct v4l2_ctrl *limiter_release_time;
struct v4l2_ctrl *tune_preemphasis;
struct v4l2_ctrl *tune_pwr_level;
struct v4l2_ctrl *tune_ant_cap;
};
struct completion work;
struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES];
int gpio_reset;
u32 power_state;
u32 rds_enabled;
u32 frequency;
u32 preemphasis;
u32 stereo;
u32 tune_rnl;
};
#endif /* ifndef SI4713_I2C_H */

View File

@@ -0,0 +1,222 @@
/*
* tef6862.c Philips TEF6862 Car Radio Enhanced Selectivity Tuner
* Copyright (c) 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include <media/v4l2-chip-ident.h>
#define DRIVER_NAME "tef6862"
#define FREQ_MUL 16000
#define TEF6862_LO_FREQ (875 * FREQ_MUL / 10)
#define TEF6862_HI_FREQ (108 * FREQ_MUL)
/* Write mode sub addresses */
#define WM_SUB_BANDWIDTH 0x0
#define WM_SUB_PLLM 0x1
#define WM_SUB_PLLL 0x2
#define WM_SUB_DAA 0x3
#define WM_SUB_AGC 0x4
#define WM_SUB_BAND 0x5
#define WM_SUB_CONTROL 0x6
#define WM_SUB_LEVEL 0x7
#define WM_SUB_IFCF 0x8
#define WM_SUB_IFCAP 0x9
#define WM_SUB_ACD 0xA
#define WM_SUB_TEST 0xF
/* Different modes of the MSA register */
#define MODE_BUFFER 0x0
#define MODE_PRESET 0x1
#define MODE_SEARCH 0x2
#define MODE_AF_UPDATE 0x3
#define MODE_JUMP 0x4
#define MODE_CHECK 0x5
#define MODE_LOAD 0x6
#define MODE_END 0x7
#define MODE_SHIFT 5
struct tef6862_state {
struct v4l2_subdev sd;
unsigned long freq;
};
static inline struct tef6862_state *to_state(struct v4l2_subdev *sd)
{
return container_of(sd, struct tef6862_state, sd);
}
static u16 tef6862_sigstr(struct i2c_client *client)
{
u8 buf[4];
int err = i2c_master_recv(client, buf, sizeof(buf));
if (err == sizeof(buf))
return buf[3] << 8;
return 0;
}
static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v)
{
if (v->index > 0)
return -EINVAL;
/* only support FM for now */
strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
v->rangelow = TEF6862_LO_FREQ;
v->rangehigh = TEF6862_HI_FREQ;
v->rxsubchans = V4L2_TUNER_SUB_MONO;
v->capability = V4L2_TUNER_CAP_LOW;
v->audmode = V4L2_TUNER_MODE_STEREO;
v->signal = tef6862_sigstr(v4l2_get_subdevdata(sd));
return 0;
}
static int tef6862_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int tef6862_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f)
{
struct tef6862_state *state = to_state(sd);
struct i2c_client *client = v4l2_get_subdevdata(sd);
u16 pll;
u8 i2cmsg[3];
int err;
if (f->tuner != 0)
return -EINVAL;
pll = 1964 + ((f->frequency - TEF6862_LO_FREQ) * 20) / FREQ_MUL;
i2cmsg[0] = (MODE_PRESET << MODE_SHIFT) | WM_SUB_PLLM;
i2cmsg[1] = (pll >> 8) & 0xff;
i2cmsg[2] = pll & 0xff;
err = i2c_master_send(client, i2cmsg, sizeof(i2cmsg));
if (err != sizeof(i2cmsg))
return err < 0 ? err : -EIO;
state->freq = f->frequency;
return 0;
}
static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
{
struct tef6862_state *state = to_state(sd);
if (f->tuner != 0)
return -EINVAL;
f->type = V4L2_TUNER_RADIO;
f->frequency = state->freq;
return 0;
}
static int tef6862_g_chip_ident(struct v4l2_subdev *sd,
struct v4l2_dbg_chip_ident *chip)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_TEF6862, 0);
}
static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = {
.g_tuner = tef6862_g_tuner,
.s_tuner = tef6862_s_tuner,
.s_frequency = tef6862_s_frequency,
.g_frequency = tef6862_g_frequency,
};
static const struct v4l2_subdev_core_ops tef6862_core_ops = {
.g_chip_ident = tef6862_g_chip_ident,
};
static const struct v4l2_subdev_ops tef6862_ops = {
.core = &tef6862_core_ops,
.tuner = &tef6862_tuner_ops,
};
/*
* Generic i2c probe
* concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
*/
static int tef6862_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tef6862_state *state;
struct v4l2_subdev *sd;
/* Check if the adapter supports the needed features */
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
v4l_info(client, "chip found @ 0x%02x (%s)\n",
client->addr << 1, client->adapter->name);
state = kzalloc(sizeof(struct tef6862_state), GFP_KERNEL);
if (state == NULL)
return -ENOMEM;
state->freq = TEF6862_LO_FREQ;
sd = &state->sd;
v4l2_i2c_subdev_init(sd, client, &tef6862_ops);
return 0;
}
static int tef6862_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
v4l2_device_unregister_subdev(sd);
kfree(to_state(sd));
return 0;
}
static const struct i2c_device_id tef6862_id[] = {
{DRIVER_NAME, 0},
{},
};
MODULE_DEVICE_TABLE(i2c, tef6862_id);
static struct i2c_driver tef6862_driver = {
.driver = {
.owner = THIS_MODULE,
.name = DRIVER_NAME,
},
.probe = tef6862_probe,
.remove = tef6862_remove,
.id_table = tef6862_id,
};
module_i2c_driver(tef6862_driver);
MODULE_DESCRIPTION("TEF6862 Car Radio Enhanced Selectivity Tuner");
MODULE_AUTHOR("Mocean Laboratories");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,17 @@
#
# TI's wl128x FM driver based on TI's ST driver.
#
menu "Texas Instruments WL128x FM driver (ST based)"
config RADIO_WL128X
tristate "Texas Instruments WL128x FM Radio"
depends on VIDEO_V4L2 && RFKILL && GPIOLIB && TTY
select TI_ST if NET
help
Choose Y here if you have this FM radio chip.
In order to control your radio card, you will need to use programs
that are compatible with the Video For Linux 2 API. Information on
this API and pointers to "v4l2" programs may be found at
<file:Documentation/video4linux/API.html>.
endmenu

View File

@@ -0,0 +1,6 @@
#
# Makefile for TI's shared transport driver based wl128x
# FM radio.
#
obj-$(CONFIG_RADIO_WL128X) += fm_drv.o
fm_drv-objs := fmdrv_common.o fmdrv_rx.o fmdrv_tx.o fmdrv_v4l2.o

View File

@@ -0,0 +1,239 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
*
* Common header for all FM driver sub-modules.
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#ifndef _FM_DRV_H
#define _FM_DRV_H
#include <linux/skbuff.h>
#include <linux/interrupt.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <linux/timer.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
#define FM_DRV_VERSION "0.1.1"
#define FM_DRV_NAME "ti_fmdrv"
#define FM_DRV_CARD_SHORT_NAME "TI FM Radio"
#define FM_DRV_CARD_LONG_NAME "Texas Instruments FM Radio"
/* Flag info */
#define FM_INTTASK_RUNNING 0
#define FM_INTTASK_SCHEDULE_PENDING 1
#define FM_FW_DW_INPROGRESS 2
#define FM_CORE_READY 3
#define FM_CORE_TRANSPORT_READY 4
#define FM_AF_SWITCH_INPROGRESS 5
#define FM_CORE_TX_XMITING 6
#define FM_TUNE_COMPLETE 0x1
#define FM_BAND_LIMIT 0x2
#define FM_DRV_TX_TIMEOUT (5*HZ) /* 5 seconds */
#define FM_DRV_RX_SEEK_TIMEOUT (20*HZ) /* 20 seconds */
#define fmerr(format, ...) \
printk(KERN_ERR "fmdrv: " format, ## __VA_ARGS__)
#define fmwarn(format, ...) \
printk(KERN_WARNING "fmdrv: " format, ##__VA_ARGS__)
#ifdef DEBUG
#define fmdbg(format, ...) \
printk(KERN_DEBUG "fmdrv: " format, ## __VA_ARGS__)
#else /* DEBUG */
#define fmdbg(format, ...) do {} while(0)
#endif
enum {
FM_MODE_OFF,
FM_MODE_TX,
FM_MODE_RX,
FM_MODE_ENTRY_MAX
};
#define FM_RX_RDS_INFO_FIELD_MAX 8 /* 4 Group * 2 Bytes */
/* RX RDS data format */
struct fm_rdsdata_format {
union {
struct {
u8 buff[FM_RX_RDS_INFO_FIELD_MAX];
} groupdatabuff;
struct {
u16 pidata;
u8 blk_b[2];
u8 blk_c[2];
u8 blk_d[2];
} groupgeneral;
struct {
u16 pidata;
u8 blk_b[2];
u8 af[2];
u8 ps[2];
} group0A;
struct {
u16 pi[2];
u8 blk_b[2];
u8 ps[2];
} group0B;
} data;
};
/* FM region (Europe/US, Japan) info */
struct region_info {
u32 chanl_space;
u32 bot_freq;
u32 top_freq;
u8 fm_band;
};
struct fmdev;
typedef void (*int_handler_prototype) (struct fmdev *);
/* FM Interrupt processing related info */
struct fm_irq {
u8 stage;
u16 flag; /* FM interrupt flag */
u16 mask; /* FM interrupt mask */
/* Interrupt process timeout handler */
struct timer_list timer;
u8 retry;
int_handler_prototype *handlers;
};
/* RDS info */
struct fm_rds {
u8 flag; /* RX RDS on/off status */
u8 last_blk_idx; /* Last received RDS block */
/* RDS buffer */
wait_queue_head_t read_queue;
u32 buf_size; /* Size is always multiple of 3 */
u32 wr_idx;
u32 rd_idx;
u8 *buff;
};
#define FM_RDS_MAX_AF_LIST 25
/*
* Current RX channel Alternate Frequency cache.
* This info is used to switch to other freq (AF)
* when current channel signal strengh is below RSSI threshold.
*/
struct tuned_station_info {
u16 picode;
u32 af_cache[FM_RDS_MAX_AF_LIST];
u8 afcache_size;
u8 af_list_max;
};
/* FM RX mode info */
struct fm_rx {
struct region_info region; /* Current selected band */
u32 freq; /* Current RX frquency */
u8 mute_mode; /* Current mute mode */
u8 deemphasis_mode; /* Current deemphasis mode */
/* RF dependent soft mute mode */
u8 rf_depend_mute;
u16 volume; /* Current volume level */
u16 rssi_threshold; /* Current RSSI threshold level */
/* Holds the index of the current AF jump */
u8 afjump_idx;
/* Will hold the frequency before the jump */
u32 freq_before_jump;
u8 rds_mode; /* RDS operation mode (RDS/RDBS) */
u8 af_mode; /* Alternate frequency on/off */
struct tuned_station_info stat_info;
struct fm_rds rds;
};
#define FMTX_RDS_TXT_STR_SIZE 25
/*
* FM TX RDS data
*
* @ text_type: is the text following PS or RT
* @ text: radio text string which could either be PS or RT
* @ af_freq: alternate frequency for Tx
* TODO: to be declared in application
*/
struct tx_rds {
u8 text_type;
u8 text[FMTX_RDS_TXT_STR_SIZE];
u8 flag;
u32 af_freq;
};
/*
* FM TX global data
*
* @ pwr_lvl: Power Level of the Transmission from mixer control
* @ xmit_state: Transmission state = Updated locally upon Start/Stop
* @ audio_io: i2S/Analog
* @ tx_frq: Transmission frequency
*/
struct fmtx_data {
u8 pwr_lvl;
u8 xmit_state;
u8 audio_io;
u8 region;
u16 aud_mode;
u32 preemph;
u32 tx_frq;
struct tx_rds rds;
};
/* FM driver operation structure */
struct fmdev {
struct video_device *radio_dev; /* V4L2 video device pointer */
struct snd_card *card; /* Card which holds FM mixer controls */
u16 asci_id;
spinlock_t rds_buff_lock; /* To protect access to RDS buffer */
spinlock_t resp_skb_lock; /* To protect access to received SKB */
long flag; /* FM driver state machine info */
u8 streg_cbdata; /* status of ST registration */
struct sk_buff_head rx_q; /* RX queue */
struct tasklet_struct rx_task; /* RX Tasklet */
struct sk_buff_head tx_q; /* TX queue */
struct tasklet_struct tx_task; /* TX Tasklet */
unsigned long last_tx_jiffies; /* Timestamp of last pkt sent */
atomic_t tx_cnt; /* Number of packets can send at a time */
struct sk_buff *resp_skb; /* Response from the chip */
/* Main task completion handler */
struct completion maintask_comp;
/* Opcode of last command sent to the chip */
u8 pre_op;
/* Handler used for wakeup when response packet is received */
struct completion *resp_comp;
struct fm_irq irq_info;
u8 curr_fmmode; /* Current FM chip mode (TX, RX, OFF) */
struct fm_rx rx; /* FM receiver info */
struct fmtx_data tx_data;
/* V4L2 ctrl framwork handler*/
struct v4l2_ctrl_handler ctrl_handler;
/* For core assisted locking */
struct mutex mutex;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* FM Common module header file
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#ifndef _FMDRV_COMMON_H
#define _FMDRV_COMMON_H
#define FM_ST_REG_TIMEOUT msecs_to_jiffies(6000) /* 6 sec */
#define FM_PKT_LOGICAL_CHAN_NUMBER 0x08 /* Logical channel 8 */
#define REG_RD 0x1
#define REG_WR 0x0
struct fm_reg_table {
u8 opcode;
u8 type;
u8 *name;
};
#define STEREO_GET 0
#define RSSI_LVL_GET 1
#define IF_COUNT_GET 2
#define FLAG_GET 3
#define RDS_SYNC_GET 4
#define RDS_DATA_GET 5
#define FREQ_SET 10
#define AF_FREQ_SET 11
#define MOST_MODE_SET 12
#define MOST_BLEND_SET 13
#define DEMPH_MODE_SET 14
#define SEARCH_LVL_SET 15
#define BAND_SET 16
#define MUTE_STATUS_SET 17
#define RDS_PAUSE_LVL_SET 18
#define RDS_PAUSE_DUR_SET 19
#define RDS_MEM_SET 20
#define RDS_BLK_B_SET 21
#define RDS_MSK_B_SET 22
#define RDS_PI_MASK_SET 23
#define RDS_PI_SET 24
#define RDS_SYSTEM_SET 25
#define INT_MASK_SET 26
#define SEARCH_DIR_SET 27
#define VOLUME_SET 28
#define AUDIO_ENABLE_SET 29
#define PCM_MODE_SET 30
#define I2S_MODE_CONFIG_SET 31
#define POWER_SET 32
#define INTX_CONFIG_SET 33
#define PULL_EN_SET 34
#define HILO_SET 35
#define SWITCH2FREF 36
#define FREQ_DRIFT_REPORT 37
#define PCE_GET 40
#define FIRM_VER_GET 41
#define ASIC_VER_GET 42
#define ASIC_ID_GET 43
#define MAN_ID_GET 44
#define TUNER_MODE_SET 45
#define STOP_SEARCH 46
#define RDS_CNTRL_SET 47
#define WRITE_HARDWARE_REG 100
#define CODE_DOWNLOAD 101
#define RESET 102
#define FM_POWER_MODE 254
#define FM_INTERRUPT 255
/* Transmitter API */
#define CHANL_SET 55
#define CHANL_BW_SET 56
#define REF_SET 57
#define POWER_ENB_SET 90
#define POWER_ATT_SET 58
#define POWER_LEV_SET 59
#define AUDIO_DEV_SET 60
#define PILOT_DEV_SET 61
#define RDS_DEV_SET 62
#define TX_BAND_SET 65
#define PUPD_SET 91
#define AUDIO_IO_SET 63
#define PREMPH_SET 64
#define MONO_SET 66
#define MUTE 92
#define MPX_LMT_ENABLE 67
#define PI_SET 93
#define ECC_SET 69
#define PTY 70
#define AF 71
#define DISPLAY_MODE 74
#define RDS_REP_SET 77
#define RDS_CONFIG_DATA_SET 98
#define RDS_DATA_SET 99
#define RDS_DATA_ENB 94
#define TA_SET 78
#define TP_SET 79
#define DI_SET 80
#define MS_SET 81
#define PS_SCROLL_SPEED 82
#define TX_AUDIO_LEVEL_TEST 96
#define TX_AUDIO_LEVEL_TEST_THRESHOLD 73
#define TX_AUDIO_INPUT_LEVEL_RANGE_SET 54
#define RX_ANTENNA_SELECT 87
#define I2C_DEV_ADDR_SET 86
#define REF_ERR_CALIB_PARAM_SET 88
#define REF_ERR_CALIB_PERIODICITY_SET 89
#define SOC_INT_TRIGGER 52
#define SOC_AUDIO_PATH_SET 83
#define SOC_PCMI_OVERRIDE 84
#define SOC_I2S_OVERRIDE 85
#define RSSI_BLOCK_SCAN_FREQ_SET 95
#define RSSI_BLOCK_SCAN_START 97
#define RSSI_BLOCK_SCAN_DATA_GET 5
#define READ_FMANT_TUNE_VALUE 104
/* SKB helpers */
struct fm_skb_cb {
__u8 fm_op;
struct completion *completion;
};
#define fm_cb(skb) ((struct fm_skb_cb *)(skb->cb))
/* FM Channel-8 command message format */
struct fm_cmd_msg_hdr {
__u8 hdr; /* Logical Channel-8 */
__u8 len; /* Number of bytes follows */
__u8 op; /* FM Opcode */
__u8 rd_wr; /* Read/Write command */
__u8 dlen; /* Length of payload */
} __attribute__ ((packed));
#define FM_CMD_MSG_HDR_SIZE 5 /* sizeof(struct fm_cmd_msg_hdr) */
/* FM Channel-8 event messgage format */
struct fm_event_msg_hdr {
__u8 header; /* Logical Channel-8 */
__u8 len; /* Number of bytes follows */
__u8 status; /* Event status */
__u8 num_fm_hci_cmds; /* Number of pkts the host allowed to send */
__u8 op; /* FM Opcode */
__u8 rd_wr; /* Read/Write command */
__u8 dlen; /* Length of payload */
} __attribute__ ((packed));
#define FM_EVT_MSG_HDR_SIZE 7 /* sizeof(struct fm_event_msg_hdr) */
/* TI's magic number in firmware file */
#define FM_FW_FILE_HEADER_MAGIC 0x42535442
#define FM_ENABLE 1
#define FM_DISABLE 0
/* FLAG_GET register bits */
#define FM_FR_EVENT (1 << 0)
#define FM_BL_EVENT (1 << 1)
#define FM_RDS_EVENT (1 << 2)
#define FM_BBLK_EVENT (1 << 3)
#define FM_LSYNC_EVENT (1 << 4)
#define FM_LEV_EVENT (1 << 5)
#define FM_IFFR_EVENT (1 << 6)
#define FM_PI_EVENT (1 << 7)
#define FM_PD_EVENT (1 << 8)
#define FM_STIC_EVENT (1 << 9)
#define FM_MAL_EVENT (1 << 10)
#define FM_POW_ENB_EVENT (1 << 11)
/*
* Firmware files of FM. ASIC ID and ASIC version will be appened to this,
* later.
*/
#define FM_FMC_FW_FILE_START ("fmc_ch8")
#define FM_RX_FW_FILE_START ("fm_rx_ch8")
#define FM_TX_FW_FILE_START ("fm_tx_ch8")
#define FM_UNDEFINED_FREQ 0xFFFFFFFF
/* Band types */
#define FM_BAND_EUROPE_US 0
#define FM_BAND_JAPAN 1
/* Seek directions */
#define FM_SEARCH_DIRECTION_DOWN 0
#define FM_SEARCH_DIRECTION_UP 1
/* Tunner modes */
#define FM_TUNER_STOP_SEARCH_MODE 0
#define FM_TUNER_PRESET_MODE 1
#define FM_TUNER_AUTONOMOUS_SEARCH_MODE 2
#define FM_TUNER_AF_JUMP_MODE 3
/* Min and Max volume */
#define FM_RX_VOLUME_MIN 0
#define FM_RX_VOLUME_MAX 70
/* Volume gain step */
#define FM_RX_VOLUME_GAIN_STEP 0x370
/* Mute modes */
#define FM_MUTE_ON 0
#define FM_MUTE_OFF 1
#define FM_MUTE_ATTENUATE 2
#define FM_RX_UNMUTE_MODE 0x00
#define FM_RX_RF_DEP_MODE 0x01
#define FM_RX_AC_MUTE_MODE 0x02
#define FM_RX_HARD_MUTE_LEFT_MODE 0x04
#define FM_RX_HARD_MUTE_RIGHT_MODE 0x08
#define FM_RX_SOFT_MUTE_FORCE_MODE 0x10
/* RF dependent mute mode */
#define FM_RX_RF_DEPENDENT_MUTE_ON 1
#define FM_RX_RF_DEPENDENT_MUTE_OFF 0
/* RSSI threshold min and max */
#define FM_RX_RSSI_THRESHOLD_MIN -128
#define FM_RX_RSSI_THRESHOLD_MAX 127
/* Stereo/Mono mode */
#define FM_STEREO_MODE 0
#define FM_MONO_MODE 1
#define FM_STEREO_SOFT_BLEND 1
/* FM RX De-emphasis filter modes */
#define FM_RX_EMPHASIS_FILTER_50_USEC 0
#define FM_RX_EMPHASIS_FILTER_75_USEC 1
/* FM RDS modes */
#define FM_RDS_DISABLE 0
#define FM_RDS_ENABLE 1
#define FM_NO_PI_CODE 0
/* FM and RX RDS block enable/disable */
#define FM_RX_PWR_SET_FM_ON_RDS_OFF 0x1
#define FM_RX_PWR_SET_FM_AND_RDS_BLK_ON 0x3
#define FM_RX_PWR_SET_FM_AND_RDS_BLK_OFF 0x0
/* RX RDS */
#define FM_RX_RDS_FLUSH_FIFO 0x1
#define FM_RX_RDS_FIFO_THRESHOLD 64 /* tuples */
#define FM_RDS_BLK_SIZE 3 /* 3 bytes */
/* RDS block types */
#define FM_RDS_BLOCK_A 0
#define FM_RDS_BLOCK_B 1
#define FM_RDS_BLOCK_C 2
#define FM_RDS_BLOCK_Ctag 3
#define FM_RDS_BLOCK_D 4
#define FM_RDS_BLOCK_E 5
#define FM_RDS_BLK_IDX_A 0
#define FM_RDS_BLK_IDX_B 1
#define FM_RDS_BLK_IDX_C 2
#define FM_RDS_BLK_IDX_D 3
#define FM_RDS_BLK_IDX_UNKNOWN 0xF0
#define FM_RDS_STATUS_ERR_MASK 0x18
/*
* Represents an RDS group type & version.
* There are 15 groups, each group has 2 versions: A and B.
*/
#define FM_RDS_GROUP_TYPE_MASK_0A ((unsigned long)1<<0)
#define FM_RDS_GROUP_TYPE_MASK_0B ((unsigned long)1<<1)
#define FM_RDS_GROUP_TYPE_MASK_1A ((unsigned long)1<<2)
#define FM_RDS_GROUP_TYPE_MASK_1B ((unsigned long)1<<3)
#define FM_RDS_GROUP_TYPE_MASK_2A ((unsigned long)1<<4)
#define FM_RDS_GROUP_TYPE_MASK_2B ((unsigned long)1<<5)
#define FM_RDS_GROUP_TYPE_MASK_3A ((unsigned long)1<<6)
#define FM_RDS_GROUP_TYPE_MASK_3B ((unsigned long)1<<7)
#define FM_RDS_GROUP_TYPE_MASK_4A ((unsigned long)1<<8)
#define FM_RDS_GROUP_TYPE_MASK_4B ((unsigned long)1<<9)
#define FM_RDS_GROUP_TYPE_MASK_5A ((unsigned long)1<<10)
#define FM_RDS_GROUP_TYPE_MASK_5B ((unsigned long)1<<11)
#define FM_RDS_GROUP_TYPE_MASK_6A ((unsigned long)1<<12)
#define FM_RDS_GROUP_TYPE_MASK_6B ((unsigned long)1<<13)
#define FM_RDS_GROUP_TYPE_MASK_7A ((unsigned long)1<<14)
#define FM_RDS_GROUP_TYPE_MASK_7B ((unsigned long)1<<15)
#define FM_RDS_GROUP_TYPE_MASK_8A ((unsigned long)1<<16)
#define FM_RDS_GROUP_TYPE_MASK_8B ((unsigned long)1<<17)
#define FM_RDS_GROUP_TYPE_MASK_9A ((unsigned long)1<<18)
#define FM_RDS_GROUP_TYPE_MASK_9B ((unsigned long)1<<19)
#define FM_RDS_GROUP_TYPE_MASK_10A ((unsigned long)1<<20)
#define FM_RDS_GROUP_TYPE_MASK_10B ((unsigned long)1<<21)
#define FM_RDS_GROUP_TYPE_MASK_11A ((unsigned long)1<<22)
#define FM_RDS_GROUP_TYPE_MASK_11B ((unsigned long)1<<23)
#define FM_RDS_GROUP_TYPE_MASK_12A ((unsigned long)1<<24)
#define FM_RDS_GROUP_TYPE_MASK_12B ((unsigned long)1<<25)
#define FM_RDS_GROUP_TYPE_MASK_13A ((unsigned long)1<<26)
#define FM_RDS_GROUP_TYPE_MASK_13B ((unsigned long)1<<27)
#define FM_RDS_GROUP_TYPE_MASK_14A ((unsigned long)1<<28)
#define FM_RDS_GROUP_TYPE_MASK_14B ((unsigned long)1<<29)
#define FM_RDS_GROUP_TYPE_MASK_15A ((unsigned long)1<<30)
#define FM_RDS_GROUP_TYPE_MASK_15B ((unsigned long)1<<31)
/* RX Alternate Frequency info */
#define FM_RDS_MIN_AF 1
#define FM_RDS_MAX_AF 204
#define FM_RDS_MAX_AF_JAPAN 140
#define FM_RDS_1_AF_FOLLOWS 225
#define FM_RDS_25_AF_FOLLOWS 249
/* RDS system type (RDS/RBDS) */
#define FM_RDS_SYSTEM_RDS 0
#define FM_RDS_SYSTEM_RBDS 1
/* AF on/off */
#define FM_RX_RDS_AF_SWITCH_MODE_ON 1
#define FM_RX_RDS_AF_SWITCH_MODE_OFF 0
/* Retry count when interrupt process goes wrong */
#define FM_IRQ_TIMEOUT_RETRY_MAX 5 /* 5 times */
/* Audio IO set values */
#define FM_RX_AUDIO_ENABLE_I2S 0x01
#define FM_RX_AUDIO_ENABLE_ANALOG 0x02
#define FM_RX_AUDIO_ENABLE_I2S_AND_ANALOG 0x03
#define FM_RX_AUDIO_ENABLE_DISABLE 0x00
/* HI/LO set values */
#define FM_RX_IFFREQ_TO_HI_SIDE 0x0
#define FM_RX_IFFREQ_TO_LO_SIDE 0x1
#define FM_RX_IFFREQ_HILO_AUTOMATIC 0x2
/*
* Default RX mode configuration. Chip will be configured
* with this default values after loading RX firmware.
*/
#define FM_DEFAULT_RX_VOLUME 10
#define FM_DEFAULT_RSSI_THRESHOLD 3
/* Range for TX power level in units for dB/uV */
#define FM_PWR_LVL_LOW 91
#define FM_PWR_LVL_HIGH 122
/* Chip specific default TX power level value */
#define FM_PWR_LVL_DEF 4
/* FM TX Pre-emphasis filter values */
#define FM_TX_PREEMPH_OFF 1
#define FM_TX_PREEMPH_50US 0
#define FM_TX_PREEMPH_75US 2
/* FM TX antenna impedance values */
#define FM_TX_ANT_IMP_50 0
#define FM_TX_ANT_IMP_200 1
#define FM_TX_ANT_IMP_500 2
/* Functions exported by FM common sub-module */
int fmc_prepare(struct fmdev *);
int fmc_release(struct fmdev *);
void fmc_update_region_info(struct fmdev *, u8);
int fmc_send_cmd(struct fmdev *, u8, u16,
void *, unsigned int, void *, int *);
int fmc_is_rds_data_available(struct fmdev *, struct file *,
struct poll_table_struct *);
int fmc_transfer_rds_from_internal_buff(struct fmdev *, struct file *,
u8 __user *, size_t);
int fmc_set_freq(struct fmdev *, u32);
int fmc_set_mode(struct fmdev *, u8);
int fmc_set_region(struct fmdev *, u8);
int fmc_set_mute_mode(struct fmdev *, u8);
int fmc_set_stereo_mono(struct fmdev *, u16);
int fmc_set_rds_mode(struct fmdev *, u8);
int fmc_get_freq(struct fmdev *, u32 *);
int fmc_get_region(struct fmdev *, u8 *);
int fmc_get_mode(struct fmdev *, u8 *);
/*
* channel spacing
*/
#define FM_CHANNEL_SPACING_50KHZ 1
#define FM_CHANNEL_SPACING_100KHZ 2
#define FM_CHANNEL_SPACING_200KHZ 4
#define FM_FREQ_MUL 50
#endif

View File

@@ -0,0 +1,849 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* This sub-module of FM driver implements FM RX functionality.
*
* Copyright (C) 2011 Texas Instruments
* Author: Raja Mani <raja_mani@ti.com>
* Author: Manjunatha Halli <manjunatha_halli@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#include "fmdrv.h"
#include "fmdrv_common.h"
#include "fmdrv_rx.h"
void fm_rx_reset_rds_cache(struct fmdev *fmdev)
{
fmdev->rx.rds.flag = FM_RDS_DISABLE;
fmdev->rx.rds.last_blk_idx = 0;
fmdev->rx.rds.wr_idx = 0;
fmdev->rx.rds.rd_idx = 0;
if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
fmdev->irq_info.mask |= FM_LEV_EVENT;
}
void fm_rx_reset_station_info(struct fmdev *fmdev)
{
fmdev->rx.stat_info.picode = FM_NO_PI_CODE;
fmdev->rx.stat_info.afcache_size = 0;
fmdev->rx.stat_info.af_list_max = 0;
}
int fm_rx_set_freq(struct fmdev *fmdev, u32 freq)
{
unsigned long timeleft;
u16 payload, curr_frq, intr_flag;
u32 curr_frq_in_khz;
u32 resp_len;
int ret;
if (freq < fmdev->rx.region.bot_freq || freq > fmdev->rx.region.top_freq) {
fmerr("Invalid frequency %d\n", freq);
return -EINVAL;
}
/* Set audio enable */
payload = FM_RX_AUDIO_ENABLE_I2S_AND_ANALOG;
ret = fmc_send_cmd(fmdev, AUDIO_ENABLE_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Set hilo to automatic selection */
payload = FM_RX_IFFREQ_HILO_AUTOMATIC;
ret = fmc_send_cmd(fmdev, HILO_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Calculate frequency index and set*/
payload = (freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Read flags - just to clear any pending interrupts if we had */
ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL);
if (ret < 0)
return ret;
/* Enable FR, BL interrupts */
intr_flag = fmdev->irq_info.mask;
fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT);
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Start tune */
payload = FM_TUNER_PRESET_MODE;
ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
goto exit;
/* Wait for tune ended interrupt */
init_completion(&fmdev->maintask_comp);
timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
FM_DRV_TX_TIMEOUT);
if (!timeleft) {
fmerr("Timeout(%d sec),didn't get tune ended int\n",
jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
ret = -ETIMEDOUT;
goto exit;
}
/* Read freq back to confirm */
ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2, &curr_frq, &resp_len);
if (ret < 0)
goto exit;
curr_frq = be16_to_cpu(curr_frq);
curr_frq_in_khz = (fmdev->rx.region.bot_freq + ((u32)curr_frq * FM_FREQ_MUL));
if (curr_frq_in_khz != freq) {
pr_info("Frequency is set to (%d) but "
"requested freq is (%d)\n", curr_frq_in_khz, freq);
}
/* Update local cache */
fmdev->rx.freq = curr_frq_in_khz;
exit:
/* Re-enable default FM interrupts */
fmdev->irq_info.mask = intr_flag;
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Reset RDS cache and current station pointers */
fm_rx_reset_rds_cache(fmdev);
fm_rx_reset_station_info(fmdev);
return ret;
}
static int fm_rx_set_channel_spacing(struct fmdev *fmdev, u32 spacing)
{
u16 payload;
int ret;
if (spacing > 0 && spacing <= 50000)
spacing = FM_CHANNEL_SPACING_50KHZ;
else if (spacing > 50000 && spacing <= 100000)
spacing = FM_CHANNEL_SPACING_100KHZ;
else
spacing = FM_CHANNEL_SPACING_200KHZ;
/* set channel spacing */
payload = spacing;
ret = fmc_send_cmd(fmdev, CHANL_BW_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.region.chanl_space = spacing * FM_FREQ_MUL;
return ret;
}
int fm_rx_seek(struct fmdev *fmdev, u32 seek_upward,
u32 wrap_around, u32 spacing)
{
u32 resp_len;
u16 curr_frq, next_frq, last_frq;
u16 payload, int_reason, intr_flag;
u16 offset, space_idx;
unsigned long timeleft;
int ret;
/* Set channel spacing */
ret = fm_rx_set_channel_spacing(fmdev, spacing);
if (ret < 0) {
fmerr("Failed to set channel spacing\n");
return ret;
}
/* Read the current frequency from chip */
ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL,
sizeof(curr_frq), &curr_frq, &resp_len);
if (ret < 0)
return ret;
curr_frq = be16_to_cpu(curr_frq);
last_frq = (fmdev->rx.region.top_freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
/* Check the offset in order to be aligned to the channel spacing*/
space_idx = fmdev->rx.region.chanl_space / FM_FREQ_MUL;
offset = curr_frq % space_idx;
next_frq = seek_upward ? curr_frq + space_idx /* Seek Up */ :
curr_frq - space_idx /* Seek Down */ ;
/*
* Add or subtract offset in order to stay aligned to the channel
* spacing.
*/
if ((short)next_frq < 0)
next_frq = last_frq - offset;
else if (next_frq > last_frq)
next_frq = 0 + offset;
again:
/* Set calculated next frequency to perform seek */
payload = next_frq;
ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Set search direction (0:Seek Down, 1:Seek Up) */
payload = (seek_upward ? FM_SEARCH_DIRECTION_UP : FM_SEARCH_DIRECTION_DOWN);
ret = fmc_send_cmd(fmdev, SEARCH_DIR_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Read flags - just to clear any pending interrupts if we had */
ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL);
if (ret < 0)
return ret;
/* Enable FR, BL interrupts */
intr_flag = fmdev->irq_info.mask;
fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT);
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Start seek */
payload = FM_TUNER_AUTONOMOUS_SEARCH_MODE;
ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Wait for tune ended/band limit reached interrupt */
init_completion(&fmdev->maintask_comp);
timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
FM_DRV_RX_SEEK_TIMEOUT);
if (!timeleft) {
fmerr("Timeout(%d sec),didn't get tune ended int\n",
jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000);
return -ENODATA;
}
int_reason = fmdev->irq_info.flag & (FM_TUNE_COMPLETE | FM_BAND_LIMIT);
/* Re-enable default FM interrupts */
fmdev->irq_info.mask = intr_flag;
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
if (int_reason & FM_BL_EVENT) {
if (wrap_around == 0) {
fmdev->rx.freq = seek_upward ?
fmdev->rx.region.top_freq :
fmdev->rx.region.bot_freq;
} else {
fmdev->rx.freq = seek_upward ?
fmdev->rx.region.bot_freq :
fmdev->rx.region.top_freq;
/* Calculate frequency index to write */
next_frq = (fmdev->rx.freq -
fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
goto again;
}
} else {
/* Read freq to know where operation tune operation stopped */
ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2,
&curr_frq, &resp_len);
if (ret < 0)
return ret;
curr_frq = be16_to_cpu(curr_frq);
fmdev->rx.freq = (fmdev->rx.region.bot_freq +
((u32)curr_frq * FM_FREQ_MUL));
}
/* Reset RDS cache and current station pointers */
fm_rx_reset_rds_cache(fmdev);
fm_rx_reset_station_info(fmdev);
return ret;
}
int fm_rx_set_volume(struct fmdev *fmdev, u16 vol_to_set)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (vol_to_set > FM_RX_VOLUME_MAX) {
fmerr("Volume is not within(%d-%d) range\n",
FM_RX_VOLUME_MIN, FM_RX_VOLUME_MAX);
return -EINVAL;
}
vol_to_set *= FM_RX_VOLUME_GAIN_STEP;
payload = vol_to_set;
ret = fmc_send_cmd(fmdev, VOLUME_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.volume = vol_to_set;
return ret;
}
/* Get volume */
int fm_rx_get_volume(struct fmdev *fmdev, u16 *curr_vol)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_vol == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_vol = fmdev->rx.volume / FM_RX_VOLUME_GAIN_STEP;
return 0;
}
/* To get current band's bottom and top frequency */
int fm_rx_get_band_freq_range(struct fmdev *fmdev, u32 *bot_freq, u32 *top_freq)
{
if (bot_freq != NULL)
*bot_freq = fmdev->rx.region.bot_freq;
if (top_freq != NULL)
*top_freq = fmdev->rx.region.top_freq;
return 0;
}
/* Returns current band index (0-Europe/US; 1-Japan) */
void fm_rx_get_region(struct fmdev *fmdev, u8 *region)
{
*region = fmdev->rx.region.fm_band;
}
/* Sets band (0-Europe/US; 1-Japan) */
int fm_rx_set_region(struct fmdev *fmdev, u8 region_to_set)
{
u16 payload;
u32 new_frq = 0;
int ret;
if (region_to_set != FM_BAND_EUROPE_US &&
region_to_set != FM_BAND_JAPAN) {
fmerr("Invalid band\n");
return -EINVAL;
}
if (fmdev->rx.region.fm_band == region_to_set) {
fmerr("Requested band is already configured\n");
return 0;
}
/* Send cmd to set the band */
payload = (u16)region_to_set;
ret = fmc_send_cmd(fmdev, BAND_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmc_update_region_info(fmdev, region_to_set);
/* Check whether current RX frequency is within band boundary */
if (fmdev->rx.freq < fmdev->rx.region.bot_freq)
new_frq = fmdev->rx.region.bot_freq;
else if (fmdev->rx.freq > fmdev->rx.region.top_freq)
new_frq = fmdev->rx.region.top_freq;
if (new_frq) {
fmdbg("Current freq is not within band limit boundary,"
"switching to %d KHz\n", new_frq);
/* Current RX frequency is not in range. So, update it */
ret = fm_rx_set_freq(fmdev, new_frq);
}
return ret;
}
/* Reads current mute mode (Mute Off/On/Attenuate)*/
int fm_rx_get_mute_mode(struct fmdev *fmdev, u8 *curr_mute_mode)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_mute_mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_mute_mode = fmdev->rx.mute_mode;
return 0;
}
static int fm_config_rx_mute_reg(struct fmdev *fmdev)
{
u16 payload, muteval;
int ret;
muteval = 0;
switch (fmdev->rx.mute_mode) {
case FM_MUTE_ON:
muteval = FM_RX_AC_MUTE_MODE;
break;
case FM_MUTE_OFF:
muteval = FM_RX_UNMUTE_MODE;
break;
case FM_MUTE_ATTENUATE:
muteval = FM_RX_SOFT_MUTE_FORCE_MODE;
break;
}
if (fmdev->rx.rf_depend_mute == FM_RX_RF_DEPENDENT_MUTE_ON)
muteval |= FM_RX_RF_DEP_MODE;
else
muteval &= ~FM_RX_RF_DEP_MODE;
payload = muteval;
ret = fmc_send_cmd(fmdev, MUTE_STATUS_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
/* Configures mute mode (Mute Off/On/Attenuate) */
int fm_rx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)
{
u8 org_state;
int ret;
if (fmdev->rx.mute_mode == mute_mode_toset)
return 0;
org_state = fmdev->rx.mute_mode;
fmdev->rx.mute_mode = mute_mode_toset;
ret = fm_config_rx_mute_reg(fmdev);
if (ret < 0) {
fmdev->rx.mute_mode = org_state;
return ret;
}
return 0;
}
/* Gets RF dependent soft mute mode enable/disable status */
int fm_rx_get_rfdepend_softmute(struct fmdev *fmdev, u8 *curr_mute_mode)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_mute_mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_mute_mode = fmdev->rx.rf_depend_mute;
return 0;
}
/* Sets RF dependent soft mute mode */
int fm_rx_set_rfdepend_softmute(struct fmdev *fmdev, u8 rfdepend_mute)
{
u8 org_state;
int ret;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_ON &&
rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_OFF) {
fmerr("Invalid RF dependent soft mute\n");
return -EINVAL;
}
if (fmdev->rx.rf_depend_mute == rfdepend_mute)
return 0;
org_state = fmdev->rx.rf_depend_mute;
fmdev->rx.rf_depend_mute = rfdepend_mute;
ret = fm_config_rx_mute_reg(fmdev);
if (ret < 0) {
fmdev->rx.rf_depend_mute = org_state;
return ret;
}
return 0;
}
/* Returns the signal strength level of current channel */
int fm_rx_get_rssi_level(struct fmdev *fmdev, u16 *rssilvl)
{
u16 curr_rssi_lel;
u32 resp_len;
int ret;
if (rssilvl == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
/* Read current RSSI level */
ret = fmc_send_cmd(fmdev, RSSI_LVL_GET, REG_RD, NULL, 2,
&curr_rssi_lel, &resp_len);
if (ret < 0)
return ret;
*rssilvl = be16_to_cpu(curr_rssi_lel);
return 0;
}
/*
* Sets the signal strength level that once reached
* will stop the auto search process
*/
int fm_rx_set_rssi_threshold(struct fmdev *fmdev, short rssi_lvl_toset)
{
u16 payload;
int ret;
if (rssi_lvl_toset < FM_RX_RSSI_THRESHOLD_MIN ||
rssi_lvl_toset > FM_RX_RSSI_THRESHOLD_MAX) {
fmerr("Invalid RSSI threshold level\n");
return -EINVAL;
}
payload = (u16)rssi_lvl_toset;
ret = fmc_send_cmd(fmdev, SEARCH_LVL_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.rssi_threshold = rssi_lvl_toset;
return 0;
}
/* Returns current RX RSSI threshold value */
int fm_rx_get_rssi_threshold(struct fmdev *fmdev, short *curr_rssi_lvl)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_rssi_lvl == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_rssi_lvl = fmdev->rx.rssi_threshold;
return 0;
}
/* Sets RX stereo/mono modes */
int fm_rx_set_stereo_mono(struct fmdev *fmdev, u16 mode)
{
u16 payload;
int ret;
if (mode != FM_STEREO_MODE && mode != FM_MONO_MODE) {
fmerr("Invalid mode\n");
return -EINVAL;
}
/* Set stereo/mono mode */
payload = (u16)mode;
ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Set stereo blending mode */
payload = FM_STEREO_SOFT_BLEND;
ret = fmc_send_cmd(fmdev, MOST_BLEND_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
/* Gets current RX stereo/mono mode */
int fm_rx_get_stereo_mono(struct fmdev *fmdev, u16 *mode)
{
u16 curr_mode;
u32 resp_len;
int ret;
if (mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_RD, NULL, 2,
&curr_mode, &resp_len);
if (ret < 0)
return ret;
*mode = be16_to_cpu(curr_mode);
return 0;
}
/* Choose RX de-emphasis filter mode (50us/75us) */
int fm_rx_set_deemphasis_mode(struct fmdev *fmdev, u16 mode)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (mode != FM_RX_EMPHASIS_FILTER_50_USEC &&
mode != FM_RX_EMPHASIS_FILTER_75_USEC) {
fmerr("Invalid rx de-emphasis mode (%d)\n", mode);
return -EINVAL;
}
payload = mode;
ret = fmc_send_cmd(fmdev, DEMPH_MODE_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.deemphasis_mode = mode;
return 0;
}
/* Gets current RX de-emphasis filter mode */
int fm_rx_get_deemph_mode(struct fmdev *fmdev, u16 *curr_deemphasis_mode)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_deemphasis_mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_deemphasis_mode = fmdev->rx.deemphasis_mode;
return 0;
}
/* Enable/Disable RX RDS */
int fm_rx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)
{
u16 payload;
int ret;
if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) {
fmerr("Invalid rds option\n");
return -EINVAL;
}
if (rds_en_dis == FM_RDS_ENABLE
&& fmdev->rx.rds.flag == FM_RDS_DISABLE) {
/* Turn on RX RDS and RDS circuit */
payload = FM_RX_PWR_SET_FM_AND_RDS_BLK_ON;
ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Clear and reset RDS FIFO */
payload = FM_RX_RDS_FLUSH_FIFO;
ret = fmc_send_cmd(fmdev, RDS_CNTRL_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Read flags - just to clear any pending interrupts. */
ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2,
NULL, NULL);
if (ret < 0)
return ret;
/* Set RDS FIFO threshold value */
payload = FM_RX_RDS_FIFO_THRESHOLD;
ret = fmc_send_cmd(fmdev, RDS_MEM_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Enable RDS interrupt */
fmdev->irq_info.mask |= FM_RDS_EVENT;
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0) {
fmdev->irq_info.mask &= ~FM_RDS_EVENT;
return ret;
}
/* Update our local flag */
fmdev->rx.rds.flag = FM_RDS_ENABLE;
} else if (rds_en_dis == FM_RDS_DISABLE
&& fmdev->rx.rds.flag == FM_RDS_ENABLE) {
/* Turn off RX RDS */
payload = FM_RX_PWR_SET_FM_ON_RDS_OFF;
ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Reset RDS pointers */
fmdev->rx.rds.last_blk_idx = 0;
fmdev->rx.rds.wr_idx = 0;
fmdev->rx.rds.rd_idx = 0;
fm_rx_reset_station_info(fmdev);
/* Update RDS local cache */
fmdev->irq_info.mask &= ~(FM_RDS_EVENT);
fmdev->rx.rds.flag = FM_RDS_DISABLE;
}
return 0;
}
/* Returns current RX RDS enable/disable status */
int fm_rx_get_rds_mode(struct fmdev *fmdev, u8 *curr_rds_en_dis)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (curr_rds_en_dis == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*curr_rds_en_dis = fmdev->rx.rds.flag;
return 0;
}
/* Sets RDS operation mode (RDS/RDBS) */
int fm_rx_set_rds_system(struct fmdev *fmdev, u8 rds_mode)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (rds_mode != FM_RDS_SYSTEM_RDS && rds_mode != FM_RDS_SYSTEM_RBDS) {
fmerr("Invalid rds mode\n");
return -EINVAL;
}
/* Set RDS operation mode */
payload = (u16)rds_mode;
ret = fmc_send_cmd(fmdev, RDS_SYSTEM_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.rds_mode = rds_mode;
return 0;
}
/* Returns current RDS operation mode */
int fm_rx_get_rds_system(struct fmdev *fmdev, u8 *rds_mode)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (rds_mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*rds_mode = fmdev->rx.rds_mode;
return 0;
}
/* Configures Alternate Frequency switch mode */
int fm_rx_set_af_switch(struct fmdev *fmdev, u8 af_mode)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (af_mode != FM_RX_RDS_AF_SWITCH_MODE_ON &&
af_mode != FM_RX_RDS_AF_SWITCH_MODE_OFF) {
fmerr("Invalid af mode\n");
return -EINVAL;
}
/* Enable/disable low RSSI interrupt based on af_mode */
if (af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
fmdev->irq_info.mask |= FM_LEV_EVENT;
else
fmdev->irq_info.mask &= ~FM_LEV_EVENT;
payload = fmdev->irq_info.mask;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->rx.af_mode = af_mode;
return 0;
}
/* Returns Alternate Frequency switch status */
int fm_rx_get_af_switch(struct fmdev *fmdev, u8 *af_mode)
{
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
if (af_mode == NULL) {
fmerr("Invalid memory\n");
return -ENOMEM;
}
*af_mode = fmdev->rx.af_mode;
return 0;
}

View File

@@ -0,0 +1,59 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* FM RX module header.
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#ifndef _FMDRV_RX_H
#define _FMDRV_RX_H
int fm_rx_set_freq(struct fmdev *, u32);
int fm_rx_set_mute_mode(struct fmdev *, u8);
int fm_rx_set_stereo_mono(struct fmdev *, u16);
int fm_rx_set_rds_mode(struct fmdev *, u8);
int fm_rx_set_rds_system(struct fmdev *, u8);
int fm_rx_set_volume(struct fmdev *, u16);
int fm_rx_set_rssi_threshold(struct fmdev *, short);
int fm_rx_set_region(struct fmdev *, u8);
int fm_rx_set_rfdepend_softmute(struct fmdev *, u8);
int fm_rx_set_deemphasis_mode(struct fmdev *, u16);
int fm_rx_set_af_switch(struct fmdev *, u8);
void fm_rx_reset_rds_cache(struct fmdev *);
void fm_rx_reset_station_info(struct fmdev *);
int fm_rx_seek(struct fmdev *, u32, u32, u32);
int fm_rx_get_rds_mode(struct fmdev *, u8 *);
int fm_rx_get_rds_system(struct fmdev *, u8 *);
int fm_rx_get_mute_mode(struct fmdev *, u8 *);
int fm_rx_get_volume(struct fmdev *, u16 *);
int fm_rx_get_band_freq_range(struct fmdev *,
u32 *, u32 *);
int fm_rx_get_stereo_mono(struct fmdev *, u16 *);
int fm_rx_get_rssi_level(struct fmdev *, u16 *);
int fm_rx_get_rssi_threshold(struct fmdev *, short *);
int fm_rx_get_rfdepend_softmute(struct fmdev *, u8 *);
int fm_rx_get_deemph_mode(struct fmdev *, u16 *);
int fm_rx_get_af_switch(struct fmdev *, u8 *);
void fm_rx_get_region(struct fmdev *, u8 *);
int fm_rx_set_chanl_spacing(struct fmdev *, u8);
int fm_rx_get_chanl_spacing(struct fmdev *, u8 *);
#endif

View File

@@ -0,0 +1,426 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* This sub-module of FM driver implements FM TX functionality.
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#include <linux/delay.h>
#include "fmdrv.h"
#include "fmdrv_common.h"
#include "fmdrv_tx.h"
int fm_tx_set_stereo_mono(struct fmdev *fmdev, u16 mode)
{
u16 payload;
int ret;
if (fmdev->tx_data.aud_mode == mode)
return 0;
fmdbg("stereo mode: %d\n", mode);
/* Set Stereo/Mono mode */
payload = (1 - mode);
ret = fmc_send_cmd(fmdev, MONO_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fmdev->tx_data.aud_mode = mode;
return ret;
}
static int set_rds_text(struct fmdev *fmdev, u8 *rds_text)
{
u16 payload;
int ret;
ret = fmc_send_cmd(fmdev, RDS_DATA_SET, REG_WR, rds_text,
strlen(rds_text), NULL, NULL);
if (ret < 0)
return ret;
/* Scroll mode */
payload = (u16)0x1;
ret = fmc_send_cmd(fmdev, DISPLAY_MODE, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
static int set_rds_data_mode(struct fmdev *fmdev, u8 mode)
{
u16 payload;
int ret;
/* Setting unique PI TODO: how unique? */
payload = (u16)0xcafe;
ret = fmc_send_cmd(fmdev, PI_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Set decoder id */
payload = (u16)0xa;
ret = fmc_send_cmd(fmdev, DI_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* TODO: RDS_MODE_GET? */
return 0;
}
static int set_rds_len(struct fmdev *fmdev, u8 type, u16 len)
{
u16 payload;
int ret;
len |= type << 8;
payload = len;
ret = fmc_send_cmd(fmdev, RDS_CONFIG_DATA_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* TODO: LENGTH_GET? */
return 0;
}
int fm_tx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)
{
u16 payload;
int ret;
u8 rds_text[] = "Zoom2\n";
fmdbg("rds_en_dis:%d(E:%d, D:%d)\n", rds_en_dis,
FM_RDS_ENABLE, FM_RDS_DISABLE);
if (rds_en_dis == FM_RDS_ENABLE) {
/* Set RDS length */
set_rds_len(fmdev, 0, strlen(rds_text));
/* Set RDS text */
set_rds_text(fmdev, rds_text);
/* Set RDS mode */
set_rds_data_mode(fmdev, 0x0);
}
/* Send command to enable RDS */
if (rds_en_dis == FM_RDS_ENABLE)
payload = 0x01;
else
payload = 0x00;
ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
if (rds_en_dis == FM_RDS_ENABLE) {
/* Set RDS length */
set_rds_len(fmdev, 0, strlen(rds_text));
/* Set RDS text */
set_rds_text(fmdev, rds_text);
}
fmdev->tx_data.rds.flag = rds_en_dis;
return 0;
}
int fm_tx_set_radio_text(struct fmdev *fmdev, u8 *rds_text, u8 rds_type)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
fm_tx_set_rds_mode(fmdev, 0);
/* Set RDS length */
set_rds_len(fmdev, rds_type, strlen(rds_text));
/* Set RDS text */
set_rds_text(fmdev, rds_text);
/* Set RDS mode */
set_rds_data_mode(fmdev, 0x0);
payload = 1;
ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
int fm_tx_set_af(struct fmdev *fmdev, u32 af)
{
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
fmdbg("AF: %d\n", af);
af = (af - 87500) / 100;
payload = (u16)af;
ret = fmc_send_cmd(fmdev, TA_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
int fm_tx_set_region(struct fmdev *fmdev, u8 region)
{
u16 payload;
int ret;
if (region != FM_BAND_EUROPE_US && region != FM_BAND_JAPAN) {
fmerr("Invalid band\n");
return -EINVAL;
}
/* Send command to set the band */
payload = (u16)region;
ret = fmc_send_cmd(fmdev, TX_BAND_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
int fm_tx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)
{
u16 payload;
int ret;
fmdbg("tx: mute mode %d\n", mute_mode_toset);
payload = mute_mode_toset;
ret = fmc_send_cmd(fmdev, MUTE, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
return 0;
}
/* Set TX Audio I/O */
static int set_audio_io(struct fmdev *fmdev)
{
struct fmtx_data *tx = &fmdev->tx_data;
u16 payload;
int ret;
/* Set Audio I/O Enable */
payload = tx->audio_io;
ret = fmc_send_cmd(fmdev, AUDIO_IO_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* TODO: is audio set? */
return 0;
}
/* Start TX Transmission */
static int enable_xmit(struct fmdev *fmdev, u8 new_xmit_state)
{
struct fmtx_data *tx = &fmdev->tx_data;
unsigned long timeleft;
u16 payload;
int ret;
/* Enable POWER_ENB interrupts */
payload = FM_POW_ENB_EVENT;
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Set Power Enable */
payload = new_xmit_state;
ret = fmc_send_cmd(fmdev, POWER_ENB_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* Wait for Power Enabled */
init_completion(&fmdev->maintask_comp);
timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
FM_DRV_TX_TIMEOUT);
if (!timeleft) {
fmerr("Timeout(%d sec),didn't get tune ended interrupt\n",
jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
return -ETIMEDOUT;
}
set_bit(FM_CORE_TX_XMITING, &fmdev->flag);
tx->xmit_state = new_xmit_state;
return 0;
}
/* Set TX power level */
int fm_tx_set_pwr_lvl(struct fmdev *fmdev, u8 new_pwr_lvl)
{
u16 payload;
struct fmtx_data *tx = &fmdev->tx_data;
int ret;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
fmdbg("tx: pwr_level_to_set %ld\n", (long int)new_pwr_lvl);
/* If the core isn't ready update global variable */
if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
tx->pwr_lvl = new_pwr_lvl;
return 0;
}
/* Set power level: Application will specify power level value in
* units of dB/uV, whereas range and step are specific to FM chip.
* For TI's WL chips, convert application specified power level value
* to chip specific value by subtracting 122 from it. Refer to TI FM
* data sheet for details.
* */
payload = (FM_PWR_LVL_HIGH - new_pwr_lvl);
ret = fmc_send_cmd(fmdev, POWER_LEV_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
/* TODO: is the power level set? */
tx->pwr_lvl = new_pwr_lvl;
return 0;
}
/*
* Sets FM TX pre-emphasis filter value (OFF, 50us, or 75us)
* Convert V4L2 specified filter values to chip specific filter values.
*/
int fm_tx_set_preemph_filter(struct fmdev *fmdev, u32 preemphasis)
{
struct fmtx_data *tx = &fmdev->tx_data;
u16 payload;
int ret;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
switch (preemphasis) {
case V4L2_PREEMPHASIS_DISABLED:
payload = FM_TX_PREEMPH_OFF;
break;
case V4L2_PREEMPHASIS_50_uS:
payload = FM_TX_PREEMPH_50US;
break;
case V4L2_PREEMPHASIS_75_uS:
payload = FM_TX_PREEMPH_75US;
break;
}
ret = fmc_send_cmd(fmdev, PREMPH_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
tx->preemph = payload;
return ret;
}
/* Get the TX tuning capacitor value.*/
int fm_tx_get_tune_cap_val(struct fmdev *fmdev)
{
u16 curr_val;
u32 resp_len;
int ret;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
ret = fmc_send_cmd(fmdev, READ_FMANT_TUNE_VALUE, REG_RD,
NULL, sizeof(curr_val), &curr_val, &resp_len);
if (ret < 0)
return ret;
curr_val = be16_to_cpu(curr_val);
return curr_val;
}
/* Set TX Frequency */
int fm_tx_set_freq(struct fmdev *fmdev, u32 freq_to_set)
{
struct fmtx_data *tx = &fmdev->tx_data;
u16 payload, chanl_index;
int ret;
if (test_bit(FM_CORE_TX_XMITING, &fmdev->flag)) {
enable_xmit(fmdev, 0);
clear_bit(FM_CORE_TX_XMITING, &fmdev->flag);
}
/* Enable FR, BL interrupts */
payload = (FM_FR_EVENT | FM_BL_EVENT);
ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
tx->tx_frq = (unsigned long)freq_to_set;
fmdbg("tx: freq_to_set %ld\n", (long int)tx->tx_frq);
chanl_index = freq_to_set / 10;
/* Set current tuner channel */
payload = chanl_index;
ret = fmc_send_cmd(fmdev, CHANL_SET, REG_WR, &payload,
sizeof(payload), NULL, NULL);
if (ret < 0)
return ret;
fm_tx_set_pwr_lvl(fmdev, tx->pwr_lvl);
fm_tx_set_preemph_filter(fmdev, tx->preemph);
tx->audio_io = 0x01; /* I2S */
set_audio_io(fmdev);
enable_xmit(fmdev, 0x01); /* Enable transmission */
tx->aud_mode = FM_STEREO_MODE;
tx->rds.flag = FM_RDS_DISABLE;
return 0;
}

View File

@@ -0,0 +1,37 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* FM TX module header.
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#ifndef _FMDRV_TX_H
#define _FMDRV_TX_H
int fm_tx_set_freq(struct fmdev *, u32);
int fm_tx_set_pwr_lvl(struct fmdev *, u8);
int fm_tx_set_region(struct fmdev *, u8);
int fm_tx_set_mute_mode(struct fmdev *, u8);
int fm_tx_set_stereo_mono(struct fmdev *, u16);
int fm_tx_set_rds_mode(struct fmdev *, u8);
int fm_tx_set_radio_text(struct fmdev *, u8 *, u8);
int fm_tx_set_af(struct fmdev *, u32);
int fm_tx_set_preemph_filter(struct fmdev *, u32);
int fm_tx_get_tune_cap_val(struct fmdev *);
#endif

View File

@@ -0,0 +1,615 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
* This file provides interfaces to V4L2 subsystem.
*
* This module registers with V4L2 subsystem as Radio
* data system interface (/dev/radio). During the registration,
* it will expose two set of function pointers.
*
* 1) File operation related API (open, close, read, write, poll...etc).
* 2) Set of V4L2 IOCTL complaint API.
*
* Copyright (C) 2011 Texas Instruments
* Author: Raja Mani <raja_mani@ti.com>
* Author: Manjunatha Halli <manjunatha_halli@ti.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#include <linux/export.h>
#include "fmdrv.h"
#include "fmdrv_v4l2.h"
#include "fmdrv_common.h"
#include "fmdrv_rx.h"
#include "fmdrv_tx.h"
static struct video_device *gradio_dev;
static u8 radio_disconnected;
/* -- V4L2 RADIO (/dev/radioX) device file operation interfaces --- */
/* Read RX RDS data */
static ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf,
size_t count, loff_t *ppos)
{
u8 rds_mode;
int ret;
struct fmdev *fmdev;
fmdev = video_drvdata(file);
if (!radio_disconnected) {
fmerr("FM device is already disconnected\n");
return -EIO;
}
if (mutex_lock_interruptible(&fmdev->mutex))
return -ERESTARTSYS;
/* Turn on RDS mode if it is disabled */
ret = fm_rx_get_rds_mode(fmdev, &rds_mode);
if (ret < 0) {
fmerr("Unable to read current rds mode\n");
goto read_unlock;
}
if (rds_mode == FM_RDS_DISABLE) {
ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE);
if (ret < 0) {
fmerr("Failed to enable rds mode\n");
goto read_unlock;
}
}
/* Copy RDS data from internal buffer to user buffer */
ret = fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count);
read_unlock:
mutex_unlock(&fmdev->mutex);
return ret;
}
/* Write TX RDS data */
static ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf,
size_t count, loff_t *ppos)
{
struct tx_rds rds;
int ret;
struct fmdev *fmdev;
ret = copy_from_user(&rds, buf, sizeof(rds));
rds.text[sizeof(rds.text) - 1] = '\0';
fmdbg("(%d)type: %d, text %s, af %d\n",
ret, rds.text_type, rds.text, rds.af_freq);
if (ret)
return -EFAULT;
fmdev = video_drvdata(file);
if (mutex_lock_interruptible(&fmdev->mutex))
return -ERESTARTSYS;
fm_tx_set_radio_text(fmdev, rds.text, rds.text_type);
fm_tx_set_af(fmdev, rds.af_freq);
mutex_unlock(&fmdev->mutex);
return sizeof(rds);
}
static u32 fm_v4l2_fops_poll(struct file *file, struct poll_table_struct *pts)
{
int ret;
struct fmdev *fmdev;
fmdev = video_drvdata(file);
mutex_lock(&fmdev->mutex);
ret = fmc_is_rds_data_available(fmdev, file, pts);
mutex_unlock(&fmdev->mutex);
if (ret < 0)
return POLLIN | POLLRDNORM;
return 0;
}
/*
* Handle open request for "/dev/radioX" device.
* Start with FM RX mode as default.
*/
static int fm_v4l2_fops_open(struct file *file)
{
int ret;
struct fmdev *fmdev = NULL;
/* Don't allow multiple open */
if (radio_disconnected) {
fmerr("FM device is already opened\n");
return -EBUSY;
}
fmdev = video_drvdata(file);
if (mutex_lock_interruptible(&fmdev->mutex))
return -ERESTARTSYS;
ret = fmc_prepare(fmdev);
if (ret < 0) {
fmerr("Unable to prepare FM CORE\n");
goto open_unlock;
}
fmdbg("Load FM RX firmware..\n");
ret = fmc_set_mode(fmdev, FM_MODE_RX);
if (ret < 0) {
fmerr("Unable to load FM RX firmware\n");
goto open_unlock;
}
radio_disconnected = 1;
open_unlock:
mutex_unlock(&fmdev->mutex);
return ret;
}
static int fm_v4l2_fops_release(struct file *file)
{
int ret;
struct fmdev *fmdev;
fmdev = video_drvdata(file);
if (!radio_disconnected) {
fmdbg("FM device is already closed\n");
return 0;
}
mutex_lock(&fmdev->mutex);
ret = fmc_set_mode(fmdev, FM_MODE_OFF);
if (ret < 0) {
fmerr("Unable to turn off the chip\n");
goto release_unlock;
}
ret = fmc_release(fmdev);
if (ret < 0) {
fmerr("FM CORE release failed\n");
goto release_unlock;
}
radio_disconnected = 0;
release_unlock:
mutex_unlock(&fmdev->mutex);
return ret;
}
/* V4L2 RADIO (/dev/radioX) device IOCTL interfaces */
static int fm_v4l2_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
strlcpy(capability->driver, FM_DRV_NAME, sizeof(capability->driver));
strlcpy(capability->card, FM_DRV_CARD_SHORT_NAME,
sizeof(capability->card));
sprintf(capability->bus_info, "UART");
capability->capabilities = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER |
V4L2_CAP_RADIO | V4L2_CAP_MODULATOR |
V4L2_CAP_AUDIO | V4L2_CAP_READWRITE |
V4L2_CAP_RDS_CAPTURE;
return 0;
}
static int fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
{
struct fmdev *fmdev = container_of(ctrl->handler,
struct fmdev, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
ctrl->val = fm_tx_get_tune_cap_val(fmdev);
break;
default:
fmwarn("%s: Unknown IOCTL: %d\n", __func__, ctrl->id);
break;
}
return 0;
}
static int fm_v4l2_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct fmdev *fmdev = container_of(ctrl->handler,
struct fmdev, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME: /* set volume */
return fm_rx_set_volume(fmdev, (u16)ctrl->val);
case V4L2_CID_AUDIO_MUTE: /* set mute */
return fmc_set_mute_mode(fmdev, (u8)ctrl->val);
case V4L2_CID_TUNE_POWER_LEVEL:
/* set TX power level - ext control */
return fm_tx_set_pwr_lvl(fmdev, (u8)ctrl->val);
case V4L2_CID_TUNE_PREEMPHASIS:
return fm_tx_set_preemph_filter(fmdev, (u8) ctrl->val);
default:
return -EINVAL;
}
}
static int fm_v4l2_vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *audio)
{
memset(audio, 0, sizeof(*audio));
strcpy(audio->name, "Radio");
audio->capability = V4L2_AUDCAP_STEREO;
return 0;
}
static int fm_v4l2_vidioc_s_audio(struct file *file, void *priv,
const struct v4l2_audio *audio)
{
if (audio->index != 0)
return -EINVAL;
return 0;
}
/* Get tuner attributes. If current mode is NOT RX, return error */
static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *tuner)
{
struct fmdev *fmdev = video_drvdata(file);
u32 bottom_freq;
u32 top_freq;
u16 stereo_mono_mode;
u16 rssilvl;
int ret;
if (tuner->index != 0)
return -EINVAL;
if (fmdev->curr_fmmode != FM_MODE_RX)
return -EPERM;
ret = fm_rx_get_band_freq_range(fmdev, &bottom_freq, &top_freq);
if (ret != 0)
return ret;
ret = fm_rx_get_stereo_mono(fmdev, &stereo_mono_mode);
if (ret != 0)
return ret;
ret = fm_rx_get_rssi_level(fmdev, &rssilvl);
if (ret != 0)
return ret;
strcpy(tuner->name, "FM");
tuner->type = V4L2_TUNER_RADIO;
/* Store rangelow and rangehigh freq in unit of 62.5 Hz */
tuner->rangelow = bottom_freq * 16;
tuner->rangehigh = top_freq * 16;
tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO |
((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0);
tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
V4L2_TUNER_CAP_LOW |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP;
tuner->audmode = (stereo_mono_mode ?
V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO);
/*
* Actual rssi value lies in between -128 to +127.
* Convert this range from 0 to 255 by adding +128
*/
rssilvl += 128;
/*
* Return signal strength value should be within 0 to 65535.
* Find out correct signal radio by multiplying (65535/255) = 257
*/
tuner->signal = rssilvl * 257;
tuner->afc = 0;
return ret;
}
/*
* Set tuner attributes. If current mode is NOT RX, set to RX.
* Currently, we set only audio mode (mono/stereo) and RDS state (on/off).
* Should we set other tuner attributes, too?
*/
static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *tuner)
{
struct fmdev *fmdev = video_drvdata(file);
u16 aud_mode;
u8 rds_mode;
int ret;
if (tuner->index != 0)
return -EINVAL;
aud_mode = (tuner->audmode == V4L2_TUNER_MODE_STEREO) ?
FM_STEREO_MODE : FM_MONO_MODE;
rds_mode = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
FM_RDS_ENABLE : FM_RDS_DISABLE;
if (fmdev->curr_fmmode != FM_MODE_RX) {
ret = fmc_set_mode(fmdev, FM_MODE_RX);
if (ret < 0) {
fmerr("Failed to set RX mode\n");
return ret;
}
}
ret = fmc_set_stereo_mono(fmdev, aud_mode);
if (ret < 0) {
fmerr("Failed to set RX stereo/mono mode\n");
return ret;
}
ret = fmc_set_rds_mode(fmdev, rds_mode);
if (ret < 0)
fmerr("Failed to set RX RDS mode\n");
return ret;
}
/* Get tuner or modulator radio frequency */
static int fm_v4l2_vidioc_g_freq(struct file *file, void *priv,
struct v4l2_frequency *freq)
{
struct fmdev *fmdev = video_drvdata(file);
int ret;
ret = fmc_get_freq(fmdev, &freq->frequency);
if (ret < 0) {
fmerr("Failed to get frequency\n");
return ret;
}
/* Frequency unit of 62.5 Hz*/
freq->frequency = (u32) freq->frequency * 16;
return 0;
}
/* Set tuner or modulator radio frequency */
static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv,
const struct v4l2_frequency *freq)
{
struct fmdev *fmdev = video_drvdata(file);
/*
* As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency
* in units of 62.5 Hz.
*/
return fmc_set_freq(fmdev, freq->frequency / 16);
}
/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */
static int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv,
const struct v4l2_hw_freq_seek *seek)
{
struct fmdev *fmdev = video_drvdata(file);
int ret;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
if (fmdev->curr_fmmode != FM_MODE_RX) {
ret = fmc_set_mode(fmdev, FM_MODE_RX);
if (ret != 0) {
fmerr("Failed to set RX mode\n");
return ret;
}
}
ret = fm_rx_seek(fmdev, seek->seek_upward, seek->wrap_around,
seek->spacing);
if (ret < 0)
fmerr("RX seek failed - %d\n", ret);
return ret;
}
/* Get modulator attributes. If mode is not TX, return no attributes. */
static int fm_v4l2_vidioc_g_modulator(struct file *file, void *priv,
struct v4l2_modulator *mod)
{
struct fmdev *fmdev = video_drvdata(file);
if (mod->index != 0)
return -EINVAL;
if (fmdev->curr_fmmode != FM_MODE_TX)
return -EPERM;
mod->txsubchans = ((fmdev->tx_data.aud_mode == FM_STEREO_MODE) ?
V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO) |
((fmdev->tx_data.rds.flag == FM_RDS_ENABLE) ?
V4L2_TUNER_SUB_RDS : 0);
mod->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
V4L2_TUNER_CAP_LOW;
return 0;
}
/* Set modulator attributes. If mode is not TX, set to TX. */
static int fm_v4l2_vidioc_s_modulator(struct file *file, void *priv,
const struct v4l2_modulator *mod)
{
struct fmdev *fmdev = video_drvdata(file);
u8 rds_mode;
u16 aud_mode;
int ret;
if (mod->index != 0)
return -EINVAL;
if (fmdev->curr_fmmode != FM_MODE_TX) {
ret = fmc_set_mode(fmdev, FM_MODE_TX);
if (ret != 0) {
fmerr("Failed to set TX mode\n");
return ret;
}
}
aud_mode = (mod->txsubchans & V4L2_TUNER_SUB_STEREO) ?
FM_STEREO_MODE : FM_MONO_MODE;
rds_mode = (mod->txsubchans & V4L2_TUNER_SUB_RDS) ?
FM_RDS_ENABLE : FM_RDS_DISABLE;
ret = fm_tx_set_stereo_mono(fmdev, aud_mode);
if (ret < 0) {
fmerr("Failed to set mono/stereo mode for TX\n");
return ret;
}
ret = fm_tx_set_rds_mode(fmdev, rds_mode);
if (ret < 0)
fmerr("Failed to set rds mode for TX\n");
return ret;
}
static const struct v4l2_file_operations fm_drv_fops = {
.owner = THIS_MODULE,
.read = fm_v4l2_fops_read,
.write = fm_v4l2_fops_write,
.poll = fm_v4l2_fops_poll,
.unlocked_ioctl = video_ioctl2,
.open = fm_v4l2_fops_open,
.release = fm_v4l2_fops_release,
};
static const struct v4l2_ctrl_ops fm_ctrl_ops = {
.s_ctrl = fm_v4l2_s_ctrl,
.g_volatile_ctrl = fm_g_volatile_ctrl,
};
static const struct v4l2_ioctl_ops fm_drv_ioctl_ops = {
.vidioc_querycap = fm_v4l2_vidioc_querycap,
.vidioc_g_audio = fm_v4l2_vidioc_g_audio,
.vidioc_s_audio = fm_v4l2_vidioc_s_audio,
.vidioc_g_tuner = fm_v4l2_vidioc_g_tuner,
.vidioc_s_tuner = fm_v4l2_vidioc_s_tuner,
.vidioc_g_frequency = fm_v4l2_vidioc_g_freq,
.vidioc_s_frequency = fm_v4l2_vidioc_s_freq,
.vidioc_s_hw_freq_seek = fm_v4l2_vidioc_s_hw_freq_seek,
.vidioc_g_modulator = fm_v4l2_vidioc_g_modulator,
.vidioc_s_modulator = fm_v4l2_vidioc_s_modulator
};
/* V4L2 RADIO device parent structure */
static struct video_device fm_viddev_template = {
.fops = &fm_drv_fops,
.ioctl_ops = &fm_drv_ioctl_ops,
.name = FM_DRV_NAME,
.release = video_device_release,
/*
* To ensure both the tuner and modulator ioctls are accessible we
* set the vfl_dir to M2M to indicate this.
*
* It is not really a mem2mem device of course, but it can both receive
* and transmit using the same radio device. It's the only radio driver
* that does this and it should really be split in two radio devices,
* but that would affect applications using this driver.
*/
.vfl_dir = VFL_DIR_M2M,
};
int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr)
{
struct v4l2_ctrl *ctrl;
int ret;
/* Init mutex for core locking */
mutex_init(&fmdev->mutex);
/* Allocate new video device */
gradio_dev = video_device_alloc();
if (NULL == gradio_dev) {
fmerr("Can't allocate video device\n");
return -ENOMEM;
}
/* Setup FM driver's V4L2 properties */
memcpy(gradio_dev, &fm_viddev_template, sizeof(fm_viddev_template));
video_set_drvdata(gradio_dev, fmdev);
gradio_dev->lock = &fmdev->mutex;
/* Register with V4L2 subsystem as RADIO device */
if (video_register_device(gradio_dev, VFL_TYPE_RADIO, radio_nr)) {
video_device_release(gradio_dev);
fmerr("Could not register video device\n");
return -ENOMEM;
}
fmdev->radio_dev = gradio_dev;
/* Register to v4l2 ctrl handler framework */
fmdev->radio_dev->ctrl_handler = &fmdev->ctrl_handler;
ret = v4l2_ctrl_handler_init(&fmdev->ctrl_handler, 5);
if (ret < 0) {
fmerr("(fmdev): Can't init ctrl handler\n");
v4l2_ctrl_handler_free(&fmdev->ctrl_handler);
return -EBUSY;
}
/*
* Following controls are handled by V4L2 control framework.
* Added in ascending ID order.
*/
v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, FM_RX_VOLUME_MIN,
FM_RX_VOLUME_MAX, 1, FM_RX_VOLUME_MAX);
v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_ctrl_new_std_menu(&fmdev->ctrl_handler, &fm_ctrl_ops,
V4L2_CID_TUNE_PREEMPHASIS, V4L2_PREEMPHASIS_75_uS,
0, V4L2_PREEMPHASIS_75_uS);
v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
V4L2_CID_TUNE_POWER_LEVEL, FM_PWR_LVL_LOW,
FM_PWR_LVL_HIGH, 1, FM_PWR_LVL_HIGH);
ctrl = v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0,
255, 1, 255);
if (ctrl)
ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
return 0;
}
void *fm_v4l2_deinit_video_device(void)
{
struct fmdev *fmdev;
fmdev = video_get_drvdata(gradio_dev);
/* Unregister to v4l2 ctrl handler framework*/
v4l2_ctrl_handler_free(&fmdev->ctrl_handler);
/* Unregister RADIO device from V4L2 subsystem */
video_unregister_device(gradio_dev);
return fmdev;
}

View File

@@ -0,0 +1,33 @@
/*
* FM Driver for Connectivity chip of Texas Instruments.
*
* FM V4L2 module header.
*
* Copyright (C) 2011 Texas Instruments
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 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
*
*/
#ifndef _FMDRV_V4L2_H
#define _FMDRV_V4L2_H
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ctrls.h>
int fm_v4l2_init_video_device(struct fmdev *, int);
void *fm_v4l2_deinit_video_device(void);
#endif