summaryrefslogtreecommitdiff
path: root/drivers/media/radio/CG2900/cg2900_fm_driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/radio/CG2900/cg2900_fm_driver.c')
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_driver.c5017
1 files changed, 5017 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..627d0f2d674
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_driver.c
@@ -0,0 +1,5017 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Driver for CG2900 FM Chip
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/device.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <asm-generic/errno-base.h>
+#include "cg2900.h"
+#include "cg2900_fm_driver.h"
+
+/*
+ * Macro for printing the HCI Packet received from Protocol Driver
+ * to FM Driver.
+ */
+#define CG2900_HEX_READ_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('<', skb->data, skb->len);
+
+/* Macro for printing the HCI Packet sent to Protocol Driver from FM Driver */
+#define CG2900_HEX_WRITE_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('>', send_buffer, num_bytes);
+
+/* Converts the given value to ASCII format*/
+#define ASCVAL(x)(((x) <= 9) ? (x) + '0' : (x) - 10 + 'a')
+
+/* The receive Packet's 1st byte indicates the packet length */
+#define FM_GET_PKT_LEN(__data) __data[0]
+
+/* The receive Packet's following bytes are the actual data. */
+#define FM_GET_RSP_PKT_ADDR(__data) (&__data[1])
+
+/*
+ * LSB consists of shifting the command Id by 3 bits
+ * to left and ORING with the number of parameters of the
+ * command.
+ */
+#define FM_CMD_GET_LSB(__cmd_id, __num_param) \
+ ((u8)(((__cmd_id << 3) & 0x00FF) | __num_param))
+
+/* MSB consists of shifting the command Id by 5 bits to right. */
+#define FM_CMD_GET_MSB(__cmd_id) \
+ ((u8)(__cmd_id >> 5))
+
+/*
+ * Command id is mapped as shifting the MSB 5 bits to left and
+ * ORING with LSB shifted 3 bits to right.
+ */
+#define FM_GET_CMD_ID(__data) \
+ ((u16)((__data[2] << 5) | __data[1] >> 3))
+
+/*
+ * Number of parameters in the response packet are the last 3 bits
+ * of the 1st byte of the received packet.
+ */
+#define FM_GET_NUM_PARAMS(__data) \
+ ((u16)((__data[1] & 0x07)))
+
+/* Function Id is mapped to the 1st byte of the received packet */
+#define FM_GET_FUNCTION_ID(__data) __data[0]
+
+/*
+ * Block Id of the FM Firmware downloaded is mapped to the
+ * 2nd byte of the received packet.
+ */
+#define FM_GET_BLOCK_ID(__data) __data[1]
+
+/* Status of the received packet is mapped to the 4th byte. */
+#define FM_GET_STATUS(__data) __data[3]
+
+/*
+ * For PG1 of CG2900, the FM Interrupt is mapped
+ * to the 3rd and 4th byte of the received packet.
+ */
+#define FM_GET_PGI_INTERRUPT(__data) \
+ ((u16)(__data[3] << 8 | __data[2]))
+
+/*
+ * For PG2 of CG2900, the FM Interrupt is mapped
+ * to the 5th and 6th byte of the received packet.
+ */
+#define FM_GET_PG2_INTERRUPT(__data) \
+ ((u16)(__data[5] << 8 | __data[4]))
+
+#define FM_GET_NUM_RDS_GRPS(__data) __data[0]
+
+/* Response buffer starts from the 4th byte if the response buffer */
+#define FM_GET_RSP_BUFFER_ADDR(__data) (&__data[3])
+
+/* FM Function buffer starts from the 5th byte if the response buffer */
+#define FM_GET_FUNCTION_ADDR(__data) (&__data[4])
+
+/*
+ * Maximum time for chip to respond including the Command
+ * Competion interrupts for some commands. This time has been
+ * adjusted to cater to increased communication time with chip
+ * when debug level is set to 4.
+ */
+#define MAX_RESPONSE_TIME_IN_MS 5000
+
+/* Byte per word */
+#define WORD_LENGTH 2
+
+/* Byte Offset Counter */
+#define COUNTER 1
+
+/* Binary shift offset for one byte */
+#define SHIFT_OFFSET 8
+
+/*
+ * enum fmd_gocmd_t - FM Driver Command state.
+ *
+ * @FMD_STATE_NONE: FM Driver in Idle state
+ * @FMD_STATE_MODE: FM Driver in setmode state
+ * @FMD_STATE_FREQUENCY: FM Driver in Set frequency state.
+ * @FMD_STATE_PA: FM Driver in SetPA state.
+ * @FMD_STATE_PA_LEVEL: FM Driver in Setpalevl state.
+ * @FMD_STATE_ANTENNA: FM Driver in Setantenna state
+ * @FMD_STATE_MUTE: FM Driver in Setmute state
+ * @FMD_STATE_SEEK: FM Driver in seek mode
+ * @FMD_STATE_SEEK_STOP: FM Driver in seek stop level state.
+ * @FMD_STATE_SCAN_BAND: FM Driver in Scanband mode
+ * @FMD_STATE_TX_SET_CTRL: FM Driver in RDS control state
+ * @FMD_STATE_TX_SET_THRSHLD: FM Driver in RDS threshld state
+ * @FMD_STATE_GEN_POWERUP: FM Driver in Power UP state.
+ * @FMD_STATE_SELECT_REF_CLK: FM Driver in Select Reference clock state.
+ * @FMD_STATE_SET_REF_CLK_PLL: FM Driver in Set Reference Freq state.
+ * @FMD_STATE_BLOCK_SCAN: FM Driver in Block Scan state.
+ * @FMD_STATE_AF_UPDATE: FM Driver in AF Update State.
+ * @FMD_STATE_AF_SWITCH: FM Driver in AF Switch State.
+ * @FMD_STATE_LAST: Last State of FM Driver
+ *
+ * Various states of the FM driver.
+ */
+enum fmd_gocmd {
+ FMD_STATE_NONE,
+ FMD_STATE_MODE,
+ FMD_STATE_FREQUENCY,
+ FMD_STATE_PA,
+ FMD_STATE_PA_LEVEL,
+ FMD_STATE_ANTENNA,
+ FMD_STATE_MUTE,
+ FMD_STATE_SEEK,
+ FMD_STATE_SEEK_STOP,
+ FMD_STATE_SCAN_BAND,
+ FMD_STATE_TX_SET_CTRL,
+ FMD_STATE_TX_SET_THRSHLD,
+ FMD_STATE_GEN_POWERUP,
+ FMD_STATE_SELECT_REF_CLK,
+ FMD_STATE_SET_REF_CLK_PLL,
+ FMD_STATE_BLOCK_SCAN,
+ FMD_STATE_AF_UPDATE,
+ FMD_STATE_AF_SWITCH,
+ FMD_STATE_LAST
+};
+
+/**
+ * struct fmd_rdsgroup_t - Rds group structure.
+ *
+ * @block: Array for RDS Block(s) received.
+ * @status: Array of Status of corresponding RDS block(s).
+ *
+ * It stores the value and status of a particular RDS group
+ * received.
+ */
+struct fmd_rds_group {
+ u16 block[NUM_OF_RDS_BLOCKS];
+ u8 status[NUM_OF_RDS_BLOCKS];
+};
+
+/**
+ * struct fmd_states_info - Main FM state info structure.
+ *
+ * @fmd_initialized: Flag indicating FM Driver is initialized or not
+ * @rx_freq_range: Receiver freq range
+ * @rx_volume: Receiver volume level
+ * @rx_antenna: Receiver Antenna
+ * @rx_seek_stop_level: RDS seek stop Level
+ * @rx_rds_on: Receiver RDS ON
+ * @rx_stereo_mode: Receiver Stereo mode
+ * @max_channels_to_scan: Maximum Number of channels to Scan.
+ * @tx_freq_range: Transmitter freq Range
+ * @tx_preemphasis: Transmitter Pre emphiasis level
+ * @tx_stereo_mode: Transmitter stero mode
+ * @tx_rds_on: Enable RDS
+ * @tx_pilot_dev: PIlot freq deviation
+ * @tx_rds_dev: RDS deviation
+ * @tx_strength: TX Signal Stregnth
+ * @irq_index: Index where last interrupt is added to Interrupt queue
+ * @interrupt_available_for_processing: Flag indicating if interrupt is
+ * available for processing or not.
+ * @interrupt_queue: Circular Queue to store the received interrupt from chip.
+ * @gocmd: Command which is in progress.
+ * @mode: Current Mode of FM Radio.
+ * @rds_group: Array of RDS group Buffer
+ * @callback: Callback registered by upper layers.
+ */
+struct fmd_states_info {
+ bool fmd_initialized;
+ u8 rx_freq_range;
+ u8 rx_volume;
+ u8 rx_antenna;
+ u16 rx_seek_stop_level;
+ bool rx_rds_on;
+ u8 rx_stereo_mode;
+ u8 tx_freq_range;
+ u8 tx_preemphasis;
+ bool tx_stereo_mode;
+ u8 max_channels_to_scan;
+ bool tx_rds_on;
+ u16 tx_pilot_dev;
+ u16 tx_rds_dev;
+ u16 tx_strength;
+ u8 irq_index;
+ bool interrupt_available_for_processing;
+ u16 interrupt_queue[MAX_COUNT_OF_IRQS];
+ enum fmd_gocmd gocmd;
+ enum fmd_mode mode;
+ struct fmd_rds_group rds_group[MAX_RDS_GROUPS];
+ fmd_radio_cb callback;
+};
+
+/**
+ * struct fmd_data - Main structure for FM data exchange.
+ *
+ * @cmd_id: Command Id of the command being exchanged.
+ * @num_parameters: Number of parameters
+ * @parameters: FM data parameters.
+ */
+struct fmd_data {
+ u32 cmd_id;
+ u16 num_parameters;
+ u8 parameters[MAX_RESP_SIZE];
+};
+
+static struct fmd_states_info fmd_state_info;
+static struct fmd_data fmd_data;
+static struct semaphore cmd_sem;
+static struct semaphore rds_sem;
+static struct semaphore interrupt_sem;
+static struct task_struct *rds_thread_task;
+static struct task_struct *irq_thread_task;
+static struct device *cg2900_fm_dev;
+static struct mutex write_mutex;
+static struct mutex send_cmd_mutex;
+static spinlock_t fmd_spinlock;
+static spinlock_t fmd_spinlock_read;
+
+/* Debug Level
+ * 1: Only Error Logs
+ * 2: Info Logs
+ * 3: Debug Logs
+ * 4: HCI Logs
+ */
+unsigned short cg2900_fm_debug_level = FM_ERROR_LOGS;
+EXPORT_SYMBOL(cg2900_fm_debug_level);
+
+static cg2900_fm_rds_cb cb_rds_func;
+static bool rds_thread_required;
+static bool irq_thread_required;
+
+static char event_name[FMD_EVENT_LAST_ELEMENT][MAX_NAME_SIZE] = {
+ "FMD_EVENT_OPERATION_COMPLETED",
+ "FMD_EVENT_ANTENNA_STATUS_CHANGED",
+ "FMD_EVENT_FREQUENCY_CHANGED",
+ "FMD_EVENT_SEEK_COMPLETED",
+ "FMD_EVENT_SCAN_BAND_COMPLETED",
+ "FMD_EVENT_BLOCK_SCAN_COMPLETED",
+ "FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE",
+ "FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE",
+ "FMD_EVENT_SEEK_STOPPED",
+ "FMD_EVENT_GEN_POWERUP",
+ "FMD_EVENT_RDSGROUP_RCVD",
+};
+
+static char interrupt_name[MAX_COUNT_OF_IRQS][MAX_NAME_SIZE] = {
+ "IRPT_OPERATION_SUCCEEDED",
+ "IRPT_OPERATION_FAILED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_RX_BUFFER_FULL_OR_TX_BUFFER_EMPTY",
+ "IRPT_RX_SIGNAL_QUALITY_LOW_OR_TX_MUTE_STATUS_CHANGED",
+ "IRPT_MONO_STEREO_TRANSITION",
+ "IRPT_RX_RDS_SYNC_FOUND_OR_TX_INPUT_OVERDRIVE",
+ "IRPT_RDS_SYNC_LOST",
+ "IRPT_PI_CODE_CHANGED",
+ "IRPT_REQUESTED_BLOCK_AVAILABLE",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_WARM_BOOT_READY",
+ "IRPT_COLD_BOOT_READY",
+};
+
+
+static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ );
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ );
+static void fmd_event_name(
+ u8 event,
+ char *event_name
+ );
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ );
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interrupt_name
+ );
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ );
+static void fmd_process_interrupt(
+ u16 interrupt
+ );
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ );
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static bool fmd_go_cmd_busy(void);
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ );
+static int fmd_send_cmd(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters
+ );
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ );
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ );
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ );
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ );
+static int fmd_rds_thread(
+ void *data
+ );
+static void fmd_start_irq_thread(void);
+static void fmd_stop_irq_thread(void);
+static int fmd_irq_thread(
+ void *data
+ );
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ );
+static int fmd_get_cmd_sem(void);
+static void fmd_set_cmd_sem(void);
+static void fmd_get_interrupt_sem(void);
+static void fmd_set_interrupt_sem(void);
+static bool fmd_driver_init(void);
+static void fmd_driver_exit(void);
+
+/* structure declared in time.h */
+struct timespec time_spec;
+
+
+/**
+ * fmd_hexdump() - Displays the HCI Data Bytes exchanged with FM Chip.
+ *
+ * @prompt: Prompt signifying the direction '<' for Rx '>' for Tx
+ * @buffer: Buffer to be displayed.
+ * @num_bytes: Number of bytes of the buffer.
+ */
+ static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ )
+{
+ int i;
+ u8 tmp_val;
+ struct timespec time;
+ static u8 pkt_write[MAX_BUFFER_SIZE], *pkt_ptr;
+
+ getnstimeofday(&time);
+ sprintf(pkt_write, "\n[%08x:%08x] [%04x] %c",
+ (unsigned int)time.tv_sec,
+ (unsigned int)time.tv_nsec,
+ num_bytes, prompt);
+
+ pkt_ptr = pkt_write + strlen(pkt_write);
+ if (buffer == NULL)
+ return;
+
+ /* Copy the buffer only if the input buffer is not NULL */
+ for (i = 0; i < num_bytes; i++) {
+ *pkt_ptr++ = ' ';
+ tmp_val = buffer[i] >> 4;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ tmp_val = buffer[i] & 0x0F;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ if (i > 20) {
+ /* Print only 20 bytes at max */
+ break;
+ }
+ }
+ *pkt_ptr++ = '\0';
+ FM_HEX_REPORT("%s", pkt_write);
+}
+
+/**
+ * fmd_get_event() - Returns the Event based on FM Driver State.
+ *
+ * @gocmd: Pending FM Command
+ *
+ * Returns: Corresponding Event
+ */
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ )
+{
+ u8 event = FMD_EVENT_OPERATION_COMPLETED;
+ switch (gocmd) {
+ case FMD_STATE_ANTENNA:
+ event = FMD_EVENT_ANTENNA_STATUS_CHANGED;
+ break;
+ case FMD_STATE_FREQUENCY:
+ event = FMD_EVENT_FREQUENCY_CHANGED;
+ break;
+ case FMD_STATE_SEEK:
+ event = FMD_EVENT_SEEK_COMPLETED;
+ break;
+ case FMD_STATE_SCAN_BAND:
+ event = FMD_EVENT_SCAN_BAND_COMPLETED;
+ break;
+ case FMD_STATE_BLOCK_SCAN:
+ event = FMD_EVENT_BLOCK_SCAN_COMPLETED;
+ break;
+ case FMD_STATE_AF_UPDATE:
+ /* Drop Down */
+ case FMD_STATE_AF_SWITCH:
+ event = FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE;
+ break;
+ case FMD_STATE_SEEK_STOP:
+ event = FMD_EVENT_SEEK_STOPPED;
+ break;
+ default:
+ event = FMD_EVENT_OPERATION_COMPLETED;
+ break;
+ }
+ return event;
+}
+
+/**
+ * fmd_event_name() - Converts the event to a displayable string.
+ *
+ * @event: Event that has occurred.
+ * @eventname: (out) Buffer to store event name.
+ */
+static void fmd_event_name(
+ u8 event,
+ char *eventname
+ )
+{
+ if (eventname == NULL) {
+ FM_ERR_REPORT("fmd_event_name: Output Buffer is NULL");
+ return;
+ }
+ if (event < FMD_EVENT_LAST_ELEMENT)
+ strcpy(eventname, event_name[event]);
+ else
+ strcpy(eventname, "FMD_EVENT_UNKNOWN");
+}
+
+/**
+ * fmd_get_fm_function_name() - Returns the FM Fucntion name.
+ *
+ * @fm_function: Function whose name is to be retrieved.
+ *
+ * Returns FM Function Name.
+ */
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ )
+{
+ switch (fm_function) {
+ case FM_FUNCTION_ENABLE:
+ return "FM_FUNCTION_ENABLE";
+ break;
+ case FM_FUNCTION_DISABLE:
+ return "FM_FUNCTION_DISABLE";
+ break;
+ case FM_FUNCTION_RESET:
+ return "FM_FUNCTION_RESET";
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ return "FM_FUNCTION_WRITE_COMMAND";
+ break;
+ case FM_FUNCTION_SET_INT_MASK_ALL:
+ return "FM_FUNCTION_SET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_GET_INT_MASK_ALL:
+ return "FM_FUNCTION_GET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_SET_INT_MASK:
+ return "FM_FUNCTION_SET_INT_MASK";
+ break;
+ case FM_FUNCTION_GET_INT_MASK:
+ return "FM_FUNCTION_GET_INT_MASK";
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ return "FM_FUNCTION_FIRMWARE_DOWNLOAD";
+ break;
+ default:
+ return "FM_FUNCTION_UNKNOWN";
+ break;
+ }
+}
+
+/**
+ * fmd_interrupt_name() - Converts the interrupt to a displayable string.
+ *
+ * @interrupt: interrupt received from FM Chip
+ * @interruptname: (out) Buffer to store interrupt name.
+ */
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interruptname
+ )
+{
+ int index;
+
+ if (interruptname == NULL) {
+ FM_ERR_REPORT("fmd_interrupt_name: Output Buffer is NULL!!!");
+ return;
+ }
+ /* Convert Interrupt to Bit */
+ for (index = 0; index < MAX_COUNT_OF_IRQS; index++) {
+ if (interrupt & (1 << index)) {
+ /* Match found, break the loop */
+ break;
+ }
+ }
+ if (index < MAX_COUNT_OF_IRQS)
+ strcpy(interruptname, interrupt_name[index]);
+ else
+ strcpy(interruptname, "IRPT_UNKNOWN");
+}
+
+/**
+ * fmd_add_interrupt_to_queue() - Add interrupt to IRQ Queue.
+ *
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ )
+{
+ FM_DEBUG_REPORT("fmd_add_interrupt_to_queue : "
+ "Interrupt Received = %04x", (u16) interrupt);
+
+ /* Reset the index if it reaches the array limit */
+ if (fmd_state_info.irq_index > MAX_COUNT_OF_IRQS - 1) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.irq_index = 0;
+ spin_unlock(&fmd_spinlock);
+ }
+
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_queue[fmd_state_info.irq_index] = interrupt;
+ fmd_state_info.irq_index++;
+ spin_unlock(&fmd_spinlock);
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_available_for_processing = true;
+ spin_unlock(&fmd_spinlock);
+ fmd_set_interrupt_sem();
+ }
+}
+
+/**
+ * fmd_process_interrupt() - Processes the Interrupt.
+ *
+ * This function processes the interrupt received from FM Chip
+ * and calls the corresponding callback registered by upper layers with
+ * proper parameters.
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_process_interrupt(
+ u16 interrupt
+ )
+{
+ char irpt_name[MAX_NAME_SIZE];
+
+ fmd_interrupt_name(interrupt, irpt_name);
+ FM_DEBUG_REPORT("%s", irpt_name);
+ if ((interrupt & IRPT_OPERATION_SUCCEEDED) |
+ (interrupt & IRPT_OPERATION_FAILED)) {
+ bool event_status = (interrupt & IRPT_OPERATION_SUCCEEDED);
+ u8 event = fmd_get_event(fmd_state_info.gocmd);
+
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_MODE:
+ /* Mode has been changed. */
+ case FMD_STATE_MUTE:
+ /* FM radio is Muter or Unmuted */
+ case FMD_STATE_PA:
+ /* Power Amplifier has been enabled/disabled */
+ case FMD_STATE_PA_LEVEL:
+ /* Power Amplifier Level has been changed. */
+ case FMD_STATE_SELECT_REF_CLK:
+ /* Reference Clock has been selected. */
+ case FMD_STATE_SET_REF_CLK_PLL:
+ /* Reference Clock frequency has been changed. */
+ case FMD_STATE_TX_SET_CTRL:
+ /* Tx Control has been set. */
+ case FMD_STATE_TX_SET_THRSHLD:
+ /* Tx Threashold has been set. */
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ case FMD_STATE_ANTENNA:
+ /* Antenna status has been changed. */
+ case FMD_STATE_SEEK_STOP:
+ /* Band scan, seek or block scan has completed. */
+ case FMD_STATE_AF_UPDATE:
+ /* AF Update has completed. */
+ case FMD_STATE_AF_SWITCH:
+ /* AF Switch has completed. */
+ case FMD_STATE_FREQUENCY:
+ /* Frequency has been changed. */
+ /*
+ * Set State to None, set the waiting semaphore,
+ * and inform upper layer.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ fmd_callback(
+ event,
+ event_status);
+ break;
+ case FMD_STATE_SEEK:
+ /* Seek has completed. */
+ case FMD_STATE_SCAN_BAND:
+ /* Band scan has completed. */
+ case FMD_STATE_BLOCK_SCAN:
+ /* Block scan has completed. */
+ /*
+ * Set State to None. No need to set the
+ * semaphore since this is an asyncronous event.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ /* Inform Upper layer. */
+ fmd_callback(event, event_status);
+ break;
+ default:
+ /* Do Nothing */
+ FM_ERR_REPORT("Default %s case of "\
+ "interrupt processing", event_status ? \
+ "Success" : "Failed");
+ break;
+ }
+ }
+
+ if (interrupt & IRPT_RX_BUFFERFULL_TX_BUFFEREMPTY) {
+ /*
+ * RDS Buffer Full or RDS Buffer Empty
+ * interrupt received from chip, indicating
+ * that RDS data is available if chip
+ * is in Rx mode or RDS data can be send
+ * to chip in case of Tx mode. Inform the
+ * upper layers about this interrupt.
+ */
+ fmd_callback(
+ FMD_EVENT_RDSGROUP_RCVD,
+ true);
+ }
+
+ if (interrupt & IRPT_RX_MONO_STEREO_TRANSITION) {
+ /*
+ * Mono Stereo Transition interrupt
+ * received from chip, inform the
+ * upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE,
+ true);
+ }
+
+ if ((interrupt & IRPT_COLD_BOOT_READY) |
+ (interrupt & IRPT_WARM_BOOT_READY)) {
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_GEN_POWERUP:
+ /*
+ * Cold Boot/ Warm Boot Interrupt received from
+ * chip, indicating transition from
+ * power off/standby state to active state.
+ * Inform the upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_GEN_POWERUP,
+ true);
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ default:
+ /* Do Nothing */
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_callback() - Callback function for upper layers.
+ *
+ * Callback function that calls the registered callback of upper
+ * layers with proper parameters.
+ * @event: event for which the callback function was called
+ * from FM Driver.
+ * @event_successful: Signifying whether the event is called from FM
+ * Driver on receiving irpt_Operation_Succeeded or irpt_Operation_Failed.
+ */
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ )
+{
+ char event_name_string[MAX_NAME_SIZE];
+
+ fmd_event_name(event, event_name_string);
+
+ FM_DEBUG_REPORT("%s %x, %d", event_name_string,
+ (unsigned int)event , (unsigned int)event_successful);
+
+ if (fmd_state_info.callback)
+ fmd_state_info.callback(
+ event,
+ event_successful);
+}
+
+/**
+ * fmd_rx_frequency_to_channel() - Converts Rx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Rx.
+ * @freq: Frequency in kHz.
+ * @channel: Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_rx_channel_to_frequency() - Converts Rx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Rx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_frequency_to_channel() - Converts Tx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Tx.
+ * @freq: Frequency in kHz.
+ * @channel: (out)Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_channel_to_frequency() - Converts Tx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Tx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel
+ * in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+ if (*frequency > max_freq)
+ *frequency = max_freq;
+ else if (*frequency < min_freq)
+ *frequency = min_freq;
+
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_go_cmd_busy() - Function to check if FM Driver is busy or idle
+ *
+ * Returns:
+ * false if FM Driver is Idle
+ * true otherwise
+ */
+static bool fmd_go_cmd_busy(void)
+{
+ return (fmd_state_info.gocmd != FMD_STATE_NONE);
+}
+
+/**
+ * fmd_read_cb() - Handle Received Data
+ *
+ * This function handles data received from connectivity protocol driver.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming form device.
+ */
+static void fmd_read_cb(
+ struct cg2900_user_data *dev,
+ struct sk_buff *skb
+ )
+{
+ FM_INFO_REPORT("fmd_read_cb");
+
+ if (skb->data == NULL || skb->len == 0)
+ return;
+
+ spin_lock(&fmd_spinlock_read);
+ CG2900_HEX_READ_PACKET_DUMP;
+ /*
+ * The first byte is length of bytes following bytes
+ * Rest of the bytes are the actual data
+ */
+ fmd_receive_data(
+ FM_GET_PKT_LEN(skb->data),
+ FM_GET_RSP_PKT_ADDR(skb->data));
+
+ kfree_skb(skb);
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_receive_data() - Processes the FM data received from device.
+ *
+ * @packet_length: Length of received Data Packet
+ * @packet_buffer: Received Data buffer.
+ */
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ )
+{
+ if (packet_buffer == NULL) {
+ FM_ERR_REPORT("fmd_receive_data: Buffer = NULL");
+ return;
+ }
+
+ if (packet_length == FM_PG1_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_CATENA_OPCODE &&
+ packet_buffer[1] == FM_EVENT_ID) {
+ /* PG 1.0 interrupt Handling */
+ u16 interrupt = FM_GET_PGI_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_length == FM_PG2_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_EVENT &&
+ packet_buffer[3] == FM_EVENT_ID) {
+ /* PG 2.0 interrupt Handling */
+ u16 interrupt = FM_GET_PG2_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_WRITE) {
+ /* Command Complete or RDS Data Handling */
+ u8 fm_status = FM_GET_STATUS(packet_buffer);;
+ switch (fm_status) {
+ case FM_CMD_STATUS_CMD_SUCCESS:
+ fmd_process_fm_function(
+ FM_GET_FUNCTION_ADDR(packet_buffer));
+ break;
+ case FM_CMD_STATUS_HCI_ERR_HW_FAILURE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_HW_FAILURE");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS");
+ break;
+ case FM_CMD_STATUS_IP_UNINIT:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_IP_UNINIT");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED");
+ break;
+ case FM_CMD_STATUS_WRONG_SEQ_NUM:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_WRONG_SEQ_NUM");
+ break;
+ case FM_CMD_STATUS_UNKNOWN_FILE_TYPE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_UNKNOWN_FILE_TYPE");
+ break;
+ case FM_CMD_STATUS_FILE_VERSION_MISMATCH:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_FILE_VERSION_MISMATCH");
+ break;
+ default:
+ FM_DEBUG_REPORT(
+ "Unknown Status = %02x", fm_status);
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_reset_cb() - Reset callback fuction.
+ *
+ * @dev: CPD device reseting.
+ */
+static void fmd_reset_cb(struct cg2900_user_data *dev)
+{
+ FM_INFO_REPORT("fmd_reset_cb: Device Reset");
+ spin_lock(&fmd_spinlock_read);
+ cg2900_handle_device_reset();
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_rds_thread() - Thread for receiving RDS data from Chip.
+ *
+ * @data: Data beng passed as parameter on starting the thread.
+ */
+static int fmd_rds_thread(
+ void *data
+ )
+{
+ FM_INFO_REPORT("fmd_rds_thread Created Successfully");
+ while (rds_thread_required) {
+ if (cb_rds_func)
+ cb_rds_func();
+ /* Give 100 ms for context switching */
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ /* Always signal the rds_sem semaphore before exiting */
+ fmd_set_rds_sem();
+ FM_DEBUG_REPORT("fmd_rds_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_start_irq_thread() - Function for starting Interrupt Thread.
+ */
+static void fmd_start_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_start_irq_thread");
+ irq_thread_task = kthread_create(fmd_irq_thread, NULL, "irq_thread");
+ if (IS_ERR(irq_thread_task)) {
+ FM_ERR_REPORT("fmd_start_irq_thread: "
+ "Unable to Create irq_thread");
+ irq_thread_task = NULL;
+ return;
+ }
+ wake_up_process(irq_thread_task);
+}
+
+/**
+ * fmd_stop_irq_thread() - Function for stopping Interrupt Thread.
+ */
+static void fmd_stop_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_irq_thread");
+ kthread_stop(irq_thread_task);
+ irq_thread_task = NULL;
+ FM_DEBUG_REPORT("-fmd_stop_irq_thread");
+}
+
+/**
+ * fmd_irq_thread() - Thread for processing Interrupts received from Chip.
+ *
+ * @data: Data being passed as parameter on starting the thread.
+ */
+
+static int fmd_irq_thread(
+ void *data
+ )
+{
+ int index;
+
+ FM_INFO_REPORT("fmd_irq_thread Created Successfully");
+
+ while (irq_thread_required) {
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ fmd_get_interrupt_sem();
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ }
+ index = 0;
+
+ if (fmd_state_info.interrupt_available_for_processing) {
+ while (index < MAX_COUNT_OF_IRQS) {
+ if (fmd_state_info.interrupt_queue[index]
+ != IRPT_INVALID) {
+ FM_DEBUG_REPORT("fmd_process_interrupt "
+ "Interrupt = %04x",
+ fmd_state_info.
+ interrupt_queue[index]);
+ fmd_process_interrupt(
+ fmd_state_info.interrupt_queue[index]);
+ fmd_state_info.interrupt_queue[index]
+ = IRPT_INVALID;
+ }
+ index++;
+ }
+ }
+ fmd_state_info.interrupt_available_for_processing = false;
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ FM_DEBUG_REPORT("fmd_irq_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_send_packet() - Sends the FM HCI Packet to the CG2900 Protocol Driver.
+ *
+ * @num_bytes: Number of bytes of Data to be sent including
+ * Channel Identifier (08)
+ * @send_buffer: Buffer containing the Data to be sent to Chip.
+ *
+ * Returns:
+ * 0, If packet was sent successfully to
+ * CG2900 Protocol Driver, otherwise the corresponding error.
+ * -EINVAL If parameters are not valid.
+ * -EIO If there is an Input/Output Error.
+ */
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ )
+{
+ int err;
+ struct sk_buff *skb;
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_send_packet");
+
+ if (send_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("fmd_send_packet: No FM device registered");
+ err = -EIO;
+ goto error;
+ }
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (!pf_data->opened) {
+ FM_ERR_REPORT("fmd_send_packet: FM channel is not opened");
+ err = -EIO;
+ goto error;
+ }
+
+ mutex_lock(&write_mutex);
+ CG2900_HEX_WRITE_PACKET_DUMP;
+
+ skb = pf_data->alloc_skb(num_bytes, GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("fmd_send_packet:Couldn't " \
+ "allocate sk_buff with length %d", num_bytes);
+ err = -EIO;
+ goto error;
+ }
+
+ /*
+ * Copy the buffer removing the FM Header as this
+ * would be done by Protocol Driver
+ */
+ memcpy(skb_put(skb, num_bytes), send_buffer, num_bytes);
+
+ err = pf_data->write(pf_data, skb);
+ if (err) {
+ FM_ERR_REPORT("fmd_send_packet: "
+ "Failed to send(%d) bytes using "
+ "cg2900_write, err = %d",
+ num_bytes, err);
+ kfree(skb);
+ err = -EIO;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ mutex_unlock(&write_mutex);
+ FM_DEBUG_REPORT("fmd_send_packet returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_get_cmd_sem() - Block on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ETIME if timeout occurs.
+ */
+static int fmd_get_cmd_sem(void)
+{
+ int ret_val;
+
+ FM_INFO_REPORT("fmd_get_cmd_sem");
+
+ ret_val = down_timeout(&cmd_sem,
+ msecs_to_jiffies(MAX_RESPONSE_TIME_IN_MS));
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_cmd_sem: down_timeout "
+ "returned error = %d", ret_val);
+
+ return ret_val;
+}
+
+/**
+ * fmd_set_cmd_sem() - Unblock on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ */
+static void fmd_set_cmd_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_cmd_sem");
+
+ up(&cmd_sem);
+}
+
+/**
+ * fmd_get_interrupt_sem() - Block on Interrupt Semaphore.
+ *
+ * Till Interrupt is received, Interrupt Task is blocked.
+ */
+static void fmd_get_interrupt_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_interrupt_sem");
+
+ ret_val = down_killable(&interrupt_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_interrupt_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+/**
+ * fmd_set_interrupt_sem() - Unblock on Interrupt Semaphore.
+ *
+ * on receiving Interrupt, Interrupt Task is un-blocked.
+ */
+static void fmd_set_interrupt_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_interrupt_sem");
+ up(&interrupt_sem);
+}
+
+/**
+ * fmd_driver_init()- Initializes the Mutex, Semaphore, etc for FM Driver.
+ *
+ * It also registers FM Driver with the Protocol Driver.
+ *
+ * Returns:
+ * true if initialization is successful
+ * false if initiialization fails.
+ */
+static bool fmd_driver_init(void)
+{
+ bool ret_val;
+ struct cg2900_rev_data rev_data;
+ struct cg2900_user_data *pf_data;
+ int err;
+
+ FM_INFO_REPORT("fmd_driver_init");
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("No device registered");
+ ret_val = false;
+ goto error;
+ }
+
+ /* Initialize the semaphores */
+ sema_init(&cmd_sem, 0);
+ sema_init(&rds_sem, 0);
+ sema_init(&interrupt_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ irq_thread_required = true;
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+
+ /* Create Mutex For Reading and Writing */
+ spin_lock_init(&fmd_spinlock_read);
+ mutex_init(&write_mutex);
+ mutex_init(&send_cmd_mutex);
+ spin_lock_init(&fmd_spinlock);
+ fmd_start_irq_thread();
+
+ /* Open the FM channel */
+ err = pf_data->open(pf_data);
+ if (err) {
+ FM_ERR_REPORT("fmd_driver_init: "
+ "Couldn't open FM channel. Either chip is not connected"
+ " or Protocol Driver is not initialized");
+ ret_val = false;
+ goto error;
+ }
+
+ if (!pf_data->get_local_revision(pf_data, &rev_data)) {
+ FM_DEBUG_REPORT("No revision data available");
+ ret_val = false;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("Read revision data revision %04x "
+ "sub_version %04x",
+ rev_data.revision, rev_data.sub_version);
+ cg2900_fm_set_chip_version(rev_data.revision, rev_data.sub_version);
+ ret_val = true;
+
+error:
+ FM_DEBUG_REPORT("fmd_driver_init: Returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * fmd_driver_exit() - Deinitializes the mutex, semaphores, etc.
+ *
+ * It also deregisters FM Driver with the Protocol Driver.
+ *
+ */
+static void fmd_driver_exit(void)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_driver_exit");
+ irq_thread_required = false;
+ mutex_destroy(&write_mutex);
+ mutex_destroy(&send_cmd_mutex);
+ fmd_stop_irq_thread();
+ /* Close the FM channel */
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (pf_data->opened)
+ pf_data->close(pf_data);
+}
+
+/**
+ * fmd_send_cmd_and_read_resp() - Send command and read response.
+ *
+ * This function sends the HCI Command to Protocol Driver and
+ * Reads back the Response Packet.
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ * @resp_num_parameters: (out) Number of paramters of the response packet.
+ * @resp_parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the command is sent successfully and the
+ * response received is also correct.
+ * -EINVAL: If the received response is not correct.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ )
+{
+ int result;
+ u16 read_cmd_id = CMD_ID_NONE;
+
+ FM_INFO_REPORT("fmd_send_cmd_and_read_resp");
+
+ mutex_lock(&send_cmd_mutex);
+ result = fmd_send_cmd(
+ cmd_id,
+ num_parameters,
+ parameters);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_read_resp(
+ &read_cmd_id,
+ resp_num_parameters,
+ resp_parameters);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Check that the response belongs to the sent command
+ */
+ if (read_cmd_id != cmd_id)
+ result = -EINVAL;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_send_cmd_and_read_resp: "
+ "returning %d", result);
+ return result;
+}
+
+/**
+ * fmd_send_cmd() - This function sends the HCI Command
+ * to Protocol Driver.
+ *
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ *
+ * Returns:
+ * 0: If the command is sent successfully to Lower Layers.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd(
+ const u16 cmd_id ,
+ const u16 num_parameters,
+ const u16 *parameters
+ )
+{
+ /*
+ * Total Length includes 6 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 total_length = num_parameters * sizeof(u16) + FM_HCI_CMD_HEADER_LEN;
+ /*
+ * Parameter Length includes 5 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 param_length = num_parameters * sizeof(u16) + FM_HCI_CMD_PARAM_LEN;
+ u8 *fm_data = kmalloc(total_length, GFP_KERNEL);
+ int err = -EINVAL;
+
+ FM_INFO_REPORT("fmd_send_cmd");
+
+ if (fm_data == NULL) {
+ err = -EIO;
+ goto error;
+ }
+
+ if (num_parameters && parameters == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* HCI encapsulation */
+ fm_data[0] = param_length;
+ fm_data[1] = FM_CATENA_OPCODE;
+ fm_data[2] = FM_WRITE;
+ fm_data[3] = FM_FUNCTION_WRITE_COMMAND;
+ fm_data[4] = FM_CMD_GET_LSB(cmd_id, num_parameters);
+ fm_data[5] = FM_CMD_GET_MSB(cmd_id);
+
+ memcpy(
+ (fm_data + FM_HCI_CMD_HEADER_LEN),
+ (void *)parameters,
+ num_parameters * sizeof(u16));
+
+ /* Send the Packet */
+ err = fmd_send_packet(total_length , fm_data);
+
+error:
+ kfree(fm_data);
+ FM_DEBUG_REPORT("fmd_send_cmd: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_read_resp() - This function reads the response packet of the previous
+ * command sent to FM Chip and copies it to the buffer provided as parameter.
+ *
+ * @cmd_id: (out) Command Id received from FM Chip.
+ * @num_parameters: (out) Number of paramters of the response packet.
+ * @parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the response buffer is copied successfully.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ )
+{
+ int err;
+ int param_offset = 0;
+ int byte_offset = 0;
+ FM_INFO_REPORT("fmd_read_resp");
+
+ /* Wait till response of the command is received */
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+
+ /* Check if the parameters are valid */
+ if (cmd_id == NULL || (fmd_data.num_parameters &&
+ (num_parameters == NULL || parameters == NULL))) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Fill the arguments */
+ *cmd_id = fmd_data.cmd_id;
+ if (fmd_data.num_parameters) {
+ *num_parameters = fmd_data.num_parameters;
+ while (param_offset <
+ (*num_parameters * sizeof(u16)) / WORD_LENGTH) {
+ parameters[param_offset] =
+ (u16)(fmd_data.parameters[byte_offset])
+ & 0x00ff;
+ parameters[param_offset] |=
+ ((u16)(fmd_data.parameters[byte_offset + COUNTER])
+ & 0x00ff) << SHIFT_OFFSET;
+ byte_offset = byte_offset + WORD_LENGTH;
+ param_offset++;
+ }
+ }
+
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_read_resp: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_process_fm_function() - Process FM Function.
+ *
+ * This function processes the Response buffer received
+ * from lower layers for the FM function and performs the necessary action to
+ * parse the same.
+ * @packet_buffer: Received Buffer.
+ */
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ )
+{
+ u8 fm_function_id;
+ u8 block_id;
+ int count = 0;
+
+ if (packet_buffer == NULL)
+ return;
+
+ fm_function_id = FM_GET_FUNCTION_ID(packet_buffer);
+ switch (fm_function_id) {
+ case FM_FUNCTION_ENABLE:
+ case FM_FUNCTION_DISABLE:
+ case FM_FUNCTION_RESET:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+
+ fmd_data.cmd_id = FM_GET_CMD_ID(packet_buffer);
+ fmd_data.num_parameters =
+ FM_GET_NUM_PARAMS(packet_buffer);
+
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "Cmd Id = 0x%04x, Num Of Parms = %02x",
+ fmd_data.cmd_id, fmd_data.num_parameters);
+
+ if (fmd_data.num_parameters) {
+ while (count <
+ (fmd_data.num_parameters * sizeof(u16))) {
+ fmd_data.parameters[count] =
+ *(FM_GET_RSP_BUFFER_ADDR(packet_buffer) + count);
+ count++;
+ }
+ }
+
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ block_id = FM_GET_BLOCK_ID(packet_buffer);
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s"
+ "block id = %02x",
+ fmd_get_fm_function_name(fm_function_id),
+ block_id);
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ default:
+ FM_ERR_REPORT(
+ "fmd_process_fm_function: "
+ "default case: command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ break;
+ }
+}
+
+/**
+ * fmd_write_file_block() - download firmware.
+ *
+ * This Function adds the header for downloading
+ * the firmware and coeffecient files and sends it to Protocol Driver.
+ * @file_block_id: Block ID of the F/W to be transmitted to FM Chip
+ * @file_block: Buffer containing the bytes to be sent.
+ * @file_block_length: Size of the Firmware buffer.
+ *
+ * Returns:
+ * 0: If there is no error.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ )
+{
+ int err;
+
+ FM_INFO_REPORT("fmd_write_file_block");
+ if (file_block == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ mutex_lock(&send_cmd_mutex);
+ file_block[0] = file_block_length + FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ file_block[1] = FM_CATENA_OPCODE;
+ file_block[2] = FM_WRITE;
+ file_block[3] = FM_FUNCTION_FIRMWARE_DOWNLOAD;
+ file_block[4] = file_block_id;
+ /* Send the Packet */
+ err = fmd_send_packet(
+ file_block_length +
+ FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ file_block);
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_write_file_block: "
+ "returning %d", err);
+ return err;
+}
+
+int fmd_init(void)
+{
+ int err;
+
+ if (!fmd_driver_init()) {
+ err = -EIO;
+ goto error;
+ }
+
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+ fmd_state_info.fmd_initialized = true;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_state_info.mode = FMD_MODE_IDLE;
+ fmd_state_info.callback = NULL;
+ fmd_state_info.rx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.rx_stereo_mode = FMD_STEREOMODE_BLENDING;
+ fmd_state_info.rx_volume = MAX_ANALOG_VOLUME;
+ fmd_state_info.rx_antenna = FMD_ANTENNA_EMBEDDED;
+ fmd_state_info.rx_rds_on = false;
+ fmd_state_info.rx_seek_stop_level = DEFAULT_RSSI_THRESHOLD;
+ fmd_state_info.tx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.tx_preemphasis = FMD_EMPHASIS_75US;
+ fmd_state_info.tx_pilot_dev = DEFAULT_PILOT_DEVIATION;
+ fmd_state_info.tx_rds_dev = DEFAULT_RDS_DEVIATION;
+ fmd_state_info.tx_strength = MAX_POWER_LEVEL;
+ fmd_state_info.max_channels_to_scan = DEFAULT_CHANNELS_TO_SCAN;
+ fmd_state_info.tx_stereo_mode = true;
+ fmd_state_info.irq_index = 0;
+ spin_lock_init(&fmd_spinlock);
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_init returning = %d", err);
+ return err;
+}
+
+void fmd_exit(void)
+{
+ fmd_set_interrupt_sem();
+ fmd_driver_exit();
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+}
+
+int fmd_register_callback(
+ fmd_radio_cb callback
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.callback = callback;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_version(
+ u16 *version
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_GET_VERSION_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (version == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GET_VERSION,
+ CMD_GET_VERSION_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ memcpy(version,
+ response_data,
+ sizeof(u16) * CMD_GET_VERSION_RSP_PARAM_LEN);
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mode(
+ u8 mode
+ )
+{
+ int err;
+ u16 parameters[CMD_GOTO_MODE_PARAM_LEN];
+ int io_result;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_MODE_TX) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ fmd_state_info.gocmd = FMD_STATE_MODE;
+ FM_ERR_REPORT("Sending Set Mode");
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_MODE,
+ CMD_GOTO_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+ fmd_state_info.mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range_properties(
+ u8 range,
+ u32 *min_freq,
+ u32 *max_freq
+ )
+{
+ int err;
+
+ if (min_freq == NULL || max_freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ switch (range) {
+ case FMD_FREQRANGE_EUROAMERICA:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_JAPAN:
+ *min_freq = FMD_JAPAN_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_JAPAN_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_CHINA:
+ *min_freq = FMD_CHINA_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_CHINA_MAX_FREQ_IN_KHZ;
+ break;
+ default:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_antenna(
+ u8 antenna
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_ANTENNA_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_ANTENNA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SET_ANTENNA,
+ CMD_SET_ANTENNA_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_antenna = antenna;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_antenna(
+ u8 *antenna
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *antenna = fmd_state_info.rx_antenna;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ fmd_state_info.rx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *range = fmd_state_info.rx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_rx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_mode(
+ u8 mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_STEREOMODE_BLENDING) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_stereo_mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_ctrl_blending_rssi(
+ u16 min_rssi,
+ u16 max_rssi)
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = min_rssi;
+ parameters[1] = max_rssi;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_CONTROL_BLENDING_RSSI,
+ CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stereo_mode(
+ u8 *mode
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_STATE_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_STATE,
+ CMD_RP_GET_STATE_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 2nd element of response is stereo signal */
+ *mode = response_data[1];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_RSSI_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_RSSI,
+ CMD_RP_GET_RSSI_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *strength = response_data[0]; /* 1st byte is the signal strength */
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stop_level(
+ u16 stoplevel
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.rx_seek_stop_level = stoplevel;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stop_level(
+ u16 *stop_level
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stop_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stop_level = fmd_state_info.rx_seek_stop_level;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_seek(
+ bool upwards
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SEARCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (upwards)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+ fmd_state_info.gocmd = FMD_STATE_SEEK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SEARCH_START,
+ CMD_SP_SEARCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_scan_band(
+ u8 max_channels_to_scan
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan > MAX_CHANNELS_TO_SCAN) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = max_channels_to_scan;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+
+ fmd_state_info.gocmd = FMD_STATE_SCAN_BAND;
+ fmd_state_info.max_channels_to_scan = max_channels_to_scan;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_START,
+ CMD_SP_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_max_channels_to_scan(
+ u8 *max_channels_to_scan
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *max_channels_to_scan = fmd_state_info.max_channels_to_scan;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_scan_band_info(
+ u32 index,
+ u16 *num_channels,
+ u16 *channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL || channels == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_GET_RESULT,
+ CMD_SP_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 1st byte indicates number of channels found */
+ *num_channels = response_data[0];
+ /* 2nd byte indicates 1st channel number */
+ channels[0] = response_data[1];
+ /* 3rd byte indicates RSSI of corresponding channel */
+ rssi[0] = response_data[2];
+ /* 4th byte indicates 2nd channel number */
+ channels[1] = response_data[3];
+ /* 5th byte indicates RSSI of corresponding channel */
+ rssi[1] = response_data[4];
+ /* 6th byte indicates 3rd channel number */
+ channels[2] = response_data[5];
+ /* 7th byte indicates RSSI of corresponding channel */
+ rssi[2] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_block_scan(
+ u32 start_freq,
+ u32 stop_freq,
+ u8 antenna
+ )
+{
+ u16 start_channel;
+ u16 stop_channel;
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (start_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ start_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (stop_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ stop_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Convert the start frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* Convert the end frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[0] = start_channel;
+ parameters[1] = stop_channel;
+ parameters[2] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_BLOCK_SCAN;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_START,
+ CMD_SP_BLOCK_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_block_scan_result(
+ u32 index,
+ u16 *num_channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_BLOCK_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_GET_RESULT,
+ CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * Response packet has 1st byte as the number
+ * of channels, and the remaining 6 bytes as
+ * rssi values of the channels.
+ */
+ *num_channels = response_data[0];
+ rssi[0] = response_data[1];
+ rssi[1] = response_data[2];
+ rssi[2] = response_data[3];
+ rssi[3] = response_data[4];
+ rssi[4] = response_data[5];
+ rssi[5] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_stop_seeking(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!(fmd_state_info.gocmd == FMD_STATE_SEEK ||
+ fmd_state_info.gocmd == FMD_STATE_SCAN_BAND ||
+ fmd_state_info.gocmd == FMD_STATE_BLOCK_SCAN)) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_SEEK_STOP;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_STOP,
+ CMD_SP_STOP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_update_start(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_UPDATE_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_AF_UPDATE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_START,
+ CMD_SP_AF_UPDATE_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_update_result(
+ u16 *af_level
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_UPDATE_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (af_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_GET_RESULT,
+ CMD_SP_AF_UPDATE_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * 1st byte of response packet is the
+ * RSSI of the AF Frequency.
+ */
+ *af_level = response_data[0];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_switch_start(
+ u32 freq,
+ u16 picode
+ )
+{
+
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_SWITCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[1] = picode;
+ parameters[2] = 0xFFFF; /* PI Mask */
+ parameters[3] = fmd_state_info.rx_seek_stop_level;
+ parameters[4] = 0x0000; /* Unmute when AF's PI matches expected PI */
+
+ fmd_state_info.gocmd = FMD_STATE_AF_SWITCH;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_START,
+ CMD_SP_AF_SWITCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_switch_results(
+ u16 *afs_conclusion,
+ u16 *afs_level,
+ u16 *afs_pi
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_SWITCH_GET_RESULT_RWSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (afs_conclusion == NULL ||
+ afs_level == NULL ||
+ afs_pi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_GET_RESULT,
+ CMD_SP_AF_SWITCH_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *afs_conclusion = response_data[0];
+ *afs_level = response_data[1];
+ *afs_pi = response_data[2];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.rx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_size(
+ u8 size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (size > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = size;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_threshold(
+ u8 threshold
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (threshold > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = threshold;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_THRESHOLD,
+ CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (on_off_state) {
+ case FMD_SWITCH_ON_RDS_SIMULATOR:
+ parameters[0] = 0xFFFF;
+ break;
+ case FMD_SWITCH_OFF_RDS:
+ default:
+ parameters[0] = 0x0000;
+ fmd_state_info.rx_rds_on = false;
+ break;
+ case FMD_SWITCH_ON_RDS:
+ parameters[0] = 0x0001;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ case FMD_SWITCH_ON_RDS_ENHANCED_MODE:
+ parameters[0] = 0x0002;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds_group_rejection(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_GROUP_REJECTION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on_off_state == FMD_RDS_GROUP_REJECTION_ON)
+ parameters[0] = 0x0001;
+ else if (on_off_state == FMD_RDS_GROUP_REJECTION_OFF)
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_GROUP_REJECTION,
+ CMD_DP_SET_GROUP_REJECTION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_low_level_rds_groups(
+ u8 index,
+ u16 *block1,
+ u16 *block2,
+ u16 *block3,
+ u16 *block4,
+ u8 *status1,
+ u8 *status2,
+ u8 *status3,
+ u8 *status4
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL ||
+ status1 == NULL ||
+ status2 == NULL ||
+ status3 == NULL ||
+ status4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *block1 = fmd_state_info.rds_group[index].block[0];
+ *block2 = fmd_state_info.rds_group[index].block[1];
+ *block3 = fmd_state_info.rds_group[index].block[2];
+ *block4 = fmd_state_info.rds_group[index].block[3];
+ *status1 = fmd_state_info.rds_group[index].status[0];
+ *status2 = fmd_state_info.rds_group[index].status[1];
+ *status3 = fmd_state_info.rds_group[index].status[2];
+ *status4 = fmd_state_info.rds_group[index].status[3];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_deemphasis(
+ u8 deemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_DEEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (deemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+
+ case FMD_EMPHASIS_75US:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+
+ case FMD_EMPHASIS_NONE:
+ default:
+ parameters[0] = FMD_EMPHASIS_NONE;
+ break;
+
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_SET_DEEMPHASIS,
+ CMD_RP_SET_DEEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pa(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ fmd_state_info.gocmd = FMD_STATE_PA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_MODE,
+ CMD_PA_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_signal_strength(
+ u16 strength
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if ((strength > MAX_POWER_LEVEL)
+ || (strength < MIN_POWER_LEVEL)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = strength;
+
+ fmd_state_info.gocmd = FMD_STATE_PA_LEVEL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_CONTROL,
+ CMD_PA_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_strength = strength;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *strength = fmd_state_info.tx_strength;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range > FMD_FREQRANGE_CHINA) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *range = fmd_state_info.tx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_preemphasis(
+ u8 preemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PREEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (preemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+ case FMD_EMPHASIS_75US:
+ default:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PREEMPHASIS,
+ CMD_RP_SET_PREEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_preemphasis = preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_preemphasis(
+ u8 *preemphasis
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (preemphasis == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *preemphasis = fmd_state_info.tx_preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_tx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_tx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_enable_stereo_mode(
+ bool enable_stereo_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = enable_stereo_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_stereo_mode = enable_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_stereo_mode(
+ bool *stereo_mode
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stereo_mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stereo_mode = fmd_state_info.tx_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pilot_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_PILOT_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PILOT_DEVIATION,
+ CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_pilot_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_pilot_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_pilot_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_RDS_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_RDS_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_RDS_DEVIATION,
+ CMD_RP_SET_RDS_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_rds_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_rds_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_on = on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_group(
+ u16 position,
+ u8 *block1,
+ u8 *block2,
+ u8 *block3,
+ u8 *block4
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_GROUP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = position;
+ memcpy(&parameters[1], block1, sizeof(u16));
+ memcpy(&parameters[2], block2, sizeof(u16));
+ memcpy(&parameters[3], block3, sizeof(u16));
+ memcpy(&parameters[4], block4, sizeof(u16));
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_GROUP,
+ CMD_DP_BUFFER_SET_GROUP_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_buffer_set_size(
+ u16 buffer_size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = buffer_size;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+
+}
+
+int fmd_tx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.tx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_balance(
+ s8 balance
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_BALANCE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert balance from percentage to chip number */
+ parameters[0] = (((s16)balance) * FMD_MAX_BALANCE) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_BALANCE,
+ CMD_SET_BALANCE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_volume(
+ u8 volume
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_VOLUME_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert volume from percentage to chip number */
+ parameters[0] = (((u16)volume) * FMD_MAX_VOLUME) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_VOLUME,
+ CMD_SET_VOLUME_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_volume = volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_volume(
+ u8 *volume
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (volume == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *volume = fmd_state_info.rx_volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = 0x0001;
+
+ fmd_state_info.gocmd = FMD_STATE_MUTE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_MUTE,
+ CMD_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_ext_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_EXT_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_EXT_SET_MUTE,
+ CMD_EXT_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_power_up(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_GEN_POWERUP;
+ FM_ERR_REPORT("Sending Gen Power Up");
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_POWERUP,
+ CMD_POWERUP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_standby(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_STANDBY,
+ CMD_GOTO_STANDBY_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_power_down(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_POWERDOWN,
+ CMD_GOTO_POWERDOWN_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_select_ref_clk(
+ u16 ref_clk
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = ref_clk;
+
+ fmd_state_info.gocmd = FMD_STATE_SELECT_REF_CLK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SELECT_REFERENCE_CLOCK,
+ CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_ref_clk_pll(
+ u16 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = freq;
+
+ fmd_state_info.gocmd = FMD_STATE_SET_REF_CLK_PLL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SET_REFERENCE_CLOCK_PLL,
+ CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_send_fm_ip_enable(void)
+{
+ int err;
+ u8 fm_ip_enable_cmd[CMD_IP_ENABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_enable_cmd[0] = CMD_IP_ENABLE_PARAM_LEN;
+ fm_ip_enable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_enable_cmd[2] = FM_WRITE ;
+ fm_ip_enable_cmd[3] = FM_FUNCTION_ENABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_ENABLE_CMD_LEN,
+ fm_ip_enable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_ip_disable(void)
+{
+ int err;
+ u8 fm_ip_disable_cmd[CMD_IP_DISABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_disable_cmd[0] = CMD_IP_DISABLE_PARAM_LEN;
+ fm_ip_disable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_disable_cmd[2] = FM_WRITE ;
+ fm_ip_disable_cmd[3] = FM_FUNCTION_DISABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_DISABLE_CMD_LEN,
+ fm_ip_disable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_firmware(
+ u8 *fw_buffer,
+ u16 fw_size
+ )
+{
+ int err = -EINVAL;
+ u16 bytes_to_write = ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ u16 bytes_remaining = fw_size;
+ u8 fm_firmware_data[ST_WRITE_FILE_BLK_SIZE + FM_HCI_CMD_HEADER_LEN];
+ u32 block_id = 0;
+
+ if (fw_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ while (bytes_remaining > 0) {
+ if (bytes_remaining <
+ (ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN))
+ bytes_to_write = bytes_remaining;
+
+ /*
+ * Five bytes of HCI Header for FM Firmware
+ * so shift the firmware data by 5 bytes
+ */
+ memcpy(
+ fm_firmware_data + FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ fw_buffer, bytes_to_write);
+ err = fmd_write_file_block(
+ block_id,
+ fm_firmware_data,
+ bytes_to_write);
+ if (err) {
+ FM_DEBUG_REPORT("fmd_send_fm_firmware: "
+ "Failed to download %d Block "
+ "error = %d", (unsigned int)block_id, err);
+ goto error;
+ }
+ /*
+ * Increment the Block Id by 1, since one
+ * block is successfully transmitted
+ * to the chip.
+ */
+ block_id++;
+ /*
+ * Increment the next firmware buffer equal
+ * to the number of bytes transmitted.
+ */
+ fw_buffer += bytes_to_write;
+ /*
+ * Decrement the number of bytes remaining
+ * equal to number of bytes transmitted successfully.
+ */
+ bytes_remaining -= bytes_to_write;
+
+ if (block_id == ST_MAX_NUMBER_OF_FILE_BLOCKS)
+ block_id = 0;
+ }
+
+error:
+ return err;
+}
+
+int fmd_int_bufferfull(
+ u16 *number_of_rds_groups
+ )
+{
+ u16 response_count;
+ u16 response_data[CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN];
+ u16 index = 0;
+ u16 rds_group_count;
+ u8 result = -ENOEXEC;
+ struct fmd_rds_group rds_group;
+
+ if (!fmd_state_info.rx_rds_on)
+ goto error;
+
+ /* get group count*/
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP_COUNT,
+ CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (result != 0)
+ goto error;
+
+ /* read RDS groups */
+ rds_group_count = FM_GET_NUM_RDS_GRPS(response_data);
+ if (rds_group_count > MAX_RDS_GROUPS)
+ rds_group_count = MAX_RDS_GROUPS;
+
+ *number_of_rds_groups = rds_group_count;
+
+ if (rds_group_count) {
+ FM_DEBUG_REPORT("rds_group_count = %d", rds_group_count);
+ while (rds_group_count-- && fmd_state_info.rx_rds_on) {
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP,
+ CMD_DP_BUFFER_GET_GROUP_PARAM_LEN,
+ NULL,
+ &response_count,
+ (u16 *)&rds_group);
+
+ if (result != 0)
+ goto error;
+
+ if (fmd_state_info.rx_rds_on)
+ fmd_state_info.rds_group[index++] = rds_group;
+ }
+ }
+error:
+ return result;
+}
+
+void fmd_start_rds_thread(
+ cg2900_fm_rds_cb cb_func
+ )
+{
+ FM_INFO_REPORT("fmd_start_rds_thread");
+ cb_rds_func = cb_func;
+ rds_thread_required = true;
+ rds_thread_task = kthread_create(fmd_rds_thread, NULL, "rds_thread");
+ if (IS_ERR(rds_thread_task)) {
+ FM_ERR_REPORT("fmd_start_rds_thread: "
+ "Unable to Create rds_thread");
+ rds_thread_task = NULL;
+ rds_thread_required = false;
+ return;
+ }
+ wake_up_process(rds_thread_task);
+}
+
+void fmd_stop_rds_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_rds_thread");
+ /* In case thread is waiting, set the rds sem */
+ fmd_set_rds_sem();
+ /* Re-initialize RDS Semaphore to zero */
+ sema_init(&rds_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ /* Wait for RDS thread to exit gracefully */
+ fmd_get_rds_sem();
+
+ if (rds_thread_task)
+ rds_thread_task = NULL;
+}
+
+void fmd_get_rds_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_rds_sem");
+ ret_val = down_killable(&rds_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_rds_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+void fmd_set_rds_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_rds_sem");
+ up(&rds_sem);
+}
+
+int fmd_set_dev(struct device *dev)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_DEBUG_REPORT("fmd_set_dev");
+
+ if (dev && cg2900_fm_dev) {
+ FM_ERR_REPORT("Only one FM device supported");
+ return -EACCES;
+ }
+
+ cg2900_fm_dev = dev;
+
+ if (!dev)
+ return 0;
+
+ pf_data = dev_get_platdata(dev);
+ pf_data->dev = dev;
+ pf_data->read_cb = fmd_read_cb;
+ pf_data->reset_cb = fmd_reset_cb;
+
+ return 0;
+}
+
+int fmd_set_test_tone_generator_status(
+ u8 test_tone_status
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_ENABLE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (test_tone_status > FMD_TST_TONE_ON_WO_SRC) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = test_tone_status;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_ENABLE,
+ CMD_TST_TONE_ENABLE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_CONNECT_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (left_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM ||
+ right_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = left_audio_mode;
+ parameters[1] = right_audio_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_CONNECT,
+ CMD_TST_TONE_CONNECT_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_SET_PARAMS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (tone_gen > FMD_TST_TONE_2 ||
+ waveform > FMD_TST_TONE_PULSE ||
+ frequency > 0x7FFF ||
+ volume > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = tone_gen;
+ parameters[1] = frequency;
+ parameters[2] = volume;
+ parameters[3] = phase_offset;
+ parameters[4] = dc;
+ parameters[5] = waveform;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_SET_PARAMS,
+ CMD_TST_TONE_SET_PARAMS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_limiter_setcontrol(
+ u16 audio_deviation,
+ u16 notification_hold_off_time
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (audio_deviation < MIN_AUDIO_DEVIATION ||
+ audio_deviation > MAX_AUDIO_DEVIATION ||
+ notification_hold_off_time > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = audio_deviation;
+ parameters[1] = notification_hold_off_time;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_LIMITER_SETCONTROL,
+ CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
+
+module_param(cg2900_fm_debug_level, ushort, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(cg2900_fm_debug_level, "cg2900_fm_debug_level: "
+ " *1: Only Error Logs* "
+ " 2: Info Logs "
+ " 3: Debug Logs "
+ " 4: HCI Logs");
+