aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/media/radio
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/media/radio
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/media/radio')
-rw-r--r--drivers/media/radio/si470x/Kconfig13
-rw-r--r--drivers/media/radio/si470x/Makefile2
-rw-r--r--drivers/media/radio/si470x/radio-si4705-common.c963
-rw-r--r--drivers/media/radio/si470x/radio-si4705-i2c.c997
-rw-r--r--drivers/media/radio/si470x/radio-si4705.h547
5 files changed, 2522 insertions, 0 deletions
diff --git a/drivers/media/radio/si470x/Kconfig b/drivers/media/radio/si470x/Kconfig
index a466654..f281022 100644
--- a/drivers/media/radio/si470x/Kconfig
+++ b/drivers/media/radio/si470x/Kconfig
@@ -35,3 +35,16 @@ config I2C_SI470X
To compile this driver as a module, choose M here: the
module will be called radio-i2c-si470x.
+
+config I2C_SI4705
+ tristate "Silicon Labs Si4705 FM Radio Receiver support with I2C"
+ depends on I2C && RADIO_SI470X
+ ---help---
+ This is a driver for I2C devices with the Silicon Labs SI4705
+ 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-si4705.
diff --git a/drivers/media/radio/si470x/Makefile b/drivers/media/radio/si470x/Makefile
index 0696481..dd36de1 100644
--- a/drivers/media/radio/si470x/Makefile
+++ b/drivers/media/radio/si470x/Makefile
@@ -4,6 +4,8 @@
radio-usb-si470x-objs := radio-si470x-usb.o radio-si470x-common.o
radio-i2c-si470x-objs := radio-si470x-i2c.o radio-si470x-common.o
+radio-i2c-si4705-objs := radio-si4705-i2c.o radio-si4705-common.o
obj-$(CONFIG_USB_SI470X) += radio-usb-si470x.o
obj-$(CONFIG_I2C_SI470X) += radio-i2c-si470x.o
+obj-$(CONFIG_I2C_SI4705) += radio-i2c-si4705.o
diff --git a/drivers/media/radio/si470x/radio-si4705-common.c b/drivers/media/radio/si470x/radio-si4705-common.c
new file mode 100644
index 0000000..975f303
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si4705-common.c
@@ -0,0 +1,963 @@
+/*
+ * drivers/media/radio/si4705/radio-si4705-common.c
+ *
+ * Driver for radios with Silicon Labs si4705 FM Radio Receivers
+ *
+ * Copyright (c) 2012 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* kernel includes */
+#include <linux/si4705_pdata.h>
+#include "radio-si4705.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: V4L2_DEEMPHASIS_DISABLED */
+/* 1: V4L2_DEEMPHASIS_50_uS (Europe, Australia, Japan) */
+/* 2: V4L2_DEEMPHASIS_75_uS (South Korea, USA) */
+static unsigned short de = 1;
+module_param(de, ushort, 0444);
+MODULE_PARM_DESC(de, "De-emphasis: 0=Disabled *1=50us* 2=75us");
+
+/* 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*");
+
+/**************************************************************************
+ * Defines
+ **************************************************************************/
+
+/* si4705 uses 10kHz unit */
+#define FREQ_DEV_TO_V4L2(x) (x * FREQ_MUL / 100)
+#define FREQ_V4L2_TO_DEV(x) (x * 100 / FREQ_MUL)
+
+/**************************************************************************
+ * Generic Functions
+ **************************************************************************/
+/*
+ * si4705_set_deemphasis
+ */
+static int si4705_set_deemphasis(struct si4705_device *radio,
+ unsigned int deemphasis)
+{
+ int retval;
+ unsigned short chip_de;
+
+ switch (deemphasis) {
+ case V4L2_DEEMPHASIS_DISABLED:
+ return 0;
+ case V4L2_DEEMPHASIS_50_uS: /* Europe, Australia, Japan */
+ chip_de = 1;
+ break;
+ case V4L2_DEEMPHASIS_75_uS: /* South Korea, USA */
+ chip_de = 0;
+ break;
+ default:
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si4705_set_property(radio, FM_DEEMPHASIS, chip_de);
+
+done:
+ if (retval < 0)
+ pr_err("%s: set de-emphasis failed with %d", __func__, retval);
+
+ return retval;
+}
+
+/*
+ * si4705_set_chan - set the channel
+ */
+static int si4705_set_chan(struct si4705_device *radio, unsigned short chan)
+{
+ int retval;
+ unsigned long timeout;
+ bool timed_out = 0;
+ u8 stcint = 0;
+
+ pr_info("%s: chan = %d", __func__, chan);
+
+ /* start tuning */
+ retval = si4705_fm_tune_freq(radio, chan, 0x00);
+ if (retval < 0)
+ goto done;
+
+ /* currently I2C driver only uses interrupt way to tune */
+ if (radio->stci_enabled) {
+ INIT_COMPLETION(radio->completion);
+
+ /* wait till tune operation has completed */
+ retval = wait_for_completion_timeout(&radio->completion,
+ msecs_to_jiffies(tune_timeout));
+ if (!retval)
+ timed_out = true;
+
+ stcint = (radio->int_status & GET_INT_STATUS_STCINT);
+ pr_err("%s: stcint = 0x%02x", __func__, stcint);
+ } else {
+ /* wait till tune operation has completed */
+ timeout = jiffies + msecs_to_jiffies(tune_timeout);
+ do {
+ retval = si4705_get_int_status(radio, NULL, NULL, NULL,
+ NULL, &stcint);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while ((stcint == 0) && (!timed_out));
+ }
+
+ if (stcint == 0)
+ pr_err("%s: tune does not complete. stcint = %d",
+ __func__, stcint);
+ if (timed_out)
+ pr_err("%s: tune timed out after %u ms",
+ __func__, tune_timeout);
+
+stop:
+ /* stop tuning */
+ retval = si4705_fm_tune_status(radio, 0x01, 0x01,
+ NULL, NULL, NULL, NULL, NULL);
+
+done:
+ return retval;
+}
+
+/*
+ * si4705_get_freq - get the frequency
+ */
+static int si4705_get_freq(struct si4705_device *radio, unsigned int *freq)
+{
+ int retval;
+ u16 chan = 0;
+
+ retval = si4705_fm_tune_status(radio, 0x00, 0x00,
+ NULL, &chan, NULL, NULL, NULL);
+
+ *freq = FREQ_DEV_TO_V4L2(chan);
+
+ pr_info("%s: frequency = %d", __func__, chan);
+
+ return retval;
+}
+
+/*
+ * si4705_set_freq - set the frequency
+ */
+int si4705_set_freq(struct si4705_device *radio, unsigned int freq)
+{
+ u16 chan = 0;
+
+ chan = FREQ_V4L2_TO_DEV(freq);
+
+ return si4705_set_chan(radio, chan);
+}
+
+/*
+ * si4705_set_seek - set seek
+ */
+static int si4705_set_seek(struct si4705_device *radio,
+ unsigned int wrap_around, unsigned int seek_upward)
+{
+ int retval = 0;
+ unsigned long timeout;
+ bool timed_out = 0;
+ u8 stcint = 0;
+ u8 bltf = 0;
+
+ /* start seeking */
+ retval = si4705_fm_seek_start(radio, seek_upward, wrap_around);
+ if (retval < 0)
+ goto done;
+
+ /* currently I2C driver only uses interrupt way to seek */
+ if (radio->stci_enabled) {
+ INIT_COMPLETION(radio->completion);
+
+ /* wait till seek operation has completed */
+ retval = wait_for_completion_timeout(&radio->completion,
+ msecs_to_jiffies(seek_timeout));
+ if (!retval)
+ timed_out = true;
+
+ stcint = (radio->int_status & GET_INT_STATUS_STCINT);
+ } else {
+ /* wait till seek operation has completed */
+ timeout = jiffies + msecs_to_jiffies(seek_timeout);
+ do {
+ retval = si4705_get_int_status(radio, NULL, NULL, NULL,
+ NULL, &stcint);
+ if (retval < 0)
+ goto stop;
+ timed_out = time_after(jiffies, timeout);
+ } while ((stcint == 0) && (!timed_out));
+ }
+
+ if (stcint == 0)
+ pr_err("%s: seek does not complete", __func__);
+ if (bltf)
+ pr_err("%s: seek failed / band limit reached", __func__);
+ if (timed_out)
+ pr_err("%s: seek timed out after %u ms",
+ __func__, seek_timeout);
+
+stop:
+ /* stop seeking */
+ retval = si4705_fm_tune_status(radio, 0x01, 0x01,
+ NULL, NULL, NULL, NULL, NULL);
+
+done:
+ /* try again, if timed out */
+ if ((retval == 0) && timed_out)
+ retval = -EAGAIN;
+
+ return retval;
+}
+
+/*
+ * si4705_start - switch on radio
+ */
+int si4705_start(struct si4705_device *radio)
+{
+ int retval;
+ u16 threshold;
+
+ retval = si4705_power_up(radio);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, GPO_IEN, GPO_IEN_STCIEN_MASK
+ | GPO_IEN_STCREP_MASK);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_deemphasis(radio, de);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, FM_SEEK_BAND_BOTTOM, 7600);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, FM_SEEK_BAND_TOP, 10800);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, FM_SEEK_FREQ_SPACING, 5);
+ if (retval < 0)
+ goto done;
+
+ threshold = si4705_get_seek_tune_snr_threshold_value(radio);
+
+ retval = si4705_set_property(radio,
+ FM_SEEK_TUNE_SNR_THRESHOLD, threshold);
+ if (retval < 0)
+ goto done;
+
+ threshold = si4705_get_seek_tune_rssi_threshold_value(radio);
+
+ retval = si4705_set_property(radio,
+ FM_SEEK_TUNE_RSSI_THRESHOLD, threshold);
+ if (retval < 0)
+ goto done;
+
+done:
+ return retval;
+}
+
+/*
+ * si4705_stop - switch off radio
+ */
+int si4705_stop(struct si4705_device *radio)
+{
+ int retval;
+
+ retval = si4705_set_property(radio, RDS_CONFIG, 0x00);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, RX_HARD_MUTE, 0x03);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_power_down(radio);
+
+done:
+ return retval;
+}
+
+/*
+ * si4705_rds_on - switch on rds reception
+ */
+static int si4705_rds_on(struct si4705_device *radio)
+{
+ int retval;
+
+ retval = si4705_set_property(radio, GPO_IEN, GPO_IEN_STCIEN_MASK
+ | GPO_IEN_RDSIEN_MASK | GPO_IEN_STCREP_MASK);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, RDS_INT_SOURCE,
+ RDS_INT_SOURCE_RECV_MASK);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, RDS_CONFIG, RDS_CONFIG_RDSEN_MASK |
+ (3 << RDS_CONFIG_BLETHA_SHFT) |
+ (3 << RDS_CONFIG_BLETHB_SHFT) |
+ (3 << RDS_CONFIG_BLETHC_SHFT) |
+ (3 << RDS_CONFIG_BLETHD_SHFT));
+
+done:
+ return retval;
+}
+
+
+/**************************************************************************
+ * File Operations Interface
+ **************************************************************************/
+
+/*
+ * si4705_fops_open - file open
+ */
+int si4705_fops_open(struct file *file)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ mutex_lock(&radio->lock);
+ radio->users++;
+
+ if (radio->users == 1) {
+ /* start radio */
+ retval = si4705_start(radio);
+ if (retval < 0)
+ goto done;
+ }
+
+done:
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_fops_release - file release
+ */
+int si4705_fops_release(struct file *file)
+{
+ struct si4705_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 = si4705_stop(radio);
+
+ mutex_unlock(&radio->lock);
+
+ return retval;
+}
+
+/*
+ * si4705_fops_read - read RDS data
+ */
+static ssize_t si4705_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ unsigned int block_count = 0;
+ u16 rdsen = 0;
+
+ /* switch on rds reception */
+ mutex_lock(&radio->lock);
+
+ retval = si4705_get_property(radio, RDS_CONFIG, &rdsen);
+ if (retval < 0)
+ goto done;
+
+ rdsen &= RDS_CONFIG_RDSEN_MASK;
+
+ if (rdsen == 0)
+ si4705_rds_on(radio);
+
+ /* block if no new data available */
+ while (radio->wr_index == radio->rd_index) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EWOULDBLOCK;
+ goto done;
+ }
+ if (wait_event_interruptible(radio->read_queue,
+ radio->wr_index != radio->rd_index) < 0) {
+ retval = -EINTR;
+ goto done;
+ }
+ }
+
+ /* calculate block count from byte count */
+ count /= 3;
+
+ /* copy RDS block out of internal buffer and to user buffer */
+ while (block_count < count) {
+ if (radio->rd_index == radio->wr_index)
+ break;
+
+ /* always transfer rds complete blocks */
+ if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
+ /* retval = -EFAULT; */
+ break;
+
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+
+ /* increment counters */
+ block_count++;
+ buf += 3;
+ retval += 3;
+ }
+
+done:
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_fops_poll - poll RDS data
+ */
+static unsigned int si4705_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ u16 rdsen = 0;
+
+ /* switch on rds reception */
+
+ mutex_lock(&radio->lock);
+
+ retval = si4705_get_property(radio, RDS_CONFIG, &rdsen);
+ if (retval < 0)
+ goto done;
+
+ rdsen &= RDS_CONFIG_RDSEN_MASK;
+
+ if (rdsen == 0)
+ si4705_rds_on(radio);
+ mutex_unlock(&radio->lock);
+
+ poll_wait(file, &radio->read_queue, pts);
+
+ if (radio->rd_index != radio->wr_index)
+ retval = POLLIN | POLLRDNORM;
+
+ return retval;
+
+done:
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_fops - file operations interface
+ */
+static const struct v4l2_file_operations si4705_fops = {
+ .owner = THIS_MODULE,
+ .read = si4705_fops_read,
+ .poll = si4705_fops_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .open = si4705_fops_open,
+ .release = si4705_fops_release,
+};
+
+
+/**************************************************************************
+ * Video4Linux Interface
+ **************************************************************************/
+
+/*
+ * si4705_vidioc_querycap - query device capabilities
+ */
+int si4705_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;
+}
+
+/*
+ * si4705_vidioc_queryctrl - enumerate control items
+ */
+static int si4705_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ struct si4705_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;
+}
+
+/*
+ * si4705_vidioc_g_ctrl - get the value of a control
+ */
+static int si4705_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ u16 vol_mute = 0;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ retval = si4705_get_property(radio, RX_VOLUME, &vol_mute);
+ if (retval)
+ goto done;
+
+ vol_mute &= RX_VOLUME_MASK;
+ ctrl->value = si4705_vol_conv_value_to_index(radio, vol_mute);
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+
+ retval = si4705_get_property(radio, RX_HARD_MUTE, &vol_mute);
+ if (retval)
+ goto done;
+
+ ctrl->value = (vol_mute == 0) ? 1 : 0;
+ break;
+ /*
+ * TODO : need to support op_mode change lp_mode <-> rich_mode
+ */
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "get control failed with %d\n", retval);
+
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_s_ctrl - set the value of a control
+ */
+static int si4705_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ u16 vol;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ vol = si4705_vol_conv_index_to_value(radio, ctrl->value);
+ retval = si4705_set_property(radio, RX_VOLUME, vol);
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ if (ctrl->value == 1)
+ retval = si4705_set_property(radio, RX_HARD_MUTE, 0x03);
+ else
+ retval = si4705_set_property(radio, RX_HARD_MUTE, 0x00);
+
+ break;
+ case V4L2_CID_TUNE_DEEMPHASIS:
+ retval = si4705_set_deemphasis(radio, ctrl->value);
+ break;
+
+ /*
+ * TODO : need to support op_mode change lp_mode <-> rich_mode
+ */
+ default:
+ retval = -EINVAL;
+ }
+
+done:
+ if (retval < 0)
+ dev_warn(&radio->videodev->dev,
+ "set control failed with %d\n", retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_g_audio - get audio attributes
+ */
+static int si4705_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;
+}
+
+/*
+ * si4705_vidioc_g_tuner - get tuner attributes
+ */
+static int si4705_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ u8 afcrl = 0;
+ u8 stblend = 0;
+ u8 rssi = 0;
+ u16 bandLow = 0;
+ u16 bandHigh = 0;
+ u16 stereo_th = 0;
+ u16 mono_th = 0;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (tuner->index != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si4705_fm_rsq_status(radio, 0x00, NULL, &afcrl,
+ &stblend, &rssi, NULL, NULL);
+ if (retval < 0)
+ goto done;
+
+ pr_err("%s: afcrl = 0x%02x, stblend = 0x%02x, rssi = 0x%02x", __func__,
+ afcrl, stblend, rssi);
+
+ /* driver constants */
+ strcpy(tuner->name, "FM");
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
+ V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO;
+
+ /* range limits */
+ retval = si4705_get_property(radio, FM_SEEK_BAND_BOTTOM, &bandLow);
+ if (retval)
+ goto done;
+
+ retval = si4705_get_property(radio, FM_SEEK_BAND_TOP, &bandHigh);
+ if (retval)
+ goto done;
+
+ tuner->rangelow = FREQ_DEV_TO_V4L2(bandLow);
+ tuner->rangehigh = FREQ_DEV_TO_V4L2(bandHigh);
+
+ /* stereo indicator == stereo (instead of mono) */
+ if ((stblend & FM_RSQ_STATUS_OUT_STBLEND) == 0)
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ else
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+ /* If there is a reliable method of detecting an RDS channel,
+ then this code should check for that before setting this
+ RDS subchannel. */
+ tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+
+ retval = si4705_get_property(radio, FM_BLEND_STEREO_THRESHOLD,
+ &stereo_th);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_get_property(radio, FM_BLEND_MONO_THRESHOLD, &mono_th);
+ if (retval < 0)
+ goto done;
+
+ /* mono/stereo selector */
+ if (stereo_th == 127 || mono_th == 127)
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ else
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+
+ /* V4L2 does not specify how to use signal strength field. */
+ /* it just descripbes higher is a better signal */
+ /* update rssi value as real dbµV unit */
+ tuner->signal = rssi;
+
+ /* 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 = (afcrl & FM_RSQ_STATUS_OUT_AFCRL) ? 1 : 0;
+
+done:
+ if (retval < 0)
+ pr_err("%s: get tuner failed with %d", __func__, retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_s_tuner - set tuner attributes
+ */
+static int si4705_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+ u16 stereo_th = 0;
+ u16 mono_th = 0;
+ u16 bandLow = 0;
+ u16 bandHigh = 0;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (tuner->index != 0)
+ goto done;
+
+ /* mono/stereo selector */
+ switch (tuner->audmode) {
+ case V4L2_TUNER_MODE_MONO:
+ stereo_th = 127;
+ mono_th = 127; /* force mono */
+ break;
+ case V4L2_TUNER_MODE_STEREO:
+ stereo_th = 0x0031;
+ mono_th = 0x001E; /* try stereo */
+ break;
+ default:
+ goto done;
+ }
+
+ retval = si4705_set_property(radio, FM_BLEND_STEREO_THRESHOLD,
+ stereo_th);
+ if (retval < 0)
+ goto done;
+
+ retval = si4705_set_property(radio, FM_BLEND_MONO_THRESHOLD, mono_th);
+ if (retval < 0)
+ goto done;
+
+ /* range limit */
+ if (tuner->rangelow) {
+ bandLow = FREQ_V4L2_TO_DEV(tuner->rangelow);
+ retval = si4705_set_property(radio,
+ FM_SEEK_BAND_BOTTOM, bandLow);
+ if (retval < 0)
+ goto done;
+ }
+
+ if (tuner->rangehigh) {
+ bandHigh = FREQ_V4L2_TO_DEV(tuner->rangehigh);
+ retval = si4705_set_property(radio, FM_SEEK_BAND_TOP, bandHigh);
+ if (retval < 0)
+ goto done;
+ }
+
+done:
+ if (retval < 0)
+ pr_err("%s: set tuner failed with %d", __func__, retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_g_frequency - get tuner or modulator radio frequency
+ */
+static int si4705_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ /* safety checks */
+ mutex_lock(&radio->lock);
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ freq->type = V4L2_TUNER_RADIO;
+ retval = si4705_get_freq(radio, &freq->frequency);
+
+done:
+ if (retval < 0)
+ pr_err("%s: set frequency failed with %d", __func__, retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_s_frequency - set tuner or modulator radio frequency
+ */
+static int si4705_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (freq->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si4705_set_freq(radio, freq->frequency);
+
+done:
+ if (retval < 0)
+ pr_err("%s: set frequency failed with %d", __func__, retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_vidioc_s_hw_freq_seek - set hardware frequency seek
+ */
+static int si4705_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ struct v4l2_hw_freq_seek *seek)
+{
+ struct si4705_device *radio = video_drvdata(file);
+ int retval = 0;
+
+ mutex_lock(&radio->lock);
+ /* safety checks */
+ retval = si4705_disconnect_check(radio);
+ if (retval)
+ goto done;
+
+ if (seek->tuner != 0) {
+ retval = -EINVAL;
+ goto done;
+ }
+
+ retval = si4705_set_seek(radio, seek->wrap_around, seek->seek_upward);
+
+done:
+ if (retval < 0)
+ pr_err("%s : set hardware frequency seek failed with %d",
+ __func__, retval);
+ mutex_unlock(&radio->lock);
+ return retval;
+}
+
+/*
+ * si4705_ioctl_ops - video device ioctl operations
+ */
+static const struct v4l2_ioctl_ops si4705_ioctl_ops = {
+ .vidioc_querycap = si4705_vidioc_querycap,
+ .vidioc_queryctrl = si4705_vidioc_queryctrl,
+ .vidioc_g_ctrl = si4705_vidioc_g_ctrl,
+ .vidioc_s_ctrl = si4705_vidioc_s_ctrl,
+ .vidioc_g_audio = si4705_vidioc_g_audio,
+ .vidioc_g_tuner = si4705_vidioc_g_tuner,
+ .vidioc_s_tuner = si4705_vidioc_s_tuner,
+ .vidioc_g_frequency = si4705_vidioc_g_frequency,
+ .vidioc_s_frequency = si4705_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = si4705_vidioc_s_hw_freq_seek,
+};
+
+/*
+ * si4705_viddev_template - video device interface
+ */
+struct video_device si4705_viddev_template = {
+ .fops = &si4705_fops,
+ .name = DRIVER_NAME,
+ .release = video_device_release,
+ .ioctl_ops = &si4705_ioctl_ops,
+};
diff --git a/drivers/media/radio/si470x/radio-si4705-i2c.c b/drivers/media/radio/si470x/radio-si4705-i2c.c
new file mode 100644
index 0000000..e77b193
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si4705-i2c.c
@@ -0,0 +1,997 @@
+/*
+ * drivers/media/radio/si4705/radio-si4705-i2c.c
+ *
+ * I2C driver for radios with Silicon Labs Si4705 FM Radio Receivers
+ *
+ * Copyright (c) 2012 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* kernel includes */
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <mach/gpio.h>
+#include <plat/gpio-cfg.h>
+#include <linux/si4705_pdata.h>
+#include "radio-si4705.h"
+
+
+/* I2C Device ID List */
+static const struct i2c_device_id si4705_i2c_id[] = {
+ /* Generic Entry */
+ { "si4705", 0 },
+ /* Terminating entry */
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, si4705_i2c_id);
+
+
+#define msb(x) ((u8)((u16) x >> 8))
+#define lsb(x) ((u8)((u16) x & 0x00FF))
+#define compose_u16(msb, lsb) (((u16)msb << 8) | lsb)
+
+#define STATE_POWER_UP 1
+#define STATE_POWER_DOWN 0
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0444);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+/* RDS buffer blocks */
+static unsigned int rds_buf = 100;
+module_param(rds_buf, uint, 0444);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+
+/* RDS maximum block errors */
+static unsigned short max_rds_errors = 1;
+/* 0 means 0 errors requiring correction */
+/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
+/* 2 means 3-5 errors requiring correction */
+/* 3 means 6+ errors or errors in checkword, correction not possible */
+module_param(max_rds_errors, ushort, 0644);
+MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
+
+#define CTS_MAX_COUNT 1000
+
+/**************************************************************************
+ * General Driver Functions - REGISTERs
+ **************************************************************************/
+
+static int si4705_get_register(struct si4705_device *radio,
+ u8 cmd_size, u8 *cmd)
+{
+ struct i2c_client *i2c = radio->client;
+ int ret = 0;
+
+ ret = i2c_master_recv(i2c, (char *)cmd, cmd_size);
+ if (ret != cmd_size)
+ return -EIO;
+
+ return 0;
+}
+
+static int si4705_set_register(struct si4705_device *radio,
+ u8 cmd_size, u8 *cmd)
+{
+ struct i2c_client *i2c = radio->client;
+ int ret = 0;
+
+ ret = i2c_master_send(i2c, (const char *)cmd, cmd_size);
+ if (ret != cmd_size)
+ return -EIO;
+
+ return 0;
+}
+
+static void si4705_wait_for_cts(struct si4705_device *radio)
+{
+ u16 count = CTS_MAX_COUNT;
+ u8 status = 0;
+ do {
+ si4705_get_register(radio, 1, &status);
+ usleep_range(5, 10);
+ } while (--count && !(status & CTS));
+
+ radio->int_status = status;
+
+ if (status & ERR)
+ pr_err("%s: status = 0x%02x", __func__, status);
+}
+
+static int si4705_send_command(struct si4705_device *radio, u8 cmd_size,
+ u8 *cmd, u8 reply_size, u8 *reply)
+{
+ int ret = 0;
+ ret = si4705_set_register(radio, cmd_size, cmd);
+ if (ret != 0) {
+ pr_err("%s: si4705 set register error %d", __func__, ret);
+ return ret;
+ }
+
+ si4705_wait_for_cts(radio);
+
+ if (reply_size == 1)
+ reply[0] = radio->int_status;
+ else if (reply_size > 1)
+ ret = si4705_get_register(radio, reply_size, reply);
+
+ if (ret != 0) {
+ pr_err("%s: si4705 get register error %d", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+
+}
+
+/*
+ * si4705_power_up : power the radio up
+ */
+int si4705_power_up(struct si4705_device *radio)
+{
+ u8 cmd[POWER_UP_NARGS] = {
+ POWER_UP,
+ POWER_UP_IN_GPO2OEN,
+ POWER_UP_IN_OPMODE_RX_ANALOG
+ };
+ u8 reply[POWER_UP_NRESP];
+ int retval = 0;
+
+ /* enable radio device gpio reset pin */
+ if (radio->pdata->reset)
+ radio->pdata->reset(true);
+
+ if (STATE_POWER_DOWN == radio->power_state) {
+ /* the power up command is accepted only in power down mode */
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0) {
+ pr_err("%s: send power up command failed", __func__);
+ retval = -EIO;
+ } else
+ radio->power_state = STATE_POWER_UP;
+ }
+
+ msleep(110);
+
+ return retval;
+}
+
+/*
+ * si4705_power_down : power the radio down
+ */
+int si4705_power_down(struct si4705_device *radio)
+{
+ u8 cmd[POWER_DOWN_NARGS] = {POWER_DOWN};
+ u8 reply[POWER_DOWN_NRESP];
+ int retval = 0;
+
+ if (STATE_POWER_UP == radio->power_state) {
+ /* the power down command is accepted only in power up mode */
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0) {
+ retval = -EIO;
+ } else
+ radio->power_state = STATE_POWER_DOWN;
+ }
+
+ /* disable radio device gpio reset pin */
+ if (radio->pdata->reset)
+ radio->pdata->reset(false);
+
+ return retval;
+}
+
+/*
+ * si4705_get_revision : get the revision information
+ */
+static int si4705_get_revision(struct si4705_device *radio)
+{
+ u8 cmd[GET_REV_NARGS] = {GET_REV};
+ u8 reply[GET_REV_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ dev_info(&radio->client->dev, "Part Number:Si47%02d\n", reply[1]);
+ dev_info(&radio->client->dev, "FirmWare Major:%c, Minor:%c\n",
+ reply[2], reply[3]);
+ dev_info(&radio->client->dev, "Patch ID :0x%x%x\n", reply[4], reply[5]);
+ dev_info(&radio->client->dev, "Component Major:%c, Minor:%c\n",
+ reply[6], reply[7]);
+ dev_info(&radio->client->dev, "Chip Revision:rev%c\n", reply[8]);
+
+ return retval;
+}
+
+/*
+ * si4705_set_property : set the property
+ */
+int si4705_set_property(struct si4705_device *radio, u16 prop, u16 val)
+{
+ u8 cmd[SET_PROPERTY_NARGS] = {
+ SET_PROPERTY,
+ 0x00,
+ msb(prop),
+ lsb(prop),
+ msb(val),
+ lsb(val)
+ };
+ u8 reply[SET_PROPERTY_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ return retval;
+}
+
+/*
+ * si4705_get_property : get the property
+ */
+int si4705_get_property(struct si4705_device *radio, u16 prop, u16 *pVal)
+{
+ u8 cmd[GET_PROPERTY_NARGS] = {
+ GET_PROPERTY,
+ 0x00,
+ msb(prop),
+ lsb(prop)
+ };
+ u8 reply[GET_PROPERTY_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ *pVal = compose_u16(reply[2], reply[3]);
+
+ return retval;
+}
+
+/*
+ * si4705_get_int_status : Updates bits 6:0 of the status byte
+ */
+int si4705_get_int_status(struct si4705_device *radio, u8 *cts, u8 *err,
+ u8 *rsqint, u8 *rdsint, u8 *stcint)
+{
+ u8 cmd[GET_INT_STATUS_NARGS] = {GET_INT_STATUS};
+ u8 reply[GET_INT_STATUS_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ pr_info("%s: int_status = 0x%02x", __func__, reply[0]);
+ if (cts != NULL)
+ *cts = (reply[0] & 0x80) >> 7;
+ if (err != NULL)
+ *err = (reply[0] & 0x40) >> 6;
+ if (rsqint != NULL)
+ *rsqint = (reply[0] & 0x08) >> 3;
+ if (rdsint != NULL)
+ *rdsint = (reply[0] & 0x04) >> 2;
+ if (stcint != NULL)
+ *stcint = (reply[0] & 0x01);
+ }
+
+ return retval;
+}
+
+/*
+ * si4705_fm_tune_freq : tune frequency
+ */
+int si4705_fm_tune_freq(struct si4705_device *radio, u16 freq, u8 antcap)
+{
+ u8 cmd[FM_TUNE_FREQ_NARGS] = {
+ FM_TUNE_FREQ,
+ 0x00,
+ msb(freq),
+ lsb(freq),
+ antcap
+ };
+ u8 reply[FM_TUNE_FREQ_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ return retval;
+}
+
+/*
+ * si4705_fm_seek_start : begins searching for a valid frequency
+ */
+int si4705_fm_seek_start(struct si4705_device *radio, u8 seekUp, u8 wrap)
+{
+ u8 cmd[FM_SEEK_START_NARGS] = {
+ FM_SEEK_START,
+ 0x00
+ };
+ u8 reply[FM_SEEK_START_NRESP];
+ int retval = 0;
+
+ if (seekUp)
+ cmd[1] = FM_SEEK_START_IN_SEEKUP;
+ if (wrap)
+ cmd[1] |= FM_SEEK_START_IN_WRAP;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ return retval;
+}
+
+/*
+ * si4705_fm_tune_status : returns the status of tune or seek commands
+ */
+int si4705_fm_tune_status(struct si4705_device *radio, u8 cancel,
+ u8 intack, u8 *bltf, u16 *freq, u8 *rssi, u8 *snr, u8 *antcap)
+{
+ u8 cmd[FM_TUNE_STATUS_NARGS] = {
+ FM_TUNE_STATUS,
+ 0x00
+ };
+ u8 reply[FM_TUNE_STATUS_NRESP];
+ int retval = 0;
+
+ if (cancel)
+ cmd[1] = FM_TUNE_STATUS_IN_CANCEL;
+ if (intack)
+ cmd[1] |= FM_TUNE_STATUS_IN_INTACK;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ pr_info("%s: tune_status = 0x%02x", __func__, reply[0]);
+ if (bltf != NULL)
+ *bltf = (reply[1] & 0x80) >> 7;
+ if (freq != NULL)
+ *freq = compose_u16(reply[2], reply[3]);
+ if (rssi != NULL)
+ *rssi = reply[4];
+ if (snr != NULL)
+ *snr = reply[5];
+ if (antcap != NULL)
+ *antcap = reply[7];
+ }
+
+ return retval;
+}
+
+/*
+ * si4705_fm_rsq_status : returns status information
+ * about the received signal quality
+ */
+int si4705_fm_rsq_status(struct si4705_device *radio, u8 intack,
+ u8 *intStatus, u8 *indStatus, u8 *stBlend, u8 *rssi,
+ u8 *snr, u8 *freqOff)
+{
+ u8 cmd[FM_RSQ_STATUS_NARGS] = {
+ FM_RSQ_STATUS,
+ 0x00
+ };
+ u8 reply[FM_RSQ_STATUS_NRESP];
+ int retval = 0;
+
+ if (intack)
+ cmd[1] = FM_RSQ_STATUS_IN_INTACK;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ if (intStatus != NULL)
+ *intStatus = reply[1];
+ if (indStatus != NULL)
+ *indStatus = reply[2];
+ if (stBlend != NULL)
+ *stBlend = reply[3];
+ if (rssi != NULL)
+ *rssi = reply[4];
+ if (snr != NULL)
+ *snr = reply[5];
+ if (freqOff != NULL)
+ *freqOff = reply[7];
+ }
+
+ return retval;
+}
+
+/*
+ * si4705_fm_rds_status : returns RDS information for current channel
+ * and reads an entry from the RDS FIFO
+ */
+int si4705_fm_rds_status(struct si4705_device *radio, u8 mtfifo,
+ u8 intack, u8 *rdsInd, u8 *sync, u8 *fifoUsed, u16 *rdsFifo,
+ u8 *ble)
+{
+ u8 cmd[FM_RDS_STATUS_NARGS] = {
+ FM_RDS_STATUS,
+ 0x00
+ };
+ u8 reply[FM_RDS_STATUS_NRESP];
+ int retval = 0;
+ int i;
+
+ if (mtfifo)
+ cmd[1] = FM_RDS_STATUS_IN_MTFIFO;
+ if (intack)
+ cmd[1] |= FM_RDS_STATUS_IN_INTACK;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ if (rdsInd != NULL)
+ *rdsInd = reply[1];
+ if (sync != NULL)
+ *sync = reply[2];
+ if (fifoUsed != NULL)
+ *fifoUsed = reply[3];
+ if (rdsFifo != NULL) {
+ for (i = 0; i < 4; i++)
+ rdsFifo[i] = compose_u16(reply[(i*2)+4],
+ reply[(i*2)+5]);
+ }
+ if (ble != NULL)
+ *ble = reply[12];
+ }
+
+ return retval;
+}
+
+/*
+ * si4705_fm_agc_status : Returns the AGC setting of the device
+ */
+int si4705_fm_agc_status(struct si4705_device *radio, u8 *rfAgcDis,
+ u8 *lnaGainIndex)
+{
+ u8 cmd[FM_AGC_STATUS_NARGS] = {
+ FM_AGC_STATUS,
+ };
+ u8 reply[FM_AGC_STATUS_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ if (rfAgcDis != NULL)
+ *rfAgcDis = (reply[1] & FM_AGC_STATUS_RFAGCDIS);
+ if (lnaGainIndex != NULL)
+ *lnaGainIndex = (reply[2] & FM_AGC_STATUS_LNAGAINIDX);
+ }
+
+ return retval;
+
+}
+
+/*
+ * si4705_fm_agc_override : Overrides AGC setting by disabling the AGC
+ * and forcing the LNA to have a certain gain that ranges between 0
+ * (minimum attenuation) and 26 (maximum attenuation).
+ */
+int si4705_fm_agc_override(struct si4705_device *radio, u8 *rfAgcDis,
+ u8 *lnaGainIndex)
+{
+ u8 cmd[FM_AGC_OVERRIDE_NARGS] = {
+ FM_AGC_OVERRIDE,
+ };
+ u8 reply[FM_AGC_OVERRIDE_NRESP];
+ int retval = 0;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+ else {
+ if (rfAgcDis != NULL)
+ *rfAgcDis = (reply[1] & FM_AGC_OVERRIDE_RFAGCDIS);
+ if (lnaGainIndex != NULL)
+ *lnaGainIndex = (reply[2] & FM_AGC_OVERRIDE_LNAGAINIDX);
+ }
+
+ return retval;
+
+}
+
+/*
+ * si4705_gpio_ctl : Enables output for GPO1, 2, and 3. GPO1, 2, and 3
+ * can be configured for output (Hi-Z or active drive) by setting
+ * the GPO1OEN, GPO2OEN, and GPO3OEN bit.
+ */
+int si4705_gpio_ctl(struct si4705_device *radio, u8 gpio1,
+ u8 gpio2, u8 gpio3)
+{
+ u8 cmd[GPIO_CTL_NARGS] = {
+ GPIO_CTL,
+ };
+ u8 reply[GPIO_CTL_NRESP];
+ int retval = 0;
+
+ if (gpio1)
+ cmd[1] = GPIO_CTL_GPO1OEN;
+ if (gpio2)
+ cmd[1] |= GPIO_CTL_GPO2OEN;
+ if (gpio3)
+ cmd[1] |= GPIO_CTL_GPO3OEN;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ return retval;
+
+}
+
+/*
+ * si4705_gpio_set : Sets the output level (high or low) for GPO1, 2, and 3.
+ */
+int si4705_gpio_set(struct si4705_device *radio, u8 gpio1,
+ u8 gpio2, u8 gpio3)
+{
+ u8 cmd[GPIO_SET_NARGS] = {
+ GPIO_SET,
+ };
+ u8 reply[GPIO_SET_NRESP];
+ int retval = 0;
+
+ if (gpio1)
+ cmd[1] = GPIO_SET_GPO1LEVEL;
+ if (gpio2)
+ cmd[1] |= GPIO_SET_GPO2LEVEL;
+ if (gpio3)
+ cmd[1] |= GPIO_SET_GPO3LEVEL;
+
+ if (si4705_send_command(radio, ARRAY_SIZE(cmd), &cmd[0],
+ ARRAY_SIZE(reply), &reply[0]) < 0)
+ retval = -EIO;
+
+ return retval;
+
+}
+
+/*
+ * si4705_vol_conv_index_to_value - convert from volume index to real value
+ */
+u16 si4705_vol_conv_index_to_value(struct si4705_device *radio, s32 index)
+{
+ if (index < 0) {
+ pr_warning("%s: out of range %d", __func__, index);
+ return 0;
+ }
+
+ if (!((radio->pdata->pdata_values & SI4705_PDATA_BIT_VOL_STEPS)
+ && (radio->pdata->pdata_values & SI4705_PDATA_BIT_VOL_TABLE))) {
+ pr_err("%s: it seems si4705_pdata isn't filled", __func__);
+ return RX_VOLUME_MAX;
+ }
+
+ if (index > radio->pdata->rx_vol_steps-1) {
+ pr_warning("%s: out of range %d", __func__, index);
+ return RX_VOLUME_MAX;
+ }
+
+ return radio->pdata->rx_vol_table[index];
+}
+
+/*
+ * si4705_vol_conv_valueto_index
+ * - convert from real volume value to volume index
+ */
+s32 si4705_vol_conv_value_to_index(struct si4705_device *radio, u16 value)
+{
+ s32 i;
+
+ if (!((radio->pdata->pdata_values & SI4705_PDATA_BIT_VOL_STEPS)
+ && (radio->pdata->pdata_values & SI4705_PDATA_BIT_VOL_TABLE))) {
+ pr_err("%s: it seems si4705_pdata isn't filled", __func__);
+ return RX_VOLUME_MAX;
+ }
+
+ for (i = 0; i < radio->pdata->rx_vol_steps; i++) {
+ if (value == radio->pdata->rx_vol_table[i])
+ return i;
+ }
+ pr_warning("%s: can not find matching volume %u", __func__, value);
+ return 0;
+}
+
+/*
+ * si4705_get_seek_tune_rssi_threshold_value
+ * - read FM_SEEK_TUNE_RSSI_THRESHOLD value from pdata
+ */
+u16 si4705_get_seek_tune_rssi_threshold_value(struct si4705_device *radio)
+{
+ if (!(radio->pdata->pdata_values & SI4705_PDATA_BIT_RSSI_THRESHOLD)) {
+ pr_err("%s: it seems si4705_pdata isn't filled", __func__);
+ return FM_SEEK_TUNE_RSSI_THRESHOLD_DEFAULT;
+ } else
+ return radio->pdata->rx_seek_tune_rssi_threshold;
+}
+
+/*
+ * si4705_get_seek_tune_snr_threshold_value
+ * - read FM_SEEK_TUNE_SNR_THRESHOLD value from pdata
+ */
+u16 si4705_get_seek_tune_snr_threshold_value(struct si4705_device *radio)
+{
+ if (!(radio->pdata->pdata_values & SI4705_PDATA_BIT_SNR_THRESHOLD)) {
+ pr_err("%s: it seems si4705_pdata isn't filled", __func__);
+ return FM_SEEK_TUNE_SNR_THRESHOLD_DEFAULT;
+ } else
+ return radio->pdata->rx_seek_tune_snr_threshold;
+}
+
+/**************************************************************************
+ * General Driver Functions - DISCONNECT_CHECK
+ **************************************************************************/
+
+/*
+ * si4705_disconnect_check - check whether radio disconnects
+ */
+int si4705_disconnect_check(struct si4705_device *radio)
+{
+ return 0;
+}
+
+
+/**************************************************************************
+ * I2C Interface
+ **************************************************************************/
+
+/*
+ * si4705_resp_workqueue - interrupt workqueue
+ */
+void si4705_resp_work(struct work_struct *work)
+{
+ struct si4705_device *radio =
+ container_of(work, struct si4705_device, resp_work);
+ unsigned char blocknum;
+ unsigned short bler; /* rds block errors */
+ unsigned short rds;
+ unsigned char tmpbuf[3];
+ int retval = 0;
+ u16 rdsConfig = 0;
+ u8 resInd = 0;
+ u16 rdsFifo[4] = {0,};
+ u8 ble = 0;
+
+ /* safety checks */
+ retval = si4705_get_property(radio, RDS_CONFIG, &rdsConfig);
+ if (retval < 0)
+ goto end;
+
+ if ((rdsConfig & RDS_CONFIG_RDSEN_MASK) == 0)
+ goto end;
+
+ retval = si4705_fm_rds_status(radio, 0x00, 0x01, &resInd, NULL, NULL,
+ rdsFifo, &ble);
+ if (retval < 0)
+ goto end;
+
+ /* get rds blocks */
+ if ((resInd & FM_RDS_STATUS_OUT_RECV) == 0)
+ /* No RDS group ready, better luck next time */
+ goto end;
+
+ for (blocknum = 0; blocknum < 4; blocknum++) {
+ switch (blocknum) {
+ default:
+ bler = (ble & FM_RDS_STATUS_OUT_BLEA)
+ >> FM_RDS_STATUS_OUT_BLEA_SHFT;
+ rds = rdsFifo[0];
+ break;
+ case 1:
+ bler = (ble & FM_RDS_STATUS_OUT_BLEB)
+ >> FM_RDS_STATUS_OUT_BLEB_SHFT;
+ rds = rdsFifo[1];
+ break;
+ case 2:
+ bler = (ble & FM_RDS_STATUS_OUT_BLEC)
+ >> FM_RDS_STATUS_OUT_BLEC_SHFT;
+ rds = rdsFifo[2];
+ break;
+ case 3:
+ bler = (ble & FM_RDS_STATUS_OUT_BLED)
+ >> FM_RDS_STATUS_OUT_BLED_SHFT;
+ rds = rdsFifo[3];
+ break;
+ };
+
+ /* Fill the V4L2 RDS buffer */
+ put_unaligned_le16(rds, &tmpbuf);
+ tmpbuf[2] = blocknum; /* offset name */
+ tmpbuf[2] |= blocknum << 3; /* received offset */
+ if (bler > max_rds_errors)
+ tmpbuf[2] |= 0x80; /* uncorrectable errors */
+ else if (bler > 0)
+ tmpbuf[2] |= 0x40; /* corrected error(s) */
+
+ /* copy RDS block to internal buffer */
+ memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
+ radio->wr_index += 3;
+
+ /* wrap write pointer */
+ if (radio->wr_index >= radio->buf_size)
+ radio->wr_index = 0;
+
+ /* check for overflow */
+ if (radio->wr_index == radio->rd_index) {
+ /* increment and wrap read pointer */
+ radio->rd_index += 3;
+ if (radio->rd_index >= radio->buf_size)
+ radio->rd_index = 0;
+ }
+ }
+
+ if (radio->wr_index != radio->rd_index)
+ wake_up_interruptible(&radio->read_queue);
+end:
+ return;
+}
+
+/*
+ * si4705_i2c_interrupt - interrupt handler
+ */
+static irqreturn_t si4705_i2c_interrupt(int irq, void *dev_id)
+{
+ struct si4705_device *radio = dev_id;
+ int retval = 0;
+ u8 stcint = 0;
+
+ /* check Seek/Tune Complete */
+ retval = si4705_get_int_status(radio, NULL, NULL, NULL, NULL, &stcint);
+ if (retval < 0)
+ goto end;
+
+ if (stcint)
+ complete(&radio->completion);
+
+ queue_work(radio->queue, &radio->resp_work);
+
+end:
+ return IRQ_HANDLED;
+}
+
+/*
+ * si4705_i2c_probe - probe for the device
+ */
+static int __devinit si4705_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct si4705_device *radio;
+ int retval = 0;
+
+ /* private data allocation and initialization */
+ radio = kzalloc(sizeof(struct si4705_device), GFP_KERNEL);
+ if (!radio) {
+ retval = -ENOMEM;
+ goto err_initial;
+ }
+ radio->pdata = client->dev.platform_data;
+
+ 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, &si4705_viddev_template,
+ sizeof(si4705_viddev_template));
+ video_set_drvdata(radio->videodev, radio);
+
+ /* set init. operational mode */
+ radio->op_mode = RADIO_OP_DEFAULT;
+
+ /* set init. power state */
+ /*
+ TODO: Need to make sure radio chip is really
+ in power down state to support hibernation feature
+ */
+ radio->power_state = STATE_POWER_DOWN;
+
+ /* power up */
+ if (si4705_power_up(radio) != 0) {
+ retval = -EIO;
+ goto err_radio;
+ }
+
+ /* read chip revision */
+ if (si4705_get_revision(radio) != 0) {
+ retval = -EIO;
+ goto err_radio;
+ }
+
+ /* 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);
+
+ /* mark Seek/Tune Complete Interrupt enabled */
+ radio->stci_enabled = true;
+ init_completion(&radio->completion);
+
+ INIT_WORK(&radio->resp_work, si4705_resp_work);
+ radio->queue = create_freezable_workqueue("si4705_wq");
+ if (radio->queue == NULL) {
+ retval = -ENOMEM;
+ pr_err("%s: Failed to create workqueue", __func__);
+ goto err_rds;
+ }
+
+ retval = request_threaded_irq(client->irq, NULL, si4705_i2c_interrupt,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ DRIVER_NAME, radio);
+ if (retval) {
+ dev_err(&client->dev, "Failed to register interrupt\n");
+ goto err_rds;
+ }
+
+ /* register video device */
+ retval = video_register_device(radio->videodev, VFL_TYPE_RADIO,
+ radio_nr);
+ if (retval) {
+ dev_warn(&client->dev, "Could not register video device\n");
+ goto err_all;
+ }
+ i2c_set_clientdata(client, radio);
+
+ /* power down */
+ if (si4705_power_down(radio) != 0) {
+ retval = -EIO;
+ goto err_radio;
+ }
+
+ return 0;
+err_all:
+ free_irq(client->irq, radio);
+err_rds:
+ kfree(radio->buffer);
+err_video:
+ video_device_release(radio->videodev);
+err_radio:
+ kfree(radio);
+err_initial:
+ return retval;
+}
+
+/*
+ * si4705_i2c_remove - remove the device
+ */
+static __devexit int si4705_i2c_remove(struct i2c_client *client)
+{
+ struct si4705_device *radio = i2c_get_clientdata(client);
+
+ free_irq(client->irq, radio);
+ destroy_workqueue(radio->queue);
+ video_unregister_device(radio->videodev);
+ kfree(radio);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * si4705_i2c_suspend - suspend the device
+ */
+static int si4705_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct si4705_device *radio = i2c_get_clientdata(client);
+
+ if (RADIO_OP_RICH_MODE == radio->op_mode) {
+ radio->power_state_at_suspend = radio->power_state;
+ /* power down */
+ if (si4705_power_down(radio) < 0)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/*
+ * si4705_i2c_resume - resume the device
+ */
+static int si4705_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct si4705_device *radio = i2c_get_clientdata(client);
+
+ if (RADIO_OP_RICH_MODE == radio->op_mode) {
+ /* power up if it was powered up at suspend: need 110ms */
+ if (radio->power_state_at_suspend == STATE_POWER_UP &&
+ si4705_power_up(radio) < 0)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(si4705_i2c_pm, si4705_i2c_suspend, si4705_i2c_resume);
+#endif
+
+/*
+ * si4705_i2c_driver - i2c driver interface
+ */
+static struct i2c_driver si4705_i2c_driver = {
+ .driver = {
+ .name = "si470x",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &si4705_i2c_pm,
+#endif
+ },
+ .probe = si4705_i2c_probe,
+ .remove = __devexit_p(si4705_i2c_remove),
+ .id_table = si4705_i2c_id,
+};
+
+
+/**************************************************************************
+ * Module Interface
+ **************************************************************************/
+
+/*
+ * si4705_i2c_init - module init
+ */
+static int __init si4705_i2c_init(void)
+{
+ printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+ return i2c_add_driver(&si4705_i2c_driver);
+}
+
+/*
+ * si4705_i2c_exit - module exit
+ */
+static void __exit si4705_i2c_exit(void)
+{
+ i2c_del_driver(&si4705_i2c_driver);
+}
+
+module_init(si4705_i2c_init);
+module_exit(si4705_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-si4705.h b/drivers/media/radio/si470x/radio-si4705.h
new file mode 100644
index 0000000..4ab3e0b
--- /dev/null
+++ b/drivers/media/radio/si470x/radio-si4705.h
@@ -0,0 +1,547 @@
+/*
+ * drivers/media/radio/si4705/radio-si4705.h
+ *
+ * Driver for radios with Silicon Labs Si4705 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-si4705"
+#define DRIVER_AUTHOR "NULL";
+#define DRIVER_KERNEL_VERSION KERNEL_VERSION(1, 0, 1)
+#define DRIVER_CARD "Silicon Labs Si4705 FM Radio Receiver"
+#define DRIVER_DESC "I2C radio driver for Si4705 FM Radio Receivers"
+#define DRIVER_VERSION "1.0.1"
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <linux/si4705_pdata.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <asm/unaligned.h>
+
+/**************************************************************************
+ * Register Definitions
+ **************************************************************************/
+
+/* Power up device and mode selection. */
+#define POWER_UP 0x01
+#define POWER_UP_NARGS 3
+#define POWER_UP_NRESP 1
+#define POWER_UP_IN_FUNC_FMRX 0x00
+#define POWER_UP_IN_FUNC_AMRX 0x01
+#define POWER_UP_IN_FUNC_FMTX 0x02
+#define POWER_UP_IN_FUNC_WBRX 0x03
+#define POWER_UP_IN_FUNC_QUERY 0x0F
+#define POWER_UP_IN_PATCH 0x20
+#define POWER_UP_IN_GPO2OEN 0x40
+#define POWER_UP_IN_CTSIEN 0x80
+#define POWER_UP_IN_OPMODE_RX_ANALOG 0x05
+#define POWER_UP_IN_OPMODE_RX_DIGITAL 0xB0
+
+/* Returns revision information on the device. */
+#define GET_REV 0x10
+#define GET_REV_NARGS 1
+#define GET_REV_NRESP 9
+
+/* Power down device. */
+#define POWER_DOWN 0x11
+#define POWER_DOWN_NARGS 1
+#define POWER_DOWN_NRESP 1
+
+/* Sets the value of a property. */
+#define SET_PROPERTY 0x12
+#define SET_PROPERTY_NARGS 6
+#define SET_PROPERTY_NRESP 1
+
+/* Retrieves a propertyˇŻs value. */
+#define GET_PROPERTY 0x13
+#define GET_PROPERTY_NARGS 4
+#define GET_PROPERTY_NRESP 4
+
+/* Reads interrupt status bits. */
+#define GET_INT_STATUS 0x14
+#define GET_INT_STATUS_NARGS 1
+#define GET_INT_STATUS_NRESP 1
+#define GET_INT_STATUS_CTS 0x80
+#define GET_INT_STATUS_ERR 0x40
+#define GET_INT_STATUS_RSQINT 0x08
+#define GET_INT_STATUS_RDSINT 0x04
+#define GET_INT_STATUS_STCINT 0x01
+#define GET_INT_STATUS_CTS_SHFT 7
+#define GET_INT_STATUS_ERR_SHFT 6
+#define GET_INT_STATUS_RSQINT_SHFT 3
+#define GET_INT_STATUS_RDSINT_SHFT 2
+#define GET_INT_STATUS_STCINT_SHFT 0
+
+/* Reserved command used for patch file downloads. */
+#define PATCH_ARGS 0x15
+
+/* Reserved command used for patch file downloads. */
+#define PATCH_DATA 0x16
+
+/* Selects the FM tuning frequency. */
+#define FM_TUNE_FREQ 0x20
+#define FM_TUNE_FREQ_NARGS 5
+#define FM_TUNE_FREQ_NRESP 1
+
+/* Begins searching for a valid frequency. */
+#define FM_SEEK_START 0x21
+#define FM_SEEK_START_NARGS 2
+#define FM_SEEK_START_NRESP 1
+#define FM_SEEK_START_IN_WRAP 0x04
+#define FM_SEEK_START_IN_SEEKUP 0x08
+
+/* Queries the status of previous FM_TUNE_FREQ or FM_SEEK_START command. */
+#define FM_TUNE_STATUS 0x22
+#define FM_TUNE_STATUS_NARGS 2
+#define FM_TUNE_STATUS_NRESP 8
+#define FM_TUNE_STATUS_IN_INTACK 0x01
+#define FM_TUNE_STATUS_IN_CANCEL 0x02
+#define FM_TUNE_STATUS_OUT_VALID 0x01
+#define FM_TUNE_STATUS_OUT_AFCRL 0x02
+#define FM_TUNE_STATUS_OUT_BTLF 0x80
+
+/* Queries the status of the Received Signal Quality (RSQ) */
+/* of the current channel. */
+#define FM_RSQ_STATUS 0x23
+#define FM_RSQ_STATUS_NARGS 2
+#define FM_RSQ_STATUS_NRESP 8
+#define FM_RSQ_STATUS_IN_INTACK 0x01
+#define FM_RSQ_STATUS_OUT_RSSILINT 0x01
+#define FM_RSQ_STATUS_OUT_RSSIHINT 0x02
+#define FM_RSQ_STATUS_OUT_ASNRLINT 0x04
+#define FM_RSQ_STATUS_OUT_ASNRHINT 0x08
+#define FM_RSQ_STATUS_OUT_BLENDINT 0x80
+#define FM_RSQ_STATUS_OUT_VALID 0x01
+#define FM_RSQ_STATUS_OUT_AFCRL 0x02
+#define FM_RSQ_STATUS_OUT_SMUTE 0x08
+#define FM_RSQ_STATUS_OUT_PILOT 0x80
+#define FM_RSQ_STATUS_OUT_STBLEND 0x7F
+
+/* Returns RDS information for current channel */
+/* and reads an entry from RDS FIFO. */
+#define FM_RDS_STATUS 0x24
+#define FM_RDS_STATUS_NARGS 2
+#define FM_RDS_STATUS_NRESP 13
+#define FM_RDS_STATUS_IN_INTACK 0x01
+#define FM_RDS_STATUS_IN_MTFIFO 0x02
+#define FM_RDS_STATUS_OUT_RECV 0x01
+#define FM_RDS_STATUS_OUT_SYNCLOST 0x02
+#define FM_RDS_STATUS_OUT_SYNCFOUND 0x04
+#define FM_RDS_STATUS_OUT_SYNC 0x01
+#define FM_RDS_STATUS_OUT_GRPLOST 0x04
+#define FM_RDS_STATUS_OUT_BLED 0x03
+#define FM_RDS_STATUS_OUT_BLEC 0x0C
+#define FM_RDS_STATUS_OUT_BLEB 0x30
+#define FM_RDS_STATUS_OUT_BLEA 0xC0
+#define FM_RDS_STATUS_OUT_BLED_SHFT 0
+#define FM_RDS_STATUS_OUT_BLEC_SHFT 2
+#define FM_RDS_STATUS_OUT_BLEB_SHFT 4
+#define FM_RDS_STATUS_OUT_BLEA_SHFT 6
+
+/* Queries the current AGC settings */
+#define FM_AGC_STATUS 0x27
+#define FM_AGC_STATUS_NARGS 1
+#define FM_AGC_STATUS_NRESP 3
+#define FM_AGC_STATUS_RFAGCDIS 0x01
+#define FM_AGC_STATUS_LNAGAINIDX 0x1F
+
+/* Override AGC setting by disabling and forcing it to a fixed value */
+#define FM_AGC_OVERRIDE 0x28
+#define FM_AGC_OVERRIDE_NARGS 3
+#define FM_AGC_OVERRIDE_NRESP 1
+#define FM_AGC_OVERRIDE_RFAGCDIS 0x01
+#define FM_AGC_OVERRIDE_LNAGAINIDX 0x1F
+
+/* Configures GPO1, 2, and 3 as output or Hi-Z. */
+#define GPIO_CTL 0x80
+#define GPIO_CTL_NARGS 2
+#define GPIO_CTL_NRESP 1
+#define GPIO_CTL_GPO3OEN 0x08
+#define GPIO_CTL_GPO2OEN 0x04
+#define GPIO_CTL_GPO1OEN 0x02
+
+/* Sets GPO1, 2, and 3 output level (low or high) */
+#define GPIO_SET 0x81
+#define GPIO_SET_NARGS 2
+#define GPIO_SET_NRESP 1
+#define GPIO_SET_GPO3LEVEL 0x08
+#define GPIO_SET_GPO2LEVEL 0x04
+#define GPIO_SET_GPO1LEVEL 0x02
+
+/* STATUS bits - Used by all methods */
+#define STCINT 0x01
+#define ASQINT 0x02
+#define RDSINT 0x04
+#define RSQINT 0x08
+#define ERR 0x40
+#define CTS 0x80
+
+/*==================================================================
+ General Properties
+==================================================================*/
+
+/* GPO_IEN */
+#define GPO_IEN 0x0001
+#define GPO_IEN_STCIEN_MASK 0x0001
+#define GPO_IEN_ASQIEN_MASK 0x0002
+#define GPO_IEN_RDSIEN_MASK 0x0004
+#define GPO_IEN_RSQIEN_MASK 0x0008
+#define GPO_IEN_ERRIEN_MASK 0x0040
+#define GPO_IEN_CTSIEN_MASK 0x0080
+#define GPO_IEN_STCREP_MASK 0x0100
+#define GPO_IEN_ASQREP_MASK 0x0200
+#define GPO_IEN_RDSREP_MASK 0x0400
+#define GPO_IEN_RSQREP_MASK 0x0800
+#define GPO_IEN_STCIEN_SHFT 0
+#define GPO_IEN_ASQIEN_SHFT 1
+#define GPO_IEN_RDSIEN_SHFT 2
+#define GPO_IEN_RSQIEN_SHFT 3
+#define GPO_IEN_ERRIEN_SHFT 6
+#define GPO_IEN_CTSIEN_SHFT 7
+#define GPO_IEN_STCREP_SHFT 8
+#define GPO_IEN_ASQREP_SHFT 9
+#define GPO_IEN_RDSREP_SHFT 10
+#define GPO_IEN_RSQREP_SHFT 11
+
+/* DIGITAL_INPUT_FORMAT */
+#define DIGITAL_INPUT_FORMAT 0x0101
+#define DIGITAL_INPUT_FORMAT_ISIZE_MASK 0x0003
+#define DIGITAL_INPUT_FORMAT_IMONO_MASK 0x0004
+#define DIGITAL_INPUT_FORMAT_IMODE_MASK 0x0078
+#define DIGITAL_INPUT_FORMAT_IFALL_MASK 0x0080
+#define DIGITAL_INPUT_FORMAT_ISIZE_SHFT 0
+#define DIGITAL_INPUT_FORMAT_IMONO_SHFT 2
+#define DIGITAL_INPUT_FORMAT_IMODE_SHFT 3
+#define DIGITAL_INPUT_FORMAT_IFALL_SHFT 7
+
+/* DIGITAL_INPUT_SAMPLE_RATE */
+#define DIGITAL_INPUT_SAMPLE_RATE 0x0103
+
+/* DIGITAL_OUTPUT_FORMAT */
+#define DIGITAL_OUTPUT_FORMAT 0x0102
+#define DIGITAL_OUTPUT_FORMAT_OSIZE_MASK 0x0003
+#define DIGITAL_OUTPUT_FORMAT_OMONO_MASK 0x0004
+#define DIGITAL_OUTPUT_FORMAT_OMODE_MASK 0x0078
+#define DIGITAL_OUTPUT_FORMAT_OFALL_MASK 0x0080
+#define DIGITAL_OUTPUT_FORMAT_OSIZE_SHFT 0
+#define DIGITAL_OUTPUT_FORMAT_OMONO_SHFT 2
+#define DIGITAL_OUTPUT_FORMAT_OMODE_SHFT 3
+#define DIGITAL_OUTPUT_FORMAT_OFALL_SHFT 7
+
+/* DIGITAL_OUTPUT_SAMPLE_RATE */
+#define DIGITAL_OUTPUT_SAMPLE_RATE 0x0104
+
+/* REFCLK_FREQ */
+#define REFCLK_FREQ 0x0201
+
+/* REFCLK_PRESCALE */
+#define REFCLK_PRESCALE 0x0202
+#define REFCLK_PRESCALE_MASK 0x0FFF
+#define REFCLK_PRESCALE_SHFT 0
+
+/*==================================================================
+ FM Receive Properties
+==================================================================*/
+
+/* FM_DEEMPHASIS */
+#define FM_DEEMPHASIS 0x1100
+#define FM_DEEMPHASIS_MASK 0x0003
+#define FM_DEEMPHASIS_SHFT 0
+
+/* FM_BLEND_STEREO_THRESHOLD */
+#define FM_BLEND_STEREO_THRESHOLD 0x1105
+#define FM_BLEND_STEREO_THRESHOLD_MASK 0x007F
+#define FM_BLEND_STEREO_THRESHOLD_SHFT 0
+
+/* FM_BLEND_MONO_THRESHOLD */
+#define FM_BLEND_MONO_THRESHOLD 0x1106
+#define FM_BLEND_MONO_THRESHOLD_MASK 0x007F
+#define FM_BLEND_MONO_THRESHOLD_SHFT 0
+
+/* FM_ANTENNA_INPUT */
+#define FM_ANTENNA_INPUT 0x1107
+#define FM_ANTENNA_INPUT_MASK 0x0001
+#define FM_ANTENNA_INPUT_SHFT 0
+
+/* FM_MAX_TUNE_ERROR */
+#define FM_MAX_TUNE_ERROR 0x1108
+#define FM_MAX_TUNE_ERROR_MASK 0x007F
+#define FM_MAX_TUNE_ERROR_SHFT 0
+
+/* FM_RSQ_INT_SOURCE */
+#define FM_RSQ_INT_SOURCE 0x1200
+#define FM_RSQ_INT_SOURCE_RSSILIEN_MASK 0x0001
+#define FM_RSQ_INT_SOURCE_RSSIHIEN_MASK 0x0002
+#define FM_RSQ_INT_SOURCE_ASNRLIEN_MASK 0x0004
+#define FM_RSQ_INT_SOURCE_ASNRHIEN_MASK 0x0008
+#define FM_RSQ_INT_SOURCE_BLENDIEN_MASK 0x0080
+#define FM_RSQ_INT_SOURCE_RSSILIEN_SHFT 0
+#define FM_RSQ_INT_SOURCE_RSSIHIEN_SHFT 1
+#define FM_RSQ_INT_SOURCE_ASNRLIEN_SHFT 2
+#define FM_RSQ_INT_SOURCE_ASNRHIEN_SHFT 3
+#define FM_RSQ_INT_SOURCE_BLENDIEN_SHFT 7
+
+/* FM_RSQ_SNR_HI_THRESHOLD */
+#define FM_RSQ_SNR_HI_THRESHOLD 0x1201
+#define FM_RSQ_SNR_HI_THRESHOLD_MASK 0x007F
+#define FM_RSQ_SNR_HI_THRESHOLD_SHFT 0
+
+/* FM_RSQ_SNR_LO_THRESHOLD */
+#define FM_RSQ_SNR_LO_THRESHOLD 0x1202
+#define FM_RSQ_SNR_LO_THRESHOLD_MASK 0x007F
+#define FM_RSQ_SNR_LO_THRESHOLD_SHFT 0
+
+/* FM_RSQ_RSSI_HI_THRESHOLD */
+#define FM_RSQ_RSSI_HI_THRESHOLD 0x1203
+#define FM_RSQ_RSSI_HI_THRESHOLD_MASK 0x007F
+#define FM_RSQ_RSSI_HI_THRESHOLD_SHFT 0
+
+/* FM_RSQ_RSSI_LO_THRESHOLD */
+#define FM_RSQ_RSSI_LO_THRESHOLD 0x1204
+#define FM_RSQ_RSSI_LO_THRESHOLD_MASK 0x007F
+#define FM_RSQ_RSSI_LO_THRESHOLD_SHFT 0
+
+/* FM_RSQ_BLEND_THRESHOLD */
+#define FM_RSQ_BLEND_THRESHOLD 0x1207
+#define FM_RSQ_BLEND_THRESHOLD_BLEND_MASK 0x007F
+#define FM_RSQ_BLEND_THRESHOLD_PILOT_MASK 0x0080
+#define FM_RSQ_BLEND_THRESHOLD_BLEND_SHFT 0
+#define FM_RSQ_BLEND_THRESHOLD_PILOT_SHFT 7
+
+/* FM_SOFT_MUTE_RATE */
+#define FM_SOFT_MUTE_RATE 0x1300
+#define FM_SOFT_MUTE_RATE_MASK 0x00FF
+#define FM_SOFT_MUTE_RATE_SHFT 0
+
+/* FM_SOFT_MUTE_MAX_ATTENUATION */
+#define FM_SOFT_MUTE_MAX_ATTENUATION 0x1302
+#define FM_SOFT_MUTE_MAX_ATTENUATION_MASK 0x001F
+#define FM_SOFT_MUTE_MAX_ATTENUATION_SHFT 0
+
+/* FM_SOFT_MUTE_SNR_THRESHOLD */
+#define FM_SOFT_MUTE_SNR_THRESHOLD 0x1303
+#define FM_SOFT_MUTE_SNR_THRESHOLD_MASK 0x000F
+#define FM_SOFT_MUTE_SNR_THRESHOLD_SHFT 0
+
+/* FM_SEEK_BAND_BOTTOM */
+#define FM_SEEK_BAND_BOTTOM 0x1400
+
+/* FM_SEEK_BAND_TOP */
+#define FM_SEEK_BAND_TOP 0x1401
+
+/* FM_SEEK_FREQ_SPACING */
+#define FM_SEEK_FREQ_SPACING 0x1402
+#define FM_SEEK_FREQ_SPACING_MASK 0x001F
+#define FM_SEEK_FREQ_SPACING_SHFT 0
+
+/* FM_SEEK_TUNE_SNR_THRESHOLD */
+#define FM_SEEK_TUNE_SNR_THRESHOLD 0x1403
+#define FM_SEEK_TUNE_SNR_THRESHOLD_MASK 0x007F
+#define FM_SEEK_TUNE_SNR_THRESHOLD_SHFT 0
+#define FM_SEEK_TUNE_SNR_THRESHOLD_DEFAULT 0xa
+
+/* FM_SEEK_TUNE_RSSI_THRESHOLD */
+#define FM_SEEK_TUNE_RSSI_THRESHOLD 0x1404
+#define FM_SEEK_TUNE_RSSI_THRESHOLD_MASK 0x007F
+#define FM_SEEK_TUNE_RSSI_THRESHOLD_SHFT 0
+#define FM_SEEK_TUNE_RSSI_THRESHOLD_DEFAULT 0x14
+
+/* RDS_INT_SOURCE */
+#define RDS_INT_SOURCE 0x1500
+#define RDS_INT_SOURCE_RECV_MASK 0x0001
+#define RDS_INT_SOURCE_SYNCLOST_MASK 0x0002
+#define RDS_INT_SOURCE_SYNCFOUND_MASK 0x0004
+#define RDS_INT_SOURCE_RECV_SHFT 0
+#define RDS_INT_SOURCE_SYNCLOST_SHFT 1
+#define RDS_INT_SOURCE_SYNCFOUND_SHFT 2
+
+/* RDS_INT_FIFO_COUNT */
+#define RDS_INT_FIFO_COUNT 0x1501
+#define RDS_INT_FIFO_COUNT_MASK 0x00FF
+#define RDS_INT_FIFO_COUNT_SHFT 0
+
+/* RDS_CONFIG */
+#define RDS_CONFIG 0x1502
+#define RDS_CONFIG_RDSEN_MASK 0x0001
+#define RDS_CONFIG_BLETHD_MASK 0x0300
+#define RDS_CONFIG_BLETHC_MASK 0x0C00
+#define RDS_CONFIG_BLETHB_MASK 0x3000
+#define RDS_CONFIG_BLETHA_MASK 0xC000
+#define RDS_CONFIG_RDSEN_SHFT 0
+#define RDS_CONFIG_BLETHD_SHFT 8
+#define RDS_CONFIG_BLETHC_SHFT 10
+#define RDS_CONFIG_BLETHB_SHFT 12
+#define RDS_CONFIG_BLETHA_SHFT 14
+
+/*==================================================================
+ General Receive Properties
+==================================================================*/
+
+/* RX_VOLUME */
+#define RX_VOLUME 0x4000
+#define RX_VOLUME_MASK 0x003F
+#define RX_VOLUME_MAX 0x003F
+#define RX_VOLUME_SHFT 0
+
+/* RX_HARD_MUTE */
+#define RX_HARD_MUTE 0x4001
+#define RX_HARD_MUTE_RMUTE_MASK 0x0001
+#define RX_HARD_MUTE_LMUTE_MASK 0x0002
+#define RX_HARD_MUTE_RMUTE_SHFT 0
+#define RX_HARD_MUTE_LMUTE_SHFT 1
+
+/*==================================================================
+ Bit Definitions for Properties
+==================================================================*/
+
+/* DIGITAL_MODE - used for input or output */
+#define DIGITAL_MODE_I2S 0x0
+#define DIGITAL_MODE_LEFT 0x6
+#define DIGITAL_MODE_MSB1ST 0xC
+#define DIGITAL_MODE_MSB2ND 0x8
+
+/* DIGITAL_SIZE - used for input or output */
+#define DIGITAL_SIZE_16 0x0
+#define DIGITAL_SIZE_20 0x1
+#define DIGITAL_SIZE_24 0x2
+#define DIGITAL_SIZE_8 0x3
+
+/* FM_DEEMPH */
+#define FM_DEEMPH_75US 0x2
+#define FM_DEEMPH_50US 0x1
+
+/* FM_RDS_BLETH - used for all block error thresholds */
+#define FM_RDS_BLETH_NO_ERRORS 0x0
+#define FM_RDS_BLETH_1OR2_ERRORS 0x1
+#define FM_RDS_BLETH_3TO5_ERRORS 0x2
+#define FM_RDS_BLETH_UNCORRECTABLE 0x3
+
+/**************************************************************************
+ * General Driver Definitions
+ **************************************************************************/
+enum {
+ RADIO_OP_LP_MODE = 0,
+ /* radio playback routed directly codec. default */
+ RADIO_OP_RICH_MODE,
+ /* radio playback routed via AP */
+ RADIO_OP_DEFAULT = RADIO_OP_LP_MODE
+};
+
+/*
+ * si4705_device - private data
+ */
+struct si4705_device {
+ struct video_device *videodev;
+
+ /* driver management */
+ unsigned int users;
+ unsigned char op_mode;
+ unsigned char power_state;
+ unsigned char power_state_at_suspend;
+
+ /* si4705 int_status (0..7) */
+ unsigned char int_status;
+
+ /* RDS receive buffer */
+ wait_queue_head_t read_queue;
+ struct mutex lock; /* buffer locking */
+ struct work_struct resp_work;
+ struct workqueue_struct *queue;
+ unsigned char *buffer; /* size is always multiple of three */
+ unsigned int buf_size;
+ unsigned int rd_index;
+ unsigned int wr_index;
+
+ struct completion completion;
+ bool stci_enabled; /* Seek/Tune Complete Interrupt */
+
+ struct i2c_client *client;
+ struct si4705_pdata *pdata;
+};
+
+
+
+/**************************************************************************
+ * 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 (10000000 / 625)
+
+
+
+/**************************************************************************
+ * Common Functions
+ **************************************************************************/
+extern struct video_device si4705_viddev_template;
+int si4705_power_up(struct si4705_device *radio);
+int si4705_power_down(struct si4705_device *radio);
+int si4705_set_property(struct si4705_device *radio, u16 prop, u16 val);
+int si4705_get_property(struct si4705_device *radio, u16 prop, u16 *pVal);
+int si4705_get_int_status(struct si4705_device *radio, u8 *cts, u8 *err,
+ u8 *rsqint, u8 *rdsint, u8 *stcint);
+int si4705_fm_tune_freq(struct si4705_device *radio, u16 freq, u8 antcap);
+int si4705_fm_seek_start(struct si4705_device *radio, u8 seekUp, u8 wrap);
+int si4705_fm_tune_status(struct si4705_device *radio, u8 cancel,
+ u8 intack, u8 *bltf, u16 *freq, u8 *rssi, u8 *snr, u8 *antcap);
+int si4705_fm_rsq_status(struct si4705_device *radio, u8 intack,
+ u8 *intStatus, u8 *indStatus, u8 *stBlend, u8 *rssi,
+ u8 *snr, u8 *freqOff);
+int si4705_fm_rds_status(struct si4705_device *radio, u8 mtfifo,
+ u8 intack, u8 *rdsInd, u8 *sync, u8 *fifoUsed, u16 *rdsFifo,
+ u8 *ble);
+int si4705_fm_agc_status(struct si4705_device *radio, u8 *rfAgcDis,
+ u8 *lnaGainIndex);
+int si4705_fm_agc_override(struct si4705_device *radio, u8 *rfAgcDis,
+ u8 *lnaGainIndex);
+int si4705_gpio_ctl(struct si4705_device *radio, u8 gpio1,
+ u8 gpio2, u8 gpio3);
+int si4705_gpio_set(struct si4705_device *radio, u8 gpio1,
+ u8 gpio2, u8 gpio3);
+int si4705_disconnect_check(struct si4705_device *radio);
+u16 si4705_vol_conv_index_to_value(struct si4705_device *radio, s32 index);
+s32 si4705_vol_conv_value_to_index(struct si4705_device *radio, u16 value);
+u16 si4705_get_seek_tune_rssi_threshold_value(struct si4705_device *radio);
+u16 si4705_get_seek_tune_snr_threshold_value(struct si4705_device *radio);