radio-tavarua: Add IOCTL commands to configure the SPUR Table

Provide interfaces to allow host to update the SPUR Table
with newly found spur frequencies and their RMSSI values.

CRs-Fixed: 352591
Change-Id: I84f296ea313971cc1995c644d604f96581be5f7f
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 c15609f..971cf10 100644
--- a/drivers/media/radio/radio-tavarua.c
+++ b/drivers/media/radio/radio-tavarua.c
@@ -120,6 +120,8 @@
 	/*PS repeatcount for PS Tx */
 	int ps_repeatcount;
 	int enable_optimized_srch_alg;
+	unsigned char spur_table_size;
+	struct fm_spur_data spur_data;
 };
 
 /**************************************************************************
@@ -147,6 +149,10 @@
 			enum radio_state_t state);
 static int tavarua_request_irq(struct tavarua_device *radio);
 static void start_pending_xfr(struct tavarua_device *radio);
+static int update_spur_table(struct tavarua_device *radio);
+static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
+	unsigned long offset, unsigned char *buf);
+
 /* work function */
 static void read_int_stat(struct work_struct *work);
 
@@ -1025,6 +1031,35 @@
 			FMDBG("write PHY_TXGAIN is successful");
 			complete(&radio->sync_req_done);
 			break;
+		case (XFR_POKE_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
+		case (XFR_POKE_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(ONE_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(TWO_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(THREE_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(FOUR_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(FIVE_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(SIX_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(SEVEN_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(EIGHT_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(NINE_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(TEN_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(ELEVEN_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(TWELVE_BYTE, 1)):
+		case (XFR_PEEK_COMPLETE | LSH_DATA(THIRTEEN_BYTE, 1)):
+			FMDBG("XFR interrupt for PEEK/POKE complete\n");
+			complete(&radio->sync_req_done);
+			break;
 		default:
 			FMDERR("UNKNOWN XFR = %d\n", xfr_status);
 		}
@@ -2158,6 +2193,7 @@
 	wait_for_completion(&radio->shutdown_done);
 	radio->handle_irq = 1;
 	radio->lp_mode = 1;
+	radio->spur_table_size = 0;
 	atomic_inc(&radio->users);
 	radio->marimba->mod_id = SLAVE_ID_BAHAMA;
 	flush_workqueue(radio->wqueue);
@@ -2481,6 +2517,135 @@
 
 	return retval;
 }
+
+static int update_spur_table(struct tavarua_device *radio)
+{
+	unsigned char xfr_buf[XFR_REG_NUM];
+	unsigned char size = 0, tbl_size = 0;
+	int index = 0, offset = 0, addr = 0x0, val = 0;
+	int retval = 0, temp = 0, cnt = 0, j = 0;
+
+	memset(xfr_buf, 0x0, XFR_REG_NUM);
+
+	/* Read the SPUR Table Size */
+	retval = xfr_rdwr_data(radio, XFR_READ, 1, SPUR_TABLE_ADDR, &tbl_size);
+	if (retval < 0) {
+		FMDERR("%s: Failed to read SPUR table size\n", __func__);
+		return retval;
+	}
+
+	/* Calculate the new SPUR Register address */
+	val = addr = (SPUR_TABLE_START_ADDR + (tbl_size * 3));
+
+	/* Save the SPUR Table length configured by user*/
+	temp = radio->spur_table_size;
+
+	/* COnfigure the new spur table length */
+	size = (radio->spur_table_size + tbl_size);
+	retval = xfr_rdwr_data(radio, XFR_WRITE, 1, SPUR_TABLE_ADDR, &size);
+	if (retval < 0) {
+		FMDERR("%s: Failed to configure SPUR table size\n", __func__);
+		return retval;
+	}
+
+	/* Program the spur table entries */
+	for (cnt = 0; cnt < (temp / 4); cnt++) {
+		offset  = 0;
+		for (j = 0; j < 4; j++) {
+			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
+				radio->spur_data.freq[index]), 1);
+			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
+				radio->spur_data.freq[index]), 0);
+			xfr_buf[offset++] =
+				radio->spur_data.rmssi[index];
+			index++;
+		}
+		retval = xfr_rdwr_data(radio, XFR_WRITE, (SPUR_DATA_SIZE * 4),
+			addr, xfr_buf);
+		if (retval < 0) {
+			FMDERR("%s: Failed to program SPUR frequencies\n",
+				__func__);
+			return retval;
+		}
+		addr += (SPUR_DATA_SIZE * 4);
+	}
+
+	/* Program the additional SPUR Frequencies */
+	temp = radio->spur_table_size;
+	temp = (temp % 4);
+	if (temp > 0) {
+		offset = 0;
+		for (j = 0; j < temp; j++) {
+			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
+				radio->spur_data.freq[index]), 1);
+			xfr_buf[offset++] = GET_FREQ(COMPUTE_SPUR(
+				radio->spur_data.freq[index]), 0);
+			xfr_buf[offset++] =
+				radio->spur_data.rmssi[index];
+			index++;
+		}
+		size   = (temp * SPUR_DATA_SIZE);
+		retval = xfr_rdwr_data(radio, XFR_WRITE, size, addr, xfr_buf);
+		if (retval < 0) {
+			FMDERR("%s: Failed to program SPUR frequencies\n",
+				__func__);
+			return retval;
+		}
+	}
+
+	return retval;
+}
+
+static int xfr_rdwr_data(struct tavarua_device *radio, int op, int size,
+	unsigned long offset, unsigned char *buf) {
+
+	unsigned char xfr_buf[XFR_REG_NUM];
+	int retval = 0, temp = 0;
+
+	memset(xfr_buf, 0x0, XFR_REG_NUM);
+	temp = size;
+
+	xfr_buf[XFR_MODE_OFFSET]     = (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) {
+		xfr_buf[XFR_MODE_OFFSET] |= (XFR_PEEK_MODE);
+		size = 3;
+	} else if (op == XFR_WRITE) {
+		xfr_buf[XFR_MODE_OFFSET] |= (XFR_POKE_MODE);
+		memcpy(&xfr_buf[XFR_DATA_OFFSET], buf, size);
+		size += 3;
+	}
+
+	retval = tavarua_write_registers(radio, XFRCTRL, xfr_buf, size);
+	if (retval < 0) {
+		FMDERR("%s: Failed to performXFR 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");
+	}
+
+	if (op == XFR_READ) {
+		retval = tavarua_read_registers(radio, XFRDAT0, size);
+		if (retval < 0) {
+			FMDERR("%s: Failed to read the XFR data\n", __func__);
+			return retval;
+		}
+		if (buf != NULL)
+			memcpy(buf, &radio->registers[XFRDAT0], size);
+	}
+
+	return retval;
+}
+
 static int peek_MPX_DCC(struct tavarua_device *radio)
 {
 	int retval = 0;
@@ -3001,6 +3166,7 @@
 		}
 		/* check if off */
 		else if ((ctrl->value == FM_OFF) && radio->registers[RDCTRL]) {
+			radio->spur_table_size = 0;
 			FMDBG("%s: turning off...\n", __func__);
 			tavarua_write_register(radio, RDCTRL, ctrl->value);
 			/* flush the event and work queues */
@@ -3299,8 +3465,20 @@
 	case V4L2_CID_PRIVATE_TAVARUA_DO_CALIBRATION:
 	case V4L2_CID_PRIVATE_SINR_THRESHOLD:
 	case V4L2_CID_PRIVATE_SINR_SAMPLES:
+	case V4L2_CID_PRIVATE_SPUR_SELECTION:
 		retval = 0;
 		break;
+	case V4L2_CID_PRIVATE_SPUR_FREQ:
+		radio->spur_data.freq[radio->spur_table_size] =
+			ctrl->value;
+		break;
+	case V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI:
+		radio->spur_data.rmssi[radio->spur_table_size++] =
+			ctrl->value;
+		break;
+	case V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE:
+		retval = update_spur_table(radio);
+		break;
 	default:
 		retval = -EINVAL;
 	}
diff --git a/include/media/tavarua.h b/include/media/tavarua.h
index 52194f9..9943287 100644
--- a/include/media/tavarua.h
+++ b/include/media/tavarua.h
@@ -172,6 +172,10 @@
 	V4L2_CID_PRIVATE_INTF_HIGH_THRESHOLD, /* 0x800002E */
 	V4L2_CID_PRIVATE_SINR_THRESHOLD,  /* 0x800002F : IRIS */
 	V4L2_CID_PRIVATE_SINR_SAMPLES,  /* 0x8000030 : IRIS */
+	V4L2_CID_PRIVATE_SPUR_FREQ,
+	V4L2_CID_PRIVATE_SPUR_FREQ_RMSSI,
+	V4L2_CID_PRIVATE_SPUR_SELECTION,
+	V4L2_CID_PRIVATE_UPDATE_SPUR_TABLE,
 
 };
 
@@ -484,4 +488,47 @@
 	TAVARUA_REGION_OTHER
 };
 
+enum {
+	ONE_BYTE = 1,
+	TWO_BYTE,
+	THREE_BYTE,
+	FOUR_BYTE,
+	FIVE_BYTE,
+	SIX_BYTE,
+	SEVEN_BYTE,
+	EIGHT_BYTE,
+	NINE_BYTE,
+	TEN_BYTE,
+	ELEVEN_BYTE,
+	TWELVE_BYTE,
+	THIRTEEN_BYTE
+};
+#define XFR_READ		(0)
+#define XFR_WRITE		(1)
+#define XFR_MODE_OFFSET		(0)
+#define XFR_ADDR_MSB_OFFSET	(1)
+#define XFR_ADDR_LSB_OFFSET	(2)
+#define XFR_DATA_OFFSET		(3)
+#define SPUR_DATA_SIZE		(3)
+#define MAX_SPUR_FREQ_LIMIT	(30)
+#define READ_COMPLETE		(0x20)
+#define SPUR_TABLE_ADDR		(0x0BB7)
+#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 COMPUTE_SPUR(val)	((((val) - (76000)) / (50)))
+#define GET_FREQ(val, bit)	((bit == 1) ? ((val) >> 8) : ((val) & 0xFF))
+
+struct fm_spur_data {
+	int freq[MAX_SPUR_FREQ_LIMIT];
+	__s8 rmssi[MAX_SPUR_FREQ_LIMIT];
+} __packed;
+
+struct fm_def_data_wr_req {
+	__u8    mode;
+	__u8    length;
+	__u8   data[XFR_REG_NUM];
+} __packed;
+
 #endif /* __LINUX_TAVARUA_H */