Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/media/radio/radio-tavarua.c b/drivers/media/radio/radio-tavarua.c
new file mode 100644
index 0000000..f04dfe5
--- /dev/null
+++ b/drivers/media/radio/radio-tavarua.c
@@ -0,0 +1,3755 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+/*
+ * Qualcomm Tavarua FM core driver
+ */
+
+/* driver definitions */
+#define DRIVER_AUTHOR "Qualcomm"
+#define DRIVER_NAME "radio-tavarua"
+#define DRIVER_CARD "Qualcomm FM Radio Transceiver"
+#define DRIVER_DESC "I2C radio driver for Qualcomm FM Radio Transceiver "
+#define DRIVER_VERSION "1.0.0"
+
+#include <linux/version.h>
+#include <linux/init.h> /* Initdata */
+#include <linux/delay.h> /* udelay */
+#include <linux/uaccess.h> /* copy to/from user */
+#include <linux/kfifo.h> /* lock free circular buffer */
+#include <linux/param.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+
+/* kernel includes */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <media/v4l2-common.h>
+#include <asm/unaligned.h>
+#include <media/v4l2-ioctl.h>
+#include <linux/unistd.h>
+#include <asm/atomic.h>
+#include <media/tavarua.h>
+#include <linux/mfd/marimba.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+/*
+regional parameters for radio device
+*/
+struct region_params_t {
+ enum tavarua_region_t region;
+ unsigned int band_high;
+ unsigned int band_low;
+ char emphasis;
+ char rds_std;
+ char spacing;
+};
+
+struct srch_params_t {
+ unsigned short srch_pi;
+ unsigned char srch_pty;
+ unsigned int preset_num;
+ int get_list;
+};
+
+/* Main radio device structure,
+acts as a shadow copy of the
+actual tavaura registers */
+struct tavarua_device {
+ struct video_device *videodev;
+ /* driver management */
+ int users;
+ /* top level driver data */
+ struct marimba *marimba;
+ struct device *dev;
+ /* platform specific functionality */
+ struct marimba_fm_platform_data *pdata;
+ unsigned int chipID;
+ /*RDS buffers + Radio event buffer*/
+ struct kfifo data_buf[TAVARUA_BUF_MAX];
+ /* search paramters */
+ struct srch_params_t srch_params;
+ /* keep track of pending xfrs */
+ int pending_xfrs[TAVARUA_XFR_MAX];
+ int xfr_bytes_left;
+ int xfr_in_progress;
+ /* Transmit data */
+ enum tavarua_xfr_ctrl_t tx_mode;
+ /* synchrnous xfr data */
+ unsigned char sync_xfr_regs[XFR_REG_NUM];
+ struct completion sync_xfr_start;
+ struct completion sync_req_done;
+ int tune_req;
+ /* internal register status */
+ unsigned char registers[RADIO_REGISTERS];
+ /* regional settings */
+ struct region_params_t region_params;
+ /* power mode */
+ int lp_mode;
+ int handle_irq;
+ /* global lock */
+ struct mutex lock;
+ /* buffer locks*/
+ spinlock_t buf_lock[TAVARUA_BUF_MAX];
+ /* work queue */
+ struct workqueue_struct *wqueue;
+ struct delayed_work work;
+ /* wait queue for blocking event read */
+ wait_queue_head_t event_queue;
+ /* wait queue for raw rds read */
+ wait_queue_head_t read_queue;
+ /* PTY for FM Tx */
+ int pty;
+ /* PI for FM TX */
+ int pi;
+ /*PS repeatcount for PS Tx */
+ int ps_repeatcount;
+};
+
+/**************************************************************************
+ * Module Parameters
+ **************************************************************************/
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+static int wait_timeout = WAIT_TIMEOUT;
+/* Bahama's version*/
+static u8 bahama_version;
+/* RDS buffer blocks */
+static unsigned int rds_buf = 100;
+module_param(rds_buf, uint, 0);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
+/* static variables */
+static struct tavarua_device *private_data;
+/* forward declerations */
+static int tavarua_disable_interrupts(struct tavarua_device *radio);
+static int tavarua_setup_interrupts(struct tavarua_device *radio,
+ enum radio_state_t state);
+static int tavarua_start(struct tavarua_device *radio,
+ enum radio_state_t state);
+static int tavarua_request_irq(struct tavarua_device *radio);
+static void start_pending_xfr(struct tavarua_device *radio);
+/* work function */
+static void read_int_stat(struct work_struct *work);
+
+static int is_bahama(void)
+{
+ int id = 0;
+
+ switch (id = adie_get_detected_connectivity_type()) {
+ case BAHAMA_ID:
+ FMDBG("It is Bahama\n");
+ return 1;
+
+ case MARIMBA_ID:
+ FMDBG("It is Marimba\n");
+ return 0;
+ default:
+ printk(KERN_ERR "%s: unexpected adie connectivity type: %d\n",
+ __func__, id);
+ return -ENODEV;
+ }
+}
+
+static int set_fm_slave_id(struct tavarua_device *radio)
+{
+ int bahama_present = is_bahama();
+
+ if (bahama_present == -ENODEV)
+ return -ENODEV;
+
+ if (bahama_present)
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA_FM;
+ else
+ radio->marimba->mod_id = MARIMBA_SLAVE_ID_FM;
+
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_isr
+=============================================================================*/
+/**
+ This function is called when GPIO is toggled. This functions queues the event
+ to interrupt queue, which is later handled by isr handling funcion.
+ i.e. INIT_DELAYED_WORK(&radio->work, read_int_stat);
+
+ @param irq: irq that is toggled.
+ @param dev_id: structure pointer passed by client.
+
+ @return IRQ_HANDLED.
+*/
+static irqreturn_t tavarua_isr(int irq, void *dev_id)
+{
+ struct tavarua_device *radio = dev_id;
+ /* schedule a tasklet to handle host intr */
+ /* The call to queue_delayed_work ensures that a minimum delay (in jiffies)
+ * passes before the work is actually executed. The return value from the
+ * function is nonzero if the work_struct was actually added to queue
+ * (otherwise, it may have already been there and will not be added a second
+ * time).
+ */
+ queue_delayed_work(radio->wqueue, &radio->work,
+ msecs_to_jiffies(TAVARUA_DELAY));
+ return IRQ_HANDLED;
+}
+
+/**************************************************************************
+ * Interface to radio internal registers over top level marimba driver
+ *************************************************************************/
+
+/*=============================================================================
+FUNCTION: tavarua_read_registers
+=============================================================================*/
+/**
+ This function is called to read a number of bytes from an I2C interface.
+ The bytes read are stored in internal register status (shadow copy).
+
+ @param radio: structure pointer passed by client.
+ @param offset: register offset.
+ @param len: num of bytes.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_read_registers(struct tavarua_device *radio,
+ unsigned char offset, int len)
+{
+ int retval = 0, i = 0;
+ retval = set_fm_slave_id(radio);
+
+ if (retval == -ENODEV)
+ return retval;
+
+ FMDBG_I2C("I2C Slave: %x, Read Offset(%x): Data [",
+ radio->marimba->mod_id,
+ offset);
+
+ retval = marimba_read(radio->marimba, offset,
+ &radio->registers[offset], len);
+
+ if (retval > 0) {
+ for (i = 0; i < len; i++)
+ FMDBG_I2C("%02x ", radio->registers[offset+i]);
+ FMDBG_I2C(" ]\n");
+
+ }
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_write_register
+=============================================================================*/
+/**
+ This function is called to write a byte over the I2C interface.
+ The corresponding shadow copy is stored in internal register status.
+
+ @param radio: structure pointer passed by client.
+ @param offset: register offset.
+ @param value: buffer to be written to the registers.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_write_register(struct tavarua_device *radio,
+ unsigned char offset, unsigned char value)
+{
+ int retval;
+ retval = set_fm_slave_id(radio);
+
+ if (retval == -ENODEV)
+ return retval;
+
+ FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[",
+ radio->marimba->mod_id,
+ offset);
+ retval = marimba_write(radio->marimba, offset, &value, 1);
+ if (retval > 0) {
+ if (offset < RADIO_REGISTERS) {
+ radio->registers[offset] = value;
+ FMDBG_I2C("%02x ", radio->registers[offset]);
+ }
+ FMDBG_I2C(" ]\n");
+ }
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_write_registers
+=============================================================================*/
+/**
+ This function is called to write a number of bytes over the I2C interface.
+ The corresponding shadow copy is stored in internal register status.
+
+ @param radio: structure pointer passed by client.
+ @param offset: register offset.
+ @param buf: buffer to be written to the registers.
+ @param len: num of bytes.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_write_registers(struct tavarua_device *radio,
+ unsigned char offset, unsigned char *buf, int len)
+{
+
+ int i;
+ int retval;
+ retval = set_fm_slave_id(radio);
+
+ if (retval == -ENODEV)
+ return retval;
+
+ FMDBG_I2C("I2C Slave: %x, Write Offset(%x): Data[",
+ radio->marimba->mod_id,
+ offset);
+ retval = marimba_write(radio->marimba, offset, buf, len);
+ if (retval > 0) { /* if write successful, update internal state too */
+ for (i = 0; i < len; i++) {
+ if ((offset+i) < RADIO_REGISTERS) {
+ radio->registers[offset+i] = buf[i];
+ FMDBG_I2C("%x ", radio->registers[offset+i]);
+ }
+ }
+ FMDBG_I2C(" ]\n");
+ }
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: read_data_blocks
+=============================================================================*/
+/**
+ This function reads Raw RDS blocks from Core regs to driver
+ internal regs (shadow copy).
+
+ @param radio: structure pointer passed by client.
+ @param offset: register offset.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int read_data_blocks(struct tavarua_device *radio, unsigned char offset)
+{
+ /* read all 3 RDS blocks */
+ return tavarua_read_registers(radio, offset, RDS_BLOCK*4);
+}
+
+/*=============================================================================
+FUNCTION: tavarua_rds_read
+=============================================================================*/
+/**
+ This is a rds processing function reads that reads Raw RDS blocks from Core
+ regs to driver internal regs (shadow copy). It then fills the V4L2 RDS buffer,
+ which is read by App using JNI interface.
+
+ @param radio: structure pointer passed by client.
+
+ @return None.
+*/
+static void tavarua_rds_read(struct tavarua_device *radio)
+{
+ struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS];
+ unsigned char blocknum;
+ unsigned char tmp[3];
+
+ if (read_data_blocks(radio, RAW_RDS) < 0)
+ return;
+ /* copy all four RDS blocks to internal buffer */
+ for (blocknum = 0; blocknum < RDS_BLOCKS_NUM; blocknum++) {
+ /* Fill the V4L2 RDS buffer */
+ put_unaligned(cpu_to_le16(radio->registers[RAW_RDS +
+ blocknum*RDS_BLOCK]), (unsigned short *) tmp);
+ tmp[2] = blocknum; /* offset name */
+ tmp[2] |= blocknum << 3; /* received offset */
+ tmp[2] |= 0x40; /* corrected error(s) */
+
+ /* copy RDS block to internal buffer */
+ kfifo_in_locked(rds_buf, tmp, 3, &radio->buf_lock[TAVARUA_BUF_RAW_RDS]);
+ }
+ /* wake up read queue */
+ if (kfifo_len(rds_buf))
+ wake_up_interruptible(&radio->read_queue);
+
+}
+
+/*=============================================================================
+FUNCTION: request_read_xfr
+=============================================================================*/
+/**
+ This function sets the desired MODE in the XFRCTRL register and also sets the
+ CTRL field to read.
+ This is an asynchronous way of reading the XFR registers. Client would request
+ by setting the desired mode in the XFRCTRL register and then would initiate
+ the actual data register read by calling copy_from_xfr up on SOC signals
+ success.
+
+ NOTE:
+
+ The Data Transfer (XFR) registers are used to pass various data and
+ configuration parameters between the Core and host processor.
+
+ To read from the XFR registers, the host processor must set the desired MODE
+ in the XFRCTRL register and set the CTRL field to read. The Core will then
+ populate the XFRDAT0 - XFRDAT15 registers with the defined mode bytes. The
+ Core will set the TRANSFER interrupt status bit and interrupt the host if the
+ TRANSFERCTRL interrupt control bit is set. The host can then extract the XFR
+ mode bytes once it detects that the Core has updated the registers.
+
+ @param radio: structure pointer passed by client.
+
+ @return Always returns 0.
+*/
+static int request_read_xfr(struct tavarua_device *radio,
+ enum tavarua_xfr_ctrl_t mode){
+
+ tavarua_write_register(radio, XFRCTRL, mode);
+ msleep(TAVARUA_DELAY);
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: copy_from_xfr
+=============================================================================*/
+/**
+ This function is used to read XFR mode bytes once it detects that the Core
+ has updated the registers. It also updates XFR regs to the appropriate
+ internal buffer n bytes.
+
+ NOTE:
+
+ This function should be used in conjuction with request_read_xfr. Refer
+ request_read_xfr for XFR mode transaction details.
+
+ @param radio: structure pointer passed by client.
+ @param buf_type: Index into RDS/Radio event buffer to use.
+ @param len: num of bytes.
+
+ @return Always returns 0.
+*/
+static int copy_from_xfr(struct tavarua_device *radio,
+ enum tavarua_buf_t buf_type, unsigned int n){
+
+ struct kfifo *data_fifo = &radio->data_buf[buf_type];
+ unsigned char *xfr_regs = &radio->registers[XFRCTRL+1];
+ kfifo_in_locked(data_fifo, xfr_regs, n, &radio->buf_lock[buf_type]);
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: write_to_xfr
+=============================================================================*/
+/**
+ This function sets the desired MODE in the XFRCTRL register and it also sets
+ the CTRL field and data to write.
+ This also writes all the XFRDATx registers with the desired input buffer.
+
+ NOTE:
+
+ The Data Transfer (XFR) registers are used to pass various data and
+ configuration parameters between the Core and host processor.
+
+ To write data to the Core, the host processor updates XFRDAT0 - XFRDAT15 with
+ the appropriate mode bytes. The host processor must then set the desired MODE
+ in the XFRCTRL register and set the CTRL field to write. The core will detect
+ that the XFRCTRL register was written to and will read the XFR mode bytes.
+ After reading all the mode bytes, the Core will set the TRANSFER interrupt
+ status bit and interrupt the host if the TRANSFERCTRL interrupt control bit
+ is set.
+
+ @param radio: structure pointer passed by client.
+ @param mode: XFR mode to write in XFRCTRL register.
+ @param buf: buffer to be written to the registers.
+ @param len: num of bytes.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int write_to_xfr(struct tavarua_device *radio, unsigned char mode,
+ char *buf, int len)
+{
+ char buffer[len+1];
+ memcpy(buffer+1, buf, len);
+ /* buffer[0] corresponds to XFRCTRL register
+ set the CTRL bit to 1 for write mode
+ */
+ buffer[0] = ((1<<7) | mode);
+ return tavarua_write_registers(radio, XFRCTRL, buffer, sizeof(buffer));
+}
+
+/*=============================================================================
+FUNCTION: xfr_intf_own
+=============================================================================*/
+/**
+ This function is used to check if there is any pending XFR mode operation.
+ If yes, wait for it to complete, else update the flag to indicate XFR
+ operation is in progress
+
+ @param radio: structure pointer passed by client.
+
+ @return 0 on success.
+ -ETIME on timeout.
+*/
+static int xfr_intf_own(struct tavarua_device *radio)
+{
+
+ mutex_lock(&radio->lock);
+ if (radio->xfr_in_progress) {
+ radio->pending_xfrs[TAVARUA_XFR_SYNC] = 1;
+ mutex_unlock(&radio->lock);
+ if (!wait_for_completion_timeout(&radio->sync_xfr_start,
+ msecs_to_jiffies(wait_timeout)))
+ return -ETIME;
+ } else {
+ FMDBG("gained ownership of xfr\n");
+ radio->xfr_in_progress = 1;
+ mutex_unlock(&radio->lock);
+ }
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: sync_read_xfr
+=============================================================================*/
+/**
+ This function is used to do synchronous XFR read operation.
+
+ @param radio: structure pointer passed by client.
+ @param xfr_type: XFR mode to write in XFRCTRL register.
+ @param buf: buffer to be read from the core.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int sync_read_xfr(struct tavarua_device *radio,
+ enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf)
+{
+ int retval;
+ retval = xfr_intf_own(radio);
+ if (retval < 0)
+ return retval;
+ retval = tavarua_write_register(radio, XFRCTRL, xfr_type);
+
+ if (retval >= 0) {
+ /* Wait for interrupt i.e. complete
+ (&radio->sync_req_done); call */
+ if (!wait_for_completion_timeout(&radio->sync_req_done,
+ msecs_to_jiffies(wait_timeout)) || (retval < 0)) {
+ retval = -ETIME;
+ } else {
+ memcpy(buf, radio->sync_xfr_regs, XFR_REG_NUM);
+ }
+ }
+ radio->xfr_in_progress = 0;
+ start_pending_xfr(radio);
+ FMDBG("%s: %d\n", __func__, retval);
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: sync_write_xfr
+=============================================================================*/
+/**
+ This function is used to do synchronous XFR write operation.
+
+ @param radio: structure pointer passed by client.
+ @param xfr_type: XFR mode to write in XFRCTRL register.
+ @param buf: buffer to be written to the core.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int sync_write_xfr(struct tavarua_device *radio,
+ enum tavarua_xfr_ctrl_t xfr_type, unsigned char *buf)
+{
+ int retval;
+ retval = xfr_intf_own(radio);
+ if (retval < 0)
+ return retval;
+ retval = write_to_xfr(radio, xfr_type, buf, XFR_REG_NUM);
+
+ if (retval >= 0) {
+ /* Wait for interrupt i.e. complete
+ (&radio->sync_req_done); call */
+ if (!wait_for_completion_timeout(&radio->sync_req_done,
+ msecs_to_jiffies(wait_timeout)) || (retval < 0)) {
+ FMDBG("Write xfr timeout");
+ }
+ }
+ radio->xfr_in_progress = 0;
+ start_pending_xfr(radio);
+ FMDBG("%s: %d\n", __func__, retval);
+ return retval;
+}
+
+
+/*=============================================================================
+FUNCTION: start_pending_xfr
+=============================================================================*/
+/**
+ This function checks if their are any pending xfr interrupts and if
+ the interrupts are either RDS PS, RDS RT, RDS AF, SCANNEXT, SEARCH or SYNC
+ then initiates corresponding read operation. Preference is given to RAW RDS
+ data (SYNC) over processed data (PS, RT, AF, etc) from core.
+
+ @param radio: structure pointer passed by client.
+
+ @return None.
+*/
+static void start_pending_xfr(struct tavarua_device *radio)
+{
+ int i;
+ enum tavarua_xfr_t xfr;
+ for (i = 0; i < TAVARUA_XFR_MAX; i++) {
+ if (radio->pending_xfrs[i]) {
+ radio->xfr_in_progress = 1;
+ xfr = (enum tavarua_xfr_t)i;
+ switch (xfr) {
+ /* priority given to synchronous xfrs */
+ case TAVARUA_XFR_SYNC:
+ complete(&radio->sync_xfr_start);
+ break;
+ /* asynchrnous xfrs */
+ case TAVARUA_XFR_SRCH_LIST:
+ request_read_xfr(radio, RX_STATIONS_0);
+ break;
+ case TAVARUA_XFR_RT_RDS:
+ request_read_xfr(radio, RDS_RT_0);
+ break;
+ case TAVARUA_XFR_PS_RDS:
+ request_read_xfr(radio, RDS_PS_0);
+ break;
+ case TAVARUA_XFR_AF_LIST:
+ request_read_xfr(radio, RDS_AF_0);
+ break;
+ default:
+ FMDERR("%s: Unsupported XFR %d\n",
+ __func__, xfr);
+ }
+ radio->pending_xfrs[i] = 0;
+ FMDBG("resurrect xfr %d\n", i);
+ }
+ }
+ return;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_q_event
+=============================================================================*/
+/**
+ This function is called to queue an event for user.
+
+ NOTE:
+ Applications call the VIDIOC_QBUF ioctl to enqueue an empty (capturing) or
+ filled (output) buffer in the driver's incoming queue.
+
+ Pleaes refer tavarua_probe where we register different ioctl's for FM.
+
+ @param radio: structure pointer passed by client.
+ @param event: event to be queued.
+
+ @return None.
+*/
+static void tavarua_q_event(struct tavarua_device *radio,
+ enum tavarua_evt_t event)
+{
+
+ struct kfifo *data_b = &radio->data_buf[TAVARUA_BUF_EVENTS];
+ unsigned char evt = event;
+ FMDBG("updating event_q with event %x\n", event);
+ if (kfifo_in_locked(data_b, &evt, 1, &radio->buf_lock[TAVARUA_BUF_EVENTS]))
+ wake_up_interruptible(&radio->event_queue);
+}
+
+/*=============================================================================
+FUNCTION: tavarua_start_xfr
+=============================================================================*/
+/**
+ This function is called to process interrupts which require multiple XFR
+ operations (RDS search, RDS PS, RDS RT, etc). if any XFR operation is
+ already in progress we store information about pending interrupt, which
+ will be processed in future when current pending operation is done.
+
+ @param radio: structure pointer passed by client.
+ @param pending_id: XFR operation (which requires multiple XFR operations in
+ steps) to start.
+ @param xfr_id: XFR mode to write in XFRCTRL register.
+
+ @return None.
+*/
+static void tavarua_start_xfr(struct tavarua_device *radio,
+ enum tavarua_xfr_t pending_id, enum tavarua_xfr_ctrl_t xfr_id)
+{
+ if (radio->xfr_in_progress)
+ radio->pending_xfrs[pending_id] = 1;
+ else {
+ radio->xfr_in_progress = 1;
+ request_read_xfr(radio, xfr_id);
+ }
+}
+
+/*=============================================================================
+FUNCTION: tavarua_handle_interrupts
+=============================================================================*/
+/**
+ This function processes the interrupts.
+
+ NOTE:
+ tavarua_q_event is used to queue events in App buffer. i.e. App calls the
+ VIDIOC_QBUF ioctl to enqueue an empty (capturing) buffer, which is filled
+ by tavarua_q_event call.
+
+ Any async event that requires multiple steps, i.e. search, RT, PS, etc is
+ handled one at a time. (We preserve other interrupts when processing one).
+ Sync interrupts are given priority.
+
+ @param radio: structure pointer passed by client.
+
+ @return None.
+*/
+static void tavarua_handle_interrupts(struct tavarua_device *radio)
+{
+ int i;
+ int retval;
+ unsigned char xfr_status;
+ if (!radio->handle_irq) {
+ FMDBG("IRQ happend, but I wont handle it\n");
+ return;
+ }
+ mutex_lock(&radio->lock);
+ tavarua_read_registers(radio, STATUS_REG1, STATUS_REG_NUM);
+
+ FMDBG("INTSTAT1 <%x>\n", radio->registers[STATUS_REG1]);
+ FMDBG("INTSTAT2 <%x>\n", radio->registers[STATUS_REG2]);
+ FMDBG("INTSTAT3 <%x>\n", radio->registers[STATUS_REG3]);
+
+ if (radio->registers[STATUS_REG1] & READY) {
+ complete(&radio->sync_req_done);
+ tavarua_q_event(radio, TAVARUA_EVT_RADIO_READY);
+ }
+
+ /* Tune completed */
+ if (radio->registers[STATUS_REG1] & TUNE) {
+ if (radio->tune_req) {
+ complete(&radio->sync_req_done);
+ radio->tune_req = 0;
+ }
+ tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC);
+ if (radio->srch_params.get_list) {
+ tavarua_start_xfr(radio, TAVARUA_XFR_SRCH_LIST,
+ RX_STATIONS_0);
+ }
+ radio->srch_params.get_list = 0;
+ radio->xfr_in_progress = 0;
+ radio->xfr_bytes_left = 0;
+ for (i = 0; i < TAVARUA_BUF_MAX; i++) {
+ if (i >= TAVARUA_BUF_RT_RDS)
+ kfifo_reset(&radio->data_buf[i]);
+ }
+ for (i = 0; i < TAVARUA_XFR_MAX; i++) {
+ if (i >= TAVARUA_XFR_RT_RDS)
+ radio->pending_xfrs[i] = 0;
+ }
+ retval = tavarua_read_registers(radio, TUNECTRL, 1);
+ /* send to user station parameters */
+ if (retval > -1) {
+ /* Signal strength */
+ if (!(radio->registers[TUNECTRL] & SIGSTATE))
+ tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH);
+ else
+ tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH);
+ /* mono/stereo */
+ if ((radio->registers[TUNECTRL] & MOSTSTATE))
+ tavarua_q_event(radio, TAVARUA_EVT_STEREO);
+ else
+ tavarua_q_event(radio, TAVARUA_EVT_MONO);
+ /* is RDS available */
+ if ((radio->registers[TUNECTRL] & RDSSYNC))
+ tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL);
+ else
+ tavarua_q_event(radio,
+ TAVARUA_EVT_RDS_NOT_AVAIL);
+ }
+
+ } else {
+ if (radio->tune_req) {
+ FMDERR("Tune INT is pending\n");
+ mutex_unlock(&radio->lock);
+ return;
+ }
+ }
+ /* Search completed (read FREQ) */
+ if (radio->registers[STATUS_REG1] & SEARCH)
+ tavarua_q_event(radio, TAVARUA_EVT_SEEK_COMPLETE);
+
+ /* Scanning for next station */
+ if (radio->registers[STATUS_REG1] & SCANNEXT)
+ tavarua_q_event(radio, TAVARUA_EVT_SCAN_NEXT);
+
+ /* Signal indicator change (read SIGSTATE) */
+ if (radio->registers[STATUS_REG1] & SIGNAL) {
+ retval = tavarua_read_registers(radio, TUNECTRL, 1);
+ if (retval > -1) {
+ if (!(radio->registers[TUNECTRL] & SIGSTATE))
+ tavarua_q_event(radio, TAVARUA_EVT_BELOW_TH);
+ else
+ tavarua_q_event(radio, TAVARUA_EVT_ABOVE_TH);
+ }
+ }
+
+ /* RDS synchronization state change (read RDSSYNC) */
+ if (radio->registers[STATUS_REG1] & SYNC) {
+ retval = tavarua_read_registers(radio, TUNECTRL, 1);
+ if (retval > -1) {
+ if ((radio->registers[TUNECTRL] & RDSSYNC))
+ tavarua_q_event(radio, TAVARUA_EVT_RDS_AVAIL);
+ else
+ tavarua_q_event(radio,
+ TAVARUA_EVT_RDS_NOT_AVAIL);
+ }
+ }
+
+ /* Audio Control indicator (read AUDIOIND) */
+ if (radio->registers[STATUS_REG1] & AUDIO) {
+ retval = tavarua_read_registers(radio, AUDIOIND, 1);
+ if (retval > -1) {
+ if ((radio->registers[AUDIOIND] & 0x01))
+ tavarua_q_event(radio, TAVARUA_EVT_STEREO);
+ else
+ tavarua_q_event(radio, TAVARUA_EVT_MONO);
+ }
+ }
+
+ /* interrupt register 2 */
+
+ /* New unread RDS data group available */
+ if (radio->registers[STATUS_REG2] & RDSDAT) {
+ FMDBG("Raw RDS Available\n");
+ tavarua_rds_read(radio);
+ tavarua_q_event(radio, TAVARUA_EVT_NEW_RAW_RDS);
+ }
+
+ /* New RDS Program Service Table available */
+ if (radio->registers[STATUS_REG2] & RDSPS) {
+ FMDBG("New PS RDS\n");
+ tavarua_start_xfr(radio, TAVARUA_XFR_PS_RDS, RDS_PS_0);
+ }
+
+ /* New RDS Radio Text available */
+ if (radio->registers[STATUS_REG2] & RDSRT) {
+ FMDBG("New RT RDS\n");
+ tavarua_start_xfr(radio, TAVARUA_XFR_RT_RDS, RDS_RT_0);
+ }
+
+ /* New RDS Radio Text available */
+ if (radio->registers[STATUS_REG2] & RDSAF) {
+ FMDBG("New AF RDS\n");
+ tavarua_start_xfr(radio, TAVARUA_XFR_AF_LIST, RDS_AF_0);
+ }
+ /* Trasmitter an RDS Group */
+ if (radio->registers[STATUS_REG2] & TXRDSDAT) {
+ FMDBG("New TXRDSDAT\n");
+ tavarua_q_event(radio, TAVARUA_EVT_TXRDSDAT);
+ }
+
+ /* Complete RDS buffer is available for transmission */
+ if (radio->registers[STATUS_REG2] & TXRDSDONE) {
+ FMDBG("New TXRDSDAT\n");
+ tavarua_q_event(radio, TAVARUA_EVT_TXRDSDONE);
+ }
+ /* interrupt register 3 */
+
+ /* Data transfer (XFR) completed */
+ if (radio->registers[STATUS_REG3] & TRANSFER) {
+ FMDBG("XFR Interrupt\n");
+ tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1);
+ FMDBG("XFRCTRL IS: %x\n", radio->registers[XFRCTRL]);
+ xfr_status = radio->registers[XFRCTRL];
+ switch (xfr_status) {
+ case RDS_PS_0:
+ FMDBG("PS Header\n");
+ copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, 5);
+ radio->xfr_bytes_left = (radio->registers[XFRCTRL+1] &
+ 0x0F) * 8;
+ FMDBG("PS RDS Length: %d\n", radio->xfr_bytes_left);
+ if ((radio->xfr_bytes_left > 0) &&
+ (radio->xfr_bytes_left < 97))
+ request_read_xfr(radio, RDS_PS_1);
+ else
+ radio->xfr_in_progress = 0;
+ break;
+ case RDS_PS_1:
+ case RDS_PS_2:
+ case RDS_PS_3:
+ case RDS_PS_4:
+ case RDS_PS_5:
+ case RDS_PS_6:
+ FMDBG("PS Data\n");
+ copy_from_xfr(radio, TAVARUA_BUF_PS_RDS, XFR_REG_NUM);
+ radio->xfr_bytes_left -= XFR_REG_NUM;
+ if (radio->xfr_bytes_left > 0) {
+ if ((xfr_status + 1) > RDS_PS_6)
+ request_read_xfr(radio, RDS_PS_6);
+ else
+ request_read_xfr(radio, xfr_status+1);
+ } else {
+ radio->xfr_in_progress = 0;
+ tavarua_q_event(radio, TAVARUA_EVT_NEW_PS_RDS);
+ }
+ break;
+ case RDS_RT_0:
+ FMDBG("RT Header\n");
+ copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, 5);
+ radio->xfr_bytes_left = radio->registers[XFRCTRL+1]
+ & 0x7F;
+ FMDBG("RT RDS Length: %d\n", radio->xfr_bytes_left);
+ /*RT_1 to RT_4 16 byte registers so 64 bytes */
+ if ((radio->xfr_bytes_left > 0)
+ && (radio->xfr_bytes_left < 65))
+ request_read_xfr(radio, RDS_RT_1);
+ break;
+ case RDS_RT_1:
+ case RDS_RT_2:
+ case RDS_RT_3:
+ case RDS_RT_4:
+ FMDBG("xfr interrupt RT data\n");
+ copy_from_xfr(radio, TAVARUA_BUF_RT_RDS, XFR_REG_NUM);
+ radio->xfr_bytes_left -= XFR_REG_NUM;
+ if (radio->xfr_bytes_left > 0)
+ request_read_xfr(radio, xfr_status+1);
+ else {
+ radio->xfr_in_progress = 0;
+ tavarua_q_event(radio, TAVARUA_EVT_NEW_RT_RDS);
+ }
+ break;
+ case RDS_AF_0:
+ copy_from_xfr(radio, TAVARUA_BUF_AF_LIST,
+ XFR_REG_NUM);
+ radio->xfr_bytes_left = radio->registers[XFRCTRL+5]-11;
+ if (radio->xfr_bytes_left > 0)
+ request_read_xfr(radio, RDS_AF_1);
+ else
+ radio->xfr_in_progress = 0;
+ break;
+ case RDS_AF_1:
+ copy_from_xfr(radio, TAVARUA_BUF_AF_LIST,
+ radio->xfr_bytes_left);
+ tavarua_q_event(radio, TAVARUA_EVT_NEW_AF_LIST);
+ radio->xfr_in_progress = 0;
+ break;
+ case RX_CONFIG:
+ case RADIO_CONFIG:
+ case RDS_CONFIG:
+ memcpy(radio->sync_xfr_regs,
+ &radio->registers[XFRCTRL+1], XFR_REG_NUM);
+ complete(&radio->sync_req_done);
+ break;
+ case RX_STATIONS_0:
+ FMDBG("Search list has %d stations\n",
+ radio->registers[XFRCTRL+1]);
+ radio->xfr_bytes_left = radio->registers[XFRCTRL+1]*2;
+ if (radio->xfr_bytes_left > 14) {
+ copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
+ XFR_REG_NUM);
+ request_read_xfr(radio, RX_STATIONS_1);
+ } else if (radio->xfr_bytes_left) {
+ FMDBG("In else RX_STATIONS_0\n");
+ copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
+ radio->xfr_bytes_left+1);
+ tavarua_q_event(radio,
+ TAVARUA_EVT_NEW_SRCH_LIST);
+ radio->xfr_in_progress = 0;
+ }
+ break;
+ case RX_STATIONS_1:
+ FMDBG("In RX_STATIONS_1");
+ copy_from_xfr(radio, TAVARUA_BUF_SRCH_LIST,
+ radio->xfr_bytes_left);
+ tavarua_q_event(radio, TAVARUA_EVT_NEW_SRCH_LIST);
+ radio->xfr_in_progress = 0;
+ break;
+ case PHY_TXGAIN:
+ FMDBG("read PHY_TXGAIN is successful");
+ complete(&radio->sync_req_done);
+ break;
+ case (0x80 | RX_CONFIG):
+ case (0x80 | RADIO_CONFIG):
+ case (0x80 | RDS_CONFIG):
+ case (0x80 | INT_CTRL):
+ complete(&radio->sync_req_done);
+ break;
+ case (0x80 | RDS_RT_0):
+ FMDBG("RT Header Sent\n");
+ complete(&radio->sync_req_done);
+ break;
+ case (0x80 | RDS_RT_1):
+ case (0x80 | RDS_RT_2):
+ case (0x80 | RDS_RT_3):
+ case (0x80 | RDS_RT_4):
+ FMDBG("xfr interrupt RT data Sent\n");
+ complete(&radio->sync_req_done);
+ break;
+ /*TX Specific transfer */
+ case (0x80 | RDS_PS_0):
+ FMDBG("PS Header Sent\n");
+ complete(&radio->sync_req_done);
+ break;
+ case (0x80 | RDS_PS_1):
+ case (0x80 | RDS_PS_2):
+ case (0x80 | RDS_PS_3):
+ case (0x80 | RDS_PS_4):
+ case (0x80 | RDS_PS_5):
+ case (0x80 | RDS_PS_6):
+ FMDBG("xfr interrupt PS data Sent\n");
+ complete(&radio->sync_req_done);
+ break;
+ case (0x80 | PHY_TXGAIN):
+ FMDBG("write PHY_TXGAIN is successful");
+ complete(&radio->sync_req_done);
+ break;
+ default:
+ FMDERR("UNKNOWN XFR = %d\n", xfr_status);
+ }
+ if (!radio->xfr_in_progress)
+ start_pending_xfr(radio);
+
+ }
+
+ /* Error occurred. Read ERRCODE to determine cause */
+ if (radio->registers[STATUS_REG3] & ERROR) {
+#ifdef FM_DEBUG
+ unsigned char xfr_buf[XFR_REG_NUM];
+ int retval = sync_read_xfr(radio, ERROR_CODE, xfr_buf);
+ FMDBG("retval of ERROR_CODE read : %d\n", retval);
+#endif
+ FMDERR("ERROR STATE\n");
+ }
+
+ mutex_unlock(&radio->lock);
+ FMDBG("Work is done\n");
+
+}
+
+/*=============================================================================
+FUNCTION: read_int_stat
+=============================================================================*/
+/**
+ This function is scheduled whenever there is an interrupt pending in interrupt
+ queue. i.e. kfmradio.
+
+ Whenever there is a GPIO interrupt, a delayed work will be queued in to the
+ 'kfmradio' work queue. Upon execution of this work in the queue, a a call
+ to read_int_stat function will be made , which would in turn handle the
+ interrupts by reading the INTSTATx registers.
+ NOTE:
+ Tasks to be run out of a workqueue need to be packaged in a struct
+ work_struct structure.
+
+ @param work: work_struct structure.
+
+ @return None.
+*/
+static void read_int_stat(struct work_struct *work)
+{
+ struct tavarua_device *radio = container_of(work,
+ struct tavarua_device, work.work);
+ tavarua_handle_interrupts(radio);
+}
+
+/*************************************************************************
+ * irq helper functions
+ ************************************************************************/
+
+/*=============================================================================
+FUNCTION: tavarua_request_irq
+=============================================================================*/
+/**
+ This function is called to acquire a FM GPIO and enable FM interrupts.
+
+ @param radio: structure pointer passed by client.
+
+ @return 0 if success else otherwise.
+*/
+static int tavarua_request_irq(struct tavarua_device *radio)
+{
+ int retval;
+ int irq = radio->pdata->irq;
+ if (radio == NULL)
+ return -EINVAL;
+
+ /* A workqueue created with create_workqueue() will have one worker thread
+ * for each CPU on the system; create_singlethread_workqueue(), instead,
+ * creates a workqueue with a single worker process. The name of the queue
+ * is limited to ten characters; it is only used for generating the "command"
+ * for the kernel thread(s) (which can be seen in ps or top).
+ */
+ radio->wqueue = create_singlethread_workqueue("kfmradio");
+ if (!radio->wqueue)
+ return -ENOMEM;
+ /* allocate an interrupt line */
+ /* On success, request_irq() returns 0 if everything goes as
+ planned. Your interrupt handler will start receiving its
+ interrupts immediately. On failure, request_irq()
+ returns:
+ -EINVAL
+ The IRQ number you requested was either
+ invalid or reserved, or your passed a NULL
+ pointer for the handler() parameter.
+
+ -EBUSY The IRQ you requested is already being
+ handled, and the IRQ cannot be shared.
+
+ -ENXIO The m68k returns this value for an invalid
+ IRQ number.
+ */
+ /* Use request_any_context_irq, So that it might work for nested or
+ nested interrupts. in MSM8x60, FM is connected to PMIC GPIO and it
+ is a nested interrupt*/
+ retval = request_any_context_irq(irq, tavarua_isr,
+ IRQ_TYPE_EDGE_FALLING, "fm interrupt", radio);
+ if (retval < 0) {
+ FMDERR("Couldn't acquire FM gpio %d\n", irq);
+ return retval;
+ } else {
+ FMDBG("FM GPIO %d registered\n", irq);
+ }
+ retval = enable_irq_wake(irq);
+ if (retval < 0) {
+ FMDERR("Could not enable FM interrupt\n ");
+ free_irq(irq , radio);
+ }
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_disable_irq
+=============================================================================*/
+/**
+ This function is called to disable FM irq and free up FM interrupt handling
+ resources.
+
+ @param radio: structure pointer passed by client.
+
+ @return 0 if success else otherwise.
+*/
+static int tavarua_disable_irq(struct tavarua_device *radio)
+{
+ int irq;
+ if (!radio)
+ return -EINVAL;
+ irq = radio->pdata->irq;
+ disable_irq_wake(irq);
+ cancel_delayed_work_sync(&radio->work);
+ flush_workqueue(radio->wqueue);
+ free_irq(irq, radio);
+ destroy_workqueue(radio->wqueue);
+ return 0;
+}
+
+/*************************************************************************
+ * fops/IOCTL helper functions
+ ************************************************************************/
+
+/*=============================================================================
+FUNCTION: tavarua_search
+=============================================================================*/
+/**
+ This interface sets the search control features.
+
+ @param radio: structure pointer passed by client.
+ @param on: The value of a control.
+ @param dir: FM search direction.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_search(struct tavarua_device *radio, int on, int dir)
+{
+ enum search_t srch = radio->registers[SRCHCTRL] & SRCH_MODE;
+
+ FMDBG("In tavarua_search\n");
+ if (on) {
+ radio->registers[SRCHRDS1] = 0x00;
+ radio->registers[SRCHRDS2] = 0x00;
+ /* Set freq band */
+ switch (srch) {
+ case SCAN_FOR_STRONG:
+ case SCAN_FOR_WEAK:
+ radio->srch_params.get_list = 1;
+ radio->registers[SRCHRDS2] =
+ radio->srch_params.preset_num;
+ break;
+ case RDS_SEEK_PTY:
+ case RDS_SCAN_PTY:
+ radio->registers[SRCHRDS2] =
+ radio->srch_params.srch_pty;
+ break;
+ case RDS_SEEK_PI:
+ radio->registers[SRCHRDS1] =
+ (radio->srch_params.srch_pi & 0xFF00) >> 8;
+ radio->registers[SRCHRDS2] =
+ (radio->srch_params.srch_pi & 0x00FF);
+ break;
+ default:
+ break;
+ }
+ radio->registers[SRCHCTRL] |= SRCH_ON;
+ } else {
+ radio->registers[SRCHCTRL] &= ~SRCH_ON;
+ radio->srch_params.get_list = 0;
+ }
+ radio->registers[SRCHCTRL] = (dir << 3) |
+ (radio->registers[SRCHCTRL] & 0xF7);
+
+ FMDBG("SRCHCTRL <%x>\n", radio->registers[SRCHCTRL]);
+ FMDBG("Search Started\n");
+ return tavarua_write_registers(radio, SRCHRDS1,
+ &radio->registers[SRCHRDS1], 3);
+}
+
+/*=============================================================================
+FUNCTION: tavarua_set_region
+=============================================================================*/
+/**
+ This interface configures the FM radio.
+
+ @param radio: structure pointer passed by client.
+ @param req_region: FM band types. These types defines the FM band minimum and
+ maximum frequencies in the FM band.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_set_region(struct tavarua_device *radio,
+ int req_region)
+{
+ int retval = 0;
+ unsigned char xfr_buf[XFR_REG_NUM];
+ unsigned char value;
+ unsigned int spacing = 0.100 * FREQ_MUL;
+ unsigned int band_low, band_high;
+ unsigned int low_band_limit = 76.0 * FREQ_MUL;
+ enum tavarua_region_t region = req_region;
+
+ /* Set freq band */
+ switch (region) {
+ case TAVARUA_REGION_US:
+ case TAVARUA_REGION_EU:
+ case TAVARUA_REGION_JAPAN_WIDE:
+ SET_REG_FIELD(radio->registers[RDCTRL], 0,
+ RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);
+ break;
+ case TAVARUA_REGION_JAPAN:
+ SET_REG_FIELD(radio->registers[RDCTRL], 1,
+ RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);
+ break;
+ default:
+ retval = sync_read_xfr(radio, RADIO_CONFIG, xfr_buf);
+ if (retval < 0) {
+ FMDERR("failed to get RADIO_CONFIG\n");
+ return retval;
+ }
+ band_low = (radio->region_params.band_low -
+ low_band_limit) / spacing;
+ band_high = (radio->region_params.band_high -
+ low_band_limit) / spacing;
+ FMDBG("low_band: %x, high_band: %x\n", band_low, band_high);
+ xfr_buf[0] = band_low >> 8;
+ xfr_buf[1] = band_low & 0xFF;
+ xfr_buf[2] = band_high >> 8;
+ xfr_buf[3] = band_high & 0xFF;
+ retval = sync_write_xfr(radio, RADIO_CONFIG, xfr_buf);
+ if (retval < 0) {
+ FMDERR("Could not set regional settings\n");
+ return retval;
+ }
+ break;
+ }
+
+ /* Set channel spacing */
+ switch (region) {
+ case TAVARUA_REGION_US:
+ case TAVARUA_REGION_EU:
+ value = 0;
+ break;
+ case TAVARUA_REGION_JAPAN:
+ value = 1;
+ break;
+ case TAVARUA_REGION_JAPAN_WIDE:
+ value = 2;
+ break;
+ default:
+ value = radio->region_params.spacing;
+ }
+
+ SET_REG_FIELD(radio->registers[RDCTRL], value,
+ RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK);
+
+ /* Set De-emphasis and soft band range*/
+ switch (region) {
+ case TAVARUA_REGION_US:
+ case TAVARUA_REGION_JAPAN:
+ case TAVARUA_REGION_JAPAN_WIDE:
+ value = 0;
+ break;
+ case TAVARUA_REGION_EU:
+ value = 1;
+ break;
+ default:
+ value = radio->region_params.emphasis;
+ }
+
+ SET_REG_FIELD(radio->registers[RDCTRL], value,
+ RDCTRL_DEEMPHASIS_OFFSET, RDCTRL_DEEMPHASIS_MASK);
+
+ /* set RDS standard */
+ switch (region) {
+ default:
+ value = radio->region_params.rds_std;
+ break;
+ case TAVARUA_REGION_US:
+ value = 0;
+ break;
+ case TAVARUA_REGION_EU:
+ value = 1;
+ break;
+ }
+ SET_REG_FIELD(radio->registers[RDSCTRL], value,
+ RDSCTRL_STANDARD_OFFSET, RDSCTRL_STANDARD_MASK);
+
+ FMDBG("RDSCTRLL %x\n", radio->registers[RDSCTRL]);
+ retval = tavarua_write_register(radio, RDSCTRL,
+ radio->registers[RDSCTRL]);
+ if (retval < 0)
+ return retval;
+
+ FMDBG("RDCTRL: %x\n", radio->registers[RDCTRL]);
+ retval = tavarua_write_register(radio, RDCTRL,
+ radio->registers[RDCTRL]);
+ if (retval < 0) {
+ FMDERR("Could not set region in rdctrl\n");
+ return retval;
+ }
+
+ /* setting soft band */
+ switch (region) {
+ case TAVARUA_REGION_US:
+ case TAVARUA_REGION_EU:
+ radio->region_params.band_low = 87.5 * FREQ_MUL;
+ radio->region_params.band_high = 108 * FREQ_MUL;
+ break;
+ case TAVARUA_REGION_JAPAN:
+ radio->region_params.band_low = 76 * FREQ_MUL;
+ radio->region_params.band_high = 90 * FREQ_MUL;
+ break;
+ case TAVARUA_REGION_JAPAN_WIDE:
+ radio->region_params.band_low = 90 * FREQ_MUL;
+ radio->region_params.band_high = 108 * FREQ_MUL;
+ break;
+ default:
+ break;
+ }
+ radio->region_params.region = region;
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_get_freq
+=============================================================================*/
+/**
+ This interface gets the current frequency.
+
+ @param radio: structure pointer passed by client.
+ @param freq: struct v4l2_frequency. This will be set to the resultant
+ frequency in units of 62.5 kHz on success.
+
+ NOTE:
+ To get the current tuner or modulator radio frequency applications set the
+ tuner field of a struct v4l2_frequency to the respective tuner or modulator
+ number (only input devices have tuners, only output devices have modulators),
+ zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a
+ pointer to this structure. The driver stores the current frequency in the
+ frequency field.
+
+ Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner or
+ struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set, in
+ units of 62.5 Hz.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_get_freq(struct tavarua_device *radio,
+ struct v4l2_frequency *freq)
+{
+ int retval;
+ unsigned short chan;
+ unsigned int band_bottom;
+ unsigned int spacing;
+ band_bottom = radio->region_params.band_low;
+ spacing = 0.100 * FREQ_MUL;
+ /* read channel */
+ retval = tavarua_read_registers(radio, FREQ, 2);
+ chan = radio->registers[FREQ];
+
+ /* Frequency (MHz) = 100 (kHz) x Channel + Bottom of Band (MHz) */
+ freq->frequency = spacing * chan + band_bottom;
+ if (radio->registers[TUNECTRL] & ADD_OFFSET)
+ freq->frequency += 800;
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_set_freq
+=============================================================================*/
+/**
+ This interface sets the current frequency.
+
+ @param radio: structure pointer passed by client.
+ @param freq: desired frequency sent by the client in 62.5 kHz units.
+
+ NOTE:
+ To change the current tuner or modulator radio frequency, applications
+ initialize the tuner, type and frequency fields, and the reserved array of a
+ struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer to
+ this structure. When the requested frequency is not possible the driver
+ assumes the closest possible value. However VIDIOC_S_FREQUENCY is a
+ write-only ioctl, it does not return the actual new frequency.
+
+ Tuning frequency is in units of 62.5 kHz, or if the struct v4l2_tuner
+ or struct v4l2_modulator capabilities flag V4L2_TUNER_CAP_LOW is set,
+ in units of 62.5 Hz.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_set_freq(struct tavarua_device *radio, unsigned int freq)
+{
+
+ unsigned int band_bottom;
+ unsigned char chan;
+ unsigned char cmd[] = {0x00, 0x00};
+ unsigned int spacing;
+ int retval;
+ band_bottom = radio->region_params.band_low;
+ spacing = 0.100 * FREQ_MUL;
+ if ((freq % 1600) == 800) {
+ cmd[1] = ADD_OFFSET;
+ freq -= 800;
+ }
+ /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / 100 (kHz) */
+ chan = (freq - band_bottom) / spacing;
+
+ cmd[0] = chan;
+ cmd[1] |= TUNE_STATION;
+ radio->tune_req = 1;
+ retval = tavarua_write_registers(radio, FREQ, cmd, 2);
+ if (retval < 0)
+ radio->tune_req = 0;
+ return retval;
+
+}
+
+/**************************************************************************
+ * File Operations Interface
+ *************************************************************************/
+
+/*=============================================================================
+FUNCTION: tavarua_fops_read
+=============================================================================*/
+/**
+ This function is called when a process, which already opened the dev file,
+ attempts to read from it.
+
+ In case of tavarua driver, it is called to read RDS data.
+
+ @param file: file descriptor.
+ @param buf: The buffer to fill with data.
+ @param count: The length of the buffer in bytes.
+ @param ppos: Our offset in the file.
+
+ @return The number of bytes put into the buffer on sucess.
+ -EFAULT if there is no access to user buffer
+*/
+static ssize_t tavarua_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ struct kfifo *rds_buf = &radio->data_buf[TAVARUA_BUF_RAW_RDS];
+
+ /* block if no new data available */
+ while (!kfifo_len(rds_buf)) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+ if (wait_event_interruptible(radio->read_queue,
+ kfifo_len(rds_buf)) < 0)
+ return -EINTR;
+ }
+
+ /* calculate block count from byte count */
+ count /= BYTES_PER_BLOCK;
+
+
+ /* check if we can write to the user buffer */
+ if (!access_ok(VERIFY_WRITE, buf, count*BYTES_PER_BLOCK))
+ return -EFAULT;
+
+ /* copy RDS block out of internal buffer and to user buffer */
+ return kfifo_out_locked(rds_buf, buf, count*BYTES_PER_BLOCK,
+ &radio->buf_lock[TAVARUA_BUF_RAW_RDS]);
+}
+
+/*=============================================================================
+FUNCTION: tavarua_fops_write
+=============================================================================*/
+/**
+ This function is called when a process, which already opened the dev file,
+ attempts to write to it.
+
+ In case of tavarua driver, it is called to write RDS data to host.
+
+ @param file: file descriptor.
+ @param buf: The buffer which has data to write.
+ @param count: The length of the buffer.
+ @param ppos: Our offset in the file.
+
+ @return The number of bytes written from the buffer.
+*/
+static ssize_t tavarua_fops_write(struct file *file, const char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval = 0;
+ int bytes_to_copy;
+ int bytes_copied = 0;
+ int bytes_left;
+ int chunk_index = 0;
+ unsigned char tx_data[XFR_REG_NUM];
+ /* Disable TX of this type first */
+ switch (radio->tx_mode) {
+ case TAVARUA_TX_RT:
+ bytes_left = min((int)count, MAX_RT_LENGTH);
+ tx_data[1] = 0;
+ break;
+ case TAVARUA_TX_PS:
+ bytes_left = min((int)count, MAX_PS_LENGTH);
+ tx_data[4] = 0;
+ break;
+ default:
+ FMDERR("%s: Unknown TX mode\n", __func__);
+ return -1;
+ }
+ retval = sync_write_xfr(radio, radio->tx_mode, tx_data);
+ if (retval < 0)
+ return retval;
+
+ /* send payload to FM hardware */
+ while (bytes_left) {
+ chunk_index++;
+ bytes_to_copy = min(bytes_left, XFR_REG_NUM);
+ if (copy_from_user(tx_data, data + bytes_copied, bytes_to_copy))
+ return -EFAULT;
+ retval = sync_write_xfr(radio, radio->tx_mode +
+ chunk_index, tx_data);
+ if (retval < 0)
+ return retval;
+
+ bytes_copied += bytes_to_copy;
+ bytes_left -= bytes_to_copy;
+ }
+
+ /* send the header */
+ switch (radio->tx_mode) {
+ case TAVARUA_TX_RT:
+ FMDBG("Writing RT header\n");
+ tx_data[0] = bytes_copied;
+ tx_data[1] = TX_ON | 0x03; /* on | PTY */
+ tx_data[2] = 0x12; /* PI high */
+ tx_data[3] = 0x34; /* PI low */
+ break;
+ case TAVARUA_TX_PS:
+ FMDBG("Writing PS header\n");
+ tx_data[0] = chunk_index;
+ tx_data[1] = 0x03; /* PTY */
+ tx_data[2] = 0x12; /* PI high */
+ tx_data[3] = 0x34; /* PI low */
+ tx_data[4] = TX_ON | 0x01;
+ break;
+ default:
+ FMDERR("%s: Unknown TX mode\n", __func__);
+ return -1;
+ }
+ retval = sync_write_xfr(radio, radio->tx_mode, tx_data);
+ if (retval < 0)
+ return retval;
+ FMDBG("done writing: %d\n", retval);
+ return bytes_copied;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_fops_open
+=============================================================================*/
+/**
+ This function is called when a process tries to open the device file, like
+ "cat /dev/mycharfile"
+
+ @param file: file descriptor.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_fops_open(struct file *file)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval = -ENODEV;
+ unsigned char value;
+ /* FM core bring up */
+ int i = 0;
+ char fm_ctl0_part1[] = { 0xCA, 0xCE, 0xD6 };
+ char fm_ctl1[] = { 0x03 };
+ char fm_ctl0_part2[] = { 0xB6, 0xB7 };
+ char buffer[] = {0x00, 0x48, 0x8A, 0x8E, 0x97, 0xB7};
+ int bahama_present = -ENODEV;
+
+ mutex_lock(&radio->lock);
+ if (radio->users) {
+ mutex_unlock(&radio->lock);
+ return -EBUSY;
+ } else {
+ radio->users++;
+ }
+ mutex_unlock(&radio->lock);
+
+ /* initial gpio pin config & Power up */
+ retval = radio->pdata->fm_setup(radio->pdata);
+ if (retval) {
+ printk(KERN_ERR "%s: failed config gpio & pmic\n", __func__);
+ goto open_err_setup;
+ }
+ if (radio->pdata->config_i2s_gpio != NULL) {
+ retval = radio->pdata->config_i2s_gpio(FM_I2S_ON);
+ if (retval) {
+ printk(KERN_ERR "%s: failed config gpio\n", __func__);
+ goto config_i2s_err;
+ }
+ }
+ /* enable irq */
+ retval = tavarua_request_irq(radio);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: failed to request irq\n", __func__);
+ goto open_err_req_irq;
+ }
+ /* call top level marimba interface here to enable FM core */
+ FMDBG("initializing SoC\n");
+
+ bahama_present = is_bahama();
+
+ if (bahama_present == -ENODEV)
+ return -ENODEV;
+
+ if (bahama_present)
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ else
+ radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA;
+
+ value = FM_ENABLE;
+ retval = marimba_write_bit_mask(radio->marimba,
+ MARIMBA_XO_BUFF_CNTRL, &value, 1, value);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n",
+ __func__);
+ goto open_err_all;
+ }
+
+
+ /* Bring up FM core */
+ if (bahama_present) {
+
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ /* Read the Bahama version*/
+ retval = marimba_read_bit_mask(radio->marimba,
+ 0x00, &bahama_version, 1, 0x1F);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: version read failed",
+ __func__);
+ goto open_err_all;
+ }
+ /* Check for Bahama V2 variant*/
+ if (bahama_version == 0x09) {
+
+ /* In case of Bahama v2, forcefully enable the
+ * internal analog and digital voltage controllers
+ */
+ value = 0x06;
+ /* value itself used as mask in these writes*/
+ retval = marimba_write_bit_mask(radio->marimba,
+ BAHAMA_LDO_DREG_CTL0, &value, 1, value);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:0xF0 write failed\n",
+ __func__);
+ goto open_err_all;
+ }
+ value = 0x86;
+ retval = marimba_write_bit_mask(radio->marimba,
+ BAHAMA_LDO_AREG_CTL0, &value, 1, value);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:0xF4 write failed\n",
+ __func__);
+ goto open_err_all;
+ }
+ }
+
+ /*write FM mode*/
+ retval = tavarua_write_register(radio, BAHAMA_FM_MODE_REG,
+ BAHAMA_FM_MODE_NORMAL);
+ if (retval < 0) {
+ printk(KERN_ERR "failed to set the FM mode: %d\n",
+ retval);
+ goto open_err_all;
+ }
+ /*Write first sequence of bytes to FM_CTL0*/
+ for (i = 0; i < 3; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL0:set-1 failure: %d\n",
+ retval);
+ goto open_err_all;
+ }
+ }
+ /*Write the FM_CTL1 sequence*/
+ for (i = 0; i < 1; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL1_REG, fm_ctl1[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL1 write failure: %d\n",
+ retval);
+ goto open_err_all;
+ }
+ }
+ /*Write second sequence of bytes to FM_CTL0*/
+ for (i = 0; i < 2; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL0:set-2 failure: %d\n",
+ retval);
+ goto open_err_all;
+ }
+ }
+ } else {
+ retval = tavarua_write_registers(radio, LEAKAGE_CNTRL,
+ buffer, 6);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: failed to bring up FM Core\n",
+ __func__);
+ goto open_err_all;
+ }
+ }
+ /* Wait for interrupt i.e. complete(&radio->sync_req_done); call */
+ /*Initialize the completion variable for
+ for the proper behavior*/
+ init_completion(&radio->sync_req_done);
+ if (!wait_for_completion_timeout(&radio->sync_req_done,
+ msecs_to_jiffies(wait_timeout))) {
+ retval = -1;
+ FMDERR("Timeout waiting for initialization\n");
+ }
+
+ /* get Chip ID */
+ retval = tavarua_write_register(radio, XFRCTRL, CHIPID);
+ if (retval < 0)
+ goto open_err_all;
+ msleep(TAVARUA_DELAY);
+ tavarua_read_registers(radio, XFRCTRL, XFR_REG_NUM+1);
+ if (radio->registers[XFRCTRL] != CHIPID)
+ goto open_err_all;
+
+ radio->chipID = (radio->registers[XFRCTRL+2] << 24) |
+ (radio->registers[XFRCTRL+5] << 16) |
+ (radio->registers[XFRCTRL+6] << 8) |
+ (radio->registers[XFRCTRL+7]);
+
+ printk(KERN_WARNING DRIVER_NAME ": Chip ID %x\n", radio->chipID);
+ if (radio->chipID == MARIMBA_A0) {
+ printk(KERN_WARNING DRIVER_NAME ": Unsupported hardware: %x\n",
+ radio->chipID);
+ retval = -1;
+ goto open_err_all;
+ }
+
+ radio->handle_irq = 0;
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ marimba_set_fm_status(radio->marimba, true);
+ return 0;
+
+
+open_err_all:
+ /*Disable FM in case of error*/
+ value = 0x00;
+ marimba_write_bit_mask(radio->marimba, MARIMBA_XO_BUFF_CNTRL,
+ &value, 1, value);
+ tavarua_disable_irq(radio);
+open_err_req_irq:
+ if (radio->pdata->config_i2s_gpio != NULL)
+ radio->pdata->config_i2s_gpio(FM_I2S_OFF);
+config_i2s_err:
+ radio->pdata->fm_shutdown(radio->pdata);
+open_err_setup:
+ radio->handle_irq = 1;
+ radio->users = 0;
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_fops_release
+=============================================================================*/
+/**
+ This function is called when a process closes the device file.
+
+ @param file: file descriptor.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_fops_release(struct file *file)
+{
+ int retval;
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ unsigned char value;
+ int i = 0;
+ /*FM Core shutdown sequence for Bahama*/
+ char fm_ctl0_part1[] = { 0xB7 };
+ char fm_ctl1[] = { 0x03 };
+ char fm_ctl0_part2[] = { 0x9F, 0x48, 0x02 };
+ int bahama_present = -ENODEV;
+ /*FM Core shutdown sequence for Marimba*/
+ char buffer[] = {0x18, 0xB7, 0x48};
+ bool bt_status = false;
+ int index;
+ /* internal regulator controllers DREG_CTL0, AREG_CTL0
+ * has to be kept in the valid state based on the bt status.
+ * 1st row is the state when no clients are active,
+ * and the second when bt is in on state.
+ */
+ char internal_vreg_ctl[2][2] = {
+ { 0x04, 0x84 },
+ { 0x00, 0x80 }
+ };
+
+ if (!radio)
+ return -ENODEV;
+ FMDBG("In %s", __func__);
+
+ /* disable radio ctrl */
+ retval = tavarua_write_register(radio, RDCTRL, 0x00);
+
+ FMDBG("%s, Disable IRQs\n", __func__);
+ /* disable irq */
+ retval = tavarua_disable_irq(radio);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: failed to disable irq\n", __func__);
+ return retval;
+ }
+
+ bahama_present = is_bahama();
+
+ if (bahama_present == -ENODEV)
+ return -ENODEV;
+
+ if (bahama_present) {
+ /*Write first sequence of bytes to FM_CTL0*/
+ for (i = 0; i < 1; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL0_REG, fm_ctl0_part1[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL0:Set-1 failure: %d\n",
+ retval);
+ break;
+ }
+ }
+ /*Write the FM_CTL1 sequence*/
+ for (i = 0; i < 1; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL1_REG, fm_ctl1[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL1 failure: %d\n",
+ retval);
+ break;
+ }
+ }
+ /*Write second sequence of bytes to FM_CTL0*/
+ for (i = 0; i < 3; i++) {
+ retval = tavarua_write_register(radio,
+ BAHAMA_FM_CTL0_REG, fm_ctl0_part2[i]);
+ if (retval < 0) {
+ printk(KERN_ERR "FM_CTL0:Set-2 failure: %d\n",
+ retval);
+ break;
+ }
+ }
+ } else {
+
+ retval = tavarua_write_registers(radio, FM_CTL0,
+ buffer, sizeof(buffer)/sizeof(buffer[0]));
+ if (retval < 0) {
+ printk(KERN_ERR "%s: failed to bring down the FM Core\n",
+ __func__);
+ return retval;
+ }
+ }
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ bt_status = marimba_get_bt_status(radio->marimba);
+ /* Set the index based on the bt status*/
+ index = bt_status ? 1 : 0;
+ /* Check for Bahama's existance and Bahama V2 variant*/
+ if (bahama_present && (bahama_version == 0x09)) {
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ /* actual value itself used as mask*/
+ retval = marimba_write_bit_mask(radio->marimba,
+ BAHAMA_LDO_DREG_CTL0, &internal_vreg_ctl[bt_status][0],
+ 1, internal_vreg_ctl[index][0]);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:0xF0 write failed\n", __func__);
+ return retval;
+ }
+ /* actual value itself used as mask*/
+ retval = marimba_write_bit_mask(radio->marimba,
+ BAHAMA_LDO_AREG_CTL0, &internal_vreg_ctl[bt_status][1],
+ 1, internal_vreg_ctl[index][1]);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:0xF4 write failed\n", __func__);
+ return retval;
+ }
+ } else {
+ /* disable fm core */
+ radio->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA;
+ }
+
+ value = 0x00;
+ retval = marimba_write_bit_mask(radio->marimba, MARIMBA_XO_BUFF_CNTRL,
+ &value, 1, FM_ENABLE);
+ if (retval < 0) {
+ printk(KERN_ERR "%s:XO_BUFF_CNTRL write failed\n", __func__);
+ return retval;
+ }
+ FMDBG("%s, Calling fm_shutdown\n", __func__);
+ /* teardown gpio and pmic */
+ radio->pdata->fm_shutdown(radio->pdata);
+ if (radio->pdata->config_i2s_gpio != NULL)
+ radio->pdata->config_i2s_gpio(FM_I2S_OFF);
+ radio->handle_irq = 1;
+ radio->users = 0;
+ radio->marimba->mod_id = SLAVE_ID_BAHAMA;
+ marimba_set_fm_status(radio->marimba, false);
+ return 0;
+}
+
+/*
+ * tavarua_fops - file operations interface
+ */
+static const struct v4l2_file_operations tavarua_fops = {
+ .owner = THIS_MODULE,
+ .read = tavarua_fops_read,
+ .write = tavarua_fops_write,
+ .ioctl = video_ioctl2,
+ .open = tavarua_fops_open,
+ .release = tavarua_fops_release,
+};
+
+/*************************************************************************
+ * Video4Linux Interface
+ *************************************************************************/
+
+/*
+ * tavarua_v4l2_queryctrl - query control
+ */
+static struct v4l2_queryctrl tavarua_v4l2_queryctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Volume",
+ .minimum = 0,
+ .maximum = 15,
+ .step = 1,
+ .default_value = 15,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BASS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+ },
+ {
+ .id = V4L2_CID_AUDIO_LOUDNESS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SRCHMODE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Search mode",
+ .minimum = 0,
+ .maximum = 7,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SCANDWELL,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Search dwell time",
+ .minimum = 0,
+ .maximum = 7,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SRCHON,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Search on/off",
+ .minimum = 0,
+ .maximum = 1,
+ .step = 1,
+ .default_value = 1,
+
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_STATE,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "radio 0ff/rx/tx/reset",
+ .minimum = 0,
+ .maximum = 3,
+ .step = 1,
+ .default_value = 1,
+
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_REGION,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "radio standard",
+ .minimum = 0,
+ .maximum = 2,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Signal Threshold",
+ .minimum = 0x80,
+ .maximum = 0x7F,
+ .step = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Search PTY",
+ .minimum = 0,
+ .maximum = 31,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_PI,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Search PI",
+ .minimum = 0,
+ .maximum = 0xFF,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Preset num",
+ .minimum = 0,
+ .maximum = 12,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_EMPHASIS,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Emphasis",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_RDS_STD,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "RDS standard",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_SPACING,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Channel spacing",
+ .minimum = 0,
+ .maximum = 2,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_RDSON,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "RDS on/off",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "RDS group mask",
+ .minimum = 0,
+ .maximum = 0xFFFFFFFF,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "RDS processing",
+ .minimum = 0,
+ .maximum = 0xFF,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "RDS data groups to buffer",
+ .minimum = 1,
+ .maximum = 21,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_PSALL,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "pass all ps strings",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_LP_MODE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Low power mode",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_ANTENNA,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "headset/internal",
+ .minimum = 0,
+ .maximum = 1,
+ .default_value = 0,
+ },
+ /* Private controls for FM TX*/
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Set PS REPEATCOUNT",
+ .minimum = 0,
+ .maximum = 15,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Stop PS NAME",
+ .minimum = 0,
+ .maximum = 1,
+ },
+ {
+ .id = V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Stop RT",
+ .minimum = 0,
+ .maximum = 1,
+ },
+
+};
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_querycap
+=============================================================================*/
+/**
+ This function is called to query device capabilities.
+
+ NOTE:
+ All V4L2 devices support the VIDIOC_QUERYCAP ioctl. It is used to identify
+ kernel devices compatible with this specification and to obtain information
+ about driver and hardware capabilities. The ioctl takes a pointer to a struct
+ v4l2_capability which is filled by the driver. When the driver is not
+ compatible with this specification the ioctl returns an EINVAL error code.
+
+ @param file: File descriptor returned by open().
+ @param capability: pointer to struct v4l2_capability.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The device is not compatible with this specification.
+*/
+static int tavarua_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+
+ strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ sprintf(capability->bus_info, "I2C");
+ capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+
+ capability->version = radio->chipID;
+
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_queryctrl
+=============================================================================*/
+/**
+ This function is called to query the device and driver for supported video
+ controls (enumerate control items).
+
+ NOTE:
+ To query the attributes of a control, the applications set the id field of
+ a struct v4l2_queryctrl and call the VIDIOC_QUERYCTRL ioctl with a pointer
+ to this structure. The driver fills the rest of the structure or returns an
+ EINVAL error code when the id is invalid.
+
+ @param file: File descriptor returned by open().
+ @param qc: pointer to struct v4l2_queryctrl.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The struct v4l2_queryctrl id is invalid.
+*/
+static int tavarua_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ unsigned char i;
+ int retval = -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(tavarua_v4l2_queryctrl); i++) {
+ if (qc->id && qc->id == tavarua_v4l2_queryctrl[i].id) {
+ memcpy(qc, &(tavarua_v4l2_queryctrl[i]), sizeof(*qc));
+ retval = 0;
+ break;
+ }
+ }
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": query conv4ltrol failed with %d\n", retval);
+
+ return retval;
+}
+static int peek_MPX_DCC(struct tavarua_device *radio)
+{
+ int retval = 0;
+ unsigned char xfr_buf[XFR_REG_NUM];
+ int MPX_DCC[] = { 0 };
+ int DCC = 0;
+ int ct = 0;
+ unsigned char size = 0;
+
+ /*
+ Poking the MPX_DCC_BYPASS register to freeze the
+ value of MPX_DCC from changing while we access it
+ */
+
+ /*Poking the MPX_DCC_BYPASS register : 0x88C0 */
+ size = 0x01;
+ xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
+ xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB;
+ xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB;
+ xfr_buf[3] = 0x01;
+
+ retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4);
+ if (retval < 0) {
+ FMDBG("Failed to write\n");
+ return retval;
+ }
+ /*Wait for the XFR interrupt */
+ msleep(TAVARUA_DELAY*15);
+
+ for (ct = 0; ct < 5; ct++)
+ xfr_buf[ct] = 0;
+
+ /* Peeking Regs 0x88C2-0x88C4 */
+ size = 0x03;
+ xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
+ xfr_buf[1] = MPX_DCC_PEEK_MSB_REG1;
+ xfr_buf[2] = MPX_DCC_PEEK_LSB_REG1;
+ retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
+ if (retval < 0) {
+ FMDBG("Failed to write\n");
+ return retval;
+ }
+ /*Wait for the XFR interrupt */
+ msleep(TAVARUA_DELAY*10);
+ retval = tavarua_read_registers(radio, XFRDAT0, 3);
+ if (retval < 0) {
+ printk(KERN_INFO "INT_DET: Read failure\n");
+ return retval;
+ }
+ MPX_DCC[0] = (int)radio->registers[XFRDAT0];
+ MPX_DCC[1] = (int)radio->registers[XFRDAT1];
+ MPX_DCC[2] = (int)radio->registers[XFRDAT2];
+
+ /*
+ Form the final MPX_DCC parameter
+ MPX_DCC[0] will form the LSB part
+ MPX_DCC[1] will be the middle part and 4 bits of
+ MPX_DCC[2] will be the MSB par of the 20-bit signed MPX_DCC
+ */
+
+ DCC = ((int)MPX_DCC[2] << 16) | ((int)MPX_DCC[1] << 8) |
+ ((int)MPX_DCC[0]);
+
+ /*
+ if bit-19 is '1',set remaining bits to '1' & make it -tive
+ */
+ if (DCC & 0x00080000) {
+ FMDBG(KERN_INFO "bit-19 is '1'\n");
+ DCC |= 0xFFF00000;
+ }
+
+ /*
+ Poking the MPX_DCC_BYPASS register to be back to normal
+ */
+
+ /*Poking the MPX_DCC_BYPASS register : 0x88C0 */
+ size = 0x01;
+ xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
+ xfr_buf[1] = MPX_DCC_BYPASS_POKE_MSB;
+ xfr_buf[2] = MPX_DCC_BYPASS_POKE_LSB;
+ xfr_buf[3] = 0x00;
+
+ retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 4);
+ if (retval < 0) {
+ FMDBG("Failed to write\n");
+ return retval;
+ }
+ /*Wait for the XFR interrupt */
+ msleep(TAVARUA_DELAY*10);
+
+ return DCC;
+}
+/*=============================================================================
+FUNCTION: tavarua_vidioc_g_ctrl
+=============================================================================*/
+/**
+ This function is called to get the value of a control.
+
+ NOTE:
+ To get the current value of a control, applications initialize the id field
+ of a struct v4l2_control and call the VIDIOC_G_CTRL ioctl with a pointer to
+ this structure.
+
+ When the id is invalid drivers return an EINVAL error code. When the value is
+ out of bounds drivers can choose to take the closest valid value or return an
+ ERANGE error code, whatever seems more appropriate.
+
+ @param file: File descriptor returned by open().
+ @param ctrl: pointer to struct v4l2_control.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The struct v4l2_control id is invalid.
+ @return ERANGE: The struct v4l2_control value is out of bounds.
+ @return EBUSY: The control is temporarily not changeable, possibly because
+ another applications took over control of the device function this control
+ belongs to.
+*/
+static int tavarua_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval = 0;
+ unsigned char xfr_buf[XFR_REG_NUM];
+ signed char cRmssiThreshold;
+ signed char ioc;
+ unsigned char size = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = radio->registers[IOCTRL] & 0x03 ;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE:
+ ctrl->value = radio->registers[SRCHCTRL] & SRCH_MODE;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL:
+ ctrl->value = (radio->registers[SRCHCTRL] & SCAN_DWELL) >> 4;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCHON:
+ ctrl->value = (radio->registers[SRCHCTRL] & SRCH_ON) >> 7 ;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_STATE:
+ ctrl->value = (radio->registers[RDCTRL] & 0x03);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_IOVERC:
+ retval = tavarua_read_registers(radio, IOVERC, 1);
+ if (retval < 0)
+ return retval;
+ ioc = radio->registers[IOVERC];
+ ctrl->value = ioc;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_INTDET:
+ size = 0x1;
+ xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
+ xfr_buf[1] = INTDET_PEEK_MSB;
+ xfr_buf[2] = INTDET_PEEK_LSB;
+ retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
+ if (retval < 0) {
+ FMDBG("Failed to write\n");
+ return retval;
+ }
+ FMDBG("INT_DET:Sync write success\n");
+ /*Wait for the XFR interrupt */
+ msleep(TAVARUA_DELAY*10);
+ /* Read the XFRDAT0 register populated by FM SoC */
+ retval = tavarua_read_registers(radio, XFRDAT0, 3);
+ if (retval < 0) {
+ FMDBG("INT_DET: Read failure\n");
+ return retval;
+ }
+ ctrl->value = radio->registers[XFRDAT0];
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_MPX_DCC:
+ ctrl->value = peek_MPX_DCC(radio);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_REGION:
+ ctrl->value = radio->region_params.region;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH:
+ retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
+ if (retval < 0) {
+ FMDBG("[G IOCTL=V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
+ FMDBG("sync_read_xfr error: [retval=%d]\n", retval);
+ break;
+ }
+ /* Since RMSSI Threshold is signed value */
+ cRmssiThreshold = (signed char)xfr_buf[0];
+ ctrl->value = cRmssiThreshold;
+ FMDBG("cRmssiThreshold: %d\n", cRmssiThreshold);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY:
+ ctrl->value = radio->srch_params.srch_pty;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI:
+ ctrl->value = radio->srch_params.srch_pi;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT:
+ ctrl->value = radio->srch_params.preset_num;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS:
+ ctrl->value = radio->region_params.emphasis;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDS_STD:
+ ctrl->value = radio->region_params.rds_std;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SPACING:
+ ctrl->value = radio->region_params.spacing;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSON:
+ ctrl->value = radio->registers[RDSCTRL] & RDS_ON;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ if (retval > -1)
+ ctrl->value = (xfr_buf[8] << 24) |
+ (xfr_buf[9] << 16) |
+ (xfr_buf[10] << 8) |
+ xfr_buf[11];
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC:
+ retval = tavarua_read_registers(radio, ADVCTRL, 1);
+ if (retval > -1)
+ ctrl->value = radio->registers[ADVCTRL];
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ if (retval > -1)
+ ctrl->value = xfr_buf[1];
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_PSALL:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ if (retval > -1)
+ ctrl->value = xfr_buf[12] & RDS_CONFIG_PSALL;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_LP_MODE:
+ ctrl->value = radio->lp_mode;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_ANTENNA:
+ ctrl->value = GET_REG_FIELD(radio->registers[IOCTRL],
+ IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK);
+ break;
+ default:
+ retval = -EINVAL;
+ }
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": get control failed with %d, id: %d\n", retval, ctrl->id);
+
+ return retval;
+}
+
+static int tavarua_vidioc_s_ext_ctrls(struct file *file, void *priv,
+ struct v4l2_ext_controls *ctrl)
+{
+ int retval = 0;
+ int bytes_to_copy;
+ int bytes_copied = 0;
+ int bytes_left = 0;
+ int chunk_index = 0;
+ char tx_data[XFR_REG_NUM];
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ char *data = NULL;
+ int extra_name_byte = 0;
+ int name_bytes = 0;
+
+ switch ((ctrl->controls[0]).id) {
+ case V4L2_CID_RDS_TX_PS_NAME: {
+ FMDBG("In V4L2_CID_RDS_TX_PS_NAME\n");
+ /*Pass a sample PS string */
+
+ chunk_index = 0;
+ bytes_copied = 0;
+ bytes_left = min((int)(ctrl->controls[0]).size,
+ MAX_PS_LENGTH);
+ data = (ctrl->controls[0]).string;
+
+ /* send payload to FM hardware */
+ while (bytes_left) {
+ chunk_index++;
+ FMDBG("chunk is %d", chunk_index);
+ bytes_to_copy = min(bytes_left, XFR_REG_NUM);
+ /*Clear the tx_data */
+ memset(tx_data, 0, XFR_REG_NUM);
+ if (copy_from_user(tx_data,
+ data + bytes_copied, bytes_to_copy))
+ return -EFAULT;
+ retval = sync_write_xfr(radio,
+ RDS_PS_0 + chunk_index, tx_data);
+ if (retval < 0) {
+ FMDBG("sync_write_xfr: %d", retval);
+ return retval;
+ }
+ bytes_copied += bytes_to_copy;
+ bytes_left -= bytes_to_copy;
+ }
+ memset(tx_data, 0, XFR_REG_NUM);
+ /*Write the PS Header*/
+ FMDBG("Writing PS header\n");
+ extra_name_byte = (bytes_copied%8) ? 1 : 0;
+ name_bytes = (bytes_copied/8) + extra_name_byte;
+ /*8 bytes are grouped as 1 name */
+ tx_data[0] = (name_bytes) & MASK_TXREPCOUNT;
+ tx_data[1] = radio->pty & MASK_PTY; /* PTY */
+ tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8);
+ tx_data[3] = radio->pi & MASK_PI_LSB;
+ /* TX ctrl + repeatCount*/
+ tx_data[4] = TX_ON |
+ (radio->ps_repeatcount & MASK_TXREPCOUNT);
+ retval = sync_write_xfr(radio, RDS_PS_0, tx_data);
+ if (retval < 0) {
+ FMDBG("sync_write_xfr returned %d", retval);
+ return retval;
+ }
+ } break;
+ case V4L2_CID_RDS_TX_RADIO_TEXT: {
+ chunk_index = 0;
+ bytes_copied = 0;
+ FMDBG("In V4L2_CID_RDS_TX_RADIO_TEXT\n");
+ /*Pass a sample PS string */
+ FMDBG("Passed RT String : %s\n",
+ (ctrl->controls[0]).string);
+ bytes_left =
+ min((int)(ctrl->controls[0]).size, MAX_RT_LENGTH);
+ data = (ctrl->controls[0]).string;
+ /* send payload to FM hardware */
+ while (bytes_left) {
+ chunk_index++;
+ bytes_to_copy = min(bytes_left, XFR_REG_NUM);
+ memset(tx_data, 0, XFR_REG_NUM);
+ if (copy_from_user(tx_data,
+ data + bytes_copied, bytes_to_copy))
+ return -EFAULT;
+ retval = sync_write_xfr(radio,
+ RDS_RT_0 + chunk_index, tx_data);
+ if (retval < 0)
+ return retval;
+ bytes_copied += bytes_to_copy;
+ bytes_left -= bytes_to_copy;
+ }
+ /*Write the RT Header */
+ tx_data[0] = bytes_copied;
+ /* PTY */
+ tx_data[1] = TX_ON | ((radio->pty & MASK_PTY) >> 8);
+ /* PI high */
+ tx_data[2] = ((radio->pi & MASK_PI_MSB) >> 8);
+ /* PI low */
+ tx_data[3] = radio->pi & MASK_PI_LSB;
+ retval = sync_write_xfr(radio, RDS_RT_0 , tx_data);
+ if (retval < 0)
+ return retval;
+ FMDBG("done RT writing: %d\n", retval);
+ } break;
+ default:
+ {
+ FMDBG("Shouldn't reach here\n");
+ retval = -1;
+ }
+ }
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_s_ctrl
+=============================================================================*/
+/**
+ This function is called to set the value of a control.
+
+ NOTE:
+ To change the value of a control, applications initialize the id and value
+ fields of a struct v4l2_control and call the VIDIOC_S_CTRL ioctl.
+
+ When the id is invalid drivers return an EINVAL error code. When the value is
+ out of bounds drivers can choose to take the closest valid value or return an
+ ERANGE error code, whatever seems more appropriate.
+
+ @param file: File descriptor returned by open().
+ @param ctrl: pointer to struct v4l2_control.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The struct v4l2_control id is invalid.
+ @return ERANGE: The struct v4l2_control value is out of bounds.
+ @return EBUSY: The control is temporarily not changeable, possibly because
+ another applications took over control of the device function this control
+ belongs to.
+*/
+static int tavarua_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval = 0;
+ unsigned char value;
+ unsigned char xfr_buf[XFR_REG_NUM];
+ unsigned char tx_data[XFR_REG_NUM];
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ value = (radio->registers[IOCTRL] & ~IOC_HRD_MUTE) |
+ (ctrl->value & 0x03);
+ retval = tavarua_write_register(radio, IOCTRL, value);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCHMODE:
+ value = (radio->registers[SRCHCTRL] & ~SRCH_MODE) |
+ ctrl->value;
+ radio->registers[SRCHCTRL] = value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SCANDWELL:
+ value = (radio->registers[SRCHCTRL] & ~SCAN_DWELL) |
+ (ctrl->value << 4);
+ radio->registers[SRCHCTRL] = value;
+ break;
+ /* start/stop search */
+ case V4L2_CID_PRIVATE_TAVARUA_SRCHON:
+ FMDBG("starting search\n");
+ tavarua_search(radio, ctrl->value, SRCH_DIR_UP);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_STATE:
+ /* check if already on */
+ radio->handle_irq = 1;
+ if (((ctrl->value == FM_RECV) || (ctrl->value == FM_TRANS))
+ && !(radio->registers[RDCTRL] &
+ ctrl->value)) {
+ FMDBG("clearing flags\n");
+ init_completion(&radio->sync_xfr_start);
+ init_completion(&radio->sync_req_done);
+ radio->xfr_in_progress = 0;
+ radio->xfr_bytes_left = 0;
+ FMDBG("turning on ..\n");
+ retval = tavarua_start(radio, ctrl->value);
+ if (retval >= 0) {
+ FMDBG("Setting audio path ...\n");
+ retval = tavarua_set_audio_path(
+ TAVARUA_AUDIO_OUT_DIGITAL_ON,
+ TAVARUA_AUDIO_OUT_ANALOG_OFF);
+ if (retval < 0) {
+ FMDERR("Error in tavarua_set_audio_path"
+ " %d\n", retval);
+ }
+ /* Enabling 'SoftMute' and 'SignalBlending' features */
+ value = (radio->registers[IOCTRL] |
+ IOC_SFT_MUTE | IOC_SIG_BLND);
+ retval = tavarua_write_register(radio, IOCTRL, value);
+ if (retval < 0)
+ FMDBG("SMute and SBlending not enabled\n");
+ }
+ }
+ /* check if off */
+ else if ((ctrl->value == FM_OFF) && radio->registers[RDCTRL]) {
+ FMDBG("turning off...\n");
+ retval = tavarua_write_register(radio, RDCTRL,
+ ctrl->value);
+ /*Make it synchronous
+ Block it till READY interrupt
+ Wait for interrupt i.e.
+ complete(&radio->sync_req_done)
+ */
+
+ if (retval >= 0) {
+
+ if (!wait_for_completion_timeout(
+ &radio->sync_req_done,
+ msecs_to_jiffies(wait_timeout)))
+ FMDBG("turning off timedout...\n");
+ }
+ }
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_REGION:
+ retval = tavarua_set_region(radio, ctrl->value);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH:
+ retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
+ if (retval < 0) {
+ FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
+ FMDERR("sync_read_xfr [retval=%d]\n", retval);
+ break;
+ }
+ /* RMSSI Threshold is a signed 8 bit value */
+ xfr_buf[0] = (unsigned char)ctrl->value;
+ xfr_buf[1] = (unsigned char)ctrl->value;
+ xfr_buf[4] = 0x01;
+ retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf);
+ if (retval < 0) {
+ FMDERR("V4L2_CID_PRIVATE_TAVARUA_SIGNAL_TH]\n");
+ FMDERR("sync_write_xfr [retval=%d]\n", retval);
+ break;
+ }
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_PTY:
+ radio->srch_params.srch_pty = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_PI:
+ radio->srch_params.srch_pi = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SRCH_CNT:
+ radio->srch_params.preset_num = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_EMPHASIS:
+ radio->region_params.emphasis = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDS_STD:
+ radio->region_params.rds_std = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_SPACING:
+ radio->region_params.spacing = ctrl->value;
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSON:
+ retval = 0;
+ if (ctrl->value != (radio->registers[RDSCTRL] & RDS_ON)) {
+ value = radio->registers[RDSCTRL] | ctrl->value;
+ retval = tavarua_write_register(radio, RDSCTRL, value);
+ }
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_MASK:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ if (retval < 0)
+ break;
+ xfr_buf[8] = (ctrl->value & 0xFF000000) >> 24;
+ xfr_buf[9] = (ctrl->value & 0x00FF0000) >> 16;
+ xfr_buf[10] = (ctrl->value & 0x0000FF00) >> 8;
+ xfr_buf[11] = (ctrl->value & 0x000000FF);
+ retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSGROUP_PROC:
+ value = radio->registers[ADVCTRL] | ctrl->value ;
+ retval = tavarua_write_register(radio, ADVCTRL, value);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_RDSD_BUF:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ if (retval < 0)
+ break;
+ xfr_buf[1] = ctrl->value;
+ retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_PSALL:
+ retval = sync_read_xfr(radio, RDS_CONFIG, xfr_buf);
+ value = ctrl->value & RDS_CONFIG_PSALL;
+ if (retval < 0)
+ break;
+ xfr_buf[12] &= ~RDS_CONFIG_PSALL;
+ xfr_buf[12] |= value;
+ retval = sync_write_xfr(radio, RDS_CONFIG, xfr_buf);
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_LP_MODE:
+ retval = 0;
+ if (ctrl->value == radio->lp_mode)
+ break;
+ if (ctrl->value) {
+ FMDBG("going into low power mode\n");
+ retval = tavarua_disable_interrupts(radio);
+ } else {
+ FMDBG("going into normal power mode\n");
+ tavarua_setup_interrupts(radio,
+ (radio->registers[RDCTRL] & 0x03));
+ }
+ break;
+ case V4L2_CID_PRIVATE_TAVARUA_ANTENNA:
+ SET_REG_FIELD(radio->registers[IOCTRL], ctrl->value,
+ IOC_ANTENNA_OFFSET, IOC_ANTENNA_MASK);
+ break;
+ /* TX Controls */
+
+ case V4L2_CID_RDS_TX_PTY: {
+ radio->pty = ctrl->value;
+ } break;
+ case V4L2_CID_RDS_TX_PI: {
+ radio->pi = ctrl->value;
+ } break;
+ case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_PS_NAME: {
+ FMDBG("In STOP_RDS_TX_PS_NAME\n");
+ /*Pass a sample PS string */
+ memset(tx_data, '0', XFR_REG_NUM);
+ FMDBG("Writing PS header\n");
+ retval = sync_write_xfr(radio, RDS_PS_0, tx_data);
+ FMDBG("retval of PS Header write: %d", retval);
+
+ } break;
+
+ case V4L2_CID_PRIVATE_TAVARUA_STOP_RDS_TX_RT: {
+ memset(tx_data, '0', XFR_REG_NUM);
+ FMDBG("Writing RT header\n");
+ retval = sync_write_xfr(radio, RDS_RT_0, tx_data);
+ FMDBG("retval of Header write: %d", retval);
+
+ } break;
+
+ case V4L2_CID_PRIVATE_TAVARUA_TX_SETPSREPEATCOUNT: {
+ radio->ps_repeatcount = ctrl->value;
+ } break;
+ case V4L2_CID_TUNE_POWER_LEVEL: {
+ unsigned char tx_power_lvl_config[FM_TX_PWR_LVL_MAX+1] = {
+ 0x85, /* tx_da<5:3> = 0 lpf<2:0> = 5*/
+ 0x95, /* tx_da<5:3> = 2 lpf<2:0> = 5*/
+ 0x9D, /* tx_da<5:3> = 3 lpf<2:0> = 5*/
+ 0xA5, /* tx_da<5:3> = 4 lpf<2:0> = 5*/
+ 0xAD, /* tx_da<5:3> = 5 lpf<2:0> = 5*/
+ 0xB5, /* tx_da<5:3> = 6 lpf<2:0> = 5*/
+ 0xBD, /* tx_da<5:3> = 7 lpf<2:0> = 5*/
+ 0xBF /* tx_da<5:3> = 7 lpf<2:0> = 7*/
+ };
+ if (ctrl->value > FM_TX_PWR_LVL_MAX)
+ ctrl->value = FM_TX_PWR_LVL_MAX;
+ if (ctrl->value < FM_TX_PWR_LVL_0)
+ ctrl->value = FM_TX_PWR_LVL_0;
+ retval = sync_read_xfr(radio, PHY_TXGAIN, xfr_buf);
+ FMDBG("return for PHY_TXGAIN is %d", retval);
+ if (retval < 0) {
+ FMDBG("read failed");
+ break;
+ }
+ xfr_buf[2] = tx_power_lvl_config[ctrl->value];
+ retval = sync_write_xfr(radio, PHY_TXGAIN, xfr_buf);
+ FMDBG("return for write PHY_TXGAIN is %d", retval);
+ if (retval < 0)
+ FMDBG("write failed");
+ } break;
+
+ default:
+ retval = -EINVAL;
+ }
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set control failed with %d, id : %d\n", retval, ctrl->id);
+
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_g_tuner
+=============================================================================*/
+/**
+ This function is called to get tuner attributes.
+
+ NOTE:
+ To query the attributes of a tuner, applications initialize the index field
+ and zero out the reserved array of a struct v4l2_tuner and call the
+ VIDIOC_G_TUNER ioctl with a pointer to this structure. Drivers fill the rest
+ of the structure or return an EINVAL error code when the index is out of
+ bounds. To enumerate all tuners applications shall begin at index zero,
+ incrementing by one until the driver returns EINVAL.
+
+ @param file: File descriptor returned by open().
+ @param tuner: pointer to struct v4l2_tuner.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The struct v4l2_tuner index is out of bounds.
+*/
+static int tavarua_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval;
+ unsigned char xfr_buf[XFR_REG_NUM];
+ char rmssi = 0;
+ unsigned char size = 0;
+
+ if (tuner->index > 0)
+ return -EINVAL;
+
+ /* read status rssi */
+ retval = tavarua_read_registers(radio, IOCTRL, 1);
+ if (retval < 0)
+ return retval;
+ /* read RMSSI */
+ size = 0x1;
+ xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
+ xfr_buf[1] = RMSSI_PEEK_MSB;
+ xfr_buf[2] = RMSSI_PEEK_LSB;
+ retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
+ msleep(TAVARUA_DELAY*10);
+ retval = tavarua_read_registers(radio, XFRDAT0, 3);
+ rmssi = radio->registers[XFRDAT0];
+ tuner->signal = rmssi;
+
+ strcpy(tuner->name, "FM");
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->rangelow = radio->region_params.band_low;
+ tuner->rangehigh = radio->region_params.band_high;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
+ tuner->capability = V4L2_TUNER_CAP_LOW;
+
+ /* Stereo indicator == Stereo (instead of Mono) */
+ if (radio->registers[IOCTRL] & IOC_MON_STR)
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+ else
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+
+ /* automatic frequency control: -1: freq to low, 1 freq to high */
+ tuner->afc = 0;
+
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_s_tuner
+=============================================================================*/
+/**
+ This function is called to set tuner attributes. Used to set mono/stereo mode.
+
+ NOTE:
+ Tuners have two writable properties, the audio mode and the radio frequency.
+ To change the audio mode, applications initialize the index, audmode and
+ reserved fields and call the VIDIOC_S_TUNER ioctl. This will not change the
+ current tuner, which is determined by the current video input. Drivers may
+ choose a different audio mode if the requested mode is invalid or unsupported.
+ Since this is a write-only ioctl, it does not return the actually selected
+ audio mode.
+
+ To change the radio frequency the VIDIOC_S_FREQUENCY ioctl is available.
+
+ @param file: File descriptor returned by open().
+ @param tuner: pointer to struct v4l2_tuner.
+
+ @return On success 0 is returned, else error code.
+ @return -EINVAL: The struct v4l2_tuner index is out of bounds.
+*/
+static int tavarua_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval;
+ int audmode;
+ if (tuner->index > 0)
+ return -EINVAL;
+
+ FMDBG("%s: set low to %d\n", __func__, tuner->rangelow);
+ radio->region_params.band_low = tuner->rangelow;
+ radio->region_params.band_high = tuner->rangehigh;
+ if (tuner->audmode == V4L2_TUNER_MODE_MONO)
+ /* Mono */
+ audmode = (radio->registers[IOCTRL] | IOC_MON_STR);
+ else
+ /* Stereo */
+ audmode = (radio->registers[IOCTRL] & ~IOC_MON_STR);
+ retval = tavarua_write_register(radio, IOCTRL, audmode);
+ if (retval < 0)
+ printk(KERN_WARNING DRIVER_NAME
+ ": set tuner failed with %d\n", retval);
+
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_g_frequency
+=============================================================================*/
+/**
+ This function is called to get tuner or modulator radio frequency.
+
+ NOTE:
+ To get the current tuner or modulator radio frequency applications set the
+ tuner field of a struct v4l2_frequency to the respective tuner or modulator
+ number (only input devices have tuners, only output devices have modulators),
+ zero out the reserved array and call the VIDIOC_G_FREQUENCY ioctl with a
+ pointer to this structure. The driver stores the current frequency in the
+ frequency field.
+
+ @param file: File descriptor returned by open().
+ @param freq: pointer to struct v4l2_frequency. This will be set to the
+ resultant
+ frequency in 62.5 khz on success.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The tuner index is out of bounds or the value in the type
+ field is wrong.
+*/
+static int tavarua_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ freq->type = V4L2_TUNER_RADIO;
+ return tavarua_get_freq(radio, freq);
+
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_s_frequency
+=============================================================================*/
+/**
+ This function is called to set tuner or modulator radio frequency.
+
+ NOTE:
+ To change the current tuner or modulator radio frequency applications
+ initialize the tuner, type and frequency fields, and the reserved array of
+ a struct v4l2_frequency and call the VIDIOC_S_FREQUENCY ioctl with a pointer
+ to this structure. When the requested frequency is not possible the driver
+ assumes the closest possible value. However VIDIOC_S_FREQUENCY is a
+ write-only ioctl, it does not return the actual new frequency.
+
+ @param file: File descriptor returned by open().
+ @param freq: pointer to struct v4l2_frequency.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The tuner index is out of bounds or the value in the type
+ field is wrong.
+*/
+static int tavarua_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int retval = -1;
+ struct v4l2_frequency getFreq;
+
+ FMDBG("%s\n", __func__);
+
+ if (freq->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ FMDBG("Calling tavarua_set_freq\n");
+
+ INIT_COMPLETION(radio->sync_req_done);
+ retval = tavarua_set_freq(radio, freq->frequency);
+ if (retval < 0) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": set frequency failed with %d\n", retval);
+ } else {
+ /* Wait for interrupt i.e. complete
+ (&radio->sync_req_done); call */
+ if (!wait_for_completion_timeout(&radio->sync_req_done,
+ msecs_to_jiffies(wait_timeout))) {
+ FMDERR("Timeout: No Tune response");
+ retval = tavarua_get_freq(radio, &getFreq);
+ radio->tune_req = 0;
+ if (retval > 0) {
+ if (getFreq.frequency == freq->frequency) {
+ /** This is success, queut the event*/
+ tavarua_q_event(radio,
+ TAVARUA_EVT_TUNE_SUCC);
+ return 0;
+ } else {
+ return -EIO;
+ }
+ }
+ }
+ }
+ radio->tune_req = 0;
+ return retval;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_dqbuf
+=============================================================================*/
+/**
+ This function is called to exchange a buffer with the driver.
+ This is main buffer function, in essense its equivalent to a blocking
+ read call.
+
+ Applications call the VIDIOC_DQBUF ioctl to dequeue a filled (capturing) or
+ displayed (output) buffer from the driver's outgoing queue. They just set
+ the type and memory fields of a struct v4l2_buffer as above, when VIDIOC_DQBUF
+ is called with a pointer to this structure the driver fills the remaining
+ fields or returns an error code.
+
+ NOTE:
+ By default VIDIOC_DQBUF blocks when no buffer is in the outgoing queue.
+ When the O_NONBLOCK flag was given to the open() function, VIDIOC_DQBUF
+ returns immediately with an EAGAIN error code when no buffer is available.
+
+ @param file: File descriptor returned by open().
+ @param buffer: pointer to struct v4l2_buffer.
+
+ @return On success 0 is returned, else error code.
+ @return EAGAIN: Non-blocking I/O has been selected using O_NONBLOCK and no
+ buffer was in the outgoing queue.
+ @return EINVAL: The buffer type is not supported, or the index is out of
+ bounds, or no buffers have been allocated yet, or the userptr or length are
+ invalid.
+ @return ENOMEM: Not enough physical or virtual memory was available to enqueue
+ a user pointer buffer.
+ @return EIO: VIDIOC_DQBUF failed due to an internal error. Can also indicate
+ temporary problems like signal loss. Note the driver might dequeue an (empty)
+ buffer despite returning an error, or even stop capturing.
+*/
+static int tavarua_vidioc_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *buffer)
+{
+
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ enum tavarua_buf_t buf_type = buffer->index;
+ struct kfifo *data_fifo;
+ unsigned char *buf = (unsigned char *)buffer->m.userptr;
+ unsigned int len = buffer->length;
+ FMDBG("%s: requesting buffer %d\n", __func__, buf_type);
+ /* check if we can access the user buffer */
+ if (!access_ok(VERIFY_WRITE, buf, len))
+ return -EFAULT;
+ if ((buf_type < TAVARUA_BUF_MAX) && (buf_type >= 0)) {
+ data_fifo = &radio->data_buf[buf_type];
+ if (buf_type == TAVARUA_BUF_EVENTS) {
+ if (wait_event_interruptible(radio->event_queue,
+ kfifo_len(data_fifo)) < 0) {
+ return -EINTR;
+ }
+ }
+ } else {
+ FMDERR("invalid buffer type\n");
+ return -EINVAL;
+ }
+ buffer->bytesused = kfifo_out_locked(data_fifo, buf, len,
+ &radio->buf_lock[buf_type]);
+
+ return 0;
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_g_fmt_type_private
+=============================================================================*/
+/**
+ This function is here to make the v4l2 framework happy.
+ We cannot use private buffers without it.
+
+ @param file: File descriptor returned by open().
+ @param f: pointer to struct v4l2_format.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The tuner index is out of bounds or the value in the type
+ field is wrong.
+*/
+static int tavarua_vidioc_g_fmt_type_private(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ return 0;
+
+}
+
+/*=============================================================================
+FUNCTION: tavarua_vidioc_s_hw_freq_seek
+=============================================================================*/
+/**
+ This function is called to perform a hardware frequency seek.
+
+ Start a hardware frequency seek from the current frequency. To do this
+ applications initialize the tuner, type, seek_upward and wrap_around fields,
+ and zero out the reserved array of a struct v4l2_hw_freq_seek and call the
+ VIDIOC_S_HW_FREQ_SEEK ioctl with a pointer to this structure.
+
+ This ioctl is supported if the V4L2_CAP_HW_FREQ_SEEK capability is set.
+
+ @param file: File descriptor returned by open().
+ @param seek: pointer to struct v4l2_hw_freq_seek.
+
+ @return On success 0 is returned, else error code.
+ @return EINVAL: The tuner index is out of bounds or the value in the type
+ field is wrong.
+ @return EAGAIN: The ioctl timed-out. Try again.
+*/
+static int tavarua_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ struct v4l2_hw_freq_seek *seek)
+{
+ struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
+ int dir;
+ if (seek->seek_upward)
+ dir = SRCH_DIR_UP;
+ else
+ dir = SRCH_DIR_DOWN;
+ FMDBG("starting search\n");
+ return tavarua_search(radio, CTRL_ON, dir);
+}
+
+/*
+ * tavarua_viddev_tamples - video device interface
+ */
+static const struct v4l2_ioctl_ops tavarua_ioctl_ops = {
+ .vidioc_querycap = tavarua_vidioc_querycap,
+ .vidioc_queryctrl = tavarua_vidioc_queryctrl,
+ .vidioc_g_ctrl = tavarua_vidioc_g_ctrl,
+ .vidioc_s_ctrl = tavarua_vidioc_s_ctrl,
+ .vidioc_g_tuner = tavarua_vidioc_g_tuner,
+ .vidioc_s_tuner = tavarua_vidioc_s_tuner,
+ .vidioc_g_frequency = tavarua_vidioc_g_frequency,
+ .vidioc_s_frequency = tavarua_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = tavarua_vidioc_s_hw_freq_seek,
+ .vidioc_dqbuf = tavarua_vidioc_dqbuf,
+ .vidioc_g_fmt_type_private = tavarua_vidioc_g_fmt_type_private,
+ .vidioc_s_ext_ctrls = tavarua_vidioc_s_ext_ctrls,
+};
+
+static struct video_device tavarua_viddev_template = {
+ .fops = &tavarua_fops,
+ .ioctl_ops = &tavarua_ioctl_ops,
+ .name = DRIVER_NAME,
+ .release = video_device_release,
+};
+
+/*==============================================================
+FUNCTION: FmQSocCom_EnableInterrupts
+==============================================================*/
+/**
+ This function enable interrupts.
+
+ @param radio: structure pointer passed by client.
+ @param state: FM radio state (receiver/transmitter/off/reset).
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_setup_interrupts(struct tavarua_device *radio,
+ enum radio_state_t state)
+{
+ int retval;
+ unsigned char int_ctrl[XFR_REG_NUM];
+
+ if (!radio->lp_mode)
+ return 0;
+
+ int_ctrl[STATUS_REG1] = READY | TUNE | SEARCH | SCANNEXT |
+ SIGNAL | INTF | SYNC | AUDIO;
+ if (state == FM_RECV)
+ int_ctrl[STATUS_REG2] = RDSDAT | RDSRT | RDSPS | RDSAF;
+ else
+ int_ctrl[STATUS_REG2] = TXRDSDAT | TXRDSDONE;
+
+ int_ctrl[STATUS_REG3] = TRANSFER | ERROR;
+
+ /* use xfr for interrupt setup */
+ if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0
+ || radio->chipID == BAHAMA_2_0) {
+ FMDBG("Setting interrupts\n");
+ retval = sync_write_xfr(radio, INT_CTRL, int_ctrl);
+ /* use register write to setup interrupts */
+ } else {
+ retval = tavarua_write_register(radio,
+ STATUS_REG1, int_ctrl[STATUS_REG1]);
+ if (retval < 0)
+ return retval;
+
+ retval = tavarua_write_register(radio,
+ STATUS_REG2, int_ctrl[STATUS_REG2]);
+ if (retval < 0)
+ return retval;
+
+ retval = tavarua_write_register(radio,
+ STATUS_REG3, int_ctrl[STATUS_REG3]);
+ if (retval < 0)
+ return retval;
+ }
+
+ radio->lp_mode = 0;
+ /* tavarua_handle_interrupts force reads all the interrupt status
+ * registers and it is not valid for MBA 2.1
+ */
+ if ((radio->chipID != MARIMBA_2_1) && (radio->chipID != BAHAMA_1_0)
+ && (radio->chipID != BAHAMA_2_0))
+ tavarua_handle_interrupts(radio);
+
+ return retval;
+
+}
+
+/*==============================================================
+FUNCTION: tavarua_disable_interrupts
+==============================================================*/
+/**
+ This function disables interrupts.
+
+ @param radio: structure pointer passed by client.
+
+ @return => 0 if successful.
+ @return < 0 if failure.
+*/
+static int tavarua_disable_interrupts(struct tavarua_device *radio)
+{
+ unsigned char lpm_buf[XFR_REG_NUM];
+ int retval;
+ if (radio->lp_mode)
+ return 0;
+ FMDBG("%s\n", __func__);
+ /* In Low power mode, disable all the interrupts that are not being
+ waited by the Application */
+ lpm_buf[STATUS_REG1] = TUNE | SEARCH | SCANNEXT;
+ lpm_buf[STATUS_REG2] = 0x00;
+ lpm_buf[STATUS_REG3] = TRANSFER;
+ /* use xfr for interrupt setup */
+ wait_timeout = 100;
+ if (radio->chipID == MARIMBA_2_1 || radio->chipID == BAHAMA_1_0
+ || radio->chipID == BAHAMA_2_0)
+ retval = sync_write_xfr(radio, INT_CTRL, lpm_buf);
+ /* use register write to setup interrupts */
+ else
+ retval = tavarua_write_registers(radio, STATUS_REG1, lpm_buf,
+ ARRAY_SIZE(lpm_buf));
+
+ /*INT_CTL writes may fail with TIME_OUT as all the
+ interrupts have been disabled
+ */
+ if (retval > -1 || retval == -ETIME) {
+ radio->lp_mode = 1;
+ /*Consider timeout as a valid case here*/
+ retval = 0;
+ }
+ wait_timeout = WAIT_TIMEOUT;
+ return retval;
+
+}
+
+/*==============================================================
+FUNCTION: tavarua_start
+==============================================================*/
+/**
+ Starts/enables the device (FM radio).
+
+ @param radio: structure pointer passed by client.
+ @param state: FM radio state (receiver/transmitter/off/reset).
+
+ @return On success 0 is returned, else error code.
+*/
+static int tavarua_start(struct tavarua_device *radio,
+ enum radio_state_t state)
+{
+
+ int retval;
+ FMDBG("%s <%d>\n", __func__, state);
+ /* set geographic region */
+ radio->region_params.region = TAVARUA_REGION_US;
+
+ /* set radio mode */
+ retval = tavarua_write_register(radio, RDCTRL, state);
+ if (retval < 0)
+ return retval;
+ /* wait for radio to init */
+ msleep(RADIO_INIT_TIME);
+ /* enable interrupts */
+ tavarua_setup_interrupts(radio, state);
+ /* default region is US */
+ radio->region_params.band_low = US_LOW_BAND * FREQ_MUL;
+ radio->region_params.band_high = US_HIGH_BAND * FREQ_MUL;
+
+ return 0;
+}
+
+/*==============================================================
+FUNCTION: tavarua_suspend
+==============================================================*/
+/**
+ Save state and stop all devices in system.
+
+ @param pdev: platform device to be suspended.
+ @param state: Power state to put each device in.
+
+ @return On success 0 is returned, else error code.
+*/
+static int tavarua_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct tavarua_device *radio = platform_get_drvdata(pdev);
+ int retval;
+ int users = 0;
+ printk(KERN_INFO DRIVER_NAME "%s: radio suspend\n\n", __func__);
+ if (radio) {
+ mutex_lock(&radio->lock);
+ users = radio->users;
+ mutex_unlock(&radio->lock);
+ if (users) {
+ retval = tavarua_disable_interrupts(radio);
+ if (retval < 0) {
+ printk(KERN_INFO DRIVER_NAME
+ "tavarua_suspend error %d\n", retval);
+ return -EIO;
+ }
+ }
+ }
+ return 0;
+}
+
+/*==============================================================
+FUNCTION: tavarua_resume
+==============================================================*/
+/**
+ Restore state of each device in system.
+
+ @param pdev: platform device to be resumed.
+
+ @return On success 0 is returned, else error code.
+*/
+static int tavarua_resume(struct platform_device *pdev)
+{
+
+ struct tavarua_device *radio = platform_get_drvdata(pdev);
+ int retval;
+ int users = 0;
+ printk(KERN_INFO DRIVER_NAME "%s: radio resume\n\n", __func__);
+ if (radio) {
+ mutex_lock(&radio->lock);
+ users = radio->users;
+ mutex_unlock(&radio->lock);
+
+ if (users) {
+ retval = tavarua_setup_interrupts(radio,
+ (radio->registers[RDCTRL] & 0x03));
+ if (retval < 0) {
+ printk(KERN_INFO DRIVER_NAME "Error in \
+ tavarua_resume %d\n", retval);
+ return -EIO;
+ }
+ }
+ }
+ return 0;
+}
+
+/*==============================================================
+FUNCTION: tavarua_set_audio_path
+==============================================================*/
+/**
+ This function will configure the audio path to and from the
+ FM core.
+
+ This interface is expected to be called from the multimedia
+ driver's thread. This interface should only be called when
+ the FM hardware is enabled. If the FM hardware is not
+ currently enabled, this interface will return an error.
+
+ @param digital_on: Digital audio from the FM core should be enabled/disbled.
+ @param analog_on: Analog audio from the FM core should be enabled/disbled.
+
+ @return On success 0 is returned, else error code.
+*/
+int tavarua_set_audio_path(int digital_on, int analog_on)
+{
+ struct tavarua_device *radio = private_data;
+ int rx_on = radio->registers[RDCTRL] & FM_RECV;
+ if (!radio)
+ return -ENOMEM;
+ /* RX */
+ FMDBG("%s: digital: %d analog: %d\n", __func__, digital_on, analog_on);
+ SET_REG_FIELD(radio->registers[AUDIOCTRL],
+ ((rx_on && analog_on) ? 1 : 0),
+ AUDIORX_ANALOG_OFFSET,
+ AUDIORX_ANALOG_MASK);
+ SET_REG_FIELD(radio->registers[AUDIOCTRL],
+ ((rx_on && digital_on) ? 1 : 0),
+ AUDIORX_DIGITAL_OFFSET,
+ AUDIORX_DIGITAL_MASK);
+ SET_REG_FIELD(radio->registers[AUDIOCTRL],
+ (rx_on ? 0 : 1),
+ AUDIOTX_OFFSET,
+ AUDIOTX_MASK);
+ /*
+
+ I2S Master/Slave configuration:
+ Setting the FM SoC as I2S Master/Slave
+ 'false' - FM SoC is I2S Slave
+ 'true' - FM SoC is I2S Master
+
+ We get this infomation from the respective target's board file :
+ MSM7x30 - FM SoC is I2S Slave
+ MSM8x60 - FM SoC is I2S Slave
+ MSM7x27A - FM SoC is I2S Master
+ */
+
+ if (!radio->pdata->is_fm_soc_i2s_master) {
+ FMDBG("FM SoC is I2S Slave\n");
+ SET_REG_FIELD(radio->registers[AUDIOCTRL],
+ (0),
+ I2SCTRL_OFFSET,
+ I2SCTRL_MASK);
+ } else {
+ FMDBG("FM SoC is I2S Master\n");
+ SET_REG_FIELD(radio->registers[AUDIOCTRL],
+ (1),
+ I2SCTRL_OFFSET,
+ I2SCTRL_MASK);
+ }
+ FMDBG("%s: %x\n", __func__, radio->registers[AUDIOCTRL]);
+ return tavarua_write_register(radio, AUDIOCTRL,
+ radio->registers[AUDIOCTRL]);
+
+}
+
+/*==============================================================
+FUNCTION: tavarua_probe
+==============================================================*/
+/**
+ Once called this functions initiates, allocates resources and registers video
+ tuner device with the v4l2 framework.
+
+ NOTE:
+ probe() should verify that the specified device hardware
+ actually exists; sometimes platform setup code can't be sure. The probing
+ can use device resources, including clocks, and device platform_data.
+
+ @param pdev: platform device to be probed.
+
+ @return On success 0 is returned, else error code.
+ -ENOMEM in low memory cases
+*/
+static int __init tavarua_probe(struct platform_device *pdev)
+{
+
+ struct marimba_fm_platform_data *tavarua_pdata;
+ struct tavarua_device *radio;
+ int retval;
+ int i;
+ FMDBG("%s: probe called\n", __func__);
+ /* private data allocation */
+ radio = kzalloc(sizeof(struct tavarua_device), GFP_KERNEL);
+ if (!radio) {
+ retval = -ENOMEM;
+ goto err_initial;
+ }
+
+ radio->marimba = platform_get_drvdata(pdev);
+ tavarua_pdata = pdev->dev.platform_data;
+ radio->pdata = tavarua_pdata;
+ radio->dev = &pdev->dev;
+ platform_set_drvdata(pdev, radio);
+
+ /* video device allocation */
+ radio->videodev = video_device_alloc();
+ if (!radio->videodev)
+ goto err_radio;
+
+ /* initial configuration */
+ memcpy(radio->videodev, &tavarua_viddev_template,
+ sizeof(tavarua_viddev_template));
+
+ /*allocate internal buffers for decoded rds and event buffer*/
+ for (i = 0; i < TAVARUA_BUF_MAX; i++) {
+ int kfifo_alloc_rc=0;
+ spin_lock_init(&radio->buf_lock[i]);
+
+ if (i == TAVARUA_BUF_RAW_RDS)
+ kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
+ rds_buf*3, GFP_KERNEL);
+ else
+ kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
+ STD_BUF_SIZE, GFP_KERNEL);
+
+ if (kfifo_alloc_rc!=0) {
+ printk(KERN_ERR "%s: failed allocating buffers %d\n",
+ __func__, kfifo_alloc_rc);
+ goto err_bufs;
+ }
+ }
+ /* init xfr status */
+ radio->users = 0;
+ radio->xfr_in_progress = 0;
+ radio->xfr_bytes_left = 0;
+ for (i = 0; i < TAVARUA_XFR_MAX; i++)
+ radio->pending_xfrs[i] = 0;
+
+ /* init transmit data */
+ radio->tx_mode = TAVARUA_TX_RT;
+ /* Init RT and PS Tx datas*/
+ radio->pty = 0;
+ radio->pi = 0;
+ radio->ps_repeatcount = 0;
+ /* init search params */
+ radio->srch_params.srch_pty = 0;
+ radio->srch_params.srch_pi = 0;
+ radio->srch_params.preset_num = 0;
+ radio->srch_params.get_list = 0;
+ /* radio initializes to low power mode */
+ radio->lp_mode = 1;
+ radio->handle_irq = 1;
+ /* init lock */
+ mutex_init(&radio->lock);
+ /* init completion flags */
+ init_completion(&radio->sync_xfr_start);
+ init_completion(&radio->sync_req_done);
+ radio->tune_req = 0;
+ /* initialize wait queue for event read */
+ init_waitqueue_head(&radio->event_queue);
+ /* initialize wait queue for raw rds read */
+ init_waitqueue_head(&radio->read_queue);
+
+ video_set_drvdata(radio->videodev, radio);
+ /*Start the worker thread for event handling and register read_int_stat
+ as worker function*/
+ INIT_DELAYED_WORK(&radio->work, read_int_stat);
+
+ /* register video device */
+ if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) {
+ printk(KERN_WARNING DRIVER_NAME
+ ": Could not register video device\n");
+ goto err_all;
+ }
+ private_data = radio;
+ return 0;
+
+err_all:
+ video_device_release(radio->videodev);
+err_bufs:
+ for (; i > -1; i--)
+ kfifo_free(&radio->data_buf[i]);
+err_radio:
+ kfree(radio);
+err_initial:
+ return retval;
+}
+
+/*==============================================================
+FUNCTION: tavarua_remove
+==============================================================*/
+/**
+ Removes the device.
+
+ @param pdev: platform device to be removed.
+
+ @return On success 0 is returned, else error code.
+*/
+static int __devexit tavarua_remove(struct platform_device *pdev)
+{
+ int i;
+ struct tavarua_device *radio = platform_get_drvdata(pdev);
+
+ /* disable irq */
+ tavarua_disable_irq(radio);
+
+ video_unregister_device(radio->videodev);
+
+ /* free internal buffers */
+ for (i = 0; i < TAVARUA_BUF_MAX; i++)
+ kfifo_free(&radio->data_buf[i]);
+
+ /* free state struct */
+ kfree(radio);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+/*
+ Platform drivers follow the standard driver model convention, where
+ discovery/enumeration is handled outside the drivers, and drivers
+ provide probe() and remove() methods. They support power management
+ and shutdown notifications using the standard conventions.
+*/
+static struct platform_driver tavarua_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "marimba_fm",
+ },
+ .remove = __devexit_p(tavarua_remove),
+ .suspend = tavarua_suspend,
+ .resume = tavarua_resume,
+}; /* platform device we're adding */
+
+
+/*************************************************************************
+ * Module Interface
+ ************************************************************************/
+
+/*==============================================================
+FUNCTION: radio_module_init
+==============================================================*/
+/**
+ Module entry - add a platform-level device.
+
+ @return Returns zero if the driver registered and bound to a device, else
+ returns a negative error code when the driver not registered.
+*/
+static int __init radio_module_init(void)
+{
+ printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
+ return platform_driver_probe(&tavarua_driver, tavarua_probe);
+}
+
+/*==============================================================
+FUNCTION: radio_module_exit
+==============================================================*/
+/**
+ Module exit - removes a platform-level device.
+
+ NOTE:
+ Note that this function will also release all memory- and port-based
+ resources owned by the device (dev->resource).
+
+ @return none.
+*/
+static void __exit radio_module_exit(void)
+{
+ platform_driver_unregister(&tavarua_driver);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+
+module_init(radio_module_init);
+module_exit(radio_module_exit);
+