/* Copyright (c) 2015-2016, The Linux Foundation. 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.
 */


#define DRIVER_NAME "radio-silabs"
#define DRIVER_CARD "Silabs FM Radio Receiver"
#define DRIVER_DESC "Driver for Silabs FM Radio receiver"

#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/gpio.h>
#include <linux/interrupt.h>

/* kernel includes */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/videodev2.h>
#include <linux/mutex.h>
#include <linux/unistd.h>
#include <linux/atomic.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
#include <linux/pinctrl/consumer.h>
#include <linux/clk.h>
#include <linux/of_gpio.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-device.h>
#include "radio-silabs.h"

struct silabs_fm_device {
	struct i2c_client *client;
	struct pwm_device *pwm;
	bool is_len_gpio_valid;
	struct fm_power_vreg_data *dreg;
	struct fm_power_vreg_data *areg;
	int reset_gpio;
	int int_gpio;
	int status_gpio;
	struct pinctrl *fm_pinctrl;
	struct pinctrl_state *gpio_state_active;
	struct pinctrl_state *gpio_state_suspend;
	struct video_device *videodev;
	struct v4l2_device v4l2_dev;
	/* driver management */
	atomic_t users;
	/* To send commands*/
	u8 write_buf[WRITE_REG_NUM];
	/* TO read events, data*/
	u8 read_buf[READ_REG_NUM];
	/*RDS buffers + Radio event buffer*/
	struct kfifo data_buf[SILABS_FM_BUF_MAX];
	struct silabs_fm_recv_conf_req recv_conf;
	struct completion sync_req_done;
	/* for the first tune, we need to set properties for digital audio. */
	u8 first_tune;
	int tune_req;
	/* 1 if tune is pending, 2 if seek is pending, 0 otherwise.*/
	u8 seek_tune_status;
	/* command that is being sent to chip. */
	u8 cmd;
	u8 antenna;
	u8 g_search_mode;
	bool is_search_cancelled;
	unsigned int mode;
	/* regional settings */
	enum silabs_region_t region;
	/* power mode */
	bool lp_mode;
	int handle_irq;
	/* global lock */
	struct mutex lock;
	/* buffer locks*/
	spinlock_t buf_lock[SILABS_FM_BUF_MAX];
	/* work queue */
	struct workqueue_struct *wqueue;
	struct workqueue_struct *wqueue_scan;
	struct workqueue_struct *wqueue_af;
	struct workqueue_struct *wqueue_rds;
	struct work_struct rds_worker;
	struct delayed_work work;
	struct delayed_work work_scan;
	struct delayed_work work_af;
	/* wait queue for blocking event read */
	wait_queue_head_t event_queue;
	/* wait queue for raw rds read */
	wait_queue_head_t read_queue;
	int irq;
	int status_irq;
	int tuned_freq_khz;
	int dwell_time_sec;
	u16 pi; /* PI of tuned channel */
	u8 pty; /* programe type of the tuned channel */
	u16 block[NO_OF_RDS_BLKS];
	u8 rt_display[MAX_RT_LEN];   /* RT that will be displayed */
	u8 rt_tmp0[MAX_RT_LEN]; /* high probability RT */
	u8 rt_tmp1[MAX_RT_LEN]; /* low probability RT */
	u8 rt_cnt[MAX_RT_LEN];  /* high probability RT's hit count */
	u8 rt_flag;          /* A/B flag of RT */
	bool valid_rt_flg;     /* validity of A/B flag */
	u8 ps_display[MAX_PS_LEN];    /* PS that will be displayed */
	u8 ps_tmp0[MAX_PS_LEN]; /* high probability PS */
	u8 ps_tmp1[MAX_PS_LEN]; /* low probability PS */
	u8 ps_cnt[MAX_PS_LEN];  /* high probability PS's hit count */
	u8 rt_plus_carrier;
	u8 ert_carrier;
	u8 ert_buf[MAX_ERT_LEN];
	u8 ert_len;
	u8 c_byt_pair_index;
	u8 utf_8_flag;
	u8 rt_ert_flag;
	u8 formatting_dir;
	bool is_af_jump_enabled;
	bool is_af_tune_in_progress;
	u16 af_avg_th;
	u8 af_wait_timer;
	u8 af_rssi_th; /* allowed rssi is 0-127 */
	u8 rssi_th; /* 0 - 127 */
	u8 sinr_th; /* 0 - 127 */
	u8 rds_fifo_cnt; /* 0 - 25 */
	struct silabs_af_info af_info1;
	struct silabs_af_info af_info2;
	struct silabs_srch_list_compl srch_list;
};

static int silabs_fm_request_irq(struct silabs_fm_device *radio);
static int tune(struct silabs_fm_device *radio, u32 freq);
static int silabs_seek(struct silabs_fm_device *radio, int dir, int wrap);
static int cancel_seek(struct silabs_fm_device *radio);
static int configure_interrupts(struct silabs_fm_device *radio, u8 val);
static void silabs_fm_q_event(struct silabs_fm_device *radio,
				enum silabs_evt_t event);

static bool is_valid_rssi(int rssi)
{
	if ((rssi >= MIN_RSSI) &&
		(rssi <= MAX_RSSI))
		return true;
	else
		return false;
}

static bool is_valid_sinr(int sinr)
{
	if ((sinr >= MIN_SNR) &&
		(sinr <= MAX_SNR))
		return true;
	else
		return false;
}

static bool is_valid_rds_fifo_cnt(int cnt)
{
	if ((cnt >= MIN_RDS_FIFO_CNT) &&
		(cnt <= MAX_RDS_FIFO_CNT))
		return true;
	else
		return false;
}

static int silabs_fm_i2c_read(struct silabs_fm_device *radio, u8 len)
{
	int i = 0, retval = 0;
	struct i2c_msg msgs[1];

	msgs[0].addr = radio->client->addr;
	msgs[0].len = len;
	msgs[0].flags = I2C_M_RD;
	msgs[0].buf = (u8 *)radio->read_buf;

	for (i = 0; i < 2; i++) {
		retval = i2c_transfer(radio->client->adapter, msgs, 1);
		if (retval == 1)
			break;
	}

	return retval;
}

static int silabs_fm_i2c_write(struct silabs_fm_device *radio, u8 len)
{
	struct i2c_msg msgs[1];
	int i = 0, retval = 0;

	msgs[0].addr = radio->client->addr;
	msgs[0].len = len;
	msgs[0].flags = 0;
	msgs[0].buf = (u8 *)radio->write_buf;

	for (i = 0; i < 2; i++) {
		retval = i2c_transfer(radio->client->adapter, msgs, 1);
		if (retval == 1)
			break;
	}

	return retval;
}

static int silabs_fm_pinctrl_select(struct silabs_fm_device *radio, bool on)
{
	struct pinctrl_state *pins_state;
	int ret;

	pins_state = on ? radio->gpio_state_active
			: radio->gpio_state_suspend;

	if (!IS_ERR_OR_NULL(pins_state)) {
		ret = pinctrl_select_state(radio->fm_pinctrl, pins_state);
		if (ret) {
			FMDERR("%s: cannot set pin state\n", __func__);
			return ret;
		}
	} else {
		FMDERR("%s: not a valid %s pin state\n", __func__,
				on ? "pmx_fm_active" : "pmx_fm_suspend");
	}

	return 0;
}

static int fm_configure_gpios(struct silabs_fm_device *radio, bool on)
{
	int rc = 0;
	int fm_reset_gpio = radio->reset_gpio;
	int fm_int_gpio = radio->int_gpio;
	int fm_status_gpio = radio->status_gpio;

	if (on) {
		/*
		 * Turn ON sequence
		 * GPO1/status gpio configuration.
		 * Keep the GPO1 to high till device comes out of reset.
		 */
		if (fm_status_gpio > 0) {
			FMDERR("status gpio is provided, setting it to high\n");
			rc = gpio_direction_output(fm_status_gpio, 1);
			if (rc) {
				FMDERR("unable to set gpio %d direction(%d)\n",
				fm_status_gpio, rc);
				return rc;
			}
			/* Wait for the value to take effect on gpio. */
			msleep(100);
		}

		/*
		 * GPO2/Interrupt gpio configuration.
		 * Keep the GPO2 to low till device comes out of reset.
		 */
		rc = gpio_direction_output(fm_int_gpio, 0);
		if (rc) {
			FMDERR("unable to set the gpio %d direction(%d)\n",
			fm_int_gpio, rc);
			return rc;
		}
		/* Wait for the value to take effect on gpio. */
		msleep(100);

		/*
		 * Reset pin configuration.
		 * write "0'' to make sure the chip is in reset.
		 */
		rc = gpio_direction_output(fm_reset_gpio, 0);
		if (rc) {
			FMDERR("Unable to set direction\n");
			return rc;
		}
		/* Wait for the value to take effect on gpio. */
		msleep(100);
		/* write "1" to bring the chip out of reset.*/
		rc = gpio_direction_output(fm_reset_gpio, 1);
		if (rc) {
			FMDERR("Unable to set direction\n");
			return rc;
		}
		/* Wait for the value to take effect on gpio. */
		msleep(100);

		rc = gpio_direction_input(fm_int_gpio);
		if (rc) {
			FMDERR("unable to set the gpio %d direction(%d)\n",
						fm_int_gpio, rc);
			return rc;
		}
		/* Wait for the value to take effect on gpio. */
		msleep(100);

		if (fm_status_gpio > 0) {
			FMDERR("setting status gpio as input\n");
			rc = gpio_direction_input(fm_status_gpio);
			if (rc) {
				FMDERR("unable to set gpio %d direction(%d)\n",
				fm_status_gpio, rc);
				return rc;
			}
			/* Wait for the value to take effect on gpio. */
			msleep(100);
		}


	} else {
		/*Turn OFF sequence */
		gpio_set_value(fm_reset_gpio, 0);

		rc = gpio_direction_input(fm_reset_gpio);
		if (rc)
			FMDERR("Unable to set direction\n");
		/* Wait for some time for the value to take effect. */
		msleep(100);
		if (fm_status_gpio > 0) {
			rc = gpio_direction_input(fm_status_gpio);
			if (rc)
				FMDERR("Unable to set dir for status gpio\n");
			msleep(100);
		}
	}
	return rc;
}

static int silabs_fm_areg_cfg(struct silabs_fm_device *radio, bool on)
{
	int rc = 0;
	struct fm_power_vreg_data *vreg;

	vreg = radio->areg;
	if (!vreg) {
		FMDERR("In %s, areg is NULL\n", __func__);
		return rc;
	}
	if (on) {
		FMDBG("vreg is : %s", vreg->name);
		if (vreg->set_voltage_sup) {
			rc = regulator_set_voltage(vreg->reg,
						vreg->low_vol_level,
						vreg->high_vol_level);
			if (rc < 0) {
				FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
				return rc;
			}
		}
		rc = regulator_enable(vreg->reg);
		if (rc < 0) {
			FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
			if (vreg->set_voltage_sup) {
				regulator_set_voltage(vreg->reg,
						0,
						vreg->high_vol_level);
			}
			return rc;
		}
		vreg->is_enabled = true;

	} else {
		rc = regulator_disable(vreg->reg);
		if (rc < 0) {
			FMDERR("reg disable(%s) fail rc=%d\n", vreg->name, rc);
			return rc;
		}
		vreg->is_enabled = false;

		if (vreg->set_voltage_sup) {
			/* Set the min voltage to 0 */
			rc = regulator_set_voltage(vreg->reg,
						0,
						vreg->high_vol_level);
			if (rc < 0) {
				FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
				return rc;
			}
		}
	}
	return rc;
}

static int silabs_fm_dreg_cfg(struct silabs_fm_device *radio, bool on)
{
	int rc = 0;
	struct fm_power_vreg_data *vreg;

	vreg = radio->dreg;
	if (!vreg) {
		FMDERR("In %s, dreg is NULL\n", __func__);
		return rc;
	}

	if (on) {
		FMDBG("vreg is : %s", vreg->name);
		if (vreg->set_voltage_sup) {
			rc = regulator_set_voltage(vreg->reg,
						vreg->low_vol_level,
						vreg->high_vol_level);
			if (rc < 0) {
				FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
				return rc;
			}
		}

		rc = regulator_enable(vreg->reg);
		if (rc < 0) {
			FMDERR("reg enable(%s) failed.rc=%d\n", vreg->name, rc);
			if (vreg->set_voltage_sup) {
				regulator_set_voltage(vreg->reg,
						0,
						vreg->high_vol_level);
			}
			return rc;
		}
			vreg->is_enabled = true;
	} else {
		rc = regulator_disable(vreg->reg);
		if (rc < 0) {
			FMDERR("reg disable(%s) fail. rc=%d\n", vreg->name, rc);
			return rc;
		}
		vreg->is_enabled = false;

		if (vreg->set_voltage_sup) {
			/* Set the min voltage to 0 */
			rc = regulator_set_voltage(vreg->reg,
						0,
						vreg->high_vol_level);
			if (rc < 0) {
				FMDERR("set_vol(%s) fail %d\n", vreg->name, rc);
				return rc;
			}
		}
	}
	return rc;
}

static int silabs_fm_power_cfg(struct silabs_fm_device *radio, bool on)
{
	int rc = 0;

	if (on) {
		/* Turn ON sequence */
		rc = silabs_fm_dreg_cfg(radio, on);
		if (rc < 0) {
			FMDERR("In %s, dreg cfg failed %x\n", __func__, rc);
			return rc;
		}
		rc = silabs_fm_areg_cfg(radio, on);
		if (rc < 0) {
			FMDERR("In %s, areg cfg failed %x\n", __func__, rc);
			silabs_fm_dreg_cfg(radio, false);
			return rc;
		}
		/* If pinctrl is supported, select active state */
		if (radio->fm_pinctrl) {
			rc = silabs_fm_pinctrl_select(radio, true);
			if (rc)
				FMDERR("%s: error setting active pin state\n",
								__func__);
		}

		rc = fm_configure_gpios(radio, on);
		if (rc < 0) {
			FMDERR("fm_power gpio config failed\n");
			silabs_fm_dreg_cfg(radio, false);
			silabs_fm_areg_cfg(radio, false);
			return rc;
		}
	} else {
		/* Turn OFF sequence */
		rc = fm_configure_gpios(radio, on);
		if (rc < 0)
			FMDERR("fm_power gpio config failed");

		/* If pinctrl is supported, select suspend state */
		if (radio->fm_pinctrl) {
			rc = silabs_fm_pinctrl_select(radio, false);
			if (rc)
				FMDERR("%s: error setting suspend pin state\n",
								__func__);
		}
		rc = silabs_fm_dreg_cfg(radio, on);
		if (rc < 0)
			FMDERR("In %s, dreg cfg failed %x\n", __func__, rc);
		rc = silabs_fm_areg_cfg(radio, on);
		if (rc < 0)
			FMDERR("In %s, areg cfg failed %x\n", __func__, rc);
	}
	return rc;
}

static bool is_enable_rx_possible(struct silabs_fm_device *radio)
{
	bool retval = true;

	if (radio->mode == FM_OFF || radio->mode == FM_RECV)
		retval = false;

	return retval;
}

static int read_cts_bit(struct silabs_fm_device *radio)
{
	int retval = 1, i = 0;

	for (i = 0; i < CTS_RETRY_COUNT; i++) {
		memset(radio->read_buf, 0, READ_REG_NUM);

		retval = silabs_fm_i2c_read(radio, READ_REG_NUM);

		if (retval < 0) {
			FMDERR("%s: failure reading the response, error %d\n",
					__func__, retval);
			continue;
		} else
			FMDBG("%s: successfully read the response from soc\n",
					__func__);

		if (radio->read_buf[0] & ERR_BIT_MASK) {
			FMDERR("%s: error bit set\n", __func__);
			switch (radio->read_buf[1]) {
			case BAD_CMD:
				FMDERR("%s: cmd %d, error BAD_CMD\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG1:
				FMDERR("%s: cmd %d, error BAD_ARG1\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG2:
				FMDERR("%s: cmd %d, error BAD_ARG2\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG3:
				FMDERR("%s: cmd %d, error BAD_ARG3\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG4:
				FMDERR("%s: cmd %d, error BAD_ARG4\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG5:
				FMDERR("%s: cmd %d, error BAD_ARG5\n",
						__func__, radio->cmd);
			case BAD_ARG6:
				FMDERR("%s: cmd %d, error BAD_ARG6\n",
						__func__, radio->cmd);
				break;
			case BAD_ARG7:
				FMDERR("%s: cmd %d, error BAD_ARG7\n",
						__func__, radio->cmd);
				break;
			case BAD_PROP:
				FMDERR("%s: cmd %d, error BAD_PROP\n",
						__func__, radio->cmd);
				break;
			case BAD_BOOT_MODE:
				FMDERR("%s:cmd %d,err BAD_BOOT_MODE\n",
						__func__, radio->cmd);
				break;
			default:
				FMDERR("%s: cmd %d, unknown error\n",
						__func__, radio->cmd);
				break;
			}
			retval = -EINVAL;
			goto bad_cmd_arg;

		}

		if (radio->read_buf[0] & CTS_INT_BIT_MASK) {
			FMDBG("In %s, CTS bit is set\n", __func__);
			break;
		}
		/*
		 * Give some time if the chip is not done with processing
		 * previous command.
		 */
		msleep(100);
	}

	FMDBG("In %s, status byte is %x\n",  __func__, radio->read_buf[0]);

bad_cmd_arg:
	return retval;
}

static int send_cmd(struct silabs_fm_device *radio, u8 total_len)
{
	int retval = 0;

	retval = silabs_fm_i2c_write(radio, total_len);

	if (retval > 0)	{
		FMDBG("In %s, successfully written command %x to soc\n",
			  __func__, radio->write_buf[0]);
	} else {
		FMDERR("In %s, error %d writing command %d to soc\n",
			   __func__, retval, radio->write_buf[1]);
	}

	retval = read_cts_bit(radio);

	return retval;
}

static int get_property(struct silabs_fm_device *radio, u16 prop, u16 *pvalue)
{
	int retval = 0;

	mutex_lock(&radio->lock);
	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = GET_PROPERTY_CMD;
	radio->write_buf[0] = GET_PROPERTY_CMD;
	/* reserved, always write 0 */
	radio->write_buf[1] = 0;
	/* property high byte */
	radio->write_buf[2] = HIGH_BYTE_16BIT(prop);
	/* property low byte */
	radio->write_buf[3] = LOW_BYTE_16BIT(prop);

	FMDBG("in %s, radio->write_buf[2] is %x\n",
					__func__, radio->write_buf[2]);
	FMDBG("in %s, radio->write_buf[3] is %x\n",
					__func__, radio->write_buf[3]);

	retval = send_cmd(radio, GET_PROP_CMD_LEN);
	if (retval < 0)
		FMDERR("In %s, error getting property %d\n", __func__, prop);
	else
		*pvalue = (radio->read_buf[2] << 8) + radio->read_buf[3];

	mutex_unlock(&radio->lock);

	return retval;
}


static int set_property(struct silabs_fm_device *radio, u16 prop, u16 value)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = SET_PROPERTY_CMD;
	radio->write_buf[0] = SET_PROPERTY_CMD;
	/* reserved, always write 0 */
	radio->write_buf[1] = 0;
	/* property high byte */
	radio->write_buf[2] = HIGH_BYTE_16BIT(prop);
	/* property low byte */
	radio->write_buf[3] = LOW_BYTE_16BIT(prop);

	/* value high byte */
	radio->write_buf[4] = HIGH_BYTE_16BIT(value);
	/* value low byte */
	radio->write_buf[5] = LOW_BYTE_16BIT(value);

	retval = send_cmd(radio, SET_PROP_CMD_LEN);
	if (retval < 0)
		FMDERR("In %s, error setting property %d\n", __func__, prop);

	mutex_unlock(&radio->lock);

	return retval;
}

static void update_search_list(struct silabs_fm_device *radio, int freq)
{
	int temp_freq = freq;

	temp_freq = temp_freq -
		(radio->recv_conf.band_low_limit * TUNE_STEP_SIZE);
	temp_freq = temp_freq / 50;
	radio->srch_list.rel_freq[radio->srch_list.num_stations_found].
					rel_freq_lsb = GET_LSB(temp_freq);
	radio->srch_list.rel_freq[radio->srch_list.num_stations_found].
					rel_freq_msb = GET_MSB(temp_freq);
	radio->srch_list.num_stations_found++;
}

static void silabs_scan(struct work_struct *work)
{
	struct silabs_fm_device *radio;
	int current_freq_khz;
	u8 valid;
	u8 bltf;
	u32 temp_freq_khz;
	int retval = 0;
	struct kfifo *data_b;
	int len = 0;

	FMDBG("+%s, getting radio handle from work struct\n", __func__);
	radio = container_of(work, struct silabs_fm_device, work_scan.work);

	if (unlikely(radio == NULL)) {
		FMDERR(":radio is null");
		return;
	}

	current_freq_khz = radio->tuned_freq_khz;
	FMDBG("current freq is %d\n", current_freq_khz);

	radio->seek_tune_status = SCAN_PENDING;
	/* tune to lowest freq of the band */
	retval = tune(radio, radio->recv_conf.band_low_limit * TUNE_STEP_SIZE);
	if (retval < 0) {
		FMDERR("%s: Tune to lower band limit failed with error %d\n",
			__func__, retval);
		goto seek_tune_fail;
	}

	/* wait for tune to complete. */
	if (!wait_for_completion_timeout(&radio->sync_req_done,
				msecs_to_jiffies(WAIT_TIMEOUT_MSEC)))
		FMDERR("In %s, didn't receive STC for tune\n", __func__);
	else
		FMDBG("In %s, received STC for tune\n", __func__);
	while (1) {
		/* If scan is cancelled or FM is not ON, break */
		if (radio->is_search_cancelled == true) {
			FMDBG("%s: scan cancelled\n", __func__);
			if (radio->g_search_mode == SCAN_FOR_STRONG)
				goto seek_tune_fail;
			else
				goto seek_cancelled;
		} else if (radio->mode != FM_RECV) {
			FMDERR("%s: FM is not in proper state\n", __func__);
			return;
		}

		retval = silabs_seek(radio, SRCH_DIR_UP, WRAP_DISABLE);
		if (retval < 0) {
			FMDERR("Scan operation failed with error %d\n", retval);
			goto seek_tune_fail;
		}
		/* wait for seek to complete */
		if (!wait_for_completion_timeout(&radio->sync_req_done,
					msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) {
			FMDERR("%s: didn't receive STC for seek\n", __func__);
			/* FM is not correct state or scan is cancelled */
			continue;
		} else
			FMDBG("%s: received STC for seek\n", __func__);

		mutex_lock(&radio->lock);
		memset(radio->write_buf, 0, WRITE_REG_NUM);

		radio->cmd = FM_TUNE_STATUS_CMD;

		radio->write_buf[0] = FM_TUNE_STATUS_CMD;
		radio->write_buf[1] = 0;

		retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
		if (retval < 0)	{
			FMDERR("%s: FM_TUNE_STATUS_CMD failed with error %d\n",
					__func__, retval);
		}

		valid = radio->read_buf[1] & VALID_MASK;
		bltf = radio->read_buf[1] & BLTF_MASK;

		temp_freq_khz = ((u32)(radio->read_buf[2] << 8) +
					radio->read_buf[3])*
					TUNE_STEP_SIZE;
		mutex_unlock(&radio->lock);
		FMDBG("In %s, freq is %d\n", __func__, temp_freq_khz);

		if ((valid) && (radio->g_search_mode == SCAN)) {
			FMDBG("val bit set, posting SILABS_EVT_TUNE_SUCC\n");
			silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC);
		}

		if (bltf) {
			FMDBG("bltf bit is set\n");
			break;
		}
		/*
		 * If scan is cancelled or FM is not ON, break ASAP so that we
		 * don't need to sleep for dwell time.
		 */
		if (radio->is_search_cancelled == true) {
			FMDBG("%s: scan cancelled\n", __func__);
			if (radio->g_search_mode == SCAN_FOR_STRONG)
				goto seek_tune_fail;
			else
				goto seek_cancelled;
		} else if (radio->mode != FM_RECV) {
			FMDERR("%s: FM is not in proper state\n", __func__);
			return;
		}

		if (radio->g_search_mode == SCAN) {
			/* sleep for dwell period */
			msleep(radio->dwell_time_sec * 1000);
			/* need to queue the event when the seek completes */
			silabs_fm_q_event(radio, SILABS_EVT_SCAN_NEXT);
		} else if ((valid) &&
				(radio->g_search_mode == SCAN_FOR_STRONG)) {
			update_search_list(radio, temp_freq_khz);
		}
	}

seek_tune_fail:
	if (radio->g_search_mode == SCAN_FOR_STRONG) {
		len = radio->srch_list.num_stations_found * 2 +
			sizeof(radio->srch_list.num_stations_found);
		data_b = &radio->data_buf[SILABS_FM_BUF_SRCH_LIST];
		kfifo_in_locked(data_b, &radio->srch_list, len,
				&radio->buf_lock[SILABS_FM_BUF_SRCH_LIST]);
		silabs_fm_q_event(radio, SILABS_EVT_NEW_SRCH_LIST);
	}
	/* tune to original frequency */
	retval = tune(radio, current_freq_khz);
	if (retval < 0)
		FMDERR("%s: Tune to orig freq failed with error %d\n",
			__func__, retval);
	else {
		if (!wait_for_completion_timeout(&radio->sync_req_done,
					msecs_to_jiffies(WAIT_TIMEOUT_MSEC)))
			FMDERR("%s: didn't receive STC for tune\n", __func__);
		else
			FMDBG("%s: received STC for tune\n", __func__);
	}
seek_cancelled:
	silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE);
	radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
}

static void silabs_search(struct silabs_fm_device *radio, bool on)
{
	int current_freq_khz;

	current_freq_khz = radio->tuned_freq_khz;

	if (on) {
		FMDBG("%s: Queuing the work onto scan work q\n", __func__);
		queue_delayed_work(radio->wqueue_scan, &radio->work_scan,
					msecs_to_jiffies(SILABS_DELAY_MSEC));
	} else {
		cancel_seek(radio);
		silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE);
	}
}

static void get_rds_status(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);
	memset(radio->write_buf, 0, WRITE_REG_NUM);
	radio->cmd = FM_RDS_STATUS_CMD;
	radio->write_buf[0] = FM_RDS_STATUS_CMD;
	radio->write_buf[1] |= FM_RDS_STATUS_IN_INTACK;

	retval = send_cmd(radio, RDS_CMD_LEN);
	if (retval < 0) {
		FMDERR("In %s, Get RDS failed %d\n", __func__, retval);
		mutex_unlock(&radio->lock);
		return;
	}

	memset(radio->read_buf, 0, sizeof(radio->read_buf));

	retval = silabs_fm_i2c_read(radio, RDS_RSP_LEN);

	if (retval < 0) {
		FMDERR("In %s, failed to read the resp from soc %d\n",
							__func__, retval);
		mutex_unlock(&radio->lock);
		return;
	} else {
		FMDBG("In %s, successfully read the response from soc\n",
								__func__);
	}

	radio->block[0] = ((u16)radio->read_buf[MSB_OF_BLK_0] << 8) |
					(u16)radio->read_buf[LSB_OF_BLK_0];
	radio->block[1] = ((u16)radio->read_buf[MSB_OF_BLK_1] << 8) |
					(u16)radio->read_buf[LSB_OF_BLK_1];
	radio->block[2] = ((u16)radio->read_buf[MSB_OF_BLK_2] << 8) |
					(u16)radio->read_buf[LSB_OF_BLK_2];
	radio->block[3] = ((u16)radio->read_buf[MSB_OF_BLK_3] << 8) |
					(u16)radio->read_buf[LSB_OF_BLK_3];
	mutex_unlock(&radio->lock);
}

static void pi_handler(struct silabs_fm_device *radio, u16 current_pi)
{
	if (radio->pi != current_pi) {
		FMDBG("PI code of radio->block[0] = %x\n", current_pi);
		radio->pi = current_pi;
	} else {
		FMDBG(" Received same PI code\n");
	}
}

static void pty_handler(struct silabs_fm_device *radio, u8 current_pty)
{
	if (radio->pty != current_pty) {
		FMDBG("PTY code of radio->block[1] = %x\n", current_pty);
		radio->pty = current_pty;
	} else {
		FMDBG("PTY repeated\n");
	}
}

static void update_ps(struct silabs_fm_device *radio, u8 addr, u8 ps)
{
	u8 i;
	bool ps_txt_chg = false;
	bool ps_cmplt = true;
	u8 *data;
	struct kfifo *data_b;

	if (radio->ps_tmp0[addr] == ps) {
		if (radio->ps_cnt[addr] < PS_VALIDATE_LIMIT) {
			radio->ps_cnt[addr]++;
		} else {
			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT;
			radio->ps_tmp1[addr] = ps;
		}
	} else if (radio->ps_tmp1[addr] == ps) {
		if (radio->ps_cnt[addr] >= PS_VALIDATE_LIMIT) {
			ps_txt_chg = true;
			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT + 1;
		} else {
			radio->ps_cnt[addr] = PS_VALIDATE_LIMIT;
		}
		radio->ps_tmp1[addr] = radio->ps_tmp0[addr];
		radio->ps_tmp0[addr] = ps;
	} else if (!radio->ps_cnt[addr]) {
		radio->ps_tmp0[addr] = ps;
		radio->ps_cnt[addr] = 1;
	} else {
		radio->ps_tmp1[addr] = ps;
	}

	if (ps_txt_chg) {
		for (i = 0; i < MAX_PS_LEN; i++) {
			if (radio->ps_cnt[i] > 1)
				radio->ps_cnt[i]--;
		}
	}

	for (i = 0; i < MAX_PS_LEN; i++) {
		if (radio->ps_cnt[i] < PS_VALIDATE_LIMIT) {
			ps_cmplt = false;
			return;
		}
	}

	if (ps_cmplt) {
		for (i = 0; (i < MAX_PS_LEN) &&
			(radio->ps_display[i] == radio->ps_tmp0[i]); i++)
				;
		if (i == MAX_PS_LEN) {
			FMDBG("Same PS string repeated\n");
			return;
		}

		for (i = 0; i < MAX_PS_LEN; i++)
			radio->ps_display[i] = radio->ps_tmp0[i];

		data = kmalloc(PS_EVT_DATA_LEN, GFP_ATOMIC);
		if (data != NULL) {
			data[0] = NO_OF_PS;
			data[1] = radio->pty;
			data[2] = (radio->pi >> 8) & 0xFF;
			data[3] = (radio->pi & 0xFF);
			data[4] = 0;
			memcpy(data + OFFSET_OF_PS,
					radio->ps_tmp0, MAX_PS_LEN);
			data_b = &radio->data_buf[SILABS_FM_BUF_PS_RDS];
			kfifo_in_locked(data_b, data, PS_EVT_DATA_LEN,
					&radio->buf_lock[SILABS_FM_BUF_PS_RDS]);
			FMDBG("Q the PS event\n");
			silabs_fm_q_event(radio, SILABS_EVT_NEW_PS_RDS);
			kfree(data);
		} else {
			FMDERR("Memory allocation failed for PTY\n");
		}
	}
}

static void display_rt(struct silabs_fm_device *radio)
{
	u8 len = 0, i = 0;
	u8 *data;
	struct kfifo *data_b;
	bool rt_cmplt = true;

	for (i = 0; i < MAX_RT_LEN; i++) {
		if (radio->rt_cnt[i] < RT_VALIDATE_LIMIT) {
			rt_cmplt = false;
			return;
		}
		if (radio->rt_tmp0[i] == END_OF_RT)
			break;
	}

	if (rt_cmplt) {
		while ((len < MAX_RT_LEN) && (radio->rt_tmp0[len] != END_OF_RT))
			len++;

		for (i = 0; (i < len) &&
			(radio->rt_display[i] == radio->rt_tmp0[i]); i++)
				;
		if (i == len) {
			FMDBG("Same RT string repeated\n");
			return;
		}
		for (i = 0; i < len; i++)
			radio->rt_display[i] = radio->rt_tmp0[i];
		data = kmalloc(len + OFFSET_OF_RT, GFP_ATOMIC);
		if (data != NULL) {
			data[0] = len; /* len of RT */
			data[1] = radio->pty;
			data[2] = (radio->pi >> 8) & 0xFF;
			data[3] = (radio->pi & 0xFF);
			data[4] = radio->rt_flag;
			memcpy(data + OFFSET_OF_RT, radio->rt_display, len);
			data_b = &radio->data_buf[SILABS_FM_BUF_RT_RDS];
			kfifo_in_locked(data_b, data, OFFSET_OF_RT + len,
				&radio->buf_lock[SILABS_FM_BUF_RT_RDS]);
			FMDBG("Q the RT event\n");
			silabs_fm_q_event(radio, SILABS_EVT_NEW_RT_RDS);
			kfree(data);
		} else {
			FMDERR("Memory allocation failed for PTY\n");
		}
	}
}

static void rt_handler(struct silabs_fm_device *radio, u8 ab_flg,
					u8 cnt, u8 addr, u8 *rt)
{
	u8 i;
	bool rt_txt_chg = 0;

	if (ab_flg != radio->rt_flag && radio->valid_rt_flg) {
		for (i = 0; i < sizeof(radio->rt_cnt); i++) {
			if (!radio->rt_tmp0[i]) {
				radio->rt_tmp0[i] = ' ';
				radio->rt_cnt[i]++;
			}
		}
		memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt));
		memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0));
		memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1));
	}

	radio->rt_flag = ab_flg;
	radio->valid_rt_flg = true;

	for (i = 0; i < cnt; i++) {
		if (radio->rt_tmp0[addr+i] == rt[i]) {
			if (radio->rt_cnt[addr+i] < RT_VALIDATE_LIMIT) {
				radio->rt_cnt[addr+i]++;
			} else {
				radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT;
				radio->rt_tmp1[addr+i] = rt[i];
			}
		} else if (radio->rt_tmp1[addr+i] == rt[i]) {
			if (radio->rt_cnt[addr+i] >= RT_VALIDATE_LIMIT) {
				rt_txt_chg = true;
				radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT + 1;
			} else {
				radio->rt_cnt[addr+i] = RT_VALIDATE_LIMIT;
			}
			radio->rt_tmp1[addr+i] = radio->rt_tmp0[addr+i];
			radio->rt_tmp0[addr+i] = rt[i];
		} else if (!radio->rt_cnt[addr+i]) {
			radio->rt_tmp0[addr+i] = rt[i];
			radio->rt_cnt[addr+i] = 1;
		} else {
			radio->rt_tmp1[addr+i] = rt[i];
		}
	}

	if (rt_txt_chg) {
		for (i = 0; i < MAX_RT_LEN; i++) {
			if (radio->rt_cnt[i] > 1)
				radio->rt_cnt[i]--;
		}
	}
	display_rt(radio);
}

static void silabs_ev_ert(struct silabs_fm_device *radio)
{
	u8 *data = NULL;
	struct kfifo *data_b;

	if (radio->ert_len <= 0)
		return;

	data = kmalloc((radio->ert_len + ERT_OFFSET), GFP_ATOMIC);
	if (data != NULL) {
		data[0] = radio->ert_len;
		data[1] = radio->utf_8_flag;
		data[2] = radio->formatting_dir;
		memcpy((data + ERT_OFFSET), radio->ert_buf, radio->ert_len);
		data_b = &radio->data_buf[SILABS_FM_BUF_ERT];
		kfifo_in_locked(data_b, data, (radio->ert_len + ERT_OFFSET),
				&radio->buf_lock[SILABS_FM_BUF_ERT]);
		silabs_fm_q_event(radio, SILABS_EVT_NEW_ERT);
		kfree(data);
	}
}

static void silabs_buff_ert(struct silabs_fm_device *radio)
{
	int i;
	u16 info_byte = 0;
	u8 byte_pair_index;

	byte_pair_index = radio->block[1] & APP_GRP_typ_MASK;
	if (byte_pair_index == 0) {
		radio->c_byt_pair_index = 0;
		radio->ert_len = 0;
	}
	FMDBG("c_byt_pair_index = %x\n", radio->c_byt_pair_index);
	if (radio->c_byt_pair_index == byte_pair_index) {
		for (i = 2; i <= 3; i++) {
			info_byte = radio->block[i];
			FMDBG("info_byte = %x\n", info_byte);
			FMDBG("ert_len = %x\n", radio->ert_len);
			if (radio->ert_len > (MAX_ERT_LEN - 2))
				return;
			radio->ert_buf[radio->ert_len] = radio->block[i] >> 8;
			radio->ert_buf[radio->ert_len + 1] =
							radio->block[i] & 0xFF;
			radio->ert_len += ERT_CNT_PER_BLK;
			FMDBG("utf_8_flag = %d\n", radio->utf_8_flag);
			if ((radio->utf_8_flag == 0) &&
					(info_byte == END_OF_RT)) {
				radio->ert_len -= ERT_CNT_PER_BLK;
				break;
			} else if ((radio->utf_8_flag == 1) &&
					(radio->block[i] >> 8 == END_OF_RT)) {
				info_byte = END_OF_RT;
				radio->ert_len -= ERT_CNT_PER_BLK;
				break;
			} else if ((radio->utf_8_flag == 1) &&
					((radio->block[i] & 0xFF)
						 == END_OF_RT)) {
				info_byte = END_OF_RT;
				radio->ert_len--;
				break;
			}
		}
		if ((byte_pair_index == MAX_ERT_SEGMENT) ||
			(info_byte == END_OF_RT)) {
			silabs_ev_ert(radio);
			radio->c_byt_pair_index = 0;
			radio->ert_len = 0;
		}
		radio->c_byt_pair_index++;
	} else {
		radio->ert_len = 0;
		radio->c_byt_pair_index = 0;
	}
}


static void silabs_rt_plus(struct silabs_fm_device *radio)
{
	u8 tag_type1, tag_type2;
	u8 *data = NULL;
	int len = 0;
	u16 grp_typ;
	struct kfifo *data_b;

	grp_typ = radio->block[1] & APP_GRP_typ_MASK;
	/*
	 *right most 3 bits of Lsb of block 2
	 * and left most 3 bits of Msb of block 3
	 */
	tag_type1 = (((grp_typ & TAG1_MSB_MASK) << TAG1_MSB_OFFSET) |
			 (radio->block[2] >> TAG1_LSB_OFFSET));
	/*
	 *right most 1 bit of lsb of 3rd block
	 * and left most 5 bits of Msb of 4th block
	 */
	tag_type2 = (((radio->block[2] & TAG2_MSB_MASK)
			 << TAG2_MSB_OFFSET) |
			 (radio->block[2] >> TAG2_LSB_OFFSET));

	if (tag_type1 != DUMMY_CLASS)
		len += RT_PLUS_LEN_1_TAG;
	if (tag_type2 != DUMMY_CLASS)
		len += RT_PLUS_LEN_1_TAG;

	if (len != 0) {
		len += RT_PLUS_OFFSET;
		data = kmalloc(len, GFP_ATOMIC);
	} else {
		FMDERR("%s:Len is zero\n", __func__);
		return;
	}
	if (data != NULL) {
		data[0] = len;
		len = RT_ERT_FLAG_OFFSET;
		data[len++] = radio->rt_ert_flag;
		if (tag_type1 != DUMMY_CLASS) {
			data[len++] = tag_type1;
			/*
			 *start position of tag1
			 *right most 5 bits of msb of 3rd block
			 *and left most bit of lsb of 3rd block
			 */
			 data[len++] = (radio->block[2] >> TAG1_POS_LSB_OFFSET)
							& TAG1_POS_MSB_MASK;
			/*
			 *length of tag1
			 *left most 6 bits of lsb of 3rd block
			 */
			data[len++] = (radio->block[2] >> TAG1_LEN_OFFSET) &
								TAG1_LEN_MASK;
		}
		if (tag_type2 != DUMMY_CLASS) {
			data[len++] = tag_type2;
			/*
			 *start position of tag2
			 *right most 3 bit of msb of 4th block
			 *and left most 3 bits of lsb of 4th block
			 */
			data[len++] = (radio->block[3] >> TAG2_POS_LSB_OFFSET) &
							TAG2_POS_MSB_MASK;
			/*
			 *length of tag2
			 *right most 5 bits of lsb of 4th block
			 */
			data[len++] = radio->block[3] & TAG2_LEN_MASK;
		}
		data_b = &radio->data_buf[SILABS_FM_BUF_RT_PLUS];
		kfifo_in_locked(data_b, data, len,
				&radio->buf_lock[SILABS_FM_BUF_RT_PLUS]);
		silabs_fm_q_event(radio, SILABS_EVT_NEW_RT_PLUS);
		kfree(data);
	} else {
		FMDERR("%s:memory allocation failed\n", __func__);
	}
}

static void silabs_raw_rds_handler(struct silabs_fm_device *radio)
{
	u16 aid, app_grp_typ;

	aid = radio->block[3];
	app_grp_typ = radio->block[1] & APP_GRP_typ_MASK;
	FMDBG("app_grp_typ = %x\n", app_grp_typ);
	FMDBG("AID = %x", aid);

	switch (aid) {
	case ERT_AID:
		radio->utf_8_flag = (radio->block[2] & 1);
		radio->formatting_dir = EXTRACT_BIT(radio->block[2],
							ERT_FORMAT_DIR_BIT);
		if (radio->ert_carrier != app_grp_typ) {
			silabs_fm_q_event(radio, SILABS_EVT_NEW_ODA);
			radio->ert_carrier = app_grp_typ;
		}
		break;
	case RT_PLUS_AID:
		/*Extract 5th bit of MSB (b7b6b5b4b3b2b1b0)*/
		radio->rt_ert_flag = EXTRACT_BIT(radio->block[2],
				 RT_ERT_FLAG_BIT);
		if (radio->rt_plus_carrier != app_grp_typ) {
			silabs_fm_q_event(radio, SILABS_EVT_NEW_ODA);
			radio->rt_plus_carrier = app_grp_typ;
		}
		break;
	default:
		FMDBG("Not handling the AID of %x\n", aid);
		break;
	}
}

static int set_hard_mute(struct silabs_fm_device *radio, bool val)
{
	int retval = 0;

	if (val == true) {
		retval = set_property(radio, RX_HARD_MUTE_PROP, HARD_MUTE_MASK);

		if (retval < 0)
			FMDERR("%s: set_hard_mute failed with error %d\n",
				__func__, retval);
	} else {
		retval = set_property(radio, RX_HARD_MUTE_PROP, 0);

		if (retval < 0)
			FMDERR("%s: set_hard_mute failed with error %d\n",
				__func__, retval);
	}

	return retval;
}

static int set_mute_mode(struct silabs_fm_device *radio, u16 val)
{
	int retval = 0;

	retval = set_property(radio, RX_HARD_MUTE_PROP, val);

	if (retval < 0)
		FMDERR("%s: set_mute_mode failed with error %d\n",
			__func__, retval);
	return retval;
}
static int get_rssi(struct silabs_fm_device *radio, u8 *prssi)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip.*/
	radio->cmd = FM_RSQ_STATUS_CMD;
	radio->write_buf[0] = FM_RSQ_STATUS_CMD;
	radio->write_buf[1] = 1;

	retval = send_cmd(radio, RSQ_STATUS_CMD_LEN);

	if (retval < 0)
		FMDERR("%s: get_rsq_status failed with error %d\n",
				__func__, retval);

	FMDBG("%s: rssi is %d\n", __func__, radio->read_buf[4]);
	*prssi = radio->read_buf[4];
	mutex_unlock(&radio->lock);

	return retval;
}

static bool is_valid_freq(struct silabs_fm_device *radio, u32 freq)
{
	u32 band_low_limit = radio->recv_conf.band_low_limit * TUNE_STEP_SIZE;
	u32 band_high_limit = radio->recv_conf.band_high_limit * TUNE_STEP_SIZE;
	u8 spacing = 0;

	if (radio->recv_conf.ch_spacing == 0)
		spacing = CH_SPACING_200;
	else if (radio->recv_conf.ch_spacing == 1)
		spacing = CH_SPACING_100;
	else if (radio->recv_conf.ch_spacing == 2)
		spacing = CH_SPACING_50;
	else
		return false;

	if ((freq >= band_low_limit) &&
		(freq <= band_high_limit) &&
		((freq - band_low_limit) % spacing == 0))
		return true;

	return false;
}

static bool is_new_freq(struct silabs_fm_device *radio, u32 freq)
{
	u8 i = 0;

	for (i = 0; i < radio->af_info2.size; i++) {
		if (freq == radio->af_info2.af_list[i])
			return false;
	}

	return true;
}

static bool is_different_af_list(struct silabs_fm_device *radio)
{
	u8 i = 0, j = 0;
	u32 freq;

	if (radio->af_info1.orig_freq_khz != radio->af_info2.orig_freq_khz)
		return true;

	/* freq is same, check if the AFs are same. */
	for (i = 0; i < radio->af_info1.size; i++) {
		freq = radio->af_info1.af_list[i];
		for (j = 0; j < radio->af_info2.size; j++) {
			if (freq == radio->af_info2.af_list[j])
				break;
		}

		/* freq is not there in list2 i.e list1, list2 are different.*/
		if (j == radio->af_info2.size)
			return true;
	}

	return false;
}

static void reset_af_info(struct silabs_fm_device *radio)
{
	radio->af_info1.inval_freq_cnt = 0;
	radio->af_info1.cnt = 0;
	radio->af_info1.index = 0;
	radio->af_info1.size = 0;
	radio->af_info1.orig_freq_khz = 0;
	memset(radio->af_info1.af_list, 0, sizeof(radio->af_info1.af_list));

	radio->af_info2.inval_freq_cnt = 0;
	radio->af_info2.cnt = 0;
	radio->af_info2.index = 0;
	radio->af_info2.size = 0;
	radio->af_info2.orig_freq_khz = 0;
	memset(radio->af_info2.af_list, 0, sizeof(radio->af_info2.af_list));
}

static void update_af_list(struct silabs_fm_device *radio)
{
	bool retval;
	u8 i = 0;
	u8 af_data = radio->block[2] >> 8;
	u32 af_freq_khz;

	struct kfifo *buff;
	struct af_list_ev ev;
	spinlock_t lock = radio->buf_lock[SILABS_FM_BUF_AF_LIST];

	for (; i < NO_OF_AF_IN_GRP; i++, af_data = radio->block[2] & 0xFF) {

		if (af_data >= MIN_AF_CNT_CODE && af_data <= MAX_AF_CNT_CODE) {

			FMDBG("%s: resetting af info, freq %u, pi %u\n",
				__func__, radio->tuned_freq_khz, radio->pi);
			radio->af_info2.inval_freq_cnt = 0;
			radio->af_info2.cnt = 0;
			radio->af_info2.orig_freq_khz = 0;

			/* AF count. */
			radio->af_info2.cnt = af_data - NO_AF_CNT_CODE;
			radio->af_info2.orig_freq_khz = radio->tuned_freq_khz;
			radio->af_info2.pi = radio->pi;

			FMDBG("%s: current freq is %u, AF cnt is %u\n",
			__func__, radio->tuned_freq_khz, radio->af_info2.cnt);

		} else if (af_data >= MIN_AF_FREQ_CODE &&
				af_data <= MAX_AF_FREQ_CODE &&
				radio->af_info2.orig_freq_khz != 0 &&
				radio->af_info2.size < MAX_NO_OF_AF) {

			af_freq_khz = SCALE_AF_CODE_TO_FREQ_KHZ(af_data);
			retval = is_valid_freq(radio, af_freq_khz);
			if (retval == false) {
				FMDBG("%s: Invalid AF\n", __func__);
				radio->af_info2.inval_freq_cnt++;
				continue;
			}

			retval = is_new_freq(radio, af_freq_khz);
			if (retval == false) {
				FMDBG("%s: Duplicate AF\n", __func__);
				radio->af_info2.inval_freq_cnt++;
				continue;
			}

			/* update the AF list */
			radio->af_info2.af_list[radio->af_info2.size++] =
								af_freq_khz;
			FMDBG("%s: AF is %u\n", __func__, af_freq_khz);
			if ((radio->af_info2.size +
				radio->af_info2.inval_freq_cnt ==
				radio->af_info2.cnt) &&
				is_different_af_list(radio)) {

				/* Copy the list to af_info1. */
				radio->af_info1.cnt = radio->af_info2.cnt;
				radio->af_info1.size = radio->af_info2.size;
				radio->af_info1.pi = radio->af_info2.pi;
				radio->af_info1.orig_freq_khz =
					radio->af_info2.orig_freq_khz;
				memset(radio->af_info1.af_list,
					0,
					sizeof(radio->af_info1.af_list));

				memcpy(radio->af_info1.af_list,
					radio->af_info2.af_list,
					sizeof(radio->af_info2.af_list));

				/* AF list changed, post it to user space */
				memset(&ev, 0, sizeof(struct af_list_ev));

				ev.tune_freq_khz =
						radio->af_info1.orig_freq_khz;
				ev.pi_code = radio->pi;
				ev.af_size = radio->af_info1.size;

				memcpy(&ev.af_list[0],
					radio->af_info1.af_list,
					GET_AF_LIST_LEN(ev.af_size));

				buff = &radio->data_buf[SILABS_FM_BUF_AF_LIST];
				kfifo_in_locked(buff,
						(u8 *)&ev,
						GET_AF_EVT_LEN(ev.af_size),
						&lock);

				FMDBG("%s: posting AF list evt, curr freq %u\n",
					__func__, ev.tune_freq_khz);

				silabs_fm_q_event(radio,
						SILABS_EVT_NEW_AF_LIST);
			}
		}
	}
}

static void silabs_af_tune(struct work_struct *work)
{
	struct silabs_fm_device *radio;
	int retval = 0, i = 0;
	u8 rssi = 0;
	u32 freq = 0;

	radio = container_of(work, struct silabs_fm_device, work_af.work);

	if (radio->af_info2.size == 0) {
		FMDBG("%s: Empty AF list\n", __func__);
		radio->is_af_tune_in_progress = false;
		return;
	}
	radio->af_avg_th = 0;
	for (i = 0; i < radio->af_wait_timer; i++) {
		retval = get_rssi(radio, &rssi);
		if (retval < 0)
			FMDERR("%s: getting rssi failed\n", __func__);
		radio->af_avg_th += rssi;
		msleep(1000);
	}
	radio->af_avg_th = radio->af_avg_th/radio->af_wait_timer;
	if (radio->af_avg_th >= radio->af_rssi_th) {
		FMDBG("Not required to do Af jump\n");
		return;
	}


	/* Disable all other interrupts except STC, RDS */
	retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS);

	/* Mute until AF tuning finishes */
	retval = set_hard_mute(radio, true);

	while (1) {
		if (radio->mode != FM_RECV) {
			FMDERR("%s: Drv is not in proper state\n", __func__);
			goto end;
		}

		if (radio->seek_tune_status != NO_SEEK_TUNE_PENDING) {
			FMDBG("%s: manual tune, search issued\n", __func__);
			break;
		}

		if (radio->is_af_jump_enabled != true) {
			FMDBG("%s: AF jump is disabled\n", __func__);
			break;
		}

		/* If no more AFs left, tune to original frequency and break */
		if (radio->af_info2.index >= radio->af_info2.size) {
			FMDBG("%s: No more AFs, tuning to original freq %u\n",
				__func__, radio->af_info2.orig_freq_khz);

			freq = radio->af_info2.orig_freq_khz;

			retval = tune(radio, freq);
			if (retval < 0) {
				FMDERR("%s: tune failed, error %d\n",
					__func__, retval);
				goto err_tune_fail;
			}

			/* wait for tune to finish */
			if (!wait_for_completion_timeout(&radio->sync_req_done,
				msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) {
				FMDERR("%s: didn't receive STC for tune\n",
					__func__);
				/* FM is not correct state */
				continue;
			} else
				FMDBG("%s: received STC for tune\n", __func__);

			goto err_tune_fail;
		}

		freq = radio->af_info2.af_list[radio->af_info2.index++];

		FMDBG("%s: tuning to freq %u\n", __func__, freq);

		retval = tune(radio, freq);
		if (retval < 0) {
			FMDERR("%s: tune failed, error %d\n",
				__func__, retval);
			goto err_tune_fail;
		}

		/* wait for tune to finish */
		if (!wait_for_completion_timeout(&radio->sync_req_done,
			msecs_to_jiffies(WAIT_TIMEOUT_MSEC))) {
			FMDERR("%s: didn't receive STC for tune\n",
				__func__);
			/* FM is not correct state */
			continue;
		} else
			FMDBG("%s: received STC for tune\n", __func__);

		retval = get_rssi(radio, &rssi);
		if (retval < 0) {
			FMDERR("%s: getting rssi failed\n", __func__);
			goto err_tune_fail;
		}

		if (rssi >= radio->af_rssi_th) {
			u8 j = 0;
			/* clear stale RDS interrupt */
			get_rds_status(radio);
			for (; j < AF_PI_WAIT_TIME && radio->pi == 0; j++) {
				/* Wait for PI to be received. */
				msleep(100);
				FMDBG("%s: sleeping for 100ms for PI\n",
					__func__);
			}

			if (radio->pi == 0 || radio->pi != radio->af_info2.pi) {
				FMDBG("%s: pi %d, af freq pi %d not equal\n",
				__func__, radio->pi, radio->af_info2.pi);
				continue;
			}

			FMDBG("%s: found AF freq(%u) >= AF th with pi %d\n",
				__func__, freq, radio->pi);
			/* Notify FM UI about the new freq */
			FMDBG("%s: posting TUNE_SUCC event\n", __func__);
			silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC);

			break;
		} else {
			FMDBG("%s: rssi: %u, af_rssi_th: %u not eq contnuing\n",
				__func__, rssi, radio->af_rssi_th);
		}
	}

err_tune_fail:
	/*
	 * At this point, we are tuned to either original freq or AF with >=
	 * AF rssi threshold
	 */
	if (freq != radio->af_info2.orig_freq_khz) {
		FMDBG("tuned freq different than original,reset af info\n");
		reset_af_info(radio);
	}

	radio->is_af_tune_in_progress = false;

	/* Clear the stale RDS int bit. */
	get_rds_status(radio);
	retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS);

	/* Clear the stale RSQ int bit. */
	get_rssi(radio, &rssi);
	retval = configure_interrupts(radio, ENABLE_RSQ_INTERRUPTS);

end:
	/* Unmute */
	retval = set_hard_mute(radio, false);
	return;
}

/* When RDS interrupt is received, read and process RDS data. */
static void rds_handler(struct work_struct *worker)
{
	struct silabs_fm_device *radio;
	u8 rt_blks[NO_OF_RDS_BLKS];
	u8 grp_type, addr, ab_flg;

	radio = container_of(worker, struct silabs_fm_device, rds_worker);

	if (!radio) {
		FMDERR("%s:radio is null\n", __func__);
		return;
	}

	FMDBG("Entered rds_handler\n");

	get_rds_status(radio);

	pi_handler(radio, radio->block[0]);

	grp_type = radio->block[1] >> OFFSET_OF_GRP_TYP;

	FMDBG("grp_type = %d\n", grp_type);

	if (grp_type & 0x01)
		pi_handler(radio, radio->block[2]);

	pty_handler(radio, (radio->block[1] >> OFFSET_OF_PTY) & PTY_MASK);

	switch (grp_type) {
	case RDS_TYPE_0A:
		update_af_list(radio);
		/*  fall through */
	case RDS_TYPE_0B:
		addr = (radio->block[1] & PS_MASK) * NO_OF_CHARS_IN_EACH_ADD;
		FMDBG("RDS is PS\n");
		update_ps(radio, addr+0, radio->block[3] >> 8);
		update_ps(radio, addr+1, radio->block[3] & 0xff);
		break;
	case RDS_TYPE_2A:
		FMDBG("RDS is RT 2A group\n");
		rt_blks[0] = (u8)(radio->block[2] >> 8);
		rt_blks[1] = (u8)(radio->block[2] & 0xFF);
		rt_blks[2] = (u8)(radio->block[3] >> 8);
		rt_blks[3] = (u8)(radio->block[3] & 0xFF);
		addr = (radio->block[1] & 0xf) * 4;
		ab_flg = (radio->block[1] & 0x0010) >> 4;
		rt_handler(radio, ab_flg, CNT_FOR_2A_GRP_RT, addr, rt_blks);
		break;
	case RDS_TYPE_2B:
		FMDBG("RDS is RT 2B group\n");
		rt_blks[0] = (u8)(radio->block[3] >> 8);
		rt_blks[1] = (u8)(radio->block[3] & 0xFF);
		rt_blks[2] = 0;
		rt_blks[3] = 0;
		addr = (radio->block[1] & 0xf) * 2;
		ab_flg = (radio->block[1] & 0x0010) >> 4;
		radio->rt_tmp0[MAX_LEN_2B_GRP_RT] = END_OF_RT;
		radio->rt_tmp1[MAX_LEN_2B_GRP_RT] = END_OF_RT;
		radio->rt_cnt[MAX_LEN_2B_GRP_RT] = RT_VALIDATE_LIMIT;
		rt_handler(radio, ab_flg, CNT_FOR_2B_GRP_RT, addr, rt_blks);
		break;
	case RDS_TYPE_3A:
		FMDBG("RDS is 3A group\n");
		silabs_raw_rds_handler(radio);
		break;
	default:
		FMDERR("Not handling the group type %d\n", grp_type);
		break;
	}
	FMDBG("rt_plus_carrier = %x\n", radio->rt_plus_carrier);
	FMDBG("ert_carrier = %x\n", radio->ert_carrier);
	if (radio->rt_plus_carrier && (grp_type == radio->rt_plus_carrier))
		silabs_rt_plus(radio);
	else if (radio->ert_carrier && (grp_type == radio->ert_carrier))
		silabs_buff_ert(radio);
	return;
}

/* to enable, disable interrupts. */
static int configure_interrupts(struct silabs_fm_device *radio, u8 val)
{
	int retval = 0;
	u16 prop_val = 0;

	switch (val) {
	case DISABLE_ALL_INTERRUPTS:
		prop_val = 0;
		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error disabling interrupts\n", __func__);
		break;
	case ENABLE_STC_RDS_INTERRUPTS:
		/* enable STC and RDS interrupts. */
		prop_val = RDS_INT_BIT_MASK | STC_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error enabling STC, RDS interrupts\n",
				__func__);
		break;
	case ENABLE_STC_INTERRUPTS:
		/* enable STC interrupts only. */
		prop_val = STC_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error enabling STC interrupts\n", __func__);
		break;
	case ENABLE_RDS_INTERRUPTS:
		/* enable RDS interrupts. */
		prop_val = RDS_INT_BIT_MASK | STC_INT_BIT_MASK;
		if (radio->is_af_jump_enabled)
			prop_val |= RSQ_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error enabling RDS interrupts\n",
				__func__);
		break;
	case DISABLE_RDS_INTERRUPTS:
		/* disable RDS interrupts. */
		prop_val = STC_INT_BIT_MASK;
		if (radio->is_af_jump_enabled)
			prop_val |= RSQ_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error disabling RDS interrupts\n",
				__func__);
		break;
	case ENABLE_RSQ_INTERRUPTS:
		/* enable RSQ interrupts. */
		prop_val = RSQ_INT_BIT_MASK | STC_INT_BIT_MASK;
		if (radio->lp_mode != true)
			prop_val |= RDS_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error enabling RSQ interrupts\n",
				__func__);
		break;
	case DISABLE_RSQ_INTERRUPTS:
		/* disable RSQ interrupts. */
		prop_val = STC_INT_BIT_MASK;
		if (radio->lp_mode != true)
			prop_val |= RDS_INT_BIT_MASK;

		retval = set_property(radio, GPO_IEN_PROP, prop_val);
		if (retval < 0)
			FMDERR("%s: error disabling RSQ interrupts\n",
				__func__);
		break;
	default:
		FMDERR("%s: invalid value %u\n", __func__, val);
		retval = -EINVAL;
		break;
	}

	return retval;
}

static int get_int_status(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip.*/
	radio->cmd = GET_INT_STATUS_CMD;
	radio->write_buf[0] = GET_INT_STATUS_CMD;

	retval = send_cmd(radio, GET_INT_STATUS_CMD_LEN);

	if (retval < 0)
		FMDERR("%s: get_int_status failed with error %d\n",
				__func__, retval);

	mutex_unlock(&radio->lock);

	return retval;
}

static void reset_rds(struct silabs_fm_device *radio)
{
	radio->pi = 0;
	/* reset PS bufferes */
	memset(radio->ps_display, 0, sizeof(radio->ps_display));
	memset(radio->ps_tmp0, 0, sizeof(radio->ps_tmp0));
	memset(radio->ps_cnt, 0, sizeof(radio->ps_cnt));

	/* reset RT buffers */
	memset(radio->rt_display, 0, sizeof(radio->rt_display));
	memset(radio->rt_tmp0, 0, sizeof(radio->rt_tmp0));
	memset(radio->rt_tmp1, 0, sizeof(radio->rt_tmp1));
	memset(radio->rt_cnt, 0, sizeof(radio->rt_cnt));
}

static int initialize_recv(struct silabs_fm_device *radio)
{
	int retval = 0;

	retval = set_property(radio,
				FM_SEEK_TUNE_SNR_THRESHOLD_PROP,
				DEFAULT_SNR_TH);
	if (retval < 0)	{
		FMDERR("%s: FM_SEEK_TUNE_SNR_THRESHOLD_PROP fail error %d\n",
				__func__, retval);
		goto set_prop_fail;
	}

	radio->sinr_th = DEFAULT_SNR_TH;

	retval = set_property(radio,
				FM_SEEK_TUNE_RSSI_THRESHOLD_PROP,
				DEFAULT_RSSI_TH);
	if (retval < 0)	{
		FMDERR("%s: FM_SEEK_TUNE_RSSI_THRESHOLD_PROP fail error %d\n",
				__func__, retval);
		goto set_prop_fail;
	}

	radio->rssi_th = DEFAULT_RSSI_TH;

	retval = set_property(radio,
				FM_RSQ_RSSI_LO_THRESHOLD_PROP,
				DEFAULT_AF_RSSI_LOW_TH);
	if (retval < 0)	{
		FMDERR("%s: FM_RSQ_RSSI_LO_THRESHOLD_PROP fail error %d\n",
				__func__, retval);
		goto set_prop_fail;
	}

	radio->af_rssi_th = DEFAULT_AF_RSSI_LOW_TH;

	retval = set_property(radio,
				FM_RSQ_INT_SOURCE_PROP,
				RSSI_LOW_TH_INT_BIT_MASK);
	if (retval < 0)	{
		FMDERR("%s: FM_RSQ_INT_SOURCE_PROP fail error %d\n",
				__func__, retval);
		goto set_prop_fail;
	}

	retval = set_property(radio,
				FM_RDS_INT_FIFO_COUNT_PROP,
				FIFO_CNT_16);
	if (retval < 0)	{
		FMDERR("%s: FM_RDS_INT_FIFO_COUNT_PROP fail error %d\n",
				__func__, retval);
		goto set_prop_fail;
	}

	radio->rds_fifo_cnt = FIFO_CNT_16;

set_prop_fail:
	return retval;

}

static void init_ssr(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);
	/*
	 * Configure status gpio to low in active state. When chip is reset for
	 * some reason, status gpio becomes high since pull-up resistor is
	 * installed. No need to return error even if it fails, since normal
	 * FM functionality can still work fine.
	 */

	radio->cmd = GPIO_CTL_CMD;
	radio->write_buf[0] = GPIO_CTL_CMD;
	radio->write_buf[1] = GPIO1_OUTPUT_ENABLE_MASK;

	retval = send_cmd(radio, GPIO_CTL_CMD_LEN);

	if (retval < 0) {
		FMDERR("%s: setting Silabs gpio1 as op to chip fail, err %d\n",
			__func__, retval);
		goto end;
	}

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip.*/
	radio->cmd = GPIO_SET_CMD;
	radio->write_buf[0] = GPIO_SET_CMD;
	radio->write_buf[1] = GPIO_OUTPUT_LOW_MASK;

	retval = send_cmd(radio, GPIO_SET_CMD_LEN);

	if (retval < 0)
		FMDERR("%s: setting gpios to low failed, error %d\n",
			__func__, retval);

end:
	mutex_unlock(&radio->lock);
}

static int enable(struct silabs_fm_device *radio)
{
	int retval = 0;

	retval = read_cts_bit(radio);

	if (retval < 0)
		return retval;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip.*/
	radio->cmd = POWER_UP_CMD;
	radio->write_buf[0] = POWER_UP_CMD;
	radio->write_buf[1] = ENABLE_GPO2_INT_MASK;

	radio->write_buf[2] = AUDIO_OPMODE_DIGITAL;

	retval = send_cmd(radio, POWER_UP_CMD_LEN);

	if (retval < 0) {
		FMDERR("%s: enable failed with error %d\n", __func__, retval);
		mutex_unlock(&radio->lock);
		goto send_cmd_fail;
	}

	mutex_unlock(&radio->lock);

	/* enable interrupts */
	retval = configure_interrupts(radio, ENABLE_STC_RDS_INTERRUPTS);
	if (retval < 0)
		FMDERR("In %s, configure_interrupts failed with error %d\n",
				__func__, retval);

	/* initialize with default configuration */
	retval = initialize_recv(radio);
	reset_rds(radio); /* Clear the existing RDS data */
	init_ssr(radio);
	if (retval >= 0) {
		if (radio->mode == FM_RECV_TURNING_ON) {
			FMDBG("In %s, posting SILABS_EVT_RADIO_READY event\n",
				__func__);
			silabs_fm_q_event(radio, SILABS_EVT_RADIO_READY);
			radio->mode = FM_RECV;
		}
	}
send_cmd_fail:
	return retval;

}

static int disable(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = POWER_DOWN_CMD;
	radio->write_buf[0] = POWER_DOWN_CMD;

	retval = send_cmd(radio, POWER_DOWN_CMD_LEN);
	if (retval < 0)
		FMDERR("%s: disable failed with error %d\n",
			__func__, retval);

	mutex_unlock(&radio->lock);

	if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) {
		FMDBG("%s: posting SILABS_EVT_RADIO_DISABLED event\n",
			__func__);
		silabs_fm_q_event(radio, SILABS_EVT_RADIO_DISABLED);
		radio->mode = FM_OFF;
	}

	return retval;
}

static int set_chan_spacing(struct silabs_fm_device *radio, u16 spacing)
{
	int retval = 0;
	u16 prop_val = 0;

	if (spacing == 0)
		prop_val = FM_RX_SPACE_200KHZ;
	else if (spacing == 1)
		prop_val = FM_RX_SPACE_100KHZ;
	else if (spacing == 2)
		prop_val = FM_RX_SPACE_50KHZ;

	retval = set_property(radio, FM_SEEK_FREQ_SPACING_PROP, prop_val);
	if (retval < 0)
		FMDERR("In %s, error setting channel spacing\n", __func__);
	else
		radio->recv_conf.ch_spacing = spacing;

	return retval;

}

static int set_emphasis(struct silabs_fm_device *radio, u16 emp)
{
	int retval = 0;
	u16 prop_val = 0;

	if (emp == 0)
		prop_val = FM_RX_EMP75;
	else if (emp == 1)
		prop_val = FM_RX_EMP50;

	retval = set_property(radio, FM_DEEMPHASIS_PROP, prop_val);
	if (retval < 0)
		FMDERR("In %s, error setting emphasis\n", __func__);
	else
		radio->recv_conf.emphasis = emp;

	return retval;

}

static int tune(struct silabs_fm_device *radio, u32 freq_khz)
{
	int retval = 0;
	u16 freq_16bit = (u16)(freq_khz/TUNE_STEP_SIZE);

	FMDBG("In %s, freq is %d\n", __func__, freq_khz);

	/*
	 * when we are tuning for the first time, we must set digital audio
	 * properties.
	 */
	if (radio->first_tune) {
		/* I2S mode, rising edge */
		retval = set_property(radio, DIGITAL_OUTPUT_FORMAT_PROP, 0);
		if (retval < 0)	{
			FMDERR("%s: set output format prop failed, error %d\n",
					__func__, retval);
			goto set_prop_fail;
		}

		/* 48khz sample rate */
		retval = set_property(radio,
					DIGITAL_OUTPUT_SAMPLE_RATE_PROP,
					SAMPLE_RATE_48_KHZ);
		if (retval < 0)	{
			FMDERR("%s: set sample rate prop failed, error %d\n",
					__func__, retval);
			goto set_prop_fail;
		}
		radio->first_tune = false;
	}

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip.*/
	radio->cmd = FM_TUNE_FREQ_CMD;

	radio->write_buf[0] = FM_TUNE_FREQ_CMD;
	/* reserved */
	radio->write_buf[1] = 0;
	/* freq high byte */
	radio->write_buf[2] = HIGH_BYTE_16BIT(freq_16bit);
	/* freq low byte */
	radio->write_buf[3] = LOW_BYTE_16BIT(freq_16bit);
	radio->write_buf[4] = 0;

	FMDBG("In %s, radio->write_buf[2] %x, radio->write_buf[3]%x\n",
		__func__, radio->write_buf[2], radio->write_buf[3]);

	retval = send_cmd(radio, TUNE_FREQ_CMD_LEN);
	if (retval < 0)
		FMDERR("In %s, tune failed with error %d\n", __func__, retval);

	mutex_unlock(&radio->lock);

set_prop_fail:
	return retval;
}

static int silabs_seek(struct silabs_fm_device *radio, int dir, int wrap)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = FM_SEEK_START_CMD;

	radio->write_buf[0] = FM_SEEK_START_CMD;
	if (wrap)
		radio->write_buf[1] = SEEK_WRAP_MASK;

	if (dir == SRCH_DIR_UP)
		radio->write_buf[1] |= SEEK_UP_MASK;

	retval = send_cmd(radio, SEEK_CMD_LEN);
	if (retval < 0)
		FMDERR("In %s, seek failed with error %d\n", __func__, retval);

	mutex_unlock(&radio->lock);
	return retval;
}


static int cancel_seek(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = FM_TUNE_STATUS_CMD;

	radio->write_buf[0] = FM_TUNE_STATUS_CMD;
	radio->write_buf[1] = CANCEL_SEEK_MASK;

	retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
	if (retval < 0)
		FMDERR("%s: cancel_seek failed, error %d\n", __func__, retval);

	mutex_unlock(&radio->lock);
	radio->is_search_cancelled = true;

	return retval;

}

static void silabs_fm_q_event(struct silabs_fm_device *radio,
				enum silabs_evt_t event)
{

	struct kfifo *data_b;
	unsigned char evt = event;

	data_b = &radio->data_buf[SILABS_FM_BUF_EVENTS];

	FMDBG("updating event_q with event %x\n", event);
	if (kfifo_in_locked(data_b,
				&evt,
				1,
				&radio->buf_lock[SILABS_FM_BUF_EVENTS]))
		wake_up_interruptible(&radio->event_queue);
}

static int clear_stc_int(struct silabs_fm_device *radio)
{
	int retval = 0;

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = FM_TUNE_STATUS_CMD;

	radio->write_buf[0] = FM_TUNE_STATUS_CMD;
	radio->write_buf[1] = INTACK_MASK;

	retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
	if (retval < 0)
		FMDERR("%s: clear_stc_int fail, error %d\n", __func__, retval);

	mutex_unlock(&radio->lock);

	return retval;
}

static void silabs_interrupts_handler(struct silabs_fm_device *radio)
{
	int retval = 0;
	u8 rssi = 0;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return;
	}

	FMDBG("%s: ISR fired for cmd %x, reading status bytes\n",
		__func__, radio->cmd);

	/* Get int status to know which interrupt is this(STC/RDS/etc) */
	retval = get_int_status(radio);

	if (retval < 0) {
		FMDERR("%s: failure reading the resp from soc with error %d\n",
			  __func__, retval);
		return;
	}
	FMDBG("%s: successfully read the resp from soc, status byte is %x\n",
		  __func__, radio->read_buf[0]);


	if (radio->read_buf[0] & STC_INT_BIT_MASK) {
		FMDBG("%s: STC bit set for cmd %x\n", __func__, radio->cmd);
		if (radio->seek_tune_status == TUNE_PENDING) {
			FMDBG("In %s, posting SILABS_EVT_TUNE_SUCC event\n",
				__func__);
			silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC);
			radio->seek_tune_status = NO_SEEK_TUNE_PENDING;
			radio->is_af_tune_in_progress = false;
		} else if (radio->seek_tune_status == SEEK_PENDING) {
			FMDBG("%s: posting SILABS_EVT_SEEK_COMPLETE event\n",
				__func__);
			silabs_fm_q_event(radio, SILABS_EVT_SEEK_COMPLETE);
			/* post tune comp evt since seek results in a tune.*/
			FMDBG("%s: posting SILABS_EVT_TUNE_SUCC\n",
				__func__);
			silabs_fm_q_event(radio, SILABS_EVT_TUNE_SUCC);
			radio->seek_tune_status = NO_SEEK_TUNE_PENDING;

		} else if (radio->seek_tune_status == SCAN_PENDING) {
			/*
			 * when scan is pending and STC int is set, signal
			 * so that scan can proceed
			 */
			FMDBG("In %s, signalling scan thread\n", __func__);
			complete(&radio->sync_req_done);
		} else if (radio->is_af_tune_in_progress == true) {
			/*
			 * when AF tune is going on and STC int is set, signal
			 * so that AF tune can proceed.
			 */
			FMDBG("In %s, signalling AF tune thread\n", __func__);
			complete(&radio->sync_req_done);
		}
		/* clear the STC interrupt. */
		clear_stc_int(radio);
		reset_rds(radio); /* Clear the existing RDS data */
		return;
	}

	if (radio->read_buf[0] & RSQ_INT_BIT_MASK) {
		FMDBG("RSQ interrupt received, clearing the RSQ int bit\n");

		/* clear RSQ interrupt bits until AF tune is complete. */
		(void)get_rssi(radio, &rssi);
		/* Don't process RSQ until AF tune is complete. */
		if (radio->is_af_tune_in_progress == true)
			return;

		if (radio->is_af_jump_enabled &&
			radio->af_info2.size != 0 &&
			rssi <= radio->af_rssi_th) {

			radio->is_af_tune_in_progress = true;
			FMDBG("%s: Queuing to AF work Q, freq %u, rssi %u\n",
					__func__, radio->tuned_freq_khz, rssi);
			queue_delayed_work(radio->wqueue_af, &radio->work_af,
					msecs_to_jiffies(SILABS_DELAY_MSEC));
		}
		return;
	}

	if (radio->read_buf[0] & RDS_INT_BIT_MASK) {
		FMDBG("RDS interrupt received\n");
		/* Don't process RDS until AF tune is complete. */
		if (radio->is_af_tune_in_progress == true) {
			/* get PI only */
			get_rds_status(radio);
			pi_handler(radio, radio->block[0]);
			return;
		}
		schedule_work(&radio->rds_worker);
		return;
	}
	return;
}

static void read_int_stat(struct work_struct *work)
{
	struct silabs_fm_device *radio;

	radio = container_of(work, struct silabs_fm_device, work.work);

	silabs_interrupts_handler(radio);
}

static void silabs_fm_disable_irq(struct silabs_fm_device *radio)
{
	int irq;

	irq = radio->irq;
	disable_irq_wake(irq);
	free_irq(irq, radio);

	irq = radio->status_irq;
	disable_irq_wake(irq);
	free_irq(irq, radio);

	cancel_work_sync(&radio->rds_worker);
	flush_workqueue(radio->wqueue_rds);
	cancel_delayed_work_sync(&radio->work);
	flush_workqueue(radio->wqueue);
	cancel_delayed_work_sync(&radio->work_scan);
	flush_workqueue(radio->wqueue_scan);
	cancel_delayed_work_sync(&radio->work_af);
	flush_workqueue(radio->wqueue_af);
}

static irqreturn_t silabs_fm_isr(int irq, void *dev_id)
{
	struct silabs_fm_device *radio = dev_id;
	/*
	 * 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(SILABS_DELAY_MSEC));

	return IRQ_HANDLED;
}

static irqreturn_t silabs_fm_status_isr(int irq, void *dev_id)
{
	struct silabs_fm_device *radio = dev_id;

	if (radio->mode == FM_TURNING_OFF || radio->mode == FM_RECV) {
		FMDERR("%s: chip in bad state, posting DISABLED event\n",
			__func__);
		silabs_fm_q_event(radio, SILABS_EVT_RADIO_DISABLED);
		radio->mode = FM_OFF;
	}

	return IRQ_HANDLED;
}

static int silabs_fm_request_irq(struct silabs_fm_device *radio)
{
	int retval;
	int irq;

	irq = radio->irq;

	/*
	 * Use request_any_context_irq, So that it might work for nested or
	 * nested interrupts.
	 */
	retval = request_any_context_irq(irq, silabs_fm_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;
	}

	irq = radio->status_irq;

	retval = request_any_context_irq(irq, silabs_fm_status_isr,
				IRQ_TYPE_EDGE_RISING, "fm status interrupt",
				radio);
	if (retval < 0) {
		FMDERR("Couldn't acquire FM status gpio %d\n", irq);
		/* Do not error out for status int. FM can work without it. */
		return 0;
	} else {
		FMDBG("FM status GPIO %d registered\n", irq);
	}
	retval = enable_irq_wake(irq);
	if (retval < 0) {
		FMDERR("Could not enable FM status interrupt\n ");
		free_irq(irq , radio);
		/* Do not error out for status int. FM can work without it. */
		return 0;
	}

	return retval;
}

static int silabs_fm_fops_open(struct file *file)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = -ENODEV;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	INIT_DELAYED_WORK(&radio->work, read_int_stat);
	INIT_DELAYED_WORK(&radio->work_scan, silabs_scan);
	INIT_DELAYED_WORK(&radio->work_af, silabs_af_tune);
	INIT_WORK(&radio->rds_worker, rds_handler);

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

	/* initial gpio pin config & Power up */
	retval = silabs_fm_power_cfg(radio, TURNING_ON);
	if (retval) {
		FMDERR("%s: failed config gpio & pmic\n", __func__);
		goto open_err_setup;
	}
	radio->irq = gpio_to_irq(radio->int_gpio);

	if (radio->irq < 0) {
		FMDERR("%s: gpio_to_irq returned %d\n", __func__, radio->irq);
		goto open_err_req_irq;
	}

	FMDBG("irq number is = %d\n", radio->irq);

	if (radio->status_gpio > 0) {
		radio->status_irq = gpio_to_irq(radio->status_gpio);

		if (radio->status_irq < 0) {
			FMDERR("%s: gpio_to_irq returned %d for status gpio\n",
				__func__, radio->irq);
			goto open_err_req_irq;
		}

		FMDBG("status irq number is = %d\n", radio->status_irq);
	}

	/* enable irq */
	retval = silabs_fm_request_irq(radio);
	if (retval < 0) {
		FMDERR("%s: failed to request irq\n", __func__);
		goto open_err_req_irq;
	}

	radio->handle_irq = 0;
	radio->first_tune = true;
	return 0;

open_err_req_irq:
	silabs_fm_power_cfg(radio, TURNING_OFF);
open_err_setup:
	radio->handle_irq = 1;
	atomic_inc(&radio->users);
	return retval;
}

static int silabs_fm_fops_release(struct file *file)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = 0;

	if (unlikely(radio == NULL))
		return -EINVAL;

	if (radio->mode == FM_RECV) {
		radio->mode = FM_OFF;
		retval = disable(radio);
		if (retval < 0)
			FMDERR("Err on disable FM %d\n", retval);
	}

	FMDBG("%s, Disabling the IRQs\n", __func__);
	/* disable irq */
	silabs_fm_disable_irq(radio);

	retval = silabs_fm_power_cfg(radio, TURNING_OFF);
	if (retval < 0)
		FMDERR("%s: failed to configure gpios\n", __func__);

	atomic_inc(&radio->users);

	return retval;
}

static struct v4l2_queryctrl silabs_fm_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_SILABS_SRCHON,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "Search on/off",
		.minimum       = 0,
		.maximum       = 1,
		.step          = 1,
		.default_value = 1,

	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_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_SILABS_REGION,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "radio standard",
		.minimum       = 0,
		.maximum       = 2,
		.step          = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_SIGNAL_TH,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "Signal Threshold",
		.minimum       = 0x80,
		.maximum       = 0x7F,
		.step          = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_EMPHASIS,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "Emphasis",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_RDS_STD,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "RDS standard",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_SPACING,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "Channel spacing",
		.minimum       = 0,
		.maximum       = 2,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_RDSON,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "RDS on/off",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_RDSGROUP_MASK,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "RDS group mask",
		.minimum       = 0,
		.maximum       = 0xFFFFFFFF,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "RDS processing",
		.minimum       = 0,
		.maximum       = 0xFF,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_RDSD_BUF,
		.type          = V4L2_CTRL_TYPE_INTEGER,
		.name          = "RDS data groups to buffer",
		.minimum       = 1,
		.maximum       = 21,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_PSALL,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "pass all ps strings",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_LP_MODE,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "Low power mode",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},
	{
		.id            = V4L2_CID_PRIVATE_SILABS_ANTENNA,
		.type          = V4L2_CTRL_TYPE_BOOLEAN,
		.name          = "headset/internal",
		.minimum       = 0,
		.maximum       = 1,
		.default_value = 0,
	},

};

static int silabs_fm_vidioc_querycap(struct file *file, void *priv,
		struct v4l2_capability *capability)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (unlikely(capability == NULL)) {
		FMDERR("%s:capability is null", __func__);
		return -EINVAL;
	}

	strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
	strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
	snprintf(capability->bus_info, 4,  "I2C");
	capability->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;

	return 0;
}

static int silabs_fm_vidioc_queryctrl(struct file *file, void *priv,
		struct v4l2_queryctrl *qc)
{
	unsigned char i;
	int retval = -EINVAL;

	if (unlikely(qc == NULL)) {
		FMDERR("%s:qc is null", __func__);
		return -EINVAL;
	}


	for (i = 0; i < ARRAY_SIZE(silabs_fm_v4l2_queryctrl); i++) {
		if (qc->id && qc->id == silabs_fm_v4l2_queryctrl[i].id) {
			memcpy(qc, &(silabs_fm_v4l2_queryctrl[i]),
				       sizeof(*qc));
			retval = 0;
			break;
		}
	}
	if (retval < 0)
		FMDERR("query conv4ltrol failed with %d\n", retval);

	return retval;
}

static int silabs_fm_vidioc_g_ctrl(struct file *file, void *priv,
		struct v4l2_control *ctrl)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = 0;

	if (unlikely(radio == NULL)) {
		FMDERR(":radio is null");
		return -EINVAL;
	}

	if (ctrl == NULL) {
		FMDERR("%s, v4l2 ctrl is null\n", __func__);
		return -EINVAL;
	}

	switch (ctrl->id) {
	case V4L2_CID_AUDIO_VOLUME:
		break;
	case V4L2_CID_AUDIO_MUTE:
		break;

	case V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC:
		ctrl->value = 0;
		retval = 0;
		break;
	case V4L2_CID_PRIVATE_SILABS_GET_SINR:

		mutex_lock(&radio->lock);
		radio->cmd = FM_TUNE_STATUS_CMD;

		radio->write_buf[0] = FM_TUNE_STATUS_CMD;
		radio->write_buf[1] = 0;

		retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
		if (retval < 0) {
			FMDERR("%s: FM_TUNE_STATUS_CMD failed with error %d\n",
					__func__, retval);
			mutex_unlock(&radio->lock);
			break;
		}

		/* sinr */
		ctrl->value = radio->read_buf[5];
		mutex_unlock(&radio->lock);
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_GET_SINR, val %d\n",
			__func__, ctrl->value);
		break;
	case V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD, val %d\n",
			__func__, radio->sinr_th);

		ctrl->value = radio->sinr_th;
		break;
	case V4L2_CID_PRIVATE_SILABS_RSSI_TH:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RSSI_TH, val %d\n",
			__func__, radio->rssi_th);

		ctrl->value = radio->rssi_th;
		break;
	case V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH, val %d\n",
			__func__, radio->af_rssi_th);

		ctrl->value = radio->af_rssi_th;
		break;

	case V4L2_CID_PRIVATE_SILABS_RDSD_BUF:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RDSD_BUF, val %d\n",
			__func__, radio->rds_fifo_cnt);

		ctrl->value = radio->rds_fifo_cnt;
		break;
	case V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES, val %d\n",
			__func__, ctrl->value);
		ctrl->value = radio->af_wait_timer;
		break;
	default:
		retval = -EINVAL;
		break;
	}

	if (retval < 0)
		FMDERR("get control failed with %d, id: %x\n",
			retval, ctrl->id);

	return retval;
}

static int silabs_fm_vidioc_s_ctrl(struct file *file, void *priv,
		struct v4l2_control *ctrl)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = 0;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (unlikely(ctrl == NULL)) {
		FMDERR("%s:ctrl is null", __func__);
		return -EINVAL;
	}

	switch (ctrl->id) {
	case V4L2_CID_AUDIO_MUTE:
		if ((ctrl->value >= 0) && (ctrl->value <= 3)) {
			set_mute_mode(radio, ctrl->value);
		} else {
			FMDERR("%s: Mute mode states are out of range\n",
					__func__);
			retval = -EINVAL;
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_STATE:
		/* check if already on */
		if (ctrl->value == FM_RECV) {
			if (is_enable_rx_possible(radio) != 0) {
				FMDERR("%s: fm is not in proper state\n",
					 __func__);
				retval = -EINVAL;
				goto end;
			}
			radio->mode = FM_RECV_TURNING_ON;

			retval = enable(radio);
			if (retval < 0) {
				FMDERR("Error while enabling RECV FM %d\n",
					retval);
				radio->mode = FM_OFF;
				goto end;
			}
		} else if (ctrl->value == FM_OFF) {
			retval = configure_interrupts(radio,
						DISABLE_ALL_INTERRUPTS);
			if (retval < 0)
				FMDERR("configure_interrupts failed %d\n",
				retval);
			flush_workqueue(radio->wqueue);
			cancel_work_sync(&radio->rds_worker);
			flush_workqueue(radio->wqueue_rds);
			radio->mode = FM_TURNING_OFF;
			retval = disable(radio);
			if (retval < 0) {
				FMDERR("Err on disable recv FM %d\n", retval);
				radio->mode = FM_RECV;
				goto end;
			}
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_SPACING:
		if (!is_valid_chan_spacing(ctrl->value)) {
			retval = -EINVAL;
			FMDERR("%s: channel spacing is not valid\n", __func__);
			goto end;
		}
		retval = set_chan_spacing(radio, (u16)ctrl->value);
		if (retval < 0) {
			FMDERR("Error in setting channel spacing\n");
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_EMPHASIS:
		retval = set_emphasis(radio, (u16)ctrl->value);
		if (retval < 0) {
			FMDERR("Error in setting emphasis\n");
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_ANTENNA:
		if (ctrl->value == 0 || ctrl->value == 1) {
			retval = set_property(radio,
						FM_ANTENNA_INPUT_PROP,
						ctrl->value);
			if (retval < 0)
				FMDERR("Setting antenna type failed\n");
			else
				radio->antenna = ctrl->value;
		} else	{
			retval = -EINVAL;
			FMDERR("%s: antenna type is not valid\n", __func__);
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_SOFT_MUTE:
		retval = 0;
		break;
	case V4L2_CID_PRIVATE_SILABS_REGION:
	case V4L2_CID_PRIVATE_SILABS_SRCH_ALGORITHM:
	case V4L2_CID_PRIVATE_SILABS_SET_AUDIO_PATH:
		/*
		 * These private controls are place holders to keep the
		 * driver compatible with changes done in the frameworks
		 * which are specific to TAVARUA.
		 */
		retval = 0;
		break;
	case V4L2_CID_PRIVATE_SILABS_SRCHMODE:
		if (is_valid_srch_mode(ctrl->value)) {
			radio->g_search_mode = ctrl->value;
		} else {
			FMDERR("%s: srch mode is not valid\n", __func__);
			retval = -EINVAL;
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_SCANDWELL:
		if ((ctrl->value >= MIN_DWELL_TIME) &&
			(ctrl->value <= MAX_DWELL_TIME)) {
			radio->dwell_time_sec = ctrl->value;
		} else {
			FMDERR("%s: scandwell period is not valid\n", __func__);
			retval = -EINVAL;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_SRCHON:
		silabs_search(radio, (bool)ctrl->value);
		break;
	case V4L2_CID_PRIVATE_SILABS_RDS_STD:
		return retval;
		break;
	case V4L2_CID_PRIVATE_SILABS_RDSON:
		return retval;
		break;
	case V4L2_CID_PRIVATE_SILABS_RDSGROUP_MASK:
		retval = set_property(radio,
				FM_RDS_INT_SOURCE_PROP,
				RDS_INT_BIT);
		if (retval < 0) {
			FMDERR("In %s, FM_RDS_INT_SOURCE_PROP failed %d\n",
				__func__, retval);
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_RDSD_BUF:
		if (is_valid_rds_fifo_cnt(ctrl->value)) {
			retval = set_property(radio,
					FM_RDS_INT_FIFO_COUNT_PROP,
					ctrl->value);
			if (retval < 0)	{
				FMDERR("%s: setting rds fifo cnt failed %d\n",
						__func__, retval);
				goto end;
			}

			radio->rds_fifo_cnt = ctrl->value;
		} else
			retval = -EINVAL;

		break;
	case V4L2_CID_PRIVATE_SILABS_RDSGROUP_PROC:
		/* Enabled all with uncorrectable */
		retval = set_property(radio,
				FM_RDS_CONFIG_PROP,
				UNCORRECTABLE_RDS_EN);
		if (retval < 0) {
			FMDERR("In %s, FM_RDS_CONFIG_PROP failed %d\n",
				__func__, retval);
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_LP_MODE:
		FMDBG("In %s, V4L2_CID_PRIVATE_SILABS_LP_MODE, val is %d\n",
			__func__, ctrl->value);
		if (ctrl->value) {
			/* disable RDS interrupts */
			retval = configure_interrupts(radio,
					ENABLE_RDS_INTERRUPTS);
			radio->lp_mode = true;
		} else {
			/* enable RDS interrupts */
			retval = configure_interrupts(radio,
					DISABLE_RDS_INTERRUPTS);
			radio->lp_mode = false;
		}

		if (retval < 0) {
			FMDERR("In %s, setting low power mode failed %d\n",
				__func__, retval);
			goto end;
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_AF_JUMP:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP, val is %d\n",
			__func__, ctrl->value);
		if (ctrl->value)
			/* enable RSQ interrupts */
			retval = configure_interrupts(radio,
					ENABLE_RSQ_INTERRUPTS);
		else
			/* disable RSQ interrupts */
			retval = configure_interrupts(radio,
					DISABLE_RSQ_INTERRUPTS);
		if (retval < 0) {
			FMDERR("%s: setting AF jump mode failed %d\n",
				__func__, retval);
			goto end;
		}
		/* Save the AF jump state */
		radio->is_af_jump_enabled = ctrl->value;
		break;
	case V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_SINR_THRESHOLD, val is %d\n",
			__func__, ctrl->value);
		if (is_valid_sinr(ctrl->value)) {
			retval = set_property(radio,
						FM_SEEK_TUNE_SNR_THRESHOLD_PROP,
						ctrl->value);
			if (retval < 0)	{
				FMDERR("%s: setting sinr th failed, error %d\n",
						__func__, retval);
				goto end;
			}

			radio->sinr_th = ctrl->value;

		} else {
			retval = -EINVAL;
			FMDERR("%s: Invalid sinr\n", __func__);
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_RSSI_TH:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_RSSI_TH, val is %d\n",
			__func__, ctrl->value);
		if (is_valid_rssi(ctrl->value)) {
			retval = set_property(radio,
					FM_SEEK_TUNE_RSSI_THRESHOLD_PROP,
					ctrl->value);
			if (retval < 0)	{
				FMDERR("%s: setting rssi th failed, error %d\n",
						__func__, retval);
				goto end;
			}

			radio->rssi_th = ctrl->value;

		} else {
			retval = -EINVAL;
			FMDERR("%s: Invalid sinr\n", __func__);
		}
		break;
	case V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_JUMP_RSSI_TH, val %d\n",
			__func__, ctrl->value);
		if (is_valid_rssi(ctrl->value)) {
			retval = set_property(radio,
						FM_RSQ_RSSI_LO_THRESHOLD_PROP,
						ctrl->value);
			if (retval < 0)	{
				FMDERR("%s: setting af rssi th failed err %d\n",
					__func__, retval);
				goto end;
			}

			radio->af_rssi_th = ctrl->value;

		} else {
			retval = -EINVAL;
			FMDERR("%s: Invalid sinr\n", __func__);
		}

		break;
	case V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES:
		FMDBG("%s: V4L2_CID_PRIVATE_SILABS_AF_RMSSI_SAMPLES, val %d\n",
			__func__, ctrl->value);
		if ((ctrl->value >= 0) || (ctrl->value <= MAX_AF_WAIT_SEC))
			radio->af_wait_timer = ctrl->value;
		break;
	case V4L2_CID_PRIVATE_SILABS_SRCH_CNT:
		retval = 0;
		break;
	default:
		retval = -EINVAL;
		break;
	}

	if (retval < 0)
		FMDERR("set control failed with %d, id:%x\n", retval, ctrl->id);

end:
	return retval;
}

static int silabs_fm_vidioc_s_tuner(struct file *file, void *priv,
	const struct v4l2_tuner *tuner)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = 0;
	u16 prop_val = 0;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (unlikely(tuner == NULL)) {
		FMDERR("%s:tuner is null", __func__);
		return -EINVAL;
	}

	if (tuner->index > 0)
		return -EINVAL;

	FMDBG("In %s, setting top and bottom band limits\n", __func__);

	prop_val = (u16)((tuner->rangelow / TUNE_PARAM) / TUNE_STEP_SIZE);
	FMDBG("In %s, tuner->rangelow is %d, setting bottom band to %d\n",
		__func__, tuner->rangelow, prop_val);

	retval = set_property(radio, FM_SEEK_BAND_BOTTOM_PROP, prop_val);
	if (retval < 0)
		FMDERR("In %s, error %d setting lower limit freq\n",
			__func__, retval);
	else
		radio->recv_conf.band_low_limit = prop_val;

	prop_val = (u16)((tuner->rangehigh / TUNE_PARAM) / TUNE_STEP_SIZE);
	FMDBG("In %s, tuner->rangehigh is %d, setting top band to %d\n",
		__func__, tuner->rangehigh, prop_val);

	retval = set_property(radio, FM_SEEK_BAND_TOP_PROP, prop_val);
	if (retval < 0)
		FMDERR("In %s, error %d setting upper limit freq\n",
			__func__, retval);
	else
		radio->recv_conf.band_high_limit = prop_val;

	if (retval < 0)
		FMDERR(": set tuner failed with %d\n", retval);

	return retval;
}

static int silabs_fm_vidioc_g_tuner(struct file *file, void *priv,
		struct v4l2_tuner *tuner)
{
	int retval = 0;
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));

	if (unlikely(radio == NULL)) {
		FMDERR(":radio is null");
		return -EINVAL;
	}
	if (tuner == NULL) {
		FMDERR("%s, tuner is null\n", __func__);
		return -EINVAL;
	}
	if (tuner->index > 0) {
		FMDERR("Invalid Tuner Index");
		return -EINVAL;
	}

	mutex_lock(&radio->lock);

	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = FM_TUNE_STATUS_CMD;

	radio->write_buf[0] = FM_TUNE_STATUS_CMD;
	radio->write_buf[1] = 0;

	retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
	if (retval < 0) {
		FMDERR("In %s, FM_TUNE_STATUS_CMD failed with error %d\n",
				__func__, retval);
		mutex_unlock(&radio->lock);
		goto get_prop_fail;
	}

	/* rssi */
	tuner->signal = radio->read_buf[4];
	mutex_unlock(&radio->lock);

	retval = get_property(radio,
				FM_SEEK_BAND_BOTTOM_PROP,
				&radio->recv_conf.band_low_limit);
	if (retval < 0) {
		FMDERR("%s: get FM_SEEK_BAND_BOTTOM_PROP failed, error %d\n",
				__func__, retval);
		goto get_prop_fail;
	}

	FMDBG("In %s, radio->recv_conf.band_low_limit is %d\n",
		__func__, radio->recv_conf.band_low_limit);
	retval = get_property(radio,
				FM_SEEK_BAND_TOP_PROP,
				&radio->recv_conf.band_high_limit);
	if (retval < 0) {
		FMDERR("In %s, get FM_SEEK_BAND_TOP_PROP failed, error %d\n",
				__func__, retval);
		goto get_prop_fail;
	}
	FMDBG("In %s, radio->recv_conf.band_high_limit is %d\n",
		__func__, radio->recv_conf.band_high_limit);

	tuner->type = V4L2_TUNER_RADIO;
	tuner->rangelow  =
		radio->recv_conf.band_low_limit * TUNE_STEP_SIZE * TUNE_PARAM;
	tuner->rangehigh =
		radio->recv_conf.band_high_limit * TUNE_STEP_SIZE * TUNE_PARAM;
	tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
	tuner->capability = V4L2_TUNER_CAP_LOW;

	tuner->audmode = 0;
	tuner->afc = 0;

get_prop_fail:
	return retval;
}

static int silabs_fm_vidioc_g_frequency(struct file *file, void *priv,
		struct v4l2_frequency *freq)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	u32 f;
	u8 snr, rssi;
	int retval = 0;

	if (unlikely(radio == NULL)) {
		FMDERR(":radio is null");
		return -EINVAL;
	}

	if (freq == NULL) {
		FMDERR("%s, v4l2 freq is null\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&radio->lock);
	memset(radio->write_buf, 0, WRITE_REG_NUM);

	/* track command that is being sent to chip. */
	radio->cmd = FM_TUNE_STATUS_CMD;

	radio->write_buf[0] = FM_TUNE_STATUS_CMD;
	radio->write_buf[1] = 0;

	retval = send_cmd(radio, TUNE_STATUS_CMD_LEN);
	if (retval < 0) {
		FMDERR("In %s, get station freq cmd failed with error %d\n",
				__func__, retval);
		mutex_unlock(&radio->lock);
		goto send_cmd_fail;
	}

	f = (radio->read_buf[2] << 8) + radio->read_buf[3];
	freq->frequency = f * TUNE_PARAM * TUNE_STEP_SIZE;
	radio->tuned_freq_khz = f * TUNE_STEP_SIZE;

	rssi = radio->read_buf[4];
	snr = radio->read_buf[5];
	mutex_unlock(&radio->lock);

	FMDBG("In %s, freq is %d, rssi %u, snr %u\n",
		__func__, f * TUNE_STEP_SIZE, rssi, snr);

send_cmd_fail:
	return retval;
}

static int silabs_fm_vidioc_s_frequency(struct file *file, void *priv,
					const struct v4l2_frequency *freq)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int retval = -1;
	u32 f = 0;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (unlikely(freq == NULL)) {
		FMDERR("%s:freq is null", __func__);
		return -EINVAL;
	}

	if (freq->type != V4L2_TUNER_RADIO)
		return -EINVAL;

	f = (freq->frequency)/TUNE_PARAM;

	FMDBG("Calling tune with freq %u\n", f);

	radio->seek_tune_status = TUNE_PENDING;

	retval = tune(radio, f);

	/* save the current frequency if tune is successful. */
	if (retval > 0) {
		radio->tuned_freq_khz = f;
		/* Clear AF list */
		reset_af_info(radio);
		cancel_delayed_work_sync(&radio->work_af);
		flush_workqueue(radio->wqueue_af);
	}

	return retval;
}

static int silabs_fm_vidioc_s_hw_freq_seek(struct file *file, void *priv,
					const struct v4l2_hw_freq_seek *seek)
{
	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	int dir;
	int retval = 0;

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (unlikely(seek == NULL)) {
		FMDERR("%s:seek is null", __func__);
		return -EINVAL;
	}

	if (seek->seek_upward)
		dir = SRCH_DIR_UP;
	else
		dir = SRCH_DIR_DOWN;

	radio->is_search_cancelled = false;

	if (radio->g_search_mode == SEEK) {
		/* seek */
		FMDBG("starting seek\n");

		radio->seek_tune_status = SEEK_PENDING;

		retval = silabs_seek(radio, dir, WRAP_ENABLE);

	} else if ((radio->g_search_mode == SCAN) ||
			(radio->g_search_mode == SCAN_FOR_STRONG)) {
		/* scan */
		if (radio->g_search_mode == SCAN_FOR_STRONG) {
			FMDBG("starting search list\n");
			memset(&radio->srch_list, 0,
				sizeof(struct silabs_srch_list_compl));
		} else {
			FMDBG("starting scan\n");
		}
		silabs_search(radio, START_SCAN);

	} else {
		retval = -EINVAL;
		FMDERR("In %s, invalid search mode %d\n",
			__func__, radio->g_search_mode);
	}

	if (retval > 0) {
		/* Clear AF list */
		reset_af_info(radio);
		cancel_delayed_work_sync(&radio->work_af);
		flush_workqueue(radio->wqueue_af);
	}

	return retval;
}

static int silabs_fm_vidioc_dqbuf(struct file *file, void *priv,
				struct v4l2_buffer *buffer)
{

	struct silabs_fm_device *radio = video_get_drvdata(video_devdata(file));
	enum silabs_buf_t buf_type = -1;
	u8 buf_fifo[STD_BUF_SIZE] = {0};
	struct kfifo *data_fifo = NULL;
	u8 *buf = NULL;
	int len = 0, retval = -1;

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

	if ((buf_type < SILABS_FM_BUF_MAX) && (buf_type >= 0)) {
		data_fifo = &radio->data_buf[buf_type];
		if (buf_type == SILABS_FM_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;
}

static int silabs_fm_pinctrl_init(struct silabs_fm_device *radio)
{
	int retval = 0;

	radio->fm_pinctrl = devm_pinctrl_get(&radio->client->dev);
	if (IS_ERR_OR_NULL(radio->fm_pinctrl)) {
		FMDERR("%s: target does not use pinctrl\n", __func__);
		retval = PTR_ERR(radio->fm_pinctrl);
		return retval;
	}

	radio->gpio_state_active =
			pinctrl_lookup_state(radio->fm_pinctrl,
						"pmx_fm_active");
	if (IS_ERR_OR_NULL(radio->gpio_state_active)) {
		FMDERR("%s: cannot get FM active state\n", __func__);
		retval = PTR_ERR(radio->gpio_state_active);
		goto err_active_state;
	}

	radio->gpio_state_suspend =
				pinctrl_lookup_state(radio->fm_pinctrl,
							"pmx_fm_suspend");
	if (IS_ERR_OR_NULL(radio->gpio_state_suspend)) {
		FMDERR("%s: cannot get FM suspend state\n", __func__);
		retval = PTR_ERR(radio->gpio_state_suspend);
		goto err_suspend_state;
	}

	return retval;

err_suspend_state:
	radio->gpio_state_suspend = 0;

err_active_state:
	radio->gpio_state_active = 0;

	return retval;
}

static int silabs_parse_dt(struct device *dev,
			struct silabs_fm_device *radio)
{
	int rc = 0;
	struct device_node *np = dev->of_node;

	radio->reset_gpio = of_get_named_gpio(np, "silabs,reset-gpio", 0);
	if (radio->reset_gpio < 0) {
		FMDERR("silabs-reset-gpio not provided in device tree");
		return radio->reset_gpio;
	}

	rc = gpio_request(radio->reset_gpio, "fm_rst_gpio_n");
	if (rc) {
		FMDERR("unable to request gpio %d (%d)\n",
					radio->reset_gpio, rc);
		return rc;
	}

	radio->int_gpio = of_get_named_gpio(np, "silabs,int-gpio", 0);
	if (radio->int_gpio < 0) {
		FMDERR("silabs-int-gpio not provided in device tree");
		rc = radio->int_gpio;
		goto err_int_gpio;
	}

	rc = gpio_request(radio->int_gpio, "silabs_fm_int_n");
	if (rc) {
		FMDERR("unable to request gpio %d (%d)\n",
						radio->int_gpio, rc);
		goto err_int_gpio;
	}

	radio->status_gpio = of_get_named_gpio(np, "silabs,status-gpio", 0);
	if (radio->status_gpio < 0) {
		FMDERR("silabs-status-gpio not provided in device tree");
	} else {
		rc = gpio_request(radio->status_gpio, "silabs_fm_stat_n");
		if (rc) {
			FMDERR("unable to request status gpio %d (%d)\n",
							radio->status_gpio, rc);
			goto err_status_gpio;
		}
	}
	return rc;

err_status_gpio:
	gpio_free(radio->int_gpio);
err_int_gpio:
	gpio_free(radio->reset_gpio);

	return rc;
}

static int silabs_dt_parse_vreg_info(struct device *dev,
			struct fm_power_vreg_data *vreg, const char *vreg_name)
{
	int ret = 0;
	u32 vol_suply[2];
	struct device_node *np = dev->of_node;

	ret = of_property_read_u32_array(np, vreg_name, vol_suply, 2);
	if (ret < 0) {
		FMDERR("Invalid property name\n");
		ret =  -EINVAL;
	} else {
		vreg->low_vol_level = vol_suply[0];
		vreg->high_vol_level = vol_suply[1];
	}
	return ret;
}

static int silabs_fm_vidioc_g_fmt_type_private(struct file *file, void *priv,
						struct v4l2_format *f)
{
	return 0;

}

static const struct v4l2_ioctl_ops silabs_fm_ioctl_ops = {
	.vidioc_querycap              = silabs_fm_vidioc_querycap,
	.vidioc_queryctrl             = silabs_fm_vidioc_queryctrl,
	.vidioc_g_ctrl                = silabs_fm_vidioc_g_ctrl,
	.vidioc_s_ctrl                = silabs_fm_vidioc_s_ctrl,
	.vidioc_g_tuner               = silabs_fm_vidioc_g_tuner,
	.vidioc_s_tuner               = silabs_fm_vidioc_s_tuner,
	.vidioc_g_frequency           = silabs_fm_vidioc_g_frequency,
	.vidioc_s_frequency           = silabs_fm_vidioc_s_frequency,
	.vidioc_s_hw_freq_seek        = silabs_fm_vidioc_s_hw_freq_seek,
	.vidioc_dqbuf                 = silabs_fm_vidioc_dqbuf,
	.vidioc_g_fmt_type_private    = silabs_fm_vidioc_g_fmt_type_private,
};

static const struct v4l2_file_operations silabs_fm_fops = {
	.owner = THIS_MODULE,
	.ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
	.compat_ioctl32 = v4l2_compat_ioctl32,
#endif
	.open = silabs_fm_fops_open,
	.release = silabs_fm_fops_release,
};


static const struct video_device silabs_fm_viddev_template = {
	.fops                   = &silabs_fm_fops,
	.ioctl_ops              = &silabs_fm_ioctl_ops,
	.name                   = DRIVER_NAME,
	.release                = video_device_release,
};

static int silabs_fm_probe(struct i2c_client *client,
				const struct i2c_device_id *id)
{

	struct silabs_fm_device *radio;
	struct regulator *vreg = NULL;
	int retval = 0;
	int i = 0;
	int kfifo_alloc_rc = 0;

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		FMDERR("%s: no support for i2c read/write byte data\n",
								__func__);
		return -EIO;
	}

	vreg = regulator_get(&client->dev, "va");

	if (IS_ERR(vreg)) {
		/*
		 * if analog voltage regulator, VA is not ready yet, return
		 * -EPROBE_DEFER to kernel so that probe will be called at
		 * later point of time.
		 */
		if (PTR_ERR(vreg) == -EPROBE_DEFER) {
			FMDERR("In %s, areg probe defer\n", __func__);
			return PTR_ERR(vreg);
		}
	}
	/* private data allocation */
	radio = kzalloc(sizeof(struct silabs_fm_device), GFP_KERNEL);
	if (!radio) {
		FMDERR("Memory not allocated for radio\n");
		retval = -ENOMEM;
		goto err_initial;
	}

	retval = silabs_parse_dt(&client->dev, radio);
	if (retval) {
		FMDERR("%s: Parsing DT failed(%d)", __func__, retval);
		regulator_put(vreg);
		kfree(radio);
		return retval;
	}

	radio->client = client;

	i2c_set_clientdata(client, radio);
	if (!IS_ERR(vreg)) {
		radio->areg = devm_kzalloc(&client->dev,
					sizeof(struct fm_power_vreg_data),
					GFP_KERNEL);
		if (!radio->areg) {
			FMDERR("%s: allocating memory for areg failed\n",
								__func__);
			regulator_put(vreg);
			kfree(radio);
			return -ENOMEM;
		}

		radio->areg->reg = vreg;
		radio->areg->name = "va";
		radio->areg->is_enabled = 0;
		retval = silabs_dt_parse_vreg_info(&client->dev,
				radio->areg, "silabs,va-supply-voltage");
		if (retval < 0) {
			FMDERR("%s: parsing va-supply failed\n", __func__);
			goto mem_alloc_fail;
		}
	}

	vreg = regulator_get(&client->dev, "vdd");

	if (IS_ERR(vreg)) {
		FMDERR("In %s, vdd supply is not provided\n", __func__);
	} else {
		radio->dreg = devm_kzalloc(&client->dev,
					sizeof(struct fm_power_vreg_data),
					GFP_KERNEL);
		if (!radio->dreg) {
			FMDERR("%s: allocating memory for dreg failed\n",
								__func__);
			retval = -ENOMEM;
			regulator_put(vreg);
			goto mem_alloc_fail;
		}

		radio->dreg->reg = vreg;
		radio->dreg->name = "vdd";
		radio->dreg->is_enabled = 0;
		retval = silabs_dt_parse_vreg_info(&client->dev,
				radio->dreg, "silabs,vdd-supply-voltage");
		if (retval < 0) {
			FMDERR("%s: parsing vdd-supply failed\n", __func__);
			goto err_dreg;
		}
	}

	/* Initialize pin control*/
	retval = silabs_fm_pinctrl_init(radio);
	if (retval) {
		FMDERR("%s: silabs_fm_pinctrl_init returned %d\n",
							__func__, retval);
		/* if pinctrl is not supported, -EINVAL is returned*/
		if (retval == -EINVAL)
			retval = 0;
	} else {
		FMDBG("silabs_fm_pinctrl_init success\n");
	}

	radio->wqueue = NULL;
	radio->wqueue_scan = NULL;
	radio->wqueue_af = NULL;
	radio->wqueue_rds = NULL;

	/* video device allocation */
	radio->videodev = video_device_alloc();
	if (!radio->videodev) {
		FMDERR("radio->videodev is NULL\n");
		goto err_dreg;
	}
	/* initial configuration */
	memcpy(radio->videodev, &silabs_fm_viddev_template,
	  sizeof(silabs_fm_viddev_template));
	strlcpy(radio->v4l2_dev.name, DRIVER_NAME,
				sizeof(radio->v4l2_dev.name));
	retval = v4l2_device_register(NULL, &radio->v4l2_dev);
	if (retval)
		goto err_dreg;
	radio->videodev->v4l2_dev = &radio->v4l2_dev;

	/*allocate internal buffers for decoded rds and event buffer*/
	for (i = 0; i < SILABS_FM_BUF_MAX; i++) {
		spin_lock_init(&radio->buf_lock[i]);

		if (i == SILABS_FM_BUF_RAW_RDS)
			kfifo_alloc_rc = kfifo_alloc(&radio->data_buf[i],
				FM_RDS_BUF * 3, GFP_KERNEL);
		else if (i == SILABS_FM_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) {
			FMDERR("%s: failed allocating buffers %d\n",
				__func__, kfifo_alloc_rc);
			retval = -ENOMEM;
			goto err_fifo_alloc;
		}
	}
	/* initializing the device count  */
	atomic_set(&radio->users, 1);

	/* radio initializes to normal mode */
	radio->lp_mode = 0;
	radio->handle_irq = 1;
	radio->af_wait_timer = AF_WAIT_SEC;
	/* init lock */
	mutex_init(&radio->lock);
	radio->tune_req = 0;
	radio->seek_tune_status = 0;
	init_completion(&radio->sync_req_done);
	/* 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("sifmradio");

	if (!radio->wqueue) {
		retval = -ENOMEM;
		goto err_fifo_alloc;
	}

	FMDBG("%s: creating work q for scan\n", __func__);
	radio->wqueue_scan  = create_singlethread_workqueue("sifmradioscan");

	if (!radio->wqueue_scan) {
		retval = -ENOMEM;
		goto err_wqueue_scan;
	}

	FMDBG("%s: creating work q for af\n", __func__);
	radio->wqueue_af  = create_singlethread_workqueue("sifmradioaf");

	if (!radio->wqueue_af) {
		retval = -ENOMEM;
		goto err_wqueue_af;
	}

	radio->wqueue_rds  = create_singlethread_workqueue("sifmradiords");

	if (!radio->wqueue_rds) {
		retval = -ENOMEM;
		goto err_wqueue_rds;
	}

	/* register video device */
	retval = video_register_device(radio->videodev,
			VFL_TYPE_RADIO,
			RADIO_NR);
	if (retval != 0) {
		FMDERR("Could not register video device\n");
		goto err_all;
	}
	return 0;

err_all:
	destroy_workqueue(radio->wqueue_rds);
err_wqueue_rds:
	destroy_workqueue(radio->wqueue_af);
err_wqueue_af:
	destroy_workqueue(radio->wqueue_scan);
err_wqueue_scan:
	destroy_workqueue(radio->wqueue);
err_fifo_alloc:
	for (i--; i >= 0; i--)
		kfifo_free(&radio->data_buf[i]);
	video_device_release(radio->videodev);
err_dreg:
	if (radio->dreg && radio->dreg->reg) {
		regulator_put(radio->dreg->reg);
		devm_kfree(&client->dev, radio->dreg);
	}
mem_alloc_fail:
	if (radio->areg && radio->areg->reg) {
		regulator_put(radio->areg->reg);
		devm_kfree(&client->dev, radio->areg);
	}
	kfree(radio);
err_initial:
	return retval;
}

static int silabs_fm_remove(struct i2c_client *client)
{
	int i;
	struct silabs_fm_device *radio = i2c_get_clientdata(client);

	if (unlikely(radio == NULL)) {
		FMDERR("%s:radio is null", __func__);
		return -EINVAL;
	}

	if (radio->dreg && radio->dreg->reg) {
		regulator_put(radio->dreg->reg);
		devm_kfree(&client->dev, radio->dreg);
	}

	if (radio->areg && radio->areg->reg) {
		regulator_put(radio->areg->reg);
		devm_kfree(&client->dev, radio->areg);
	}
	/* disable irq */
	destroy_workqueue(radio->wqueue);
	destroy_workqueue(radio->wqueue_scan);
	destroy_workqueue(radio->wqueue_af);
	destroy_workqueue(radio->wqueue_rds);

	video_unregister_device(radio->videodev);

	/* free internal buffers */
	for (i = 0; i < SILABS_FM_BUF_MAX; i++)
		kfifo_free(&radio->data_buf[i]);

	/* free state struct */
	kfree(radio);

	return 0;
}

static const struct i2c_device_id silabs_i2c_id[] = {
	{ DRIVER_NAME, 0 },
	{ },
};
MODULE_DEVICE_TABLE(i2c, silabs_i2c_id);

static const struct of_device_id silabs_fm_match[] = {
	{.compatible = "silabs,si4705"},
	{}
};

static struct i2c_driver silabs_fm_driver = {
	.probe  = silabs_fm_probe,
	.driver = {
		.owner  = THIS_MODULE,
		.name   = "silabs-fm",
		.of_match_table = silabs_fm_match,
	},
	.remove  = silabs_fm_remove,
	.id_table       = silabs_i2c_id,
};


static int __init radio_module_init(void)
{
	return i2c_add_driver(&silabs_fm_driver);
}
module_init(radio_module_init);

static void __exit radio_module_exit(void)
{
	i2c_del_driver(&silabs_fm_driver);
}
module_exit(radio_module_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION(DRIVER_DESC);

