diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/media/radio | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_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/Kconfig | 13 | ||||
-rw-r--r-- | drivers/media/radio/si470x/Makefile | 2 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si4705-common.c | 963 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si4705-i2c.c | 997 | ||||
-rw-r--r-- | drivers/media/radio/si470x/radio-si4705.h | 547 |
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); |