diff options
Diffstat (limited to 'drivers/media/radio/CG2900/cg2900_fm_driver.c')
-rw-r--r-- | drivers/media/radio/CG2900/cg2900_fm_driver.c | 4917 |
1 files changed, 4917 insertions, 0 deletions
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..1d3a2552da2 --- /dev/null +++ b/drivers/media/radio/CG2900/cg2900_fm_driver.c @@ -0,0 +1,4917 @@ +/* + * 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 + +/* + * 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; +}; + +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); + + if (*frequency > max_freq) + *frequency = max_freq; + else if (*frequency < min_freq) + *frequency = min_freq; + +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)); + } + 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; + 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; + memcpy( + parameters, + fmd_data.parameters, + (*num_parameters * sizeof(u16))); + } + + 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; + + 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) { + fmd_data.parameters = + FM_GET_RSP_BUFFER_ADDR(packet_buffer); + memcpy(fmd_data.parameters, + FM_GET_RSP_BUFFER_ADDR(packet_buffer), + fmd_data.num_parameters * sizeof(u16)); + } + /* 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_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_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; + 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; + 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"); + |