aboutsummaryrefslogtreecommitdiff
path: root/drivers/media/radio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig78
-rw-r--r--drivers/media/radio/Makefile7
-rw-r--r--drivers/media/radio/dsbr100.c4
-rw-r--r--drivers/media/radio/radio-aztech.c81
-rw-r--r--drivers/media/radio/radio-cadet.c94
-rw-r--r--drivers/media/radio/radio-isa.c15
-rw-r--r--drivers/media/radio/radio-keene.c37
-rw-r--r--drivers/media/radio/radio-ma901.c471
-rw-r--r--drivers/media/radio/radio-maxiradio.c15
-rw-r--r--drivers/media/radio/radio-miropcm20.c179
-rw-r--r--drivers/media/radio/radio-mr800.c14
-rw-r--r--drivers/media/radio/radio-raremono.c387
-rw-r--r--drivers/media/radio/radio-rtrack2.c5
-rw-r--r--drivers/media/radio/radio-sf16fmi.c131
-rw-r--r--drivers/media/radio/radio-sf16fmr2.c7
-rw-r--r--drivers/media/radio/radio-shark.c6
-rw-r--r--drivers/media/radio/radio-shark2.c4
-rw-r--r--drivers/media/radio/radio-si476x.c1588
-rw-r--r--drivers/media/radio/radio-tea5764.c194
-rw-r--r--drivers/media/radio/radio-tea5777.c9
-rw-r--r--drivers/media/radio/radio-timb.c85
-rw-r--r--drivers/media/radio/radio-wl1273.c18
-rw-r--r--drivers/media/radio/saa7706h.c66
-rw-r--r--drivers/media/radio/si470x/radio-si470x-common.c10
-rw-r--r--drivers/media/radio/si470x/radio-si470x-i2c.c4
-rw-r--r--drivers/media/radio/si470x/radio-si470x-usb.c92
-rw-r--r--drivers/media/radio/si470x/radio-si470x.h5
-rw-r--r--drivers/media/radio/si4713/Kconfig40
-rw-r--r--drivers/media/radio/si4713/Makefile7
-rw-r--r--drivers/media/radio/si4713/radio-platform-si4713.c (renamed from drivers/media/radio/radio-si4713.c)205
-rw-r--r--drivers/media/radio/si4713/radio-usb-si4713.c540
-rw-r--r--drivers/media/radio/si4713/si4713.c (renamed from drivers/media/radio/si4713-i2c.c)1322
-rw-r--r--drivers/media/radio/si4713/si4713.h (renamed from drivers/media/radio/si4713-i2c.h)70
-rw-r--r--drivers/media/radio/tea575x.c584
-rw-r--r--drivers/media/radio/tef6862.c48
-rw-r--r--drivers/media/radio/wl128x/Kconfig2
-rw-r--r--drivers/media/radio/wl128x/fmdrv.h2
-rw-r--r--drivers/media/radio/wl128x/fmdrv_common.c29
-rw-r--r--drivers/media/radio/wl128x/fmdrv_v4l2.c26
39 files changed, 4660 insertions, 1821 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 8090b87b306..192f36f2f4a 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -12,12 +12,38 @@ menuconfig RADIO_ADAPTERS
if RADIO_ADAPTERS && VIDEO_V4L2
+config RADIO_TEA575X
+ tristate
+
config RADIO_SI470X
bool "Silicon Labs Si470x FM Radio Receiver support"
depends on VIDEO_V4L2
source "drivers/media/radio/si470x/Kconfig"
+config RADIO_SI4713
+ tristate "Silicon Labs Si4713 FM Radio with RDS Transmitter support"
+ depends on VIDEO_V4L2
+
+source "drivers/media/radio/si4713/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
@@ -44,7 +70,8 @@ config USB_DSBR
config RADIO_MAXIRADIO
tristate "Guillemot MAXI Radio FM 2000 radio"
- depends on VIDEO_V4L2 && PCI && SND
+ depends on VIDEO_V4L2 && PCI
+ select RADIO_TEA575X
---help---
Choose Y here if you have this radio card. This card may also be
found as Gemtek PCI FM.
@@ -59,7 +86,8 @@ config RADIO_MAXIRADIO
config RADIO_SHARK
tristate "Griffin radioSHARK USB radio receiver"
- depends on USB && SND
+ depends on USB
+ select RADIO_TEA575X
---help---
Choose Y here if you have this radio receiver.
@@ -91,38 +119,41 @@ config RADIO_SHARK2
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
+config USB_KEENE
+ tristate "Keene FM Transmitter USB support"
+ depends on USB && VIDEO_V4L2
---help---
- Say Y here if you want support to Si4713 I2C device.
- This device driver supports only i2c bus.
+ 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 si4713.
+ module will be called radio-keene.
-config RADIO_SI4713
- tristate "Silicon Labs Si4713 FM Radio Transmitter support"
- depends on I2C && VIDEO_V4L2
- select I2C_SI4713
+config USB_RAREMONO
+ tristate "Thanko's Raremono AM/FM/SW radio support"
+ depends on USB && VIDEO_V4L2
---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.
+ The 'Thanko's Raremono' device contains the Si4734 chip from Silicon Labs Inc.
+ It is one of the very few or perhaps the only consumer USB radio device
+ to receive the AM/FM/SW bands.
+
+ Say Y here if you want to connect this type of AM/FM/SW receiver
+ to your computer's USB port.
To compile this driver as a module, choose M here: the
- module will be called radio-si4713.
+ module will be called radio-raremono.
-config USB_KEENE
- tristate "Keene FM Transmitter USB support"
+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 FM transmitter
- to your computer's USB port.
+ 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-keene.
+ module will be called radio-ma901.
config RADIO_TEA5764
tristate "TEA5764 I2C FM radio support"
@@ -364,7 +395,8 @@ config RADIO_SF16FMI
config RADIO_SF16FMR2
tristate "SF16-FMR2/SF16-FMD2 Radio"
- depends on ISA && VIDEO_V4L2 && SND
+ depends on ISA && VIDEO_V4L2
+ select RADIO_TEA575X
---help---
Choose Y here if you have one of these FM radio cards.
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index c03ce4fe74e..120e791199b 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -17,19 +17,22 @@ 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_RADIO_SI4713) += si4713/
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/
+obj-$(CONFIG_RADIO_TEA575X) += tea575x.o
+obj-$(CONFIG_USB_RAREMONO) += radio-raremono.o
shark2-objs := radio-shark2.o radio-tea5777.o
diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c
index 63b112b555b..142c2ee64d3 100644
--- a/drivers/media/radio/dsbr100.c
+++ b/drivers/media/radio/dsbr100.c
@@ -208,13 +208,13 @@ static int vidioc_g_tuner(struct file *file, void *priv,
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct dsbr100_device *radio = video_drvdata(file);
diff --git a/drivers/media/radio/radio-aztech.c b/drivers/media/radio/radio-aztech.c
index 177bcbd7a7c..705dd6f9162 100644
--- a/drivers/media/radio/radio-aztech.c
+++ b/drivers/media/radio/radio-aztech.c
@@ -26,6 +26,7 @@
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include "radio-isa.h"
+#include "lm7000.h"
MODULE_AUTHOR("Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath");
MODULE_DESCRIPTION("A driver for the Aztech radio card.");
@@ -54,18 +55,29 @@ struct aztech {
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);
-}
+/* bit definitions for register read */
+#define AZTECH_BIT_NOT_TUNED (1 << 0)
+#define AZTECH_BIT_MONO (1 << 1)
+/* bit definitions for register write */
+#define AZTECH_BIT_TUN_CE (1 << 1)
+#define AZTECH_BIT_TUN_CLK (1 << 6)
+#define AZTECH_BIT_TUN_DATA (1 << 7)
+/* bits 0 and 2 are volume control, bits 3..5 are not connected */
-static void send_1_byte(struct aztech *az)
+static void aztech_set_pins(void *handle, u8 pins)
{
- udelay(radio_wait_time);
- outb_p(128 + 2 + az->curvol, az->isa.io);
- outb_p(128 + 64 + 2 + az->curvol, az->isa.io);
+ struct radio_isa_card *isa = handle;
+ struct aztech *az = container_of(isa, struct aztech, isa);
+ u8 bits = az->curvol;
+
+ if (pins & LM7000_DATA)
+ bits |= AZTECH_BIT_TUN_DATA;
+ if (pins & LM7000_CLK)
+ bits |= AZTECH_BIT_TUN_CLK;
+ if (pins & LM7000_CE)
+ bits |= AZTECH_BIT_TUN_CE;
+
+ outb_p(bits, az->isa.io);
}
static struct radio_isa_card *aztech_alloc(void)
@@ -77,58 +89,21 @@ static struct radio_isa_card *aztech_alloc(void)
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);
+ lm7000_set_freq(freq, isa, aztech_set_pins);
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)
+ if (inb(isa->io) & AZTECH_BIT_MONO)
return V4L2_TUNER_SUB_MONO;
return V4L2_TUNER_SUB_STEREO;
}
-static int aztech_s_stereo(struct radio_isa_card *isa, bool stereo)
+static u32 aztech_g_signal(struct radio_isa_card *isa)
{
- return aztech_s_frequency(isa, isa->freq);
+ return (inb(isa->io) & AZTECH_BIT_NOT_TUNED) ? 0 : 0xffff;
}
static int aztech_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol)
@@ -146,8 +121,8 @@ 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,
+ .g_signal = aztech_g_signal,
};
static const int aztech_ioports[] = { 0x350, 0x358 };
@@ -165,7 +140,7 @@ static struct radio_isa_driver aztech_driver = {
.radio_nr_params = radio_nr,
.io_ports = aztech_ioports,
.num_of_io_ports = ARRAY_SIZE(aztech_ioports),
- .region_size = 2,
+ .region_size = 8,
.card = "Aztech Radio",
.ops = &aztech_ops,
.has_stereo = true,
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c
index 643d80ac28f..d719e59e217 100644
--- a/drivers/media/radio/radio-cadet.c
+++ b/drivers/media/radio/radio-cadet.c
@@ -90,6 +90,26 @@ static u16 sigtable[2][4] = {
{ 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)
{
@@ -196,6 +216,8 @@ static void cadet_setfreq(struct cadet *dev, unsigned freq)
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
@@ -248,6 +270,16 @@ reset_rds:
outb(inb(dev->io + 1) & 0x7f, dev->io + 1);
}
+static bool cadet_has_rds_data(struct cadet *dev)
+{
+ bool result;
+
+ mutex_lock(&dev->lock);
+ result = dev->rdsin != dev->rdsout;
+ mutex_unlock(&dev->lock);
+ return result;
+}
+
static void cadet_handler(unsigned long data)
{
@@ -257,13 +289,12 @@ static void cadet_handler(unsigned long data)
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");
+ pr_err("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
+ if (dev->rdsin + 1 != dev->rdsout)
dev->rdsin++;
}
mutex_unlock(&dev->lock);
@@ -272,7 +303,7 @@ static void cadet_handler(unsigned long data)
/*
* Service pending read
*/
- if (dev->rdsin != dev->rdsout)
+ if (cadet_has_rds_data(dev))
wake_up_interruptible(&dev->read_queue);
/*
@@ -305,22 +336,21 @@ static ssize_t cadet_read(struct file *file, char __user *data, size_t count, lo
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);
- }
+ mutex_unlock(&dev->lock);
+
+ if (!cadet_has_rds_data(dev) && (file->f_flags & O_NONBLOCK))
+ return -EWOULDBLOCK;
+ i = wait_event_interruptible(dev->read_queue, cadet_has_rds_data(dev));
+ if (i)
+ return i;
+
+ mutex_lock(&dev->lock);
while (i < count && dev->rdsin != dev->rdsout)
readbuf[i++] = dev->rdsbuf[dev->rdsout++];
+ mutex_unlock(&dev->lock);
if (i && copy_to_user(data, readbuf, i))
- i = -EFAULT;
-unlock:
- mutex_unlock(&dev->lock);
+ return -EFAULT;
return i;
}
@@ -330,33 +360,13 @@ static int vidioc_querycap(struct file *file, void *priv,
{
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));
+ strlcpy(v->bus_info, "ISA:radio-cadet", 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 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 vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *v)
{
@@ -388,7 +398,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
@@ -418,7 +428,7 @@ static int vidioc_g_frequency(struct file *file, void *priv,
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct cadet *dev = video_drvdata(file);
@@ -426,8 +436,6 @@ static int vidioc_s_frequency(struct file *file, void *priv,
return -EINVAL;
dev->is_fm_band =
f->frequency >= (bands[0].rangehigh + bands[1].rangelow) / 2;
- clamp(f->frequency, bands[dev->is_fm_band].rangelow,
- bands[dev->is_fm_band].rangehigh);
cadet_setfreq(dev, f->frequency);
return 0;
}
@@ -491,7 +499,7 @@ static unsigned int cadet_poll(struct file *file, struct poll_table_struct *wait
cadet_start_rds(dev);
mutex_unlock(&dev->lock);
}
- if (dev->rdsin != dev->rdsout)
+ if (cadet_has_rds_data(dev))
res |= POLLIN | POLLRDNORM;
return res;
}
diff --git a/drivers/media/radio/radio-isa.c b/drivers/media/radio/radio-isa.c
index 84b7b9f4385..6ff350831d5 100644
--- a/drivers/media/radio/radio-isa.c
+++ b/drivers/media/radio/radio-isa.c
@@ -51,8 +51,8 @@ static int radio_isa_querycap(struct file *file, void *priv,
strlcpy(v->card, isa->drv->card, sizeof(v->card));
snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", isa->v4l2_dev.name);
- v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
- v->device_caps = v->capabilities | V4L2_CAP_DEVICE_CAPS;
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
@@ -87,7 +87,7 @@ static int radio_isa_g_tuner(struct file *file, void *priv,
}
static int radio_isa_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
struct radio_isa_card *isa = video_drvdata(file);
const struct radio_isa_ops *ops = isa->drv->ops;
@@ -102,17 +102,18 @@ static int radio_isa_s_tuner(struct file *file, void *priv,
}
static int radio_isa_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ 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;
- f->frequency = clamp(f->frequency, FREQ_LOW, FREQ_HIGH);
- res = isa->drv->ops->s_frequency(isa, f->frequency);
+ freq = clamp(freq, FREQ_LOW, FREQ_HIGH);
+ res = isa->drv->ops->s_frequency(isa, freq);
if (res == 0)
- isa->freq = f->frequency;
+ isa->freq = freq;
return res;
}
diff --git a/drivers/media/radio/radio-keene.c b/drivers/media/radio/radio-keene.c
index e10e525f33e..3d127825ece 100644
--- a/drivers/media/radio/radio-keene.c
+++ b/drivers/media/radio/radio-keene.c
@@ -93,7 +93,7 @@ static int keene_cmd_main(struct keene_device *radio, unsigned freq, bool play)
/* 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.
+ then enter transmit mode.
*/
radio->buffer[5] = (radio->muted ? 4 : 8) | (play ? 1 : 2) |
(freq ? 0x10 : 0);
@@ -123,7 +123,7 @@ static int keene_cmd_set(struct keene_device *radio)
/* 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[3] = (radio->stereo ? 0 : 1) | (radio->preemph_75_us ? 4 : 0);
radio->buffer[4] = 0x00;
radio->buffer[5] = 0x00;
radio->buffer[6] = 0x00;
@@ -215,15 +215,15 @@ static int vidioc_s_modulator(struct file *file, void *priv,
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ 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;
- f->frequency = clamp(f->frequency,
- FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
- return keene_cmd_main(radio, f->frequency, true);
+ 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,
@@ -350,7 +350,6 @@ static int usb_keene_probe(struct usb_interface *intf,
radio->pa = 118;
radio->tx = 0x32;
radio->stereo = true;
- radio->curfreq = 95.16 * FREQ_MUL;
if (hdl->error) {
retval = hdl->error;
@@ -374,6 +373,7 @@ static int usb_keene_probe(struct usb_interface *intf,
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;
@@ -382,6 +382,10 @@ static int usb_keene_probe(struct usb_interface *intf,
video_set_drvdata(&radio->vdev, radio);
set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
+ /* at least 11ms is needed in order to settle hardware */
+ msleep(20);
+ keene_cmd_main(radio, 95.16 * FREQ_MUL, false);
+
retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1);
if (retval < 0) {
dev_err(&intf->dev, "could not register video device\n");
@@ -412,22 +416,5 @@ static struct usb_driver usb_keene_driver = {
.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);
+module_usb_driver(usb_keene_driver);
diff --git a/drivers/media/radio/radio-ma901.c b/drivers/media/radio/radio-ma901.c
new file mode 100644
index 00000000000..a85b064cb7b
--- /dev/null
+++ b/drivers/media/radio/radio-ma901.c
@@ -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);
diff --git a/drivers/media/radio/radio-maxiradio.c b/drivers/media/radio/radio-maxiradio.c
index bd4d3a7cdad..5236035f0f2 100644
--- a/drivers/media/radio/radio-maxiradio.c
+++ b/drivers/media/radio/radio-maxiradio.c
@@ -42,7 +42,7 @@
#include <linux/videodev2.h>
#include <linux/io.h>
#include <linux/slab.h>
-#include <sound/tea575x-tuner.h>
+#include <media/tea575x.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
@@ -200,15 +200,4 @@ static struct pci_driver maxiradio_driver = {
.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);
+module_pci_driver(maxiradio_driver);
diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c
index 11f76ed4c6f..a7e93d7477d 100644
--- a/drivers/media/radio/radio-miropcm20.c
+++ b/drivers/media/radio/radio-miropcm20.c
@@ -17,49 +17,36 @@
#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)");
-static bool mono;
-module_param(mono, bool, 0);
-MODULE_PARM_DESC(mono, "Force tuner into mono mode.");
-
struct pcm20 {
struct v4l2_device v4l2_dev;
struct video_device vdev;
+ struct v4l2_ctrl_handler ctrl_handler;
unsigned long freq;
- int muted;
+ u32 audmode;
struct snd_miro_aci *aci;
struct mutex lock;
};
static struct pcm20 pcm20_card = {
- .freq = 87*16000,
- .muted = 1,
+ .freq = 87 * 16000,
+ .audmode = V4L2_TUNER_MODE_STEREO,
};
-static int pcm20_mute(struct pcm20 *dev, unsigned char mute)
-{
- dev->muted = mute;
- return snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, mute, -1);
-}
-
-static int pcm20_stereo(struct pcm20 *dev, unsigned char stereo)
-{
- return snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, !stereo, -1);
-}
-
static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq)
{
unsigned char freql;
unsigned char freqh;
struct snd_miro_aci *aci = dev->aci;
- dev->freq = freq;
-
freq /= 160;
if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0))
freq /= 10; /* I don't know exactly which version
@@ -67,46 +54,68 @@ static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq)
freql = freq & 0xff;
freqh = freq >> 8;
- pcm20_stereo(dev, !mono);
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));
- strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
- v->version = 0x1;
- v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ 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)
{
- if (v->index) /* Only 1 tuner */
+ 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;
- v->signal = 0xffff;
- v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
- v->capability = V4L2_TUNER_CAP_LOW;
- v->audmode = V4L2_TUNER_MODE_MONO;
+ 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,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
- return v->index ? -EINVAL : 0;
+ 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,
@@ -124,82 +133,28 @@ static int vidioc_g_frequency(struct file *file, void *priv,
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct pcm20 *dev = video_drvdata(file);
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
- dev->freq = f->frequency;
- pcm20_setfreq(dev, f->frequency);
- 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 pcm20 *dev = video_drvdata(file);
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_MUTE:
- ctrl->value = dev->muted;
- break;
- default:
- return -EINVAL;
- }
+ dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U);
+ pcm20_setfreq(dev, dev->freq);
return 0;
}
-static int vidioc_s_ctrl(struct file *file, void *priv,
- struct v4l2_control *ctrl)
+static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl)
{
- struct pcm20 *dev = video_drvdata(file);
+ struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
- pcm20_mute(dev, ctrl->value);
- break;
- default:
- return -EINVAL;
+ snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1);
+ return 0;
}
- return 0;
-}
-
-static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
-{
- *i = 0;
- return 0;
-}
-
-static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
-{
- 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;
+ return -EINVAL;
}
static const struct v4l2_ioctl_ops pcm20_ioctl_ops = {
@@ -208,19 +163,20 @@ static const struct v4l2_ioctl_ops pcm20_ioctl_ops = {
.vidioc_s_tuner = vidioc_s_tuner,
.vidioc_g_frequency = vidioc_g_frequency,
.vidioc_s_frequency = vidioc_s_frequency,
- .vidioc_queryctrl = vidioc_queryctrl,
- .vidioc_g_ctrl = vidioc_g_ctrl,
- .vidioc_s_ctrl = vidioc_s_ctrl,
- .vidioc_g_audio = vidioc_g_audio,
- .vidioc_s_audio = vidioc_s_audio,
- .vidioc_g_input = vidioc_g_input,
- .vidioc_s_input = vidioc_s_input,
+ .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();
@@ -229,7 +185,7 @@ static int __init pcm20_init(void)
"you must load the snd-miro driver first!\n");
return -ENODEV;
}
- strlcpy(v4l2_dev->name, "miropcm20", sizeof(v4l2_dev->name));
+ strlcpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name));
mutex_init(&dev->lock);
res = v4l2_device_register(NULL, v4l2_dev);
@@ -238,20 +194,35 @@ static int __init pcm20_init(void)
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 fail;
+ goto err_hdl;
v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n");
return 0;
-fail:
+err_hdl:
+ v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(v4l2_dev);
return -EINVAL;
}
@@ -265,6 +236,8 @@ 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);
}
diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c
index 9c5a267b60b..a360227ca3a 100644
--- a/drivers/media/radio/radio-mr800.c
+++ b/drivers/media/radio/radio-mr800.c
@@ -203,10 +203,14 @@ static int amradio_set_mute(struct amradio_device *radio, bool mute)
/* 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 = 0x10 + (freq >> 3) / 25;
+ 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;
@@ -305,7 +309,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
/* vidioc_s_tuner - set tuner attributes */
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
struct amradio_device *radio = video_drvdata(file);
@@ -323,14 +327,13 @@ static int vidioc_s_tuner(struct file *file, void *priv,
/* vidioc_s_frequency - set tuner radio frequency */
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct amradio_device *radio = video_drvdata(file);
if (f->tuner != 0)
return -EINVAL;
- return amradio_set_freq(radio, clamp_t(unsigned, f->frequency,
- FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL));
+ return amradio_set_freq(radio, f->frequency);
}
/* vidioc_g_frequency - get tuner radio frequency */
@@ -389,6 +392,7 @@ static int vidioc_s_hw_freq_seek(struct file *file, void *priv,
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,
diff --git a/drivers/media/radio/radio-raremono.c b/drivers/media/radio/radio-raremono.c
new file mode 100644
index 00000000000..7b3bdbb1be7
--- /dev/null
+++ b/drivers/media/radio/radio-raremono.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2013 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/mutex.h>
+#include <linux/videodev2.h>
+#include <asm/unaligned.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+
+/*
+ * 'Thanko's Raremono' is a Japanese si4734-based AM/FM/SW USB receiver:
+ *
+ * http://www.raremono.jp/product/484.html/
+ *
+ * The USB protocol has been reversed engineered using wireshark, initially
+ * by Dinesh Ram <dinesh.ram@cern.ch> and finished by Hans Verkuil
+ * <hverkuil@xs4all.nl>.
+ *
+ * Sadly the firmware used in this product hides lots of goodies since the
+ * si4734 has more features than are supported by the firmware. Oh well...
+ */
+
+/* driver and module definitions */
+MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+MODULE_DESCRIPTION("Thanko's Raremono AM/FM/SW Receiver USB driver");
+MODULE_LICENSE("GPL v2");
+
+/*
+ * The Device announces itself as Cygnal Integrated Products, Inc.
+ *
+ * The vendor and product IDs (and in fact all other lsusb information as
+ * well) are identical to the si470x Silicon Labs USB FM Radio Reference
+ * Design board, even though this card has a si4734 device. Clearly the
+ * designer of this product never bothered to change the USB IDs.
+ */
+
+/* USB Device ID List */
+static struct usb_device_id usb_raremono_device_table[] = {
+ {USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, usb_raremono_device_table);
+
+#define BUFFER_LENGTH 64
+
+/* Timeout is set to a high value, could probably be reduced. Need more tests */
+#define USB_TIMEOUT 10000
+
+/* Frequency limits in KHz */
+#define FM_FREQ_RANGE_LOW 64000
+#define FM_FREQ_RANGE_HIGH 108000
+
+#define AM_FREQ_RANGE_LOW 520
+#define AM_FREQ_RANGE_HIGH 1710
+
+#define SW_FREQ_RANGE_LOW 2300
+#define SW_FREQ_RANGE_HIGH 26100
+
+enum { BAND_FM, BAND_AM, BAND_SW };
+
+static const struct v4l2_frequency_band bands[] = {
+ /* Band FM */
+ {
+ .type = V4L2_TUNER_RADIO,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = FM_FREQ_RANGE_LOW * 16,
+ .rangehigh = FM_FREQ_RANGE_HIGH * 16,
+ .modulation = V4L2_BAND_MODULATION_FM,
+ },
+ /* Band AM */
+ {
+ .type = V4L2_TUNER_RADIO,
+ .index = 1,
+ .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = AM_FREQ_RANGE_LOW * 16,
+ .rangehigh = AM_FREQ_RANGE_HIGH * 16,
+ .modulation = V4L2_BAND_MODULATION_AM,
+ },
+ /* Band SW */
+ {
+ .type = V4L2_TUNER_RADIO,
+ .index = 2,
+ .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = SW_FREQ_RANGE_LOW * 16,
+ .rangehigh = SW_FREQ_RANGE_HIGH * 16,
+ .modulation = V4L2_BAND_MODULATION_AM,
+ },
+};
+
+struct raremono_device {
+ struct usb_device *usbdev;
+ struct usb_interface *intf;
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct mutex lock;
+
+ u8 *buffer;
+ u32 band;
+ unsigned curfreq;
+};
+
+static inline struct raremono_device *to_raremono_dev(struct v4l2_device *v4l2_dev)
+{
+ return container_of(v4l2_dev, struct raremono_device, v4l2_dev);
+}
+
+/* Set frequency. */
+static int raremono_cmd_main(struct raremono_device *radio, unsigned band, unsigned freq)
+{
+ unsigned band_offset;
+ int ret;
+
+ switch (band) {
+ case BAND_FM:
+ band_offset = 1;
+ freq /= 10;
+ break;
+ case BAND_AM:
+ band_offset = 0;
+ break;
+ default:
+ band_offset = 2;
+ break;
+ }
+ radio->buffer[0] = 0x04 + band_offset;
+ radio->buffer[1] = freq >> 8;
+ radio->buffer[2] = freq & 0xff;
+
+ ret = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
+ HID_REQ_SET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 0x0300 + radio->buffer[0], 2,
+ radio->buffer, 3, USB_TIMEOUT);
+
+ if (ret < 0) {
+ dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret);
+ return ret;
+ }
+ radio->curfreq = (band == BAND_FM) ? freq * 10 : freq;
+ return 0;
+}
+
+/* Handle unplugging the device.
+ * We call video_unregister_device in any case.
+ * The last function called in this procedure is
+ * usb_raremono_device_release.
+ */
+static void usb_raremono_disconnect(struct usb_interface *intf)
+{
+ struct raremono_device *radio = to_raremono_dev(usb_get_intfdata(intf));
+
+ dev_info(&intf->dev, "Thanko's Raremono disconnected\n");
+
+ 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);
+}
+
+/*
+ * Linux Video interface
+ */
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ struct raremono_device *radio = video_drvdata(file);
+
+ strlcpy(v->driver, "radio-raremono", sizeof(v->driver));
+ strlcpy(v->card, "Thanko's Raremono", sizeof(v->card));
+ usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ 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)
+{
+ if (band->tuner != 0)
+ return -EINVAL;
+
+ if (band->index >= ARRAY_SIZE(bands))
+ return -EINVAL;
+
+ *band = bands[band->index];
+
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct raremono_device *radio = video_drvdata(file);
+ int ret;
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ strlcpy(v->name, "AM/FM/SW", sizeof(v->name));
+ v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_FREQ_BANDS;
+ v->rangelow = AM_FREQ_RANGE_LOW * 16;
+ v->rangehigh = FM_FREQ_RANGE_HIGH * 16;
+ v->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO;
+ v->audmode = (radio->curfreq < FM_FREQ_RANGE_LOW) ?
+ V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
+ memset(radio->buffer, 1, BUFFER_LENGTH);
+ ret = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ 1, 0xa1, 0x030d, 2, radio->buffer, BUFFER_LENGTH, USB_TIMEOUT);
+
+ if (ret < 0) {
+ dev_warn(radio->v4l2_dev.dev, "%s failed (%d)\n", __func__, ret);
+ return ret;
+ }
+ v->signal = ((radio->buffer[1] & 0xf) << 8 | radio->buffer[2]) << 4;
+ 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 raremono_device *radio = video_drvdata(file);
+ u32 freq = f->frequency;
+ unsigned band;
+
+ if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ if (f->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) * 8)
+ band = BAND_FM;
+ else if (f->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) * 8)
+ band = BAND_AM;
+ else
+ band = BAND_SW;
+
+ freq = clamp_t(u32, f->frequency, bands[band].rangelow, bands[band].rangehigh);
+ return raremono_cmd_main(radio, band, freq / 16);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct raremono_device *radio = video_drvdata(file);
+
+ if (f->tuner != 0)
+ return -EINVAL;
+ f->type = V4L2_TUNER_RADIO;
+ f->frequency = radio->curfreq * 16;
+ return 0;
+}
+
+/* File system interface */
+static const struct v4l2_file_operations usb_raremono_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = v4l2_fh_release,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops usb_raremono_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,
+};
+
+/* check if the device is present and register with v4l and usb if it is */
+static int usb_raremono_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct raremono_device *radio;
+ int retval = 0;
+
+ radio = devm_kzalloc(&intf->dev, sizeof(struct raremono_device), GFP_KERNEL);
+ if (radio)
+ radio->buffer = devm_kmalloc(&intf->dev, BUFFER_LENGTH, GFP_KERNEL);
+
+ if (!radio || !radio->buffer)
+ return -ENOMEM;
+
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->intf = intf;
+
+ /*
+ * This device uses the same USB IDs as the si470x SiLabs reference
+ * design. So do an additional check: attempt to read the device ID
+ * from the si470x: the lower 12 bits are 0x0242 for the si470x. The
+ * Raremono always returns 0x0800 (the meaning of that is unknown, but
+ * at least it works).
+ *
+ * We use this check to determine which device we are dealing with.
+ */
+ msleep(20);
+ retval = usb_control_msg(radio->usbdev,
+ usb_rcvctrlpipe(radio->usbdev, 0),
+ HID_REQ_GET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 1, 2,
+ radio->buffer, 3, 500);
+ if (retval != 3 ||
+ (get_unaligned_be16(&radio->buffer[1]) & 0xfff) == 0x0242) {
+ dev_info(&intf->dev, "this is not Thanko's Raremono.\n");
+ return -ENODEV;
+ }
+
+ dev_info(&intf->dev, "Thanko's Raremono connected: (%04X:%04X)\n",
+ id->idVendor, id->idProduct);
+
+ retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
+ if (retval < 0) {
+ dev_err(&intf->dev, "couldn't register v4l2_device\n");
+ return retval;
+ }
+
+ mutex_init(&radio->lock);
+
+ strlcpy(radio->vdev.name, radio->v4l2_dev.name,
+ sizeof(radio->vdev.name));
+ radio->vdev.v4l2_dev = &radio->v4l2_dev;
+ radio->vdev.fops = &usb_raremono_fops;
+ radio->vdev.ioctl_ops = &usb_raremono_ioctl_ops;
+ radio->vdev.lock = &radio->lock;
+ radio->vdev.release = video_device_release_empty;
+
+ usb_set_intfdata(intf, &radio->v4l2_dev);
+
+ video_set_drvdata(&radio->vdev, radio);
+ set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
+
+ raremono_cmd_main(radio, BAND_FM, 95160);
+
+ retval = video_register_device(&radio->vdev, VFL_TYPE_RADIO, -1);
+ if (retval == 0) {
+ dev_info(&intf->dev, "V4L2 device registered as %s\n",
+ video_device_node_name(&radio->vdev));
+ return 0;
+ }
+ dev_err(&intf->dev, "could not register video device\n");
+ v4l2_device_unregister(&radio->v4l2_dev);
+ return retval;
+}
+
+/* USB subsystem interface */
+static struct usb_driver usb_raremono_driver = {
+ .name = "radio-raremono",
+ .probe = usb_raremono_probe,
+ .disconnect = usb_raremono_disconnect,
+ .id_table = usb_raremono_device_table,
+};
+
+module_usb_driver(usb_raremono_driver);
diff --git a/drivers/media/radio/radio-rtrack2.c b/drivers/media/radio/radio-rtrack2.c
index b1f844c64fd..09cfbc373c9 100644
--- a/drivers/media/radio/radio-rtrack2.c
+++ b/drivers/media/radio/radio-rtrack2.c
@@ -8,6 +8,8 @@
*
* 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 */
@@ -81,8 +83,7 @@ static int rtrack2_s_frequency(struct radio_isa_card *isa, u32 freq)
zero(isa);
outb_p(0xc8, isa->io);
- if (!v4l2_ctrl_g_ctrl(isa->mute))
- outb_p(0, isa->io);
+ outb_p(v4l2_ctrl_g_ctrl(isa->mute), isa->io);
return 0;
}
diff --git a/drivers/media/radio/radio-sf16fmi.c b/drivers/media/radio/radio-sf16fmi.c
index 637a5556495..6f4318ff0db 100644
--- a/drivers/media/radio/radio-sf16fmi.c
+++ b/drivers/media/radio/radio-sf16fmi.c
@@ -27,6 +27,8 @@
#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-event.h>
#include "lm7000.h"
MODULE_AUTHOR("Petr Vandrovec, vandrove@vc.cvut.cz and M. Kirkwood");
@@ -44,10 +46,11 @@ module_param(radio_nr, int, 0);
struct fmi
{
struct v4l2_device v4l2_dev;
+ struct v4l2_ctrl_handler hdl;
struct video_device vdev;
int io;
bool mute;
- unsigned long curfreq; /* freq in kHz */
+ u32 curfreq; /* freq in kHz */
struct mutex lock;
};
@@ -55,8 +58,8 @@ static struct fmi fmi_card;
static struct pnp_dev *dev;
bool pnp_attached;
-#define RSF16_MINFREQ (87 * 16000)
-#define RSF16_MAXFREQ (108 * 16000)
+#define RSF16_MINFREQ (87U * 16000)
+#define RSF16_MAXFREQ (108U * 16000)
#define FMI_BIT_TUN_CE (1 << 0)
#define FMI_BIT_TUN_CLK (1 << 1)
@@ -115,13 +118,22 @@ static inline int fmi_getsigstr(struct fmi *fmi)
return (res & 2) ? 0 : 0xFFFF;
}
+static void fmi_set_freq(struct fmi *fmi)
+{
+ fmi->curfreq = clamp(fmi->curfreq, RSF16_MINFREQ, RSF16_MAXFREQ);
+ /* rounding in steps of 800 to match the freq
+ that will be used */
+ lm7000_set_freq((fmi->curfreq / 800) * 800, fmi, fmi_set_pins);
+}
+
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;
+ strlcpy(v->bus_info, "ISA:radio-sf16fmi", sizeof(v->bus_info));
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
@@ -145,24 +157,22 @@ static int vidioc_g_tuner(struct file *file, void *priv,
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
return v->index ? -EINVAL : 0;
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ 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);
+
+ fmi->curfreq = f->frequency;
+ fmi_set_freq(fmi);
+
return 0;
}
@@ -178,74 +188,31 @@ static int vidioc_g_frequency(struct file *file, void *priv,
return 0;
}
-static int vidioc_queryctrl(struct file *file, void *priv,
- struct v4l2_queryctrl *qc)
+static int fmi_s_ctrl(struct v4l2_ctrl *ctrl)
{
- 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);
+ struct fmi *fmi = container_of(ctrl->handler, struct fmi, hdl);
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)
+ if (ctrl->val)
fmi_mute(fmi);
else
fmi_unmute(fmi);
- fmi->mute = ctrl->value;
+ fmi->mute = ctrl->val;
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_ctrl_ops fmi_ctrl_ops = {
+ .s_ctrl = fmi_s_ctrl,
+};
static const struct v4l2_file_operations fmi_fops = {
.owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = v4l2_fh_release,
+ .poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
@@ -253,15 +220,11 @@ 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,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/* ladis: this is my card. does any other types exist? */
@@ -311,6 +274,7 @@ static int __init fmi_init(void)
{
struct fmi *fmi = &fmi_card;
struct v4l2_device *v4l2_dev = &fmi->v4l2_dev;
+ struct v4l2_ctrl_handler *hdl = &fmi->hdl;
int res, i;
int probe_ports[] = { 0, 0x284, 0x384 };
@@ -363,19 +327,35 @@ static int __init fmi_init(void)
return res;
}
+ v4l2_ctrl_handler_init(hdl, 1);
+ v4l2_ctrl_new_std(hdl, &fmi_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");
+ v4l2_ctrl_handler_free(hdl);
+ v4l2_device_unregister(v4l2_dev);
+ 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;
+ set_bit(V4L2_FL_USE_FH_PRIO, &fmi->vdev.flags);
video_set_drvdata(&fmi->vdev, fmi);
mutex_init(&fmi->lock);
- /* mute card - prevents noisy bootups */
- fmi_mute(fmi);
+ /* mute card and set default frequency */
+ fmi->mute = 1;
+ fmi->curfreq = RSF16_MINFREQ;
+ fmi_set_freq(fmi);
if (video_register_device(&fmi->vdev, VFL_TYPE_RADIO, radio_nr) < 0) {
+ v4l2_ctrl_handler_free(hdl);
v4l2_device_unregister(v4l2_dev);
release_region(fmi->io, 2);
if (pnp_attached)
@@ -391,6 +371,7 @@ static void __exit fmi_exit(void)
{
struct fmi *fmi = &fmi_card;
+ v4l2_ctrl_handler_free(&fmi->hdl);
video_unregister_device(&fmi->vdev);
v4l2_device_unregister(&fmi->v4l2_dev);
release_region(fmi->io, 2);
diff --git a/drivers/media/radio/radio-sf16fmr2.c b/drivers/media/radio/radio-sf16fmr2.c
index 9c0990457a7..93d864eb830 100644
--- a/drivers/media/radio/radio-sf16fmr2.c
+++ b/drivers/media/radio/radio-sf16fmr2.c
@@ -14,7 +14,7 @@
#include <linux/io.h> /* outb, outb_p */
#include <linux/isa.h>
#include <linux/pnp.h>
-#include <sound/tea575x-tuner.h>
+#include <media/tea575x.h>
MODULE_AUTHOR("Ondrej Zary");
MODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver");
@@ -74,8 +74,8 @@ 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;
+ 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)
@@ -295,7 +295,6 @@ static void fmr2_remove(struct fmr2 *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;
}
diff --git a/drivers/media/radio/radio-shark.c b/drivers/media/radio/radio-shark.c
index 8c309c7134d..050b3bb96fe 100644
--- a/drivers/media/radio/radio-shark.c
+++ b/drivers/media/radio/radio-shark.c
@@ -33,7 +33,7 @@
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <media/v4l2-device.h>
-#include <sound/tea575x-tuner.h>
+#include <media/tea575x.h>
#if defined(CONFIG_LEDS_CLASS) || \
(defined(CONFIG_LEDS_CLASS_MODULE) && defined(CONFIG_RADIO_SHARK_MODULE))
@@ -271,7 +271,7 @@ static void shark_unregister_leds(struct shark_device *shark)
cancel_work_sync(&shark->led_work);
}
-static void shark_resume_leds(struct shark_device *shark)
+static inline 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);
@@ -284,7 +284,7 @@ static void shark_resume_leds(struct shark_device *shark)
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
v4l2_warn(&shark->v4l2_dev,
- "CONFIG_LED_CLASS not enabled, LED support disabled\n");
+ "CONFIG_LEDS_CLASS not enabled, LED support disabled\n");
return 0;
}
static inline void shark_unregister_leds(struct shark_device *shark) { }
diff --git a/drivers/media/radio/radio-shark2.c b/drivers/media/radio/radio-shark2.c
index ef65ebbd536..8654e0dc5c9 100644
--- a/drivers/media/radio/radio-shark2.c
+++ b/drivers/media/radio/radio-shark2.c
@@ -237,7 +237,7 @@ static void shark_unregister_leds(struct shark_device *shark)
cancel_work_sync(&shark->led_work);
}
-static void shark_resume_leds(struct shark_device *shark)
+static inline void shark_resume_leds(struct shark_device *shark)
{
int i;
@@ -250,7 +250,7 @@ static void shark_resume_leds(struct shark_device *shark)
static int shark_register_leds(struct shark_device *shark, struct device *dev)
{
v4l2_warn(&shark->v4l2_dev,
- "CONFIG_LED_CLASS not enabled, LED support disabled\n");
+ "CONFIG_LEDS_CLASS not enabled, LED support disabled\n");
return 0;
}
static inline void shark_unregister_leds(struct shark_device *shark) { }
diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
new file mode 100644
index 00000000000..2fd9009f866
--- /dev/null
+++ b/drivers/media/radio/radio-si476x.c
@@ -0,0 +1,1588 @@
+/*
+ * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <andrew.smirnov@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; version 2 of the License.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/atomic.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.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 <media/si476x.h>
+#include <linux/mfd/si476x-core.h>
+
+#define FM_FREQ_RANGE_LOW 64000000
+#define FM_FREQ_RANGE_HIGH 108000000
+
+#define AM_FREQ_RANGE_LOW 520000
+#define AM_FREQ_RANGE_HIGH 30000000
+
+#define PWRLINEFLTR (1 << 8)
+
+#define FREQ_MUL (10000000 / 625)
+
+#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0x80 & (status))
+
+#define DRIVER_NAME "si476x-radio"
+#define DRIVER_CARD "SI476x AM/FM Receiver"
+
+enum si476x_freq_bands {
+ SI476X_BAND_FM,
+ SI476X_BAND_AM,
+};
+
+static const struct v4l2_frequency_band si476x_bands[] = {
+ [SI476X_BAND_FM] = {
+ .type = V4L2_TUNER_RADIO,
+ .index = SI476X_BAND_FM,
+ .capability = V4L2_TUNER_CAP_LOW
+ | V4L2_TUNER_CAP_STEREO
+ | V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 64 * FREQ_MUL,
+ .rangehigh = 108 * FREQ_MUL,
+ .modulation = V4L2_BAND_MODULATION_FM,
+ },
+ [SI476X_BAND_AM] = {
+ .type = V4L2_TUNER_RADIO,
+ .index = SI476X_BAND_AM,
+ .capability = V4L2_TUNER_CAP_LOW
+ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 0.52 * FREQ_MUL,
+ .rangehigh = 30 * FREQ_MUL,
+ .modulation = V4L2_BAND_MODULATION_AM,
+ },
+};
+
+static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band)
+{
+ return freq >= si476x_bands[band].rangelow &&
+ freq <= si476x_bands[band].rangehigh;
+}
+
+static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high,
+ int band)
+{
+ return low >= si476x_bands[band].rangelow &&
+ high <= si476x_bands[band].rangehigh;
+}
+
+static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl);
+static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl);
+
+enum phase_diversity_modes_idx {
+ SI476X_IDX_PHDIV_DISABLED,
+ SI476X_IDX_PHDIV_PRIMARY_COMBINING,
+ SI476X_IDX_PHDIV_PRIMARY_ANTENNA,
+ SI476X_IDX_PHDIV_SECONDARY_ANTENNA,
+ SI476X_IDX_PHDIV_SECONDARY_COMBINING,
+};
+
+static const char * const phase_diversity_modes[] = {
+ [SI476X_IDX_PHDIV_DISABLED] = "Disabled",
+ [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary",
+ [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna",
+ [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna",
+ [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary",
+};
+
+static inline enum phase_diversity_modes_idx
+si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode)
+{
+ switch (mode) {
+ default: /* FALLTHROUGH */
+ case SI476X_PHDIV_DISABLED:
+ return SI476X_IDX_PHDIV_DISABLED;
+ case SI476X_PHDIV_PRIMARY_COMBINING:
+ return SI476X_IDX_PHDIV_PRIMARY_COMBINING;
+ case SI476X_PHDIV_PRIMARY_ANTENNA:
+ return SI476X_IDX_PHDIV_PRIMARY_ANTENNA;
+ case SI476X_PHDIV_SECONDARY_ANTENNA:
+ return SI476X_IDX_PHDIV_SECONDARY_ANTENNA;
+ case SI476X_PHDIV_SECONDARY_COMBINING:
+ return SI476X_IDX_PHDIV_SECONDARY_COMBINING;
+ }
+}
+
+static inline enum si476x_phase_diversity_mode
+si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx)
+{
+ static const int idx_to_value[] = {
+ [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED,
+ [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING,
+ [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA,
+ [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA,
+ [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING,
+ };
+
+ return idx_to_value[idx];
+}
+
+static const struct v4l2_ctrl_ops si476x_ctrl_ops = {
+ .g_volatile_ctrl = si476x_radio_g_volatile_ctrl,
+ .s_ctrl = si476x_radio_s_ctrl,
+};
+
+
+enum si476x_ctrl_idx {
+ SI476X_IDX_RSSI_THRESHOLD,
+ SI476X_IDX_SNR_THRESHOLD,
+ SI476X_IDX_MAX_TUNE_ERROR,
+ SI476X_IDX_HARMONICS_COUNT,
+ SI476X_IDX_DIVERSITY_MODE,
+ SI476X_IDX_INTERCHIP_LINK,
+};
+static struct v4l2_ctrl_config si476x_ctrls[] = {
+
+ /**
+ * SI476X during its station seeking(or tuning) process uses several
+ * parameters to detrmine if "the station" is valid:
+ *
+ * - Signal's SNR(in dBuV) must be lower than
+ * #V4L2_CID_SI476X_SNR_THRESHOLD
+ * - Signal's RSSI(in dBuV) must be greater than
+ * #V4L2_CID_SI476X_RSSI_THRESHOLD
+ * - Signal's frequency deviation(in units of 2ppm) must not be
+ * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR
+ */
+ [SI476X_IDX_RSSI_THRESHOLD] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_RSSI_THRESHOLD,
+ .name = "Valid RSSI Threshold",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -128,
+ .max = 127,
+ .step = 1,
+ },
+ [SI476X_IDX_SNR_THRESHOLD] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_SNR_THRESHOLD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Valid SNR Threshold",
+ .min = -128,
+ .max = 127,
+ .step = 1,
+ },
+ [SI476X_IDX_MAX_TUNE_ERROR] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_MAX_TUNE_ERROR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Max Tune Errors",
+ .min = 0,
+ .max = 126 * 2,
+ .step = 2,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics
+ * built-in power-line noise supression filter is to reject
+ * during AM-mode operation.
+ */
+ [SI476X_IDX_HARMONICS_COUNT] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_HARMONICS_COUNT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+
+ .name = "Count of Harmonics to Reject",
+ .min = 0,
+ .max = 20,
+ .step = 1,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which
+ * two tuners working in diversity mode are to work in.
+ *
+ * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled
+ * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is
+ * on, primary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is
+ * off, primary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is
+ * off, secondary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is
+ * on, secondary tuner's antenna is the main one.
+ */
+ [SI476X_IDX_DIVERSITY_MODE] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_DIVERSITY_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Phase Diversity Mode",
+ .qmenu = phase_diversity_modes,
+ .min = 0,
+ .max = ARRAY_SIZE(phase_diversity_modes) - 1,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in
+ * diversity mode indicator. Allows user to determine if two
+ * chips working in diversity mode have established a link
+ * between each other and if the system as a whole uses
+ * signals from both antennas to receive FM radio.
+ */
+ [SI476X_IDX_INTERCHIP_LINK] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_INTERCHIP_LINK,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
+ .name = "Inter-Chip Link",
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ },
+};
+
+struct si476x_radio;
+
+/**
+ * struct si476x_radio_ops - vtable of tuner functions
+ *
+ * This table holds pointers to functions implementing particular
+ * operations depending on the mode in which the tuner chip was
+ * configured to start in. If the function is not supported
+ * corresponding element is set to #NULL.
+ *
+ * @tune_freq: Tune chip to a specific frequency
+ * @seek_start: Star station seeking
+ * @rsq_status: Get Received Signal Quality(RSQ) status
+ * @rds_blckcnt: Get received RDS blocks count
+ * @phase_diversity: Change phase diversity mode of the tuner
+ * @phase_div_status: Get phase diversity mode status
+ * @acf_status: Get the status of Automatically Controlled
+ * Features(ACF)
+ * @agc_status: Get Automatic Gain Control(AGC) status
+ */
+struct si476x_radio_ops {
+ int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *);
+ int (*seek_start)(struct si476x_core *, bool, bool);
+ int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *,
+ struct si476x_rsq_status_report *);
+ int (*rds_blckcnt)(struct si476x_core *, bool,
+ struct si476x_rds_blockcount_report *);
+
+ int (*phase_diversity)(struct si476x_core *,
+ enum si476x_phase_diversity_mode);
+ int (*phase_div_status)(struct si476x_core *);
+ int (*acf_status)(struct si476x_core *,
+ struct si476x_acf_status_report *);
+ int (*agc_status)(struct si476x_core *,
+ struct si476x_agc_status_report *);
+};
+
+/**
+ * struct si476x_radio - radio device
+ *
+ * @core: Pointer to underlying core device
+ * @videodev: Pointer to video device created by V4L2 subsystem
+ * @ops: Vtable of functions. See struct si476x_radio_ops for details
+ * @kref: Reference counter
+ * @core_lock: An r/w semaphore to brebvent the deletion of underlying
+ * core structure is the radio device is being used
+ */
+struct si476x_radio {
+ struct v4l2_device v4l2dev;
+ struct video_device videodev;
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct si476x_core *core;
+ /* This field should not be accesses unless core lock is held */
+ const struct si476x_radio_ops *ops;
+
+ struct dentry *debugfs;
+ u32 audmode;
+};
+
+static inline struct si476x_radio *
+v4l2_dev_to_radio(struct v4l2_device *d)
+{
+ return container_of(d, struct si476x_radio, v4l2dev);
+}
+
+static inline struct si476x_radio *
+v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d)
+{
+ return container_of(d, struct si476x_radio, ctrl_handler);
+}
+
+/*
+ * si476x_vidioc_querycap - query device capabilities
+ */
+static int si476x_radio_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+
+ strlcpy(capability->driver, radio->v4l2dev.name,
+ sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ snprintf(capability->bus_info, sizeof(capability->bus_info),
+ "platform:%s", radio->v4l2dev.name);
+
+ capability->device_caps = V4L2_CAP_TUNER
+ | V4L2_CAP_RADIO
+ | V4L2_CAP_HW_FREQ_SEEK;
+
+ si476x_core_lock(radio->core);
+ if (!si476x_core_is_a_secondary_tuner(radio->core))
+ capability->device_caps |= V4L2_CAP_RDS_CAPTURE
+ | V4L2_CAP_READWRITE;
+ si476x_core_unlock(radio->core);
+
+ capability->capabilities = capability->device_caps
+ | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int si476x_radio_enum_freq_bands(struct file *file, void *priv,
+ struct v4l2_frequency_band *band)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (band->tuner != 0)
+ return -EINVAL;
+
+ switch (radio->core->chip_id) {
+ /* AM/FM tuners -- all bands are supported */
+ case SI476X_CHIP_SI4761:
+ case SI476X_CHIP_SI4764:
+ if (band->index < ARRAY_SIZE(si476x_bands)) {
+ *band = si476x_bands[band->index];
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+ /* FM companion tuner chips -- only FM bands are
+ * supported */
+ case SI476X_CHIP_SI4768:
+ if (band->index == SI476X_BAND_FM) {
+ *band = si476x_bands[band->index];
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int si476x_radio_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ int err;
+ struct si476x_rsq_status_report report;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ if (tuner->index != 0)
+ return -EINVAL;
+
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies
+ * in multiples of
+ * 62.5 Hz */
+ | V4L2_TUNER_CAP_STEREO
+ | V4L2_TUNER_CAP_HWSEEK_BOUNDED
+ | V4L2_TUNER_CAP_HWSEEK_WRAP
+ | V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
+
+ si476x_core_lock(radio->core);
+
+ if (si476x_core_is_a_secondary_tuner(radio->core)) {
+ strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name));
+ tuner->rxsubchans = 0;
+ tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow;
+ } else if (si476x_core_has_am(radio->core)) {
+ if (si476x_core_is_a_primary_tuner(radio->core))
+ strlcpy(tuner->name, "AM/FM (primary)",
+ sizeof(tuner->name));
+ else
+ strlcpy(tuner->name, "AM/FM", sizeof(tuner->name));
+
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO
+ | V4L2_TUNER_SUB_RDS;
+ tuner->capability |= V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS;
+
+ tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow;
+ } else {
+ strlcpy(tuner->name, "FM", sizeof(tuner->name));
+ tuner->rxsubchans = V4L2_TUNER_SUB_RDS;
+ tuner->capability |= V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS;
+ tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow;
+ }
+
+ tuner->audmode = radio->audmode;
+
+ tuner->afc = 1;
+ tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh;
+
+ err = radio->ops->rsq_status(radio->core,
+ &args, &report);
+ if (err < 0) {
+ tuner->signal = 0;
+ } else {
+ /*
+ * tuner->signal value range: 0x0000 .. 0xFFFF,
+ * report.rssi: -128 .. 127
+ */
+ tuner->signal = (report.rssi + 128) * 257;
+ }
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+
+static int si476x_radio_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *tuner)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (tuner->index != 0)
+ return -EINVAL;
+
+ if (tuner->audmode == V4L2_TUNER_MODE_MONO ||
+ tuner->audmode == V4L2_TUNER_MODE_STEREO)
+ radio->audmode = tuner->audmode;
+ else
+ radio->audmode = V4L2_TUNER_MODE_STEREO;
+
+ return 0;
+}
+
+static int si476x_radio_init_vtable(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ static const struct si476x_radio_ops fm_ops = {
+ .tune_freq = si476x_core_cmd_fm_tune_freq,
+ .seek_start = si476x_core_cmd_fm_seek_start,
+ .rsq_status = si476x_core_cmd_fm_rsq_status,
+ .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount,
+ .phase_diversity = si476x_core_cmd_fm_phase_diversity,
+ .phase_div_status = si476x_core_cmd_fm_phase_div_status,
+ .acf_status = si476x_core_cmd_fm_acf_status,
+ .agc_status = si476x_core_cmd_agc_status,
+ };
+
+ static const struct si476x_radio_ops am_ops = {
+ .tune_freq = si476x_core_cmd_am_tune_freq,
+ .seek_start = si476x_core_cmd_am_seek_start,
+ .rsq_status = si476x_core_cmd_am_rsq_status,
+ .rds_blckcnt = NULL,
+ .phase_diversity = NULL,
+ .phase_div_status = NULL,
+ .acf_status = si476x_core_cmd_am_acf_status,
+ .agc_status = NULL,
+ };
+
+ switch (func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ radio->ops = &fm_ops;
+ return 0;
+
+ case SI476X_FUNC_AM_RECEIVER:
+ radio->ops = &am_ops;
+ return 0;
+ default:
+ WARN(1, "Unexpected tuner function value\n");
+ return -EINVAL;
+ }
+}
+
+static int si476x_radio_pretune(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int retval;
+
+ struct si476x_tune_freq_args args = {
+ .zifsr = false,
+ .hd = false,
+ .injside = SI476X_INJSIDE_AUTO,
+ .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE,
+ .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO,
+ .antcap = 0,
+ };
+
+ switch (func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ args.freq = v4l2_to_si476x(radio->core,
+ 92 * FREQ_MUL);
+ retval = radio->ops->tune_freq(radio->core, &args);
+ break;
+ case SI476X_FUNC_AM_RECEIVER:
+ args.freq = v4l2_to_si476x(radio->core,
+ 0.6 * FREQ_MUL);
+ retval = radio->ops->tune_freq(radio->core, &args);
+ break;
+ default:
+ WARN(1, "Unexpected tuner function value\n");
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int err;
+
+ /* regcache_mark_dirty(radio->core->regmap); */
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE,
+ SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_AUDIO_DEEMPHASIS,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_INT_CTL_ENABLE,
+ SI476X_PROP_INT_CTL_ENABLE);
+ if (err < 0)
+ return err;
+
+ /*
+ * Is there any point in restoring SNR and the like
+ * when switching between AM/FM?
+ */
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_VALID_SNR_THRESHOLD,
+ SI476X_PROP_VALID_RSSI_THRESHOLD);
+ if (err < 0)
+ return err;
+
+ if (func == SI476X_FUNC_FM_RECEIVER) {
+ if (si476x_core_has_diversity(radio->core)) {
+ err = si476x_core_cmd_fm_phase_diversity(radio->core,
+ radio->core->diversity_mode);
+ if (err < 0)
+ return err;
+ }
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_SOURCE,
+ SI476X_PROP_FM_RDS_CONFIG);
+ if (err < 0)
+ return err;
+ }
+
+ return si476x_radio_init_vtable(radio, func);
+
+}
+
+static int si476x_radio_change_func(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int err;
+ bool soft;
+ /*
+ * Since power/up down is a very time consuming operation,
+ * try to avoid doing it if the requested mode matches the one
+ * the tuner is in
+ */
+ if (func == radio->core->power_up_parameters.func)
+ return 0;
+
+ soft = true;
+ err = si476x_core_stop(radio->core, soft);
+ if (err < 0) {
+ /*
+ * OK, if the chip does not want to play nice let's
+ * try to reset it in more brutal way
+ */
+ soft = false;
+ err = si476x_core_stop(radio->core, soft);
+ if (err < 0)
+ return err;
+ }
+ /*
+ Set the desired radio tuner function
+ */
+ radio->core->power_up_parameters.func = func;
+
+ err = si476x_core_start(radio->core, soft);
+ if (err < 0)
+ return err;
+
+ /*
+ * No need to do the rest of manipulations for the bootlader
+ * mode
+ */
+ if (func != SI476X_FUNC_FM_RECEIVER &&
+ func != SI476X_FUNC_AM_RECEIVER)
+ return err;
+
+ return si476x_radio_do_post_powerup_init(radio, func);
+}
+
+static int si476x_radio_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (f->tuner != 0 ||
+ f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ if (radio->ops->rsq_status) {
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = true,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ if (!err)
+ f->frequency = si476x_to_v4l2(radio->core,
+ report.readfreq);
+ } else {
+ err = -EINVAL;
+ }
+
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+
+static int si476x_radio_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *f)
+{
+ int err;
+ u32 freq = f->frequency;
+ struct si476x_tune_freq_args args;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh +
+ si476x_bands[SI476X_BAND_FM].rangelow) / 2;
+ const int band = (freq > midrange) ?
+ SI476X_BAND_FM : SI476X_BAND_AM;
+ const enum si476x_func func = (band == SI476X_BAND_AM) ?
+ SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER;
+
+ if (f->tuner != 0 ||
+ f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ freq = clamp(freq,
+ si476x_bands[band].rangelow,
+ si476x_bands[band].rangehigh);
+
+ if (si476x_radio_freq_is_inside_of_the_band(freq,
+ SI476X_BAND_AM) &&
+ (!si476x_core_has_am(radio->core) ||
+ si476x_core_is_a_secondary_tuner(radio->core))) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = si476x_radio_change_func(radio, func);
+ if (err < 0)
+ goto unlock;
+
+ args.zifsr = false;
+ args.hd = false;
+ args.injside = SI476X_INJSIDE_AUTO;
+ args.freq = v4l2_to_si476x(radio->core, freq);
+ args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE;
+ args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO;
+ args.antcap = 0;
+
+ err = radio->ops->tune_freq(radio->core, &args);
+
+unlock:
+ si476x_core_unlock(radio->core);
+ return err;
+}
+
+static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv,
+ const struct v4l2_hw_freq_seek *seek)
+{
+ int err;
+ enum si476x_func func;
+ u32 rangelow, rangehigh;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ if (seek->tuner != 0 ||
+ seek->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ if (!seek->rangelow) {
+ err = regmap_read(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_BOTTOM,
+ &rangelow);
+ if (!err)
+ rangelow = si476x_to_v4l2(radio->core, rangelow);
+ else
+ goto unlock;
+ }
+ if (!seek->rangehigh) {
+ err = regmap_read(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_TOP,
+ &rangehigh);
+ if (!err)
+ rangehigh = si476x_to_v4l2(radio->core, rangehigh);
+ else
+ goto unlock;
+ }
+
+ if (rangelow > rangehigh) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh,
+ SI476X_BAND_FM)) {
+ func = SI476X_FUNC_FM_RECEIVER;
+
+ } else if (si476x_core_has_am(radio->core) &&
+ si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh,
+ SI476X_BAND_AM)) {
+ func = SI476X_FUNC_AM_RECEIVER;
+ } else {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = si476x_radio_change_func(radio, func);
+ if (err < 0)
+ goto unlock;
+
+ if (seek->rangehigh) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_TOP,
+ v4l2_to_si476x(radio->core,
+ seek->rangehigh));
+ if (err)
+ goto unlock;
+ }
+ if (seek->rangelow) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_BOTTOM,
+ v4l2_to_si476x(radio->core,
+ seek->rangelow));
+ if (err)
+ goto unlock;
+ }
+ if (seek->spacing) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_FREQUENCY_SPACING,
+ v4l2_to_si476x(radio->core,
+ seek->spacing));
+ if (err)
+ goto unlock;
+ }
+
+ err = radio->ops->seek_start(radio->core,
+ seek->seek_upward,
+ seek->wrap_around);
+unlock:
+ si476x_core_unlock(radio->core);
+
+
+
+ return err;
+}
+
+static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int retval;
+ struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler);
+
+ si476x_core_lock(radio->core);
+
+ switch (ctrl->id) {
+ case V4L2_CID_SI476X_INTERCHIP_LINK:
+ if (si476x_core_has_diversity(radio->core)) {
+ if (radio->ops->phase_diversity) {
+ retval = radio->ops->phase_div_status(radio->core);
+ if (retval < 0)
+ break;
+
+ ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval);
+ retval = 0;
+ break;
+ } else {
+ retval = -ENOTTY;
+ break;
+ }
+ }
+ retval = -EINVAL;
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ si476x_core_unlock(radio->core);
+ return retval;
+
+}
+
+static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int retval;
+ enum si476x_phase_diversity_mode mode;
+ struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler);
+
+ si476x_core_lock(radio->core);
+
+ switch (ctrl->id) {
+ case V4L2_CID_SI476X_HARMONICS_COUNT:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_HARMONICS_MASK,
+ ctrl->val);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ switch (ctrl->val) {
+ case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_ENABLE_MASK,
+ 0);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_GRID_MASK,
+ SI476X_PROP_PWR_GRID_50HZ);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_GRID_MASK,
+ SI476X_PROP_PWR_GRID_60HZ);
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ break;
+ case V4L2_CID_SI476X_RSSI_THRESHOLD:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_RSSI_THRESHOLD,
+ ctrl->val);
+ break;
+ case V4L2_CID_SI476X_SNR_THRESHOLD:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_SNR_THRESHOLD,
+ ctrl->val);
+ break;
+ case V4L2_CID_SI476X_MAX_TUNE_ERROR:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR,
+ ctrl->val);
+ break;
+ case V4L2_CID_RDS_RECEPTION:
+ /*
+ * It looks like RDS related properties are
+ * inaccesable when tuner is in AM mode, so cache the
+ * changes
+ */
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
+ regcache_cache_only(radio->core->regmap, true);
+
+ if (ctrl->val) {
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT,
+ radio->core->rds_fifo_depth);
+ if (retval < 0)
+ break;
+
+ if (radio->core->client->irq) {
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_SOURCE,
+ SI476X_RDSRECV);
+ if (retval < 0)
+ break;
+ }
+
+ /* Drain RDS FIFO before enabling RDS processing */
+ retval = si476x_core_cmd_fm_rds_status(radio->core,
+ false,
+ true,
+ true,
+ NULL);
+ if (retval < 0)
+ break;
+
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_FM_RDS_CONFIG,
+ SI476X_PROP_RDSEN_MASK,
+ SI476X_PROP_RDSEN);
+ } else {
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_FM_RDS_CONFIG,
+ SI476X_PROP_RDSEN_MASK,
+ !SI476X_PROP_RDSEN);
+ }
+
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
+ regcache_cache_only(radio->core->regmap, false);
+ break;
+ case V4L2_CID_TUNE_DEEMPHASIS:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_AUDIO_DEEMPHASIS,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_SI476X_DIVERSITY_MODE:
+ mode = si476x_phase_diversity_idx_to_mode(ctrl->val);
+
+ if (mode == radio->core->diversity_mode) {
+ retval = 0;
+ break;
+ }
+
+ if (si476x_core_is_in_am_receiver_mode(radio->core)) {
+ /*
+ * Diversity cannot be configured while tuner
+ * is in AM mode so save the changes and carry on.
+ */
+ radio->core->diversity_mode = mode;
+ retval = 0;
+ } else {
+ retval = radio->ops->phase_diversity(radio->core, mode);
+ if (!retval)
+ radio->core->diversity_mode = mode;
+ }
+ break;
+
+ default:
+ retval = -EINVAL;
+ break;
+ }
+
+ si476x_core_unlock(radio->core);
+
+ return retval;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int si476x_radio_g_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+ int err;
+ unsigned int value;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ si476x_core_lock(radio->core);
+ reg->size = 2;
+ err = regmap_read(radio->core->regmap,
+ (unsigned int)reg->reg, &value);
+ reg->val = value;
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+static int si476x_radio_s_register(struct file *file, void *fh,
+ const struct v4l2_dbg_register *reg)
+{
+
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ si476x_core_lock(radio->core);
+ err = regmap_write(radio->core->regmap,
+ (unsigned int)reg->reg,
+ (unsigned int)reg->val);
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+#endif
+
+static int si476x_radio_fops_open(struct file *file)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+ int err;
+
+ err = v4l2_fh_open(file);
+ if (err)
+ return err;
+
+ if (v4l2_fh_is_singular_file(file)) {
+ si476x_core_lock(radio->core);
+ err = si476x_core_set_power_state(radio->core,
+ SI476X_POWER_UP_FULL);
+ if (err < 0)
+ goto done;
+
+ err = si476x_radio_do_post_powerup_init(radio,
+ radio->core->power_up_parameters.func);
+ if (err < 0)
+ goto power_down;
+
+ err = si476x_radio_pretune(radio,
+ radio->core->power_up_parameters.func);
+ if (err < 0)
+ goto power_down;
+
+ si476x_core_unlock(radio->core);
+ /*Must be done after si476x_core_unlock to prevent a deadlock*/
+ v4l2_ctrl_handler_setup(&radio->ctrl_handler);
+ }
+
+ return err;
+
+power_down:
+ si476x_core_set_power_state(radio->core,
+ SI476X_POWER_DOWN);
+done:
+ si476x_core_unlock(radio->core);
+ v4l2_fh_release(file);
+
+ return err;
+}
+
+static int si476x_radio_fops_release(struct file *file)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (v4l2_fh_is_singular_file(file) &&
+ atomic_read(&radio->core->is_alive))
+ si476x_core_set_power_state(radio->core,
+ SI476X_POWER_DOWN);
+
+ err = v4l2_fh_release(file);
+
+ return err;
+}
+
+static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t rval;
+ size_t fifo_len;
+ unsigned int copied;
+
+ struct si476x_radio *radio = video_drvdata(file);
+
+ /* block if no new data available */
+ if (kfifo_is_empty(&radio->core->rds_fifo)) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ rval = wait_event_interruptible(radio->core->rds_read_queue,
+ (!kfifo_is_empty(&radio->core->rds_fifo) ||
+ !atomic_read(&radio->core->is_alive)));
+ if (rval < 0)
+ return -EINTR;
+
+ if (!atomic_read(&radio->core->is_alive))
+ return -ENODEV;
+ }
+
+ fifo_len = kfifo_len(&radio->core->rds_fifo);
+
+ if (kfifo_to_user(&radio->core->rds_fifo, buf,
+ min(fifo_len, count),
+ &copied) != 0) {
+ dev_warn(&radio->videodev.dev,
+ "Error during FIFO to userspace copy\n");
+ rval = -EIO;
+ } else {
+ rval = (ssize_t)copied;
+ }
+
+ return rval;
+}
+
+static unsigned int si476x_radio_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+ unsigned long req_events = poll_requested_events(pts);
+ unsigned int err = v4l2_ctrl_poll(file, pts);
+
+ if (req_events & (POLLIN | POLLRDNORM)) {
+ if (atomic_read(&radio->core->is_alive))
+ poll_wait(file, &radio->core->rds_read_queue, pts);
+
+ if (!atomic_read(&radio->core->is_alive))
+ err = POLLHUP;
+
+ if (!kfifo_is_empty(&radio->core->rds_fifo))
+ err = POLLIN | POLLRDNORM;
+ }
+
+ return err;
+}
+
+static const struct v4l2_file_operations si476x_fops = {
+ .owner = THIS_MODULE,
+ .read = si476x_radio_fops_read,
+ .poll = si476x_radio_fops_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .open = si476x_radio_fops_open,
+ .release = si476x_radio_fops_release,
+};
+
+
+static const struct v4l2_ioctl_ops si4761_ioctl_ops = {
+ .vidioc_querycap = si476x_radio_querycap,
+ .vidioc_g_tuner = si476x_radio_g_tuner,
+ .vidioc_s_tuner = si476x_radio_s_tuner,
+
+ .vidioc_g_frequency = si476x_radio_g_frequency,
+ .vidioc_s_frequency = si476x_radio_s_frequency,
+ .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek,
+ .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = si476x_radio_g_register,
+ .vidioc_s_register = si476x_radio_s_register,
+#endif
+};
+
+
+static const struct video_device si476x_viddev_template = {
+ .fops = &si476x_fops,
+ .name = DRIVER_NAME,
+ .release = video_device_release_empty,
+};
+
+
+
+static ssize_t si476x_radio_read_acf_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_acf_status_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->acf_status)
+ err = radio->ops->acf_status(radio->core, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_acf_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_acf_blob,
+};
+
+static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rds_blockcount_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rds_blckcnt(radio->core, true,
+ &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rds_blckcnt_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rds_blckcnt_blob,
+};
+
+static ssize_t si476x_radio_read_agc_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_agc_status_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->agc_status(radio->core, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_agc_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_agc_blob,
+};
+
+static ssize_t si476x_radio_read_rsq_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rsq_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rsq_blob,
+};
+
+static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = true,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rsq_primary_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rsq_primary_blob,
+};
+
+
+static int si476x_radio_init_debugfs(struct si476x_radio *radio)
+{
+ struct dentry *dentry;
+ int ret;
+
+ dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto exit;
+ }
+ radio->debugfs = dentry;
+
+ dentry = debugfs_create_file("acf", S_IRUGO,
+ radio->debugfs, radio, &radio_acf_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rds_blckcnt", S_IRUGO,
+ radio->debugfs, radio,
+ &radio_rds_blckcnt_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("agc", S_IRUGO,
+ radio->debugfs, radio, &radio_agc_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rsq", S_IRUGO,
+ radio->debugfs, radio, &radio_rsq_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rsq_primary", S_IRUGO,
+ radio->debugfs, radio,
+ &radio_rsq_primary_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ return 0;
+cleanup:
+ debugfs_remove_recursive(radio->debugfs);
+exit:
+ return ret;
+}
+
+
+static int si476x_radio_add_new_custom(struct si476x_radio *radio,
+ enum si476x_ctrl_idx idx)
+{
+ int rval;
+ struct v4l2_ctrl *ctrl;
+
+ ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler,
+ &si476x_ctrls[idx],
+ NULL);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval)
+ dev_err(radio->v4l2dev.dev,
+ "Could not initialize '%s' control %d\n",
+ si476x_ctrls[idx].name, rval);
+
+ return rval;
+}
+
+static int si476x_radio_probe(struct platform_device *pdev)
+{
+ int rval;
+ struct si476x_radio *radio;
+ struct v4l2_ctrl *ctrl;
+
+ static atomic_t instance = ATOMIC_INIT(0);
+
+ radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL);
+ if (!radio)
+ return -ENOMEM;
+
+ radio->core = i2c_mfd_cell_to_core(&pdev->dev);
+
+ v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance);
+
+ rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev);
+ if (rval) {
+ dev_err(&pdev->dev, "Cannot register v4l2_device.\n");
+ return rval;
+ }
+
+ memcpy(&radio->videodev, &si476x_viddev_template,
+ sizeof(struct video_device));
+
+ radio->videodev.v4l2_dev = &radio->v4l2dev;
+ radio->videodev.ioctl_ops = &si4761_ioctl_ops;
+
+ video_set_drvdata(&radio->videodev, radio);
+ platform_set_drvdata(pdev, radio);
+
+ set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags);
+
+ radio->v4l2dev.ctrl_handler = &radio->ctrl_handler;
+ v4l2_ctrl_handler_init(&radio->ctrl_handler,
+ 1 + ARRAY_SIZE(si476x_ctrls));
+
+ if (si476x_core_has_am(radio->core)) {
+ ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler,
+ &si476x_ctrl_ops,
+ V4L2_CID_POWER_LINE_FREQUENCY,
+ V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ 0, 0);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n",
+ rval);
+ goto exit;
+ }
+
+ rval = si476x_radio_add_new_custom(radio,
+ SI476X_IDX_HARMONICS_COUNT);
+ if (rval < 0)
+ goto exit;
+ }
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD);
+ if (rval < 0)
+ goto exit;
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD);
+ if (rval < 0)
+ goto exit;
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR);
+ if (rval < 0)
+ goto exit;
+
+ ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler,
+ &si476x_ctrl_ops,
+ V4L2_CID_TUNE_DEEMPHASIS,
+ V4L2_DEEMPHASIS_75_uS, 0, 0);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n",
+ rval);
+ goto exit;
+ }
+
+ ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops,
+ V4L2_CID_RDS_RECEPTION,
+ 0, 1, 1, 1);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n",
+ rval);
+ goto exit;
+ }
+
+ if (si476x_core_has_diversity(radio->core)) {
+ si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def =
+ si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode);
+ si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE);
+ if (rval < 0)
+ goto exit;
+
+ si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK);
+ if (rval < 0)
+ goto exit;
+ }
+
+ /* register video device */
+ rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1);
+ if (rval < 0) {
+ dev_err(&pdev->dev, "Could not register video device\n");
+ goto exit;
+ }
+
+ rval = si476x_radio_init_debugfs(radio);
+ if (rval < 0) {
+ dev_err(&pdev->dev, "Could not creat debugfs interface\n");
+ goto exit;
+ }
+
+ return 0;
+exit:
+ v4l2_ctrl_handler_free(radio->videodev.ctrl_handler);
+ return rval;
+}
+
+static int si476x_radio_remove(struct platform_device *pdev)
+{
+ struct si476x_radio *radio = platform_get_drvdata(pdev);
+
+ v4l2_ctrl_handler_free(radio->videodev.ctrl_handler);
+ video_unregister_device(&radio->videodev);
+ v4l2_device_unregister(&radio->v4l2dev);
+ debugfs_remove_recursive(radio->debugfs);
+
+ return 0;
+}
+
+MODULE_ALIAS("platform:si476x-radio");
+
+static struct platform_driver si476x_radio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = si476x_radio_probe,
+ .remove = si476x_radio_remove,
+};
+module_platform_driver(si476x_radio_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
+MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/radio/radio-tea5764.c b/drivers/media/radio/radio-tea5764.c
index 1978516af67..3ed1f5669f7 100644
--- a/drivers/media/radio/radio-tea5764.c
+++ b/drivers/media/radio/radio-tea5764.c
@@ -39,6 +39,9 @@
#include <linux/i2c.h> /* I2C */
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
#define DRIVER_VERSION "0.0.2"
@@ -57,8 +60,8 @@
/* 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_MIN 87500U
+#define FREQ_MAX 108000U
#define FREQ_MUL 16
/* TEA5764 registers */
@@ -138,8 +141,10 @@ static int radio_nr = -1;
static int use_xtal = RADIO_TEA5764_XTAL;
struct tea5764_device {
+ struct v4l2_device v4l2_dev;
+ struct v4l2_ctrl_handler ctrl_handler;
struct i2c_client *i2c_client;
- struct video_device *videodev;
+ struct video_device vdev;
struct tea5764_regs regs;
struct mutex mutex;
};
@@ -187,18 +192,6 @@ static int tea5764_i2c_write(struct tea5764_device *radio)
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;
@@ -291,23 +284,19 @@ static void tea5764_mute(struct tea5764_device *radio, int on)
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;
+ struct video_device *dev = &radio->vdev;
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;
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
@@ -320,8 +309,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
if (v->index > 0)
return -EINVAL;
- memset(v, 0, sizeof(*v));
- strcpy(v->name, "FM");
+ strlcpy(v->name, "FM", sizeof(v->name));
v->type = V4L2_TUNER_RADIO;
tea5764_i2c_read(radio);
v->rangelow = FREQ_MIN * FREQ_MUL;
@@ -339,7 +327,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
struct tea5764_device *radio = video_drvdata(file);
@@ -351,22 +339,26 @@ static int vidioc_s_tuner(struct file *file, void *priv,
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct tea5764_device *radio = video_drvdata(file);
+ unsigned freq = f->frequency;
if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
return -EINVAL;
- if (f->frequency == 0) {
+ if (freq == 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))
+ /* Yes, that's what is returned in this case. This
+ whole special case is non-compliant and should really
+ be replaced with something better, but changing this
+ might well break code that depends on this behavior.
+ So we keep it as-is. */
return -EINVAL;
+ }
+ freq = clamp(freq, FREQ_MIN * FREQ_MUL, FREQ_MAX * FREQ_MUL);
tea5764_power_up(radio);
- tea5764_tune(radio, (f->frequency * 125) / 2);
+ tea5764_tune(radio, (freq * 125) / 2);
return 0;
}
@@ -379,7 +371,6 @@ static int vidioc_g_frequency(struct file *file, void *priv,
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;
@@ -389,83 +380,29 @@ static int vidioc_g_frequency(struct file *file, void *priv,
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)
+static int tea5764_s_ctrl(struct v4l2_ctrl *ctrl)
{
- struct tea5764_device *radio = video_drvdata(file);
+ struct tea5764_device *radio =
+ container_of(ctrl->handler, struct tea5764_device, ctrl_handler);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
- tea5764_i2c_read(radio);
- ctrl->value = tea5764_is_muted(radio) ? 1 : 0;
+ tea5764_mute(radio, ctrl->val);
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;
-}
+static const struct v4l2_ctrl_ops tea5764_ctrl_ops = {
+ .s_ctrl = tea5764_s_ctrl,
+};
/* File system interface */
static const struct v4l2_file_operations tea5764_fops = {
.owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = v4l2_fh_release,
+ .poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
@@ -473,15 +410,11 @@ 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,
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/* V4L2 interface */
@@ -489,7 +422,7 @@ static struct video_device tea5764_radio_template = {
.name = "TEA5764 FM-Radio",
.fops = &tea5764_fops,
.ioctl_ops = &tea5764_ioctl_ops,
- .release = video_device_release,
+ .release = video_device_release_empty,
};
/* I2C probe: check if the device exists and register with v4l if it is */
@@ -497,6 +430,8 @@ static int tea5764_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tea5764_device *radio;
+ struct v4l2_device *v4l2_dev;
+ struct v4l2_ctrl_handler *hdl;
struct tea5764_regs *r;
int ret;
@@ -505,31 +440,45 @@ static int tea5764_i2c_probe(struct i2c_client *client,
if (!radio)
return -ENOMEM;
+ v4l2_dev = &radio->v4l2_dev;
+ ret = v4l2_device_register(&client->dev, v4l2_dev);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "could not register v4l2_device\n");
+ goto errfr;
+ }
+
+ hdl = &radio->ctrl_handler;
+ v4l2_ctrl_handler_init(hdl, 1);
+ v4l2_ctrl_new_std(hdl, &tea5764_ctrl_ops,
+ V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
+ v4l2_dev->ctrl_handler = hdl;
+ if (hdl->error) {
+ ret = hdl->error;
+ v4l2_err(v4l2_dev, "Could not register controls\n");
+ goto errunreg;
+ }
+
mutex_init(&radio->mutex);
radio->i2c_client = client;
ret = tea5764_i2c_read(radio);
if (ret)
- goto errfr;
+ goto errunreg;
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;
+ goto errunreg;
}
- radio->videodev = video_device_alloc();
- if (!(radio->videodev)) {
- ret = -ENOMEM;
- goto errfr;
- }
- memcpy(radio->videodev, &tea5764_radio_template,
- sizeof(tea5764_radio_template));
+ radio->vdev = tea5764_radio_template;
i2c_set_clientdata(client, radio);
- video_set_drvdata(radio->videodev, radio);
- radio->videodev->lock = &radio->mutex;
+ video_set_drvdata(&radio->vdev, radio);
+ radio->vdev.lock = &radio->mutex;
+ radio->vdev.v4l2_dev = v4l2_dev;
+ set_bit(V4L2_FL_USE_FH_PRIO, &radio->vdev.flags);
/* initialize and power off the chip */
tea5764_i2c_read(radio);
@@ -537,16 +486,17 @@ static int tea5764_i2c_probe(struct i2c_client *client,
tea5764_mute(radio, 1);
tea5764_power_down(radio);
- ret = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
+ ret = video_register_device(&radio->vdev, VFL_TYPE_RADIO, radio_nr);
if (ret < 0) {
PWARN("Could not register video device!");
- goto errrel;
+ goto errunreg;
}
PINFO("registered.");
return 0;
-errrel:
- video_device_release(radio->videodev);
+errunreg:
+ v4l2_ctrl_handler_free(hdl);
+ v4l2_device_unregister(v4l2_dev);
errfr:
kfree(radio);
return ret;
@@ -559,7 +509,9 @@ static int tea5764_i2c_remove(struct i2c_client *client)
PDEBUG("remove");
if (radio) {
tea5764_power_down(radio);
- video_unregister_device(radio->videodev);
+ video_unregister_device(&radio->vdev);
+ v4l2_ctrl_handler_free(&radio->ctrl_handler);
+ v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio);
}
return 0;
diff --git a/drivers/media/radio/radio-tea5777.c b/drivers/media/radio/radio-tea5777.c
index 4b5190d4fea..e2455970725 100644
--- a/drivers/media/radio/radio-tea5777.c
+++ b/drivers/media/radio/radio-tea5777.c
@@ -336,7 +336,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
}
static int vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ const struct v4l2_tuner *v)
{
struct radio_tea5777 *tea = video_drvdata(file);
u32 orig_audmode = tea->audmode;
@@ -344,10 +344,9 @@ static int vidioc_s_tuner(struct file *file, void *priv,
if (v->index)
return -EINVAL;
- if (v->audmode > V4L2_TUNER_MODE_STEREO)
- v->audmode = V4L2_TUNER_MODE_STEREO;
-
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);
@@ -368,7 +367,7 @@ static int vidioc_g_frequency(struct file *file, void *priv,
}
static int vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct radio_tea5777 *tea = video_drvdata(file);
diff --git a/drivers/media/radio/radio-timb.c b/drivers/media/radio/radio-timb.c
index b87effeb5dc..0817964d917 100644
--- a/drivers/media/radio/radio-timb.c
+++ b/drivers/media/radio/radio-timb.c
@@ -19,6 +19,8 @@
#include <linux/io.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
@@ -44,7 +46,8 @@ static int timbradio_vidioc_querycap(struct file *file, void *priv,
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;
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
return 0;
}
@@ -56,42 +59,14 @@ static int timbradio_vidioc_g_tuner(struct file *file, void *priv,
}
static int timbradio_vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *v)
+ 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,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
struct timbradio *tr = video_drvdata(file);
return v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f);
@@ -104,44 +79,22 @@ static int timbradio_vidioc_g_frequency(struct file *file, void *priv,
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
+ .vidioc_log_status = v4l2_ctrl_log_status,
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static const struct v4l2_file_operations timbradio_fops = {
.owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = v4l2_fh_release,
+ .poll = v4l2_ctrl_poll,
.unlocked_ioctl = video_ioctl2,
};
@@ -173,6 +126,7 @@ static int timbradio_probe(struct platform_device *pdev)
tr->video_dev.release = video_device_release_empty;
tr->video_dev.minor = -1;
tr->video_dev.lock = &tr->lock;
+ set_bit(V4L2_FL_USE_FH_PRIO, &tr->video_dev.flags);
strlcpy(tr->v4l2_dev.name, DRIVER_NAME, sizeof(tr->v4l2_dev.name));
err = v4l2_device_register(NULL, &tr->v4l2_dev);
@@ -181,6 +135,15 @@ static int timbradio_probe(struct platform_device *pdev)
tr->video_dev.v4l2_dev = &tr->v4l2_dev;
+ tr->sd_tuner = v4l2_i2c_new_subdev_board(&tr->v4l2_dev,
+ i2c_get_adapter(pdata->i2c_adapter), pdata->tuner, NULL);
+ tr->sd_dsp = v4l2_i2c_new_subdev_board(&tr->v4l2_dev,
+ i2c_get_adapter(pdata->i2c_adapter), pdata->dsp, NULL);
+ if (tr->sd_tuner == NULL || tr->sd_dsp == NULL)
+ goto err_video_req;
+
+ tr->v4l2_dev.ctrl_handler = tr->sd_dsp->ctrl_handler;
+
err = video_register_device(&tr->video_dev, VFL_TYPE_RADIO, -1);
if (err) {
dev_err(&pdev->dev, "Error reg video\n");
@@ -193,7 +156,6 @@ static int timbradio_probe(struct platform_device *pdev)
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);
@@ -206,10 +168,7 @@ 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;
}
diff --git a/drivers/media/radio/radio-wl1273.c b/drivers/media/radio/radio-wl1273.c
index c48be195bba..9cf6731fb81 100644
--- a/drivers/media/radio/radio-wl1273.c
+++ b/drivers/media/radio/radio-wl1273.c
@@ -375,7 +375,7 @@ static int wl1273_fm_set_tx_freq(struct wl1273_device *radio, unsigned int freq)
if (r)
return r;
- INIT_COMPLETION(radio->busy);
+ reinit_completion(&radio->busy);
/* wait for the FR IRQ */
r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(2000));
@@ -389,7 +389,7 @@ static int wl1273_fm_set_tx_freq(struct wl1273_device *radio, unsigned int freq)
if (r)
return r;
- INIT_COMPLETION(radio->busy);
+ reinit_completion(&radio->busy);
/* wait for the POWER_ENB IRQ */
r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(1000));
@@ -444,7 +444,7 @@ static int wl1273_fm_set_rx_freq(struct wl1273_device *radio, unsigned int freq)
goto err;
}
- INIT_COMPLETION(radio->busy);
+ reinit_completion(&radio->busy);
r = wait_for_completion_timeout(&radio->busy, msecs_to_jiffies(2000));
if (!r) {
@@ -805,7 +805,7 @@ static int wl1273_fm_set_seek(struct wl1273_device *radio,
if (level < SCHAR_MIN || level > SCHAR_MAX)
return -EINVAL;
- INIT_COMPLETION(radio->busy);
+ reinit_completion(&radio->busy);
dev_dbg(radio->dev, "%s: BUSY\n", __func__);
r = core->write(core, WL1273_INT_MASK_SET, radio->irq_flags);
@@ -847,7 +847,7 @@ static int wl1273_fm_set_seek(struct wl1273_device *radio,
if (r)
goto out;
- INIT_COMPLETION(radio->busy);
+ reinit_completion(&radio->busy);
dev_dbg(radio->dev, "%s: BUSY\n", __func__);
r = core->write(core, WL1273_TUNER_MODE_SET, TUNER_MODE_AUTO_SEEK);
@@ -1559,7 +1559,7 @@ out:
}
static int wl1273_fm_vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
+ const struct v4l2_tuner *tuner)
{
struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
struct wl1273_core *core = radio->core;
@@ -1640,7 +1640,7 @@ static int wl1273_fm_vidioc_g_frequency(struct file *file, void *priv,
}
static int wl1273_fm_vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *freq)
+ const struct v4l2_frequency *freq)
{
struct wl1273_device *radio = video_get_drvdata(video_devdata(file));
struct wl1273_core *core = radio->core;
@@ -1971,6 +1971,7 @@ static struct video_device wl1273_viddev_template = {
.ioctl_ops = &wl1273_ioctl_ops,
.name = WL1273_FM_DRIVER_NAME,
.release = wl1273_vdev_release,
+ .vfl_dir = VFL_DIR_TX,
};
static int wl1273_fm_radio_remove(struct platform_device *pdev)
@@ -2084,8 +2085,7 @@ static int wl1273_fm_radio_probe(struct platform_device *pdev)
}
/* V4L2 configuration */
- memcpy(&radio->videodev, &wl1273_viddev_template,
- sizeof(wl1273_viddev_template));
+ radio->videodev = wl1273_viddev_template;
radio->videodev.v4l2_dev = &radio->v4l2dev;
diff --git a/drivers/media/radio/saa7706h.c b/drivers/media/radio/saa7706h.c
index 06c06cc9ff2..ec805b09c60 100644
--- a/drivers/media/radio/saa7706h.c
+++ b/drivers/media/radio/saa7706h.c
@@ -25,7 +25,7 @@
#include <linux/i2c.h>
#include <linux/slab.h>
#include <media/v4l2-device.h>
-#include <media/v4l2-chip-ident.h>
+#include <media/v4l2-ctrls.h>
#define DRIVER_NAME "saa7706h"
@@ -127,6 +127,7 @@
struct saa7706h_state {
struct v4l2_subdev sd;
+ struct v4l2_ctrl_handler hdl;
unsigned muted;
};
@@ -317,51 +318,32 @@ static int saa7706h_mute(struct v4l2_subdev *sd)
return err;
}
-static int saa7706h_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
+static int saa7706h_s_ctrl(struct v4l2_ctrl *ctrl)
{
- 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);
+ struct saa7706h_state *state =
+ container_of(ctrl->handler, struct saa7706h_state, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_MUTE:
- ctrl->value = state->muted;
- return 0;
+ if (ctrl->val)
+ return saa7706h_mute(&state->sd);
+ return saa7706h_unmute(&state->sd);
}
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_ctrl_ops saa7706h_ctrl_ops = {
+ .s_ctrl = saa7706h_s_ctrl,
+};
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,
+ .g_ext_ctrls = v4l2_subdev_g_ext_ctrls,
+ .try_ext_ctrls = v4l2_subdev_try_ext_ctrls,
+ .s_ext_ctrls = v4l2_subdev_s_ext_ctrls,
+ .g_ctrl = v4l2_subdev_g_ctrl,
+ .s_ctrl = v4l2_subdev_s_ctrl,
+ .queryctrl = v4l2_subdev_queryctrl,
+ .querymenu = v4l2_subdev_querymenu,
};
static const struct v4l2_subdev_ops saa7706h_ops = {
@@ -393,13 +375,20 @@ static int saa7706h_probe(struct i2c_client *client,
sd = &state->sd;
v4l2_i2c_subdev_init(sd, client, &saa7706h_ops);
+ v4l2_ctrl_handler_init(&state->hdl, 4);
+ v4l2_ctrl_new_std(&state->hdl, &saa7706h_ctrl_ops,
+ V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
+ sd->ctrl_handler = &state->hdl;
+ err = state->hdl.error;
+ if (err)
+ goto err;
+
/* 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 */
@@ -411,6 +400,7 @@ static int saa7706h_probe(struct i2c_client *client,
err:
v4l2_device_unregister_subdev(sd);
+ v4l2_ctrl_handler_free(&state->hdl);
kfree(to_state(sd));
printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err);
@@ -421,9 +411,11 @@ err:
static int saa7706h_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct saa7706h_state *state = to_state(sd);
saa7706h_mute(sd);
v4l2_device_unregister_subdev(sd);
+ v4l2_ctrl_handler_free(&state->hdl);
kfree(to_state(sd));
return 0;
}
diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c
index 18989388ddc..0e750aef656 100644
--- a/drivers/media/radio/si470x/radio-si470x-common.c
+++ b/drivers/media/radio/si470x/radio-si470x-common.c
@@ -218,7 +218,7 @@ static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
goto done;
/* wait till tune operation has completed */
- INIT_COMPLETION(radio->completion);
+ reinit_completion(&radio->completion);
retval = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(tune_timeout));
if (!retval)
@@ -254,7 +254,7 @@ static unsigned int si470x_get_step(struct si470x_device *radio)
/* 2: 50 kHz */
default:
return 50 * 16;
- };
+ }
}
@@ -341,7 +341,7 @@ static int si470x_set_seek(struct si470x_device *radio,
return retval;
/* wait till tune operation has completed */
- INIT_COMPLETION(radio->completion);
+ reinit_completion(&radio->completion);
retval = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(seek_timeout));
if (!retval)
@@ -636,7 +636,7 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv,
* si470x_vidioc_s_tuner - set tuner attributes
*/
static int si470x_vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
+ const struct v4l2_tuner *tuner)
{
struct si470x_device *radio = video_drvdata(file);
@@ -678,7 +678,7 @@ static int si470x_vidioc_g_frequency(struct file *file, void *priv,
* si470x_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int si470x_vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *freq)
+ const struct v4l2_frequency *freq)
{
struct si470x_device *radio = video_drvdata(file);
int retval;
diff --git a/drivers/media/radio/si470x/radio-si470x-i2c.c b/drivers/media/radio/si470x/radio-si470x-i2c.c
index e5fc9acd0c4..2a497c80c77 100644
--- a/drivers/media/radio/si470x/radio-si470x-i2c.c
+++ b/drivers/media/radio/si470x/radio-si470x-i2c.c
@@ -463,7 +463,7 @@ static int si470x_i2c_remove(struct i2c_client *client)
}
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
/*
* si470x_i2c_suspend - suspend the device
*/
@@ -509,7 +509,7 @@ static struct i2c_driver si470x_i2c_driver = {
.driver = {
.name = "si470x",
.owner = THIS_MODULE,
-#ifdef CONFIG_PM
+#ifdef CONFIG_PM_SLEEP
.pm = &si470x_i2c_pm,
#endif
},
diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c
index 62f3edec39b..07ef40595ef 100644
--- a/drivers/media/radio/si470x/radio-si470x-usb.c
+++ b/drivers/media/radio/si470x/radio-si470x-usb.c
@@ -137,13 +137,13 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
/* interrupt out endpoint 2 every 1 millisecond */
#define UNUSED_REPORT 23
+#define MAX_REPORT_SIZE 64
+
/**************************************************************************
* Software/Hardware Versions from Scratch Page
**************************************************************************/
-#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
-#define RADIO_SW_VERSION 1
#define RADIO_HW_VERSION 1
@@ -210,7 +210,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
*/
static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
{
- unsigned char *report = (unsigned char *) buf;
+ unsigned char *report = buf;
int retval;
retval = usb_control_msg(radio->usbdev,
@@ -233,7 +233,7 @@ static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
*/
static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
{
- unsigned char *report = (unsigned char *) buf;
+ unsigned char *report = buf;
int retval;
retval = usb_control_msg(radio->usbdev,
@@ -256,15 +256,14 @@ static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
*/
int si470x_get_register(struct si470x_device *radio, int regnr)
{
- unsigned char buf[REGISTER_REPORT_SIZE];
int retval;
- buf[0] = REGISTER_REPORT(regnr);
+ radio->usb_buf[0] = REGISTER_REPORT(regnr);
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+ retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
if (retval >= 0)
- radio->registers[regnr] = get_unaligned_be16(&buf[1]);
+ radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]);
return (retval < 0) ? -EINVAL : 0;
}
@@ -275,13 +274,12 @@ int si470x_get_register(struct si470x_device *radio, int regnr)
*/
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]);
+ radio->usb_buf[0] = REGISTER_REPORT(regnr);
+ put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]);
- retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
+ retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
return (retval < 0) ? -EINVAL : 0;
}
@@ -297,18 +295,17 @@ int si470x_set_register(struct si470x_device *radio, int regnr)
*/
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;
+ radio->usb_buf[0] = ENTIRE_REPORT;
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+ retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE);
if (retval >= 0)
for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
radio->registers[regnr] = get_unaligned_be16(
- &buf[regnr * RADIO_REGISTER_SIZE + 1]);
+ &radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]);
return (retval < 0) ? -EINVAL : 0;
}
@@ -325,14 +322,13 @@ static int si470x_get_all_registers(struct si470x_device *radio)
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;
+ radio->usb_buf[0] = LED_REPORT;
+ radio->usb_buf[1] = LED_COMMAND;
+ radio->usb_buf[2] = led_state;
- retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
+ retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE);
return (retval < 0) ? -EINVAL : 0;
}
@@ -348,19 +344,18 @@ static int si470x_set_led_state(struct si470x_device *radio,
*/
static int si470x_get_scratch_page_versions(struct si470x_device *radio)
{
- unsigned char buf[SCRATCH_REPORT_SIZE];
int retval;
- buf[0] = SCRATCH_REPORT;
+ radio->usb_buf[0] = SCRATCH_REPORT;
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+ retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE);
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];
+ radio->software_version = radio->usb_buf[1];
+ radio->hardware_version = radio->usb_buf[2];
}
return (retval < 0) ? -EINVAL : 0;
@@ -511,6 +506,7 @@ static void si470x_usb_release(struct v4l2_device *v4l2_dev)
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->int_in_buffer);
kfree(radio->buffer);
+ kfree(radio->usb_buf);
kfree(radio);
}
@@ -595,6 +591,11 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
retval = -ENOMEM;
goto err_initial;
}
+ radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL);
+ if (radio->usb_buf == NULL) {
+ retval = -ENOMEM;
+ goto err_radio;
+ }
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
radio->band = 1; /* Default to 76 - 108 MHz */
@@ -614,7 +615,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
if (!radio->int_in_endpoint) {
dev_info(&intf->dev, "could not find interrupt in endpoint\n");
retval = -EIO;
- goto err_radio;
+ goto err_usbbuf;
}
int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
@@ -623,7 +624,7 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
if (!radio->int_in_buffer) {
dev_info(&intf->dev, "could not allocate int_in_buffer");
retval = -ENOMEM;
- goto err_radio;
+ goto err_usbbuf;
}
radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
@@ -634,6 +635,30 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
}
radio->v4l2_dev.release = si470x_usb_release;
+
+ /*
+ * The si470x SiLabs reference design uses the same USB IDs as
+ * 'Thanko's Raremono' si4734 based receiver. So check here which we
+ * have: attempt to read the device ID from the si470x: the lower 12
+ * bits should be 0x0242 for the si470x.
+ *
+ * We use this check to determine which device we are dealing with.
+ */
+ if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) {
+ retval = usb_control_msg(radio->usbdev,
+ usb_rcvctrlpipe(radio->usbdev, 0),
+ HID_REQ_GET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 1, 2,
+ radio->usb_buf, 3, 500);
+ if (retval != 3 ||
+ (get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) {
+ dev_info(&intf->dev, "this is not a si470x device.\n");
+ retval = -ENODEV;
+ goto err_urb;
+ }
+ }
+
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
@@ -682,15 +707,6 @@ static int si470x_usb_driver_probe(struct usb_interface *intf,
}
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 "
@@ -754,6 +770,8 @@ err_urb:
usb_free_urb(radio->int_in_urb);
err_intbuffer:
kfree(radio->int_in_buffer);
+err_usbbuf:
+ kfree(radio->usb_buf);
err_radio:
kfree(radio);
err_initial:
diff --git a/drivers/media/radio/si470x/radio-si470x.h b/drivers/media/radio/si470x/radio-si470x.h
index 2f089b4252d..4b7660470e2 100644
--- a/drivers/media/radio/si470x/radio-si470x.h
+++ b/drivers/media/radio/si470x/radio-si470x.h
@@ -163,10 +163,11 @@ struct si470x_device {
struct completion completion;
bool status_rssi_auto_update; /* Does RSSI get updated automatic? */
-#if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
+#if IS_ENABLED(CONFIG_USB_SI470X)
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
+ char *usb_buf;
/* Interrupt endpoint handling */
char *int_in_buffer;
@@ -179,7 +180,7 @@ struct si470x_device {
unsigned char hardware_version;
#endif
-#if defined(CONFIG_I2C_SI470X) || defined(CONFIG_I2C_SI470X_MODULE)
+#if IS_ENABLED(CONFIG_I2C_SI470X)
struct i2c_client *client;
#endif
};
diff --git a/drivers/media/radio/si4713/Kconfig b/drivers/media/radio/si4713/Kconfig
new file mode 100644
index 00000000000..9c8b887cff7
--- /dev/null
+++ b/drivers/media/radio/si4713/Kconfig
@@ -0,0 +1,40 @@
+config USB_SI4713
+ tristate "Silicon Labs Si4713 FM Radio Transmitter support with USB"
+ depends on USB && I2C && RADIO_SI4713
+ select I2C_SI4713
+ ---help---
+ This is a driver for USB devices with the Silicon Labs SI4713
+ chip. Currently these devices are known to work.
+ - 10c4:8244: Silicon Labs FM Transmitter USB device.
+
+ 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-si4713.
+
+config PLATFORM_SI4713
+ tristate "Silicon Labs Si4713 FM Radio Transmitter support with I2C"
+ depends on I2C && RADIO_SI4713
+ select I2C_SI4713
+ ---help---
+ This is a driver for I2C devices with the Silicon Labs SI4713
+ 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-platform-si4713.
+
+config I2C_SI4713
+ tristate "Silicon Labs Si4713 FM Radio Transmitter support"
+ depends on I2C && RADIO_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 si4713.
diff --git a/drivers/media/radio/si4713/Makefile b/drivers/media/radio/si4713/Makefile
new file mode 100644
index 00000000000..ddaaf925e88
--- /dev/null
+++ b/drivers/media/radio/si4713/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for radios with Silicon Labs Si4713 FM Radio Transmitters
+#
+
+obj-$(CONFIG_I2C_SI4713) += si4713.o
+obj-$(CONFIG_USB_SI4713) += radio-usb-si4713.o
+obj-$(CONFIG_PLATFORM_SI4713) += radio-platform-si4713.o
diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/si4713/radio-platform-si4713.c
index a082e400ed0..ba4cfc94686 100644
--- a/drivers/media/radio/radio-si4713.c
+++ b/drivers/media/radio/si4713/radio-platform-si4713.c
@@ -31,6 +31,9 @@
#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 */
@@ -39,54 +42,30 @@ module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr,
"Minor number for radio device (-1 ==> auto assign)");
-MODULE_LICENSE("GPL");
+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 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 */
-static int radio_si4713_fill_audout(struct v4l2_audioout *vao)
-{
- /* TODO: check presence of audio output */
- strlcpy(vao->name, "FM Modulator Audio Out", 32);
-
- return 0;
-}
-
-static int radio_si4713_enumaudout(struct file *file, void *priv,
- struct v4l2_audioout *vao)
-{
- return radio_si4713_fill_audout(vao);
-}
-
-static int radio_si4713_g_audout(struct file *file, void *priv,
- struct v4l2_audioout *vao)
-{
- int rval = radio_si4713_fill_audout(vao);
-
- vao->index = 0;
-
- return rval;
-}
-
-static int radio_si4713_s_audout(struct file *file, void *priv,
- const struct v4l2_audioout *vao)
-{
- return vao->index ? -EINVAL : 0;
-}
/* radio_si4713_querycap - query device capabilities */
static int radio_si4713_querycap(struct file *file, void *priv,
@@ -94,67 +73,15 @@ static int radio_si4713_querycap(struct file *file, void *priv,
{
strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver));
strlcpy(capability->card, "Silicon Labs Si4713 Modulator",
- sizeof(capability->card));
- capability->capabilities = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;
+ 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;
}
-/* radio_si4713_queryctrl - enumerate control items */
-static int radio_si4713_queryctrl(struct file *file, void *priv,
- struct v4l2_queryctrl *qc)
-{
- /* Must be sorted from low to high control ID! */
- static const u32 user_ctrls[] = {
- V4L2_CID_USER_CLASS,
- V4L2_CID_AUDIO_MUTE,
- 0
- };
-
- /* Must be sorted from low to high control ID! */
- static const u32 fmtx_ctrls[] = {
- V4L2_CID_FM_TX_CLASS,
- V4L2_CID_RDS_TX_DEVIATION,
- V4L2_CID_RDS_TX_PI,
- V4L2_CID_RDS_TX_PTY,
- V4L2_CID_RDS_TX_PS_NAME,
- V4L2_CID_RDS_TX_RADIO_TEXT,
- V4L2_CID_AUDIO_LIMITER_ENABLED,
- V4L2_CID_AUDIO_LIMITER_RELEASE_TIME,
- V4L2_CID_AUDIO_LIMITER_DEVIATION,
- V4L2_CID_AUDIO_COMPRESSION_ENABLED,
- V4L2_CID_AUDIO_COMPRESSION_GAIN,
- V4L2_CID_AUDIO_COMPRESSION_THRESHOLD,
- V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME,
- V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME,
- V4L2_CID_PILOT_TONE_ENABLED,
- V4L2_CID_PILOT_TONE_DEVIATION,
- V4L2_CID_PILOT_TONE_FREQUENCY,
- V4L2_CID_TUNE_PREEMPHASIS,
- V4L2_CID_TUNE_POWER_LEVEL,
- V4L2_CID_TUNE_ANTENNA_CAPACITOR,
- 0
- };
- static const u32 *ctrl_classes[] = {
- user_ctrls,
- fmtx_ctrls,
- NULL
- };
- struct radio_si4713_device *rsdev;
-
- rsdev = video_get_drvdata(video_devdata(file));
-
- qc->id = v4l2_ctrl_next(ctrl_classes, qc->id);
- if (qc->id == 0)
- return -EINVAL;
-
- if (qc->id == V4L2_CID_USER_CLASS || qc->id == V4L2_CID_FM_TX_CLASS)
- return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0);
-
- return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core,
- queryctrl, qc);
-}
-
/*
* v4l2 ioctl call backs.
* we are just a wrapper for v4l2_sub_devs.
@@ -164,83 +91,50 @@ 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_ext_ctrls(struct file *file, void *p,
- struct v4l2_ext_controls *vecs)
-{
- return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
- g_ext_ctrls, vecs);
-}
-
-static int radio_si4713_s_ext_ctrls(struct file *file, void *p,
- struct v4l2_ext_controls *vecs)
-{
- return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
- s_ext_ctrls, vecs);
-}
-
-static int radio_si4713_g_ctrl(struct file *file, void *p,
- struct v4l2_control *vc)
-{
- return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
- g_ctrl, vc);
-}
-
-static int radio_si4713_s_ctrl(struct file *file, void *p,
- struct v4l2_control *vc)
-{
- return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
- s_ctrl, vc);
-}
-
static int radio_si4713_g_modulator(struct file *file, void *p,
- struct v4l2_modulator *vm)
+ struct v4l2_modulator *vm)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
- g_modulator, vm);
+ g_modulator, vm);
}
static int radio_si4713_s_modulator(struct file *file, void *p,
- const struct v4l2_modulator *vm)
+ const struct v4l2_modulator *vm)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
- s_modulator, vm);
+ s_modulator, vm);
}
static int radio_si4713_g_frequency(struct file *file, void *p,
- struct v4l2_frequency *vf)
+ struct v4l2_frequency *vf)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
- g_frequency, vf);
+ g_frequency, vf);
}
static int radio_si4713_s_frequency(struct file *file, void *p,
- struct v4l2_frequency *vf)
+ const struct v4l2_frequency *vf)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
- s_frequency, vf);
+ s_frequency, vf);
}
static long radio_si4713_default(struct file *file, void *p,
- bool valid_prio, int cmd, void *arg)
+ bool valid_prio, unsigned int cmd, void *arg)
{
return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
- ioctl, cmd, arg);
+ ioctl, cmd, arg);
}
static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
- .vidioc_enumaudout = radio_si4713_enumaudout,
- .vidioc_g_audout = radio_si4713_g_audout,
- .vidioc_s_audout = radio_si4713_s_audout,
.vidioc_querycap = radio_si4713_querycap,
- .vidioc_queryctrl = radio_si4713_queryctrl,
- .vidioc_g_ext_ctrls = radio_si4713_g_ext_ctrls,
- .vidioc_s_ext_ctrls = radio_si4713_s_ext_ctrls,
- .vidioc_g_ctrl = radio_si4713_g_ctrl,
- .vidioc_s_ctrl = radio_si4713_s_ctrl,
.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,
};
@@ -248,8 +142,9 @@ static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
static struct video_device radio_si4713_vdev_template = {
.fops = &radio_si4713_fops,
.name = "radio-si4713",
- .release = video_device_release,
+ .release = video_device_release_empty,
.ioctl_ops = &radio_si4713_ioctl_ops,
+ .vfl_dir = VFL_DIR_TX,
};
/* Platform driver interface */
@@ -274,6 +169,7 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev)
rval = -ENOMEM;
goto exit;
}
+ mutex_init(&rsdev->lock);
rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);
if (rval) {
@@ -284,40 +180,35 @@ static int radio_si4713_pdriver_probe(struct platform_device *pdev)
adapter = i2c_get_adapter(pdata->i2c_bus);
if (!adapter) {
dev_err(&pdev->dev, "Cannot get i2c adapter %d\n",
- pdata->i2c_bus);
+ pdata->i2c_bus);
rval = -ENODEV;
goto unregister_v4l2_dev;
}
sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter,
- pdata->subdev_board_info, NULL);
+ pdata->subdev_board_info, NULL);
if (!sd) {
dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");
rval = -ENODEV;
goto put_adapter;
}
- rsdev->radio_dev = video_device_alloc();
- if (!rsdev->radio_dev) {
- dev_err(&pdev->dev, "Failed to alloc video device.\n");
- rval = -ENOMEM;
- goto put_adapter;
- }
-
- memcpy(rsdev->radio_dev, &radio_si4713_vdev_template,
- sizeof(radio_si4713_vdev_template));
- video_set_drvdata(rsdev->radio_dev, rsdev);
- if (video_register_device(rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) {
+ 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 free_vdev;
+ goto put_adapter;
}
dev_info(&pdev->dev, "New device successfully probed\n");
goto exit;
-free_vdev:
- video_device_release(rsdev->radio_dev);
put_adapter:
i2c_put_adapter(adapter);
unregister_v4l2_dev:
@@ -327,17 +218,16 @@ exit:
}
/* radio_si4713_pdriver_remove - remove the device */
-static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev)
+static int radio_si4713_pdriver_remove(struct platform_device *pdev)
{
struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
- struct radio_si4713_device *rsdev = container_of(v4l2_dev,
- struct radio_si4713_device,
- v4l2_dev);
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;
- video_unregister_device(rsdev->radio_dev);
+ 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);
@@ -347,9 +237,10 @@ static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev)
static struct platform_driver radio_si4713_pdriver = {
.driver = {
.name = "radio-si4713",
+ .owner = THIS_MODULE,
},
.probe = radio_si4713_pdriver_probe,
- .remove = __exit_p(radio_si4713_pdriver_remove),
+ .remove = radio_si4713_pdriver_remove,
};
module_platform_driver(radio_si4713_pdriver);
diff --git a/drivers/media/radio/si4713/radio-usb-si4713.c b/drivers/media/radio/si4713/radio-usb-si4713.c
new file mode 100644
index 00000000000..86502b2786d
--- /dev/null
+++ b/drivers/media/radio/si4713/radio-usb-si4713.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2013 Cisco Systems, Inc. and/or its affiliates.
+ * All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+/* V4l includes */
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/si4713.h>
+
+#include "si4713.h"
+
+/* driver and module definitions */
+MODULE_AUTHOR("Dinesh Ram <dinesh.ram@cern.ch>");
+MODULE_DESCRIPTION("Si4713 FM Transmitter USB driver");
+MODULE_LICENSE("GPL v2");
+
+/* The Device announces itself as Cygnal Integrated Products, Inc. */
+#define USB_SI4713_VENDOR 0x10c4
+#define USB_SI4713_PRODUCT 0x8244
+
+#define BUFFER_LENGTH 64
+#define USB_TIMEOUT 1000
+#define USB_RESP_TIMEOUT 50000
+
+/* USB Device ID List */
+static struct usb_device_id usb_si4713_usb_device_table[] = {
+ {USB_DEVICE_AND_INTERFACE_INFO(USB_SI4713_VENDOR, USB_SI4713_PRODUCT,
+ USB_CLASS_HID, 0, 0) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, usb_si4713_usb_device_table);
+
+struct si4713_usb_device {
+ struct usb_device *usbdev;
+ struct usb_interface *intf;
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev *v4l2_subdev;
+ struct mutex lock;
+ struct i2c_adapter i2c_adapter;
+
+ u8 *buffer;
+};
+
+static inline struct si4713_usb_device *to_si4713_dev(struct v4l2_device *v4l2_dev)
+{
+ return container_of(v4l2_dev, struct si4713_usb_device, v4l2_dev);
+}
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ struct si4713_usb_device *radio = video_drvdata(file);
+
+ strlcpy(v->driver, "radio-usb-si4713", sizeof(v->driver));
+ strlcpy(v->card, "Si4713 FM Transmitter", sizeof(v->card));
+ usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info));
+ v->device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;
+ v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int vidioc_g_modulator(struct file *file, void *priv,
+ struct v4l2_modulator *vm)
+{
+ struct si4713_usb_device *radio = video_drvdata(file);
+
+ return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_modulator, vm);
+}
+
+static int vidioc_s_modulator(struct file *file, void *priv,
+ const struct v4l2_modulator *vm)
+{
+ struct si4713_usb_device *radio = video_drvdata(file);
+
+ return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_modulator, vm);
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+ const struct v4l2_frequency *vf)
+{
+ struct si4713_usb_device *radio = video_drvdata(file);
+
+ return v4l2_subdev_call(radio->v4l2_subdev, tuner, s_frequency, vf);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *vf)
+{
+ struct si4713_usb_device *radio = video_drvdata(file);
+
+ return v4l2_subdev_call(radio->v4l2_subdev, tuner, g_frequency, vf);
+}
+
+static const struct v4l2_ioctl_ops usb_si4713_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,
+};
+
+/* File system interface */
+static const struct v4l2_file_operations usb_si4713_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = v4l2_fh_release,
+ .poll = v4l2_ctrl_poll,
+ .unlocked_ioctl = video_ioctl2,
+};
+
+static void usb_si4713_video_device_release(struct v4l2_device *v4l2_dev)
+{
+ struct si4713_usb_device *radio = to_si4713_dev(v4l2_dev);
+ struct i2c_adapter *adapter = &radio->i2c_adapter;
+
+ i2c_del_adapter(adapter);
+ v4l2_device_unregister(&radio->v4l2_dev);
+ kfree(radio->buffer);
+ kfree(radio);
+}
+
+/*
+ * This command sequence emulates the behaviour of the Windows driver.
+ * The structure of these commands was determined by sniffing the
+ * usb traffic of the device during startup.
+ * Most likely, these commands make some queries to the device.
+ * Commands are sent to enquire parameters like the bus mode,
+ * component revision, boot mode, the device serial number etc.
+ *
+ * These commands are necessary to be sent in this order during startup.
+ * The device fails to powerup if these commands are not sent.
+ *
+ * The complete list of startup commands is given in the start_seq table below.
+ */
+static int si4713_send_startup_command(struct si4713_usb_device *radio)
+{
+ unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1;
+ u8 *buffer = radio->buffer;
+ int retval;
+
+ /* send the command */
+ retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
+ 0x09, 0x21, 0x033f, 0, radio->buffer,
+ BUFFER_LENGTH, USB_TIMEOUT);
+ if (retval < 0)
+ return retval;
+
+ for (;;) {
+ /* receive the response */
+ retval = usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
+ 0x01, 0xa1, 0x033f, 0, radio->buffer,
+ BUFFER_LENGTH, USB_TIMEOUT);
+ if (retval < 0)
+ return retval;
+ if (!radio->buffer[1]) {
+ /* USB traffic sniffing showed that some commands require
+ * additional checks. */
+ switch (buffer[1]) {
+ case 0x32:
+ if (radio->buffer[2] == 0)
+ return 0;
+ break;
+ case 0x14:
+ case 0x12:
+ if (radio->buffer[2] & SI4713_CTS)
+ return 0;
+ break;
+ case 0x06:
+ if ((radio->buffer[2] & SI4713_CTS) && radio->buffer[9] == 0x08)
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+ }
+ if (time_is_before_jiffies(until_jiffies))
+ return -EIO;
+ msleep(3);
+ }
+
+ return retval;
+}
+
+struct si4713_start_seq_table {
+ int len;
+ u8 payload[8];
+};
+
+/*
+ * Some of the startup commands that could be recognized are :
+ * (0x03): Get serial number of the board (Response : CB000-00-00)
+ * (0x06, 0x03, 0x03, 0x08, 0x01, 0x0f) : Get Component revision
+ */
+static const struct si4713_start_seq_table start_seq[] = {
+
+ { 1, { 0x03 } },
+ { 2, { 0x32, 0x7f } },
+ { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } },
+ { 2, { 0x14, 0x02 } },
+ { 2, { 0x09, 0x90 } },
+ { 3, { 0x08, 0x90, 0xfa } },
+ { 2, { 0x36, 0x01 } },
+ { 2, { 0x05, 0x03 } },
+ { 7, { 0x06, 0x00, 0x06, 0x0e, 0x01, 0x0f, 0x05 } },
+ { 1, { 0x12 } },
+ /* Commands that are sent after pressing the 'Initialize'
+ button in the windows application */
+ { 1, { 0x03 } },
+ { 1, { 0x01 } },
+ { 2, { 0x09, 0x90 } },
+ { 3, { 0x08, 0x90, 0xfa } },
+ { 1, { 0x34 } },
+ { 2, { 0x35, 0x01 } },
+ { 2, { 0x36, 0x01 } },
+ { 2, { 0x30, 0x09 } },
+ { 4, { 0x30, 0x06, 0x00, 0xe2 } },
+ { 3, { 0x31, 0x01, 0x30 } },
+ { 3, { 0x31, 0x04, 0x09 } },
+ { 2, { 0x05, 0x02 } },
+ { 6, { 0x06, 0x03, 0x03, 0x08, 0x01, 0x0f } },
+};
+
+static int si4713_start_seq(struct si4713_usb_device *radio)
+{
+ int retval = 0;
+ int i;
+
+ radio->buffer[0] = 0x3f;
+
+ for (i = 0; i < ARRAY_SIZE(start_seq); i++) {
+ int len = start_seq[i].len;
+ const u8 *payload = start_seq[i].payload;
+
+ memcpy(radio->buffer + 1, payload, len);
+ memset(radio->buffer + len + 1, 0, BUFFER_LENGTH - 1 - len);
+ retval = si4713_send_startup_command(radio);
+ }
+
+ return retval;
+}
+
+static struct i2c_board_info si4713_board_info = {
+ I2C_BOARD_INFO("si4713", SI4713_I2C_ADDR_BUSEN_HIGH),
+};
+
+struct si4713_command_table {
+ int command_id;
+ u8 payload[8];
+};
+
+/*
+ * Structure of a command :
+ * Byte 1 : 0x3f (always)
+ * Byte 2 : 0x06 (send a command)
+ * Byte 3 : Unknown
+ * Byte 4 : Number of arguments + 1 (for the command byte)
+ * Byte 5 : Number of response bytes
+ */
+static struct si4713_command_table command_table[] = {
+
+ { SI4713_CMD_POWER_UP, { 0x00, SI4713_PWUP_NARGS + 1, SI4713_PWUP_NRESP} },
+ { SI4713_CMD_GET_REV, { 0x03, 0x01, SI4713_GETREV_NRESP } },
+ { SI4713_CMD_POWER_DOWN, { 0x00, 0x01, SI4713_PWDN_NRESP} },
+ { SI4713_CMD_SET_PROPERTY, { 0x00, SI4713_SET_PROP_NARGS + 1, SI4713_SET_PROP_NRESP } },
+ { SI4713_CMD_GET_PROPERTY, { 0x00, SI4713_GET_PROP_NARGS + 1, SI4713_GET_PROP_NRESP } },
+ { SI4713_CMD_TX_TUNE_FREQ, { 0x03, SI4713_TXFREQ_NARGS + 1, SI4713_TXFREQ_NRESP } },
+ { SI4713_CMD_TX_TUNE_POWER, { 0x03, SI4713_TXPWR_NARGS + 1, SI4713_TXPWR_NRESP } },
+ { SI4713_CMD_TX_TUNE_MEASURE, { 0x03, SI4713_TXMEA_NARGS + 1, SI4713_TXMEA_NRESP } },
+ { SI4713_CMD_TX_TUNE_STATUS, { 0x00, SI4713_TXSTATUS_NARGS + 1, SI4713_TXSTATUS_NRESP } },
+ { SI4713_CMD_TX_ASQ_STATUS, { 0x03, SI4713_ASQSTATUS_NARGS + 1, SI4713_ASQSTATUS_NRESP } },
+ { SI4713_CMD_GET_INT_STATUS, { 0x03, 0x01, SI4713_GET_STATUS_NRESP } },
+ { SI4713_CMD_TX_RDS_BUFF, { 0x03, SI4713_RDSBUFF_NARGS + 1, SI4713_RDSBUFF_NRESP } },
+ { SI4713_CMD_TX_RDS_PS, { 0x00, SI4713_RDSPS_NARGS + 1, SI4713_RDSPS_NRESP } },
+};
+
+static int send_command(struct si4713_usb_device *radio, u8 *payload, char *data, int len)
+{
+ int retval;
+
+ radio->buffer[0] = 0x3f;
+ radio->buffer[1] = 0x06;
+
+ memcpy(radio->buffer + 2, payload, 3);
+ memcpy(radio->buffer + 5, data, len);
+ memset(radio->buffer + 5 + len, 0, BUFFER_LENGTH - 5 - len);
+
+ /* send the command */
+ retval = usb_control_msg(radio->usbdev, usb_sndctrlpipe(radio->usbdev, 0),
+ 0x09, 0x21, 0x033f, 0, radio->buffer,
+ BUFFER_LENGTH, USB_TIMEOUT);
+
+ return retval < 0 ? retval : 0;
+}
+
+static int si4713_i2c_read(struct si4713_usb_device *radio, char *data, int len)
+{
+ unsigned long until_jiffies = jiffies + usecs_to_jiffies(USB_RESP_TIMEOUT) + 1;
+ int retval;
+
+ /* receive the response */
+ for (;;) {
+ retval = usb_control_msg(radio->usbdev,
+ usb_rcvctrlpipe(radio->usbdev, 0),
+ 0x01, 0xa1, 0x033f, 0, radio->buffer,
+ BUFFER_LENGTH, USB_TIMEOUT);
+ if (retval < 0)
+ return retval;
+
+ /*
+ * Check that we get a valid reply back (buffer[1] == 0) and
+ * that CTS is set before returning, otherwise we wait and try
+ * again. The i2c driver also does the CTS check, but the timeouts
+ * used there are much too small for this USB driver, so we wait
+ * for it here.
+ */
+ if (radio->buffer[1] == 0 && (radio->buffer[2] & SI4713_CTS)) {
+ memcpy(data, radio->buffer + 2, len);
+ return 0;
+ }
+ if (time_is_before_jiffies(until_jiffies)) {
+ /* Zero the status value, ensuring CTS isn't set */
+ data[0] = 0;
+ return 0;
+ }
+ msleep(3);
+ }
+}
+
+static int si4713_i2c_write(struct si4713_usb_device *radio, char *data, int len)
+{
+ int retval = -EINVAL;
+ int i;
+
+ if (len > BUFFER_LENGTH - 5)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(command_table); i++) {
+ if (data[0] == command_table[i].command_id)
+ retval = send_command(radio, command_table[i].payload,
+ data, len);
+ }
+
+ return retval < 0 ? retval : 0;
+}
+
+static int si4713_transfer(struct i2c_adapter *i2c_adapter,
+ struct i2c_msg *msgs, int num)
+{
+ struct si4713_usb_device *radio = i2c_get_adapdata(i2c_adapter);
+ int retval = -EINVAL;
+ int i;
+
+ if (num <= 0)
+ return 0;
+
+ for (i = 0; i < num; i++) {
+ if (msgs[i].flags & I2C_M_RD)
+ retval = si4713_i2c_read(radio, msgs[i].buf, msgs[i].len);
+ else
+ retval = si4713_i2c_write(radio, msgs[i].buf, msgs[i].len);
+ if (retval)
+ break;
+ }
+
+ return retval ? retval : num;
+}
+
+static u32 si4713_functionality(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm si4713_algo = {
+ .master_xfer = si4713_transfer,
+ .functionality = si4713_functionality,
+};
+
+/* This name value shows up in the sysfs filename associated
+ with this I2C adapter */
+static struct i2c_adapter si4713_i2c_adapter_template = {
+ .name = "si4713-i2c",
+ .owner = THIS_MODULE,
+ .algo = &si4713_algo,
+};
+
+static int si4713_register_i2c_adapter(struct si4713_usb_device *radio)
+{
+ radio->i2c_adapter = si4713_i2c_adapter_template;
+ /* set up sysfs linkage to our parent device */
+ radio->i2c_adapter.dev.parent = &radio->usbdev->dev;
+ i2c_set_adapdata(&radio->i2c_adapter, radio);
+
+ return i2c_add_adapter(&radio->i2c_adapter);
+}
+
+/* check if the device is present and register with v4l and usb if it is */
+static int usb_si4713_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct si4713_usb_device *radio;
+ struct i2c_adapter *adapter;
+ struct v4l2_subdev *sd;
+ int retval = -ENOMEM;
+
+ dev_info(&intf->dev, "Si4713 development board discovered: (%04X:%04X)\n",
+ id->idVendor, id->idProduct);
+
+ /* Initialize local device structure */
+ radio = kzalloc(sizeof(struct si4713_usb_device), GFP_KERNEL);
+ if (radio)
+ radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
+
+ if (!radio || !radio->buffer) {
+ dev_err(&intf->dev, "kmalloc for si4713_usb_device failed\n");
+ kfree(radio);
+ return -ENOMEM;
+ }
+
+ mutex_init(&radio->lock);
+
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->intf = intf;
+ usb_set_intfdata(intf, &radio->v4l2_dev);
+
+ retval = si4713_start_seq(radio);
+ if (retval < 0)
+ 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;
+ }
+
+ retval = si4713_register_i2c_adapter(radio);
+ if (retval < 0) {
+ dev_err(&intf->dev, "could not register i2c device\n");
+ goto err_i2cdev;
+ }
+
+ adapter = &radio->i2c_adapter;
+ sd = v4l2_i2c_new_subdev_board(&radio->v4l2_dev, adapter,
+ &si4713_board_info, NULL);
+ radio->v4l2_subdev = sd;
+ if (!sd) {
+ dev_err(&intf->dev, "cannot get v4l2 subdevice\n");
+ retval = -ENODEV;
+ goto del_adapter;
+ }
+
+ radio->vdev.ctrl_handler = sd->ctrl_handler;
+ radio->v4l2_dev.release = usb_si4713_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_si4713_fops;
+ radio->vdev.ioctl_ops = &usb_si4713_ioctl_ops;
+ radio->vdev.lock = &radio->lock;
+ radio->vdev.release = video_device_release_empty;
+ radio->vdev.vfl_dir = VFL_DIR_TX;
+
+ 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 del_adapter;
+ }
+
+ dev_info(&intf->dev, "V4L2 device registered as %s\n",
+ video_device_node_name(&radio->vdev));
+
+ return 0;
+
+del_adapter:
+ i2c_del_adapter(adapter);
+err_i2cdev:
+ v4l2_device_unregister(&radio->v4l2_dev);
+err_v4l2:
+ kfree(radio->buffer);
+ kfree(radio);
+ return retval;
+}
+
+static void usb_si4713_disconnect(struct usb_interface *intf)
+{
+ struct si4713_usb_device *radio = to_si4713_dev(usb_get_intfdata(intf));
+
+ dev_info(&intf->dev, "Si4713 development board now disconnected\n");
+
+ 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);
+}
+
+/* USB subsystem interface */
+static struct usb_driver usb_si4713_driver = {
+ .name = "radio-usb-si4713",
+ .probe = usb_si4713_probe,
+ .disconnect = usb_si4713_disconnect,
+ .id_table = usb_si4713_usb_device_table,
+};
+
+module_usb_driver(usb_si4713_driver);
diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713/si4713.c
index bd61b3bd0ca..07d5153811e 100644
--- a/drivers/media/radio/si4713-i2c.c
+++ b/drivers/media/radio/si4713/si4713.c
@@ -21,20 +21,18 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/gpio.h>
-#include <linux/regulator/consumer.h>
#include <linux/module.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
-#include "si4713-i2c.h"
+#include "si4713.h"
/* module parameters */
static int debug;
@@ -46,25 +44,18 @@ MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
MODULE_DESCRIPTION("I2C driver for Si4713 FM Radio Transmitter");
MODULE_VERSION("0.0.1");
-static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = {
- "vio",
- "vdd",
-};
-
#define DEFAULT_RDS_PI 0x00
#define DEFAULT_RDS_PTY 0x00
-#define DEFAULT_RDS_PS_NAME ""
-#define DEFAULT_RDS_RADIO_TEXT DEFAULT_RDS_PS_NAME
#define DEFAULT_RDS_DEVIATION 0x00C8
#define DEFAULT_RDS_PS_REPEAT_COUNT 0x0003
#define DEFAULT_LIMITER_RTIME 0x1392
#define DEFAULT_LIMITER_DEV 0x102CA
-#define DEFAULT_PILOT_FREQUENCY 0x4A38
+#define DEFAULT_PILOT_FREQUENCY 0x4A38
#define DEFAULT_PILOT_DEVIATION 0x1A5E
#define DEFAULT_ACOMP_ATIME 0x0000
#define DEFAULT_ACOMP_RTIME 0xF4240L
#define DEFAULT_ACOMP_GAIN 0x0F
-#define DEFAULT_ACOMP_THRESHOLD (-0x28)
+#define DEFAULT_ACOMP_THRESHOLD (-0x28)
#define DEFAULT_MUTE 0x01
#define DEFAULT_POWER_LEVEL 88
#define DEFAULT_FREQUENCY 8800
@@ -108,7 +99,6 @@ static const char *si4713_supply_names[SI4713_NUM_SUPPLIES] = {
(status & SI4713_ERR))
/* mute definition */
#define set_mute(p) ((p & 1) | ((p & 1) << 1));
-#define get_mute(p) (p & 0x01)
#ifdef DEBUG
#define DBG_BUFFER(device, message, buffer, size) \
@@ -190,21 +180,6 @@ static int usecs_to_dev(unsigned long usecs, unsigned long const array[],
return rval;
}
-static unsigned long dev_to_usecs(int value, unsigned long const array[],
- int size)
-{
- int i;
- int rval = -EINVAL;
-
- for (i = 0; i < size / 2; i++)
- if (array[i * 2] == value) {
- rval = array[(i * 2) + 1];
- break;
- }
-
- return rval;
-}
-
/* si4713_handler: IRQ handler, just complete work */
static irqreturn_t si4713_handler(int irq, void *dev)
{
@@ -232,6 +207,7 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command,
u8 response[], const int respn, const int usecs)
{
struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd);
+ unsigned long until_jiffies;
u8 data1[MAX_ARGS + 1];
int err;
@@ -247,30 +223,42 @@ static int si4713_send_command(struct si4713_device *sdev, const u8 command,
if (err != argn + 1) {
v4l2_err(&sdev->sd, "Error while sending command 0x%02x\n",
command);
- return (err > 0) ? -EIO : err;
+ return err < 0 ? err : -EIO;
}
+ until_jiffies = jiffies + usecs_to_jiffies(usecs) + 1;
+
/* Wait response from interrupt */
- if (!wait_for_completion_timeout(&sdev->work,
+ if (client->irq) {
+ if (!wait_for_completion_timeout(&sdev->work,
usecs_to_jiffies(usecs) + 1))
- v4l2_warn(&sdev->sd,
+ v4l2_warn(&sdev->sd,
"(%s) Device took too much time to answer.\n",
__func__);
-
- /* Then get the response */
- err = i2c_master_recv(client, response, respn);
- if (err != respn) {
- v4l2_err(&sdev->sd,
- "Error while reading response for command 0x%02x\n",
- command);
- return (err > 0) ? -EIO : err;
}
- DBG_BUFFER(&sdev->sd, "Response", response, respn);
- if (check_command_failed(response[0]))
- return -EBUSY;
+ do {
+ err = i2c_master_recv(client, response, respn);
+ if (err != respn) {
+ v4l2_err(&sdev->sd,
+ "Error %d while reading response for command 0x%02x\n",
+ err, command);
+ return err < 0 ? err : -EIO;
+ }
+
+ DBG_BUFFER(&sdev->sd, "Response", response, respn);
+ if (!check_command_failed(response[0]))
+ return 0;
- return 0;
+ if (client->irq)
+ return -EBUSY;
+ if (usecs <= 1000)
+ usleep_range(usecs, 1000);
+ else
+ usleep_range(1000, 2000);
+ } while (time_is_after_jiffies(until_jiffies));
+
+ return -EBUSY;
}
/*
@@ -284,9 +272,9 @@ static int si4713_read_property(struct si4713_device *sdev, u16 prop, u32 *pv)
int err;
u8 val[SI4713_GET_PROP_NRESP];
/*
- * .First byte = 0
- * .Second byte = property's MSB
- * .Third byte = property's LSB
+ * .First byte = 0
+ * .Second byte = property's MSB
+ * .Third byte = property's LSB
*/
const u8 args[SI4713_GET_PROP_NARGS] = {
0x00,
@@ -321,11 +309,11 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val)
int rval;
u8 resp[SI4713_SET_PROP_NRESP];
/*
- * .First byte = 0
- * .Second byte = property's MSB
- * .Third byte = property's LSB
- * .Fourth byte = value's MSB
- * .Fifth byte = value's LSB
+ * .First byte = 0
+ * .Second byte = property's MSB
+ * .Third byte = property's LSB
+ * .Fourth byte = value's MSB
+ * .Fifth byte = value's LSB
*/
const u8 args[SI4713_SET_PROP_NARGS] = {
0x00,
@@ -363,31 +351,36 @@ static int si4713_write_property(struct si4713_device *sdev, u16 prop, u16 val)
*/
static int si4713_powerup(struct si4713_device *sdev)
{
+ struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd);
int err;
u8 resp[SI4713_PWUP_NRESP];
/*
- * .First byte = Enabled interrupts and boot function
- * .Second byte = Input operation mode
+ * .First byte = Enabled interrupts and boot function
+ * .Second byte = Input operation mode
*/
- const u8 args[SI4713_PWUP_NARGS] = {
- SI4713_PWUP_CTSIEN | SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX,
+ u8 args[SI4713_PWUP_NARGS] = {
+ SI4713_PWUP_GPO2OEN | SI4713_PWUP_FUNC_TX,
SI4713_PWUP_OPMOD_ANALOG,
};
if (sdev->power_state)
return 0;
- err = regulator_bulk_enable(ARRAY_SIZE(sdev->supplies),
- sdev->supplies);
- if (err) {
- v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err);
- return err;
+ if (sdev->supplies) {
+ err = regulator_bulk_enable(sdev->supplies, sdev->supply_data);
+ if (err) {
+ v4l2_err(&sdev->sd, "Failed to enable supplies: %d\n", err);
+ return err;
+ }
}
if (gpio_is_valid(sdev->gpio_reset)) {
udelay(50);
gpio_set_value(sdev->gpio_reset, 1);
}
+ if (client->irq)
+ args[0] |= SI4713_PWUP_CTSIEN;
+
err = si4713_send_command(sdev, SI4713_CMD_POWER_UP,
args, ARRAY_SIZE(args),
resp, ARRAY_SIZE(resp),
@@ -399,13 +392,15 @@ static int si4713_powerup(struct si4713_device *sdev)
v4l2_dbg(1, debug, &sdev->sd, "Device in power up mode\n");
sdev->power_state = POWER_ON;
- err = si4713_write_property(sdev, SI4713_GPO_IEN,
+ if (client->irq)
+ err = si4713_write_property(sdev, SI4713_GPO_IEN,
SI4713_STC_INT | SI4713_CTS);
- } else {
- if (gpio_is_valid(sdev->gpio_reset))
- gpio_set_value(sdev->gpio_reset, 0);
- err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies),
- sdev->supplies);
+ return err;
+ }
+ if (gpio_is_valid(sdev->gpio_reset))
+ gpio_set_value(sdev->gpio_reset, 0);
+ if (sdev->supplies) {
+ err = regulator_bulk_disable(sdev->supplies, sdev->supply_data);
if (err)
v4l2_err(&sdev->sd,
"Failed to disable supplies: %d\n", err);
@@ -437,11 +432,13 @@ static int si4713_powerdown(struct si4713_device *sdev)
v4l2_dbg(1, debug, &sdev->sd, "Device in reset mode\n");
if (gpio_is_valid(sdev->gpio_reset))
gpio_set_value(sdev->gpio_reset, 0);
- err = regulator_bulk_disable(ARRAY_SIZE(sdev->supplies),
- sdev->supplies);
- if (err)
- v4l2_err(&sdev->sd,
- "Failed to disable supplies: %d\n", err);
+ if (sdev->supplies) {
+ err = regulator_bulk_disable(sdev->supplies,
+ sdev->supply_data);
+ if (err)
+ v4l2_err(&sdev->sd,
+ "Failed to disable supplies: %d\n", err);
+ }
sdev->power_state = POWER_OFF;
}
@@ -458,26 +455,21 @@ static int si4713_checkrev(struct si4713_device *sdev)
int rval;
u8 resp[SI4713_GETREV_NRESP];
- mutex_lock(&sdev->mutex);
-
rval = si4713_send_command(sdev, SI4713_CMD_GET_REV,
NULL, 0,
resp, ARRAY_SIZE(resp),
DEFAULT_TIMEOUT);
if (rval < 0)
- goto unlock;
+ return rval;
if (resp[1] == SI4713_PRODUCT_NUMBER) {
v4l2_info(&sdev->sd, "chip found @ 0x%02x (%s)\n",
client->addr << 1, client->adapter->name);
} else {
- v4l2_err(&sdev->sd, "Invalid product number\n");
+ v4l2_err(&sdev->sd, "Invalid product number 0x%X\n", resp[1]);
rval = -EINVAL;
}
-
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
@@ -489,39 +481,45 @@ unlock:
*/
static int si4713_wait_stc(struct si4713_device *sdev, const int usecs)
{
- int err;
+ struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd);
u8 resp[SI4713_GET_STATUS_NRESP];
+ unsigned long start_jiffies = jiffies;
+ int err;
- /* Wait response from STC interrupt */
- if (!wait_for_completion_timeout(&sdev->work,
- usecs_to_jiffies(usecs) + 1))
+ if (client->irq &&
+ !wait_for_completion_timeout(&sdev->work, usecs_to_jiffies(usecs) + 1))
v4l2_warn(&sdev->sd,
- "%s: device took too much time to answer (%d usec).\n",
- __func__, usecs);
-
- /* Clear status bits */
- err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- DEFAULT_TIMEOUT);
-
- if (err < 0)
- goto exit;
-
- v4l2_dbg(1, debug, &sdev->sd,
- "%s: status bits: 0x%02x\n", __func__, resp[0]);
-
- if (!(resp[0] & SI4713_STC_INT))
- err = -EIO;
-
-exit:
- return err;
+ "(%s) Device took too much time to answer.\n", __func__);
+
+ for (;;) {
+ /* Clear status bits */
+ err = si4713_send_command(sdev, SI4713_CMD_GET_INT_STATUS,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ DEFAULT_TIMEOUT);
+ /* The USB device returns errors when it waits for the
+ * STC bit to be set. Hence polling */
+ if (err >= 0) {
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: status bits: 0x%02x\n", __func__, resp[0]);
+
+ if (resp[0] & SI4713_STC_INT)
+ return 0;
+ }
+ if (jiffies_to_usecs(jiffies - start_jiffies) > usecs)
+ return err < 0 ? err : -EIO;
+ /* We sleep here for 3-4 ms in order to avoid flooding the device
+ * with USB requests. The si4713 USB driver was developed
+ * by reverse engineering the Windows USB driver. The windows
+ * driver also has a ~2.5 ms delay between responses. */
+ usleep_range(3000, 4000);
+ }
}
/*
* si4713_tx_tune_freq - Sets the state of the RF carrier and sets the tuning
- * frequency between 76 and 108 MHz in 10 kHz units and
- * steps of 50 kHz.
+ * frequency between 76 and 108 MHz in 10 kHz units and
+ * steps of 50 kHz.
* @sdev: si4713_device structure for the device we are communicating
* @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz)
*/
@@ -530,9 +528,9 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency)
int err;
u8 val[SI4713_TXFREQ_NRESP];
/*
- * .First byte = 0
- * .Second byte = frequency's MSB
- * .Third byte = frequency's LSB
+ * .First byte = 0
+ * .Second byte = frequency's MSB
+ * .Third byte = frequency's LSB
*/
const u8 args[SI4713_TXFREQ_NARGS] = {
0x00,
@@ -559,14 +557,14 @@ static int si4713_tx_tune_freq(struct si4713_device *sdev, u16 frequency)
}
/*
- * si4713_tx_tune_power - Sets the RF voltage level between 88 and 115 dBuV in
- * 1 dB units. A value of 0x00 indicates off. The command
- * also sets the antenna tuning capacitance. A value of 0
- * indicates autotuning, and a value of 1 - 191 indicates
- * a manual override, which results in a tuning
- * capacitance of 0.25 pF x @antcap.
+ * si4713_tx_tune_power - Sets the RF voltage level between 88 and 120 dBuV in
+ * 1 dB units. A value of 0x00 indicates off. The command
+ * also sets the antenna tuning capacitance. A value of 0
+ * indicates autotuning, and a value of 1 - 191 indicates
+ * a manual override, which results in a tuning
+ * capacitance of 0.25 pF x @antcap.
* @sdev: si4713_device structure for the device we are communicating
- * @power: tuning power (88 - 115 dBuV, unit/step 1 dB)
+ * @power: tuning power (88 - 120 dBuV, unit/step 1 dB)
* @antcap: value of antenna tuning capacitor (0 - 191)
*/
static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power,
@@ -575,21 +573,21 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power,
int err;
u8 val[SI4713_TXPWR_NRESP];
/*
- * .First byte = 0
- * .Second byte = 0
- * .Third byte = power
- * .Fourth byte = antcap
+ * .First byte = 0
+ * .Second byte = 0
+ * .Third byte = power
+ * .Fourth byte = antcap
*/
- const u8 args[SI4713_TXPWR_NARGS] = {
+ u8 args[SI4713_TXPWR_NARGS] = {
0x00,
0x00,
power,
antcap,
};
- if (((power > 0) && (power < SI4713_MIN_POWER)) ||
- power > SI4713_MAX_POWER || antcap > SI4713_MAX_ANTCAP)
- return -EDOM;
+ /* Map power values 1-87 to MIN_POWER (88) */
+ if (power > 0 && power < SI4713_MIN_POWER)
+ args[2] = power = SI4713_MIN_POWER;
err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_POWER,
args, ARRAY_SIZE(args), val,
@@ -607,12 +605,12 @@ static int si4713_tx_tune_power(struct si4713_device *sdev, u8 power,
/*
* si4713_tx_tune_measure - Enters receive mode and measures the received noise
- * level in units of dBuV on the selected frequency.
- * The Frequency must be between 76 and 108 MHz in 10 kHz
- * units and steps of 50 kHz. The command also sets the
- * antenna tuning capacitance. A value of 0 means
- * autotuning, and a value of 1 to 191 indicates manual
- * override.
+ * level in units of dBuV on the selected frequency.
+ * The Frequency must be between 76 and 108 MHz in 10 kHz
+ * units and steps of 50 kHz. The command also sets the
+ * antenna tuning capacitance. A value of 0 means
+ * autotuning, and a value of 1 to 191 indicates manual
+ * override.
* @sdev: si4713_device structure for the device we are communicating
* @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz)
* @antcap: value of antenna tuning capacitor (0 - 191)
@@ -623,10 +621,10 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency,
int err;
u8 val[SI4713_TXMEA_NRESP];
/*
- * .First byte = 0
- * .Second byte = frequency's MSB
- * .Third byte = frequency's LSB
- * .Fourth byte = antcap
+ * .First byte = 0
+ * .Second byte = frequency's MSB
+ * .Third byte = frequency's LSB
+ * .Fourth byte = antcap
*/
const u8 args[SI4713_TXMEA_NARGS] = {
0x00,
@@ -656,11 +654,11 @@ static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency,
/*
* si4713_tx_tune_status- Returns the status of the tx_tune_freq, tx_tune_mea or
- * tx_tune_power commands. This command return the current
- * frequency, output voltage in dBuV, the antenna tunning
- * capacitance value and the received noise level. The
- * command also clears the stcint interrupt bit when the
- * first bit of its arguments is high.
+ * tx_tune_power commands. This command return the current
+ * frequency, output voltage in dBuV, the antenna tunning
+ * capacitance value and the received noise level. The
+ * command also clears the stcint interrupt bit when the
+ * first bit of its arguments is high.
* @sdev: si4713_device structure for the device we are communicating
* @intack: 0x01 to clear the seek/tune complete interrupt status indicator.
* @frequency: returned frequency
@@ -675,7 +673,7 @@ static int si4713_tx_tune_status(struct si4713_device *sdev, u8 intack,
int err;
u8 val[SI4713_TXSTATUS_NRESP];
/*
- * .First byte = intack bit
+ * .First byte = intack bit
*/
const u8 args[SI4713_TXSTATUS_NARGS] = {
intack & SI4713_INTACK_MASK,
@@ -778,17 +776,9 @@ static int si4713_tx_rds_ps(struct si4713_device *sdev, u8 psid,
static int si4713_set_power_state(struct si4713_device *sdev, u8 value)
{
- int rval;
-
- mutex_lock(&sdev->mutex);
-
if (value)
- rval = si4713_powerup(sdev);
- else
- rval = si4713_powerdown(sdev);
-
- mutex_unlock(&sdev->mutex);
- return rval;
+ return si4713_powerup(sdev);
+ return si4713_powerdown(sdev);
}
static int si4713_set_mute(struct si4713_device *sdev, u16 mute)
@@ -797,17 +787,10 @@ static int si4713_set_mute(struct si4713_device *sdev, u16 mute)
mute = set_mute(mute);
- mutex_lock(&sdev->mutex);
-
if (sdev->power_state)
rval = si4713_write_property(sdev,
SI4713_TX_LINE_INPUT_MUTE, mute);
- if (rval >= 0)
- sdev->mute = get_mute(mute);
-
- mutex_unlock(&sdev->mutex);
-
return rval;
}
@@ -820,15 +803,13 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name)
if (!strlen(ps_name))
memset(ps_name, 0, MAX_RDS_PS_NAME + 1);
- mutex_lock(&sdev->mutex);
-
if (sdev->power_state) {
/* Write the new ps name and clear the padding */
for (i = 0; i < MAX_RDS_PS_NAME; i += (RDS_BLOCK / 2)) {
rval = si4713_tx_rds_ps(sdev, (i / (RDS_BLOCK / 2)),
ps_name + i);
if (rval < 0)
- goto unlock;
+ return rval;
}
/* Setup the size to be sent */
@@ -841,51 +822,45 @@ static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name)
SI4713_TX_RDS_PS_MESSAGE_COUNT,
rds_ps_nblocks(len));
if (rval < 0)
- goto unlock;
+ return rval;
rval = si4713_write_property(sdev,
SI4713_TX_RDS_PS_REPEAT_COUNT,
DEFAULT_RDS_PS_REPEAT_COUNT * 2);
if (rval < 0)
- goto unlock;
+ return rval;
}
- strncpy(sdev->rds_info.ps_name, ps_name, MAX_RDS_PS_NAME);
-
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
-static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt)
+static int si4713_set_rds_radio_text(struct si4713_device *sdev, const char *rt)
{
+ static const char cr[RDS_RADIOTEXT_BLK_SIZE] = { RDS_CARRIAGE_RETURN, 0 };
int rval = 0, i;
u16 t_index = 0;
u8 b_index = 0, cr_inserted = 0;
s8 left;
- mutex_lock(&sdev->mutex);
-
if (!sdev->power_state)
- goto copy;
+ return rval;
rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left);
if (rval < 0)
- goto unlock;
+ return rval;
if (!strlen(rt))
- goto copy;
+ return rval;
do {
/* RDS spec says that if the last block isn't used,
* then apply a carriage return
*/
- if (t_index < (RDS_RADIOTEXT_INDEX_MAX *
- RDS_RADIOTEXT_BLK_SIZE)) {
+ if (t_index < (RDS_RADIOTEXT_INDEX_MAX * RDS_RADIOTEXT_BLK_SIZE)) {
for (i = 0; i < RDS_RADIOTEXT_BLK_SIZE; i++) {
- if (!rt[t_index + i] || rt[t_index + i] ==
- RDS_CARRIAGE_RETURN) {
- rt[t_index + i] = RDS_CARRIAGE_RETURN;
+ if (!rt[t_index + i] ||
+ rt[t_index + i] == RDS_CARRIAGE_RETURN) {
+ rt = cr;
cr_inserted = 1;
break;
}
@@ -898,7 +873,7 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt)
compose_u16(rt[t_index + 2], rt[t_index + 3]),
&left);
if (rval < 0)
- goto unlock;
+ return rval;
t_index += RDS_RADIOTEXT_BLK_SIZE;
@@ -906,16 +881,38 @@ static int si4713_set_rds_radio_text(struct si4713_device *sdev, char *rt)
break;
} while (left > 0);
-copy:
- strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT);
+ return rval;
+}
+
+/*
+ * si4713_update_tune_status - update properties from tx_tune_status
+ * command. Must be called with sdev->mutex held.
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_update_tune_status(struct si4713_device *sdev)
+{
+ int rval;
+ u16 f = 0;
+ u8 p = 0, a = 0, n = 0;
+
+ rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n);
+
+ if (rval < 0)
+ goto exit;
-unlock:
- mutex_unlock(&sdev->mutex);
+/* TODO: check that power_level and antenna_capacitor really are not
+ changed by the hardware. If they are, then these controls should become
+ volatiles.
+ sdev->power_level = p;
+ sdev->antenna_capacitor = a;*/
+ sdev->tune_rnl = n;
+
+exit:
return rval;
}
static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id,
- u32 **shadow, s32 *bit, s32 *mask, u16 *property, int *mul,
+ s32 *bit, s32 *mask, u16 *property, int *mul,
unsigned long **table, int *size)
{
s32 rval = 0;
@@ -925,157 +922,71 @@ static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id,
case V4L2_CID_RDS_TX_PI:
*property = SI4713_TX_RDS_PI;
*mul = 1;
- *shadow = &sdev->rds_info.pi;
break;
case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD:
*property = SI4713_TX_ACOMP_THRESHOLD;
*mul = 1;
- *shadow = &sdev->acomp_info.threshold;
break;
case V4L2_CID_AUDIO_COMPRESSION_GAIN:
*property = SI4713_TX_ACOMP_GAIN;
*mul = 1;
- *shadow = &sdev->acomp_info.gain;
break;
case V4L2_CID_PILOT_TONE_FREQUENCY:
*property = SI4713_TX_PILOT_FREQUENCY;
*mul = 1;
- *shadow = &sdev->pilot_info.frequency;
break;
case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME:
*property = SI4713_TX_ACOMP_ATTACK_TIME;
*mul = ATTACK_TIME_UNIT;
- *shadow = &sdev->acomp_info.attack_time;
break;
case V4L2_CID_PILOT_TONE_DEVIATION:
*property = SI4713_TX_PILOT_DEVIATION;
*mul = 10;
- *shadow = &sdev->pilot_info.deviation;
break;
case V4L2_CID_AUDIO_LIMITER_DEVIATION:
*property = SI4713_TX_AUDIO_DEVIATION;
*mul = 10;
- *shadow = &sdev->limiter_info.deviation;
break;
case V4L2_CID_RDS_TX_DEVIATION:
*property = SI4713_TX_RDS_DEVIATION;
*mul = 1;
- *shadow = &sdev->rds_info.deviation;
break;
case V4L2_CID_RDS_TX_PTY:
*property = SI4713_TX_RDS_PS_MISC;
*bit = 5;
*mask = 0x1F << 5;
- *shadow = &sdev->rds_info.pty;
break;
case V4L2_CID_AUDIO_LIMITER_ENABLED:
*property = SI4713_TX_ACOMP_ENABLE;
*bit = 1;
*mask = 1 << 1;
- *shadow = &sdev->limiter_info.enabled;
break;
case V4L2_CID_AUDIO_COMPRESSION_ENABLED:
*property = SI4713_TX_ACOMP_ENABLE;
*bit = 0;
*mask = 1 << 0;
- *shadow = &sdev->acomp_info.enabled;
break;
case V4L2_CID_PILOT_TONE_ENABLED:
*property = SI4713_TX_COMPONENT_ENABLE;
*bit = 0;
*mask = 1 << 0;
- *shadow = &sdev->pilot_info.enabled;
break;
case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME:
*property = SI4713_TX_LIMITER_RELEASE_TIME;
*table = limiter_times;
*size = ARRAY_SIZE(limiter_times);
- *shadow = &sdev->limiter_info.release_time;
break;
case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME:
*property = SI4713_TX_ACOMP_RELEASE_TIME;
*table = acomp_rtimes;
*size = ARRAY_SIZE(acomp_rtimes);
- *shadow = &sdev->acomp_info.release_time;
break;
case V4L2_CID_TUNE_PREEMPHASIS:
*property = SI4713_TX_PREEMPHASIS;
*table = preemphasis_values;
*size = ARRAY_SIZE(preemphasis_values);
- *shadow = &sdev->preemphasis;
- break;
-
- default:
- rval = -EINVAL;
- }
-
- return rval;
-}
-
-static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
-
-/* write string property */
-static int si4713_write_econtrol_string(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
-{
- struct v4l2_queryctrl vqc;
- int len;
- s32 rval = 0;
-
- vqc.id = control->id;
- rval = si4713_queryctrl(&sdev->sd, &vqc);
- if (rval < 0)
- goto exit;
-
- switch (control->id) {
- case V4L2_CID_RDS_TX_PS_NAME: {
- char ps_name[MAX_RDS_PS_NAME + 1];
-
- len = control->size - 1;
- if (len < 0 || len > MAX_RDS_PS_NAME) {
- rval = -ERANGE;
- goto exit;
- }
- rval = copy_from_user(ps_name, control->string, len);
- if (rval) {
- rval = -EFAULT;
- goto exit;
- }
- ps_name[len] = '\0';
-
- if (strlen(ps_name) % vqc.step) {
- rval = -ERANGE;
- goto exit;
- }
-
- rval = si4713_set_rds_ps_name(sdev, ps_name);
- }
- break;
-
- case V4L2_CID_RDS_TX_RADIO_TEXT: {
- char radio_text[MAX_RDS_RADIO_TEXT + 1];
-
- len = control->size - 1;
- if (len < 0 || len > MAX_RDS_RADIO_TEXT) {
- rval = -ERANGE;
- goto exit;
- }
- rval = copy_from_user(radio_text, control->string, len);
- if (rval) {
- rval = -EFAULT;
- goto exit;
- }
- radio_text[len] = '\0';
-
- if (strlen(radio_text) % vqc.step) {
- rval = -ERANGE;
- goto exit;
- }
-
- rval = si4713_set_rds_radio_text(sdev, radio_text);
- }
break;
default:
@@ -1083,136 +994,10 @@ static int si4713_write_econtrol_string(struct si4713_device *sdev,
break;
}
-exit:
return rval;
}
-static int validate_range(struct v4l2_subdev *sd,
- struct v4l2_ext_control *control)
-{
- struct v4l2_queryctrl vqc;
- int rval;
-
- vqc.id = control->id;
- rval = si4713_queryctrl(sd, &vqc);
- if (rval < 0)
- goto exit;
-
- if (control->value < vqc.minimum || control->value > vqc.maximum)
- rval = -ERANGE;
-
-exit:
- return rval;
-}
-
-/* properties which use tx_tune_power*/
-static int si4713_write_econtrol_tune(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
-{
- s32 rval = 0;
- u8 power, antcap;
-
- rval = validate_range(&sdev->sd, control);
- if (rval < 0)
- goto exit;
-
- mutex_lock(&sdev->mutex);
-
- switch (control->id) {
- case V4L2_CID_TUNE_POWER_LEVEL:
- power = control->value;
- antcap = sdev->antenna_capacitor;
- break;
- case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
- power = sdev->power_level;
- antcap = control->value;
- break;
- default:
- rval = -EINVAL;
- goto unlock;
- }
-
- if (sdev->power_state)
- rval = si4713_tx_tune_power(sdev, power, antcap);
-
- if (rval == 0) {
- sdev->power_level = power;
- sdev->antenna_capacitor = antcap;
- }
-
-unlock:
- mutex_unlock(&sdev->mutex);
-exit:
- return rval;
-}
-
-static int si4713_write_econtrol_integers(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
-{
- s32 rval;
- u32 *shadow = NULL, val = 0;
- s32 bit = 0, mask = 0;
- u16 property = 0;
- int mul = 0;
- unsigned long *table = NULL;
- int size = 0;
-
- rval = validate_range(&sdev->sd, control);
- if (rval < 0)
- goto exit;
-
- rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit,
- &mask, &property, &mul, &table, &size);
- if (rval < 0)
- goto exit;
-
- val = control->value;
- if (mul) {
- val = control->value / mul;
- } else if (table) {
- rval = usecs_to_dev(control->value, table, size);
- if (rval < 0)
- goto exit;
- val = rval;
- rval = 0;
- }
-
- mutex_lock(&sdev->mutex);
-
- if (sdev->power_state) {
- if (mask) {
- rval = si4713_read_property(sdev, property, &val);
- if (rval < 0)
- goto unlock;
- val = set_bits(val, control->value, bit, mask);
- }
-
- rval = si4713_write_property(sdev, property, val);
- if (rval < 0)
- goto unlock;
- if (mask)
- val = control->value;
- }
-
- if (mul) {
- *shadow = val * mul;
- } else if (table) {
- rval = dev_to_usecs(val, table, size);
- if (rval < 0)
- goto unlock;
- *shadow = rval;
- rval = 0;
- } else {
- *shadow = val;
- }
-
-unlock:
- mutex_unlock(&sdev->mutex);
-exit:
- return rval;
-}
-
-static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f);
+static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f);
static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulator *);
/*
* si4713_setup - Sets the device up with current configuration.
@@ -1220,111 +1005,25 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato
*/
static int si4713_setup(struct si4713_device *sdev)
{
- struct v4l2_ext_control ctrl;
struct v4l2_frequency f;
struct v4l2_modulator vm;
- struct si4713_device *tmp;
- int rval = 0;
-
- tmp = kmalloc(sizeof(*tmp), GFP_KERNEL);
- if (!tmp)
- return -ENOMEM;
-
- /* Get a local copy to avoid race */
- mutex_lock(&sdev->mutex);
- memcpy(tmp, sdev, sizeof(*sdev));
- mutex_unlock(&sdev->mutex);
-
- ctrl.id = V4L2_CID_RDS_TX_PI;
- ctrl.value = tmp->rds_info.pi;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_COMPRESSION_THRESHOLD;
- ctrl.value = tmp->acomp_info.threshold;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_COMPRESSION_GAIN;
- ctrl.value = tmp->acomp_info.gain;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_PILOT_TONE_FREQUENCY;
- ctrl.value = tmp->pilot_info.frequency;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME;
- ctrl.value = tmp->acomp_info.attack_time;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_PILOT_TONE_DEVIATION;
- ctrl.value = tmp->pilot_info.deviation;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_LIMITER_DEVIATION;
- ctrl.value = tmp->limiter_info.deviation;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_RDS_TX_DEVIATION;
- ctrl.value = tmp->rds_info.deviation;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_RDS_TX_PTY;
- ctrl.value = tmp->rds_info.pty;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_LIMITER_ENABLED;
- ctrl.value = tmp->limiter_info.enabled;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_COMPRESSION_ENABLED;
- ctrl.value = tmp->acomp_info.enabled;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_PILOT_TONE_ENABLED;
- ctrl.value = tmp->pilot_info.enabled;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_LIMITER_RELEASE_TIME;
- ctrl.value = tmp->limiter_info.release_time;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME;
- ctrl.value = tmp->acomp_info.release_time;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_TUNE_PREEMPHASIS;
- ctrl.value = tmp->preemphasis;
- rval |= si4713_write_econtrol_integers(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_RDS_TX_PS_NAME;
- rval |= si4713_set_rds_ps_name(sdev, tmp->rds_info.ps_name);
-
- ctrl.id = V4L2_CID_RDS_TX_RADIO_TEXT;
- rval |= si4713_set_rds_radio_text(sdev, tmp->rds_info.radio_text);
+ int rval;
/* Device procedure needs to set frequency first */
- f.frequency = tmp->frequency ? tmp->frequency : DEFAULT_FREQUENCY;
+ f.tuner = 0;
+ f.frequency = sdev->frequency ? sdev->frequency : DEFAULT_FREQUENCY;
f.frequency = si4713_to_v4l2(f.frequency);
- rval |= si4713_s_frequency(&sdev->sd, &f);
-
- ctrl.id = V4L2_CID_TUNE_POWER_LEVEL;
- ctrl.value = tmp->power_level;
- rval |= si4713_write_econtrol_tune(sdev, &ctrl);
-
- ctrl.id = V4L2_CID_TUNE_ANTENNA_CAPACITOR;
- ctrl.value = tmp->antenna_capacitor;
- rval |= si4713_write_econtrol_tune(sdev, &ctrl);
+ rval = si4713_s_frequency(&sdev->sd, &f);
vm.index = 0;
- if (tmp->stereo)
+ if (sdev->stereo)
vm.txsubchans = V4L2_TUNER_SUB_STEREO;
else
vm.txsubchans = V4L2_TUNER_SUB_MONO;
- if (tmp->rds_info.enabled)
+ if (sdev->rds_enabled)
vm.txsubchans |= V4L2_TUNER_SUB_RDS;
si4713_s_modulator(&sdev->sd, &vm);
- kfree(tmp);
-
return rval;
}
@@ -1338,434 +1037,125 @@ static int si4713_initialize(struct si4713_device *sdev)
rval = si4713_set_power_state(sdev, POWER_ON);
if (rval < 0)
- goto exit;
+ return rval;
rval = si4713_checkrev(sdev);
if (rval < 0)
- goto exit;
+ return rval;
rval = si4713_set_power_state(sdev, POWER_OFF);
if (rval < 0)
- goto exit;
-
- mutex_lock(&sdev->mutex);
-
- sdev->rds_info.pi = DEFAULT_RDS_PI;
- sdev->rds_info.pty = DEFAULT_RDS_PTY;
- sdev->rds_info.deviation = DEFAULT_RDS_DEVIATION;
- strlcpy(sdev->rds_info.ps_name, DEFAULT_RDS_PS_NAME, MAX_RDS_PS_NAME);
- strlcpy(sdev->rds_info.radio_text, DEFAULT_RDS_RADIO_TEXT,
- MAX_RDS_RADIO_TEXT);
- sdev->rds_info.enabled = 1;
-
- sdev->limiter_info.release_time = DEFAULT_LIMITER_RTIME;
- sdev->limiter_info.deviation = DEFAULT_LIMITER_DEV;
- sdev->limiter_info.enabled = 1;
-
- sdev->pilot_info.deviation = DEFAULT_PILOT_DEVIATION;
- sdev->pilot_info.frequency = DEFAULT_PILOT_FREQUENCY;
- sdev->pilot_info.enabled = 1;
-
- sdev->acomp_info.release_time = DEFAULT_ACOMP_RTIME;
- sdev->acomp_info.attack_time = DEFAULT_ACOMP_ATIME;
- sdev->acomp_info.threshold = DEFAULT_ACOMP_THRESHOLD;
- sdev->acomp_info.gain = DEFAULT_ACOMP_GAIN;
- sdev->acomp_info.enabled = 1;
+ return rval;
sdev->frequency = DEFAULT_FREQUENCY;
- sdev->preemphasis = DEFAULT_PREEMPHASIS;
- sdev->mute = DEFAULT_MUTE;
- sdev->power_level = DEFAULT_POWER_LEVEL;
- sdev->antenna_capacitor = 0;
sdev->stereo = 1;
sdev->tune_rnl = DEFAULT_TUNE_RNL;
-
- mutex_unlock(&sdev->mutex);
-
-exit:
- return rval;
-}
-
-/* read string property */
-static int si4713_read_econtrol_string(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
-{
- s32 rval = 0;
-
- switch (control->id) {
- case V4L2_CID_RDS_TX_PS_NAME:
- if (strlen(sdev->rds_info.ps_name) + 1 > control->size) {
- control->size = MAX_RDS_PS_NAME + 1;
- rval = -ENOSPC;
- goto exit;
- }
- rval = copy_to_user(control->string, sdev->rds_info.ps_name,
- strlen(sdev->rds_info.ps_name) + 1);
- if (rval)
- rval = -EFAULT;
- break;
-
- case V4L2_CID_RDS_TX_RADIO_TEXT:
- if (strlen(sdev->rds_info.radio_text) + 1 > control->size) {
- control->size = MAX_RDS_RADIO_TEXT + 1;
- rval = -ENOSPC;
- goto exit;
- }
- rval = copy_to_user(control->string, sdev->rds_info.radio_text,
- strlen(sdev->rds_info.radio_text) + 1);
- if (rval)
- rval = -EFAULT;
- break;
-
- default:
- rval = -EINVAL;
- break;
- }
-
-exit:
- return rval;
-}
-
-/*
- * si4713_update_tune_status - update properties from tx_tune_status
- * command. Must be called with sdev->mutex held.
- * @sdev: si4713_device structure for the device we are communicating
- */
-static int si4713_update_tune_status(struct si4713_device *sdev)
-{
- int rval;
- u16 f = 0;
- u8 p = 0, a = 0, n = 0;
-
- rval = si4713_tx_tune_status(sdev, 0x00, &f, &p, &a, &n);
-
- if (rval < 0)
- goto exit;
-
- sdev->power_level = p;
- sdev->antenna_capacitor = a;
- sdev->tune_rnl = n;
-
-exit:
- return rval;
-}
-
-/* properties which use tx_tune_status */
-static int si4713_read_econtrol_tune(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
-{
- s32 rval = 0;
-
- mutex_lock(&sdev->mutex);
-
- if (sdev->power_state) {
- rval = si4713_update_tune_status(sdev);
- if (rval < 0)
- goto unlock;
- }
-
- switch (control->id) {
- case V4L2_CID_TUNE_POWER_LEVEL:
- control->value = sdev->power_level;
- break;
- case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
- control->value = sdev->antenna_capacitor;
- break;
- default:
- rval = -EINVAL;
- }
-
-unlock:
- mutex_unlock(&sdev->mutex);
- return rval;
+ return 0;
}
-static int si4713_read_econtrol_integers(struct si4713_device *sdev,
- struct v4l2_ext_control *control)
+/* si4713_s_ctrl - set the value of a control */
+static int si4713_s_ctrl(struct v4l2_ctrl *ctrl)
{
- s32 rval;
- u32 *shadow = NULL, val = 0;
+ struct si4713_device *sdev =
+ container_of(ctrl->handler, struct si4713_device, ctrl_handler);
+ u32 val = 0;
s32 bit = 0, mask = 0;
u16 property = 0;
int mul = 0;
unsigned long *table = NULL;
int size = 0;
+ bool force = false;
+ int c;
+ int ret = 0;
- rval = si4713_choose_econtrol_action(sdev, control->id, &shadow, &bit,
- &mask, &property, &mul, &table, &size);
- if (rval < 0)
- goto exit;
-
- mutex_lock(&sdev->mutex);
-
- if (sdev->power_state) {
- rval = si4713_read_property(sdev, property, &val);
- if (rval < 0)
- goto unlock;
-
- /* Keep negative values for threshold */
- if (control->id == V4L2_CID_AUDIO_COMPRESSION_THRESHOLD)
- *shadow = (s16)val;
- else if (mask)
- *shadow = get_status_bit(val, bit, mask);
- else if (mul)
- *shadow = val * mul;
- else
- *shadow = dev_to_usecs(val, table, size);
- }
-
- control->value = *shadow;
-
-unlock:
- mutex_unlock(&sdev->mutex);
-exit:
- return rval;
-}
-
-/*
- * Video4Linux Subdev Interface
- */
-/* si4713_s_ext_ctrls - set extended controls value */
-static int si4713_s_ext_ctrls(struct v4l2_subdev *sd,
- struct v4l2_ext_controls *ctrls)
-{
- struct si4713_device *sdev = to_si4713_device(sd);
- int i;
-
- if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX)
+ if (ctrl->id != V4L2_CID_AUDIO_MUTE)
return -EINVAL;
-
- for (i = 0; i < ctrls->count; i++) {
- int err;
-
- switch ((ctrls->controls + i)->id) {
- case V4L2_CID_RDS_TX_PS_NAME:
- case V4L2_CID_RDS_TX_RADIO_TEXT:
- err = si4713_write_econtrol_string(sdev,
- ctrls->controls + i);
- break;
- case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
- case V4L2_CID_TUNE_POWER_LEVEL:
- err = si4713_write_econtrol_tune(sdev,
- ctrls->controls + i);
- break;
- default:
- err = si4713_write_econtrol_integers(sdev,
- ctrls->controls + i);
- }
-
- if (err < 0) {
- ctrls->error_idx = i;
- return err;
+ if (ctrl->is_new) {
+ if (ctrl->val) {
+ ret = si4713_set_mute(sdev, ctrl->val);
+ if (!ret)
+ ret = si4713_set_power_state(sdev, POWER_DOWN);
+ return ret;
}
+ ret = si4713_set_power_state(sdev, POWER_UP);
+ if (!ret)
+ ret = si4713_set_mute(sdev, ctrl->val);
+ if (!ret)
+ ret = si4713_setup(sdev);
+ if (ret)
+ return ret;
+ force = true;
}
- return 0;
-}
-
-/* si4713_g_ext_ctrls - get extended controls value */
-static int si4713_g_ext_ctrls(struct v4l2_subdev *sd,
- struct v4l2_ext_controls *ctrls)
-{
- struct si4713_device *sdev = to_si4713_device(sd);
- int i;
+ if (!sdev->power_state)
+ return 0;
- if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX)
- return -EINVAL;
+ for (c = 1; !ret && c < ctrl->ncontrols; c++) {
+ ctrl = ctrl->cluster[c];
- for (i = 0; i < ctrls->count; i++) {
- int err;
+ if (!force && !ctrl->is_new)
+ continue;
- switch ((ctrls->controls + i)->id) {
+ switch (ctrl->id) {
case V4L2_CID_RDS_TX_PS_NAME:
+ ret = si4713_set_rds_ps_name(sdev, ctrl->string);
+ break;
+
case V4L2_CID_RDS_TX_RADIO_TEXT:
- err = si4713_read_econtrol_string(sdev,
- ctrls->controls + i);
+ ret = si4713_set_rds_radio_text(sdev, ctrl->string);
break;
+
case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
+ /* don't handle this control if we force setting all
+ * controls since in that case it will be handled by
+ * V4L2_CID_TUNE_POWER_LEVEL. */
+ if (force)
+ break;
+ /* fall through */
case V4L2_CID_TUNE_POWER_LEVEL:
- err = si4713_read_econtrol_tune(sdev,
- ctrls->controls + i);
+ ret = si4713_tx_tune_power(sdev,
+ sdev->tune_pwr_level->val, sdev->tune_ant_cap->val);
+ if (!ret) {
+ /* Make sure we don't set this twice */
+ sdev->tune_ant_cap->is_new = false;
+ sdev->tune_pwr_level->is_new = false;
+ }
break;
- default:
- err = si4713_read_econtrol_integers(sdev,
- ctrls->controls + i);
- }
-
- if (err < 0) {
- ctrls->error_idx = i;
- return err;
- }
- }
-
- return 0;
-}
-
-/* si4713_queryctrl - enumerate control items */
-static int si4713_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
-{
- int rval = 0;
-
- switch (qc->id) {
- /* User class controls */
- case V4L2_CID_AUDIO_MUTE:
- rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, DEFAULT_MUTE);
- break;
- /* FM_TX class controls */
- case V4L2_CID_RDS_TX_PI:
- rval = v4l2_ctrl_query_fill(qc, 0, 0xFFFF, 1, DEFAULT_RDS_PI);
- break;
- case V4L2_CID_RDS_TX_PTY:
- rval = v4l2_ctrl_query_fill(qc, 0, 31, 1, DEFAULT_RDS_PTY);
- break;
- case V4L2_CID_RDS_TX_DEVIATION:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_DEVIATION,
- 10, DEFAULT_RDS_DEVIATION);
- break;
- case V4L2_CID_RDS_TX_PS_NAME:
- /*
- * Report step as 8. From RDS spec, psname
- * should be 8. But there are receivers which scroll strings
- * sized as 8xN.
- */
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_PS_NAME, 8, 0);
- break;
- case V4L2_CID_RDS_TX_RADIO_TEXT:
- /*
- * Report step as 32 (2A block). From RDS spec,
- * radio text should be 32 for 2A block. But there are receivers
- * which scroll strings sized as 32xN. Setting default to 32.
- */
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_RDS_RADIO_TEXT, 32, 0);
- break;
-
- case V4L2_CID_AUDIO_LIMITER_ENABLED:
- rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
- break;
- case V4L2_CID_AUDIO_LIMITER_RELEASE_TIME:
- rval = v4l2_ctrl_query_fill(qc, 250, MAX_LIMITER_RELEASE_TIME,
- 50, DEFAULT_LIMITER_RTIME);
- break;
- case V4L2_CID_AUDIO_LIMITER_DEVIATION:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_LIMITER_DEVIATION,
- 10, DEFAULT_LIMITER_DEV);
- break;
- case V4L2_CID_AUDIO_COMPRESSION_ENABLED:
- rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
- break;
- case V4L2_CID_AUDIO_COMPRESSION_GAIN:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_GAIN, 1,
- DEFAULT_ACOMP_GAIN);
- break;
- case V4L2_CID_AUDIO_COMPRESSION_THRESHOLD:
- rval = v4l2_ctrl_query_fill(qc, MIN_ACOMP_THRESHOLD,
- MAX_ACOMP_THRESHOLD, 1,
- DEFAULT_ACOMP_THRESHOLD);
- break;
- case V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_ACOMP_ATTACK_TIME,
- 500, DEFAULT_ACOMP_ATIME);
- break;
- case V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME:
- rval = v4l2_ctrl_query_fill(qc, 100000, MAX_ACOMP_RELEASE_TIME,
- 100000, DEFAULT_ACOMP_RTIME);
- break;
-
- case V4L2_CID_PILOT_TONE_ENABLED:
- rval = v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
- break;
- case V4L2_CID_PILOT_TONE_DEVIATION:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_DEVIATION,
- 10, DEFAULT_PILOT_DEVIATION);
- break;
- case V4L2_CID_PILOT_TONE_FREQUENCY:
- rval = v4l2_ctrl_query_fill(qc, 0, MAX_PILOT_FREQUENCY,
- 1, DEFAULT_PILOT_FREQUENCY);
- break;
-
- case V4L2_CID_TUNE_PREEMPHASIS:
- rval = v4l2_ctrl_query_fill(qc, V4L2_PREEMPHASIS_DISABLED,
- V4L2_PREEMPHASIS_75_uS, 1,
- V4L2_PREEMPHASIS_50_uS);
- break;
- case V4L2_CID_TUNE_POWER_LEVEL:
- rval = v4l2_ctrl_query_fill(qc, 0, 120, 1, DEFAULT_POWER_LEVEL);
- break;
- case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
- rval = v4l2_ctrl_query_fill(qc, 0, 191, 1, 0);
- break;
- default:
- rval = -EINVAL;
- break;
- }
-
- return rval;
-}
-
-/* si4713_g_ctrl - get the value of a control */
-static int si4713_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
- struct si4713_device *sdev = to_si4713_device(sd);
- int rval = 0;
-
- if (!sdev)
- return -ENODEV;
-
- mutex_lock(&sdev->mutex);
-
- if (sdev->power_state) {
- rval = si4713_read_property(sdev, SI4713_TX_LINE_INPUT_MUTE,
- &sdev->mute);
-
- if (rval < 0)
- goto unlock;
- }
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_MUTE:
- ctrl->value = get_mute(sdev->mute);
- break;
- }
-
-unlock:
- mutex_unlock(&sdev->mutex);
- return rval;
-}
-
-/* si4713_s_ctrl - set the value of a control */
-static int si4713_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl)
-{
- struct si4713_device *sdev = to_si4713_device(sd);
- int rval = 0;
-
- if (!sdev)
- return -ENODEV;
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_MUTE:
- if (ctrl->value) {
- rval = si4713_set_mute(sdev, ctrl->value);
- if (rval < 0)
- goto exit;
-
- rval = si4713_set_power_state(sdev, POWER_DOWN);
- } else {
- rval = si4713_set_power_state(sdev, POWER_UP);
- if (rval < 0)
- goto exit;
+ default:
+ ret = si4713_choose_econtrol_action(sdev, ctrl->id, &bit,
+ &mask, &property, &mul, &table, &size);
+ if (ret < 0)
+ break;
+
+ val = ctrl->val;
+ if (mul) {
+ val = val / mul;
+ } else if (table) {
+ ret = usecs_to_dev(val, table, size);
+ if (ret < 0)
+ break;
+ val = ret;
+ ret = 0;
+ }
- rval = si4713_setup(sdev);
- if (rval < 0)
- goto exit;
+ if (mask) {
+ ret = si4713_read_property(sdev, property, &val);
+ if (ret < 0)
+ break;
+ val = set_bits(val, ctrl->val, bit, mask);
+ }
- rval = si4713_set_mute(sdev, ctrl->value);
+ ret = si4713_write_property(sdev, property, val);
+ if (ret < 0)
+ break;
+ if (mask)
+ val = ctrl->val;
+ break;
}
- break;
}
-exit:
- return rval;
+ return ret;
}
/* si4713_ioctl - deal with private ioctls (only rnl for now) */
@@ -1779,7 +1169,6 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
if (!arg)
return -EINVAL;
- mutex_lock(&sdev->mutex);
switch (cmd) {
case SI4713_IOC_MEASURE_RNL:
frequency = v4l2_to_si4713(rnl->frequency);
@@ -1788,11 +1177,11 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
/* Set desired measurement frequency */
rval = si4713_tx_tune_measure(sdev, frequency, 0);
if (rval < 0)
- goto unlock;
+ return rval;
/* get results from tune status */
rval = si4713_update_tune_status(sdev);
if (rval < 0)
- goto unlock;
+ return rval;
}
rnl->rnl = sdev->tune_rnl;
break;
@@ -1802,35 +1191,20 @@ static long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
rval = -ENOIOCTLCMD;
}
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
-static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = {
- .queryctrl = si4713_queryctrl,
- .g_ext_ctrls = si4713_g_ext_ctrls,
- .s_ext_ctrls = si4713_s_ext_ctrls,
- .g_ctrl = si4713_g_ctrl,
- .s_ctrl = si4713_s_ctrl,
- .ioctl = si4713_ioctl,
-};
-
/* si4713_g_modulator - get modulator attributes */
static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm)
{
struct si4713_device *sdev = to_si4713_device(sd);
int rval = 0;
- if (!sdev) {
- rval = -ENODEV;
- goto exit;
- }
+ if (!sdev)
+ return -ENODEV;
- if (vm->index > 0) {
- rval = -EINVAL;
- goto exit;
- }
+ if (vm->index > 0)
+ return -EINVAL;
strncpy(vm->name, "FM Modulator", 32);
vm->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW |
@@ -1840,18 +1214,15 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm)
vm->rangelow = si4713_to_v4l2(FREQ_RANGE_LOW);
vm->rangehigh = si4713_to_v4l2(FREQ_RANGE_HIGH);
- mutex_lock(&sdev->mutex);
-
if (sdev->power_state) {
u32 comp_en = 0;
rval = si4713_read_property(sdev, SI4713_TX_COMPONENT_ENABLE,
&comp_en);
if (rval < 0)
- goto unlock;
+ return rval;
sdev->stereo = get_status_bit(comp_en, 1, 1 << 1);
- sdev->rds_info.enabled = get_status_bit(comp_en, 2, 1 << 2);
}
/* Report current audio mode: mono or stereo */
@@ -1861,14 +1232,11 @@ static int si4713_g_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm)
vm->txsubchans = V4L2_TUNER_SUB_MONO;
/* Report rds feature status */
- if (sdev->rds_info.enabled)
+ if (sdev->rds_enabled)
vm->txsubchans |= V4L2_TUNER_SUB_RDS;
else
vm->txsubchans &= ~V4L2_TUNER_SUB_RDS;
-unlock:
- mutex_unlock(&sdev->mutex);
-exit:
return rval;
}
@@ -1896,13 +1264,11 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato
rds = !!(vm->txsubchans & V4L2_TUNER_SUB_RDS);
- mutex_lock(&sdev->mutex);
-
if (sdev->power_state) {
rval = si4713_read_property(sdev,
SI4713_TX_COMPONENT_ENABLE, &p);
if (rval < 0)
- goto unlock;
+ return rval;
p = set_bits(p, stereo, 1, 1 << 1);
p = set_bits(p, rds, 2, 1 << 2);
@@ -1910,14 +1276,12 @@ static int si4713_s_modulator(struct v4l2_subdev *sd, const struct v4l2_modulato
rval = si4713_write_property(sdev,
SI4713_TX_COMPONENT_ENABLE, p);
if (rval < 0)
- goto unlock;
+ return rval;
}
sdev->stereo = stereo;
- sdev->rds_info.enabled = rds;
+ sdev->rds_enabled = rds;
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
@@ -1927,9 +1291,8 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
struct si4713_device *sdev = to_si4713_device(sd);
int rval = 0;
- f->type = V4L2_TUNER_RADIO;
-
- mutex_lock(&sdev->mutex);
+ if (f->tuner)
+ return -EINVAL;
if (sdev->power_state) {
u16 freq;
@@ -1937,46 +1300,49 @@ static int si4713_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
rval = si4713_tx_tune_status(sdev, 0x00, &freq, &p, &a, &n);
if (rval < 0)
- goto unlock;
+ return rval;
sdev->frequency = freq;
}
f->frequency = si4713_to_v4l2(sdev->frequency);
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
/* si4713_s_frequency - set tuner or modulator radio frequency */
-static int si4713_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
+static int si4713_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *f)
{
struct si4713_device *sdev = to_si4713_device(sd);
int rval = 0;
u16 frequency = v4l2_to_si4713(f->frequency);
- /* Check frequency range */
- if (frequency < FREQ_RANGE_LOW || frequency > FREQ_RANGE_HIGH)
- return -EDOM;
+ if (f->tuner)
+ return -EINVAL;
- mutex_lock(&sdev->mutex);
+ /* Check frequency range */
+ frequency = clamp_t(u16, frequency, FREQ_RANGE_LOW, FREQ_RANGE_HIGH);
if (sdev->power_state) {
rval = si4713_tx_tune_freq(sdev, frequency);
if (rval < 0)
- goto unlock;
+ return rval;
frequency = rval;
rval = 0;
}
sdev->frequency = frequency;
- f->frequency = si4713_to_v4l2(frequency);
-unlock:
- mutex_unlock(&sdev->mutex);
return rval;
}
+static const struct v4l2_ctrl_ops si4713_ctrl_ops = {
+ .s_ctrl = si4713_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops si4713_subdev_core_ops = {
+ .ioctl = si4713_ioctl,
+};
+
static const struct v4l2_subdev_tuner_ops si4713_subdev_tuner_ops = {
.g_frequency = si4713_g_frequency,
.s_frequency = si4713_s_frequency,
@@ -1998,9 +1364,10 @@ static int si4713_probe(struct i2c_client *client,
{
struct si4713_device *sdev;
struct si4713_platform_data *pdata = client->dev.platform_data;
+ struct v4l2_ctrl_handler *hdl;
int rval, i;
- sdev = kzalloc(sizeof *sdev, GFP_KERNEL);
+ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
if (!sdev) {
dev_err(&client->dev, "Failed to alloc video device.\n");
rval = -ENOMEM;
@@ -2017,13 +1384,14 @@ static int si4713_probe(struct i2c_client *client,
}
sdev->gpio_reset = pdata->gpio_reset;
gpio_direction_output(sdev->gpio_reset, 0);
+ sdev->supplies = pdata->supplies;
}
- for (i = 0; i < ARRAY_SIZE(sdev->supplies); i++)
- sdev->supplies[i].supply = si4713_supply_names[i];
+ for (i = 0; i < sdev->supplies; i++)
+ sdev->supply_data[i].supply = pdata->supply_names[i];
- rval = regulator_bulk_get(&client->dev, ARRAY_SIZE(sdev->supplies),
- sdev->supplies);
+ rval = regulator_bulk_get(&client->dev, sdev->supplies,
+ sdev->supply_data);
if (rval) {
dev_err(&client->dev, "Cannot get regulators: %d\n", rval);
goto free_gpio;
@@ -2031,12 +1399,89 @@ static int si4713_probe(struct i2c_client *client,
v4l2_i2c_subdev_init(&sdev->sd, client, &si4713_subdev_ops);
- mutex_init(&sdev->mutex);
init_completion(&sdev->work);
+ hdl = &sdev->ctrl_handler;
+ v4l2_ctrl_handler_init(hdl, 20);
+ sdev->mute = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_MUTE, 0, 1, 1, DEFAULT_MUTE);
+
+ sdev->rds_pi = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_RDS_TX_PI, 0, 0xffff, 1, DEFAULT_RDS_PI);
+ sdev->rds_pty = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_RDS_TX_PTY, 0, 31, 1, DEFAULT_RDS_PTY);
+ sdev->rds_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_RDS_TX_DEVIATION, 0, MAX_RDS_DEVIATION,
+ 10, DEFAULT_RDS_DEVIATION);
+ /*
+ * Report step as 8. From RDS spec, psname
+ * should be 8. But there are receivers which scroll strings
+ * sized as 8xN.
+ */
+ sdev->rds_ps_name = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_RDS_TX_PS_NAME, 0, MAX_RDS_PS_NAME, 8, 0);
+ /*
+ * Report step as 32 (2A block). From RDS spec,
+ * radio text should be 32 for 2A block. But there are receivers
+ * which scroll strings sized as 32xN. Setting default to 32.
+ */
+ sdev->rds_radio_text = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_RDS_TX_RADIO_TEXT, 0, MAX_RDS_RADIO_TEXT, 32, 0);
+
+ sdev->limiter_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_LIMITER_ENABLED, 0, 1, 1, 1);
+ sdev->limiter_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_LIMITER_RELEASE_TIME, 250,
+ MAX_LIMITER_RELEASE_TIME, 10, DEFAULT_LIMITER_RTIME);
+ sdev->limiter_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_LIMITER_DEVIATION, 0,
+ MAX_LIMITER_DEVIATION, 10, DEFAULT_LIMITER_DEV);
+
+ sdev->compression_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_COMPRESSION_ENABLED, 0, 1, 1, 1);
+ sdev->compression_gain = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_COMPRESSION_GAIN, 0, MAX_ACOMP_GAIN, 1,
+ DEFAULT_ACOMP_GAIN);
+ sdev->compression_threshold = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_COMPRESSION_THRESHOLD,
+ MIN_ACOMP_THRESHOLD, MAX_ACOMP_THRESHOLD, 1,
+ DEFAULT_ACOMP_THRESHOLD);
+ sdev->compression_attack_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME, 0,
+ MAX_ACOMP_ATTACK_TIME, 500, DEFAULT_ACOMP_ATIME);
+ sdev->compression_release_time = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME, 100000,
+ MAX_ACOMP_RELEASE_TIME, 100000, DEFAULT_ACOMP_RTIME);
+
+ sdev->pilot_tone_enabled = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_PILOT_TONE_ENABLED, 0, 1, 1, 1);
+ sdev->pilot_tone_deviation = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_PILOT_TONE_DEVIATION, 0, MAX_PILOT_DEVIATION,
+ 10, DEFAULT_PILOT_DEVIATION);
+ sdev->pilot_tone_freq = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_PILOT_TONE_FREQUENCY, 0, MAX_PILOT_FREQUENCY,
+ 1, DEFAULT_PILOT_FREQUENCY);
+
+ sdev->tune_preemphasis = v4l2_ctrl_new_std_menu(hdl, &si4713_ctrl_ops,
+ V4L2_CID_TUNE_PREEMPHASIS,
+ V4L2_PREEMPHASIS_75_uS, 0, V4L2_PREEMPHASIS_50_uS);
+ sdev->tune_pwr_level = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_TUNE_POWER_LEVEL, 0, SI4713_MAX_POWER,
+ 1, DEFAULT_POWER_LEVEL);
+ sdev->tune_ant_cap = v4l2_ctrl_new_std(hdl, &si4713_ctrl_ops,
+ V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0, SI4713_MAX_ANTCAP,
+ 1, 0);
+
+ if (hdl->error) {
+ rval = hdl->error;
+ goto free_ctrls;
+ }
+ v4l2_ctrl_cluster(20, &sdev->mute);
+ sdev->sd.ctrl_handler = hdl;
+
if (client->irq) {
rval = request_irq(client->irq,
- si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED,
+ si4713_handler, IRQF_TRIGGER_FALLING,
client->name, sdev);
if (rval < 0) {
v4l2_err(&sdev->sd, "Could not request IRQ\n");
@@ -2058,8 +1503,10 @@ static int si4713_probe(struct i2c_client *client,
free_irq:
if (client->irq)
free_irq(client->irq, sdev);
+free_ctrls:
+ v4l2_ctrl_handler_free(hdl);
put_reg:
- regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies);
+ regulator_bulk_free(sdev->supplies, sdev->supply_data);
free_gpio:
if (gpio_is_valid(sdev->gpio_reset))
gpio_free(sdev->gpio_reset);
@@ -2082,7 +1529,8 @@ static int si4713_remove(struct i2c_client *client)
free_irq(client->irq, sdev);
v4l2_device_unregister_subdev(sd);
- regulator_bulk_free(ARRAY_SIZE(sdev->supplies), sdev->supplies);
+ v4l2_ctrl_handler_free(sd->ctrl_handler);
+ regulator_bulk_free(sdev->supplies, sdev->supply_data);
if (gpio_is_valid(sdev->gpio_reset))
gpio_free(sdev->gpio_reset);
kfree(sdev);
diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713/si4713.h
index c6dfa7fb101..4837cf6e0e1 100644
--- a/drivers/media/radio/si4713-i2c.h
+++ b/drivers/media/radio/si4713/si4713.h
@@ -15,7 +15,9 @@
#ifndef SI4713_I2C_H
#define SI4713_I2C_H
+#include <linux/regulator/consumer.h>
#include <media/v4l2-subdev.h>
+#include <media/v4l2-ctrls.h>
#include <media/si4713.h>
#define SI4713_PRODUCT_NUMBER 0x0D
@@ -160,56 +162,33 @@
#define POWER_UP 0x01
#define POWER_DOWN 0x00
-struct rds_info {
- u32 pi;
#define MAX_RDS_PTY 31
- u32 pty;
#define MAX_RDS_DEVIATION 90000
- u32 deviation;
+
/*
* 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
- u8 ps_name[MAX_RDS_PS_NAME + 1];
+
/*
* 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
- u8 radio_text[MAX_RDS_RADIO_TEXT + 1];
- u32 enabled;
-};
-struct limiter_info {
#define MAX_LIMITER_RELEASE_TIME 102390
- u32 release_time;
#define MAX_LIMITER_DEVIATION 90000
- u32 deviation;
- u32 enabled;
-};
-struct pilot_info {
#define MAX_PILOT_DEVIATION 90000
- u32 deviation;
#define MAX_PILOT_FREQUENCY 19000
- u32 frequency;
- u32 enabled;
-};
-struct acomp_info {
#define MAX_ACOMP_RELEASE_TIME 1000000
- u32 release_time;
#define MAX_ACOMP_ATTACK_TIME 5000
- u32 attack_time;
#define MAX_ACOMP_THRESHOLD 0
#define MIN_ACOMP_THRESHOLD (-40)
- s32 threshold;
#define MAX_ACOMP_GAIN 20
- u32 gain;
- u32 enabled;
-};
#define SI4713_NUM_SUPPLIES 2
@@ -219,21 +198,42 @@ struct acomp_info {
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 mutex mutex;
+ 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 rds_info rds_info;
- struct limiter_info limiter_info;
- struct pilot_info pilot_info;
- struct acomp_info acomp_info;
- struct regulator_bulk_data supplies[SI4713_NUM_SUPPLIES];
+ unsigned supplies;
+ struct regulator_bulk_data supply_data[SI4713_NUM_SUPPLIES];
int gpio_reset;
+ u32 power_state;
+ u32 rds_enabled;
u32 frequency;
u32 preemphasis;
- u32 mute;
- u32 power_level;
- u32 power_state;
- u32 antenna_capacitor;
u32 stereo;
u32 tune_rnl;
};
diff --git a/drivers/media/radio/tea575x.c b/drivers/media/radio/tea575x.c
new file mode 100644
index 00000000000..7c14060a40b
--- /dev/null
+++ b/drivers/media/radio/tea575x.c
@@ -0,0 +1,584 @@
+/*
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <asm/io.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 <media/tea575x.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
+MODULE_LICENSE("GPL");
+
+/*
+ * definitions
+ */
+
+#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */
+#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */
+#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 TEA575X_BIT_BAND_MW (1<<20)
+#define TEA575X_BIT_BAND_LW (2<<20)
+#define TEA575X_BIT_BAND_SW (3<<20)
+#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */
+#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */
+#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */
+#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */
+#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */
+#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */
+#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */
+#define TEA575X_BIT_DUMMY (1<<15) /* buffer */
+#define TEA575X_BIT_FREQ_MASK 0x7fff
+
+enum { BAND_FM, BAND_FM_JAPAN, 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,
+ .rangelow = 87500 * 16,
+ .rangehigh = 108000 * 16,
+ .modulation = V4L2_BAND_MODULATION_FM,
+ },
+ {
+ .type = V4L2_TUNER_RADIO,
+ .index = 0,
+ .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 76000 * 16,
+ .rangehigh = 91000 * 16,
+ .modulation = V4L2_BAND_MODULATION_FM,
+ },
+ {
+ .type = V4L2_TUNER_RADIO,
+ .index = 1,
+ .capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 530 * 16,
+ .rangehigh = 1710 * 16,
+ .modulation = V4L2_BAND_MODULATION_AM,
+ },
+};
+
+/*
+ * lowlevel part
+ */
+
+static void snd_tea575x_write(struct snd_tea575x *tea, unsigned int val)
+{
+ u16 l;
+ u8 data;
+
+ if (tea->ops->write_val)
+ return tea->ops->write_val(tea, val);
+
+ tea->ops->set_direction(tea, 1);
+ udelay(16);
+
+ for (l = 25; l > 0; l--) {
+ data = (val >> 24) & TEA575X_DATA;
+ val <<= 1; /* shift data */
+ tea->ops->set_pins(tea, data | TEA575X_WREN);
+ udelay(2);
+ tea->ops->set_pins(tea, data | TEA575X_WREN | TEA575X_CLK);
+ udelay(2);
+ tea->ops->set_pins(tea, data | TEA575X_WREN);
+ udelay(2);
+ }
+
+ if (!tea->mute)
+ tea->ops->set_pins(tea, 0);
+}
+
+static u32 snd_tea575x_read(struct snd_tea575x *tea)
+{
+ u16 l, rdata;
+ u32 data = 0;
+
+ if (tea->ops->read_val)
+ return tea->ops->read_val(tea);
+
+ tea->ops->set_direction(tea, 0);
+ tea->ops->set_pins(tea, 0);
+ udelay(16);
+
+ for (l = 24; l--;) {
+ tea->ops->set_pins(tea, TEA575X_CLK);
+ udelay(2);
+ if (!l)
+ tea->tuned = tea->ops->get_pins(tea) & TEA575X_MOST ? 0 : 1;
+ tea->ops->set_pins(tea, 0);
+ udelay(2);
+ data <<= 1; /* shift data */
+ rdata = tea->ops->get_pins(tea);
+ if (!l)
+ tea->stereo = (rdata & TEA575X_MOST) ? 0 : 1;
+ if (rdata & TEA575X_DATA)
+ data++;
+ udelay(2);
+ }
+
+ if (tea->mute)
+ tea->ops->set_pins(tea, TEA575X_WREN);
+
+ return data;
+}
+
+static u32 snd_tea575x_val_to_freq(struct snd_tea575x *tea, u32 val)
+{
+ u32 freq = val & TEA575X_BIT_FREQ_MASK;
+
+ if (freq == 0)
+ return freq;
+
+ switch (tea->band) {
+ case BAND_FM:
+ /* freq *= 12.5 */
+ freq *= 125;
+ freq /= 10;
+ /* crystal fixup */
+ freq -= TEA575X_FMIF;
+ break;
+ case BAND_FM_JAPAN:
+ /* freq *= 12.5 */
+ freq *= 125;
+ freq /= 10;
+ /* crystal fixup */
+ freq += TEA575X_FMIF;
+ break;
+ case BAND_AM:
+ /* crystal fixup */
+ freq -= TEA575X_AMIF;
+ break;
+ }
+
+ return clamp(freq * 16, bands[tea->band].rangelow,
+ bands[tea->band].rangehigh); /* from kHz */
+}
+
+static u32 snd_tea575x_get_freq(struct snd_tea575x *tea)
+{
+ return snd_tea575x_val_to_freq(tea, snd_tea575x_read(tea));
+}
+
+void snd_tea575x_set_freq(struct snd_tea575x *tea)
+{
+ u32 freq = tea->freq / 16; /* to kHz */
+ u32 band = 0;
+
+ switch (tea->band) {
+ case BAND_FM:
+ band = TEA575X_BIT_BAND_FM;
+ /* crystal fixup */
+ freq += TEA575X_FMIF;
+ /* freq /= 12.5 */
+ freq *= 10;
+ freq /= 125;
+ break;
+ case BAND_FM_JAPAN:
+ band = TEA575X_BIT_BAND_FM;
+ /* crystal fixup */
+ freq -= TEA575X_FMIF;
+ /* freq /= 12.5 */
+ freq *= 10;
+ freq /= 125;
+ break;
+ case BAND_AM:
+ band = TEA575X_BIT_BAND_MW;
+ /* crystal fixup */
+ freq += TEA575X_AMIF;
+ break;
+ }
+
+ tea->val &= ~(TEA575X_BIT_FREQ_MASK | TEA575X_BIT_BAND_MASK);
+ tea->val |= band;
+ tea->val |= freq & TEA575X_BIT_FREQ_MASK;
+ snd_tea575x_write(tea, tea->val);
+ tea->freq = snd_tea575x_val_to_freq(tea, tea->val);
+}
+
+/*
+ * Linux Video interface
+ */
+
+static int vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *v)
+{
+ struct snd_tea575x *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, tea->tea5759 ? " TEA5759" : " TEA5757", sizeof(v->card));
+ strlcpy(v->bus_info, tea->bus_info, sizeof(v->bus_info));
+ v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+ if (!tea->cannot_read_data)
+ 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 snd_tea575x *tea = video_drvdata(file);
+ int index;
+
+ if (band->tuner != 0)
+ return -EINVAL;
+
+ switch (band->index) {
+ case 0:
+ if (tea->tea5759)
+ index = BAND_FM_JAPAN;
+ else
+ index = BAND_FM;
+ break;
+ case 1:
+ if (tea->has_am) {
+ index = BAND_AM;
+ break;
+ }
+ /* Fall through */
+ default:
+ return -EINVAL;
+ }
+
+ *band = bands[index];
+ if (!tea->cannot_read_data)
+ band->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
+
+ return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *v)
+{
+ struct snd_tea575x *tea = video_drvdata(file);
+ struct v4l2_frequency_band band_fm = { 0, };
+
+ if (v->index > 0)
+ return -EINVAL;
+
+ snd_tea575x_read(tea);
+ vidioc_enum_freq_bands(file, priv, &band_fm);
+
+ memset(v, 0, sizeof(*v));
+ strlcpy(v->name, tea->has_am ? "FM/AM" : "FM", sizeof(v->name));
+ v->type = V4L2_TUNER_RADIO;
+ v->capability = band_fm.capability;
+ v->rangelow = tea->has_am ? bands[BAND_AM].rangelow : band_fm.rangelow;
+ v->rangehigh = band_fm.rangehigh;
+ v->rxsubchans = tea->stereo ? V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO;
+ v->audmode = (tea->val & TEA575X_BIT_MONO) ?
+ V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO;
+ v->signal = tea->tuned ? 0xffff : 0;
+ return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+ const struct v4l2_tuner *v)
+{
+ struct snd_tea575x *tea = video_drvdata(file);
+ u32 orig_val = tea->val;
+
+ if (v->index)
+ return -EINVAL;
+ tea->val &= ~TEA575X_BIT_MONO;
+ if (v->audmode == V4L2_TUNER_MODE_MONO)
+ tea->val |= TEA575X_BIT_MONO;
+ /* Only apply changes if currently tuning FM */
+ if (tea->band != BAND_AM && tea->val != orig_val)
+ snd_tea575x_set_freq(tea);
+
+ return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ struct snd_tea575x *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 snd_tea575x *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 if (tea->tea5759)
+ tea->band = BAND_FM_JAPAN;
+ else
+ tea->band = BAND_FM;
+
+ tea->freq = clamp_t(u32, f->frequency, bands[tea->band].rangelow,
+ bands[tea->band].rangehigh);
+ snd_tea575x_set_freq(tea);
+ return 0;
+}
+
+static int vidioc_s_hw_freq_seek(struct file *file, void *fh,
+ const struct v4l2_hw_freq_seek *a)
+{
+ struct snd_tea575x *tea = video_drvdata(file);
+ unsigned long timeout;
+ int i, spacing;
+
+ if (tea->cannot_read_data)
+ return -ENOTTY;
+ if (a->tuner || a->wrap_around)
+ return -EINVAL;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ if (a->rangelow || a->rangehigh) {
+ for (i = 0; i < ARRAY_SIZE(bands); i++) {
+ if ((i == BAND_FM && tea->tea5759) ||
+ (i == BAND_FM_JAPAN && !tea->tea5759) ||
+ (i == BAND_AM && !tea->has_am))
+ continue;
+ if (bands[i].rangelow == a->rangelow &&
+ bands[i].rangehigh == a->rangehigh)
+ break;
+ }
+ if (i == ARRAY_SIZE(bands))
+ return -EINVAL; /* No matching band found */
+ if (i != tea->band) {
+ tea->band = i;
+ tea->freq = clamp(tea->freq, bands[i].rangelow,
+ bands[i].rangehigh);
+ snd_tea575x_set_freq(tea);
+ }
+ }
+
+ spacing = (tea->band == BAND_AM) ? 5 : 50; /* kHz */
+
+ /* clear the frequency, HW will fill it in */
+ tea->val &= ~TEA575X_BIT_FREQ_MASK;
+ tea->val |= TEA575X_BIT_SEARCH;
+ if (a->seek_upward)
+ tea->val |= TEA575X_BIT_UPDOWN;
+ else
+ tea->val &= ~TEA575X_BIT_UPDOWN;
+ snd_tea575x_write(tea, tea->val);
+ timeout = jiffies + msecs_to_jiffies(10000);
+ for (;;) {
+ if (time_after(jiffies, timeout))
+ break;
+ if (schedule_timeout_interruptible(msecs_to_jiffies(10))) {
+ /* some signal arrived, stop search */
+ tea->val &= ~TEA575X_BIT_SEARCH;
+ snd_tea575x_set_freq(tea);
+ return -ERESTARTSYS;
+ }
+ if (!(snd_tea575x_read(tea) & TEA575X_BIT_SEARCH)) {
+ u32 freq;
+
+ /* Found a frequency, wait until it can be read */
+ for (i = 0; i < 100; i++) {
+ msleep(10);
+ freq = snd_tea575x_get_freq(tea);
+ if (freq) /* available */
+ break;
+ }
+ if (freq == 0) /* shouldn't happen */
+ break;
+ /*
+ * if we moved by less than the spacing, or in the
+ * wrong direction, continue seeking
+ */
+ if (abs(tea->freq - freq) < 16 * spacing ||
+ (a->seek_upward && freq < tea->freq) ||
+ (!a->seek_upward && freq > tea->freq)) {
+ snd_tea575x_write(tea, tea->val);
+ continue;
+ }
+ tea->freq = freq;
+ tea->val &= ~TEA575X_BIT_SEARCH;
+ return 0;
+ }
+ }
+ tea->val &= ~TEA575X_BIT_SEARCH;
+ snd_tea575x_set_freq(tea);
+ return -ENODATA;
+}
+
+static int tea575x_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ tea->mute = ctrl->val;
+ snd_tea575x_set_freq(tea);
+ return 0;
+ }
+
+ 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 snd_tea575x_hw_init(struct snd_tea575x *tea)
+{
+ tea->mute = true;
+
+ /* Not all devices can or know how to read the data back.
+ Such devices can set cannot_read_data to true. */
+ if (!tea->cannot_read_data) {
+ snd_tea575x_write(tea, 0x55AA);
+ if (snd_tea575x_read(tea) != 0x55AA)
+ return -ENODEV;
+ }
+
+ tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_5_28;
+ tea->freq = 90500 * 16; /* 90.5Mhz default */
+ snd_tea575x_set_freq(tea);
+
+ return 0;
+}
+EXPORT_SYMBOL(snd_tea575x_hw_init);
+
+int snd_tea575x_init(struct snd_tea575x *tea, struct module *owner)
+{
+ int retval = snd_tea575x_hw_init(tea);
+
+ if (retval)
+ return retval;
+
+ 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);
+ /* disable hw_freq_seek if we can't use it */
+ if (tea->cannot_read_data)
+ v4l2_disable_ioctl(&tea->vd, VIDIOC_S_HW_FREQ_SEEK);
+
+ if (!tea->cannot_mute) {
+ 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);
+ retval = tea->ctrl_handler.error;
+ if (retval) {
+ v4l2_err(tea->v4l2_dev, "can't initialize controls\n");
+ v4l2_ctrl_handler_free(&tea->ctrl_handler);
+ return retval;
+ }
+
+ if (tea->ext_init) {
+ retval = tea->ext_init(tea);
+ if (retval) {
+ v4l2_ctrl_handler_free(&tea->ctrl_handler);
+ return retval;
+ }
+ }
+
+ v4l2_ctrl_handler_setup(&tea->ctrl_handler);
+ }
+
+ retval = video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->radio_nr);
+ if (retval) {
+ v4l2_err(tea->v4l2_dev, "can't register video device!\n");
+ v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
+ return retval;
+ }
+
+ return 0;
+}
+
+void snd_tea575x_exit(struct snd_tea575x *tea)
+{
+ video_unregister_device(&tea->vd);
+ v4l2_ctrl_handler_free(tea->vd.ctrl_handler);
+}
+
+static int __init alsa_tea575x_module_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_tea575x_module_exit(void)
+{
+}
+
+module_init(alsa_tea575x_module_init)
+module_exit(alsa_tea575x_module_exit)
+
+EXPORT_SYMBOL(snd_tea575x_init);
+EXPORT_SYMBOL(snd_tea575x_exit);
+EXPORT_SYMBOL(snd_tea575x_set_freq);
diff --git a/drivers/media/radio/tef6862.c b/drivers/media/radio/tef6862.c
index b18c2dc268b..a9319a24c7e 100644
--- a/drivers/media/radio/tef6862.c
+++ b/drivers/media/radio/tef6862.c
@@ -25,14 +25,13 @@
#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)
+#define TEF6862_LO_FREQ (875U * FREQ_MUL / 10)
+#define TEF6862_HI_FREQ (108U * FREQ_MUL)
/* Write mode sub addresses */
#define WM_SUB_BANDWIDTH 0x0
@@ -49,15 +48,15 @@
#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
+#define MSA_MODE_BUFFER 0x0
+#define MSA_MODE_PRESET 0x1
+#define MSA_MODE_SEARCH 0x2
+#define MSA_MODE_AF_UPDATE 0x3
+#define MSA_MODE_JUMP 0x4
+#define MSA_MODE_CHECK 0x5
+#define MSA_MODE_LOAD 0x6
+#define MSA_MODE_END 0x7
+#define MSA_MODE_SHIFT 5
struct tef6862_state {
struct v4l2_subdev sd;
@@ -96,15 +95,16 @@ static int tef6862_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v)
return 0;
}
-static int tef6862_s_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *v)
+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, struct v4l2_frequency *f)
+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);
+ unsigned freq = f->frequency;
u16 pll;
u8 i2cmsg[3];
int err;
@@ -112,8 +112,9 @@ static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
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;
+ freq = clamp(freq, TEF6862_LO_FREQ, TEF6862_HI_FREQ);
+ pll = 1964 + ((freq - TEF6862_LO_FREQ) * 20) / FREQ_MUL;
+ i2cmsg[0] = (MSA_MODE_PRESET << MSA_MODE_SHIFT) | WM_SUB_PLLM;
i2cmsg[1] = (pll >> 8) & 0xff;
i2cmsg[2] = pll & 0xff;
@@ -121,7 +122,7 @@ static int tef6862_s_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
if (err != sizeof(i2cmsg))
return err < 0 ? err : -EIO;
- state->freq = f->frequency;
+ state->freq = freq;
return 0;
}
@@ -136,14 +137,6 @@ static int tef6862_g_frequency(struct v4l2_subdev *sd, struct v4l2_frequency *f)
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,
@@ -151,12 +144,7 @@ static const struct v4l2_subdev_tuner_ops tef6862_tuner_ops = {
.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,
};
diff --git a/drivers/media/radio/wl128x/Kconfig b/drivers/media/radio/wl128x/Kconfig
index ea1e6545df3..f359be7e9dd 100644
--- a/drivers/media/radio/wl128x/Kconfig
+++ b/drivers/media/radio/wl128x/Kconfig
@@ -4,7 +4,7 @@
menu "Texas Instruments WL128x FM driver (ST based)"
config RADIO_WL128X
tristate "Texas Instruments WL128x FM Radio"
- depends on VIDEO_V4L2 && RFKILL && GPIOLIB
+ depends on VIDEO_V4L2 && RFKILL && GPIOLIB && TTY
select TI_ST if NET
help
Choose Y here if you have this FM radio chip.
diff --git a/drivers/media/radio/wl128x/fmdrv.h b/drivers/media/radio/wl128x/fmdrv.h
index aac0f025f76..a587c9bac93 100644
--- a/drivers/media/radio/wl128x/fmdrv.h
+++ b/drivers/media/radio/wl128x/fmdrv.h
@@ -30,6 +30,7 @@
#include <linux/timer.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
#include <media/v4l2-ctrls.h>
#define FM_DRV_VERSION "0.1.1"
@@ -202,6 +203,7 @@ struct fmtx_data {
/* FM driver operation structure */
struct fmdev {
struct video_device *radio_dev; /* V4L2 video device pointer */
+ struct v4l2_device v4l2_dev; /* V4L2 top level struct */
struct snd_card *card; /* Card which holds FM mixer controls */
u16 asci_id;
spinlock_t rds_buff_lock; /* To protect access to RDS buffer */
diff --git a/drivers/media/radio/wl128x/fmdrv_common.c b/drivers/media/radio/wl128x/fmdrv_common.c
index 602ef7ac8c2..4b2e9e8298e 100644
--- a/drivers/media/radio/wl128x/fmdrv_common.c
+++ b/drivers/media/radio/wl128x/fmdrv_common.c
@@ -175,7 +175,7 @@ static int_handler_prototype int_handler_table[] = {
fm_irq_handle_intmsk_cmd_resp
};
-long (*g_st_write) (struct sk_buff *skb);
+static long (*g_st_write) (struct sk_buff *skb);
static struct completion wait_for_fmdrv_reg_comp;
static inline void fm_irq_call(struct fmdev *fmdev)
@@ -715,7 +715,7 @@ static void fm_irq_handle_rdsdata_getcmd_resp(struct fmdev *fmdev)
struct fm_rdsdata_format rds_fmt;
struct fm_rds *rds = &fmdev->rx.rds;
unsigned long group_idx, flags;
- u8 *rds_data, meta_data, tmpbuf[3];
+ u8 *rds_data, meta_data, tmpbuf[FM_RDS_BLK_SIZE];
u8 type, blk_idx;
u16 cur_picode;
u32 rds_len;
@@ -1073,6 +1073,7 @@ int fmc_transfer_rds_from_internal_buff(struct fmdev *fmdev, struct file *file,
u8 __user *buf, size_t count)
{
u32 block_count;
+ u8 tmpbuf[FM_RDS_BLK_SIZE];
unsigned long flags;
int ret;
@@ -1087,29 +1088,32 @@ int fmc_transfer_rds_from_internal_buff(struct fmdev *fmdev, struct file *file,
}
/* Calculate block count from byte count */
- count /= 3;
+ count /= FM_RDS_BLK_SIZE;
block_count = 0;
ret = 0;
- spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
-
while (block_count < count) {
- if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx)
- break;
+ spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
- if (copy_to_user(buf, &fmdev->rx.rds.buff[fmdev->rx.rds.rd_idx],
- FM_RDS_BLK_SIZE))
+ if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx) {
+ spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
break;
-
+ }
+ memcpy(tmpbuf, &fmdev->rx.rds.buff[fmdev->rx.rds.rd_idx],
+ FM_RDS_BLK_SIZE);
fmdev->rx.rds.rd_idx += FM_RDS_BLK_SIZE;
if (fmdev->rx.rds.rd_idx >= fmdev->rx.rds.buf_size)
fmdev->rx.rds.rd_idx = 0;
+ spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
+
+ if (copy_to_user(buf, tmpbuf, FM_RDS_BLK_SIZE))
+ break;
+
block_count++;
buf += FM_RDS_BLK_SIZE;
ret += FM_RDS_BLK_SIZE;
}
- spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
return ret;
}
@@ -1563,8 +1567,7 @@ int fmc_prepare(struct fmdev *fmdev)
fmdev->irq_info.mask = FM_MAL_EVENT;
/* Region info */
- memcpy(&fmdev->rx.region, &region_configs[default_radio_region],
- sizeof(struct region_info));
+ fmdev->rx.region = region_configs[default_radio_region];
fmdev->rx.mute_mode = FM_MUTE_OFF;
fmdev->rx.rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF;
diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c
index 048de453603..b55012c1184 100644
--- a/drivers/media/radio/wl128x/fmdrv_v4l2.c
+++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c
@@ -331,7 +331,7 @@ static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv,
* Should we set other tuner attributes, too?
*/
static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
+ const struct v4l2_tuner *tuner)
{
struct fmdev *fmdev = video_drvdata(file);
u16 aud_mode;
@@ -388,7 +388,7 @@ static int fm_v4l2_vidioc_g_freq(struct file *file, void *priv,
/* Set tuner or modulator radio frequency */
static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv,
- struct v4l2_frequency *freq)
+ const struct v4l2_frequency *freq)
{
struct fmdev *fmdev = video_drvdata(file);
@@ -396,9 +396,7 @@ static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv,
* As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency
* in units of 62.5 Hz.
*/
- freq->frequency = (u32)(freq->frequency / 16);
-
- return fmc_set_freq(fmdev, freq->frequency);
+ return fmc_set_freq(fmdev, freq->frequency / 16);
}
/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */
@@ -518,6 +516,16 @@ static struct video_device fm_viddev_template = {
.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)
@@ -525,6 +533,11 @@ int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr)
struct v4l2_ctrl *ctrl;
int ret;
+ strlcpy(fmdev->v4l2_dev.name, FM_DRV_NAME, sizeof(fmdev->v4l2_dev.name));
+ ret = v4l2_device_register(NULL, &fmdev->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
/* Init mutex for core locking */
mutex_init(&fmdev->mutex);
@@ -541,6 +554,7 @@ int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr)
video_set_drvdata(gradio_dev, fmdev);
gradio_dev->lock = &fmdev->mutex;
+ gradio_dev->v4l2_dev = &fmdev->v4l2_dev;
/* Register with V4L2 subsystem as RADIO device */
if (video_register_device(gradio_dev, VFL_TYPE_RADIO, radio_nr)) {
@@ -603,5 +617,7 @@ void *fm_v4l2_deinit_video_device(void)
/* Unregister RADIO device from V4L2 subsystem */
video_unregister_device(gradio_dev);
+ v4l2_device_unregister(&fmdev->v4l2_dev);
+
return fmdev;
}