diff options
Diffstat (limited to 'drivers')
-rwxr-xr-x | drivers/media/radio/CG2900/Makefile | 12 | ||||
-rw-r--r-- | drivers/media/radio/CG2900/cg2900_fm_api.c | 3389 | ||||
-rw-r--r-- | drivers/media/radio/CG2900/cg2900_fm_api.h | 1077 | ||||
-rw-r--r-- | drivers/media/radio/CG2900/cg2900_fm_driver.c | 5017 | ||||
-rw-r--r-- | drivers/media/radio/CG2900/cg2900_fm_driver.h | 1856 | ||||
-rw-r--r-- | drivers/media/radio/CG2900/radio-cg2900.c | 3024 | ||||
-rw-r--r-- | drivers/media/radio/Kconfig | 16 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 1 |
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 *) ¤tFreq); + } 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 *) ¤tFreq); + } + 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, + ¶meters[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, + ¶meters[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, + ¶meters[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, + ¶meters[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(¶meters[1], block1, sizeof(u16)); + memcpy(¶meters[2], block2, sizeof(u16)); + memcpy(¶meters[3], block3, sizeof(u16)); + memcpy(¶meters[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 |