summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorRicardo Perez Olivares <x0081762@ti.com>2010-08-12 18:53:03 -0500
committerRicardo Perez Olivares <x0081762@ti.com>2010-08-12 18:53:03 -0500
commitc7d99c33566df93e7cc546d319086f1797790c5d (patch)
tree0be5b20bc2225b1566d94d3fa58fe2d8ccc2a919 /drivers
parent507a06f776aea2069ce77236d3cf7cde42a9cfe6 (diff)
parent018ebb1c1573a1184bd2ace83abb6b8cfb37008c (diff)
Merge branch 'btfm_next' of git://dev.omapzoom.org/pub/scm/raja/L24x-btfm into L24x9
Diffstat (limited to 'drivers')
-rw-r--r--drivers/staging/ti-st/Kconfig10
-rw-r--r--drivers/staging/ti-st/Makefile2
-rw-r--r--drivers/staging/ti-st/TODO13
-rw-r--r--drivers/staging/ti-st/bt_drv.c29
-rw-r--r--drivers/staging/ti-st/fmdrv.h225
-rw-r--r--drivers/staging/ti-st/fmdrv_common.c2134
-rw-r--r--drivers/staging/ti-st/fmdrv_common.h459
-rw-r--r--drivers/staging/ti-st/fmdrv_rx.c812
-rw-r--r--drivers/staging/ti-st/fmdrv_rx.h56
-rw-r--r--drivers/staging/ti-st/fmdrv_v4l2.c557
-rw-r--r--drivers/staging/ti-st/fmdrv_v4l2.h32
-rw-r--r--drivers/staging/ti-st/st.h68
-rw-r--r--drivers/staging/ti-st/st_core.c208
-rw-r--r--drivers/staging/ti-st/st_core.h74
-rw-r--r--drivers/staging/ti-st/st_kim.c353
-rw-r--r--drivers/staging/ti-st/st_kim.h94
-rw-r--r--drivers/staging/ti-st/st_ll.c15
-rw-r--r--drivers/staging/ti-st/st_ll.h9
-rw-r--r--drivers/staging/ti-st/sysfs-uim12
19 files changed, 4766 insertions, 396 deletions
diff --git a/drivers/staging/ti-st/Kconfig b/drivers/staging/ti-st/Kconfig
index 3ab204ddc29d..4019c15326cd 100644
--- a/drivers/staging/ti-st/Kconfig
+++ b/drivers/staging/ti-st/Kconfig
@@ -4,7 +4,7 @@
#
menu "Texas Instruments shared transport line discipline"
config TI_ST
- tristate "shared transport core driver"
+ tristate "Shared transport core driver"
depends on RFKILL
select FW_LOADER
help
@@ -22,4 +22,12 @@ config ST_BT
This enables the Bluetooth driver for TI BT/FM/GPS combo devices.
This makes use of shared transport line discipline core driver to
communicate with the BT core of the combo chip.
+
+config ST_FM
+ tristate "fm driver for ST"
+ select TI_ST
+ help
+ This enables the FM driver for TI BT/FM/GPS combo devices
+ This makes use of shared transport line discipline core driver to
+ communicate with the FM core of the combo chip.
endmenu
diff --git a/drivers/staging/ti-st/Makefile b/drivers/staging/ti-st/Makefile
index 0167d1d2c255..e6af3f1f0867 100644
--- a/drivers/staging/ti-st/Makefile
+++ b/drivers/staging/ti-st/Makefile
@@ -5,3 +5,5 @@
obj-$(CONFIG_TI_ST) += st_drv.o
st_drv-objs := st_core.o st_kim.o st_ll.o
obj-$(CONFIG_ST_BT) += bt_drv.o
+obj-$(CONFIG_ST_FM) += fm_drv.o
+fm_drv-objs := fmdrv_common.o fmdrv_rx.o fmdrv_v4l2.o
diff --git a/drivers/staging/ti-st/TODO b/drivers/staging/ti-st/TODO
index 2c4fe583901d..ebfd6bb60176 100644
--- a/drivers/staging/ti-st/TODO
+++ b/drivers/staging/ti-st/TODO
@@ -1,17 +1,6 @@
TODO:
-1. A per-device/tty port context required to support multiple devices
-on same platform.
-
-2. REMOVE the sysfs entry PID passing mechanism, since there should
-be a better way to request user-space to install line discipline.
-
-3. Re-view/Re-work on the locking.
-
-4. Re-structure to make the ldisc driver more generic for chipsets which mux
-multiple connectivity (BT, FM, GPS) upon 1 TTY port.
-
-5. Step up and maintain this driver to ensure that it continues
+1. Step up and maintain this driver to ensure that it continues
to work. Having the hardware for this is pretty much a
requirement. If this does not happen, the will be removed in
the 2.6.35 kernel release.
diff --git a/drivers/staging/ti-st/bt_drv.c b/drivers/staging/ti-st/bt_drv.c
index d8420b5c91fa..61ae98833b17 100644
--- a/drivers/staging/ti-st/bt_drv.c
+++ b/drivers/staging/ti-st/bt_drv.c
@@ -80,31 +80,33 @@ static inline void hci_st_tx_complete(struct hci_st *hst, int pkt_type)
* status.hci_st_open() function will wait for signal from this
* API when st_register() function returns ST_PENDING.
*/
-static void hci_st_registration_completion_cb(char data)
+static void hci_st_registration_completion_cb(void *priv_data, char data)
{
+ struct hci_st *lhst = (struct hci_st *)priv_data;
BTDRV_API_START();
/* hci_st_open() function needs value of 'data' to know
* the registration status(success/fail),So have a back
* up of it.
*/
- hst->streg_cbdata = data;
+ lhst->streg_cbdata = data;
/* Got a feedback from ST for BT driver registration
* request.Wackup hci_st_open() function to continue
* it's open operation.
*/
- complete(&hst->wait_for_btdrv_reg_completion);
+ complete(&lhst->wait_for_btdrv_reg_completion);
BTDRV_API_EXIT(0);
}
/* Called by Shared Transport layer when receive data is
* available */
-static long hci_st_receive(struct sk_buff *skb)
+static long hci_st_receive(void *priv_data, struct sk_buff *skb)
{
int err;
int len;
+ struct hci_st *lhst = (struct hci_st *)priv_data;
BTDRV_API_START();
@@ -116,13 +118,13 @@ static long hci_st_receive(struct sk_buff *skb)
BTDRV_API_EXIT(-EFAULT);
return -EFAULT;
}
- if (!hst) {
+ if (!lhst) {
kfree_skb(skb);
BT_DRV_ERR("Invalid hci_st memory,freeing SKB");
BTDRV_API_EXIT(-EFAULT);
return -EFAULT;
}
- if (!test_bit(BT_DRV_RUNNING, &hst->flags)) {
+ if (!test_bit(BT_DRV_RUNNING, &lhst->flags)) {
kfree_skb(skb);
BT_DRV_ERR("Device is not running,freeing SKB");
BTDRV_API_EXIT(-EINVAL);
@@ -130,7 +132,7 @@ static long hci_st_receive(struct sk_buff *skb)
}
len = skb->len;
- skb->dev = (struct net_device *)hst->hdev;
+ skb->dev = (struct net_device *)lhst->hdev;
/* Forward skb to HCI CORE layer */
err = hci_recv_frame(skb);
@@ -141,7 +143,7 @@ static long hci_st_receive(struct sk_buff *skb)
BTDRV_API_EXIT(err);
return err;
}
- hst->hdev->stat.byte_rx += len;
+ lhst->hdev->stat.byte_rx += len;
BTDRV_API_EXIT(0);
return 0;
@@ -189,9 +191,14 @@ static int hci_st_open(struct hci_dev *hdev)
* make it as NULL */
hci_st_proto.write = NULL;
+ /* send in the hst to be received at registration complete callback
+ * and during st's receive
+ */
+ hci_st_proto.priv_data = hst;
+
/* Register with ST layer */
err = st_register(&hci_st_proto);
- if (err == ST_ERR_PENDING) {
+ if (err == -EINPROGRESS) {
/* Prepare wait-for-completion handler data structures.
* Needed to syncronize this and st_registration_completion_cb()
* functions.
@@ -232,7 +239,7 @@ static int hci_st_open(struct hci_dev *hdev)
return -EAGAIN;
}
err = 0;
- } else if (err == ST_ERR_FAILURE) {
+ } else if (err == -1) {
BT_DRV_ERR("st_register failed %d", err);
BTDRV_API_EXIT(-EAGAIN);
return -EAGAIN;
@@ -280,7 +287,7 @@ static int hci_st_close(struct hci_dev *hdev)
/* Unregister from ST layer */
if (test_and_clear_bit(BT_ST_REGISTERED, &hst->flags)) {
err = st_unregister(ST_BT);
- if (err != ST_SUCCESS) {
+ if (err != 0) {
BT_DRV_ERR("st_unregister failed %d", err);
BTDRV_API_EXIT(-EBUSY);
return -EBUSY;
diff --git a/drivers/staging/ti-st/fmdrv.h b/drivers/staging/ti-st/fmdrv.h
new file mode 100644
index 000000000000..d56057056166
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv.h
@@ -0,0 +1,225 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ *
+ * Common header for all FM driver sub-modules.
+ *
+ * Copyright (C) 2009 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _FM_DRV_H
+#define _FM_DRV_H
+
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <linux/timer.h>
+#include <linux/version.h>
+
+#define FM_DRV_VERSION "0.01"
+/* Should match with FM_DRV_VERSION */
+#define FM_DRV_RADIO_VERSION KERNEL_VERSION(0, 0, 1)
+#define FM_DRV_NAME "ti_fmdrv"
+#define FM_DRV_CARD_SHORT_NAME "TI FM Radio"
+#define FM_DRV_CARD_LONG_NAME "Texas Instruments FM Radio"
+
+/* Flag info */
+#define FM_INTTASK_RUNNING 0
+#define FM_INTTASK_SCHEDULE_PENDING 1
+#define FM_FIRMWARE_DW_INPROGRESS 2
+#define FM_CORE_READY 3
+#define FM_CORE_TRANSPORT_READY 4
+#define FM_AF_SWITCH_INPROGRESS 5
+#define FM_CORE_TX_XMITING 6
+
+#define FM_DRV_TX_TIMEOUT (5*HZ) /* 5 seconds */
+#define FM_DRV_RX_SEEK_TIMEOUT (20*HZ) /* 20 seconds */
+
+#define NO_OF_ENTRIES_IN_ARRAY(array) (sizeof(array) / sizeof(array[0]))
+
+enum {
+ FM_MODE_OFF,
+ FM_MODE_TX,
+ FM_MODE_RX,
+ FM_MODE_ENTRY_MAX
+};
+
+#define FM_RX_RDS_INFO_FIELD_MAX 8 /* 4 Group * 2 Bytes */
+
+/* RX RDS data format */
+struct fm_rdsdata_format {
+ union {
+ struct {
+ unsigned char rdsbuff[FM_RX_RDS_INFO_FIELD_MAX];
+ } groupdatabuff;
+ struct {
+ unsigned short pidata;
+ unsigned char block_b_byte1;
+ unsigned char block_b_byte2;
+ unsigned char block_c_byte1;
+ unsigned char block_c_byte2;
+ unsigned char block_d_byte1;
+ unsigned char block_d_byte2;
+ } groupgeneral;
+ struct {
+ unsigned short pidata;
+ unsigned char block_b_byte1;
+ unsigned char block_b_byte2;
+ unsigned char firstaf;
+ unsigned char secondaf;
+ unsigned char firstpsbyte;
+ unsigned char secondpsbyte;
+ } group0A;
+
+ struct {
+ unsigned short pidata;
+ unsigned char block_b_byte1;
+ unsigned char block_b_byte2;
+ unsigned short pidata2;
+ unsigned char firstpsbyte;
+ unsigned char secondpsbyte;
+ } group0B;
+ } rdsdata;
+};
+
+/* FM region (Europe/US, Japan) info */
+struct region_info {
+ unsigned int channel_spacing;
+ unsigned int bottom_frequency;
+ unsigned int top_frequency;
+ unsigned char region_index;
+};
+
+typedef void (*int_handler_prototype) (void *);
+
+/* FM Interrupt processing related info */
+struct fm_irq {
+ unsigned char stage_index;
+ unsigned short flag; /* FM interrupt flag */
+ unsigned short mask; /* FM interrupt mask */
+ /* Interrupt process timeout handler */
+ struct timer_list int_timeout_timer;
+ unsigned char irq_service_timeout_retry;
+ int_handler_prototype *fm_int_handlers;
+};
+
+/* RDS info */
+struct fm_rds {
+ unsigned char flag; /* RX RDS on/off status */
+ unsigned char last_block_index; /* Last received RDS block */
+
+ /* RDS buffer */
+ wait_queue_head_t read_queue;
+ unsigned int buf_size; /* Size is always multiple of 3 */
+ unsigned int wr_index;
+ unsigned int rd_index;
+ unsigned char *buffer;
+};
+
+#define FM_RDS_MAX_AF_LIST 25
+
+/* Current RX channel Alternate Frequency cache.
+ * This info is used to switch to other freq (AF)
+ * when current channel signal strengh is below RSSI threshold.
+ */
+struct tuned_station_info {
+ unsigned short picode;
+ unsigned int af_cache[FM_RDS_MAX_AF_LIST];
+ unsigned char no_of_items_in_afcache;
+ unsigned char af_list_max;
+};
+
+/* FM RX mode info */
+struct fm_rx {
+ struct region_info region; /* Current selected band */
+ unsigned int curr_freq; /* Current RX frquency */
+ unsigned char curr_mute_mode; /* Current mute mode */
+ /* RF dependent soft mute mode */
+ unsigned char curr_rf_depend_mute;
+ unsigned short curr_volume; /* Current volume level */
+ short curr_rssi_threshold; /* Current RSSI threshold level */
+ /* Holds the index of the current AF jump */
+ unsigned char cur_afjump_index;
+ /* Will hold the frequency before the jump */
+ unsigned int freq_before_jump;
+ unsigned char rds_mode; /* RDS operation mode (RDS/RDBS) */
+ unsigned char af_mode; /* Alternate frequency on/off */
+ struct tuned_station_info cur_station_info;
+ struct fm_rds rds;
+};
+
+/*
+ * FM TX RDS data
+ *
+ * @ text_type: is the text following PS or RT
+ * @ text: radio text string which could either be PS or RT
+ * @ af_freq: alternate frequency for Tx
+ * TODO: to be declared in application
+ */
+struct tx_rds {
+ unsigned char text_type;
+ unsigned char text[25];
+ unsigned int af_freq;
+};
+/*
+ * FM TX global data
+ *
+ * @ pwr_lvl: Power Level of the Transmission from mixer control
+ * @ xmit_state: Transmission state = Updated locally upon Start/Stop
+ * @ audio_io: i2S/Analog
+ * @ tx_frq: Transmission frequency
+ */
+struct fmtx_data {
+ unsigned char pwr_lvl;
+ unsigned char xmit_state;
+ unsigned char audio_io;
+ unsigned long tx_frq;
+ struct tx_rds rds;
+};
+
+/* FM driver operation structure */
+struct fmdrv_ops {
+ struct video_device *radio_dev; /* V4L2 video device pointer */
+ struct snd_card *card; /* Card which holds FM mixer controls */
+ unsigned short asci_id;
+ spinlock_t rds_buff_lock; /* To protect access to RDS buffer */
+ spinlock_t resp_skb_lock; /* To protect access to received SKB */
+
+ long flag; /* FM driver state machine info */
+ char streg_cbdata; /* status of ST registration */
+
+ struct sk_buff_head rx_q; /* RX queue */
+ struct tasklet_struct rx_task; /* RX Tasklet */
+
+ struct sk_buff_head tx_q; /* TX queue */
+ struct tasklet_struct tx_task; /* TX Tasklet */
+ unsigned long last_tx_jiffies; /* Timestamp of last pkt sent */
+ atomic_t tx_cnt; /* Number of packets can send at a time */
+
+ struct sk_buff *response_skb; /* Response from the chip */
+ /* Main task completion handler */
+ struct completion maintask_completion;
+ /* Opcode of last command sent to the chip */
+ unsigned char last_sent_pkt_opcode;
+ /* Handler used for wakeup when response packet is received */
+ struct completion *response_completion;
+ struct fm_irq irq_info;
+ unsigned char curr_fmmode; /* Current FM chip mode (TX, RX, OFF) */
+ struct fm_rx rx; /* FM receiver info */
+ struct fmtx_data tx_data;
+};
+#endif
diff --git a/drivers/staging/ti-st/fmdrv_common.c b/drivers/staging/ti-st/fmdrv_common.c
new file mode 100644
index 000000000000..815203188910
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_common.c
@@ -0,0 +1,2134 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ *
+ * This sub-module of FM driver is common for FM RX and TX
+ * functionality. This module is responsible for:
+ * 1) Forming group of Channel-8 commands to perform particular
+ * functionality (eg., frequency set require more than
+ * one Channel-8 command to be sent to the chip).
+ * 2) Sending each Channel-8 command to the chip and reading
+ * response back over Shared Transport.
+ * 3) Managing TX and RX Queues and Tasklets.
+ * 4) Handling FM Interrupt packet and taking appropriate action.
+ * 5) Loading FM firmware to the chip (common, FM TX, and FM RX
+ * firmware files based on mode selection)
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include "fmdrv.h"
+#include "fmdrv_v4l2.h"
+#include "fmdrv_common.h"
+#include "st.h"
+#include "fmdrv_rx.h"
+/* TODO: Enable when FM TX is supported */
+/* #include "fmdrv_tx.h" */
+
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
+
+/* FM chip register table */
+static struct fm_reg_table fm_reg_info[] = {
+ /* ----- FM RX registers -------*/
+ /* opcode, type(rd/wr), reg name */
+ {0x00, REG_RD, "STEREO_GET"},
+ {0x01, REG_RD, "RSSI_LVL_GET"},
+ {0x02, REG_RD, "IF_COUNT_GET"},
+ {0x03, REG_RD, "FLAG_GET"},
+ {0x04, REG_RD, "RDS_SYNC_GET"},
+ {0x05, REG_RD, "RDS_DATA_GET"},
+ {0x0a, REG_WR, "FREQ_SET"},
+ {0x0a, REG_RD, "FREQ_GET"},
+ {0x0b, REG_WR, "AF_FREQ_SET"},
+ {0x0b, REG_RD, "AF_FREQ_GET"},
+ {0x0c, REG_WR, "MOST_MODE_SET"},
+ {0x0c, REG_RD, "MOST_MODE_GET"},
+ {0x0d, REG_WR, "MOST_BLEND_SET"},
+ {0x0d, REG_RD, "MOST_BLEND_GET"},
+ {0x0e, REG_WR, "DEMPH_MODE_SET"},
+ {0x0e, REG_RD, "DEMPH_MODE_GET"},
+ {0x0f, REG_WR, "SEARCH_LVL_SET"},
+ {0x0f, REG_RD, "SEARCH_LVL_GET"},
+ {0x10, REG_WR, "RX_BAND_SET"},
+ {0x10, REG_RD, "RX_BAND_GET"},
+ {0x11, REG_WR, "MUTE_STATUS_SET"},
+ {0x11, REG_RD, "MUTE_STATUS_GET"},
+ {0x12, REG_WR, "RDS_PAUSE_LVL_SET"},
+ {0x12, REG_RD, "RDS_PAUSE_LVL_GET"},
+ {0x13, REG_WR, "RDS_PAUSE_DUR_SET"},
+ {0x13, REG_RD, "RDS_PAUSE_DUR_GET"},
+ {0x14, REG_WR, "RDS_MEM_SET"},
+ {0x14, REG_RD, "RDS_MEM_GET"},
+ {0x15, REG_WR, "RDS_BLK_B_SET"},
+ {0x15, REG_RD, "RDS_BLK_B_GET"},
+ {0x16, REG_WR, "RDS_MSK_B_SET"},
+ {0x16, REG_RD, "RDS_MSK_B_GET"},
+ {0x17, REG_WR, "RDS_PI_MASK_SET"},
+ {0x17, REG_RD, "RDS_PI_MASK_GET"},
+ {0x18, REG_WR, "RDS_PI_SET"},
+ {0x18, REG_RD, "RDS_PI_GET"},
+ {0x19, REG_WR, "RDS_SYSTEM_SET"},
+ {0x19, REG_RD, "RDS_SYSTEM_GET"},
+ {0x1a, REG_WR, "INT_MASK_SET"},
+ {0x1a, REG_RD, "INT_MASK_GET"},
+ {0x1b, REG_WR, "SRCH_DIR_SET"},
+ {0x1b, REG_RD, "SRCH_DIR_GET"},
+ {0x1c, REG_WR, "VOLUME_SET"},
+ {0x1c, REG_RD, "VOLUME_GET"},
+ {0x1d, REG_WR, "AUDIO_ENABLE(SET)"},
+ {0x1d, REG_RD, "AUDIO_ENABLE(GET)"},
+ {0x1e, REG_WR, "PCM_MODE_SET"},
+ {0x1e, REG_RD, "PCM_MODE_SET"},
+ {0x1f, REG_WR, "I2S_MD_CFG_SET"},
+ {0x1f, REG_RD, "I2S_MD_CFG_GET"},
+ {0x20, REG_WR, "POWER_SET"},
+ {0x20, REG_RD, "POWER_GET"},
+ {0x21, REG_WR, "INTx_CONFIG_SET"},
+ {0x21, REG_RD, "INTx_CONFIG_GET"},
+ {0x22, REG_WR, "PULL_EN_SET"},
+ {0x22, REG_RD, "PULL_EN_GET"},
+ {0x23, REG_WR, "HILO_SET"},
+ {0x23, REG_RD, "HILO_GET"},
+ {0x24, REG_WR, "SWITCH2FREF"},
+ {0x25, REG_WR, "FREQ_DRIFT_REP"},
+ {0x28, REG_RD, "PCE_GET"},
+ {0x29, REG_RD, "FIRM_VER_GET"},
+ {0x2a, REG_RD, "ASIC_VER_GET"},
+ {0x2b, REG_RD, "ASIC_ID_GET"},
+ {0x2c, REG_RD, "MAIN_ID_GET"},
+ {0x2d, REG_WR, "TUNER_MODE_SET"},
+ {0x2e, REG_WR, "STOP_SEARCH"},
+ {0x2f, REG_WR, "RDS_CNTRL_SET"},
+ {0x64, REG_WR, "WR_HW_REG"},
+ {0x65, REG_WR, "CODE_DOWNLOAD"},
+ {0x66, REG_WR, "RESET"},
+ {0xfe, REG_WR, "FM_POWER_MODE(SET)"},
+ {0xff, REG_RD, "FM_INTERRUPT"},
+
+ /* --- FM TX registers ------ */
+ {0x37, REG_WR, "CHANL_SET"},
+ {0x37, REG_RD, "CHANL_GET"},
+ {0x38, REG_WR, "CHANL_BW_SET"},
+ {0x38, REG_RD, "CHANL_BW_GET"},
+ {0x87, REG_WR, "REF_SET"},
+ {0x87, REG_RD, "REF_GET"},
+ {0x5a, REG_WR, "POWER_ENB_SET"},
+ {0x3a, REG_WR, "POWER_ATT_SET"},
+ {0x3a, REG_RD, "POWER_ATT_GET"},
+ {0x3b, REG_WR, "POWER_LEL_SET"},
+ {0x3b, REG_RD, "POWER_LEL_GET"},
+ {0x3c, REG_WR, "AUDIO_DEV_SET"},
+ {0x3c, REG_RD, "AUDIO_DEV_GET"},
+ {0x3d, REG_WR, "PILOT_DEV_SET"},
+ {0x3d, REG_RD, "PILOT_DEV_GET"},
+ {0x3e, REG_WR, "RDS_DEV_SET"},
+ {0x3e, REG_RD, "RDS_DEV_GET"},
+ {0x5b, REG_WR, "PUPD_SET"},
+ {0x3f, REG_WR, "AUDIO_IO_SET"},
+ {0x40, REG_WR, "PREMPH_SET"},
+ {0x40, REG_RD, "PREMPH_GET"},
+ {0x41, REG_WR, "TX_BAND_SET"},
+ {0x41, REG_RD, "TX_BAND_GET"},
+ {0x42, REG_WR, "MONO_SET"},
+ {0x42, REG_RD, "MONO_GET"},
+ {0x5C, REG_WR, "MUTE"},
+ {0x43, REG_WR, "MPX_LMT_ENABLE"},
+ {0x06, REG_RD, "LOCK_GET"},
+ {0x5d, REG_WR, "REF_ERR_SET"},
+ {0x44, REG_WR, "PI_SET"},
+ {0x44, REG_RD, "PI_GET"},
+ {0x45, REG_WR, "TYPE_SET"},
+ {0x45, REG_RD, "TYPE_GET"},
+ {0x46, REG_WR, "PTY_SET"},
+ {0x46, REG_RD, "PTY_GET"},
+ {0x47, REG_WR, "AF_SET"},
+ {0x47, REG_RD, "AF_GET"},
+ {0x48, REG_WR, "DISPLAY_SIZE_SET"},
+ {0x48, REG_RD, "DISPLAY_SIZE_GET"},
+ {0x49, REG_WR, "RDS_MODE_SET"},
+ {0x49, REG_RD, "RDS_MODE_GET"},
+ {0x4a, REG_WR, "DISPLAY_MODE_SET"},
+ {0x4a, REG_RD, "DISPLAY_MODE_GET"},
+ {0x62, REG_WR, "LENGHT_SET"},
+ {0x4b, REG_RD, "LENGHT_GET"},
+ {0x4c, REG_WR, "TOGGLE_AB_SET"},
+ {0x4c, REG_RD, "TOGGLE_AB_GET"},
+ {0x4d, REG_WR, "RDS_REP_SET"},
+ {0x4d, REG_RD, "RDS_REP_GET"},
+ {0x63, REG_WR, "RDS_DATA_SET"},
+ {0x5e, REG_WR, "RDS_DATA_ENB"},
+ {0x4e, REG_WR, "TA_SET"},
+ {0x4e, REG_RD, "TA_GET"},
+ {0x4f, REG_WR, "TP_SET"},
+ {0x4f, REG_RD, "TP_GET"},
+ {0x50, REG_WR, "DI_SET"},
+ {0x50, REG_RD, "DI_GET"},
+ {0x51, REG_WR, "MS_SET"},
+ {0x51, REG_RD, "MS_GET"},
+ {0x52, REG_WR, "PS_SCROLL_SPEED_SET"},
+ {0x52, REG_RD, "PS_SCROLL_SPEED_GET"},
+};
+
+/* Region info */
+static struct region_info region_configs[] = {
+ /* Europe/US */
+ {
+ .channel_spacing = 50, /* 50 KHz */
+ .bottom_frequency = 87500, /* 87.5 MHz */
+ .top_frequency = 108000, /* 108 MHz */
+ .region_index = 0,
+ },
+ /* Japan */
+ {
+ .channel_spacing = 50, /* 50 KHz */
+ .bottom_frequency = 76000, /* 76 MHz */
+ .top_frequency = 90000, /* 90 MHz */
+ .region_index = 1,
+ },
+};
+
+/* Band selection */
+static unsigned char default_radio_region; /* Europe/US */
+module_param(default_radio_region, byte, 0);
+MODULE_PARM_DESC(default_radio_region, "Region: 0=Europe/US, 1=Japan");
+
+/* RDS buffer blocks */
+static unsigned int default_rds_buf = 300;
+module_param(default_rds_buf, uint, 0444);
+MODULE_PARM_DESC(rds_buf, "RDS buffer entries");
+
+/* Radio Nr */
+static int radio_nr = -1;
+module_param(radio_nr, int, 0444);
+MODULE_PARM_DESC(radio_nr, "Radio Nr");
+
+/* FM irq handlers forward declaration */
+static void fm_irq_send_flag_getcmd(void *);
+static void fm_irq_handle_flag_getcmd_resp(void *);
+static void fm_irq_handle_hw_malfunction(void *);
+static void fm_irq_handle_rds_start(void *);
+static void fm_irq_send_rdsdata_getcmd(void *);
+static void fm_irq_handle_rdsdata_getcmd_resp(void *);
+static void fm_irq_handle_rds_finish(void *);
+static void fm_irq_handle_tune_op_ended(void *);
+static void fm_irq_handle_power_enb(void *);
+static void fm_irq_handle_low_rssi_start(void *);
+static void fm_irq_afjump_set_pi(void *);
+static void fm_irq_handle_set_pi_resp(void *);
+static void fm_irq_afjump_set_pimask(void *);
+static void fm_irq_handle_set_pimask_resp(void *);
+static void fm_irq_afjump_setfreq(void *);
+static void fm_irq_handle_setfreq_resp(void *);
+static void fm_irq_afjump_enableint(void *);
+static void fm_irq_afjump_enableint_resp(void *);
+static void fm_irq_start_afjump(void *);
+static void fm_irq_handle_start_afjump_resp(void *);
+static void fm_irq_afjump_rd_freq(void *);
+static void fm_irq_afjump_rd_freq_resp(void *);
+static void fm_irq_handle_low_rssi_finish(void *);
+static void fm_irq_send_intmsk_cmd(void *);
+static void fm_irq_handle_intmsk_cmd_resp(void *);
+
+/* When FM common module receives interrupt packet, following handlers
+ * will be executed one after another to service the interrupt(s)
+ */
+enum fmc_irq_handler_index{
+ FM_SEND_FLAG_GETCMD_INDEX,
+ FM_HANDLE_FLAG_GETCMD_RESP_INDEX,
+
+ /* HW malfunction irq handler */
+ FM_HW_MAL_FUNC_INDEX,
+
+ /* RDS threshold reached irq handler */
+ FM_RDS_START_INDEX,
+ FM_RDS_SEND_RDS_GETCMD_INDEX,
+ FM_RDS_HANDLE_RDS_GETCMD_RESP_INDEX,
+ FM_RDS_FINISH_INDEX,
+
+ /* Tune operation ended irq handler */
+ FM_HW_TUNE_OP_ENDED_INDEX,
+
+ /* TX power enable irq handler */
+ FM_HW_POWER_ENB_INDEX,
+
+ /* Low RSSI irq handler */
+ FM_LOW_RSSI_START_INDEX,
+ FM_AF_JUMP_SETPI_INDEX,
+ FM_AF_JUMP_HANDLE_SETPI_RESP_INDEX,
+ FM_AF_JUMP_SETPI_MASK_INDEX,
+ FM_AF_JUMP_HANDLE_SETPI_MASK_RESP_INDEX,
+ FM_AF_JUMP_SET_AF_FREQ_INDEX,
+ FM_AF_JUMP_HENDLE_SET_AFFREQ_RESP_INDEX,
+ FM_AF_JUMP_ENABLE_INT_INDEX,
+ FM_AF_JUMP_ENABLE_INT_RESP_INDEX,
+ FM_AF_JUMP_START_AFJUMP_INDEX,
+ FM_AF_JUMP_HANDLE_START_AFJUMP_RESP_INDEX,
+ FM_AF_JUMP_RD_FREQ_INDEX,
+ FM_AF_JUMP_RD_FREQ_RESP_INDEX,
+ FM_LOW_RSSI_FINISH_INDEX,
+
+ /* Interrupt process post action */
+ FM_SEND_INTMSK_CMD_INDEX,
+ FM_HANDLE_INTMSK_CMD_RESP_INDEX,
+};
+
+/* FM interrupt handler table */
+static int_handler_prototype g_IntHandlerTable[] = {
+ fm_irq_send_flag_getcmd,
+ fm_irq_handle_flag_getcmd_resp,
+ fm_irq_handle_hw_malfunction,
+ fm_irq_handle_rds_start, /* RDS threshold reached irq handler */
+ fm_irq_send_rdsdata_getcmd,
+ fm_irq_handle_rdsdata_getcmd_resp,
+ fm_irq_handle_rds_finish,
+ fm_irq_handle_tune_op_ended,
+ fm_irq_handle_power_enb, /* TX power enable irq handler */
+ fm_irq_handle_low_rssi_start,
+ fm_irq_afjump_set_pi,
+ fm_irq_handle_set_pi_resp,
+ fm_irq_afjump_set_pimask,
+ fm_irq_handle_set_pimask_resp,
+ fm_irq_afjump_setfreq,
+ fm_irq_handle_setfreq_resp,
+ fm_irq_afjump_enableint,
+ fm_irq_afjump_enableint_resp,
+ fm_irq_start_afjump,
+ fm_irq_handle_start_afjump_resp,
+ fm_irq_afjump_rd_freq,
+ fm_irq_afjump_rd_freq_resp,
+ fm_irq_handle_low_rssi_finish,
+ fm_irq_send_intmsk_cmd, /* Interrupt process post action */
+ fm_irq_handle_intmsk_cmd_resp
+};
+
+long (*g_st_write) (struct sk_buff *skb);
+static struct completion wait_for_fmdrv_reg_comp;
+
+#ifdef FM_DUMP_TXRX_PKT
+ /* To dump outgoing FM Channel-8 packets */
+inline void dump_tx_skb_data(struct sk_buff *skb)
+{
+ int len, len_org;
+ char index;
+ struct fm_cmd_msg_hdr *cmd_hdr;
+
+ cmd_hdr = (struct fm_cmd_msg_hdr *)skb->data;
+ printk(KERN_INFO "<<%shdr:%02x len:%02x opcode:%02x type:%s dlen:%02x",
+ fm_cb(skb)->completion ? " " : "*", cmd_hdr->header,
+ cmd_hdr->len, cmd_hdr->fm_opcode,
+ cmd_hdr->rd_wr ? "RD" : "WR", cmd_hdr->dlen);
+
+ len_org = skb->len - FM_CMD_MSG_HDR_SIZE;
+ if (len_org > 0) {
+ printk("\n data(%d): ", cmd_hdr->dlen);
+ len = min(len_org, 14);
+ for (index = 0; index < len; index++)
+ printk("%x ",
+ skb->data[FM_CMD_MSG_HDR_SIZE + index]);
+ printk("%s", (len_org > 14) ? ".." : "");
+ }
+ printk("\n");
+}
+
+ /* To dump incoming FM Channel-8 packets */
+inline void dump_rx_skb_data(struct sk_buff *skb)
+{
+ int len, len_org;
+ char index;
+ struct fm_event_msg_hdr *evt_hdr;
+
+ evt_hdr = (struct fm_event_msg_hdr *)skb->data;
+ printk(KERN_INFO ">> hdr:%02x len:%02x sts:%02x numhci:%02x "
+ "opcode:%02x type:%s dlen:%02x", evt_hdr->header, evt_hdr->len,
+ evt_hdr->status, evt_hdr->num_fm_hci_cmds, evt_hdr->fm_opcode,
+ (evt_hdr->rd_wr) ? "RD" : "WR", evt_hdr->dlen);
+
+ len_org = skb->len - FM_EVT_MSG_HDR_SIZE;
+ if (len_org > 0) {
+ printk("\n data(%d): ", evt_hdr->dlen);
+ len = min(len_org, 14);
+ for (index = 0; index < len; index++)
+ printk("%x ",
+ skb->data[FM_EVT_MSG_HDR_SIZE + index]);
+ printk("%s", (len_org > 14) ? ".." : "");
+ }
+ printk("\n");
+}
+#endif
+
+void fmc_update_region_info(struct fmdrv_ops *fmdev,
+ unsigned char region_to_set)
+{
+ memcpy(&fmdev->rx.region, &region_configs[region_to_set],
+ sizeof(struct region_info));
+}
+
+/* FM common sub-module will schedule this tasklet whenever it receives
+ * FM packet from ST driver.
+ */
+static void __recv_tasklet(unsigned long arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct fm_event_msg_hdr *fm_evt_hdr;
+ struct sk_buff *skb;
+ unsigned char num_fm_hci_cmds;
+ unsigned long flags;
+
+ fmdev = (struct fmdrv_ops *)arg;
+ /* Process all packets in the RX queue */
+ while ((skb = skb_dequeue(&fmdev->rx_q))) {
+ if (skb->len < sizeof(struct fm_event_msg_hdr)) {
+ pr_err("(fmdrv): skb(%p) has only %d bytes"
+ "atleast need %d bytes to decode",
+ skb, skb->len,
+ sizeof(struct fm_event_msg_hdr));
+ kfree_skb(skb);
+ continue;
+ }
+
+ fm_evt_hdr = (void *)skb->data;
+ num_fm_hci_cmds = fm_evt_hdr->num_fm_hci_cmds;
+
+ /* FM interrupt packet? */
+ if (fm_evt_hdr->fm_opcode == fm_reg_info[FM_INTERRUPT].opcode) {
+ /* FM interrupt handler started already? */
+ if (!test_bit(FM_INTTASK_RUNNING, &fmdev->flag)) {
+ set_bit(FM_INTTASK_RUNNING, &fmdev->flag);
+ if (fmdev->irq_info.stage_index != 0) {
+ pr_err("(fmdrv): Invalid stage index,"
+ "resetting to zero");
+ fmdev->irq_info.stage_index = 0;
+ }
+
+ /* Execute first function
+ * in interrupt handler table
+ */
+ fmdev->irq_info.fm_int_handlers
+ [fmdev->irq_info.stage_index](fmdev);
+ } else {
+ set_bit(FM_INTTASK_SCHEDULE_PENDING,
+ &fmdev->flag);
+ }
+ kfree_skb(skb);
+ }
+ /* Anyone waiting for this with completion handler? */
+ else if (fm_evt_hdr->fm_opcode == fmdev->last_sent_pkt_opcode &&
+ fmdev->response_completion != NULL) {
+ if (fmdev->response_skb != NULL)
+ pr_err("(fmdrv): Response SKB ptr not NULL");
+
+ spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
+ fmdev->response_skb = skb;
+ spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
+ complete(fmdev->response_completion);
+
+ fmdev->response_completion = NULL;
+ atomic_set(&fmdev->tx_cnt, 1);
+ }
+ /* Is this for interrupt handler? */
+ else if (fm_evt_hdr->fm_opcode == fmdev->last_sent_pkt_opcode &&
+ fmdev->response_completion == NULL) {
+ if (fmdev->response_skb != NULL)
+ pr_err("(fmdrv): Response SKB ptr not NULL");
+
+ spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
+ fmdev->response_skb = skb;
+ spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
+
+ /* Execute interrupt handler where state index points */
+ fmdev->irq_info.fm_int_handlers
+ [fmdev->irq_info.stage_index](fmdev);
+
+ kfree_skb(skb);
+ atomic_set(&fmdev->tx_cnt, 1);
+ } else {
+ pr_err("(fmdrv): Nobody claimed SKB(%p),purging", skb);
+ }
+
+ /* Check flow control field.
+ * If Num_FM_HCI_Commands field is not zero,
+ * schedule FM TX tasklet.
+ */
+ if (num_fm_hci_cmds && atomic_read(&fmdev->tx_cnt)) {
+ if (!skb_queue_empty(&fmdev->tx_q))
+ tasklet_schedule(&fmdev->tx_task);
+ }
+ }
+}
+
+/* FM send tasklet: is scheduled when FM packet has to be sent to chip */
+static void __send_tasklet(unsigned long arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int len;
+
+ fmdev = (struct fmdrv_ops *)arg;
+ /* Check, is there any timeout happenned to last transmitted packet */
+ if (!atomic_read(&fmdev->tx_cnt) &&
+ ((jiffies - fmdev->last_tx_jiffies) > FM_DRV_TX_TIMEOUT)) {
+ pr_err("(fmdrv): TX timeout occurred");
+ atomic_set(&fmdev->tx_cnt, 1);
+ }
+ /* Send queued FM TX packets */
+ if (atomic_read(&fmdev->tx_cnt)) {
+ skb = skb_dequeue(&fmdev->tx_q);
+ if (skb) {
+ atomic_dec(&fmdev->tx_cnt);
+ fmdev->last_sent_pkt_opcode = fm_cb(skb)->fm_opcode;
+
+ if (fmdev->response_completion != NULL)
+ pr_err("(fmdrv): Response completion handler"
+ "is not NULL");
+
+ fmdev->response_completion = fm_cb(skb)->completion;
+
+ /* Write FM packet to ST driver */
+ len = g_st_write(skb);
+ if (len < 0) {
+ kfree_skb(skb);
+ fmdev->response_completion = NULL;
+ pr_err("(fmdrv): TX tasklet failed to send" \
+ "skb(%p)", skb);
+ atomic_set(&fmdev->tx_cnt, 1);
+ } else {
+ fmdev->last_tx_jiffies = jiffies;
+ }
+ }
+ }
+}
+
+/* Queues FM Channel-8 packet to FM TX queue and schedules FM TX tasklet for
+ * transmission */
+static int __fm_send_cmd(struct fmdrv_ops *fmdev, unsigned char fmreg_index,
+ void *payload, int payload_len,
+ struct completion *wait_completion)
+{
+ struct sk_buff *skb;
+ struct fm_cmd_msg_hdr *cmd_hdr;
+ int size;
+
+
+ if (fmreg_index >= FM_REG_MAX_ENTRIES) {
+ pr_err("(fmdrv): Invalid fm register index");
+ return -EINVAL;
+ }
+ if (test_bit(FM_FIRMWARE_DW_INPROGRESS, &fmdev->flag) &&
+ payload == NULL) {
+ pr_err("(fmdrv): Payload data is NULL during fw download");
+ return -EINVAL;
+ }
+ if (!test_bit(FM_FIRMWARE_DW_INPROGRESS, &fmdev->flag))
+ size =
+ FM_CMD_MSG_HDR_SIZE + ((payload == NULL) ? 0 : payload_len);
+ else
+ size = payload_len;
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb) {
+ pr_err("(fmdrv): No memory to create new SKB");
+ return -ENOMEM;
+ }
+ /* Don't fill FM header info for the commands which come from
+ * FM firmware file */
+ if (!test_bit(FM_FIRMWARE_DW_INPROGRESS, &fmdev->flag) ||
+ test_bit(FM_INTTASK_RUNNING, &fmdev->flag)) {
+ /* Fill command header info */
+ cmd_hdr =
+ (struct fm_cmd_msg_hdr *)skb_put(skb, FM_CMD_MSG_HDR_SIZE);
+ cmd_hdr->header = FM_PKT_LOGICAL_CHAN_NUMBER; /* 0x08 */
+ /* 3 (fm_opcode,rd_wr,dlen) + payload len) */
+ cmd_hdr->len = ((payload == NULL) ? 0 : payload_len) + 3;
+ /* FM opcode */
+ cmd_hdr->fm_opcode = fm_reg_info[fmreg_index].opcode;
+ /* read/write type */
+ cmd_hdr->rd_wr = fm_reg_info[fmreg_index].type;
+ cmd_hdr->dlen = payload_len;
+ fm_cb(skb)->fm_opcode = fm_reg_info[fmreg_index].opcode;
+ } else if (payload != NULL) {
+ fm_cb(skb)->fm_opcode = *((char *)payload + 2);
+ }
+ if (payload != NULL)
+ memcpy(skb_put(skb, payload_len), payload, payload_len);
+
+ fm_cb(skb)->completion = wait_completion;
+ skb_queue_tail(&fmdev->tx_q, skb);
+ tasklet_schedule(&fmdev->tx_task);
+
+ return 0;
+}
+
+/* Sends FM Channel-8 command to the chip and waits for the reponse */
+int fmc_send_cmd(struct fmdrv_ops *fmdev, unsigned char fmreg_index,
+ void *payload, int payload_len,
+ struct completion *wait_completion, void *reponse,
+ int *reponse_len)
+{
+ struct sk_buff *skb;
+ struct fm_event_msg_hdr *fm_evt_hdr;
+ unsigned long timeleft;
+ unsigned long flags;
+ int ret;
+
+ init_completion(wait_completion);
+ ret = __fm_send_cmd(fmdev, fmreg_index, payload, payload_len,
+ wait_completion);
+ if (ret < 0)
+ return ret;
+
+ timeleft = wait_for_completion_timeout(wait_completion,
+ FM_DRV_TX_TIMEOUT);
+ if (!timeleft) {
+ pr_err("(fmdrv): Timeout(%d sec),didn't get reg"
+ "completion signal from RX tasklet",
+ jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
+ return -ETIMEDOUT;
+ }
+ if (!fmdev->response_skb) {
+ pr_err("(fmdrv): Reponse SKB is missing ");
+ return -EFAULT;
+ }
+ spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
+ skb = fmdev->response_skb;
+ fmdev->response_skb = NULL;
+ spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
+
+ fm_evt_hdr = (void *)skb->data;
+ if (fm_evt_hdr->status != 0) {
+ pr_err("(fmdrv): Received event pkt status(%d) is not zero",
+ fm_evt_hdr->status);
+ kfree_skb(skb);
+ return -EIO;
+ }
+ /* Send reponse data to caller */
+ if (reponse != NULL && reponse_len != NULL && fm_evt_hdr->dlen) {
+ /* Skip header info and copy only response data */
+ skb_pull(skb, sizeof(struct fm_event_msg_hdr));
+ memcpy(reponse, skb->data, fm_evt_hdr->dlen);
+ *reponse_len = fm_evt_hdr->dlen;
+ } else if (reponse_len != NULL && fm_evt_hdr->dlen == 0) {
+ *reponse_len = 0;
+ }
+ kfree_skb(skb);
+ return 0;
+}
+
+/* --- Helper functions used in FM interrupt handlers ---*/
+static inline int __check_cmdresp_status(struct fmdrv_ops *fmdev,
+ struct sk_buff **skb)
+{
+ struct fm_event_msg_hdr *fm_evt_hdr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
+ *skb = fmdev->response_skb;
+ fmdev->response_skb = NULL;
+ spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
+
+ fm_evt_hdr = (void *)(*skb)->data;
+ if (fm_evt_hdr->status != 0) {
+ pr_err("(fmdrv): irq: opcode %x response status is not zero",
+ fm_evt_hdr->fm_opcode);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Interrupt process timeout handler.
+ * One of the irq handler did not get proper response from the chip. So take
+ * recovery action here. FM interrupts are disabled in the beginning of
+ * interrupt process. Therefore reset stage index to re-enable default
+ * interrupts. So that next interrupt will be processed as usual.
+ */
+static void __int_timeout_handler(unsigned long data)
+{
+ struct fmdrv_ops *fmdev;
+
+ pr_info("(fmdrv): irq: timeout,trying to re-enable fm interrupts");
+ fmdev = (struct fmdrv_ops *)data;
+ fmdev->irq_info.irq_service_timeout_retry++;
+
+ if (fmdev->irq_info.irq_service_timeout_retry <=
+ FM_IRQ_TIMEOUT_RETRY_MAX) {
+ fmdev->irq_info.stage_index = FM_SEND_INTMSK_CMD_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.
+ stage_index] (fmdev);
+ } else {
+ /* Stop recovery action (interrupt reenable process) and
+ * reset stage index & retry count values
+ */
+ fmdev->irq_info.stage_index = 0;
+ fmdev->irq_info.irq_service_timeout_retry = 0;
+ pr_err("(fmdrv): Recovery action failed during \
+ irq processing, max retry reached");
+ }
+}
+
+/* --------- FM interrupt handlers ------------*/
+static void fm_irq_send_flag_getcmd(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short flag;
+ int ret;
+
+ fmdev = arg;
+ /* Send FLAG_GET command , to know the source of interrupt */
+ ret = __fm_send_cmd(fmdev, FLAG_GET, NULL, sizeof(flag), NULL);
+ if (ret)
+ pr_err("(fmdrv): irq: failed to send flag_get command,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index = FM_HANDLE_FLAG_GETCMD_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_flag_getcmd_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ struct fm_event_msg_hdr *fm_evt_hdr;
+ char ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ fm_evt_hdr = (void *)skb->data;
+
+ /* Skip header info and copy only response data */
+ skb_pull(skb, sizeof(struct fm_event_msg_hdr));
+ memcpy(&fmdev->irq_info.flag, skb->data, fm_evt_hdr->dlen);
+
+ FM_STORE_BE16_TO_LE16(fmdev->irq_info.flag, fmdev->irq_info.flag);
+ pr_info("(fmdrv): irq: flag register(0x%x)", fmdev->irq_info.flag);
+
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_HW_MAL_FUNC_INDEX;
+
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_hw_malfunction(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ if (fmdev->irq_info.flag & FM_MAL_EVENT & fmdev->irq_info.mask)
+ pr_err("(fmdrv): irq: HW MAL interrupt received - do nothing");
+
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_RDS_START_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_rds_start(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ if (fmdev->irq_info.flag & FM_RDS_EVENT & fmdev->irq_info.mask) {
+ pr_info("(fmdrv): irq: rds threshold reached");
+ fmdev->irq_info.stage_index = FM_RDS_SEND_RDS_GETCMD_INDEX;
+ } else {
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_HW_TUNE_OP_ENDED_INDEX;
+ }
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_send_rdsdata_getcmd(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ int ret;
+
+ fmdev = arg;
+ /* Send the command to read RDS data from the chip */
+ ret = __fm_send_cmd(fmdev, RDS_DATA_GET, NULL,
+ (FM_RX_RDS_FIFO_THRESHOLD * 3), NULL);
+ if (ret < 0)
+ pr_err("(fmdrv): irq : failed to send rds get command,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index =
+ FM_RDS_HANDLE_RDS_GETCMD_RESP_INDEX;
+
+ /* Start timer to track timeout */
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+/* Keeps track of current RX channel AF (Alternate Frequency) */
+static void __fm_rx_update_af_cache(struct fmdrv_ops *fmdev,
+ unsigned char af)
+{
+ unsigned char index;
+ unsigned int freq;
+
+ /* First AF indicates the number of AF follows. Reset the list */
+ if ((af >= FM_RDS_1_AF_FOLLOWS) && (af <= FM_RDS_25_AF_FOLLOWS)) {
+ fmdev->rx.cur_station_info.af_list_max =
+ (af - FM_RDS_1_AF_FOLLOWS + 1);
+ fmdev->rx.cur_station_info.no_of_items_in_afcache = 0;
+ pr_info("(fmdrv): No of expected AF : %d",
+ fmdev->rx.cur_station_info.af_list_max);
+ } else if (((af >= FM_RDS_MIN_AF)
+ && (fmdev->rx.region.region_index == FM_BAND_EUROPE_US)
+ && (af <= FM_RDS_MAX_AF)) || ((af >= FM_RDS_MIN_AF)
+ && (fmdev->rx.region.
+ region_index ==
+ FM_BAND_JAPAN)
+ && (af <=
+ FM_RDS_MAX_AF_JAPAN))) {
+ freq = fmdev->rx.region.bottom_frequency + (af * 100);
+ if (freq == fmdev->rx.curr_freq) {
+ pr_info("(fmdrv): Current frequency(%d) is \
+ matching with received AF(%d)",
+ fmdev->rx.curr_freq, freq);
+ return;
+ }
+ /* Do check in AF cache */
+ for (index = 0;
+ index < fmdev->rx.cur_station_info.no_of_items_in_afcache;
+ index++) {
+ if (fmdev->rx.cur_station_info.af_cache[index] == freq)
+ break;
+ }
+ /* Reached the limit of the list - ignore the next AF */
+ if (index == fmdev->rx.cur_station_info.af_list_max) {
+ pr_info("(fmdrv): AF cache is full");
+ return;
+ }
+ /* If we reached the end of the list then
+ * this AF is not in the list - add it
+ */
+ if (index == fmdev->rx.cur_station_info.
+ no_of_items_in_afcache) {
+ pr_info("(fmdrv): Storing AF %d to AF cache index %d",
+ freq, index);
+ fmdev->rx.cur_station_info.af_cache[index] = freq;
+ fmdev->rx.cur_station_info.no_of_items_in_afcache++;
+ }
+ }
+}
+
+/* Converts RDS buffer data from big endian format
+ * to little endian format
+ */
+static void __fm_rdsparse_swapbytes(struct fmdrv_ops *fmdev,
+ struct fm_rdsdata_format *rds_format)
+{
+ unsigned char byte1;
+ unsigned char index = 0;
+ char *rds_buff;
+
+ /* Since in Orca the 2 RDS Data bytes are in little endian and
+ * in Dolphin they are in big endian, the parsing of the RDS data
+ * is chip dependent */
+ if (fmdev->asci_id != 0x6350) {
+ rds_buff = &rds_format->rdsdata.groupdatabuff.rdsbuff[0];
+ while (index + 1 < FM_RX_RDS_INFO_FIELD_MAX) {
+ byte1 = rds_buff[index];
+ rds_buff[index] = rds_buff[index + 1];
+ rds_buff[index + 1] = byte1;
+ index += 2;
+ }
+ }
+}
+
+static void fm_irq_handle_rdsdata_getcmd_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ char *rds_data;
+ char meta_data;
+ unsigned char type, block_index;
+ unsigned long group_index;
+ struct fm_rdsdata_format rds_format;
+ int rds_len, ret;
+ unsigned short cur_picode;
+ unsigned char tmpbuf[3];
+ unsigned long flags;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* Skip header info */
+ skb_pull(skb, sizeof(struct fm_event_msg_hdr));
+ rds_data = skb->data;
+ rds_len = skb->len;
+
+ /* Parse the RDS data */
+ while (rds_len >= FM_RDS_BLOCK_SIZE) {
+ meta_data = rds_data[2];
+ /* Get the type:
+ * 0=A, 1=B, 2=C, 3=C', 4=D, 5=E */
+ type = (meta_data & 0x07);
+
+ /* Transform the block type
+ * into an index sequence (0, 1, 2, 3, 4) */
+ block_index = (type <= FM_RDS_BLOCK_C ? type : (type - 1));
+ pr_info("(fmdrv): Block index:%d(%s) ", block_index,
+ (meta_data & FM_RDS_STATUS_ERROR_MASK) ? "Bad" :
+ "Ok");
+ if (((meta_data & FM_RDS_STATUS_ERROR_MASK) == 0)
+ && (block_index == FM_RDS_BLOCK_INDEX_A
+ || (block_index == fmdev->rx.rds.last_block_index + 1
+ && block_index <= FM_RDS_BLOCK_INDEX_D))) {
+ /* Skip checkword (control) byte
+ * and copy only data byte */
+ memcpy(&rds_format.rdsdata.groupdatabuff.
+ rdsbuff[block_index * (FM_RDS_BLOCK_SIZE - 1)],
+ rds_data, (FM_RDS_BLOCK_SIZE - 1));
+ fmdev->rx.rds.last_block_index = block_index;
+
+ /* If completed a whole group then handle it */
+ if (block_index == FM_RDS_BLOCK_INDEX_D) {
+ pr_info("(fmdrv): Good block received");
+ __fm_rdsparse_swapbytes(fmdev, &rds_format);
+
+ /* Extract PI code and store in local cache.
+ * We need this during AF switch processing */
+ cur_picode =
+ FM_BE16_TO_LE16(rds_format.rdsdata.
+ groupgeneral.pidata);
+ if (fmdev->rx.cur_station_info.picode !=
+ cur_picode)
+ fmdev->rx.cur_station_info.picode =
+ cur_picode;
+ pr_info("(fmdrv): picode:%d", cur_picode);
+
+ group_index =
+ (rds_format.rdsdata.groupgeneral.
+ block_b_byte1 >> 3);
+ pr_info("(fmdrv):Group:%ld%s", group_index / 2,
+ (group_index % 2) ? "B" : "A");
+
+ group_index =
+ 1 << (rds_format.rdsdata.groupgeneral.
+ block_b_byte1 >> 3);
+ if (group_index == FM_RDS_GROUP_TYPE_MASK_0A) {
+ __fm_rx_update_af_cache
+ (fmdev, rds_format.rdsdata.
+ group0A.firstaf);
+ __fm_rx_update_af_cache
+ (fmdev, rds_format.
+ rdsdata.group0A.secondaf);
+ }
+ }
+ } else {
+ pr_info("(fmdrv): Block sequence mismatch");
+ fmdev->rx.rds.last_block_index = -1;
+ }
+ rds_len -= FM_RDS_BLOCK_SIZE;
+ rds_data += FM_RDS_BLOCK_SIZE;
+ }
+
+ /* Copy raw rds data to internal rds buffer */
+ rds_data = skb->data;
+ rds_len = skb->len;
+
+ spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
+ while (rds_len > 0) {
+ /* Fill RDS buffer as per V4L2 specification.
+ * Store control byte
+ */
+ type = (rds_data[2] & 0x07);
+ block_index = (type <= FM_RDS_BLOCK_C ? type : (type - 1));
+ tmpbuf[2] = block_index; /* Offset name */
+ tmpbuf[2] |= block_index << 3; /* Received offset */
+
+ /* Store data byte */
+ tmpbuf[0] = rds_data[0];
+ tmpbuf[1] = rds_data[1];
+
+ memcpy(&fmdev->rx.rds.buffer[fmdev->rx.rds.wr_index], &tmpbuf,
+ FM_RDS_BLOCK_SIZE);
+ fmdev->rx.rds.wr_index =
+ (fmdev->rx.rds.wr_index +
+ FM_RDS_BLOCK_SIZE) % fmdev->rx.rds.buf_size;
+
+ /* Check for overflow & start over */
+ if (fmdev->rx.rds.wr_index == fmdev->rx.rds.rd_index) {
+ pr_info("(fmdrv): RDS buffer overflow");
+ fmdev->rx.rds.wr_index = 0;
+ fmdev->rx.rds.rd_index = 0;
+ break;
+ }
+ rds_len -= FM_RDS_BLOCK_SIZE;
+ rds_data += FM_RDS_BLOCK_SIZE;
+ }
+ spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
+
+ /* Wakeup read queue */
+ if (fmdev->rx.rds.wr_index != fmdev->rx.rds.rd_index)
+ wake_up_interruptible(&fmdev->rx.rds.read_queue);
+
+ fmdev->irq_info.stage_index = FM_RDS_FINISH_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_rds_finish(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_HW_TUNE_OP_ENDED_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_tune_op_ended(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ if (fmdev->irq_info.flag & (FM_FR_EVENT | FM_BL_EVENT) & fmdev->
+ irq_info.mask) {
+ pr_info("(fmdrv): irq: tune ended/bandlimit reached");
+ if (test_and_clear_bit(FM_AF_SWITCH_INPROGRESS, &fmdev->flag)) {
+ fmdev->irq_info.stage_index = FM_AF_JUMP_RD_FREQ_INDEX;
+ } else {
+ complete(&fmdev->maintask_completion);
+ fmdev->irq_info.stage_index = FM_HW_POWER_ENB_INDEX;
+ }
+ } else
+ fmdev->irq_info.stage_index = FM_HW_POWER_ENB_INDEX;
+
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_power_enb(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ if (fmdev->irq_info.flag & FM_POW_ENB_EVENT) {
+ pr_info("(fmdrv): irq: Power Enabled/Disabled");
+ complete(&fmdev->maintask_completion);
+ }
+
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_LOW_RSSI_START_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_low_rssi_start(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ if ((fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON) &&
+ (fmdev->irq_info.flag & FM_LEV_EVENT & fmdev->irq_info.mask) &&
+ (fmdev->rx.curr_freq != FM_UNDEFINED_FREQ) &&
+ (fmdev->rx.cur_station_info.no_of_items_in_afcache != 0)) {
+ pr_info("(fmdrv): irq: rssi level has fallen below" \
+ " threshold level");
+
+ /* Disable further low RSSI interrupts */
+ fmdev->irq_info.mask &= ~FM_LEV_EVENT;
+
+ fmdev->rx.cur_afjump_index = 0;
+ fmdev->rx.freq_before_jump = fmdev->rx.curr_freq;
+ fmdev->irq_info.stage_index = FM_AF_JUMP_SETPI_INDEX;
+ } else {
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_SEND_INTMSK_CMD_INDEX;
+ }
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_afjump_set_pi(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ int ret;
+ unsigned short payload;
+
+ fmdev = arg;
+ /* Set PI code - must be updated if the AF list is not empty */
+ payload = FM_LE16_TO_BE16(fmdev->rx.cur_station_info.picode);
+ ret = __fm_send_cmd(fmdev, RDS_PI_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret < 0)
+ pr_err("(fmdrv): irq : failed to set PI,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index =
+ FM_AF_JUMP_HANDLE_SETPI_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_set_pi_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_AF_JUMP_SETPI_MASK_INDEX;
+
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+/* Set PI mask.
+ * 0xFFFF = Enable PI code matching
+ * 0x0000 = Disable PI code matching
+ */
+static void fm_irq_afjump_set_pimask(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ int ret;
+ unsigned short payload;
+
+ fmdev = arg;
+ payload = 0x0000;
+ ret = __fm_send_cmd(fmdev, RDS_PI_MASK_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret < 0)
+ pr_err("(fmdrv): irq: failed to set PI mask,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index =
+ FM_AF_JUMP_HANDLE_SETPI_MASK_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_set_pimask_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_AF_JUMP_SET_AF_FREQ_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_afjump_setfreq(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short frq_index;
+ unsigned short payload;
+ int ret;
+
+ fmdev = arg;
+ pr_info("(fmdrv): Swtiching to %d KHz\n",
+ fmdev->rx.cur_station_info.af_cache[fmdev->rx.cur_afjump_index]);
+ frq_index =
+ (fmdev->rx.cur_station_info.af_cache[fmdev->rx.cur_afjump_index] -
+ fmdev->rx.region.bottom_frequency) /
+ fmdev->rx.region.channel_spacing;
+
+ FM_STORE_LE16_TO_BE16(payload, frq_index);
+ ret = __fm_send_cmd(fmdev, AF_FREQ_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret < 0)
+ pr_err("(fmdrv): irq : failed to set AF freq,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index =
+ FM_AF_JUMP_HENDLE_SET_AFFREQ_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_setfreq_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ } else {
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_AF_JUMP_ENABLE_INT_INDEX;
+ }
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_afjump_enableint(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short payload;
+ int ret;
+
+ fmdev = arg;
+ /* Enable FR (tuning operation ended) interrupt */
+ payload = FM_LE16_TO_BE16(FM_FR_EVENT);
+ ret = __fm_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret)
+ pr_err("(fmdrv): irq : failed to enable FR interrupt,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index = FM_AF_JUMP_ENABLE_INT_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_afjump_enableint_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_AF_JUMP_START_AFJUMP_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_start_afjump(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short payload;
+ int ret;
+
+ fmdev = arg;
+ FM_STORE_LE16_TO_BE16(payload, FM_TUNER_AF_JUMP_MODE);
+ ret = __fm_send_cmd(fmdev, TUNER_MODE_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret)
+ pr_err("(fmdrv): irq : failed to start af switch,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index =
+ FM_AF_JUMP_HANDLE_START_AFJUMP_RESP_INDEX;
+
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_start_afjump_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ fmdev->irq_info.stage_index = FM_SEND_FLAG_GETCMD_INDEX;
+ set_bit(FM_AF_SWITCH_INPROGRESS, &fmdev->flag);
+ clear_bit(FM_INTTASK_RUNNING, &fmdev->flag);
+}
+
+static void fm_irq_afjump_rd_freq(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short payload;
+ int ret;
+
+ fmdev = arg;
+ ret = __fm_send_cmd(fmdev, FREQ_GET, NULL, sizeof(payload), NULL);
+ if (ret < 0)
+ pr_err("(fmdrv): irq: failed to read cur freq,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index = FM_AF_JUMP_RD_FREQ_RESP_INDEX;
+
+ /* Start timer to track timeout */
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_afjump_rd_freq_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ unsigned short read_freq;
+ unsigned int curr_freq, jumped_freq;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* Skip header info and copy only response data */
+ skb_pull(skb, sizeof(struct fm_event_msg_hdr));
+ memcpy(&read_freq, skb->data, sizeof(read_freq));
+ read_freq = FM_BE16_TO_LE16(read_freq);
+ curr_freq = fmdev->rx.region.bottom_frequency +
+ ((unsigned int)read_freq * fmdev->rx.region.channel_spacing);
+
+ jumped_freq =
+ fmdev->rx.cur_station_info.af_cache[fmdev->rx.cur_afjump_index];
+
+ /* If the frequency was changed the jump succeeded */
+ if ((curr_freq != fmdev->rx.freq_before_jump) &&
+ (curr_freq == jumped_freq)) {
+ pr_info("(fmdrv): Successfully switched to alternate" \
+ "frequency %d", curr_freq);
+ fmdev->rx.curr_freq = curr_freq;
+ fm_rx_reset_rds_cache(fmdev);
+
+ /* AF feature is on, enable low level RSSI interrupt */
+ if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
+ fmdev->irq_info.mask |= FM_LEV_EVENT;
+
+ fmdev->irq_info.stage_index = FM_LOW_RSSI_FINISH_INDEX;
+ } else { /* jump to the next freq in the AF list */
+ fmdev->rx.cur_afjump_index++;
+
+ /* If we reached the end of the list - stop searching */
+ if (fmdev->rx.cur_afjump_index >=
+ fmdev->rx.cur_station_info.no_of_items_in_afcache) {
+ pr_info("(fmdrv): AF switch processing failed");
+ fmdev->irq_info.stage_index = FM_LOW_RSSI_FINISH_INDEX;
+ } else { /* AF List is not over - try next one */
+
+ pr_info("(fmdrv): Trying next frequency in af cache");
+ fmdev->irq_info.stage_index = FM_AF_JUMP_SETPI_INDEX;
+ }
+ }
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_handle_low_rssi_finish(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = arg;
+ /* Continue next function in interrupt handler table */
+ fmdev->irq_info.stage_index = FM_SEND_INTMSK_CMD_INDEX;
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index](fmdev);
+}
+
+static void fm_irq_send_intmsk_cmd(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ unsigned short payload;
+ int ret;
+
+ fmdev = arg;
+
+ /* Re-enable FM interrupts */
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = __fm_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ NULL);
+ if (ret)
+ pr_err("(fmdrv): irq: failed to send int_mask_set cmd,"
+ "initiating irq recovery process");
+ else
+ fmdev->irq_info.stage_index = FM_HANDLE_INTMSK_CMD_RESP_INDEX;
+
+ /* Start timer to track timeout */
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+}
+
+static void fm_irq_handle_intmsk_cmd_resp(void *arg)
+{
+ struct fmdrv_ops *fmdev;
+ struct sk_buff *skb;
+ int ret;
+
+ fmdev = arg;
+ del_timer(&fmdev->irq_info.int_timeout_timer);
+
+ ret = __check_cmdresp_status(fmdev, &skb);
+ if (ret < 0) {
+ pr_err("(fmdrv): Initiating irq recovery process");
+ mod_timer(&fmdev->irq_info.int_timeout_timer, jiffies +
+ FM_DRV_TX_TIMEOUT);
+ return;
+ }
+ /* This is last function in interrupt table to be executed.
+ * So, reset stage index to 0.
+ */
+ fmdev->irq_info.stage_index = FM_SEND_FLAG_GETCMD_INDEX;
+
+ /* Start processing any pending interrupt */
+ if (test_and_clear_bit(FM_INTTASK_SCHEDULE_PENDING, &fmdev->flag)) {
+ fmdev->irq_info.fm_int_handlers[fmdev->irq_info.stage_index]
+ (fmdev);
+ } else
+ clear_bit(FM_INTTASK_RUNNING, &fmdev->flag);
+}
+
+/* Returns availability of RDS data in internel buffer */
+int fmc_is_rds_data_available(struct fmdrv_ops *fmdev, struct file *file,
+ struct poll_table_struct *pts)
+{
+ poll_wait(file, &fmdev->rx.rds.read_queue, pts);
+ if (fmdev->rx.rds.rd_index != fmdev->rx.rds.wr_index)
+ return 0;
+
+ return -EAGAIN;
+}
+
+/* Copies RDS data from internal buffer to user buffer */
+int fmc_transfer_rds_from_internal_buff(struct fmdrv_ops *fmdev,
+ struct file *file,
+ char __user *buf, size_t count)
+{
+ unsigned int block_count;
+ unsigned long flags;
+ int ret;
+
+ if (fmdev->rx.rds.wr_index == fmdev->rx.rds.rd_index) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ ret = wait_event_interruptible(fmdev->rx.rds.read_queue,
+ (fmdev->rx.rds.wr_index !=
+ fmdev->rx.rds.rd_index));
+ if (ret)
+ return -EINTR;
+ }
+
+ /* Calculate block count from byte count */
+ count /= 3;
+ block_count = 0;
+ ret = 0;
+
+ spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
+
+ while (block_count < count) {
+ if (fmdev->rx.rds.wr_index == fmdev->rx.rds.rd_index)
+ break;
+
+ if (copy_to_user
+ (buf, &fmdev->rx.rds.buffer[fmdev->rx.rds.rd_index],
+ FM_RDS_BLOCK_SIZE))
+ break;
+
+ fmdev->rx.rds.rd_index += FM_RDS_BLOCK_SIZE;
+ if (fmdev->rx.rds.rd_index >= fmdev->rx.rds.buf_size)
+ fmdev->rx.rds.rd_index = 0;
+
+ block_count++;
+ buf += FM_RDS_BLOCK_SIZE;
+ ret += FM_RDS_BLOCK_SIZE;
+ }
+ spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
+ return ret;
+}
+
+int fmc_set_frequency(struct fmdrv_ops *fmdev, unsigned int freq_to_set)
+{
+ int ret;
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ ret = fm_rx_set_frequency(fmdev, freq_to_set);
+ break;
+
+ case FM_MODE_TX:
+ /* TODO: Enable when FM TX is supported */
+ /* ret = fm_tx_set_frequency(fmdev, freq_to_set); */
+ /* break; */
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+int fmc_get_frequency(struct fmdrv_ops *fmdev, unsigned int *cur_tuned_frq)
+{
+ int ret = 0;
+
+ if (fmdev->rx.curr_freq == FM_UNDEFINED_FREQ) {
+ pr_err("(fmdrv): RX frequency is not set");
+ ret = -EPERM;
+ goto exit;
+ }
+ if (cur_tuned_frq == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ *cur_tuned_frq = fmdev->rx.curr_freq;
+ break;
+
+ case FM_MODE_TX:
+ *cur_tuned_frq = 0; /* TODO : Change this later */
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+exit:
+ return ret;
+}
+
+/* Returns current band index (0-Europe/US; 1-Japan) */
+int fmc_get_region(struct fmdrv_ops *fmdev, unsigned char *region)
+{
+ *region = fmdev->rx.region.region_index;
+ return 0;
+}
+
+int fmc_set_region(struct fmdrv_ops *fmdev, unsigned char region_to_set)
+{
+ int ret;
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ ret = fm_rx_set_region(fmdev, region_to_set);
+ break;
+
+ case FM_MODE_TX:
+ /* TODO: Enable when FM TX is supported */
+ /* ret = fm_tx_set_region(fmdev, region_to_set); */
+ /* break; */
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+int fmc_set_mute_mode(struct fmdrv_ops *fmdev, unsigned char mute_mode_toset)
+{
+ int ret;
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ ret = fm_rx_set_mute_mode(fmdev, mute_mode_toset);
+ break;
+
+ case FM_MODE_TX:
+ /* TODO: Enable when FM TX is supported */
+ /* ret = fm_tx_set_mute_mode(fmdev, mute_mode_toset); */
+ /* break; */
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+int fmc_set_stereo_mono(struct fmdrv_ops *fmdev, unsigned short mode)
+{
+ int ret;
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ ret = fm_rx_set_stereo_mono(fmdev, mode);
+ break;
+
+ case FM_MODE_TX:
+ /* TODO: Enable when FM TX is supported */
+ /* ret = fm_tx_set_stereo_mono(fmdev, mode); */
+ /* break; */
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+int fmc_set_rds_mode(struct fmdrv_ops *fmdev, unsigned char rds_en_dis)
+{
+ int ret;
+
+ switch (fmdev->curr_fmmode) {
+ case FM_MODE_RX:
+ ret = fm_rx_set_rds_mode(fmdev, rds_en_dis);
+ break;
+
+ case FM_MODE_TX:
+ /* TODO: Enable when FM TX is supported */
+ /* ret = fm_tx_set_rds_mode(fmdev, rds_en_dis); */
+ /* break; */
+
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/* Sends power off command to the chip */
+static int fm_power_down(struct fmdrv_ops *fmdev)
+{
+ unsigned short payload;
+ int ret = 0;
+
+ if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
+ pr_err("(fmdrv): FM core is not ready");
+ ret = -EPERM;
+ goto exit;
+ }
+ if (fmdev->curr_fmmode == FM_MODE_OFF) {
+ pr_err("(fmdrv): FM chip is already in OFF state");
+ goto exit;
+ }
+
+ FM_STORE_LE16_TO_BE16(payload, 0x0);
+ ret = fmc_send_cmd(fmdev, FM_POWER_MODE, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ ret = fmc_release(fmdev);
+ if (ret < 0)
+ pr_err("(fmdrv): FM CORE release failed");
+
+exit:
+ return ret;
+}
+
+/* Reads init command from FM firmware file and loads to the chip */
+static int fm_download_firmware(struct fmdrv_ops *fmdev,
+ const char *firmware_name)
+{
+ const struct firmware *fw_entry;
+ struct bts_header *fw_header;
+ struct bts_action *action;
+ struct bts_action_delay *delay;
+ char *fw_data;
+ int ret, fw_len, cmd_cnt;
+
+ cmd_cnt = 0;
+ set_bit(FM_FIRMWARE_DW_INPROGRESS, &fmdev->flag);
+
+ ret = request_firmware(&fw_entry, firmware_name,
+ &fmdev->radio_dev->dev);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to read firmware(%s) content\n",
+ firmware_name);
+ goto exit;
+ }
+ pr_info("(fmdrv): Firmware(%s) length : %d bytes", firmware_name,
+ fw_entry->size);
+
+ fw_data = (void *)fw_entry->data;
+ fw_len = fw_entry->size;
+
+ fw_header = (struct bts_header *)fw_data;
+ if (fw_header->magic != FM_FW_FILE_HEADER_MAGIC) {
+ pr_err("(fmdrv): %s not a legal TI firmware file",
+ firmware_name);
+ ret = -EINVAL;
+ goto rel_fw;
+ }
+ pr_info("(fmdrv): Firmware(%s) magic number : 0x%x", firmware_name,
+ fw_header->magic);
+
+ /* Skip file header info , we already verified it */
+ fw_data += sizeof(struct bts_header);
+ fw_len -= sizeof(struct bts_header);
+
+ while (fw_data && fw_len > 0) {
+ action = (struct bts_action *)fw_data;
+
+ switch (action->type) {
+ case ACTION_SEND_COMMAND: /* Send */
+ ret = fmc_send_cmd(fmdev, 0, action->data,
+ action->size, &fmdev->maintask_completion,
+ NULL, NULL);
+ if (ret < 0)
+ goto rel_fw;
+
+ cmd_cnt++;
+ break;
+
+ case ACTION_DELAY: /* Delay */
+ delay = (struct bts_action_delay *)action->data;
+ mdelay(delay->msec);
+ break;
+ }
+
+ fw_data += (sizeof(struct bts_action) + (action->size));
+ fw_len -= (sizeof(struct bts_action) + (action->size));
+ }
+ pr_info("(fmdrv): Firmare commands(%d) loaded to the chip", cmd_cnt);
+rel_fw:
+ release_firmware(fw_entry);
+ clear_bit(FM_FIRMWARE_DW_INPROGRESS, &fmdev->flag);
+exit:
+ return ret;
+}
+
+/* Loads default RX configuration to the chip */
+static int __load_default_rx_configuration(struct fmdrv_ops *fmdev)
+{
+ int ret;
+
+ ret = fm_rx_set_volume(fmdev, FM_DEFAULT_RX_VOLUME);
+ if (ret < 0)
+ return ret;
+
+ ret = fm_rx_set_rssi_threshold(fmdev, FM_DEFAULT_RSSI_THRESHOLD);
+ return ret;
+}
+
+/* Does FM power on sequence */
+static int fm_power_up(struct fmdrv_ops *fmdev, unsigned char fw_option)
+{
+ unsigned short payload, asic_id, asic_ver;
+ int resp_len, ret;
+ char fw_name[50];
+
+ if (fw_option >= FM_MODE_ENTRY_MAX) {
+ pr_err("(fmdrv): Invalid firmware download option");
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Initialize FM common module. FM GPIO toggling is
+ * taken care in Shared Transport driver.
+ */
+ ret = fmc_prepare(fmdev);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to prepare FM Common");
+ goto exit;
+ }
+
+ FM_STORE_LE16_TO_BE16(payload, FM_ENABLE);
+ ret = fmc_send_cmd(fmdev, FM_POWER_MODE, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed enable FM over Channel 8");
+ goto rel;
+ }
+
+ /* Allow the chip to settle down in Channel-8 mode */
+ msleep(5);
+
+ ret = fmc_send_cmd(fmdev, ASIC_ID_GET, NULL, sizeof(asic_id),
+ &fmdev->maintask_completion, &asic_id,
+ &resp_len);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed read FM chip ASIC ID");
+ goto rel;
+ }
+ ret = fmc_send_cmd(fmdev, ASIC_VER_GET, NULL, sizeof(asic_ver),
+ &fmdev->maintask_completion, &asic_ver,
+ &resp_len);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed read FM chip ASIC Version");
+ goto rel;
+ }
+
+ pr_info("(fmdrv): ASIC ID: 0x%x , ASIC Version: %d",
+ FM_BE16_TO_LE16(asic_id), FM_BE16_TO_LE16(asic_ver));
+
+ sprintf(fw_name, "%s_%x.%d.bts", FM_FMC_FW_FILE_START,
+ FM_BE16_TO_LE16(asic_id), FM_BE16_TO_LE16(asic_ver));
+ ret = fm_download_firmware(fmdev, fw_name);
+ if (ret < 0) {
+ pr_info("(fmdrv): Failed to download firmware file %s\n",
+ fw_name);
+ goto rel;
+ }
+
+ sprintf(fw_name, "%s_%x.%d.bts", (fw_option == FM_MODE_RX) ?
+ FM_RX_FW_FILE_START : FM_TX_FW_FILE_START,
+ FM_BE16_TO_LE16(asic_id), FM_BE16_TO_LE16(asic_ver));
+ ret = fm_download_firmware(fmdev, fw_name);
+ if (ret < 0) {
+ pr_info("(fmdrv): Failed to download firmware file %s\n",
+ fw_name);
+ goto rel;
+ } else
+ goto exit;
+rel:
+ fmc_release(fmdev);
+exit:
+ return ret;
+}
+
+/* Set FM Modes(TX, RX, OFF) */
+int fmc_set_mode(struct fmdrv_ops *fmdev, unsigned char fm_mode)
+{
+ int ret = 0;
+
+ if (fm_mode >= FM_MODE_ENTRY_MAX) {
+ pr_err("(fmdrv): Invalid FM mode");
+ ret = -EINVAL;
+ goto exit;
+ }
+ if (fmdev->curr_fmmode == fm_mode) {
+ pr_info("(fmdrv): Already fm is in mode(%d)", fm_mode);
+ goto exit;
+ }
+
+ switch (fm_mode) {
+ case FM_MODE_OFF: /* OFF Mode */
+ ret = fm_power_down(fmdev);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to set OFF mode");
+ goto exit;
+ }
+ break;
+
+ case FM_MODE_TX: /* TX Mode */
+ case FM_MODE_RX: /* RX Mode */
+ /* Power down before switching to TX or RX mode */
+ if (fmdev->curr_fmmode != FM_MODE_OFF) {
+ ret = fm_power_down(fmdev);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to set OFF mode");
+ goto exit;
+ }
+ msleep(30);
+ }
+ ret = fm_power_up(fmdev, fm_mode);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to load firmware\n");
+ goto exit;
+ }
+ }
+ fmdev->curr_fmmode = fm_mode;
+
+ /* Set default configuration */
+ if (fmdev->curr_fmmode == FM_MODE_RX) {
+ pr_info("(fmdrv): Loading default rx configuration..\n");
+ ret = __load_default_rx_configuration(fmdev);
+ if (ret < 0)
+ pr_err("(fmdrv): Failed to load default values\n");
+ }
+exit:
+ return ret;
+}
+
+/* Returns current FM mode (TX, RX, OFF) */
+int fmc_get_mode(struct fmdrv_ops *fmdev, unsigned char *fmmode)
+{
+ if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
+ pr_err("(fmdrv): FM core is not ready");
+ return -EPERM;
+ }
+ if (fmmode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+
+ *fmmode = fmdev->curr_fmmode;
+ return 0;
+}
+
+/* Called by ST layer when FM packet is available */
+static long fm_st_receive(void *arg, struct sk_buff *skb)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = (struct fmdrv_ops *)arg;
+
+ if (skb == NULL) {
+ pr_err("(fmdrv): Invalid SKB received from ST");
+ return -EFAULT;
+ }
+
+ if (skb->cb[0] != FM_PKT_LOGICAL_CHAN_NUMBER) {
+ pr_err("(fmdrv): Received SKB (%p) is not FM Channel 8 pkt",
+ skb);
+ return -EINVAL;
+ }
+
+ memcpy(skb_push(skb, 1), &skb->cb[0], 1);
+ skb_queue_tail(&fmdev->rx_q, skb);
+ tasklet_schedule(&fmdev->rx_task);
+
+ return 0;
+}
+
+/* Called by ST layer to indicate protocol registration completion
+ * status.
+ */
+static void fm_st_reg_comp_cb(void *arg, char data)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = (struct fmdrv_ops *)arg;
+ fmdev->streg_cbdata = data;
+ complete(&wait_for_fmdrv_reg_comp);
+}
+
+/* This function will be called from FM V4L2 open function.
+ * Register with ST driver and initialize driver data.
+ */
+int fmc_prepare(struct fmdrv_ops *fmdev)
+{
+ static struct st_proto_s fm_st_proto;
+ unsigned long timeleft;
+ int ret = 0;
+
+ if (test_bit(FM_CORE_READY, &fmdev->flag)) {
+ pr_info("(fmdrv): FM Core is already up");
+ goto exit;
+ }
+
+ memset(&fm_st_proto, 0, sizeof(fm_st_proto));
+ fm_st_proto.type = ST_FM;
+ fm_st_proto.recv = fm_st_receive;
+ fm_st_proto.match_packet = NULL;
+ fm_st_proto.reg_complete_cb = fm_st_reg_comp_cb;
+ fm_st_proto.write = NULL; /* TI ST driver will fill write pointer */
+ fm_st_proto.priv_data = fmdev;
+
+ ret = st_register(&fm_st_proto);
+ if (ret == -EINPROGRESS) {
+ init_completion(&wait_for_fmdrv_reg_comp);
+ fmdev->streg_cbdata = -EINPROGRESS;
+ pr_info("(fmdrv): %s waiting for ST reg completion signal",
+ __func__);
+
+ timeleft =
+ wait_for_completion_timeout(&wait_for_fmdrv_reg_comp,
+ FM_ST_REGISTER_TIMEOUT);
+
+ if (!timeleft) {
+ pr_err("(fmdrv): Timeout(%d sec), didn't get reg"
+ "completion signal from ST",
+ jiffies_to_msecs(FM_ST_REGISTER_TIMEOUT) /
+ 1000);
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+ if (fmdev->streg_cbdata != 0) {
+ pr_err("(fmdrv): ST reg comp CB called with error"
+ "status %d", fmdev->streg_cbdata);
+ ret = -EAGAIN;
+ goto exit;
+ }
+ ret = 0;
+ } else if (ret == -1) {
+ pr_err("(fmdrv): st_register failed %d", ret);
+ ret = -EAGAIN;
+ goto exit;
+ }
+
+ if (fm_st_proto.write != NULL) {
+ g_st_write = fm_st_proto.write;
+ } else {
+ pr_err("(fmdrv): Failed to get ST write func pointer");
+ ret = st_unregister(ST_FM);
+ if (ret < 0)
+ pr_err("(fmdrv): st_unregister failed %d", ret);
+ ret = -EAGAIN;
+ goto exit;
+ }
+
+ spin_lock_init(&fmdev->rds_buff_lock);
+ spin_lock_init(&fmdev->resp_skb_lock);
+
+ /* Initialize TX queue and TX tasklet */
+ skb_queue_head_init(&fmdev->tx_q);
+ tasklet_init(&fmdev->tx_task, __send_tasklet, (unsigned long)fmdev);
+
+ /* Initialize RX Queue and RX tasklet */
+ skb_queue_head_init(&fmdev->rx_q);
+ tasklet_init(&fmdev->rx_task, __recv_tasklet, (unsigned long)fmdev);
+
+ fmdev->irq_info.stage_index = 0;
+ atomic_set(&fmdev->tx_cnt, 1);
+ fmdev->response_completion = NULL;
+
+ init_timer(&fmdev->irq_info.int_timeout_timer);
+ fmdev->irq_info.int_timeout_timer.function =
+ &__int_timeout_handler;
+ fmdev->irq_info.int_timeout_timer.data = (unsigned long)fmdev;
+ fmdev->irq_info.mask =
+ FM_MAL_EVENT /*| FM_STIC_EVENT <<Enable this later>> */ ;
+
+ /* Region info */
+ memcpy(&fmdev->rx.region, &region_configs[default_radio_region],
+ sizeof(struct region_info));
+
+ fmdev->rx.curr_mute_mode = FM_MUTE_OFF;
+ fmdev->rx.curr_rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF;
+ fmdev->rx.rds.flag = FM_RDS_DISABLE;
+ fmdev->rx.curr_freq = FM_UNDEFINED_FREQ;
+ fmdev->rx.rds_mode = FM_RDS_SYSTEM_RDS;
+ fmdev->rx.af_mode = FM_RX_RDS_AF_SWITCH_MODE_OFF;
+ fmdev->irq_info.irq_service_timeout_retry = 0;
+
+ fm_rx_reset_rds_cache(fmdev);
+ init_waitqueue_head(&fmdev->rx.rds.read_queue);
+
+ fm_rx_reset_curr_station_info(fmdev);
+ set_bit(FM_CORE_READY, &fmdev->flag);
+exit:
+ return ret;
+}
+
+/* This function will be called from FM V4L2 release function.
+ * Unregister from ST driver.
+ */
+int fmc_release(struct fmdrv_ops *fmdev)
+{
+ int ret;
+
+ if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
+ pr_info("(fmdrv): FM Core is already down");
+ return 0;
+ }
+ /* Sevice pending read */
+ wake_up_interruptible(&fmdev->rx.rds.read_queue);
+
+ tasklet_kill(&fmdev->tx_task);
+ tasklet_kill(&fmdev->rx_task);
+
+ skb_queue_purge(&fmdev->tx_q);
+ skb_queue_purge(&fmdev->rx_q);
+
+ fmdev->response_completion = NULL;
+ fmdev->rx.curr_freq = 0;
+
+ ret = st_unregister(ST_FM);
+ if (ret < 0)
+ pr_err("(fmdrv): Failed to de-register FM from ST - %d", ret);
+ else
+ pr_info("(fmdrv): Successfully unregistered from ST");
+
+ clear_bit(FM_CORE_READY, &fmdev->flag);
+ return ret;
+}
+
+/* Module init function. Ask FM V4L module to register video device.
+ * Allocate memory for FM driver context and RX RDS buffer.
+ */
+static int __init fm_drv_init(void)
+{
+ struct fmdrv_ops *fmdev = NULL;
+ int ret = -ENOMEM;
+
+ pr_info("(fmdrv): FM driver version %s", FM_DRV_VERSION);
+
+ fmdev = kzalloc(sizeof(struct fmdrv_ops), GFP_KERNEL);
+ if (NULL == fmdev) {
+ pr_err("(fmdrv): Can't allocate operation structure memory");
+ goto exit;
+ }
+ fmdev->rx.rds.buf_size = default_rds_buf * FM_RDS_BLOCK_SIZE;
+ fmdev->rx.rds.buffer = kzalloc(fmdev->rx.rds.buf_size, GFP_KERNEL);
+ if (NULL == fmdev->rx.rds.buffer) {
+ pr_err("(fmdrv): Can't allocate rds ring buffer");
+ goto rel_dev;
+ }
+
+ ret = fm_v4l2_init_video_device(fmdev, radio_nr);
+ if (ret < 0)
+ goto rel_rdsbuf;
+
+ fmdev->irq_info.fm_int_handlers = g_IntHandlerTable;
+ fmdev->curr_fmmode = FM_MODE_OFF;
+ goto exit;
+
+rel_rdsbuf:
+ kfree(fmdev->rx.rds.buffer);
+rel_dev:
+ kfree(fmdev);
+exit:
+ return ret;
+}
+
+/* Module exit function. Ask FM V4L module to unregister video device */
+static void __exit fm_drv_exit(void)
+{
+ struct fmdrv_ops *fmdev = NULL;
+
+ fmdev = fm_v4l2_deinit_video_device();
+ if (fmdev != NULL) {
+ kfree(fmdev->rx.rds.buffer);
+ kfree(fmdev);
+ }
+}
+
+module_init(fm_drv_init);
+module_exit(fm_drv_exit);
+
+/* ------------- Module Info ------------- */
+MODULE_AUTHOR("Raja Mani <raja_mani@ti.com>");
+MODULE_DESCRIPTION("FM Driver for Connectivity chip of Texas Instruments. "
+ FM_DRV_VERSION);
+MODULE_VERSION(FM_DRV_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/ti-st/fmdrv_common.h b/drivers/staging/ti-st/fmdrv_common.h
new file mode 100644
index 000000000000..7fb55f3cdca9
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_common.h
@@ -0,0 +1,459 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ * FM Common module header file
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _FMDRV_COMMON_H
+#define _FMDRV_COMMON_H
+
+#define FM_ST_REGISTER_TIMEOUT msecs_to_jiffies(6000) /* 6 sec */
+#define FM_PKT_LOGICAL_CHAN_NUMBER 0x08 /* Logical channel 8 */
+
+#define REG_RD 0x1
+#define REG_WR 0x0
+
+struct fm_reg_table {
+ unsigned char opcode;
+ unsigned char type;
+ char *name;
+};
+
+/* FM register index */
+enum fm_reg_index {
+ /* FM RX registers */
+ STEREO_GET,
+ RSSI_LVL_GET,
+ IF_COUNT_GET,
+ FLAG_GET,
+ RDS_SYNC_GET,
+ RDS_DATA_GET,
+ FREQ_SET,
+ FREQ_GET,
+ AF_FREQ_SET,
+ AF_FREQ_GET,
+ MOST_MODE_SET,
+ MOST_MODE_GET,
+ MOST_BLEND_SET,
+ MOST_BLEND_GET,
+ DEMPH_MODE_SET,
+ DEMPH_MODE_GET,
+ SEARCH_LVL_SET,
+ SEARCH_LVL_GET,
+ RX_BAND_SET,
+ RX_BAND_GET,
+ MUTE_STATUS_SET,
+ MUTE_STATUS_GET,
+ RDS_PAUSE_LVL_SET,
+ RDS_PAUSE_LVL_GET,
+ RDS_PAUSE_DUR_SET,
+ RDS_PAUSE_DUR_GET,
+ RDS_MEM_SET,
+ RDS_MEM_GET,
+ RDS_BLK_B_SET,
+ RDS_BLK_B_GET,
+ RDS_MSK_B_SET,
+ RDS_MSK_B_GET,
+ RDS_PI_MASK_SET,
+ RDS_PI_MASK_GET,
+ RDS_PI_SET,
+ RDS_PI_GET,
+ RDS_SYSTEM_SET,
+ RDS_SYSTEM_GET,
+ INT_MASK_SET,
+ INT_MASK_GET,
+ SEARCH_DIR_SET,
+ SEARCH_DIR_GET,
+ VOLUME_SET,
+ VOLUME_GET,
+ AUDIO_ENABLE_SET,
+ AUDIO_ENABLE_GET,
+ PCM_MODE_SET,
+ PCM_MODE_GET,
+ I2S_MODE_CONFIG_SET,
+ I2S_MODE_CONFIG_GET,
+ POWER_SET,
+ POWER_GET,
+ INTx_CONFIG_SET,
+ INTx_CONFIG_GET,
+ PULL_EN_SET,
+ PULL_EN_GET,
+ HILO_SET,
+ HILO_GET,
+ SWITCH2FREF,
+ FREQ_DRIFT_REPORT,
+ PCE_GET,
+ FIRM_VER_GET,
+ ASIC_VER_GET,
+ ASIC_ID_GET,
+ MAIN_ID_GET,
+ TUNER_MODE_SET,
+ STOP_SEARCH,
+ RDS_CNTRL_SET,
+ WRITE_HARDWARE_REG,
+ CODE_DOWNLOAD,
+ RESET,
+ FM_POWER_MODE,
+ FM_INTERRUPT,
+
+ /* FM TX registers */
+ CHANL_SET,
+ CHANL_GET,
+ CHANL_BW_SET,
+ CHANL_BW_GET,
+ REF_SET,
+ REF_GET,
+ POWER_ENB_SET,
+ POWER_ATT_SET,
+ POWER_ATT_GET,
+ POWER_LEL_SET,
+ POWER_LEL_GET,
+ AUDIO_DEV_SET,
+ AUDIO_DEV_GET,
+ PILOT_DEV_SET,
+ PILOT_DEV_GET,
+ RDS_DEV_SET,
+ RDS_DEV_GET,
+ PUPD_SET,
+ AUDIO_IO_SET,
+ PREMPH_SET,
+ PREMPH_GET,
+ TX_BAND_SET,
+ TX_BAND_GET,
+ MONO_SET,
+ MONO_GET,
+ MUTE,
+ MPX_LMT_ENABLE,
+ LOCK_GET,
+ REF_ERR_SET,
+ PI_SET,
+ PI_GET,
+ TYPE_SET,
+ TYPE_GET,
+ PTY_SET,
+ PTY_GET,
+ AF_SET,
+ AF_GET,
+ DISPLAY_SIZE_SET,
+ DISPLAY_SIZE_GET,
+ RDS_MODE_SET,
+ RDS_MODE_GET,
+ DISPLAY_MODE_SET,
+ DISPLAY_MODE_GET,
+ LENGHT_SET,
+ LENGHT_GET,
+ TOGGLE_AB_SET,
+ TOGGLE_AB_GET,
+ RDS_REP_SET,
+ RDS_REP_GET,
+ RDS_DATA_SET,
+ RDS_DATA_ENB,
+ TA_SET,
+ TA_GET,
+ TP_SET,
+ TP_GET,
+ DI_SET,
+ DI_GET,
+ MS_SET,
+ MS_GET,
+ PS_SCROLL_SPEED_SET,
+ PS_SCROLL_SPEED_GET,
+
+ FM_REG_MAX_ENTRIES
+};
+
+/* SKB helpers */
+struct fm_skb_cb {
+ __u8 fm_opcode;
+ struct completion *completion;
+};
+
+#define fm_cb(skb) ((struct fm_skb_cb *)(skb->cb))
+
+/* FM Channel-8 command message format */
+struct fm_cmd_msg_hdr {
+ __u8 header; /* Logical Channel-8 */
+ __u8 len; /* Number of bytes follows */
+ __u8 fm_opcode; /* FM Opcode */
+ __u8 rd_wr; /* Read/Write command */
+ __u8 dlen; /* Length of payload */
+} __attribute__ ((packed));
+
+#define FM_CMD_MSG_HDR_SIZE 5 /* sizeof(struct fm_cmd_msg_hdr) */
+
+/* FM Channel-8 event messgage format */
+struct fm_event_msg_hdr {
+ __u8 header; /* Logical Channel-8 */
+ __u8 len; /* Number of bytes follows */
+ __u8 status; /* Event status */
+ __u8 num_fm_hci_cmds; /* Number of pkts the host allowed to send */
+ __u8 fm_opcode; /* FM Opcode */
+ __u8 rd_wr; /* Read/Write command */
+ __u8 dlen; /* Length of payload */
+} __attribute__ ((packed));
+
+#define FM_EVT_MSG_HDR_SIZE 7 /* sizeof(struct fm_event_msg_hdr) */
+
+/* TI's magic number in firmware file */
+#define FM_FW_FILE_HEADER_MAGIC 0x42535442
+
+/* Firmware header */
+struct bts_header {
+ uint32_t magic;
+ uint32_t version;
+ uint8_t future[24];
+ uint8_t actions[0];
+} __attribute__ ((packed));
+
+/* Firmware action */
+struct bts_action {
+ uint16_t type;
+ uint16_t size;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+/* Firmware delay */
+struct bts_action_delay {
+ uint32_t msec;
+} __attribute__ ((packed));
+
+#define ACTION_SEND_COMMAND 1
+#define ACTION_WAIT_EVENT 2
+#define ACTION_SERIAL 3
+#define ACTION_DELAY 4
+#define ACTION_REMARKS 6
+
+/* Converts little endian to big endian */
+#define FM_STORE_LE16_TO_BE16(data, value) \
+ (data = ((value >> 8) | ((value & 0xFF) << 8)))
+#define FM_LE16_TO_BE16(value) (((value >> 8) | ((value & 0xFF) << 8)))
+
+/* Converts big endian to little endian */
+#define FM_STORE_BE16_TO_LE16(data, value) \
+ (data = ((value & 0xFF) << 8) | ((value >> 8)))
+#define FM_BE16_TO_LE16(value) (((value & 0xFF) << 8) | ((value >> 8)))
+
+#define FM_ENABLE 1
+#define FM_DISABLE 0
+
+/* FLAG_GET register bits */
+#define FM_FR_EVENT (1 << 0)
+#define FM_BL_EVENT (1 << 1)
+#define FM_RDS_EVENT (1 << 2)
+#define FM_BBLK_EVENT (1 << 3)
+#define FM_LSYNC_EVENT (1 << 4)
+#define FM_LEV_EVENT (1 << 5)
+#define FM_IFFR_EVENT (1 << 6)
+#define FM_PI_EVENT (1 << 7)
+#define FM_PD_EVENT (1 << 8)
+#define FM_STIC_EVENT (1 << 9)
+#define FM_MAL_EVENT (1 << 10)
+#define FM_POW_ENB_EVENT (1 << 11)
+
+/* Firmware files of the FM, ASIC ID and
+ * ASIC version will be appened to this later
+ */
+#define FM_FMC_FW_FILE_START ("fmc_ch8")
+#define FM_RX_FW_FILE_START ("fm_rx_ch8")
+#define FM_TX_FW_FILE_START ("fm_tx_ch8")
+
+#define FM_CHECK_SEND_CMD_STATUS(ret) \
+ if (ret < 0) {\
+ return ret;\
+ }
+#define FM_UNDEFINED_FREQ 0xFFFFFFFF
+
+/* Band types */
+#define FM_BAND_EUROPE_US 0
+#define FM_BAND_JAPAN 1
+
+/* Seek directions */
+#define FM_SEARCH_DIRECTION_DOWN 0
+#define FM_SEARCH_DIRECTION_UP 1
+
+/* Tunner modes */
+#define FM_TUNER_STOP_SEARCH_MODE 0
+#define FM_TUNER_PRESET_MODE 1
+#define FM_TUNER_AUTONOMOUS_SEARCH_MODE 2
+#define FM_TUNER_AF_JUMP_MODE 3
+
+/* Min and Max volume */
+#define FM_RX_VOLUME_MIN 0
+#define FM_RX_VOLUME_MAX 70
+
+/* Volume gain step */
+#define FM_RX_VOLUME_GAIN_STEP 0x370
+
+/* Mute modes */
+#define FM_MUTE_ON 0
+#define FM_MUTE_OFF 1
+#define FM_MUTE_ATTENUATE 2
+
+#define FM_RX_MUTE_UNMUTE_MODE 0x00
+#define FM_RX_MUTE_RF_DEP_MODE 0x01
+#define FM_RX_MUTE_AC_MUTE_MODE 0x02
+#define FM_RX_MUTE_HARD_MUTE_LEFT_MODE 0x04
+#define FM_RX_MUTE_HARD_MUTE_RIGHT_MODE 0x08
+#define FM_RX_MUTE_SOFT_MUTE_FORCE_MODE 0x10
+
+/* RF dependent mute mode */
+#define FM_RX_RF_DEPENDENT_MUTE_ON 1
+#define FM_RX_RF_DEPENDENT_MUTE_OFF 0
+
+/* RSSI threshold min and max */
+#define FM_RX_RSSI_THRESHOLD_MIN -128
+#define FM_RX_RSSI_THRESHOLD_MAX 127
+
+/* Stereo/Mono mode */
+#define FM_STEREO_MODE 0
+#define FM_MONO_MODE 1
+#define FM_STEREO_SOFT_BLEND 1
+
+/* FM RX De-emphasis filter modes */
+#define FM_RX_EMPHASIS_FILTER_50_USEC 0
+#define FM_RX_EMPHASIS_FILTER_75_USEC 1
+
+/* FM RDS modes */
+#define FM_RDS_DISABLE 0
+#define FM_RDS_ENABLE 1
+
+#define FM_NO_PI_CODE 0
+
+/* FM and RX RDS block enable/disable */
+#define FM_RX_POWER_SET_FM_ON_RDS_OFF 0x1
+#define FM_RX_POWET_SET_FM_AND_RDS_BLK_ON 0x3
+#define FM_RX_POWET_SET_FM_AND_RDS_BLK_OFF 0x0
+
+/* RX RDS */
+#define FM_RX_RDS_FLUSH_FIFO 0x1
+#define FM_RX_RDS_FIFO_THRESHOLD 64 /* tuples */
+#define FM_RDS_BLOCK_SIZE 3 /* 3 bytes */
+
+/* RDS block types */
+#define FM_RDS_BLOCK_A 0
+#define FM_RDS_BLOCK_B 1
+#define FM_RDS_BLOCK_C 2
+#define FM_RDS_BLOCK_Ctag 3
+#define FM_RDS_BLOCK_D 4
+#define FM_RDS_BLOCK_E 5
+
+#define FM_RDS_BLOCK_INDEX_A 0
+#define FM_RDS_BLOCK_INDEX_B 1
+#define FM_RDS_BLOCK_INDEX_C 2
+#define FM_RDS_BLOCK_INDEX_D 3
+#define FM_RDS_BLOCK_INDEX_UNKNOWN 0xF0
+
+#define FM_RDS_STATUS_ERROR_MASK 0x18
+
+/* Represents an RDS group type & version.
+ * There are 15 groups, each group has 2
+ * versions: A and B.
+ */
+#define FM_RDS_GROUP_TYPE_MASK_0A ((unsigned long)1<<0)
+#define FM_RDS_GROUP_TYPE_MASK_0B ((unsigned long)1<<1)
+#define FM_RDS_GROUP_TYPE_MASK_1A ((unsigned long)1<<2)
+#define FM_RDS_GROUP_TYPE_MASK_1B ((unsigned long)1<<3)
+#define FM_RDS_GROUP_TYPE_MASK_2A ((unsigned long)1<<4)
+#define FM_RDS_GROUP_TYPE_MASK_2B ((unsigned long)1<<5)
+#define FM_RDS_GROUP_TYPE_MASK_3A ((unsigned long)1<<6)
+#define FM_RDS_GROUP_TYPE_MASK_3B ((unsigned long)1<<7)
+#define FM_RDS_GROUP_TYPE_MASK_4A ((unsigned long)1<<8)
+#define FM_RDS_GROUP_TYPE_MASK_4B ((unsigned long)1<<9)
+#define FM_RDS_GROUP_TYPE_MASK_5A ((unsigned long)1<<10)
+#define FM_RDS_GROUP_TYPE_MASK_5B ((unsigned long)1<<11)
+#define FM_RDS_GROUP_TYPE_MASK_6A ((unsigned long)1<<12)
+#define FM_RDS_GROUP_TYPE_MASK_6B ((unsigned long)1<<13)
+#define FM_RDS_GROUP_TYPE_MASK_7A ((unsigned long)1<<14)
+#define FM_RDS_GROUP_TYPE_MASK_7B ((unsigned long)1<<15)
+#define FM_RDS_GROUP_TYPE_MASK_8A ((unsigned long)1<<16)
+#define FM_RDS_GROUP_TYPE_MASK_8B ((unsigned long)1<<17)
+#define FM_RDS_GROUP_TYPE_MASK_9A ((unsigned long)1<<18)
+#define FM_RDS_GROUP_TYPE_MASK_9B ((unsigned long)1<<19)
+#define FM_RDS_GROUP_TYPE_MASK_10A ((unsigned long)1<<20)
+#define FM_RDS_GROUP_TYPE_MASK_10B ((unsigned long)1<<21)
+#define FM_RDS_GROUP_TYPE_MASK_11A ((unsigned long)1<<22)
+#define FM_RDS_GROUP_TYPE_MASK_11B ((unsigned long)1<<23)
+#define FM_RDS_GROUP_TYPE_MASK_12A ((unsigned long)1<<24)
+#define FM_RDS_GROUP_TYPE_MASK_12B ((unsigned long)1<<25)
+#define FM_RDS_GROUP_TYPE_MASK_13A ((unsigned long)1<<26)
+#define FM_RDS_GROUP_TYPE_MASK_13B ((unsigned long)1<<27)
+#define FM_RDS_GROUP_TYPE_MASK_14A ((unsigned long)1<<28)
+#define FM_RDS_GROUP_TYPE_MASK_14B ((unsigned long)1<<29)
+#define FM_RDS_GROUP_TYPE_MASK_15A ((unsigned long)1<<30)
+#define FM_RDS_GROUP_TYPE_MASK_15B ((unsigned long)1<<31)
+
+/* RX Alternate Frequency info */
+#define FM_RDS_MIN_AF 1
+#define FM_RDS_MAX_AF 204
+#define FM_RDS_MAX_AF_JAPAN 140
+#define FM_RDS_1_AF_FOLLOWS 225
+#define FM_RDS_25_AF_FOLLOWS 249
+
+/* RDS system type (RDS/RBDS) */
+#define FM_RDS_SYSTEM_RDS 0
+#define FM_RDS_SYSTEM_RBDS 1
+
+/* AF on/off */
+#define FM_RX_RDS_AF_SWITCH_MODE_ON 1
+#define FM_RX_RDS_AF_SWITCH_MODE_OFF 0
+
+/* Retry count when interrupt process goes wrong */
+#define FM_IRQ_TIMEOUT_RETRY_MAX 5 /* 5 times */
+
+/* Audio IO set values */
+#define FM_RX_FM_AUDIO_ENABLE_I2S 0x01
+#define FM_RX_FM_AUDIO_ENABLE_ANALOG 0x02
+#define FM_RX_FM_AUDIO_ENABLE_I2S_AND_ANALOG 0x03
+#define FM_RX_FM_AUDIO_ENABLE_DISABLE 0x00
+
+/* HI/LO set values */
+#define FM_RX_IFFREQ_TO_HI_SIDE 0x0
+#define FM_RX_IFFREQ_TO_LO_SIDE 0x1
+#define FM_RX_IFFREQ_HILO_AUTOMATIC 0x2
+
+/* Default RX mode configuration. Chip will be configured
+ * with this default values after loading RX firmware.
+ */
+#define FM_DEFAULT_RX_VOLUME 10
+#define FM_DEFAULT_RSSI_THRESHOLD 3
+
+/* Functions exported by FM common sub-module */
+int fmc_prepare(struct fmdrv_ops *);
+int fmc_release(struct fmdrv_ops *);
+
+void fmc_update_region_info(struct fmdrv_ops *, unsigned char);
+int fmc_send_cmd(struct fmdrv_ops *, unsigned char, void *, int,
+ struct completion *, void *, int *);
+int fmc_is_rds_data_available(struct fmdrv_ops *, struct file *,
+ struct poll_table_struct *);
+int fmc_transfer_rds_from_internal_buff(struct fmdrv_ops *, struct file *,
+ char __user *, size_t);
+
+int fmc_set_frequency(struct fmdrv_ops *, unsigned int);
+int fmc_set_mode(struct fmdrv_ops *, unsigned char);
+int fmc_set_region(struct fmdrv_ops *, unsigned char);
+int fmc_set_mute_mode(struct fmdrv_ops *, unsigned char);
+int fmc_set_stereo_mono(struct fmdrv_ops *, unsigned short);
+int fmc_set_rds_mode(struct fmdrv_ops *, unsigned char);
+
+int fmc_get_frequency(struct fmdrv_ops *, unsigned int *);
+int fmc_get_region(struct fmdrv_ops *, unsigned char *);
+int fmc_get_mode(struct fmdrv_ops *, unsigned char *);
+
+#endif
+
diff --git a/drivers/staging/ti-st/fmdrv_rx.c b/drivers/staging/ti-st/fmdrv_rx.c
new file mode 100644
index 000000000000..a9df59f47840
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_rx.c
@@ -0,0 +1,812 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ * This sub-module of FM driver implements FM RX functionality.
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "fmdrv.h"
+#include "fmdrv_common.h"
+#include "fmdrv_rx.h"
+
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
+
+void fm_rx_reset_rds_cache(struct fmdrv_ops *fmdev)
+{
+ fmdev->rx.rds.flag = FM_RDS_DISABLE;
+ fmdev->rx.rds.last_block_index = 0;
+ fmdev->rx.rds.wr_index = 0;
+ fmdev->rx.rds.rd_index = 0;
+
+ if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
+ fmdev->irq_info.mask |= FM_LEV_EVENT;
+
+}
+
+void fm_rx_reset_curr_station_info(struct fmdrv_ops *fmdev)
+{
+ fmdev->rx.cur_station_info.picode = FM_NO_PI_CODE;
+ fmdev->rx.cur_station_info.no_of_items_in_afcache = 0;
+ fmdev->rx.cur_station_info.af_list_max = 0;
+}
+
+int fm_rx_set_frequency(struct fmdrv_ops *fmdev, unsigned int freq_to_set)
+{
+ unsigned long timeleft;
+ unsigned short payload, curr_frq, frq_index;
+ unsigned int curr_frq_in_khz;
+ int ret, resp_len;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX) {
+ ret = -EPERM;
+ goto exit;
+ }
+
+ if (freq_to_set < fmdev->rx.region.bottom_frequency ||
+ freq_to_set > fmdev->rx.region.top_frequency) {
+ pr_err("(fmdrv): Invalid frequency %d", freq_to_set);
+ ret = -EINVAL;
+ goto exit;
+ }
+ /* Set audio enable */
+ FM_STORE_LE16_TO_BE16(payload, FM_RX_FM_AUDIO_ENABLE_I2S_AND_ANALOG);
+ ret = fmc_send_cmd(fmdev, AUDIO_ENABLE_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Set hilo to automatic selection */
+ FM_STORE_LE16_TO_BE16(payload, FM_RX_IFFREQ_HILO_AUTOMATIC);
+ ret = fmc_send_cmd(fmdev, HILO_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Calculate frequency index to write */
+ frq_index = (freq_to_set - fmdev->rx.region.bottom_frequency) /
+ fmdev->rx.region.channel_spacing;
+
+ /* Set frequency index */
+ FM_STORE_LE16_TO_BE16(payload, frq_index);
+ ret = fmc_send_cmd(fmdev, FREQ_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Read flags - just to clear any pending interrupts if we had */
+ ret = fmc_send_cmd(fmdev, FLAG_GET, NULL, 2,
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Enable FR, BL interrupts */
+ fmdev->irq_info.mask |= (FM_FR_EVENT | FM_BL_EVENT);
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Start tune */
+ FM_STORE_LE16_TO_BE16(payload, FM_TUNER_PRESET_MODE);
+ ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Wait for tune ended interrupt */
+ init_completion(&fmdev->maintask_completion);
+ timeleft = wait_for_completion_timeout(&fmdev->maintask_completion,
+ FM_DRV_TX_TIMEOUT);
+ if (!timeleft) {
+ pr_err("(fmdrv): Timeout(%d sec),didn't get tune ended int",
+ jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+
+ /* Read freq back to confirm */
+ ret = fmc_send_cmd(fmdev, FREQ_GET, NULL, 2,
+ &fmdev->maintask_completion, &curr_frq, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ curr_frq = FM_BE16_TO_LE16(curr_frq);
+ curr_frq_in_khz = (fmdev->rx.region.bottom_frequency
+ + ((unsigned int)curr_frq * fmdev->rx.region.channel_spacing));
+
+ /* Re-enable default FM interrupts */
+ fmdev->irq_info.mask &= ~(FM_FR_EVENT | FM_BL_EVENT);
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ if (curr_frq_in_khz != freq_to_set) {
+ pr_err("(fmdrv): Current chip frequency(%d) is not matching"
+ " with requested frequency(%d)", curr_frq_in_khz,
+ freq_to_set);
+ ret = -EAGAIN;
+ goto exit;
+ }
+
+ /* Update local cache */
+ fmdev->rx.curr_freq = curr_frq_in_khz;
+
+ /* Reset RDS cache and current station pointers */
+ fm_rx_reset_rds_cache(fmdev);
+ fm_rx_reset_curr_station_info(fmdev);
+
+ /* Do we need to reset anything else? */
+exit:
+ return ret;
+}
+
+int fm_rx_seek(struct fmdrv_ops *fmdev, unsigned int seek_upward,
+ unsigned int wrap_around)
+{
+ int resp_len;
+ unsigned short curr_frq, next_frq, last_frq;
+ unsigned short payload, int_reason;
+ char offset;
+ unsigned long timeleft;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX) {
+ ret = -EPERM;
+ goto exit;
+ }
+
+ /* Read the current frequency from chip */
+ ret = fmc_send_cmd(fmdev, FREQ_GET, NULL, sizeof(curr_frq),
+ &fmdev->maintask_completion, &curr_frq, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ curr_frq = FM_BE16_TO_LE16(curr_frq);
+
+ last_frq =
+ (fmdev->rx.region.top_frequency - fmdev->rx.region.bottom_frequency)
+ / fmdev->rx.region.channel_spacing;
+
+ /* Check the offset in order to be aligned to the 100KHz steps */
+ offset = curr_frq % 2;
+
+ next_frq = seek_upward ? curr_frq + 2 /* Seek Up */ :
+ curr_frq - 2 /* Seek Down */ ;
+
+ /* Add or subtract offset (0/1) in order
+ * to stay aligned to the 100KHz steps
+ */
+ if ((short)next_frq < 0)
+ next_frq = last_frq - offset;
+ else if (next_frq > last_frq)
+ next_frq = 0 + offset;
+
+ /* Set calculated next frequency to perform seek */
+ FM_STORE_LE16_TO_BE16(payload, next_frq);
+ ret = fmc_send_cmd(fmdev, FREQ_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Set search direction (0:Seek Down, 1:Seek Up) */
+ FM_STORE_LE16_TO_BE16(payload, (seek_upward ? FM_SEARCH_DIRECTION_UP :
+ FM_SEARCH_DIRECTION_DOWN));
+ ret = fmc_send_cmd(fmdev, SEARCH_DIR_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Read flags - just to clear any pending interrupts if we had */
+ ret = fmc_send_cmd(fmdev, FLAG_GET, NULL, 2,
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Enable FR, BL interrupts */
+ fmdev->irq_info.mask |= (FM_FR_EVENT | FM_BL_EVENT);
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Start seek */
+ FM_STORE_LE16_TO_BE16(payload, FM_TUNER_AUTONOMOUS_SEARCH_MODE);
+ ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Wait for tune ended/band limit reached interrupt */
+ init_completion(&fmdev->maintask_completion);
+ timeleft = wait_for_completion_timeout(&fmdev->maintask_completion,
+ FM_DRV_RX_SEEK_TIMEOUT);
+ if (!timeleft) {
+ pr_err("(fmdrv): Timeout(%d sec),didn't get tune ended int",
+ jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000);
+ ret = -ETIMEDOUT;
+ goto exit;
+ }
+ int_reason = fmdev->irq_info.flag & 0x3;
+
+ /* Re-enable default FM interrupts */
+ fmdev->irq_info.mask &= ~(FM_FR_EVENT | FM_BL_EVENT);
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Read freq to know where operation tune operation stopped */
+ ret = fmc_send_cmd(fmdev, FREQ_GET, NULL, 2,
+ &fmdev->maintask_completion, &curr_frq, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ curr_frq = FM_BE16_TO_LE16(curr_frq);
+ fmdev->rx.curr_freq = (fmdev->rx.region.bottom_frequency +
+ ((unsigned int)curr_frq *
+ fmdev->rx.region.channel_spacing));
+
+ /* Reset RDS cache and current station pointers */
+ fm_rx_reset_rds_cache(fmdev);
+ fm_rx_reset_curr_station_info(fmdev);
+
+ /* Return error if band limit is reached */
+ if (int_reason & FM_BL_EVENT) {
+ pr_info("(fmdrv): band limit reached\n");
+ ret = -EAGAIN;
+ }
+exit:
+ return ret;
+}
+
+int fm_rx_set_volume(struct fmdrv_ops *fmdev, unsigned short vol_to_set)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (vol_to_set > FM_RX_VOLUME_MAX) {
+ pr_err("(fmdrv): Volume is not within(%d-%d) range",
+ FM_RX_VOLUME_MIN, FM_RX_VOLUME_MAX);
+ return -EINVAL;
+ }
+ vol_to_set *= FM_RX_VOLUME_GAIN_STEP;
+
+ FM_STORE_LE16_TO_BE16(payload, vol_to_set);
+ ret = fmc_send_cmd(fmdev, VOLUME_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ fmdev->rx.curr_volume = vol_to_set;
+ return ret;
+}
+
+/* Get volume */
+int fm_rx_get_volume(struct fmdrv_ops *fmdev, unsigned short *curr_vol)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (curr_vol == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *curr_vol = fmdev->rx.curr_volume / FM_RX_VOLUME_GAIN_STEP;
+ return 0;
+}
+
+/* To get current band's bottom and top frequency */
+int fm_rx_get_currband_lowhigh_freq(struct fmdrv_ops *fmdev,
+ unsigned int *bottom_frequency,
+ unsigned int *top_frequency)
+{
+ if (bottom_frequency != NULL)
+ *bottom_frequency = fmdev->rx.region.bottom_frequency;
+
+ if (top_frequency != NULL)
+ *top_frequency = fmdev->rx.region.top_frequency;
+
+ return 0;
+}
+
+/* Sets band (0-Europe/US; 1-Japan) */
+int fm_rx_set_region(struct fmdrv_ops *fmdev,
+ unsigned char region_to_set)
+{
+ unsigned short payload;
+ unsigned int new_frq;
+ int ret = -EPERM;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ goto exit;
+
+ if (region_to_set != FM_BAND_EUROPE_US &&
+ region_to_set != FM_BAND_JAPAN) {
+ pr_err("(fmdrv): Invalid band\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ if (fmdev->rx.region.region_index == region_to_set) {
+ pr_err("(fmdrv): Requested band is already configured\n");
+ goto exit;
+ }
+ /* Send cmd to set the band */
+ FM_STORE_LE16_TO_BE16(payload, (unsigned short)region_to_set);
+ ret = fmc_send_cmd(fmdev, RX_BAND_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ fmc_update_region_info(fmdev, region_to_set);
+
+ /* Check whether current RX frequency is within band boundary */
+ if (fmdev->curr_fmmode == FM_MODE_RX) {
+ new_frq = 0;
+ if (fmdev->rx.curr_freq < fmdev->rx.region.bottom_frequency)
+ new_frq = fmdev->rx.region.bottom_frequency;
+ else if (fmdev->rx.curr_freq > fmdev->rx.region.top_frequency)
+ new_frq = fmdev->rx.region.top_frequency;
+
+ if (new_frq) {
+ pr_info("(fmdrv): "
+ "Current freq is not within band limit boundary,"
+ "switching to %d KHz", new_frq);
+ /* Current RX frequency is not
+ * within boundary. So, update it
+ */
+ ret = fm_rx_set_frequency(fmdev, new_frq);
+ }
+ }
+exit:
+ return ret;
+}
+
+/* Reads current mute mode (Mute Off/On/Attenuate)*/
+int fm_rx_get_mute_mode(struct fmdrv_ops *fmdev,
+ unsigned char *curr_mute_mode)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (curr_mute_mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *curr_mute_mode = fmdev->rx.curr_mute_mode;
+ return 0;
+}
+
+static int __fm_config_rx_mute_reg(struct fmdrv_ops *fmdev)
+{
+ unsigned short payload, muteval;
+ int ret;
+
+ muteval = 0;
+ switch (fmdev->rx.curr_mute_mode) {
+ case FM_MUTE_ON:
+ muteval = FM_RX_MUTE_AC_MUTE_MODE;
+ break;
+ case FM_MUTE_OFF:
+ muteval = FM_RX_MUTE_UNMUTE_MODE;
+ break;
+ case FM_MUTE_ATTENUATE:
+ muteval = FM_RX_MUTE_SOFT_MUTE_FORCE_MODE;
+ break;
+ }
+ if (fmdev->rx.curr_rf_depend_mute == FM_RX_RF_DEPENDENT_MUTE_ON)
+ muteval |= FM_RX_MUTE_RF_DEP_MODE;
+ else
+ muteval &= ~FM_RX_MUTE_RF_DEP_MODE;
+
+ FM_STORE_LE16_TO_BE16(payload, muteval);
+ ret = fmc_send_cmd(fmdev, MUTE_STATUS_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+ return 0;
+}
+
+/* Configures mute mode (Mute Off/On/Attenuate) */
+int fm_rx_set_mute_mode(struct fmdrv_ops *fmdev,
+ unsigned char mute_mode_toset)
+{
+ unsigned char org_state;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (fmdev->rx.curr_mute_mode == mute_mode_toset)
+ return 0;
+
+ org_state = fmdev->rx.curr_mute_mode;
+ fmdev->rx.curr_mute_mode = mute_mode_toset;
+
+ ret = __fm_config_rx_mute_reg(fmdev);
+ if (ret < 0) {
+ fmdev->rx.curr_mute_mode = org_state;
+ return ret;
+ }
+ return 0;
+}
+
+/* Gets RF dependent soft mute mode enable/disable status */
+int fm_rx_get_rfdepend_softmute(struct fmdrv_ops *fmdev,
+ unsigned char *curr_mute_mode)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+
+ if (curr_mute_mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *curr_mute_mode = fmdev->rx.curr_rf_depend_mute;
+ return 0;
+}
+
+/* Sets RF dependent soft mute mode */
+int fm_rx_set_rfdepend_softmute(struct fmdrv_ops *fmdev,
+ unsigned char rfdepend_mute)
+{
+ unsigned char org_state;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+
+ if (rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_ON &&
+ rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_OFF) {
+ pr_err("(fmdrv): Invalid RF dependent soft mute");
+ return -EINVAL;
+ }
+ if (fmdev->rx.curr_rf_depend_mute == rfdepend_mute)
+ return 0;
+
+ org_state = fmdev->rx.curr_rf_depend_mute;
+ fmdev->rx.curr_rf_depend_mute = rfdepend_mute;
+
+ ret = __fm_config_rx_mute_reg(fmdev);
+ if (ret < 0) {
+ fmdev->rx.curr_rf_depend_mute = org_state;
+ return ret;
+ }
+ return 0;
+}
+
+/* Returns the signal strength level of current channel */
+int fm_rx_get_rssi_level(struct fmdrv_ops *fmdev,
+ unsigned short *rssilvl)
+{
+ unsigned short curr_rssi_lel;
+ int resp_len;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (rssilvl == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ /* Read current RSSI level */
+ ret = fmc_send_cmd(fmdev, RSSI_LVL_GET, NULL, 2,
+ &fmdev->maintask_completion, &curr_rssi_lel, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ *rssilvl = FM_BE16_TO_LE16(curr_rssi_lel);
+
+ return 0;
+}
+
+/* Sets the signal strength level that once reached
+ * will stop the auto search process
+ */
+int fm_rx_set_rssi_threshold(struct fmdrv_ops *fmdev,
+ short rssi_lvl_toset)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (rssi_lvl_toset < FM_RX_RSSI_THRESHOLD_MIN ||
+ rssi_lvl_toset > FM_RX_RSSI_THRESHOLD_MAX) {
+ pr_err("(fmdrv): Invalid RSSI threshold level");
+ return -EINVAL;
+ }
+ FM_STORE_LE16_TO_BE16(payload, (unsigned short)rssi_lvl_toset);
+ ret = fmc_send_cmd(fmdev, SEARCH_LVL_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ fmdev->rx.curr_rssi_threshold = rssi_lvl_toset;
+ return 0;
+}
+
+/* Returns current RX RSSI threshold value */
+int fm_rx_get_rssi_threshold(struct fmdrv_ops *fmdev, short *curr_rssi_lvl)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (curr_rssi_lvl == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *curr_rssi_lvl = fmdev->rx.curr_rssi_threshold;
+ return 0;
+}
+
+/* Sets RX stereo/mono modes */
+int fm_rx_set_stereo_mono(struct fmdrv_ops *fmdev, unsigned short mode)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (mode != FM_STEREO_MODE && mode != FM_MONO_MODE) {
+ pr_err("(fmdrv): Invalid mode");
+ return -EINVAL;
+ }
+ /* Set stereo/mono mode */
+ FM_STORE_LE16_TO_BE16(payload, (unsigned short)mode);
+ ret = fmc_send_cmd(fmdev, MOST_MODE_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Set stereo blending mode */
+ FM_STORE_LE16_TO_BE16(payload, FM_STEREO_SOFT_BLEND);
+ ret = fmc_send_cmd(fmdev, MOST_BLEND_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ return 0;
+}
+
+/* Gets current RX stereo/mono mode */
+int fm_rx_get_stereo_mono(struct fmdrv_ops *fmdev, unsigned short *mode)
+{
+ unsigned short curr_mode;
+ int ret, resp_len;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ ret = fmc_send_cmd(fmdev, MOST_MODE_GET, NULL, 2,
+ &fmdev->maintask_completion, &curr_mode, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ *mode = FM_BE16_TO_LE16(curr_mode);
+ return 0;
+}
+
+/* Choose RX de-emphasis filter mode (50us/75us) */
+int fm_rx_set_deemphasis_mode(struct fmdrv_ops *fmdev, unsigned short mode)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (mode != FM_RX_EMPHASIS_FILTER_50_USEC &&
+ mode != FM_RX_EMPHASIS_FILTER_75_USEC) {
+ pr_err("(fmdrv): Invalid rx de-emphasis mode");
+ return -EINVAL;
+ }
+ FM_STORE_LE16_TO_BE16(payload, mode);
+ ret = fmc_send_cmd(fmdev, DEMPH_MODE_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ return 0;
+}
+
+/* Gets current RX de-emphasis filter mode */
+int fm_rx_get_deemphasis_mode(struct fmdrv_ops *fmdev,
+ unsigned short *curr_deemphasis_mode)
+{
+ unsigned short curr_mode;
+ int ret, resp_len;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (curr_deemphasis_mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ ret = fmc_send_cmd(fmdev, DEMPH_MODE_GET, NULL, 2,
+ &fmdev->maintask_completion, &curr_mode, &resp_len);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ *curr_deemphasis_mode = FM_BE16_TO_LE16(curr_mode);
+ return 0;
+}
+
+/* Enable/Disable RX RDS */
+int fm_rx_set_rds_mode(struct fmdrv_ops *fmdev, unsigned char rds_en_dis)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) {
+ pr_err("(fmdrv): Invalid rds option");
+ return -EINVAL;
+ }
+ if (rds_en_dis == FM_RDS_ENABLE
+ && fmdev->rx.rds.flag == FM_RDS_DISABLE) {
+ /* Turn on RX RDS */
+ /* Turn on RDS circuit */
+ FM_STORE_LE16_TO_BE16(payload,
+ FM_RX_POWET_SET_FM_AND_RDS_BLK_ON);
+ ret = fmc_send_cmd(fmdev, POWER_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Clear and rest RDS FIFO */
+ FM_STORE_LE16_TO_BE16(payload, FM_RX_RDS_FLUSH_FIFO);
+ ret = fmc_send_cmd(fmdev, RDS_CNTRL_SET, &payload,
+ sizeof(payload), &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Read flags - just to clear any
+ * pending interrupts if we had
+ */
+ ret = fmc_send_cmd(fmdev, FLAG_GET, NULL, 2,
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Set RDS FIFO threshold value */
+ FM_STORE_LE16_TO_BE16(payload, FM_RX_RDS_FIFO_THRESHOLD);
+ ret = fmc_send_cmd(fmdev, RDS_MEM_SET, &payload,
+ sizeof(payload), &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Enable RDS interrupt */
+ fmdev->irq_info.mask |= FM_RDS_EVENT;
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload,
+ sizeof(payload), &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Update our local flag */
+ fmdev->rx.rds.flag = FM_RDS_ENABLE;
+ } else if (rds_en_dis == FM_RDS_DISABLE
+ && fmdev->rx.rds.flag == FM_RDS_ENABLE) {
+ /* Turn off RX RDS */
+ /* Turn off RDS circuit */
+ FM_STORE_LE16_TO_BE16(payload, FM_RX_POWER_SET_FM_ON_RDS_OFF);
+ ret = fmc_send_cmd(fmdev, POWER_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ /* Reset RDS pointers */
+ fmdev->rx.rds.last_block_index = 0;
+ fmdev->rx.rds.wr_index = 0;
+ fmdev->rx.rds.rd_index = 0;
+ fm_rx_reset_curr_station_info(fmdev);
+
+ /* Update RDS local cache */
+ fmdev->irq_info.mask &= ~(FM_RDS_EVENT);
+ fmdev->rx.rds.flag = FM_RDS_DISABLE;
+ }
+ return 0;
+}
+
+/* Returns current RX RDS enable/disable status */
+int fm_rx_get_rds_mode(struct fmdrv_ops *fmdev,
+ unsigned char *curr_rds_en_dis)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (curr_rds_en_dis == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *curr_rds_en_dis = fmdev->rx.rds.flag;
+ return 0;
+}
+
+/* Sets RDS operation mode (RDS/RDBS) */
+int fm_rx_set_rds_system(struct fmdrv_ops *fmdev, unsigned char rds_mode)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (rds_mode != FM_RDS_SYSTEM_RDS && rds_mode != FM_RDS_SYSTEM_RBDS) {
+ pr_err("(fmdrv): Invalid rds mode");
+ return -EINVAL;
+ }
+ /* Set RDS operation mode */
+ FM_STORE_LE16_TO_BE16(payload, (unsigned short)rds_mode);
+ ret = fmc_send_cmd(fmdev, RDS_SYSTEM_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ fmdev->rx.rds_mode = rds_mode;
+ return 0;
+}
+
+/* Returns current RDS operation mode */
+int fm_rx_get_rds_system(struct fmdrv_ops *fmdev,
+ unsigned char *rds_mode)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (rds_mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *rds_mode = fmdev->rx.rds_mode;
+ return 0;
+}
+
+/* Configures Alternate Frequency switch mode */
+int fm_rx_set_af_switch(struct fmdrv_ops *fmdev, unsigned char af_mode)
+{
+ unsigned short payload;
+ int ret;
+
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (af_mode != FM_RX_RDS_AF_SWITCH_MODE_ON &&
+ af_mode != FM_RX_RDS_AF_SWITCH_MODE_OFF) {
+ pr_err("(fmdrv): Invalid af mode");
+ return -EINVAL;
+ }
+ /* Enable/disable low RSSI interrupt based on af_mode */
+ if (af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
+ fmdev->irq_info.mask |= FM_LEV_EVENT;
+ else
+ fmdev->irq_info.mask &= ~FM_LEV_EVENT;
+
+ FM_STORE_LE16_TO_BE16(payload, fmdev->irq_info.mask);
+ ret = fmc_send_cmd(fmdev, INT_MASK_SET, &payload, sizeof(payload),
+ &fmdev->maintask_completion, NULL, NULL);
+ FM_CHECK_SEND_CMD_STATUS(ret);
+
+ fmdev->rx.af_mode = af_mode;
+ return 0;
+}
+
+/* Returns Alternate Frequency switch status */
+int fm_rx_get_af_switch(struct fmdrv_ops *fmdev, unsigned char *af_mode)
+{
+ if (fmdev->curr_fmmode != FM_MODE_RX)
+ return -EPERM;
+
+ if (af_mode == NULL) {
+ pr_err("(fmdrv): Invalid memory");
+ return -ENOMEM;
+ }
+ *af_mode = fmdev->rx.af_mode;
+ return 0;
+}
diff --git a/drivers/staging/ti-st/fmdrv_rx.h b/drivers/staging/ti-st/fmdrv_rx.h
new file mode 100644
index 000000000000..2ca3eda4879b
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_rx.h
@@ -0,0 +1,56 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ * FM RX module header.
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _FMDRV_RX_H
+#define _FMDRV_RX_H
+
+int fm_rx_set_frequency(struct fmdrv_ops*, unsigned int);
+int fm_rx_set_mute_mode(struct fmdrv_ops*, unsigned char);
+int fm_rx_set_stereo_mono(struct fmdrv_ops*, unsigned short);
+int fm_rx_set_rds_mode(struct fmdrv_ops*, unsigned char);
+int fm_rx_set_rds_system(struct fmdrv_ops *, unsigned char);
+int fm_rx_set_volume(struct fmdrv_ops*, unsigned short);
+int fm_rx_set_rssi_threshold(struct fmdrv_ops*, short);
+int fm_rx_set_region(struct fmdrv_ops*, unsigned char);
+int fm_rx_set_rfdepend_softmute(struct fmdrv_ops *, unsigned char);
+int fm_rx_set_deemphasis_mode(struct fmdrv_ops *, unsigned short);
+int fm_rx_set_af_switch(struct fmdrv_ops *, unsigned char);
+
+void fm_rx_reset_rds_cache(struct fmdrv_ops *);
+void fm_rx_reset_curr_station_info(struct fmdrv_ops *);
+
+int fm_rx_seek(struct fmdrv_ops*, unsigned int, unsigned int);
+
+int fm_rx_get_rds_mode(struct fmdrv_ops*, unsigned char*);
+int fm_rx_get_rds_system(struct fmdrv_ops *, unsigned char*);
+int fm_rx_get_mute_mode(struct fmdrv_ops*, unsigned char*);
+int fm_rx_get_volume(struct fmdrv_ops*, unsigned short*);
+int fm_rx_get_currband_lowhigh_freq(struct fmdrv_ops*,
+ unsigned int*, unsigned int*);
+int fm_rx_get_stereo_mono(struct fmdrv_ops *, unsigned short*);
+int fm_rx_get_rssi_level(struct fmdrv_ops *, unsigned short*);
+int fm_rx_get_rssi_threshold(struct fmdrv_ops *, short*);
+int fm_rx_get_rfdepend_softmute(struct fmdrv_ops *, unsigned char*);
+int fm_rx_get_deemphasis_mode(struct fmdrv_ops *, unsigned short*);
+int fm_rx_get_af_switch(struct fmdrv_ops *, unsigned char *);
+
+#endif
+
diff --git a/drivers/staging/ti-st/fmdrv_v4l2.c b/drivers/staging/ti-st/fmdrv_v4l2.c
new file mode 100644
index 000000000000..c546a0f0cf0c
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_v4l2.c
@@ -0,0 +1,557 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ * This file provides interfaces to V4L2 subsystem.
+ *
+ * This module registers with V4L2 subsystem as Radio
+ * data system interface (/dev/radio). During the registration,
+ * it will expose two set of function pointers to V4L2 subsystem.
+ *
+ * 1) File operation related API (open, close, read, write, poll...etc).
+ * 2) Set of V4L2 IOCTL complaint API.
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "fmdrv.h"
+#include "fmdrv_v4l2.h"
+#include "fmdrv_common.h"
+#include "fmdrv_rx.h"
+/* TODO: Enable when FM TX is supported */
+/* #include "fmdrv_tx.h" */
+
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
+
+static struct video_device *gradio_dev;
+static unsigned char radio_disconnected;
+
+/* Query control */
+static struct v4l2_queryctrl fmdrv_v4l2_queryctrl[] = {
+ {
+ .id = V4L2_CID_AUDIO_VOLUME,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Volume",
+ .minimum = FM_RX_VOLUME_MIN,
+ .maximum = FM_RX_VOLUME_MAX,
+ .step = 1,
+ .default_value = FM_DEFAULT_RX_VOLUME,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BALANCE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_BASS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_TREBLE,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+ {
+ .id = V4L2_CID_AUDIO_MUTE,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .name = "Mute",
+ .minimum = 0,
+ .maximum = 2,
+ .step = 1,
+ .default_value = FM_MUTE_OFF,
+ },
+ {
+ .id = V4L2_CID_AUDIO_LOUDNESS,
+ .flags = V4L2_CTRL_FLAG_DISABLED,
+ },
+};
+
+/* -- V4L2 RADIO (/dev/radioX) device file operation interfaces --- */
+/* Read RX RDS data */
+static ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned char rds_mode;
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+
+ if (!radio_disconnected) {
+ pr_err("(fmdrv): FM device is already disconnected\n");
+ ret = -EIO;
+ goto exit;
+ }
+
+ /* Turn on RDS mode , if it is disabled */
+ ret = fm_rx_get_rds_mode(fmdev, &rds_mode);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to read current rds mode");
+ goto exit;
+ }
+ if (rds_mode == FM_RDS_DISABLE) {
+ ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to enable rds mode");
+ goto exit;
+ }
+ }
+ /* Copy RDS data from internal buffer to user buffer */
+ ret = fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count);
+
+exit:
+ return ret;
+}
+
+/* Write RDS data.
+ * TODO: When FM TX support is added, use "V4L2_CID_RDS_TX_XXXX" codes,
+ * instead of write operation.
+ */
+static ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ struct tx_rds rds;
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ ret = copy_from_user(&rds, buf, sizeof(rds));
+ pr_info("(fmdrv): (%d)type: %d, text %s, af %d",
+ ret, rds.text_type, rds.text, rds.af_freq);
+
+ fmdev = video_drvdata(file);
+ /* TODO: Enable when FM TX is supported */
+ /* fm_tx_set_radio_text(fmdev, rds.text, rds.text_type); */
+ /* fm_tx_set_af(fmdev, rds.af_freq); */
+
+ return 0;
+}
+
+static unsigned int fm_v4l2_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+ ret = fmc_is_rds_data_available(fmdev, file, pts);
+ if (!ret)
+ return POLLIN | POLLRDNORM;
+ return 0;
+}
+
+/* Handle open request for "/dev/radioX" device.
+ * Start with FM RX mode as default.
+ */
+static int fm_v4l2_fops_open(struct file *file)
+{
+ int ret;
+ struct fmdrv_ops *fmdev = NULL;
+
+ /* Don't allow multiple open */
+ if (radio_disconnected) {
+ pr_err("(fmdrv): FM device is already opened\n");
+ ret = -EBUSY;
+ goto exit;
+ }
+
+ fmdev = video_drvdata(file);
+ ret = fmc_prepare(fmdev);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to prepare FM CORE");
+ goto exit;
+ }
+
+ pr_info("(fmdrv): Load FM RX firmware..");
+ ret = fmc_set_mode(fmdev, FM_MODE_RX);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to load FM RX firmware");
+ goto exit;
+ }
+ radio_disconnected = 1;
+
+exit:
+ return ret;
+}
+
+static int fm_v4l2_fops_release(struct file *file)
+{
+ int ret = 0;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+ if (!radio_disconnected) {
+ pr_info("(fmdrv):FM dev already closed, close called again?");
+ goto exit;
+ }
+ ret = fmc_set_mode(fmdev, FM_MODE_OFF);
+ if (ret < 0) {
+ pr_err("(fmdrv): Unable to turn off the chip");
+ goto exit;
+ }
+ ret = fmc_release(fmdev);
+ if (ret < 0) {
+ pr_err("(fmdrv): FM CORE release failed");
+ goto exit;
+ }
+ radio_disconnected = 0;
+
+exit:
+ return ret;
+}
+
+/* V4L2 RADIO (/dev/radioX) device IOCTL interfaces */
+static int fm_v4l2_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ strlcpy(capability->driver, FM_DRV_NAME, sizeof(capability->driver));
+ strlcpy(capability->card, FM_DRV_CARD_SHORT_NAME,
+ sizeof(capability->card));
+ sprintf(capability->bus_info, "UART");
+ capability->version = FM_DRV_RADIO_VERSION;
+ capability->capabilities = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER |
+ V4L2_CAP_RADIO | V4L2_CAP_MODULATOR |
+ V4L2_CAP_AUDIO | V4L2_CAP_READWRITE |
+ V4L2_CAP_RDS_CAPTURE;
+ return 0;
+}
+
+static int fm_v4l2_vidioc_queryctrl(struct file *file, void *priv,
+ struct v4l2_queryctrl *qc)
+{
+ int index;
+ int ret = -EINVAL;
+
+ if (qc->id < V4L2_CID_BASE)
+ return ret;
+
+ /* Search control ID and copy its properties */
+ for (index = 0; index < NO_OF_ENTRIES_IN_ARRAY(fmdrv_v4l2_queryctrl);\
+ index++) {
+ if (qc->id && qc->id == fmdrv_v4l2_queryctrl[index].id) {
+ memcpy(qc, &(fmdrv_v4l2_queryctrl[index]), sizeof(*qc));
+ ret = 0;
+ break;
+ }
+ }
+ return ret;
+}
+
+static int fm_v4l2_vidioc_g_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ int ret = -EINVAL;
+ unsigned short curr_vol;
+ unsigned char curr_mute_mode;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE: /* get mute mode */
+ ret = fm_rx_get_mute_mode(fmdev, &curr_mute_mode);
+ if (ret < 0)
+ goto exit;
+ ctrl->value = curr_mute_mode;
+ break;
+ case V4L2_CID_AUDIO_VOLUME: /* get volume */
+ ret = fm_rx_get_volume(fmdev, &curr_vol);
+ if (ret < 0)
+ goto exit;
+ ctrl->value = curr_vol;
+ break;
+ }
+
+exit:
+ return ret;
+}
+
+static int fm_v4l2_vidioc_s_ctrl(struct file *file, void *priv,
+ struct v4l2_control *ctrl)
+{
+ int ret = -EINVAL;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE: /* set mute */
+ ret = fmc_set_mute_mode(fmdev, (unsigned char)ctrl->value);
+ if (ret < 0)
+ goto exit;
+ break;
+ case V4L2_CID_AUDIO_VOLUME: /* set volume */
+ ret = fm_rx_set_volume(fmdev, (unsigned short)ctrl->value);
+ if (ret < 0)
+ goto exit;
+ break;
+ }
+
+exit:
+ return ret;
+}
+
+static int fm_v4l2_vidioc_g_audio(struct file *file, void *priv,
+ struct v4l2_audio *audio)
+{
+ memset(audio, 0, sizeof(*audio));
+ audio->index = 0;
+ strcpy(audio->name, "Radio");
+ audio->capability = V4L2_AUDCAP_STEREO;
+
+ return 0;
+}
+
+static int fm_v4l2_vidioc_s_audio(struct file *file, void *priv,
+ struct v4l2_audio *audio)
+{
+ if (audio->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/* Get tuner attributes. If current mode is NOT RX, set to RX */
+static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ unsigned int bottom_frequency;
+ unsigned int top_frequency;
+ unsigned short stereo_mono_mode;
+ unsigned short rssilvl;
+ int ret = -EINVAL;
+ struct fmdrv_ops *fmdev;
+
+ if (tuner->index != 0)
+ goto exit;
+
+ fmdev = video_drvdata(file);
+ if (fmdev->curr_fmmode != FM_MODE_RX) {
+ ret = fmc_set_mode(fmdev, FM_MODE_RX);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to set RX mode; unable to " \
+ "read tuner attributes\n");
+ goto exit;
+ }
+ }
+
+ ret = fm_rx_get_currband_lowhigh_freq(fmdev, &bottom_frequency,
+ &top_frequency);
+ if (ret < 0)
+ goto exit;
+
+ ret = fm_rx_get_stereo_mono(fmdev, &stereo_mono_mode);
+ if (ret < 0)
+ goto exit;
+
+ ret = fm_rx_get_rssi_level(fmdev, &rssilvl);
+ if (ret < 0)
+ goto exit;
+
+ strcpy(tuner->name, "FM");
+ tuner->type = V4L2_TUNER_RADIO;
+ /* Store rangelow and rangehigh freq in unit of 62.5 KHz */
+ tuner->rangelow = (bottom_frequency * 10000) / 625;
+ tuner->rangehigh = (top_frequency * 10000) / 625;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO |
+ ((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0);
+ tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS;
+ tuner->audmode = (stereo_mono_mode ?
+ V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO);
+
+ /* Actual rssi value lies in between -128 to +127.
+ * Convert this range from 0 to 255 by adding +128
+ */
+ rssilvl += 128;
+
+ /* Return signal strength value should be within 0 to 65535.
+ * Find out correct signal radio by multiplying (65535/255) = 257
+ */
+ tuner->signal = rssilvl * 257;
+ tuner->afc = 0;
+
+exit:
+ return ret;
+}
+
+/* Set tuner attributes. If current mode is NOT RX, set to RX.
+ * Currently, we set only audio mode (mono/stereo) and RDS state (on/off).
+ * Should we set other tuner attributes, too?
+ */
+static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ unsigned short aud_mode;
+ unsigned char rds_mode;
+ int ret = -EINVAL;
+ struct fmdrv_ops *fmdev;
+
+ if (tuner->index != 0)
+ goto exit;
+
+ aud_mode = (tuner->audmode == V4L2_TUNER_MODE_STEREO) ?
+ FM_STEREO_MODE : FM_MONO_MODE;
+ rds_mode = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
+ FM_RDS_ENABLE : FM_RDS_DISABLE;
+
+ fmdev = video_drvdata(file);
+ if (fmdev->curr_fmmode != FM_MODE_RX) {
+ ret = fmc_set_mode(fmdev, FM_MODE_RX);
+ if (ret < 0) {
+ pr_err("(fmdrv): Failed to set RX mode; unable to" \
+ "write tuner attributes\n");
+ goto exit;
+ }
+ }
+
+ ret = fmc_set_stereo_mono(fmdev, aud_mode);
+ if (ret < 0)
+ goto exit;
+
+ ret = fmc_set_rds_mode(fmdev, rds_mode);
+ if (ret < 0)
+ goto exit;
+
+exit:
+ return ret;
+}
+
+/* Get tuner or modulator radio frequency */
+static int fm_v4l2_vidioc_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+ ret = fmc_get_frequency(fmdev, &freq->frequency);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+/* Set tuner or modulator radio frequency */
+static int fm_v4l2_vidioc_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *freq)
+{
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+ ret = fmc_set_frequency(fmdev, freq->frequency);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */
+static int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv,
+ struct v4l2_hw_freq_seek *seek)
+{
+ int ret;
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_drvdata(file);
+ if (fmdev->curr_fmmode != FM_MODE_RX) {
+ ret = fmc_set_mode(fmdev, FM_MODE_RX);
+ if (ret != 0) {
+ pr_err("(fmdrv): Failed to set RX mode; unable to " \
+ "start HW frequency seek\n");
+ goto exit;
+ }
+ }
+
+ ret = fm_rx_seek(fmdev, seek->seek_upward, seek->wrap_around);
+ if (ret < 0)
+ goto exit;
+
+exit:
+ return ret;
+}
+
+static const struct v4l2_file_operations fm_drv_fops = {
+ .owner = THIS_MODULE,
+ .read = fm_v4l2_fops_read,
+ .write = fm_v4l2_fops_write,
+ .poll = fm_v4l2_fops_poll,
+ .ioctl = video_ioctl2,
+ .open = fm_v4l2_fops_open,
+ .release = fm_v4l2_fops_release,
+};
+
+static const struct v4l2_ioctl_ops fm_drv_ioctl_ops = {
+ .vidioc_querycap = fm_v4l2_vidioc_querycap,
+ .vidioc_queryctrl = fm_v4l2_vidioc_queryctrl,
+ .vidioc_g_ctrl = fm_v4l2_vidioc_g_ctrl,
+ .vidioc_s_ctrl = fm_v4l2_vidioc_s_ctrl,
+ .vidioc_g_audio = fm_v4l2_vidioc_g_audio,
+ .vidioc_s_audio = fm_v4l2_vidioc_s_audio,
+ .vidioc_g_tuner = fm_v4l2_vidioc_g_tuner,
+ .vidioc_s_tuner = fm_v4l2_vidioc_s_tuner,
+ .vidioc_g_frequency = fm_v4l2_vidioc_g_frequency,
+ .vidioc_s_frequency = fm_v4l2_vidioc_s_frequency,
+ .vidioc_s_hw_freq_seek = fm_v4l2_vidioc_s_hw_freq_seek
+};
+
+/* V4L2 RADIO device parent structure */
+static struct video_device fm_viddev_template = {
+ .fops = &fm_drv_fops,
+ .ioctl_ops = &fm_drv_ioctl_ops,
+ .name = FM_DRV_NAME,
+ .release = video_device_release,
+};
+
+int fm_v4l2_init_video_device(struct fmdrv_ops *fmdev, int radio_nr)
+{
+ int ret = -ENOMEM;
+
+ gradio_dev = NULL;
+ /* Allocate new video device */
+ gradio_dev = video_device_alloc();
+ if (NULL == gradio_dev) {
+ pr_err("(fmdrv): Can't allocate video device");
+ goto exit;
+ }
+
+ /* Setup FM driver's V4L2 properties */
+ memcpy(gradio_dev, &fm_viddev_template, sizeof(fm_viddev_template));
+
+ video_set_drvdata(gradio_dev, fmdev);
+
+ /* Register with V4L2 subsystem as RADIO device */
+ if (video_register_device(gradio_dev, VFL_TYPE_RADIO, radio_nr)) {
+ video_device_release(gradio_dev);
+ pr_err("(fmdrv): Could not register video device");
+ goto exit;
+ }
+
+ fmdev->radio_dev = gradio_dev;
+ ret = 0;
+
+exit:
+ return ret;
+}
+
+void *fm_v4l2_deinit_video_device(void)
+{
+ struct fmdrv_ops *fmdev;
+
+ fmdev = video_get_drvdata(gradio_dev);
+ /* Unregister RADIO device from V4L2 subsystem */
+ video_unregister_device(gradio_dev);
+
+ return fmdev;
+}
diff --git a/drivers/staging/ti-st/fmdrv_v4l2.h b/drivers/staging/ti-st/fmdrv_v4l2.h
new file mode 100644
index 000000000000..6f0a4159f4bb
--- /dev/null
+++ b/drivers/staging/ti-st/fmdrv_v4l2.h
@@ -0,0 +1,32 @@
+/*
+ * FM Driver for Connectivity chip of Texas Instruments.
+ *
+ * FM V4L2 module header.
+ *
+ * Copyright (C) 2010 Texas Instruments
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef _FMDRV_V4L2_H
+#define _FMDRV_V4L2_H
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+int fm_v4l2_init_video_device(struct fmdrv_ops *, int);
+void *fm_v4l2_deinit_video_device(void);
+
+#endif
diff --git a/drivers/staging/ti-st/st.h b/drivers/staging/ti-st/st.h
index e8fc97e32c94..cb7a818b1e47 100644
--- a/drivers/staging/ti-st/st.h
+++ b/drivers/staging/ti-st/st.h
@@ -24,24 +24,24 @@
#define ST_H
#include <linux/skbuff.h>
-/*
- * st.h
- */
/* TODO:
* Move the following to tty.h upon acceptance
*/
-#define N_TI_WL 20 /* Ldisc for TI's WL BT, FM, GPS combo chips */
+#define N_TI_WL 22 /* Ldisc for TI's WL BT, FM, GPS combo chips */
-/* some gpios have active high, others like fm have
- * active low
+/**
+ * enum kim_gpio_state - Few protocols such as FM have ACTIVE LOW
+ * gpio states for their chip/core enable gpios
*/
enum kim_gpio_state {
KIM_GPIO_INACTIVE,
KIM_GPIO_ACTIVE,
};
-/*
- * the list of protocols on chip
+
+/**
+ * enum proto-type - The protocol on WiLink chips which share a
+ * common physical interface like UART.
*/
enum proto_type {
ST_BT,
@@ -50,41 +50,35 @@ enum proto_type {
ST_MAX,
};
-enum {
- ST_ERR_FAILURE = -1, /* check struct */
- ST_SUCCESS,
- ST_ERR_PENDING = -5, /* to call reg_complete_cb */
- ST_ERR_ALREADY, /* already registered */
- ST_ERR_INPROGRESS,
- ST_ERR_NOPROTO, /* protocol not supported */
-};
-
-/* per protocol structure
- * for BT/FM and GPS
+/**
+ * struct st_proto_s - Per Protocol structure from BT/FM/GPS to ST
+ * @type: type of the protocol being registered among the
+ * available proto_type(BT, FM, GPS the protocol which share TTY).
+ * @recv: the receiver callback pointing to a function in the
+ * protocol drivers called by the ST driver upon receiving
+ * relevant data.
+ * @match_packet: reserved for future use, to make ST more generic
+ * @reg_complete_cb: callback handler pointing to a function in protocol
+ * handler called by ST when the pending registrations are complete.
+ * The registrations are marked pending, in situations when fw
+ * download is in progress.
+ * @write: pointer to function in ST provided to protocol drivers from ST,
+ * to be made use when protocol drivers have data to send to TTY.
+ * @priv_data: privdate data holder for the protocol drivers, sent
+ * from the protocol drivers during registration, and sent back on
+ * reg_complete_cb and recv.
*/
struct st_proto_s {
enum proto_type type;
-/*
- * to be called by ST when data arrives
- */
- long (*recv) (struct sk_buff *);
-/*
- * for future use, logic now to be in ST
- */
+ long (*recv) (void *, struct sk_buff *);
unsigned char (*match_packet) (const unsigned char *data);
-/*
- * subsequent registration return PENDING,
- * signalled complete by this callback function
- */
- void (*reg_complete_cb) (char data);
-/*
- * write function, sent in as NULL and to be returned to
- * protocol drivers
- */
+ void (*reg_complete_cb) (void *, char data);
long (*write) (struct sk_buff *skb);
+ void *priv_data;
};
-extern long st_register(struct st_proto_s *new_proto);
-extern long st_unregister(enum proto_type type);
+extern long st_register(struct st_proto_s *);
+extern long st_unregister(enum proto_type);
+extern struct platform_device *st_get_plat_device(void);
#endif /* ST_H */
diff --git a/drivers/staging/ti-st/st_core.c b/drivers/staging/ti-st/st_core.c
index 4e93694e1c21..f9897023d380 100644
--- a/drivers/staging/ti-st/st_core.c
+++ b/drivers/staging/ti-st/st_core.c
@@ -38,7 +38,13 @@
#include "st_ll.h"
#include "st.h"
-#ifdef DEBUG
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
+
/* strings to be used for rfkill entries and by
* ST Core to be used for sysfs debug entry
*/
@@ -48,7 +54,6 @@ const unsigned char *protocol_strngs[] = {
PROTO_ENTRY(ST_FM, "FM"),
PROTO_ENTRY(ST_GPS, "GPS"),
};
-#endif
/* function pointer pointing to either,
* st_kim_recv during registration to receive fw download responses
* st_int_recv after registration to receive proto stack responses
@@ -61,7 +66,7 @@ void (*st_recv) (void*, const unsigned char*, long);
bool is_protocol_list_empty(void)
{
unsigned char i = 0;
- pr_info(" %s ", __func__);
+ pr_debug(" %s ", __func__);
for (i = 0; i < ST_MAX; i++) {
if (st_gdata->list[i] != NULL)
return ST_NOTEMPTY;
@@ -71,6 +76,7 @@ bool is_protocol_list_empty(void)
return ST_EMPTY;
}
#endif
+
/* can be called in from
* -- KIM (during fw download)
* -- ST Core (during st_write)
@@ -81,20 +87,15 @@ bool is_protocol_list_empty(void)
int st_int_write(struct st_data_s *st_gdata,
const unsigned char *data, int count)
{
-#ifdef VERBOSE /* for debug */
- int i;
-#endif
struct tty_struct *tty;
if (unlikely(st_gdata == NULL || st_gdata->tty == NULL)) {
pr_err("tty unavailable to perform write");
- return ST_ERR_FAILURE;
+ return -1;
}
tty = st_gdata->tty;
#ifdef VERBOSE
- printk(KERN_ERR "start data..\n");
- for (i = 0; i < count; i++) /* no newlines for each datum */
- printk(" %x", data[i]);
- printk(KERN_ERR "\n ..end data\n");
+ print_hex_dump(KERN_DEBUG, "<out<", DUMP_PREFIX_NONE,
+ 16, 1, data, count, 0);
#endif
return tty->ops->write(tty, data, count);
@@ -122,8 +123,10 @@ void st_send_frame(enum proto_type protoid, struct st_data_s *st_gdata)
* protocol stack driver
*/
if (likely(st_gdata->list[protoid]->recv != NULL)) {
- if (unlikely(st_gdata->list[protoid]->recv(st_gdata->rx_skb)
- != ST_SUCCESS)) {
+ if (unlikely
+ (st_gdata->list[protoid]->recv
+ (st_gdata->list[protoid]->priv_data, st_gdata->rx_skb)
+ != 0)) {
pr_err(" proto stack %d's ->recv failed", protoid);
kfree_skb(st_gdata->rx_skb);
return;
@@ -132,11 +135,11 @@ void st_send_frame(enum proto_type protoid, struct st_data_s *st_gdata)
pr_err(" proto stack %d's ->recv null", protoid);
kfree_skb(st_gdata->rx_skb);
}
- pr_info(" done %s", __func__);
return;
}
-/*
+/**
+ * st_reg_complete -
* to call registration complete callbacks
* of all protocol stack drivers
*/
@@ -147,7 +150,8 @@ void st_reg_complete(struct st_data_s *st_gdata, char err)
for (i = 0; i < ST_MAX; i++) {
if (likely(st_gdata != NULL && st_gdata->list[i] != NULL &&
st_gdata->list[i]->reg_complete_cb != NULL))
- st_gdata->list[i]->reg_complete_cb(err);
+ st_gdata->list[i]->reg_complete_cb
+ (st_gdata->list[i]->priv_data, err);
}
}
@@ -156,7 +160,7 @@ static inline int st_check_data_len(struct st_data_s *st_gdata,
{
register int room = skb_tailroom(st_gdata->rx_skb);
- pr_info("len %d room %d", len, room);
+ pr_debug("len %d room %d", len, room);
if (!len) {
/* Received packet has only packet header and
@@ -190,8 +194,9 @@ static inline int st_check_data_len(struct st_data_s *st_gdata,
return 0;
}
-/* internal function for action when wake-up ack
- * received
+/**
+ * st_wakeup_ack - internal function for action when wake-up ack
+ * received
*/
static inline void st_wakeup_ack(struct st_data_s *st_gdata,
unsigned char cmd)
@@ -214,9 +219,13 @@ static inline void st_wakeup_ack(struct st_data_s *st_gdata,
st_tx_wakeup(st_gdata);
}
-/* Decodes received RAW data and forwards to corresponding
- * client drivers (Bluetooth,FM,GPS..etc).
- *
+/**
+ * st_int_recv - ST's internal receive function.
+ * Decodes received RAW data and forwards to corresponding
+ * client drivers (Bluetooth,FM,GPS..etc).
+ * This can receive various types of packets,
+ * HCI-Events, ACL, SCO, 4 types of HCI-LL PM packets
+ * CH-8 packets from FM, CH-9 packets from GPS cores.
*/
void st_int_recv(void *disc_data,
const unsigned char *data, long count)
@@ -259,7 +268,7 @@ void st_int_recv(void *disc_data,
/* Waiting for complete packet ? */
case ST_BT_W4_DATA:
- pr_info("Complete pkt received");
+ pr_debug("Complete pkt received");
/* Ask ST CORE to forward
* the packet to protocol driver */
@@ -275,7 +284,7 @@ void st_int_recv(void *disc_data,
eh = (struct hci_event_hdr *)st_gdata->rx_skb->
data;
- pr_info("Event header: evt 0x%2.2x"
+ pr_debug("Event header: evt 0x%2.2x"
"plen %d", eh->evt, eh->plen);
st_check_data_len(st_gdata, protoid, eh->plen);
@@ -439,45 +448,43 @@ void st_int_recv(void *disc_data,
break;
}
}
- pr_info("done %s", __func__);
+ pr_debug("done %s", __func__);
return;
}
-/* internal de-Q function
- * -- return previous in-completely written skb
- * or return the skb in the txQ
+/**
+ * st_int_dequeue - internal de-Q function.
+ * If the previous data set was not written
+ * completely, return that skb which has the pending data.
+ * In normal cases, return top of txq.
*/
struct sk_buff *st_int_dequeue(struct st_data_s *st_gdata)
{
struct sk_buff *returning_skb;
- pr_info("%s", __func__);
- /* if the previous skb wasn't written completely
- */
+ pr_debug("%s", __func__);
if (st_gdata->tx_skb != NULL) {
returning_skb = st_gdata->tx_skb;
st_gdata->tx_skb = NULL;
return returning_skb;
}
-
- /* de-Q from the txQ always if previous write is complete */
return skb_dequeue(&st_gdata->txq);
}
-/* internal Q-ing function
- * will either Q the skb to txq or the tx_waitq
- * depending on the ST LL state
- *
- * lock the whole func - since ll_getstate and Q-ing should happen
- * in one-shot
+/**
+ * st_int_enqueue - internal Q-ing function.
+ * Will either Q the skb to txq or the tx_waitq
+ * depending on the ST LL state.
+ * If the chip is asleep, then Q it onto waitq and
+ * wakeup the chip.
+ * txq and waitq needs protection since the other contexts
+ * may be sending data, waking up chip.
*/
void st_int_enqueue(struct st_data_s *st_gdata, struct sk_buff *skb)
{
unsigned long flags = 0;
- pr_info("%s", __func__);
- /* this function can be invoked in more then one context.
- * so have a lock */
+ pr_debug("%s", __func__);
spin_lock_irqsave(&st_gdata->lock, flags);
switch (st_ll_getstate(st_gdata)) {
@@ -488,16 +495,12 @@ void st_int_enqueue(struct st_data_s *st_gdata, struct sk_buff *skb)
case ST_LL_ASLEEP_TO_AWAKE:
skb_queue_tail(&st_gdata->tx_waitq, skb);
break;
- case ST_LL_AWAKE_TO_ASLEEP: /* host cannot be in this state */
+ case ST_LL_AWAKE_TO_ASLEEP:
pr_err("ST LL is illegal state(%ld),"
"purging received skb.", st_ll_getstate(st_gdata));
kfree_skb(skb);
break;
-
case ST_LL_ASLEEP:
- /* call a function of ST LL to put data
- * in tx_waitQ and wake_ind in txQ
- */
skb_queue_tail(&st_gdata->tx_waitq, skb);
st_ll_wakeup(st_gdata);
break;
@@ -507,8 +510,9 @@ void st_int_enqueue(struct st_data_s *st_gdata, struct sk_buff *skb)
kfree_skb(skb);
break;
}
+
spin_unlock_irqrestore(&st_gdata->lock, flags);
- pr_info("done %s", __func__);
+ pr_debug("done %s", __func__);
return;
}
@@ -522,7 +526,7 @@ void st_tx_wakeup(struct st_data_s *st_data)
{
struct sk_buff *skb;
unsigned long flags; /* for irq save flags */
- pr_info("%s", __func__);
+ pr_debug("%s", __func__);
/* check for sending & set flag sending here */
if (test_and_set_bit(ST_TX_SENDING, &st_data->tx_state)) {
pr_info("ST already sending");
@@ -563,33 +567,13 @@ void st_tx_wakeup(struct st_data_s *st_data)
/********************************************************************/
/* functions called from ST KIM
*/
-void kim_st_list_protocols(struct st_data_s *st_gdata, char *buf)
+void kim_st_list_protocols(struct st_data_s *st_gdata, void *buf)
{
- unsigned long flags = 0;
-#ifdef DEBUG
- unsigned char i = ST_MAX;
-#endif
- spin_lock_irqsave(&st_gdata->lock, flags);
-#ifdef DEBUG /* more detailed log */
- for (i = 0; i < ST_MAX; i++) {
- if (i == 0) {
- sprintf(buf, "%s is %s", protocol_strngs[i],
- st_gdata->list[i] !=
- NULL ? "Registered" : "Unregistered");
- } else {
- sprintf(buf, "%s\n%s is %s", buf, protocol_strngs[i],
- st_gdata->list[i] !=
- NULL ? "Registered" : "Unregistered");
- }
- }
- sprintf(buf, "%s\n", buf);
-#else /* limited info */
- sprintf(buf, "BT=%c\nFM=%c\nGPS=%c\n",
- st_gdata->list[ST_BT] != NULL ? 'R' : 'U',
- st_gdata->list[ST_FM] != NULL ? 'R' : 'U',
- st_gdata->list[ST_GPS] != NULL ? 'R' : 'U');
-#endif
- spin_unlock_irqrestore(&st_gdata->lock, flags);
+ seq_printf(buf, "[%d]\nBT=%c\nFM=%c\nGPS=%c\n",
+ st_gdata->protos_registered,
+ st_gdata->list[ST_BT] != NULL ? 'R' : 'U',
+ st_gdata->list[ST_FM] != NULL ? 'R' : 'U',
+ st_gdata->list[ST_GPS] != NULL ? 'R' : 'U');
}
/********************************************************************/
@@ -600,7 +584,7 @@ void kim_st_list_protocols(struct st_data_s *st_gdata, char *buf)
long st_register(struct st_proto_s *new_proto)
{
struct st_data_s *st_gdata;
- long err = ST_SUCCESS;
+ long err = 0;
unsigned long flags = 0;
st_kim_ref(&st_gdata);
@@ -608,17 +592,17 @@ long st_register(struct st_proto_s *new_proto)
if (st_gdata == NULL || new_proto == NULL || new_proto->recv == NULL
|| new_proto->reg_complete_cb == NULL) {
pr_err("gdata/new_proto/recv or reg_complete_cb not ready");
- return ST_ERR_FAILURE;
+ return -1;
}
if (new_proto->type < ST_BT || new_proto->type >= ST_MAX) {
pr_err("protocol %d not supported", new_proto->type);
- return ST_ERR_NOPROTO;
+ return -EPROTONOSUPPORT;
}
if (st_gdata->list[new_proto->type] != NULL) {
pr_err("protocol %d already registered", new_proto->type);
- return ST_ERR_ALREADY;
+ return -EALREADY;
}
/* can be from process context only */
@@ -630,11 +614,12 @@ long st_register(struct st_proto_s *new_proto)
st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE);
st_gdata->list[new_proto->type] = new_proto;
+ st_gdata->protos_registered++;
new_proto->write = st_write;
set_bit(ST_REG_PENDING, &st_gdata->st_state);
spin_unlock_irqrestore(&st_gdata->lock, flags);
- return ST_ERR_PENDING;
+ return -EINPROGRESS;
} else if (st_gdata->protos_registered == ST_EMPTY) {
pr_info(" protocol list empty :%d ", new_proto->type);
set_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state);
@@ -648,16 +633,16 @@ long st_register(struct st_proto_s *new_proto)
/* this may take a while to complete
* since it involves BT fw download
*/
- err = st_kim_start();
- if (err != ST_SUCCESS) {
+ err = st_kim_start(st_gdata->kim_data);
+ if (err != 0) {
clear_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state);
if ((st_gdata->protos_registered != ST_EMPTY) &&
(test_bit(ST_REG_PENDING, &st_gdata->st_state))) {
pr_err(" KIM failure complete callback ");
- st_reg_complete(st_gdata, ST_ERR_FAILURE);
+ st_reg_complete(st_gdata, -1);
}
- return ST_ERR_FAILURE;
+ return -1;
}
/* the protocol might require other gpios to be toggled
@@ -672,9 +657,8 @@ long st_register(struct st_proto_s *new_proto)
*/
if ((st_gdata->protos_registered != ST_EMPTY) &&
(test_bit(ST_REG_PENDING, &st_gdata->st_state))) {
- pr_info(" call reg complete callback ");
- st_gdata->protos_registered++;
- st_reg_complete(st_gdata, ST_SUCCESS);
+ pr_debug(" call reg complete callback ");
+ st_reg_complete(st_gdata, 0);
}
clear_bit(ST_REG_PENDING, &st_gdata->st_state);
@@ -684,11 +668,12 @@ long st_register(struct st_proto_s *new_proto)
if (st_gdata->list[new_proto->type] != NULL) {
pr_err(" proto %d already registered ",
new_proto->type);
- return ST_ERR_ALREADY;
+ return -EALREADY;
}
spin_lock_irqsave(&st_gdata->lock, flags);
st_gdata->list[new_proto->type] = new_proto;
+ st_gdata->protos_registered++;
new_proto->write = st_write;
spin_unlock_irqrestore(&st_gdata->lock, flags);
return err;
@@ -707,18 +692,19 @@ long st_register(struct st_proto_s *new_proto)
default:
pr_err("%d protocol not supported",
new_proto->type);
- err = ST_ERR_NOPROTO;
+ err = -EPROTONOSUPPORT;
/* something wrong */
break;
}
st_gdata->list[new_proto->type] = new_proto;
+ st_gdata->protos_registered++;
new_proto->write = st_write;
/* lock already held before entering else */
spin_unlock_irqrestore(&st_gdata->lock, flags);
return err;
}
- pr_info("done %s(%d) ", __func__, new_proto->type);
+ pr_debug("done %s(%d) ", __func__, new_proto->type);
}
EXPORT_SYMBOL_GPL(st_register);
@@ -727,16 +713,16 @@ EXPORT_SYMBOL_GPL(st_register);
*/
long st_unregister(enum proto_type type)
{
- long err = ST_SUCCESS;
+ long err = 0;
unsigned long flags = 0;
struct st_data_s *st_gdata;
- pr_info("%s: %d ", __func__, type);
+ pr_debug("%s: %d ", __func__, type);
st_kim_ref(&st_gdata);
if (type < ST_BT || type >= ST_MAX) {
pr_err(" protocol %d not supported", type);
- return ST_ERR_NOPROTO;
+ return -EPROTONOSUPPORT;
}
spin_lock_irqsave(&st_gdata->lock, flags);
@@ -744,7 +730,7 @@ long st_unregister(enum proto_type type)
if (st_gdata->list[type] == NULL) {
pr_err(" protocol %d not registered", type);
spin_unlock_irqrestore(&st_gdata->lock, flags);
- return ST_ERR_NOPROTO;
+ return -EPROTONOSUPPORT;
}
st_gdata->protos_registered--;
@@ -768,7 +754,7 @@ long st_unregister(enum proto_type type)
}
/* all protocols now unregistered */
- st_kim_stop();
+ st_kim_stop(st_gdata->kim_data);
/* disable ST LL */
st_ll_disable(st_gdata);
}
@@ -791,7 +777,7 @@ long st_write(struct sk_buff *skb)
if (unlikely(skb == NULL || st_gdata == NULL
|| st_gdata->tty == NULL)) {
pr_err("data/tty unavailable to perform write");
- return ST_ERR_FAILURE;
+ return -1;
}
#ifdef DEBUG /* open-up skb to read the 1st byte */
switch (skb->data[0]) {
@@ -810,10 +796,10 @@ long st_write(struct sk_buff *skb)
if (unlikely(st_gdata->list[protoid] == NULL)) {
pr_err(" protocol %d not registered, and writing? ",
protoid);
- return ST_ERR_FAILURE;
+ return -1;
}
#endif
- pr_info("%d to be written", skb->len);
+ pr_debug("%d to be written", skb->len);
len = skb->len;
/* st_ll to decide where to enqueue the skb */
@@ -834,7 +820,7 @@ EXPORT_SYMBOL_GPL(st_unregister);
*/
static int st_tty_open(struct tty_struct *tty)
{
- int err = ST_SUCCESS;
+ int err = 0;
struct st_data_s *st_gdata;
pr_info("%s ", __func__);
@@ -855,8 +841,8 @@ static int st_tty_open(struct tty_struct *tty)
* signal to UIM via KIM that -
* installation of N_TI_WL ldisc is complete
*/
- st_kim_complete();
- pr_info("done %s", __func__);
+ st_kim_complete(st_gdata->kim_data);
+ pr_debug("done %s", __func__);
return err;
}
@@ -878,12 +864,13 @@ static void st_tty_close(struct tty_struct *tty)
pr_err("%d not un-registered", i);
st_gdata->list[i] = NULL;
}
+ st_gdata->protos_registered = 0;
spin_unlock_irqrestore(&st_gdata->lock, flags);
/*
* signal to UIM via KIM that -
* N_TI_WL ldisc is un-installed
*/
- st_kim_complete();
+ st_kim_complete(st_gdata->kim_data);
st_gdata->tty = NULL;
/* Flush any pending characters in the driver and discipline. */
tty_ldisc_flush(tty);
@@ -900,7 +887,7 @@ static void st_tty_close(struct tty_struct *tty)
st_gdata->rx_skb = NULL;
spin_unlock_irqrestore(&st_gdata->lock, flags);
- pr_info("%s: done ", __func__);
+ pr_debug("%s: done ", __func__);
}
static void st_tty_receive(struct tty_struct *tty, const unsigned char *data,
@@ -908,11 +895,8 @@ static void st_tty_receive(struct tty_struct *tty, const unsigned char *data,
{
#ifdef VERBOSE
- long i;
- printk(KERN_ERR "incoming data...\n");
- for (i = 0; i < count; i++)
- printk(" %x", data[i]);
- printk(KERN_ERR "\n.. data end\n");
+ print_hex_dump(KERN_DEBUG, ">in>", DUMP_PREFIX_NONE,
+ 16, 1, data, count, 0);
#endif
/*
@@ -920,7 +904,7 @@ static void st_tty_receive(struct tty_struct *tty, const unsigned char *data,
* to KIM for validation
*/
st_recv(tty->disc_data, data, count);
- pr_info("done %s", __func__);
+ pr_debug("done %s", __func__);
}
/* wake-up function called in from the TTY layer
@@ -929,7 +913,7 @@ static void st_tty_receive(struct tty_struct *tty, const unsigned char *data,
static void st_tty_wakeup(struct tty_struct *tty)
{
struct st_data_s *st_gdata = tty->disc_data;
- pr_info("%s ", __func__);
+ pr_debug("%s ", __func__);
/* don't do an wakeup for now */
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
@@ -940,7 +924,7 @@ static void st_tty_wakeup(struct tty_struct *tty)
static void st_tty_flush_buffer(struct tty_struct *tty)
{
struct st_data_s *st_gdata = tty->disc_data;
- pr_info("%s ", __func__);
+ pr_debug("%s ", __func__);
kfree_skb(st_gdata->tx_skb);
st_gdata->tx_skb = NULL;
@@ -979,7 +963,7 @@ int st_core_init(struct st_data_s **core_data)
kfree(st_ldisc_ops);
return err;
}
- pr_info("registered n_shared line discipline");
+ pr_debug("registered n_shared line discipline");
st_gdata = kzalloc(sizeof(struct st_data_s), GFP_KERNEL);
if (!st_gdata) {
diff --git a/drivers/staging/ti-st/st_core.h b/drivers/staging/ti-st/st_core.h
index f271c88a8087..e0c32d149f5f 100644
--- a/drivers/staging/ti-st/st_core.h
+++ b/drivers/staging/ti-st/st_core.h
@@ -36,57 +36,87 @@
#define ST_REG_PENDING 3
#define ST_WAITING_FOR_RESP 4
-/*
- * local data required for ST/KIM/ST-HCI-LL
+/**
+ * struct st_data_s - ST core internal structure
+ * @st_state: different states of ST like initializing, registration
+ * in progress, this is mainly used to return relevant err codes
+ * when protocol drivers are registering. It is also used to track
+ * the recv function, as in during fw download only HCI events
+ * can occur , where as during other times other events CH8, CH9
+ * can occur.
+ * @tty: tty provided by the TTY core for line disciplines.
+ * @ldisc_ops: the procedures that this line discipline registers with TTY.
+ * @tx_skb: If for some reason the tty's write returns lesser bytes written
+ * then to maintain the rest of data to be written on next instance.
+ * This needs to be protected, hence the lock inside wakeup func.
+ * @tx_state: if the data is being written onto the TTY and protocol driver
+ * wants to send more, queue up data and mark that there is
+ * more data to send.
+ * @list: the list of protocols registered, only MAX can exist, one protocol
+ * can register only once.
+ * @rx_state: states to be maintained inside st's tty receive
+ * @rx_count: count to be maintained inside st's tty receieve
+ * @rx_skb: the skb where all data for a protocol gets accumulated,
+ * since tty might not call receive when a complete event packet
+ * is received, the states, count and the skb needs to be maintained.
+ * @txq: the list of skbs which needs to be sent onto the TTY.
+ * @tx_waitq: if the chip is not in AWAKE state, the skbs needs to be queued
+ * up in here, PM(WAKEUP_IND) data needs to be sent and then the skbs
+ * from waitq can be moved onto the txq.
+ * Needs locking too.
+ * @lock: the lock to protect skbs, queues, and ST states.
+ * @protos_registered: count of the protocols registered, also when 0 the
+ * chip enable gpio can be toggled, and when it changes to 1 the fw
+ * needs to be downloaded to initialize chip side ST.
+ * @ll_state: the various PM states the chip can be, the states are notified
+ * to us, when the chip sends relevant PM packets(SLEEP_IND, WAKE_IND).
+ * @kim_data: reference to the parent encapsulating structure.
+ *
*/
struct st_data_s {
unsigned long st_state;
-/*
- * an instance of tty_struct & ldisc ops to move around
- */
struct tty_struct *tty;
struct tty_ldisc_ops *ldisc_ops;
-/*
- * the tx skb -
- * if the skb is already dequeued and the tty failed to write the same
- * maintain the skb to write in the next transaction
- */
struct sk_buff *tx_skb;
#define ST_TX_SENDING 1
#define ST_TX_WAKEUP 2
unsigned long tx_state;
-/*
- * list of protocol registered
- */
struct st_proto_s *list[ST_MAX];
-/*
- * lock
- */
unsigned long rx_state;
unsigned long rx_count;
struct sk_buff *rx_skb;
struct sk_buff_head txq, tx_waitq;
- spinlock_t lock; /* ST LL state lock */
+ spinlock_t lock;
unsigned char protos_registered;
- unsigned long ll_state; /* ST LL power state */
+ unsigned long ll_state;
+ void *kim_data;
};
-/* point this to tty->driver->write or tty->ops->write
+/**
+ * st_int_write -
+ * point this to tty->driver->write or tty->ops->write
* depending upon the kernel version
*/
int st_int_write(struct st_data_s*, const unsigned char*, int);
-/* internal write function, passed onto protocol drivers
+
+/**
+ * st_write -
+ * internal write function, passed onto protocol drivers
* via the write function ptr of protocol struct
*/
long st_write(struct sk_buff *);
-/* function to be called from ST-LL
- */
+
+/* function to be called from ST-LL */
void st_ll_send_frame(enum proto_type, struct sk_buff *);
+
/* internal wake up function */
void st_tx_wakeup(struct st_data_s *st_data);
+/* init, exit entry funcs called from KIM */
int st_core_init(struct st_data_s **);
void st_core_exit(struct st_data_s *);
+
+/* ask for reference from KIM */
void st_kim_ref(struct st_data_s **);
#define GPS_STUB_TEST
diff --git a/drivers/staging/ti-st/st_kim.c b/drivers/staging/ti-st/st_kim.c
index 98cbabba3844..033962c50d2f 100644
--- a/drivers/staging/ti-st/st_kim.c
+++ b/drivers/staging/ti-st/st_kim.c
@@ -26,6 +26,8 @@
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/gpio.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
#include <linux/sched.h>
@@ -35,6 +37,12 @@
#include <net/bluetooth/hci_core.h>
#include <net/bluetooth/hci.h>
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
static int kim_probe(struct platform_device *pdev);
static int kim_remove(struct platform_device *pdev);
@@ -55,37 +63,10 @@ static struct platform_driver kim_platform_driver = {
},
};
-#ifndef LEGACY_RFKILL_SUPPORT
-static ssize_t show_pid(struct device *dev, struct device_attribute
- *attr, char *buf);
-static ssize_t store_pid(struct device *dev, struct device_attribute
- *devattr, char *buf, size_t count);
-static ssize_t show_list(struct device *dev, struct device_attribute
- *attr, char *buf);
-
-/* structures specific for sysfs entries */
-static struct kobj_attribute pid_attr =
-__ATTR(pid, 0644, (void *)show_pid, (void *)store_pid);
-
-static struct kobj_attribute list_protocols =
-__ATTR(protocols, 0444, (void *)show_list, NULL);
-
-static struct attribute *uim_attrs[] = {
- &pid_attr.attr,
- /* add more debug sysfs entries */
- &list_protocols.attr,
- NULL,
-};
-
-static struct attribute_group uim_attr_grp = {
- .attrs = uim_attrs,
-};
-#else
static int kim_toggle_radio(void*, bool);
static const struct rfkill_ops kim_rfkill_ops = {
.set_block = kim_toggle_radio,
};
-#endif /* LEGACY_RFKILL_SUPPORT */
/* strings to be used for rfkill entries and by
* ST Core to be used for sysfs debug entry
@@ -97,18 +78,19 @@ const unsigned char *protocol_names[] = {
PROTO_ENTRY(ST_GPS, "GPS"),
};
-struct kim_data_s *kim_gdata;
/**********************************************************************/
/* internal functions */
-/*
- * function to return whether the firmware response was proper
- * in case of error don't complete so that waiting for proper
- * response times out
+/**
+ * validate_firmware_response -
+ * function to return whether the firmware response was proper
+ * in case of error don't complete so that waiting for proper
+ * response times out
*/
-void validate_firmware_response(struct sk_buff *skb)
+void validate_firmware_response(struct kim_data_s *kim_gdata)
{
+ struct sk_buff *skb = kim_gdata->rx_skb;
if (unlikely(skb->data[5] != 0)) {
pr_err("no proper response during fw download");
pr_err("data6 %x", skb->data[5]);
@@ -122,14 +104,14 @@ void validate_firmware_response(struct sk_buff *skb)
/* check for data len received inside kim_int_recv
* most often hit the last case to update state to waiting for data
*/
-static inline int kim_check_data_len(int len)
+static inline int kim_check_data_len(struct kim_data_s *kim_gdata, int len)
{
register int room = skb_tailroom(kim_gdata->rx_skb);
- pr_info("len %d room %d", len, room);
+ pr_debug("len %d room %d", len, room);
if (!len) {
- validate_firmware_response(kim_gdata->rx_skb);
+ validate_firmware_response(kim_gdata);
} else if (len > room) {
/* Received packet's payload length is larger.
* We can't accommodate it in created skb.
@@ -155,18 +137,20 @@ static inline int kim_check_data_len(int len)
return 0;
}
-/* receive function called during firmware download
- * - firmware download responses on different UART drivers
- * have been observed to come in bursts of different
- * tty_receive and hence the logic
+/**
+ * kim_int_recv - receive function called during firmware download
+ * firmware download responses on different UART drivers
+ * have been observed to come in bursts of different
+ * tty_receive and hence the logic
*/
-void kim_int_recv(const unsigned char *data, long count)
+void kim_int_recv(struct kim_data_s *kim_gdata,
+ const unsigned char *data, long count)
{
register char *ptr;
struct hci_event_hdr *eh;
register int len = 0, type = 0;
- pr_info("%s", __func__);
+ pr_debug("%s", __func__);
/* Decode received bytes here */
ptr = (char *)data;
if (unlikely(ptr == NULL)) {
@@ -188,8 +172,8 @@ void kim_int_recv(const unsigned char *data, long count)
switch (kim_gdata->rx_state) {
/* Waiting for complete packet ? */
case ST_BT_W4_DATA:
- pr_info("Complete pkt received");
- validate_firmware_response(kim_gdata->rx_skb);
+ pr_debug("Complete pkt received");
+ validate_firmware_response(kim_gdata);
kim_gdata->rx_state = ST_W4_PACKET_TYPE;
kim_gdata->rx_skb = NULL;
continue;
@@ -197,9 +181,9 @@ void kim_int_recv(const unsigned char *data, long count)
case ST_BT_W4_EVENT_HDR:
eh = (struct hci_event_hdr *)kim_gdata->
rx_skb->data;
- pr_info("Event header: evt 0x%2.2x"
+ pr_debug("Event header: evt 0x%2.2x"
"plen %d", eh->evt, eh->plen);
- kim_check_data_len(eh->plen);
+ kim_check_data_len(kim_gdata, eh->plen);
continue;
} /* end of switch */
} /* end of if rx_state */
@@ -216,7 +200,7 @@ void kim_int_recv(const unsigned char *data, long count)
ptr++;
count--;
continue;
- } /* end of switch *ptr */
+ }
ptr++;
count--;
kim_gdata->rx_skb =
@@ -226,34 +210,35 @@ void kim_int_recv(const unsigned char *data, long count)
kim_gdata->rx_state = ST_W4_PACKET_TYPE;
kim_gdata->rx_count = 0;
return;
- } /* not necessary in this case */
+ }
bt_cb(kim_gdata->rx_skb)->pkt_type = type;
- } /* end of while count */
+ }
pr_info("done %s", __func__);
return;
}
-static long read_local_version(char *bts_scr_name)
+static long read_local_version(struct kim_data_s *kim_gdata, char *bts_scr_name)
{
unsigned short version = 0, chip = 0, min_ver = 0, maj_ver = 0;
char read_ver_cmd[] = { 0x01, 0x01, 0x10, 0x00 };
- pr_info("%s", __func__);
+ pr_debug("%s", __func__);
INIT_COMPLETION(kim_gdata->kim_rcvd);
if (4 != st_int_write(kim_gdata->core_data, read_ver_cmd, 4)) {
pr_err("kim: couldn't write 4 bytes");
- return ST_ERR_FAILURE;
+ return -1;
}
if (!wait_for_completion_timeout
(&kim_gdata->kim_rcvd, msecs_to_jiffies(CMD_RESP_TIME))) {
pr_err(" waiting for ver info- timed out ");
- return ST_ERR_FAILURE;
+ return -1;
}
version =
- MAKEWORD(kim_gdata->resp_buffer[13], kim_gdata->resp_buffer[14]);
+ MAKEWORD(kim_gdata->resp_buffer[13],
+ kim_gdata->resp_buffer[14]);
chip = (version & 0x7C00) >> 10;
min_ver = (version & 0x007F);
maj_ver = (version & 0x0380) >> 7;
@@ -262,25 +247,32 @@ static long read_local_version(char *bts_scr_name)
maj_ver |= 0x0008;
sprintf(bts_scr_name, "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver);
+
+ /* to be accessed later via sysfs entry */
+ kim_gdata->version.full = version;
+ kim_gdata->version.chip = chip;
+ kim_gdata->version.maj_ver = maj_ver;
+ kim_gdata->version.min_ver = min_ver;
+
pr_info("%s", bts_scr_name);
- return ST_SUCCESS;
+ return 0;
}
-/* internal function which parses through the .bts firmware script file
- * intreprets SEND, DELAY actions only as of now
+/**
+ * download_firmware -
+ * internal function which parses through the .bts firmware
+ * script file intreprets SEND, DELAY actions only as of now
*/
-static long download_firmware(void)
+static long download_firmware(struct kim_data_s *kim_gdata)
{
- long err = ST_SUCCESS;
+ long err = 0;
long len = 0;
register unsigned char *ptr = NULL;
register unsigned char *action_ptr = NULL;
unsigned char bts_scr_name[30] = { 0 }; /* 30 char long bts scr name? */
- pr_info("%s", __func__);
-
- err = read_local_version(bts_scr_name);
- if (err != ST_SUCCESS) {
+ err = read_local_version(kim_gdata, bts_scr_name);
+ if (err != 0) {
pr_err("kim: failed to read local ver");
return err;
}
@@ -291,7 +283,7 @@ static long download_firmware(void)
(kim_gdata->fw_entry->size == 0))) {
pr_err(" request_firmware failed(errno %ld) for %s", err,
bts_scr_name);
- return ST_ERR_FAILURE;
+ return -1;
}
ptr = (void *)kim_gdata->fw_entry->data;
len = kim_gdata->fw_entry->size;
@@ -302,7 +294,7 @@ static long download_firmware(void)
len -= sizeof(struct bts_header);
while (len > 0 && ptr) {
- pr_info(" action size %d, type %d ",
+ pr_debug(" action size %d, type %d ",
((struct bts_action *)ptr)->size,
((struct bts_action *)ptr)->type);
@@ -315,8 +307,8 @@ static long download_firmware(void)
/* ignore remote change
* baud rate HCI VS command */
pr_err
- (" change remote baud\
- rate command in firmware");
+ (" change remote baud"
+ " rate command in firmware");
break;
}
@@ -326,7 +318,7 @@ static long download_firmware(void)
((struct bts_action *)ptr)->size);
if (unlikely(err < 0)) {
release_firmware(kim_gdata->fw_entry);
- return ST_ERR_FAILURE;
+ return -1;
}
if (!wait_for_completion_timeout
(&kim_gdata->kim_rcvd,
@@ -335,7 +327,7 @@ static long download_firmware(void)
(" response timeout during fw download ");
/* timed out */
release_firmware(kim_gdata->fw_entry);
- return ST_ERR_FAILURE;
+ return -1;
}
break;
case ACTION_DELAY: /* sleep */
@@ -353,19 +345,23 @@ static long download_firmware(void)
}
/* fw download complete */
release_firmware(kim_gdata->fw_entry);
- return ST_SUCCESS;
+ return 0;
}
/**********************************************************************/
/* functions called from ST core */
-
/* function to toggle the GPIO
* needs to know whether the GPIO is active high or active low
*/
void st_kim_chip_toggle(enum proto_type type, enum kim_gpio_state state)
{
+ struct platform_device *kim_pdev;
+ struct kim_data_s *kim_gdata;
pr_info(" %s ", __func__);
+ kim_pdev = st_get_plat_device();
+ kim_gdata = dev_get_drvdata(&kim_pdev->dev);
+
if (kim_gdata->gpios[type] == -1) {
pr_info(" gpio not requested for protocol %s",
protocol_names[type]);
@@ -405,6 +401,9 @@ void st_kim_chip_toggle(enum proto_type type, enum kim_gpio_state state)
*/
void st_kim_recv(void *disc_data, const unsigned char *data, long count)
{
+ struct st_data_s *st_gdata = (struct st_data_s *)disc_data;
+ struct kim_data_s *kim_gdata = st_gdata->kim_data;
+
pr_info(" %s ", __func__);
/* copy to local buffer */
if (unlikely(data[4] == 0x01 && data[5] == 0x10 && data[0] == 0x04)) {
@@ -413,7 +412,7 @@ void st_kim_recv(void *disc_data, const unsigned char *data, long count)
complete_all(&kim_gdata->kim_rcvd);
return;
} else {
- kim_int_recv(data, count);
+ kim_int_recv(kim_gdata, data, count);
/* either completes or times out */
}
return;
@@ -422,27 +421,33 @@ void st_kim_recv(void *disc_data, const unsigned char *data, long count)
/* to signal completion of line discipline installation
* called from ST Core, upon tty_open
*/
-void st_kim_complete(void)
+void st_kim_complete(void *kim_data)
{
+ struct kim_data_s *kim_gdata = (struct kim_data_s *)kim_data;
complete(&kim_gdata->ldisc_installed);
}
-/* called from ST Core upon 1st registration
-*/
-long st_kim_start(void)
+/**
+ * st_kim_start - called from ST Core upon 1st registration
+ * This involves toggling the chip enable gpio, reading
+ * the firmware version from chip, forming the fw file name
+ * based on the chip version, requesting the fw, parsing it
+ * and perform download(send/recv).
+ */
+long st_kim_start(void *kim_data)
{
- long err = ST_SUCCESS;
+ long err = 0;
long retry = POR_RETRY_COUNT;
+ struct kim_data_s *kim_gdata = (struct kim_data_s *)kim_data;
+
pr_info(" %s", __func__);
do {
-#ifdef LEGACY_RFKILL_SUPPORT
/* TODO: this is only because rfkill sub-system
* doesn't send events to user-space if the state
* isn't changed
*/
rfkill_set_hw_state(kim_gdata->rfkill[ST_BT], 1);
-#endif
/* Configure BT nShutdown to HIGH state */
gpio_set_value(kim_gdata->gpios[ST_BT], GPIO_LOW);
mdelay(5); /* FIXME: a proper toggle */
@@ -450,30 +455,29 @@ long st_kim_start(void)
mdelay(100);
/* re-initialize the completion */
INIT_COMPLETION(kim_gdata->ldisc_installed);
-#ifndef LEGACY_RFKILL_SUPPORT
+#if 0 /* older way of signalling user-space UIM */
/* send signal to UIM */
err = kill_pid(find_get_pid(kim_gdata->uim_pid), SIGUSR2, 0);
if (err != 0) {
pr_info(" sending SIGUSR2 to uim failed %ld", err);
- err = ST_ERR_FAILURE;
+ err = -1;
continue;
}
-#else
+#endif
/* unblock and send event to UIM via /dev/rfkill */
rfkill_set_hw_state(kim_gdata->rfkill[ST_BT], 0);
-#endif
/* wait for ldisc to be installed */
err = wait_for_completion_timeout(&kim_gdata->ldisc_installed,
msecs_to_jiffies(LDISC_TIME));
if (!err) { /* timeout */
pr_err("line disc installation timed out ");
- err = ST_ERR_FAILURE;
+ err = -1;
continue;
} else {
/* ldisc installed now */
pr_info(" line discipline installed ");
- err = download_firmware();
- if (err != ST_SUCCESS) {
+ err = download_firmware(kim_gdata);
+ if (err != 0) {
pr_err("download firmware failed");
continue;
} else { /* on success don't retry */
@@ -484,31 +488,33 @@ long st_kim_start(void)
return err;
}
-/* called from ST Core, on the last un-registration
-*/
-long st_kim_stop(void)
+/**
+ * st_kim_stop - called from ST Core, on the last un-registration
+ * toggle low the chip enable gpio
+ */
+long st_kim_stop(void *kim_data)
{
- long err = ST_SUCCESS;
+ long err = 0;
+ struct kim_data_s *kim_gdata = (struct kim_data_s *)kim_data;
INIT_COMPLETION(kim_gdata->ldisc_installed);
-#ifndef LEGACY_RFKILL_SUPPORT
+#if 0 /* older way of signalling user-space UIM */
/* send signal to UIM */
err = kill_pid(find_get_pid(kim_gdata->uim_pid), SIGUSR2, 1);
if (err != 0) {
pr_err("sending SIGUSR2 to uim failed %ld", err);
- return ST_ERR_FAILURE;
+ return -1;
}
-#else
+#endif
/* set BT rfkill to be blocked */
err = rfkill_set_hw_state(kim_gdata->rfkill[ST_BT], 1);
-#endif
/* wait for ldisc to be un-installed */
err = wait_for_completion_timeout(&kim_gdata->ldisc_installed,
msecs_to_jiffies(LDISC_TIME));
if (!err) { /* timeout */
pr_err(" timed out waiting for ldisc to be un-installed");
- return ST_ERR_FAILURE;
+ return -1;
}
/* By default configure BT nShutdown to LOW state */
@@ -522,37 +528,24 @@ long st_kim_stop(void)
/**********************************************************************/
/* functions called from subsystems */
+/* called when debugfs entry is read from */
-#ifndef LEGACY_RFKILL_SUPPORT
-/* called when sysfs entry is written to */
-static ssize_t store_pid(struct device *dev, struct device_attribute
- *devattr, char *buf, size_t count)
-{
- pr_info("%s: pid %s ", __func__, buf);
- sscanf(buf, "%ld", &kim_gdata->uim_pid);
- /* to be made use by kim_start to signal SIGUSR2
- */
- return strlen(buf);
-}
-
-/* called when sysfs entry is read from */
-static ssize_t show_pid(struct device *dev, struct device_attribute
- *attr, char *buf)
+static int show_version(struct seq_file *s, void *unused)
{
- sprintf(buf, "%ld", kim_gdata->uim_pid);
- return strlen(buf);
+ struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private;
+ seq_printf(s, "%04X %d.%d.%d\n", kim_gdata->version.full,
+ kim_gdata->version.chip, kim_gdata->version.maj_ver,
+ kim_gdata->version.min_ver);
+ return 0;
}
-/* called when sysfs entry is read from */
-static ssize_t show_list(struct device *dev, struct device_attribute
- *attr, char *buf)
+static int show_list(struct seq_file *s, void *unused)
{
- kim_st_list_protocols(kim_gdata->core_data, buf);
- return strlen(buf);
+ struct kim_data_s *kim_gdata = (struct kim_data_s *)s->private;
+ kim_st_list_protocols(kim_gdata->core_data, s);
+ return 0;
}
-#else /* LEGACY_RFKILL_SUPPORT */
-
/* function called from rfkill subsystem, when someone from
* user space would write 0/1 on the sysfs entry
* /sys/class/rfkill/rfkill0,1,3/state
@@ -560,7 +553,7 @@ static ssize_t show_list(struct device *dev, struct device_attribute
static int kim_toggle_radio(void *data, bool blocked)
{
enum proto_type type = *((enum proto_type *)data);
- pr_info(" %s: %d ", __func__, type);
+ pr_debug(" %s: %d ", __func__, type);
switch (type) {
case ST_BT:
@@ -577,33 +570,79 @@ static int kim_toggle_radio(void *data, bool blocked)
pr_err(" wrong proto type ");
break;
}
- return ST_SUCCESS;
+ return 0;
}
-#endif /* LEGACY_RFKILL_SUPPORT */
-
+/**
+ * st_kim_ref - reference the core's data
+ * This references the per-ST platform device in the arch/xx/
+ * board-xx.c file.
+ * This would enable multiple such platform devices to exist
+ * on a given platform
+ */
void st_kim_ref(struct st_data_s **core_data)
{
+ struct platform_device *pdev;
+ struct kim_data_s *kim_gdata;
+ /* get kim_gdata reference from platform device */
+ pdev = st_get_plat_device();
+ kim_gdata = dev_get_drvdata(&pdev->dev);
*core_data = kim_gdata->core_data;
}
+static int kim_version_open(struct inode *i, struct file *f)
+{
+ return single_open(f, show_version, i->i_private);
+}
+
+static int kim_list_open(struct inode *i, struct file *f)
+{
+ return single_open(f, show_list, i->i_private);
+}
+
+static const struct file_operations version_debugfs_fops = {
+ /* version info */
+ .open = kim_version_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+static const struct file_operations list_debugfs_fops = {
+ /* protocols info */
+ .open = kim_list_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
/**********************************************************************/
/* functions called from platform device driver subsystem
* need to have a relevant platform device entry in the platform's
* board-*.c file
*/
+struct dentry *kim_debugfs_dir;
static int kim_probe(struct platform_device *pdev)
{
long status;
long proto;
long *gpios = pdev->dev.platform_data;
+ struct kim_data_s *kim_gdata;
+
+ kim_gdata = kzalloc(sizeof(struct kim_data_s), GFP_ATOMIC);
+ if (!kim_gdata) {
+ pr_err("no mem to allocate");
+ return -ENOMEM;
+ }
+ dev_set_drvdata(&pdev->dev, kim_gdata);
status = st_core_init(&kim_gdata->core_data);
if (status != 0) {
pr_err(" ST core init failed");
- return ST_ERR_FAILURE;
+ return -1;
}
+ /* refer to itself */
+ kim_gdata->core_data->kim_data = kim_gdata;
for (proto = 0; proto < ST_MAX; proto++) {
kim_gdata->gpios[proto] = gpios[proto];
@@ -639,30 +678,12 @@ static int kim_probe(struct platform_device *pdev)
return status;
}
}
-#ifndef LEGACY_RFKILL_SUPPORT
- /* pdev to contain BT, FM and GPS enable/N-Shutdown GPIOs
- * execute request_gpio, set output direction
- */
- kim_gdata->kim_kobj = kobject_create_and_add("uim", NULL);
- /* create the sysfs entry for UIM to put in pid */
- if (sysfs_create_group(kim_gdata->kim_kobj, &uim_attr_grp)) {
- pr_err(" sysfs entry creation failed");
- kobject_put(kim_gdata->kim_kobj);
- /* free requested GPIOs and fail probe */
- for (proto = ST_BT; proto < ST_MAX; proto++) {
- if (gpios[proto] != -1)
- gpio_free(gpios[proto]);
- }
- return -1; /* fail insmod */
- }
- pr_info(" sysfs entry created ");
-#endif
/* get reference of pdev for request_firmware
*/
kim_gdata->kim_pdev = pdev;
init_completion(&kim_gdata->kim_rcvd);
init_completion(&kim_gdata->ldisc_installed);
-#ifdef LEGACY_RFKILL_SUPPORT
+
for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) {
/* TODO: should all types be rfkill_type_bt ? */
kim_gdata->rf_protos[proto] = proto;
@@ -685,8 +706,20 @@ static int kim_probe(struct platform_device *pdev)
}
pr_info("rfkill entry created for %ld", gpios[proto]);
}
-#endif
- return ST_SUCCESS;
+
+ kim_debugfs_dir = debugfs_create_dir("ti-st", NULL);
+ if (IS_ERR(kim_debugfs_dir)) {
+ pr_err(" debugfs entries creation failed ");
+ kim_debugfs_dir = NULL;
+ return -1;
+ }
+
+ debugfs_create_file("version", S_IRUGO, kim_debugfs_dir,
+ kim_gdata, &version_debugfs_fops);
+ debugfs_create_file("protocols", S_IRUGO, kim_debugfs_dir,
+ kim_gdata, &list_debugfs_fops);
+ pr_info(" debugfs entries created ");
+ return 0;
}
static int kim_remove(struct platform_device *pdev)
@@ -695,27 +728,27 @@ static int kim_remove(struct platform_device *pdev)
*/
long *gpios = pdev->dev.platform_data;
long proto;
+ struct kim_data_s *kim_gdata;
+
+ kim_gdata = dev_get_drvdata(&pdev->dev);
for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) {
/* Claim the Bluetooth/FM/GPIO
* nShutdown gpio from the system
*/
gpio_free(gpios[proto]);
-#ifdef LEGACY_RFKILL_SUPPORT
rfkill_unregister(kim_gdata->rfkill[proto]);
rfkill_destroy(kim_gdata->rfkill[proto]);
kim_gdata->rfkill[proto] = NULL;
-#endif
}
pr_info("kim: GPIO Freed");
-#ifndef LEGACY_RFKILL_SUPPORT
- /* delete the sysfs entries */
- sysfs_remove_group(kim_gdata->kim_kobj, &uim_attr_grp);
- kobject_put(kim_gdata->kim_kobj);
-#endif
+ debugfs_remove_recursive(kim_debugfs_dir);
kim_gdata->kim_pdev = NULL;
st_core_exit(kim_gdata->core_data);
- return ST_SUCCESS;
+
+ kfree(kim_gdata);
+ kim_gdata = NULL;
+ return 0;
}
/**********************************************************************/
@@ -723,27 +756,19 @@ static int kim_remove(struct platform_device *pdev)
static int __init st_kim_init(void)
{
- long ret = ST_SUCCESS;
- kim_gdata = kzalloc(sizeof(struct kim_data_s), GFP_ATOMIC);
- if (!kim_gdata) {
- pr_err("no mem to allocate");
- return -ENOMEM;
- }
-
+ long ret = 0;
ret = platform_driver_register(&kim_platform_driver);
if (ret != 0) {
pr_err("platform drv registration failed");
- return ST_ERR_FAILURE;
+ return -1;
}
- return ST_SUCCESS;
+ return 0;
}
static void __exit st_kim_deinit(void)
{
/* the following returns void */
platform_driver_unregister(&kim_platform_driver);
- kfree(kim_gdata);
- kim_gdata = NULL;
}
diff --git a/drivers/staging/ti-st/st_kim.h b/drivers/staging/ti-st/st_kim.h
index ff3270ec7847..7de2541f2dea 100644
--- a/drivers/staging/ti-st/st_kim.h
+++ b/drivers/staging/ti-st/st_kim.h
@@ -43,50 +43,72 @@
* since the self-test for chip takes a while
*/
#define POR_RETRY_COUNT 5
-/*
- * legacy rfkill support where-in 3 rfkill
- * devices are created for the 3 gpios
- * that ST has requested
+
+/**
+ * struct chip_version - save the chip version
*/
-#define LEGACY_RFKILL_SUPPORT
-/*
- * header file for ST provided by KIM
+struct chip_version {
+ unsigned short full;
+ unsigned short chip;
+ unsigned short min_ver;
+ unsigned short maj_ver;
+};
+
+/**
+ * struct kim_data_s - the KIM internal data, embedded as the
+ * platform's drv data. One for each ST device in the system.
+ * @uim_pid: KIM needs to communicate with UIM to request to install
+ * the ldisc by opening UART when protocol drivers register.
+ * @kim_pdev: the platform device added in one of the board-XX.c file
+ * in arch/XX/ directory, 1 for each ST device.
+ * @kim_rcvd: completion handler to notify when data was received,
+ * mainly used during fw download, which involves multiple send/wait
+ * for each of the HCI-VS commands.
+ * @ldisc_installed: completion handler to notify that the UIM accepted
+ * the request to install ldisc, notify from tty_open which suggests
+ * the ldisc was properly installed.
+ * @resp_buffer: data buffer for the .bts fw file name.
+ * @fw_entry: firmware class struct to request/release the fw.
+ * @gpios: the list of core/chip enable gpios for BT, FM and GPS cores.
+ * @rx_state: the rx state for kim's receive func during fw download.
+ * @rx_count: the rx count for the kim's receive func during fw download.
+ * @rx_skb: all of fw data might not come at once, and hence data storage for
+ * whole of the fw response, only HCI_EVENTs and hence diff from ST's
+ * response.
+ * @rfkill: rfkill data for each of the cores to be registered with rfkill.
+ * @rf_protos: proto types of the data registered with rfkill sub-system.
+ * @core_data: ST core's data, which mainly is the tty's disc_data
+ * @version: chip version available via a sysfs entry.
+ *
*/
struct kim_data_s {
long uim_pid;
struct platform_device *kim_pdev;
struct completion kim_rcvd, ldisc_installed;
- /* MAX len of the .bts firmware script name */
char resp_buffer[30];
const struct firmware *fw_entry;
long gpios[ST_MAX];
- struct kobject *kim_kobj;
-/* used by kim_int_recv to validate fw response */
unsigned long rx_state;
unsigned long rx_count;
struct sk_buff *rx_skb;
-#ifdef LEGACY_RFKILL_SUPPORT
struct rfkill *rfkill[ST_MAX];
enum proto_type rf_protos[ST_MAX];
-#endif
struct st_data_s *core_data;
+ struct chip_version version;
};
-long st_kim_start(void);
-long st_kim_stop(void);
-/*
- * called from st_tty_receive to authenticate fw_download
+/**
+ * functions called when 1 of the protocol drivers gets
+ * registered, these need to communicate with UIM to request
+ * ldisc installed, read chip_version, download relevant fw
*/
-void st_kim_recv(void *, const unsigned char *, long count);
+long st_kim_start(void *);
+long st_kim_stop(void *);
+void st_kim_recv(void *, const unsigned char *, long count);
void st_kim_chip_toggle(enum proto_type, enum kim_gpio_state);
-
-void st_kim_complete(void);
-
-/* function called from ST KIM to ST Core, to
- * list out the protocols registered
- */
-void kim_st_list_protocols(struct st_data_s *, char *);
+void st_kim_complete(void *);
+void kim_st_list_protocols(struct st_data_s *, void *);
/*
* BTS headers
@@ -98,9 +120,13 @@ void kim_st_list_protocols(struct st_data_s *, char *);
#define ACTION_RUN_SCRIPT 5
#define ACTION_REMARKS 6
-/*
- * * BRF Firmware header
- * */
+/**
+ * struct bts_header - the fw file is NOT binary which can
+ * be sent onto TTY as is. The .bts is more a script
+ * file which has different types of actions.
+ * Each such action needs to be parsed by the KIM and
+ * relevant procedure to be called.
+ */
struct bts_header {
uint32_t magic;
uint32_t version;
@@ -108,9 +134,10 @@ struct bts_header {
uint8_t actions[0];
} __attribute__ ((packed));
-/*
- * * BRF Actions structure
- * */
+/**
+ * struct bts_action - Each .bts action has its own type of
+ * data.
+ */
struct bts_action {
uint16_t type;
uint16_t size;
@@ -136,8 +163,11 @@ struct bts_action_serial {
uint32_t flow_control;
} __attribute__ ((packed));
-/* for identifying the change speed HCI VS
- * command
+/**
+ * struct hci_command - the HCI-VS for intrepreting
+ * the change baud rate of host-side UART, which
+ * needs to be ignored, since UIM would do that
+ * when it receives request from KIM for ldisc installation.
*/
struct hci_command {
uint8_t prefix;
diff --git a/drivers/staging/ti-st/st_ll.c b/drivers/staging/ti-st/st_ll.c
index 0685a100db6a..8cca877ec2a3 100644
--- a/drivers/staging/ti-st/st_ll.c
+++ b/drivers/staging/ti-st/st_ll.c
@@ -21,6 +21,13 @@
#define pr_fmt(fmt) "(stll) :" fmt
#include "st_ll.h"
+#ifndef DEBUG
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, arg...)
+#endif
+#endif
+
/**********************************************************************/
/* internal functions */
static void send_ll_cmd(struct st_data_s *st_data,
@@ -34,7 +41,7 @@ static void send_ll_cmd(struct st_data_s *st_data,
static void ll_device_want_to_sleep(struct st_data_s *st_data)
{
- pr_info("%s", __func__);
+ pr_debug("%s", __func__);
/* sanity check */
if (st_data->ll_state != ST_LL_AWAKE)
pr_err("ERR hcill: ST_LL_GO_TO_SLEEP_IND"
@@ -101,7 +108,7 @@ void st_ll_wakeup(struct st_data_s *ll)
/* called when ST Core wants the state */
unsigned long st_ll_getstate(struct st_data_s *ll)
{
- pr_info(" returning state %ld", ll->ll_state);
+ pr_debug(" returning state %ld", ll->ll_state);
return ll->ll_state;
}
@@ -127,9 +134,9 @@ unsigned long st_ll_sleep_state(struct st_data_s *st_data,
break;
default:
pr_err(" unknown input/state ");
- return ST_ERR_FAILURE;
+ return -1;
}
- return ST_SUCCESS;
+ return 0;
}
/* Called from ST CORE to initialize ST LL */
diff --git a/drivers/staging/ti-st/st_ll.h b/drivers/staging/ti-st/st_ll.h
index 77dfbf07e7b8..e4dfacd83d90 100644
--- a/drivers/staging/ti-st/st_ll.h
+++ b/drivers/staging/ti-st/st_ll.h
@@ -41,6 +41,7 @@
#define ST_LL_AWAKE_TO_ASLEEP 3
#define ST_LL_INVALID 4
+/* different PM notifications coming from chip */
#define LL_SLEEP_IND 0x30
#define LL_SLEEP_ACK 0x31
#define LL_WAKE_UP_IND 0x32
@@ -50,13 +51,19 @@
long st_ll_init(struct st_data_s *);
long st_ll_deinit(struct st_data_s *);
-/* enable/disable ST LL along with KIM start/stop
+/**
+ * enable/disable ST LL along with KIM start/stop
* called by ST Core
*/
void st_ll_enable(struct st_data_s *);
void st_ll_disable(struct st_data_s *);
+/**
+ * various funcs used by ST core to set/get the various PM states
+ * of the chip.
+ */
unsigned long st_ll_getstate(struct st_data_s *);
unsigned long st_ll_sleep_state(struct st_data_s *, unsigned char);
void st_ll_wakeup(struct st_data_s *);
+
#endif /* ST_LL_H */
diff --git a/drivers/staging/ti-st/sysfs-uim b/drivers/staging/ti-st/sysfs-uim
index 10311afcbd45..626bda51ee87 100644
--- a/drivers/staging/ti-st/sysfs-uim
+++ b/drivers/staging/ti-st/sysfs-uim
@@ -14,3 +14,15 @@ Description:
uninstallation would be ppolling on this device and listening
on events which would suggest either to install or un-install
line discipline
+
+What: /sys/kernel/debug/ti-st/version
+Contact: Pavan Savoy <pavan_savoy@ti.com>
+Description:
+ WiLink chip's ROM version exposed to user-space for some
+ proprietary protocol stacks to make use of.
+
+What: /sys/kernel/debug/ti-st/protocols
+Contact: Pavan Savoy <pavan_savoy@ti.com>
+Description:
+ The reason for chip being ON, the list of protocols registered.
+