radio-tavarua: Validate whether the current station is good or bad

Provide mechanism to decide whether the currently tuned station
is valid or not.

CRs-Fixed: 371689
Change-Id: I1a28537d242fbb636732b120c9259ad5e7fffcc0
Signed-off-by: Anantha Krishnan <ananthk@codeaurora.org>
diff --git a/drivers/media/radio/radio-tavarua.c b/drivers/media/radio/radio-tavarua.c
index 116b7f9..af4c2c9 100644
--- a/drivers/media/radio/radio-tavarua.c
+++ b/drivers/media/radio/radio-tavarua.c
@@ -122,6 +122,8 @@
 	int enable_optimized_srch_alg;
 	unsigned char spur_table_size;
 	struct fm_spur_data spur_data;
+	atomic_t validate_channel;
+	unsigned char is_station_valid;
 };
 
 /**************************************************************************
@@ -152,6 +154,7 @@
 static int update_spur_table(struct tavarua_device *radio);
 static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
 	unsigned long offset, unsigned char *buf);
+static int compute_MPX_DCC(struct tavarua_device *radio, int *val);
 
 /* work function */
 static void read_int_stat(struct work_struct *work);
@@ -734,7 +737,7 @@
 static void tavarua_handle_interrupts(struct tavarua_device *radio)
 {
 	int i;
-	int retval;
+	int retval, adj_channel_tune_req = 0;
 	unsigned char xfr_status;
 	if (!radio->handle_irq) {
 		FMDBG("IRQ happend, but I wont handle it\n");
@@ -758,7 +761,25 @@
 			complete(&radio->sync_req_done);
 			radio->tune_req = 0;
 		}
-		tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC);
+
+		/*
+		 * Do not queue the TUNE event while validating if the station
+		 * is good or not. As part of channel validation we tune to the
+		 * adjacent station, measure its MPX_DCC value, then tune back
+		 * to the original station and measure its MPX_DCC value.
+		 * Compare the MPX_DCC values of curent and adjacent stations
+		 * and decide if the channel is valid or not. During this period
+		 * we should not queue the TUNE event to the upper layers.
+		 */
+		adj_channel_tune_req = atomic_read(&radio->validate_channel);
+		if (adj_channel_tune_req) {
+			complete(&radio->sync_req_done);
+			FMDBG("Tune event for adjacent channel\n");
+		} else {
+			tavarua_q_event(radio, TAVARUA_EVT_TUNE_SUCC);
+			FMDBG("Queueing Tune event\n");
+		}
+
 		if (radio->srch_params.get_list) {
 			tavarua_start_xfr(radio, TAVARUA_XFR_SRCH_LIST,
 							RX_STATIONS_0);
@@ -2603,40 +2624,57 @@
 static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
 	unsigned long offset, unsigned char *buf) {
 
-	unsigned char xfr_buf[XFR_REG_NUM];
+	unsigned char xfr_buf[XFR_REG_NUM + 1];
 	int retval = 0, temp = 0;
 
+	/* zero initialize the buffer */
 	memset(xfr_buf, 0x0, XFR_REG_NUM);
+
+	/* save the 'size' parameter */
 	temp = size;
 
-	xfr_buf[XFR_MODE_OFFSET]     = (size << 1);
+	/* Populate the XFR bytes */
+	xfr_buf[XFR_MODE_OFFSET]     = LSH_DATA(size, 1);
 	xfr_buf[XFR_ADDR_MSB_OFFSET] = GET_FREQ(offset, 1);
 	xfr_buf[XFR_ADDR_LSB_OFFSET] = GET_FREQ(offset, 0);
-
 	if (op == XFR_READ) {
+		if (size > XFR_REG_NUM) {
+			FMDERR("%s: Cant read more than 16 bytes\n", __func__);
+			return -EINVAL;
+		}
 		xfr_buf[XFR_MODE_OFFSET] |= (XFR_PEEK_MODE);
 		size = 3;
 	} else if (op == XFR_WRITE) {
+		if (size > (XFR_REG_NUM - 2)) {
+			FMDERR("%s: Cant write more than 14 bytes\n", __func__);
+			return -EINVAL;
+		}
 		xfr_buf[XFR_MODE_OFFSET] |= (XFR_POKE_MODE);
 		memcpy(&xfr_buf[XFR_DATA_OFFSET], buf, size);
 		size += 3;
 	}
 
+	/* Perform the XFR READ/WRITE operation */
+	init_completion(&radio->sync_req_done);
 	retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, size);
 	if (retval < 0) {
-		FMDERR("%s: Failed to performXFR operation\n", __func__);
+		FMDERR("%s: Failed to perform XFR operation\n", __func__);
 		return retval;
 	}
 
-	size = temp;
-
 	/*Wait for the XFR interrupt */
-	init_completion(&radio->sync_req_done);
 	if (!wait_for_completion_timeout(&radio->sync_req_done,
 		msecs_to_jiffies(WAIT_TIMEOUT))) {
 		FMDERR("Timeout: No XFR interrupt");
+		return -ETIMEDOUT;
 	}
 
+	/*
+	 * For XFR READ operation save the XFR data provided by the SOC.
+	 * Firmware reads the data from the address specified and places
+	 * them in to the registers XFRDAT0-XFRDAT15 which the host can read.
+	 */
+	size = temp;
 	if (op == XFR_READ) {
 		retval = tavarua_read_registers(radio, XFRDAT0, size);
 		if (retval < 0) {
@@ -2645,6 +2683,10 @@
 		}
 		if (buf != NULL)
 			memcpy(buf, &radio->registers[XFRDAT0], size);
+		else {
+			FMDERR("%s: No buffer to copy XFR data\n", __func__);
+			return -EINVAL;
+		}
 	}
 
 	return retval;
@@ -2966,6 +3008,9 @@
 	case V4L2_CID_PRIVATE_IRIS_GET_SINR:
 		retval = 0;
 		break;
+	case V4L2_CID_PRIVATE_VALID_CHANNEL:
+		ctrl->value = radio->is_station_valid;
+		break;
 	default:
 		retval = -EINVAL;
 	}
@@ -3114,12 +3159,15 @@
 		struct v4l2_control *ctrl)
 {
 	struct tavarua_device *radio = video_get_drvdata(video_devdata(file));
-	int retval = 0;
-	int size = 0, cnt = 0;
+	int retval = 0, size = 0, cnt = 0;
 	unsigned char value;
 	unsigned char xfr_buf[XFR_REG_NUM];
 	unsigned char tx_data[XFR_REG_NUM];
 	unsigned char dis_buf[XFR_REG_NUM];
+	unsigned int freq = 0, mpx_dcc = 0;
+	unsigned long curr = 0, prev = 0;
+
+	memset(xfr_buf, 0x0, XFR_REG_NUM);
 
 	switch (ctrl->id) {
 	case V4L2_CID_AUDIO_VOLUME:
@@ -3538,6 +3586,104 @@
 		if (retval < 0)
 			FMDERR("Tone generator failed\n");
 		break;
+	case V4L2_CID_PRIVATE_VALID_CHANNEL:
+		/* Do not notify the host of tune event */
+		atomic_set(&radio->validate_channel, 1);
+
+		FMDBG("Going into low power mode\n");
+		retval = tavarua_disable_interrupts(radio);
+
+		/*
+		 * Tune to 50KHz adjacent channel. If the requested station
+		 * falls in JAPAN band and on the lower band-limit, then the
+		 * adjacnet channel to be considered is 50KHz to the right side
+		 * of the requested station as firmware does not allows to tune
+		 * to frequency outside the range: 76000KHz to 108000KHz.
+		 */
+		if (ctrl->value == REGION_JAPAN_STANDARD_BAND_LOW)
+			freq = (ctrl->value + ADJ_CHANNEL_KHZ);
+		else
+			freq = (ctrl->value - ADJ_CHANNEL_KHZ);
+		INIT_COMPLETION(radio->sync_req_done);
+		retval = tavarua_set_freq(radio, (freq * TUNE_MULT));
+		if (retval < 0) {
+			FMDERR("Failed to tune to adjacent station\n");
+			goto error;
+		}
+		if (!wait_for_completion_timeout(&radio->sync_req_done,
+			msecs_to_jiffies(wait_timeout))) {
+			FMDERR("Timeout: No Tune response\n");
+			retval = -ETIMEDOUT;
+			goto error;
+		}
+
+		/*
+		 * Wait for a minimum of 100ms for the firmware
+		 * to start collecting the MPX_DCC values
+		 */
+		msleep(TAVARUA_DELAY * 10);
+
+		/* Compute MPX_DCC of adjacent station */
+		retval = compute_MPX_DCC(radio, &mpx_dcc);
+		if (retval < 0) {
+			FMDERR("Failed to get MPX_DCC of adjacent station\n");
+			goto error;
+		}
+		/* Calculate the absolute value of MPX_DCC */
+		prev = abs(mpx_dcc);
+
+		/* Tune back to original station */
+		INIT_COMPLETION(radio->sync_req_done);
+		retval = tavarua_set_freq(radio, (ctrl->value * TUNE_MULT));
+		if (retval < 0) {
+			FMDERR("Failed to tune to requested station\n");
+			goto error;
+		}
+		if (!wait_for_completion_timeout(&radio->sync_req_done,
+			msecs_to_jiffies(wait_timeout))) {
+			FMDERR("Timeout: No Tune response\n");
+			retval = -ETIMEDOUT;
+			goto error;
+		}
+
+		/*
+		 * Wait for a minimum of 100ms for the firmware
+		 * to start collecting the MPX_DCC values
+		 */
+		msleep(TAVARUA_DELAY * 10);
+
+		/* Compute MPX_DCC of current station */
+		retval = compute_MPX_DCC(radio, &mpx_dcc);
+		if (retval < 0) {
+			FMDERR("Failed to get MPX_DCC of current station\n");
+			goto error;
+		}
+		/* Calculate the absolute value of MPX_DCC */
+		curr = abs(mpx_dcc);
+
+		FMDBG("Going into normal power mode\n");
+		tavarua_setup_interrupts(radio,
+			(radio->registers[RDCTRL] & 0x03));
+
+		FMDBG("Absolute MPX_DCC of current station  : %lu\n", curr);
+		FMDBG("Absolute MPX_DCC of adjacent station : %lu\n", prev);
+
+		/*
+		 * For valid stations, the absolute MPX_DCC value will be within
+		 * the range 0 <= MPX_DCC <= 12566 and the MPX_DCC value of the
+		 * adjacent station will be greater than 20,000.
+		 */
+		if ((curr <= MPX_DCC_LIMIT) &&
+			(prev > MPX_DCC_UPPER_LIMIT)) {
+			FMDBG("%d KHz is A VALID STATION!\n", ctrl->value);
+			radio->is_station_valid = VALID_CHANNEL;
+		} else {
+			FMDBG("%d KHz is NOT A VALID STATION!\n", ctrl->value);
+			radio->is_station_valid = INVALID_CHANNEL;
+		}
+error:
+		atomic_set(&radio->validate_channel, 0);
+		break;
 	default:
 		retval = -EINVAL;
 	}
@@ -3548,6 +3694,56 @@
 	return retval;
 }
 
+static int compute_MPX_DCC(struct tavarua_device *radio, int *val)
+{
+
+	int DCC = 0, retval = 0;
+	int MPX_DCC[3];
+	unsigned char value;
+	unsigned char xfr_buf[XFR_REG_NUM];
+
+	/* Freeze the MPX_DCC value from changing */
+	value = CTRL_ON;
+	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, MPX_DCC_BYPASS_REG, &value);
+	if (retval < 0) {
+		FMDERR("%s: Failed to freeze MPX_DCC\n", __func__);
+		return retval;
+	}
+
+	/* Measure the MPX_DCC of current station. */
+	retval = xfr_rdwr_data(radio, XFR_READ, 3, MPX_DCC_DATA_REG, xfr_buf);
+	if (retval < 0) {
+		FMDERR("%s: Failed to read MPX_DCC\n", __func__);
+		return retval;
+	}
+	MPX_DCC[0] = xfr_buf[0];
+	MPX_DCC[1] = xfr_buf[1];
+	MPX_DCC[2] = xfr_buf[2];
+	/*
+	 * Form the final MPX_DCC parameter
+	 * MPX_DCC[0] will form the LSB part
+	 * MPX_DCC[1] will be the middle part and 4 bits of
+	 * MPX_DCC[2] will be the MSB part of the 20-bit signed MPX_DCC
+	 */
+	DCC = (LSH_DATA(MPX_DCC[2], 16) | LSH_DATA(MPX_DCC[1], 8) | MPX_DCC[0]);
+
+	/* if bit-19 is '1',set remaining bits to '1' & make it -tive */
+	if (DCC & 0x00080000)
+		DCC |= 0xFFF00000;
+
+	*val = DCC;
+
+	/* Un-freeze the MPX_DCC value */
+	value = CTRL_OFF;
+	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, 0x88C0, &value);
+	if (retval < 0) {
+		FMDERR("%s: Failed to un-freeze MPX_DCC\n", __func__);
+		return retval;
+	}
+
+	return retval;
+}
+
 /*=============================================================================
 FUNCTION:  tavarua_vidioc_g_tuner
 =============================================================================*/
diff --git a/include/media/tavarua.h b/include/media/tavarua.h
index adbdada..d7b1340 100644
--- a/include/media/tavarua.h
+++ b/include/media/tavarua.h
@@ -176,6 +176,7 @@
 	V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI,
 	V4L2_CID_PRIVATE_SPUR_SELECTION,
 	V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE,
+	V4L2_CID_PRIVATE_VALID_CHANNEL,
 
 };
 
@@ -336,6 +337,16 @@
 	RDS_AF_JUMP,
 };
 
+/* Band limits */
+#define REGION_US_EU_BAND_LOW		87500
+#define REGION_US_EU_BAND_HIGH		108000
+#define REGION_JAPAN_STANDARD_BAND_LOW	76000
+#define REGION_JAPAN_STANDARD_BAND_HIGH	90000
+#define REGION_JAPAN_WIDE_BAND_LOW	90000
+#define REGION_JAPAN_WIDE_BAND_HIGH	108000
+#define MPX_DCC_BYPASS_REG		0x88C0
+#define MPX_DCC_DATA_REG		0x88C2
+
 enum audio_path {
 	FM_DIGITAL_PATH,
 	FM_ANALOG_PATH
@@ -534,6 +545,12 @@
 #define SPUR_TABLE_START_ADDR	(SPUR_TABLE_ADDR + 1)
 #define XFR_PEEK_COMPLETE	(XFR_PEEK_MODE | READ_COMPLETE)
 #define XFR_POKE_COMPLETE	(XFR_POKE_MODE)
+#define TUNE_MULT		(16)
+#define ADJ_CHANNEL_KHZ		(50)
+#define MPX_DCC_UPPER_LIMIT	(20000)
+#define MPX_DCC_LIMIT		(12566)
+#define INVALID_CHANNEL		(0)
+#define VALID_CHANNEL		(1)
 
 #define COMPUTE_SPUR(val)	((((val) - (76000)) / (50)))
 #define GET_FREQ(val, bit)	((bit == 1) ? ((val) >> 8) : ((val) & 0xFF))