diff options
author | Ricardo Perez Olivares <x0081762@ti.com> | 2010-08-12 18:53:03 -0500 |
---|---|---|
committer | Ricardo Perez Olivares <x0081762@ti.com> | 2010-08-12 18:53:03 -0500 |
commit | c7d99c33566df93e7cc546d319086f1797790c5d (patch) | |
tree | 0be5b20bc2225b1566d94d3fa58fe2d8ccc2a919 /drivers | |
parent | 507a06f776aea2069ce77236d3cf7cde42a9cfe6 (diff) | |
parent | 018ebb1c1573a1184bd2ace83abb6b8cfb37008c (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/Kconfig | 10 | ||||
-rw-r--r-- | drivers/staging/ti-st/Makefile | 2 | ||||
-rw-r--r-- | drivers/staging/ti-st/TODO | 13 | ||||
-rw-r--r-- | drivers/staging/ti-st/bt_drv.c | 29 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv.h | 225 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_common.c | 2134 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_common.h | 459 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_rx.c | 812 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_rx.h | 56 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_v4l2.c | 557 | ||||
-rw-r--r-- | drivers/staging/ti-st/fmdrv_v4l2.h | 32 | ||||
-rw-r--r-- | drivers/staging/ti-st/st.h | 68 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_core.c | 208 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_core.h | 74 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_kim.c | 353 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_kim.h | 94 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_ll.c | 15 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_ll.h | 9 | ||||
-rw-r--r-- | drivers/staging/ti-st/sysfs-uim | 12 |
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, ®ion_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, ®ion_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. + |