/* Copyright (c) 2009-2012, 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 */
	atomic_t 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 shutdown_done;
	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;
	int enable_optimized_srch_alg;
	unsigned char spur_table_size;
	struct fm_spur_data spur_data;
	atomic_t validate_channel;
	unsigned char is_station_valid;
};

/**************************************************************************
 * 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);
static int update_spur_table(struct tavarua_device *radio);
static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
	unsigned long offset, unsigned char *buf);
static int compute_MPX_DCC(struct tavarua_device *radio, int *val);

/* 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, adj_channel_tune_req = 0;
	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;
		}

		/*
		 * Do not queue the TUNE event while validating if the station
		 * is good or not. As part of channel validation we tune to the
		 * adjacent station, measure its MPX_DCC value, then tune back
		 * to the original station and measure its MPX_DCC value.
		 * Compare the MPX_DCC values of curent and adjacent stations
		 * and decide if the channel is valid or not. During this period
		 * we should not queue the TUNE event to the upper layers.
		 */
		adj_channel_tune_req = atomic_read(&radio->validate_channel);
		if (adj_channel_tune_req) {
			complete(&radio->sync_req_done);
			FMDBG("Tune event for adjacent channel\n");
		} else {
			tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC);
			FMDBG("Queueing Tune event\n");
		}

		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 (XFR_EXT | 0x80):
			FMDBG("Set tone generator successful\n");
			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;
		case (XFR_POKE_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
		case (XFR_POKE_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
		case (XFR_PEEK_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
			FMDBG("XFR interrupt for PEEK/POKE complete\n");
			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);
}

static void fm_shutdown(struct work_struct *work)
{
	struct tavarua_device *radio = container_of(work,
					struct tavarua_device, work.work);
	FMDERR("%s: Releasing the FM I2S GPIO\n", __func__);
	if (radio->pdata->config_i2s_gpio != NULL)
		radio->pdata->config_i2s_gpio(FM_I2S_OFF);
	FMDERR("%s: Shutting down FM SOC\n", __func__);
	radio->pdata->fm_shutdown(radio->pdata);
	complete(&radio->shutdown_done);
}


/*************************************************************************
 * 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).
   */
  /* 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);
	free_irq(irq, radio);
	cancel_delayed_work_sync(&radio->work);
	flush_workqueue(radio->wqueue);
	return 0;
}

static int optimized_search_algorithm(struct tavarua_device *radio,
				int region)
{
	unsigned char adie_type_bahma;
	int retval = 0;
	unsigned int rdsMask = 0;
	unsigned char value = 0;

	adie_type_bahma = is_bahama();

	switch (region) {
	case TAVARUA_REGION_US:
	/*
	Radio band for all the 200KHz channel-spaced regions
	coming under EUROPE too, have been set as TAVARUA_REGION_US.
	*/
	FMDBG("%s: The region selected from APP is"
		" : TAVARUA_REGION_US", __func__);
	break;
	case TAVARUA_REGION_EU:
	/*
	Radio band for all the 50KHz channel-spaced regions
	coming under EUROPE, have been set as TAVARUA_REGION_EU.
	*/
	FMDBG("%s: The region selected from APP is : "
		"TAVARUA_REGION_EU", __func__);
	break;
	case TAVARUA_REGION_JAPAN:
	/*
	Radio band for the 100KHz channel-spaced JAPAN region
	has been set as TAVARUA_REGION_JAPAN.
	*/
	FMDBG("%s: The region selected from APP is"
		" : TAVARUA_REGION_JAPAN", __func__);
	break;
	case TAVARUA_REGION_JAPAN_WIDE:
	/*
	Radio band for the 50KHz channel-spaced JAPAN WIDE region
	has been set as TAVARUA_REGION_JAPAN_WIDE.
	*/
	FMDBG("%s: The region selected from APP is"
		" : TAVARUA_REGION_JAPAN_WIDE", __func__);
	break;
	case TAVARUA_REGION_OTHER:
	/*
	Radio band for all the 100KHz channel-spaced regions
	including those coming under EUROPE have been set as
	TAVARUA_REGION_OTHER.
	*/
	FMDBG("%s: The region selected from APP is"
		" : TAVARUA_REGION_OTHER", __func__);
	break;
	default:
		pr_err("%s: Should not reach here.", __func__);
		break;

	}

	/* Enable or Disable the 200KHz enforcer */
	switch (region) {
	case TAVARUA_REGION_US:
	case TAVARUA_REGION_JAPAN:
	case TAVARUA_REGION_OTHER:
	/*
	These are the 3 bands for which we need to enable the
	200KHz enforcer in ADVCTL reg.
	*/
		if (adie_type_bahma) {
			FMDBG("Adie type : Bahama\n");
			FMDBG("%s: Enabling the 200KHz enforcer for"
				" Region : %d", __func__, region);
			/*Enable the 200KHz enforcer*/
			retval = tavarua_read_registers(radio,
				ADVCTRL, 1);
			if (retval >= 0) {
				rdsMask = radio->registers[ADVCTRL];
				SET_REG_FIELD(rdsMask, ENF_SRCH200khz,
					SRCH200KHZ_OFFSET, SRCH_MASK);
				retval = tavarua_write_register(radio,
					ADVCTRL, rdsMask);
			} else
				return retval;
		} /* if Marimba do nothing */
		break;
	case TAVARUA_REGION_EU:
	case TAVARUA_REGION_JAPAN_WIDE:
	/*
	These are the 2 bands for which we need to disable the
	200KHz enforcer in ADVCTL reg.
	Radio band for all the 50KHz channel-spaced regions
	coming under EUROPE have been set as TAVARUA_REGION_EU.
	*/
		if (adie_type_bahma) {
			FMDBG("Adie type : Bahama\n");
			FMDBG("%s: Disabling the 200KHz enforcer for"
				" Region : %d", __func__, region);
			/*
			Disable 200KHz enforcer for all 50 KHz
			spaced regions.
			*/
			retval = tavarua_read_registers(radio,
				ADVCTRL, 1);
			if (retval >= 0) {
				rdsMask = radio->registers[ADVCTRL];
				SET_REG_FIELD(rdsMask, NO_SRCH200khz,
					SRCH200KHZ_OFFSET, SRCH_MASK);
				retval = tavarua_write_register(radio,
					ADVCTRL, rdsMask);
			} else
				return retval;
		} /* if Marimba do nothing */
		break;
	default:
		FMDBG("%s: Defaulting in case of Enabling/Disabling"
			"the 200KHz Enforcer", __func__);
		break;
	}

	/* Set channel spacing */
	switch (region) {
	case TAVARUA_REGION_US:
		if (adie_type_bahma) {
			FMDBG("Adie type : Bahama\n");
			/*
			Configuring all 200KHZ spaced regions as 100KHz due to
			change in the new Bahma FM SoC search algorithm.
			*/
			value = FM_CH_SPACE_100KHZ;
		} else {
			FMDBG("Adie type : Marimba\n");
			value = FM_CH_SPACE_200KHZ;
		}
		break;
	case TAVARUA_REGION_JAPAN:
	case TAVARUA_REGION_OTHER:
		if (adie_type_bahma) {
			FMDBG("Adie type : Bahama\n");
			FMDBG("%s: Configuring the channel-spacing as 50KHz"
				"for the Region : %d", __func__, region);
			/*
			Configuring all 100KHZ spaced regions as 50KHz due to
			change in the new Bahma FM SoC search algorithm.
			*/
			value = FM_CH_SPACE_50KHZ;
		} else {
			FMDBG("Adie type : Marimba\n");
			value = FM_CH_SPACE_100KHZ;
		}
		break;
	case TAVARUA_REGION_EU:
	case TAVARUA_REGION_JAPAN_WIDE:
		value = FM_CH_SPACE_50KHZ;
		break;
	default:
		FMDBG("%s: Defualting in case of Channel-Spacing", __func__);
		break;
	}

	SET_REG_FIELD(radio->registers[RDCTRL], value,
		RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK);

	return retval;
}
/*************************************************************************
 * 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 int rdsMask = 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;
	unsigned char adie_type_bahma;

	adie_type_bahma = is_bahama();

	/* Set freq band */
	if (region == TAVARUA_REGION_JAPAN)
		SET_REG_FIELD(radio->registers[RDCTRL], 1,
			RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);
	else
		SET_REG_FIELD(radio->registers[RDCTRL], 0,
			RDCTRL_BAND_OFFSET, RDCTRL_BAND_MASK);

	/* Set De-emphasis and soft band range*/
	SET_REG_FIELD(radio->registers[RDCTRL], radio->region_params.emphasis,
		RDCTRL_DEEMPHASIS_OFFSET, RDCTRL_DEEMPHASIS_MASK);

	/* set RDS standard */
	SET_REG_FIELD(radio->registers[RDSCTRL], radio->region_params.rds_std,
		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) {
		FMDERR("Failed to set RDS/RBDS standard\n");
		return retval;
	}

	/* Set the lower and upper band limits*/
	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;

	xfr_buf[0] = RSH_DATA(band_low, 8);
	xfr_buf[1] = GET_ABS_VAL(band_low);
	xfr_buf[2] = RSH_DATA(band_high, 8);
	xfr_buf[3] = GET_ABS_VAL(band_high);
	xfr_buf[4] = 0; /* Active LOW */
	retval = sync_write_xfr(radio, RADIO_CONFIG, xfr_buf);
	if (retval < 0) {
		FMDERR("Could not set regional settings\n");
		return retval;
	}
	radio->region_params.region = region;

	/* Check for the FM Algorithm used */
	if (radio->enable_optimized_srch_alg) {
		FMDBG("Optimized Srch Algorithm!!!");
		optimized_search_algorithm(radio, region);
	} else {
		FMDBG("Native Srch Algorithm!!!");
		/* Enable/Disable the 200KHz enforcer */
		switch (region) {
		case TAVARUA_REGION_US:
			if (adie_type_bahma) {
				FMDBG("Adie type : Bahama\n");
				/*Enable the 200KHz enforcer*/
				retval = tavarua_read_registers(radio,
					ADVCTRL, 1);
				if (retval >= 0) {
					rdsMask = radio->registers[ADVCTRL];
					SET_REG_FIELD(rdsMask, ENF_SRCH200khz,
						SRCH200KHZ_OFFSET, SRCH_MASK);
					retval = tavarua_write_register(radio,
						ADVCTRL, rdsMask);
				} else
					return retval;
			} /* if Marimba do nothing */
			break;
		case TAVARUA_REGION_EU:
		case TAVARUA_REGION_JAPAN:
		case TAVARUA_REGION_JAPAN_WIDE:
		default:
			if (adie_type_bahma) {
				FMDBG("Adie type : Bahama\n");
				/*
				Disable 200KHz enforcer for all 100/50 KHz
				spaced regions.
				*/
				retval = tavarua_read_registers(radio,
					ADVCTRL, 1);
				if (retval >= 0) {
					rdsMask = radio->registers[ADVCTRL];
					SET_REG_FIELD(rdsMask, NO_SRCH200khz,
						SRCH200KHZ_OFFSET, SRCH_MASK);
					retval = tavarua_write_register(radio,
						ADVCTRL, rdsMask);
				} else
					return retval;
			} /* if Marimba do nothing */
			break;
		}

		/* Set channel spacing */
		if (region == TAVARUA_REGION_US) {
			if (adie_type_bahma) {
				FMDBG("Adie type : Bahama\n");
				/*
				Configuring all 200KHZ spaced regions as
				100KHz due to change in the new Bahma
				FM SoC search algorithm.
				*/
				value = FM_CH_SPACE_100KHZ;
			} else {
				FMDBG("Adie type : Marimba\n");
				value = FM_CH_SPACE_200KHZ;
			}
		} else {
			/*
			Set the channel spacing as configured from
			the upper layers.
			*/
			value = radio->region_params.spacing;
		}
		SET_REG_FIELD(radio->registers[RDCTRL], value,
			RDCTRL_CHSPACE_OFFSET, RDCTRL_CHSPACE_MASK);

	}

	/* Write the config values into RDCTL register */
	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;
	}

	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;

	INIT_DELAYED_WORK(&radio->work, read_int_stat);
	if (!atomic_dec_and_test(&radio->users)) {
		pr_err("%s: Device already in use."
			"Try again later", __func__);
		atomic_inc(&radio->users);
		return -EBUSY;
	}

	/* 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;
	atomic_inc(&radio->users);
	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) {
		pr_err("%s: Radio device not available...", __func__);
		return -ENODEV;
	}

	FMDBG("In %s", __func__);

	FMDBG("%s, Disabling the 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;
	}

	/* disable radio ctrl */
	retval = tavarua_write_register(radio, RDCTRL, 0x00);
	if (retval < 0) {
		printk(KERN_ERR "%s: failed to disable FM\n", __func__);
		return retval;
	}

	init_completion(&radio->shutdown_done);

	bahama_present = is_bahama();

	if (bahama_present == -ENODEV)
		return -ENODEV;

	INIT_DELAYED_WORK(&radio->work, fm_shutdown);

	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__);
			goto exit;
		}
		/* 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__);
			goto exit;
		}
	} 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__);
		goto exit;
	}
exit:
	FMDBG("%s, Calling fm_shutdown\n", __func__);
	queue_delayed_work(radio->wqueue, &radio->work,
				msecs_to_jiffies(TAVARUA_DELAY/2));
	/* teardown gpio and pmic */
	marimba_set_fm_status(radio->marimba, false);
	wait_for_completion(&radio->shutdown_done);
	radio->handle_irq = 1;
	radio->lp_mode = 1;
	radio->spur_table_size = 0;
	atomic_inc(&radio->users);
	radio->marimba->mod_id = SLAVE_ID_BAHAMA;
	flush_workqueue(radio->wqueue);
	return retval;
}

/*
 * 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,
	},
	{	.id	       = V4L2_CID_PRIVATE_SET_NOTCH_FILTER,
		.type	       = V4L2_CTRL_TYPE_INTEGER,
		.name	       = "Notch filter",
		.minimum       = 0,
		.maximum       = 2,
	},

};

/*=============================================================================
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 update_spur_table(struct tavarua_device *radio)
{
	unsigned char xfr_buf[XFR_REG_NUM];
	unsigned char size = 0, tbl_size = 0;
	int index = 0, offset = 0, addr = 0x0, val = 0;
	int retval = 0, temp = 0, cnt = 0, j = 0;

	memset(xfr_buf, 0x0, XFR_REG_NUM);

	/* Read the SPUR Table Size */
	retval = xfr_rdwr_data(radio, XFR_READ, 1, SPUR_TABLE_ADDR, &tbl_size);
	if (retval < 0) {
		FMDERR("%s: Failed to read SPUR table size\n", __func__);
		return retval;
	}

	/* Calculate the new SPUR Register address */
	val = addr = (SPUR_TABLE_START_ADDR + (tbl_size * 3));

	/* Save the SPUR Table length configured by user*/
	temp = radio->spur_table_size;

	/* COnfigure the new spur table length */
	size = (radio->spur_table_size + tbl_size);
	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, SPUR_TABLE_ADDR, &size);
	if (retval < 0) {
		FMDERR("%s: Failed to configure SPUR table size\n", __func__);
		return retval;
	}

	/* Program the spur table entries */
	for (cnt = 0; cnt < (temp / 4); cnt++) {
		offset  = 0;
		for (j = 0; j < 4; j++) {
			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
				radio->spur_data.freq[index]), 1);
			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
				radio->spur_data.freq[index]), 0);
			xfr_buf[offset++] =
				radio->spur_data.rmssi[index];
			index++;
		}
		retval = xfr_rdwr_data(radio, XFR_WRITE, (SPUR_DATA_SIZE * 4),
			addr, xfr_buf);
		if (retval < 0) {
			FMDERR("%s: Failed to program SPUR frequencies\n",
				__func__);
			return retval;
		}
		addr += (SPUR_DATA_SIZE * 4);
	}

	/* Program the additional SPUR Frequencies */
	temp = radio->spur_table_size;
	temp = (temp % 4);
	if (temp > 0) {
		offset = 0;
		for (j = 0; j < temp; j++) {
			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
				radio->spur_data.freq[index]), 1);
			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
				radio->spur_data.freq[index]), 0);
			xfr_buf[offset++] =
				radio->spur_data.rmssi[index];
			index++;
		}
		size   = (temp * SPUR_DATA_SIZE);
		retval = xfr_rdwr_data(radio, XFR_WRITE, size, addr, xfr_buf);
		if (retval < 0) {
			FMDERR("%s: Failed to program SPUR frequencies\n",
				__func__);
			return retval;
		}
	}

	return retval;
}

static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
	unsigned long offset, unsigned char *buf) {

	unsigned char xfr_buf[XFR_REG_NUM + 1];
	int retval = 0, temp = 0;

	/* zero initialize the buffer */
	memset(xfr_buf, 0x0, XFR_REG_NUM);

	/* save the 'size' parameter */
	temp = size;

	/* Populate the XFR bytes */
	xfr_buf[XFR_MODE_OFFSET]     = LSH_DATA(size, 1);
	xfr_buf[XFR_ADDR_MSB_OFFSET] = GET_FREQ(offset, 1);
	xfr_buf[XFR_ADDR_LSB_OFFSET] = GET_FREQ(offset, 0);
	if (op == XFR_READ) {
		if (size > XFR_REG_NUM) {
			FMDERR("%s: Cant read more than 16 bytes\n", __func__);
			return -EINVAL;
		}
		xfr_buf[XFR_MODE_OFFSET] |= (XFR_PEEK_MODE);
		size = 3;
	} else if (op == XFR_WRITE) {
		if (size > (XFR_REG_NUM - 2)) {
			FMDERR("%s: Cant write more than 14 bytes\n", __func__);
			return -EINVAL;
		}
		xfr_buf[XFR_MODE_OFFSET] |= (XFR_POKE_MODE);
		memcpy(&xfr_buf[XFR_DATA_OFFSET], buf, size);
		size += 3;
	}

	/* Perform the XFR READ/WRITE operation */
	init_completion(&radio->sync_req_done);
	retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, size);
	if (retval < 0) {
		FMDERR("%s: Failed to perform XFR operation\n", __func__);
		return retval;
	}

	/*Wait for the XFR interrupt */
	if (!wait_for_completion_timeout(&radio->sync_req_done,
		msecs_to_jiffies(WAIT_TIMEOUT))) {
		FMDERR("Timeout: No XFR interrupt");
		return -ETIMEDOUT;
	}

	/*
	 * For XFR READ operation save the XFR data provided by the SOC.
	 * Firmware reads the data from the address specified and places
	 * them in to the registers XFRDAT0-XFRDAT15 which the host can read.
	 */
	size = temp;
	if (op == XFR_READ) {
		retval = tavarua_read_registers(radio, XFRDAT0, size);
		if (retval < 0) {
			FMDERR("%s: Failed to read the XFR data\n", __func__);
			return retval;
		}
		if (buf != NULL)
			memcpy(buf, &radio->registers[XFRDAT0], size);
		else {
			FMDERR("%s: No buffer to copy XFR data\n", __func__);
			return -EINVAL;
		}
	}

	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;
	int cnt = 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];
		msleep(TAVARUA_DELAY*5);
		break;
	case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA:
		retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
		if (retval < 0) {
			FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
			FMDERR("sync_read_xfr [retval=%d]\n", retval);
			break;
		}
		ctrl->value = (unsigned char)xfr_buf[4];
		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;
	case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD:
		size = 0x04;
		xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
		xfr_buf[1] = ON_CHANNEL_TH_MSB;
		xfr_buf[2] = ON_CHANNEL_TH_LSB;
		retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
		if (retval < 0) {
			pr_err("%s: Failed to write\n", __func__);
			return retval;
		}
		/*Wait for the XFR interrupt */
		msleep(TAVARUA_DELAY*10);
		retval = tavarua_read_registers(radio, XFRDAT0, 4);
		if (retval < 0) {
			pr_err("%s: On Ch. DET: Read failure\n", __func__);
			return retval;
		}
		for (cnt = 0; cnt < 4; cnt++)
			FMDBG("On-Channel data set is : 0x%x\t",
				(int)radio->registers[XFRDAT0+cnt]);

		ctrl->value =	LSH_DATA(radio->registers[XFRDAT0],   24) |
				LSH_DATA(radio->registers[XFRDAT0+1], 16) |
				LSH_DATA(radio->registers[XFRDAT0+2],  8) |
				(radio->registers[XFRDAT0+3]);
		FMDBG("The On Channel Threshold value is : 0x%x", ctrl->value);
		break;
	case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD:
		size = 0x04;
		xfr_buf[0] = (XFR_PEEK_MODE | (size << 1));
		xfr_buf[1] = OFF_CHANNEL_TH_MSB;
		xfr_buf[2] = OFF_CHANNEL_TH_LSB;
		retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, 3);
		if (retval < 0) {
			pr_err("%s: Failed to write\n", __func__);
			return retval;
		}
		/*Wait for the XFR interrupt */
		msleep(TAVARUA_DELAY*10);
		retval = tavarua_read_registers(radio, XFRDAT0, 4);
		if (retval < 0) {
			pr_err("%s: Off Ch. DET: Read failure\n", __func__);
			return retval;
		}
		for (cnt = 0; cnt < 4; cnt++)
			FMDBG("Off-channel data set is : 0x%x\t",
				(int)radio->registers[XFRDAT0+cnt]);

		ctrl->value =	LSH_DATA(radio->registers[XFRDAT0],   24) |
				LSH_DATA(radio->registers[XFRDAT0+1], 16) |
				LSH_DATA(radio->registers[XFRDAT0+2],  8) |
				(radio->registers[XFRDAT0+3]);
		FMDBG("The Off Channel Threshold value is : 0x%x", ctrl->value);
		break;
	/*
	 * These IOCTL's are place holders to keep the
	 * driver compatible with change in frame works for IRIS
	 */
	case V4L2_CID_PRIVATE_SINR_THRESHOLD:
	case V4L2_CID_PRIVATE_SINR_SAMPLES:
	case V4L2_CID_PRIVATE_IRIS_GET_SINR:
		retval = 0;
		break;
	case V4L2_CID_PRIVATE_VALID_CHANNEL:
		ctrl->value = radio->is_station_valid;
		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, size = 0, cnt = 0;
	unsigned char value;
	unsigned char xfr_buf[XFR_REG_NUM];
	unsigned char tx_data[XFR_REG_NUM];
	unsigned char dis_buf[XFR_REG_NUM];
	unsigned int freq = 0, mpx_dcc = 0;
	unsigned long curr = 0, prev = 0;

	memset(xfr_buf, 0x0, 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) {
				/* Enabling 'SoftMute' & 'SignalBlending' */
				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]) {
			radio->spur_table_size = 0;
			FMDBG("%s: turning off...\n", __func__);
			tavarua_write_register(radio, RDCTRL, ctrl->value);
			/* flush the event and work queues */
			kfifo_reset(&radio->data_buf[TAVARUA_BUF_EVENTS]);
			flush_workqueue(radio->wqueue);
			/*
			 * queue the READY event from the host side
			 * in case of FM off
			 */
			tavarua_q_event(radio, TAVARUA_EVT_RADIO_DISABLED);

			FMDBG("%s, Disable All Interrupts\n", __func__);
			/* disable irq */
			dis_buf[STATUS_REG1] = 0x00;
			dis_buf[STATUS_REG2] = 0x00;
			dis_buf[STATUS_REG3] = TRANSFER;
			retval = sync_write_xfr(radio, INT_CTRL, dis_buf);
			if (retval < 0) {
				pr_err("%s: failed to disable"
						"Interrupts\n", __func__);
				return retval;
			}
		}
		break;
	case V4L2_CID_PRIVATE_TAVARUA_SET_AUDIO_PATH:
		FMDBG("Setting audio path ...\n");
		if (ctrl->value == FM_DIGITAL_PATH) {
			FMDBG("Digital audio path enabled ...\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);
			}
		} else if (ctrl->value == FM_ANALOG_PATH) {
			FMDBG("Analog audio path enabled ...\n");
			retval = tavarua_set_audio_path(
				TAVARUA_AUDIO_OUT_DIGITAL_OFF,
				TAVARUA_AUDIO_OUT_ANALOG_ON);
			if (retval < 0) {
				FMDERR("Error in tavarua_set_audio_path"
					" %d\n", retval);
			}
		}
		break;
	case V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM:
		radio->enable_optimized_srch_alg = ctrl->value;
		FMDBG("V4L2_CID_PRIVATE_TAVARUA_SRCH_ALGORITHM : %d",
			radio->enable_optimized_srch_alg);
		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;
		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_AF_JUMP:
		retval = tavarua_read_registers(radio, ADVCTRL, 1);
		SET_REG_FIELD(radio->registers[ADVCTRL], ctrl->value,
			RDSAF_OFFSET, RDSAF_MASK);
		msleep(TAVARUA_DELAY*5);
		retval = tavarua_write_register(radio,
			ADVCTRL, radio->registers[ADVCTRL]);
		msleep(TAVARUA_DELAY*5);
		break;
	case V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA:
		retval = sync_read_xfr(radio, RX_CONFIG, xfr_buf);
		if (retval < 0) {
			FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
			FMDERR("sync_read_xfr [retval=%d]\n", retval);
			break;
		}
		xfr_buf[4] = (unsigned char)ctrl->value;
		retval = sync_write_xfr(radio, RX_CONFIG, xfr_buf);
		if (retval < 0) {
			FMDERR("V4L2_CID_PRIVATE_TAVARUA_RSSI_DELTA]\n");
			FMDERR("sync_write_xfr [retval=%d]\n", retval);
			break;
		}
		break;
	case V4L2_CID_PRIVATE_TAVARUA_HLSI:
		retval = tavarua_read_registers(radio, RDCTRL, 1);
		SET_REG_FIELD(radio->registers[RDCTRL], ctrl->value,
			RDCTRL_HLSI_OFFSET, RDCTRL_HLSI_MASK);
		retval = tavarua_write_register(radio, RDCTRL,
			radio->registers[RDCTRL]);
		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;
	case V4L2_CID_PRIVATE_INTF_LOW_THRESHOLD:
		size = 0x04;
		/* Poking the value of ON Channel Threshold value */
		xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
		xfr_buf[1] = ON_CHANNEL_TH_MSB;
		xfr_buf[2] = ON_CHANNEL_TH_LSB;
		/* Data to be poked into the register */
		xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24;
		xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16;
		xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8;
		xfr_buf[6] = (ctrl->value & 0x000000FF);

		for (cnt = 3; cnt < 7; cnt++) {
			FMDBG("On-channel data to be poked is : %d",
				(int)xfr_buf[cnt]);
		}

		retval = tavarua_write_registers(radio, XFRCTRL,
				xfr_buf, size+3);
		if (retval < 0) {
			pr_err("%s: Failed to write\n", __func__);
			return retval;
		}
		/*Wait for the XFR interrupt */
		msleep(TAVARUA_DELAY*10);
		break;
	case V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD:
		size = 0x04;
		/* Poking the value of OFF Channel Threshold value */
		xfr_buf[0] = (XFR_POKE_MODE | (size << 1));
		xfr_buf[1] = OFF_CHANNEL_TH_MSB;
		xfr_buf[2] = OFF_CHANNEL_TH_LSB;
		/* Data to be poked into the register */
		xfr_buf[3] = (ctrl->value & 0xFF000000) >> 24;
		xfr_buf[4] = (ctrl->value & 0x00FF0000) >> 16;
		xfr_buf[5] = (ctrl->value & 0x0000FF00) >> 8;
		xfr_buf[6] = (ctrl->value & 0x000000FF);

		for (cnt = 3; cnt < 7; cnt++) {
			FMDBG("Off-channel data to be poked is : %d",
				(int)xfr_buf[cnt]);
		}

		retval = tavarua_write_registers(radio, XFRCTRL,
				xfr_buf, size+3);
		if (retval < 0) {
			pr_err("%s: Failed to write\n", __func__);
			return retval;
		}
		/*Wait for the XFR interrupt */
		msleep(TAVARUA_DELAY*10);
		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;
	case V4L2_CID_PRIVATE_SOFT_MUTE:
		radio->registers[IOCTRL] &= ~(IOC_SFT_MUTE);
		if (ctrl->value)
			radio->registers[IOCTRL] |= IOC_SFT_MUTE;
		retval = tavarua_write_register(radio, IOCTRL,
					radio->registers[IOCTRL]);
		if (retval < 0)
			FMDERR("Failed to enable/disable SMute\n");
		break;
	/*These IOCTL's are place holders to keep the
	driver compatible with change in frame works for IRIS */
	case V4L2_CID_PRIVATE_RIVA_ACCS_ADDR:
	case V4L2_CID_PRIVATE_RIVA_ACCS_LEN:
	case V4L2_CID_PRIVATE_RIVA_PEEK:
	case V4L2_CID_PRIVATE_RIVA_POKE:
	case V4L2_CID_PRIVATE_SSBI_ACCS_ADDR:
	case V4L2_CID_PRIVATE_SSBI_PEEK:
	case V4L2_CID_PRIVATE_SSBI_POKE:
	case V4L2_CID_PRIVATE_RDS_GRP_COUNTERS:
	case V4L2_CID_PRIVATE_SET_NOTCH_FILTER:
	case V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION:
	case V4L2_CID_PRIVATE_SINR_THRESHOLD:
	case V4L2_CID_PRIVATE_SINR_SAMPLES:
	case V4L2_CID_PRIVATE_SPUR_SELECTION:
		retval = 0;
		break;
	case V4L2_CID_PRIVATE_SPUR_FREQ:
		radio->spur_data.freq[radio->spur_table_size] =
			ctrl->value;
		break;
	case V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI:
		radio->spur_data.rmssi[radio->spur_table_size++] =
			ctrl->value;
		break;
	case V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE:
		retval = update_spur_table(radio);
		break;
	case V4L2_CID_PRIVATE_TX_TONE:
		retval = 0;
		memset(xfr_buf, 0, sizeof(xfr_buf));
		switch (ctrl->value) {
		case ONE_KHZ_LR_EQUA_0DBFS:
			xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
				= TONE_LEFT_RIGHT_CH_ENABLED;
			xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
			xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
			break;
		case ONE_KHZ_LEFTONLY_EQUA_0DBFS:
			xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
				 = TONE_LEFT_CH_ENABLED;
			xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
			break;
		case ONE_KHZ_RIGHTONLY_EQUA_0DBFS:
			xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
				 = TONE_RIGHT_CH_ENABLED;
			xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
			break;
		case ONE_KHZ_LR_EQUA_l8DBFS:
			xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
				 = (LSH_DATA(TONE_SCALE_IND_12,
						 TONE_SCALING_SHIFT)
					 | TONE_LEFT_RIGHT_CH_ENABLED);
			xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x01;
			xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x01;
			break;
		case FIFTEEN_KHZ_LR_EQUA_l8DBFS:
			xfr_buf[TONE_CHANNEL_EN_AND_SCALING_BYTE]
				 = (LSH_DATA(TONE_SCALE_IND_12,
						 TONE_SCALING_SHIFT)
					 | TONE_LEFT_RIGHT_CH_ENABLED);
			xfr_buf[TONE_LEFT_FREQ_BYTE] = 0x0F;
			xfr_buf[TONE_RIGHT_FREQ_BYTE] = 0x0F;
			break;
		default:
			retval = -1;
			FMDERR("tone generator value not valid\n");
			break;
		}
		if (retval >= 0) {
			xfr_buf[TONE_GEN_CTRL_BYTE] = 0x01;
			retval = sync_write_xfr(radio, XFR_EXT, xfr_buf);
		}
		if (retval < 0)
			FMDERR("Tone generator failed\n");
		break;
	case V4L2_CID_PRIVATE_VALID_CHANNEL:
		/* Do not notify the host of tune event */
		atomic_set(&radio->validate_channel, 1);

		FMDBG("Going into low power mode\n");
		retval = tavarua_disable_interrupts(radio);

		/*
		 * Tune to 50KHz adjacent channel. If the requested station
		 * falls in JAPAN band and on the lower band-limit, then the
		 * adjacnet channel to be considered is 50KHz to the right side
		 * of the requested station as firmware does not allows to tune
		 * to frequency outside the range: 76000KHz to 108000KHz.
		 */
		if (ctrl->value == REGION_JAPAN_STANDARD_BAND_LOW)
			freq = (ctrl->value + ADJ_CHANNEL_KHZ);
		else
			freq = (ctrl->value - ADJ_CHANNEL_KHZ);
		INIT_COMPLETION(radio->sync_req_done);
		retval = tavarua_set_freq(radio, (freq * TUNE_MULT));
		if (retval < 0) {
			FMDERR("Failed to tune to adjacent station\n");
			goto error;
		}
		if (!wait_for_completion_timeout(&radio->sync_req_done,
			msecs_to_jiffies(wait_timeout))) {
			FMDERR("Timeout: No Tune response\n");
			retval = -ETIMEDOUT;
			goto error;
		}

		/*
		 * Wait for a minimum of 100ms for the firmware
		 * to start collecting the MPX_DCC values
		 */
		msleep(TAVARUA_DELAY * 10);

		/* Compute MPX_DCC of adjacent station */
		retval = compute_MPX_DCC(radio, &mpx_dcc);
		if (retval < 0) {
			FMDERR("Failed to get MPX_DCC of adjacent station\n");
			goto error;
		}
		/* Calculate the absolute value of MPX_DCC */
		prev = abs(mpx_dcc);

		/* Tune back to original station */
		INIT_COMPLETION(radio->sync_req_done);
		retval = tavarua_set_freq(radio, (ctrl->value * TUNE_MULT));
		if (retval < 0) {
			FMDERR("Failed to tune to requested station\n");
			goto error;
		}
		if (!wait_for_completion_timeout(&radio->sync_req_done,
			msecs_to_jiffies(wait_timeout))) {
			FMDERR("Timeout: No Tune response\n");
			retval = -ETIMEDOUT;
			goto error;
		}

		/*
		 * Wait for a minimum of 100ms for the firmware
		 * to start collecting the MPX_DCC values
		 */
		msleep(TAVARUA_DELAY * 10);

		/* Compute MPX_DCC of current station */
		retval = compute_MPX_DCC(radio, &mpx_dcc);
		if (retval < 0) {
			FMDERR("Failed to get MPX_DCC of current station\n");
			goto error;
		}
		/* Calculate the absolute value of MPX_DCC */
		curr = abs(mpx_dcc);

		FMDBG("Going into normal power mode\n");
		tavarua_setup_interrupts(radio,
			(radio->registers[RDCTRL] & 0x03));

		FMDBG("Absolute MPX_DCC of current station  : %lu\n", curr);
		FMDBG("Absolute MPX_DCC of adjacent station : %lu\n", prev);

		/*
		 * For valid stations, the absolute MPX_DCC value will be within
		 * the range 0 <= MPX_DCC <= 12566 and the MPX_DCC value of the
		 * adjacent station will be greater than 20,000.
		 */
		if ((curr <= MPX_DCC_LIMIT) &&
			(prev > MPX_DCC_UPPER_LIMIT)) {
			FMDBG("%d KHz is A VALID STATION!\n", ctrl->value);
			radio->is_station_valid = VALID_CHANNEL;
		} else {
			FMDBG("%d KHz is NOT A VALID STATION!\n", ctrl->value);
			radio->is_station_valid = INVALID_CHANNEL;
		}
error:
		atomic_set(&radio->validate_channel, 0);
		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;
}

static int compute_MPX_DCC(struct tavarua_device *radio, int *val)
{

	int DCC = 0, retval = 0;
	int MPX_DCC[3];
	unsigned char value;
	unsigned char xfr_buf[XFR_REG_NUM];

	/* Freeze the MPX_DCC value from changing */
	value = CTRL_ON;
	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, MPX_DCC_BYPASS_REG, &value);
	if (retval < 0) {
		FMDERR("%s: Failed to freeze MPX_DCC\n", __func__);
		return retval;
	}

	/* Measure the MPX_DCC of current station. */
	retval = xfr_rdwr_data(radio, XFR_READ, 3, MPX_DCC_DATA_REG, xfr_buf);
	if (retval < 0) {
		FMDERR("%s: Failed to read MPX_DCC\n", __func__);
		return retval;
	}
	MPX_DCC[0] = xfr_buf[0];
	MPX_DCC[1] = xfr_buf[1];
	MPX_DCC[2] = xfr_buf[2];
	/*
	 * 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 part of the 20-bit signed MPX_DCC
	 */
	DCC = (LSH_DATA(MPX_DCC[2], 16) | LSH_DATA(MPX_DCC[1], 8) | MPX_DCC[0]);

	/* if bit-19 is '1',set remaining bits to '1' & make it -tive */
	if (DCC & 0x00080000)
		DCC |= 0xFFF00000;

	*val = DCC;

	/* Un-freeze the MPX_DCC value */
	value = CTRL_OFF;
	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, 0x88C0, &value);
	if (retval < 0) {
		FMDERR("%s: Failed to un-freeze MPX_DCC\n", __func__);
		return retval;
	}

	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 = -1;
	unsigned char buf_fifo[STD_BUF_SIZE] = {0};
	struct kfifo *data_fifo = NULL;
	unsigned char *buf = NULL;
	unsigned int len = 0, retval = -1;

	if ((radio == NULL) || (buffer == NULL)) {
		FMDERR("radio/buffer is NULL\n");
		return -ENXIO;
	}
	buf_type = buffer->index;
	buf = (unsigned char *)buffer->m.userptr;
	len = buffer->length;
	FMDBG("%s: requesting buffer %d\n", __func__, buf_type);

	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;
	}
	if (len <= STD_BUF_SIZE) {
		buffer->bytesused = kfifo_out_locked(data_fifo, &buf_fifo[0],
					len, &radio->buf_lock[buf_type]);
	} else {
		FMDERR("kfifo_out_locked can not use len more than 128\n");
		return -EINVAL;
	}
	retval = copy_to_user(buf, &buf_fifo[0], buffer->bytesused);
	if (retval > 0) {
		FMDERR("Failed to copy %d bytes of data\n", retval);
		return -EAGAIN;
	}

	return retval;
}

/*=============================================================================
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) {
		users = atomic_read(&radio->users);
		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) {
		users = atomic_read(&radio->users);

		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;
	int retval = 0;
	if (!radio)
		return -ENOMEM;
	/* RX */
	FMDBG("%s: digital: %d analog: %d\n", __func__, digital_on, analog_on);
	if ((radio->pdata != NULL) && (radio->pdata->config_i2s_gpio != NULL)) {
		if (digital_on) {
			retval = radio->pdata->config_i2s_gpio(FM_I2S_ON);
			if (retval) {
				pr_err("%s: config_i2s_gpio failed\n",
								__func__);
			}
		} else {
			retval = radio->pdata->config_i2s_gpio(FM_I2S_OFF);
			if (retval) {
				pr_err("%s: config_i2s_gpio failed\n",
								__func__);
			}
		}
	}

	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 if (i == TAVARUA_BUF_RT_RDS)
			kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
				STD_BUF_SIZE * 2, 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;
		}
	}
	/* initializing the device count  */
	atomic_set(&radio->users, 1);
	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*/
	radio->wqueue  = create_singlethread_workqueue("kfmradio");
	if (!radio->wqueue)
		return -ENOMEM;

	/* 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);

	destroy_workqueue(radio->wqueue);

	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);

