Return-Path: MIME-Version: 1.0 Date: Fri, 22 Oct 2010 12:35:16 +0200 Message-ID: Subject: [PATCH 1/9] mfd: Add support for the ST-Ericsson CG2900. From: Par-Gunnar Hjalmdahl To: linus.walleij@stericsson.com, linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org Content-Type: text/plain; charset=ISO-8859-1 Sender: linux-bluetooth-owner@vger.kernel.org List-ID: This patch adds support for the ST-Ericsson CG2900 Connectivity Combo controller. This patch adds the central framework to be able to register CG2900 users, transports, and chip handlers. Signed-off-by: Par-Gunnar Hjalmdahl --- drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 3 + drivers/mfd/cg2900/Makefile | 7 + drivers/mfd/cg2900/cg2900_core.c | 2401 +++++++++++++++++++++++++++++++++++++ drivers/mfd/cg2900/cg2900_core.h | 303 +++++ drivers/mfd/cg2900/cg2900_debug.h | 76 ++ drivers/mfd/cg2900/hci_defines.h | 81 ++ include/linux/mfd/cg2900.h | 187 +++ 8 files changed, 3066 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/cg2900/Makefile create mode 100644 drivers/mfd/cg2900/cg2900_core.c create mode 100644 drivers/mfd/cg2900/cg2900_core.h create mode 100644 drivers/mfd/cg2900/cg2900_debug.h create mode 100644 drivers/mfd/cg2900/hci_defines.h create mode 100644 include/linux/mfd/cg2900.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6c6b9f0..3ee9c66 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -262,6 +262,14 @@ config MFD_TC6393XB help Support for Toshiba Mobile IO Controller TC6393XB +config MFD_CG2900 + tristate "Support ST-Ericsson CG2900 main structure" + depends on NET + help + Support for ST-Ericsson CG2900 Connectivity Combo controller main structure. + Supports multiple functionalities muxed over a Bluetooth HCI H:4 interface. + CG2900 support Bluetooth, FM radio, and GPS. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 70b2699..acf1fcb 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -79,3 +79,6 @@ obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o obj-$(CONFIG_MFD_VX855) += vx855.o + +obj-y += cg2900/ + diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile new file mode 100644 index 0000000..a736101 --- /dev/null +++ b/drivers/mfd/cg2900/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for ST-Ericsson CG2900 connectivity combo controller +# + +obj-$(CONFIG_MFD_CG2900) += cg2900_core.o +export-objs := cg2900_core.o + diff --git a/drivers/mfd/cg2900/cg2900_core.c b/drivers/mfd/cg2900/cg2900_core.c new file mode 100644 index 0000000..a5951cb --- /dev/null +++ b/drivers/mfd/cg2900/cg2900_core.c @@ -0,0 +1,2401 @@ +/* + * drivers/mfd/cg2900/cg2900_core.c + * + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cg2900_core.h" +#include "cg2900_debug.h" +#include "hci_defines.h" + +/* Device names */ +#define CG2900_CDEV_NAME "cg2900_core_test" +#define CG2900_CLASS_NAME "cg2900_class" +#define CG2900_DEVICE_NAME "cg2900_driver" +#define CORE_WQ_NAME "cg2900_core_wq" + +#define SET_MAIN_STATE(__core_new_state) \ + CG2900_SET_STATE("main_state", core_info->main_state, \ + __core_new_state) +#define SET_BOOT_STATE(__core_new_state) \ + CG2900_SET_STATE("boot_state", core_info->boot_state, __core_new_state) +#define SET_TRANSPORT_STATE(__core_new_state) \ + CG2900_SET_STATE("transport_state", core_info->transport_state, \ + __core_new_state) + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define RX_SKB_RESERVE 8 + +/* + * Timeout values + */ +#define CHIP_STARTUP_TIMEOUT (15000) /* ms */ +#define CHIP_SHUTDOWN_TIMEOUT (15000) /* ms */ +#define LINE_TOGGLE_DETECT_TIMEOUT (50) /* ms */ +#define CHIP_READY_TIMEOUT (100) /* ms */ +#define REVISION_READOUT_TIMEOUT (500) /* ms */ + +/* + * We can have up to 32 char devs with current bit mask and we also have + * the parent device here in the transport so that is 33 devices in total. + */ +#define MAX_NBR_OF_DEVS 33 + +/* Default H4 channels which may change depending on connected controller */ +#define HCI_FM_RADIO_H4_CHANNEL 0x08 +#define HCI_GNSS_H4_CHANNEL 0x09 + +/* + * Internal type definitions + */ + +/** + * enum main_state - Main-state for CG2900 Core. + * @CORE_INITIALIZING: CG2900 Core initializing. + * @CORE_IDLE: No user registered to CG2900 Core. + * @CORE_BOOTING: CG2900 Core booting after first user is registered. + * @CORE_CLOSING: CG2900 Core closing after last user has deregistered. + * @CORE_RESETING: CG2900 Core reset requested. + * @CORE_ACTIVE: CG2900 Core up and running with at least one user. + */ +enum main_state { + CORE_INITIALIZING, + CORE_IDLE, + CORE_BOOTING, + CORE_CLOSING, + CORE_RESETING, + CORE_ACTIVE +}; + +/** + * enum boot_state - BOOT-state for CG2900 Core. + * @BOOT_NOT_STARTED: Boot has not yet started. + * @BOOT_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation + * command has been sent. + * @BOOT_READY: CG2900 Core boot is ready. + * @BOOT_FAILED: CG2900 Core boot failed. + */ +enum boot_state { + BOOT_NOT_STARTED, + BOOT_READ_LOCAL_VERSION_INFORMATION, + BOOT_READY, + BOOT_FAILED +}; + +/** + * enum transport_state - State for the CG2900 transport. + * @TRANS_INITIALIZING: Transport initializing. + * @TRANS_OPENED: Transport is opened (data can be sent). + * @TRANS_CLOSED: Transport is closed (data cannot be sent). + */ +enum transport_state { + TRANS_INITIALIZING, + TRANS_OPENED, + TRANS_CLOSED +}; + +/** + * struct cg2900_users - Stores all current users of CG2900 Core. + * @bt_cmd: BT command channel user. + * @bt_acl: BT ACL channel user. + * @bt_evt: BT event channel user. + * @fm_radio: FM radio channel user. + * @gnss GNSS: GNSS channel user. + * @debug Debug: Internal debug channel user. + * @ste_tools: ST-E tools channel user. + * @hci_logger: HCI logger channel user. + * @us_ctrl: User space control channel user. + * @bt_audio: BT audio command channel user. + * @fm_radio_audio: FM audio command channel user. + * @core: Core command channel user. + * @nbr_of_users: Number of users currently registered (not including + * the HCI logger). + */ +struct cg2900_users { + struct cg2900_device *bt_cmd; + struct cg2900_device *bt_acl; + struct cg2900_device *bt_evt; + struct cg2900_device *fm_radio; + struct cg2900_device *gnss; + struct cg2900_device *debug; + struct cg2900_device *ste_tools; + struct cg2900_device *hci_logger; + struct cg2900_device *us_ctrl; + struct cg2900_device *bt_audio; + struct cg2900_device *fm_radio_audio; + struct cg2900_device *core; + unsigned int nbr_of_users; +}; + +/** + * struct local_chip_info - Stores local controller info. + * @version_set: true if version data is valid. + * @hci_version: HCI version of local controller. + * @hci_revision: HCI revision of local controller. + * @lmp_pal_version: LMP/PAL version of local controller. + * @manufacturer: Manufacturer of local controller. + * @lmp_pal_subversion: LMP/PAL sub-version of local controller. + * + * According to Bluetooth HCI Read Local Version Information command. + */ +struct local_chip_info { + bool version_set; + u8 hci_version; + u16 hci_revision; + u8 lmp_pal_version; + u16 manufacturer; + u16 lmp_pal_subversion; +}; + +/** + * struct chip_handler_item - Structure to store chip handler cb. + * @list: list_head struct. + * @cb: Chip handler callback struct. + */ +struct chip_handler_item { + struct list_head list; + struct cg2900_id_callbacks cb; +}; + +/** + * struct cg2900_work_struct - Work structure for CG2900 Core module. + * @work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct cg2900_work_struct{ + struct work_struct work; + void *data; +}; + +/** + * struct test_char_dev_info - Stores device information. + * @test_miscdev: Registered Misc Device. + * @rx_queue: RX data queue. + */ +struct test_char_dev_info { + struct miscdevice test_miscdev; + struct sk_buff_head rx_queue; +}; + +/** + * struct trans_info - Stores transport information. + * @dev: Transport device. + * @cb: Transport cb. + */ +struct trans_info { + struct cg2900_trans_dev dev; + struct cg2900_trans_callbacks cb; +}; + +/** + * struct core_info - Main info structure for CG2900 Core. + * @users: Stores all users of CG2900 Core. + * @local_chip_info: Stores information of local controller. + * @main_state: Current Main-state of CG2900 Core. + * @boot_state: Current BOOT-state of CG2900 Core. + * @transport_state: Current TRANSPORT-state of CG2900 Core. + * @wq: CG2900 Core workqueue. + * @hci_logger_config: Stores HCI logger configuration. + * @chip_dev: Device structure for chip driver. + * @h4_channels: HCI H:4 channel used by this device. + * @test_char_dev: Stores information of test char dev. + * @trans_info: Stores information about current transport. + * @dev: Device structure for STE Connectivity driver. + */ +struct core_info { + struct cg2900_users users; + struct local_chip_info local_chip_info; + enum main_state main_state; + enum boot_state boot_state; + enum transport_state transport_state; + struct workqueue_struct *wq; + struct cg2900_hci_logger_config hci_logger_config; + struct cg2900_chip_dev chip_dev; + struct cg2900_h4_channels h4_channels; + struct test_char_dev_info *test_char_dev; + struct trans_info *trans_info; + struct device *dev; +}; + +/* core_info - Main information object for CG2900 Core. */ +static struct core_info *core_info; + +/* Module parameters */ +int cg2900_debug_level = CG2900_DEFAULT_DEBUG_LEVEL; +EXPORT_SYMBOL(cg2900_debug_level); + +u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00}; +EXPORT_SYMBOL(bd_address); +int bd_addr_count = BT_BDADDR_SIZE; + +/* Setting default values to ST-E CG2900 */ +int default_manufacturer = 0x30; +EXPORT_SYMBOL(default_manufacturer); +int default_hci_revision = 0x0700; +EXPORT_SYMBOL(default_hci_revision); +int default_sub_version = 0x0011; +EXPORT_SYMBOL(default_sub_version); + +static int sleep_timeout_ms = 100; + +/* + * chip_handlers - List of the register handlers for different chips. + */ +LIST_HEAD(chip_handlers); + +/* + * main_wait_queue - Main Wait Queue in CG2900 Core. + */ +static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue); + +/* + * main_wait_queue - Char device Wait Queue in CG2900 Core. + */ +static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue); + +/** + * free_user_dev - Frees user device and also sets it to NULL to inform caller. + * @dev: Pointer to user device. + */ +static void free_user_dev(struct cg2900_device **dev) +{ + if (*dev) { + kfree((*dev)->cb); + kfree(*dev); + *dev = NULL; + } +} + +/** + * handle_reset_of_user - Calls the reset callback and frees the device. + * @dev: Pointer to CG2900 device. + */ +static void handle_reset_of_user(struct cg2900_device **dev) +{ + if (*dev) { + if ((*dev)->cb->reset_cb) + (*dev)->cb->reset_cb((*dev)); + free_user_dev(dev); + } +} + +/** + * transmit_skb_to_chip() - Transmit buffer to the transport. + * @skb: Data packet. + * @use_logger: True if HCI logger shall be used, false otherwise. + * + * The transmit_skb_to_chip() function transmit buffer to the transport. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void transmit_skb_to_chip(struct sk_buff *skb, bool use_logger) +{ + int err; + struct sk_buff *skb_log; + struct trans_info *trans_info = core_info->trans_info; + struct cg2900_device *logger; + + CG2900_DBG_DATA("transmit_skb_to_chip %d bytes. First byte 0x%02X", + skb->len, *(skb->data)); + + if (TRANS_CLOSED == core_info->transport_state) { + CG2900_ERR("Trying to write on a closed channel"); + kfree_skb(skb); + return; + } + + /* + * If HCI logging is enabled for this channel, copy the data to + * the HCI logging output. + */ + logger = core_info->users.hci_logger; + if (!use_logger || !logger) + goto transmit; + + /* + * Alloc a new sk_buff and copy the data into it. Then send it to + * the HCI logger. + */ + skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC); + if (!skb_log) { + CG2900_ERR("Couldn't allocate skb_log"); + goto transmit; + } + + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + skb_log->data[0] = (u8) LOGGER_DIRECTION_TX; + + if (logger->cb->read_cb) + logger->cb->read_cb(logger, skb_log); + +transmit: + if (trans_info && trans_info->cb.write) { + err = trans_info->cb.write(&trans_info->dev, skb); + if (err) + CG2900_ERR("Transport write failed (%d)", err); + } else { + CG2900_ERR("No way to write to chip"); + err = -EPERM; + } + + if (err) + kfree_skb(skb); +} + +/** + * create_and_send_bt_cmd() - Copy and send sk_buffer. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The create_and_send_bt_cmd() function allocates sk_buffer, copy supplied + * data to it, and send the sk_buffer to the transport. + */ +static void create_and_send_bt_cmd(void *data, int length) +{ + struct sk_buff *skb; + + skb = cg2900_alloc_skb(length, GFP_ATOMIC); + if (!skb) { + CG2900_ERR("Couldn't allocate sk_buff with length %d", + length); + return; + } + + memcpy(skb_put(skb, length), data, length); + skb_push(skb, CG2900_SKB_RESERVE); + skb->data[0] = HCI_BT_CMD_H4_CHANNEL; + + transmit_skb_to_chip(skb, core_info->hci_logger_config.bt_cmd_enable); +} + +/** + * chip_not_detected() - Called when it is not possible to detect the chip. + * + * This function sets chip information to default values if it is not possible + * to read out information from the chip. This is common when running module + * tests. + */ +static void chip_not_detected(void) +{ + struct list_head *cursor; + struct chip_handler_item *tmp; + + CG2900_ERR("Could not read out revision from the chip. This is " + "typical when running stubbed CG2900.\n" + "Switching to default value:\n" + "\tman 0x%04X\n" + "\trev 0x%04X\n" + "\tsub 0x%04X", + default_manufacturer, + default_hci_revision, + default_sub_version); + + core_info->chip_dev.chip.manufacturer = default_manufacturer; + core_info->chip_dev.chip.hci_revision = default_hci_revision; + core_info->chip_dev.chip.hci_sub_version = default_sub_version; + + memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb)); + + /* Find the handler for our default chip */ + list_for_each(cursor, &chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + if (tmp->cb.check_chip_support(&(core_info->chip_dev))) { + CG2900_INFO("Chip handler found"); + SET_BOOT_STATE(BOOT_READY); + break; + } + } +} + +/** + * enable_hci_logger() - Enable HCI logger for each device. + * @skb: Received sk buffer. + * + * The enable_hci_logger() change HCI logger configuration for all registered + * devices. + * + * Returns: + * 0 if there is no error. + * -EACCES if bad structure was supplied. + */ +static int enable_hci_logger(struct sk_buff *skb) +{ + struct cg2900_users *users; + struct cg2900_hci_logger_config *config; + + if (skb->len != sizeof(*config)) { + CG2900_ERR("Trying to configure HCI logger with bad structure"); + return -EACCES; + } + + users = &(core_info->users); + config = &(core_info->hci_logger_config); + + /* First store the logger config */ + memcpy(config, skb->data, sizeof(*config)); + + /* Then go through all devices and set the right settings */ + if (users->bt_cmd) + users->bt_cmd->logger_enabled = config->bt_cmd_enable; + if (users->bt_audio) + users->bt_audio->logger_enabled = config->bt_audio_enable; + if (users->bt_acl) + users->bt_acl->logger_enabled = config->bt_acl_enable; + if (users->bt_evt) + users->bt_evt->logger_enabled = config->bt_evt_enable; + if (users->fm_radio) + users->fm_radio->logger_enabled = config->fm_radio_enable; + if (users->fm_radio_audio) + users->fm_radio_audio->logger_enabled = + config->fm_radio_audio_enable; + if (users->gnss) + users->gnss->logger_enabled = config->gnss_enable; + + kfree_skb(skb); + return 0; +} + +/** + * find_bt_audio_user() - Check if data packet is an audio related packet. + * @h4_channel: H4 channel. + * @dev: Stored CG2900 device. + * @skb: skb with received packet. + * Returns: + * 0 - if no error occurred. + * -ENXIO - if cg2900_device not found. + */ +static int find_bt_audio_user(int h4_channel, struct cg2900_device **dev, + const struct sk_buff * const skb) +{ + if (core_info->chip_dev.cb.is_bt_audio_user && + core_info->chip_dev.cb.is_bt_audio_user(h4_channel, skb)) { + *dev = core_info->users.bt_audio; + if (!(*dev)) { + CG2900_ERR("H:4 channel not registered in core_info: " + "0x%X", h4_channel); + return -ENXIO; + } + } + return 0; +} + +/** + * find_fm_audio_user() - Check if data packet is an audio related packet. + * @h4_channel: H4 channel. + * @dev: Stored CG2900 device. + * @skb: skb with received packet. + * Returns: + * 0 if no error occurred. + * -ENXIO if cg2900_device not found. + */ +static int find_fm_audio_user(int h4_channel, struct cg2900_device **dev, + const struct sk_buff * const skb) +{ + if (core_info->chip_dev.cb.is_fm_audio_user && + core_info->chip_dev.cb.is_fm_audio_user(h4_channel, skb)) { + *dev = core_info->users.fm_radio_audio; + if (!(*dev)) { + CG2900_ERR("H:4 channel not registered in core_info: " + "0x%X", h4_channel); + return -ENXIO; + } + } + return 0; +} + +/** + * find_h4_user() - Get H4 user based on supplied H4 channel. + * @h4_channel: H4 channel. + * @dev: Stored CG2900 device. + * @skb: (optional) skb with received packet. Set to NULL if NA. + * + * Returns: + * 0 if there is no error. + * -EINVAL if bad channel is supplied or no user was found. + * -ENXIO if channel is audio channel but not registered with CG2900. + */ +static int find_h4_user(int h4_channel, struct cg2900_device **dev, + const struct sk_buff * const skb) +{ + int err = 0; + struct cg2900_users *users = &(core_info->users); + struct cg2900_h4_channels *chan = &(core_info->h4_channels); + + if (h4_channel == chan->bt_cmd_channel) { + *dev = users->bt_cmd; + } else if (h4_channel == chan->bt_acl_channel) { + *dev = users->bt_acl; + } else if (h4_channel == chan->bt_evt_channel) { + *dev = users->bt_evt; + /* Check if it's event generated by previously sent audio user + * command. If so then that event should be dispatched to audio + * user*/ + err = find_bt_audio_user(h4_channel, dev, skb); + } else if (h4_channel == chan->gnss_channel) { + *dev = users->gnss; + } else if (h4_channel == chan->fm_radio_channel) { + *dev = users->fm_radio; + /* Check if it's an event generated by previously sent audio + * user command. If so then that event should be dispatched to + * audio user */ + err = find_fm_audio_user(h4_channel, dev, skb); + } else if (h4_channel == chan->debug_channel) { + *dev = users->debug; + } else if (h4_channel == chan->ste_tools_channel) { + *dev = users->ste_tools; + } else if (h4_channel == chan->hci_logger_channel) { + *dev = users->hci_logger; + } else if (h4_channel == chan->us_ctrl_channel) { + *dev = users->us_ctrl; + } else if (h4_channel == chan->core_channel) { + *dev = users->core; + } else { + *dev = NULL; + CG2900_ERR("Bad H:4 channel supplied: 0x%X", h4_channel); + return -EINVAL; + } + + return err; +} + +/** + * add_h4_user() - Add H4 user to user storage based on supplied H4 channel. + * @dev: Stored CG2900 device. + * @name: Device name to identify different devices that are using + * the same H4 channel. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer or bad channel is supplied. + * -EBUSY if there already is a user for supplied channel. + */ +static int add_h4_user(struct cg2900_device *dev, const char * const name) +{ + int err = 0; + struct cg2900_users *users = &(core_info->users); + struct cg2900_hci_logger_config *config = + &(core_info->hci_logger_config); + struct cg2900_h4_channels *chan = &(core_info->h4_channels); + + if (!dev) { + CG2900_ERR("NULL device supplied"); + return -EINVAL; + } + + if (dev->h4_channel == chan->bt_cmd_channel) { + if (!users->bt_cmd && + 0 == strncmp(name, CG2900_BT_CMD, CG2900_MAX_NAME_SIZE)) { + users->bt_cmd = dev; + users->bt_cmd->logger_enabled = config->bt_cmd_enable; + (users->nbr_of_users)++; + } else if (!users->bt_audio && + 0 == strncmp(name, CG2900_BT_AUDIO, + CG2900_MAX_NAME_SIZE)) { + users->bt_audio = dev; + users->bt_audio->logger_enabled = + config->bt_audio_enable; + (users->nbr_of_users)++; + } else { + err = -EBUSY; + CG2900_ERR("name %s bt_cmd 0x%X bt_audio 0x%X", + name, (int)users->bt_cmd, + (int)users->bt_audio); + } + } else if (dev->h4_channel == chan->bt_acl_channel) { + if (!users->bt_acl) { + users->bt_acl = dev; + users->bt_acl->logger_enabled = config->bt_acl_enable; + (users->nbr_of_users)++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == chan->bt_evt_channel) { + if (!users->bt_evt) { + users->bt_evt = dev; + users->bt_evt->logger_enabled = config->bt_evt_enable; + (users->nbr_of_users)++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == chan->gnss_channel) { + if (!users->gnss) { + users->gnss = dev; + users->gnss->logger_enabled = config->gnss_enable; + (users->nbr_of_users)++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == chan->fm_radio_channel) { + if (!users->fm_radio && + 0 == strncmp(name, CG2900_FM_RADIO, + CG2900_MAX_NAME_SIZE)) { + users->fm_radio = dev; + users->fm_radio->logger_enabled = + config->fm_radio_enable; + (users->nbr_of_users)++; + } else if (!users->fm_radio_audio && + 0 == strncmp(name, CG2900_FM_RADIO_AUDIO, + CG2900_MAX_NAME_SIZE)) { + users->fm_radio_audio = dev; + users->fm_radio_audio->logger_enabled = + config->fm_radio_audio_enable; + (users->nbr_of_users)++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == chan->debug_channel) { + if (!users->debug) + users->debug = dev; + else + err = -EBUSY; + } else if (dev->h4_channel == chan->ste_tools_channel) { + if (!users->ste_tools) + users->ste_tools = dev; + else + err = -EBUSY; + } else if (dev->h4_channel == chan->hci_logger_channel) { + if (!users->hci_logger) + users->hci_logger = dev; + else + err = -EBUSY; + } else if (dev->h4_channel == chan->us_ctrl_channel) { + if (!users->us_ctrl) + users->us_ctrl = dev; + else + err = -EBUSY; + } else if (dev->h4_channel == chan->core_channel) { + if (!users->core) { + (users->nbr_of_users)++; + users->core = dev; + } else { + err = -EBUSY; + } + } else { + err = -EINVAL; + CG2900_ERR("Bad H:4 channel supplied: 0x%X", dev->h4_channel); + } + + if (err) + CG2900_ERR("H:4 channel 0x%X, not registered (%d)", + dev->h4_channel, err); + + return err; +} + +/** + * remove_h4_user() - Remove H4 user from user storage. + * @dev: Stored CG2900 device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied, bad channel is supplied, or if there + * is no user for supplied channel. + */ +static int remove_h4_user(struct cg2900_device **dev) +{ + int err = 0; + struct cg2900_users *users = &(core_info->users); + struct cg2900_h4_channels *chan = &(core_info->h4_channels); + struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb); + + if (!dev || !(*dev)) { + CG2900_ERR("NULL device supplied"); + return -EINVAL; + } + + if ((*dev)->h4_channel == chan->bt_cmd_channel) { + CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X", + (int)users->bt_cmd, + (int)users->bt_audio, (int)*dev); + + if (*dev == users->bt_cmd) { + users->bt_cmd = NULL; + (users->nbr_of_users)--; + } else if (*dev == users->bt_audio) { + users->bt_audio = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + + CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X", + (int)users->bt_cmd, + (int)users->bt_audio, (int)*dev); + + /* + * If both BT Command channel users are de-registered we + * inform the chip handler. + */ + if (!users->bt_cmd && !users->bt_audio && + cb->last_bt_user_removed) + cb->last_bt_user_removed(&(core_info->chip_dev)); + } else if ((*dev)->h4_channel == chan->bt_acl_channel) { + if (*dev == users->bt_acl) { + users->bt_acl = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->bt_evt_channel) { + if (*dev == users->bt_evt) { + users->bt_evt = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->gnss_channel) { + if (*dev == users->gnss) { + users->gnss = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + + /* + * If the GNSS channel user is de-registered we inform + * the chip handler. + */ + if (users->gnss == NULL && cb->last_gnss_user_removed) + cb->last_gnss_user_removed(&(core_info->chip_dev)); + } else if ((*dev)->h4_channel == chan->fm_radio_channel) { + if (*dev == users->fm_radio) { + users->fm_radio = NULL; + (users->nbr_of_users)--; + } else if (*dev == users->fm_radio_audio) { + users->fm_radio_audio = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + + /* + * If both FM Radio channel users are de-registered we inform + * the chip handler. + */ + if (!users->fm_radio && !users->fm_radio_audio && + cb->last_fm_user_removed) + cb->last_fm_user_removed(&(core_info->chip_dev)); + } else if ((*dev)->h4_channel == chan->debug_channel) { + if (*dev == users->debug) + users->debug = NULL; + else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->ste_tools_channel) { + if (*dev == users->ste_tools) + users->ste_tools = NULL; + else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->hci_logger_channel) { + if (*dev == users->hci_logger) + users->hci_logger = NULL; + else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->us_ctrl_channel) { + if (*dev == users->us_ctrl) + users->us_ctrl = NULL; + else + err = -EINVAL; + } else if ((*dev)->h4_channel == chan->core_channel) { + if (*dev == users->core) { + users->core = NULL; + (users->nbr_of_users)--; + } else + err = -EINVAL; + } else { + CG2900_ERR("Bad H:4 channel supplied: 0x%X", + (*dev)->h4_channel); + return -EINVAL; + } + + if (err) + CG2900_ERR("Trying to remove device that was not registered"); + + /* + * Free the device even if there is an error with the device. + * Also set to NULL to inform caller about the free. + */ + free_user_dev(dev); + + return err; +} + +/** + * chip_startup() - Start the connectivity controller and download patches and settings. + */ +static void chip_startup(void) +{ + struct hci_command_hdr cmd; + + CG2900_INFO("chip_startup"); + + SET_MAIN_STATE(CORE_BOOTING); + SET_BOOT_STATE(BOOT_NOT_STARTED); + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + create_and_send_bt_cmd(&cmd, sizeof(cmd)); +} + +/** + * chip_shutdown() - Reset and power the chip off. + */ +static void chip_shutdown(void) +{ + int err = 0; + struct trans_info *trans_info = core_info->trans_info; + struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb); + + CG2900_INFO("chip_shutdown"); + + /* First do a quick power switch of the chip to assure a good state */ + if (trans_info && trans_info->cb.set_chip_power) + trans_info->cb.set_chip_power(false); + + /* + * Wait 50ms before continuing to be sure that the chip detects + * chip power off. + */ + schedule_timeout_interruptible( + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + + if (trans_info && trans_info->cb.set_chip_power) + trans_info->cb.set_chip_power(true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + /* + * Let the chip handler finish the reset if any callback is registered. + * Otherwise we are finished. + */ + if (!cb->chip_shutdown) { + CG2900_DBG("No registered handler. Finishing shutdown."); + cg2900_chip_shutdown_finished(err); + return; + } + + err = cb->chip_shutdown(&(core_info->chip_dev)); + if (err) { + CG2900_ERR("chip_shutdown failed (%d). Finishing shutdown.", + err); + cg2900_chip_shutdown_finished(err); + } +} + +/** + * handle_reset_cmd_complete_evt() - Handle a received HCI Command Complete event for a Reset command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_reset_cmd_complete_evt(u8 *data) +{ + bool pkt_handled = false; + u8 status = data[0]; + struct hci_command_hdr cmd; + + CG2900_INFO("Received Reset complete event with status 0x%X", status); + + if ((core_info->main_state == CORE_BOOTING || + core_info->main_state == CORE_INITIALIZING) && + core_info->boot_state == BOOT_NOT_STARTED) { + /* Transmit HCI Read Local Version Information command */ + SET_BOOT_STATE(BOOT_READ_LOCAL_VERSION_INFORMATION); + cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION); + cmd.plen = 0; /* No parameters for HCI reset */ + create_and_send_bt_cmd(&cmd, sizeof(cmd)); + + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * handle_read_local_version_info_cmd_complete_evt() - Handle a received HCI Command Complete event for a ReadLocalVersionInformation command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_read_local_version_info_cmd_complete_evt(u8 *data) +{ + bool chip_handled = false; + struct list_head *cursor; + struct chip_handler_item *tmp; + struct local_chip_info *chip; + struct cg2900_chip_info *chip_info; + struct cg2900_chip_callbacks *cb; + int err; + struct hci_rp_read_local_version *evt; + struct cg2900_platform_data *pf_data; + + /* Check we're in the right state */ + if ((core_info->main_state != CORE_BOOTING && + core_info->main_state != CORE_INITIALIZING) || + core_info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION) + return false; + + /* We got an answer for our HCI command. Extract data */ + evt = (struct hci_rp_read_local_version *)data; + + /* We will handle the packet */ + if (HCI_BT_ERROR_NO_ERROR != evt->status) { + CG2900_ERR("Received Read Local Version Information with " + "status 0x%X", evt->status); + SET_BOOT_STATE(BOOT_FAILED); + cg2900_reset(NULL); + return true; + } + + /* The command worked. Store the data */ + chip = &(core_info->local_chip_info); + chip->version_set = true; + chip->hci_version = evt->hci_ver; + chip->hci_revision = le16_to_cpu(evt->hci_rev); + chip->lmp_pal_version = evt->lmp_ver; + chip->manufacturer = le16_to_cpu(evt->manufacturer); + chip->lmp_pal_subversion = le16_to_cpu(evt->lmp_subver); + CG2900_DBG("Received Read Local Version Information with:\n" + "\thci_version: 0x%X\n" + "\thci_revision: 0x%X\n" + "\tlmp_pal_version: 0x%X\n" + "\tmanufacturer: 0x%X\n" + "\tlmp_pal_subversion: 0x%X", + chip->hci_version, chip->hci_revision, + chip->lmp_pal_version, chip->manufacturer, + chip->lmp_pal_subversion); + + pf_data = dev_get_platdata(core_info->dev); + if (pf_data->set_hci_revision) + pf_data->set_hci_revision(chip->hci_version, + chip->hci_revision, + chip->lmp_pal_version, + chip->lmp_pal_subversion, + chip->manufacturer); + + /* Received good confirmation. Find handler for the chip. */ + chip_info = &(core_info->chip_dev.chip); + chip_info->hci_revision = chip->hci_revision; + chip_info->hci_sub_version = chip->lmp_pal_subversion; + chip_info->manufacturer = chip->manufacturer; + + memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb)); + + list_for_each(cursor, &chip_handlers) { + tmp = list_entry(cursor, struct chip_handler_item, list); + chip_handled = tmp->cb.check_chip_support( + &(core_info->chip_dev)); + if (chip_handled) { + CG2900_INFO("Chip handler found"); + break; + } + } + + if (core_info->main_state == CORE_INITIALIZING) { + /* + * We are now finished with the start-up during HwRegistered + * operation. + */ + SET_BOOT_STATE(BOOT_READY); + wake_up_interruptible(&main_wait_queue); + } else if (!chip_handled) { + CG2900_INFO("No chip handler found. Start-up complete"); + SET_BOOT_STATE(BOOT_READY); + cg2900_chip_startup_finished(0); + } else { + cb = &(core_info->chip_dev.cb); + if (!cb->chip_startup) + cg2900_chip_startup_finished(0); + else { + err = cb->chip_startup(&(core_info->chip_dev)); + if (err) + cg2900_chip_startup_finished(err); + } + } + + return true; +} + +/** + * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core. + * @skb: Data packet + * + * The handle_rx_data_bt_evt() function checks if received data should be + * handled in CG2900 Core. If so handle it correctly. + * Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool handle_rx_data_bt_evt(struct sk_buff *skb) +{ + bool pkt_handled = false; + u8 *data = &(skb->data[CG2900_SKB_RESERVE]); + struct hci_event_hdr *evt; + struct hci_ev_cmd_complete *cmd_complete; + u16 op_code; + + evt = (struct hci_event_hdr *)data; + + /* First check the event code */ + if (HCI_EV_CMD_COMPLETE != evt->evt) + return false; + + data += sizeof(*evt); + cmd_complete = (struct hci_ev_cmd_complete *)data; + + op_code = le16_to_cpu(cmd_complete->opcode); + + CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code); + data += sizeof(*cmd_complete); /* Move to first byte after OCF */ + + if (op_code == HCI_OP_RESET) + pkt_handled = handle_reset_cmd_complete_evt(data); + else if (op_code == HCI_OP_READ_LOCAL_VERSION) + pkt_handled = + handle_read_local_version_info_cmd_complete_evt(data); + + if (pkt_handled) + kfree_skb(skb); + + return pkt_handled; +} + +/** + * test_char_dev_tx_received() - Handle data received from CG2900 Core. + * @dev: Current transport device information. + * @skb: Buffer with data coming form device. + */ +static int test_char_dev_tx_received(struct cg2900_trans_dev *dev, + struct sk_buff *skb) +{ + skb_queue_tail(&core_info->test_char_dev->rx_queue, skb); + wake_up_interruptible(&char_wait_queue); + return 0; +} + +/** + * test_char_dev_open() - User space char device has been opened. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + * -EACCES if transport already exists. + * -ENOMEM if allocation fails. + * Errors from create_work_item. + */ +static int test_char_dev_open(struct inode *inode, struct file *filp) +{ + struct cg2900_trans_callbacks cb = { + .write = test_char_dev_tx_received, + .open = NULL, + .close = NULL, + .set_chip_power = NULL + }; + + CG2900_INFO("test_char_dev_open"); + return cg2900_register_trans_driver(&cb, NULL); +} + +/** + * test_char_dev_release() - User space char device has been closed. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * Returns: + * 0 if there is no error. + */ +static int test_char_dev_release(struct inode *inode, struct file *filp) +{ + /* Clean the message queue */ + skb_queue_purge(&core_info->test_char_dev->rx_queue); + return cg2900_deregister_trans_driver(); +} + +/** + * test_char_dev_read() - Queue and copy buffer to user space char device. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Count of received data in bytes. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes read. + * -EFAULT if copy_to_user fails. + */ +static ssize_t test_char_dev_read(struct file *filp, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + int bytes_to_copy; + int err; + struct sk_buff_head *rx_queue = &core_info->test_char_dev->rx_queue; + + CG2900_INFO("test_char_dev_read"); + + if (skb_queue_empty(rx_queue)) + wait_event_interruptible(char_wait_queue, + !(skb_queue_empty(rx_queue))); + + skb = skb_dequeue(rx_queue); + if (!skb) { + CG2900_INFO("skb queue is empty - return with zero bytes"); + bytes_to_copy = 0; + goto finished; + } + + bytes_to_copy = min(count, skb->len); + err = copy_to_user(buf, skb->data, bytes_to_copy); + if (err) { + skb_queue_head(rx_queue, skb); + return -EFAULT; + } + + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) + skb_queue_head(rx_queue, skb); + else + kfree_skb(skb); + +finished: + return bytes_to_copy; +} + +/** + * test_char_dev_write() - Copy buffer from user and write to CG2900 Core. + * @filp: Pointer to the file struct. + * @buf: Read buffer. + * @count: Size of the buffer write. + * @f_pos: Position in buffer. + * + * Returns: + * >= 0 is number of bytes written. + * -EFAULT if copy_from_user fails. + */ +static ssize_t test_char_dev_write(struct file *filp, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct sk_buff *skb; + + CG2900_INFO("test_char_dev_write count %d", count); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(count + RX_SKB_RESERVE, GFP_ATOMIC); + if (!skb) { + CG2900_ERR("Failed to alloc skb"); + return -ENOMEM; + } + skb_reserve(skb, RX_SKB_RESERVE); + + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + cg2900_data_from_chip(skb); + + return count; +} + +/** + * test_char_dev_poll() - Handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * Returns: + * Mask of current set POLL values (0 or (POLLIN | POLLRDNORM)) + */ +static unsigned int test_char_dev_poll(struct file *filp, poll_table *wait) +{ + unsigned int mask = 0; + + poll_wait(filp, &char_wait_queue, wait); + + if (!(skb_queue_empty(&core_info->test_char_dev->rx_queue))) + mask |= POLLIN | POLLRDNORM; + + return mask; +} + +/* + * struct test_char_dev_fops - Test char devices file operations. + * @read: Function that reads from the char device. + * @write: Function that writes to the char device. + * @poll: Function that handles poll call to the fd. + */ +static const struct file_operations test_char_dev_fops = { + .open = test_char_dev_open, + .release = test_char_dev_release, + .read = test_char_dev_read, + .write = test_char_dev_write, + .poll = test_char_dev_poll +}; + +/** + * test_char_dev_create() - Create a char device for testing. + * + * Creates a separate char device that will interact directly with userspace + * test application. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * -EBUSY if device has already been allocated. + * Error codes from misc_register. + */ +static int test_char_dev_create(void) +{ + int err; + + if (core_info->test_char_dev) { + CG2900_ERR("Trying to allocate test_char_dev twice"); + return -EBUSY; + } + + core_info->test_char_dev = kzalloc(sizeof(*(core_info->test_char_dev)), + GFP_KERNEL); + if (!core_info->test_char_dev) { + CG2900_ERR("Couldn't allocate test_char_dev"); + return -ENOMEM; + } + + /* Initialize the RX queue */ + skb_queue_head_init(&core_info->test_char_dev->rx_queue); + + /* Prepare miscdevice struct before registering the device */ + core_info->test_char_dev->test_miscdev.minor = MISC_DYNAMIC_MINOR; + core_info->test_char_dev->test_miscdev.name = CG2900_CDEV_NAME; + core_info->test_char_dev->test_miscdev.fops = &test_char_dev_fops; + core_info->test_char_dev->test_miscdev.parent = core_info->dev; + + err = misc_register(&core_info->test_char_dev->test_miscdev); + if (err) { + CG2900_ERR("Error %d registering misc dev!", err); + kfree(core_info->test_char_dev); + core_info->test_char_dev = NULL; + return err; + } + + return 0; +} + +/** + * test_char_dev_destroy() - Clean up after test_char_dev_create(). + */ +static void test_char_dev_destroy(void) +{ + int err; + + if (!core_info->test_char_dev) + return; + + err = misc_deregister(&core_info->test_char_dev->test_miscdev); + if (err) + CG2900_ERR("Error %d deregistering misc dev!", err); + + /* Clean the message queue */ + skb_queue_purge(&core_info->test_char_dev->rx_queue); + + kfree(core_info->test_char_dev); + core_info->test_char_dev = NULL; +} + +/** + * open_transport() - Open the CG2900 transport for data transfers. + * + * Returns: + * 0 if there is no error, + * -EACCES if write to transport failed, + * -EIO if transport has not been selected or chip did not answer to commands. + */ +static int open_transport(void) +{ + int err = 0; + struct trans_info *trans_info = core_info->trans_info; + + CG2900_INFO("open_transport"); + + if (trans_info && trans_info->cb.open) { + err = trans_info->cb.open(&trans_info->dev); + if (err) + CG2900_ERR("Transport open failed (%d)", err); + } + + if (!err) + SET_TRANSPORT_STATE(TRANS_OPENED); + + return err; +} + +/** + * close_transport() - Close the CG2900 transport for data transfers. + */ +static void close_transport(void) +{ + struct trans_info *trans_info = core_info->trans_info; + + CG2900_INFO("close_transport"); + + /* Check so transport has not already been removed */ + if (TRANS_OPENED == core_info->transport_state) + SET_TRANSPORT_STATE(TRANS_CLOSED); + + if (trans_info && trans_info->cb.close) { + int err = trans_info->cb.close(&trans_info->dev); + if (err) + CG2900_ERR("Transport close failed (%d)", err); + } +} + +/** + * create_work_item() - Create work item and add it to the work queue. + * @wq: work queue struct where the work will be added. + * @work_func: Work function. + * @data: Private data for the work. + * + * Returns: + * 0 if there is no error. + * -EBUSY if not possible to queue work. + * -ENOMEM if allocation fails. + */ +static int create_work_item(struct workqueue_struct *wq, work_func_t work_func, + void *data) +{ + struct cg2900_work_struct *new_work; + int err; + + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC); + if (!new_work) { + CG2900_ERR("Failed to alloc memory for cg2900_work_struct!"); + return -ENOMEM; + } + + new_work->data = data; + INIT_WORK(&new_work->work, work_func); + + err = queue_work(wq, &new_work->work); + if (!err) { + CG2900_ERR("Failed to queue work_struct because it's already " + "in the queue!"); + kfree(new_work); + return -EBUSY; + } + + return 0; +} + +/** + * work_hw_registered() - Called when the interface to HW has been established. + * @work: Reference to work data. + * + * Since there now is a transport identify the connected chip and decide which + * chip handler to use. + */ +static void work_hw_registered(struct work_struct *work) +{ + struct cg2900_work_struct *current_work = NULL; + bool run_shutdown = true; + struct cg2900_chip_callbacks *cb; + struct cg2900_h4_channels *chan; + struct trans_info *trans_info = core_info->trans_info; + struct hci_command_hdr cmd; + + CG2900_INFO("work_hw_registered"); + + if (!work) { + CG2900_ERR("work == NULL"); + return; + } + + current_work = container_of(work, struct cg2900_work_struct, work); + + SET_MAIN_STATE(CORE_INITIALIZING); + SET_BOOT_STATE(BOOT_NOT_STARTED); + + /* + * This might look strange, but we need to read out + * the revision info in order to be able to shutdown the chip properly. + */ + if (trans_info && trans_info->cb.set_chip_power) + trans_info->cb.set_chip_power(true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + /* + * Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + cmd.opcode = cpu_to_le16(HCI_OP_RESET); + cmd.plen = 0; /* No parameters for HCI reset */ + create_and_send_bt_cmd(&cmd, sizeof(cmd)); + + /* Wait up to 500 milliseconds for revision to be read out */ + CG2900_DBG("Wait up to 500 milliseconds for revision to be read."); + wait_event_interruptible_timeout(main_wait_queue, + (BOOT_READY == core_info->boot_state), + msecs_to_jiffies(REVISION_READOUT_TIMEOUT)); + + /* + * If we are in BOOT_READY we have a good revision. + * Otherwise handle this as an error and switch to default handler. + */ + if (BOOT_READY != core_info->boot_state) { + chip_not_detected(); + run_shutdown = false; + } + + /* Read out the channels for connected chip */ + cb = &(core_info->chip_dev.cb); + chan = &(core_info->h4_channels); + if (cb->get_h4_channel) { + /* Get the H4 channel ID for all channels */ + cb->get_h4_channel(CG2900_BT_CMD, &(chan->bt_cmd_channel)); + cb->get_h4_channel(CG2900_BT_ACL, &(chan->bt_acl_channel)); + cb->get_h4_channel(CG2900_BT_EVT, &(chan->bt_evt_channel)); + cb->get_h4_channel(CG2900_GNSS, &(chan->gnss_channel)); + cb->get_h4_channel(CG2900_FM_RADIO, &(chan->fm_radio_channel)); + cb->get_h4_channel(CG2900_DEBUG, &(chan->debug_channel)); + cb->get_h4_channel(CG2900_STE_TOOLS, + &(chan->ste_tools_channel)); + cb->get_h4_channel(CG2900_HCI_LOGGER, + &(chan->hci_logger_channel)); + cb->get_h4_channel(CG2900_US_CTRL, &(chan->us_ctrl_channel)); + cb->get_h4_channel(CG2900_CORE, &(chan->core_channel)); + } + + /* + * Now it is time to shutdown the controller to reduce + * power consumption until any users register + */ + if (run_shutdown) + chip_shutdown(); + else + cg2900_chip_shutdown_finished(0); + + kfree(current_work); +} + +/** + * cg2900_register_user() - Register CG2900 user. + * @name: Name of HCI H:4 channel to register to. + * @cb: Callback structure to use for the H:4 channel. + * + * Returns: + * Pointer to CG2900 device structure if successful. + * NULL upon failure. + */ +struct cg2900_device *cg2900_register_user(char *name, + struct cg2900_callbacks *cb) +{ + struct cg2900_device *current_dev; + int err; + struct trans_info *trans_info = core_info->trans_info; + + CG2900_INFO("cg2900_register_user %s", name); + + BUG_ON(!core_info); + + /* Wait for state CORE_IDLE or CORE_ACTIVE. */ + err = wait_event_interruptible_timeout(main_wait_queue, + (CORE_IDLE == core_info->main_state || + CORE_ACTIVE == core_info->main_state), + msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT)); + + if (err <= 0) { + if (CORE_INITIALIZING == core_info->main_state) + CG2900_ERR("Transport not opened"); + else + CG2900_ERR("cg2900_register_user currently busy (0x%X)." + " Try again.", core_info->main_state); + return NULL; + } + + /* Allocate device */ + current_dev = kzalloc(sizeof(*current_dev), GFP_ATOMIC); + if (!current_dev) { + CG2900_ERR("Couldn't allocate current dev"); + goto error_handling; + } + + if (!core_info->chip_dev.cb.get_h4_channel) { + CG2900_ERR("No channel handler registered"); + goto error_handling; + } + err = core_info->chip_dev.cb.get_h4_channel(name, + &(current_dev->h4_channel)); + if (err) { + CG2900_ERR("Couldn't find H4 channel for %s", name); + goto error_handling; + } + current_dev->dev = core_info->dev; + current_dev->cb = kmalloc(sizeof(*(current_dev->cb)), + GFP_ATOMIC); + if (!current_dev->cb) { + CG2900_ERR("Couldn't allocate cb "); + goto error_handling; + } + memcpy((char *)current_dev->cb, (char *)cb, + sizeof(*(current_dev->cb))); + + /* Retrieve pointer to the correct CG2900 Core user structure */ + err = add_h4_user(current_dev, name); + + if (!err) { + CG2900_DBG("H:4 channel 0x%X registered", + current_dev->h4_channel); + } else { + CG2900_ERR("H:4 channel 0x%X already registered " + "or other error (%d)", + current_dev->h4_channel, err); + goto error_handling; + } + + if (CORE_ACTIVE != core_info->main_state && + core_info->users.nbr_of_users == 1) { + /* Open transport and start-up the chip */ + if (trans_info && trans_info->cb.set_chip_power) + trans_info->cb.set_chip_power(true); + + /* Wait 100ms to be sure that the chip is ready */ + schedule_timeout_interruptible( + msecs_to_jiffies(CHIP_READY_TIMEOUT)); + + err = open_transport(); + if (err) { + /* + * Remove the user. If there is no error it will be + * freed as well. + */ + remove_h4_user(¤t_dev); + goto finished; + } + + chip_startup(); + + /* Wait up to 15 seconds for chip to start */ + CG2900_DBG("Wait up to 15 seconds for chip to start.."); + wait_event_interruptible_timeout(main_wait_queue, + (CORE_ACTIVE == core_info->main_state || + CORE_IDLE == core_info->main_state), + msecs_to_jiffies(CHIP_STARTUP_TIMEOUT)); + if (CORE_ACTIVE != core_info->main_state) { + CG2900_ERR("ST-Ericsson CG2900 driver failed to " + "start"); + + /* Close the transport and power off the chip */ + close_transport(); + + /* + * Remove the user. If there is no error it will be + * freed as well. + */ + remove_h4_user(¤t_dev); + + /* Chip shut-down finished, set correct state. */ + SET_MAIN_STATE(CORE_IDLE); + } + } + goto finished; + +error_handling: + free_user_dev(¤t_dev); +finished: + return current_dev; +} +EXPORT_SYMBOL(cg2900_register_user); + +/** + * cg2900_deregister_user() - Remove registration of CG2900 user. + * @dev: CG2900 device. + */ +void cg2900_deregister_user(struct cg2900_device *dev) +{ + int h4_channel; + int err = 0; + + CG2900_INFO("cg2900_deregister_user"); + + BUG_ON(!core_info); + + if (!dev) { + CG2900_ERR("Calling with NULL pointer"); + return; + } + + h4_channel = dev->h4_channel; + + /* Remove the user. If there is no error it will be freed as well */ + err = remove_h4_user(&dev); + if (err) { + CG2900_ERR("Trying to deregister non-registered " + "H:4 channel 0x%X or other error %d", + h4_channel, err); + return; + } + + CG2900_DBG("H:4 channel 0x%X deregistered", h4_channel); + + if (0 != core_info->users.nbr_of_users) + /* This was not the last user, we're done. */ + return; + + if (CORE_IDLE == core_info->main_state) + /* Chip has already been shut down. */ + return; + + SET_MAIN_STATE(CORE_CLOSING); + chip_shutdown(); + + /* Wait up to 15 seconds for chip to shut-down */ + CG2900_DBG("Wait up to 15 seconds for chip to shut-down.."); + wait_event_interruptible_timeout(main_wait_queue, + (CORE_IDLE == core_info->main_state), + msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT)); + + /* Force shutdown if we timed out */ + if (CORE_IDLE != core_info->main_state) { + CG2900_ERR("ST-Ericsson CG2900 Core Driver was shut-down with " + "problems."); + + /* Close the transport and power off the chip */ + close_transport(); + + /* Chip shut-down finished, set correct state. */ + SET_MAIN_STATE(CORE_IDLE); + } +} +EXPORT_SYMBOL(cg2900_deregister_user); + +/** + * cg2900_reset() - Reset the CG2900 controller. + * @dev: CG2900 device. + * + * Returns: + * 0 if there is no error. + * -EACCES if driver has not been initialized. + */ +int cg2900_reset(struct cg2900_device *dev) +{ + CG2900_INFO("cg2900_reset"); + + BUG_ON(!core_info); + + SET_MAIN_STATE(CORE_RESETING); + + /* Shutdown the chip */ + chip_shutdown(); + + /* + * Inform all registered users about the reset and free the user devices + * Don't send reset for debug and logging channels + */ + handle_reset_of_user(&(core_info->users.bt_cmd)); + handle_reset_of_user(&(core_info->users.bt_audio)); + handle_reset_of_user(&(core_info->users.bt_acl)); + handle_reset_of_user(&(core_info->users.bt_evt)); + handle_reset_of_user(&(core_info->users.fm_radio)); + handle_reset_of_user(&(core_info->users.fm_radio_audio)); + handle_reset_of_user(&(core_info->users.gnss)); + handle_reset_of_user(&(core_info->users.core)); + + core_info->users.nbr_of_users = 0; + + /* Reset finished. We are now idle until first user is registered */ + SET_MAIN_STATE(CORE_IDLE); + + /* + * Send wake-up since this might have been called from a failed boot. + * No harm done if it is a CG2900 Core user who called. + */ + wake_up_interruptible(&main_wait_queue); + + return 0; +} +EXPORT_SYMBOL(cg2900_reset); + +/** + * cg2900_alloc_skb() - Alloc an sk_buff structure for CG2900 handling. + * @size: Size in number of octets. + * @priority: Allocation priority, e.g. GFP_KERNEL. + * + * Returns: + * Pointer to sk_buff buffer structure if successful. + * NULL upon allocation failure. + */ +struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + CG2900_INFO("cg2900_alloc_skb"); + CG2900_DBG("size %d bytes", size); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CG2900_SKB_RESERVE, priority); + if (skb) + skb_reserve(skb, CG2900_SKB_RESERVE); + + return skb; +} +EXPORT_SYMBOL(cg2900_alloc_skb); + +/** + * cg2900_write() - Send data to the connectivity controller. + * @dev: CG2900 device. + * @skb: Data packet. + * + * The cg2900_write() function sends data to the connectivity controller. + * If the return value is 0 the skb will be freed by the driver, + * otherwise it won't be freed. + * + * Returns: + * 0 if there is no error. + * -EACCES if driver has not been initialized or trying to write while driver + * is not active. + * -EINVAL if NULL pointer was supplied. + * -EPERM if operation is not permitted, e.g. trying to write to a channel + * that doesn't handle write operations. + * Error codes returned from core_enable_hci_logger. + */ +int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb) +{ + int err = 0; + u8 *h4_header; + struct cg2900_chip_callbacks *cb; + + CG2900_DBG_DATA("cg2900_write"); + + BUG_ON(!core_info); + + if (!dev) { + CG2900_ERR("cg2900_write with no device"); + return -EINVAL; + } + + if (!skb) { + CG2900_ERR("cg2900_write with no sk_buffer"); + return -EINVAL; + } + + CG2900_DBG_DATA("Length %d bytes", skb->len); + + if (core_info->h4_channels.hci_logger_channel == dev->h4_channel) { + /* + * Treat the HCI logger write differently. + * A write can only mean a change of configuration. + */ + err = enable_hci_logger(skb); + } else if (core_info->h4_channels.core_channel == dev->h4_channel) { + CG2900_ERR("Not possible to write data on core channel, " + "it only supports enable / disable chip"); + err = -EPERM; + } else if (CORE_ACTIVE == core_info->main_state) { + /* + * Move the data pointer to the H:4 header position and + * store the H4 header. + */ + h4_header = skb_push(skb, CG2900_SKB_RESERVE); + *h4_header = (u8)dev->h4_channel; + + /* + * Check if the chip handler wants to handle this packet. + * If not, send it to the transport. + */ + cb = &(core_info->chip_dev.cb); + if (!cb->data_to_chip || + !(cb->data_to_chip(&(core_info->chip_dev), dev, skb))) + transmit_skb_to_chip(skb, dev->logger_enabled); + } else { + CG2900_ERR("Trying to transmit data when CG2900 Core is not " + "active"); + err = -EACCES; + } + + return err; +} +EXPORT_SYMBOL(cg2900_write); + +/** + * cg2900_get_local_revision() - Read revision of the connected controller. + * @rev_data: Revision data structure to fill. Must be allocated by caller. + * + * The cg2900_get_local_revision() function returns the revision data of the + * local controller if available. If data is not available, e.g. because the + * controller has not yet been started this function will return false. + * + * Returns: + * true if revision data is available. + * false if no revision data is available. + */ +bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data) +{ + BUG_ON(!core_info); + + if (!rev_data) { + CG2900_ERR("Calling with rev_data NULL"); + return false; + } + + if (!core_info->local_chip_info.version_set) + return false; + + rev_data->revision = core_info->local_chip_info.hci_revision; + rev_data->sub_version = core_info->local_chip_info.lmp_pal_subversion; + + return true; +} +EXPORT_SYMBOL(cg2900_get_local_revision); + +int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb) +{ + struct chip_handler_item *item; + + CG2900_INFO("cg2900_register_chip_driver"); + + if (!cb) { + CG2900_ERR("NULL supplied as cb"); + return -EINVAL; + } + + item = kzalloc(sizeof(*item), GFP_ATOMIC); + if (!item) { + CG2900_ERR("Failed to alloc memory!"); + return -ENOMEM; + } + + memcpy(&(item->cb), cb, sizeof(cb)); + list_add_tail(&item->list, &chip_handlers); + return 0; +} +EXPORT_SYMBOL(cg2900_register_chip_driver); + +int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb, void *data) +{ + int err; + + BUG_ON(!core_info); + + CG2900_INFO("cg2900_register_trans_driver"); + + if (core_info->trans_info) { + CG2900_ERR("trans_info already exists"); + return -EACCES; + } + + core_info->trans_info = kzalloc(sizeof(*(core_info->trans_info)), + GFP_KERNEL); + if (!core_info->trans_info) { + CG2900_ERR("Could not allocate trans_info"); + return -ENOMEM; + } + + memcpy(&(core_info->trans_info->cb), cb, sizeof(*cb)); + core_info->trans_info->dev.dev = core_info->dev; + core_info->trans_info->dev.user_data = data; + + err = create_work_item(core_info->wq, work_hw_registered, NULL); + if (err) { + CG2900_ERR("Could not create work item (%d) " + "work_hw_registered", err); + } + + return err; +} +EXPORT_SYMBOL(cg2900_register_trans_driver); + +int cg2900_deregister_trans_driver(void) +{ + BUG_ON(!core_info); + + CG2900_INFO("cg2900_deregister_trans_driver"); + + SET_MAIN_STATE(CORE_INITIALIZING); + SET_TRANSPORT_STATE(TRANS_INITIALIZING); + + if (!core_info->trans_info) + return -EACCES; + + kfree(core_info->trans_info); + core_info->trans_info = NULL; + + return 0; +} +EXPORT_SYMBOL(cg2900_deregister_trans_driver); + +int cg2900_chip_startup_finished(int err) +{ + CG2900_INFO("cg2900_chip_startup_finished (%d)", err); + + if (err) + /* Shutdown the chip */ + chip_shutdown(); + else + SET_MAIN_STATE(CORE_ACTIVE); + + wake_up_interruptible(&main_wait_queue); + + if (!core_info->trans_info->cb.chip_startup_finished) + CG2900_ERR("chip_startup_finished callback not found."); + else + core_info->trans_info->cb.chip_startup_finished(); + return 0; +} +EXPORT_SYMBOL(cg2900_chip_startup_finished); + +int cg2900_chip_shutdown_finished(int err) +{ + CG2900_INFO("cg2900_chip_shutdown_finished (%d)", err); + + /* Close the transport, which will power off the chip */ + close_transport(); + + /* Chip shut-down finished, set correct state and wake up the chip. */ + SET_MAIN_STATE(CORE_IDLE); + wake_up_interruptible(&main_wait_queue); + + return 0; +} +EXPORT_SYMBOL(cg2900_chip_shutdown_finished); + +int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger) +{ + transmit_skb_to_chip(skb, use_logger); + return 0; +} +EXPORT_SYMBOL(cg2900_send_to_chip); + +struct cg2900_device *cg2900_get_bt_cmd_dev(void) +{ + if (core_info) + return core_info->users.bt_cmd; + else + return NULL; +} +EXPORT_SYMBOL(cg2900_get_bt_cmd_dev); + +struct cg2900_device *cg2900_get_fm_radio_dev(void) +{ + if (core_info) + return core_info->users.fm_radio; + else + return NULL; +} +EXPORT_SYMBOL(cg2900_get_fm_radio_dev); + +struct cg2900_device *cg2900_get_bt_audio_dev(void) +{ + if (core_info) + return core_info->users.bt_audio; + else + return NULL; +} +EXPORT_SYMBOL(cg2900_get_bt_audio_dev); + +struct cg2900_device *cg2900_get_fm_audio_dev(void) +{ + if (core_info) + return core_info->users.fm_radio_audio; + else + return NULL; +} +EXPORT_SYMBOL(cg2900_get_fm_audio_dev); + +struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void) +{ + if (core_info) + return &(core_info->hci_logger_config); + else + return NULL; +} +EXPORT_SYMBOL(cg2900_get_hci_logger_config); + +unsigned long cg2900_get_sleep_timeout(void) +{ + if (CORE_ACTIVE != core_info->main_state || !sleep_timeout_ms) + return 0; + + return msecs_to_jiffies(sleep_timeout_ms); +} +EXPORT_SYMBOL(cg2900_get_sleep_timeout); + +void cg2900_data_from_chip(struct sk_buff *skb) +{ + struct cg2900_device *dev = NULL; + u8 h4_channel; + int err = 0; + struct cg2900_chip_callbacks *cb; + struct sk_buff *skb_log; + struct cg2900_device *logger; + + CG2900_INFO("cg2900_data_from_chip"); + + if (!skb) { + CG2900_ERR("No data supplied"); + return; + } + + h4_channel = *(skb->data); + + /* + * First check if this is the response for something + * we have sent internally. + */ + if ((core_info->main_state == CORE_BOOTING || + core_info->main_state == CORE_INITIALIZING) && + (HCI_BT_EVT_H4_CHANNEL == h4_channel) && + handle_rx_data_bt_evt(skb)) { + CG2900_DBG("Received packet handled internally"); + return; + } + + /* Find out where to route the data */ + err = find_h4_user(h4_channel, &dev, skb); + + /* Check if the chip handler wants to deal with the packet. */ + cb = &(core_info->chip_dev.cb); + if (!err && cb->data_from_chip && + cb->data_from_chip(&(core_info->chip_dev), dev, skb)) + return; + + if (err || !dev) { + CG2900_ERR("H:4 channel: 0x%X, does not match device", + h4_channel); + kfree_skb(skb); + return; + } + + /* + * If HCI logging is enabled for this channel, copy the data to + * the HCI logging output. + */ + logger = core_info->users.hci_logger; + if (!logger || !dev->logger_enabled) + goto transmit; + + /* + * Alloc a new sk_buffer and copy the data into it. + * Then send it to the HCI logger. + */ + skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC); + if (!skb_log) { + CG2900_ERR("Couldn't allocate skb_log"); + goto transmit; + } + + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + skb_log->data[0] = (u8) LOGGER_DIRECTION_RX; + + if (logger->cb->read_cb) + logger->cb->read_cb(logger, skb_log); + +transmit: + /* Remove the H4 header */ + (void)skb_pull(skb, CG2900_SKB_RESERVE); + + /* Call the Read callback */ + if (dev->cb->read_cb) + dev->cb->read_cb(dev, skb); +} +EXPORT_SYMBOL(cg2900_data_from_chip); + +static struct cg2900_bt_platform_data cg2900_bt_data = { + .bus = HCI_UART, +}; + +static struct mfd_cell cg2900_devs[] = { + { + .name = "cg2900-bt", + .platform_data = &cg2900_bt_data, + .data_size = sizeof(cg2900_bt_data), + }, + { + .name = "cg2900-fm", + }, + { + .name = "cg2900-gnss", + }, + { + .name = "cg2900-audio", + }, + { + .name = "cg2900-core", + }, + { + .name = "cg2900-chardev", + }, + { + .name = "cg2900-uart", + }, + { + .name = "cg2900-chip", + }, + { + .name = "stlc2690-chip", + }, +}; + +/** + * cg2900_probe() - Initialize module. + * + * @pdev: Platform device. + * + * This function initialize the transport and CG2900 Core, then + * register to the transport framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by platform init, alloc_chrdev_region, + * class_create, device_create, core_init, tty_register_ldisc, + * create_work_item. + */ +static int __devinit cg2900_probe(struct platform_device *pdev) +{ + int err; + struct cg2900_platform_data *pf_data; + + CG2900_INFO("cg2900_probe"); + + pf_data = dev_get_platdata(&pdev->dev); + if (!pf_data) { + CG2900_ERR("Missing platform data"); + return -EINVAL; + } + + if (pf_data->init) { + err = pf_data->init(); + if (err) { + CG2900_ERR("Platform init failed (%d)", err); + return err; + } + } + + core_info = kzalloc(sizeof(*core_info), GFP_KERNEL); + if (!core_info) { + CG2900_ERR("Couldn't allocate core_info"); + return -ENOMEM; + } + + core_info->dev = &pdev->dev; + + /* Set the internal states */ + core_info->main_state = CORE_INITIALIZING; + core_info->boot_state = BOOT_NOT_STARTED; + core_info->transport_state = TRANS_INITIALIZING; + + /* Get the H4 channel ID for all channels */ + core_info->h4_channels.bt_cmd_channel = HCI_BT_CMD_H4_CHANNEL; + core_info->h4_channels.bt_acl_channel = HCI_BT_ACL_H4_CHANNEL; + core_info->h4_channels.bt_evt_channel = HCI_BT_EVT_H4_CHANNEL; + core_info->h4_channels.gnss_channel = HCI_FM_RADIO_H4_CHANNEL; + core_info->h4_channels.fm_radio_channel = HCI_GNSS_H4_CHANNEL; + + core_info->wq = create_singlethread_workqueue(CORE_WQ_NAME); + if (!core_info->wq) { + CG2900_ERR("Could not create workqueue"); + err = -ENOMEM; + goto error_handling; + } + + /* Create and add test char device. */ + err = test_char_dev_create(); + if (err) + goto error_handling_deregister; + + cg2900_bt_data.bus = pf_data->bus; + + err = mfd_add_devices(core_info->dev, 0, cg2900_devs, + ARRAY_SIZE(cg2900_devs), NULL, 0); + if (err) { + CG2900_ERR("Failed to add MFD devices (%d)", err); + goto error_handling_test_destroy; + } + + return 0; + +error_handling_test_destroy: + test_char_dev_destroy(); +error_handling_deregister: + destroy_workqueue(core_info->wq); +error_handling: + core_info->dev = NULL; + kfree(core_info); + core_info = NULL; + return err; +} + +/** + * cg2900_remove() - Remove module. + * + * @pdev: Platform device. + * + * Returns: + * 0 if success. + * -ENOMEM if core_info does not exist. + * -EINVAL if platform data does not exist in the device. + */ +static int __devexit cg2900_remove(struct platform_device *pdev) +{ + struct cg2900_platform_data *pf_data; + + CG2900_INFO("cg2900_remove"); + + if (!core_info) { + CG2900_ERR("CG2900 Core not initiated"); + return -ENOMEM; + } + + mfd_remove_devices(core_info->dev); + + test_char_dev_destroy(); + + /* Free the user devices */ + free_user_dev(&(core_info->users.bt_cmd)); + free_user_dev(&(core_info->users.bt_acl)); + free_user_dev(&(core_info->users.bt_evt)); + free_user_dev(&(core_info->users.fm_radio)); + free_user_dev(&(core_info->users.gnss)); + free_user_dev(&(core_info->users.debug)); + free_user_dev(&(core_info->users.ste_tools)); + free_user_dev(&(core_info->users.hci_logger)); + free_user_dev(&(core_info->users.us_ctrl)); + free_user_dev(&(core_info->users.core)); + + core_info->dev = NULL; + + destroy_workqueue(core_info->wq); + + kfree(core_info); + core_info = NULL; + + pf_data = dev_get_platdata(&pdev->dev); + if (!pf_data) { + CG2900_ERR("Missing platform data"); + return -EINVAL; + } + + if (pf_data->exit) + pf_data->exit(); + + return 0; +} + +static struct platform_driver cg2900_driver = { + .driver = { + .name = "cg2900", + .owner = THIS_MODULE, + }, + .probe = cg2900_probe, + .remove = __devexit_p(cg2900_remove), +}; + +/** + * cg2900_init() - Initialize module. + * + * Registers platform driver. + */ +static int __init cg2900_init(void) +{ + CG2900_INFO("cg2900_init"); + return platform_driver_register(&cg2900_driver); +} + +/** + * cg2900_exit() - Remove module. + * + * Unregisters platform driver. + */ +static void __exit cg2900_exit(void) +{ + CG2900_INFO("cg2900_exit"); + platform_driver_unregister(&cg2900_driver); +} + +module_init(cg2900_init); +module_exit(cg2900_exit); + +module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(sleep_timeout_ms, + "Sleep timeout for data transmissions:\n" + "\t0 = disable \n" + "\t>0 = sleep timeout in milliseconds"); + +module_param(cg2900_debug_level, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(cg2900_debug_level, + "Debug level. Default 1. Possible values:\n" + "\t0 = No debug\n" + "\t1 = Error prints\n" + "\t10 = General info, e.g. function entries\n" + "\t20 = Debug info, e.g. steps in a functionality\n" + "\t25 = Data info, i.e. prints when data is transferred\n" + "\t30 = Data content, i.e. contents of the transferred data"); + +module_param_array(bd_address, byte, &bd_addr_count, + S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(bd_address, + "Bluetooth Device address. " + "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. " + "Enter as comma separated value."); + +module_param(default_hci_revision, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(default_hci_revision, + "Default HCI revision according to Bluetooth Assigned " + "Numbers."); + +module_param(default_manufacturer, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(default_manufacturer, + "Default Manufacturer according to Bluetooth Assigned " + "Numbers."); + +module_param(default_sub_version, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(default_sub_version, + "Default HCI sub-version according to Bluetooth Assigned " + "Numbers."); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity Device Driver"); diff --git a/drivers/mfd/cg2900/cg2900_core.h b/drivers/mfd/cg2900/cg2900_core.h new file mode 100644 index 0000000..d200d45 --- /dev/null +++ b/drivers/mfd/cg2900/cg2900_core.h @@ -0,0 +1,303 @@ +/* + * drivers/mfd/cg2900/cg2900_core.h + * + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller. + */ + +#ifndef _CG2900_CORE_H_ +#define _CG2900_CORE_H_ + +#include +#include + +/* Reserve 1 byte for the HCI H:4 header */ +#define CG2900_SKB_RESERVE 1 + +#define BT_BDADDR_SIZE 6 + +struct cg2900_h4_channels { + int bt_cmd_channel; + int bt_acl_channel; + int bt_evt_channel; + int gnss_channel; + int fm_radio_channel; + int debug_channel; + int ste_tools_channel; + int hci_logger_channel; + int us_ctrl_channel; + int core_channel; +}; + +/** + * struct cg2900_hci_logger_config - Configures the HCI logger. + * @bt_cmd_enable: Enable BT command logging. + * @bt_acl_enable: Enable BT ACL logging. + * @bt_evt_enable: Enable BT event logging. + * @gnss_enable: Enable GNSS logging. + * @fm_radio_enable: Enable FM radio logging. + * @bt_audio_enable: Enable BT audio command logging. + * @fm_radio_audio_enable: Enable FM radio audio command logging. + * + * Set using cg2900_write on CHANNEL_HCI_LOGGER H4 channel. + */ +struct cg2900_hci_logger_config { + bool bt_cmd_enable; + bool bt_acl_enable; + bool bt_evt_enable; + bool gnss_enable; + bool fm_radio_enable; + bool bt_audio_enable; + bool fm_radio_audio_enable; +}; + +/** + * struct cg2900_chip_info - Chip info structure. + * @manufacturer: Chip manufacturer. + * @hci_revision: Chip revision, i.e. which chip is this. + * @hci_sub_version: Chip sub-version, i.e. which tape-out is this. + * + * Note that these values match the Bluetooth Assigned Numbers, + * see http://www.bluetooth.org/ + */ +struct cg2900_chip_info { + int manufacturer; + int hci_revision; + int hci_sub_version; +}; + +struct cg2900_chip_dev; + +/** + * struct cg2900_chip_callbacks - Callback functions registered by chip handler. + * @chip_startup: Called when chip is started up. + * @chip_shutdown: Called when chip is shut down. + * @data_to_chip: Called when data shall be transmitted to chip. + * Return true when CG2900 Core shall not send it + * to chip. + * @data_from_chip: Called when data shall be transmitted to user. + * Return true when packet is taken care of by + * Return chip return handler. + * @get_h4_channel: Connects channel name with H:4 channel number. + * @is_bt_audio_user: Return true if current packet is for + * the BT audio user. + * @is_fm_audio_user: Return true if current packet is for + * the FM audio user. + * @last_bt_user_removed: Last BT channel user has been removed. + * @last_fm_user_removed: Last FM channel user has been removed. + * @last_gnss_user_removed: Last GNSS channel user has been removed. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_chip_callbacks { + int (*chip_startup)(struct cg2900_chip_dev *dev); + int (*chip_shutdown)(struct cg2900_chip_dev *dev); + bool (*data_to_chip)(struct cg2900_chip_dev *dev, + struct cg2900_device *cg2900_dev, + struct sk_buff *skb); + bool (*data_from_chip)(struct cg2900_chip_dev *dev, + struct cg2900_device *cg2900_dev, + struct sk_buff *skb); + int (*get_h4_channel)(char *name, int *h4_channel); + bool (*is_bt_audio_user)(int h4_channel, + const struct sk_buff * const skb); + bool (*is_fm_audio_user)(int h4_channel, + const struct sk_buff * const skb); + void (*last_bt_user_removed)(struct cg2900_chip_dev *dev); + void (*last_fm_user_removed)(struct cg2900_chip_dev *dev); + void (*last_gnss_user_removed)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_chip_dev - Chip handler info structure. + * @chip: Chip info such as manufacturer. + * @cb: Callback structure for the chip handler. + * @user_data: Arbitrary data set by chip handler. + */ +struct cg2900_chip_dev { + struct cg2900_chip_info chip; + struct cg2900_chip_callbacks cb; + void *user_data; +}; + +/** + * struct cg2900_id_callbacks - Chip handler identification callbacks. + * @check_chip_support: Called when chip is connected. If chip is supported by + * driver, return true and fill in @callbacks in @dev. + * + * Note that the callback may be NULL. It must always be NULL checked before + * calling. + */ +struct cg2900_id_callbacks { + bool (*check_chip_support)(struct cg2900_chip_dev *dev); +}; + +/** + * struct cg2900_trans_dev - CG2900 transport info structure. + * @dev: Parent device from CG2900 Core. + * @user_data: Arbitrary data set by chip handler. + */ +struct cg2900_trans_dev { + struct device *dev; + void *user_data; +}; + +/** + * struct cg2900_trans_callbacks - Callback functions registered by transport. + * @open: CG2900 Core needs a transport. + * @close: CG2900 Core does not need a transport. + * @write: CG2900 Core transmits to the chip. + * @set_chip_power: CG2900 Core enables or disables the chip. + * @chip_startup_finished: CG2900 Chip startup finished notification. + * + * Note that some callbacks may be NULL. They must always be NULL checked before + * calling. + */ +struct cg2900_trans_callbacks { + int (*open)(struct cg2900_trans_dev *dev); + int (*close)(struct cg2900_trans_dev *dev); + int (*write)(struct cg2900_trans_dev *dev, struct sk_buff *skb); + void (*set_chip_power)(bool chip_on); + void (*chip_startup_finished)(void); +}; + +/** + * cg2900_register_chip_driver() - Register a chip handler. + * @cb: Callbacks to call when chip is connected. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +extern int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb); + +/** + * cg2900_register_trans_driver() - Register a transport driver. + * @cb: Callbacks to call when chip is connected. + * @data: Arbitrary data used by the transport driver. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +extern int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb, + void *data); + +/** + * cg2900_deregister_trans_driver() - Deregister a transport driver. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied as @cb. + * -ENOMEM if allocation fails or work queue can't be created. + */ +extern int cg2900_deregister_trans_driver(void); + +/** + * cg2900_chip_startup_finished() - Called from chip handler when start-up is finished. + * @err: Result of the start-up. + * + * Returns: + * 0 if there is no error. + */ +extern int cg2900_chip_startup_finished(int err); + +/** + * cg2900_chip_shutdown_finished() - Called from chip handler when shutdown is finished. + * @err: Result of the shutdown. + * + * Returns: + * 0 if there is no error. + */ +extern int cg2900_chip_shutdown_finished(int err); + +/** + * cg2900_send_to_chip() - Send data to chip. + * @skb: Packet to transmit. + * @use_logger: true if hci_logger should copy data content. + * + * Returns: + * 0 if there is no error. + */ +extern int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger); + +/** + * cg2900_get_bt_cmd_dev() - Return user of the BT command H:4 channel. + * + * Returns: + * User of the BT command H:4 channel. + * NULL if no user is registered. + */ +extern struct cg2900_device *cg2900_get_bt_cmd_dev(void); + +/** + * cg2900_get_fm_radio_dev() - Return user of the FM radio H:4 channel. + * + * Returns: + * User of the FM radio H:4 channel. + * NULL if no user is registered. + */ +extern struct cg2900_device *cg2900_get_fm_radio_dev(void); + +/** + * cg2900_get_bt_audio_dev() - Return user of the BT audio H:4 channel. + * + * Returns: + * User of the BT audio H:4 channel. + * NULL if no user is registered. + */ +extern struct cg2900_device *cg2900_get_bt_audio_dev(void); + +/** + * cg2900_get_fm_audio_dev() - Return user of the FM audio H:4 channel. + * + * Returns: + * User of the FM audio H:4 channel. + * NULL if no user is registered. + */ +extern struct cg2900_device *cg2900_get_fm_audio_dev(void); + +/** + * cg2900_get_hci_logger_config() - Return HCI Logger configuration. + * + * Returns: + * HCI logger configuration. + * NULL if CG2900 Core has not yet been started. + */ +extern struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void); + +/** + * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies. + * + * Returns: + * Sleep timeout in jiffies. 0 means that sleep timeout shall not be used. + */ +extern unsigned long cg2900_get_sleep_timeout(void); + +/** + * cg2900_data_from_chip() - Data received from connectivity controller. + * @skb: Data packet + * + * The cg2900_data_from_chip() function checks which channel + * the data was received on and send to the right user. + */ +extern void cg2900_data_from_chip(struct sk_buff *skb); + +/* module_param declared in cg2900_core.c */ +extern u8 bd_address[BT_BDADDR_SIZE]; +extern int default_manufacturer; +extern int default_hci_revision; +extern int default_sub_version; + +#endif /* _CG2900_CORE_H_ */ diff --git a/drivers/mfd/cg2900/cg2900_debug.h b/drivers/mfd/cg2900/cg2900_debug.h new file mode 100644 index 0000000..c3d8fc8 --- /dev/null +++ b/drivers/mfd/cg2900/cg2900_debug.h @@ -0,0 +1,76 @@ +/* + * drivers/mfd/cg2900/cg2900_debug.h + * + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Debug functionality for the Linux Bluetooth HCI H:4 Driver for ST-Ericsson + * CG2900 connectivity controller. + */ + +#ifndef _CG2900_DEBUG_H_ +#define _CG2900_DEBUG_H_ + +#include +#include +#include + +#define CG2900_DEFAULT_DEBUG_LEVEL 1 + +/* module_param declared in cg2900_core.c */ +extern int cg2900_debug_level; + +#if defined(NDEBUG) || CG2900_DEFAULT_DEBUG_LEVEL == 0 + #define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len) + #define CG2900_DBG_DATA(fmt, arg...) + #define CG2900_DBG(fmt, arg...) + #define CG2900_INFO(fmt, arg...) + #define CG2900_ERR(fmt, arg...) +#else + + #define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len) \ + do { \ + if (cg2900_debug_level >= 30) \ + print_hex_dump_bytes("CG2900 " __prefix ": " , \ + DUMP_PREFIX_NONE, __buf, __len); \ + } while (0) + + #define CG2900_DBG_DATA(fmt, arg...) \ + do { \ + if (cg2900_debug_level >= 25) \ + pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \ + } while (0) + + #define CG2900_DBG(fmt, arg...) \ + do { \ + if (cg2900_debug_level >= 20) \ + pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \ + } while (0) + + #define CG2900_INFO(fmt, arg...) \ + do { \ + if (cg2900_debug_level >= 10) \ + pr_info("CG2900: " fmt "\n" , ## arg); \ + } while (0) + + #define CG2900_ERR(fmt, arg...) \ + do { \ + if (cg2900_debug_level >= 1) \ + pr_err("CG2900 %s: " fmt "\n" , __func__ , ## arg); \ + } while (0) + +#endif /* NDEBUG */ + +#define CG2900_SET_STATE(__name, __var, __new_state) \ +do { \ + CG2900_DBG("New %s: 0x%X", __name, (uint32_t)__new_state); \ + __var = __new_state; \ +} while (0) + +#endif /* _CG2900_DEBUG_H_ */ diff --git a/drivers/mfd/cg2900/hci_defines.h b/drivers/mfd/cg2900/hci_defines.h new file mode 100644 index 0000000..6210e1b --- /dev/null +++ b/drivers/mfd/cg2900/hci_defines.h @@ -0,0 +1,81 @@ +/* + * drivers/mfd/cg2900/hci_defines.h + * + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI defines for ST-Ericsson CG2900 connectivity controller. + */ + +#ifndef _BLUETOOTH_DEFINES_H_ +#define _BLUETOOTH_DEFINES_H_ + +#include + +/* H:4 offset in an HCI packet */ +#define HCI_H4_POS 0 +#define HCI_H4_SIZE 1 + +/* Standardized Bluetooth H:4 channels */ +#define HCI_BT_CMD_H4_CHANNEL 0x01 +#define HCI_BT_ACL_H4_CHANNEL 0x02 +#define HCI_BT_SCO_H4_CHANNEL 0x03 +#define HCI_BT_EVT_H4_CHANNEL 0x04 + +/* Bluetooth Opcode Group Field (OGF) */ +#define HCI_BT_OGF_LINK_CTRL 0x01 +#define HCI_BT_OGF_LINK_POLICY 0x02 +#define HCI_BT_OGF_CTRL_BB 0x03 +#define HCI_BT_OGF_LINK_INFO 0x04 +#define HCI_BT_OGF_LINK_STATUS 0x05 +#define HCI_BT_OGF_LINK_TESTING 0x06 +#define HCI_BT_OGF_VS 0x3F + +/* Bluetooth Opcode Command Field (OCF) */ +#define HCI_BT_OCF_READ_LOCAL_VERSION_INFO 0x0001 +#define HCI_BT_OCF_RESET 0x0003 + +/* Bluetooth HCI command OpCodes in LSB/MSB fashion */ +#define HCI_BT_RESET_CMD_LSB 0x03 +#define HCI_BT_RESET_CMD_MSB 0x0C +#define HCI_BT_READ_LOCAL_VERSION_CMD_LSB 0x01 +#define HCI_BT_READ_LOCAL_VERSION_CMD_MSB 0x10 + +/* Bluetooth Event OpCodes */ +#define HCI_BT_EVT_CMD_COMPLETE 0x0E +#define HCI_BT_EVT_CMD_STATUS 0x0F + +/* Bluetooth Command offsets */ +#define HCI_BT_CMD_ID_POS 1 +#define HCI_BT_CMD_PARAM_LEN_POS 3 +#define HCI_BT_CMD_PARAM_POS 4 +#define HCI_BT_CMD_HDR_SIZE 4 + +/* Bluetooth Event offsets for CG2900 users, i.e. not including H:4 channel */ +#define HCI_BT_EVT_ID_POS 0 +#define HCI_BT_EVT_LEN_POS 1 +#define HCI_BT_EVT_CMD_COMPL_ID_POS 3 +#define HCI_BT_EVT_CMD_STATUS_ID_POS 4 +#define HCI_BT_EVT_CMD_COMPL_STATUS_POS 5 +#define HCI_BT_EVT_CMD_STATUS_STATUS_POS 2 +#define HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS 2 +#define HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS 3 + +/* Bluetooth error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 +#define HCI_BT_ERROR_CMD_DISALLOWED 0x0C + +/* Bluetooth lengths */ +#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE 254 + +#define HCI_BT_RESET_LEN 3 +#define HCI_BT_RESET_PARAM_LEN 0 +#define HCI_BT_CMD_COMPLETE_NO_PARAM_LEN 4 + +#endif /* _BLUETOOTH_DEFINES_H_ */ diff --git a/include/linux/mfd/cg2900.h b/include/linux/mfd/cg2900.h new file mode 100644 index 0000000..ca7d81b --- /dev/null +++ b/include/linux/mfd/cg2900.h @@ -0,0 +1,187 @@ +/* + * include/linux/mfd/cg2900.h + * + * Copyright (C) ST-Ericsson SA 2010 + * Authors: + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for ST-Ericsson. + * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson. + * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson. + * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson. + * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 connectivity + * controller. + */ + +#ifndef _CG2900_H_ +#define _CG2900_H_ + +#include + +#define CG2900_MAX_NAME_SIZE 30 + +/* + * Channel names to use when registering to CG2900 driver + */ + +/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands. + */ +#define CG2900_BT_CMD "cg2900_bt_cmd" + +/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data. + */ +#define CG2900_BT_ACL "cg2900_bt_acl" + +/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events. + */ +#define CG2900_BT_EVT "cg2900_bt_evt" + +/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio. + */ +#define CG2900_FM_RADIO "cg2900_fm_radio" + +/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS. + */ +#define CG2900_GNSS "cg2900_gnss" + +/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data. + */ +#define CG2900_DEBUG "cg2900_debug" + +/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data. + */ +#define CG2900_STE_TOOLS "cg2900_ste_tools" + +/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets. + * Data read is copy of all data transferred on the other channels. + * Only write allowed is configuration of the HCI Logger. + */ +#define CG2900_HCI_LOGGER "cg2900_hci_logger" + +/** CG2900_US_CTRL - Channel for user space init and control of CG2900. + */ +#define CG2900_US_CTRL "cg2900_us_ctrl" + +/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands. + * Maps to Bluetooth command and event channels. + */ +#define CG2900_BT_AUDIO "cg2900_bt_audio" + +/** CG2900_FM_RADIO_AUDIO - HCI channel for FM audio configuration commands. + * Maps to FM Radio channel. + */ +#define CG2900_FM_RADIO_AUDIO "cg2900_fm_audio" + +/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled. + * Opening this channel forces the chip to stay powered. + * No data can be written to or read from this channel. + */ +#define CG2900_CORE "cg2900_core" + +struct cg2900_callbacks; + +/** + * struct cg2900_device - Device structure for CG2900 user. + * @h4_channel: HCI H:4 channel used by this device. + * @cb: Callback functions registered by this device. + * @logger_enabled: true if HCI logger is enabled for this channel, + * false otherwise. + * @user_data: Arbitrary data used by caller. + * @dev: Parent device this driver is connected to. + * + * Defines data needed to access an HCI channel. + */ +struct cg2900_device { + int h4_channel; + struct cg2900_callbacks *cb; + bool logger_enabled; + void *user_data; + struct device *dev; +}; + +/** + * struct cg2900_callbacks - Callback structure for CG2900 user. + * @read_cb: Callback function called when data is received from + * the connectivity controller. + * @reset_cb: Callback function called when the connectivity controller has + * been reset. + * + * Defines the callback functions provided from the caller. + */ +struct cg2900_callbacks { + void (*read_cb) (struct cg2900_device *dev, struct sk_buff *skb); + void (*reset_cb) (struct cg2900_device *dev); +}; + +/** + * struct cg2900_rev_data - Contains revision data for the local controller. + * @revision: Revision of the controller, e.g. to indicate that it is + * a CG2900 controller. + * @sub_version: Subversion of the controller, e.g. to indicate a certain + * tape-out of the controller. + * + * The values to match retrieved values to each controller may be retrieved from + * the manufacturer. + */ +struct cg2900_rev_data { + int revision; + int sub_version; +}; + +/** + * struct cg2900_platform_data - Contains platform data for CG2900. + * @init: Callback called upon system start. + * @exit: Callback called upon system shutdown. + * @enable_chip: Callback called for enabling CG2900 chip. + * @disable_chip: Callback called for disabling CG2900 chip. + * @set_hci_revision: Callback called when HCI revision has been detected. + * @get_power_switch_off_cmd: Callback called to retrieve + * HCI VS_Power_Switch_Off command (command + * HCI requires platform specific GPIO data). + * @bus: Transport used, see @include/net/bluetooth/hci.h. + * @cts_irq: Interrupt for the UART CTS pin. + * @enable_uart: Callback called when switching from UART GPIO to + * UART HW. + * @disable_uart: Callback called when switching from UART HW to + * UART GPIO. + * @uart: Platform data structure for UART transport. + * + * Any callback may be NULL if not needed. + */ +struct cg2900_platform_data { + int (*init)(void); + void (*exit)(void); + void (*enable_chip)(void); + void (*disable_chip)(void); + void (*set_hci_revision)(u8 hci_version, u16 hci_revision, + u8 lmp_version, u8 lmp_subversion, + u16 manufacturer); + struct sk_buff* (*get_power_switch_off_cmd)(u16 *op_code); + + __u8 bus; + + struct { + int cts_irq; + int (*enable_uart)(void); + int (*disable_uart)(void); + } uart; +}; + +/** + * struct cg2900_bt_platform_data - Contains platform data for CG2900 Bluetooth. + * @bus: Transport used, see @include/net/bluetooth/hci.h. + */ +struct cg2900_bt_platform_data { + __u8 bus; +}; + +extern struct cg2900_device *cg2900_register_user(char *name, + struct cg2900_callbacks *cb); +extern void cg2900_deregister_user(struct cg2900_device *dev); +extern int cg2900_reset(struct cg2900_device *dev); +extern struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority); +extern int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb); +extern bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data); + +#endif /* _CG2900_H_ */ -- 1.6.3.3