summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rwxr-xr-xdrivers/media/radio/CG2900/Makefile12
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_api.c3389
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_api.h1077
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_driver.c5017
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_driver.h1856
-rw-r--r--drivers/media/radio/CG2900/radio-cg2900.c3024
-rw-r--r--drivers/media/radio/Kconfig16
-rw-r--r--drivers/media/radio/Makefile1
8 files changed, 14392 insertions, 0 deletions
diff --git a/drivers/media/radio/CG2900/Makefile b/drivers/media/radio/CG2900/Makefile
new file mode 100755
index 00000000000..60b12dd9c35
--- /dev/null
+++ b/drivers/media/radio/CG2900/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the CG2900 FM Radio Driver
+#
+
+radio_cg2900-objs := radio-cg2900.o cg2900_fm_api.o cg2900_fm_driver.o
+
+obj-$(CONFIG_RADIO_CG2900) += radio_cg2900.o
+
+ccflags-y := \
+ -Idrivers/staging/cg2900/include \
+
+
diff --git a/drivers/media/radio/CG2900/cg2900_fm_api.c b/drivers/media/radio/CG2900/cg2900_fm_api.c
new file mode 100644
index 00000000000..f73f3ef39ee
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_api.c
@@ -0,0 +1,3389 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Host API's for ST-Ericsson FM Chip.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/kthread.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include "cg2900_fm_driver.h"
+
+#define CG2910_FM_LUT_INFO_FILE "FM_FW_CG2910_1_0_P1_4_lut_info.fw"
+#define CG2910_FM_PROG_INFO_FILE "FM_FW_CG2910_1_0_P1_4_prog_info.fw"
+#define CG2910_LUT_IDX 0
+#define CG2910_PROG_IDX 1
+#define CG2910_MAX_FILES_DL 2
+
+#define CG2900_FM_BT_SRC_COEFF_INFO_FILE "cg2900_fm_bt_src_coeff_info.fw"
+#define CG2900_FM_EXT_SRC_COEFF_INFO_FILE "cg2900_fm_ext_src_coeff_info.fw"
+#define CG2900_FM_FM_COEFF_INFO_FILE "cg2900_fm_fm_coeff_info.fw"
+#define CG2900_FM_FM_PROG_INFO_FILE "cg2900_fm_fm_prog_info.fw"
+#define CG2900_FM_LINE_BUFFER_LENGTH 128
+#define CG2900_FM_FILENAME_MAX 128
+#define FW_FILE_PARAM_LEN 3
+/* RDS Tx PTY set to Other music */
+#define OTHER_MUSIC 15
+#define DEFAULT_AUDIO_DEVIATION 0x1AA9
+#define DEFAULT_NOTIFICATION_HOLD_OFF_TIME 0x000A
+
+/* Specific chip version data */
+#define CG2900_PG1_REV 0x0101
+#define CG2900_PG2_REV 0x0200
+#define CG2900_PG1_SPECIAL_REV 0x0700
+#define CG2905_PG1_1_REV 0x1805
+#define CG2910_PG1_REV 0x1004
+#define CG2910_PG2_REV 0x1008
+
+static bool fm_rds_status;
+static bool fm_prev_rds_status;
+static u16 program_identification_code;
+static u16 default_program_identification_code = 0x1234;
+static u16 program_type_code;
+static u16 default_program_type_code = OTHER_MUSIC;
+static char program_service[MAX_PSN_SIZE];
+static char default_program_service[MAX_PSN_SIZE] = "FM-Xmit ";
+static char radio_text[MAX_RT_SIZE];
+static char default_radio_text[MAX_RT_SIZE] = "Default Radio Text "
+ "Default Radio Text Default Radio Text Default";
+static bool a_b_flag;
+u8 fm_event;
+static struct mutex rds_mutex;
+struct cg2900_fm_rds_buf fm_rds_buf[MAX_RDS_BUFFER][MAX_RDS_GROUPS];
+struct cg2900_fm_rds_info fm_rds_info;
+static enum cg2900_fm_state fm_state;
+static enum cg2900_fm_mode fm_mode;
+static struct cg2900_version_info version_info;
+
+/**
+ * cg2900_fm_get_one_line_of_text()- Get One line of text from a file.
+ *
+ * Replacement function for stdio function fgets.This function extracts one
+ * line of text from input file.
+ *
+ * @wr_buffer: Buffer to copy text to.
+ * @max_nbr_of_bytes: Max number of bytes to read, i.e. size of rd_buffer.
+ * @rd_buffer: Data to parse.
+ * @bytes_copied: Number of bytes copied to wr_buffer.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *cg2900_fm_get_one_line_of_text(
+ char *wr_buffer,
+ int max_nbr_of_bytes,
+ char *rd_buffer,
+ int *bytes_copied
+ )
+{
+ char *curr_wr = wr_buffer;
+ char *curr_rd = rd_buffer;
+ char in_byte;
+
+ *bytes_copied = 0;
+
+ do {
+ *curr_wr = *curr_rd;
+ in_byte = *curr_wr;
+ curr_wr++;
+ curr_rd++;
+ (*bytes_copied)++;
+ } while ((*bytes_copied <= max_nbr_of_bytes) && (in_byte != '\0')
+ && (in_byte != '\n'));
+ *curr_wr = '\0';
+ return curr_rd;
+}
+
+/**
+ * cg2900_fm_get_file_to_load() - Parse info file and find correct target file.
+ *
+ * @fw: Firmware structure containing file data.
+ * @file_name: (out) Pointer to name of requested file.
+ *
+ * Returns:
+ * True, if target file was found,
+ * False, otherwise.
+ */
+static bool cg2900_fm_get_file_to_load(
+ const struct firmware *fw,
+ char **file_name
+ )
+{
+ char *line_buffer;
+ char *curr_file_buffer;
+ int bytes_left_to_parse = fw->size;
+ int bytes_read = 0;
+ bool file_found = false;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kmalloc(CG2900_FM_LINE_BUFFER_LENGTH,
+ GFP_KERNEL);
+
+ if (line_buffer == NULL) {
+ FM_ERR_REPORT("Failed to allocate:"
+ "file_name 0x%X, line_buffer 0x%X",
+ (unsigned int)file_name,
+ (unsigned int)line_buffer);
+ goto error;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer =
+ cg2900_fm_get_one_line_of_text(line_buffer,
+ min
+ (CG2900_FM_LINE_BUFFER_LENGTH,
+ (int)(fw->size -
+ bytes_read)),
+ curr_file_buffer,
+ &bytes_read);
+
+ bytes_left_to_parse -= bytes_read;
+ if (bytes_left_to_parse <= 0) {
+ /* End of file => Leave while loop */
+ FM_ERR_REPORT("Reached end of file."
+ "No file found!");
+ break;
+ }
+
+ /*
+ * Check if the line of text is a comment
+ * or not, comments begin with '#'
+ */
+ if (*line_buffer != '#') {
+ u32 hci_rev = 0;
+ u32 lmp_sub = 0;
+
+ FM_DEBUG_REPORT("Found a valid line <%s>",
+ line_buffer);
+
+ /*
+ * Check if we can find the correct
+ * HCI revision and LMP subversion
+ * as well as a file name in the text line
+ * Store the filename if the actual file can
+ * be found in the file system
+ */
+ if (sscanf(line_buffer, "%x%x%s",
+ (unsigned int *)&hci_rev,
+ (unsigned int *)&lmp_sub,
+ *file_name) == FW_FILE_PARAM_LEN
+ && hci_rev == version_info.revision
+ && lmp_sub == version_info.sub_version) {
+ FM_INFO_REPORT("File name = %s "
+ "HCI Revision"
+ "= 0x%04X LMP "
+ "Subversion = 0x%04X",
+ *file_name,
+ (unsigned int)hci_rev,
+ (unsigned int)lmp_sub);
+
+ /*
+ * Name has already been stored above.
+ * Nothing more to do
+ */
+ file_found = true;
+ } else {
+ /*Zero the name buffer so it is clear to next read*/
+ memset(*file_name, 0x00,
+ CG2900_FM_FILENAME_MAX);
+ }
+ }
+ }
+ kfree(line_buffer);
+error:
+ return file_found;
+}
+
+
+/**
+ * cg2910_fm_load_firmware() - Loads the FM lut and
+ * Program firmware files for CG2910
+ *
+ * @device: Pointer to char device requesting the operation.
+ *
+ * Returns:
+ * 0, if firmware download is successful
+ * -ENOENT, file not found.
+ * -ENOMEM, out of memory
+ */
+static int cg2910_fm_load_firmware(
+ struct device *device
+ )
+{
+ int err;
+ bool file_found;
+ int result = 0;
+ const struct firmware *fm_fw_info[2];
+ const struct firmware *fm_firmware[2];
+ char *fm_fw_file_name = NULL;
+ int loopi = 0;
+
+ FM_INFO_REPORT("+cg2910_fm_load_firmware");
+ fm_fw_info[CG2910_LUT_IDX] = NULL;
+ fm_fw_info[CG2910_PROG_IDX] = NULL;
+ fm_firmware[CG2910_LUT_IDX] = NULL;
+ fm_firmware[CG2910_PROG_IDX] = NULL;
+
+ /* Open fm_fw_info lut file. */
+ err = request_firmware(&fm_fw_info[CG2910_LUT_IDX],
+ CG2910_FM_LUT_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_fw_info lut file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_fw_info prog file. */
+ err = request_firmware(&fm_fw_info[CG2910_PROG_IDX],
+ CG2910_FM_PROG_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_fw_info prog file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ fm_fw_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_fw_file_name == NULL) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_fw_file_name");
+ result = -ENOMEM;
+ goto error;
+ }
+
+ /* Put a loop for downloading lut and prog */
+ for (loopi = 0; loopi < CG2910_MAX_FILES_DL; loopi++) {
+ /*
+ * Now we have the fm_fw_info file. See if we can
+ * find the right fm_fw_file_name file as well
+ */
+ file_found = cg2900_fm_get_file_to_load(fm_fw_info[loopi],
+ &fm_fw_file_name);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't find fm_fw_file_name file!! "
+ "Major error!!!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * OK. Now it is time to download the firmware
+ * First download lut file & then prog
+ */
+ err = request_firmware(&fm_firmware[loopi],
+ fm_fw_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_firmware"
+ " file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2910_fm_load_firmware: "
+ "Downloading %s of %d bytes",
+ fm_fw_file_name, fm_firmware[loopi]->size);
+ if (fmd_send_fm_firmware((u8 *) fm_firmware[loopi]->data,
+ fm_firmware[loopi]->size)) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: Error in "
+ "downloading %s", fm_fw_file_name);
+ result = -ENOENT;
+ goto error;
+ }
+ }
+
+error:
+ /* Release fm_fw_info lut and prog file */
+ if (fm_fw_info[CG2910_LUT_IDX])
+ release_firmware(fm_fw_info[CG2910_LUT_IDX]);
+ if (fm_fw_info[CG2910_PROG_IDX])
+ release_firmware(fm_fw_info[CG2910_PROG_IDX]);
+
+ if (fm_firmware[CG2910_LUT_IDX])
+ release_firmware(fm_firmware[CG2910_LUT_IDX]);
+ if (fm_firmware[CG2910_PROG_IDX])
+ release_firmware(fm_firmware[CG2910_PROG_IDX]);
+
+ /* Free Allocated memory */
+ kfree(fm_fw_file_name);
+ FM_DEBUG_REPORT("-cg2910_fm_load_firmware: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_load_firmware() - Loads the FM Coeffecients and F/W file(s)
+ * for CG2900
+ * @device: Pointer to char device requesting the operation.
+ *
+ * Returns:
+ * 0, if firmware download is successful
+ * -ENOENT, file not found.
+ * -ENOMEM, out of memory
+ */
+static int cg2900_fm_load_firmware(
+ struct device *device
+ )
+{
+ int err;
+ bool file_found;
+ int result = 0;
+ const struct firmware *bt_src_coeff_info;
+ const struct firmware *ext_src_coeff_info;
+ const struct firmware *fm_coeff_info;
+ const struct firmware *fm_prog_info;
+ char *bt_src_coeff_file_name = NULL;
+ char *ext_src_coeff_file_name = NULL;
+ char *fm_coeff_file_name = NULL;
+ char *fm_prog_file_name = NULL;
+
+ FM_INFO_REPORT("+cg2900_fm_load_firmware");
+
+ /* Open bt_src_coeff info file. */
+ err = request_firmware(&bt_src_coeff_info,
+ CG2900_FM_BT_SRC_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get bt_src_coeff info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the bt_src_coeff info file.
+ * See if we can find the right bt_src_coeff file as well
+ */
+ bt_src_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (bt_src_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "bt_src_coeff_file_name");
+ release_firmware(bt_src_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(bt_src_coeff_info,
+ &bt_src_coeff_file_name);
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(bt_src_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find bt_src_coeff file!! "
+ "Major error!!!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open ext_src_coeff info file. */
+ err = request_firmware(&ext_src_coeff_info,
+ CG2900_FM_EXT_SRC_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get ext_src_coeff_info info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the ext_src_coeff info file. See if we can
+ * find the right ext_src_coeff file as well
+ */
+ ext_src_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (ext_src_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "ext_src_coeff_file_name");
+ release_firmware(ext_src_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(ext_src_coeff_info,
+ &ext_src_coeff_file_name);
+
+ /* Now we are finished with the ext_src_coeff info file */
+ release_firmware(ext_src_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find ext_src_coeff_info "
+ "file!!! Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_coeff info file. */
+ err = request_firmware(&fm_coeff_info,
+ CG2900_FM_FM_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_coeff info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the fm_coeff_info info file.
+ * See if we can find the right fm_coeff_info file as well
+ */
+ fm_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_coeff_file_name");
+ release_firmware(fm_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(fm_coeff_info,
+ &fm_coeff_file_name);
+
+ /* Now we are finished with the fm_coeff info file */
+ release_firmware(fm_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find fm_coeff file!!! "
+ "Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_prog info file. */
+ err = request_firmware(&fm_prog_info,
+ CG2900_FM_FM_PROG_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_prog_info info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the fm_prog info file.
+ * See if we can find the right fm_prog file as well
+ */
+ fm_prog_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_prog_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_prog_file_name");
+ release_firmware(fm_prog_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(fm_prog_info,
+ &fm_prog_file_name);
+
+ /* Now we are finished with fm_prog patch info file */
+ release_firmware(fm_prog_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find fm_prog_info file!!! "
+ "Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* OK. Now it is time to download the firmware */
+ err = request_firmware(&bt_src_coeff_info,
+ bt_src_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get bt_src_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ bt_src_coeff_file_name, bt_src_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) bt_src_coeff_info->data,
+ bt_src_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", bt_src_coeff_file_name);
+ release_firmware(bt_src_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(bt_src_coeff_info);
+ err = request_firmware(&ext_src_coeff_info,
+ ext_src_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get ext_src_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ ext_src_coeff_file_name, ext_src_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) ext_src_coeff_info->data,
+ ext_src_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", ext_src_coeff_file_name);
+ release_firmware(ext_src_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(ext_src_coeff_info);
+
+ err = request_firmware(&fm_coeff_info, fm_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ fm_coeff_file_name, fm_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) fm_coeff_info->data,
+ fm_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", fm_coeff_file_name);
+ release_firmware(fm_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(fm_coeff_info);
+
+ err = request_firmware(&fm_prog_info, fm_prog_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_prog file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ fm_prog_file_name, fm_prog_info->size);
+ if (fmd_send_fm_firmware((u8 *) fm_prog_info->data,
+ fm_prog_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", fm_prog_file_name);
+ release_firmware(fm_prog_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(fm_prog_info);
+
+error:
+ /* Free Allocated memory */
+ if (bt_src_coeff_file_name != NULL)
+ kfree(bt_src_coeff_file_name);
+ if (ext_src_coeff_file_name != NULL)
+ kfree(ext_src_coeff_file_name);
+ if (fm_coeff_file_name != NULL)
+ kfree(fm_coeff_file_name);
+ if (fm_prog_file_name != NULL)
+ kfree(fm_prog_file_name);
+ FM_DEBUG_REPORT("-cg2900_fm_load_firmware: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_transmit_rds_groups()- Transmits the RDS Groups.
+ *
+ * Stores the RDS Groups in Chip's buffer and each group is
+ * transmitted every 87.6 ms.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise
+ */
+static int cg2900_fm_transmit_rds_groups(void)
+{
+ int result = 0;
+ u16 group_position = 0;
+ u8 block1[2];
+ u8 block2[2];
+ u8 block3[2];
+ u8 block4[2];
+ int index1 = 0;
+ int index2 = 0;
+ int group_0B_count = 0;
+ int group_2A_count = 0;
+
+ FM_INFO_REPORT("cg2900_fm_transmit_rds_groups");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_transmit_rds_groups: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ while (group_position < 20 && result == 0) {
+ if (group_position < 4) {
+ /* Transmit PSN in Group 0B */
+ block1[0] = program_identification_code;
+ block1[1] = program_identification_code >> 8;
+ /* M/S bit set to Music */
+ if (group_0B_count % 4 == 0) {
+ /* Manipulate DI bit */
+ block2[0] =
+ (0x08 | ((program_type_code & 0x07)
+ << 5))
+ + group_0B_count;
+ } else {
+ block2[0] =
+ (0x0C | ((program_type_code & 0x07)
+ << 5))
+ + group_0B_count;
+ }
+ block2[1] =
+ 0x08 | ((program_type_code & 0x18) >> 3);
+ block3[0] = program_identification_code;
+ block3[1] = program_identification_code >> 8;
+ block4[0] = program_service[index1 + 1];
+ block4[1] = program_service[index1 + 0];
+ index1 += 2;
+ group_0B_count++;
+ } else {
+ /* Transmit RT in Group 2A */
+ block1[0] = program_identification_code;
+ block1[1] = program_identification_code >> 8;
+ if (a_b_flag)
+ block2[0] = (0x10 |
+ ((program_type_code & 0x07)
+ << 5)) + group_2A_count;
+ else
+ block2[0] = (0x00 |
+ ((program_type_code & 0x07)
+ << 5)) + group_2A_count;
+ block2[1] = 0x20 | ((program_type_code & 0x18)
+ >> 3);
+ block3[0] = radio_text[index2 + 1];
+ block3[1] = radio_text[index2 + 0];
+ block4[0] = radio_text[index2 + 3];
+ block4[1] = radio_text[index2 + 2];
+ index2 += 4;
+ group_2A_count++;
+ }
+ FM_DEBUG_REPORT("%02x%02x "
+ "%02x%02x "
+ "%02x%02x "
+ "%02x%02x ",
+ block1[1], block1[0],
+ block2[1], block2[0],
+ block3[1], block3[0],
+ block4[1], block4[0]);
+ result = fmd_tx_set_group(
+ group_position,
+ block1,
+ block2,
+ block3,
+ block4);
+ group_position++;
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_transmit_rds_groups: "
+ "fmd_tx_set_group failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ break;
+ }
+ }
+ a_b_flag = !a_b_flag;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_transmit_rds_groups: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_check_rds_status()- Checks whether RDS was On previously
+ *
+ * This method is called on receiving interrupt for Seek Completion,
+ * Scan completion and Block Scan completion. It will check whether RDS
+ * was forcefully disabled before the above operations started and if the
+ * previous RDS state was true, then RDS will be enabled back
+ */
+static void cg2900_fm_check_rds_status(void)
+{
+ FM_INFO_REPORT("cg2900_fm_check_rds_status");
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active previously */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+}
+
+/**
+ * cg2900_fm_driver_callback()- Callback function indicating the event.
+ *
+ * This callback function is called on receiving irpt_CommandSucceeded,
+ * irpt_CommandFailed, irpt_bufferFull, etc from FM chip.
+ * @event: event for which the callback function was caled
+ * from FM Driver.
+ * @event_successful: Signifying whether the event is called from FM Driver
+ * on receiving irpt_OperationSucceeded or irpt_OperationFailed.
+ */
+static void cg2900_fm_driver_callback(
+ u8 event,
+ bool event_successful
+ )
+{
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_fm_driver_callback: "
+ "event = %02x, event_successful = %x",
+ event, event_successful);
+
+ switch (event) {
+ case FMD_EVENT_GEN_POWERUP:
+ FM_DEBUG_REPORT("FMD_EVENT_GEN_POWERUP");
+ break;
+ case FMD_EVENT_ANTENNA_STATUS_CHANGED:
+ FM_DEBUG_REPORT("FMD_EVENT_ANTENNA_STATUS_CHANGED");
+ break;
+ case FMD_EVENT_FREQUENCY_CHANGED:
+ FM_DEBUG_REPORT("FMD_EVENT_FREQUENCY_CHANGED ");
+ break;
+ case FMD_EVENT_SEEK_STOPPED:
+ FM_DEBUG_REPORT("FMD_EVENT_SEEK_STOPPED");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SCAN_CANCELLED;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_SEEK_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_SEEK_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SEARCH_CHANNEL_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_SCAN_BAND_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_SCAN_BAND_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SCAN_CHANNELS_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_BLOCK_SCAN_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_BLOCK_SCAN_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE:
+ FM_DEBUG_REPORT("FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE");
+ break;
+ case FMD_EVENT_RDSGROUP_RCVD:
+ FM_DEBUG_REPORT("FMD_EVENT_RDSGROUP_RCVD");
+ /*
+ * Release the rds semaphore, poll queue
+ * will be woken-up in rds callback
+ */
+ fmd_set_rds_sem();
+ break;
+ case FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE:
+ FM_ERR_REPORT(
+ "FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_MONO_STEREO_TRANSITION;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ default:
+ FM_INFO_REPORT("cg2900_fm_driver_callback: "
+ "Unknown event = %x", event);
+ break;
+ }
+}
+
+/**
+ * cg2900_fm_rds_callback()- Function to retrieve the RDS groups.
+ *
+ * This is called when the chip has received enough RDS groups
+ * so an interrupt irpt_BufferFull is generated to read the groups.
+ */
+static void cg2900_fm_rds_callback(void)
+{
+ u8 index = 0;
+ u16 rds_local_buf_count;
+ int result;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_fm_rds_callback");
+
+ /*
+ * Wait till interrupt is RDS Buffer
+ * full interrupt is received
+ */
+ fmd_get_rds_sem();
+
+ if (!fm_rds_status)
+ return;
+
+ /* RDS Data available, Read the Groups */
+ mutex_lock(&rds_mutex);
+ result = fmd_int_bufferfull(&rds_local_buf_count);
+
+ if (0 != result)
+ goto error;
+
+ while (index < rds_local_buf_count) {
+ /*
+ * Status are in reverse order because of Endianness
+ * of status byte received from chip
+ */
+ result = fmd_rx_get_low_level_rds_groups(
+ index,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block1,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block2,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block3,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block4,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status2,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status1,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status4,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status3);
+ FM_INFO_REPORT("%04x %04x %04x %04x %02x %02x %02x %02x",
+ fm_rds_buf[fm_rds_info.rds_head][index].block1,
+ fm_rds_buf[fm_rds_info.rds_head][index].block2,
+ fm_rds_buf[fm_rds_info.rds_head][index].block3,
+ fm_rds_buf[fm_rds_info.rds_head][index].block4,
+ fm_rds_buf[fm_rds_info.rds_head][index].status1,
+ fm_rds_buf[fm_rds_info.rds_head][index].status2,
+ fm_rds_buf[fm_rds_info.rds_head][index].status3,
+ fm_rds_buf[fm_rds_info.rds_head][index].status4);
+
+ if (0 != result)
+
+ goto error;
+
+ if (!fm_rds_status)
+ return;
+
+ index++;
+ }
+ fm_rds_info.rds_head++;
+ if (fm_rds_info.rds_head == MAX_RDS_BUFFER)
+ fm_rds_info.rds_head = 0;
+
+ /* Queue the RDS event */
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_rds_callback: "
+ "Unable to Allocate Memory");
+ goto error;
+ }
+ skb->data[0] = CG2900_EVENT_RDS_EVENT;
+ skb->data[1] = true;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+
+ /* Wake up the poll queue */
+ wake_up_poll_queue();
+error:
+ mutex_unlock(&rds_mutex);
+}
+
+int cg2900_fm_init(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_init");
+
+ if (CG2900_FM_STATE_DEINITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_init: Already Initialized");
+ result = -EINVAL;
+ goto error;
+ }
+
+ mutex_init(&rds_mutex);
+
+ memset(&fm_rds_info, 0, sizeof(struct cg2900_fm_rds_info));
+ memset(&version_info, 0, sizeof(struct cg2900_version_info));
+ memset(
+ fm_rds_buf,
+ 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+
+ /* Initalize the Driver */
+ if (fmd_init() != 0) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Register the callback */
+ if (fmd_register_callback(
+ (fmd_radio_cb) cg2900_fm_driver_callback) != 0) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* initialize global variables */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ fm_state = CG2900_FM_STATE_INITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ fm_prev_rds_status = false;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_init: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_deinit(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_deinit");
+
+ if (CG2900_FM_STATE_INITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_deinit: Already de-Initialized");
+ result = -EINVAL;
+ goto error;
+ }
+ fmd_exit();
+ mutex_destroy(&rds_mutex);
+ fm_state = CG2900_FM_STATE_DEINITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_deinit: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_switch_on(
+ struct device *device
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_switch_on");
+
+ if (CG2900_FM_STATE_INITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Enable FM IP */
+ FM_DEBUG_REPORT("cg2900_fm_switch_on: " "Sending FM IP Enable");
+
+ if (fmd_send_fm_ip_enable()) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in fmd_send_fm_ip_enable");
+ result = -EINVAL;
+ goto error;
+ }
+
+ if (version_info.revision == CG2910_PG1_REV
+ || version_info.revision == CG2910_PG2_REV
+ || version_info.revision == CG2905_PG1_1_REV) {
+ /* Now Download CG2910 lut and program Firmware files */
+ if (cg2910_fm_load_firmware(device) != 0) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in downloading firmware for CG2910/05");
+ result = -EINVAL;
+ goto error;
+ }
+ } else if (version_info.revision == CG2900_PG1_REV
+ || version_info.revision == CG2900_PG2_REV
+ || version_info.revision == CG2900_PG1_SPECIAL_REV) {
+ /* Now Download the Coefficient Files and FM Firmware */
+ if (cg2900_fm_load_firmware(device) != 0) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in downloading firmware for CG2900");
+ result = -EINVAL;
+ goto error;
+ }
+ } else {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Unsupported Chip revision");
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Switch Mode To Idle */
+ result = fmd_set_mode(FMD_MODE_IDLE);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_switch_on: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_switch_off(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_switch_off");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Stop the RDS Thread if it is running */
+ if (fm_rds_status) {
+ fm_rds_status = false;
+ fmd_stop_rds_thread();
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ if (fmd_send_fm_ip_disable()) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "Problem in fmd_send_fm_ip_"
+ "disable");
+ result = -EINVAL;
+ goto error;
+ }
+ if (0 == result) {
+ fm_state = CG2900_FM_STATE_INITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_switch_off: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_standby(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_standby");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_standby: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ result = fmd_goto_standby();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_standby: "
+ "FMLGotoStandby failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ fm_state = CG2900_FM_STATE_STAND_BY;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_standby: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_power_up_from_standby(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_power_up_from_standby");
+
+ if (CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_power_up_from_standby: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_power_up_from_standby: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else {
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT
+ ("cg2900_fm_power_up_from_standby:"
+ " fmd_tx_set_pa " "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_power_up_from_standby: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_rx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ )
+{
+ int result;
+ u8 vol_in_percentage;
+
+ FM_INFO_REPORT("cg2900_fm_set_rx_default_settings: freq = %d Hz, "
+ "band = %d, grid = %d, RDS = %d, Stereo Mode = %d",
+ freq, band, grid, enable_rds, enable_stereo);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ fm_mode = CG2900_FM_RX_MODE;
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set mode to Rx");
+ result = fmd_set_mode(FMD_MODE_RX);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Grid */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending fmd_rx_set_grid ");
+ result = fmd_rx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_grid failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set fmd_set_freq_range");
+ result = fmd_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_set_freq_range failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Frequency */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set fmd_rx_set_frequency");
+ result = fmd_rx_set_frequency(
+ freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "SetFrequency interrupt received, "
+ "Sending Set fmd_rx_set_stereo_mode");
+
+ if (enable_stereo) {
+ /* Set the Stereo Blending mode */
+ result = fmd_rx_set_stereo_mode(
+ FMD_STEREOMODE_BLENDING);
+ } else {
+ /* Set the Mono mode */
+ result = fmd_rx_set_stereo_mode(
+ FMD_STEREOMODE_MONO);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_stereo_mode "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ if (enable_stereo) {
+ /* Set the Stereo Blending RSSI control */
+ result = fmd_rx_set_stereo_ctrl_blending_rssi(
+ STEREO_BLENDING_MIN_RSSI,
+ STEREO_BLENDING_MAX_RSSI);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_stereo_ctrl_blending_rssi "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set RDS Group rejection Off */
+ result = fmd_rx_set_rds_group_rejection(
+ FMD_RDS_GROUP_REJECTION_OFF);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_rds_group_rejection "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Remove all Interrupt from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set rds");
+
+ if (enable_rds) {
+ /* Enable RDS */
+ a_b_flag = false;
+ result = cg2900_fm_rds_on();
+ } else {
+ /* Disable RDS */
+ result = cg2900_fm_rds_off();
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "cg2900_fm_rds_on "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Currently, not supported for CG2905/10 */
+ if (version_info.revision == CG2900_PG1_REV
+ || version_info.revision == CG2900_PG2_REV
+ || version_info.revision == CG2900_PG1_SPECIAL_REV) {
+ /* Set the Analog Out Volume to Max */
+ vol_in_percentage = (u8)
+ (((u16) (MAX_ANALOG_VOLUME) * 100)
+ / MAX_ANALOG_VOLUME);
+ result = fmd_set_volume(vol_in_percentage);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "FMRSetVolume failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_tx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_tx_default_settings: freq = %d Hz, "
+ "band = %d, grid = %d, RDS = %d, Stereo Mode = %d",
+ freq, band, grid, enable_rds, enable_stereo);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ fm_mode = CG2900_FM_TX_MODE;
+ if (fm_rds_status) {
+ fm_rds_status = false;
+ fmd_stop_rds_thread();
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Give 50 ms delay to exit the RDS thread */
+ schedule_timeout_interruptible(msecs_to_jiffies(50));
+ }
+ /* Remove all Interrupt from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+
+ /* Switch To Tx mode */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set mode to Tx");
+ result = fmd_set_mode(FMD_MODE_TX);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Sets the Limiter Values */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_limiter_setcontrol");
+ result = fmd_limiter_setcontrol(
+ DEFAULT_AUDIO_DEVIATION,
+ DEFAULT_NOTIFICATION_HOLD_OFF_TIME);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_limiter_setcontrol failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Grid */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_grid ");
+ result = fmd_tx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_grid failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_freq_range");
+ result = fmd_tx_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_freq_range failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_preemphasis");
+ result = fmd_tx_set_preemphasis(FMD_EMPHASIS_75US);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_tx_set_preemphasis failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Frequency */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set fmd_tx_set_frequency");
+ result = fmd_tx_set_frequency(
+ freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_tx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "SetFrequency interrupt received, "
+ "Sending Set fmd_tx_enable_stereo_mode");
+
+ /* Set the Stereo mode */
+ result = fmd_tx_enable_stereo_mode(enable_stereo);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_enable_stereo_mode "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_pa "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "set PA interrupt received, "
+ "Sending Set fmd_tx_set_signal_strength");
+
+ /* Set the Signal Strength to Max */
+ result = fmd_tx_set_signal_strength(
+ MAX_POWER_LEVEL);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_signal_strength "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Enable Tx RDS */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set cg2900_fm_tx_rds");
+ result = cg2900_fm_tx_rds(enable_rds);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "cg2900_fm_tx_rds "
+ "failed %x", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_grid(
+ u8 grid
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_grid: Grid = %d", grid);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_grid: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_grid: "
+ "fmd_rx_set_grid failed");
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_grid: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_band(
+ u8 band
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_band: Band = %d", band);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_band: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_band: "
+ "fmd_set_freq_range failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_band: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_search_up_freq(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_search_up_freq");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_search_up_freq: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_seek(CG2900_DIR_UP);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_search_up_freq: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_search_up_freq: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_search_down_freq(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_search_down_freq");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_search_down_freq: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_seek(CG2900_DIR_DOWN);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_search_down_freq: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_search_down_freq: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_start_band_scan(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_start_band_scan");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_start_band_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_scan_band(DEFAULT_CHANNELS_TO_SCAN);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_start_band_scan: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_start_band_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_stop_scan(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_stop_scan");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_stop_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_stop_seeking();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_stop_scan: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ result = 0;
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active earlier */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_stop_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_scan_result(
+ u16 *num_of_scanfreq,
+ u32 *scan_freq,
+ u32 *scan_freq_rssi_level
+ )
+{
+ int result;
+ u32 cnt;
+ u32 index;
+ u32 minfreq;
+ u32 maxfreq;
+ u16 channels[3];
+ u16 rssi[3];
+ u8 freq_range;
+ u8 max_channels = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_scan_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_scan_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_freq_range(&freq_range);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range_properties(
+ freq_range,
+ &minfreq,
+ &maxfreq);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_rx_get_max_channels_to_scan(&max_channels);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* In 1 iteration we can retreive max 3 channels */
+ cnt = (max_channels / 3) + 1;
+ while ((cnt--) && (result == 0)) {
+ /*
+ * Get all channels, including empty ones.
+ * In 1 iteration at max 3 channels can be found.
+ */
+ result = fmd_rx_get_scan_band_info(cnt * 3,
+ num_of_scanfreq,
+ channels, rssi);
+ if (0 == result) {
+ index = cnt * 3;
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index] = (minfreq +
+ channels[0] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index] = rssi[0];
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index + 1] = (minfreq +
+ channels[1] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index + 1] = rssi[1];
+ /* Check if we donot overwrite the array */
+ if (cnt < (max_channels / 3)) {
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index + 2] = (minfreq +
+ channels[2] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index + 2]
+ = rssi[2];
+ }
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_scan_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_start_block_scan(
+ u32 start_freq,
+ u32 end_freq
+ )
+{
+ int result;
+ u8 antenna;
+
+ FM_INFO_REPORT("cg2900_fm_start_block_scan");
+
+ FM_DEBUG_REPORT("cg2900_fm_start_block_scan: Start Freq = %d, "
+ "End Freq = %d", start_freq, end_freq);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_start_block_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_get_antenna(
+ &antenna);
+ result = fmd_block_scan(
+ start_freq/FREQUENCY_CONVERTOR_KHZ_HZ,
+ end_freq/FREQUENCY_CONVERTOR_KHZ_HZ,
+ antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_start_block_scan: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_start_block_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_block_scan_result(
+ u16 *num_of_scanchan,
+ u16 *scan_freq_rssi_level
+ )
+{
+ int result = 0;
+ u32 cnt;
+ u32 index;
+ u16 rssi[6];
+
+ FM_INFO_REPORT("cg2900_fm_get_block_scan_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_block_scan_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ cnt = 33;
+ while ((cnt--) && (result == 0)) {
+ /* Get all channels, including empty ones */
+ result = fmd_get_block_scan_result(
+ cnt * 6,
+ num_of_scanchan,
+ rssi);
+ if (0 == result) {
+ index = cnt * 6;
+ scan_freq_rssi_level[index]
+ = rssi[0];
+ scan_freq_rssi_level[index + 1]
+ = rssi[1];
+ scan_freq_rssi_level[index + 2]
+ = rssi[2];
+ scan_freq_rssi_level[index + 3]
+ = rssi[3];
+ scan_freq_rssi_level[index + 4]
+ = rssi[4];
+ scan_freq_rssi_level[index + 5]
+ = rssi[5];
+ }
+ }
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_block_scan_result:"
+ " Sending Set fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_block_scan_result:"
+ " fmd_tx_set_pa "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_block_scan_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_tx_rds(
+ bool enable_rds
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_rds: enable_rds = %d", enable_rds);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (enable_rds) {
+ /* Set the Tx Buffer Size */
+ result = fmd_tx_buffer_set_size(
+ MAX_RDS_GROUPS - 2);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_buffer_set_size "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else {
+ result = fmd_tx_set_rds(true);
+ }
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_set_rds "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ program_identification_code =
+ default_program_identification_code;
+ program_type_code = default_program_type_code;
+ memcpy(program_service,
+ default_program_service,
+ MAX_PSN_SIZE);
+ memcpy(radio_text,
+ default_radio_text, MAX_RT_SIZE);
+ radio_text[strlen(radio_text)] = 0x0D;
+ cg2900_fm_transmit_rds_groups();
+ result = 0;
+ } else {
+ result = fmd_tx_set_rds(false);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_set_rds "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_rds: returning %d",
+ result);
+
+ return result;
+}
+
+int cg2900_fm_tx_set_pi_code(
+ u16 pi_code
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pi_code: PI = %04x", pi_code);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pi_code: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ program_identification_code = pi_code;
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pi_code: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pty_code(
+ u16 pty_code
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pty_code: PTY = %04x", pty_code);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pty_code: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ program_type_code = pty_code;
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pty_code: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_program_station_name(
+ char *psn,
+ u8 len
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_program_station_name: PSN = %s",
+ psn);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_program_station_name: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (len < (MAX_PSN_SIZE - 1)) {
+ int count = len;
+ while (count < (MAX_PSN_SIZE - 1))
+ psn[count++] = ' ';
+ }
+ memcpy(program_service, psn, MAX_PSN_SIZE);
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_program_station_name: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_radio_text(
+ char *rt,
+ u8 len
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_radio_text: RT = %s", rt);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_radio_text: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ rt[len] = 0x0D;
+ memcpy(radio_text, rt, len + 1);
+
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_radio_text: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_rds_deviation(
+ u16 *deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_rds_deviation");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_rds_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_rds_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_rds_deviation: "
+ "fmd_tx_get_rds_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_rds_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_rds_deviation(
+ u16 deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_rds_deviation: deviation = %d",
+ deviation);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_rds_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_rds_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_rds_deviation: "
+ "fmd_tx_set_rds_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_rds_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_pilot_tone_status(
+ bool *enable
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_pilot_tone_status");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_tone_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_stereo_mode(enable);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_tone_status: "
+ "fmd_tx_get_stereo_mode failed %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_pilot_tone_status: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pilot_tone_status(
+ bool enable
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pilot_tone_status: enable = %d",
+ enable);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_tone_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_enable_stereo_mode(enable);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_tone_status: "
+ "fmd_tx_enable_stereo_mode failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pilot_tone_status: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_pilot_deviation(
+ u16 *deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_pilot_deviation");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_pilot_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_deviation: "
+ "fmd_tx_get_pilot_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_pilot_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pilot_deviation(
+ u16 deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pilot_deviation: deviation = %d",
+ deviation);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_pilot_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_deviation: "
+ "fmd_tx_set_pilot_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pilot_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_preemphasis(
+ u8 *preemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_preemphasis");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_preemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_preemphasis(preemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_preemphasis: "
+ "fmd_tx_get_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_preemphasis: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_preemphasis(
+ u8 preemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_preemphasis: preemphasis = %d",
+ preemphasis);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_preemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_preemphasis(preemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_preemphasis: "
+ "fmd_tx_set_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_preemphasis: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_rx_set_deemphasis(
+ u8 deemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rx_set_deemphasis: deemphasis = %02x",
+ deemphasis);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rx_set_deemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_set_deemphasis(deemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rx_set_deemphasis: "
+ "fmd_rx_set_deemphasis failed %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rx_set_deemphasis: returning %d", result);
+ return result;
+}
+
+int cg2900_fm_tx_get_power_level(
+ u16 *power_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_power_level");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_power_level: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_signal_strength(power_level);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_power_level: "
+ "fmd_tx_get_signal_strength failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_power_level: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_power_level(
+ u16 power_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_power_level: power_level = %d",
+ power_level);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_power_level: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_signal_strength(power_level);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_power_level: "
+ "fmd_tx_set_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_power_level: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_audio_balance(
+ s8 balance
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_audio_balance, balance = %d", balance);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_audio_balance: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_balance(balance);
+ if (0 != result) {
+ FM_ERR_REPORT("FMRSetAudioBalance : "
+ "Failed in fmd_set_balance, err = %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_audio_balance: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_volume(
+ u8 vol_level
+ )
+{
+ int result;
+ u8 vol_in_percentage;
+
+ FM_INFO_REPORT("cg2900_fm_set_volume: Volume Level = %d", vol_level);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_volume: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ vol_in_percentage =
+ (u8) (((u16) (vol_level) * 100) / MAX_ANALOG_VOLUME);
+ result = fmd_set_volume(vol_in_percentage);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_increase_volume: "
+ "FMRSetVolume failed, err = %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_volume: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_volume(
+ u8 *vol_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_volume");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_volume: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *vol_level = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_volume(vol_level);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_volume: returning %d, VolLevel = %d",
+ result, *vol_level);
+ return result;
+}
+
+int cg2900_fm_rds_off(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rds_off");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rds_off: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ result = fmd_rx_set_rds(FMD_SWITCH_OFF_RDS);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_off: fmd_rx_set_rds failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop the RDS Thread */
+ FM_DEBUG_REPORT("cg2900_fm_rds_off: "
+ "Stopping RDS Thread");
+ fmd_stop_rds_thread();
+ fm_rds_status = false;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rds_off: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_rds_on(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rds_on");
+ if (fm_rds_status) {
+ result = 0;
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: rds is on "
+ "return result = %d", result);
+ return result;
+ }
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on:"
+ " Sending fmd_rx_buffer_set_size");
+ result = fmd_rx_buffer_set_size(MAX_RDS_GROUPS);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_buffer_set_size"
+ "failed, err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: Sending "
+ "fmd_rx_buffer_set_threshold");
+ result = fmd_rx_buffer_set_threshold(MAX_RDS_GROUPS - 1);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_buffer_set_threshold "
+ "failed, err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: Sending fmd_rx_set_rds");
+ result = fmd_rx_set_rds(FMD_SWITCH_ON_RDS_ENHANCED_MODE);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_set_rds failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Start the RDS Thread to read the RDS Buffers */
+ fm_rds_status = true;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ fmd_start_rds_thread(cg2900_fm_rds_callback);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_rds_status(
+ bool *rds_status
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_rds_status");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_rds_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_rx_get_rds");
+ result = fmd_rx_get_rds(rds_status);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_tx_get_rds");
+ result = fmd_tx_get_rds(rds_status);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_get_rds failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: returning %d, "
+ "rds_status = %d", result,
+ *rds_status);
+ return result;
+}
+
+int cg2900_fm_mute(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_mute");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Mute Analog DAC */
+ result = fmd_set_mute(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "fmd_set_mute failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Mute Ext Src */
+ result = fmd_ext_set_mute(true);
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_mute: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_unmute(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_unmute");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_unmute: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Unmute Analog DAC */
+ result = fmd_set_mute(false);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "fmd_set_mute failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Unmute Ext Src */
+ result = fmd_ext_set_mute(false);
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_unmute: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_frequency(
+ u32 *freq
+ )
+{
+ int result = 0;
+ u32 currentFreq = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_frequency");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_frequency: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *freq = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "fmd_rx_get_frequency");
+ result = fmd_rx_get_frequency(
+ (u32 *) &currentFreq);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "fmd_tx_get_frequency");
+ result = fmd_tx_get_frequency(
+ (u32 *) &currentFreq);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_frequency: "
+ "fmd_rx_get_frequency failed %d",
+ (unsigned int)result);
+ *freq = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ /* Convert To Hz */
+ *freq = currentFreq * FREQUENCY_CONVERTOR_KHZ_HZ;
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "Current Frequency = %d Hz", *freq);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_frequency(
+ u32 new_freq
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_set_frequency, new_freq = %d",
+ (int)new_freq);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Check if RDS needs to be disabled before Setting Frequency */
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: "
+ "fmd_rx_set_frequency");
+ result = fmd_rx_set_frequency(
+ new_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: "
+ "fmd_tx_set_frequency");
+ result = fmd_tx_set_frequency(
+ new_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ }
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active earlier */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+ if (result != 0) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency: "
+ "fmd_rx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency:"
+ " Sending Set" "fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency:"
+ " fmd_tx_set_pa "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_signal_strength(
+ u16 *signal_strength
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_signal_strength");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_signal_strength: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *signal_strength = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: "
+ "fmd_rx_get_signal_strength");
+ result = fmd_rx_get_signal_strength(
+ signal_strength);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: "
+ "fmd_tx_get_signal_strength");
+ result = fmd_tx_get_signal_strength(
+ signal_strength);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_signal_strength: "
+ "Error Code %d", (unsigned int)result);
+ *signal_strength = 0;
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_update_get_result(
+ u16 *af_update_rssi
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_update_get_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_update_get_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *af_update_rssi = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_get_af_update_result(af_update_rssi);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_update_get_result: "
+ "Error Code %d", (unsigned int)result);
+ *af_update_rssi = 0;
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_update_get_result: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_update_start(
+ u32 af_freq
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_update_start");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_update_start: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_af_update_start(
+ af_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_update_start: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_update_start: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_switch_get_result(
+ u16 *af_switch_conclusion
+ )
+{
+ int result;
+ u16 af_rssi;
+ u16 af_pi;
+
+ FM_INFO_REPORT("cg2900_fm_af_switch_get_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_get_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_get_af_switch_results(
+ af_switch_conclusion,
+ &af_rssi, &af_pi);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_get_result: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_get_result: "
+ "AF Switch conclusion = %d "
+ "AF Switch RSSI level = %d "
+ "AF Switch PI code = %d ",
+ *af_switch_conclusion, af_rssi, af_pi);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_get_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_af_switch_start(
+ u32 af_switch_freq,
+ u16 af_switch_pi
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_switch_start");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_start: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_af_switch_start(
+ af_switch_freq / FREQUENCY_CONVERTOR_KHZ_HZ,
+ af_switch_pi);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_start: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_start: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_mode(
+ u8 *cur_mode
+ )
+{
+ int result = 0;
+ bool stereo_mode;
+
+ FM_INFO_REPORT("cg2900_fm_get_mode");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_mode: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *cur_mode = CG2900_MODE_MONO;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: "
+ "fmd_rx_get_stereo_mode");
+ result = fmd_rx_get_stereo_mode(cur_mode);
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: cur_mode = %x", *cur_mode);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: "
+ "fmd_tx_get_stereo_mode");
+ result = fmd_tx_get_stereo_mode(&stereo_mode);
+ if (stereo_mode)
+ *cur_mode = CG2900_MODE_STEREO;
+ else
+ *cur_mode = CG2900_MODE_MONO;
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_mode: "
+ "fmd_get_stereo_mode failed, "
+ "Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: returning %d, mode = %d",
+ result, *cur_mode);
+ return result;
+}
+
+int cg2900_fm_set_mode(
+ u8 mode
+ )
+{
+ int result = 0;
+ bool enable_stereo_mode = false;
+
+ FM_INFO_REPORT("cg2900_fm_set_mode: mode = %d", mode);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_mode: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: "
+ "fmd_rx_set_stereo_mode");
+ result = fmd_rx_set_stereo_mode(mode);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: "
+ "fmd_tx_set_stereo_mode");
+ if (mode == CG2900_MODE_STEREO)
+ enable_stereo_mode = true;
+ result =
+ fmd_tx_enable_stereo_mode(
+ enable_stereo_mode);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_mode: "
+ "fmd_rx_set_stereo_mode failed, "
+ "Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_select_antenna(
+ u8 antenna
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_select_antenna: Antenna = %d", antenna);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_select_antenna: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_antenna(antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_select_antenna: "
+ "fmd_set_antenna failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_select_antenna: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_antenna(
+ u8 *antenna
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_antenna");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_antenna: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_antenna(antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_antenna: "
+ "fmd_get_antenna failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_antenna: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_rssi_threshold(
+ u16 *rssi_thresold
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_rssi_threshold");
+
+ result = fmd_rx_get_stop_level(rssi_thresold);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_rssi_threshold: "
+ "fmd_rx_get_stop_level failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_rssi_threshold: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_rssi_threshold(
+ u16 rssi_thresold
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_rssi_threshold: "
+ "RssiThresold = %d", rssi_thresold);
+
+ result = fmd_rx_set_stop_level(rssi_thresold);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rssi_threshold: "
+ "fmd_rx_set_stop_level failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_rssi_threshold: returning %d",
+ result);
+ return result;
+}
+
+void cg2900_fm_set_chip_version(
+ u16 revision,
+ u16 sub_version
+ )
+{
+ version_info.revision = revision;
+ version_info.sub_version = sub_version;
+}
+
+int cg2900_fm_set_test_tone_generator(
+ u8 test_tone_status
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_test_tone_generator: "
+ "test_tone_status = %02x", test_tone_status);
+
+ result = fmd_set_test_tone_generator_status(test_tone_status);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_test_tone_generator: "
+ "fmd_set_test_tone_generator_status failed"
+ ", Error Code %d", result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_test_tone_generator: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_test_tone_connect: "
+ "left_audio_mode = %02x right_audio_mode = %02x",
+ left_audio_mode, right_audio_mode);
+
+ result = fmd_test_tone_connect(left_audio_mode, right_audio_mode);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_test_tone_connect: "
+ "fmd_set_test_tone_connect failed, Error Code %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_test_tone_connect: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_test_tone_set_params: "
+ "tone_gen = %02x frequency = %04x "
+ "volume = %04x phase_offset = %04x "
+ "dc offset = %04x waveform = %02x",
+ tone_gen, frequency,
+ volume, phase_offset,
+ dc, waveform);
+
+ result = fmd_test_tone_set_params(
+ tone_gen,
+ frequency,
+ volume,
+ phase_offset,
+ dc,
+ waveform);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_test_tone_set_params: "
+ "fmd_test_tone_set_params failed, Error Code %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_test_tone_set_params: returning %d",
+ result);
+ return result;
+}
+
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/radio/CG2900/cg2900_fm_api.h b/drivers/media/radio/CG2900/cg2900_fm_api.h
new file mode 100644
index 00000000000..ec9e6e86f77
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_api.h
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Host API's for ST-Ericsson FM Chip.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef CG2900_FM_API_H
+#define CG2900_FM_API_H
+
+#include <linux/device.h>
+#include <linux/skbuff.h>
+
+/* Callback function to receive RDS Data. */
+typedef void (*cg2900_fm_rds_cb)(void);
+
+extern struct sk_buff_head fm_interrupt_queue;
+
+/**
+ * struct cg2900_fm_rds_buf - RDS Group Receiving Structure
+ *
+ * @block1: RDS Block A
+ * @block2: RDS Block B
+ * @block3: RDS Block C
+ * @block4: RDS Block D
+ * @status1: Status of received RDS Block A
+ * @status2: Status of received RDS Block B
+ * @status3: Status of received RDS Block C
+ * @status4: Status of received RDS Block D
+ *
+ * Structure for receiving the RDS Group from FM Chip.
+ */
+struct cg2900_fm_rds_buf {
+ u16 block1;
+ u16 block2;
+ u16 block3;
+ u16 block4;
+ u8 status1;
+ u8 status2;
+ u8 status3;
+ u8 status4;
+};
+
+/**
+ * struct cg2900_fm_rds_info - RDS Information Structure
+ *
+ * @rds_head: RDS Queue Head for storing next valid data.
+ * @rds_tail: RDS Queue Tail for retreiving next valid data.
+ * @rds_group_sent: Number of RDS Groups sent to Application.
+ * @rds_block_sent: Number of RDS Blocks sent to Application.
+ *
+ * Structure for storing the RDS data queue information.
+ */
+struct cg2900_fm_rds_info {
+ u8 rds_head;
+ u8 rds_tail;
+ u8 rds_group_sent;
+ u8 rds_block_sent;
+};
+
+/**
+ * struct cg2900_version_info - Chip HCI Version Info
+ *
+ * @revision: Revision of the controller, e.g. to indicate that it is
+ * a CG2900 controller.
+ * @sub_version: Subversion of the controller, e.g. to indicate a certain
+ * tape-out of the controller.
+ *
+ * Structure for storing the HCI Version Information of the Controller.
+ */
+struct cg2900_version_info {
+ u16 revision;
+ u16 sub_version;
+};
+
+/**
+ * enum cg2900_fm_state - States of FM Driver.
+ *
+ * @CG2900_FM_STATE_DEINITIALIZED: FM driver is not initialized.
+ * @CG2900_FM_STATE_INITIALIZED: FM driver is initialized.
+ * @CG2900_FM_STATE_SWITCHED_ON: FM driver is switched on and in active state.
+ * @CG2900_FM_STATE_STAND_BY: FM Radio is switched on but not in active state.
+ *
+ * Various states of FM Driver.
+ */
+enum cg2900_fm_state {
+ CG2900_FM_STATE_DEINITIALIZED,
+ CG2900_FM_STATE_INITIALIZED,
+ CG2900_FM_STATE_SWITCHED_ON,
+ CG2900_FM_STATE_STAND_BY
+};
+
+/**
+ * enum cg2900_fm_mode - FM Driver Command state .
+ *
+ * @CG2900_FM_IDLE_MODE: FM Radio is in Idle Mode.
+ * @CG2900_FM_RX_MODE: FM Radio is configured in Rx mode.
+ * @CG2900_FM_TX_MODE: FM Radio is configured in Tx mode.
+ *
+ * Various Modes of the FM Radio.
+ */
+enum cg2900_fm_mode {
+ CG2900_FM_IDLE_MODE,
+ CG2900_FM_RX_MODE,
+ CG2900_FM_TX_MODE
+};
+
+/**
+ * enum cg2900_fm_band - Various Frequency band supported.
+ *
+ * @CG2900_FM_BAND_US_EU: European / US Band.
+ * @CG2900_FM_BAND_JAPAN: Japan Band.
+ * @CG2900_FM_BAND_CHINA: China Band.
+ * @CG2900_FM_BAND_CUSTOM: Custom Band.
+ *
+ * Various Frequency band supported.
+ */
+enum cg2900_fm_band {
+ CG2900_FM_BAND_US_EU,
+ CG2900_FM_BAND_JAPAN,
+ CG2900_FM_BAND_CHINA,
+ CG2900_FM_BAND_CUSTOM
+};
+
+/**
+ * enum cg2900_fm_grid - Various Frequency grids supported.
+ *
+ * @CG2900_FM_GRID_50: 50 kHz spacing.
+ * @CG2900_FM_GRID_100: 100 kHz spacing.
+ * @CG2900_FM_GRID_200: 200 kHz spacing.
+ *
+ * Various Frequency grids supported.
+ */
+enum cg2900_fm_grid {
+ CG2900_FM_GRID_50,
+ CG2900_FM_GRID_100,
+ CG2900_FM_GRID_200
+};
+
+/**
+ * enum cg2900_fm_event - Various Events reported by FM API layer.
+ *
+ * @CG2900_EVENT_NO_EVENT: No Event.
+ * @CG2900_EVENT_SEARCH_CHANNEL_FOUND: Seek operation is completed.
+ * @CG2900_EVENT_SCAN_CHANNELS_FOUND: Band Scan is completed.
+ * @CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND: Block Scan is completed.
+ * @CG2900_EVENT_SCAN_CANCELLED: Scan/Seek is cancelled.
+ * @CG2900_EVENT_MONO_STEREO_TRANSITION: Mono/Stereo Transition has taken place.
+ * @CG2900_EVENT_DEVICE_RESET: CG2900 has been reset by some other IP.
+ * @CG2900_EVENT_RDS_EVENT: RDS data interrupt has been received from chip.
+ *
+ * Various Events reported by FM API layer.
+ */
+enum cg2900_fm_event {
+ CG2900_EVENT_NO_EVENT,
+ CG2900_EVENT_SEARCH_CHANNEL_FOUND,
+ CG2900_EVENT_SCAN_CHANNELS_FOUND,
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND,
+ CG2900_EVENT_SCAN_CANCELLED,
+ CG2900_EVENT_MONO_STEREO_TRANSITION,
+ CG2900_EVENT_DEVICE_RESET,
+ CG2900_EVENT_RDS_EVENT
+};
+
+/**
+ * enum cg2900_fm_direction - Directions used while seek.
+ *
+ * @CG2900_DIR_DOWN: Search in downwards direction.
+ * @CG2900_DIR_UP: Search in upwards direction.
+ *
+ * Directions used while seek.
+ */
+enum cg2900_fm_direction {
+ CG2900_DIR_DOWN,
+ CG2900_DIR_UP
+};
+
+/**
+ * enum cg2900_fm_stereo_mode - Stereo Modes.
+ *
+ * @CG2900_MODE_MONO: Mono Mode.
+ * @CG2900_MODE_STEREO: Stereo Mode.
+ *
+ * Stereo Modes.
+ */
+enum cg2900_fm_stereo_mode {
+ CG2900_MODE_MONO,
+ CG2900_MODE_STEREO
+};
+
+#define CG2900_FM_DEFAULT_RSSI_THRESHOLD 100
+#define MAX_RDS_BUFFER 10
+#define MAX_RDS_GROUPS 22
+#define MIN_ANALOG_VOLUME 0
+#define MAX_ANALOG_VOLUME 20
+#define NUM_OF_RDS_BLOCKS 4
+#define RDS_BLOCK_MASK 0x1C
+#define RDS_ERROR_STATUS_MASK 0x03
+#define RDS_UPTO_TWO_BITS_CORRECTED 0x01
+#define RDS_UPTO_FIVE_BITS_CORRECTED 0x02
+#define MAX_RT_SIZE 65
+#define MAX_PSN_SIZE 9
+#define DEFAULT_CHANNELS_TO_SCAN 32
+#define MAX_CHANNELS_TO_SCAN 99
+#define MAX_CHANNELS_FOR_BLOCK_SCAN 198
+#define SKB_FM_INTERRUPT_DATA 2
+
+extern u8 fm_event;
+extern struct cg2900_fm_rds_buf fm_rds_buf[MAX_RDS_BUFFER][MAX_RDS_GROUPS];
+extern struct cg2900_fm_rds_info fm_rds_info;
+
+/**
+ * cg2900_fm_init()- Initializes FM Radio.
+ *
+ * Initializes the Variables and structures required for FM Driver.
+ * It also registers the callback to receive the events for command
+ * completion, etc
+ *
+ * Returns:
+ * 0, if Initialization successful
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_init(void);
+
+/**
+ * cg2900_fm_deinit()- De-initializes FM Radio.
+ *
+ * De-initializes the Variables and structures required for FM Driver.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_deinit(void);
+
+/**
+ * cg2900_fm_switch_on()- Start up procedure of the FM radio.
+ *
+ * @device: Character device requesting the operation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_switch_on(
+ struct device *device
+ );
+
+/**
+ * cg2900_fm_switch_off()- Switches off FM radio
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_switch_off(void);
+
+/**
+ * cg2900_fm_standby()- Makes the FM Radio Go in Standby mode.
+ *
+ * The FM Radio memorizes the the last state, i.e. Volume, last
+ * tuned station, etc that helps in resuming quickly to previous state.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_standby(void);
+
+/**
+ * cg2900_fm_power_up_from_standby()- Power Up FM Radio from Standby mode.
+ *
+ * It retruns the FM radio to the same state as it was before
+ * going to Standby.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_power_up_from_standby(void);
+
+/**
+ * cg2900_fm_set_rx_default_settings()- Loads FM Rx Default Settings.
+ *
+ * @freq: Frequency in Hz to be set on the FM Radio.
+ * @band: Band To be Set.
+ * (0: US/EU, 1: Japan, 2: China, 3: Custom)
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz, 1: 100 KHz, 2: 200 Khz)
+ * @enable_rds: Flag indicating enable or disable rds transmission.
+ * @enable_stereo: Flag indicating enable or disable stereo mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_rx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ );
+
+/**
+ * cg2900_fm_set_tx_default_settings()- Loads FM Tx Default Settings.
+ *
+ * @freq: Frequency in Hz to be set on the FM Radio.
+ * @band: Band To be Set.
+ * (0: US/EU, 1: Japan, 2: China, 3: Custom)
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz, 1: 100 KHz, 2: 200 Khz)
+ * @enable_rds: Flag indicating enable or disable rds transmission.
+ * @enable_stereo: Flag indicating enable or disable stereo mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_tx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ );
+
+/**
+ * cg2900_fm_set_grid()- Sets the Grid on the FM Radio.
+ *
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz,1: 100 KHz,2: 200 Khz)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_grid(
+ u8 grid
+ );
+
+/**
+ * cg2900_fm_set_band()- Sets the Band on the FM Radio.
+ *
+ * @band: Band specifying Region.
+ * (0: US_EU,1: Japan,2: China,3: Custom)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_band(
+ u8 band
+ );
+
+/**
+ * cg2900_fm_search_up_freq()- seek Up.
+ *
+ * Searches the next available station in Upward Direction
+ * starting from the Current freq.
+ *
+ * If the operation is started successfully, the chip will generate the
+ * irpt_OperationSucced. interrupt when the operation is completed
+ * and will tune to the next available frequency.
+ * If no station is found, the chip is still tuned to the original station
+ * before starting the search
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_search_up_freq(void);
+
+/**
+ * cg2900_fm_search_down_freq()- seek Down.
+ *
+ * Searches the next available station in Downward Direction
+ * starting from the Current freq.
+ *
+ * If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced. interrupt when the operation is completed.
+ * and will tune to the next available frequency. If no station is found,
+ * the chip is still tuned to the original station before starting the search.
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_search_down_freq(void);
+
+/**
+ * cg2900_fm_start_band_scan()- Band Scan.
+ *
+ * Searches for available Stations in the entire Band starting from
+ * current freq.
+ * If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced. interrupt when the operation is completed.
+ * After completion the chip will still be tuned the original station before
+ * starting the Scan. on reception of interrupt, the host should call the AP
+ * cg2900_fm_get_scan_result() to retrieve the Stations and corresponding
+ * RSSI of stations found in the Band.
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan, cg2900_fm_switch_off, cg2900_fm_standby and
+ * cg2900_fm_get_frequency.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_start_band_scan(void);
+
+/**
+ * cg2900_fm_stop_scan()- Stops an active ongoing seek or Band Scan.
+ *
+ * If the operation is started successfully, the chip will generate the
+ * irpt_OperationSucced interrupt when the operation is completed.
+ * Till the interrupt is received, no more API's should be called.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_stop_scan(void);
+
+/**
+ * cg2900_fm_get_scan_result()- Retreives Band Scan Result
+ *
+ * Retrieves the Scan Band Results of the stations found and
+ * the corressponding RSSI values of the stations.
+ * @num_of_scanfreq: (out) Number of Stations found
+ * during Scanning.
+ * @scan_freq: (out) Frequency of Stations in Hz
+ * found during Scanning.
+ * @scan_freq_rssi_level: (out) RSSI level of Stations
+ * found during Scanning.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_scan_result(
+ u16 *num_of_scanfreq,
+ u32 *scan_freq,
+ u32 *scan_freq_rssi_level
+ );
+
+/**
+ * cg2900_fm_start_block_scan()- Block Scan.
+ *
+ * Searches for RSSI level of all the channels between the start and stop
+ * channels. If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced interrupt when the operation is completed.
+ * After completion the chip will still be tuned the original station before
+ * starting the Scan. On reception of interrupt, the host should call the AP
+ * cg2900_fm_get_block_scan_result() to retrieve the RSSI of channels.
+ * Till the interrupt is received, no more API's should be called from Host
+ * except cg2900_fm_stop_scan, cg2900_fm_switch_off, cg2900_fm_standby and
+ * cg2900_fm_get_frequency.
+ * @start_freq: Start channel block scan Frequency.
+ * @end_freq: End channel block scan Frequency
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_start_block_scan(
+ u32 start_freq,
+ u32 end_freq
+ );
+
+/**
+ * cg2900_fm_get_scan_result()- Retreives Band Scan Result
+ *
+ * Retrieves the Scan Band Results of the stations found and
+ * the corressponding RSSI values of the stations.
+ * @num_of_scanchan: (out) Number of Stations found
+ * during Scanning.
+ * @scan_freq_rssi_level: (out) RSSI level of Stations
+ * found during Scanning.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_block_scan_result(
+ u16 *num_of_scanchan,
+ u16 *scan_freq_rssi_level
+ );
+
+/**
+ * cg2900_fm_tx_get_rds_deviation()- Gets RDS Deviation.
+ *
+ * Retrieves the RDS Deviation level set for FM Tx.
+ * @deviation: (out) Rds Deviation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_rds_deviation(
+ u16 *deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_rds_deviation()- Sets RDS Deviation.
+ *
+ * Sets the RDS Deviation level on FM Tx.
+ * @deviation: Rds Deviation to set on FM Tx.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_rds_deviation(
+ u16 deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_pi_code()- Sets PI code for RDS Transmission.
+ *
+ * Sets the Program Identification code to be transmitted.
+ * @pi_code: PI code to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pi_code(
+ u16 pi_code
+ );
+
+/**
+ * cg2900_fm_tx_set_pty_code()- Sets PTY code for RDS Transmission.
+ *
+ * Sets the Program Type code to be transmitted.
+ * @pty_code: PTY code to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pty_code(
+ u16 pty_code
+ );
+
+/**
+ * cg2900_fm_tx_set_program_station_name()- Sets PSN for RDS Transmission.
+ *
+ * Sets the Program Station Name to be transmitted.
+ * @psn: Program Station Name to be transmitted.
+ * @len: Length of Program Station Name to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_program_station_name(
+ char *psn,
+ u8 len
+ );
+
+/**
+ * cg2900_fm_tx_set_radio_text()- Sets RT for RDS Transmission.
+ *
+ * Sets the radio text to be transmitted.
+ * @rt: Radio Text to be transmitted.
+ * @len: Length of Radio Text to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_radio_text(
+ char *rt,
+ u8 len
+ );
+
+/**
+ * cg2900_fm_tx_get_rds_deviation()- Gets Pilot Tone status
+ *
+ * Gets the current status of pilot tone for FM Tx.
+ * @enable: (out) Flag indicating Pilot Tone is enabled or disabled.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_pilot_tone_status(
+ bool *enable
+ );
+
+/**
+ * cg2900_fm_tx_set_pilot_tone_status()- Enables/Disables Pilot Tone.
+ *
+ * Enables or disables the pilot tone for FM Tx.
+ * @enable: Flag indicating enabling or disabling Pilot Tone.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pilot_tone_status(
+ bool enable
+ );
+
+/**
+ * cg2900_fm_tx_get_pilot_deviation()- Gets Pilot Deviation.
+ *
+ * Retrieves the Pilot Tone Deviation level set for FM Tx.
+ * @deviation: (out) Pilot Tone Deviation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_pilot_deviation(
+ u16 *deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_pilot_deviation()- Sets Pilot Deviation.
+ *
+ * Sets the Pilot Tone Deviation level on FM Tx.
+ * @deviation: Pilot Tone Deviation to set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pilot_deviation(
+ u16 deviation
+ );
+
+/**
+ * cg2900_fm_tx_get_preemphasis()- Gets Pre-emhasis level.
+ *
+ * Retrieves the Preemphasis level set for FM Tx.
+ * @preemphasis: (out) Preemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_preemphasis(
+ u8 *preemphasis
+ );
+
+/**
+ * cg2900_fm_tx_set_preemphasis()- Sets Pre-emhasis level.
+ *
+ * Sets the Preemphasis level on FM Tx.
+ * @preemphasis: Preemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_preemphasis(
+ u8 preemphasis
+ );
+
+/**
+ * cg2900_fm_tx_get_power_level()- Gets Power level.
+ *
+ * Retrieves the Power level set for FM Tx.
+ * @power_level: (out) Power level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_power_level(
+ u16 *power_level
+ );
+
+/**
+ * cg2900_fm_tx_set_power_level()- Sets Power level.
+ *
+ * Sets the Power level for FM Tx.
+ * @power_level: Power level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_power_level(
+ u16 power_level
+ );
+
+/**
+ * cg2900_fm_tx_rds()- Enable or disable Tx RDS.
+ *
+ * Enable or disable RDS transmission.
+ * @enable_rds: Flag indicating enabling or disabling RDS.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_rds(
+ bool enable_rds
+ );
+
+/**
+ * cg2900_fm_set_audio_balance()- Sets Audio Balance.
+ *
+ * @balance: Audio Balnce to be Set in Percentage.
+ * (-100: Right Mute.... 0: Both on.... 100: Left Mute)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_audio_balance(
+ s8 balance
+ );
+
+/**
+ * cg2900_fm_set_volume()- Sets the Analog Out Gain of FM Chip.
+ *
+ * @vol_level: Volume Level to be set on Tuner (0-20).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_volume(
+ u8 vol_level
+ );
+
+/**
+ * cg2900_fm_get_volume()- Gets the currently set Analog Out Gain of FM Chip.
+ *
+ * @vol_level: (out)Volume Level set on Tuner (0-20).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_volume(
+ u8 *vol_level
+ );
+
+/**
+ * cg2900_fm_rds_off()- Disables the RDS decoding algorithm in FM chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rds_off(void);
+
+/**
+ * cg2900_fm_rds_on()- Enables the RDS decoding algorithm in FM chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rds_on(void);
+
+/**
+ * cg2900_fm_get_rds_status()- Retrieves the status whether RDS is enabled or not
+ *
+ * @rds_status: (out) Status of RDS
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_rds_status(
+ bool *rds_status
+ );
+
+/**
+ * cg2900_fm_mute()- Mutes the Audio output from FM Chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_mute(void);
+
+/**
+ * cg2900_fm_unmute()- Unmutes the Audio output from FM Chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_unmute(void);
+
+/**
+ * cg2900_fm_get_frequency()- Gets the Curently tuned Frequency on FM Radio
+ *
+ * @freq: (out) Frequency in Hz set on the FM Radio.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * cg2900_fm_set_frequency()- Sets the frequency on FM Radio
+ *
+ * @new_freq: Frequency in Hz to be set on the FM Radio.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_frequency(
+ u32 new_freq
+ );
+
+/**
+ * cg2900_fm_get_signal_strength()- Gets the RSSI level.
+ *
+ * @signal_strength: (out) RSSI level of the currently
+ * tuned frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_signal_strength(
+ u16 *signal_strength
+ );
+
+/**
+ * cg2900_fm_get_af_updat()- Retrives results of AF Update
+ *
+ * @af_update_rssi: (out) RSSI level of the Alternative frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_update_get_result(
+ u16 *af_update_rssi
+ );
+
+
+/**
+ * cg2900_fm_af_update_start()- PErforms AF Update.
+ *
+ * @af_freq: AF frequency in Hz whose RSSI is to be retrived.
+ * tuned frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+
+int cg2900_fm_af_update_start(
+ u32 af_freq
+ );
+
+/**
+ * cg2900_fm_af_switch_get_result()- Retrives the AF switch result.
+ *
+ * @af_switch_conclusion: (out) Conclusion of the AF Switch.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_switch_get_result(
+ u16 *af_switch_conclusion
+ );
+
+/**
+ * cg2900_fm_af_switch_start()- PErforms AF switch.
+ *
+ * @af_switch_freq: Alternate Frequency in Hz to be switched.
+ * @af_switch_pi: picode of the Alternative frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_switch_start(
+ u32 af_switch_freq,
+ u16 af_switch_pi
+ );
+
+/**
+ * cg2900_fm_get_mode()- Gets the mode of the Radio tuner.
+ *
+ * @cur_mode: (out) Current mode set on FM Radio
+ * (0: Stereo, 1: Mono, 2: Blending, 3: Switching).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_mode(
+ u8 *cur_mode
+ );
+
+/**
+ * cg2900_fm_set_mode()- Sets the mode on the Radio tuner.
+ *
+ * @mode: mode to be set on FM Radio
+ * (0: Stereo, 1: Mono, 2: Blending, 3: Switching.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_mode(
+ u8 mode
+ );
+
+/**
+ * cg2900_fm_select_antenna()- Selects the Antenna of the Radio tuner.
+ *
+ * @antenna: (0: Embedded, 1: Wired.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_select_antenna(
+ u8 antenna
+ );
+
+/**
+ * cg2900_fm_get_antenna()- Retreives the currently selected antenna.
+ *
+ * @antenna: out (0: Embedded, 1: Wired.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_antenna(
+ u8 *antenna
+ );
+
+/**
+ * cg2900_fm_get_rssi_threshold()- Gets the rssi threshold currently
+ *
+ * set on FM radio.
+ * @rssi_thresold: (out) Current rssi threshold set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_rssi_threshold(
+ u16 *rssi_thresold
+ );
+
+/**
+ * cg2900_fm_set_rssi_threshold()- Sets the rssi threshold to be used during
+ *
+ * Band Scan and seek Stations
+ * @rssi_thresold: rssi threshold to be set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_rssi_threshold(
+ u16 rssi_thresold
+ );
+
+/**
+ * cg2900_handle_device_reset()- Handle The reset of Device
+ */
+void cg2900_handle_device_reset(void);
+
+/**
+ * wake_up_poll_queue()- Wakes up the Task waiting on Poll Queue.
+ * This function is called when Scan Band or seek has completed.
+ */
+void wake_up_poll_queue(void);
+
+/**
+ * void cg2900_fm_set_chip_version()- Sets the Version of the Controller.
+ *
+ * This function is used to update the Chip Version information at time
+ * of intitialization of FM driver.
+ * @revision: Revision of the controller, e.g. to indicate that it is
+ * a CG2900 controller.
+ * @sub_version: Subversion of the controller, e.g. to indicate a certain
+ * tape-out of the controller.
+ */
+void cg2900_fm_set_chip_version(
+ u16 revision,
+ u16 sub_version
+ );
+
+/**
+ * cg2900_fm_rx_set_deemphasis()- Sets de-emhasis level.
+ *
+ * Sets the Deemphasis level on FM Rx.
+ * @deemphasis: Deemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rx_set_deemphasis(
+ u8 deemphasis
+ );
+
+/**
+ * cg2900_fm_set_test_tone_generator()- Sets the Test Tone Generator.
+ *
+ * This function is used to enable/disable the Internal Tone Generator of
+ * CG2900.
+ * @test_tone_status: Status of tone generator.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_test_tone_generator(
+ u8 test_tone_status
+ );
+
+
+/**
+ * cg2900_fm_test_tone_connect()- Connect Audio outputs/inputs.
+ *
+ * This function connects the audio outputs/inputs of the Internal Tone
+ * Generator of CG2900.
+ * @left_audio_mode: Left Audio Output Mode.
+ * @right_audio_mode: Right Audio Output Mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ );
+
+/**
+ * cg2900_fm_test_tone_set_params()- Sets the Test Tone Parameters.
+ *
+ * This function is used to set the parameters of the Internal Tone Generator of
+ * CG2900.
+ * @tone_gen: Tone to be configured (Tone 1 or Tone 2)
+ * @frequency: Frequency of the tone.
+ * @volume: Volume of the tone.
+ * @phase_offset: Phase offset of the tone.
+ * @dc: DC to add to tone.
+ * @waveform: Waveform to generate, sine or pulse.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ );
+
+#endif /* CG2900_FM_API_H */
diff --git a/drivers/media/radio/CG2900/cg2900_fm_driver.c b/drivers/media/radio/CG2900/cg2900_fm_driver.c
new file mode 100644
index 00000000000..627d0f2d674
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_driver.c
@@ -0,0 +1,5017 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Driver for CG2900 FM Chip
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/device.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <asm-generic/errno-base.h>
+#include "cg2900.h"
+#include "cg2900_fm_driver.h"
+
+/*
+ * Macro for printing the HCI Packet received from Protocol Driver
+ * to FM Driver.
+ */
+#define CG2900_HEX_READ_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('<', skb->data, skb->len);
+
+/* Macro for printing the HCI Packet sent to Protocol Driver from FM Driver */
+#define CG2900_HEX_WRITE_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('>', send_buffer, num_bytes);
+
+/* Converts the given value to ASCII format*/
+#define ASCVAL(x)(((x) <= 9) ? (x) + '0' : (x) - 10 + 'a')
+
+/* The receive Packet's 1st byte indicates the packet length */
+#define FM_GET_PKT_LEN(__data) __data[0]
+
+/* The receive Packet's following bytes are the actual data. */
+#define FM_GET_RSP_PKT_ADDR(__data) (&__data[1])
+
+/*
+ * LSB consists of shifting the command Id by 3 bits
+ * to left and ORING with the number of parameters of the
+ * command.
+ */
+#define FM_CMD_GET_LSB(__cmd_id, __num_param) \
+ ((u8)(((__cmd_id << 3) & 0x00FF) | __num_param))
+
+/* MSB consists of shifting the command Id by 5 bits to right. */
+#define FM_CMD_GET_MSB(__cmd_id) \
+ ((u8)(__cmd_id >> 5))
+
+/*
+ * Command id is mapped as shifting the MSB 5 bits to left and
+ * ORING with LSB shifted 3 bits to right.
+ */
+#define FM_GET_CMD_ID(__data) \
+ ((u16)((__data[2] << 5) | __data[1] >> 3))
+
+/*
+ * Number of parameters in the response packet are the last 3 bits
+ * of the 1st byte of the received packet.
+ */
+#define FM_GET_NUM_PARAMS(__data) \
+ ((u16)((__data[1] & 0x07)))
+
+/* Function Id is mapped to the 1st byte of the received packet */
+#define FM_GET_FUNCTION_ID(__data) __data[0]
+
+/*
+ * Block Id of the FM Firmware downloaded is mapped to the
+ * 2nd byte of the received packet.
+ */
+#define FM_GET_BLOCK_ID(__data) __data[1]
+
+/* Status of the received packet is mapped to the 4th byte. */
+#define FM_GET_STATUS(__data) __data[3]
+
+/*
+ * For PG1 of CG2900, the FM Interrupt is mapped
+ * to the 3rd and 4th byte of the received packet.
+ */
+#define FM_GET_PGI_INTERRUPT(__data) \
+ ((u16)(__data[3] << 8 | __data[2]))
+
+/*
+ * For PG2 of CG2900, the FM Interrupt is mapped
+ * to the 5th and 6th byte of the received packet.
+ */
+#define FM_GET_PG2_INTERRUPT(__data) \
+ ((u16)(__data[5] << 8 | __data[4]))
+
+#define FM_GET_NUM_RDS_GRPS(__data) __data[0]
+
+/* Response buffer starts from the 4th byte if the response buffer */
+#define FM_GET_RSP_BUFFER_ADDR(__data) (&__data[3])
+
+/* FM Function buffer starts from the 5th byte if the response buffer */
+#define FM_GET_FUNCTION_ADDR(__data) (&__data[4])
+
+/*
+ * Maximum time for chip to respond including the Command
+ * Competion interrupts for some commands. This time has been
+ * adjusted to cater to increased communication time with chip
+ * when debug level is set to 4.
+ */
+#define MAX_RESPONSE_TIME_IN_MS 5000
+
+/* Byte per word */
+#define WORD_LENGTH 2
+
+/* Byte Offset Counter */
+#define COUNTER 1
+
+/* Binary shift offset for one byte */
+#define SHIFT_OFFSET 8
+
+/*
+ * enum fmd_gocmd_t - FM Driver Command state.
+ *
+ * @FMD_STATE_NONE: FM Driver in Idle state
+ * @FMD_STATE_MODE: FM Driver in setmode state
+ * @FMD_STATE_FREQUENCY: FM Driver in Set frequency state.
+ * @FMD_STATE_PA: FM Driver in SetPA state.
+ * @FMD_STATE_PA_LEVEL: FM Driver in Setpalevl state.
+ * @FMD_STATE_ANTENNA: FM Driver in Setantenna state
+ * @FMD_STATE_MUTE: FM Driver in Setmute state
+ * @FMD_STATE_SEEK: FM Driver in seek mode
+ * @FMD_STATE_SEEK_STOP: FM Driver in seek stop level state.
+ * @FMD_STATE_SCAN_BAND: FM Driver in Scanband mode
+ * @FMD_STATE_TX_SET_CTRL: FM Driver in RDS control state
+ * @FMD_STATE_TX_SET_THRSHLD: FM Driver in RDS threshld state
+ * @FMD_STATE_GEN_POWERUP: FM Driver in Power UP state.
+ * @FMD_STATE_SELECT_REF_CLK: FM Driver in Select Reference clock state.
+ * @FMD_STATE_SET_REF_CLK_PLL: FM Driver in Set Reference Freq state.
+ * @FMD_STATE_BLOCK_SCAN: FM Driver in Block Scan state.
+ * @FMD_STATE_AF_UPDATE: FM Driver in AF Update State.
+ * @FMD_STATE_AF_SWITCH: FM Driver in AF Switch State.
+ * @FMD_STATE_LAST: Last State of FM Driver
+ *
+ * Various states of the FM driver.
+ */
+enum fmd_gocmd {
+ FMD_STATE_NONE,
+ FMD_STATE_MODE,
+ FMD_STATE_FREQUENCY,
+ FMD_STATE_PA,
+ FMD_STATE_PA_LEVEL,
+ FMD_STATE_ANTENNA,
+ FMD_STATE_MUTE,
+ FMD_STATE_SEEK,
+ FMD_STATE_SEEK_STOP,
+ FMD_STATE_SCAN_BAND,
+ FMD_STATE_TX_SET_CTRL,
+ FMD_STATE_TX_SET_THRSHLD,
+ FMD_STATE_GEN_POWERUP,
+ FMD_STATE_SELECT_REF_CLK,
+ FMD_STATE_SET_REF_CLK_PLL,
+ FMD_STATE_BLOCK_SCAN,
+ FMD_STATE_AF_UPDATE,
+ FMD_STATE_AF_SWITCH,
+ FMD_STATE_LAST
+};
+
+/**
+ * struct fmd_rdsgroup_t - Rds group structure.
+ *
+ * @block: Array for RDS Block(s) received.
+ * @status: Array of Status of corresponding RDS block(s).
+ *
+ * It stores the value and status of a particular RDS group
+ * received.
+ */
+struct fmd_rds_group {
+ u16 block[NUM_OF_RDS_BLOCKS];
+ u8 status[NUM_OF_RDS_BLOCKS];
+};
+
+/**
+ * struct fmd_states_info - Main FM state info structure.
+ *
+ * @fmd_initialized: Flag indicating FM Driver is initialized or not
+ * @rx_freq_range: Receiver freq range
+ * @rx_volume: Receiver volume level
+ * @rx_antenna: Receiver Antenna
+ * @rx_seek_stop_level: RDS seek stop Level
+ * @rx_rds_on: Receiver RDS ON
+ * @rx_stereo_mode: Receiver Stereo mode
+ * @max_channels_to_scan: Maximum Number of channels to Scan.
+ * @tx_freq_range: Transmitter freq Range
+ * @tx_preemphasis: Transmitter Pre emphiasis level
+ * @tx_stereo_mode: Transmitter stero mode
+ * @tx_rds_on: Enable RDS
+ * @tx_pilot_dev: PIlot freq deviation
+ * @tx_rds_dev: RDS deviation
+ * @tx_strength: TX Signal Stregnth
+ * @irq_index: Index where last interrupt is added to Interrupt queue
+ * @interrupt_available_for_processing: Flag indicating if interrupt is
+ * available for processing or not.
+ * @interrupt_queue: Circular Queue to store the received interrupt from chip.
+ * @gocmd: Command which is in progress.
+ * @mode: Current Mode of FM Radio.
+ * @rds_group: Array of RDS group Buffer
+ * @callback: Callback registered by upper layers.
+ */
+struct fmd_states_info {
+ bool fmd_initialized;
+ u8 rx_freq_range;
+ u8 rx_volume;
+ u8 rx_antenna;
+ u16 rx_seek_stop_level;
+ bool rx_rds_on;
+ u8 rx_stereo_mode;
+ u8 tx_freq_range;
+ u8 tx_preemphasis;
+ bool tx_stereo_mode;
+ u8 max_channels_to_scan;
+ bool tx_rds_on;
+ u16 tx_pilot_dev;
+ u16 tx_rds_dev;
+ u16 tx_strength;
+ u8 irq_index;
+ bool interrupt_available_for_processing;
+ u16 interrupt_queue[MAX_COUNT_OF_IRQS];
+ enum fmd_gocmd gocmd;
+ enum fmd_mode mode;
+ struct fmd_rds_group rds_group[MAX_RDS_GROUPS];
+ fmd_radio_cb callback;
+};
+
+/**
+ * struct fmd_data - Main structure for FM data exchange.
+ *
+ * @cmd_id: Command Id of the command being exchanged.
+ * @num_parameters: Number of parameters
+ * @parameters: FM data parameters.
+ */
+struct fmd_data {
+ u32 cmd_id;
+ u16 num_parameters;
+ u8 parameters[MAX_RESP_SIZE];
+};
+
+static struct fmd_states_info fmd_state_info;
+static struct fmd_data fmd_data;
+static struct semaphore cmd_sem;
+static struct semaphore rds_sem;
+static struct semaphore interrupt_sem;
+static struct task_struct *rds_thread_task;
+static struct task_struct *irq_thread_task;
+static struct device *cg2900_fm_dev;
+static struct mutex write_mutex;
+static struct mutex send_cmd_mutex;
+static spinlock_t fmd_spinlock;
+static spinlock_t fmd_spinlock_read;
+
+/* Debug Level
+ * 1: Only Error Logs
+ * 2: Info Logs
+ * 3: Debug Logs
+ * 4: HCI Logs
+ */
+unsigned short cg2900_fm_debug_level = FM_ERROR_LOGS;
+EXPORT_SYMBOL(cg2900_fm_debug_level);
+
+static cg2900_fm_rds_cb cb_rds_func;
+static bool rds_thread_required;
+static bool irq_thread_required;
+
+static char event_name[FMD_EVENT_LAST_ELEMENT][MAX_NAME_SIZE] = {
+ "FMD_EVENT_OPERATION_COMPLETED",
+ "FMD_EVENT_ANTENNA_STATUS_CHANGED",
+ "FMD_EVENT_FREQUENCY_CHANGED",
+ "FMD_EVENT_SEEK_COMPLETED",
+ "FMD_EVENT_SCAN_BAND_COMPLETED",
+ "FMD_EVENT_BLOCK_SCAN_COMPLETED",
+ "FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE",
+ "FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE",
+ "FMD_EVENT_SEEK_STOPPED",
+ "FMD_EVENT_GEN_POWERUP",
+ "FMD_EVENT_RDSGROUP_RCVD",
+};
+
+static char interrupt_name[MAX_COUNT_OF_IRQS][MAX_NAME_SIZE] = {
+ "IRPT_OPERATION_SUCCEEDED",
+ "IRPT_OPERATION_FAILED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_RX_BUFFER_FULL_OR_TX_BUFFER_EMPTY",
+ "IRPT_RX_SIGNAL_QUALITY_LOW_OR_TX_MUTE_STATUS_CHANGED",
+ "IRPT_MONO_STEREO_TRANSITION",
+ "IRPT_RX_RDS_SYNC_FOUND_OR_TX_INPUT_OVERDRIVE",
+ "IRPT_RDS_SYNC_LOST",
+ "IRPT_PI_CODE_CHANGED",
+ "IRPT_REQUESTED_BLOCK_AVAILABLE",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_WARM_BOOT_READY",
+ "IRPT_COLD_BOOT_READY",
+};
+
+
+static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ );
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ );
+static void fmd_event_name(
+ u8 event,
+ char *event_name
+ );
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ );
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interrupt_name
+ );
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ );
+static void fmd_process_interrupt(
+ u16 interrupt
+ );
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ );
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static bool fmd_go_cmd_busy(void);
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ );
+static int fmd_send_cmd(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters
+ );
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ );
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ );
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ );
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ );
+static int fmd_rds_thread(
+ void *data
+ );
+static void fmd_start_irq_thread(void);
+static void fmd_stop_irq_thread(void);
+static int fmd_irq_thread(
+ void *data
+ );
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ );
+static int fmd_get_cmd_sem(void);
+static void fmd_set_cmd_sem(void);
+static void fmd_get_interrupt_sem(void);
+static void fmd_set_interrupt_sem(void);
+static bool fmd_driver_init(void);
+static void fmd_driver_exit(void);
+
+/* structure declared in time.h */
+struct timespec time_spec;
+
+
+/**
+ * fmd_hexdump() - Displays the HCI Data Bytes exchanged with FM Chip.
+ *
+ * @prompt: Prompt signifying the direction '<' for Rx '>' for Tx
+ * @buffer: Buffer to be displayed.
+ * @num_bytes: Number of bytes of the buffer.
+ */
+ static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ )
+{
+ int i;
+ u8 tmp_val;
+ struct timespec time;
+ static u8 pkt_write[MAX_BUFFER_SIZE], *pkt_ptr;
+
+ getnstimeofday(&time);
+ sprintf(pkt_write, "\n[%08x:%08x] [%04x] %c",
+ (unsigned int)time.tv_sec,
+ (unsigned int)time.tv_nsec,
+ num_bytes, prompt);
+
+ pkt_ptr = pkt_write + strlen(pkt_write);
+ if (buffer == NULL)
+ return;
+
+ /* Copy the buffer only if the input buffer is not NULL */
+ for (i = 0; i < num_bytes; i++) {
+ *pkt_ptr++ = ' ';
+ tmp_val = buffer[i] >> 4;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ tmp_val = buffer[i] & 0x0F;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ if (i > 20) {
+ /* Print only 20 bytes at max */
+ break;
+ }
+ }
+ *pkt_ptr++ = '\0';
+ FM_HEX_REPORT("%s", pkt_write);
+}
+
+/**
+ * fmd_get_event() - Returns the Event based on FM Driver State.
+ *
+ * @gocmd: Pending FM Command
+ *
+ * Returns: Corresponding Event
+ */
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ )
+{
+ u8 event = FMD_EVENT_OPERATION_COMPLETED;
+ switch (gocmd) {
+ case FMD_STATE_ANTENNA:
+ event = FMD_EVENT_ANTENNA_STATUS_CHANGED;
+ break;
+ case FMD_STATE_FREQUENCY:
+ event = FMD_EVENT_FREQUENCY_CHANGED;
+ break;
+ case FMD_STATE_SEEK:
+ event = FMD_EVENT_SEEK_COMPLETED;
+ break;
+ case FMD_STATE_SCAN_BAND:
+ event = FMD_EVENT_SCAN_BAND_COMPLETED;
+ break;
+ case FMD_STATE_BLOCK_SCAN:
+ event = FMD_EVENT_BLOCK_SCAN_COMPLETED;
+ break;
+ case FMD_STATE_AF_UPDATE:
+ /* Drop Down */
+ case FMD_STATE_AF_SWITCH:
+ event = FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE;
+ break;
+ case FMD_STATE_SEEK_STOP:
+ event = FMD_EVENT_SEEK_STOPPED;
+ break;
+ default:
+ event = FMD_EVENT_OPERATION_COMPLETED;
+ break;
+ }
+ return event;
+}
+
+/**
+ * fmd_event_name() - Converts the event to a displayable string.
+ *
+ * @event: Event that has occurred.
+ * @eventname: (out) Buffer to store event name.
+ */
+static void fmd_event_name(
+ u8 event,
+ char *eventname
+ )
+{
+ if (eventname == NULL) {
+ FM_ERR_REPORT("fmd_event_name: Output Buffer is NULL");
+ return;
+ }
+ if (event < FMD_EVENT_LAST_ELEMENT)
+ strcpy(eventname, event_name[event]);
+ else
+ strcpy(eventname, "FMD_EVENT_UNKNOWN");
+}
+
+/**
+ * fmd_get_fm_function_name() - Returns the FM Fucntion name.
+ *
+ * @fm_function: Function whose name is to be retrieved.
+ *
+ * Returns FM Function Name.
+ */
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ )
+{
+ switch (fm_function) {
+ case FM_FUNCTION_ENABLE:
+ return "FM_FUNCTION_ENABLE";
+ break;
+ case FM_FUNCTION_DISABLE:
+ return "FM_FUNCTION_DISABLE";
+ break;
+ case FM_FUNCTION_RESET:
+ return "FM_FUNCTION_RESET";
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ return "FM_FUNCTION_WRITE_COMMAND";
+ break;
+ case FM_FUNCTION_SET_INT_MASK_ALL:
+ return "FM_FUNCTION_SET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_GET_INT_MASK_ALL:
+ return "FM_FUNCTION_GET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_SET_INT_MASK:
+ return "FM_FUNCTION_SET_INT_MASK";
+ break;
+ case FM_FUNCTION_GET_INT_MASK:
+ return "FM_FUNCTION_GET_INT_MASK";
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ return "FM_FUNCTION_FIRMWARE_DOWNLOAD";
+ break;
+ default:
+ return "FM_FUNCTION_UNKNOWN";
+ break;
+ }
+}
+
+/**
+ * fmd_interrupt_name() - Converts the interrupt to a displayable string.
+ *
+ * @interrupt: interrupt received from FM Chip
+ * @interruptname: (out) Buffer to store interrupt name.
+ */
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interruptname
+ )
+{
+ int index;
+
+ if (interruptname == NULL) {
+ FM_ERR_REPORT("fmd_interrupt_name: Output Buffer is NULL!!!");
+ return;
+ }
+ /* Convert Interrupt to Bit */
+ for (index = 0; index < MAX_COUNT_OF_IRQS; index++) {
+ if (interrupt & (1 << index)) {
+ /* Match found, break the loop */
+ break;
+ }
+ }
+ if (index < MAX_COUNT_OF_IRQS)
+ strcpy(interruptname, interrupt_name[index]);
+ else
+ strcpy(interruptname, "IRPT_UNKNOWN");
+}
+
+/**
+ * fmd_add_interrupt_to_queue() - Add interrupt to IRQ Queue.
+ *
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ )
+{
+ FM_DEBUG_REPORT("fmd_add_interrupt_to_queue : "
+ "Interrupt Received = %04x", (u16) interrupt);
+
+ /* Reset the index if it reaches the array limit */
+ if (fmd_state_info.irq_index > MAX_COUNT_OF_IRQS - 1) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.irq_index = 0;
+ spin_unlock(&fmd_spinlock);
+ }
+
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_queue[fmd_state_info.irq_index] = interrupt;
+ fmd_state_info.irq_index++;
+ spin_unlock(&fmd_spinlock);
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_available_for_processing = true;
+ spin_unlock(&fmd_spinlock);
+ fmd_set_interrupt_sem();
+ }
+}
+
+/**
+ * fmd_process_interrupt() - Processes the Interrupt.
+ *
+ * This function processes the interrupt received from FM Chip
+ * and calls the corresponding callback registered by upper layers with
+ * proper parameters.
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_process_interrupt(
+ u16 interrupt
+ )
+{
+ char irpt_name[MAX_NAME_SIZE];
+
+ fmd_interrupt_name(interrupt, irpt_name);
+ FM_DEBUG_REPORT("%s", irpt_name);
+ if ((interrupt & IRPT_OPERATION_SUCCEEDED) |
+ (interrupt & IRPT_OPERATION_FAILED)) {
+ bool event_status = (interrupt & IRPT_OPERATION_SUCCEEDED);
+ u8 event = fmd_get_event(fmd_state_info.gocmd);
+
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_MODE:
+ /* Mode has been changed. */
+ case FMD_STATE_MUTE:
+ /* FM radio is Muter or Unmuted */
+ case FMD_STATE_PA:
+ /* Power Amplifier has been enabled/disabled */
+ case FMD_STATE_PA_LEVEL:
+ /* Power Amplifier Level has been changed. */
+ case FMD_STATE_SELECT_REF_CLK:
+ /* Reference Clock has been selected. */
+ case FMD_STATE_SET_REF_CLK_PLL:
+ /* Reference Clock frequency has been changed. */
+ case FMD_STATE_TX_SET_CTRL:
+ /* Tx Control has been set. */
+ case FMD_STATE_TX_SET_THRSHLD:
+ /* Tx Threashold has been set. */
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ case FMD_STATE_ANTENNA:
+ /* Antenna status has been changed. */
+ case FMD_STATE_SEEK_STOP:
+ /* Band scan, seek or block scan has completed. */
+ case FMD_STATE_AF_UPDATE:
+ /* AF Update has completed. */
+ case FMD_STATE_AF_SWITCH:
+ /* AF Switch has completed. */
+ case FMD_STATE_FREQUENCY:
+ /* Frequency has been changed. */
+ /*
+ * Set State to None, set the waiting semaphore,
+ * and inform upper layer.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ fmd_callback(
+ event,
+ event_status);
+ break;
+ case FMD_STATE_SEEK:
+ /* Seek has completed. */
+ case FMD_STATE_SCAN_BAND:
+ /* Band scan has completed. */
+ case FMD_STATE_BLOCK_SCAN:
+ /* Block scan has completed. */
+ /*
+ * Set State to None. No need to set the
+ * semaphore since this is an asyncronous event.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ /* Inform Upper layer. */
+ fmd_callback(event, event_status);
+ break;
+ default:
+ /* Do Nothing */
+ FM_ERR_REPORT("Default %s case of "\
+ "interrupt processing", event_status ? \
+ "Success" : "Failed");
+ break;
+ }
+ }
+
+ if (interrupt & IRPT_RX_BUFFERFULL_TX_BUFFEREMPTY) {
+ /*
+ * RDS Buffer Full or RDS Buffer Empty
+ * interrupt received from chip, indicating
+ * that RDS data is available if chip
+ * is in Rx mode or RDS data can be send
+ * to chip in case of Tx mode. Inform the
+ * upper layers about this interrupt.
+ */
+ fmd_callback(
+ FMD_EVENT_RDSGROUP_RCVD,
+ true);
+ }
+
+ if (interrupt & IRPT_RX_MONO_STEREO_TRANSITION) {
+ /*
+ * Mono Stereo Transition interrupt
+ * received from chip, inform the
+ * upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE,
+ true);
+ }
+
+ if ((interrupt & IRPT_COLD_BOOT_READY) |
+ (interrupt & IRPT_WARM_BOOT_READY)) {
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_GEN_POWERUP:
+ /*
+ * Cold Boot/ Warm Boot Interrupt received from
+ * chip, indicating transition from
+ * power off/standby state to active state.
+ * Inform the upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_GEN_POWERUP,
+ true);
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ default:
+ /* Do Nothing */
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_callback() - Callback function for upper layers.
+ *
+ * Callback function that calls the registered callback of upper
+ * layers with proper parameters.
+ * @event: event for which the callback function was called
+ * from FM Driver.
+ * @event_successful: Signifying whether the event is called from FM
+ * Driver on receiving irpt_Operation_Succeeded or irpt_Operation_Failed.
+ */
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ )
+{
+ char event_name_string[MAX_NAME_SIZE];
+
+ fmd_event_name(event, event_name_string);
+
+ FM_DEBUG_REPORT("%s %x, %d", event_name_string,
+ (unsigned int)event , (unsigned int)event_successful);
+
+ if (fmd_state_info.callback)
+ fmd_state_info.callback(
+ event,
+ event_successful);
+}
+
+/**
+ * fmd_rx_frequency_to_channel() - Converts Rx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Rx.
+ * @freq: Frequency in kHz.
+ * @channel: Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_rx_channel_to_frequency() - Converts Rx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Rx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_frequency_to_channel() - Converts Tx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Tx.
+ * @freq: Frequency in kHz.
+ * @channel: (out)Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_channel_to_frequency() - Converts Tx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Tx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel
+ * in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+ if (*frequency > max_freq)
+ *frequency = max_freq;
+ else if (*frequency < min_freq)
+ *frequency = min_freq;
+
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_go_cmd_busy() - Function to check if FM Driver is busy or idle
+ *
+ * Returns:
+ * false if FM Driver is Idle
+ * true otherwise
+ */
+static bool fmd_go_cmd_busy(void)
+{
+ return (fmd_state_info.gocmd != FMD_STATE_NONE);
+}
+
+/**
+ * fmd_read_cb() - Handle Received Data
+ *
+ * This function handles data received from connectivity protocol driver.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming form device.
+ */
+static void fmd_read_cb(
+ struct cg2900_user_data *dev,
+ struct sk_buff *skb
+ )
+{
+ FM_INFO_REPORT("fmd_read_cb");
+
+ if (skb->data == NULL || skb->len == 0)
+ return;
+
+ spin_lock(&fmd_spinlock_read);
+ CG2900_HEX_READ_PACKET_DUMP;
+ /*
+ * The first byte is length of bytes following bytes
+ * Rest of the bytes are the actual data
+ */
+ fmd_receive_data(
+ FM_GET_PKT_LEN(skb->data),
+ FM_GET_RSP_PKT_ADDR(skb->data));
+
+ kfree_skb(skb);
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_receive_data() - Processes the FM data received from device.
+ *
+ * @packet_length: Length of received Data Packet
+ * @packet_buffer: Received Data buffer.
+ */
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ )
+{
+ if (packet_buffer == NULL) {
+ FM_ERR_REPORT("fmd_receive_data: Buffer = NULL");
+ return;
+ }
+
+ if (packet_length == FM_PG1_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_CATENA_OPCODE &&
+ packet_buffer[1] == FM_EVENT_ID) {
+ /* PG 1.0 interrupt Handling */
+ u16 interrupt = FM_GET_PGI_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_length == FM_PG2_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_EVENT &&
+ packet_buffer[3] == FM_EVENT_ID) {
+ /* PG 2.0 interrupt Handling */
+ u16 interrupt = FM_GET_PG2_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_WRITE) {
+ /* Command Complete or RDS Data Handling */
+ u8 fm_status = FM_GET_STATUS(packet_buffer);;
+ switch (fm_status) {
+ case FM_CMD_STATUS_CMD_SUCCESS:
+ fmd_process_fm_function(
+ FM_GET_FUNCTION_ADDR(packet_buffer));
+ break;
+ case FM_CMD_STATUS_HCI_ERR_HW_FAILURE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_HW_FAILURE");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS");
+ break;
+ case FM_CMD_STATUS_IP_UNINIT:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_IP_UNINIT");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED");
+ break;
+ case FM_CMD_STATUS_WRONG_SEQ_NUM:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_WRONG_SEQ_NUM");
+ break;
+ case FM_CMD_STATUS_UNKNOWN_FILE_TYPE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_UNKNOWN_FILE_TYPE");
+ break;
+ case FM_CMD_STATUS_FILE_VERSION_MISMATCH:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_FILE_VERSION_MISMATCH");
+ break;
+ default:
+ FM_DEBUG_REPORT(
+ "Unknown Status = %02x", fm_status);
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_reset_cb() - Reset callback fuction.
+ *
+ * @dev: CPD device reseting.
+ */
+static void fmd_reset_cb(struct cg2900_user_data *dev)
+{
+ FM_INFO_REPORT("fmd_reset_cb: Device Reset");
+ spin_lock(&fmd_spinlock_read);
+ cg2900_handle_device_reset();
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_rds_thread() - Thread for receiving RDS data from Chip.
+ *
+ * @data: Data beng passed as parameter on starting the thread.
+ */
+static int fmd_rds_thread(
+ void *data
+ )
+{
+ FM_INFO_REPORT("fmd_rds_thread Created Successfully");
+ while (rds_thread_required) {
+ if (cb_rds_func)
+ cb_rds_func();
+ /* Give 100 ms for context switching */
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ /* Always signal the rds_sem semaphore before exiting */
+ fmd_set_rds_sem();
+ FM_DEBUG_REPORT("fmd_rds_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_start_irq_thread() - Function for starting Interrupt Thread.
+ */
+static void fmd_start_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_start_irq_thread");
+ irq_thread_task = kthread_create(fmd_irq_thread, NULL, "irq_thread");
+ if (IS_ERR(irq_thread_task)) {
+ FM_ERR_REPORT("fmd_start_irq_thread: "
+ "Unable to Create irq_thread");
+ irq_thread_task = NULL;
+ return;
+ }
+ wake_up_process(irq_thread_task);
+}
+
+/**
+ * fmd_stop_irq_thread() - Function for stopping Interrupt Thread.
+ */
+static void fmd_stop_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_irq_thread");
+ kthread_stop(irq_thread_task);
+ irq_thread_task = NULL;
+ FM_DEBUG_REPORT("-fmd_stop_irq_thread");
+}
+
+/**
+ * fmd_irq_thread() - Thread for processing Interrupts received from Chip.
+ *
+ * @data: Data being passed as parameter on starting the thread.
+ */
+
+static int fmd_irq_thread(
+ void *data
+ )
+{
+ int index;
+
+ FM_INFO_REPORT("fmd_irq_thread Created Successfully");
+
+ while (irq_thread_required) {
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ fmd_get_interrupt_sem();
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ }
+ index = 0;
+
+ if (fmd_state_info.interrupt_available_for_processing) {
+ while (index < MAX_COUNT_OF_IRQS) {
+ if (fmd_state_info.interrupt_queue[index]
+ != IRPT_INVALID) {
+ FM_DEBUG_REPORT("fmd_process_interrupt "
+ "Interrupt = %04x",
+ fmd_state_info.
+ interrupt_queue[index]);
+ fmd_process_interrupt(
+ fmd_state_info.interrupt_queue[index]);
+ fmd_state_info.interrupt_queue[index]
+ = IRPT_INVALID;
+ }
+ index++;
+ }
+ }
+ fmd_state_info.interrupt_available_for_processing = false;
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ FM_DEBUG_REPORT("fmd_irq_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_send_packet() - Sends the FM HCI Packet to the CG2900 Protocol Driver.
+ *
+ * @num_bytes: Number of bytes of Data to be sent including
+ * Channel Identifier (08)
+ * @send_buffer: Buffer containing the Data to be sent to Chip.
+ *
+ * Returns:
+ * 0, If packet was sent successfully to
+ * CG2900 Protocol Driver, otherwise the corresponding error.
+ * -EINVAL If parameters are not valid.
+ * -EIO If there is an Input/Output Error.
+ */
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ )
+{
+ int err;
+ struct sk_buff *skb;
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_send_packet");
+
+ if (send_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("fmd_send_packet: No FM device registered");
+ err = -EIO;
+ goto error;
+ }
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (!pf_data->opened) {
+ FM_ERR_REPORT("fmd_send_packet: FM channel is not opened");
+ err = -EIO;
+ goto error;
+ }
+
+ mutex_lock(&write_mutex);
+ CG2900_HEX_WRITE_PACKET_DUMP;
+
+ skb = pf_data->alloc_skb(num_bytes, GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("fmd_send_packet:Couldn't " \
+ "allocate sk_buff with length %d", num_bytes);
+ err = -EIO;
+ goto error;
+ }
+
+ /*
+ * Copy the buffer removing the FM Header as this
+ * would be done by Protocol Driver
+ */
+ memcpy(skb_put(skb, num_bytes), send_buffer, num_bytes);
+
+ err = pf_data->write(pf_data, skb);
+ if (err) {
+ FM_ERR_REPORT("fmd_send_packet: "
+ "Failed to send(%d) bytes using "
+ "cg2900_write, err = %d",
+ num_bytes, err);
+ kfree(skb);
+ err = -EIO;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ mutex_unlock(&write_mutex);
+ FM_DEBUG_REPORT("fmd_send_packet returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_get_cmd_sem() - Block on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ETIME if timeout occurs.
+ */
+static int fmd_get_cmd_sem(void)
+{
+ int ret_val;
+
+ FM_INFO_REPORT("fmd_get_cmd_sem");
+
+ ret_val = down_timeout(&cmd_sem,
+ msecs_to_jiffies(MAX_RESPONSE_TIME_IN_MS));
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_cmd_sem: down_timeout "
+ "returned error = %d", ret_val);
+
+ return ret_val;
+}
+
+/**
+ * fmd_set_cmd_sem() - Unblock on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ */
+static void fmd_set_cmd_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_cmd_sem");
+
+ up(&cmd_sem);
+}
+
+/**
+ * fmd_get_interrupt_sem() - Block on Interrupt Semaphore.
+ *
+ * Till Interrupt is received, Interrupt Task is blocked.
+ */
+static void fmd_get_interrupt_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_interrupt_sem");
+
+ ret_val = down_killable(&interrupt_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_interrupt_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+/**
+ * fmd_set_interrupt_sem() - Unblock on Interrupt Semaphore.
+ *
+ * on receiving Interrupt, Interrupt Task is un-blocked.
+ */
+static void fmd_set_interrupt_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_interrupt_sem");
+ up(&interrupt_sem);
+}
+
+/**
+ * fmd_driver_init()- Initializes the Mutex, Semaphore, etc for FM Driver.
+ *
+ * It also registers FM Driver with the Protocol Driver.
+ *
+ * Returns:
+ * true if initialization is successful
+ * false if initiialization fails.
+ */
+static bool fmd_driver_init(void)
+{
+ bool ret_val;
+ struct cg2900_rev_data rev_data;
+ struct cg2900_user_data *pf_data;
+ int err;
+
+ FM_INFO_REPORT("fmd_driver_init");
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("No device registered");
+ ret_val = false;
+ goto error;
+ }
+
+ /* Initialize the semaphores */
+ sema_init(&cmd_sem, 0);
+ sema_init(&rds_sem, 0);
+ sema_init(&interrupt_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ irq_thread_required = true;
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+
+ /* Create Mutex For Reading and Writing */
+ spin_lock_init(&fmd_spinlock_read);
+ mutex_init(&write_mutex);
+ mutex_init(&send_cmd_mutex);
+ spin_lock_init(&fmd_spinlock);
+ fmd_start_irq_thread();
+
+ /* Open the FM channel */
+ err = pf_data->open(pf_data);
+ if (err) {
+ FM_ERR_REPORT("fmd_driver_init: "
+ "Couldn't open FM channel. Either chip is not connected"
+ " or Protocol Driver is not initialized");
+ ret_val = false;
+ goto error;
+ }
+
+ if (!pf_data->get_local_revision(pf_data, &rev_data)) {
+ FM_DEBUG_REPORT("No revision data available");
+ ret_val = false;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("Read revision data revision %04x "
+ "sub_version %04x",
+ rev_data.revision, rev_data.sub_version);
+ cg2900_fm_set_chip_version(rev_data.revision, rev_data.sub_version);
+ ret_val = true;
+
+error:
+ FM_DEBUG_REPORT("fmd_driver_init: Returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * fmd_driver_exit() - Deinitializes the mutex, semaphores, etc.
+ *
+ * It also deregisters FM Driver with the Protocol Driver.
+ *
+ */
+static void fmd_driver_exit(void)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_driver_exit");
+ irq_thread_required = false;
+ mutex_destroy(&write_mutex);
+ mutex_destroy(&send_cmd_mutex);
+ fmd_stop_irq_thread();
+ /* Close the FM channel */
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (pf_data->opened)
+ pf_data->close(pf_data);
+}
+
+/**
+ * fmd_send_cmd_and_read_resp() - Send command and read response.
+ *
+ * This function sends the HCI Command to Protocol Driver and
+ * Reads back the Response Packet.
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ * @resp_num_parameters: (out) Number of paramters of the response packet.
+ * @resp_parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the command is sent successfully and the
+ * response received is also correct.
+ * -EINVAL: If the received response is not correct.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ )
+{
+ int result;
+ u16 read_cmd_id = CMD_ID_NONE;
+
+ FM_INFO_REPORT("fmd_send_cmd_and_read_resp");
+
+ mutex_lock(&send_cmd_mutex);
+ result = fmd_send_cmd(
+ cmd_id,
+ num_parameters,
+ parameters);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_read_resp(
+ &read_cmd_id,
+ resp_num_parameters,
+ resp_parameters);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Check that the response belongs to the sent command
+ */
+ if (read_cmd_id != cmd_id)
+ result = -EINVAL;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_send_cmd_and_read_resp: "
+ "returning %d", result);
+ return result;
+}
+
+/**
+ * fmd_send_cmd() - This function sends the HCI Command
+ * to Protocol Driver.
+ *
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ *
+ * Returns:
+ * 0: If the command is sent successfully to Lower Layers.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd(
+ const u16 cmd_id ,
+ const u16 num_parameters,
+ const u16 *parameters
+ )
+{
+ /*
+ * Total Length includes 6 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 total_length = num_parameters * sizeof(u16) + FM_HCI_CMD_HEADER_LEN;
+ /*
+ * Parameter Length includes 5 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 param_length = num_parameters * sizeof(u16) + FM_HCI_CMD_PARAM_LEN;
+ u8 *fm_data = kmalloc(total_length, GFP_KERNEL);
+ int err = -EINVAL;
+
+ FM_INFO_REPORT("fmd_send_cmd");
+
+ if (fm_data == NULL) {
+ err = -EIO;
+ goto error;
+ }
+
+ if (num_parameters && parameters == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* HCI encapsulation */
+ fm_data[0] = param_length;
+ fm_data[1] = FM_CATENA_OPCODE;
+ fm_data[2] = FM_WRITE;
+ fm_data[3] = FM_FUNCTION_WRITE_COMMAND;
+ fm_data[4] = FM_CMD_GET_LSB(cmd_id, num_parameters);
+ fm_data[5] = FM_CMD_GET_MSB(cmd_id);
+
+ memcpy(
+ (fm_data + FM_HCI_CMD_HEADER_LEN),
+ (void *)parameters,
+ num_parameters * sizeof(u16));
+
+ /* Send the Packet */
+ err = fmd_send_packet(total_length , fm_data);
+
+error:
+ kfree(fm_data);
+ FM_DEBUG_REPORT("fmd_send_cmd: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_read_resp() - This function reads the response packet of the previous
+ * command sent to FM Chip and copies it to the buffer provided as parameter.
+ *
+ * @cmd_id: (out) Command Id received from FM Chip.
+ * @num_parameters: (out) Number of paramters of the response packet.
+ * @parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the response buffer is copied successfully.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ )
+{
+ int err;
+ int param_offset = 0;
+ int byte_offset = 0;
+ FM_INFO_REPORT("fmd_read_resp");
+
+ /* Wait till response of the command is received */
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+
+ /* Check if the parameters are valid */
+ if (cmd_id == NULL || (fmd_data.num_parameters &&
+ (num_parameters == NULL || parameters == NULL))) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Fill the arguments */
+ *cmd_id = fmd_data.cmd_id;
+ if (fmd_data.num_parameters) {
+ *num_parameters = fmd_data.num_parameters;
+ while (param_offset <
+ (*num_parameters * sizeof(u16)) / WORD_LENGTH) {
+ parameters[param_offset] =
+ (u16)(fmd_data.parameters[byte_offset])
+ & 0x00ff;
+ parameters[param_offset] |=
+ ((u16)(fmd_data.parameters[byte_offset + COUNTER])
+ & 0x00ff) << SHIFT_OFFSET;
+ byte_offset = byte_offset + WORD_LENGTH;
+ param_offset++;
+ }
+ }
+
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_read_resp: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_process_fm_function() - Process FM Function.
+ *
+ * This function processes the Response buffer received
+ * from lower layers for the FM function and performs the necessary action to
+ * parse the same.
+ * @packet_buffer: Received Buffer.
+ */
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ )
+{
+ u8 fm_function_id;
+ u8 block_id;
+ int count = 0;
+
+ if (packet_buffer == NULL)
+ return;
+
+ fm_function_id = FM_GET_FUNCTION_ID(packet_buffer);
+ switch (fm_function_id) {
+ case FM_FUNCTION_ENABLE:
+ case FM_FUNCTION_DISABLE:
+ case FM_FUNCTION_RESET:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+
+ fmd_data.cmd_id = FM_GET_CMD_ID(packet_buffer);
+ fmd_data.num_parameters =
+ FM_GET_NUM_PARAMS(packet_buffer);
+
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "Cmd Id = 0x%04x, Num Of Parms = %02x",
+ fmd_data.cmd_id, fmd_data.num_parameters);
+
+ if (fmd_data.num_parameters) {
+ while (count <
+ (fmd_data.num_parameters * sizeof(u16))) {
+ fmd_data.parameters[count] =
+ *(FM_GET_RSP_BUFFER_ADDR(packet_buffer) + count);
+ count++;
+ }
+ }
+
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ block_id = FM_GET_BLOCK_ID(packet_buffer);
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s"
+ "block id = %02x",
+ fmd_get_fm_function_name(fm_function_id),
+ block_id);
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ default:
+ FM_ERR_REPORT(
+ "fmd_process_fm_function: "
+ "default case: command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ break;
+ }
+}
+
+/**
+ * fmd_write_file_block() - download firmware.
+ *
+ * This Function adds the header for downloading
+ * the firmware and coeffecient files and sends it to Protocol Driver.
+ * @file_block_id: Block ID of the F/W to be transmitted to FM Chip
+ * @file_block: Buffer containing the bytes to be sent.
+ * @file_block_length: Size of the Firmware buffer.
+ *
+ * Returns:
+ * 0: If there is no error.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ )
+{
+ int err;
+
+ FM_INFO_REPORT("fmd_write_file_block");
+ if (file_block == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ mutex_lock(&send_cmd_mutex);
+ file_block[0] = file_block_length + FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ file_block[1] = FM_CATENA_OPCODE;
+ file_block[2] = FM_WRITE;
+ file_block[3] = FM_FUNCTION_FIRMWARE_DOWNLOAD;
+ file_block[4] = file_block_id;
+ /* Send the Packet */
+ err = fmd_send_packet(
+ file_block_length +
+ FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ file_block);
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_write_file_block: "
+ "returning %d", err);
+ return err;
+}
+
+int fmd_init(void)
+{
+ int err;
+
+ if (!fmd_driver_init()) {
+ err = -EIO;
+ goto error;
+ }
+
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+ fmd_state_info.fmd_initialized = true;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_state_info.mode = FMD_MODE_IDLE;
+ fmd_state_info.callback = NULL;
+ fmd_state_info.rx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.rx_stereo_mode = FMD_STEREOMODE_BLENDING;
+ fmd_state_info.rx_volume = MAX_ANALOG_VOLUME;
+ fmd_state_info.rx_antenna = FMD_ANTENNA_EMBEDDED;
+ fmd_state_info.rx_rds_on = false;
+ fmd_state_info.rx_seek_stop_level = DEFAULT_RSSI_THRESHOLD;
+ fmd_state_info.tx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.tx_preemphasis = FMD_EMPHASIS_75US;
+ fmd_state_info.tx_pilot_dev = DEFAULT_PILOT_DEVIATION;
+ fmd_state_info.tx_rds_dev = DEFAULT_RDS_DEVIATION;
+ fmd_state_info.tx_strength = MAX_POWER_LEVEL;
+ fmd_state_info.max_channels_to_scan = DEFAULT_CHANNELS_TO_SCAN;
+ fmd_state_info.tx_stereo_mode = true;
+ fmd_state_info.irq_index = 0;
+ spin_lock_init(&fmd_spinlock);
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_init returning = %d", err);
+ return err;
+}
+
+void fmd_exit(void)
+{
+ fmd_set_interrupt_sem();
+ fmd_driver_exit();
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+}
+
+int fmd_register_callback(
+ fmd_radio_cb callback
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.callback = callback;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_version(
+ u16 *version
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_GET_VERSION_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (version == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GET_VERSION,
+ CMD_GET_VERSION_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ memcpy(version,
+ response_data,
+ sizeof(u16) * CMD_GET_VERSION_RSP_PARAM_LEN);
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mode(
+ u8 mode
+ )
+{
+ int err;
+ u16 parameters[CMD_GOTO_MODE_PARAM_LEN];
+ int io_result;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_MODE_TX) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ fmd_state_info.gocmd = FMD_STATE_MODE;
+ FM_ERR_REPORT("Sending Set Mode");
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_MODE,
+ CMD_GOTO_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+ fmd_state_info.mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range_properties(
+ u8 range,
+ u32 *min_freq,
+ u32 *max_freq
+ )
+{
+ int err;
+
+ if (min_freq == NULL || max_freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ switch (range) {
+ case FMD_FREQRANGE_EUROAMERICA:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_JAPAN:
+ *min_freq = FMD_JAPAN_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_JAPAN_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_CHINA:
+ *min_freq = FMD_CHINA_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_CHINA_MAX_FREQ_IN_KHZ;
+ break;
+ default:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_antenna(
+ u8 antenna
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_ANTENNA_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_ANTENNA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SET_ANTENNA,
+ CMD_SET_ANTENNA_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_antenna = antenna;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_antenna(
+ u8 *antenna
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *antenna = fmd_state_info.rx_antenna;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ fmd_state_info.rx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *range = fmd_state_info.rx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_rx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_mode(
+ u8 mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_STEREOMODE_BLENDING) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_stereo_mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_ctrl_blending_rssi(
+ u16 min_rssi,
+ u16 max_rssi)
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = min_rssi;
+ parameters[1] = max_rssi;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_CONTROL_BLENDING_RSSI,
+ CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stereo_mode(
+ u8 *mode
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_STATE_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_STATE,
+ CMD_RP_GET_STATE_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 2nd element of response is stereo signal */
+ *mode = response_data[1];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_RSSI_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_RSSI,
+ CMD_RP_GET_RSSI_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *strength = response_data[0]; /* 1st byte is the signal strength */
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stop_level(
+ u16 stoplevel
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.rx_seek_stop_level = stoplevel;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stop_level(
+ u16 *stop_level
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stop_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stop_level = fmd_state_info.rx_seek_stop_level;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_seek(
+ bool upwards
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SEARCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (upwards)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+ fmd_state_info.gocmd = FMD_STATE_SEEK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SEARCH_START,
+ CMD_SP_SEARCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_scan_band(
+ u8 max_channels_to_scan
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan > MAX_CHANNELS_TO_SCAN) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = max_channels_to_scan;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+
+ fmd_state_info.gocmd = FMD_STATE_SCAN_BAND;
+ fmd_state_info.max_channels_to_scan = max_channels_to_scan;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_START,
+ CMD_SP_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_max_channels_to_scan(
+ u8 *max_channels_to_scan
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *max_channels_to_scan = fmd_state_info.max_channels_to_scan;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_scan_band_info(
+ u32 index,
+ u16 *num_channels,
+ u16 *channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL || channels == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_GET_RESULT,
+ CMD_SP_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 1st byte indicates number of channels found */
+ *num_channels = response_data[0];
+ /* 2nd byte indicates 1st channel number */
+ channels[0] = response_data[1];
+ /* 3rd byte indicates RSSI of corresponding channel */
+ rssi[0] = response_data[2];
+ /* 4th byte indicates 2nd channel number */
+ channels[1] = response_data[3];
+ /* 5th byte indicates RSSI of corresponding channel */
+ rssi[1] = response_data[4];
+ /* 6th byte indicates 3rd channel number */
+ channels[2] = response_data[5];
+ /* 7th byte indicates RSSI of corresponding channel */
+ rssi[2] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_block_scan(
+ u32 start_freq,
+ u32 stop_freq,
+ u8 antenna
+ )
+{
+ u16 start_channel;
+ u16 stop_channel;
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (start_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ start_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (stop_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ stop_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Convert the start frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* Convert the end frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[0] = start_channel;
+ parameters[1] = stop_channel;
+ parameters[2] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_BLOCK_SCAN;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_START,
+ CMD_SP_BLOCK_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_block_scan_result(
+ u32 index,
+ u16 *num_channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_BLOCK_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_GET_RESULT,
+ CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * Response packet has 1st byte as the number
+ * of channels, and the remaining 6 bytes as
+ * rssi values of the channels.
+ */
+ *num_channels = response_data[0];
+ rssi[0] = response_data[1];
+ rssi[1] = response_data[2];
+ rssi[2] = response_data[3];
+ rssi[3] = response_data[4];
+ rssi[4] = response_data[5];
+ rssi[5] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_stop_seeking(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!(fmd_state_info.gocmd == FMD_STATE_SEEK ||
+ fmd_state_info.gocmd == FMD_STATE_SCAN_BAND ||
+ fmd_state_info.gocmd == FMD_STATE_BLOCK_SCAN)) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_SEEK_STOP;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_STOP,
+ CMD_SP_STOP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_update_start(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_UPDATE_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_AF_UPDATE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_START,
+ CMD_SP_AF_UPDATE_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_update_result(
+ u16 *af_level
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_UPDATE_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (af_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_GET_RESULT,
+ CMD_SP_AF_UPDATE_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * 1st byte of response packet is the
+ * RSSI of the AF Frequency.
+ */
+ *af_level = response_data[0];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_switch_start(
+ u32 freq,
+ u16 picode
+ )
+{
+
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_SWITCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[1] = picode;
+ parameters[2] = 0xFFFF; /* PI Mask */
+ parameters[3] = fmd_state_info.rx_seek_stop_level;
+ parameters[4] = 0x0000; /* Unmute when AF's PI matches expected PI */
+
+ fmd_state_info.gocmd = FMD_STATE_AF_SWITCH;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_START,
+ CMD_SP_AF_SWITCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_switch_results(
+ u16 *afs_conclusion,
+ u16 *afs_level,
+ u16 *afs_pi
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_SWITCH_GET_RESULT_RWSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (afs_conclusion == NULL ||
+ afs_level == NULL ||
+ afs_pi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_GET_RESULT,
+ CMD_SP_AF_SWITCH_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *afs_conclusion = response_data[0];
+ *afs_level = response_data[1];
+ *afs_pi = response_data[2];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.rx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_size(
+ u8 size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (size > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = size;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_threshold(
+ u8 threshold
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (threshold > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = threshold;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_THRESHOLD,
+ CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (on_off_state) {
+ case FMD_SWITCH_ON_RDS_SIMULATOR:
+ parameters[0] = 0xFFFF;
+ break;
+ case FMD_SWITCH_OFF_RDS:
+ default:
+ parameters[0] = 0x0000;
+ fmd_state_info.rx_rds_on = false;
+ break;
+ case FMD_SWITCH_ON_RDS:
+ parameters[0] = 0x0001;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ case FMD_SWITCH_ON_RDS_ENHANCED_MODE:
+ parameters[0] = 0x0002;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds_group_rejection(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_GROUP_REJECTION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on_off_state == FMD_RDS_GROUP_REJECTION_ON)
+ parameters[0] = 0x0001;
+ else if (on_off_state == FMD_RDS_GROUP_REJECTION_OFF)
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_GROUP_REJECTION,
+ CMD_DP_SET_GROUP_REJECTION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_low_level_rds_groups(
+ u8 index,
+ u16 *block1,
+ u16 *block2,
+ u16 *block3,
+ u16 *block4,
+ u8 *status1,
+ u8 *status2,
+ u8 *status3,
+ u8 *status4
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL ||
+ status1 == NULL ||
+ status2 == NULL ||
+ status3 == NULL ||
+ status4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *block1 = fmd_state_info.rds_group[index].block[0];
+ *block2 = fmd_state_info.rds_group[index].block[1];
+ *block3 = fmd_state_info.rds_group[index].block[2];
+ *block4 = fmd_state_info.rds_group[index].block[3];
+ *status1 = fmd_state_info.rds_group[index].status[0];
+ *status2 = fmd_state_info.rds_group[index].status[1];
+ *status3 = fmd_state_info.rds_group[index].status[2];
+ *status4 = fmd_state_info.rds_group[index].status[3];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_deemphasis(
+ u8 deemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_DEEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (deemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+
+ case FMD_EMPHASIS_75US:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+
+ case FMD_EMPHASIS_NONE:
+ default:
+ parameters[0] = FMD_EMPHASIS_NONE;
+ break;
+
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_SET_DEEMPHASIS,
+ CMD_RP_SET_DEEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pa(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ fmd_state_info.gocmd = FMD_STATE_PA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_MODE,
+ CMD_PA_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_signal_strength(
+ u16 strength
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if ((strength > MAX_POWER_LEVEL)
+ || (strength < MIN_POWER_LEVEL)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = strength;
+
+ fmd_state_info.gocmd = FMD_STATE_PA_LEVEL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_CONTROL,
+ CMD_PA_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_strength = strength;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *strength = fmd_state_info.tx_strength;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range > FMD_FREQRANGE_CHINA) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *range = fmd_state_info.tx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_preemphasis(
+ u8 preemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PREEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (preemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+ case FMD_EMPHASIS_75US:
+ default:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PREEMPHASIS,
+ CMD_RP_SET_PREEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_preemphasis = preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_preemphasis(
+ u8 *preemphasis
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (preemphasis == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *preemphasis = fmd_state_info.tx_preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_tx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_tx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_enable_stereo_mode(
+ bool enable_stereo_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = enable_stereo_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_stereo_mode = enable_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_stereo_mode(
+ bool *stereo_mode
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stereo_mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stereo_mode = fmd_state_info.tx_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pilot_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_PILOT_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PILOT_DEVIATION,
+ CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_pilot_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_pilot_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_pilot_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_RDS_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_RDS_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_RDS_DEVIATION,
+ CMD_RP_SET_RDS_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_rds_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_rds_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_on = on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_group(
+ u16 position,
+ u8 *block1,
+ u8 *block2,
+ u8 *block3,
+ u8 *block4
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_GROUP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = position;
+ memcpy(&parameters[1], block1, sizeof(u16));
+ memcpy(&parameters[2], block2, sizeof(u16));
+ memcpy(&parameters[3], block3, sizeof(u16));
+ memcpy(&parameters[4], block4, sizeof(u16));
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_GROUP,
+ CMD_DP_BUFFER_SET_GROUP_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_buffer_set_size(
+ u16 buffer_size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = buffer_size;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+
+}
+
+int fmd_tx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.tx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_balance(
+ s8 balance
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_BALANCE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert balance from percentage to chip number */
+ parameters[0] = (((s16)balance) * FMD_MAX_BALANCE) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_BALANCE,
+ CMD_SET_BALANCE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_volume(
+ u8 volume
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_VOLUME_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert volume from percentage to chip number */
+ parameters[0] = (((u16)volume) * FMD_MAX_VOLUME) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_VOLUME,
+ CMD_SET_VOLUME_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_volume = volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_volume(
+ u8 *volume
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (volume == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *volume = fmd_state_info.rx_volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = 0x0001;
+
+ fmd_state_info.gocmd = FMD_STATE_MUTE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_MUTE,
+ CMD_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_ext_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_EXT_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_EXT_SET_MUTE,
+ CMD_EXT_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_power_up(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_GEN_POWERUP;
+ FM_ERR_REPORT("Sending Gen Power Up");
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_POWERUP,
+ CMD_POWERUP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_standby(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_STANDBY,
+ CMD_GOTO_STANDBY_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_power_down(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_POWERDOWN,
+ CMD_GOTO_POWERDOWN_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_select_ref_clk(
+ u16 ref_clk
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = ref_clk;
+
+ fmd_state_info.gocmd = FMD_STATE_SELECT_REF_CLK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SELECT_REFERENCE_CLOCK,
+ CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_ref_clk_pll(
+ u16 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = freq;
+
+ fmd_state_info.gocmd = FMD_STATE_SET_REF_CLK_PLL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SET_REFERENCE_CLOCK_PLL,
+ CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_send_fm_ip_enable(void)
+{
+ int err;
+ u8 fm_ip_enable_cmd[CMD_IP_ENABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_enable_cmd[0] = CMD_IP_ENABLE_PARAM_LEN;
+ fm_ip_enable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_enable_cmd[2] = FM_WRITE ;
+ fm_ip_enable_cmd[3] = FM_FUNCTION_ENABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_ENABLE_CMD_LEN,
+ fm_ip_enable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_ip_disable(void)
+{
+ int err;
+ u8 fm_ip_disable_cmd[CMD_IP_DISABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_disable_cmd[0] = CMD_IP_DISABLE_PARAM_LEN;
+ fm_ip_disable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_disable_cmd[2] = FM_WRITE ;
+ fm_ip_disable_cmd[3] = FM_FUNCTION_DISABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_DISABLE_CMD_LEN,
+ fm_ip_disable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_firmware(
+ u8 *fw_buffer,
+ u16 fw_size
+ )
+{
+ int err = -EINVAL;
+ u16 bytes_to_write = ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ u16 bytes_remaining = fw_size;
+ u8 fm_firmware_data[ST_WRITE_FILE_BLK_SIZE + FM_HCI_CMD_HEADER_LEN];
+ u32 block_id = 0;
+
+ if (fw_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ while (bytes_remaining > 0) {
+ if (bytes_remaining <
+ (ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN))
+ bytes_to_write = bytes_remaining;
+
+ /*
+ * Five bytes of HCI Header for FM Firmware
+ * so shift the firmware data by 5 bytes
+ */
+ memcpy(
+ fm_firmware_data + FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ fw_buffer, bytes_to_write);
+ err = fmd_write_file_block(
+ block_id,
+ fm_firmware_data,
+ bytes_to_write);
+ if (err) {
+ FM_DEBUG_REPORT("fmd_send_fm_firmware: "
+ "Failed to download %d Block "
+ "error = %d", (unsigned int)block_id, err);
+ goto error;
+ }
+ /*
+ * Increment the Block Id by 1, since one
+ * block is successfully transmitted
+ * to the chip.
+ */
+ block_id++;
+ /*
+ * Increment the next firmware buffer equal
+ * to the number of bytes transmitted.
+ */
+ fw_buffer += bytes_to_write;
+ /*
+ * Decrement the number of bytes remaining
+ * equal to number of bytes transmitted successfully.
+ */
+ bytes_remaining -= bytes_to_write;
+
+ if (block_id == ST_MAX_NUMBER_OF_FILE_BLOCKS)
+ block_id = 0;
+ }
+
+error:
+ return err;
+}
+
+int fmd_int_bufferfull(
+ u16 *number_of_rds_groups
+ )
+{
+ u16 response_count;
+ u16 response_data[CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN];
+ u16 index = 0;
+ u16 rds_group_count;
+ u8 result = -ENOEXEC;
+ struct fmd_rds_group rds_group;
+
+ if (!fmd_state_info.rx_rds_on)
+ goto error;
+
+ /* get group count*/
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP_COUNT,
+ CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (result != 0)
+ goto error;
+
+ /* read RDS groups */
+ rds_group_count = FM_GET_NUM_RDS_GRPS(response_data);
+ if (rds_group_count > MAX_RDS_GROUPS)
+ rds_group_count = MAX_RDS_GROUPS;
+
+ *number_of_rds_groups = rds_group_count;
+
+ if (rds_group_count) {
+ FM_DEBUG_REPORT("rds_group_count = %d", rds_group_count);
+ while (rds_group_count-- && fmd_state_info.rx_rds_on) {
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP,
+ CMD_DP_BUFFER_GET_GROUP_PARAM_LEN,
+ NULL,
+ &response_count,
+ (u16 *)&rds_group);
+
+ if (result != 0)
+ goto error;
+
+ if (fmd_state_info.rx_rds_on)
+ fmd_state_info.rds_group[index++] = rds_group;
+ }
+ }
+error:
+ return result;
+}
+
+void fmd_start_rds_thread(
+ cg2900_fm_rds_cb cb_func
+ )
+{
+ FM_INFO_REPORT("fmd_start_rds_thread");
+ cb_rds_func = cb_func;
+ rds_thread_required = true;
+ rds_thread_task = kthread_create(fmd_rds_thread, NULL, "rds_thread");
+ if (IS_ERR(rds_thread_task)) {
+ FM_ERR_REPORT("fmd_start_rds_thread: "
+ "Unable to Create rds_thread");
+ rds_thread_task = NULL;
+ rds_thread_required = false;
+ return;
+ }
+ wake_up_process(rds_thread_task);
+}
+
+void fmd_stop_rds_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_rds_thread");
+ /* In case thread is waiting, set the rds sem */
+ fmd_set_rds_sem();
+ /* Re-initialize RDS Semaphore to zero */
+ sema_init(&rds_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ /* Wait for RDS thread to exit gracefully */
+ fmd_get_rds_sem();
+
+ if (rds_thread_task)
+ rds_thread_task = NULL;
+}
+
+void fmd_get_rds_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_rds_sem");
+ ret_val = down_killable(&rds_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_rds_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+void fmd_set_rds_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_rds_sem");
+ up(&rds_sem);
+}
+
+int fmd_set_dev(struct device *dev)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_DEBUG_REPORT("fmd_set_dev");
+
+ if (dev && cg2900_fm_dev) {
+ FM_ERR_REPORT("Only one FM device supported");
+ return -EACCES;
+ }
+
+ cg2900_fm_dev = dev;
+
+ if (!dev)
+ return 0;
+
+ pf_data = dev_get_platdata(dev);
+ pf_data->dev = dev;
+ pf_data->read_cb = fmd_read_cb;
+ pf_data->reset_cb = fmd_reset_cb;
+
+ return 0;
+}
+
+int fmd_set_test_tone_generator_status(
+ u8 test_tone_status
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_ENABLE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (test_tone_status > FMD_TST_TONE_ON_WO_SRC) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = test_tone_status;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_ENABLE,
+ CMD_TST_TONE_ENABLE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_CONNECT_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (left_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM ||
+ right_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = left_audio_mode;
+ parameters[1] = right_audio_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_CONNECT,
+ CMD_TST_TONE_CONNECT_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_SET_PARAMS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (tone_gen > FMD_TST_TONE_2 ||
+ waveform > FMD_TST_TONE_PULSE ||
+ frequency > 0x7FFF ||
+ volume > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = tone_gen;
+ parameters[1] = frequency;
+ parameters[2] = volume;
+ parameters[3] = phase_offset;
+ parameters[4] = dc;
+ parameters[5] = waveform;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_SET_PARAMS,
+ CMD_TST_TONE_SET_PARAMS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_limiter_setcontrol(
+ u16 audio_deviation,
+ u16 notification_hold_off_time
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (audio_deviation < MIN_AUDIO_DEVIATION ||
+ audio_deviation > MAX_AUDIO_DEVIATION ||
+ notification_hold_off_time > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = audio_deviation;
+ parameters[1] = notification_hold_off_time;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_LIMITER_SETCONTROL,
+ CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
+
+module_param(cg2900_fm_debug_level, ushort, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(cg2900_fm_debug_level, "cg2900_fm_debug_level: "
+ " *1: Only Error Logs* "
+ " 2: Info Logs "
+ " 3: Debug Logs "
+ " 4: HCI Logs");
+
diff --git a/drivers/media/radio/CG2900/cg2900_fm_driver.h b/drivers/media/radio/CG2900/cg2900_fm_driver.h
new file mode 100644
index 00000000000..62703fa4690
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_driver.h
@@ -0,0 +1,1856 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Driver for CG2900 FM Chip
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _FMDRIVER_H_
+#define _FMDRIVER_H_
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/semaphore.h>
+#include <linux/version.h>
+#include <linux/kthread.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/mutex.h>
+#include "cg2900_fm_api.h"
+
+/* structure declared in cg2900_fm_driver.c */
+extern struct timespec time_spec;
+
+/* module_param declared in cg2900_fm_driver.c */
+extern unsigned short cg2900_fm_debug_level;
+
+/**
+ * enum fmd_debug_levels - FM Driver Debug Levels.
+ *
+ * @FM_NO_LOGS: No Logs are displayed.
+ * @FM_ERROR_LOGS: Only Error Logs are displayed.
+ * @FM_INFO_LOGS: Function Entry logs are displayed.
+ * @FM_DEBUG_LOGS: Full debugging support.
+ * @FM_HCI_PACKET_LOGS: HCI Packet Sent/received to/by
+ * FM Driver are displayed.
+ *
+ * Various debug levels for FM Driver.
+ */
+enum fmd_debug_levels {
+ FM_NO_LOGS,
+ FM_ERROR_LOGS,
+ FM_INFO_LOGS,
+ FM_DEBUG_LOGS,
+ FM_HCI_PACKET_LOGS
+};
+
+#define FM_HEX_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) { \
+ printk(KERN_INFO fmt "\r\n" , ## arg); \
+ }
+
+#define FM_DEBUG_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level > FM_INFO_LOGS && \
+ cg2900_fm_debug_level < FM_HCI_PACKET_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_INFO "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define FM_INFO_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level > FM_ERROR_LOGS && \
+ cg2900_fm_debug_level < FM_HCI_PACKET_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_INFO "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define FM_ERR_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level >= FM_ERROR_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_ERR "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define MAX_COUNT_OF_IRQS 16
+#define MAX_BUFFER_SIZE 512
+#define MAX_NAME_SIZE 100
+/* Maximum size of parsable data in bytes, received from CG2900 FM IP */
+#define MAX_RESP_SIZE 20
+/* Minimum Power level for CG2900. The value is in units of dBuV */
+#define MIN_POWER_LEVEL 88
+/* Maximum Power level for CG2900. The value is in units of dBuV */
+#define MAX_POWER_LEVEL 123
+/* Minimum RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define MIN_RDS_DEVIATION 0
+/* Default RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define DEFAULT_RDS_DEVIATION 200
+/* Maximum RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define MAX_RDS_DEVIATION 750
+#define FMD_EU_US_MIN_FREQ_IN_KHZ 87500
+#define FMD_EU_US_MAX_FREQ_IN_KHZ 108000
+#define FMD_JAPAN_MIN_FREQ_IN_KHZ 76000
+#define FMD_JAPAN_MAX_FREQ_IN_KHZ 90000
+#define FMD_CHINA_MIN_FREQ_IN_KHZ 70000
+#define FMD_CHINA_MAX_FREQ_IN_KHZ 108000
+#define FMD_MIN_CHANNEL_NUMBER 0
+#define FMD_MAX_CHANNEL_NUMBER 760
+/*
+ * Maximum supported balance for CG2900. This is just a hexadecimal number
+ * with no units.
+ */
+#define FMD_MAX_BALANCE 0x7FFF
+/*
+ * Maximum supported volume for CG2900. This is just a hexadecimal number
+ * with no units.
+ */
+#define FMD_MAX_VOLUME 0x7FFF
+/* Minimum Program Identification value as per RDS specification */
+#define MIN_PI_VALUE 0x0000
+/* Maximum Program Identification value as per RDS specification */
+#define MAX_PI_VALUE 0xFFFF
+/* Minimum Program Type code value as per RDS specification */
+#define MIN_PTY_VALUE 0
+/* Maximum Program Type code value as per RDS specification */
+#define MAX_PTY_VALUE 31
+/* Minimum Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define MIN_PILOT_DEVIATION 0
+/* Default Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define DEFAULT_PILOT_DEVIATION 675
+/* Maximum Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define MAX_PILOT_DEVIATION 1000
+/*
+ * Default RSSI Threshold for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_RSSI_THRESHOLD 0x0100
+/*
+ * Default Peak Noise level for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_PEAK_NOISE_VALUE 0x0035
+/* Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will stop limiting the channel separation */
+#define STEREO_BLENDING_MIN_RSSI 0x0005
+/* Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will start limiting the channel separation */
+#define STEREO_BLENDING_MAX_RSSI 0x0100
+/*
+ * Default Average Noise level for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_AVERAGE_NOISE_MAX_VALUE 0x0030
+/*
+ * Minimum Audio Deviation Level, as per CG2900 FM User Manual.
+ * This is units of 10 Hz.
+ */
+#define MIN_AUDIO_DEVIATION 0x157C
+/*
+ * Maximum Audio Deviation Level, as per CG2900 FM UserManual.
+ * This is units of 10 Hz.
+ */
+#define MAX_AUDIO_DEVIATION 0x3840
+#define FREQUENCY_CONVERTOR_KHZ_HZ 1000
+#define CHANNEL_FREQ_CONVERTER_MHZ 50
+/* Interrupt(s) for CG2900 */
+#define IRPT_INVALID 0x0000
+#define IRPT_OPERATION_SUCCEEDED 0x0001
+#define IRPT_OPERATION_FAILED 0x0002
+#define IRPT_RX_BUFFERFULL_TX_BUFFEREMPTY 0x0008
+#define IRPT_RX_SIGNAL_QUALITYLOW_MUTE_STATUS_CHANGED 0x0010
+#define IRPT_RX_MONO_STEREO_TRANSITION 0x0020
+#define IRPT_TX_OVERMODULATION 0x0030
+#define IRPT_RX_RDS_SYNCFOUND_TX_OVERDRIVE 0x0040
+#define IRPT_RDS_SYNC_LOST 0x0080
+#define IRPT_PI_CODE_CHANGED 0x0100
+#define IRPT_REQUESTED_BLOCK_AVAILABLE 0x0200
+#define IRPT_BUFFER_CLEARED 0x2000
+#define IRPT_WARM_BOOT_READY 0x4000
+#define IRPT_COLD_BOOT_READY 0x8000
+/* FM Commands Id */
+#define CMD_ID_NONE 0x0000
+#define CMD_AUP_EXT_SET_MUTE 0x01E2
+#define CMD_AUP_SET_BALANCE 0x0042
+#define CMD_AUP_SET_MUTE 0x0062
+#define CMD_AUP_SET_VOLUME 0x0022
+#define CMD_FMR_DP_BUFFER_GET_GROUP 0x0303
+#define CMD_FMR_DP_BUFFER_GET_GROUP_COUNT 0x0323
+#define CMD_FMR_DP_BUFFER_SET_SIZE 0x0343
+#define CMD_FMR_DP_BUFFER_SET_THRESHOLD 0x06C3
+#define CMD_FMR_DP_SET_CONTROL 0x02A3
+#define CMD_FMR_DP_SET_GROUP_REJECTION 0x0543
+#define CMD_FMR_RP_GET_RSSI 0x0083
+#define CMD_FMR_RP_GET_STATE 0x0063
+#define CMD_FMR_RP_STEREO_SET_MODE 0x0123
+#define CMD_FMR_RP_STEREO_SET_CONTROL_BLENDING_RSSI 0x0143
+#define CMD_FMR_SET_ANTENNA 0x0663
+#define CMD_FMR_SP_AF_SWITCH_GET_RESULT 0x0603
+#define CMD_FMR_SP_AF_SWITCH_START 0x04A3
+#define CMD_FMR_SP_AF_UPDATE_GET_RESULT 0x0483
+#define CMD_FMR_SP_AF_UPDATE_START 0x0463
+#define CMD_FMR_SP_BLOCK_SCAN_GET_RESULT 0x06A3
+#define CMD_FMR_SP_BLOCK_SCAN_START 0x0683
+#define CMD_FMR_SP_SCAN_GET_RESULT 0x0423
+#define CMD_FMR_SP_SCAN_START 0x0403
+#define CMD_FMR_SP_SEARCH_START 0x03E3
+#define CMD_FMR_SP_STOP 0x0383
+#define CMD_FMR_SP_TUNE_GET_CHANNEL 0x03A3
+#define CMD_FMR_SP_TUNE_SET_CHANNEL 0x03C3
+#define CMD_FMR_TN_SET_BAND 0x0023
+#define CMD_FMR_TN_SET_GRID 0x0043
+#define CMD_FMR_RP_SET_DEEMPHASIS 0x00C3
+#define CMD_FMT_DP_BUFFER_GET_POSITION 0x0204
+#define CMD_FMT_DP_BUFFER_SET_GROUP 0x0244
+#define CMD_FMT_DP_BUFFER_SET_SIZE 0x0224
+#define CMD_FMT_DP_BUFFER_SET_THRESHOLD 0x0284
+#define CMD_FMT_DP_SET_CONTROL 0x0264
+#define CMD_FMT_PA_SET_CONTROL 0x01A4
+#define CMD_FMT_PA_SET_MODE 0x01E4
+#define CMD_FMT_RP_SET_PILOT_DEVIATION 0x02A4
+#define CMD_FMT_RP_SET_PREEMPHASIS 0x00C4
+#define CMD_FMT_RP_SET_RDS_DEVIATION 0x0344
+#define CMD_FMT_RP_STEREO_SET_MODE 0x0164
+#define CMD_FMT_SP_TUNE_GET_CHANNEL 0x0184
+#define CMD_FMT_SP_TUNE_SET_CHANNEL 0x0064
+#define CMD_FMT_TN_SET_BAND 0x0024
+#define CMD_FMT_TN_SET_GRID 0x0044
+#define CMD_GEN_GET_MODE 0x0021
+#define CMD_GEN_GET_REGISTER_VALUE 0x00E1
+#define CMD_GEN_GET_VERSION 0x00C1
+#define CMD_GEN_GOTO_MODE 0x0041
+#define CMD_GEN_GOTO_POWERDOWN 0x0081
+#define CMD_GEN_GOTO_STANDBY 0x0061
+#define CMD_GEN_POWERUP 0x0141
+#define CMD_GEN_SELECT_REFERENCE_CLOCK 0x0201
+#define CMD_GEN_SET_REFERENCE_CLOCK 0x0161
+#define CMD_GEN_SET_REFERENCE_CLOCK_PLL 0x01A1
+#define CMD_GEN_SET_REGISTER_VALUE 0x0101
+#define CMD_TST_TONE_ENABLE 0x0027
+#define CMD_TST_TONE_CONNECT 0x0047
+#define CMD_TST_TONE_SET_PARAMS 0x0067
+#define CMD_FMT_RP_LIMITER_SETCONTROL 0x01C4
+
+/* FM Command Id Parameter Length */
+#define CMD_GET_VERSION_PARAM_LEN 0
+#define CMD_GET_VERSION_RSP_PARAM_LEN 7
+#define CMD_GOTO_MODE_PARAM_LEN 1
+#define CMD_SET_ANTENNA_PARAM_LEN 1
+#define CMD_TN_SET_BAND_PARAM_LEN 3
+#define CMD_TN_SET_GRID_PARAM_LEN 1
+#define CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN 1
+#define CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN 0
+#define CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN 1
+#define CMD_RP_STEREO_SET_MODE_PARAM_LEN 1
+#define CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN 2
+#define CMD_RP_GET_RSSI_PARAM_LEN 0
+#define CMD_RP_GET_RSSI_RSP_PARAM_LEN 1
+#define CMD_RP_GET_STATE_PARAM_LEN 0
+#define CMD_RP_GET_STATE_RSP_PARAM_LEN 2
+#define CMD_SP_SEARCH_START_PARAM_LEN 4
+#define CMD_SP_SCAN_START_PARAM_LEN 4
+#define CMD_SP_SCAN_GET_RESULT_PARAM_LEN 1
+#define CMD_SP_SCAN_GET_RESULT_RSP_PARAM_LEN 7
+#define CMD_SP_BLOCK_SCAN_START_PARAM_LEN 3
+#define CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN 1
+#define CMD_SP_BLOCK_SCAN_GET_RESULT_RSP_PARAM_LEN 7
+#define CMD_SP_STOP_PARAM_LEN 0
+#define CMD_SP_AF_UPDATE_START_PARAM_LEN 1
+#define CMD_SP_AF_UPDATE_GET_RESULT_PARAM_LEN 0
+#define CMD_SP_AF_UPDATE_GET_RESULT_RSP_PARAM_LEN 1
+#define CMD_SP_AF_SWITCH_START_PARAM_LEN 5
+#define CMD_SP_AF_SWITCH_GET_RESULT_PARAM_LEN 0
+#define CMD_SP_AF_SWITCH_GET_RESULT_RWSP_PARAM_LEN 3
+#define CMD_DP_BUFFER_SET_SIZE_PARAM_LEN 1
+#define CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN 1
+#define CMD_DP_SET_CONTROL_PARAM_LEN 1
+#define CMD_DP_SET_GROUP_REJECTION_PARAM_LEN 1
+#define CMD_PA_SET_MODE_PARAM_LEN 1
+#define CMD_PA_SET_CONTROL_PARAM_LEN 1
+#define CMD_RP_SET_PREEMPHASIS_PARAM_LEN 1
+#define CMD_RP_SET_DEEMPHASIS_PARAM_LEN 1
+#define CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN 1
+#define CMD_RP_SET_RDS_DEVIATION_PARAM_LEN 1
+#define CMD_DP_BUFFER_SET_GROUP_PARAM_LEN 5
+#define CMD_SET_BALANCE_PARAM_LEN 1
+#define CMD_SET_VOLUME_PARAM_LEN 1
+#define CMD_SET_MUTE_PARAM_LEN 2
+#define CMD_EXT_SET_MUTE_PARAM_LEN 1
+#define CMD_POWERUP_PARAM_LEN 0
+#define CMD_GOTO_STANDBY_PARAM_LEN 0
+#define CMD_GOTO_POWERDOWN_PARAM_LEN 0
+#define CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN 1
+#define CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN 1
+#define CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN 0
+#define CMD_DP_BUFFER_GET_GROUP_PARAM_LEN 0
+#define CMD_IP_ENABLE_CMD_LEN 4
+#define CMD_IP_ENABLE_PARAM_LEN 3
+#define CMD_IP_DISABLE_CMD_LEN 4
+#define CMD_IP_DISABLE_PARAM_LEN 3
+#define CMD_TST_TONE_ENABLE_PARAM_LEN 1
+#define CMD_TST_TONE_CONNECT_PARAM_LEN 2
+#define CMD_TST_TONE_SET_PARAMS_PARAM_LEN 6
+#define CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN 2
+
+/* FM HCI Command and event specific */
+#define FM_WRITE 0x00
+#define FM_READ 0x01
+#define FM_CATENA_OPCODE 0xFE
+#define HCI_CMD_FM 0xFD50
+#define HCI_CMD_VS_WRITE_FILE_BLOCK 0xFC2E
+#define FM_EVENT_ID 0x15
+#define FM_SUCCESS_STATUS 0x00
+#define FM_EVENT 0x01
+#define HCI_COMMAND_COMPLETE_EVENT 0x0E
+#define HCI_VS_DBG_EVENT 0xFF
+#define ST_WRITE_FILE_BLK_SIZE 254
+#define ST_MAX_NUMBER_OF_FILE_BLOCKS 256
+#define FM_PG1_INTERRUPT_EVENT_LEN 0x04
+#define FM_PG2_INTERRUPT_EVENT_LEN 0x06
+#define FM_HCI_CMD_HEADER_LEN 6
+#define FM_HCI_CMD_PARAM_LEN 5
+#define FM_HCI_WRITE_FILE_BLK_HEADER_LEN 5
+#define FM_HCI_WRITE_FILE_BLK_PARAM_LEN 4
+#define HCI_PACKET_INDICATOR_CMD 0x01
+#define HCI_PACKET_INDICATOR_EVENT 0x04
+#define HCI_PACKET_INDICATOR_FM_CMD_EVT 0x08
+/* FM Functions specific to CG2900 */
+#define FM_FUNCTION_ENABLE 0x00
+#define FM_FUNCTION_DISABLE 0x01
+#define FM_FUNCTION_RESET 0x02
+#define FM_FUNCTION_WRITE_COMMAND 0x10
+#define FM_FUNCTION_SET_INT_MASK_ALL 0x20
+#define FM_FUNCTION_GET_INT_MASK_ALL 0x21
+#define FM_FUNCTION_SET_INT_MASK 0x22
+#define FM_FUNCTION_GET_INT_MASK 0x23
+#define FM_FUNCTION_FIRMWARE_DOWNLOAD 0x30
+/* Command succeeded */
+#define FM_CMD_STATUS_CMD_SUCCESS 0x00
+/* HCI_ERR_HW_FAILURE when no response from the IP */
+#define FM_CMD_STATUS_HCI_ERR_HW_FAILURE 0x03
+/* HCI_ERR_INVALID_PARAMETERS. */
+#define FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS 0x12
+/* When the host tries to send a command to an IP that hasn't been
+ * initialized.
+ */
+#define FM_CMD_STATUS_IP_UNINIT 0x15
+/* HCI_ERR_UNSPECIFIED_ERROR: any other error */
+#define FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR 0x1F
+/* HCI_ERR_CMD_DISALLOWED when the host asks for an unauthorized operation
+ * (FM state transition for instance)
+ */
+#define FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED 0x0C
+/* Wrong sequence number for FM FW download command */
+#define FM_CMD_STATUS_WRONG_SEQ_NUM 0xF1
+/* Unknown file type for FM FW download command */
+#define FM_CMD_STATUS_UNKNOWN_FILE_TYPE 0xF2
+/* File version mismatch for FM FW download command */
+#define FM_CMD_STATUS_FILE_VERSION_MISMATCH 0xF3
+
+
+/**
+ * enum fmd_event - Events received.
+ *
+ * @FMD_EVENT_OPERATION_COMPLETED: Previous operation has been completed.
+ * @FMD_EVENT_ANTENNA_STATUS_CHANGED: Antenna has been changed.
+ * @FMD_EVENT_FREQUENCY_CHANGED: Frequency has been changed.
+ * @FMD_EVENT_SEEK_COMPLETED: Seek operation has completed.
+ * @FMD_EVENT_SCAN_BAND_COMPLETED: Band Scan completed.
+ * @FMD_EVENT_BLOCK_SCAN_COMPLETED: Block Scan completed.
+ * @FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE: Af Update or AF Switch is complete.
+ * @FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE: Mono stereo transition is
+ * completed.
+ * @FMD_EVENT_SEEK_STOPPED: Previous Seek/Band Scan/ Block Scan operation is
+ * stopped.
+ * @FMD_EVENT_GEN_POWERUP: FM IP Powerup has been powered up.
+ * @FMD_EVENT_RDSGROUP_RCVD: RDS Groups Full interrupt.
+ * @FMD_EVENT_LAST_ELEMENT: Last event, used for keeping count of
+ * number of events.
+ *
+ * Various events received from FM driver for Upper Layer(s) processing.
+ */
+enum fmd_event {
+ FMD_EVENT_OPERATION_COMPLETED,
+ FMD_EVENT_ANTENNA_STATUS_CHANGED,
+ FMD_EVENT_FREQUENCY_CHANGED,
+ FMD_EVENT_SEEK_COMPLETED,
+ FMD_EVENT_SCAN_BAND_COMPLETED,
+ FMD_EVENT_BLOCK_SCAN_COMPLETED,
+ FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE,
+ FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE,
+ FMD_EVENT_SEEK_STOPPED,
+ FMD_EVENT_GEN_POWERUP,
+ FMD_EVENT_RDSGROUP_RCVD,
+ FMD_EVENT_LAST_ELEMENT
+};
+
+/**
+ * enum fmd_mode - FM Driver Modes.
+ *
+ * @FMD_MODE_IDLE: FM Driver in Idle mode.
+ * @FMD_MODE_RX: FM Driver in Rx mode.
+ * @FMD_MODE_TX: FM Driver in Tx mode.
+ *
+ * Various Modes of FM Radio.
+ */
+enum fmd_mode {
+ FMD_MODE_IDLE,
+ FMD_MODE_RX,
+ FMD_MODE_TX
+};
+
+/**
+ * enum fmd_antenna - Antenna selection.
+ *
+ * @FMD_ANTENNA_EMBEDDED: Embedded Antenna.
+ * @FMD_ANTENNA_WIRED: Wired Antenna.
+ *
+ * Antenna to be used for FM Radio.
+ */
+enum fmd_antenna {
+ FMD_ANTENNA_EMBEDDED,
+ FMD_ANTENNA_WIRED
+};
+
+/**
+ * enum fmd_grid - Grid used on FM Radio.
+ *
+ * @FMD_GRID_50KHZ: 50 kHz grid spacing.
+ * @FMD_GRID_100KHZ: 100 kHz grid spacing.
+ * @FMD_GRID_200KHZ: 200 kHz grid spacing.
+ *
+ * Spacing used on FM Radio.
+ */
+enum fmd_grid {
+ FMD_GRID_50KHZ,
+ FMD_GRID_100KHZ,
+ FMD_GRID_200KHZ
+};
+
+/**
+ * enum fmd_emphasis - De-emphasis/Pre-emphasis level.
+ *
+ * @FMD_EMPHASIS_NONE: De-emphasis Disabled.
+ * @FMD_EMPHASIS_50US: 50 us de-emphasis/pre-emphasis level.
+ * @FMD_EMPHASIS_75US: 75 us de-emphasis/pre-emphasis level.
+ *
+ * De-emphasis/Pre-emphasis level used on FM Radio.
+ */
+enum fmd_emphasis {
+ FMD_EMPHASIS_NONE = 0,
+ FMD_EMPHASIS_50US = 1,
+ FMD_EMPHASIS_75US = 2
+};
+
+/**
+ * enum fmd_freq_range - Frequency range.
+ *
+ * @FMD_FREQRANGE_EUROAMERICA: EU/US Range (87.5 - 108 MHz).
+ * @FMD_FREQRANGE_JAPAN: Japan Range (76 - 90 MHz).
+ * @FMD_FREQRANGE_CHINA: China Range (70 - 108 MHz).
+ *
+ * Various Frequency range(s) supported by FM Radio.
+ */
+enum fmd_freq_range {
+ FMD_FREQRANGE_EUROAMERICA,
+ FMD_FREQRANGE_JAPAN,
+ FMD_FREQRANGE_CHINA
+};
+
+/**
+ * enum fmd_stereo_mode - FM Driver Stereo Modes.
+ *
+ * @FMD_STEREOMODE_OFF: Streo Blending Off.
+ * @FMD_STEREOMODE_MONO: Mono Mode.
+ * @FMD_STEREOMODE_BLENDING: Blending Mode.
+ *
+ * Various Stereo Modes of FM Radio.
+ */
+enum fmd_stereo_mode {
+ FMD_STEREOMODE_OFF,
+ FMD_STEREOMODE_MONO,
+ FMD_STEREOMODE_BLENDING
+};
+
+/**
+ * enum fmd_pilot_tone - Pilot Tone Selection
+ *
+ * @FMD_PILOT_TONE_DISABLED: Pilot Tone to be disabled.
+ * @FMD_PILOT_TONE_ENABLED: Pilot Tone to be enabled.
+ *
+ * Pilot Tone to be enabled or disabled.
+ */
+enum fmd_pilot_tone {
+ FMD_PILOT_TONE_DISABLED,
+ FMD_PILOT_TONE_ENABLED
+};
+
+/**
+ * enum fmd_output - Output of Sample Rate Converter.
+ *
+ * @FMD_OUTPUT_DISABLED: Sample Rate converter in disabled.
+ * @FMD_OUTPUT_I2S: I2S Output from Sample rate converter.
+ * @FMD_OUTPUT_PARALLEL: Parallel output from sample rate converter.
+ *
+ * Sample Rate Converter's output to be set on Connectivity Controller.
+ */
+enum fmd_output {
+ FMD_OUTPUT_DISABLED,
+ FMD_OUTPUT_I2S,
+ FMD_OUTPUT_PARALLEL
+};
+
+/**
+ * enum fmd_input - Audio Input to Sample Rate Converter.
+ *
+ * @FMD_INPUT_ANALOG: Selects the ADC's as audio source
+ * @FMD_INPUT_DIGITAL: Selects Digital Input as audio source.
+ *
+ * Audio Input source for Sample Rate Converter.
+ */
+enum fmd_input {
+ FMD_INPUT_ANALOG,
+ FMD_INPUT_DIGITAL
+};
+
+/**
+ * enum fmd_rds_mode - RDS Mode to be selected for FM Rx.
+ *
+ * @FMD_SWITCH_OFF_RDS: RDS Decoding disabled in FM Chip.
+ * @FMD_SWITCH_ON_RDS: RDS Decoding enabled in FM Chip.
+ * @FMD_SWITCH_ON_RDS_ENHANCED_MODE: Enhanced RDS Mode.
+ * @FMD_SWITCH_ON_RDS_SIMULATOR: RDS Simulator switched on in FM Chip.
+ *
+ * RDS Mode to be selected for FM Rx.
+ */
+enum fmd_rds_mode {
+ FMD_SWITCH_OFF_RDS,
+ FMD_SWITCH_ON_RDS,
+ FMD_SWITCH_ON_RDS_ENHANCED_MODE,
+ FMD_SWITCH_ON_RDS_SIMULATOR
+};
+
+/**
+ * enum fmd_rds_group_rejection_mode - RDS Group Rejection
+ * to be selected for FM Rx.
+ *
+ * @FMD_RDS_GROUP_REJECTION_ON: Group rejection is enabled in FM Chip.
+ * @FMD_RDS_GROUP_REJECTION_OFF: Group rejection is disabled in FM Chip.
+ *
+ * RDS Group rejection to be selected for FM Rx.
+ */
+enum fmd_rds_group_rejection_mode {
+ FMD_RDS_GROUP_REJECTION_ON,
+ FMD_RDS_GROUP_REJECTION_OFF
+};
+
+/**
+ * enum fmd_tst_tone_status - Test Tone Generator Status.
+ *
+ * @FMD_TST_TONE_OFF: Test Tone Generator is off.
+ * @FMD_TST_TONE_ON_W_SRC: Test Tone Gen. is on with Sample Rate Conversion.
+ * @FMD_TST_TONE_ON_WO_SRC: Test Tone Gen. is on without Sample Rate Conversion.
+ *
+ * Test Tone Generator status to be set.
+ */
+enum fmd_tst_tone_status {
+ FMD_TST_TONE_OFF,
+ FMD_TST_TONE_ON_W_SRC,
+ FMD_TST_TONE_ON_WO_SRC
+};
+
+/**
+ * enum fmd_tst_tone_audio_mode - Test Tone Generator Audio Output/Input Mode.
+ *
+ * @FMD_TST_TONE_AUDIO_NORMAL: Normal Audio.
+ * @FMD_TST_TONE_AUDIO_ZERO: Zero.
+ * @FMD_TST_TONE_AUDIO_TONE_1: Tone 1.
+ * @FMD_TST_TONE_AUDIO_TONE_2: Tone 2.
+ * @FMD_TST_TONE_AUDIO_TONE_SUM: Sum of Tone 1 and Tone 2.
+ *
+ * Test Tone Generator Audio Output/Input Modes.
+ */
+enum fmd_tst_tone_audio_mode {
+ FMD_TST_TONE_AUDIO_NORMAL,
+ FMD_TST_TONE_AUDIO_ZERO,
+ FMD_TST_TONE_AUDIO_TONE_1,
+ FMD_TST_TONE_AUDIO_TONE_2,
+ FMD_TST_TONE_AUDIO_TONE_SUM
+};
+
+/**
+ * enum fmd_tst_tone - Test Tone of Internal Tone Generator.
+ *
+ * @FMD_TST_TONE_1: Test Tone 1
+ * @FMD_TST_TONE_2: Test Tone 2
+ *
+ * Test Tone.
+ */
+enum fmd_tst_tone {
+ FMD_TST_TONE_1,
+ FMD_TST_TONE_2
+};
+
+/**
+ * enum fmd_tst_tone_waveform - Test Tone Waveform of Internal Tone Generator.
+ *
+ * @FMD_TST_TONE_SINE: Sine wave
+ * @FMD_TST_TONE_PULSE: Pulse wave
+ *
+ * Test Tone waveform.
+ */
+enum fmd_tst_tone_waveform {
+ FMD_TST_TONE_SINE,
+ FMD_TST_TONE_PULSE
+};
+
+/* Callback function to receive radio events. */
+typedef void(*fmd_radio_cb)(
+ u8 event,
+ bool event_successful
+ );
+
+/**
+ * fmd_init() - Initialize the FM Driver internal structures.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EIO, if there is an error.
+ */
+int fmd_init(void);
+
+/**
+ * fmd_exit() - De-initialize the FM Driver.
+ */
+void fmd_exit(void);
+
+/**
+ * fmd_register_callback() - Function to register callback function.
+ *
+ * This function registers the callback function provided by upper layers.
+ * @callback: Fmradio call back Function pointer
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_register_callback(
+ fmd_radio_cb callback
+ );
+
+/**
+ * fmd_get_version() - Retrieves the FM HW and FW version.
+ *
+ * @version: (out) Version Array
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_get_version(
+ u16 *version
+ );
+
+/**
+ * fmd_set_mode() - Starts a transition to the given mode.
+ *
+ * @mode: Transition mode
+ *
+ * Returns:
+ * 0, if set mode done successfully.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_mode(
+ u8 mode
+ );
+
+/**
+ * fmd_get_freq_range_properties() - Retrieves Freq Range Properties.
+ *
+ * @range: range of freq
+ * @min_freq: (out) Minimum Frequency of the Band in kHz.
+ * @max_freq: (out) Maximum Frequency of the Band in kHz
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ */
+int fmd_get_freq_range_properties(
+ u8 range,
+ u32 *min_freq,
+ u32 *max_freq
+ );
+
+/**
+ * fmd_set_antenna() - Selects the antenna to be used in receive mode.
+ *
+ * embedded - Selects the embedded antenna, wired- Selects the wired antenna.
+ * @antenna: Antenna Type
+ *
+ * Returns:
+ * 0, if set antenna done successfully.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_antenna(
+ u8 antenna
+ );
+
+/**
+ * fmd_get_antenna() - Retrieves the currently used antenna type.
+ *
+ * @antenna: (out) Antenna Selected on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_get_antenna(
+ u8 *antenna
+ );
+
+/**
+ * fmd_set_freq_range() - Sets the FM band.
+ *
+ * @range: freq range
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_freq_range(
+ u8 range
+ );
+
+/**
+ * fmd_get_freq_range() - Gets the FM band currently in use.
+ *
+ * @range: (out) Frequency Range set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_get_freq_range(
+ u8 *range
+ );
+
+/**
+ * fmd_rx_set_grid() - Sets the tuning grid.
+ *
+ * @grid: Tuning grid size
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_grid(
+ u8 grid
+ );
+
+/**
+ * fmd_rx_set_frequency() - Sets the FM Channel.
+ *
+ * @freq: Frequency to Set in Khz
+ *
+ * Returns:
+ * 0, if set frequency done successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_frequency(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_frequency() - Gets the currently used FM Channel.
+ *
+ * @freq: (out) Current Frequency set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * fmd_rx_set_stereo_mode() - Sets the stereomode functionality.
+ *
+ * @mode: FMD_STEREOMODE_MONO, FMD_STEREOMODE_STEREO and
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_stereo_mode(
+ u8 mode
+ );
+
+/**
+ * fmd_rx_set_stereo_ctrl_blending_rssi() - Sets the stereo blending control setting.
+ *
+ * @min_rssi: Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will stop limiting the channel separation
+ * @max_rssi: Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will start limiting the channel separation.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_stereo_ctrl_blending_rssi(
+ u16 min_rssi,
+ u16 max_rssi
+ );
+
+/**
+ * fmd_rx_get_stereo_mode() - Gets the currently used FM mode.
+ *
+ * FMD_STEREOMODE_MONO, FMD_STEREOMODE_STEREO and
+ * FMD_STEREOMODE_AUTO.
+ * @mode: (out) Mode set on FM Radio, stereo or mono.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_stereo_mode(
+ u8 *mode
+ );
+
+/**
+ * fmd_rx_get_signal_strength() - Gets the RSSI level of current frequency.
+ *
+ * @strength: (out) RSSI level of current channel.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_signal_strength(
+ u16 *strength
+ );
+
+/**
+ * fmd_rx_set_stop_level() - Sets the FM Rx Seek stop level.
+ *
+ * @stoplevel: seek stop level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_set_stop_level(
+ u16 stoplevel
+ );
+
+/**
+ * fmd_rx_get_stop_level() - Gets the current FM Rx Seek stop level.
+ *
+ * @stoplevel: (out) RSSI Threshold set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_stop_level(
+ u16 *stoplevel
+ );
+
+/**
+ * fmd_rx_seek() - Perform FM Seek.
+ *
+ * Starts searching relative to the actual channel with
+ * a specific direction, stop.
+ * level and optional noise levels
+ * @upwards: scan up
+ *
+ * Returns:
+ * 0, if seek started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_seek(
+ bool upwards
+ );
+
+/**
+ * fmd_rx_stop_seeking() - Stops a currently active seek or scan band.
+ *
+ * Returns:
+ * 0, if stop seek done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -ENOEXEC, if FM Driver is
+ * not currently in Seek or Scan State..
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_stop_seeking(void);
+
+/**
+ * fmd_rx_af_update_start() - Perform AF update.
+ *
+ * This is used to switch to a shortly tune to a AF freq,
+ * measure its RSSI and tune back to the original frequency.
+ * @freq: Alternative frequncy in KHz to be set for AF updation.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_af_update_start(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_af_update_result() - Retrive result of AF update.
+ *
+ * Retrive the RSSI level of the Alternative frequency.
+ * @af_level: RSSI level of the Alternative frequency.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_get_af_update_result(
+ u16 *af_level
+ );
+
+/**
+ * fmd_af_switch_start() -Performs AF switch.
+ *
+ * @freq: Frequency to Set in Khz.
+ * @picode:programable id,unique for each station.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error and if AF switch started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_af_switch_start(
+ u32 freq,
+ u16 picode
+ );
+
+/**
+ * fmd_rx_get_af_switch_results() -Retrieves the results of AF Switch.
+ *
+ * @afs_conclusion: Conclusion of AF switch.
+ * @afs_level: RSSI level of the Alternative frequnecy.
+ * @afs_pi: PI code of the alternative channel (if found).
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_get_af_switch_results(
+ u16 *afs_conclusion,
+ u16 *afs_level,
+ u16 *afs_pi
+ );
+
+/**
+ * fmd_rx_scan_band() - Starts Band Scan.
+ *
+ * Starts scanning the active band for the strongest
+ * channels above a threshold.
+ * @max_channels_to_scan: Maximum number of channels to scan.
+ *
+ * Returns:
+ * 0, if scan band started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_scan_band(
+ u8 max_channels_to_scan
+ );
+
+/**
+ * fmd_rx_get_max_channels_to_scan() - Retreives the maximum channels.
+ *
+ * Retrieves the maximum number of channels that can be found during
+ * band scann.
+ * @max_channels_to_scan: (out) Maximum number of channels to scan.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_max_channels_to_scan(
+ u8 *max_channels_to_scan
+ );
+
+/**
+ * fmd_rx_get_scan_band_info() - Retrieves Channels found during scanning.
+ *
+ * Retrieves the scanned active band
+ * for the strongest channels above a threshold.
+ * @index: (out) Index value to retrieve the channels.
+ * @numchannels: (out) Number of channels found during Band Scan.
+ * @channels: (out) Channels found during band scan.
+ * @rssi: (out) Rssi of channels found during Band scan.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_scan_band_info(
+ u32 index,
+ u16 *numchannels,
+ u16 *channels,
+ u16 *rssi
+ );
+
+/**
+ * fmd_block_scan() - Starts Block Scan.
+ *
+ * Starts block scan for retriving the RSSI level of channels
+ * in the given block.
+ * @start_freq: Starting frequency of the block from where scanning has
+ * to be started.
+ * @stop_freq: End frequency of the block to be scanned.
+ * @antenna: Antenna to be used during scanning.
+ *
+ * Returns:
+ * 0, if scan band started successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_block_scan(
+ u32 start_freq,
+ u32 stop_freq,
+ u8 antenna
+ );
+
+/**
+ * fmd_get_block_scan_result() - Retrieves RSSI Level of channels.
+ *
+ * Retrieves the RSSI level of the channels in the block.
+ * @index: (out) Index value to retrieve the channels.
+ * @numchannels: (out) Number of channels found during Band Scan.
+ * @rssi: (out) Rssi of channels found during Band scan.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_get_block_scan_result(
+ u32 index,
+ u16 *numchannels,
+ u16 *rssi
+ );
+
+/**
+ * fmd_rx_get_rds() - Gets the current status of RDS transmission.
+ *
+ * @on: (out) RDS status
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_rds(
+ bool *on
+ );
+
+/**
+ * fmd_rx_buffer_set_size() - Sets the number of groups that the data buffer.
+ * can contain and clears the buffer.
+ *
+ * @size: buffer size
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_buffer_set_size(
+ u8 size
+ );
+
+/**
+ * fmd_rx_buffer_set_threshold() - RDS Buffer Threshold level in FM Chip.
+ *
+ * Sets the group number at which the RDS buffer full interrupt must be
+ * generated. The interrupt will be set after reception of the group.
+ * @threshold: threshold level.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_buffer_set_threshold(
+ u8 threshold
+ );
+
+/**
+ * fmd_rx_set_rds() - Enables or disables demodulation of RDS data.
+ *
+ * @on_off_state : Rx Set ON/OFF control
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_rds(
+ u8 on_off_state
+ );
+
+/**
+ * fmd_rx_set_rds_group_rejection() - Enables or disables group rejection
+ * in case groups with erroneous blocks are received.
+ *
+ * @on_off_state : Rx Group Rejection ON /OFF control
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+
+int fmd_rx_set_rds_group_rejection(
+ u8 on_off_state
+ );
+
+/**
+ * fmd_rx_get_low_level_rds_groups() - Gets Low level RDS group data.
+ *
+ * @index: RDS group index
+ * @block1: (out) RDS Block 1
+ * @block2: (out) RDS Block 2
+ * @block3: (out) RDS Block 3
+ * @block4: (out) RDS Block 4
+ * @status1: (out) RDS data status 1
+ * @status2: (out) RDS data status 2
+ * @status3: (out) RDS data status 3
+ * @status4: (out) RDS data status 4
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_low_level_rds_groups(
+ u8 index,
+ u16 *block1,
+ u16 *block2,
+ u16 *block3,
+ u16 *block4,
+ u8 *status1,
+ u8 *status2,
+ u8 *status3,
+ u8 *status4
+ );
+
+/**
+ * fmd_tx_set_pa() - Enables or disables the Power Amplifier.
+ *
+ * @on: Power Amplifier current state to set
+ *
+ * Returns:
+ * 0, if set Power Amplifier done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_pa(
+ bool on
+ );
+
+/**
+ * fmd_tx_set_signal_strength() - Sets the RF-level of the output FM signal.
+ *
+ * @strength: Signal strength to be set for FM Tx in dBuV.
+ *
+ * Returns:
+ * 0, if set RSSI Level done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_signal_strength(
+ u16 strength
+ );
+
+/**
+ * fmd_tx_get_signal_strength() - Retrieves current RSSI of FM Tx.
+ *
+ * @strength: (out) Strength of signal being transmitted in dBuV.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_signal_strength(
+ u16 *strength
+ );
+
+/**
+ * fmd_tx_set_freq_range() - Sets the FM band and specifies the custom band.
+ *
+ * @range: Freq range to set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_freq_range(
+ u8 range
+ );
+
+/**
+ * fmd_tx_get_freq_range() - Gets the FM band currently in use.
+ *
+ * @range: (out) Frequency Range set on Fm Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ */
+int fmd_tx_get_freq_range(
+ u8 *range
+ );
+
+/**
+ * fmd_tx_set_grid() - Sets the tuning grid size.
+ *
+ * @grid: FM Grid (50 Khz, 100 Khz, 200 Khz) to be set for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_grid(
+ u8 grid
+ );
+
+/**
+ * fmd_tx_get_grid() - Gets the current tuning grid size.
+ *
+ * @grid: (out) FM Grid (50 Khz, 100 Khz, 200 Khz) currently set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_grid(
+ u8 *grid
+ );
+
+/**
+ * fmd_tx_set_preemphasis() - Sets the Preemphasis characteristic of the Tx.
+ *
+ * @preemphasis: Pre-emphasis level to be set for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_preemphasis(
+ u8 preemphasis
+ );
+
+/**
+ * fmd_tx_get_preemphasis() - Gets the currently used Preemphasis char of th FM Tx.
+ *
+ * @preemphasis: (out) Preemphasis Level used for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_preemphasis(
+ u8 *preemphasis
+ );
+
+/**
+ * fmd_tx_set_frequency() - Sets the FM Channel for Tx.
+ *
+ * @freq: Freq to be set for transmission.
+ *
+ * Returns:
+ * 0, if set frequency done successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_frequency(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_frequency() - Gets the currently used Channel for Tx.
+ *
+ * @freq: (out) Frequency set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * fmd_tx_enable_stereo_mode() - Sets Stereo mode state for TX.
+ *
+ * @enable_stereo_mode: Flag indicating enabling or disabling Stereo mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_enable_stereo_mode(
+ bool enable_stereo_mode
+ );
+
+/**
+ * fmd_tx_get_stereo_mode() - Gets the currently used FM Tx stereo mode.
+ *
+ * @stereo_mode: (out) Stereo Mode state set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_stereo_mode(
+ bool *stereo_mode
+ );
+
+/**
+ * fmd_tx_set_pilot_deviation() - Sets pilot deviation in HZ
+ *
+ * @deviation: Pilot deviation in HZ to set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_pilot_deviation(
+ u16 deviation
+ );
+
+/**
+ * fmd_tx_get_pilot_deviation() - Retrieves the current pilot deviation.
+ *
+ * @deviation: (out) Pilot deviation set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_pilot_deviation(
+ u16 *deviation
+ );
+
+/**
+ * fmd_tx_set_rds_deviation() - Sets Rds deviation in HZ.
+ *
+ * @deviation: RDS deviation in HZ.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_rds_deviation(
+ u16 deviation
+ );
+
+/**
+ * fmd_tx_get_rds_deviation() - Retrieves the current Rds deviation.
+ *
+ * @deviation: (out) RDS deviation currently set.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_rds_deviation(
+ u16 *deviation
+ );
+
+/**
+ * fmd_tx_set_rds() - Enables or disables RDS transmission for Tx.
+ *
+ * @on: Boolean - RDS ON
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_rds(
+ bool on
+ );
+
+/**
+ * fmd_rx_get_rds() - Gets the current status of RDS transmission for FM Tx.
+ *
+ * @on: (out) Rds enabled or disabled.
+ *
+ *Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_rds(
+ bool *on
+ );
+
+/**
+ * fmd_tx_set_group() - Programs a grp on a certain position in the RDS buffer.
+ *
+ * @position: RDS group position
+ * @block1: Data to be transmitted in Block 1
+ * @block2: Data to be transmitted in Block 2
+ * @block3: Data to be transmitted in Block 3
+ * @block4: Data to be transmitted in Block 4
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if parameters are invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_group(
+ u16 position,
+ u8 *block1,
+ u8 *block2,
+ u8 *block3,
+ u8 *block4
+ );
+
+/**
+ * fmd_tx_buffer_set_size() - Controls the size of the RDS buffer in groups.
+ *
+ * @buffer_size: RDS buffer size.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_buffer_set_size(
+ u16 buffer_size
+ );
+
+/**
+ * fmd_set_volume() - Sets the receive audio volume.
+ *
+ * @volume: Audio volume level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_volume(
+ u8 volume
+ );
+
+/**
+ * fmd_get_volume() - Retrives the current audio volume.
+ *
+ * @volume: Analog Volume level.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_get_volume(
+ u8 *volume
+ );
+
+/**
+ * fmd_set_balance() - Controls the receiver audio balance.
+ *
+ * @balance: Audio balance level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_balance(
+ s8 balance
+ );
+
+/**
+ * fmd_set_mute() - Enables or disables muting of the analog audio(DAC).
+ *
+ * @mute_on: bool of mute on
+ *
+ * Returns:
+ * 0, if mute done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_mute(
+ bool mute_on
+ );
+
+/**
+ * fmd_ext_set_mute() - Enables or disables muting of the audio channel.
+ *
+ * @mute_on: bool to Mute
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_ext_set_mute(
+ bool mute_on
+ );
+
+/**
+ * fmd_power_up() - Puts the system in Powerup state.
+ *
+ * Returns:
+ * 0, if power up command sent successfully to chip.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_power_up(void);
+
+/**
+ * fmd_goto_standby() - Puts the system in standby mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_goto_standby(void);
+
+/**
+ * fmd_goto_power_down() - Puts the system in Powerdown mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_goto_power_down(void);
+
+/**
+ * fmd_select_ref_clk() - Selects the FM reference clock.
+ *
+ * @ref_clk: Ref Clock.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_select_ref_clk(
+ u16 ref_clk
+ );
+
+/**
+ * fmd_set_ref_clk_pll() - Sets the freq of Referece Clock.
+ *
+ * Sets frequency and offset correction properties of the external
+ * reference clock of the PLL
+ * @freq: PLL Frequency/ 2 in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_ref_clk_pll(
+ u16 freq
+ );
+
+/**
+ * fmd_send_fm_ip_enable()- Enables the FM IP.
+ *
+ * Returns:
+ * 0: If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_ip_enable(void);
+
+/**
+ * fmd_send_fm_ip_disable()- Disables the FM IP.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_ip_disable(void);
+
+/**
+ * fmd_send_fm_firmware() - Send the FM Firmware File to Device.
+ *
+ * @fw_buffer: Firmware to be downloaded.
+ * @fw_size: Size of firmware to be downloaded.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_firmware(
+ u8 *fw_buffer,
+ u16 fw_size
+ );
+
+/**
+ * fmd_int_bufferfull() - RDS Groups availabe for reading by Host.
+ *
+ * Gets the number of groups that are available in the
+ * buffer. This function is called in RX mode to read RDS groups.
+ * @number_of_rds_groups: Number of RDS groups ready to
+ * be read from the Host.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * corresponding error Otherwise
+ */
+int fmd_int_bufferfull(
+ u16 *number_of_rds_groups
+ );
+
+/**
+ * fmd_start_rds_thread() - Starts the RDS Thread for receiving RDS Data.
+ *
+ * This is started by Application when it wants to receive RDS Data.
+ * @cb_func: Callback function for receiving RDS Data
+ */
+void fmd_start_rds_thread(
+ cg2900_fm_rds_cb cb_func
+ );
+/**
+ * fmd_stop_rds_thread() - Stops the RDS Thread when Application does not
+ * want to receive RDS.
+ */
+void fmd_stop_rds_thread(void);
+
+/**
+ * fmd_get_rds_sem() - Block on RDS Semaphore.
+ * Till irpt_BufferFull is received, RDS Task is blocked.
+ */
+void fmd_get_rds_sem(void);
+
+/**
+ * fmd_set_rds_sem() - Unblock on RDS Semaphore.
+ * on receiving irpt_BufferFull, RDS Task is un-blocked.
+ */
+void fmd_set_rds_sem(void);
+
+/**
+ * fmd_set_dev() - Set FM device.
+ *
+ * @dev: FM Device
+ *
+ * Returns:
+ * 0, If there is no error.
+ * corresponding error Otherwise
+ */
+int fmd_set_dev(
+ struct device *dev
+ );
+
+/**
+ * fmd_set_test_tone_generator_status()- Sets the Test Tone Generator.
+ *
+ * This function is used to enable/disable the Internal Tone Generator of
+ * CG2900.
+ * @test_tone_status: Status of tone generator.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_set_test_tone_generator_status(
+ u8 test_tone_status
+ );
+
+/**
+ * fmd_test_tone_connect()- Connect Audio outputs/inputs.
+ *
+ * This function connects the audio outputs/inputs of the
+ * Internal Tone Generator of CG2900.
+ * @left_audio_mode: Left Audio Output Mode.
+ * @right_audio_mode: Right Audio Output Mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ );
+
+/**
+ * fmd_test_tone_set_params()- Sets the Test Tone Parameters.
+ *
+ * This function is used to set the parameters of
+ * the Internal Tone Generator of CG2900.
+ * @tone_gen: Tone to be configured (Tone 1 or Tone 2)
+ * @frequency: Frequency of the tone.
+ * @volume: Volume of the tone.
+ * @phase_offset: Phase offset of the tone.
+ * @dc: DC to add to tone.
+ * @waveform: Waveform to generate, sine or pulse.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ );
+
+/**
+ * fmd_rx_set_deemphasis()- Connect Audio outputs/inputs.
+ *
+ * This function sets the de-emphasis filter to the
+ * specified de-empahsis level.
+ * @deemphasis: De-emphasis level to set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_rx_set_deemphasis(
+ u8 deemphasis
+ );
+
+/**
+ * fmd_limiter_setcontrol()- Sets the Limiter Controls.
+ *
+ * This function sets the limiter control.
+ * @audio_deviation: Limiting level of Audio Deviation.
+ * @notification_hold_off_time: Minimum time between
+ * two limiting interrupts.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_limiter_setcontrol(
+ u16 audio_deviation,
+ u16 notification_hold_off_time
+ );
+
+#endif /* _FMDRIVER_H_ */
diff --git a/drivers/media/radio/CG2900/radio-cg2900.c b/drivers/media/radio/CG2900/radio-cg2900.c
new file mode 100644
index 00000000000..9ccb4e6b85d
--- /dev/null
+++ b/drivers/media/radio/CG2900/radio-cg2900.c
@@ -0,0 +1,3024 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux Wrapper for V4l2 FM Driver for CG2900.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include<linux/init.h>
+#include<linux/videodev2.h>
+#include<media/v4l2-ioctl.h>
+#include<media/v4l2-common.h>
+#include<linux/module.h>
+#include <linux/platform_device.h>
+#include<linux/string.h>
+#include<linux/wait.h>
+#include"cg2900.h"
+#include"cg2900_fm_driver.h"
+
+#define RADIO_CG2900_VERSION KERNEL_VERSION(1, 1, 0)
+#define BANNER "ST-Ericsson FM Radio Card driver v1.1.0"
+
+#define FMR_HZ_TO_MHZ_CONVERTER 1000000
+#define FMR_EU_US_LOW_FREQ_IN_MHZ 87.5
+#define FMR_EU_US_HIGH_FREQ_IN_MHZ 108
+#define FMR_JAPAN_LOW_FREQ_IN_MHZ 76
+#define FMR_JAPAN_HIGH_FREQ_IN_MHZ 90
+#define FMR_CHINA_LOW_FREQ_IN_MHZ 70
+#define FMR_CHINA_HIGH_FREQ_IN_MHZ 108
+#define FMR_MAX_BLOCK_SCAN_CHANNELS 198
+#define FMR_CHINA_GRID_IN_HZ 50000
+#define FMR_EUROPE_GRID_IN_HZ 100000
+#define FMR_USA_GRID_IN_HZ 200000
+#define FMR_AF_SWITCH_DATA_SIZE 2
+#define FMR_BLOCK_SCAN_DATA_SIZE 2
+#define FMR_GET_INTERRUPT_DATA_SIZE 2
+#define FMR_TEST_TONE_CONNECT_DATA_SIZE 2
+#define FMR_TEST_TONE_SET_PARAMS_DATA_SIZE 6
+
+/* freq in Hz to V4l2 freq (units of 62.5Hz) */
+#define HZ_TO_V4L2(X) (2*(X)/125)
+/* V4l2 freq (units of 62.5Hz) to freq in Hz */
+#define V4L2_TO_HZ(X) (((X)*125)/(2))
+
+static int cg2900_open(
+ struct file *file
+ );
+static int cg2900_release(
+ struct file *file
+ );
+static ssize_t cg2900_read(
+ struct file *file,
+ char __user *data,
+ size_t count,
+ loff_t *pos
+ );
+static unsigned int cg2900_poll(
+ struct file *file,
+ struct poll_table_struct *wait
+ );
+static int vidioc_querycap(
+ struct file *file,
+ void *priv,
+ struct v4l2_capability *query_caps
+ );
+static int vidioc_get_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ );
+static int vidioc_set_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ );
+static int vidioc_get_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ );
+static int vidioc_set_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ );
+static int vidioc_get_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ );
+static int vidioc_set_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ );
+static int vidioc_query_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_queryctrl *query_ctrl
+ );
+static int vidioc_get_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ );
+static int vidioc_set_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ );
+static int vidioc_get_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ );
+static int vidioc_set_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ );
+static int vidioc_set_hw_freq_seek(
+ struct file *file,
+ void *priv,
+ struct v4l2_hw_freq_seek *freq_seek
+ );
+static int vidioc_get_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ );
+static int vidioc_set_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ );
+static int vidioc_get_input(
+ struct file *filp,
+ void *priv,
+ unsigned int *input
+ );
+static int vidioc_set_input(
+ struct file *filp,
+ void *priv,
+ unsigned int input
+ );
+static void cg2900_convert_err_to_v4l2(
+ char status_byte,
+ char *out_byte
+ );
+static int cg2900_map_event_to_v4l2(
+ u8 fm_event
+ );
+
+static u32 freq_low;
+static u32 freq_high;
+
+/* Module Parameters */
+static int radio_nr = -1;
+static int grid;
+static int band;
+
+/* cg2900_poll_queue - Main Wait Queue for polling (Scan/Seek) */
+static wait_queue_head_t cg2900_poll_queue;
+
+struct sk_buff_head fm_interrupt_queue;
+
+/**
+ * enum fm_seek_status - Seek status of FM Radio.
+ *
+ * @FMR_SEEK_NONE: No seek in progress.
+ * @FMR_SEEK_IN_PROGRESS: Seek is in progress.
+ *
+ * Seek status of FM Radio.
+ */
+enum fm_seek_status {
+ FMR_SEEK_NONE,
+ FMR_SEEK_IN_PROGRESS
+};
+
+/**
+ * enum fm_power_state - Power states of FM Radio.
+ *
+ * @FMR_SWITCH_OFF: FM Radio is switched off.
+ * @FMR_SWITCH_ON: FM Radio is switched on.
+ * @FMR_STANDBY: FM Radio in standby state.
+ *
+ * Power states of FM Radio.
+ */
+enum fm_power_state {
+ FMR_SWITCH_OFF,
+ FMR_SWITCH_ON,
+ FMR_STANDBY
+};
+
+/**
+ * struct cg2900_device - Stores FM Device Info.
+ *
+ * @state: state of FM Radio
+ * @muted: FM Radio Mute/Unmute status
+ * @seekstatus: seek status
+ * @rx_rds_enabled: Rds enable/disable status for FM Rx
+ * @tx_rds_enabled: Rds enable/disable status for FM Tx
+ * @rx_stereo_status: Stereo Mode status for FM Rx
+ * @tx_stereo_status: Stereo Mode status for FM Tx
+ * @volume: Analog Volume Gain of FM Radio
+ * @rssi_threshold: rssi Thresold set on FM Radio
+ * @frequency: Frequency tuned on FM Radio in V4L2 Format
+ * @audiopath: Audio Balance
+ * @wait_on_read_queue: Flag for waiting on read queue.
+ * @fm_mode: Enum for storing the current FM Mode.
+ *
+ * FM Driver Information Structure.
+ */
+struct cg2900_device {
+ u8 state;
+ u8 muted;
+ u8 seekstatus;
+ bool rx_rds_enabled;
+ bool tx_rds_enabled;
+ bool rx_stereo_status;
+ bool tx_stereo_status;
+ int volume;
+ u16 rssi_threshold;
+ u32 frequency;
+ u32 audiopath;
+ bool wait_on_read_queue;
+ enum cg2900_fm_mode fm_mode;
+};
+
+/* Global Structure to store the maintain FM Driver device info */
+static struct cg2900_device cg2900_device;
+
+/* V4l2 File Operation Structure */
+static const struct v4l2_file_operations cg2900_fops = {
+ .owner = THIS_MODULE,
+ .open = cg2900_open,
+ .release = cg2900_release,
+ .read = cg2900_read,
+ .poll = cg2900_poll,
+ .ioctl = video_ioctl2,
+};
+
+/* V4L2 IOCTL Operation Structure */
+static const struct v4l2_ioctl_ops cg2900_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_get_tuner,
+ .vidioc_s_tuner = vidioc_set_tuner,
+ .vidioc_g_modulator = vidioc_get_modulator,
+ .vidioc_s_modulator = vidioc_set_modulator,
+ .vidioc_g_frequency = vidioc_get_frequency,
+ .vidioc_s_frequency = vidioc_set_frequency,
+ .vidioc_queryctrl = vidioc_query_ctrl,
+ .vidioc_g_ctrl = vidioc_get_ctrl,
+ .vidioc_s_ctrl = vidioc_set_ctrl,
+ .vidioc_g_ext_ctrls = vidioc_get_ext_ctrls,
+ .vidioc_s_ext_ctrls = vidioc_set_ext_ctrls,
+ .vidioc_s_hw_freq_seek = vidioc_set_hw_freq_seek,
+ .vidioc_g_audio = vidioc_get_audio,
+ .vidioc_s_audio = vidioc_set_audio,
+ .vidioc_g_input = vidioc_get_input,
+ .vidioc_s_input = vidioc_set_input,
+};
+
+/* V4L2 Video Device Structure */
+static struct video_device cg2900_video_device = {
+ .name = "STE CG2900 FM Rx/Tx Radio",
+ .fops = &cg2900_fops,
+ .ioctl_ops = &cg2900_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+static u16 no_of_scan_freq;
+static u16 no_of_block_scan_freq;
+static u32 scanfreq_rssi_level[MAX_CHANNELS_TO_SCAN];
+static u16 block_scan_rssi_level[MAX_CHANNELS_FOR_BLOCK_SCAN];
+static u32 scanfreq[MAX_CHANNELS_TO_SCAN];
+static struct mutex fm_mutex;
+static spinlock_t fm_spinlock;
+static int users;
+
+/**
+ * vidioc_querycap()- Query FM Driver Capabilities.
+ *
+ * This function is used to query the capabilities of the
+ * FM Driver. This function is called when the application issues the IOCTL
+ * VIDIOC_QUERYCAP.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @query_caps: v4l2_capability structure.
+ *
+ * Returns: 0
+ */
+static int vidioc_querycap(
+ struct file *file,
+ void *priv,
+ struct v4l2_capability *query_caps
+ )
+{
+ FM_INFO_REPORT("vidioc_querycap");
+ memset(
+ query_caps,
+ 0,
+ sizeof(*query_caps)
+ );
+ strlcpy(
+ query_caps->driver,
+ "CG2900 Driver",
+ sizeof(query_caps->driver)
+ );
+ strlcpy(
+ query_caps->card,
+ "CG2900 FM Radio",
+ sizeof(query_caps->card)
+ );
+ strcpy(
+ query_caps->bus_info,
+ "platform"
+ );
+ query_caps->version = RADIO_CG2900_VERSION;
+ query_caps->capabilities =
+ V4L2_CAP_TUNER |
+ V4L2_CAP_MODULATOR |
+ V4L2_CAP_RADIO |
+ V4L2_CAP_READWRITE |
+ V4L2_CAP_RDS_CAPTURE |
+ V4L2_CAP_HW_FREQ_SEEK |
+ V4L2_CAP_RDS_OUTPUT;
+ FM_DEBUG_REPORT("vidioc_querycap returning 0");
+ return 0;
+}
+
+/**
+ * vidioc_get_tuner()- Get FM Tuner Features.
+ *
+ * This function is used to get the tuner features.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_G_TUNER
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @tuner: v4l2_tuner structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ )
+{
+ int status = 0;
+ u8 mode;
+ bool rds_enabled;
+ u16 rssi;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_tuner");
+
+ if (tuner->index > 0) {
+ FM_ERR_REPORT("vidioc_get_tuner: Only 1 tuner supported");
+ goto error;
+ }
+
+ memset(tuner, 0, sizeof(*tuner));
+ strcpy(tuner->name, "CG2900 FM Receiver");
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->rangelow = HZ_TO_V4L2(freq_low);
+ tuner->rangehigh = HZ_TO_V4L2(freq_high);
+ tuner->capability =
+ V4L2_TUNER_CAP_LOW /* Frequency steps = 1/16 kHz */
+ | V4L2_TUNER_CAP_STEREO /* Can receive stereo */
+ | V4L2_TUNER_CAP_RDS; /* Supports RDS Capture */
+
+ if (cg2900_device.fm_mode == CG2900_FM_RX_MODE) {
+
+ status = cg2900_fm_get_mode(&mode);
+
+ FM_DEBUG_REPORT("vidioc_get_tuner: mode = %x, ", mode);
+
+ if (0 != status) {
+ /* Get mode API failed, set mode to mono */
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ goto error;
+ }
+
+ switch (mode) {
+ case CG2900_MODE_STEREO:
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
+ break;
+ case CG2900_MODE_MONO:
+ default:
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ break;
+ }
+
+ status = cg2900_fm_get_rds_status(&rds_enabled);
+
+ if (0 != status) {
+ tuner->rxsubchans &= ~V4L2_TUNER_SUB_RDS;
+ goto error;
+ }
+
+ if (rds_enabled)
+ tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ tuner->rxsubchans &= ~V4L2_TUNER_SUB_RDS;
+ } else {
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ }
+
+ if (cg2900_device.fm_mode == CG2900_FM_RX_MODE) {
+ status = cg2900_fm_get_signal_strength(&rssi);
+
+ if (0 != status) {
+ tuner->signal = 0;
+ goto error;
+ }
+ tuner->signal = rssi;
+ } else {
+ tuner->signal = 0;
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_tuner: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_tuner()- Set FM Tuner Features.
+ *
+ * This function is used to set the tuner features.
+ * It also sets the default FM Rx settings.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_S_TUNER
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @tuner: v4l2_tuner structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ )
+{
+ bool rds_status = false;
+ bool stereo_status = false;
+ int status = 0;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_tuner");
+ if (tuner->index != 0) {
+ FM_ERR_REPORT("vidioc_set_tuner: Only 1 tuner supported");
+ goto error;
+ }
+
+ if (cg2900_device.fm_mode != CG2900_FM_RX_MODE) {
+ /*
+ * FM Rx mode should be configured
+ * as earlier mode was not FM Rx
+ */
+ if (CG2900_FM_BAND_US_EU == band) {
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (CG2900_FM_BAND_JAPAN == band) {
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (CG2900_FM_BAND_CHINA == band) {
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+ cg2900_device.fm_mode = CG2900_FM_RX_MODE;
+ cg2900_device.rx_rds_enabled =
+ (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (tuner->rxsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (tuner->rxsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ cg2900_device.rx_stereo_status = stereo_status;
+ status = cg2900_fm_set_rx_default_settings(freq_low,
+ band,
+ grid,
+ cg2900_device.rx_rds_enabled,
+ cg2900_device.rx_stereo_status);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_rx_default_settings returned "
+ " %d", status);
+ goto error;
+ }
+ status = cg2900_fm_set_rssi_threshold(
+ cg2900_device.rssi_threshold);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_rssi_threshold returned "
+ " %d", status);
+ goto error;
+ }
+ } else {
+ /*
+ * Mode was FM Rx only, change the RDS settings or stereo mode
+ * if they are changed by application
+ */
+ rds_status = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (tuner->rxsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (tuner->rxsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ if (stereo_status != cg2900_device.rx_stereo_status) {
+ cg2900_device.rx_stereo_status = stereo_status;
+ if (stereo_status)
+ status =
+ cg2900_fm_set_mode(
+ FMD_STEREOMODE_BLENDING);
+ else
+ status = cg2900_fm_set_mode(
+ FMD_STEREOMODE_MONO);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_mode returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ if (rds_status != cg2900_device.rx_rds_enabled) {
+ cg2900_device.rx_rds_enabled = rds_status;
+ if (rds_status)
+ status = cg2900_fm_rds_on();
+ else
+ status = cg2900_fm_rds_off();
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_rds returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_tuner: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_modulator()- Get FM Modulator Features.
+ *
+ * This function is used to get the modulator features.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_G_MODULATOR
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @modulator: v4l2_modulator structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ )
+{
+ int status = 0;
+ bool rds_enabled;
+ u8 mode;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_modulator");
+
+ if (modulator->index > 0) {
+ FM_ERR_REPORT("vidioc_get_modulator: Only 1 "
+ "modulator supported");
+ goto error;
+ }
+
+ memset(modulator, 0, sizeof(*modulator));
+ strcpy(modulator->name, "CG2900 FM Transmitter");
+ modulator->rangelow = freq_low;
+ modulator->rangehigh = freq_high;
+ modulator->capability = V4L2_TUNER_CAP_NORM /* Freq steps = 1/16 kHz */
+ | V4L2_TUNER_CAP_STEREO /* Can receive stereo */
+ | V4L2_TUNER_CAP_RDS; /* Supports RDS Capture */
+
+ if (cg2900_device.fm_mode == CG2900_FM_TX_MODE) {
+ status = cg2900_fm_get_mode(&mode);
+ FM_DEBUG_REPORT("vidioc_get_modulator: mode = %x", mode);
+ if (0 != status) {
+ /* Get mode API failed, set mode to mono */
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+ goto error;
+ }
+ switch (mode) {
+ /* Stereo */
+ case CG2900_MODE_STEREO:
+ modulator->txsubchans = V4L2_TUNER_SUB_STEREO;
+ break;
+ /* Mono */
+ case CG2900_MODE_MONO:
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+ break;
+ /* Switching or Blending, set mode as Stereo */
+ default:
+ modulator->txsubchans = V4L2_TUNER_SUB_STEREO;
+ }
+ status = cg2900_fm_get_rds_status(&rds_enabled);
+ if (0 != status) {
+ modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+ goto error;
+ }
+ if (rds_enabled)
+ modulator->txsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+ } else
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_modulator: returning %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_modulator()- Set FM Modulator Features.
+ *
+ * This function is used to set the Modulaotr features.
+ * It also sets the default FM Tx settings.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_S_MODULATOR
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @modulator: v4l2_modulator structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ )
+{
+ bool rds_status = false;
+ bool stereo_status = false;
+ int status = 0;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_modulator");
+ if (modulator->index != 0) {
+ FM_ERR_REPORT("vidioc_set_modulator: Only 1 "
+ "modulator supported");
+ goto error;
+ }
+
+ if (cg2900_device.fm_mode != CG2900_FM_TX_MODE) {
+ /*
+ * FM Tx mode should be configured as
+ * earlier mode was not FM Tx
+ */
+ if (band == CG2900_FM_BAND_US_EU) {
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (band == CG2900_FM_BAND_JAPAN) {
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (band == CG2900_FM_BAND_CHINA) {
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+ cg2900_device.fm_mode = CG2900_FM_TX_MODE;
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.tx_rds_enabled =
+ (modulator->txsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (modulator->txsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (modulator->txsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ cg2900_device.tx_stereo_status = stereo_status;
+
+ status = cg2900_fm_set_tx_default_settings(freq_low,
+ band,
+ grid,
+ cg2900_device.tx_rds_enabled,
+ cg2900_device.
+ tx_stereo_status);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_set_tx_default_settings returned "
+ " %d", status);
+ goto error;
+ }
+ } else {
+ /*
+ * Mode was FM Tx only, change the RDS settings or stereo mode
+ * if they are changed by application
+ */
+ rds_status = (modulator->txsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (modulator->txsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (modulator->txsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ if (stereo_status != cg2900_device.tx_stereo_status) {
+ cg2900_device.tx_stereo_status = stereo_status;
+ status = cg2900_fm_set_mode(stereo_status);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_set_mode returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ if (rds_status != cg2900_device.tx_rds_enabled) {
+ cg2900_device.tx_rds_enabled = rds_status;
+ status = cg2900_fm_tx_rds(rds_status);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_tx_rds returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_modulator: returning %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_frequency()- Get the Current FM Frequnecy.
+ *
+ * This function is used to get the currently tuned
+ * frequency on FM Radio. This function is called when the application
+ * issues the IOCTL VIDIOC_G_FREQUENCY
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq: v4l2_frequency structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ )
+{
+ int status;
+ u32 frequency;
+ int ret_val = -EINVAL;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("vidioc_get_frequency: Status = %d",
+ cg2900_device.seekstatus);
+
+ status = cg2900_fm_get_frequency(&frequency);
+
+ if (0 != status) {
+ freq->frequency = cg2900_device.frequency;
+ goto error;
+ }
+
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ if (skb_queue_empty(&fm_interrupt_queue)) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("vidioc_get_frequency: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ goto error;
+ }
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("vidioc_get_frequency: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ goto error;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT("vidioc_get_frequency: Interrupt = %x",
+ fm_event);
+ /* Check if seek is finished or not */
+ if (CG2900_EVENT_SEARCH_CHANNEL_FOUND == fm_event) {
+ /* seek is finished */
+ spin_lock(&fm_spinlock);
+ cg2900_device.frequency = HZ_TO_V4L2(frequency);
+ freq->frequency = cg2900_device.frequency;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ spin_unlock(&fm_spinlock);
+ } else {
+ /* Some other interrupt, queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ } else {
+ spin_lock(&fm_spinlock);
+ cg2900_device.frequency = HZ_TO_V4L2(frequency);
+ freq->frequency = cg2900_device.frequency;
+ spin_unlock(&fm_spinlock);
+ }
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_frequency: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_frequency()- Set the FM Frequnecy.
+ *
+ * This function is used to set the frequency
+ * on FM Radio. This function is called when the application
+ * issues the IOCTL VIDIOC_S_FREQUENCY
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq: v4l2_frequency structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ )
+{
+ u32 frequency = freq->frequency;
+ u32 freq_low, freq_high;
+ int status;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_frequency: Frequency = "
+ "%d ", V4L2_TO_HZ(frequency));
+
+ /* Check which band is set currently */
+ switch (band) {
+ case CG2900_FM_BAND_US_EU:
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ case CG2900_FM_BAND_CHINA:
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ case CG2900_FM_BAND_JAPAN:
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ default:
+ /* Set to US_MAX and CHINA_MIN band */
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+
+ /* Check if the frequency set is out of current band */
+ if ((V4L2_TO_HZ(frequency) < freq_low) ||
+ (V4L2_TO_HZ(frequency) > freq_high))
+ goto error;
+
+ spin_lock(&fm_spinlock);
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ cg2900_device.frequency = frequency;
+ status = cg2900_fm_set_frequency(V4L2_TO_HZ(frequency));
+
+ if (0 != status)
+ goto error;
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_frequency: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_query_ctrl()- Query the FM Driver control features.
+ *
+ * This function is used to query the control features on FM Radio.
+ * This function is called when the application
+ * issues the IOCTL VIDIOC_QUERYCTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @query_ctrl: v4l2_queryctrl structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_query_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_queryctrl *query_ctrl
+ )
+{
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_query_ctrl");
+ /* Check which control is requested */
+ switch (query_ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_MUTE");
+ query_ctrl->type = V4L2_CTRL_TYPE_BOOLEAN;
+ query_ctrl->minimum = 0;
+ query_ctrl->maximum = 1;
+ query_ctrl->step = 1;
+ query_ctrl->default_value = 0;
+ query_ctrl->flags = 0;
+ strncpy(query_ctrl->name, "CG2900 Mute", 32);
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_VOLUME:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_VOLUME");
+
+ strncpy(query_ctrl->name, "CG2900 Volume", 32);
+ query_ctrl->minimum = MIN_ANALOG_VOLUME;
+ query_ctrl->maximum = MAX_ANALOG_VOLUME;
+ query_ctrl->step = 1;
+ query_ctrl->default_value = MAX_ANALOG_VOLUME;
+ query_ctrl->flags = 0;
+ query_ctrl->type = V4L2_CTRL_TYPE_INTEGER;
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_BALANCE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_BALANCE ");
+ strncpy(query_ctrl->name, "CG2900 Audio Balance", 32);
+ query_ctrl->type = V4L2_CTRL_TYPE_INTEGER;
+ query_ctrl->minimum = 0x0000;
+ query_ctrl->maximum = 0xFFFF;
+ query_ctrl->step = 0x0001;
+ query_ctrl->default_value = 0x0000;
+ query_ctrl->flags = 0;
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_BASS:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "V4L2_CID_AUDIO_BASS (unsupported)");
+ break;
+
+ case V4L2_CID_AUDIO_TREBLE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "V4L2_CID_AUDIO_TREBLE (unsupported)");
+ break;
+
+ default:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "--> unsupported id = %x", query_ctrl->id);
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_query_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_ctrl()- Get the value of a particular Control.
+ *
+ * This function is used to get the value of a
+ * particular control from the FM Driver. This function is called
+ * when the application issues the IOCTL VIDIOC_G_CTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ctrl: v4l2_control structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ )
+{
+ int status;
+ u8 value;
+ u16 rssi;
+ u8 antenna;
+ u16 conclusion;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_ctrl");
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ status = cg2900_fm_get_volume(&value);
+ if (0 == status) {
+ ctrl->value = value;
+ cg2900_device.volume = value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = cg2900_device.muted;
+ ret_val = 0;
+ break;
+ case V4L2_CID_AUDIO_BALANCE:
+ ctrl->value = cg2900_device.audiopath;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD:
+ ctrl->value = cg2900_device.rssi_threshold;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_SELECT_ANTENNA:
+ status = cg2900_fm_get_antenna(&antenna);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: Antenna = %x", antenna);
+ if (0 == status) {
+ ctrl->value = antenna;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT:
+ status = cg2900_fm_af_update_get_result(&rssi);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: AF RSSI Level = %x", rssi);
+ if (0 == status) {
+ ctrl->value = rssi;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT:
+ status = cg2900_fm_af_switch_get_result(&conclusion);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: AF Switch conclusion = %x",
+ conclusion);
+ if (0 != status)
+ break;
+ if (conclusion == 0) {
+ ctrl->value = conclusion;
+ FM_DEBUG_REPORT("vidioc_get_ctrl: "
+ "AF Switch conclusion = %d",
+ ctrl->value);
+ ret_val = 0;
+ } else {
+ /*
+ * Convert positive error code returned by chip
+ * into negative error codes to be in line with linux.
+ */
+ ctrl->value = -conclusion;
+ FM_ERR_REPORT("vidioc_get_ctrl: "
+ "AF-Switch failed with value %d", ctrl->value);
+ ret_val = 0;
+ }
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_get_ctrl: "
+ "unsupported (id = %x)", (int)ctrl->id);
+ ret_val = -EINVAL;
+ }
+ FM_DEBUG_REPORT("vidioc_get_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_ctrl()- Set the value of a particular Control.
+ *
+ * This function is used to set the value of a
+ * particular control from the FM Driver. This function is called when the
+ * application issues the IOCTL VIDIOC_S_CTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ctrl: v4l2_control structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ERANGE when the parameter is out of range.
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+ FM_INFO_REPORT("vidioc_set_ctrl");
+ /* Check which control is requested */
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_MUTE, "
+ "value = %d", ctrl->value);
+ if (ctrl->value > 1 && ctrl->value < 0) {
+ ret_val = -ERANGE;
+ break;
+ }
+
+ if (ctrl->value) {
+ FM_DEBUG_REPORT("vidioc_set_ctrl: Ctrl_Id = "
+ "V4L2_CID_AUDIO_MUTE, "
+ "Muting the Radio");
+ status = cg2900_fm_mute();
+ } else {
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "Ctrl_Id = V4L2_CID_AUDIO_MUTE, "
+ "UnMuting the Radio");
+ status = cg2900_fm_unmute();
+ }
+ if (0 == status) {
+ cg2900_device.muted = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_VOLUME, "
+ "value = %d", ctrl->value);
+ if (ctrl->value > MAX_ANALOG_VOLUME &&
+ ctrl->value < MIN_ANALOG_VOLUME) {
+ ret_val = -ERANGE;
+ break;
+ }
+ status = cg2900_fm_set_volume(ctrl->value);
+ if (0 == status) {
+ cg2900_device.volume = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_BALANCE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_BALANCE, "
+ "value = %d", ctrl->value);
+ status = cg2900_fm_set_audio_balance(ctrl->value);
+ if (0 == status) {
+ cg2900_device.audiopath = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_CHIP_STATE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_CHIP_STATE, "
+ "value = %d", ctrl->value);
+ if (V4L2_CG2900_RADIO_STANDBY == ctrl->value)
+ status = cg2900_fm_standby();
+ else if (V4L2_CG2900_RADIO_POWERUP == ctrl->value)
+ status = cg2900_fm_power_up_from_standby();
+ else
+ break;
+ if (0 != status)
+ break;
+ if (V4L2_CG2900_RADIO_STANDBY == ctrl->value)
+ cg2900_device.state = FMR_STANDBY;
+ else if (V4L2_CG2900_RADIO_POWERUP == ctrl->value)
+ cg2900_device.state = FMR_SWITCH_ON;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_SELECT_ANTENNA:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_SELECT_ANTENNA, "
+ "value = %d", ctrl->value);
+ status = cg2900_fm_select_antenna(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_BANDSCAN:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_BANDSCAN, "
+ "value = %d", ctrl->value);
+ if (V4L2_CG2900_RADIO_BANDSCAN_START == ctrl->value) {
+ cg2900_device.seekstatus = FMR_SEEK_IN_PROGRESS;
+ no_of_scan_freq = 0;
+ status = cg2900_fm_start_band_scan();
+ } else if (V4L2_CG2900_RADIO_BANDSCAN_STOP == ctrl->value) {
+ status = cg2900_fm_stop_scan();
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ } else
+ break;
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD "
+ "= %d", ctrl->value);
+ status = cg2900_fm_set_rssi_threshold(ctrl->value);
+ if (0 == status) {
+ cg2900_device.rssi_threshold = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START "
+ "freq = %d Hz", ctrl->value);
+ status = cg2900_fm_af_update_start(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS "
+ "state = %d ", ctrl->value);
+ if (ctrl->value < V4L2_CG2900_RADIO_TEST_TONE_GEN_OFF ||
+ ctrl->value >
+ V4L2_CG2900_RADIO_TEST_TONE_GENERATOR_ON_WO_SRC) {
+ FM_ERR_REPORT("Invalid parameter = %d", ctrl->value);
+ break;
+ }
+ status = cg2900_fm_set_test_tone_generator(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS, "
+ "Value = %d", ctrl->value);
+
+ if ((V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED >
+ ctrl->value) ||
+ (V4L2_CG2900_RADIO_DEEMPHASIS_75_uS <
+ ctrl->value)) {
+ FM_ERR_REPORT("Unsupported deemphasis = %d",
+ ctrl->value);
+ break;
+ }
+
+ switch (ctrl->value) {
+ case V4L2_CG2900_RADIO_DEEMPHASIS_50_uS:
+ ctrl->value = FMD_EMPHASIS_50US;
+ break;
+ case V4L2_CG2900_RADIO_DEEMPHASIS_75_uS:
+ ctrl->value = FMD_EMPHASIS_75US;
+ break;
+ case V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED:
+ /* Drop Down */
+ default:
+ ctrl->value = FMD_EMPHASIS_NONE;
+ break;
+ }
+ status = cg2900_fm_rx_set_deemphasis(ctrl->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "unsupported (id = %x)", ctrl->id);
+ }
+ FM_DEBUG_REPORT("vidioc_set_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_ext_ctrls()- Get the values of a particular control.
+ *
+ * This function is used to get the value of a
+ * particular control from the FM Driver. This is used when the data to
+ * be received is more than 1 paramter. This function is called when the
+ * application issues the IOCTL VIDIOC_G_EXT_CTRLS
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ext_ctrl: v4l2_ext_controls structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ENOSPC: when there is no space to copy the data into the buffer provided
+ * by application.
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ )
+{
+ u32 *dest_buffer;
+ int index = 0;
+ int count = 0;
+ int ret_val = -EINVAL;
+ int status;
+ struct sk_buff *skb;
+ u8 mode;
+ s8 interrupt_success;
+ int *fm_interrupt_buffer;
+
+ FM_INFO_REPORT("vidioc_get_ext_ctrls: Id = %04x,"
+ "ext_ctrl->ctrl_class = %04x",
+ ext_ctrl->controls->id,
+ ext_ctrl->ctrl_class);
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_FM_TX &&
+ ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: Unsupported "
+ "ctrl_class = %04x", ext_ctrl->ctrl_class);
+ goto error;
+ }
+
+ switch (ext_ctrl->controls->id) {
+ case V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS) {
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT(
+ "V4L2_CID_CG2900_RADIO"
+ "_BANDSCAN_GET_RESULTS: "
+ "fm_event = %x", fm_event);
+ if (fm_event ==
+ CG2900_EVENT_SCAN_CHANNELS_FOUND) {
+ /* Check to get Scan Result */
+ status =
+ cg2900_fm_get_scan_result
+ (&no_of_scan_freq, scanfreq,
+ scanfreq_rssi_level);
+ if (0 != status) {
+ FM_ERR_REPORT
+ ("vidioc_get_ext_ctrls: "
+ "cg2900_fm_get_scan_"
+ "result: returned %d",
+ status);
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ } else {
+ /* Some other interrupt, Queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ }
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "SeekStatus = %x, GlobalEvent = %x, "
+ "numchannels = %x",
+ cg2900_device.seekstatus,
+ fm_event, no_of_scan_freq);
+
+ if (ext_ctrl->controls->size == 0 &&
+ ext_ctrl->controls->string == NULL) {
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS &&
+ CG2900_EVENT_SCAN_CHANNELS_FOUND
+ == fm_event) {
+ spin_lock(&fm_spinlock);
+ ext_ctrl->controls->size =
+ no_of_scan_freq;
+ cg2900_device.seekstatus
+ = FMR_SEEK_NONE;
+ fm_event =
+ CG2900_EVENT_NO_EVENT;
+ spin_unlock(&fm_spinlock);
+ return -ENOSPC;
+ }
+ } else if (ext_ctrl->controls->string != NULL) {
+ dest_buffer =
+ (u32 *) ext_ctrl->controls->string;
+ while (index < no_of_scan_freq) {
+ *(dest_buffer + count + 0) =
+ HZ_TO_V4L2(scanfreq[index]);
+ *(dest_buffer + count + 1) =
+ scanfreq_rssi_level[index];
+ count += 2;
+ index++;
+ }
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_BLOCKSCAN_GET_RESULTS:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN"
+ "_GET_RESULTS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT(
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN"
+ "GET_RESULTS: "
+ "fm_event = %x", fm_event);
+ if (fm_event ==
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND) {
+ /* Check for BlockScan Result */
+ status =
+ cg2900_fm_get_block_scan_result
+ (&no_of_block_scan_freq,
+ block_scan_rssi_level);
+ if (0 != status) {
+ FM_ERR_REPORT
+ ("vidioc_get_ext_ctrls: "
+ "cg2900_fm_get_block_scan_"
+ "result: returned %d",
+ status);
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ } else {
+ /* Some other interrupt,
+ Queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ }
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "SeekStatus = %x, GlobalEvent = %x, "
+ "numchannels = %x",
+ cg2900_device.seekstatus,
+ fm_event, no_of_block_scan_freq);
+ if (ext_ctrl->controls->size == 0 &&
+ ext_ctrl->controls->string == NULL) {
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS &&
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND
+ == fm_event) {
+ spin_lock(&fm_spinlock);
+ ext_ctrl->controls->size =
+ no_of_block_scan_freq;
+ cg2900_device.seekstatus
+ = FMR_SEEK_NONE;
+ fm_event =
+ CG2900_EVENT_NO_EVENT;
+ spin_unlock(&fm_spinlock);
+ return -ENOSPC;
+ }
+ } else if (ext_ctrl->controls->size >=
+ no_of_block_scan_freq &&
+ ext_ctrl->controls->string != NULL) {
+ dest_buffer =
+ (u32 *) ext_ctrl->controls->string;
+ while (index < no_of_block_scan_freq) {
+ *(dest_buffer + index) =
+ block_scan_rssi_level
+ [index];
+ index++;
+ }
+ ret_val = 0;
+ return ret_val;
+ }
+ break;
+ case V4L2_CID_RDS_TX_DEVIATION:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_RDS_TX_DEVIATION");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_rds_deviation((u16 *) &
+ ext_ctrl->
+ controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_PILOT_TONE_ENABLED:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_ENABLED");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_pilot_tone_status(
+ (bool *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_PILOT_TONE_DEVIATION:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_DEVIATION");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_pilot_deviation(
+ (u16 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_TUNE_PREEMPHASIS:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_TUNE_PREEMPHASIS");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_preemphasis(
+ (u8 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_TUNE_POWER_LEVEL:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_TUNE_POWER_LEVEL");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_power_level(
+ (u16 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_GET_INTERRUPT:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_GET_INTERRUPT "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (ext_ctrl->controls->size != FMR_GET_INTERRUPT_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_GET_INTERRUPT "
+ "Invalid parameters, ext_ctrl->controls->size = %x "
+ "ext_ctrl->controls->string = %08x",
+ ext_ctrl->controls->size,
+ (unsigned int)ext_ctrl->controls->string);
+ ret_val = -ENOSPC;
+ break;
+ }
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("V4L2_CID_CG2900_RADIO_GET_INTERRUPT: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ interrupt_success = (s8)skb->data[1];
+ FM_DEBUG_REPORT("vidioc_get_ctrl: Interrupt = %x "
+ "interrupt_success = %x",
+ fm_event, interrupt_success);
+ fm_interrupt_buffer =
+ (int *) ext_ctrl->controls->string;
+ /* Interrupt that has occurred */
+ *fm_interrupt_buffer = cg2900_map_event_to_v4l2(fm_event);
+
+ /* Interrupt success or failed */
+ if (interrupt_success) {
+ /* Interrupt Success, return 0 */
+ *(fm_interrupt_buffer + 1) = 0;
+ } else {
+ spin_lock(&fm_spinlock);
+ no_of_scan_freq = 0;
+ no_of_block_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ /* Interrupt Success, return negative error */
+ *(fm_interrupt_buffer + 1) = -1;
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: Interrupt = %d "
+ "failed with reason = %d",
+ (*fm_interrupt_buffer),
+ (*(fm_interrupt_buffer + 1)));
+ /*
+ * Update return value, so that application
+ * can read the event failure reason.
+ */
+ ret_val = 0;
+ break;
+ }
+
+ if (CG2900_EVENT_MONO_STEREO_TRANSITION
+ == fm_event) {
+ /*
+ * In case of Mono/Stereo Interrupt,
+ * get the current value from chip
+ */
+ status = cg2900_fm_get_mode(&mode);
+ cg2900_device.rx_stereo_status = (bool)mode;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ } else if (CG2900_EVENT_SCAN_CANCELLED ==
+ fm_event) {
+ /* Scan/Search cancelled by User */
+ spin_lock(&fm_spinlock);
+ no_of_scan_freq = 0;
+ no_of_block_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ } else {
+ /* Queue the interrupt back
+ for later dequeuing */
+ FM_DEBUG_REPORT("V4L2_CID_CG2900"
+ "_RADIO_GET_INTERRUPT: "
+ "Queuing the interrupt"
+ "again to head of list");
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ ret_val = 0;
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "unsupported (id = %x)",
+ ext_ctrl->controls->id);
+ }
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: returning = %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_ext_ctrls()- Set the values of a particular control.
+ *
+ * This function is used to set the value of a
+ * particular control on the FM Driver. This is used when the data to
+ * be set is more than 1 paramter. This function is called when the
+ * application issues the IOCTL VIDIOC_S_EXT_CTRLS
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ext_ctrl: v4l2_ext_controls structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ENOSPC: when there is no space to copy the data into the buffer provided
+ * by application.
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ )
+{
+ int ret_val = -EINVAL;
+ int status;
+
+ FM_INFO_REPORT("vidioc_set_ext_ctrls: Id = %04x, ctrl_class = %04x",
+ ext_ctrl->controls->id, ext_ctrl->ctrl_class);
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_FM_TX &&
+ ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: Unsupported "
+ "ctrl_class = %04x", ext_ctrl->ctrl_class);
+ goto error;
+ }
+
+ switch (ext_ctrl->controls->id) {
+ case V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START:
+ {
+ u32 af_switch_freq;
+ u16 af_switch_pi;
+ u32 *af_switch_buf;
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_AF_SWITCH_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ af_switch_buf = (u32 *) ext_ctrl->controls->string;
+ af_switch_freq = V4L2_TO_HZ(*af_switch_buf);
+ af_switch_pi = *(af_switch_buf + 1);
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START: "
+ "AF Switch Freq =%d Hz AF Switch PI = %04x",
+ (int)af_switch_freq, af_switch_pi);
+
+ if (af_switch_freq < (FMR_CHINA_LOW_FREQ_IN_MHZ
+ * FMR_HZ_TO_MHZ_CONVERTER) ||
+ af_switch_freq > (FMR_CHINA_HIGH_FREQ_IN_MHZ
+ * FMR_HZ_TO_MHZ_CONVERTER)) {
+ FM_ERR_REPORT("Invalid Freq = %04x",
+ af_switch_freq);
+ break;
+ }
+
+ status = cg2900_fm_af_switch_start(
+ af_switch_freq,
+ af_switch_pi);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_DEVIATION:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_DEVIATION, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_RDS_DEVIATION &&
+ ext_ctrl->controls->value > MAX_RDS_DEVIATION) {
+ FM_ERR_REPORT("Invalid RDS Deviation = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_rds_deviation(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PI:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PI, PI = %04x",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_PI_VALUE &&
+ ext_ctrl->controls->value > MAX_PI_VALUE) {
+ FM_ERR_REPORT("Invalid PI = %04x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pi_code(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PTY:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PTY, PTY = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value < MIN_PTY_VALUE &&
+ ext_ctrl->controls->value > MAX_PTY_VALUE) {
+ FM_ERR_REPORT("Invalid PTY = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pty_code(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PS_NAME:
+ {
+ if (ext_ctrl->controls->size > MAX_PSN_SIZE
+ || ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("Invalid PSN");
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PS_NAME, "
+ "PSN = %s, Len = %x",
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ status = cg2900_fm_tx_set_program_station_name(
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_RADIO_TEXT:
+ {
+ if (ext_ctrl->controls->size >= MAX_RT_SIZE
+ || ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("Invalid RT");
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_RADIO_TEXT, "
+ "RT = %s, Len = %x",
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ status = cg2900_fm_tx_set_radio_text(
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_PILOT_TONE_ENABLED:
+ {
+ bool enable;
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_ENABLED, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (FMD_PILOT_TONE_ENABLED ==
+ ext_ctrl->controls->value)
+ enable = true;
+ else if (FMD_PILOT_TONE_DISABLED ==
+ ext_ctrl->controls->value)
+ enable = false;
+ else {
+ FM_ERR_REPORT("Unsupported Value = %d",
+ ext_ctrl->controls->value);
+ break;
+ }
+ status = cg2900_fm_tx_set_pilot_tone_status(enable);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_PILOT_TONE_DEVIATION:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_DEVIATION, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_PILOT_DEVIATION &&
+ ext_ctrl->controls->value > MAX_PILOT_DEVIATION) {
+ FM_ERR_REPORT("Invalid Pilot Deviation = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pilot_deviation(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_TUNE_PREEMPHASIS:
+ {
+ u8 preemphasis;
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_TUNE_PREEMPHASIS, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if ((V4L2_PREEMPHASIS_50_uS >
+ ext_ctrl->controls->value) ||
+ (V4L2_PREEMPHASIS_75_uS <
+ ext_ctrl->controls->value)) {
+ FM_ERR_REPORT("Unsupported Preemphasis = %d",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ if (V4L2_PREEMPHASIS_50_uS ==
+ ext_ctrl->controls->value) {
+ preemphasis = FMD_EMPHASIS_50US;
+ } else if (V4L2_PREEMPHASIS_75_uS ==
+ ext_ctrl->controls->value) {
+ preemphasis = FMD_EMPHASIS_75US;
+ }
+
+ status = cg2900_fm_tx_set_preemphasis(preemphasis);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_TUNE_POWER_LEVEL:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_TUNE_POWER_LEVEL, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+ if (ext_ctrl->controls->value < MIN_POWER_LEVEL &&
+ ext_ctrl->controls->value > MAX_POWER_LEVEL) {
+ FM_ERR_REPORT("Invalid Power Level = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_power_level(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_BLOCKSCAN_START:
+ {
+ u32 start_freq;
+ u32 end_freq;
+ u32 *block_scan_buf;
+ u32 current_grid;
+ u32 low_freq;
+ u32 high_freq;
+ u32 result_freq;
+ u8 no_of_block_scan_channels;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_BLOCK_SCAN_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN_START "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* Check for current grid */
+ if (grid == CG2900_FM_GRID_50)
+ current_grid = FMR_CHINA_GRID_IN_HZ;
+ else if (grid == CG2900_FM_GRID_100)
+ current_grid = FMR_EUROPE_GRID_IN_HZ;
+ else
+ current_grid = FMR_USA_GRID_IN_HZ;
+
+ /* Check for current band */
+ if (band == CG2900_FM_BAND_US_EU) {
+ low_freq = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+
+ } else if (band == CG2900_FM_BAND_JAPAN) {
+ low_freq = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+
+ } else {
+ low_freq = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+
+ /* V4L2 Extended control */
+
+ block_scan_buf = (u32 *)ext_ctrl->controls->string;
+ start_freq = V4L2_TO_HZ(*block_scan_buf);
+ end_freq = V4L2_TO_HZ(*(block_scan_buf + 1));
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_"
+ "BLOCKSCAN_START: "
+ "Start Freq = %d Hz "
+ "End Freq = %d Hz",
+ (int)start_freq,
+ (int)end_freq);
+
+ result_freq = end_freq - start_freq;
+ no_of_block_scan_channels =
+ (u8)(result_freq / current_grid);
+
+ /* Frequency Check */
+ if (end_freq < start_freq) {
+ FM_ERR_REPORT("Start Freq (%d Hz) "
+ " > End Freq (%d Hz)",
+ (int)start_freq,
+ (int)end_freq);
+ break;
+ }
+
+ if ((start_freq < low_freq) ||
+ (start_freq > high_freq)) {
+ FM_ERR_REPORT("Out of Band Freq: "
+ "Start Freq = %d Hz",
+ (int)start_freq);
+ break;
+ }
+
+ if ((end_freq < low_freq) ||
+ (end_freq > high_freq)) {
+ FM_ERR_REPORT("Out of Band Freq: "
+ "End Freq = %d Hz",
+ (int)end_freq);
+ break;
+ }
+
+ /* Maximum allowed block scan range */
+ if (FMR_MAX_BLOCK_SCAN_CHANNELS <
+ no_of_block_scan_channels) {
+ FM_ERR_REPORT("No of channels (%x)"
+ "exceeds Max Block Scan (%x)",
+ no_of_block_scan_channels,
+ FMR_MAX_BLOCK_SCAN_CHANNELS);
+ break;
+ }
+
+ status = cg2900_fm_start_block_scan(
+ start_freq,
+ end_freq);
+ if (0 == status) {
+ cg2900_device.seekstatus =
+ FMR_SEEK_IN_PROGRESS;
+ ret_val = 0;
+ }
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT:
+ {
+ u8 left_audio_mode;
+ u8 right_audio_mode;
+ u8 *test_tone_connect_buf;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_"
+ "TEST_TONE_CONNECT "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_TEST_TONE_CONNECT_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST"
+ "_TONE_CONNECT "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* V4L2 Extended control */
+ test_tone_connect_buf =
+ (u8 *)ext_ctrl->controls->string;
+ left_audio_mode = *test_tone_connect_buf;
+ right_audio_mode = *(test_tone_connect_buf + 1);
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT"
+ "left_audio_mode Freq = %02x"
+ "right_audio_modeFreq = %02x",
+ left_audio_mode,
+ right_audio_mode);
+
+ /* Range Check */
+ if (left_audio_mode > \
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM) {
+ FM_ERR_REPORT("Invalid Value of "
+ "left_audio_mode (%02x) ",
+ left_audio_mode);
+ break;
+ }
+
+ if (right_audio_mode > \
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM) {
+ FM_ERR_REPORT("Invalid Value of "
+ "right_audio_mode (%02x) ",
+ left_audio_mode);
+ break;
+ }
+
+ status = cg2900_fm_test_tone_connect(
+ left_audio_mode,
+ right_audio_mode);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS:
+ {
+ u8 tone_gen;
+ u16 frequency;
+ u16 volume;
+ u16 phase_offset;
+ u16 dc;
+ u8 waveform;
+ u16 *test_tone_set_params_buf;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_TEST_TONE_SET_PARAMS_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "FMR_TEST_TONE_SET_PARAMS_DATA_SIZE "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* V4L2 Extended control */
+ test_tone_set_params_buf = \
+ (u16 *)ext_ctrl->controls->string;
+
+ tone_gen = (u8)(*test_tone_set_params_buf);
+ frequency = *(test_tone_set_params_buf + 1);
+ volume = *(test_tone_set_params_buf + 2);
+ phase_offset = *(test_tone_set_params_buf + 3);
+ dc = *(test_tone_set_params_buf + 4);
+ waveform = (u8)(*(test_tone_set_params_buf + 5));
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS"
+ "tone_gen = %02x frequency = %04x"
+ "volume = %04x phase_offset = %04x"
+ "dc = %04x waveform = %02x",
+ tone_gen, frequency,
+ volume, phase_offset,
+ dc, waveform);
+
+ /* Range Check */
+ if (tone_gen > FMD_TST_TONE_2) {
+ FM_ERR_REPORT("Invalid Value of "
+ "tone_gen (%02x) ",
+ tone_gen);
+ break;
+ }
+
+ if (waveform > FMD_TST_TONE_PULSE) {
+ FM_ERR_REPORT("Invalid Value of "
+ "waveform (%02x) ",
+ waveform);
+ break;
+ }
+
+ if (frequency > 0x7FFF) {
+ FM_ERR_REPORT("Invalid Value of "
+ "frequency (%04x) ",
+ frequency);
+ break;
+ }
+
+ if (volume > 0x7FFF) {
+ FM_ERR_REPORT("Invalid Value of "
+ "volume (%04x) ",
+ volume);
+ break;
+ }
+
+ status = cg2900_fm_test_tone_set_params(
+ tone_gen,
+ frequency,
+ volume,
+ phase_offset,
+ dc,
+ waveform);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ default:
+ {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "Unsupported Id = %04x",
+ ext_ctrl->controls->id);
+ }
+ }
+error:
+ return ret_val;
+}
+
+/**
+ * vidioc_set_hw_freq_seek()- seek Up/Down Frequency.
+ *
+ * This function is used to start seek
+ * on the FM Radio. Direction if seek is as inicated by the parameter
+ * inside the v4l2_hw_freq_seek structure. This function is called when the
+ * application issues the IOCTL VIDIOC_S_HW_FREQ_SEEK
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq_seek: v4l2_hw_freq_seek structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_hw_freq_seek(
+ struct file *file,
+ void *priv,
+ struct v4l2_hw_freq_seek *freq_seek
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_hw_freq_seek");
+
+ FM_DEBUG_REPORT("vidioc_set_hw_freq_seek: Status = %x, "
+ "Upwards = %x, Wrap Around = %x",
+ cg2900_device.seekstatus,
+ freq_seek->seek_upward, freq_seek->wrap_around);
+
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ FM_ERR_REPORT("vidioc_set_hw_freq_seek: "
+ "VIDIOC_S_HW_FREQ_SEEK, "
+ "freq_seek in progress");
+ goto error;
+ }
+
+ spin_lock(&fm_spinlock);
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+
+ if (CG2900_DIR_UP == freq_seek->seek_upward)
+ status = cg2900_fm_search_up_freq();
+ else if (CG2900_DIR_DOWN == freq_seek->seek_upward)
+ status = cg2900_fm_search_down_freq();
+ else
+ goto error;
+
+ if (0 != status)
+ goto error;
+
+ cg2900_device.seekstatus = FMR_SEEK_IN_PROGRESS;
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_hw_freq_seek: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_audio()- Get Audio features of FM Driver.
+ *
+ * This function is used to get the audio features of FM Driver.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @audio: (out) v4l2_audio structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ )
+{
+ FM_INFO_REPORT("vidioc_get_audio");
+ strcpy(audio->name, "");
+ audio->capability = 0;
+ audio->mode = 0;
+ return 0;
+}
+
+/**
+ * vidioc_set_audio()- Set Audio features of FM Driver.
+ *
+ * This function is used to set the audio features of FM Driver.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @audio: v4l2_audio structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ )
+{
+ FM_INFO_REPORT("vidioc_set_audio");
+ if (audio->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * vidioc_get_input()- Get the Input Value
+ *
+ * This function is used to get the Input.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @input: (out) Value to be stored.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_input(
+ struct file *file,
+ void *priv,
+ unsigned int *input
+ )
+{
+ FM_INFO_REPORT("vidioc_get_input");
+ *input = 0;
+ return 0;
+}
+
+/**
+ * vidioc_set_input()- Set the input value.
+ *
+ * This function is used to set input.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @input: Value to set
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_input(
+ struct file *file,
+ void *priv,
+ unsigned int input
+ )
+{
+ FM_INFO_REPORT("vidioc_set_input");
+ if (input != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * cg2900_convert_err_to_v4l2()- Convert Error Bits to V4L2 RDS format.
+ *
+ * This function converts the error bits in RDS Block
+ * as received from Chip into V4L2 RDS data specification.
+ *
+ * @status_byte: The status byte as received in RDS Group for
+ * particular RDS Block
+ * @out_byte: byte to store the modified byte with the err bits
+ * alligned as per V4L2 RDS Specifications.
+ */
+static void cg2900_convert_err_to_v4l2(
+ char status_byte,
+ char *out_byte
+ )
+{
+ if ((status_byte & RDS_ERROR_STATUS_MASK) == RDS_ERROR_STATUS_MASK) {
+ /* Uncorrectable Block */
+ *out_byte = (*out_byte | V4L2_RDS_BLOCK_ERROR);
+ } else if (((status_byte & RDS_UPTO_TWO_BITS_CORRECTED)
+ == RDS_UPTO_TWO_BITS_CORRECTED) ||
+ ((status_byte & RDS_UPTO_FIVE_BITS_CORRECTED)
+ == RDS_UPTO_FIVE_BITS_CORRECTED)) {
+ /* Corrected Bits in Block */
+ *out_byte = (*out_byte | V4L2_RDS_BLOCK_CORRECTED);
+ }
+}
+
+/**
+ * cg2900_map_event_to_v4l2()- Maps cg2900 event to v4l2 events .
+ *
+ * This function maps cg2900 events to corresponding v4l2 events.
+ *
+ * @event: This contains the cg2900 event to be converted.
+ *
+ * Returns: Corresponding V4L2 events.
+ */
+static int cg2900_map_event_to_v4l2(
+ u8 event
+ )
+{
+ switch (event) {
+ case CG2900_EVENT_MONO_STEREO_TRANSITION:
+ return V4L2_CG2900_RADIO_INTERRUPT_MONO_STEREO_TRANSITION;
+ case CG2900_EVENT_SEARCH_CHANNEL_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_SEARCH_COMPLETED;
+ case CG2900_EVENT_SCAN_CHANNELS_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED;
+ case CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED;
+ case CG2900_EVENT_SCAN_CANCELLED:
+ return V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED;
+ case CG2900_EVENT_DEVICE_RESET:
+ return V4L2_CG2900_RADIO_INTERRUPT_DEVICE_RESET;
+ case CG2900_EVENT_RDS_EVENT:
+ return V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED;
+ default:
+ return V4L2_CG2900_RADIO_INTERRUPT_UNKNOWN;
+ }
+}
+
+/**
+ * cg2900_open()- This function nitializes and switches on FM.
+ *
+ * This is called when the application opens the character device.
+ *
+ * @file: File structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int cg2900_open(
+ struct file *file
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+ struct video_device *vdev = video_devdata(file);
+
+ mutex_lock(&fm_mutex);
+ users++;
+ FM_INFO_REPORT("cg2900_open: users = %d", users);
+
+ if (users > 1) {
+ FM_INFO_REPORT("cg2900_open: FM already switched on!!!");
+ ret_val = 0;
+ /*
+ * No need to perform the initialization and switch on FM
+ * since it is already done during the first open call to
+ * this driver.
+ */
+ goto done;
+ }
+
+ status = cg2900_fm_init();
+ if (0 != status)
+ goto init_error;
+
+ FM_DEBUG_REPORT("cg2900_open: Switching on FM");
+ status = cg2900_fm_switch_on(&(vdev->dev));
+ if (0 != status)
+ goto switch_on_error;
+
+ cg2900_device.state = FMR_SWITCH_ON;
+ cg2900_device.frequency = HZ_TO_V4L2(freq_low);
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.muted = false;
+ cg2900_device.audiopath = 0;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ cg2900_device.rssi_threshold = CG2900_FM_DEFAULT_RSSI_THRESHOLD;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ cg2900_device.fm_mode = CG2900_FM_IDLE_MODE;
+ ret_val = 0;
+ goto done;
+
+switch_on_error:
+ cg2900_fm_deinit();
+init_error:
+ users--;
+done:
+ mutex_unlock(&fm_mutex);
+ FM_DEBUG_REPORT("cg2900_open: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * cg2900_release()- This function switches off FM.
+ *
+ * This function switches off FM and releases the resources.
+ * This is called when the application closes the character
+ * device.
+ *
+ * @file: File structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int cg2900_release(
+ struct file *file
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+
+ mutex_lock(&fm_mutex);
+
+ FM_INFO_REPORT("cg2900_release");
+ if (users <= 0) {
+ FM_ERR_REPORT("cg2900_release: No users registered "
+ "with FM Driver");
+ goto done;
+ }
+
+ users--;
+ FM_INFO_REPORT("cg2900_release: users = %d", users);
+
+ if (0 == users) {
+ FM_DEBUG_REPORT("cg2900_release: Switching Off FM");
+ status = cg2900_fm_switch_off();
+ status = cg2900_fm_deinit();
+ if (0 != status)
+ goto done;
+
+ cg2900_device.state = FMR_SWITCH_OFF;
+ cg2900_device.frequency = 0;
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.muted = false;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ }
+ ret_val = 0;
+
+done:
+ mutex_unlock(&fm_mutex);
+ FM_DEBUG_REPORT("cg2900_release: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * cg2900_read()- This function is invoked when the application
+ * calls read() to receive RDS Data.
+ *
+ * @file: File structure.
+ * @data: buffer provided by application for receving the data.
+ * @count: Number of bytes that application wants to read from driver
+ * @pos: offset
+ *
+ * Returns:
+ * Number of bytes copied to the user buffer
+ * -EFAULT: If there is problem in copying data to buffer supplied
+ * by application
+ * -EIO: If the number of bytes to be read are not a multiple of
+ * struct v4l2_rds_data.
+ * -EAGAIN: More than 22 blocks requested to be read or read
+ * was called in non blocking mode and no data was available for reading.
+ * -EINTR: If read was interrupted by a signal before data was avaialble.
+ * 0 when no data available for reading.
+ */
+static ssize_t cg2900_read(
+ struct file *file,
+ char __user *data,
+ size_t count, loff_t *pos
+ )
+{
+ int current_rds_grp;
+ int index = 0;
+ int blocks_to_read;
+ struct v4l2_rds_data rdsbuf[MAX_RDS_GROUPS * NUM_OF_RDS_BLOCKS];
+ struct v4l2_rds_data *rdslocalbuf = rdsbuf;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_read");
+
+ blocks_to_read = (count / sizeof(struct v4l2_rds_data));
+
+ if (!cg2900_device.rx_rds_enabled) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ return 0;
+ }
+
+ if (count % sizeof(struct v4l2_rds_data) != 0) {
+ FM_ERR_REPORT("cg2900_read: Invalid Number of bytes %x "
+ "requested to read", count);
+ return -EIO;
+ }
+
+ if (blocks_to_read > MAX_RDS_GROUPS * NUM_OF_RDS_BLOCKS) {
+ FM_ERR_REPORT("cg2900_read: Too many blocks(%d) "
+ "requested to be read", blocks_to_read);
+ return -EAGAIN;
+ }
+
+ current_rds_grp = fm_rds_info.rds_group_sent;
+
+ if ((fm_rds_info.rds_head == fm_rds_info.rds_tail) ||
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1 == 0x0000)) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ return 0;
+ }
+
+ spin_lock(&fm_spinlock);
+ while (index < blocks_to_read) {
+ /* Check which Block needs to be transferred next */
+ switch (fm_rds_info.rds_block_sent % NUM_OF_RDS_BLOCKS) {
+ case 0:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status1
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status1,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 1:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block2;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block2 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status2
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status2,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 2:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block3;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block3 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status3
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status3,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 3:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block4;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block4 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status4
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status4,
+ &(rdslocalbuf + index)->block);
+ current_rds_grp++;
+ if (current_rds_grp == MAX_RDS_GROUPS) {
+ fm_rds_info.rds_tail++;
+ current_rds_grp = 0;
+ /* Dequeue Rds Interrupt here */
+ skb = skb_dequeue(&fm_interrupt_queue);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("cg2900_read: "
+ "skb is NULL. Major error");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ fm_event = (u8)skb->data[0];
+ if (fm_event != CG2900_EVENT_RDS_EVENT) {
+ /* RDS interrupt not found */
+ FM_ERR_REPORT("cg2900_read:"
+ "RDS interrupt not found"
+ "for de-queuing."
+ "fm_event = %x", fm_event);
+ /* Queue the event back */
+ skb_queue_head(&fm_interrupt_queue,
+ skb);
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ kfree_skb(skb);
+ }
+ break;
+ default:
+ FM_ERR_REPORT("Invalid RDS Group!!!");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ index++;
+ fm_rds_info.rds_block_sent++;
+ if (fm_rds_info.rds_block_sent == NUM_OF_RDS_BLOCKS)
+ fm_rds_info.rds_block_sent = 0;
+
+ if (!cg2900_device.rx_rds_enabled) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ }
+ /* Update the RDS Group Count Sent to Application */
+ fm_rds_info.rds_group_sent = current_rds_grp;
+ if (fm_rds_info.rds_tail == MAX_RDS_BUFFER)
+ fm_rds_info.rds_tail = 0;
+
+ spin_unlock(&fm_spinlock);
+ if (copy_to_user(data, rdslocalbuf, count)) {
+ FM_ERR_REPORT("cg2900_read: Error "
+ "in copying, returning");
+ return -EFAULT;
+ }
+ return count;
+}
+
+/**
+ * cg2900_poll()- Check if the operation is complete or not.
+ *
+ * This function is invoked by application on calling poll() and is used to
+ * wait till the any FM interrupt is received from the chip.
+ * The application decides to read the corresponding data depending on FM
+ * interrupt.
+ *
+ * @file: File structure.
+ * @wait: poll table
+ *
+ * Returns:
+ * POLLRDNORM|POLLIN whenever FM interrupt has occurred.
+ * 0 whenever the call times out.
+ */
+static unsigned int cg2900_poll(
+ struct file *file,
+ struct poll_table_struct *wait
+ )
+{
+ int ret_val = 0;
+
+ FM_INFO_REPORT("cg2900_poll");
+
+ /* Check if we have some data in queue already */
+ if (skb_queue_empty(&fm_interrupt_queue)) {
+ FM_DEBUG_REPORT("cg2900_poll: Interrupt Queue Empty, waiting");
+ /* No Interrupt, wait for it to occur */
+ poll_wait(file, &cg2900_poll_queue, wait);
+ }
+ /* Check if we now have interrupt to read in queue */
+ if (skb_queue_empty(&fm_interrupt_queue))
+ goto done;
+
+ ret_val = POLLIN | POLLRDNORM;
+
+done:
+ FM_DEBUG_REPORT("poll_wait returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * radio_cg2900_probe()- This function registers FM Driver with V4L2 Driver.
+ *
+ * This function is called whenever the driver is probed by the device system,
+ * i.e. when a CG2900 controller has connected. It registers the FM Driver with
+ * Video4Linux as a character device.
+ *
+ * @pdev: Platform device.
+ *
+ * Returns:
+ * 0 on success
+ * -EINVAL on error
+ */
+static int __devinit radio_cg2900_probe(
+ struct platform_device *pdev
+ )
+{
+ int err;
+
+ FM_INFO_REPORT(BANNER);
+
+ err = fmd_set_dev(&pdev->dev);
+ if (err) {
+ FM_ERR_REPORT("Could not set device %s", pdev->name);
+ return err;
+ }
+
+ radio_nr = 0;
+ grid = CG2900_FM_GRID_100;
+ band = CG2900_FM_BAND_US_EU;
+ FM_INFO_REPORT("radio_cg2900_probe: radio_nr= %d.", radio_nr);
+
+ /* Initialize the parameters */
+ if (video_register_device(
+ &cg2900_video_device,
+ VFL_TYPE_RADIO,
+ radio_nr) == -1) {
+ FM_ERR_REPORT("radio_cg2900_probe: video_register_device err");
+ return -EINVAL;
+ }
+ mutex_init(&fm_mutex);
+ spin_lock_init(&fm_spinlock);
+ init_waitqueue_head(&cg2900_poll_queue);
+ skb_queue_head_init(&fm_interrupt_queue);
+ users = 0;
+ return 0;
+}
+
+/**
+ * radio_cg2900_remove()- This function removes the FM Driver.
+ *
+ * This function is called whenever the driver is removed by the device system,
+ * i.e. when a CG2900 controller has disconnected. It unregisters the FM Driver
+ * from Video4Linux.
+ *
+ * @pdev: Platform device.
+ *
+ * Returns: 0 on success
+ */
+static int __devexit radio_cg2900_remove(
+ struct platform_device *pdev
+ )
+{
+ FM_INFO_REPORT("radio_cg2900_remove");
+ /* Wake up the poll queue since we are now exiting */
+ wake_up_poll_queue();
+ /* Give some time for application to exit the poll thread */
+ schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+ /* Try to Switch Off FM in case it is still switched on */
+ cg2900_fm_switch_off();
+ cg2900_fm_deinit();
+ skb_queue_purge(&fm_interrupt_queue);
+ mutex_destroy(&fm_mutex);
+ video_unregister_device(&cg2900_video_device);
+ fmd_set_dev(NULL);
+ return 0;
+}
+
+static struct platform_driver radio_cg2900_driver = {
+ .driver = {
+ .name = "cg2900-fm",
+ .owner = THIS_MODULE,
+ },
+ .probe = radio_cg2900_probe,
+ .remove = __devexit_p(radio_cg2900_remove),
+};
+
+/**
+ * radio_cg2900_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init radio_cg2900_init(void)
+{
+ FM_INFO_REPORT("radio_cg2900_init");
+ return platform_driver_register(&radio_cg2900_driver);
+}
+
+/**
+ * radio_cg2900_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit radio_cg2900_exit(void)
+{
+ FM_INFO_REPORT("radio_cg2900_exit");
+ platform_driver_unregister(&radio_cg2900_driver);
+}
+
+void wake_up_poll_queue(void)
+{
+ FM_INFO_REPORT("wake_up_poll_queue");
+ wake_up_interruptible(&cg2900_poll_queue);
+}
+
+void cg2900_handle_device_reset(void)
+{
+ struct sk_buff *skb;
+ FM_INFO_REPORT("cg2900_handle_device_reset");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA, GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_handle_device_reset: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_DEVICE_RESET;
+ skb->data[1] = true;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+}
+
+module_init(radio_cg2900_init);
+module_exit(radio_cg2900_exit);
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
+
+module_param(radio_nr, int, S_IRUGO);
+
+module_param(grid, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(grid, "Grid:"
+ "0=50 kHz"
+ "*1=100 kHz*"
+ "2=200 kHz");
+
+module_param(band, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(band, "Band:"
+ "*0=87.5-108 MHz*"
+ "1=76-90 MHz"
+ "2=70-108 MHz");
+
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 8db2d7f4b52..3fadf6aff35 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -161,6 +161,22 @@ config RADIO_WL1273
To compile this driver as a module, choose M here: the
module will be called radio-wl1273.
+config RADIO_CG2900
+ tristate "ST-Ericsson CG2900 FM Radio support"
+ depends on CG2900 && VIDEO_V4L2
+ ---help---
+ Choose Y here if you have one of these FM radio cards. This is a BT,
+ FM and GPS combo chip controlled via HCI.
+
+ In order to control your radio card, you will need to use programs
+ that are compatible with the Video For Linux API. Information on
+ this API and pointers to "v4l" programs may be found at
+ <file:Documentation/video4linux/API.html>.
+
+ To compile this driver as a module, choose M here: the
+ module will be called radio-CG2900.
+
+
# TI's ST based wl128x FM radio
source "drivers/media/radio/wl128x/Kconfig"
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index ca8c7d134b9..8435dabcb3c 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -28,5 +28,6 @@ obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o
obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o
obj-$(CONFIG_RADIO_WL128X) += wl128x/
+obj-$(CONFIG_RADIO_CG2900) += CG2900/
ccflags-y += -Isound