diff options
Diffstat (limited to 'drivers/staging/ti-st')
-rw-r--r-- | drivers/staging/ti-st/Kconfig | 25 | ||||
-rw-r--r-- | drivers/staging/ti-st/Makefile | 7 | ||||
-rw-r--r-- | drivers/staging/ti-st/TODO | 19 | ||||
-rw-r--r-- | drivers/staging/ti-st/bt_drv.c | 502 | ||||
-rw-r--r-- | drivers/staging/ti-st/bt_drv.h | 61 | ||||
-rw-r--r-- | drivers/staging/ti-st/fm.h | 13 | ||||
-rw-r--r-- | drivers/staging/ti-st/st.h | 90 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_core.c | 1062 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_core.h | 98 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_kim.c | 754 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_kim.h | 150 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_ll.c | 147 | ||||
-rw-r--r-- | drivers/staging/ti-st/st_ll.h | 62 | ||||
-rw-r--r-- | drivers/staging/ti-st/sysfs-uim | 16 |
14 files changed, 3006 insertions, 0 deletions
diff --git a/drivers/staging/ti-st/Kconfig b/drivers/staging/ti-st/Kconfig new file mode 100644 index 000000000000..3ab204ddc29d --- /dev/null +++ b/drivers/staging/ti-st/Kconfig @@ -0,0 +1,25 @@ +# +# TI's shared transport line discipline and the protocol +# drivers (BT, FM and GPS) +# +menu "Texas Instruments shared transport line discipline" +config TI_ST + tristate "shared transport core driver" + depends on RFKILL + select FW_LOADER + help + This enables the shared transport core driver for TI + BT / FM and GPS combo chips. This enables protocol drivers + to register themselves with core and send data, the responses + are returned to relevant protocol drivers based on their + packet types. + +config ST_BT + tristate "BlueZ bluetooth driver for ST" + depends on BT + select TI_ST + help + 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. +endmenu diff --git a/drivers/staging/ti-st/Makefile b/drivers/staging/ti-st/Makefile new file mode 100644 index 000000000000..0167d1d2c255 --- /dev/null +++ b/drivers/staging/ti-st/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for TI's shared transport line discipline +# and its protocol drivers (BT, FM, GPS) +# +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 diff --git a/drivers/staging/ti-st/TODO b/drivers/staging/ti-st/TODO new file mode 100644 index 000000000000..2c4fe583901d --- /dev/null +++ b/drivers/staging/ti-st/TODO @@ -0,0 +1,19 @@ +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 +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. + +Please send patches to Greg Kroah-Hartman <greg@kroah.com>. diff --git a/drivers/staging/ti-st/bt_drv.c b/drivers/staging/ti-st/bt_drv.c new file mode 100644 index 000000000000..d8420b5c91fa --- /dev/null +++ b/drivers/staging/ti-st/bt_drv.c @@ -0,0 +1,502 @@ +/* + * Texas Instrument's Bluetooth Driver For Shared Transport. + * + * Bluetooth Driver acts as interface between HCI CORE and + * TI Shared Transport Layer. + * + * 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 + * + */ + +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> + +#include "st.h" +#include "bt_drv.h" + +/* Define this macro to get debug msg */ +#undef DEBUG + +#ifdef DEBUG +#define BT_DRV_DBG(fmt, arg...) printk(KERN_INFO "(btdrv):"fmt"\n" , ## arg) +#define BTDRV_API_START() printk(KERN_INFO "(btdrv): %s Start\n", \ + __func__) +#define BTDRV_API_EXIT(errno) printk(KERN_INFO "(btdrv): %s Exit(%d)\n", \ + __func__, errno) +#else +#define BT_DRV_DBG(fmt, arg...) +#define BTDRV_API_START() +#define BTDRV_API_EXIT(errno) +#endif + +#define BT_DRV_ERR(fmt, arg...) printk(KERN_ERR "(btdrv):"fmt"\n" , ## arg) + +static int reset; +static struct hci_st *hst; + +/* Increments HCI counters based on pocket ID (cmd,acl,sco) */ +static inline void hci_st_tx_complete(struct hci_st *hst, int pkt_type) +{ + struct hci_dev *hdev; + + BTDRV_API_START(); + + hdev = hst->hdev; + + /* Update HCI stat counters */ + switch (pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + + case HCI_SCODATA_PKT: + hdev->stat.cmd_tx++; + break; + } + + BTDRV_API_EXIT(0); +} + +/* ------- Interfaces to Shared Transport ------ */ + +/* Called by ST layer to indicate protocol registration completion + * 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) +{ + 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; + + /* 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); + + BTDRV_API_EXIT(0); +} + +/* Called by Shared Transport layer when receive data is + * available */ +static long hci_st_receive(struct sk_buff *skb) +{ + int err; + int len; + + BTDRV_API_START(); + + err = 0; + len = 0; + + if (skb == NULL) { + BT_DRV_ERR("Invalid SKB received from ST"); + BTDRV_API_EXIT(-EFAULT); + return -EFAULT; + } + if (!hst) { + 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)) { + kfree_skb(skb); + BT_DRV_ERR("Device is not running,freeing SKB"); + BTDRV_API_EXIT(-EINVAL); + return -EINVAL; + } + + len = skb->len; + skb->dev = (struct net_device *)hst->hdev; + + /* Forward skb to HCI CORE layer */ + err = hci_recv_frame(skb); + if (err) { + kfree_skb(skb); + BT_DRV_ERR("Unable to push skb to HCI CORE(%d),freeing SKB", + err); + BTDRV_API_EXIT(err); + return err; + } + hst->hdev->stat.byte_rx += len; + + BTDRV_API_EXIT(0); + return 0; +} + +/* ------- Interfaces to HCI layer ------ */ + +/* Called from HCI core to initialize the device */ +static int hci_st_open(struct hci_dev *hdev) +{ + static struct st_proto_s hci_st_proto; + unsigned long timeleft; + int err; + + BTDRV_API_START(); + + err = 0; + + BT_DRV_DBG("%s %p", hdev->name, hdev); + + /* Already registered with ST ? */ + if (test_bit(BT_ST_REGISTERED, &hst->flags)) { + BT_DRV_ERR("Registered with ST already,open called again?"); + BTDRV_API_EXIT(0); + return 0; + } + + /* Populate BT driver info required by ST */ + memset(&hci_st_proto, 0, sizeof(hci_st_proto)); + + /* BT driver ID */ + hci_st_proto.type = ST_BT; + + /* Receive function which called from ST */ + hci_st_proto.recv = hci_st_receive; + + /* Packet match function may used in future */ + hci_st_proto.match_packet = NULL; + + /* Callback to be called when registration is pending */ + hci_st_proto.reg_complete_cb = hci_st_registration_completion_cb; + + /* This is write function pointer of ST. BT driver will make use of this + * for sending any packets to chip. ST will assign and give to us, so + * make it as NULL */ + hci_st_proto.write = NULL; + + /* Register with ST layer */ + err = st_register(&hci_st_proto); + if (err == ST_ERR_PENDING) { + /* Prepare wait-for-completion handler data structures. + * Needed to syncronize this and st_registration_completion_cb() + * functions. + */ + init_completion(&hst->wait_for_btdrv_reg_completion); + + /* Reset ST registration callback status flag , this value + * will be updated in hci_st_registration_completion_cb() + * function whenever it called from ST driver. + */ + hst->streg_cbdata = -EINPROGRESS; + + /* ST is busy with other protocol registration(may be busy with + * firmware download).So,Wait till the registration callback + * (passed as a argument to st_register() function) getting + * called from ST. + */ + BT_DRV_DBG(" %s waiting for reg completion signal from ST", + __func__); + + timeleft = + wait_for_completion_timeout + (&hst->wait_for_btdrv_reg_completion, + msecs_to_jiffies(BT_REGISTER_TIMEOUT)); + if (!timeleft) { + BT_DRV_ERR("Timeout(%ld sec),didn't get reg" + "completion signal from ST", + BT_REGISTER_TIMEOUT / 1000); + BTDRV_API_EXIT(-ETIMEDOUT); + return -ETIMEDOUT; + } + + /* Is ST registration callback called with ERROR value? */ + if (hst->streg_cbdata != 0) { + BT_DRV_ERR("ST reg completion CB called with invalid" + "status %d", hst->streg_cbdata); + BTDRV_API_EXIT(-EAGAIN); + return -EAGAIN; + } + err = 0; + } else if (err == ST_ERR_FAILURE) { + BT_DRV_ERR("st_register failed %d", err); + BTDRV_API_EXIT(-EAGAIN); + return -EAGAIN; + } + + /* Do we have proper ST write function? */ + if (hci_st_proto.write != NULL) { + /* We need this pointer for sending any Bluetooth pkts */ + hst->st_write = hci_st_proto.write; + } else { + BT_DRV_ERR("failed to get ST write func pointer"); + + /* Undo registration with ST */ + err = st_unregister(ST_BT); + if (err < 0) + BT_DRV_ERR("st_unregister failed %d", err); + + hst->st_write = NULL; + BTDRV_API_EXIT(-EAGAIN); + return -EAGAIN; + } + + /* Registration with ST layer is completed successfully, + * now chip is ready to accept commands from HCI CORE. + * Mark HCI Device flag as RUNNING + */ + set_bit(HCI_RUNNING, &hdev->flags); + + /* Registration with ST successful */ + set_bit(BT_ST_REGISTERED, &hst->flags); + + BTDRV_API_EXIT(err); + return err; +} + +/* Close device */ +static int hci_st_close(struct hci_dev *hdev) +{ + int err; + + BTDRV_API_START(); + + err = 0; + + /* Unregister from ST layer */ + if (test_and_clear_bit(BT_ST_REGISTERED, &hst->flags)) { + err = st_unregister(ST_BT); + if (err != ST_SUCCESS) { + BT_DRV_ERR("st_unregister failed %d", err); + BTDRV_API_EXIT(-EBUSY); + return -EBUSY; + } + } + + hst->st_write = NULL; + + /* ST layer would have moved chip to inactive state. + * So,clear HCI device RUNNING flag. + */ + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) { + BTDRV_API_EXIT(0); + return 0; + } + + BTDRV_API_EXIT(err); + return err; +} + +/* Called from HCI CORE , Sends frames to Shared Transport */ +static int hci_st_send_frame(struct sk_buff *skb) +{ + struct hci_dev *hdev; + struct hci_st *hst; + long len; + + BTDRV_API_START(); + + if (skb == NULL) { + BT_DRV_ERR("Invalid skb received from HCI CORE"); + BTDRV_API_EXIT(-ENOMEM); + return -ENOMEM; + } + hdev = (struct hci_dev *)skb->dev; + if (!hdev) { + BT_DRV_ERR("SKB received for invalid HCI Device (hdev=NULL)"); + BTDRV_API_EXIT(-ENODEV); + return -ENODEV; + } + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + BT_DRV_ERR("Device is not running"); + BTDRV_API_EXIT(-EBUSY); + return -EBUSY; + } + + hst = (struct hci_st *)hdev->driver_data; + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); + + BT_DRV_DBG(" %s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type, + skb->len); + + /* Insert skb to shared transport layer's transmit queue. + * Freeing skb memory is taken care in shared transport layer, + * so don't free skb memory here. + */ + if (!hst->st_write) { + kfree_skb(skb); + BT_DRV_ERR(" Can't write to ST, st_write null?"); + BTDRV_API_EXIT(-EAGAIN); + return -EAGAIN; + } + len = hst->st_write(skb); + if (len < 0) { + /* Something went wrong in st write , free skb memory */ + kfree_skb(skb); + BT_DRV_ERR(" ST write failed (%ld)", len); + BTDRV_API_EXIT(-EAGAIN); + return -EAGAIN; + } + + /* ST accepted our skb. So, Go ahead and do rest */ + hdev->stat.byte_tx += len; + hci_st_tx_complete(hst, bt_cb(skb)->pkt_type); + + BTDRV_API_EXIT(0); + return 0; +} + +static void hci_st_destruct(struct hci_dev *hdev) +{ + BTDRV_API_START(); + + if (!hdev) { + BT_DRV_ERR("Destruct called with invalid HCI Device" + "(hdev=NULL)"); + BTDRV_API_EXIT(0); + return; + } + + BT_DRV_DBG("%s", hdev->name); + + /* free hci_st memory */ + if (hdev->driver_data != NULL) + kfree(hdev->driver_data); + + BTDRV_API_EXIT(0); + return; +} + +/* Creates new HCI device */ +static int hci_st_register_dev(struct hci_st *hst) +{ + struct hci_dev *hdev; + + BTDRV_API_START(); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_DRV_ERR("Can't allocate HCI device"); + BTDRV_API_EXIT(-ENOMEM); + return -ENOMEM; + } + BT_DRV_DBG(" HCI device allocated. hdev= %p", hdev); + + hst->hdev = hdev; + hdev->bus = HCI_UART; + hdev->driver_data = hst; + hdev->open = hci_st_open; + hdev->close = hci_st_close; + hdev->flush = NULL; + hdev->send = hci_st_send_frame; + hdev->destruct = hci_st_destruct; + hdev->owner = THIS_MODULE; + + if (reset) + set_bit(HCI_QUIRK_NO_RESET, &hdev->quirks); + + if (hci_register_dev(hdev) < 0) { + BT_DRV_ERR("Can't register HCI device"); + hci_free_dev(hdev); + BTDRV_API_EXIT(-ENODEV); + return -ENODEV; + } + + BT_DRV_DBG(" HCI device registered. hdev= %p", hdev); + BTDRV_API_EXIT(0); + return 0; +} + +/* ------- Module Init interface ------ */ + +static int __init bt_drv_init(void) +{ + int err; + + BTDRV_API_START(); + + err = 0; + + BT_DRV_DBG(" Bluetooth Driver Version %s", VERSION); + + /* Allocate local resource memory */ + hst = kzalloc(sizeof(struct hci_st), GFP_KERNEL); + if (!hst) { + BT_DRV_ERR("Can't allocate control structure"); + BTDRV_API_EXIT(-ENFILE); + return -ENFILE; + } + + /* Expose "hciX" device to user space */ + err = hci_st_register_dev(hst); + if (err) { + /* Release local resource memory */ + kfree(hst); + + BT_DRV_ERR("Unable to expose hci0 device(%d)", err); + BTDRV_API_EXIT(err); + return err; + } + set_bit(BT_DRV_RUNNING, &hst->flags); + + BTDRV_API_EXIT(err); + return err; +} + +/* ------- Module Exit interface ------ */ + +static void __exit bt_drv_exit(void) +{ + BTDRV_API_START(); + + /* Deallocate local resource's memory */ + if (hst) { + struct hci_dev *hdev = hst->hdev; + + if (hdev == NULL) { + BT_DRV_ERR("Invalid hdev memory"); + kfree(hst); + } else { + hci_st_close(hdev); + if (test_and_clear_bit(BT_DRV_RUNNING, &hst->flags)) { + /* Remove HCI device (hciX) created + * in module init. + */ + hci_unregister_dev(hdev); + + /* Free HCI device memory */ + hci_free_dev(hdev); + } + } + } + BTDRV_API_EXIT(0); +} + +module_init(bt_drv_init); +module_exit(bt_drv_exit); + +/* ------ Module Info ------ */ + +module_param(reset, bool, 0644); +MODULE_PARM_DESC(reset, "Send HCI reset command on initialization"); +MODULE_AUTHOR("Raja Mani <raja_mani@ti.com>"); +MODULE_DESCRIPTION("Bluetooth Driver for TI Shared Transport" VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/ti-st/bt_drv.h b/drivers/staging/ti-st/bt_drv.h new file mode 100644 index 000000000000..a0beebec8465 --- /dev/null +++ b/drivers/staging/ti-st/bt_drv.h @@ -0,0 +1,61 @@ +/* + * Texas Instrument's Bluetooth Driver For Shared Transport. + * + * Bluetooth Driver acts as interface between HCI CORE and + * TI Shared Transport Layer. + * + * 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 _BT_DRV_H +#define _BT_DRV_H + +/* Bluetooth Driver Version */ +#define VERSION "1.0" + +/* Defines number of seconds to wait for reg completion + * callback getting called from ST (in case,registration + * with ST returns PENDING status) + */ +#define BT_REGISTER_TIMEOUT msecs_to_jiffies(6000) /* 6 sec */ + +/* BT driver's local status */ +#define BT_DRV_RUNNING 0 +#define BT_ST_REGISTERED 1 + +/* BT driver operation structure */ +struct hci_st { + + /* hci device pointer which binds to bt driver */ + struct hci_dev *hdev; + + /* used locally,to maintain various BT driver status */ + unsigned long flags; + + /* to hold ST registration callback status */ + char streg_cbdata; + + /* write function pointer of ST driver */ + long (*st_write) (struct sk_buff *); + + /* Wait on comepletion handler needed to synchronize + * hci_st_open() and hci_st_registration_completion_cb() + * functions.*/ + struct completion wait_for_btdrv_reg_completion; +}; + +#endif diff --git a/drivers/staging/ti-st/fm.h b/drivers/staging/ti-st/fm.h new file mode 100644 index 000000000000..be41453649ed --- /dev/null +++ b/drivers/staging/ti-st/fm.h @@ -0,0 +1,13 @@ +struct fm_event_hdr { + unsigned char plen; +} __attribute__ ((packed)); + +#define FM_MAX_FRAME_SIZE 0xFF /* TODO: */ +#define FM_EVENT_HDR_SIZE 1 /* size of fm_event_hdr */ +#define ST_FM_CH8_PKT 0x8 + +/* gps stuff */ +struct gps_event_hdr { +unsigned char opcode; +unsigned short plen; +} __attribute__ ((packed)); diff --git a/drivers/staging/ti-st/st.h b/drivers/staging/ti-st/st.h new file mode 100644 index 000000000000..e8fc97e32c94 --- /dev/null +++ b/drivers/staging/ti-st/st.h @@ -0,0 +1,90 @@ +/* + * Shared Transport Header file + * To be included by the protocol stack drivers for + * Texas Instruments BT,FM and GPS combo chip drivers + * + * 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 ST_H +#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 */ + +/* some gpios have active high, others like fm have + * active low + */ +enum kim_gpio_state { + KIM_GPIO_INACTIVE, + KIM_GPIO_ACTIVE, +}; +/* + * the list of protocols on chip + */ +enum proto_type { + ST_BT, + ST_FM, + ST_GPS, + 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 { + 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 + */ + 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 + */ + long (*write) (struct sk_buff *skb); +}; + +extern long st_register(struct st_proto_s *new_proto); +extern long st_unregister(enum proto_type type); + +#endif /* ST_H */ diff --git a/drivers/staging/ti-st/st_core.c b/drivers/staging/ti-st/st_core.c new file mode 100644 index 000000000000..4e93694e1c21 --- /dev/null +++ b/drivers/staging/ti-st/st_core.c @@ -0,0 +1,1062 @@ +/* + * Shared Transport Line discipline driver Core + * This hooks up ST KIM driver and ST LL driver + * 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 + * + */ + +#define pr_fmt(fmt) "(stc): " fmt +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/tty.h> + +/* understand BT, FM and GPS for now */ +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include <net/bluetooth/hci.h> +#include "fm.h" +/* + * packet formats for fm and gps + * #include "gps.h" + */ +#include "st_core.h" +#include "st_kim.h" +#include "st_ll.h" +#include "st.h" + +#ifdef DEBUG +/* strings to be used for rfkill entries and by + * ST Core to be used for sysfs debug entry + */ +#define PROTO_ENTRY(type, name) name +const unsigned char *protocol_strngs[] = { + PROTO_ENTRY(ST_BT, "Bluetooth"), + 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 + */ +void (*st_recv) (void*, const unsigned char*, long); + +/********************************************************************/ +#if 0 +/* internal misc functions */ +bool is_protocol_list_empty(void) +{ + unsigned char i = 0; + pr_info(" %s ", __func__); + for (i = 0; i < ST_MAX; i++) { + if (st_gdata->list[i] != NULL) + return ST_NOTEMPTY; + /* not empty */ + } + /* list empty */ + return ST_EMPTY; +} +#endif +/* can be called in from + * -- KIM (during fw download) + * -- ST Core (during st_write) + * + * This is the internal write function - a wrapper + * to tty->ops->write + */ +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; + } + 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"); +#endif + return tty->ops->write(tty, data, count); + +} + +/* + * push the skb received to relevant + * protocol stacks + */ +void st_send_frame(enum proto_type protoid, struct st_data_s *st_gdata) +{ + pr_info(" %s(prot:%d) ", __func__, protoid); + + if (unlikely + (st_gdata == NULL || st_gdata->rx_skb == NULL + || st_gdata->list[protoid] == NULL)) { + pr_err("protocol %d not registered, no data to send?", + protoid); + kfree_skb(st_gdata->rx_skb); + return; + } + /* this cannot fail + * this shouldn't take long + * - should be just skb_queue_tail for the + * protocol stack driver + */ + if (likely(st_gdata->list[protoid]->recv != NULL)) { + if (unlikely(st_gdata->list[protoid]->recv(st_gdata->rx_skb) + != ST_SUCCESS)) { + pr_err(" proto stack %d's ->recv failed", protoid); + kfree_skb(st_gdata->rx_skb); + return; + } + } else { + pr_err(" proto stack %d's ->recv null", protoid); + kfree_skb(st_gdata->rx_skb); + } + pr_info(" done %s", __func__); + return; +} + +/* + * to call registration complete callbacks + * of all protocol stack drivers + */ +void st_reg_complete(struct st_data_s *st_gdata, char err) +{ + unsigned char i = 0; + pr_info(" %s ", __func__); + 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); + } +} + +static inline int st_check_data_len(struct st_data_s *st_gdata, + int protoid, int len) +{ + register int room = skb_tailroom(st_gdata->rx_skb); + + pr_info("len %d room %d", len, room); + + if (!len) { + /* Received packet has only packet header and + * has zero length payload. So, ask ST CORE to + * forward the packet to protocol driver (BT/FM/GPS) + */ + st_send_frame(protoid, st_gdata); + + } else if (len > room) { + /* Received packet's payload length is larger. + * We can't accommodate it in created skb. + */ + pr_err("Data length is too large len %d room %d", len, + room); + kfree_skb(st_gdata->rx_skb); + } else { + /* Packet header has non-zero payload length and + * we have enough space in created skb. Lets read + * payload data */ + st_gdata->rx_state = ST_BT_W4_DATA; + st_gdata->rx_count = len; + return len; + } + + /* Change ST state to continue to process next + * packet */ + st_gdata->rx_state = ST_W4_PACKET_TYPE; + st_gdata->rx_skb = NULL; + st_gdata->rx_count = 0; + + return 0; +} + +/* internal function for action when wake-up ack + * received + */ +static inline void st_wakeup_ack(struct st_data_s *st_gdata, + unsigned char cmd) +{ + register struct sk_buff *waiting_skb; + unsigned long flags = 0; + + spin_lock_irqsave(&st_gdata->lock, flags); + /* de-Q from waitQ and Q in txQ now that the + * chip is awake + */ + while ((waiting_skb = skb_dequeue(&st_gdata->tx_waitq))) + skb_queue_tail(&st_gdata->txq, waiting_skb); + + /* state forwarded to ST LL */ + st_ll_sleep_state(st_gdata, (unsigned long)cmd); + spin_unlock_irqrestore(&st_gdata->lock, flags); + + /* wake up to send the recently copied skbs from waitQ */ + st_tx_wakeup(st_gdata); +} + +/* Decodes received RAW data and forwards to corresponding + * client drivers (Bluetooth,FM,GPS..etc). + * + */ +void st_int_recv(void *disc_data, + const unsigned char *data, long count) +{ + register char *ptr; + struct hci_event_hdr *eh; + struct hci_acl_hdr *ah; + struct hci_sco_hdr *sh; + struct fm_event_hdr *fm; + struct gps_event_hdr *gps; + register int len = 0, type = 0, dlen = 0; + static enum proto_type protoid = ST_MAX; + struct st_data_s *st_gdata = (struct st_data_s *)disc_data; + + ptr = (char *)data; + /* tty_receive sent null ? */ + if (unlikely(ptr == NULL) || (st_gdata == NULL)) { + pr_err(" received null from TTY "); + return; + } + + pr_info("count %ld rx_state %ld" + "rx_count %ld", count, st_gdata->rx_state, + st_gdata->rx_count); + + /* Decode received bytes here */ + while (count) { + if (st_gdata->rx_count) { + len = min_t(unsigned int, st_gdata->rx_count, count); + memcpy(skb_put(st_gdata->rx_skb, len), ptr, len); + st_gdata->rx_count -= len; + count -= len; + ptr += len; + + if (st_gdata->rx_count) + continue; + + /* Check ST RX state machine , where are we? */ + switch (st_gdata->rx_state) { + + /* Waiting for complete packet ? */ + case ST_BT_W4_DATA: + pr_info("Complete pkt received"); + + /* Ask ST CORE to forward + * the packet to protocol driver */ + st_send_frame(protoid, st_gdata); + + st_gdata->rx_state = ST_W4_PACKET_TYPE; + st_gdata->rx_skb = NULL; + protoid = ST_MAX; /* is this required ? */ + continue; + + /* Waiting for Bluetooth event header ? */ + case ST_BT_W4_EVENT_HDR: + eh = (struct hci_event_hdr *)st_gdata->rx_skb-> + data; + + pr_info("Event header: evt 0x%2.2x" + "plen %d", eh->evt, eh->plen); + + st_check_data_len(st_gdata, protoid, eh->plen); + continue; + + /* Waiting for Bluetooth acl header ? */ + case ST_BT_W4_ACL_HDR: + ah = (struct hci_acl_hdr *)st_gdata->rx_skb-> + data; + dlen = __le16_to_cpu(ah->dlen); + + pr_info("ACL header: dlen %d", dlen); + + st_check_data_len(st_gdata, protoid, dlen); + continue; + + /* Waiting for Bluetooth sco header ? */ + case ST_BT_W4_SCO_HDR: + sh = (struct hci_sco_hdr *)st_gdata->rx_skb-> + data; + + pr_info("SCO header: dlen %d", sh->dlen); + + st_check_data_len(st_gdata, protoid, sh->dlen); + continue; + case ST_FM_W4_EVENT_HDR: + fm = (struct fm_event_hdr *)st_gdata->rx_skb-> + data; + pr_info("FM Header: "); + st_check_data_len(st_gdata, ST_FM, fm->plen); + continue; + /* TODO : Add GPS packet machine logic here */ + case ST_GPS_W4_EVENT_HDR: + /* [0x09 pkt hdr][R/W byte][2 byte len] */ + gps = (struct gps_event_hdr *)st_gdata->rx_skb-> + data; + pr_info("GPS Header: "); + st_check_data_len(st_gdata, ST_GPS, gps->plen); + continue; + } /* end of switch rx_state */ + } + + /* end of if rx_count */ + /* Check first byte of packet and identify module + * owner (BT/FM/GPS) */ + switch (*ptr) { + + /* Bluetooth event packet? */ + case HCI_EVENT_PKT: + pr_info("Event packet"); + st_gdata->rx_state = ST_BT_W4_EVENT_HDR; + st_gdata->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + protoid = ST_BT; + break; + + /* Bluetooth acl packet? */ + case HCI_ACLDATA_PKT: + pr_info("ACL packet"); + st_gdata->rx_state = ST_BT_W4_ACL_HDR; + st_gdata->rx_count = HCI_ACL_HDR_SIZE; + type = HCI_ACLDATA_PKT; + protoid = ST_BT; + break; + + /* Bluetooth sco packet? */ + case HCI_SCODATA_PKT: + pr_info("SCO packet"); + st_gdata->rx_state = ST_BT_W4_SCO_HDR; + st_gdata->rx_count = HCI_SCO_HDR_SIZE; + type = HCI_SCODATA_PKT; + protoid = ST_BT; + break; + + /* Channel 8(FM) packet? */ + case ST_FM_CH8_PKT: + pr_info("FM CH8 packet"); + type = ST_FM_CH8_PKT; + st_gdata->rx_state = ST_FM_W4_EVENT_HDR; + st_gdata->rx_count = FM_EVENT_HDR_SIZE; + protoid = ST_FM; + break; + + /* Channel 9(GPS) packet? */ + case 0x9: /*ST_LL_GPS_CH9_PKT */ + pr_info("GPS CH9 packet"); + type = 0x9; /* ST_LL_GPS_CH9_PKT; */ + protoid = ST_GPS; + st_gdata->rx_state = ST_GPS_W4_EVENT_HDR; + st_gdata->rx_count = 3; /* GPS_EVENT_HDR_SIZE -1*/ + break; + case LL_SLEEP_IND: + case LL_SLEEP_ACK: + case LL_WAKE_UP_IND: + pr_info("PM packet"); + /* this takes appropriate action based on + * sleep state received -- + */ + st_ll_sleep_state(st_gdata, *ptr); + ptr++; + count--; + continue; + case LL_WAKE_UP_ACK: + pr_info("PM packet"); + /* wake up ack received */ + st_wakeup_ack(st_gdata, *ptr); + ptr++; + count--; + continue; + /* Unknow packet? */ + default: + pr_err("Unknown packet type %2.2x", (__u8) *ptr); + ptr++; + count--; + continue; + }; + ptr++; + count--; + + switch (protoid) { + case ST_BT: + /* Allocate new packet to hold received data */ + st_gdata->rx_skb = + bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!st_gdata->rx_skb) { + pr_err("Can't allocate mem for new packet"); + st_gdata->rx_state = ST_W4_PACKET_TYPE; + st_gdata->rx_count = 0; + return; + } + bt_cb(st_gdata->rx_skb)->pkt_type = type; + break; + case ST_FM: /* for FM */ + st_gdata->rx_skb = + alloc_skb(FM_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!st_gdata->rx_skb) { + pr_err("Can't allocate mem for new packet"); + st_gdata->rx_state = ST_W4_PACKET_TYPE; + st_gdata->rx_count = 0; + return; + } + /* place holder 0x08 */ + skb_reserve(st_gdata->rx_skb, 1); + st_gdata->rx_skb->cb[0] = ST_FM_CH8_PKT; + break; + case ST_GPS: + /* for GPS */ + st_gdata->rx_skb = + alloc_skb(100 /*GPS_MAX_FRAME_SIZE */ , GFP_ATOMIC); + if (!st_gdata->rx_skb) { + pr_err("Can't allocate mem for new packet"); + st_gdata->rx_state = ST_W4_PACKET_TYPE; + st_gdata->rx_count = 0; + return; + } + /* place holder 0x09 */ + skb_reserve(st_gdata->rx_skb, 1); + st_gdata->rx_skb->cb[0] = 0x09; /*ST_GPS_CH9_PKT; */ + break; + case ST_MAX: + break; + } + } + pr_info("done %s", __func__); + return; +} + +/* internal de-Q function + * -- return previous in-completely written skb + * or return the skb in the 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 + */ + 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 + */ +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 */ + spin_lock_irqsave(&st_gdata->lock, flags); + + switch (st_ll_getstate(st_gdata)) { + case ST_LL_AWAKE: + pr_info("ST LL is AWAKE, sending normally"); + skb_queue_tail(&st_gdata->txq, skb); + break; + 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 */ + 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; + default: + pr_err("ST LL is illegal state(%ld)," + "purging received skb.", st_ll_getstate(st_gdata)); + kfree_skb(skb); + break; + } + spin_unlock_irqrestore(&st_gdata->lock, flags); + pr_info("done %s", __func__); + return; +} + +/* + * internal wakeup function + * called from either + * - TTY layer when write's finished + * - st_write (in context of the protocol stack) + */ +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__); + /* check for sending & set flag sending here */ + if (test_and_set_bit(ST_TX_SENDING, &st_data->tx_state)) { + pr_info("ST already sending"); + /* keep sending */ + set_bit(ST_TX_WAKEUP, &st_data->tx_state); + return; + /* TX_WAKEUP will be checked in another + * context + */ + } + do { /* come back if st_tx_wakeup is set */ + /* woke-up to write */ + clear_bit(ST_TX_WAKEUP, &st_data->tx_state); + while ((skb = st_int_dequeue(st_data))) { + int len; + spin_lock_irqsave(&st_data->lock, flags); + /* enable wake-up from TTY */ + set_bit(TTY_DO_WRITE_WAKEUP, &st_data->tty->flags); + len = st_int_write(st_data, skb->data, skb->len); + skb_pull(skb, len); + /* if skb->len = len as expected, skb->len=0 */ + if (skb->len) { + /* would be the next skb to be sent */ + st_data->tx_skb = skb; + spin_unlock_irqrestore(&st_data->lock, flags); + break; + } + kfree_skb(skb); + spin_unlock_irqrestore(&st_data->lock, flags); + } + /* if wake-up is set in another context- restart sending */ + } while (test_bit(ST_TX_WAKEUP, &st_data->tx_state)); + + /* clear flag sending */ + clear_bit(ST_TX_SENDING, &st_data->tx_state); +} + +/********************************************************************/ +/* functions called from ST KIM +*/ +void kim_st_list_protocols(struct st_data_s *st_gdata, char *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); +} + +/********************************************************************/ +/* + * functions called from protocol stack drivers + * to be EXPORT-ed + */ +long st_register(struct st_proto_s *new_proto) +{ + struct st_data_s *st_gdata; + long err = ST_SUCCESS; + unsigned long flags = 0; + + st_kim_ref(&st_gdata); + pr_info("%s(%d) ", __func__, new_proto->type); + 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; + } + + if (new_proto->type < ST_BT || new_proto->type >= ST_MAX) { + pr_err("protocol %d not supported", new_proto->type); + return ST_ERR_NOPROTO; + } + + if (st_gdata->list[new_proto->type] != NULL) { + pr_err("protocol %d already registered", new_proto->type); + return ST_ERR_ALREADY; + } + + /* can be from process context only */ + spin_lock_irqsave(&st_gdata->lock, flags); + + if (test_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state)) { + pr_info(" ST_REG_IN_PROGRESS:%d ", new_proto->type); + /* fw download in progress */ + st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE); + + st_gdata->list[new_proto->type] = new_proto; + 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; + } 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); + st_recv = st_kim_recv; + + /* release lock previously held - re-locked below */ + spin_unlock_irqrestore(&st_gdata->lock, flags); + + /* enable the ST LL - to set default chip state */ + st_ll_enable(st_gdata); + /* this may take a while to complete + * since it involves BT fw download + */ + err = st_kim_start(); + if (err != ST_SUCCESS) { + 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); + } + + return ST_ERR_FAILURE; + } + + /* the protocol might require other gpios to be toggled + */ + st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE); + + clear_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state); + st_recv = st_int_recv; + + /* this is where all pending registration + * are signalled to be complete by calling callback functions + */ + 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); + } + clear_bit(ST_REG_PENDING, &st_gdata->st_state); + + /* check for already registered once more, + * since the above check is old + */ + if (st_gdata->list[new_proto->type] != NULL) { + pr_err(" proto %d already registered ", + new_proto->type); + return ST_ERR_ALREADY; + } + + spin_lock_irqsave(&st_gdata->lock, flags); + st_gdata->list[new_proto->type] = new_proto; + new_proto->write = st_write; + spin_unlock_irqrestore(&st_gdata->lock, flags); + return err; + } + /* if fw is already downloaded & new stack registers protocol */ + else { + switch (new_proto->type) { + case ST_BT: + /* do nothing */ + break; + case ST_FM: + case ST_GPS: + st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE); + break; + case ST_MAX: + default: + pr_err("%d protocol not supported", + new_proto->type); + err = ST_ERR_NOPROTO; + /* something wrong */ + break; + } + st_gdata->list[new_proto->type] = new_proto; + 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); +} +EXPORT_SYMBOL_GPL(st_register); + +/* to unregister a protocol - + * to be called from protocol stack driver + */ +long st_unregister(enum proto_type type) +{ + long err = ST_SUCCESS; + unsigned long flags = 0; + struct st_data_s *st_gdata; + + pr_info("%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; + } + + spin_lock_irqsave(&st_gdata->lock, flags); + + if (st_gdata->list[type] == NULL) { + pr_err(" protocol %d not registered", type); + spin_unlock_irqrestore(&st_gdata->lock, flags); + return ST_ERR_NOPROTO; + } + + st_gdata->protos_registered--; + st_gdata->list[type] = NULL; + + /* kim ignores BT in the below function + * and handles the rest, BT is toggled + * only in kim_start and kim_stop + */ + st_kim_chip_toggle(type, KIM_GPIO_INACTIVE); + spin_unlock_irqrestore(&st_gdata->lock, flags); + + if ((st_gdata->protos_registered == ST_EMPTY) && + (!test_bit(ST_REG_PENDING, &st_gdata->st_state))) { + pr_info(" all protocols unregistered "); + + /* stop traffic on tty */ + if (st_gdata->tty) { + tty_ldisc_flush(st_gdata->tty); + stop_tty(st_gdata->tty); + } + + /* all protocols now unregistered */ + st_kim_stop(); + /* disable ST LL */ + st_ll_disable(st_gdata); + } + return err; +} + +/* + * called in protocol stack drivers + * via the write function pointer + */ +long st_write(struct sk_buff *skb) +{ + struct st_data_s *st_gdata; +#ifdef DEBUG + enum proto_type protoid = ST_MAX; +#endif + long len; + + st_kim_ref(&st_gdata); + if (unlikely(skb == NULL || st_gdata == NULL + || st_gdata->tty == NULL)) { + pr_err("data/tty unavailable to perform write"); + return ST_ERR_FAILURE; + } +#ifdef DEBUG /* open-up skb to read the 1st byte */ + switch (skb->data[0]) { + case HCI_COMMAND_PKT: + case HCI_ACLDATA_PKT: + case HCI_SCODATA_PKT: + protoid = ST_BT; + break; + case ST_FM_CH8_PKT: + protoid = ST_FM; + break; + case 0x09: + protoid = ST_GPS; + break; + } + if (unlikely(st_gdata->list[protoid] == NULL)) { + pr_err(" protocol %d not registered, and writing? ", + protoid); + return ST_ERR_FAILURE; + } +#endif + pr_info("%d to be written", skb->len); + len = skb->len; + + /* st_ll to decide where to enqueue the skb */ + st_int_enqueue(st_gdata, skb); + /* wake up */ + st_tx_wakeup(st_gdata); + + /* return number of bytes written */ + return len; +} + +/* for protocols making use of shared transport */ +EXPORT_SYMBOL_GPL(st_unregister); + +/********************************************************************/ +/* + * functions called from TTY layer + */ +static int st_tty_open(struct tty_struct *tty) +{ + int err = ST_SUCCESS; + struct st_data_s *st_gdata; + pr_info("%s ", __func__); + + st_kim_ref(&st_gdata); + st_gdata->tty = tty; + tty->disc_data = st_gdata; + + /* don't do an wakeup for now */ + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + /* mem already allocated + */ + tty->receive_room = 65536; + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + tty_driver_flush_buffer(tty); + /* + * signal to UIM via KIM that - + * installation of N_TI_WL ldisc is complete + */ + st_kim_complete(); + pr_info("done %s", __func__); + return err; +} + +static void st_tty_close(struct tty_struct *tty) +{ + unsigned char i = ST_MAX; + unsigned long flags = 0; + struct st_data_s *st_gdata = tty->disc_data; + + pr_info("%s ", __func__); + + /* TODO: + * if a protocol has been registered & line discipline + * un-installed for some reason - what should be done ? + */ + spin_lock_irqsave(&st_gdata->lock, flags); + for (i = ST_BT; i < ST_MAX; i++) { + if (st_gdata->list[i] != NULL) + pr_err("%d not un-registered", i); + st_gdata->list[i] = NULL; + } + spin_unlock_irqrestore(&st_gdata->lock, flags); + /* + * signal to UIM via KIM that - + * N_TI_WL ldisc is un-installed + */ + st_kim_complete(); + st_gdata->tty = NULL; + /* Flush any pending characters in the driver and discipline. */ + tty_ldisc_flush(tty); + tty_driver_flush_buffer(tty); + + spin_lock_irqsave(&st_gdata->lock, flags); + /* empty out txq and tx_waitq */ + skb_queue_purge(&st_gdata->txq); + skb_queue_purge(&st_gdata->tx_waitq); + /* reset the TTY Rx states of ST */ + st_gdata->rx_count = 0; + st_gdata->rx_state = ST_W4_PACKET_TYPE; + kfree_skb(st_gdata->rx_skb); + st_gdata->rx_skb = NULL; + spin_unlock_irqrestore(&st_gdata->lock, flags); + + pr_info("%s: done ", __func__); +} + +static void st_tty_receive(struct tty_struct *tty, const unsigned char *data, + char *tty_flags, int count) +{ + +#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"); +#endif + + /* + * if fw download is in progress then route incoming data + * to KIM for validation + */ + st_recv(tty->disc_data, data, count); + pr_info("done %s", __func__); +} + +/* wake-up function called in from the TTY layer + * inside the internal wakeup function will be called + */ +static void st_tty_wakeup(struct tty_struct *tty) +{ + struct st_data_s *st_gdata = tty->disc_data; + pr_info("%s ", __func__); + /* don't do an wakeup for now */ + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + /* call our internal wakeup */ + st_tx_wakeup((void *)st_gdata); +} + +static void st_tty_flush_buffer(struct tty_struct *tty) +{ + struct st_data_s *st_gdata = tty->disc_data; + pr_info("%s ", __func__); + + kfree_skb(st_gdata->tx_skb); + st_gdata->tx_skb = NULL; + + tty->ops->flush_buffer(tty); + return; +} + +/********************************************************************/ +int st_core_init(struct st_data_s **core_data) +{ + struct st_data_s *st_gdata; + long err; + static struct tty_ldisc_ops *st_ldisc_ops; + + /* populate and register to TTY line discipline */ + st_ldisc_ops = kzalloc(sizeof(*st_ldisc_ops), GFP_KERNEL); + if (!st_ldisc_ops) { + pr_err("no mem to allocate"); + return -ENOMEM; + } + + st_ldisc_ops->magic = TTY_LDISC_MAGIC; + st_ldisc_ops->name = "n_st"; /*"n_hci"; */ + st_ldisc_ops->open = st_tty_open; + st_ldisc_ops->close = st_tty_close; + st_ldisc_ops->receive_buf = st_tty_receive; + st_ldisc_ops->write_wakeup = st_tty_wakeup; + st_ldisc_ops->flush_buffer = st_tty_flush_buffer; + st_ldisc_ops->owner = THIS_MODULE; + + err = tty_register_ldisc(N_TI_WL, st_ldisc_ops); + if (err) { + pr_err("error registering %d line discipline %ld", + N_TI_WL, err); + kfree(st_ldisc_ops); + return err; + } + pr_info("registered n_shared line discipline"); + + st_gdata = kzalloc(sizeof(struct st_data_s), GFP_KERNEL); + if (!st_gdata) { + pr_err("memory allocation failed"); + err = tty_unregister_ldisc(N_TI_WL); + if (err) + pr_err("unable to un-register ldisc %ld", err); + kfree(st_ldisc_ops); + err = -ENOMEM; + return err; + } + + /* Initialize ST TxQ and Tx waitQ queue head. All BT/FM/GPS module skb's + * will be pushed in this queue for actual transmission. + */ + skb_queue_head_init(&st_gdata->txq); + skb_queue_head_init(&st_gdata->tx_waitq); + + /* Locking used in st_int_enqueue() to avoid multiple execution */ + spin_lock_init(&st_gdata->lock); + + /* ldisc_ops ref to be only used in __exit of module */ + st_gdata->ldisc_ops = st_ldisc_ops; + +#if 0 + err = st_kim_init(); + if (err) { + pr_err("error during kim initialization(%ld)", err); + kfree(st_gdata); + err = tty_unregister_ldisc(N_TI_WL); + if (err) + pr_err("unable to un-register ldisc"); + kfree(st_ldisc_ops); + return -1; + } +#endif + + err = st_ll_init(st_gdata); + if (err) { + pr_err("error during st_ll initialization(%ld)", err); + kfree(st_gdata); + err = tty_unregister_ldisc(N_TI_WL); + if (err) + pr_err("unable to un-register ldisc"); + kfree(st_ldisc_ops); + return -1; + } + *core_data = st_gdata; + return 0; +} + +void st_core_exit(struct st_data_s *st_gdata) +{ + long err; + /* internal module cleanup */ + err = st_ll_deinit(st_gdata); + if (err) + pr_err("error during deinit of ST LL %ld", err); +#if 0 + err = st_kim_deinit(); + if (err) + pr_err("error during deinit of ST KIM %ld", err); +#endif + if (st_gdata != NULL) { + /* Free ST Tx Qs and skbs */ + skb_queue_purge(&st_gdata->txq); + skb_queue_purge(&st_gdata->tx_waitq); + kfree_skb(st_gdata->rx_skb); + kfree_skb(st_gdata->tx_skb); + /* TTY ldisc cleanup */ + err = tty_unregister_ldisc(N_TI_WL); + if (err) + pr_err("unable to un-register ldisc %ld", err); + kfree(st_gdata->ldisc_ops); + /* free the global data pointer */ + kfree(st_gdata); + } +} + + diff --git a/drivers/staging/ti-st/st_core.h b/drivers/staging/ti-st/st_core.h new file mode 100644 index 000000000000..f271c88a8087 --- /dev/null +++ b/drivers/staging/ti-st/st_core.h @@ -0,0 +1,98 @@ +/* + * Shared Transport Core header file + * + * 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 ST_CORE_H +#define ST_CORE_H + +#include <linux/skbuff.h> +#include "st.h" + +/* states of protocol list */ +#define ST_NOTEMPTY 1 +#define ST_EMPTY 0 + +/* + * possible st_states + */ +#define ST_INITIALIZING 1 +#define ST_REG_IN_PROGRESS 2 +#define ST_REG_PENDING 3 +#define ST_WAITING_FOR_RESP 4 + +/* + * local data required for ST/KIM/ST-HCI-LL + */ +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 */ + unsigned char protos_registered; + unsigned long ll_state; /* ST LL power state */ +}; + +/* 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 + * via the write function ptr of protocol struct + */ +long st_write(struct sk_buff *); +/* 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); + +int st_core_init(struct st_data_s **); +void st_core_exit(struct st_data_s *); +void st_kim_ref(struct st_data_s **); + +#define GPS_STUB_TEST +#ifdef GPS_STUB_TEST +int gps_chrdrv_stub_write(const unsigned char*, int); +void gps_chrdrv_stub_init(void); +#endif + +#endif /*ST_CORE_H */ diff --git a/drivers/staging/ti-st/st_kim.c b/drivers/staging/ti-st/st_kim.c new file mode 100644 index 000000000000..98cbabba3844 --- /dev/null +++ b/drivers/staging/ti-st/st_kim.c @@ -0,0 +1,754 @@ +/* + * Shared Transport Line discipline driver Core + * Init Manager module responsible for GPIO control + * and firmware download + * 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 + * + */ + +#define pr_fmt(fmt) "(stk) :" fmt +#include <linux/platform_device.h> +#include <linux/jiffies.h> +#include <linux/firmware.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/gpio.h> + +#include <linux/sched.h> + +#include "st_kim.h" +/* understand BT events for fw response */ +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci_core.h> +#include <net/bluetooth/hci.h> + + +static int kim_probe(struct platform_device *pdev); +static int kim_remove(struct platform_device *pdev); + +/* KIM platform device driver structure */ +static struct platform_driver kim_platform_driver = { + .probe = kim_probe, + .remove = kim_remove, + /* TODO: ST driver power management during suspend/resume ? + */ +#if 0 + .suspend = kim_suspend, + .resume = kim_resume, +#endif + .driver = { + .name = "kim", + .owner = THIS_MODULE, + }, +}; + +#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 + */ +#define PROTO_ENTRY(type, name) name +const unsigned char *protocol_names[] = { + PROTO_ENTRY(ST_BT, "Bluetooth"), + PROTO_ENTRY(ST_FM, "FM"), + 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 + */ +void validate_firmware_response(struct sk_buff *skb) +{ + if (unlikely(skb->data[5] != 0)) { + pr_err("no proper response during fw download"); + pr_err("data6 %x", skb->data[5]); + return; /* keep waiting for the proper response */ + } + /* becos of all the script being downloaded */ + complete_all(&kim_gdata->kim_rcvd); + kfree_skb(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) +{ + register int room = skb_tailroom(kim_gdata->rx_skb); + + pr_info("len %d room %d", len, room); + + if (!len) { + validate_firmware_response(kim_gdata->rx_skb); + } else if (len > room) { + /* Received packet's payload length is larger. + * We can't accommodate it in created skb. + */ + pr_err("Data length is too large len %d room %d", len, + room); + kfree_skb(kim_gdata->rx_skb); + } else { + /* Packet header has non-zero payload length and + * we have enough space in created skb. Lets read + * payload data */ + kim_gdata->rx_state = ST_BT_W4_DATA; + kim_gdata->rx_count = len; + return len; + } + + /* Change ST LL state to continue to process next + * packet */ + kim_gdata->rx_state = ST_W4_PACKET_TYPE; + kim_gdata->rx_skb = NULL; + kim_gdata->rx_count = 0; + + 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 + */ +void kim_int_recv(const unsigned char *data, long count) +{ + register char *ptr; + struct hci_event_hdr *eh; + register int len = 0, type = 0; + + pr_info("%s", __func__); + /* Decode received bytes here */ + ptr = (char *)data; + if (unlikely(ptr == NULL)) { + pr_err(" received null from TTY "); + return; + } + while (count) { + if (kim_gdata->rx_count) { + len = min_t(unsigned int, kim_gdata->rx_count, count); + memcpy(skb_put(kim_gdata->rx_skb, len), ptr, len); + kim_gdata->rx_count -= len; + count -= len; + ptr += len; + + if (kim_gdata->rx_count) + continue; + + /* Check ST RX state machine , where are we? */ + 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); + kim_gdata->rx_state = ST_W4_PACKET_TYPE; + kim_gdata->rx_skb = NULL; + continue; + /* Waiting for Bluetooth event header ? */ + case ST_BT_W4_EVENT_HDR: + eh = (struct hci_event_hdr *)kim_gdata-> + rx_skb->data; + pr_info("Event header: evt 0x%2.2x" + "plen %d", eh->evt, eh->plen); + kim_check_data_len(eh->plen); + continue; + } /* end of switch */ + } /* end of if rx_state */ + switch (*ptr) { + /* Bluetooth event packet? */ + case HCI_EVENT_PKT: + pr_info("Event packet"); + kim_gdata->rx_state = ST_BT_W4_EVENT_HDR; + kim_gdata->rx_count = HCI_EVENT_HDR_SIZE; + type = HCI_EVENT_PKT; + break; + default: + pr_info("unknown packet"); + ptr++; + count--; + continue; + } /* end of switch *ptr */ + ptr++; + count--; + kim_gdata->rx_skb = + bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC); + if (!kim_gdata->rx_skb) { + pr_err("can't allocate mem for new packet"); + 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) +{ + unsigned short version = 0, chip = 0, min_ver = 0, maj_ver = 0; + char read_ver_cmd[] = { 0x01, 0x01, 0x10, 0x00 }; + + pr_info("%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; + } + + 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; + } + + version = + MAKEWORD(kim_gdata->resp_buffer[13], kim_gdata->resp_buffer[14]); + chip = (version & 0x7C00) >> 10; + min_ver = (version & 0x007F); + maj_ver = (version & 0x0380) >> 7; + + if (version & 0x8000) + maj_ver |= 0x0008; + + sprintf(bts_scr_name, "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver); + pr_info("%s", bts_scr_name); + return ST_SUCCESS; +} + +/* internal function which parses through the .bts firmware script file + * intreprets SEND, DELAY actions only as of now + */ +static long download_firmware(void) +{ + long err = ST_SUCCESS; + 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) { + pr_err("kim: failed to read local ver"); + return err; + } + err = + request_firmware(&kim_gdata->fw_entry, bts_scr_name, + &kim_gdata->kim_pdev->dev); + if (unlikely((err != 0) || (kim_gdata->fw_entry->data == NULL) || + (kim_gdata->fw_entry->size == 0))) { + pr_err(" request_firmware failed(errno %ld) for %s", err, + bts_scr_name); + return ST_ERR_FAILURE; + } + ptr = (void *)kim_gdata->fw_entry->data; + len = kim_gdata->fw_entry->size; + /* bts_header to remove out magic number and + * version + */ + ptr += sizeof(struct bts_header); + len -= sizeof(struct bts_header); + + while (len > 0 && ptr) { + pr_info(" action size %d, type %d ", + ((struct bts_action *)ptr)->size, + ((struct bts_action *)ptr)->type); + + switch (((struct bts_action *)ptr)->type) { + case ACTION_SEND_COMMAND: /* action send */ + action_ptr = &(((struct bts_action *)ptr)->data[0]); + if (unlikely + (((struct hci_command *)action_ptr)->opcode == + 0xFF36)) { + /* ignore remote change + * baud rate HCI VS command */ + pr_err + (" change remote baud\ + rate command in firmware"); + break; + } + + INIT_COMPLETION(kim_gdata->kim_rcvd); + err = st_int_write(kim_gdata->core_data, + ((struct bts_action_send *)action_ptr)->data, + ((struct bts_action *)ptr)->size); + if (unlikely(err < 0)) { + release_firmware(kim_gdata->fw_entry); + return ST_ERR_FAILURE; + } + if (!wait_for_completion_timeout + (&kim_gdata->kim_rcvd, + msecs_to_jiffies(CMD_RESP_TIME))) { + pr_err + (" response timeout during fw download "); + /* timed out */ + release_firmware(kim_gdata->fw_entry); + return ST_ERR_FAILURE; + } + break; + case ACTION_DELAY: /* sleep */ + pr_info("sleep command in scr"); + action_ptr = &(((struct bts_action *)ptr)->data[0]); + mdelay(((struct bts_action_delay *)action_ptr)->msec); + break; + } + len = + len - (sizeof(struct bts_action) + + ((struct bts_action *)ptr)->size); + ptr = + ptr + sizeof(struct bts_action) + + ((struct bts_action *)ptr)->size; + } + /* fw download complete */ + release_firmware(kim_gdata->fw_entry); + return ST_SUCCESS; +} + +/**********************************************************************/ +/* 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) +{ + pr_info(" %s ", __func__); + + if (kim_gdata->gpios[type] == -1) { + pr_info(" gpio not requested for protocol %s", + protocol_names[type]); + return; + } + switch (type) { + case ST_BT: + /*Do Nothing */ + break; + + case ST_FM: + if (state == KIM_GPIO_ACTIVE) + gpio_set_value(kim_gdata->gpios[ST_FM], GPIO_LOW); + else + gpio_set_value(kim_gdata->gpios[ST_FM], GPIO_HIGH); + break; + + case ST_GPS: + if (state == KIM_GPIO_ACTIVE) + gpio_set_value(kim_gdata->gpios[ST_GPS], GPIO_HIGH); + else + gpio_set_value(kim_gdata->gpios[ST_GPS], GPIO_LOW); + break; + + case ST_MAX: + default: + break; + } + + return; +} + +/* called from ST Core, when REG_IN_PROGRESS (registration in progress) + * can be because of + * 1. response to read local version + * 2. during send/recv's of firmware download + */ +void st_kim_recv(void *disc_data, const unsigned char *data, long count) +{ + pr_info(" %s ", __func__); + /* copy to local buffer */ + if (unlikely(data[4] == 0x01 && data[5] == 0x10 && data[0] == 0x04)) { + /* must be the read_ver_cmd */ + memcpy(kim_gdata->resp_buffer, data, count); + complete_all(&kim_gdata->kim_rcvd); + return; + } else { + kim_int_recv(data, count); + /* either completes or times out */ + } + return; +} + +/* to signal completion of line discipline installation + * called from ST Core, upon tty_open + */ +void st_kim_complete(void) +{ + complete(&kim_gdata->ldisc_installed); +} + +/* called from ST Core upon 1st registration +*/ +long st_kim_start(void) +{ + long err = ST_SUCCESS; + long retry = POR_RETRY_COUNT; + 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 */ + gpio_set_value(kim_gdata->gpios[ST_BT], GPIO_HIGH); + mdelay(100); + /* re-initialize the completion */ + INIT_COMPLETION(kim_gdata->ldisc_installed); +#ifndef LEGACY_RFKILL_SUPPORT + /* 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; + continue; + } +#else + /* 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; + continue; + } else { + /* ldisc installed now */ + pr_info(" line discipline installed "); + err = download_firmware(); + if (err != ST_SUCCESS) { + pr_err("download firmware failed"); + continue; + } else { /* on success don't retry */ + break; + } + } + } while (retry--); + return err; +} + +/* called from ST Core, on the last un-registration +*/ +long st_kim_stop(void) +{ + long err = ST_SUCCESS; + + INIT_COMPLETION(kim_gdata->ldisc_installed); +#ifndef LEGACY_RFKILL_SUPPORT + /* 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; + } +#else + /* 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; + } + + /* By default configure BT nShutdown to LOW state */ + gpio_set_value(kim_gdata->gpios[ST_BT], GPIO_LOW); + mdelay(1); + gpio_set_value(kim_gdata->gpios[ST_BT], GPIO_HIGH); + mdelay(1); + gpio_set_value(kim_gdata->gpios[ST_BT], GPIO_LOW); + return err; +} + +/**********************************************************************/ +/* functions called from subsystems */ + +#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) +{ + sprintf(buf, "%ld", kim_gdata->uim_pid); + return strlen(buf); +} + +/* called when sysfs entry is read from */ +static ssize_t show_list(struct device *dev, struct device_attribute + *attr, char *buf) +{ + kim_st_list_protocols(kim_gdata->core_data, buf); + return strlen(buf); +} + +#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 + */ +static int kim_toggle_radio(void *data, bool blocked) +{ + enum proto_type type = *((enum proto_type *)data); + pr_info(" %s: %d ", __func__, type); + + switch (type) { + case ST_BT: + /* do nothing */ + break; + case ST_FM: + case ST_GPS: + if (blocked) + st_kim_chip_toggle(type, KIM_GPIO_INACTIVE); + else + st_kim_chip_toggle(type, KIM_GPIO_ACTIVE); + break; + case ST_MAX: + pr_err(" wrong proto type "); + break; + } + return ST_SUCCESS; +} + +#endif /* LEGACY_RFKILL_SUPPORT */ + +void st_kim_ref(struct st_data_s **core_data) +{ + *core_data = kim_gdata->core_data; +} + +/**********************************************************************/ +/* functions called from platform device driver subsystem + * need to have a relevant platform device entry in the platform's + * board-*.c file + */ + +static int kim_probe(struct platform_device *pdev) +{ + long status; + long proto; + long *gpios = pdev->dev.platform_data; + + status = st_core_init(&kim_gdata->core_data); + if (status != 0) { + pr_err(" ST core init failed"); + return ST_ERR_FAILURE; + } + + for (proto = 0; proto < ST_MAX; proto++) { + kim_gdata->gpios[proto] = gpios[proto]; + pr_info(" %ld gpio to be requested", gpios[proto]); + } + + for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) { + /* Claim the Bluetooth/FM/GPIO + * nShutdown gpio from the system + */ + status = gpio_request(gpios[proto], "kim"); + if (unlikely(status)) { + pr_err(" gpio %ld request failed ", gpios[proto]); + proto -= 1; + while (proto >= 0) { + if (gpios[proto] != -1) + gpio_free(gpios[proto]); + } + return status; + } + + /* Configure nShutdown GPIO as output=0 */ + status = + gpio_direction_output(gpios[proto], 0); + if (unlikely(status)) { + pr_err(" unable to configure gpio %ld", + gpios[proto]); + proto -= 1; + while (proto >= 0) { + if (gpios[proto] != -1) + gpio_free(gpios[proto]); + } + 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; + kim_gdata->rfkill[proto] = rfkill_alloc(protocol_names[proto], + &pdev->dev, RFKILL_TYPE_BLUETOOTH, + &kim_rfkill_ops, &kim_gdata->rf_protos[proto]); + if (kim_gdata->rfkill[proto] == NULL) { + pr_err("cannot create rfkill entry for gpio %ld", + gpios[proto]); + continue; + } + /* block upon creation */ + rfkill_init_sw_state(kim_gdata->rfkill[proto], 1); + status = rfkill_register(kim_gdata->rfkill[proto]); + if (unlikely(status)) { + pr_err("rfkill registration failed for gpio %ld", + gpios[proto]); + rfkill_unregister(kim_gdata->rfkill[proto]); + continue; + } + pr_info("rfkill entry created for %ld", gpios[proto]); + } +#endif + return ST_SUCCESS; +} + +static int kim_remove(struct platform_device *pdev) +{ + /* free the GPIOs requested + */ + long *gpios = pdev->dev.platform_data; + long proto; + + 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 + kim_gdata->kim_pdev = NULL; + st_core_exit(kim_gdata->core_data); + return ST_SUCCESS; +} + +/**********************************************************************/ +/* entry point for ST KIM module, called in from ST Core */ + +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; + } + + ret = platform_driver_register(&kim_platform_driver); + if (ret != 0) { + pr_err("platform drv registration failed"); + return ST_ERR_FAILURE; + } + return ST_SUCCESS; +} + +static void __exit st_kim_deinit(void) +{ + /* the following returns void */ + platform_driver_unregister(&kim_platform_driver); + kfree(kim_gdata); + kim_gdata = NULL; +} + + +module_init(st_kim_init); +module_exit(st_kim_deinit); +MODULE_AUTHOR("Pavan Savoy <pavan_savoy@ti.com>"); +MODULE_DESCRIPTION("Shared Transport Driver for TI BT/FM/GPS combo chips "); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/ti-st/st_kim.h b/drivers/staging/ti-st/st_kim.h new file mode 100644 index 000000000000..ff3270ec7847 --- /dev/null +++ b/drivers/staging/ti-st/st_kim.h @@ -0,0 +1,150 @@ +/* + * Shared Transport Line discipline driver Core + * Init Manager Module header file + * 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 ST_KIM_H +#define ST_KIM_H + +#include <linux/types.h> +#include "st.h" +#include "st_core.h" +#include "st_ll.h" +#include <linux/rfkill.h> + +/* time in msec to wait for + * line discipline to be installed + */ +#define LDISC_TIME 500 +#define CMD_RESP_TIME 500 +#define MAKEWORD(a, b) ((unsigned short)(((unsigned char)(a)) \ + | ((unsigned short)((unsigned char)(b))) << 8)) + +#define GPIO_HIGH 1 +#define GPIO_LOW 0 + +/* the Power-On-Reset logic, requires to attempt + * to download firmware onto chip more than once + * 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 + */ +#define LEGACY_RFKILL_SUPPORT +/* + * header file for ST provided by KIM + */ +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; +}; + +long st_kim_start(void); +long st_kim_stop(void); +/* + * called from st_tty_receive to authenticate fw_download + */ +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 *); + +/* + * BTS headers + */ +#define ACTION_SEND_COMMAND 1 +#define ACTION_WAIT_EVENT 2 +#define ACTION_SERIAL 3 +#define ACTION_DELAY 4 +#define ACTION_RUN_SCRIPT 5 +#define ACTION_REMARKS 6 + +/* + * * BRF Firmware header + * */ +struct bts_header { + uint32_t magic; + uint32_t version; + uint8_t future[24]; + uint8_t actions[0]; +} __attribute__ ((packed)); + +/* + * * BRF Actions structure + * */ +struct bts_action { + uint16_t type; + uint16_t size; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct bts_action_send { + uint8_t data[0]; +} __attribute__ ((packed)); + +struct bts_action_wait { + uint32_t msec; + uint32_t size; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct bts_action_delay { + uint32_t msec; +} __attribute__ ((packed)); + +struct bts_action_serial { + uint32_t baud; + uint32_t flow_control; +} __attribute__ ((packed)); + +/* for identifying the change speed HCI VS + * command + */ +struct hci_command { + uint8_t prefix; + uint16_t opcode; + uint8_t plen; + uint32_t speed; +} __attribute__ ((packed)); + + +#endif /* ST_KIM_H */ diff --git a/drivers/staging/ti-st/st_ll.c b/drivers/staging/ti-st/st_ll.c new file mode 100644 index 000000000000..0685a100db6a --- /dev/null +++ b/drivers/staging/ti-st/st_ll.c @@ -0,0 +1,147 @@ +/* + * Shared Transport driver + * HCI-LL module responsible for TI proprietary HCI_LL protocol + * 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 + * + */ + +#define pr_fmt(fmt) "(stll) :" fmt +#include "st_ll.h" + +/**********************************************************************/ +/* internal functions */ +static void send_ll_cmd(struct st_data_s *st_data, + unsigned char cmd) +{ + + pr_info("%s: writing %x", __func__, cmd); + st_int_write(st_data, &cmd, 1); + return; +} + +static void ll_device_want_to_sleep(struct st_data_s *st_data) +{ + pr_info("%s", __func__); + /* sanity check */ + if (st_data->ll_state != ST_LL_AWAKE) + pr_err("ERR hcill: ST_LL_GO_TO_SLEEP_IND" + "in state %ld", st_data->ll_state); + + send_ll_cmd(st_data, LL_SLEEP_ACK); + /* update state */ + st_data->ll_state = ST_LL_ASLEEP; +} + +static void ll_device_want_to_wakeup(struct st_data_s *st_data) +{ + /* diff actions in diff states */ + switch (st_data->ll_state) { + case ST_LL_ASLEEP: + send_ll_cmd(st_data, LL_WAKE_UP_ACK); /* send wake_ack */ + break; + case ST_LL_ASLEEP_TO_AWAKE: + /* duplicate wake_ind */ + pr_err("duplicate wake_ind while waiting for Wake ack"); + break; + case ST_LL_AWAKE: + /* duplicate wake_ind */ + pr_err("duplicate wake_ind already AWAKE"); + break; + case ST_LL_AWAKE_TO_ASLEEP: + /* duplicate wake_ind */ + pr_err("duplicate wake_ind"); + break; + } + /* update state */ + st_data->ll_state = ST_LL_AWAKE; +} + +/**********************************************************************/ +/* functions invoked by ST Core */ + +/* called when ST Core wants to + * enable ST LL */ +void st_ll_enable(struct st_data_s *ll) +{ + ll->ll_state = ST_LL_AWAKE; +} + +/* called when ST Core /local module wants to + * disable ST LL */ +void st_ll_disable(struct st_data_s *ll) +{ + ll->ll_state = ST_LL_INVALID; +} + +/* called when ST Core wants to update the state */ +void st_ll_wakeup(struct st_data_s *ll) +{ + if (likely(ll->ll_state != ST_LL_AWAKE)) { + send_ll_cmd(ll, LL_WAKE_UP_IND); /* WAKE_IND */ + ll->ll_state = ST_LL_ASLEEP_TO_AWAKE; + } else { + /* don't send the duplicate wake_indication */ + pr_err(" Chip already AWAKE "); + } +} + +/* 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); + return ll->ll_state; +} + +/* called from ST Core, when a PM related packet arrives */ +unsigned long st_ll_sleep_state(struct st_data_s *st_data, + unsigned char cmd) +{ + switch (cmd) { + case LL_SLEEP_IND: /* sleep ind */ + pr_info("sleep indication recvd"); + ll_device_want_to_sleep(st_data); + break; + case LL_SLEEP_ACK: /* sleep ack */ + pr_err("sleep ack rcvd: host shouldn't"); + break; + case LL_WAKE_UP_IND: /* wake ind */ + pr_info("wake indication recvd"); + ll_device_want_to_wakeup(st_data); + break; + case LL_WAKE_UP_ACK: /* wake ack */ + pr_info("wake ack rcvd"); + st_data->ll_state = ST_LL_AWAKE; + break; + default: + pr_err(" unknown input/state "); + return ST_ERR_FAILURE; + } + return ST_SUCCESS; +} + +/* Called from ST CORE to initialize ST LL */ +long st_ll_init(struct st_data_s *ll) +{ + /* set state to invalid */ + ll->ll_state = ST_LL_INVALID; + return 0; +} + +/* Called from ST CORE to de-initialize ST LL */ +long st_ll_deinit(struct st_data_s *ll) +{ + return 0; +} diff --git a/drivers/staging/ti-st/st_ll.h b/drivers/staging/ti-st/st_ll.h new file mode 100644 index 000000000000..77dfbf07e7b8 --- /dev/null +++ b/drivers/staging/ti-st/st_ll.h @@ -0,0 +1,62 @@ +/* + * Shared Transport Low Level (ST LL) + * + * 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 ST_LL_H +#define ST_LL_H + +#include <linux/skbuff.h> +#include "st.h" +#include "st_core.h" + +/* ST LL receiver states */ +#define ST_W4_PACKET_TYPE 0 +#define ST_BT_W4_EVENT_HDR 1 +#define ST_BT_W4_ACL_HDR 2 +#define ST_BT_W4_SCO_HDR 3 +#define ST_BT_W4_DATA 4 +#define ST_FM_W4_EVENT_HDR 5 +#define ST_GPS_W4_EVENT_HDR 6 + +/* ST LL state machines */ +#define ST_LL_ASLEEP 0 +#define ST_LL_ASLEEP_TO_AWAKE 1 +#define ST_LL_AWAKE 2 +#define ST_LL_AWAKE_TO_ASLEEP 3 +#define ST_LL_INVALID 4 + +#define LL_SLEEP_IND 0x30 +#define LL_SLEEP_ACK 0x31 +#define LL_WAKE_UP_IND 0x32 +#define LL_WAKE_UP_ACK 0x33 + +/* initialize and de-init ST LL */ +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 + * called by ST Core + */ +void st_ll_enable(struct st_data_s *); +void st_ll_disable(struct st_data_s *); + +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 new file mode 100644 index 000000000000..10311afcbd45 --- /dev/null +++ b/drivers/staging/ti-st/sysfs-uim @@ -0,0 +1,16 @@ +What: /sys/class/rfkill/rfkill%d/ +Date: March 22 +Contact: Pavan Savoy <pavan_savoy@ti.com> +Description: + Creates the rfkill entries for Radio apps like + BT app, FM app or GPS app to toggle corresponding + cores of the chip + +What: /dev/rfkill +Date: March 22 +Contact: Pavan Savoy <pavan_savoy@ti.com> +Description: + A daemon which maintains the ldisc installation and + uninstallation would be ppolling on this device and listening + on events which would suggest either to install or un-install + line discipline |