summaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/Kconfig59
-rw-r--r--drivers/media/radio/Makefile4
-rw-r--r--drivers/media/radio/radio-cadet.c6
-rw-r--r--drivers/media/radio/radio-si470x.c1863
-rw-r--r--drivers/media/radio/radio-si4713.c367
-rw-r--r--drivers/media/radio/si470x/Kconfig37
-rw-r--r--drivers/media/radio/si470x/Makefile9
-rw-r--r--drivers/media/radio/si470x/radio-si470x-common.c798
-rw-r--r--drivers/media/radio/si470x/radio-si470x-i2c.c401
-rw-r--r--drivers/media/radio/si470x/radio-si470x-usb.c988
-rw-r--r--drivers/media/radio/si470x/radio-si470x.h225
-rw-r--r--drivers/media/radio/si4713-i2c.c2060
-rw-r--r--drivers/media/radio/si4713-i2c.h237
13 files changed, 5156 insertions, 1898 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 3315cac875e..25a36ad60c5 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -288,16 +288,6 @@ config RADIO_TYPHOON
To compile this driver as a module, choose M here: the
module will be called radio-typhoon.
-config RADIO_TYPHOON_PROC_FS
- bool "Support for /proc/radio-typhoon"
- depends on PROC_FS && RADIO_TYPHOON
- help
- Say Y here if you want the typhoon radio card driver to write
- status information (frequency, volume, muted, mute frequency,
- base address) to /proc/radio-typhoon. The file can be viewed with
- your favorite pager (i.e. use "more /proc/radio-typhoon" or "less
- /proc/radio-typhoon" or simply "cat /proc/radio-typhoon").
-
config RADIO_TYPHOON_PORT
hex "Typhoon I/O port (0x316 or 0x336)"
depends on RADIO_TYPHOON=y
@@ -339,6 +329,29 @@ config RADIO_ZOLTRIX_PORT
help
Enter the I/O port of your Zoltrix radio card.
+config I2C_SI4713
+ tristate "I2C driver for Silicon Labs Si4713 device"
+ depends on I2C && VIDEO_V4L2
+ ---help---
+ Say Y here if you want support to Si4713 I2C device.
+ This device driver supports only i2c bus.
+
+ To compile this driver as a module, choose M here: the
+ module will be called si4713.
+
+config RADIO_SI4713
+ tristate "Silicon Labs Si4713 FM Radio Transmitter support"
+ depends on I2C && VIDEO_V4L2
+ select I2C_SI4713
+ ---help---
+ Say Y here if you want support to Si4713 FM Radio Transmitter.
+ This device can transmit audio through FM. It can transmit
+ EDS and EBDS signals as well. This module is the v4l2 radio
+ interface for the i2c driver of this device.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-si4713.
+
config USB_DSBR
tristate "D-Link/GemTek USB FM radio support"
depends on USB && VIDEO_V4L2
@@ -351,29 +364,11 @@ config USB_DSBR
To compile this driver as a module, choose M here: the
module will be called dsbr100.
-config USB_SI470X
- tristate "Silicon Labs Si470x FM Radio Receiver support"
- depends on USB && VIDEO_V4L2
- ---help---
- This is a driver for USB devices with the Silicon Labs SI470x
- chip. Currently these devices are known to work:
- - 10c4:818a: Silicon Labs USB FM Radio Reference Design
- - 06e1:a155: ADS/Tech FM Radio Receiver (formerly Instant FM Music)
- - 1b80:d700: KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
-
- Sound is provided by the ALSA USB Audio/MIDI driver. Therefore
- if you don't want to use the device solely for RDS receiving,
- it is recommended to also select SND_USB_AUDIO.
-
- Please have a look at the documentation, especially on how
- to redirect the audio stream from the radio to your sound device:
- Documentation/video4linux/si470x.txt
-
- Say Y here if you want to connect this type of radio to your
- computer's USB port.
+config RADIO_SI470X
+ bool "Silicon Labs Si470x FM Radio Receiver support"
+ depends on VIDEO_V4L2
- To compile this driver as a module, choose M here: the
- module will be called radio-si470x.
+source "drivers/media/radio/si470x/Kconfig"
config USB_MR800
tristate "AverMedia MR 800 USB FM radio support"
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index 0f2b35b3e56..2a1be3bf4f7 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -15,9 +15,11 @@ obj-$(CONFIG_RADIO_ZOLTRIX) += radio-zoltrix.o
obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o
obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
+obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o
+obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o
obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o
obj-$(CONFIG_USB_DSBR) += dsbr100.o
-obj-$(CONFIG_USB_SI470X) += radio-si470x.o
+obj-$(CONFIG_RADIO_SI470X) += si470x/
obj-$(CONFIG_USB_MR800) += radio-mr800.o
obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
diff --git a/drivers/media/radio/radio-cadet.c b/drivers/media/radio/radio-cadet.c
index d30fc0ce82c..8b1440136c4 100644
--- a/drivers/media/radio/radio-cadet.c
+++ b/drivers/media/radio/radio-cadet.c
@@ -359,7 +359,8 @@ static int vidioc_querycap(struct file *file, void *priv,
strlcpy(v->card, "ADS Cadet", sizeof(v->card));
strlcpy(v->bus_info, "ISA", sizeof(v->bus_info));
v->version = CADET_VERSION;
- v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_READWRITE;
+ v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
+ V4L2_CAP_READWRITE | V4L2_CAP_RDS_CAPTURE;
return 0;
}
@@ -372,7 +373,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
switch (v->index) {
case 0:
strlcpy(v->name, "FM", sizeof(v->name));
- v->capability = V4L2_TUNER_CAP_STEREO;
+ v->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS;
v->rangelow = 1400; /* 87.5 MHz */
v->rangehigh = 1728; /* 108.0 MHz */
v->rxsubchans = cadet_getstereo(dev);
@@ -386,6 +387,7 @@ static int vidioc_g_tuner(struct file *file, void *priv,
default:
break;
}
+ v->rxsubchans |= V4L2_TUNER_SUB_RDS;
break;
case 1:
strlcpy(v->name, "AM", sizeof(v->name));
diff --git a/drivers/media/radio/radio-si470x.c b/drivers/media/radio/radio-si470x.c
deleted file mode 100644
index e85f318b4d2..00000000000
--- a/drivers/media/radio/radio-si470x.c
+++ /dev/null
@@ -1,1863 +0,0 @@
-/*
- * drivers/media/radio/radio-si470x.c
- *
- * Driver for USB radios for the Silicon Labs Si470x FM Radio Receivers:
- * - Silicon Labs USB FM Radio Reference Design
- * - ADS/Tech FM Radio Receiver (formerly Instant FM Music) (RDX-155-EF)
- * - KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
- * - Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear)
- *
- * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-
-/*
- * History:
- * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.0
- * - First working version
- * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.1
- * - Improved error handling, every function now returns errno
- * - Improved multi user access (start/mute/stop)
- * - Channel doesn't get lost anymore after start/mute/stop
- * - RDS support added (polling mode via interrupt EP 1)
- * - marked default module parameters with *value*
- * - switched from bit structs to bit masks
- * - header file cleaned and integrated
- * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.2
- * - hex values are now lower case
- * - commented USB ID for ADS/Tech moved on todo list
- * - blacklisted si470x in hid-quirks.c
- * - rds buffer handling functions integrated into *_work, *_read
- * - rds_command in si470x_poll exchanged against simple retval
- * - check for firmware version 15
- * - code order and prototypes still remain the same
- * - spacing and bottom of band codes remain the same
- * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.3
- * - code reordered to avoid function prototypes
- * - switch/case defaults are now more user-friendly
- * - unified comment style
- * - applied all checkpatch.pl v1.12 suggestions
- * except the warning about the too long lines with bit comments
- * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
- * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.4
- * - avoid poss. locking when doing copy_to_user which may sleep
- * - RDS is automatically activated on read now
- * - code cleaned of unnecessary rds_commands
- * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
- * (thanks to Guillaume RAMOUSSE)
- * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.5
- * - number of seek_retries changed to tune_timeout
- * - fixed problem with incomplete tune operations by own buffers
- * - optimization of variables and printf types
- * - improved error logging
- * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Oliver Neukum <oliver@neukum.org>
- * Version 1.0.6
- * - fixed coverity checker warnings in *_usb_driver_disconnect
- * - probe()/open() race by correct ordering in probe()
- * - DMA coherency rules by separate allocation of all buffers
- * - use of endianness macros
- * - abuse of spinlock, replaced by mutex
- * - racy handling of timer in disconnect,
- * replaced by delayed_work
- * - racy interruptible_sleep_on(),
- * replaced with wait_event_interruptible()
- * - handle signals in read()
- * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Oliver Neukum <oliver@neukum.org>
- * Version 1.0.7
- * - usb autosuspend support
- * - unplugging fixed
- * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net>
- * Version 1.0.8
- * - hardware frequency seek support
- * - afc indication
- * - more safety checks, let si470x_get_freq return errno
- * - vidioc behavior corrected according to v4l2 spec
- * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com>
- * - add support for KWorld USB FM Radio FM700
- * - blacklisted KWorld radio in hid-core.c and hid-ids.h
- * 2008-12-03 Mark Lord <mlord@pobox.com>
- * - add support for DealExtreme USB Radio
- * 2009-01-31 Bob Ross <pigiron@gmx.com>
- * - correction of stereo detection/setting
- * - correction of signal strength indicator scaling
- * 2009-01-31 Rick Bronson <rick@efn.org>
- * Tobias Lorenz <tobias.lorenz@gmx.net>
- * - add LED status output
- * - get HW/SW version from scratchpad
- *
- * ToDo:
- * - add firmware download/update support
- * - RDS support: interrupt mode, instead of polling
- */
-
-
-/* driver definitions */
-#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
-#define DRIVER_NAME "radio-si470x"
-#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 9)
-#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
-#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
-#define DRIVER_VERSION "1.0.9"
-
-
-/* kernel includes */
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/smp_lock.h>
-#include <linux/input.h>
-#include <linux/usb.h>
-#include <linux/hid.h>
-#include <linux/version.h>
-#include <linux/videodev2.h>
-#include <linux/mutex.h>
-#include <media/v4l2-common.h>
-#include <media/v4l2-ioctl.h>
-#include <media/rds.h>
-#include <asm/unaligned.h>
-
-
-/* USB Device ID List */
-static struct usb_device_id si470x_usb_driver_id_table[] = {
- /* Silicon Labs USB FM Radio Reference Design */
- { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
- /* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
- { USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
- /* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
- { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
- /* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
- { USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
- /* Terminating entry */
- { }
-};
-MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
-
-
-
-/**************************************************************************
- * Module Parameters
- **************************************************************************/
-
-/* Radio Nr */
-static int radio_nr = -1;
-module_param(radio_nr, int, 0444);
-MODULE_PARM_DESC(radio_nr, "Radio Nr");
-
-/* Spacing (kHz) */
-/* 0: 200 kHz (USA, Australia) */
-/* 1: 100 kHz (Europe, Japan) */
-/* 2: 50 kHz */
-static unsigned short space = 2;
-module_param(space, ushort, 0444);
-MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");
-
-/* Bottom of Band (MHz) */
-/* 0: 87.5 - 108 MHz (USA, Europe)*/
-/* 1: 76 - 108 MHz (Japan wide band) */
-/* 2: 76 - 90 MHz (Japan) */
-static unsigned short band = 1;
-module_param(band, ushort, 0444);
-MODULE_PARM_DESC(band, "Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz");
-
-/* De-emphasis */
-/* 0: 75 us (USA) */
-/* 1: 50 us (Europe, Australia, Japan) */
-static unsigned short de = 1;
-module_param(de, ushort, 0444);
-MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");
-
-/* USB timeout */
-static unsigned int usb_timeout = 500;
-module_param(usb_timeout, uint, 0644);
-MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
-
-/* Tune timeout */
-static unsigned int tune_timeout = 3000;
-module_param(tune_timeout, uint, 0644);
-MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
-
-/* Seek timeout */
-static unsigned int seek_timeout = 5000;
-module_param(seek_timeout, uint, 0644);
-MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
-
-/* RDS buffer blocks */
-static unsigned int rds_buf = 100;
-module_param(rds_buf, uint, 0444);
-MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
-
-/* RDS maximum block errors */
-static unsigned short max_rds_errors = 1;
-/* 0 means 0 errors requiring correction */
-/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
-/* 2 means 3-5 errors requiring correction */
-/* 3 means 6+ errors or errors in checkword, correction not possible */
-module_param(max_rds_errors, ushort, 0644);
-MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
-
-/* RDS poll frequency */
-static unsigned int rds_poll_time = 40;
-/* 40 is used by the original USBRadio.exe */
-/* 50 is used by radio-cadet */
-/* 75 should be okay */
-/* 80 is the usual RDS receive interval */
-module_param(rds_poll_time, uint, 0644);
-MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*");
-
-
-
-/**************************************************************************
- * Register Definitions
- **************************************************************************/
-#define RADIO_REGISTER_SIZE 2 /* 16 register bit width */
-#define RADIO_REGISTER_NUM 16 /* DEVICEID ... RDSD */
-#define RDS_REGISTER_NUM 6 /* STATUSRSSI ... RDSD */
-
-#define DEVICEID 0 /* Device ID */
-#define DEVICEID_PN 0xf000 /* bits 15..12: Part Number */
-#define DEVICEID_MFGID 0x0fff /* bits 11..00: Manufacturer ID */
-
-#define CHIPID 1 /* Chip ID */
-#define CHIPID_REV 0xfc00 /* bits 15..10: Chip Version */
-#define CHIPID_DEV 0x0200 /* bits 09..09: Device */
-#define CHIPID_FIRMWARE 0x01ff /* bits 08..00: Firmware Version */
-
-#define POWERCFG 2 /* Power Configuration */
-#define POWERCFG_DSMUTE 0x8000 /* bits 15..15: Softmute Disable */
-#define POWERCFG_DMUTE 0x4000 /* bits 14..14: Mute Disable */
-#define POWERCFG_MONO 0x2000 /* bits 13..13: Mono Select */
-#define POWERCFG_RDSM 0x0800 /* bits 11..11: RDS Mode (Si4701 only) */
-#define POWERCFG_SKMODE 0x0400 /* bits 10..10: Seek Mode */
-#define POWERCFG_SEEKUP 0x0200 /* bits 09..09: Seek Direction */
-#define POWERCFG_SEEK 0x0100 /* bits 08..08: Seek */
-#define POWERCFG_DISABLE 0x0040 /* bits 06..06: Powerup Disable */
-#define POWERCFG_ENABLE 0x0001 /* bits 00..00: Powerup Enable */
-
-#define CHANNEL 3 /* Channel */
-#define CHANNEL_TUNE 0x8000 /* bits 15..15: Tune */
-#define CHANNEL_CHAN 0x03ff /* bits 09..00: Channel Select */
-
-#define SYSCONFIG1 4 /* System Configuration 1 */
-#define SYSCONFIG1_RDSIEN 0x8000 /* bits 15..15: RDS Interrupt Enable (Si4701 only) */
-#define SYSCONFIG1_STCIEN 0x4000 /* bits 14..14: Seek/Tune Complete Interrupt Enable */
-#define SYSCONFIG1_RDS 0x1000 /* bits 12..12: RDS Enable (Si4701 only) */
-#define SYSCONFIG1_DE 0x0800 /* bits 11..11: De-emphasis (0=75us 1=50us) */
-#define SYSCONFIG1_AGCD 0x0400 /* bits 10..10: AGC Disable */
-#define SYSCONFIG1_BLNDADJ 0x00c0 /* bits 07..06: Stereo/Mono Blend Level Adjustment */
-#define SYSCONFIG1_GPIO3 0x0030 /* bits 05..04: General Purpose I/O 3 */
-#define SYSCONFIG1_GPIO2 0x000c /* bits 03..02: General Purpose I/O 2 */
-#define SYSCONFIG1_GPIO1 0x0003 /* bits 01..00: General Purpose I/O 1 */
-
-#define SYSCONFIG2 5 /* System Configuration 2 */
-#define SYSCONFIG2_SEEKTH 0xff00 /* bits 15..08: RSSI Seek Threshold */
-#define SYSCONFIG2_BAND 0x0080 /* bits 07..06: Band Select */
-#define SYSCONFIG2_SPACE 0x0030 /* bits 05..04: Channel Spacing */
-#define SYSCONFIG2_VOLUME 0x000f /* bits 03..00: Volume */
-
-#define SYSCONFIG3 6 /* System Configuration 3 */
-#define SYSCONFIG3_SMUTER 0xc000 /* bits 15..14: Softmute Attack/Recover Rate */
-#define SYSCONFIG3_SMUTEA 0x3000 /* bits 13..12: Softmute Attenuation */
-#define SYSCONFIG3_SKSNR 0x00f0 /* bits 07..04: Seek SNR Threshold */
-#define SYSCONFIG3_SKCNT 0x000f /* bits 03..00: Seek FM Impulse Detection Threshold */
-
-#define TEST1 7 /* Test 1 */
-#define TEST1_AHIZEN 0x4000 /* bits 14..14: Audio High-Z Enable */
-
-#define TEST2 8 /* Test 2 */
-/* TEST2 only contains reserved bits */
-
-#define BOOTCONFIG 9 /* Boot Configuration */
-/* BOOTCONFIG only contains reserved bits */
-
-#define STATUSRSSI 10 /* Status RSSI */
-#define STATUSRSSI_RDSR 0x8000 /* bits 15..15: RDS Ready (Si4701 only) */
-#define STATUSRSSI_STC 0x4000 /* bits 14..14: Seek/Tune Complete */
-#define STATUSRSSI_SF 0x2000 /* bits 13..13: Seek Fail/Band Limit */
-#define STATUSRSSI_AFCRL 0x1000 /* bits 12..12: AFC Rail */
-#define STATUSRSSI_RDSS 0x0800 /* bits 11..11: RDS Synchronized (Si4701 only) */
-#define STATUSRSSI_BLERA 0x0600 /* bits 10..09: RDS Block A Errors (Si4701 only) */
-#define STATUSRSSI_ST 0x0100 /* bits 08..08: Stereo Indicator */
-#define STATUSRSSI_RSSI 0x00ff /* bits 07..00: RSSI (Received Signal Strength Indicator) */
-
-#define READCHAN 11 /* Read Channel */
-#define READCHAN_BLERB 0xc000 /* bits 15..14: RDS Block D Errors (Si4701 only) */
-#define READCHAN_BLERC 0x3000 /* bits 13..12: RDS Block C Errors (Si4701 only) */
-#define READCHAN_BLERD 0x0c00 /* bits 11..10: RDS Block B Errors (Si4701 only) */
-#define READCHAN_READCHAN 0x03ff /* bits 09..00: Read Channel */
-
-#define RDSA 12 /* RDSA */
-#define RDSA_RDSA 0xffff /* bits 15..00: RDS Block A Data (Si4701 only) */
-
-#define RDSB 13 /* RDSB */
-#define RDSB_RDSB 0xffff /* bits 15..00: RDS Block B Data (Si4701 only) */
-
-#define RDSC 14 /* RDSC */
-#define RDSC_RDSC 0xffff /* bits 15..00: RDS Block C Data (Si4701 only) */
-
-#define RDSD 15 /* RDSD */
-#define RDSD_RDSD 0xffff /* bits 15..00: RDS Block D Data (Si4701 only) */
-
-
-
-/**************************************************************************
- * USB HID Reports
- **************************************************************************/
-
-/* Reports 1-16 give direct read/write access to the 16 Si470x registers */
-/* with the (REPORT_ID - 1) corresponding to the register address across USB */
-/* endpoint 0 using GET_REPORT and SET_REPORT */
-#define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1)
-#define REGISTER_REPORT(reg) ((reg) + 1)
-
-/* Report 17 gives direct read/write access to the entire Si470x register */
-/* map across endpoint 0 using GET_REPORT and SET_REPORT */
-#define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
-#define ENTIRE_REPORT 17
-
-/* Report 18 is used to send the lowest 6 Si470x registers up the HID */
-/* interrupt endpoint 1 to Windows every 20 milliseconds for status */
-#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
-#define RDS_REPORT 18
-
-/* Report 19: LED state */
-#define LED_REPORT_SIZE 3
-#define LED_REPORT 19
-
-/* Report 19: stream */
-#define STREAM_REPORT_SIZE 3
-#define STREAM_REPORT 19
-
-/* Report 20: scratch */
-#define SCRATCH_PAGE_SIZE 63
-#define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1)
-#define SCRATCH_REPORT 20
-
-/* Reports 19-22: flash upgrade of the C8051F321 */
-#define WRITE_REPORT_SIZE 4
-#define WRITE_REPORT 19
-#define FLASH_REPORT_SIZE 64
-#define FLASH_REPORT 20
-#define CRC_REPORT_SIZE 3
-#define CRC_REPORT 21
-#define RESPONSE_REPORT_SIZE 2
-#define RESPONSE_REPORT 22
-
-/* Report 23: currently unused, but can accept 60 byte reports on the HID */
-/* interrupt out endpoint 2 every 1 millisecond */
-#define UNUSED_REPORT 23
-
-
-
-/**************************************************************************
- * Software/Hardware Versions
- **************************************************************************/
-#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
-#define RADIO_SW_VERSION 7
-#define RADIO_SW_VERSION_CURRENT 15
-#define RADIO_HW_VERSION 1
-
-#define SCRATCH_PAGE_SW_VERSION 1
-#define SCRATCH_PAGE_HW_VERSION 2
-
-
-
-/**************************************************************************
- * LED State Definitions
- **************************************************************************/
-#define LED_COMMAND 0x35
-
-#define NO_CHANGE_LED 0x00
-#define ALL_COLOR_LED 0x01 /* streaming state */
-#define BLINK_GREEN_LED 0x02 /* connect state */
-#define BLINK_RED_LED 0x04
-#define BLINK_ORANGE_LED 0x10 /* disconnect state */
-#define SOLID_GREEN_LED 0x20 /* tuning/seeking state */
-#define SOLID_RED_LED 0x40 /* bootload state */
-#define SOLID_ORANGE_LED 0x80
-
-
-
-/**************************************************************************
- * Stream State Definitions
- **************************************************************************/
-#define STREAM_COMMAND 0x36
-#define STREAM_VIDPID 0x00
-#define STREAM_AUDIO 0xff
-
-
-
-/**************************************************************************
- * Bootloader / Flash Commands
- **************************************************************************/
-
-/* unique id sent to bootloader and required to put into a bootload state */
-#define UNIQUE_BL_ID 0x34
-
-/* mask for the flash data */
-#define FLASH_DATA_MASK 0x55
-
-/* bootloader commands */
-#define GET_SW_VERSION_COMMAND 0x00
-#define SET_PAGE_COMMAND 0x01
-#define ERASE_PAGE_COMMAND 0x02
-#define WRITE_PAGE_COMMAND 0x03
-#define CRC_ON_PAGE_COMMAND 0x04
-#define READ_FLASH_BYTE_COMMAND 0x05
-#define RESET_DEVICE_COMMAND 0x06
-#define GET_HW_VERSION_COMMAND 0x07
-#define BLANK 0xff
-
-/* bootloader command responses */
-#define COMMAND_OK 0x01
-#define COMMAND_FAILED 0x02
-#define COMMAND_PENDING 0x03
-
-
-
-/**************************************************************************
- * General Driver Definitions
- **************************************************************************/
-
-/*
- * si470x_device - private data
- */
-struct si470x_device {
- /* reference to USB and video device */
- struct usb_device *usbdev;
- struct usb_interface *intf;
- struct video_device *videodev;
-
- /* driver management */
- unsigned int users;
- unsigned char disconnected;
- struct mutex disconnect_lock;
-
- /* Silabs internal registers (0..15) */
- unsigned short registers[RADIO_REGISTER_NUM];
-
- /* RDS receive buffer */
- struct delayed_work work;
- wait_queue_head_t read_queue;
- struct mutex lock; /* buffer locking */
- unsigned char *buffer; /* size is always multiple of three */
- unsigned int buf_size;
- unsigned int rd_index;
- unsigned int wr_index;
-
- /* scratch page */
- unsigned char software_version;
- unsigned char hardware_version;
-};
-
-
-/*
- * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW,
- * 62.5 kHz otherwise.
- * The tuner is able to have a channel spacing of 50, 100 or 200 kHz.
- * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW
- * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000
- */
-#define FREQ_MUL (1000000 / 62.5)
-
-
-
-/**************************************************************************
- * General Driver Functions - REGISTER_REPORTs
- **************************************************************************/
-
-/*
- * si470x_get_report - receive a HID report
- */
-static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
-{
- unsigned char *report = (unsigned char *) buf;
- int retval;
-
- retval = usb_control_msg(radio->usbdev,
- usb_rcvctrlpipe(radio->usbdev, 0),
- HID_REQ_GET_REPORT,
- USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
- report[0], 2,
- buf, size, usb_timeout);
-
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": si470x_get_report: usb_control_msg returned %d\n",
- retval);
- return retval;
-}
-
-
-/*
- * si470x_set_report - send a HID report
- */
-static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
-{
- unsigned char *report = (unsigned char *) buf;
- int retval;
-
- retval = usb_control_msg(radio->usbdev,
- usb_sndctrlpipe(radio->usbdev, 0),
- HID_REQ_SET_REPORT,
- USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
- report[0], 2,
- buf, size, usb_timeout);
-
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": si470x_set_report: usb_control_msg returned %d\n",
- retval);
- return retval;
-}
-
-
-/*
- * si470x_get_register - read register
- */
-static int si470x_get_register(struct si470x_device *radio, int regnr)
-{
- unsigned char buf[REGISTER_REPORT_SIZE];
- int retval;
-
- buf[0] = REGISTER_REPORT(regnr);
-
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
-
- if (retval >= 0)
- radio->registers[regnr] = get_unaligned_be16(&buf[1]);
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-/*
- * si470x_set_register - write register
- */
-static int si470x_set_register(struct si470x_device *radio, int regnr)
-{
- unsigned char buf[REGISTER_REPORT_SIZE];
- int retval;
-
- buf[0] = REGISTER_REPORT(regnr);
- put_unaligned_be16(radio->registers[regnr], &buf[1]);
-
- retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-/*
- * si470x_set_chan - set the channel
- */
-static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
-{
- int retval;
- unsigned long timeout;
- bool timed_out = 0;
-
- /* start tuning */
- radio->registers[CHANNEL] &= ~CHANNEL_CHAN;
- radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
- retval = si470x_set_register(radio, CHANNEL);
- if (retval < 0)
- goto done;
-
- /* wait till tune operation has completed */
- timeout = jiffies + msecs_to_jiffies(tune_timeout);
- do {
- retval = si470x_get_register(radio, STATUSRSSI);
- if (retval < 0)
- goto stop;
- timed_out = time_after(jiffies, timeout);
- } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
- (!timed_out));
- if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
- printk(KERN_WARNING DRIVER_NAME ": tune does not complete\n");
- if (timed_out)
- printk(KERN_WARNING DRIVER_NAME
- ": tune timed out after %u ms\n", tune_timeout);
-
-stop:
- /* stop tuning */
- radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
- retval = si470x_set_register(radio, CHANNEL);
-
-done:
- return retval;
-}
-
-
-/*
- * si470x_get_freq - get the frequency
- */
-static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
-{
- unsigned int spacing, band_bottom;
- unsigned short chan;
- int retval;
-
- /* Spacing (kHz) */
- switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
- /* 0: 200 kHz (USA, Australia) */
- case 0:
- spacing = 0.200 * FREQ_MUL; break;
- /* 1: 100 kHz (Europe, Japan) */
- case 1:
- spacing = 0.100 * FREQ_MUL; break;
- /* 2: 50 kHz */
- default:
- spacing = 0.050 * FREQ_MUL; break;
- };
-
- /* Bottom of Band (MHz) */
- switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
- /* 0: 87.5 - 108 MHz (USA, Europe) */
- case 0:
- band_bottom = 87.5 * FREQ_MUL; break;
- /* 1: 76 - 108 MHz (Japan wide band) */
- default:
- band_bottom = 76 * FREQ_MUL; break;
- /* 2: 76 - 90 MHz (Japan) */
- case 2:
- band_bottom = 76 * FREQ_MUL; break;
- };
-
- /* read channel */
- retval = si470x_get_register(radio, READCHAN);
- chan = radio->registers[READCHAN] & READCHAN_READCHAN;
-
- /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
- *freq = chan * spacing + band_bottom;
-
- return retval;
-}
-
-
-/*
- * si470x_set_freq - set the frequency
- */
-static int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
-{
- unsigned int spacing, band_bottom;
- unsigned short chan;
-
- /* Spacing (kHz) */
- switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
- /* 0: 200 kHz (USA, Australia) */
- case 0:
- spacing = 0.200 * FREQ_MUL; break;
- /* 1: 100 kHz (Europe, Japan) */
- case 1:
- spacing = 0.100 * FREQ_MUL; break;
- /* 2: 50 kHz */
- default:
- spacing = 0.050 * FREQ_MUL; break;
- };
-
- /* Bottom of Band (MHz) */
- switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
- /* 0: 87.5 - 108 MHz (USA, Europe) */
- case 0:
- band_bottom = 87.5 * FREQ_MUL; break;
- /* 1: 76 - 108 MHz (Japan wide band) */
- default:
- band_bottom = 76 * FREQ_MUL; break;
- /* 2: 76 - 90 MHz (Japan) */
- case 2:
- band_bottom = 76 * FREQ_MUL; break;
- };
-
- /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
- chan = (freq - band_bottom) / spacing;
-
- return si470x_set_chan(radio, chan);
-}
-
-
-/*
- * si470x_set_seek - set seek
- */
-static int si470x_set_seek(struct si470x_device *radio,
- unsigned int wrap_around, unsigned int seek_upward)
-{
- int retval = 0;
- unsigned long timeout;
- bool timed_out = 0;
-
- /* start seeking */
- radio->registers[POWERCFG] |= POWERCFG_SEEK;
- if (wrap_around == 1)
- radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
- else
- radio->registers[POWERCFG] |= POWERCFG_SKMODE;
- if (seek_upward == 1)
- radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
- else
- radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
- retval = si470x_set_register(radio, POWERCFG);
- if (retval < 0)
- goto done;
-
- /* wait till seek operation has completed */
- timeout = jiffies + msecs_to_jiffies(seek_timeout);
- do {
- retval = si470x_get_register(radio, STATUSRSSI);
- if (retval < 0)
- goto stop;
- timed_out = time_after(jiffies, timeout);
- } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
- (!timed_out));
- if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
- printk(KERN_WARNING DRIVER_NAME ": seek does not complete\n");
- if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
- printk(KERN_WARNING DRIVER_NAME
- ": seek failed / band limit reached\n");
- if (timed_out)
- printk(KERN_WARNING DRIVER_NAME
- ": seek timed out after %u ms\n", seek_timeout);
-
-stop:
- /* stop seeking */
- radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
- retval = si470x_set_register(radio, POWERCFG);
-
-done:
- /* try again, if timed out */
- if ((retval == 0) && timed_out)
- retval = -EAGAIN;
-
- return retval;
-}
-
-
-/*
- * si470x_start - switch on radio
- */
-static int si470x_start(struct si470x_device *radio)
-{
- int retval;
-
- /* powercfg */
- radio->registers[POWERCFG] =
- POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
- retval = si470x_set_register(radio, POWERCFG);
- if (retval < 0)
- goto done;
-
- /* sysconfig 1 */
- radio->registers[SYSCONFIG1] = SYSCONFIG1_DE;
- retval = si470x_set_register(radio, SYSCONFIG1);
- if (retval < 0)
- goto done;
-
- /* sysconfig 2 */
- radio->registers[SYSCONFIG2] =
- (0x3f << 8) | /* SEEKTH */
- ((band << 6) & SYSCONFIG2_BAND) | /* BAND */
- ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */
- 15; /* VOLUME (max) */
- retval = si470x_set_register(radio, SYSCONFIG2);
- if (retval < 0)
- goto done;
-
- /* reset last channel */
- retval = si470x_set_chan(radio,
- radio->registers[CHANNEL] & CHANNEL_CHAN);
-
-done:
- return retval;
-}
-
-
-/*
- * si470x_stop - switch off radio
- */
-static int si470x_stop(struct si470x_device *radio)
-{
- int retval;
-
- /* sysconfig 1 */
- radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
- retval = si470x_set_register(radio, SYSCONFIG1);
- if (retval < 0)
- goto done;
-
- /* powercfg */
- radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
- /* POWERCFG_ENABLE has to automatically go low */
- radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
- retval = si470x_set_register(radio, POWERCFG);
-
-done:
- return retval;
-}
-
-
-/*
- * si470x_rds_on - switch on rds reception
- */
-static int si470x_rds_on(struct si470x_device *radio)
-{
- int retval;
-
- /* sysconfig 1 */
- mutex_lock(&radio->lock);
- radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
- retval = si470x_set_register(radio, SYSCONFIG1);
- if (retval < 0)
- radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
- mutex_unlock(&radio->lock);
-
- return retval;
-}
-
-
-
-/**************************************************************************
- * General Driver Functions - ENTIRE_REPORT
- **************************************************************************/
-
-/*
- * si470x_get_all_registers - read entire registers
- */
-static int si470x_get_all_registers(struct si470x_device *radio)
-{
- unsigned char buf[ENTIRE_REPORT_SIZE];
- int retval;
- unsigned char regnr;
-
- buf[0] = ENTIRE_REPORT;
-
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
-
- if (retval >= 0)
- for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
- radio->registers[regnr] = get_unaligned_be16(
- &buf[regnr * RADIO_REGISTER_SIZE + 1]);
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-
-/**************************************************************************
- * General Driver Functions - RDS_REPORT
- **************************************************************************/
-
-/*
- * si470x_get_rds_registers - read rds registers
- */
-static int si470x_get_rds_registers(struct si470x_device *radio)
-{
- unsigned char buf[RDS_REPORT_SIZE];
- int retval;
- int size;
- unsigned char regnr;
-
- buf[0] = RDS_REPORT;
-
- retval = usb_interrupt_msg(radio->usbdev,
- usb_rcvintpipe(radio->usbdev, 1),
- (void *) &buf, sizeof(buf), &size, usb_timeout);
- if (size != sizeof(buf))
- printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
- "return size differs: %d != %zu\n", size, sizeof(buf));
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME ": si470x_get_rds_registers: "
- "usb_interrupt_msg returned %d\n", retval);
-
- if (retval >= 0)
- for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
- radio->registers[STATUSRSSI + regnr] =
- get_unaligned_be16(
- &buf[regnr * RADIO_REGISTER_SIZE + 1]);
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-
-/**************************************************************************
- * General Driver Functions - LED_REPORT
- **************************************************************************/
-
-/*
- * si470x_set_led_state - sets the led state
- */
-static int si470x_set_led_state(struct si470x_device *radio,
- unsigned char led_state)
-{
- unsigned char buf[LED_REPORT_SIZE];
- int retval;
-
- buf[0] = LED_REPORT;
- buf[1] = LED_COMMAND;
- buf[2] = led_state;
-
- retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-
-/**************************************************************************
- * General Driver Functions - SCRATCH_REPORT
- **************************************************************************/
-
-/*
- * si470x_get_scratch_versions - gets the scratch page and version infos
- */
-static int si470x_get_scratch_page_versions(struct si470x_device *radio)
-{
- unsigned char buf[SCRATCH_REPORT_SIZE];
- int retval;
-
- buf[0] = SCRATCH_REPORT;
-
- retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
-
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME ": si470x_get_scratch: "
- "si470x_get_report returned %d\n", retval);
- else {
- radio->software_version = buf[1];
- radio->hardware_version = buf[2];
- }
-
- return (retval < 0) ? -EINVAL : 0;
-}
-
-
-
-/**************************************************************************
- * RDS Driver Functions
- **************************************************************************/
-
-/*
- * si470x_rds - rds processing function
- */
-static void si470x_rds(struct si470x_device *radio)
-{
- unsigned char blocknum;
- unsigned short bler; /* rds block errors */
- unsigned short rds;
- unsigned char tmpbuf[3];
-
- /* get rds blocks */
- if (si470x_get_rds_registers(radio) < 0)
- return;
- if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
- /* No RDS group ready */
- return;
- }
- if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
- /* RDS decoder not synchronized */
- return;
- }
-
- /* copy all four RDS blocks to internal buffer */
- mutex_lock(&radio->lock);
- for (blocknum = 0; blocknum < 4; blocknum++) {
- switch (blocknum) {
- default:
- bler = (radio->registers[STATUSRSSI] &
- STATUSRSSI_BLERA) >> 9;
- rds = radio->registers[RDSA];
- break;
- case 1:
- bler = (radio->registers[READCHAN] &
- READCHAN_BLERB) >> 14;
- rds = radio->registers[RDSB];
- break;
- case 2:
- bler = (radio->registers[READCHAN] &
- READCHAN_BLERC) >> 12;
- rds = radio->registers[RDSC];
- break;
- case 3:
- bler = (radio->registers[READCHAN] &
- READCHAN_BLERD) >> 10;
- rds = radio->registers[RDSD];
- break;
- };
-
- /* Fill the V4L2 RDS buffer */
- put_unaligned_le16(rds, &tmpbuf);
- tmpbuf[2] = blocknum; /* offset name */
- tmpbuf[2] |= blocknum << 3; /* received offset */
- if (bler > max_rds_errors)
- tmpbuf[2] |= 0x80; /* uncorrectable errors */
- else if (bler > 0)
- tmpbuf[2] |= 0x40; /* corrected error(s) */
-
- /* copy RDS block to internal buffer */
- memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
- radio->wr_index += 3;
-
- /* wrap write pointer */
- if (radio->wr_index >= radio->buf_size)
- radio->wr_index = 0;
-
- /* check for overflow */
- if (radio->wr_index == radio->rd_index) {
- /* increment and wrap read pointer */
- radio->rd_index += 3;
- if (radio->rd_index >= radio->buf_size)
- radio->rd_index = 0;
- }
- }
- mutex_unlock(&radio->lock);
-
- /* wake up read queue */
- if (radio->wr_index != radio->rd_index)
- wake_up_interruptible(&radio->read_queue);
-}
-
-
-/*
- * si470x_work - rds work function
- */
-static void si470x_work(struct work_struct *work)
-{
- struct si470x_device *radio = container_of(work, struct si470x_device,
- work.work);
-
- /* safety checks */
- if (radio->disconnected)
- return;
- if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
- return;
-
- si470x_rds(radio);
- schedule_delayed_work(&radio->work, msecs_to_jiffies(rds_poll_time));
-}
-
-
-
-/**************************************************************************
- * File Operations Interface
- **************************************************************************/
-
-/*
- * si470x_fops_read - read RDS data
- */
-static ssize_t si470x_fops_read(struct file *file, char __user *buf,
- size_t count, loff_t *ppos)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
- unsigned int block_count = 0;
-
- /* switch on rds reception */
- if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
- si470x_rds_on(radio);
- schedule_delayed_work(&radio->work,
- msecs_to_jiffies(rds_poll_time));
- }
-
- /* block if no new data available */
- while (radio->wr_index == radio->rd_index) {
- if (file->f_flags & O_NONBLOCK) {
- retval = -EWOULDBLOCK;
- goto done;
- }
- if (wait_event_interruptible(radio->read_queue,
- radio->wr_index != radio->rd_index) < 0) {
- retval = -EINTR;
- goto done;
- }
- }
-
- /* calculate block count from byte count */
- count /= 3;
-
- /* copy RDS block out of internal buffer and to user buffer */
- mutex_lock(&radio->lock);
- while (block_count < count) {
- if (radio->rd_index == radio->wr_index)
- break;
-
- /* always transfer rds complete blocks */
- if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
- /* retval = -EFAULT; */
- break;
-
- /* increment and wrap read pointer */
- radio->rd_index += 3;
- if (radio->rd_index >= radio->buf_size)
- radio->rd_index = 0;
-
- /* increment counters */
- block_count++;
- buf += 3;
- retval += 3;
- }
- mutex_unlock(&radio->lock);
-
-done:
- return retval;
-}
-
-
-/*
- * si470x_fops_poll - poll RDS data
- */
-static unsigned int si470x_fops_poll(struct file *file,
- struct poll_table_struct *pts)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* switch on rds reception */
- if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) {
- si470x_rds_on(radio);
- schedule_delayed_work(&radio->work,
- msecs_to_jiffies(rds_poll_time));
- }
-
- poll_wait(file, &radio->read_queue, pts);
-
- if (radio->rd_index != radio->wr_index)
- retval = POLLIN | POLLRDNORM;
-
- return retval;
-}
-
-
-/*
- * si470x_fops_open - file open
- */
-static int si470x_fops_open(struct file *file)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval;
-
- lock_kernel();
- radio->users++;
-
- retval = usb_autopm_get_interface(radio->intf);
- if (retval < 0) {
- radio->users--;
- retval = -EIO;
- goto done;
- }
-
- if (radio->users == 1) {
- /* start radio */
- retval = si470x_start(radio);
- if (retval < 0)
- usb_autopm_put_interface(radio->intf);
- }
-
-done:
- unlock_kernel();
- return retval;
-}
-
-
-/*
- * si470x_fops_release - file release
- */
-static int si470x_fops_release(struct file *file)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety check */
- if (!radio) {
- retval = -ENODEV;
- goto done;
- }
-
- mutex_lock(&radio->disconnect_lock);
- radio->users--;
- if (radio->users == 0) {
- if (radio->disconnected) {
- video_unregister_device(radio->videodev);
- kfree(radio->buffer);
- kfree(radio);
- goto unlock;
- }
-
- /* stop rds reception */
- cancel_delayed_work_sync(&radio->work);
-
- /* cancel read processes */
- wake_up_interruptible(&radio->read_queue);
-
- /* stop radio */
- retval = si470x_stop(radio);
- usb_autopm_put_interface(radio->intf);
- }
-unlock:
- mutex_unlock(&radio->disconnect_lock);
-done:
- return retval;
-}
-
-
-/*
- * si470x_fops - file operations interface
- */
-static const struct v4l2_file_operations si470x_fops = {
- .owner = THIS_MODULE,
- .read = si470x_fops_read,
- .poll = si470x_fops_poll,
- .ioctl = video_ioctl2,
- .open = si470x_fops_open,
- .release = si470x_fops_release,
-};
-
-
-
-/**************************************************************************
- * Video4Linux Interface
- **************************************************************************/
-
-/*
- * si470x_v4l2_queryctrl - query control
- */
-static struct v4l2_queryctrl si470x_v4l2_queryctrl[] = {
- {
- .id = V4L2_CID_AUDIO_VOLUME,
- .type = V4L2_CTRL_TYPE_INTEGER,
- .name = "Volume",
- .minimum = 0,
- .maximum = 15,
- .step = 1,
- .default_value = 15,
- },
- {
- .id = V4L2_CID_AUDIO_MUTE,
- .type = V4L2_CTRL_TYPE_BOOLEAN,
- .name = "Mute",
- .minimum = 0,
- .maximum = 1,
- .step = 1,
- .default_value = 1,
- },
-};
-
-
-/*
- * si470x_vidioc_querycap - query device capabilities
- */
-static int si470x_vidioc_querycap(struct file *file, void *priv,
- struct v4l2_capability *capability)
-{
- struct si470x_device *radio = video_drvdata(file);
-
- strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
- strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
- usb_make_path(radio->usbdev, capability->bus_info, sizeof(capability->bus_info));
- capability->version = DRIVER_KERNEL_VERSION;
- capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
- V4L2_CAP_TUNER | V4L2_CAP_RADIO;
-
- return 0;
-}
-
-
-/*
- * si470x_vidioc_queryctrl - enumerate control items
- */
-static int si470x_vidioc_queryctrl(struct file *file, void *priv,
- struct v4l2_queryctrl *qc)
-{
- unsigned char i = 0;
- int retval = -EINVAL;
-
- /* abort if qc->id is below V4L2_CID_BASE */
- if (qc->id < V4L2_CID_BASE)
- goto done;
-
- /* search video control */
- for (i = 0; i < ARRAY_SIZE(si470x_v4l2_queryctrl); i++) {
- if (qc->id == si470x_v4l2_queryctrl[i].id) {
- memcpy(qc, &(si470x_v4l2_queryctrl[i]), sizeof(*qc));
- retval = 0; /* found */
- break;
- }
- }
-
- /* disable unsupported base controls */
- /* to satisfy kradio and such apps */
- if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {
- qc->flags = V4L2_CTRL_FLAG_DISABLED;
- retval = 0;
- }
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": query controls failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_g_ctrl - get the value of a control
- */
-static int si470x_vidioc_g_ctrl(struct file *file, void *priv,
- struct v4l2_control *ctrl)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_VOLUME:
- ctrl->value = radio->registers[SYSCONFIG2] &
- SYSCONFIG2_VOLUME;
- break;
- case V4L2_CID_AUDIO_MUTE:
- ctrl->value = ((radio->registers[POWERCFG] &
- POWERCFG_DMUTE) == 0) ? 1 : 0;
- break;
- default:
- retval = -EINVAL;
- }
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": get control failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_s_ctrl - set the value of a control
- */
-static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
- struct v4l2_control *ctrl)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
-
- switch (ctrl->id) {
- case V4L2_CID_AUDIO_VOLUME:
- radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
- radio->registers[SYSCONFIG2] |= ctrl->value;
- retval = si470x_set_register(radio, SYSCONFIG2);
- break;
- case V4L2_CID_AUDIO_MUTE:
- if (ctrl->value == 1)
- radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
- else
- radio->registers[POWERCFG] |= POWERCFG_DMUTE;
- retval = si470x_set_register(radio, POWERCFG);
- break;
- default:
- retval = -EINVAL;
- }
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": set control failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_g_audio - get audio attributes
- */
-static int si470x_vidioc_g_audio(struct file *file, void *priv,
- struct v4l2_audio *audio)
-{
- /* driver constants */
- audio->index = 0;
- strcpy(audio->name, "Radio");
- audio->capability = V4L2_AUDCAP_STEREO;
- audio->mode = 0;
-
- return 0;
-}
-
-
-/*
- * si470x_vidioc_g_tuner - get tuner attributes
- */
-static int si470x_vidioc_g_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
- if (tuner->index != 0) {
- retval = -EINVAL;
- goto done;
- }
-
- retval = si470x_get_register(radio, STATUSRSSI);
- if (retval < 0)
- goto done;
-
- /* driver constants */
- strcpy(tuner->name, "FM");
- tuner->type = V4L2_TUNER_RADIO;
- tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
-
- /* range limits */
- switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
- /* 0: 87.5 - 108 MHz (USA, Europe, default) */
- default:
- tuner->rangelow = 87.5 * FREQ_MUL;
- tuner->rangehigh = 108 * FREQ_MUL;
- break;
- /* 1: 76 - 108 MHz (Japan wide band) */
- case 1 :
- tuner->rangelow = 76 * FREQ_MUL;
- tuner->rangehigh = 108 * FREQ_MUL;
- break;
- /* 2: 76 - 90 MHz (Japan) */
- case 2 :
- tuner->rangelow = 76 * FREQ_MUL;
- tuner->rangehigh = 90 * FREQ_MUL;
- break;
- };
-
- /* stereo indicator == stereo (instead of mono) */
- if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)
- tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
- else
- tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
-
- /* mono/stereo selector */
- if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
- tuner->audmode = V4L2_TUNER_MODE_STEREO;
- else
- tuner->audmode = V4L2_TUNER_MODE_MONO;
-
- /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
- /* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */
- tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);
- /* the ideal factor is 0xffff/75 = 873,8 */
- tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);
-
- /* automatic frequency control: -1: freq to low, 1 freq to high */
- /* AFCRL does only indicate that freq. differs, not if too low/high */
- tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": get tuner failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_s_tuner - set tuner attributes
- */
-static int si470x_vidioc_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = -EINVAL;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
- if (tuner->index != 0)
- goto done;
-
- /* mono/stereo selector */
- switch (tuner->audmode) {
- case V4L2_TUNER_MODE_MONO:
- radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
- break;
- case V4L2_TUNER_MODE_STEREO:
- radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
- break;
- default:
- goto done;
- }
-
- retval = si470x_set_register(radio, POWERCFG);
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": set tuner failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_g_frequency - get tuner or modulator radio frequency
- */
-static int si470x_vidioc_g_frequency(struct file *file, void *priv,
- struct v4l2_frequency *freq)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
- if (freq->tuner != 0) {
- retval = -EINVAL;
- goto done;
- }
-
- freq->type = V4L2_TUNER_RADIO;
- retval = si470x_get_freq(radio, &freq->frequency);
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": get frequency failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_s_frequency - set tuner or modulator radio frequency
- */
-static int si470x_vidioc_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *freq)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
- if (freq->tuner != 0) {
- retval = -EINVAL;
- goto done;
- }
-
- retval = si470x_set_freq(radio, freq->frequency);
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": set frequency failed with %d\n", retval);
- return retval;
-}
-
-
-/*
- * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
- */
-static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
- struct v4l2_hw_freq_seek *seek)
-{
- struct si470x_device *radio = video_drvdata(file);
- int retval = 0;
-
- /* safety checks */
- if (radio->disconnected) {
- retval = -EIO;
- goto done;
- }
- if (seek->tuner != 0) {
- retval = -EINVAL;
- goto done;
- }
-
- retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward);
-
-done:
- if (retval < 0)
- printk(KERN_WARNING DRIVER_NAME
- ": set hardware frequency seek failed with %d\n",
- retval);
- return retval;
-}
-
-
-/*
- * si470x_ioctl_ops - video device ioctl operations
- */
-static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
- .vidioc_querycap = si470x_vidioc_querycap,
- .vidioc_queryctrl = si470x_vidioc_queryctrl,
- .vidioc_g_ctrl = si470x_vidioc_g_ctrl,
- .vidioc_s_ctrl = si470x_vidioc_s_ctrl,
- .vidioc_g_audio = si470x_vidioc_g_audio,
- .vidioc_g_tuner = si470x_vidioc_g_tuner,
- .vidioc_s_tuner = si470x_vidioc_s_tuner,
- .vidioc_g_frequency = si470x_vidioc_g_frequency,
- .vidioc_s_frequency = si470x_vidioc_s_frequency,
- .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
-};
-
-
-/*
- * si470x_viddev_template - video device interface
- */
-static struct video_device si470x_viddev_template = {
- .fops = &si470x_fops,
- .name = DRIVER_NAME,
- .release = video_device_release,
- .ioctl_ops = &si470x_ioctl_ops,
-};
-
-
-
-/**************************************************************************
- * USB Interface
- **************************************************************************/
-
-/*
- * si470x_usb_driver_probe - probe for the device
- */
-static int si470x_usb_driver_probe(struct usb_interface *intf,
- const struct usb_device_id *id)
-{
- struct si470x_device *radio;
- int retval = 0;
-
- /* private data allocation and initialization */
- radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
- if (!radio) {
- retval = -ENOMEM;
- goto err_initial;
- }
- radio->users = 0;
- radio->disconnected = 0;
- radio->usbdev = interface_to_usbdev(intf);
- radio->intf = intf;
- mutex_init(&radio->disconnect_lock);
- mutex_init(&radio->lock);
-
- /* video device allocation and initialization */
- radio->videodev = video_device_alloc();
- if (!radio->videodev) {
- retval = -ENOMEM;
- goto err_radio;
- }
- memcpy(radio->videodev, &si470x_viddev_template,
- sizeof(si470x_viddev_template));
- video_set_drvdata(radio->videodev, radio);
-
- /* show some infos about the specific si470x device */
- if (si470x_get_all_registers(radio) < 0) {
- retval = -EIO;
- goto err_video;
- }
- printk(KERN_INFO DRIVER_NAME ": DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
- radio->registers[DEVICEID], radio->registers[CHIPID]);
-
- /* get software and hardware versions */
- if (si470x_get_scratch_page_versions(radio) < 0) {
- retval = -EIO;
- goto err_video;
- }
- printk(KERN_INFO DRIVER_NAME
- ": software version %d, hardware version %d\n",
- radio->software_version, radio->hardware_version);
-
- /* check if device and firmware is current */
- if ((radio->registers[CHIPID] & CHIPID_FIRMWARE)
- < RADIO_SW_VERSION_CURRENT) {
- printk(KERN_WARNING DRIVER_NAME
- ": This driver is known to work with "
- "firmware version %hu,\n", RADIO_SW_VERSION_CURRENT);
- printk(KERN_WARNING DRIVER_NAME
- ": but the device has firmware version %hu.\n",
- radio->registers[CHIPID] & CHIPID_FIRMWARE);
- printk(KERN_WARNING DRIVER_NAME
- ": If you have some trouble using this driver,\n");
- printk(KERN_WARNING DRIVER_NAME
- ": please report to V4L ML at "
- "linux-media@vger.kernel.org\n");
- }
-
- /* set initial frequency */
- si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
-
- /* set led to connect state */
- si470x_set_led_state(radio, BLINK_GREEN_LED);
-
- /* rds buffer allocation */
- radio->buf_size = rds_buf * 3;
- radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
- if (!radio->buffer) {
- retval = -EIO;
- goto err_video;
- }
-
- /* rds buffer configuration */
- radio->wr_index = 0;
- radio->rd_index = 0;
- init_waitqueue_head(&radio->read_queue);
-
- /* prepare rds work function */
- INIT_DELAYED_WORK(&radio->work, si470x_work);
-
- /* register video device */
- retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
- if (retval) {
- printk(KERN_WARNING DRIVER_NAME
- ": Could not register video device\n");
- goto err_all;
- }
- usb_set_intfdata(intf, radio);
-
- return 0;
-err_all:
- kfree(radio->buffer);
-err_video:
- video_device_release(radio->videodev);
-err_radio:
- kfree(radio);
-err_initial:
- return retval;
-}
-
-
-/*
- * si470x_usb_driver_suspend - suspend the device
- */
-static int si470x_usb_driver_suspend(struct usb_interface *intf,
- pm_message_t message)
-{
- struct si470x_device *radio = usb_get_intfdata(intf);
-
- printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
-
- cancel_delayed_work_sync(&radio->work);
-
- return 0;
-}
-
-
-/*
- * si470x_usb_driver_resume - resume the device
- */
-static int si470x_usb_driver_resume(struct usb_interface *intf)
-{
- struct si470x_device *radio = usb_get_intfdata(intf);
-
- printk(KERN_INFO DRIVER_NAME ": resuming now...\n");
-
- mutex_lock(&radio->lock);
- if (radio->users && radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)
- schedule_delayed_work(&radio->work,
- msecs_to_jiffies(rds_poll_time));
- mutex_unlock(&radio->lock);
-
- return 0;
-}
-
-
-/*
- * si470x_usb_driver_disconnect - disconnect the device
- */
-static void si470x_usb_driver_disconnect(struct usb_interface *intf)
-{
- struct si470x_device *radio = usb_get_intfdata(intf);
-
- mutex_lock(&radio->disconnect_lock);
- radio->disconnected = 1;
- cancel_delayed_work_sync(&radio->work);
- usb_set_intfdata(intf, NULL);
- if (radio->users == 0) {
- /* set led to disconnect state */
- si470x_set_led_state(radio, BLINK_ORANGE_LED);
-
- video_unregister_device(radio->videodev);
- kfree(radio->buffer);
- kfree(radio);
- }
- mutex_unlock(&radio->disconnect_lock);
-}
-
-
-/*
- * si470x_usb_driver - usb driver interface
- */
-static struct usb_driver si470x_usb_driver = {
- .name = DRIVER_NAME,
- .probe = si470x_usb_driver_probe,
- .disconnect = si470x_usb_driver_disconnect,
- .suspend = si470x_usb_driver_suspend,
- .resume = si470x_usb_driver_resume,
- .id_table = si470x_usb_driver_id_table,
- .supports_autosuspend = 1,
-};
-
-
-
-/**************************************************************************
- * Module Interface
- **************************************************************************/
-
-/*
- * si470x_module_init - module init
- */
-static int __init si470x_module_init(void)
-{
- printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
- return usb_register(&si470x_usb_driver);
-}
-
-
-/*
- * si470x_module_exit - module exit
- */
-static void __exit si470x_module_exit(void)
-{
- usb_deregister(&si470x_usb_driver);
-}
-
-
-module_init(si470x_module_init);
-module_exit(si470x_module_exit);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR(DRIVER_AUTHOR);
-MODULE_DESCRIPTION(DRIVER_DESC);
-MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c
new file mode 100644
index 00000000000..65c14b70458
--- /dev/null
+++ b/drivers/media/radio/radio-si4713.c
@@ -0,0 +1,367 @@
+/*
+ * drivers/media/radio/radio-si4713.c
+ *
+ * Platform Driver for Silicon Labs Si4713 FM Radio Transmitter:
+ *
+ * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
+ * Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/radio-si4713.h>
+
+/* module parameters */
+static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr,
+ "Minor number for radio device (-1 ==> auto assign)");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
+MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter");
+MODULE_VERSION("0.0.1");
+
+/* Driver state struct */
+struct radio_si4713_device {
+ struct v4l2_device v4l2_dev;
+ struct video_device *radio_dev;
+};
+
+/* radio_si4713_fops - file operations interface */
+static const struct v4l2_file_operations radio_si4713_fops = {
+ .owner = THIS_MODULE,
+ .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,
+ 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,
+ struct v4l2_capability *capability)
+{
+ struct radio_si4713_device *rsdev;
+
+ rsdev = video_get_drvdata(video_devdata(file));
+
+ 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;
+
+ 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.
+ */
+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)
+{
+ return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
+ g_modulator, vm);
+}
+
+static int radio_si4713_s_modulator(struct file *file, void *p,
+ struct v4l2_modulator *vm)
+{
+ return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
+ s_modulator, vm);
+}
+
+static int radio_si4713_g_frequency(struct file *file, void *p,
+ struct v4l2_frequency *vf)
+{
+ return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
+ g_frequency, vf);
+}
+
+static int radio_si4713_s_frequency(struct file *file, void *p,
+ struct v4l2_frequency *vf)
+{
+ return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
+ s_frequency, vf);
+}
+
+static long radio_si4713_default(struct file *file, void *p, int cmd, void *arg)
+{
+ return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
+ ioctl, cmd, arg);
+}
+
+static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
+ .vidioc_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_default = radio_si4713_default,
+};
+
+/* radio_si4713_vdev_template - video device interface */
+static struct video_device radio_si4713_vdev_template = {
+ .fops = &radio_si4713_fops,
+ .name = "radio-si4713",
+ .release = video_device_release,
+ .ioctl_ops = &radio_si4713_ioctl_ops,
+};
+
+/* Platform driver interface */
+/* radio_si4713_pdriver_probe - probe for the device */
+static int radio_si4713_pdriver_probe(struct platform_device *pdev)
+{
+ struct radio_si4713_platform_data *pdata = pdev->dev.platform_data;
+ struct radio_si4713_device *rsdev;
+ struct i2c_adapter *adapter;
+ struct v4l2_subdev *sd;
+ int rval = 0;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "Cannot proceed without platform data.\n");
+ rval = -EINVAL;
+ goto exit;
+ }
+
+ rsdev = kzalloc(sizeof *rsdev, GFP_KERNEL);
+ if (!rsdev) {
+ dev_err(&pdev->dev, "Failed to alloc video device.\n");
+ rval = -ENOMEM;
+ goto exit;
+ }
+
+ rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);
+ if (rval) {
+ dev_err(&pdev->dev, "Failed to register v4l2 device.\n");
+ goto free_rsdev;
+ }
+
+ adapter = i2c_get_adapter(pdata->i2c_bus);
+ if (!adapter) {
+ dev_err(&pdev->dev, "Cannot get i2c adapter %d\n",
+ pdata->i2c_bus);
+ rval = -ENODEV;
+ goto unregister_v4l2_dev;
+ }
+
+ sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter, "si4713_i2c",
+ pdata->subdev_board_info, NULL);
+ if (!sd) {
+ dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");
+ rval = -ENODEV;
+ goto unregister_v4l2_dev;
+ }
+
+ rsdev->radio_dev = video_device_alloc();
+ if (!rsdev->radio_dev) {
+ dev_err(&pdev->dev, "Failed to alloc video device.\n");
+ rval = -ENOMEM;
+ goto unregister_v4l2_dev;
+ }
+
+ 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)) {
+ dev_err(&pdev->dev, "Could not register video device.\n");
+ rval = -EIO;
+ goto free_vdev;
+ }
+ dev_info(&pdev->dev, "New device successfully probed\n");
+
+ goto exit;
+
+free_vdev:
+ video_device_release(rsdev->radio_dev);
+unregister_v4l2_dev:
+ v4l2_device_unregister(&rsdev->v4l2_dev);
+free_rsdev:
+ kfree(rsdev);
+exit:
+ return rval;
+}
+
+/* radio_si4713_pdriver_remove - remove the device */
+static int __exit 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);
+
+ video_unregister_device(rsdev->radio_dev);
+ v4l2_device_unregister(&rsdev->v4l2_dev);
+ kfree(rsdev);
+
+ return 0;
+}
+
+static struct platform_driver radio_si4713_pdriver = {
+ .driver = {
+ .name = "radio-si4713",
+ },
+ .probe = radio_si4713_pdriver_probe,
+ .remove = __exit_p(radio_si4713_pdriver_remove),
+};
+
+/* Module Interface */
+static int __init radio_si4713_module_init(void)
+{
+ return platform_driver_register(&radio_si4713_pdriver);
+}
+
+static void __exit radio_si4713_module_exit(void)
+{
+ platform_driver_unregister(&radio_si4713_pdriver);
+}
+
+module_init(radio_si4713_module_init);
+module_exit(radio_si4713_module_exit);
+
diff --git a/drivers/media/radio/si470x/Kconfig b/drivers/media/radio/si470x/Kconfig
new file mode 100644
index 00000000000..a466654ee5c
--- /dev/null
+++ b/drivers/media/radio/si470x/Kconfig
@@ -0,0 +1,37 @@
+config USB_SI470X
+ tristate "Silicon Labs Si470x FM Radio Receiver support with USB"
+ depends on USB && RADIO_SI470X
+ ---help---
+ This is a driver for USB devices with the Silicon Labs SI470x
+ chip. Currently these devices are known to work:
+ - 10c4:818a: Silicon Labs USB FM Radio Reference Design
+ - 06e1:a155: ADS/Tech FM Radio Receiver (formerly Instant FM Music)
+ - 1b80:d700: KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
+ - 10c5:819a: Sanei Electric FM USB Radio (aka DealExtreme.com PCear)
+
+ Sound is provided by the ALSA USB Audio/MIDI driver. Therefore
+ if you don't want to use the device solely for RDS receiving,
+ it is recommended to also select SND_USB_AUDIO.
+
+ Please have a look at the documentation, especially on how
+ to redirect the audio stream from the radio to your sound device:
+ Documentation/video4linux/si470x.txt
+
+ Say Y here if you want to connect this type of radio to your
+ computer's USB port.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-usb-si470x.
+
+config I2C_SI470X
+ tristate "Silicon Labs Si470x FM Radio Receiver support with I2C"
+ depends on I2C && RADIO_SI470X && !USB_SI470X
+ ---help---
+ This is a driver for I2C devices with the Silicon Labs SI470x
+ chip.
+
+ Say Y here if you want to connect this type of radio to your
+ computer's I2C port.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-i2c-si470x.
diff --git a/drivers/media/radio/si470x/Makefile b/drivers/media/radio/si470x/Makefile
new file mode 100644
index 00000000000..06964816cfd
--- /dev/null
+++ b/drivers/media/radio/si470x/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for radios with Silicon Labs Si470x FM Radio Receivers
+#
+
+radio-usb-si470x-objs := radio-si470x-usb.o radio-si470x-common.o
+radio-i2c-si470x-objs := radio-si470x-i2c.o radio-si470x-common.o
+
+obj-$(CONFIG_USB_SI470X) += radio-usb-si470x.o
+obj-$(CONFIG_I2C_SI470X) += radio-i2c-si470x.o
diff --git a/drivers/media/radio/si470x/radio-si470x-common.c b/drivers/media/radio/si470x/radio-si470x-common.c
new file mode 100644
index 00000000000..f33315f2c54
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si470x-common.c
@@ -0,0 +1,798 @@
+/*
+ * drivers/media/radio/si470x/radio-si470x-common.c
+ *
+ * Driver for radios with Silicon Labs Si470x FM Radio Receivers
+ *
+ * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * History:
+ * 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.0
+ * - First working version
+ * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.1
+ * - Improved error handling, every function now returns errno
+ * - Improved multi user access (start/mute/stop)
+ * - Channel doesn't get lost anymore after start/mute/stop
+ * - RDS support added (polling mode via interrupt EP 1)
+ * - marked default module parameters with *value*
+ * - switched from bit structs to bit masks
+ * - header file cleaned and integrated
+ * 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.2
+ * - hex values are now lower case
+ * - commented USB ID for ADS/Tech moved on todo list
+ * - blacklisted si470x in hid-quirks.c
+ * - rds buffer handling functions integrated into *_work, *_read
+ * - rds_command in si470x_poll exchanged against simple retval
+ * - check for firmware version 15
+ * - code order and prototypes still remain the same
+ * - spacing and bottom of band codes remain the same
+ * 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.3
+ * - code reordered to avoid function prototypes
+ * - switch/case defaults are now more user-friendly
+ * - unified comment style
+ * - applied all checkpatch.pl v1.12 suggestions
+ * except the warning about the too long lines with bit comments
+ * - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
+ * 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.4
+ * - avoid poss. locking when doing copy_to_user which may sleep
+ * - RDS is automatically activated on read now
+ * - code cleaned of unnecessary rds_commands
+ * - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
+ * (thanks to Guillaume RAMOUSSE)
+ * 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.5
+ * - number of seek_retries changed to tune_timeout
+ * - fixed problem with incomplete tune operations by own buffers
+ * - optimization of variables and printf types
+ * - improved error logging
+ * 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Oliver Neukum <oliver@neukum.org>
+ * Version 1.0.6
+ * - fixed coverity checker warnings in *_usb_driver_disconnect
+ * - probe()/open() race by correct ordering in probe()
+ * - DMA coherency rules by separate allocation of all buffers
+ * - use of endianness macros
+ * - abuse of spinlock, replaced by mutex
+ * - racy handling of timer in disconnect,
+ * replaced by delayed_work
+ * - racy interruptible_sleep_on(),
+ * replaced with wait_event_interruptible()
+ * - handle signals in read()
+ * 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Oliver Neukum <oliver@neukum.org>
+ * Version 1.0.7
+ * - usb autosuspend support
+ * - unplugging fixed
+ * 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net>
+ * Version 1.0.8
+ * - hardware frequency seek support
+ * - afc indication
+ * - more safety checks, let si470x_get_freq return errno
+ * - vidioc behavior corrected according to v4l2 spec
+ * 2008-10-20 Alexey Klimov <klimov.linux@gmail.com>
+ * - add support for KWorld USB FM Radio FM700
+ * - blacklisted KWorld radio in hid-core.c and hid-ids.h
+ * 2008-12-03 Mark Lord <mlord@pobox.com>
+ * - add support for DealExtreme USB Radio
+ * 2009-01-31 Bob Ross <pigiron@gmx.com>
+ * - correction of stereo detection/setting
+ * - correction of signal strength indicator scaling
+ * 2009-01-31 Rick Bronson <rick@efn.org>
+ * Tobias Lorenz <tobias.lorenz@gmx.net>
+ * - add LED status output
+ * - get HW/SW version from scratchpad
+ * 2009-06-16 Edouard Lafargue <edouard@lafargue.name>
+ * Version 1.0.10
+ * - add support for interrupt mode for RDS endpoint,
+ * instead of polling.
+ * Improves RDS reception significantly
+ */
+
+
+/* kernel includes */
+#include "radio-si470x.h"
+
+
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Spacing (kHz) */
+/* 0: 200 kHz (USA, Australia) */
+/* 1: 100 kHz (Europe, Japan) */
+/* 2: 50 kHz */
+static unsigned short space = 2;
+module_param(space, ushort, 0444);
+MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");
+
+/* Bottom of Band (MHz) */
+/* 0: 87.5 - 108 MHz (USA, Europe)*/
+/* 1: 76 - 108 MHz (Japan wide band) */
+/* 2: 76 - 90 MHz (Japan) */
+static unsigned short band = 1;
+module_param(band, ushort, 0444);
+MODULE_PARM_DESC(band, "Band: 0=87.5..108MHz *1=76..108MHz* 2=76..90MHz");
+
+/* De-emphasis */
+/* 0: 75 us (USA) */
+/* 1: 50 us (Europe, Australia, Japan) */
+static unsigned short de = 1;
+module_param(de, ushort, 0444);
+MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");
+
+/* Tune timeout */
+static unsigned int tune_timeout = 3000;
+module_param(tune_timeout, uint, 0644);
+MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
+
+/* Seek timeout */
+static unsigned int seek_timeout = 5000;
+module_param(seek_timeout, uint, 0644);
+MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
+
+
+
+/**************************************************************************
+ * Generic Functions
+ **************************************************************************/
+
+/*
+ * si470x_set_chan - set the channel
+ */
+static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
+{
+ int retval;
+ unsigned long timeout;
+ bool timed_out = 0;
+
+ /* start tuning */
+ radio->registers[CHANNEL] &= ~CHANNEL_CHAN;
+ radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
+ retval = si470x_set_register(radio, CHANNEL);
+ if (retval < 0)
+ goto done;
+
+ /* wait till tune operation has completed */
+ timeout = jiffies + msecs_to_jiffies(tune_timeout);
+ do {
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
+ (!timed_out));
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+ dev_warn(&radio->videodev->dev, "tune does not complete\n");
+ if (timed_out)
+ dev_warn(&radio->videodev->dev,
+ "tune timed out after %u ms\n", tune_timeout);
+
+stop:
+ /* stop tuning */
+ radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
+ retval = si470x_set_register(radio, CHANNEL);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_get_freq - get the frequency
+ */
+static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
+{
+ unsigned int spacing, band_bottom;
+ unsigned short chan;
+ int retval;
+
+ /* Spacing (kHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
+ /* 0: 200 kHz (USA, Australia) */
+ case 0:
+ spacing = 0.200 * FREQ_MUL; break;
+ /* 1: 100 kHz (Europe, Japan) */
+ case 1:
+ spacing = 0.100 * FREQ_MUL; break;
+ /* 2: 50 kHz */
+ default:
+ spacing = 0.050 * FREQ_MUL; break;
+ };
+
+ /* Bottom of Band (MHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe) */
+ case 0:
+ band_bottom = 87.5 * FREQ_MUL; break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ default:
+ band_bottom = 76 * FREQ_MUL; break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2:
+ band_bottom = 76 * FREQ_MUL; break;
+ };
+
+ /* read channel */
+ retval = si470x_get_register(radio, READCHAN);
+ chan = radio->registers[READCHAN] & READCHAN_READCHAN;
+
+ /* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
+ *freq = chan * spacing + band_bottom;
+
+ return retval;
+}
+
+
+/*
+ * si470x_set_freq - set the frequency
+ */
+int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
+{
+ unsigned int spacing, band_bottom;
+ unsigned short chan;
+
+ /* Spacing (kHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
+ /* 0: 200 kHz (USA, Australia) */
+ case 0:
+ spacing = 0.200 * FREQ_MUL; break;
+ /* 1: 100 kHz (Europe, Japan) */
+ case 1:
+ spacing = 0.100 * FREQ_MUL; break;
+ /* 2: 50 kHz */
+ default:
+ spacing = 0.050 * FREQ_MUL; break;
+ };
+
+ /* Bottom of Band (MHz) */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe) */
+ case 0:
+ band_bottom = 87.5 * FREQ_MUL; break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ default:
+ band_bottom = 76 * FREQ_MUL; break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2:
+ band_bottom = 76 * FREQ_MUL; break;
+ };
+
+ /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
+ chan = (freq - band_bottom) / spacing;
+
+ return si470x_set_chan(radio, chan);
+}
+
+
+/*
+ * si470x_set_seek - set seek
+ */
+static int si470x_set_seek(struct si470x_device *radio,
+ unsigned int wrap_around, unsigned int seek_upward)
+{
+ int retval = 0;
+ unsigned long timeout;
+ bool timed_out = 0;
+
+ /* start seeking */
+ radio->registers[POWERCFG] |= POWERCFG_SEEK;
+ if (wrap_around == 1)
+ radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
+ else
+ radio->registers[POWERCFG] |= POWERCFG_SKMODE;
+ if (seek_upward == 1)
+ radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
+ else
+ radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
+ retval = si470x_set_register(radio, POWERCFG);
+ if (retval < 0)
+ goto done;
+
+ /* wait till seek operation has completed */
+ timeout = jiffies + msecs_to_jiffies(seek_timeout);
+ do {
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while (((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0) &&
+ (!timed_out));
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
+ dev_warn(&radio->videodev->dev, "seek does not complete\n");
+ if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
+ dev_warn(&radio->videodev->dev,
+ "seek failed / band limit reached\n");
+ if (timed_out)
+ dev_warn(&radio->videodev->dev,
+ "seek timed out after %u ms\n", seek_timeout);
+
+stop:
+ /* stop seeking */
+ radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ /* try again, if timed out */
+ if ((retval == 0) && timed_out)
+ retval = -EAGAIN;
+
+ return retval;
+}
+
+
+/*
+ * si470x_start - switch on radio
+ */
+int si470x_start(struct si470x_device *radio)
+{
+ int retval;
+
+ /* powercfg */
+ radio->registers[POWERCFG] =
+ POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
+ retval = si470x_set_register(radio, POWERCFG);
+ if (retval < 0)
+ goto done;
+
+ /* sysconfig 1 */
+ radio->registers[SYSCONFIG1] = SYSCONFIG1_DE;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ goto done;
+
+ /* sysconfig 2 */
+ radio->registers[SYSCONFIG2] =
+ (0x3f << 8) | /* SEEKTH */
+ ((band << 6) & SYSCONFIG2_BAND) | /* BAND */
+ ((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */
+ 15; /* VOLUME (max) */
+ retval = si470x_set_register(radio, SYSCONFIG2);
+ if (retval < 0)
+ goto done;
+
+ /* reset last channel */
+ retval = si470x_set_chan(radio,
+ radio->registers[CHANNEL] & CHANNEL_CHAN);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_stop - switch off radio
+ */
+int si470x_stop(struct si470x_device *radio)
+{
+ int retval;
+
+ /* sysconfig 1 */
+ radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ goto done;
+
+ /* powercfg */
+ radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
+ /* POWERCFG_ENABLE has to automatically go low */
+ radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_rds_on - switch on rds reception
+ */
+int si470x_rds_on(struct si470x_device *radio)
+{
+ int retval;
+
+ /* sysconfig 1 */
+ mutex_lock(&radio->lock);
+ radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
+ retval = si470x_set_register(radio, SYSCONFIG1);
+ if (retval < 0)
+ radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
+ mutex_unlock(&radio->lock);
+
+ return retval;
+}
+
+
+
+/**************************************************************************
+ * Video4Linux Interface
+ **************************************************************************/
+
+/*
+ * si470x_vidioc_queryctrl - enumerate control items
+ */
+static int si470x_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = -EINVAL;
+
+ /* abort if qc->id is below V4L2_CID_BASE */
+ if (qc->id < V4L2_CID_BASE)
+ goto done;
+
+ /* search video control */
+ switch (qc->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ return v4l2_ctrl_query_fill(qc, 0, 15, 1, 15);
+ case V4L2_CID_AUDIO_MUTE:
+ return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1);
+ }
+
+ /* disable unsupported base controls */
+ /* to satisfy kradio and such apps */
+ if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {
+ qc->flags = V4L2_CTRL_FLAG_DISABLED;
+ retval = 0;
+ }
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "query controls failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_ctrl - get the value of a control
+ */
+static int si470x_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ ctrl->value = radio->registers[SYSCONFIG2] &
+ SYSCONFIG2_VOLUME;
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = ((radio->registers[POWERCFG] &
+ POWERCFG_DMUTE) == 0) ? 1 : 0;
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "get control failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_ctrl - set the value of a control
+ */
+static int si470x_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
+ radio->registers[SYSCONFIG2] |= ctrl->value;
+ retval = si470x_set_register(radio, SYSCONFIG2);
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value == 1)
+ radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
+ else
+ radio->registers[POWERCFG] |= POWERCFG_DMUTE;
+ retval = si470x_set_register(radio, POWERCFG);
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "set control failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_audio - get audio attributes
+ */
+static int si470x_vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *audio)
+{
+ /* driver constants */
+ audio->index = 0;
+ strcpy(audio->name, "Radio");
+ audio->capability = V4L2_AUDCAP_STEREO;
+ audio->mode = 0;
+
+ return 0;
+}
+
+
+/*
+ * si470x_vidioc_g_tuner - get tuner attributes
+ */
+static int si470x_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (tuner->index != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_get_register(radio, STATUSRSSI);
+ if (retval < 0)
+ goto done;
+
+ /* driver constants */
+ strcpy(tuner->name, "FM");
+ tuner->type = V4L2_TUNER_RADIO;
+#if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
+ tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_RDS;
+#else
+ tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
+#endif
+
+ /* range limits */
+ switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_BAND) >> 6) {
+ /* 0: 87.5 - 108 MHz (USA, Europe, default) */
+ default:
+ tuner->rangelow = 87.5 * FREQ_MUL;
+ tuner->rangehigh = 108 * FREQ_MUL;
+ break;
+ /* 1: 76 - 108 MHz (Japan wide band) */
+ case 1:
+ tuner->rangelow = 76 * FREQ_MUL;
+ tuner->rangehigh = 108 * FREQ_MUL;
+ break;
+ /* 2: 76 - 90 MHz (Japan) */
+ case 2:
+ tuner->rangelow = 76 * FREQ_MUL;
+ tuner->rangehigh = 90 * FREQ_MUL;
+ break;
+ };
+
+ /* stereo indicator == stereo (instead of mono) */
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ else
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+#if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
+ /* If there is a reliable method of detecting an RDS channel,
+ then this code should check for that before setting this
+ RDS subchannel. */
+ tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+#endif
+
+ /* mono/stereo selector */
+ if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+
+ /* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
+ /* measured in units of db쨉V in 1 db increments (max at ~75 db쨉V) */
+ tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);
+ /* the ideal factor is 0xffff/75 = 873,8 */
+ tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);
+
+ /* automatic frequency control: -1: freq to low, 1 freq to high */
+ /* AFCRL does only indicate that freq. differs, not if too low/high */
+ tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "get tuner failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_tuner - set tuner attributes
+ */
+static int si470x_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = -EINVAL;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (tuner->index != 0)
+ goto done;
+
+ /* mono/stereo selector */
+ switch (tuner->audmode) {
+ case V4L2_TUNER_MODE_MONO:
+ radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
+ break;
+ default:
+ goto done;
+ }
+
+ retval = si470x_set_register(radio, POWERCFG);
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "set tuner failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_g_frequency - get tuner or modulator radio frequency
+ */
+static int si470x_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ freq->type = V4L2_TUNER_RADIO;
+ retval = si470x_get_freq(radio, &freq->frequency);
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "get frequency failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_frequency - set tuner or modulator radio frequency
+ */
+static int si470x_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_set_freq(radio, freq->frequency);
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "set frequency failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
+ */
+static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ struct v4l2_hw_freq_seek *seek)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ retval = si470x_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (seek->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si470x_set_seek(radio, seek->wrap_around, seek->seek_upward);
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "set hardware frequency seek failed with %d\n", retval);
+ return retval;
+}
+
+
+/*
+ * si470x_ioctl_ops - video device ioctl operations
+ */
+static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
+ .vidioc_querycap = si470x_vidioc_querycap,
+ .vidioc_queryctrl = si470x_vidioc_queryctrl,
+ .vidioc_g_ctrl = si470x_vidioc_g_ctrl,
+ .vidioc_s_ctrl = si470x_vidioc_s_ctrl,
+ .vidioc_g_audio = si470x_vidioc_g_audio,
+ .vidioc_g_tuner = si470x_vidioc_g_tuner,
+ .vidioc_s_tuner = si470x_vidioc_s_tuner,
+ .vidioc_g_frequency = si470x_vidioc_g_frequency,
+ .vidioc_s_frequency = si470x_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
+};
+
+
+/*
+ * si470x_viddev_template - video device interface
+ */
+struct video_device si470x_viddev_template = {
+ .fops = &si470x_fops,
+ .name = DRIVER_NAME,
+ .release = video_device_release,
+ .ioctl_ops = &si470x_ioctl_ops,
+};
diff --git a/drivers/media/radio/si470x/radio-si470x-i2c.c b/drivers/media/radio/si470x/radio-si470x-i2c.c
new file mode 100644
index 00000000000..2d53b6a9409
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si470x-i2c.c
@@ -0,0 +1,401 @@
+/*
+ * drivers/media/radio/si470x/radio-si470x-i2c.c
+ *
+ * I2C driver for radios with Silicon Labs Si470x FM Radio Receivers
+ *
+ * Copyright (c) 2009 Samsung Electronics Co.Ltd
+ * Author: Joonyoung Shim <jy0922.shim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * ToDo:
+ * - RDS support
+ */
+
+
+/* driver definitions */
+#define DRIVER_AUTHOR "Joonyoung Shim <jy0922.shim@samsung.com>";
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 0)
+#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
+#define DRIVER_DESC "I2C radio driver for Si470x FM Radio Receivers"
+#define DRIVER_VERSION "1.0.0"
+
+/* kernel includes */
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include "radio-si470x.h"
+
+
+/* I2C Device ID List */
+static const struct i2c_device_id si470x_i2c_id[] = {
+ /* Generic Entry */
+ { "si470x", 0 },
+ /* Terminating entry */
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, si470x_i2c_id);
+
+
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0444);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+
+
+/**************************************************************************
+ * I2C Definitions
+ **************************************************************************/
+
+/* Write starts with the upper byte of register 0x02 */
+#define WRITE_REG_NUM 8
+#define WRITE_INDEX(i) (i + 0x02)
+
+/* Read starts with the upper byte of register 0x0a */
+#define READ_REG_NUM RADIO_REGISTER_NUM
+#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
+
+
+
+/**************************************************************************
+ * General Driver Functions - REGISTERs
+ **************************************************************************/
+
+/*
+ * si470x_get_register - read register
+ */
+int si470x_get_register(struct si470x_device *radio, int regnr)
+{
+ u16 buf[READ_REG_NUM];
+ struct i2c_msg msgs[1] = {
+ { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM,
+ (void *)buf },
+ };
+
+ if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
+ return -EIO;
+
+ radio->registers[regnr] = __be16_to_cpu(buf[READ_INDEX(regnr)]);
+
+ return 0;
+}
+
+
+/*
+ * si470x_set_register - write register
+ */
+int si470x_set_register(struct si470x_device *radio, int regnr)
+{
+ int i;
+ u16 buf[WRITE_REG_NUM];
+ struct i2c_msg msgs[1] = {
+ { radio->client->addr, 0, sizeof(u16) * WRITE_REG_NUM,
+ (void *)buf },
+ };
+
+ for (i = 0; i < WRITE_REG_NUM; i++)
+ buf[i] = __cpu_to_be16(radio->registers[WRITE_INDEX(i)]);
+
+ if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
+ return -EIO;
+
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - ENTIRE REGISTERS
+ **************************************************************************/
+
+/*
+ * si470x_get_all_registers - read entire registers
+ */
+static int si470x_get_all_registers(struct si470x_device *radio)
+{
+ int i;
+ u16 buf[READ_REG_NUM];
+ struct i2c_msg msgs[1] = {
+ { radio->client->addr, I2C_M_RD, sizeof(u16) * READ_REG_NUM,
+ (void *)buf },
+ };
+
+ if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
+ return -EIO;
+
+ for (i = 0; i < READ_REG_NUM; i++)
+ radio->registers[i] = __be16_to_cpu(buf[READ_INDEX(i)]);
+
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - DISCONNECT_CHECK
+ **************************************************************************/
+
+/*
+ * si470x_disconnect_check - check whether radio disconnects
+ */
+int si470x_disconnect_check(struct si470x_device *radio)
+{
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+/*
+ * si470x_fops_open - file open
+ */
+static int si470x_fops_open(struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ mutex_lock(&radio->lock);
+ radio->users++;
+
+ if (radio->users == 1)
+ /* start radio */
+ retval = si470x_start(radio);
+
+ mutex_unlock(&radio->lock);
+
+ return retval;
+}
+
+
+/*
+ * si470x_fops_release - file release
+ */
+static int si470x_fops_release(struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety check */
+ if (!radio)
+ return -ENODEV;
+
+ mutex_lock(&radio->lock);
+ radio->users--;
+ if (radio->users == 0)
+ /* stop radio */
+ retval = si470x_stop(radio);
+
+ mutex_unlock(&radio->lock);
+
+ return retval;
+}
+
+
+/*
+ * si470x_fops - file operations interface
+ */
+const struct v4l2_file_operations si470x_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = video_ioctl2,
+ .open = si470x_fops_open,
+ .release = si470x_fops_release,
+};
+
+
+
+/**************************************************************************
+ * Video4Linux Interface
+ **************************************************************************/
+
+/*
+ * si470x_vidioc_querycap - query device capabilities
+ */
+int si470x_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ capability->version = DRIVER_KERNEL_VERSION;
+ capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
+ V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * I2C Interface
+ **************************************************************************/
+
+/*
+ * si470x_i2c_probe - probe for the device
+ */
+static int __devinit si470x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct si470x_device *radio;
+ int retval = 0;
+ unsigned char version_warning = 0;
+
+ /* private data allocation and initialization */
+ radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
+ if (!radio) {
+ retval = -ENOMEM;
+ goto err_initial;
+ }
+ radio->users = 0;
+ radio->client = client;
+ mutex_init(&radio->lock);
+
+ /* video device allocation and initialization */
+ radio->videodev = video_device_alloc();
+ if (!radio->videodev) {
+ retval = -ENOMEM;
+ goto err_radio;
+ }
+ memcpy(radio->videodev, &si470x_viddev_template,
+ sizeof(si470x_viddev_template));
+ video_set_drvdata(radio->videodev, radio);
+
+ /* power up : need 110ms */
+ radio->registers[POWERCFG] = POWERCFG_ENABLE;
+ if (si470x_set_register(radio, POWERCFG) < 0) {
+ retval = -EIO;
+ goto err_all;
+ }
+ msleep(110);
+
+ /* get device and chip versions */
+ if (si470x_get_all_registers(radio) < 0) {
+ retval = -EIO;
+ goto err_video;
+ }
+ dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
+ radio->registers[DEVICEID], radio->registers[CHIPID]);
+ if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
+ dev_warn(&client->dev,
+ "This driver is known to work with "
+ "firmware version %hu,\n", RADIO_FW_VERSION);
+ dev_warn(&client->dev,
+ "but the device has firmware version %hu.\n",
+ radio->registers[CHIPID] & CHIPID_FIRMWARE);
+ version_warning = 1;
+ }
+
+ /* give out version warning */
+ if (version_warning == 1) {
+ dev_warn(&client->dev,
+ "If you have some trouble using this driver,\n");
+ dev_warn(&client->dev,
+ "please report to V4L ML at "
+ "linux-media@vger.kernel.org\n");
+ }
+
+ /* set initial frequency */
+ si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
+
+ /* register video device */
+ retval = video_register_device(radio->videodev, VFL_TYPE_RADIO,
+ radio_nr);
+ if (retval) {
+ dev_warn(&client->dev, "Could not register video device\n");
+ goto err_all;
+ }
+ i2c_set_clientdata(client, radio);
+
+ return 0;
+err_all:
+err_video:
+ video_device_release(radio->videodev);
+err_radio:
+ kfree(radio);
+err_initial:
+ return retval;
+}
+
+
+/*
+ * si470x_i2c_remove - remove the device
+ */
+static __devexit int si470x_i2c_remove(struct i2c_client *client)
+{
+ struct si470x_device *radio = i2c_get_clientdata(client);
+
+ video_unregister_device(radio->videodev);
+ kfree(radio);
+ i2c_set_clientdata(client, NULL);
+
+ return 0;
+}
+
+
+/*
+ * si470x_i2c_driver - i2c driver interface
+ */
+static struct i2c_driver si470x_i2c_driver = {
+ .driver = {
+ .name = "si470x",
+ .owner = THIS_MODULE,
+ },
+ .probe = si470x_i2c_probe,
+ .remove = __devexit_p(si470x_i2c_remove),
+ .id_table = si470x_i2c_id,
+};
+
+
+
+/**************************************************************************
+ * Module Interface
+ **************************************************************************/
+
+/*
+ * si470x_i2c_init - module init
+ */
+static int __init si470x_i2c_init(void)
+{
+ printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+ return i2c_add_driver(&si470x_i2c_driver);
+}
+
+
+/*
+ * si470x_i2c_exit - module exit
+ */
+static void __exit si470x_i2c_exit(void)
+{
+ i2c_del_driver(&si470x_i2c_driver);
+}
+
+
+module_init(si470x_i2c_init);
+module_exit(si470x_i2c_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/media/radio/si470x/radio-si470x-usb.c b/drivers/media/radio/si470x/radio-si470x-usb.c
new file mode 100644
index 00000000000..f2d0e1ddb30
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si470x-usb.c
@@ -0,0 +1,988 @@
+/*
+ * drivers/media/radio/si470x/radio-si470x-usb.c
+ *
+ * USB driver for radios with Silicon Labs Si470x FM Radio Receivers
+ *
+ * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * ToDo:
+ * - add firmware download/update support
+ */
+
+
+/* driver definitions */
+#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 10)
+#define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver"
+#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
+#define DRIVER_VERSION "1.0.10"
+
+/* kernel includes */
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#include "radio-si470x.h"
+
+
+/* USB Device ID List */
+static struct usb_device_id si470x_usb_driver_id_table[] = {
+ /* Silicon Labs USB FM Radio Reference Design */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
+ /* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
+ /* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
+ /* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
+ { USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
+ /* Terminating entry */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
+
+
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0444);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+/* USB timeout */
+static unsigned int usb_timeout = 500;
+module_param(usb_timeout, uint, 0644);
+MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
+
+/* RDS buffer blocks */
+static unsigned int rds_buf = 100;
+module_param(rds_buf, uint, 0444);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+
+/* RDS maximum block errors */
+static unsigned short max_rds_errors = 1;
+/* 0 means 0 errors requiring correction */
+/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
+/* 2 means 3-5 errors requiring correction */
+/* 3 means 6+ errors or errors in checkword, correction not possible */
+module_param(max_rds_errors, ushort, 0644);
+MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
+
+
+
+/**************************************************************************
+ * USB HID Reports
+ **************************************************************************/
+
+/* Reports 1-16 give direct read/write access to the 16 Si470x registers */
+/* with the (REPORT_ID - 1) corresponding to the register address across USB */
+/* endpoint 0 using GET_REPORT and SET_REPORT */
+#define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1)
+#define REGISTER_REPORT(reg) ((reg) + 1)
+
+/* Report 17 gives direct read/write access to the entire Si470x register */
+/* map across endpoint 0 using GET_REPORT and SET_REPORT */
+#define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
+#define ENTIRE_REPORT 17
+
+/* Report 18 is used to send the lowest 6 Si470x registers up the HID */
+/* interrupt endpoint 1 to Windows every 20 milliseconds for status */
+#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
+#define RDS_REPORT 18
+
+/* Report 19: LED state */
+#define LED_REPORT_SIZE 3
+#define LED_REPORT 19
+
+/* Report 19: stream */
+#define STREAM_REPORT_SIZE 3
+#define STREAM_REPORT 19
+
+/* Report 20: scratch */
+#define SCRATCH_PAGE_SIZE 63
+#define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1)
+#define SCRATCH_REPORT 20
+
+/* Reports 19-22: flash upgrade of the C8051F321 */
+#define WRITE_REPORT_SIZE 4
+#define WRITE_REPORT 19
+#define FLASH_REPORT_SIZE 64
+#define FLASH_REPORT 20
+#define CRC_REPORT_SIZE 3
+#define CRC_REPORT 21
+#define RESPONSE_REPORT_SIZE 2
+#define RESPONSE_REPORT 22
+
+/* Report 23: currently unused, but can accept 60 byte reports on the HID */
+/* interrupt out endpoint 2 every 1 millisecond */
+#define UNUSED_REPORT 23
+
+
+
+/**************************************************************************
+ * Software/Hardware Versions from Scratch Page
+ **************************************************************************/
+#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
+#define RADIO_SW_VERSION 7
+#define RADIO_HW_VERSION 1
+
+
+
+/**************************************************************************
+ * LED State Definitions
+ **************************************************************************/
+#define LED_COMMAND 0x35
+
+#define NO_CHANGE_LED 0x00
+#define ALL_COLOR_LED 0x01 /* streaming state */
+#define BLINK_GREEN_LED 0x02 /* connect state */
+#define BLINK_RED_LED 0x04
+#define BLINK_ORANGE_LED 0x10 /* disconnect state */
+#define SOLID_GREEN_LED 0x20 /* tuning/seeking state */
+#define SOLID_RED_LED 0x40 /* bootload state */
+#define SOLID_ORANGE_LED 0x80
+
+
+
+/**************************************************************************
+ * Stream State Definitions
+ **************************************************************************/
+#define STREAM_COMMAND 0x36
+#define STREAM_VIDPID 0x00
+#define STREAM_AUDIO 0xff
+
+
+
+/**************************************************************************
+ * Bootloader / Flash Commands
+ **************************************************************************/
+
+/* unique id sent to bootloader and required to put into a bootload state */
+#define UNIQUE_BL_ID 0x34
+
+/* mask for the flash data */
+#define FLASH_DATA_MASK 0x55
+
+/* bootloader commands */
+#define GET_SW_VERSION_COMMAND 0x00
+#define SET_PAGE_COMMAND 0x01
+#define ERASE_PAGE_COMMAND 0x02
+#define WRITE_PAGE_COMMAND 0x03
+#define CRC_ON_PAGE_COMMAND 0x04
+#define READ_FLASH_BYTE_COMMAND 0x05
+#define RESET_DEVICE_COMMAND 0x06
+#define GET_HW_VERSION_COMMAND 0x07
+#define BLANK 0xff
+
+/* bootloader command responses */
+#define COMMAND_OK 0x01
+#define COMMAND_FAILED 0x02
+#define COMMAND_PENDING 0x03
+
+
+
+/**************************************************************************
+ * General Driver Functions - REGISTER_REPORTs
+ **************************************************************************/
+
+/*
+ * si470x_get_report - receive a HID report
+ */
+static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
+{
+ unsigned char *report = (unsigned char *) buf;
+ int retval;
+
+ retval = usb_control_msg(radio->usbdev,
+ usb_rcvctrlpipe(radio->usbdev, 0),
+ HID_REQ_GET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ report[0], 2,
+ buf, size, usb_timeout);
+
+ if (retval < 0)
+ dev_warn(&radio->intf->dev,
+ "si470x_get_report: usb_control_msg returned %d\n",
+ retval);
+ return retval;
+}
+
+
+/*
+ * si470x_set_report - send a HID report
+ */
+static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
+{
+ unsigned char *report = (unsigned char *) buf;
+ int retval;
+
+ retval = usb_control_msg(radio->usbdev,
+ usb_sndctrlpipe(radio->usbdev, 0),
+ HID_REQ_SET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ report[0], 2,
+ buf, size, usb_timeout);
+
+ if (retval < 0)
+ dev_warn(&radio->intf->dev,
+ "si470x_set_report: usb_control_msg returned %d\n",
+ retval);
+ return retval;
+}
+
+
+/*
+ * si470x_get_register - read register
+ */
+int si470x_get_register(struct si470x_device *radio, int regnr)
+{
+ unsigned char buf[REGISTER_REPORT_SIZE];
+ int retval;
+
+ buf[0] = REGISTER_REPORT(regnr);
+
+ retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+
+ if (retval >= 0)
+ radio->registers[regnr] = get_unaligned_be16(&buf[1]);
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+/*
+ * si470x_set_register - write register
+ */
+int si470x_set_register(struct si470x_device *radio, int regnr)
+{
+ unsigned char buf[REGISTER_REPORT_SIZE];
+ int retval;
+
+ buf[0] = REGISTER_REPORT(regnr);
+ put_unaligned_be16(radio->registers[regnr], &buf[1]);
+
+ retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - ENTIRE_REPORT
+ **************************************************************************/
+
+/*
+ * si470x_get_all_registers - read entire registers
+ */
+static int si470x_get_all_registers(struct si470x_device *radio)
+{
+ unsigned char buf[ENTIRE_REPORT_SIZE];
+ int retval;
+ unsigned char regnr;
+
+ buf[0] = ENTIRE_REPORT;
+
+ retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+
+ if (retval >= 0)
+ for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
+ radio->registers[regnr] = get_unaligned_be16(
+ &buf[regnr * RADIO_REGISTER_SIZE + 1]);
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - LED_REPORT
+ **************************************************************************/
+
+/*
+ * si470x_set_led_state - sets the led state
+ */
+static int si470x_set_led_state(struct si470x_device *radio,
+ unsigned char led_state)
+{
+ unsigned char buf[LED_REPORT_SIZE];
+ int retval;
+
+ buf[0] = LED_REPORT;
+ buf[1] = LED_COMMAND;
+ buf[2] = led_state;
+
+ retval = si470x_set_report(radio, (void *) &buf, sizeof(buf));
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - SCRATCH_REPORT
+ **************************************************************************/
+
+/*
+ * si470x_get_scratch_versions - gets the scratch page and version infos
+ */
+static int si470x_get_scratch_page_versions(struct si470x_device *radio)
+{
+ unsigned char buf[SCRATCH_REPORT_SIZE];
+ int retval;
+
+ buf[0] = SCRATCH_REPORT;
+
+ retval = si470x_get_report(radio, (void *) &buf, sizeof(buf));
+
+ if (retval < 0)
+ dev_warn(&radio->intf->dev, "si470x_get_scratch: "
+ "si470x_get_report returned %d\n", retval);
+ else {
+ radio->software_version = buf[1];
+ radio->hardware_version = buf[2];
+ }
+
+ return (retval < 0) ? -EINVAL : 0;
+}
+
+
+
+/**************************************************************************
+ * General Driver Functions - DISCONNECT_CHECK
+ **************************************************************************/
+
+/*
+ * si470x_disconnect_check - check whether radio disconnects
+ */
+int si470x_disconnect_check(struct si470x_device *radio)
+{
+ if (radio->disconnected)
+ return -EIO;
+ else
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * RDS Driver Functions
+ **************************************************************************/
+
+/*
+ * si470x_int_in_callback - rds callback and processing function
+ *
+ * TODO: do we need to use mutex locks in some sections?
+ */
+static void si470x_int_in_callback(struct urb *urb)
+{
+ struct si470x_device *radio = urb->context;
+ unsigned char buf[RDS_REPORT_SIZE];
+ int retval;
+ unsigned char regnr;
+ unsigned char blocknum;
+ unsigned short bler; /* rds block errors */
+ unsigned short rds;
+ unsigned char tmpbuf[3];
+
+ if (urb->status) {
+ if (urb->status == -ENOENT ||
+ urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN) {
+ return;
+ } else {
+ dev_warn(&radio->intf->dev,
+ "non-zero urb status (%d)\n", urb->status);
+ goto resubmit; /* Maybe we can recover. */
+ }
+ }
+
+ /* safety checks */
+ if (radio->disconnected)
+ return;
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
+ goto resubmit;
+
+ if (urb->actual_length > 0) {
+ /* Update RDS registers with URB data */
+ buf[0] = RDS_REPORT;
+ for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++)
+ radio->registers[STATUSRSSI + regnr] =
+ get_unaligned_be16(&radio->int_in_buffer[
+ regnr * RADIO_REGISTER_SIZE + 1]);
+ /* get rds blocks */
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
+ /* No RDS group ready, better luck next time */
+ goto resubmit;
+ }
+ if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
+ /* RDS decoder not synchronized */
+ goto resubmit;
+ }
+ for (blocknum = 0; blocknum < 4; blocknum++) {
+ switch (blocknum) {
+ default:
+ bler = (radio->registers[STATUSRSSI] &
+ STATUSRSSI_BLERA) >> 9;
+ rds = radio->registers[RDSA];
+ break;
+ case 1:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERB) >> 14;
+ rds = radio->registers[RDSB];
+ break;
+ case 2:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERC) >> 12;
+ rds = radio->registers[RDSC];
+ break;
+ case 3:
+ bler = (radio->registers[READCHAN] &
+ READCHAN_BLERD) >> 10;
+ rds = radio->registers[RDSD];
+ break;
+ };
+
+ /* Fill the V4L2 RDS buffer */
+ put_unaligned_le16(rds, &tmpbuf);
+ tmpbuf[2] = blocknum; /* offset name */
+ tmpbuf[2] |= blocknum << 3; /* received offset */
+ if (bler > max_rds_errors)
+ tmpbuf[2] |= 0x80; /* uncorrectable errors */
+ else if (bler > 0)
+ tmpbuf[2] |= 0x40; /* corrected error(s) */
+
+ /* copy RDS block to internal buffer */
+ memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
+ radio->wr_index += 3;
+
+ /* wrap write pointer */
+ if (radio->wr_index >= radio->buf_size)
+ radio->wr_index = 0;
+
+ /* check for overflow */
+ if (radio->wr_index == radio->rd_index) {
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+ }
+ }
+ if (radio->wr_index != radio->rd_index)
+ wake_up_interruptible(&radio->read_queue);
+ }
+
+resubmit:
+ /* Resubmit if we're still running. */
+ if (radio->int_in_running && radio->usbdev) {
+ retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
+ if (retval) {
+ dev_warn(&radio->intf->dev,
+ "resubmitting urb failed (%d)", retval);
+ radio->int_in_running = 0;
+ }
+ }
+}
+
+
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+/*
+ * si470x_fops_read - read RDS data
+ */
+static ssize_t si470x_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+ unsigned int block_count = 0;
+
+ /* switch on rds reception */
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
+ si470x_rds_on(radio);
+
+ /* block if no new data available */
+ while (radio->wr_index == radio->rd_index) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EWOULDBLOCK;
+ goto done;
+ }
+ if (wait_event_interruptible(radio->read_queue,
+ radio->wr_index != radio->rd_index) < 0) {
+ retval = -EINTR;
+ goto done;
+ }
+ }
+
+ /* calculate block count from byte count */
+ count /= 3;
+
+ /* copy RDS block out of internal buffer and to user buffer */
+ mutex_lock(&radio->lock);
+ while (block_count < count) {
+ if (radio->rd_index == radio->wr_index)
+ break;
+
+ /* always transfer rds complete blocks */
+ if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
+ /* retval = -EFAULT; */
+ break;
+
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+
+ /* increment counters */
+ block_count++;
+ buf += 3;
+ retval += 3;
+ }
+ mutex_unlock(&radio->lock);
+
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_fops_poll - poll RDS data
+ */
+static unsigned int si470x_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* switch on rds reception */
+ if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
+ si470x_rds_on(radio);
+
+ poll_wait(file, &radio->read_queue, pts);
+
+ if (radio->rd_index != radio->wr_index)
+ retval = POLLIN | POLLRDNORM;
+
+ return retval;
+}
+
+
+/*
+ * si470x_fops_open - file open
+ */
+static int si470x_fops_open(struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval;
+
+ lock_kernel();
+ radio->users++;
+
+ retval = usb_autopm_get_interface(radio->intf);
+ if (retval < 0) {
+ radio->users--;
+ retval = -EIO;
+ goto done;
+ }
+
+ if (radio->users == 1) {
+ /* start radio */
+ retval = si470x_start(radio);
+ if (retval < 0) {
+ usb_autopm_put_interface(radio->intf);
+ goto done;
+ }
+
+ /* initialize interrupt urb */
+ usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
+ usb_rcvintpipe(radio->usbdev,
+ radio->int_in_endpoint->bEndpointAddress),
+ radio->int_in_buffer,
+ le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
+ si470x_int_in_callback,
+ radio,
+ radio->int_in_endpoint->bInterval);
+
+ radio->int_in_running = 1;
+ mb();
+
+ retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
+ if (retval) {
+ dev_info(&radio->intf->dev,
+ "submitting int urb failed (%d)\n", retval);
+ radio->int_in_running = 0;
+ usb_autopm_put_interface(radio->intf);
+ }
+ }
+
+done:
+ unlock_kernel();
+ return retval;
+}
+
+
+/*
+ * si470x_fops_release - file release
+ */
+static int si470x_fops_release(struct file *file)
+{
+ struct si470x_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety check */
+ if (!radio) {
+ retval = -ENODEV;
+ goto done;
+ }
+
+ mutex_lock(&radio->disconnect_lock);
+ radio->users--;
+ if (radio->users == 0) {
+ /* shutdown interrupt handler */
+ if (radio->int_in_running) {
+ radio->int_in_running = 0;
+ if (radio->int_in_urb)
+ usb_kill_urb(radio->int_in_urb);
+ }
+
+ if (radio->disconnected) {
+ video_unregister_device(radio->videodev);
+ kfree(radio->int_in_buffer);
+ kfree(radio->buffer);
+ kfree(radio);
+ goto unlock;
+ }
+
+ /* cancel read processes */
+ wake_up_interruptible(&radio->read_queue);
+
+ /* stop radio */
+ retval = si470x_stop(radio);
+ usb_autopm_put_interface(radio->intf);
+ }
+unlock:
+ mutex_unlock(&radio->disconnect_lock);
+done:
+ return retval;
+}
+
+
+/*
+ * si470x_fops - file operations interface
+ */
+const struct v4l2_file_operations si470x_fops = {
+ .owner = THIS_MODULE,
+ .read = si470x_fops_read,
+ .poll = si470x_fops_poll,
+ .ioctl = video_ioctl2,
+ .open = si470x_fops_open,
+ .release = si470x_fops_release,
+};
+
+
+
+/**************************************************************************
+ * Video4Linux Interface
+ **************************************************************************/
+
+/*
+ * si470x_vidioc_querycap - query device capabilities
+ */
+int si470x_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ struct si470x_device *radio = video_drvdata(file);
+
+ strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ usb_make_path(radio->usbdev, capability->bus_info,
+ sizeof(capability->bus_info));
+ capability->version = DRIVER_KERNEL_VERSION;
+ capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
+ V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
+
+ return 0;
+}
+
+
+
+/**************************************************************************
+ * USB Interface
+ **************************************************************************/
+
+/*
+ * si470x_usb_driver_probe - probe for the device
+ */
+static int si470x_usb_driver_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct si470x_device *radio;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int i, int_end_size, retval = 0;
+ unsigned char version_warning = 0;
+
+ /* private data allocation and initialization */
+ radio = kzalloc(sizeof(struct si470x_device), GFP_KERNEL);
+ if (!radio) {
+ retval = -ENOMEM;
+ goto err_initial;
+ }
+ radio->users = 0;
+ radio->disconnected = 0;
+ radio->usbdev = interface_to_usbdev(intf);
+ radio->intf = intf;
+ mutex_init(&radio->disconnect_lock);
+ mutex_init(&radio->lock);
+
+ iface_desc = intf->cur_altsetting;
+
+ /* Set up interrupt endpoint information. */
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+ if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
+ USB_DIR_IN) && ((endpoint->bmAttributes &
+ USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT))
+ radio->int_in_endpoint = endpoint;
+ }
+ if (!radio->int_in_endpoint) {
+ dev_info(&intf->dev, "could not find interrupt in endpoint\n");
+ retval = -EIO;
+ goto err_radio;
+ }
+
+ int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
+
+ radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
+ if (!radio->int_in_buffer) {
+ dev_info(&intf->dev, "could not allocate int_in_buffer");
+ retval = -ENOMEM;
+ goto err_radio;
+ }
+
+ radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!radio->int_in_urb) {
+ dev_info(&intf->dev, "could not allocate int_in_urb");
+ retval = -ENOMEM;
+ goto err_intbuffer;
+ }
+
+ /* video device allocation and initialization */
+ radio->videodev = video_device_alloc();
+ if (!radio->videodev) {
+ retval = -ENOMEM;
+ goto err_intbuffer;
+ }
+ memcpy(radio->videodev, &si470x_viddev_template,
+ sizeof(si470x_viddev_template));
+ video_set_drvdata(radio->videodev, radio);
+
+ /* get device and chip versions */
+ if (si470x_get_all_registers(radio) < 0) {
+ retval = -EIO;
+ goto err_video;
+ }
+ dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
+ radio->registers[DEVICEID], radio->registers[CHIPID]);
+ if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
+ dev_warn(&intf->dev,
+ "This driver is known to work with "
+ "firmware version %hu,\n", RADIO_FW_VERSION);
+ dev_warn(&intf->dev,
+ "but the device has firmware version %hu.\n",
+ radio->registers[CHIPID] & CHIPID_FIRMWARE);
+ version_warning = 1;
+ }
+
+ /* get software and hardware versions */
+ if (si470x_get_scratch_page_versions(radio) < 0) {
+ retval = -EIO;
+ goto err_video;
+ }
+ dev_info(&intf->dev, "software version %d, hardware version %d\n",
+ radio->software_version, radio->hardware_version);
+ if (radio->software_version < RADIO_SW_VERSION) {
+ dev_warn(&intf->dev,
+ "This driver is known to work with "
+ "software version %hu,\n", RADIO_SW_VERSION);
+ dev_warn(&intf->dev,
+ "but the device has software version %hu.\n",
+ radio->software_version);
+ version_warning = 1;
+ }
+ if (radio->hardware_version < RADIO_HW_VERSION) {
+ dev_warn(&intf->dev,
+ "This driver is known to work with "
+ "hardware version %hu,\n", RADIO_HW_VERSION);
+ dev_warn(&intf->dev,
+ "but the device has hardware version %hu.\n",
+ radio->hardware_version);
+ version_warning = 1;
+ }
+
+ /* give out version warning */
+ if (version_warning == 1) {
+ dev_warn(&intf->dev,
+ "If you have some trouble using this driver,\n");
+ dev_warn(&intf->dev,
+ "please report to V4L ML at "
+ "linux-media@vger.kernel.org\n");
+ }
+
+ /* set initial frequency */
+ si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
+
+ /* set led to connect state */
+ si470x_set_led_state(radio, BLINK_GREEN_LED);
+
+ /* rds buffer allocation */
+ radio->buf_size = rds_buf * 3;
+ radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
+ if (!radio->buffer) {
+ retval = -EIO;
+ goto err_video;
+ }
+
+ /* rds buffer configuration */
+ radio->wr_index = 0;
+ radio->rd_index = 0;
+ init_waitqueue_head(&radio->read_queue);
+
+ /* register video device */
+ retval = video_register_device(radio->videodev, VFL_TYPE_RADIO,
+ radio_nr);
+ if (retval) {
+ dev_warn(&intf->dev, "Could not register video device\n");
+ goto err_all;
+ }
+ usb_set_intfdata(intf, radio);
+
+ return 0;
+err_all:
+ kfree(radio->buffer);
+err_video:
+ video_device_release(radio->videodev);
+err_intbuffer:
+ kfree(radio->int_in_buffer);
+err_radio:
+ kfree(radio);
+err_initial:
+ return retval;
+}
+
+
+/*
+ * si470x_usb_driver_suspend - suspend the device
+ */
+static int si470x_usb_driver_suspend(struct usb_interface *intf,
+ pm_message_t message)
+{
+ dev_info(&intf->dev, "suspending now...\n");
+
+ return 0;
+}
+
+
+/*
+ * si470x_usb_driver_resume - resume the device
+ */
+static int si470x_usb_driver_resume(struct usb_interface *intf)
+{
+ dev_info(&intf->dev, "resuming now...\n");
+
+ return 0;
+}
+
+
+/*
+ * si470x_usb_driver_disconnect - disconnect the device
+ */
+static void si470x_usb_driver_disconnect(struct usb_interface *intf)
+{
+ struct si470x_device *radio = usb_get_intfdata(intf);
+
+ mutex_lock(&radio->disconnect_lock);
+ radio->disconnected = 1;
+ usb_set_intfdata(intf, NULL);
+ if (radio->users == 0) {
+ /* set led to disconnect state */
+ si470x_set_led_state(radio, BLINK_ORANGE_LED);
+
+ /* Free data structures. */
+ usb_free_urb(radio->int_in_urb);
+
+ kfree(radio->int_in_buffer);
+ video_unregister_device(radio->videodev);
+ kfree(radio->buffer);
+ kfree(radio);
+ }
+ mutex_unlock(&radio->disconnect_lock);
+}
+
+
+/*
+ * si470x_usb_driver - usb driver interface
+ */
+static struct usb_driver si470x_usb_driver = {
+ .name = DRIVER_NAME,
+ .probe = si470x_usb_driver_probe,
+ .disconnect = si470x_usb_driver_disconnect,
+ .suspend = si470x_usb_driver_suspend,
+ .resume = si470x_usb_driver_resume,
+ .id_table = si470x_usb_driver_id_table,
+ .supports_autosuspend = 1,
+};
+
+
+
+/**************************************************************************
+ * Module Interface
+ **************************************************************************/
+
+/*
+ * si470x_module_init - module init
+ */
+static int __init si470x_module_init(void)
+{
+ printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+ return usb_register(&si470x_usb_driver);
+}
+
+
+/*
+ * si470x_module_exit - module exit
+ */
+static void __exit si470x_module_exit(void)
+{
+ usb_deregister(&si470x_usb_driver);
+}
+
+
+module_init(si470x_module_init);
+module_exit(si470x_module_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
diff --git a/drivers/media/radio/si470x/radio-si470x.h b/drivers/media/radio/si470x/radio-si470x.h
new file mode 100644
index 00000000000..d0af194d194
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si470x.h
@@ -0,0 +1,225 @@
+/*
+ * drivers/media/radio/si470x/radio-si470x.h
+ *
+ * Driver for radios with Silicon Labs Si470x FM Radio Receivers
+ *
+ * Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* driver definitions */
+#define DRIVER_NAME "radio-si470x"
+
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/input.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/rds.h>
+#include <asm/unaligned.h>
+
+
+
+/**************************************************************************
+ * Register Definitions
+ **************************************************************************/
+#define RADIO_REGISTER_SIZE 2 /* 16 register bit width */
+#define RADIO_REGISTER_NUM 16 /* DEVICEID ... RDSD */
+#define RDS_REGISTER_NUM 6 /* STATUSRSSI ... RDSD */
+
+#define DEVICEID 0 /* Device ID */
+#define DEVICEID_PN 0xf000 /* bits 15..12: Part Number */
+#define DEVICEID_MFGID 0x0fff /* bits 11..00: Manufacturer ID */
+
+#define CHIPID 1 /* Chip ID */
+#define CHIPID_REV 0xfc00 /* bits 15..10: Chip Version */
+#define CHIPID_DEV 0x0200 /* bits 09..09: Device */
+#define CHIPID_FIRMWARE 0x01ff /* bits 08..00: Firmware Version */
+
+#define POWERCFG 2 /* Power Configuration */
+#define POWERCFG_DSMUTE 0x8000 /* bits 15..15: Softmute Disable */
+#define POWERCFG_DMUTE 0x4000 /* bits 14..14: Mute Disable */
+#define POWERCFG_MONO 0x2000 /* bits 13..13: Mono Select */
+#define POWERCFG_RDSM 0x0800 /* bits 11..11: RDS Mode (Si4701 only) */
+#define POWERCFG_SKMODE 0x0400 /* bits 10..10: Seek Mode */
+#define POWERCFG_SEEKUP 0x0200 /* bits 09..09: Seek Direction */
+#define POWERCFG_SEEK 0x0100 /* bits 08..08: Seek */
+#define POWERCFG_DISABLE 0x0040 /* bits 06..06: Powerup Disable */
+#define POWERCFG_ENABLE 0x0001 /* bits 00..00: Powerup Enable */
+
+#define CHANNEL 3 /* Channel */
+#define CHANNEL_TUNE 0x8000 /* bits 15..15: Tune */
+#define CHANNEL_CHAN 0x03ff /* bits 09..00: Channel Select */
+
+#define SYSCONFIG1 4 /* System Configuration 1 */
+#define SYSCONFIG1_RDSIEN 0x8000 /* bits 15..15: RDS Interrupt Enable (Si4701 only) */
+#define SYSCONFIG1_STCIEN 0x4000 /* bits 14..14: Seek/Tune Complete Interrupt Enable */
+#define SYSCONFIG1_RDS 0x1000 /* bits 12..12: RDS Enable (Si4701 only) */
+#define SYSCONFIG1_DE 0x0800 /* bits 11..11: De-emphasis (0=75us 1=50us) */
+#define SYSCONFIG1_AGCD 0x0400 /* bits 10..10: AGC Disable */
+#define SYSCONFIG1_BLNDADJ 0x00c0 /* bits 07..06: Stereo/Mono Blend Level Adjustment */
+#define SYSCONFIG1_GPIO3 0x0030 /* bits 05..04: General Purpose I/O 3 */
+#define SYSCONFIG1_GPIO2 0x000c /* bits 03..02: General Purpose I/O 2 */
+#define SYSCONFIG1_GPIO1 0x0003 /* bits 01..00: General Purpose I/O 1 */
+
+#define SYSCONFIG2 5 /* System Configuration 2 */
+#define SYSCONFIG2_SEEKTH 0xff00 /* bits 15..08: RSSI Seek Threshold */
+#define SYSCONFIG2_BAND 0x0080 /* bits 07..06: Band Select */
+#define SYSCONFIG2_SPACE 0x0030 /* bits 05..04: Channel Spacing */
+#define SYSCONFIG2_VOLUME 0x000f /* bits 03..00: Volume */
+
+#define SYSCONFIG3 6 /* System Configuration 3 */
+#define SYSCONFIG3_SMUTER 0xc000 /* bits 15..14: Softmute Attack/Recover Rate */
+#define SYSCONFIG3_SMUTEA 0x3000 /* bits 13..12: Softmute Attenuation */
+#define SYSCONFIG3_SKSNR 0x00f0 /* bits 07..04: Seek SNR Threshold */
+#define SYSCONFIG3_SKCNT 0x000f /* bits 03..00: Seek FM Impulse Detection Threshold */
+
+#define TEST1 7 /* Test 1 */
+#define TEST1_AHIZEN 0x4000 /* bits 14..14: Audio High-Z Enable */
+
+#define TEST2 8 /* Test 2 */
+/* TEST2 only contains reserved bits */
+
+#define BOOTCONFIG 9 /* Boot Configuration */
+/* BOOTCONFIG only contains reserved bits */
+
+#define STATUSRSSI 10 /* Status RSSI */
+#define STATUSRSSI_RDSR 0x8000 /* bits 15..15: RDS Ready (Si4701 only) */
+#define STATUSRSSI_STC 0x4000 /* bits 14..14: Seek/Tune Complete */
+#define STATUSRSSI_SF 0x2000 /* bits 13..13: Seek Fail/Band Limit */
+#define STATUSRSSI_AFCRL 0x1000 /* bits 12..12: AFC Rail */
+#define STATUSRSSI_RDSS 0x0800 /* bits 11..11: RDS Synchronized (Si4701 only) */
+#define STATUSRSSI_BLERA 0x0600 /* bits 10..09: RDS Block A Errors (Si4701 only) */
+#define STATUSRSSI_ST 0x0100 /* bits 08..08: Stereo Indicator */
+#define STATUSRSSI_RSSI 0x00ff /* bits 07..00: RSSI (Received Signal Strength Indicator) */
+
+#define READCHAN 11 /* Read Channel */
+#define READCHAN_BLERB 0xc000 /* bits 15..14: RDS Block D Errors (Si4701 only) */
+#define READCHAN_BLERC 0x3000 /* bits 13..12: RDS Block C Errors (Si4701 only) */
+#define READCHAN_BLERD 0x0c00 /* bits 11..10: RDS Block B Errors (Si4701 only) */
+#define READCHAN_READCHAN 0x03ff /* bits 09..00: Read Channel */
+
+#define RDSA 12 /* RDSA */
+#define RDSA_RDSA 0xffff /* bits 15..00: RDS Block A Data (Si4701 only) */
+
+#define RDSB 13 /* RDSB */
+#define RDSB_RDSB 0xffff /* bits 15..00: RDS Block B Data (Si4701 only) */
+
+#define RDSC 14 /* RDSC */
+#define RDSC_RDSC 0xffff /* bits 15..00: RDS Block C Data (Si4701 only) */
+
+#define RDSD 15 /* RDSD */
+#define RDSD_RDSD 0xffff /* bits 15..00: RDS Block D Data (Si4701 only) */
+
+
+
+/**************************************************************************
+ * General Driver Definitions
+ **************************************************************************/
+
+/*
+ * si470x_device - private data
+ */
+struct si470x_device {
+ struct video_device *videodev;
+
+ /* driver management */
+ unsigned int users;
+
+ /* Silabs internal registers (0..15) */
+ unsigned short registers[RADIO_REGISTER_NUM];
+
+ /* RDS receive buffer */
+ wait_queue_head_t read_queue;
+ struct mutex lock; /* buffer locking */
+ unsigned char *buffer; /* size is always multiple of three */
+ unsigned int buf_size;
+ unsigned int rd_index;
+ unsigned int wr_index;
+
+#if defined(CONFIG_USB_SI470X) || defined(CONFIG_USB_SI470X_MODULE)
+ /* reference to USB and video device */
+ struct usb_device *usbdev;
+ struct usb_interface *intf;
+
+ /* Interrupt endpoint handling */
+ char *int_in_buffer;
+ struct usb_endpoint_descriptor *int_in_endpoint;
+ struct urb *int_in_urb;
+ int int_in_running;
+
+ /* scratch page */
+ unsigned char software_version;
+ unsigned char hardware_version;
+
+ /* driver management */
+ unsigned char disconnected;
+ struct mutex disconnect_lock;
+#endif
+
+#if defined(CONFIG_I2C_SI470X) || defined(CONFIG_I2C_SI470X_MODULE)
+ struct i2c_client *client;
+#endif
+};
+
+
+
+/**************************************************************************
+ * Firmware Versions
+ **************************************************************************/
+
+#define RADIO_FW_VERSION 15
+
+
+
+/**************************************************************************
+ * Frequency Multiplicator
+ **************************************************************************/
+
+/*
+ * The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW,
+ * 62.5 kHz otherwise.
+ * The tuner is able to have a channel spacing of 50, 100 or 200 kHz.
+ * tuner->capability is therefore set to V4L2_TUNER_CAP_LOW
+ * The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000
+ */
+#define FREQ_MUL (1000000 / 62.5)
+
+
+
+/**************************************************************************
+ * Common Functions
+ **************************************************************************/
+extern const struct v4l2_file_operations si470x_fops;
+extern struct video_device si470x_viddev_template;
+int si470x_get_register(struct si470x_device *radio, int regnr);
+int si470x_set_register(struct si470x_device *radio, int regnr);
+int si470x_disconnect_check(struct si470x_device *radio);
+int si470x_set_freq(struct si470x_device *radio, unsigned int freq);
+int si470x_start(struct si470x_device *radio);
+int si470x_stop(struct si470x_device *radio);
+int si470x_rds_on(struct si470x_device *radio);
+int si470x_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability);
diff --git a/drivers/media/radio/si4713-i2c.c b/drivers/media/radio/si4713-i2c.c
new file mode 100644
index 00000000000..6a0028eb461
--- /dev/null
+++ b/drivers/media/radio/si4713-i2c.c
@@ -0,0 +1,2060 @@
+/*
+ * drivers/media/radio/si4713-i2c.c
+ *
+ * Silicon Labs Si4713 FM Radio Transmitter I2C commands.
+ *
+ * Copyright (c) 2009 Nokia Corporation
+ * Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/mutex.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-common.h>
+
+#include "si4713-i2c.h"
+
+/* module parameters */
+static int debug;
+module_param(debug, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug level (0 - 2)");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
+MODULE_DESCRIPTION("I2C driver for Si4713 FM Radio Transmitter");
+MODULE_VERSION("0.0.1");
+
+#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_DEVIATION 0x1A5E
+#define DEFAULT_ACOMP_ATIME 0x0000
+#define DEFAULT_ACOMP_RTIME 0xF4240L
+#define DEFAULT_ACOMP_GAIN 0x0F
+#define DEFAULT_ACOMP_THRESHOLD (-0x28)
+#define DEFAULT_MUTE 0x01
+#define DEFAULT_POWER_LEVEL 88
+#define DEFAULT_FREQUENCY 8800
+#define DEFAULT_PREEMPHASIS FMPE_EU
+#define DEFAULT_TUNE_RNL 0xFF
+
+#define to_si4713_device(sd) container_of(sd, struct si4713_device, sd)
+
+/* frequency domain transformation (using times 10 to avoid floats) */
+#define FREQDEV_UNIT 100000
+#define FREQV4L2_MULTI 625
+#define si4713_to_v4l2(f) ((f * FREQDEV_UNIT) / FREQV4L2_MULTI)
+#define v4l2_to_si4713(f) ((f * FREQV4L2_MULTI) / FREQDEV_UNIT)
+#define FREQ_RANGE_LOW 7600
+#define FREQ_RANGE_HIGH 10800
+
+#define MAX_ARGS 7
+
+#define RDS_BLOCK 8
+#define RDS_BLOCK_CLEAR 0x03
+#define RDS_BLOCK_LOAD 0x04
+#define RDS_RADIOTEXT_2A 0x20
+#define RDS_RADIOTEXT_BLK_SIZE 4
+#define RDS_RADIOTEXT_INDEX_MAX 0x0F
+#define RDS_CARRIAGE_RETURN 0x0D
+
+#define rds_ps_nblocks(len) ((len / RDS_BLOCK) + (len % RDS_BLOCK ? 1 : 0))
+
+#define get_status_bit(p, b, m) (((p) & (m)) >> (b))
+#define set_bits(p, v, b, m) (((p) & ~(m)) | ((v) << (b)))
+
+#define ATTACK_TIME_UNIT 500
+
+#define POWER_OFF 0x00
+#define POWER_ON 0x01
+
+#define msb(x) ((u8)((u16) x >> 8))
+#define lsb(x) ((u8)((u16) x & 0x00FF))
+#define compose_u16(msb, lsb) (((u16)msb << 8) | lsb)
+#define check_command_failed(status) (!(status & SI4713_CTS) || \
+ (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) \
+ { \
+ int i; \
+ char str[(size)*5]; \
+ for (i = 0; i < size; i++) \
+ sprintf(str + i * 5, " 0x%02x", buffer[i]); \
+ v4l2_dbg(2, debug, device, "%s:%s\n", message, str); \
+ }
+#else
+#define DBG_BUFFER(device, message, buffer, size)
+#endif
+
+/*
+ * Values for limiter release time (sorted by second column)
+ * device release
+ * value time (us)
+ */
+static long limiter_times[] = {
+ 2000, 250,
+ 1000, 500,
+ 510, 1000,
+ 255, 2000,
+ 170, 3000,
+ 127, 4020,
+ 102, 5010,
+ 85, 6020,
+ 73, 7010,
+ 64, 7990,
+ 57, 8970,
+ 51, 10030,
+ 25, 20470,
+ 17, 30110,
+ 13, 39380,
+ 10, 51190,
+ 8, 63690,
+ 7, 73140,
+ 6, 85330,
+ 5, 102390,
+};
+
+/*
+ * Values for audio compression release time (sorted by second column)
+ * device release
+ * value time (us)
+ */
+static unsigned long acomp_rtimes[] = {
+ 0, 100000,
+ 1, 200000,
+ 2, 350000,
+ 3, 525000,
+ 4, 1000000,
+};
+
+/*
+ * Values for preemphasis (sorted by second column)
+ * device preemphasis
+ * value value (v4l2)
+ */
+static unsigned long preemphasis_values[] = {
+ FMPE_DISABLED, V4L2_PREEMPHASIS_DISABLED,
+ FMPE_EU, V4L2_PREEMPHASIS_50_uS,
+ FMPE_USA, V4L2_PREEMPHASIS_75_uS,
+};
+
+static int usecs_to_dev(unsigned long usecs, unsigned long const array[],
+ int size)
+{
+ int i;
+ int rval = -EINVAL;
+
+ for (i = 0; i < size / 2; i++)
+ if (array[(i * 2) + 1] >= usecs) {
+ rval = array[i * 2];
+ break;
+ }
+
+ 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)
+{
+ struct si4713_device *sdev = dev;
+
+ v4l2_dbg(2, debug, &sdev->sd,
+ "%s: sending signal to completion work.\n", __func__);
+ complete(&sdev->work);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * si4713_send_command - sends a command to si4713 and waits its response
+ * @sdev: si4713_device structure for the device we are communicating
+ * @command: command id
+ * @args: command arguments we are sending (up to 7)
+ * @argn: actual size of @args
+ * @response: buffer to place the expected response from the device (up to 15)
+ * @respn: actual size of @response
+ * @usecs: amount of time to wait before reading the response (in usecs)
+ */
+static int si4713_send_command(struct si4713_device *sdev, const u8 command,
+ const u8 args[], const int argn,
+ u8 response[], const int respn, const int usecs)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd);
+ u8 data1[MAX_ARGS + 1];
+ int err;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ /* First send the command and its arguments */
+ data1[0] = command;
+ memcpy(data1 + 1, args, argn);
+ DBG_BUFFER(&sdev->sd, "Parameters", data1, argn + 1);
+
+ err = i2c_master_send(client, data1, argn + 1);
+ if (err != argn + 1) {
+ v4l2_err(&sdev->sd, "Error while sending command 0x%02x\n",
+ command);
+ return (err > 0) ? -EIO : err;
+ }
+
+ /* Wait response from interrupt */
+ if (!wait_for_completion_timeout(&sdev->work,
+ usecs_to_jiffies(usecs) + 1))
+ 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;
+
+ return 0;
+}
+
+/*
+ * si4713_read_property - reads a si4713 property
+ * @sdev: si4713_device structure for the device we are communicating
+ * @prop: property identification number
+ * @pv: property value to be returned on success
+ */
+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
+ */
+ const u8 args[SI4713_GET_PROP_NARGS] = {
+ 0x00,
+ msb(prop),
+ lsb(prop),
+ };
+
+ err = si4713_send_command(sdev, SI4713_CMD_GET_PROPERTY,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (err < 0)
+ return err;
+
+ *pv = compose_u16(val[2], val[3]);
+
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: property=0x%02x value=0x%02x status=0x%02x\n",
+ __func__, prop, *pv, val[0]);
+
+ return err;
+}
+
+/*
+ * si4713_write_property - modifies a si4713 property
+ * @sdev: si4713_device structure for the device we are communicating
+ * @prop: property identification number
+ * @val: new value for that property
+ */
+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
+ */
+ const u8 args[SI4713_SET_PROP_NARGS] = {
+ 0x00,
+ msb(prop),
+ lsb(prop),
+ msb(val),
+ lsb(val),
+ };
+
+ rval = si4713_send_command(sdev, SI4713_CMD_SET_PROPERTY,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ DEFAULT_TIMEOUT);
+
+ if (rval < 0)
+ return rval;
+
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: property=0x%02x value=0x%02x status=0x%02x\n",
+ __func__, prop, val, resp[0]);
+
+ /*
+ * As there is no command response for SET_PROPERTY,
+ * wait Tcomp time to finish before proceed, in order
+ * to have property properly set.
+ */
+ msleep(TIMEOUT_SET_PROPERTY);
+
+ return rval;
+}
+
+/*
+ * si4713_powerup - Powers the device up
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_powerup(struct si4713_device *sdev)
+{
+ int err;
+ u8 resp[SI4713_PWUP_NRESP];
+ /*
+ * .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,
+ SI4713_PWUP_OPMOD_ANALOG,
+ };
+
+ if (sdev->power_state)
+ return 0;
+
+ sdev->platform_data->set_power(1);
+ err = si4713_send_command(sdev, SI4713_CMD_POWER_UP,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ TIMEOUT_POWER_UP);
+
+ if (!err) {
+ v4l2_dbg(1, debug, &sdev->sd, "Powerup response: 0x%02x\n",
+ resp[0]);
+ 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,
+ SI4713_STC_INT | SI4713_CTS);
+ } else {
+ sdev->platform_data->set_power(0);
+ }
+
+ return err;
+}
+
+/*
+ * si4713_powerdown - Powers the device down
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_powerdown(struct si4713_device *sdev)
+{
+ int err;
+ u8 resp[SI4713_PWDN_NRESP];
+
+ if (!sdev->power_state)
+ return 0;
+
+ err = si4713_send_command(sdev, SI4713_CMD_POWER_DOWN,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ DEFAULT_TIMEOUT);
+
+ if (!err) {
+ v4l2_dbg(1, debug, &sdev->sd, "Power down response: 0x%02x\n",
+ resp[0]);
+ v4l2_dbg(1, debug, &sdev->sd, "Device in reset mode\n");
+ sdev->platform_data->set_power(0);
+ sdev->power_state = POWER_OFF;
+ }
+
+ return err;
+}
+
+/*
+ * si4713_checkrev - Checks if we are treating a device with the correct rev.
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_checkrev(struct si4713_device *sdev)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(&sdev->sd);
+ 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;
+
+ 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");
+ rval = -EINVAL;
+ }
+
+unlock:
+ mutex_unlock(&sdev->mutex);
+ return rval;
+}
+
+/*
+ * si4713_wait_stc - Waits STC interrupt and clears status bits. Usefull
+ * for TX_TUNE_POWER, TX_TUNE_FREQ and TX_TUNE_MEAS
+ * @sdev: si4713_device structure for the device we are communicating
+ * @usecs: timeout to wait for STC interrupt signal
+ */
+static int si4713_wait_stc(struct si4713_device *sdev, const int usecs)
+{
+ int err;
+ u8 resp[SI4713_GET_STATUS_NRESP];
+
+ /* Wait response from STC interrupt */
+ if (!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;
+}
+
+/*
+ * 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.
+ * @sdev: si4713_device structure for the device we are communicating
+ * @frequency: desired frequency (76 - 108 MHz, unit 10 KHz, step 50 kHz)
+ */
+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
+ */
+ const u8 args[SI4713_TXFREQ_NARGS] = {
+ 0x00,
+ msb(frequency),
+ lsb(frequency),
+ };
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_FREQ,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (err < 0)
+ return err;
+
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: frequency=0x%02x status=0x%02x\n", __func__,
+ frequency, val[0]);
+
+ err = si4713_wait_stc(sdev, TIMEOUT_TX_TUNE);
+ if (err < 0)
+ return err;
+
+ return compose_u16(args[1], args[2]);
+}
+
+/*
+ * 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.
+ * @sdev: si4713_device structure for the device we are communicating
+ * @power: tuning power (88 - 115 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,
+ u8 antcap)
+{
+ int err;
+ u8 val[SI4713_TXPWR_NRESP];
+ /*
+ * .First byte = 0
+ * .Second byte = 0
+ * .Third byte = power
+ * .Fourth byte = antcap
+ */
+ const 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;
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_POWER,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (err < 0)
+ return err;
+
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: power=0x%02x antcap=0x%02x status=0x%02x\n",
+ __func__, power, antcap, val[0]);
+
+ return si4713_wait_stc(sdev, TIMEOUT_TX_TUNE_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.
+ * @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)
+ */
+static int si4713_tx_tune_measure(struct si4713_device *sdev, u16 frequency,
+ u8 antcap)
+{
+ int err;
+ u8 val[SI4713_TXMEA_NRESP];
+ /*
+ * .First byte = 0
+ * .Second byte = frequency's MSB
+ * .Third byte = frequency's LSB
+ * .Fourth byte = antcap
+ */
+ const u8 args[SI4713_TXMEA_NARGS] = {
+ 0x00,
+ msb(frequency),
+ lsb(frequency),
+ antcap,
+ };
+
+ sdev->tune_rnl = DEFAULT_TUNE_RNL;
+
+ if (antcap > SI4713_MAX_ANTCAP)
+ return -EDOM;
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_MEASURE,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (err < 0)
+ return err;
+
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: frequency=0x%02x antcap=0x%02x status=0x%02x\n",
+ __func__, frequency, antcap, val[0]);
+
+ return si4713_wait_stc(sdev, TIMEOUT_TX_TUNE);
+}
+
+/*
+ * 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.
+ * @sdev: si4713_device structure for the device we are communicating
+ * @intack: 0x01 to clear the seek/tune complete interrupt status indicator.
+ * @frequency: returned frequency
+ * @power: returned power
+ * @antcap: returned antenna capacitance
+ * @noise: returned noise level
+ */
+static int si4713_tx_tune_status(struct si4713_device *sdev, u8 intack,
+ u16 *frequency, u8 *power,
+ u8 *antcap, u8 *noise)
+{
+ int err;
+ u8 val[SI4713_TXSTATUS_NRESP];
+ /*
+ * .First byte = intack bit
+ */
+ const u8 args[SI4713_TXSTATUS_NARGS] = {
+ intack & SI4713_INTACK_MASK,
+ };
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_TUNE_STATUS,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (!err) {
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: status=0x%02x\n", __func__, val[0]);
+ *frequency = compose_u16(val[2], val[3]);
+ sdev->frequency = *frequency;
+ *power = val[5];
+ *antcap = val[6];
+ *noise = val[7];
+ v4l2_dbg(1, debug, &sdev->sd, "%s: response: %d x 10 kHz "
+ "(power %d, antcap %d, rnl %d)\n", __func__,
+ *frequency, *power, *antcap, *noise);
+ }
+
+ return err;
+}
+
+/*
+ * si4713_tx_rds_buff - Loads the RDS group buffer FIFO or circular buffer.
+ * @sdev: si4713_device structure for the device we are communicating
+ * @mode: the buffer operation mode.
+ * @rdsb: RDS Block B
+ * @rdsc: RDS Block C
+ * @rdsd: RDS Block D
+ * @cbleft: returns the number of available circular buffer blocks minus the
+ * number of used circular buffer blocks.
+ */
+static int si4713_tx_rds_buff(struct si4713_device *sdev, u8 mode, u16 rdsb,
+ u16 rdsc, u16 rdsd, s8 *cbleft)
+{
+ int err;
+ u8 val[SI4713_RDSBUFF_NRESP];
+
+ const u8 args[SI4713_RDSBUFF_NARGS] = {
+ mode & SI4713_RDSBUFF_MODE_MASK,
+ msb(rdsb),
+ lsb(rdsb),
+ msb(rdsc),
+ lsb(rdsc),
+ msb(rdsd),
+ lsb(rdsd),
+ };
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_RDS_BUFF,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (!err) {
+ v4l2_dbg(1, debug, &sdev->sd,
+ "%s: status=0x%02x\n", __func__, val[0]);
+ *cbleft = (s8)val[2] - val[3];
+ v4l2_dbg(1, debug, &sdev->sd, "%s: response: interrupts"
+ " 0x%02x cb avail: %d cb used %d fifo avail"
+ " %d fifo used %d\n", __func__, val[1],
+ val[2], val[3], val[4], val[5]);
+ }
+
+ return err;
+}
+
+/*
+ * si4713_tx_rds_ps - Loads the program service buffer.
+ * @sdev: si4713_device structure for the device we are communicating
+ * @psid: program service id to be loaded.
+ * @pschar: assumed 4 size char array to be loaded into the program service
+ */
+static int si4713_tx_rds_ps(struct si4713_device *sdev, u8 psid,
+ unsigned char *pschar)
+{
+ int err;
+ u8 val[SI4713_RDSPS_NRESP];
+
+ const u8 args[SI4713_RDSPS_NARGS] = {
+ psid & SI4713_RDSPS_PSID_MASK,
+ pschar[0],
+ pschar[1],
+ pschar[2],
+ pschar[3],
+ };
+
+ err = si4713_send_command(sdev, SI4713_CMD_TX_RDS_PS,
+ args, ARRAY_SIZE(args), val,
+ ARRAY_SIZE(val), DEFAULT_TIMEOUT);
+
+ if (err < 0)
+ return err;
+
+ v4l2_dbg(1, debug, &sdev->sd, "%s: status=0x%02x\n", __func__, val[0]);
+
+ return err;
+}
+
+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;
+}
+
+static int si4713_set_mute(struct si4713_device *sdev, u16 mute)
+{
+ int rval = 0;
+
+ 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;
+}
+
+static int si4713_set_rds_ps_name(struct si4713_device *sdev, char *ps_name)
+{
+ int rval = 0, i;
+ u8 len = 0;
+
+ /* We want to clear the whole thing */
+ 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;
+ }
+
+ /* Setup the size to be sent */
+ if (strlen(ps_name))
+ len = strlen(ps_name) - 1;
+ else
+ len = 1;
+
+ rval = si4713_write_property(sdev,
+ SI4713_TX_RDS_PS_MESSAGE_COUNT,
+ rds_ps_nblocks(len));
+ if (rval < 0)
+ goto unlock;
+
+ rval = si4713_write_property(sdev,
+ SI4713_TX_RDS_PS_REPEAT_COUNT,
+ DEFAULT_RDS_PS_REPEAT_COUNT * 2);
+ if (rval < 0)
+ goto unlock;
+ }
+
+ 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)
+{
+ 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;
+
+ rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_CLEAR, 0, 0, 0, &left);
+ if (rval < 0)
+ goto unlock;
+
+ if (!strlen(rt))
+ goto copy;
+
+ 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)) {
+ 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;
+ cr_inserted = 1;
+ break;
+ }
+ }
+ }
+
+ rval = si4713_tx_rds_buff(sdev, RDS_BLOCK_LOAD,
+ compose_u16(RDS_RADIOTEXT_2A, b_index++),
+ compose_u16(rt[t_index], rt[t_index + 1]),
+ compose_u16(rt[t_index + 2], rt[t_index + 3]),
+ &left);
+ if (rval < 0)
+ goto unlock;
+
+ t_index += RDS_RADIOTEXT_BLK_SIZE;
+
+ if (cr_inserted)
+ break;
+ } while (left > 0);
+
+copy:
+ strncpy(sdev->rds_info.radio_text, rt, MAX_RDS_RADIO_TEXT);
+
+unlock:
+ mutex_unlock(&sdev->mutex);
+ return rval;
+}
+
+static int si4713_choose_econtrol_action(struct si4713_device *sdev, u32 id,
+ u32 **shadow, s32 *bit, s32 *mask, u16 *property, int *mul,
+ unsigned long **table, int *size)
+{
+ s32 rval = 0;
+
+ switch (id) {
+ /* FM_TX class controls */
+ 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 > MAX_RDS_PS_NAME) {
+ rval = -ERANGE;
+ goto exit;
+ }
+ rval = copy_from_user(ps_name, control->string, len);
+ if (rval < 0)
+ 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 > MAX_RDS_RADIO_TEXT) {
+ rval = -ERANGE;
+ goto exit;
+ }
+ rval = copy_from_user(radio_text, control->string, len);
+ if (rval < 0)
+ 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:
+ rval = -EINVAL;
+ 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_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *);
+/*
+ * si4713_setup - Sets the device up with current configuration.
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+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);
+
+ /* Device procedure needs to set frequency first */
+ f.frequency = tmp->frequency ? tmp->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);
+
+ vm.index = 0;
+ if (tmp->stereo)
+ vm.txsubchans = V4L2_TUNER_SUB_STEREO;
+ else
+ vm.txsubchans = V4L2_TUNER_SUB_MONO;
+ if (tmp->rds_info.enabled)
+ vm.txsubchans |= V4L2_TUNER_SUB_RDS;
+ si4713_s_modulator(&sdev->sd, &vm);
+
+ kfree(tmp);
+
+ return rval;
+}
+
+/*
+ * si4713_initialize - Sets the device up with default configuration.
+ * @sdev: si4713_device structure for the device we are communicating
+ */
+static int si4713_initialize(struct si4713_device *sdev)
+{
+ int rval;
+
+ rval = si4713_set_power_state(sdev, POWER_ON);
+ if (rval < 0)
+ goto exit;
+
+ rval = si4713_checkrev(sdev);
+ if (rval < 0)
+ goto exit;
+
+ 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;
+
+ 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);
+ 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);
+ 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;
+}
+
+static int si4713_read_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 = 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)
+ 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;
+ }
+ }
+
+ 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 (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX)
+ 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_read_econtrol_string(sdev,
+ ctrls->controls + i);
+ break;
+ case V4L2_CID_TUNE_ANTENNA_CAPACITOR:
+ case V4L2_CID_TUNE_POWER_LEVEL:
+ err = si4713_read_econtrol_tune(sdev,
+ ctrls->controls + i);
+ 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;
+
+ rval = si4713_setup(sdev);
+ if (rval < 0)
+ goto exit;
+
+ rval = si4713_set_mute(sdev, ctrl->value);
+ }
+ break;
+ }
+
+exit:
+ return rval;
+}
+
+/* si4713_ioctl - deal with private ioctls (only rnl for now) */
+long si4713_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
+{
+ struct si4713_device *sdev = to_si4713_device(sd);
+ struct si4713_rnl *rnl = arg;
+ u16 frequency;
+ int rval = 0;
+
+ if (!arg)
+ return -EINVAL;
+
+ mutex_lock(&sdev->mutex);
+ switch (cmd) {
+ case SI4713_IOC_MEASURE_RNL:
+ frequency = v4l2_to_si4713(rnl->frequency);
+
+ if (sdev->power_state) {
+ /* Set desired measurement frequency */
+ rval = si4713_tx_tune_measure(sdev, frequency, 0);
+ if (rval < 0)
+ goto unlock;
+ /* get results from tune status */
+ rval = si4713_update_tune_status(sdev);
+ if (rval < 0)
+ goto unlock;
+ }
+ rnl->rnl = sdev->tune_rnl;
+ break;
+
+ default:
+ /* nothing */
+ 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 (vm->index > 0) {
+ rval = -EINVAL;
+ goto exit;
+ }
+
+ strncpy(vm->name, "FM Modulator", 32);
+ vm->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW |
+ V4L2_TUNER_CAP_RDS;
+
+ /* Report current frequency range limits */
+ 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;
+
+ 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 */
+ if (sdev->stereo)
+ vm->txsubchans = V4L2_TUNER_SUB_STEREO;
+ else
+ vm->txsubchans = V4L2_TUNER_SUB_MONO;
+
+ /* Report rds feature status */
+ if (sdev->rds_info.enabled)
+ vm->txsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ vm->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+
+unlock:
+ mutex_unlock(&sdev->mutex);
+exit:
+ return rval;
+}
+
+/* si4713_s_modulator - set modulator attributes */
+static int si4713_s_modulator(struct v4l2_subdev *sd, struct v4l2_modulator *vm)
+{
+ struct si4713_device *sdev = to_si4713_device(sd);
+ int rval = 0;
+ u16 stereo, rds;
+ u32 p;
+
+ if (!sdev)
+ return -ENODEV;
+
+ if (vm->index > 0)
+ return -EINVAL;
+
+ /* Set audio mode: mono or stereo */
+ if (vm->txsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo = 1;
+ else if (vm->txsubchans & V4L2_TUNER_SUB_MONO)
+ stereo = 0;
+ else
+ return -EINVAL;
+
+ 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;
+
+ p = set_bits(p, stereo, 1, 1 << 1);
+ p = set_bits(p, rds, 2, 1 << 2);
+
+ rval = si4713_write_property(sdev,
+ SI4713_TX_COMPONENT_ENABLE, p);
+ if (rval < 0)
+ goto unlock;
+ }
+
+ sdev->stereo = stereo;
+ sdev->rds_info.enabled = rds;
+
+unlock:
+ mutex_unlock(&sdev->mutex);
+ return rval;
+}
+
+/* si4713_g_frequency - get tuner or modulator radio frequency */
+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 (sdev->power_state) {
+ u16 freq;
+ u8 p, a, n;
+
+ rval = si4713_tx_tune_status(sdev, 0x00, &freq, &p, &a, &n);
+ if (rval < 0)
+ goto unlock;
+
+ 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)
+{
+ 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;
+
+ mutex_lock(&sdev->mutex);
+
+ if (sdev->power_state) {
+ rval = si4713_tx_tune_freq(sdev, frequency);
+ if (rval < 0)
+ goto unlock;
+ frequency = rval;
+ rval = 0;
+ }
+ sdev->frequency = frequency;
+ f->frequency = si4713_to_v4l2(frequency);
+
+unlock:
+ mutex_unlock(&sdev->mutex);
+ return rval;
+}
+
+static const struct v4l2_subdev_tuner_ops si4713_subdev_tuner_ops = {
+ .g_frequency = si4713_g_frequency,
+ .s_frequency = si4713_s_frequency,
+ .g_modulator = si4713_g_modulator,
+ .s_modulator = si4713_s_modulator,
+};
+
+static const struct v4l2_subdev_ops si4713_subdev_ops = {
+ .core = &si4713_subdev_core_ops,
+ .tuner = &si4713_subdev_tuner_ops,
+};
+
+/*
+ * I2C driver interface
+ */
+/* si4713_probe - probe for the device */
+static int si4713_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct si4713_device *sdev;
+ int rval;
+
+ sdev = kzalloc(sizeof *sdev, GFP_KERNEL);
+ if (!sdev) {
+ dev_err(&client->dev, "Failed to alloc video device.\n");
+ rval = -ENOMEM;
+ goto exit;
+ }
+
+ sdev->platform_data = client->dev.platform_data;
+ if (!sdev->platform_data) {
+ v4l2_err(&sdev->sd, "No platform data registered.\n");
+ rval = -ENODEV;
+ goto free_sdev;
+ }
+
+ v4l2_i2c_subdev_init(&sdev->sd, client, &si4713_subdev_ops);
+
+ mutex_init(&sdev->mutex);
+ init_completion(&sdev->work);
+
+ if (client->irq) {
+ rval = request_irq(client->irq,
+ si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED,
+ client->name, sdev);
+ if (rval < 0) {
+ v4l2_err(&sdev->sd, "Could not request IRQ\n");
+ goto free_sdev;
+ }
+ v4l2_dbg(1, debug, &sdev->sd, "IRQ requested.\n");
+ } else {
+ v4l2_warn(&sdev->sd, "IRQ not configured. Using timeouts.\n");
+ }
+
+ rval = si4713_initialize(sdev);
+ if (rval < 0) {
+ v4l2_err(&sdev->sd, "Failed to probe device information.\n");
+ goto free_irq;
+ }
+
+ return 0;
+
+free_irq:
+ if (client->irq)
+ free_irq(client->irq, sdev);
+free_sdev:
+ kfree(sdev);
+exit:
+ return rval;
+}
+
+/* si4713_remove - remove the device */
+static int si4713_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct si4713_device *sdev = to_si4713_device(sd);
+
+ if (sdev->power_state)
+ si4713_set_power_state(sdev, POWER_DOWN);
+
+ if (client->irq > 0)
+ free_irq(client->irq, sdev);
+
+ v4l2_device_unregister_subdev(sd);
+
+ kfree(sdev);
+
+ return 0;
+}
+
+/* si4713_i2c_driver - i2c driver interface */
+static const struct i2c_device_id si4713_id[] = {
+ { "si4713" , 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, si4713_id);
+
+static struct i2c_driver si4713_i2c_driver = {
+ .driver = {
+ .name = "si4713",
+ },
+ .probe = si4713_probe,
+ .remove = si4713_remove,
+ .id_table = si4713_id,
+};
+
+/* Module Interface */
+static int __init si4713_module_init(void)
+{
+ return i2c_add_driver(&si4713_i2c_driver);
+}
+
+static void __exit si4713_module_exit(void)
+{
+ i2c_del_driver(&si4713_i2c_driver);
+}
+
+module_init(si4713_module_init);
+module_exit(si4713_module_exit);
+
diff --git a/drivers/media/radio/si4713-i2c.h b/drivers/media/radio/si4713-i2c.h
new file mode 100644
index 00000000000..faf8cff124f
--- /dev/null
+++ b/drivers/media/radio/si4713-i2c.h
@@ -0,0 +1,237 @@
+/*
+ * drivers/media/radio/si4713-i2c.h
+ *
+ * Property and commands definitions for Si4713 radio transmitter chip.
+ *
+ * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
+ * Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+#ifndef SI4713_I2C_H
+#define SI4713_I2C_H
+
+#include <media/v4l2-subdev.h>
+#include <media/si4713.h>
+
+#define SI4713_PRODUCT_NUMBER 0x0D
+
+/* Command Timeouts */
+#define DEFAULT_TIMEOUT 500
+#define TIMEOUT_SET_PROPERTY 20
+#define TIMEOUT_TX_TUNE_POWER 30000
+#define TIMEOUT_TX_TUNE 110000
+#define TIMEOUT_POWER_UP 200000
+
+/*
+ * Command and its arguments definitions
+ */
+#define SI4713_PWUP_CTSIEN (1<<7)
+#define SI4713_PWUP_GPO2OEN (1<<6)
+#define SI4713_PWUP_PATCH (1<<5)
+#define SI4713_PWUP_XOSCEN (1<<4)
+#define SI4713_PWUP_FUNC_TX 0x02
+#define SI4713_PWUP_FUNC_PATCH 0x0F
+#define SI4713_PWUP_OPMOD_ANALOG 0x50
+#define SI4713_PWUP_OPMOD_DIGITAL 0x0F
+#define SI4713_PWUP_NARGS 2
+#define SI4713_PWUP_NRESP 1
+#define SI4713_CMD_POWER_UP 0x01
+
+#define SI4713_GETREV_NRESP 9
+#define SI4713_CMD_GET_REV 0x10
+
+#define SI4713_PWDN_NRESP 1
+#define SI4713_CMD_POWER_DOWN 0x11
+
+#define SI4713_SET_PROP_NARGS 5
+#define SI4713_SET_PROP_NRESP 1
+#define SI4713_CMD_SET_PROPERTY 0x12
+
+#define SI4713_GET_PROP_NARGS 3
+#define SI4713_GET_PROP_NRESP 4
+#define SI4713_CMD_GET_PROPERTY 0x13
+
+#define SI4713_GET_STATUS_NRESP 1
+#define SI4713_CMD_GET_INT_STATUS 0x14
+
+#define SI4713_CMD_PATCH_ARGS 0x15
+#define SI4713_CMD_PATCH_DATA 0x16
+
+#define SI4713_MAX_FREQ 10800
+#define SI4713_MIN_FREQ 7600
+#define SI4713_TXFREQ_NARGS 3
+#define SI4713_TXFREQ_NRESP 1
+#define SI4713_CMD_TX_TUNE_FREQ 0x30
+
+#define SI4713_MAX_POWER 120
+#define SI4713_MIN_POWER 88
+#define SI4713_MAX_ANTCAP 191
+#define SI4713_MIN_ANTCAP 0
+#define SI4713_TXPWR_NARGS 4
+#define SI4713_TXPWR_NRESP 1
+#define SI4713_CMD_TX_TUNE_POWER 0x31
+
+#define SI4713_TXMEA_NARGS 4
+#define SI4713_TXMEA_NRESP 1
+#define SI4713_CMD_TX_TUNE_MEASURE 0x32
+
+#define SI4713_INTACK_MASK 0x01
+#define SI4713_TXSTATUS_NARGS 1
+#define SI4713_TXSTATUS_NRESP 8
+#define SI4713_CMD_TX_TUNE_STATUS 0x33
+
+#define SI4713_OVERMOD_BIT (1 << 2)
+#define SI4713_IALH_BIT (1 << 1)
+#define SI4713_IALL_BIT (1 << 0)
+#define SI4713_ASQSTATUS_NARGS 1
+#define SI4713_ASQSTATUS_NRESP 5
+#define SI4713_CMD_TX_ASQ_STATUS 0x34
+
+#define SI4713_RDSBUFF_MODE_MASK 0x87
+#define SI4713_RDSBUFF_NARGS 7
+#define SI4713_RDSBUFF_NRESP 6
+#define SI4713_CMD_TX_RDS_BUFF 0x35
+
+#define SI4713_RDSPS_PSID_MASK 0x1F
+#define SI4713_RDSPS_NARGS 5
+#define SI4713_RDSPS_NRESP 1
+#define SI4713_CMD_TX_RDS_PS 0x36
+
+#define SI4713_CMD_GPO_CTL 0x80
+#define SI4713_CMD_GPO_SET 0x81
+
+/*
+ * Bits from status response
+ */
+#define SI4713_CTS (1<<7)
+#define SI4713_ERR (1<<6)
+#define SI4713_RDS_INT (1<<2)
+#define SI4713_ASQ_INT (1<<1)
+#define SI4713_STC_INT (1<<0)
+
+/*
+ * Property definitions
+ */
+#define SI4713_GPO_IEN 0x0001
+#define SI4713_DIG_INPUT_FORMAT 0x0101
+#define SI4713_DIG_INPUT_SAMPLE_RATE 0x0103
+#define SI4713_REFCLK_FREQ 0x0201
+#define SI4713_REFCLK_PRESCALE 0x0202
+#define SI4713_TX_COMPONENT_ENABLE 0x2100
+#define SI4713_TX_AUDIO_DEVIATION 0x2101
+#define SI4713_TX_PILOT_DEVIATION 0x2102
+#define SI4713_TX_RDS_DEVIATION 0x2103
+#define SI4713_TX_LINE_INPUT_LEVEL 0x2104
+#define SI4713_TX_LINE_INPUT_MUTE 0x2105
+#define SI4713_TX_PREEMPHASIS 0x2106
+#define SI4713_TX_PILOT_FREQUENCY 0x2107
+#define SI4713_TX_ACOMP_ENABLE 0x2200
+#define SI4713_TX_ACOMP_THRESHOLD 0x2201
+#define SI4713_TX_ACOMP_ATTACK_TIME 0x2202
+#define SI4713_TX_ACOMP_RELEASE_TIME 0x2203
+#define SI4713_TX_ACOMP_GAIN 0x2204
+#define SI4713_TX_LIMITER_RELEASE_TIME 0x2205
+#define SI4713_TX_ASQ_INTERRUPT_SOURCE 0x2300
+#define SI4713_TX_ASQ_LEVEL_LOW 0x2301
+#define SI4713_TX_ASQ_DURATION_LOW 0x2302
+#define SI4713_TX_ASQ_LEVEL_HIGH 0x2303
+#define SI4713_TX_ASQ_DURATION_HIGH 0x2304
+#define SI4713_TX_RDS_INTERRUPT_SOURCE 0x2C00
+#define SI4713_TX_RDS_PI 0x2C01
+#define SI4713_TX_RDS_PS_MIX 0x2C02
+#define SI4713_TX_RDS_PS_MISC 0x2C03
+#define SI4713_TX_RDS_PS_REPEAT_COUNT 0x2C04
+#define SI4713_TX_RDS_PS_MESSAGE_COUNT 0x2C05
+#define SI4713_TX_RDS_PS_AF 0x2C06
+#define SI4713_TX_RDS_FIFO_SIZE 0x2C07
+
+#define PREEMPHASIS_USA 75
+#define PREEMPHASIS_EU 50
+#define PREEMPHASIS_DISABLED 0
+#define FMPE_USA 0x00
+#define FMPE_EU 0x01
+#define FMPE_DISABLED 0x02
+
+#define POWER_UP 0x01
+#define POWER_DOWN 0x00
+
+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;
+};
+
+/*
+ * si4713_device - private data
+ */
+struct si4713_device {
+ /* v4l2_subdev and i2c reference (v4l2_subdev priv data) */
+ struct v4l2_subdev sd;
+ /* private data structures */
+ struct mutex mutex;
+ struct completion work;
+ struct si4713_platform_data *platform_data;
+ struct rds_info rds_info;
+ struct limiter_info limiter_info;
+ struct pilot_info pilot_info;
+ struct acomp_info acomp_info;
+ u32 frequency;
+ u32 preemphasis;
+ u32 mute;
+ u32 power_level;
+ u32 power_state;
+ u32 antenna_capacitor;
+ u32 stereo;
+ u32 tune_rnl;
+};
+#endif /* ifndef SI4713_I2C_H */