diff options
28 files changed, 9183 insertions, 10 deletions
diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index ebac3b087a9..08cef0c4124 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,9 +15,10 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ mac80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml media.xml \ - dma.xml gpio.xml i2s.xml i2c.xml u8500_usb.xml u8500_usb_dma.xml mmc.xml \ + dma.xml gpio.xml i2s.xml i2c.xml ste_conn.xml mmc.xml \ prcmu-fw-api.xml keypad.xml msp.xml stmpe1601.xml stmpe2401.xml \ - tc35892.xml touchp.xml isa_hsi.xml mcde.xml b2r2.xml shrm.xml + tc35892.xml + # u8500_usb_dma.xml isa_hsi.xml mcde.xml b2r2.xml shrm.xml ### # The build process is as follows (targets): diff --git a/Documentation/DocBook/ste_conn.tmpl b/Documentation/DocBook/ste_conn.tmpl new file mode 100755 index 00000000000..aa0861930d8 --- /dev/null +++ b/Documentation/DocBook/ste_conn.tmpl @@ -0,0 +1,1213 @@ +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []> + +<book id="STE-Connectivity-template"> + <bookinfo> + <title>ST-Ericsson Connectivity Driver</title> + + <authorgroup> + <author> + <firstname>Henrik</firstname> + <surname>Possung</surname> + <affiliation> + <address> + <email>henrik.possung@stericsson.com</email> + </address> + </affiliation> + </author> + <author> + <firstname>Par-Gunnar</firstname> + <surname>Hjalmdahl</surname> + <affiliation> + <address> + <email>par-gunnar.p.hjalmdahl@stericsson.com</email> + </address> + </affiliation> + </author> + </authorgroup> + + <copyright> + <year>2010</year> + <holder>ST-Ericsson AB</holder> + </copyright> + + <subjectset> + <subject> + <subjectterm>Connectivity</subjectterm> + </subject> + </subjectset> + + <legalnotice> + <!-- Do NOT remove the legal notice below --> + + <para> + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + </para> + + <para> + 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. + </para> + + <para> + 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 + </para> + + <para> + For more details see the file COPYING in the source + distribution of Linux. + </para> + </legalnotice> + </bookinfo> + + <toc></toc> + + <chapter id="intro"> + <title>Introduction</title> + <!-- Do NOT change the chapter id or title! --> + <para> + This documentation describes the functions provided by the ST-Ericsson Connectivity Driver for enabling + ST-Ericsson Connectivity Combo Controller Hardware. + + </para> + </chapter> + + <chapter id="gettingstarted"> + <title>Getting Started</title> + <!-- Do NOT change the chapter id or title! --> + <para> + There are no special compilation flags needed to build the STE connectivity driver. + </para> + <para> + There must be firmware and settings files that match the used chip version inside the firmware folder. + The files: + <itemizedlist> + <listitem><para>ste_conn_patch_info.fw.org</para></listitem> + <listitem><para>ste_conn_settings_info.fw.org</para></listitem> + </itemizedlist> + handle the mapping between chip version and correct firmware file (patch resp static settings file). + The necessary patch and settings files should be placed with the extension <constant>.fw.org.</constant>. + Note that there is a limitation in the Kernel firmware system regarding name length of a file. + </para> + <para> + The files: + <itemizedlist> + <listitem><para>ste_conn_devices.c</para></listitem> + <listitem><para>include/mach/ste_conn_devices.h</para></listitem> + </itemizedlist> + must exist inside the corresponding Board configuration folder, e.g. arch/arm/mach-stn8500. + </para> + + <!-- TODO: If the driver needs preparations to be used + (special compilation flags, files in the file system, + knowledge about a specific domain etc), specify it here. + Remove this chapter completely if there is nothing + to mention and there is no tutorial needed. + Do NOT change the chapter id or title! --> + <!-- TODO: This guideline for this chapter may be extended + during the user-guide guidelines drop. --> + + <section id="basic-tutorial"> + <title>Basic Tutorial</title> + <para> + To enable the ST-Ericsson connectivity driver using KConfig go to <constant>Device Drivers -> Multifunction Device Drivers</constant> + and enable the STE Connectivity Driver. If BlueZ shall be used as Bluetooth stack also enable the STE HCI Connectivity driver. + Depending on choice the driver will either be included as LKM or built into the Kernel. + If building as LKM, 2 files will be generated: + <itemizedlist> + <listitem><para>ste_conn.ko which contains the main driver</para></listitem> + <listitem><para>ste_conn_hci_driver.ko which contains the registration and mapping towards the BlueZ Bluetooth stack</para></listitem> + </itemizedlist> + + <!-- TODO: Provide a basic tutorial, outlining how + to test the presence of the driver, + for example how to configure, compile and run the + example(s). + Several sections with different tutorials, + all located within the Getting Started + chapter may be provided. --> + </para> + + <para> + <!-- TODO: This guideline for this section may be extended + during the user-guide guidelines drop. --> + </para> + </section> + + </chapter> + + <chapter id="concepts"> + <title>Concepts</title> + <!-- Do NOT change the chapter id or title! --> + <para> + The ST-Ericsson Connectivity driver works as a multiplexer between different users, such as a Bluetooth stack and a FM driver, + and the connectivity chip. The driver supports multiple physical transports, currently SPI and UART. + Apart from just transporting data between stacks and the chip, the ST-Ericsson Connectivity driver also deals with power handling, + powering up and down the chip and also downloading necessary patches and settings for the chip to start up properly. + <!-- TODO: A brief introduction about the concepts + which are introduced by the driver. + Remove this chapter completely if there are no + special concepts introduced by this driver. + Do NOT change the chapter id or title! --> + <!-- TODO: This guideline for this chapter may be extended + during the user-guide guidelines drop. --> + </para> + </chapter> + + <chapter id="tasks"> + <title>Tasks</title> + <!-- Do NOT change the chapter id or title! --> + + <para> + <variablelist> + <varlistentry> + <term>Opening a channel</term> + <listitem> + <para> + In order to be able to send and receive data on an H:4 channel, the user (i.e. respective stack) must open the channel. + Opening a channel will make it possible to send data to and receive data from the connectivity controller. + If the controller were earlier powered down, opening a channel will also cause the controller to be powered up. + When chip is powered up, patches and settings for the ARM subsystem will be downloaded as well. + Other IPs within the controller must however download each respective patches and settings. + If chip was already powered up when opening the channel no patch will be automatically downloaded. + + <variablelist> + <varlistentry> + <term>Opening a channel from Kernel space</term> + <listitem> + <para> + When a stack is placed in Kernel space, it shall open a channel by calling the API function <function>ste_conn_register</function>. + This function will search for the supplied channel by using name look-up and open the channel. + The function will return with a device reference that shall be used when calling the other STE_CONN API functions. + </para> + </listitem> + </varlistentry> + </variablelist> + + <variablelist> + <varlistentry> + <term>Opening a channel from User space</term> + <listitem> + <para> + When a stack is placed in User space, it shall open a channel by calling the syscall function <function>open</function> on the corresponding file. + The files are located in folder <filename>/dev/</filename> and are named <filename>ste_conn_gnss</filename> and similar. Each file + corresponds to one H:4 channel. + This function will open the channel. + </para> + </listitem> + </varlistentry> + </variablelist> + + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Closing a channel</term> + <listitem> + <para> + When a user, i.e. a stack has no need for a functionality, it should close the corresponding H:4 channel. + This is usually done when a user disables a certain feature, for example Bluetooth. The reason why the channels + need to be closed is that the ST-E connectivity driver will free the resources and also shutdown the controller if there are + no more active users of the chip. This will lower the power consumption thereby increasing battery life. + + <variablelist> + <varlistentry> + <term>Closing a channel from Kernel space</term> + <listitem> + <para> + When a stack is placed in Kernel space, it shall close a channel by calling the API function + <function>ste_conn_deregister</function>. + This function will close the channel and also free the allocated device that was allocated when registering. + </para> + </listitem> + </varlistentry> + </variablelist> + + <variablelist> + <varlistentry> + <term>Closing a channel from User space</term> + <listitem> + <para> + When a stack is placed in User space, it shall close a channel by calling the syscall function + <function>close</function> on the corresponding file. + </para> + </listitem> + </varlistentry> + </variablelist> + + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Writing to a channel</term> + <listitem> + <para> + When a stack (Bluetooth, FM, or GNSS) wants to send a packet it shall perform a write operation. + The packet shall not contain the H:4 header since this is added by the ST-E connectivity driver. + All other data in the packet shall however exist in the packet in the format correct for that HCI channel. + The ST-E connectivity driver does not perform any flow control over the H:4 channel so any ticket handling + or similar must be handled by respective stack. + + <variablelist> + <varlistentry> + <term>Writing to a channel from Kernel space</term> + <listitem> + <para> + When a stack is placed in Kernel space, it shall start with allocating a packet of the correct size using + <function>ste_conn_alloc_skb</function>. This function will return an sk_buff (Socket buffer) structure that + has necessary space reserved for ST-E driver operation. + The stack shall then copy the data, preferrably using <function>skb_put</function>, and then call + <function>ste_conn_write</function> to perform the write operation. When the function returns, the buffer has + been transferred and there is no need for the calling function to free the buffer. If the operation fails, i.e. + an error code is returned, the caller must however free the buffer. + </para> + </listitem> + </varlistentry> + </variablelist> + + <variablelist> + <varlistentry> + <term>Writing to a channel from User space</term> + <listitem> + <para> + When a stack is placed in User space, it shall call the <function>write</function> function on + the corresponding file to perform a transmit operation. After function returns the data has been + copied and is considered sent. + The caller does not need to preserve the data. + </para> + </listitem> + </varlistentry> + </variablelist> + + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Reading from a channel</term> + <listitem> + <para> + When a stack (Bluetooth, FM, or GNSS) wants to receive a packet it shall perform a receive operation. + The packet returned does not contain the H:4 header since this is removed by the ST-E connectivity driver. + All other data in the packet in the packet is in the format correct for that HCI channel. + The ST-E connectivity driver does not perform any flow control over the H:4 channel so any ticket handling + or similar must be handled by respective stack. + + <variablelist> + <varlistentry> + <term>Reading from a channel from Kernel space</term> + <listitem> + <para> + When a stack is placed in Kernel space, it has to supply a callback function for the receive functionality when calling + <function>ste_conn_register</function>. This callback function will be called when the ST-E connectivity driver has + received a packet. The packet received will always be a complete HCI packet, i.e. no fragmention on HCI layer. + When the packet has been received it is the responsability of the receiver to see to that the packet is freed using + <function>kfree_skb</function> when it is no more needed. + </para> + </listitem> + </varlistentry> + </variablelist> + + <variablelist> + <varlistentry> + <term>Reading from a channel from User space</term> + <listitem> + <para> + When a stack is placed in User space, it shall call the <function>read</function> function on + the corresponding file to perform a receive operation. This function will read as many bytes as there are present + up to the size of the supplied buffer. If no data is available the function will hang until data becomes available, reset + occurs, or the channel is closed. + For smooth operation it is recommended to use the <function>poll</function> functionality on the file, preferrably + from a dedicated thread. This way one thread can monitor both read and reset operations in one common thread while transmit + operations may continue unblocked in a separate thread. + </para> + </listitem> + </varlistentry> + </variablelist> + + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Reset handling</term> + <listitem> + <para> + The stacks shall always try to avoid performing Reset operations. The Reset will result in a hardware reset of the controller + and will therefore cause all existing links and settings to be lost. All stacks using the controller must also be informed + about the reset and handle it in a proper way. + The reset operation should only be used when there is no other option to get the controller into a working state, for example + if the controller has stopped answering to commands. + After the hardware reset, the ST-E connectivity driver will automatically perform deregister the channel so it has to be reopened again. + + <variablelist> + <varlistentry> + <term>Reset handling from Kernel space</term> + <listitem> + <para> + When a stack is placed in Kernel space, it initiates a Reset operation by calling <function>ste_conn_reset</function>. + This will trigger a hardware reset of the controller. When the hardware reset is finished all registered users will be called + through respective reset callback. When the callback function is finished the registered device will be removed and when all + registered users have been informed and removed, the chip is shutdown. This is similar to a deregistration of all registered + channels. The stack will then have to reregister to the ST-E connectivity driver in order to use the channel once again. + </para> + </listitem> + </varlistentry> + </variablelist> + + <variablelist> + <varlistentry> + <term>Reset handling from User space</term> + <listitem> + <para> + When a stack is placed in User space, it shall call the <function>ioctl</function> function on + the corresponding file to perform a reset operation. The command parameter <constant>STE_CONN_CHAR_DEV_IOCTL_RESET</constant> + shall be used when calling <function>ioctl</function>. + When the <function>ioctl</function> returns, the stack shall close the channel and then re-open it again. This must be done so + the channel is registered correctly in Kernel space. + For smooth operation it is recommended to use the <function>poll</function> functionality on the file, preferrably + from a dedicated thread. This way one thread can monitor both read and reset operations in one common thread while transmit + operations may continue unblocked in a separate thread. + </para> + </listitem> + </varlistentry> + </variablelist> + + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Example code Kernel space</term> + <listitem> + <para> + This example will register to the FM channel, write a packet, read a packet and then deregister. + + <programlisting> + struct ste_conn_device *my_dev; + bool event_received; + + void read_cb(struct ste_conn_device *dev, struct sk_buff *skb) + { + event_received = true; + kfree_skb(skb); + } + + void reset_cb(struct ste_conn_device *dev) + { + /* Handle reset. Device will be automatically freed by the ST-E driver */ + my_dev = NULL; + } + + static struct ste_conn_callbacks my_cb = { + .read_cb = read_cb, + .reset_cb = reset_cb + }; + + void example_open(void) + { + my_dev = ste_conn_register(STE_CONN_DEVICES_FM_RADIO, &my_cb); + if (!my_dev) { + printk("Error! Couldn't register!\n"); + } + } + + void example_close(void) + { + ste_conn_deregister(my_dev); + my_dev = NULL; + } + + void example_write_and_read(uint8_t *data, int len) + { + int err; + struct sk_buff *skb = ste_conn_alloc_skb(len, GFP_KERNEL); + + if (skb) { + memcpy(skb_put(skb, len), data, len); + err = ste_conn_write(my_dev, skb); + if (!err) { + event_received = false; + + while (!event_received) { + /* Wait for ack event. Received in read_cb() above */ + schedule_timeout_interruptible(jiffies + 50); + } + } else { + printk("Couldn't write to controller (%d)\n", err); + kfree_skb(skb); + } + } + } + </programlisting> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Example code User space</term> + <listitem> + <para> + This example will open the GNSS channel, write a packet, read a packet and then close the channel. + In this example all functions are performed in the same thread. + It is however adviced to perform <function>read</function> and <function>ioctl</function> read through a separate thread, + preferrably using <function>poll</function>. + + <programlisting> + struct my_info_t { + int fd; + }; + + static struct my_info_t my_info; + + /* This is a fake command and has nothing to do with real GNSS commands. + * Note that the command does NOT contain the H:4 header. + * The header is added by the ST-E Connectivity driver. + */ + static const uint8_t tx_cmd[] = {0x12, 0x34, 0x56}; + + int main(int argc, char **argv) + { + uint8_t rx_buffer[100]; + int rx_bytes = 0; + int err; + + my_info.fd = open("/dev/ste_conn_gnss", O_RDWR); + if (my_info.fd < 0) { + printf("Error on open file: %d (%s)\n", errno, strerror(errno)); + return errno; + } + if (0 > write(my_info.fd, tx_cmd, sizeof(tx_cmd))) { + printf("Error on write file: %d (%s)\n", errno, strerror(errno)); + return errno; + } + /* Read will sleep until there is data available */ + rx_bytes = read(my_info.fd, rx_buffer, 100); + if (rx_bytes >= 0) { + printf("Received %d bytes\n", rx_bytes); + } else { + printf("Error on read file: %d (%s)\n", errno, strerror(errno)); + return errno; + } + err = close(my_info.fd); + if (err) { + printf("Error on close file: %d (%s)\n", errno, strerror(errno)); + return errno; + } + return 0; + } + </programlisting> + </para> + </listitem> + </varlistentry> + </variablelist> + + <!-- TODO: Task descriptions are step by step instructions + for performing specific actions and tasks. + Each task is typically one scenario. + Each task is described in a separate (section). + (section) tags can be nested, which is + especially recommended if + the task consists of several scenarios. + Remove this chapter completely if there are no + tasks to mention and there is no tutorial needed. + Do NOT change the chapter id or title! --> + <!-- TODO: This guideline for this chapter may be extended + during the user-guide guidelines drop. --> + </para> + </chapter> + + <chapter id="driver-configuration"> + <title>Driver Configuration and Interaction</title> + <!-- Do NOT change the chapter id or title! --> + <para> + For debug purposes the define STE_CONN_DEBUG_LEVEL in the file ste_conn_debug.h can be changed to set how much debug printouts + that shall be generated. + <itemizedlist> + <listitem><para>0 - No debug</para></listitem> + <listitem><para>1 - Error printouts</para></listitem> + <listitem><para>10 - Info printouts such as start of each function</para></listitem> + <listitem><para>20 - Debug printouts such as descriptions of operations</para></listitem> + <listitem><para>25 - Data printouts without content</para></listitem> + <listitem><para>30 - Data printouts with content</para></listitem> + </itemizedlist> + <!-- TODO: Use this paragraph as an introduction to driver + configuration and interaction. Describe the big picture. --> + <!-- TODO: This chapter contains driver specific way to perform + configuration and interaction. The chapter includes a + number of sections. They should not be removed and if + the driver does not have the specific support for + configuration or interaction should the text "not + applicable" be inserted. Do NOT change the chapter id + or title! --> + <!-- TODO: This guideline for this chapter may be extended + during the user-guide guidelines drop. --> + </para> + + <section id="driver-implemented-operations"> + <title>Implemented operations in driver</title> + <para> + <!-- TODO: Describe the actual usage of the driver. Specify the actual + implemented operations in struct <structname>file_operations</structname> + and any other set of operations. Create a table with two columns + (see example in intro chapter how to create a table). + Column one list all operations supported (read, + write, open, close, ioctl etc) and column two a description of the + semantics of the operations in the specific context of the device + driver from the users perspective. Document the operations in a way + that a user of the driver can be helped. --> + </para> + <para> + <table> + <title> Supported device driver operations when using character device </title> + <tgroup cols="2"><tbody> + <row><entry> open </entry> <entry> Opening a character device will register the caller to that HCI channel.</entry> </row> + <row><entry> release </entry> <entry> Releasing a character device will deregister the caller from that HCI channel</entry> </row> + <row><entry> poll </entry> <entry> Polling a character device will check if there is data to read on that HCI channel</entry> </row> + <row><entry> read </entry> <entry> Reading from a character device reads from that HCI channel</entry> </row> + <row><entry> write </entry> <entry> Writing to a character device writes to that HCI channel</entry> </row> + <row><entry> unlocked_ioctl </entry> <entry> Performing IO control on a character device will perform special operations such as reset on that HCI channel</entry> </row> + </tbody></tgroup> + </table> + </para> + </section> + + <section id="driver-loading"> + <title>Driver loading parameters</title> + <para> + <!-- TODO: Describe parameters that can be specified at kernel + driver loading with insmod or modprobe. If the driver + has no parameters to be specified at load time, replace this + text with "Not Applicable". --> + </para> + <variablelist> + <varlistentry> + <term>char_dev_usage</term> + <listitem> + <para> + <variablelist> + <varlistentry> + <term>Parameter type</term> + <listitem><synopsis><type>int</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Default value</term> + <listitem><para>0x44</para></listitem> + </varlistentry> + <varlistentry> + <term>Runtime readable/modifiable</term> + <listitem><para>Readable</para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The parameter char_dev_usage in ste_conn_ccd.c can be set to control which HCI channels that can be accessed as character devices. + The supported commands for a character device is listed in Table 5.1. Each channel that is not setup as a character device can + instead be accessed through direct API commands, see ste_conn.h. + The char_dev_usage parameter is a bitmask with the following values: + <itemizedlist> + <listitem><para>0x00 = No char devs</para></listitem> + <listitem><para>0x01 = BT (all 3 Bluetooth channels)</para></listitem> + <listitem><para>0x02 = FM radio</para></listitem> + <listitem><para>0x04 = GNSS</para></listitem> + <listitem><para>0x08 = Debug (STE chip internal debug)</para></listitem> + <listitem><para>0x10 = STE tools (channel for STE tools)</para></listitem> + <listitem><para>0x20 = CCD debug (used for debug of the CCD module)</para></listitem> + <listitem><para>0x40 = HCI Logger (copies all data transported through the driver)</para></listitem> + <listitem><para>0x80 = STE_CONN test (character device stub used for testing the driver)</para></listitem> + </itemizedlist> + Default value is GNSS and HCI logger as character devices, i.e. char_dev_usage=0x44. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>uart_default_baud</term> + <listitem> + <para> + <variablelist> + <varlistentry> + <term>Parameter type</term> + <listitem><synopsis><type>int</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Default value</term> + <listitem><para>115200</para></listitem> + </varlistentry> + <varlistentry> + <term>Runtime readable/modifiable</term> + <listitem><para>Readable</para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The parameter uart_default_baud in ste_conn_ccd.c defines the baud rate used after a chip has just been powered up. + It shall be set to the default baud rate of the controller. + For ST-Ericsson controllers STLC2690 and CG2900 this value shall be 115200. + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>uart_high_baud</term> + <listitem> + <para> + <variablelist> + <varlistentry> + <term>Parameter type</term> + <listitem><synopsis><type>int</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Default value</term> + <listitem><para>3000000</para></listitem> + </varlistentry> + <varlistentry> + <term>Runtime readable/modifiable</term> + <listitem><para>Modifiable</para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The parameter uart_high_baud in ste_conn_ccd.c defines the baud rate to use for normal data transfer. + This should normally be the highest allowed by the system with regards to flow control, clocks, etc. + For ST-Ericsson controllers STLC2690 and CG2900 the following values are supported: + <itemizedlist> + <listitem><para>57600</para></listitem> + <listitem><para>115200</para></listitem> + <listitem><para>230400</para></listitem> + <listitem><para>460800</para></listitem> + <listitem><para>921600</para></listitem> + <listitem><para>2000000</para></listitem> + <listitem><para>3000000</para></listitem> + <listitem><para>4000000</para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>ste_debug_level</term> + <listitem> + <para> + <variablelist> + <varlistentry> + <term>Parameter type</term> + <listitem><synopsis><type>int</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Default value</term> + <listitem><para>1</para></listitem> + </varlistentry> + <varlistentry> + <term>Runtime readable/modifiable</term> + <listitem><para>Modifiable</para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The parameter ste_debug_level in ste_conn_ccd.c defines the debug level that is currently used. + The higher the debug level the more print-outs are received in the terminal window. + The following values are supported: + <itemizedlist> + <listitem><para>0 = No debug</para></listitem> + <listitem><para>1 = Error prints</para></listitem> + <listitem><para>10 = General info, e.g. function entries</para></listitem> + <listitem><para>20 = Debug info, e.g. steps in a functionality</para></listitem> + <listitem><para>25 = Data info, i.e. prints when data is transferred</para></listitem> + <listitem><para>30 = Data content, i.e. contents of the transferred data</para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </listitem> + </varlistentry> + </variablelist> + <para> + <!-- TODO: This guideline for this section may be extended + during the user-guide guidelines drop. --> + </para> + </section> + + <section id="driver-ioctl"> + <title>Driver IO Control</title> + <para> + <!-- TODO: Describe driver parameters that can be modified + in runtime. Make a list of all device-dependent request code with + description of arguments, meaning etc. If the driver has no IO control + interface, replace this text with "Not Applicable". --> + </para> + <variablelist> + <varlistentry> + <term><constant>STE_CONN_CHAR_DEV_IOCTL_RESET</constant></term> + <listitem> + <variablelist> + <varlistentry> + <term>Direction</term> + <listitem><para>Set</para></listitem> + </varlistentry> + <varlistentry> + <term>Parameter</term> + <listitem><synopsis><type>void</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The <constant>STE_CONN_CHAR_DEV_IOCTL_RESET</constant> IOCTL starts a reset + of the connectivity chip. This will affect the current open channel and + all other open channels as well. + </para><para> + IOCTL value created using <constant>_IOW('U', 210, int)</constant>. + </para><para> + Returned values are: + <itemizedlist> + <listitem><para>If reset is performed without errors the IOCTL function will return 0.</para></listitem> + <listitem><para>A negative value will indicate error.</para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>STE_CONN_CHAR_DEV_IOCTL_WAIT4RESET</constant></term> + <listitem> + <variablelist> + <varlistentry> + <term>Direction</term> + <listitem><para>Get</para></listitem> + </varlistentry> + <varlistentry> + <term>Parameter</term> + <listitem><synopsis><type>void</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The <constant>STE_CONN_CHAR_DEV_IOCTL_WAIT4RESET</constant> IOCTL tells the IOCTL functions to wait + until a reset has occurred or the character device is closed. + </para><para> + IOCTL value created using <constant>_IOW('U', 211, int)</constant>. + </para><para> + Returned values are: + <itemizedlist> + <listitem><para>If reset has occurred or device has been closed the IOCTL function will return 0.</para></listitem> + <listitem><para>A negative value will indicate error.</para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term><constant>STE_CONN_CHAR_DEV_IOCTL_CHECK4RESET</constant></term> + <listitem> + <variablelist> + <varlistentry> + <term>Direction</term> + <listitem><para>Query</para></listitem> + </varlistentry> + <varlistentry> + <term>Parameter</term> + <listitem><synopsis><type>void</type></synopsis></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para> + The <constant>STE_CONN_CHAR_DEV_IOCTL_CHECK4RESET</constant> IOCTL checks if a reset + has been performed on a device. + </para><para> + IOCTL value created using <constant>_IOW('U', 212, int)</constant>. + </para><para> + Returned values are: + <itemizedlist> + <listitem><para>If device is still open the IOCTL function will return 0.</para></listitem> + <listitem><para>If reset has occurred the IOCTL function will return 1.</para></listitem> + <listitem><para>If device has been closed the IOCTL function will return 2.</para></listitem> + <listitem><para>A negative value will indicate error.</para></listitem> + </itemizedlist> + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + </variablelist> + </section> + + <section id="driver-sysfs"> + <title>Driver Interaction with Sysfs</title> + <para> + <!-- TODO: Describe data available for read and write on the drivers + Sysfs entry. Specify where the entry for the device is located in + Sysfs such as <filename>/sys/devices/*</filename>, <filename>/sys/devices/*</filename> + , etc. + Specify the data types for the attributes. Specify if the + attributes are read-only or write-only. If the driver has no Sysfs + interface, replace this text with "Not Applicable". --> + Not Applicable + </para> + </section> + + <section id="driver-proc"> + <title>Driver Interaction using /proc filesystem</title> + <para> + Not Applicable + <!-- TODO: Describe data available for read and write on the drivers + /proc entry. Specify where the entry for the device is located. + Specify the data types for the attributes. Specify if the + attributes are read-only or writeonly. If the driver has no /proc + interface, replace this text with "Not Applicable". --> + </para> + </section> + + <section id="driver-other"> + <title>Other means for Driver Interaction</title> + <para> + <!-- TODO: Does the driver have any configurations files? Describe other means + for driver status access or configuration. If the driver has no other + means (besides the one in already described in this chapter), replace + this text with "Not Applicable". --> + Not Applicable + </para> + </section> + + <section id="driver-node"> + <title>Driver Node File</title> + <variablelist> + <varlistentry> + <term>STE_CONN main device</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_driver0</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_driver represents the main parent node for all other character devices supplied in the ST-Ericsson connectivity driver except for the CCD Test device. It does not support any operations such as read or write.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>BT Command</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_bt_cmd</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_bt_cmd is the device for the HCI Bluetooth command channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>BT ACL</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_bt_acl</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_bt_acl is the device for the HCI Bluetooth ACL channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>BT Event</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_bt_evt</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_bt_evt is the device for the HCI Bluetooth event channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>FM Radio</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_fm_radio</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_fm_radio is the device for the HCI FM Radio channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>GNSS</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_gnss</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_gnss is the device for the HCI GNSS channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>Debug</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_debug</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_debug is the device for the HCI Debug channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>ST-Ericsson Tools</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_ste_tools</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_ste_tools is the device for the HCI ST-Ericsson tools channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>HCI Logger</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_hci_logger</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_hci_logger is the device for the HCI logger channel.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>User Space Control</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_user_space_control</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_user_space_control is the device for initialization and control of the STE CONN driver.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + <varlistentry> + <term>CCD Test stub</term> + <listitem> + <variablelist> + <varlistentry> + <term>File</term> + <listitem><para><filename>/dev/ste_conn_ccd_test</filename></para></listitem> + </varlistentry> + <varlistentry> + <term>Description</term> + <listitem> + <para>The ste_conn_ccd_test is the device for performing module tests of the ST-Ericsson connectivity driver. It acts as a stub replacing the transport towards the chip. It is of the type Misc devices.</para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + + </variablelist> + </section> + + + </chapter> + + + <chapter id="bugs"> + <title>Known Bugs And Assumptions</title> + <!-- Do NOT change the chapter id or title! --> + <para> + <variablelist> + <varlistentry> + <term>Driver supports only one user per HCI channel.</term> + <listitem> + <para> + To simplify design and limitation as well as keeping the API simple and reliable, the driver only supports one user per HCI channel. + <!-- TODO: Briefly describe the limitation, unless all + information is already present in the title. + Use full english sentences. + Repeat the varlistentry for each limitation. + If none are known, replace this varlistentry + with the one below. --> + <!-- TODO: This guideline for this chapter may be extended + during the user-guide guidelines drop. --> + </para> + </listitem> + </varlistentry> + </variablelist> + </para> + </chapter> + +<chapter id="pubfunctions"> + <title>Public Functions Provided</title> + <para> + List of public functions. + </para> + <!-- Do NOT change the chapter id or title! --> + <!-- TODO: Replace with link to appropriate headerfile(s). + One per row, ensure the + exclamation mark is on the first column! If no + appropriate header file exist describing a public interface, + replace the inclusion with a paragraph containing the text + "Not Applicable" --> + <section id="ste_conn.h"> + <title>ste_conn.h</title> +!Einclude/linux/mfd/ste_conn.h +!Iinclude/linux/mfd/ste_conn.h + </section> + +</chapter> + +<chapter id="internal-functions"> + <title>Internal Functions Provided</title> + <para> + List of internal functions. + </para> + <!-- Do NOT change the chapter id or title! --> + <!-- TODO: Replace with link to appropriate headerfile(s), + source file(s), or both. One per row, ensure the + exclamation mark is on the first column! If no + appropriate header or source file exist describing a public interface, + replace the inclusion with a paragraph containing the text + "Not Applicable"--> + <section id="ste_conn_cpd.h"> + <title>ste_conn_cpd.h</title> +!Idrivers/mfd/ste_conn/ste_conn_cpd.h + </section> + <section id="ste_conn_cpd.c"> + <title>ste_conn_cpd.c</title> +!Idrivers/mfd/ste_conn/ste_conn_cpd.c + </section> + <section id="ste_conn_ccd.h"> + <title>ste_conn_ccd.h</title> +!Idrivers/mfd/ste_conn/ste_conn_ccd.h + </section> + <section id="ste_conn_ccd.c"> + <title>ste_conn_ccd.c</title> +!Idrivers/mfd/ste_conn/ste_conn_ccd.c + </section> + <section id="ste_conn_char_devices.h"> + <title>ste_conn_char_devices.h</title> +!Idrivers/mfd/ste_conn/ste_conn_char_devices.h + </section> + <section id="ste_conn_char_devices.c"> + <title>ste_conn_char_devices.c</title> +!Idrivers/mfd/ste_conn/ste_conn_char_devices.c + </section> + <section id="ste_conn_hci_driver.c"> + <title>ste_conn_hci_driver.c</title> +!Idrivers/mfd/ste_conn/ste_conn_hci_driver.c + </section> + <section id="ste_conn_devices.h"> + <title>ste_conn_devices.h</title> +!Earch/arm/mach-u8500/include/mach/ste_conn_devices.h + </section> + <section id="ste_conn_devices.c"> + <title>ste_conn_devices.c</title> +!Iarch/arm/mach-u8500/ste_conn_devices.c + </section> +</chapter> + +</book> diff --git a/arch/arm/configs/mop500_defconfig b/arch/arm/configs/mop500_defconfig index b43a2d27fb7..a00ac77dbf7 100644 --- a/arch/arm/configs/mop500_defconfig +++ b/arch/arm/configs/mop500_defconfig @@ -33,7 +33,7 @@ CONFIG_EXPERIMENTAL=y CONFIG_LOCK_KERNEL=y CONFIG_INIT_ENV_ARG_LIMIT=32 CONFIG_LOCALVERSION="" -CONFIG_LOCALVERSION_AUTO=y +# CONFIG_LOCALVERSION_AUTO is not set # CONFIG_SWAP is not set CONFIG_SYSVIPC=y CONFIG_SYSVIPC_SYSCTL=y @@ -475,7 +475,24 @@ CONFIG_NET_SCH_FIFO=y # CONFIG_HAMRADIO is not set # CONFIG_CAN is not set # CONFIG_IRDA is not set -# CONFIG_BT is not set +CONFIG_BT=y +CONFIG_BT_L2CAP=y +CONFIG_BT_SCO=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +# CONFIG_BT_BNEP is not set +CONFIG_BT_HIDP=y + +# +# Bluetooth device drivers +# +# CONFIG_BT_HCIBTUSB is not set +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set # CONFIG_AF_RXRPC is not set CONFIG_PHONET=y CONFIG_WIRELESS=y @@ -501,7 +518,10 @@ CONFIG_MAC80211_LEDS=y # CONFIG_MAC80211_DEBUGFS is not set # CONFIG_MAC80211_DEBUG_MENU is not set # CONFIG_WIMAX is not set -# CONFIG_RFKILL is not set +CONFIG_RFKILL=y +CONFIG_RFKILL_PM=y +CONFIG_RFKILL_INPUT=y +CONFIG_RFKILL_LEDS=y # CONFIG_NET_9P is not set # @@ -702,9 +722,17 @@ CONFIG_U8500_TSC=y CONFIG_U8500_TSC_SINGLETOUCH=y # CONFIG_U8500_TSC_MULTITOUCH is not set # CONFIG_TOUCHP_EXT_CLK is not set -# CONFIG_INPUT_MISC is not set +CONFIG_INPUT_MISC=y +# CONFIG_INPUT_ATI_REMOTE is not set +# CONFIG_INPUT_ATI_REMOTE2 is not set +# CONFIG_INPUT_KEYSPAN_REMOTE is not set +# CONFIG_INPUT_POWERMATE is not set +# CONFIG_INPUT_YEALINK is not set +# CONFIG_INPUT_CM109 is not set +CONFIG_INPUT_UINPUT=y +# CONFIG_INPUT_GPIO is not set +# CONFIG_INPUT_KEYCHORD is not set -# # Hardware I/O ports # CONFIG_SERIO=y @@ -872,6 +900,8 @@ CONFIG_SSB_POSSIBLE=y # CONFIG_MFD_TMIO is not set # CONFIG_MFD_T7L66XB is not set # CONFIG_MFD_TC6387XB is not set +CONFIG_MFD_STE_CONN=y +CONFIG_MFD_STE_CONN_BLUEZ=y # CONFIG_MFD_TC6393XB is not set CONFIG_MFD_AB3550_CORE=y # CONFIG_PMIC_DA903X is not set @@ -956,6 +986,7 @@ CONFIG_LOGO_LINUX_CLUT224=y # CONFIG_SOUND is not set # CONFIG_U8500_ACODEC is not set # CONFIG_HID_SUPPORT is not set +CONFIG_HID=y CONFIG_USB_SUPPORT=y CONFIG_USB_ARCH_HAS_HCD=y # CONFIG_USB_ARCH_HAS_OHCI is not set diff --git a/arch/arm/configs/mop500_ed_defconfig b/arch/arm/configs/mop500_ed_defconfig index 15310212b5a..35529c7262e 100644 --- a/arch/arm/configs/mop500_ed_defconfig +++ b/arch/arm/configs/mop500_ed_defconfig @@ -33,7 +33,7 @@ CONFIG_EXPERIMENTAL=y CONFIG_LOCK_KERNEL=y CONFIG_INIT_ENV_ARG_LIMIT=32 CONFIG_LOCALVERSION="" -CONFIG_LOCALVERSION_AUTO=y +# CONFIG_LOCALVERSION_AUTO is not set # CONFIG_SWAP is not set CONFIG_SYSVIPC=y CONFIG_SYSVIPC_SYSCTL=y @@ -457,7 +457,24 @@ CONFIG_NET_SCH_FIFO=y # CONFIG_HAMRADIO is not set # CONFIG_CAN is not set # CONFIG_IRDA is not set -# CONFIG_BT is not set +CONFIG_BT=y +CONFIG_BT_L2CAP=y +CONFIG_BT_SCO=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +# CONFIG_BT_BNEP is not set +CONFIG_BT_HIDP=y + +# +# Bluetooth device drivers +# +# CONFIG_BT_HCIBTUSB is not set +# CONFIG_BT_HCIBTSDIO is not set +# CONFIG_BT_HCIUART is not set +# CONFIG_BT_HCIBCM203X is not set +# CONFIG_BT_HCIBPA10X is not set +# CONFIG_BT_HCIBFUSB is not set +# CONFIG_BT_HCIVHCI is not set # CONFIG_AF_RXRPC is not set # CONFIG_PHONET is not set CONFIG_WIRELESS=y @@ -483,7 +500,10 @@ CONFIG_MAC80211_LEDS=y # CONFIG_MAC80211_DEBUGFS is not set # CONFIG_MAC80211_DEBUG_MENU is not set # CONFIG_WIMAX is not set -# CONFIG_RFKILL is not set +CONFIG_RFKILL=y +CONFIG_RFKILL_PM=y +CONFIG_RFKILL_INPUT=y +CONFIG_RFKILL_LEDS=y # CONFIG_NET_9P is not set # @@ -541,6 +561,7 @@ CONFIG_ANDROID_PMEM=y # CONFIG_ENCLOSURE_SERVICES is not set # CONFIG_KERNEL_DEBUGGER_CORE is not set # CONFIG_UID_STAT is not set +# CONFIG_WL127X_RFKILL is not set # CONFIG_APANIC is not set CONFIG_APANIC_PLABEL="kpanic" CONFIG_AB8500=y @@ -849,6 +870,8 @@ CONFIG_SSB_POSSIBLE=y # CONFIG_MFD_TMIO is not set # CONFIG_MFD_T7L66XB is not set # CONFIG_MFD_TC6387XB is not set +CONFIG_MFD_STE_CONN=y +CONFIG_MFD_STE_CONN_BLUEZ=y # CONFIG_MFD_TC6393XB is not set # CONFIG_MFD_AB3550_CORE is not set # CONFIG_PMIC_DA903X is not set @@ -933,6 +956,7 @@ CONFIG_LOGO_LINUX_CLUT224=y # CONFIG_SOUND is not set # CONFIG_U8500_ACODEC is not set # CONFIG_HID_SUPPORT is not set +CONFIG_HID=y CONFIG_USB_SUPPORT=y CONFIG_USB_ARCH_HAS_HCD=y # CONFIG_USB_ARCH_HAS_OHCI is not set diff --git a/arch/arm/mach-u8500/Makefile b/arch/arm/mach-u8500/Makefile index a2817348c2d..59bf1aa34b1 100644 --- a/arch/arm/mach-u8500/Makefile +++ b/arch/arm/mach-u8500/Makefile @@ -22,6 +22,12 @@ obj-$(CONFIG_U8500_CPUIDLE) += cpuidle.o obj-$(CONFIG_U8500_CPUFREQ) += cpufreq.o obj-$(CONFIG_PM) += pm.o +ifeq ($(CONFIG_MFD_STE_CONN), m) +obj-y += ste_conn_devices.o +else +obj-$(CONFIG_MFD_STE_CONN) += ste_conn_devices.o +endif + PDEV_HEADER = arch/arm/mach-u8500/include/mach/board_devices.h $(TOPDIR)/$(PDEV_HEADER): diff --git a/arch/arm/mach-u8500/include/mach/ste_conn_devices.h b/arch/arm/mach-u8500/include/mach/ste_conn_devices.h new file mode 100755 index 00000000000..301476a7133 --- /dev/null +++ b/arch/arm/mach-u8500/include/mach/ste_conn_devices.h @@ -0,0 +1,218 @@ +/* + * file ste_conn_devices.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Board specific device support for the Linux Bluetooth HCI H4 Driver + * for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_DEVICES_H_ +#define _STE_CONN_DEVICES_H_ + +#include <linux/types.h> +#include <linux/skbuff.h> + +#define STE_CONN_MAX_NAME_SIZE 30 + +/** STE_CONN_DEVICES_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_BT_CMD "ste_conn_bt_cmd\0" + +/** STE_CONN_DEVICES_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_BT_ACL "ste_conn_bt_acl\0" + +/** STE_CONN_DEVICES_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_BT_EVT "ste_conn_bt_evt\0" + +/** STE_CONN_DEVICES_FM_RADIO - Bluetooth HCI H4 channel for FM radio in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_FM_RADIO "ste_conn_fm_radio\0" + +/** STE_CONN_DEVICES_GNSS - Bluetooth HCI H4 channel for GNSS in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_GNSS "ste_conn_gnss\0" + +/** STE_CONN_DEVICES_DEBUG - Bluetooth HCI H4 channel for internal debug data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_DEBUG "ste_conn_debug\0" + +/** STE_CONN_DEVICES_STE_TOOLS - Bluetooth HCI H4 channel for development tools data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_STE_TOOLS "ste_conn_ste_tools\0" + +/** STE_CONN_DEVICES_HCI_LOGGER - Bluetooth HCI H4 channel for logging all transmitted H4 packets (on all channels). + */ +#define STE_CONN_DEVICES_HCI_LOGGER "ste_conn_hci_logger\0" + +/** STE_CONN_DEVICES_US_CTRL - Bluetooth HCI H:4 channel + * for user space initialization and control of the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_US_CTRL "ste_conn_us_ctrl\0" + +/** STE_CONN_DEVICES_BT_AUDIO - Bluetooth HCI H:4 channel + * for Bluetooth audio configuration related commands in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_BT_AUDIO "ste_conn_bt_audio\0" + +/** STE_CONN_DEVICES_FM_RADIO_AUDIO - HCI H:4 channel + * for FM audio configuration related commands in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_DEVICES_FM_RADIO_AUDIO "ste_conn_fm_audio\0" + +/** STE_CONN_NO_CHAR_DEV - Module parameter for + * no character devices to be initiated. + */ +#define STE_CONN_NO_CHAR_DEV 0x00000000 + +/** STE_CONN_CHAR_DEV_BT - Module parameter for character device Bluetooth. + */ +#define STE_CONN_CHAR_DEV_BT 0x00000001 + +/** STE_CONN_CHAR_DEV_FM_RADIO - Module parameter for character device FM radio. + */ +#define STE_CONN_CHAR_DEV_FM_RADIO 0x00000002 + +/** STE_CONN_CHAR_DEV_GNSS - Module parameter for character device GNSS. + */ +#define STE_CONN_CHAR_DEV_GNSS 0x00000004 + +/** STE_CONN_CHAR_DEV_DEBUG - Module parameter for character device Debug. + */ +#define STE_CONN_CHAR_DEV_DEBUG 0x00000008 + +/** STE_CONN_CHAR_DEV_STE_TOOLS - Module parameter for character device STE tools. + */ +#define STE_CONN_CHAR_DEV_STE_TOOLS 0x00000010 + +/** STE_CONN_CHAR_DEV_CCD_DEBUG - Module parameter for character device CCD debug. + */ +#define STE_CONN_CHAR_DEV_CCD_DEBUG 0x00000020 + +/** STE_CONN_CHAR_DEV_HCI_LOGGER - Module parameter for character device HCI logger. + */ +#define STE_CONN_CHAR_DEV_HCI_LOGGER 0x00000040 + +/** STE_CONN_CHAR_DEV_US_CTRL - Module parameter for + * character device user space control. + */ +#define STE_CONN_CHAR_DEV_US_CTRL 0x00000080 + +/** STE_CONN_CHAR_TEST_DEV - Module parameter for character device test device. + */ +#define STE_CONN_CHAR_TEST_DEV 0x00000100 + +/** STE_CONN_CHAR_DEV_BT_AUDIO - Module parameter for character device BT CMD audio application. + */ +#define STE_CONN_CHAR_DEV_BT_AUDIO 0x00000200 + +/** STE_CONN_CHAR_DEV_FM_RADIO_AUDIO - Module parameter for character device FM audio application. + */ +#define STE_CONN_CHAR_DEV_FM_RADIO_AUDIO 0x00000400 + +/** STE_CONN_CHAR_TEST_DEV - Module parameter for all character devices to be initiated. + */ +#define STE_CONN_ALL_CHAR_DEVS 0xFFFFFFFF + +/** + * ste_conn_devices_get_h4_channel() - Get H4 channel by name. + * @name: Name of connectivity user. + * @h4_channel: HCI H4 channel to register to. + * + * Returns: + * 0 if there is no error. + * -ENXIO if channel was not found. + */ +extern int ste_conn_devices_get_h4_channel(char *name, int *h4_channel); + +/** + * ste_conn_devices_enable_chip() - Enable the controller. + */ +extern void ste_conn_devices_enable_chip(void); + +/** + * ste_conn_devices_disable_chip() - Disable the controller. + */ +extern void ste_conn_devices_disable_chip(void); + +/** + * ste_conn_devices_set_hci_revision() - Stores HCI revision info for the connected connectivity controller. + * @hci_version: HCI version from the controller. + * @hci_revision: HCI revision from the controller. + * @lmp_version: LMP version from the controller. + * @lmp_subversion: LMP subversion from the controller. + * @manufacturer: Manufacturer ID from the controller. + * + * See Bluetooth specification and white paper for used controller for details about parameters. + */ +extern void ste_conn_devices_set_hci_revision(uint8_t hci_version, + uint16_t hci_revision, + uint8_t lmp_version, + uint8_t lmp_subversion, + uint16_t manufacturer); + +/** + * ste_conn_devices_get_reset_cmd() - Get HCI reset command to use based on connected connectivity controller. + * @op_lsb: LSB of HCI opcode in generated packet. NULL if not needed. + * @op_msb: MSB of HCI opcode in generated packet. NULL if not needed. + * + * This command does not add the H4 channel header in front of the message. + * + * Returns: + * NULL if no command shall be sent, + * sk_buffer with command otherwise. + */ +extern struct sk_buff *ste_conn_devices_get_reset_cmd(uint8_t *op_lsb, uint8_t *op_msb); + +/** + * ste_conn_devices_get_power_switch_off_cmd() - Get HCI power switch off command to use based on connected connectivity controller. + * @op_lsb: LSB of HCI opcode in generated packet. NULL if not needed. + * @op_msb: MSB of HCI opcode in generated packet. NULL if not needed. + * + * This command does not add the H4 channel header in front of the message. + * + * Returns: + * NULL if no command shall be sent, + * sk_buffer with command otherwise. + */ +extern struct sk_buff *ste_conn_devices_get_power_switch_off_cmd(uint8_t *op_lsb, uint8_t *op_msb); + +/** + * ste_conn_devices_get_bt_enable_cmd() - Get HCI BT enable command to use based on connected connectivity controller. + * @op_lsb: LSB of HCI opcode in generated packet. NULL if not needed. + * @op_msb: MSB of HCI opcode in generated packet. NULL if not needed. + * @bt_enable: true if Bluetooth IP shall be enabled, false otherwise. + * + * This command does not add the H4 channel header in front of the message. + * + * Returns: + * NULL if no command shall be sent, + * sk_buffer with command otherwise. + */ +extern struct sk_buff *ste_conn_devices_get_bt_enable_cmd(uint8_t *op_lsb, uint8_t *op_msb, bool bt_enable); + +/** + * ste_conn_devices_init() - Initialize the board config. + * + * Returns: + * 0 if there is no error. + * Error codes from gpio_request and gpio_direction_output. + */ +extern int ste_conn_devices_init(void); + +/** + * ste_conn_devices_exit() - Exit function for the board config. + */ +extern void ste_conn_devices_exit(void); + +#endif /* _STE_CONN_DEVICES_H_ */ diff --git a/arch/arm/mach-u8500/ste_conn_devices.c b/arch/arm/mach-u8500/ste_conn_devices.c new file mode 100755 index 00000000000..8731b51c7ce --- /dev/null +++ b/arch/arm/mach-u8500/ste_conn_devices.c @@ -0,0 +1,381 @@ +/* + * file ste_conn_devices.c + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Board specific device support for the Linux Bluetooth HCI H:4 Driver + * for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#include <linux/string.h> +#include <linux/types.h> +#include <linux/skbuff.h> +#include <asm-generic/errno-base.h> +#include <mach/ste_conn_devices.h> +#include <linux/module.h> +#include <linux/gpio.h> + +/** STE_CONN_H4_CHANNEL_BT_CMD - Bluetooth HCI H:4 channel + * for Bluetooth commands in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_BT_CMD 0x01 + +/** STE_CONN_H4_CHANNEL_BT_ACL - Bluetooth HCI H:4 channel + * for Bluetooth ACL data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_BT_ACL 0x02 + +/** STE_CONN_H4_CHANNEL_BT_EVT - Bluetooth HCI H:4 channel + * for Bluetooth events in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_BT_EVT 0x04 + +/** STE_CONN_H4_CHANNEL_FM_RADIO - Bluetooth HCI H:4 channel + * for FM radio in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_FM_RADIO 0x08 + +/** STE_CONN_H4_CHANNEL_GNSS - Bluetooth HCI H:4 channel + * for GNSS in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_GNSS 0x09 + +/** STE_CONN_H4_CHANNEL_DEBUG - Bluetooth HCI H:4 channel + * for internal debug data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_DEBUG 0x0B + +/** STE_CONN_H4_CHANNEL_STE_TOOLS - Bluetooth HCI H:4 channel + * for development tools data in the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_STE_TOOLS 0x0D + +/** STE_CONN_H4_CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel + * for logging all transmitted H4 packets (on all channels). + */ +#define STE_CONN_H4_CHANNEL_HCI_LOGGER 0xFA + +/** STE_CONN_H4_CHANNEL_US_CTRL - Bluetooth HCI H:4 channel + * for user space control of the ST-Ericsson connectivity controller. + */ +#define STE_CONN_H4_CHANNEL_US_CTRL 0xFC + +/** BT_ENABLE_GPIO - GPIO to enable/disable the BT module. + */ +#define BT_ENABLE_GPIO (GPIO(170)) + +/** GBF_ENA_RESET_GPIO - GPIO to enable/disable the controller. + */ +#define GBF_ENA_RESET_GPIO (GPIO(171)) + +/** GBF_ENA_RESET_NAME - Name of GPIO for enabling/disabling. + */ +#define GBF_ENA_RESET_NAME "gbf_ena_reset" + +/** GBF_ENA_RESET_NAME - Name of GPIO for enabling/disabling. + */ +#define BT_ENABLE_NAME "bt_enable" + +#define STE_CONN_ERR(fmt, arg...) printk(KERN_ERR "STE_CONN %s: " fmt "\n" , __func__ , ## arg) + +/* Bluetooth Opcode Group Field */ +#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 + +#define MAKE_FIRST_BYTE_IN_CMD(__ocf) ((uint8_t)(__ocf & 0x00FF)) +#define MAKE_SECOND_BYTE_IN_CMD(__ogf, __ocf) ((uint8_t)(__ogf << 2) | ((__ocf >> 8) & 0x0003)) + +/* Bluetooth Opcode Command Field */ +#define HCI_BT_OCF_RESET 0x0003 +#define HCI_BT_OCF_VS_POWER_SWITCH_OFF 0x0140 +#define HCI_BT_OCF_VS_SYSTEM_RESET 0x0312 +#define HCI_BT_OCF_VS_BT_ENABLE 0x0310 + +/* Bluetooth HCI command lengths */ +#define HCI_BT_LEN_RESET 0x03 +#define HCI_BT_LEN_VS_POWER_SWITCH_OFF 0x09 +#define HCI_BT_LEN_VS_SYSTEM_RESET 0x03 +#define HCI_BT_LEN_VS_BT_ENABLE 0x04 + +#define H4_HEADER_LENGTH 0x01 + +#define LOWEST_STLC2690_HCI_REV 0x0600 +#define LOWEST_CG2900_HCI_REV 0x0700 + +/** + * struct ste_conn_device_id - Structure for connecting H4 channel to named user. + * @name: Name of device. + * @h4_channel: HCI H:4 channel used by this device. + */ +struct ste_conn_device_id { + char *name; + int h4_channel; +}; + +static uint8_t ste_conn_hci_version; +static uint8_t ste_conn_lmp_version; +static uint8_t ste_conn_lmp_subversion; +static uint16_t ste_conn_hci_revision; +static uint16_t ste_conn_manufacturer; + +/* + * ste_conn_devices() - Array containing available H4 channels for current + * ST-Ericsson Connectivity controller. + */ +struct ste_conn_device_id ste_conn_devices[] = { + {STE_CONN_DEVICES_BT_CMD, STE_CONN_H4_CHANNEL_BT_CMD}, + {STE_CONN_DEVICES_BT_ACL, STE_CONN_H4_CHANNEL_BT_ACL}, + {STE_CONN_DEVICES_BT_EVT, STE_CONN_H4_CHANNEL_BT_EVT}, + {STE_CONN_DEVICES_GNSS, STE_CONN_H4_CHANNEL_GNSS}, + {STE_CONN_DEVICES_FM_RADIO, STE_CONN_H4_CHANNEL_FM_RADIO}, + {STE_CONN_DEVICES_DEBUG, STE_CONN_H4_CHANNEL_DEBUG}, + {STE_CONN_DEVICES_STE_TOOLS, STE_CONN_H4_CHANNEL_STE_TOOLS}, + {STE_CONN_DEVICES_HCI_LOGGER, STE_CONN_H4_CHANNEL_HCI_LOGGER}, + {STE_CONN_DEVICES_US_CTRL, STE_CONN_H4_CHANNEL_US_CTRL}, + {STE_CONN_DEVICES_BT_AUDIO, STE_CONN_H4_CHANNEL_BT_CMD}, + {STE_CONN_DEVICES_FM_RADIO_AUDIO, STE_CONN_H4_CHANNEL_FM_RADIO} +}; + +#define STE_CONN_NBR_OF_DEVS 9 + +int ste_conn_devices_get_h4_channel(char *name, int *h4_channel) +{ + int i; + int err = -ENXIO; + + *h4_channel = -1; + + for (i = 0; *h4_channel == -1 && i < STE_CONN_NBR_OF_DEVS; i++) { + if (0 == strncmp(name, ste_conn_devices[i].name, STE_CONN_MAX_NAME_SIZE)) { + /* Device found. Return H4 channel */ + *h4_channel = ste_conn_devices[i].h4_channel; + err = 0; + } + } + + return err; +} + +void ste_conn_devices_enable_chip(void) +{ + gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_HIGH); +} + +void ste_conn_devices_disable_chip(void) +{ + gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_LOW); +} + +void ste_conn_devices_set_hci_revision(uint8_t hci_version, + uint16_t hci_revision, + uint8_t lmp_version, + uint8_t lmp_subversion, + uint16_t manufacturer) +{ + ste_conn_hci_version = hci_version; + ste_conn_hci_revision = hci_revision; + ste_conn_lmp_version = lmp_version; + ste_conn_lmp_subversion = lmp_subversion; + ste_conn_manufacturer = manufacturer; +} + +struct sk_buff *ste_conn_devices_get_reset_cmd(uint8_t *op_lsb, uint8_t *op_msb) +{ + struct sk_buff *skb = NULL; + uint8_t *data = NULL; + + if (LOWEST_CG2900_HCI_REV <= ste_conn_hci_revision) { + /* CG2900 used. Send System reset */ + skb = alloc_skb(HCI_BT_LEN_VS_SYSTEM_RESET + H4_HEADER_LENGTH, GFP_KERNEL); + if (skb) { + skb_reserve(skb, H4_HEADER_LENGTH); + data = skb_put(skb, HCI_BT_LEN_VS_SYSTEM_RESET); + data[0] = MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_SYSTEM_RESET); + data[1] = MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_SYSTEM_RESET); + data[2] = 0x00; + } else { + STE_CONN_ERR("Could not allocate skb"); + } + } else { + /* STLC 2690 or older used. Send HCI reset */ + skb = alloc_skb(HCI_BT_LEN_RESET + H4_HEADER_LENGTH, GFP_KERNEL); + if (skb) { + skb_reserve(skb, H4_HEADER_LENGTH); + data = skb_put(skb, HCI_BT_LEN_RESET); + data[0] = MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_RESET); + data[1] = MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_CTRL_BB, HCI_BT_OCF_RESET); + data[2] = 0x00; + } else { + STE_CONN_ERR("Could not allocate skb"); + } + } + + if (skb) { + if (op_lsb) { + *op_lsb = data[0]; + } + if (op_msb) { + *op_msb = data[1]; + } + } + + return skb; +} + +struct sk_buff *ste_conn_devices_get_power_switch_off_cmd(uint8_t *op_lsb, uint8_t *op_msb) +{ + struct sk_buff *skb = NULL; + uint8_t *data = NULL; + + if (LOWEST_CG2900_HCI_REV <= ste_conn_hci_revision) { + /* CG2900 used */ + skb = alloc_skb(HCI_BT_LEN_VS_POWER_SWITCH_OFF + H4_HEADER_LENGTH, GFP_KERNEL); + if (skb) { + skb_reserve(skb, H4_HEADER_LENGTH); + data = skb_put(skb, HCI_BT_LEN_VS_POWER_SWITCH_OFF); + data[0] = MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_POWER_SWITCH_OFF); + data[1] = MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_POWER_SWITCH_OFF); + data[2] = 0x06; + /* Enter system specific GPIO settings here: + * Section data[3-5] is GPIO pull-up selection + * Section data[6-8] is GPIO pull-down selection + * Each section is a bitfield where + * - byte 0 bit 0 is GPIO 0 + * - byte 0 bit 1 is GPIO 1 + * - up to + * - byte 2 bit 4 which is GPIO 20 + * where each bit means: + * - 0: No pull-up / no pull-down + * - 1: Pull-up / pull-down + * All GPIOs are set as input. + */ + data[3] = 0x00; /* GPIO 0-7 pull-up */ + data[4] = 0x00; /* GPIO 8-15 pull-up */ + data[5] = 0x00; /* GPIO 16-20 pull-up */ + data[6] = 0x00; /* GPIO 0-7 pull-down */ + data[7] = 0x00; /* GPIO 8-15 pull-down */ + data[8] = 0x00; /* GPIO 16-20 pull-down */ + } else { + STE_CONN_ERR("Could not allocate skb"); + } + } else { + /* No command to send. Leave as NULL */ + } + + if (skb) { + if (op_lsb) { + *op_lsb = data[0]; + } + if (op_msb) { + *op_msb = data[1]; + } + } + + return skb; +} + +struct sk_buff *ste_conn_devices_get_bt_enable_cmd(uint8_t *op_lsb, uint8_t *op_msb, bool bt_enable) +{ + struct sk_buff *skb = NULL; + uint8_t *data = NULL; + + if (LOWEST_CG2900_HCI_REV <= ste_conn_hci_revision) { + /* CG2900 used */ + skb = alloc_skb(HCI_BT_LEN_VS_BT_ENABLE + H4_HEADER_LENGTH, GFP_KERNEL); + if (skb) { + skb_reserve(skb, H4_HEADER_LENGTH); + data = skb_put(skb, HCI_BT_LEN_VS_BT_ENABLE); + data[0] = MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_BT_ENABLE); + data[1] = MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_BT_ENABLE); + data[2] = 0x01; + if (bt_enable) { + data[3] = 0x01; + } else { + data[3] = 0x00; + } + } else { + STE_CONN_ERR("Could not allocate skb"); + } + } else { + /* No command to send. Leave as NULL */ + } + + if (skb) { + if (op_lsb) { + *op_lsb = data[0]; + } + if (op_msb) { + *op_msb = data[1]; + } + } + + return skb; +} + +int ste_conn_devices_init(void) +{ + int err = 0; + + err = gpio_request(GBF_ENA_RESET_GPIO, GBF_ENA_RESET_NAME); + if (err < 0) { + STE_CONN_ERR("gpio_request failed with err: %d", err); + goto finished; + } + + err = gpio_direction_output(GBF_ENA_RESET_GPIO, GPIO_HIGH); + if (err < 0) { + STE_CONN_ERR("gpio_direction_output failed with err: %d", err); + goto error_handling; + } + + err = gpio_request(BT_ENABLE_GPIO, BT_ENABLE_NAME); + if (err < 0) { + STE_CONN_ERR("gpio_request failed with err: %d", err); + goto finished; + } + + err = gpio_direction_output(BT_ENABLE_GPIO, GPIO_HIGH); + if (err < 0) { + STE_CONN_ERR("gpio_direction_output failed with err: %d", err); + goto error_handling; + } + + goto finished; + +error_handling: + gpio_free(GBF_ENA_RESET_GPIO); + +finished: + ste_conn_devices_disable_chip(); + return err; +} + +void ste_conn_devices_exit(void) +{ + ste_conn_devices_disable_chip(); + + gpio_free(GBF_ENA_RESET_GPIO); +} + +EXPORT_SYMBOL(ste_conn_devices_get_h4_channel); +EXPORT_SYMBOL(ste_conn_devices_enable_chip); +EXPORT_SYMBOL(ste_conn_devices_disable_chip); +EXPORT_SYMBOL(ste_conn_devices_set_hci_revision); +EXPORT_SYMBOL(ste_conn_devices_get_reset_cmd); +EXPORT_SYMBOL(ste_conn_devices_get_power_switch_off_cmd); +EXPORT_SYMBOL(ste_conn_devices_get_bt_enable_cmd); +EXPORT_SYMBOL(ste_conn_devices_init); +EXPORT_SYMBOL(ste_conn_devices_exit); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a2b4676ae4e..5b0a1733303 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -174,6 +174,19 @@ config MFD_TC6387XB help Support for Toshiba Mobile IO Controller TC6387XB +config MFD_STE_CONN + tristate "Support ST-Ericsson Connectivity controller" + depends on ARM && RFKILL + help + Support for ST-Ericsson Connectivity controller, e.g. CG2900 + +config MFD_STE_CONN_BLUEZ + tristate "Use BlueZ as Bluetooth stack with ST-Ericsson Connectivity controller" + depends on ARM && MFD_STE_CONN && BT + help + Select if BlueZ shall be used as Bluetooth stack with + ST-Ericsson Connectivity controller, e.g. CG2900 + config MFD_TC6393XB bool "Support Toshiba TC6393XB" depends on GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4d5570f72f1..7fed01d398e 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_MCP_UCB1200_TS) += ucb1x00-ts.o ifeq ($(CONFIG_SA1100_ASSABET),y) obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o endif +obj-y += ste_conn/ obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o obj-$(CONFIG_PMIC_DA903X) += da903x.o diff --git a/drivers/mfd/ste_conn/Makefile b/drivers/mfd/ste_conn/Makefile new file mode 100755 index 00000000000..cb0d03bdef9 --- /dev/null +++ b/drivers/mfd/ste_conn/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ST-Ericsson connectivity multifunction miscellaneous devices +# +#EXTRA_CFLAGS += $(KBUILD_GCOV_FLAGS) + +obj-$(CONFIG_MFD_STE_CONN) += ste_conn.o +ste_conn-objs := ste_conn_ccd.o ste_conn_cpd.o ste_conn_char_devices.o +export-objs := ste_conn_cpd.o + +obj-$(CONFIG_MFD_STE_CONN_BLUEZ) += ste_conn_hci_driver.o diff --git a/drivers/mfd/ste_conn/ste_conn_ccd.c b/drivers/mfd/ste_conn/ste_conn_ccd.c new file mode 100755 index 00000000000..2da640857b2 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_ccd.c @@ -0,0 +1,2023 @@ +/* + * file ste_conn_ccd.c + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/rfkill.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/skbuff.h> +#include <linux/pm.h> +#include <linux/gpio.h> +#include <linux/tty.h> +#include <linux/tty_ldisc.h> +#include <linux/poll.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> + +#include <linux/mfd/ste_conn.h> +#include "ste_conn_cpd.h" +#include "ste_conn_debug.h" +#include "ste_conn_ccd.h" + +#define VERSION "0.2" + +/* Device names */ +#define STE_CONN_CDEV_NAME "ste_conn_ccd_test" +#define STE_CONN_CLASS_NAME "ste_conn_class" +#define STE_CONN_DEVICE_NAME "ste_conn_driver" + +/* Workqueues' names */ +#define STE_CONN_UART_TX_WQ_NAME "ste_conn_uart_tx_wq\0" +#define STE_CONN_CCD_WQ_NAME "ste_conn_ccd_wq\0" + +/* Different supported transports */ +enum ccd_transport { + CCD_TRANSPORT_UNKNOWN, + CCD_TRANSPORT_UART, + CCD_TRANSPORT_CHAR_DEV +}; + +/* Number of bytes to reserve at start of sk_buffer when receiving packet */ +#define CCD_RX_SKB_RESERVE 8 +/* Max size of received packet (not including reserved bytes) */ +#define CCD_RX_SKB_MAX_SIZE 1024 + +/* Max size of bytes we can receive on the UART */ +#define CCD_UART_RECEIVE_ROOM 65536 + +/* Size of the header in the different packets */ +#define CCD_HCI_BT_EVT_HDR_SIZE 2 +#define CCD_HCI_BT_ACL_HDR_SIZE 4 +#define CCD_HCI_FM_RADIO_HDR_SIZE 1 +#define CCD_HCI_GNSS_HDR_SIZE 3 + +/* Position of length field in the different packets */ +#define CCD_HCI_EVT_LEN_POS 2 +#define CCD_HCI_ACL_LEN_POS 3 +#define CCD_FM_RADIO_LEN_POS 1 +#define CCD_GNSS_LEN_POS 2 + +#define CCD_H4_HEADER_LEN 0x01 + +/* Bytes in the command Hci_Reset */ +#define CCD_HCI_RESET_LSB 0x03 +#define CCD_HCI_RESET_MSB 0x0C +#define CCD_HCI_RESET_PAYL_LEN 0x00 +#define CCD_HCI_RESET_LEN 0x03 + +/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */ +#define CCD_SET_BAUD_RATE_LSB 0x09 +#define CCD_SET_BAUD_RATE_MSB 0xFC +#define CCD_SET_BAUD_RATE_PAYL_LEN 0x01 +#define CCD_SET_BAUD_RATE_LEN 0x04 +#define CCD_BAUD_RATE_57600 0x03 +#define CCD_BAUD_RATE_115200 0x02 +#define CCD_BAUD_RATE_230400 0x01 +#define CCD_BAUD_RATE_460800 0x00 +#define CCD_BAUD_RATE_921600 0x20 +#define CCD_BAUD_RATE_2000000 0x25 +#define CCD_BAUD_RATE_3000000 0x27 +#define CCD_BAUD_RATE_4000000 0x2B + +/* Baud rate defines */ +#define CCD_DEFAULT_BAUD_RATE 115200 +#define CCD_HIGH_BAUD_RATE 921600 + +/* Bytes for the HCI Command Complete event */ +#define HCI_BT_EVT_CMD_COMPLETE 0x0E +#define HCI_BT_EVT_CMD_COMPLETE_LEN 0x04 + +/* HCI error values */ +#define HCI_BT_ERROR_NO_ERROR 0x00 + +/* HCI TTY line discipline value */ +#ifndef N_HCI +#define N_HCI 15 +#endif + +/* IOCTLs for UART */ +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) + +/* UART break control parameters */ +#define TTY_BREAK_ON (-1) +#define TTY_BREAK_OFF (0) + + +/** + * enum ccd_state - Main-state for CCD. + * @CCD_STATE_INITIALIZING: CCD initializing. + * @CCD_STATE_OPENED: CCD is opened (data can be sent). + * @CCD_STATE_CLOSED: CCD is closed (data cannot be sent). + */ +enum ccd_state { + CCD_STATE_INITIALIZING, + CCD_STATE_OPENED, + CCD_STATE_CLOSED +}; + +/** + * enum ccd_uart_rx_state - UART RX-state for CCD. + * @W4_PACKET_TYPE: Waiting for packet type. + * @W4_EVENT_HDR: Waiting for BT event header. + * @W4_ACL_HDR: Waiting for BT ACL header. + * @W4_FM_RADIO_HDR: Waiting for FM header. + * @W4_GNSS_HDR: Waiting for GNSS header. + * @W4_DATA: Waiting for data in rest of the packet (after header). + */ +enum ccd_uart_rx_state { + W4_PACKET_TYPE, + W4_EVENT_HDR, + W4_ACL_HDR, + W4_FM_RADIO_HDR, + W4_GNSS_HDR, + W4_DATA +}; + +/** + * enum ccd_sleep_state - Sleep-state for CCD. + * @CHIP_AWAKE: Chip is awake. + * @CHIP_ASLEEP: Chip is asleep. + */ +enum ccd_sleep_state { + CHIP_AWAKE, + CHIP_ASLEEP +}; + +/** + * enum ccd_baud_rate_change_state - Baud rate-state for CCD. + * @BAUD_IDLE: No baud rate change is ongoing. + * @BAUD_SENDING_RESET: HCI reset has been sent. Waiting for command complete event. + * @BAUD_START: Set baud rate cmd scheduled for sending. + * @BAUD_SENDING: Set baud rate cmd sending in progress. + * @BAUD_WAITING: Set baud rate cmd sent, waiting for command complete event. + * @BAUD_SUCCESS: Baud rate change has succeeded. + * @BAUD_FAIL: Baud rate change has failed. + */ +enum ccd_baud_rate_change_state { + BAUD_IDLE, + BAUD_SENDING_RESET, + BAUD_START, + BAUD_SENDING, + BAUD_WAITING, + BAUD_SUCCESS, + BAUD_FAIL +}; + +/** + * struct ccd_uart_rx - UART RX info structure. + * @rx_state: Current RX state. + * @rx_count: Number of bytes left to receive. + * @rx_skb: SK_buffer to store the received data into. + * + * Contains all necessary information for receiving and packaging data on the + * UART. + */ +struct ccd_uart_rx { + enum ccd_uart_rx_state rx_state; + unsigned long rx_count; + struct sk_buff *rx_skb; +}; + +/** + * struct ccd_uart - UART info structure. + * @tty: TTY info structure. + * @tx_wq: TX working queue. + * @rx_lock: RX spin lock. + * @rx_info: UART RX info structure. + * @tx_mutex: TX mutex. + * + * Contains all necessary information for the UART transport. + */ +struct ccd_uart { + struct tty_struct *tty; + struct workqueue_struct *tx_wq; + spinlock_t rx_lock; + struct ccd_uart_rx rx_info; + struct mutex tx_mutex; +}; + + +/** + * struct ccd_work_struct - Work structure for CCD module. + * @work: Work structure. + * @data: Pointer to private data. + * + * This structure is used to pack work for work queue. + */ +struct ccd_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 test_char_dev_info - Main CCD info structure. + * @ccd_class: Class for the STE_CONN driver. + * @dev: Parent device for the STE_CONN driver. + * @state: CCD main state. + * @sleep_state: CCD sleep state. + * @baud_rate_state: CCD baud rate change state. + * @baud_rate: current baud rate setting. + * @wq: CCD work queue. + * @timer: CCD timer (for chip sleep). + * @tx_queue: TX queue for sending data to chip. + * @c_uart: UART info structure. + * @char_test_dev: Character device for testing purposes. + * @h4_channels: Stored H:4 channels. + */ +struct ccd_info { + struct class *ccd_class; + struct device *dev; + enum ccd_state state; + enum ccd_sleep_state sleep_state; + enum ccd_baud_rate_change_state baud_rate_state; + int baud_rate; + struct workqueue_struct *wq; + struct timer_list timer; + struct sk_buff_head tx_queue; + struct ccd_uart *c_uart; + struct test_char_dev_info *char_test_dev; + struct ste_conn_ccd_h4_channels h4_channels; +}; + +#if 0 +static int char_dev_usage = STE_CONN_ALL_CHAR_DEVS; +#else +static int char_dev_usage = STE_CONN_CHAR_DEV_GNSS | STE_CONN_CHAR_DEV_HCI_LOGGER | + STE_CONN_CHAR_DEV_US_CTRL | STE_CONN_CHAR_DEV_BT_AUDIO | STE_CONN_CHAR_DEV_FM_RADIO_AUDIO; +#endif + +static struct ccd_info *ccd_info; +static enum ccd_transport ste_conn_transport = CCD_TRANSPORT_UNKNOWN; +static int uart_default_baud = CCD_DEFAULT_BAUD_RATE; +static int uart_high_baud = CCD_HIGH_BAUD_RATE; +int ste_debug_level = STE_CONN_DEFAULT_DEBUG_LEVEL; +uint8_t ste_conn_bd_address[] = {0x00, 0x80, 0xDE, 0xAD, 0xBE, 0xEF}; +int bd_addr_count = BT_BDADDR_SIZE; +/* Global parameter set to NULL by default */ +static int use_sleep_timeout; +/* Global parameter set to NULL by default */ +static unsigned long ccd_timeout_jiffies; + +/* CCD wait queues */ +static DECLARE_WAIT_QUEUE_HEAD(test_char_rx_wait_queue); +static DECLARE_WAIT_QUEUE_HEAD(ste_conn_ccd_wait_queue); + +/* Time structures */ +static struct timeval time_100ms = { + .tv_sec = 0, + .tv_usec = 100 * USEC_PER_MSEC +}; + +static struct timeval time_500ms = { + .tv_sec = 0, + .tv_usec = 500 * USEC_PER_MSEC +}; + +static struct timeval time_1s = { + .tv_sec = 1, + .tv_usec = 0 +}; + +/* UART internal functions */ +static int uart_set_baud_rate(int baud); +static void uart_finish_setting_baud_rate(struct tty_struct *tty); +static void uart_send_skb_to_cpd(struct sk_buff *skb); +static int uart_send_skb_to_chip(struct sk_buff *skb); +static int uart_tx_wakeup(struct ccd_uart *c_uart); +static void work_uart_do_transmit(struct work_struct *work); +static int uart_tty_open(struct tty_struct *tty); +static void uart_tty_close(struct tty_struct *tty); +static void uart_tty_wakeup(struct tty_struct *tty); +static void uart_tty_receive(struct tty_struct *tty, const uint8_t *data, char *flags, int count); +static int uart_tty_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg); +static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file, unsigned char __user *buf, size_t nr); +static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file, const unsigned char *data, size_t count); +static unsigned int uart_tty_poll(struct tty_struct *tty, struct file *filp, poll_table *wait); +static void uart_check_data_len(struct ccd_uart_rx *rx_info, int len); +static int uart_receive_skb(struct ccd_uart *c_uart, const uint8_t *data, int count); + +/* Generic internal functions */ +static void ccd_work_hw_registered(struct work_struct *work); +static void ccd_work_hw_deregistered(struct work_struct *work); +static int ccd_create_work_item(struct workqueue_struct *wq, work_func_t work_func, void *data); +static void update_timer(void); +static void ccd_timer_expired(unsigned long data); +static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority); +static int test_char_device_create(void); +static void test_char_device_destroy(void); +static ssize_t test_char_dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); +static ssize_t test_char_dev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); +static unsigned int test_char_dev_poll(struct file *filp, poll_table *wait); +static void test_char_dev_tx_received(struct sk_buff_head *rx_queue, struct sk_buff *skb); +static int __init ste_conn_init(void); +static void __exit ste_conn_exit(void); +static struct sk_buff *alloc_set_baud_rate_cmd(int baud); + +/* + * 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 struct file_operations test_char_dev_fops = { + .read = test_char_dev_read, + .write = test_char_dev_write, + .poll = test_char_dev_poll +}; + + +/* --------------- Exported functions ------------------------- */ + +int ste_conn_ccd_open(void) +{ + int err = 0; + + STE_CONN_INFO("ste_conn_ccd_open"); + + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + { + uint8_t data[CCD_HCI_RESET_LEN + CCD_H4_HEADER_LEN]; + struct tty_struct *tty = NULL; + int bytes_written; + + /* Chip has just been started up. It has an system to autodetect + * exact baud rate and transport to use. There are only a few commands + * it will recognize and HCI Reset is one of them. + * We therefore start with sending that before actually changing baud rate. + * + * Create the Hci_Reset packet + */ + data[0] = (uint8_t)ccd_info->h4_channels.bt_cmd_channel; + data[1] = CCD_HCI_RESET_LSB; + data[2] = CCD_HCI_RESET_MSB; + data[3] = CCD_HCI_RESET_PAYL_LEN; + + /* Get the TTY info and send the packet */ + tty = ccd_info->c_uart->tty; + ccd_info->baud_rate_state = BAUD_SENDING_RESET; + STE_CONN_DBG("Sending HCI reset before baud rate change"); + bytes_written = tty->ops->write(tty, data, CCD_HCI_RESET_LEN + CCD_H4_HEADER_LEN); + if (bytes_written != CCD_HCI_RESET_LEN + CCD_H4_HEADER_LEN) { + STE_CONN_ERR("Only wrote %d bytes", bytes_written); + err = -EACCES; + goto finished; + } + + /* Wait for command complete. If error, exit without changing baud rate. */ + wait_event_interruptible_timeout(ste_conn_ccd_wait_queue, + BAUD_IDLE == ccd_info->baud_rate_state, + timeval_to_jiffies(&time_500ms)); + if (BAUD_IDLE != ccd_info->baud_rate_state) { + STE_CONN_ERR("Failed to send HCI Reset"); + err = -EIO; + goto finished; + } + + err = uart_set_baud_rate(uart_high_baud); + } + break; + case CCD_TRANSPORT_CHAR_DEV: + /* Nothing to be done in test mode. */ + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + err = -EIO; + goto finished; + break; + }; + + if (!err) { + ccd_info->state = CCD_STATE_OPENED; + } + +finished: + return err; +} + +void ste_conn_ccd_close(void) +{ + STE_CONN_INFO("ste_conn_ccd_close"); + + ccd_info->state = CCD_STATE_CLOSED; + + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + /* The chip is already shut down. Power off the chip. */ + ste_conn_ccd_set_chip_power(false); + + break; + case CCD_TRANSPORT_CHAR_DEV: + /* Nothing to be done in test mode. */ + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + break; + }; +} + +void ste_conn_ccd_set_chip_power(bool chip_on) +{ + STE_CONN_INFO("ste_conn_ccd_set_chip_power: %s", (chip_on?"ENABLE":"DISABLE")); + + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + if (chip_on) { + ste_conn_devices_enable_chip(); + } else { + struct ktermios *old_termios; + struct tty_struct *tty = NULL; + + ste_conn_devices_disable_chip(); + + /* Now we have to put the digital baseband UART back + * to default baudrate since we have powered off the + * chip + */ + if (ccd_info->c_uart && ccd_info->c_uart->tty) { + tty = ccd_info->c_uart->tty; + } else { + STE_CONN_ERR("Important structs not allocated!"); + return; + } + + old_termios = kzalloc(sizeof(*old_termios), GFP_KERNEL); + if (!old_termios) { + STE_CONN_ERR("Could not allocate termios"); + return; + } + + mutex_lock(&(tty->termios_mutex)); + /* Now set the termios struct to the default baudrate. + * Start by storing the old termios. + */ + memcpy(old_termios, tty->termios, sizeof(*old_termios)); + /* Now set the default baudrate */ + tty_encode_baud_rate(tty, uart_default_baud, + uart_default_baud); + /* Finally inform the driver */ + if (tty->ops->set_termios) { + tty->ops->set_termios(tty, old_termios); + } + mutex_unlock(&(tty->termios_mutex)); + kfree(old_termios); + } + break; + case CCD_TRANSPORT_CHAR_DEV: + /* Nothing to be done in test mode. */ + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + break; + }; +} + +int ste_conn_ccd_write(struct sk_buff *skb) +{ + int err = 0; + STE_CONN_INFO("ste_conn_ccd_write"); + + if (CCD_STATE_CLOSED == ccd_info->state) { + STE_CONN_ERR("Trying to write on a closed channel"); + return -EPERM; /* Operation not permitted */ + } + + /* (Re)start sleep timer. */ + update_timer(); + + /* Pass the buffer to physical transport. */ + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + err = uart_send_skb_to_chip(skb); + break; + case CCD_TRANSPORT_CHAR_DEV: + /* Test mode enabled, pass the buffer to the test char device. */ + test_char_dev_tx_received(&ccd_info->char_test_dev->rx_queue, skb); + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + break; + }; + return err; +} + + +/* -------------- Internal functions --------------------------- */ + + +/* -------------- UART functions --------------------------- */ + +/** + * uart_set_baud_rate() - Sets new baud rate for the UART. + * @baud: New baud rate. + * + * This function first sends the HCI command + * Hci_Cmd_ST_Set_Uart_Baud_Rate. It then changes the baud rate in HW, and + * finally it waits for the Command Complete event for the + * Hci_Cmd_ST_Set_Uart_Baud_Rate command. + * + * Returns: + * 0 if there is no error. + * -EALREADY if baud rate change is already in progress. + * -EFAULT if one or more of the UART related structs is not allocated. + * -ENOMEM if skb allocation has failed. + * -EPERM if setting the new baud rate has failed. + * Error codes generated by uart_send_skb_to_chip. + */ +static int uart_set_baud_rate(int baud) +{ + struct tty_struct *tty = NULL; + int err = 0; + struct sk_buff *skb; + int old_baud_rate; + + STE_CONN_INFO("uart_set_baud_rate (%d baud)", baud); + + if (ccd_info->baud_rate_state != BAUD_IDLE) { + STE_CONN_ERR("Trying to set new baud rate before old setting is finished"); + err = -EALREADY; + goto finished; + } + + if (ccd_info->c_uart && ccd_info->c_uart->tty) { + tty = ccd_info->c_uart->tty; + } else { + STE_CONN_ERR("Important structs not allocated!"); + err = -EFAULT; + goto finished; + } + + /* Store old baud rate so that we can restore it if something goes wrong. */ + old_baud_rate = ccd_info->baud_rate; + + skb = alloc_set_baud_rate_cmd(baud); + if (!skb) { + err = -ENOMEM; + STE_CONN_ERR("alloc_set_baud_rate_cmd failed"); + goto finished; + } + + ccd_info->baud_rate_state = BAUD_START; + ccd_info->baud_rate = baud; + + err = uart_send_skb_to_chip(skb); + if (err) { + STE_CONN_ERR("Failed to send change baud rate cmd!"); + ccd_info->baud_rate_state = BAUD_IDLE; + ccd_info->baud_rate = old_baud_rate; + goto finished; + } else { + STE_CONN_DBG("Set baud rate cmd scheduled for sending."); + } + + /* Now wait for the command complete. It will come at the new baudrate */ + wait_event_interruptible_timeout(ste_conn_ccd_wait_queue, + ((BAUD_SUCCESS == ccd_info->baud_rate_state) || + (BAUD_FAIL == ccd_info->baud_rate_state)), + timeval_to_jiffies(&time_1s)); + if (BAUD_SUCCESS == ccd_info->baud_rate_state) { + STE_CONN_DBG("Baudrate changed to %d baud", baud); + } else { + STE_CONN_ERR("Failed to set new baudrate (%d)", ccd_info->baud_rate_state); + err = -EPERM; + } + + /* Finally flush the TTY so we are sure that is no bad data there */ + if (tty->ops->flush_buffer) { + STE_CONN_DBG("Flushing TTY after baud rate change"); + tty->ops->flush_buffer(tty); + } + + /* Finished. Set state to IDLE */ + ccd_info->baud_rate_state = BAUD_IDLE; + +finished: + return err; +} + +/** + * alloc_set_baud_rate_cmd() - Allocates new sk_buff and fills in the change baud rate hci cmd. + * @baud: requested new baud rate. + * + * Returns: + * pointer to allocated sk_buff if successful; + * NULL otherwise. + */ +static struct sk_buff *alloc_set_baud_rate_cmd(int baud) +{ + struct sk_buff *skb; + uint8_t data[CCD_SET_BAUD_RATE_LEN]; + uint8_t *h4; + + skb = ste_conn_alloc_skb(CCD_SET_BAUD_RATE_LEN, GFP_KERNEL); + if (skb) { + /* Create the Hci_Cmd_ST_Set_Uart_Baud_Rate packet */ + data[0] = CCD_SET_BAUD_RATE_LSB; + data[1] = CCD_SET_BAUD_RATE_MSB; + data[2] = CCD_SET_BAUD_RATE_PAYL_LEN; + + switch (baud) { + case 57600: + data[3] = CCD_BAUD_RATE_57600; + break; + case 115200: + data[3] = CCD_BAUD_RATE_115200; + break; + case 230400: + data[3] = CCD_BAUD_RATE_230400; + break; + case 460800: + data[3] = CCD_BAUD_RATE_460800; + break; + case 921600: + data[3] = CCD_BAUD_RATE_921600; + break; + case 2000000: + data[3] = CCD_BAUD_RATE_2000000; + break; + case 3000000: + data[3] = CCD_BAUD_RATE_3000000; + break; + case 4000000: + data[3] = CCD_BAUD_RATE_4000000; + break; + default: + STE_CONN_ERR("Invalid speed requested (%d), using 115200 bps instead\n", baud); + data[3] = CCD_BAUD_RATE_115200; + baud = 115200; + break; + } + memcpy(skb_put(skb, CCD_SET_BAUD_RATE_LEN), data, CCD_SET_BAUD_RATE_LEN); + h4 = skb_push(skb, CCD_H4_HEADER_LEN); + *h4 = (uint8_t)ccd_info->h4_channels.bt_cmd_channel; + } else { + STE_CONN_ERR("Failed to alloc skb!"); + } + return skb; +} + +/** + * is_set_baud_rate_cmd() - Checks if data contains set baud rate hci cmd. + * @data: Pointer to data array to check. + * + * Returns: + * true - if cmd found; + * false - otherwise. + */ +static bool is_set_baud_rate_cmd(const char *data) +{ + bool cmd_match = false; + + if ((data[0] == ccd_info->h4_channels.bt_cmd_channel) && + (data[1] == CCD_SET_BAUD_RATE_LSB) && + (data[2] == CCD_SET_BAUD_RATE_MSB) && + (data[3] == CCD_SET_BAUD_RATE_PAYL_LEN)) { + cmd_match = true; + } + return cmd_match; +} + + +/** + * uart_send_skb_to_cpd() - Sends packet received from UART to CPD. + * @skb: Received data packet. + * + * This function checks if CCD is waiting for Command complete event, + * see uart_set_baud_rate. + * If it is waiting it checks if it is the expected packet and the status. + * If not is passes the packet to CPD. + */ +static void uart_send_skb_to_cpd(struct sk_buff *skb) +{ + if (!skb) { + STE_CONN_ERR("Received NULL as skb"); + return; + } + + if (BAUD_WAITING == ccd_info->baud_rate_state) { + /* Should only really be one packet received now: + * the CmdComplete for the SetBaudrate command + * Let's see if this is the packet we are waiting for. + */ + if ((ccd_info->h4_channels.bt_evt_channel == skb->data[0]) && + (HCI_BT_EVT_CMD_COMPLETE == skb->data[1]) && + (HCI_BT_EVT_CMD_COMPLETE_LEN == skb->data[2]) && + (CCD_SET_BAUD_RATE_LSB == skb->data[4]) && + (CCD_SET_BAUD_RATE_MSB == skb->data[5])) { + /* We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == skb->data[6]) { + STE_CONN_DBG("Received baud rate change complete event OK"); + ccd_info->baud_rate_state = BAUD_SUCCESS; + } else { + STE_CONN_ERR("Received baud rate change complete event with status 0x%X", skb->data[6]); + ccd_info->baud_rate_state = BAUD_FAIL; + } + wake_up_interruptible(&ste_conn_ccd_wait_queue); + kfree_skb(skb); + } else { + /* Received other event. Should not really happen, + * but pass the data to CPD anyway. + */ + STE_CONN_DBG_DATA("Sending packet to CPD while waiting for CmdComplete"); + ste_conn_cpd_data_received(skb); + } + } else if (BAUD_SENDING_RESET == ccd_info->baud_rate_state) { + /* Should only really be one packet received now: + * the CmdComplete for the Reset command + * Let's see if this is the packet we are waiting for. + */ + if ((ccd_info->h4_channels.bt_evt_channel == skb->data[0]) && + (HCI_BT_EVT_CMD_COMPLETE == skb->data[1]) && + (HCI_BT_EVT_CMD_COMPLETE_LEN == skb->data[2]) && + (CCD_HCI_RESET_LSB == skb->data[4]) && + (CCD_HCI_RESET_MSB == skb->data[5])) { + /* We have received complete event for our baud rate + * change command + */ + if (HCI_BT_ERROR_NO_ERROR == skb->data[6]) { + STE_CONN_DBG("Received HCI reset complete event OK"); + /* Go back to BAUD_IDLE since this was not really baud rate change + * but just a preparation of the chip to be ready to receive commands. + */ + ccd_info->baud_rate_state = BAUD_IDLE; + } else { + STE_CONN_ERR("Received HCI reset complete event with status 0x%X", skb->data[6]); + ccd_info->baud_rate_state = BAUD_FAIL; + } + wake_up_interruptible(&ste_conn_ccd_wait_queue); + kfree_skb(skb); + } else { + /* Received other event. Should not really happen, + * but pass the data to CPD anyway. + */ + STE_CONN_DBG_DATA("Sending packet to CPD while waiting for CmdComplete"); + ste_conn_cpd_data_received(skb); + } + } else { + /* Just pass data to CPD */ + ste_conn_cpd_data_received(skb); + } +} + +/** + * uart_send_skb_to_chip() - Transmit data packet to connectivity controller over UART. + * @skb: Data packet. + * + * Returns: + * 0 if there is no error. + * -ENODEV if UART has not been opened. + * Error codes from uart_tx_wakeup. + */ +static int uart_send_skb_to_chip(struct sk_buff *skb) +{ + int err = 0; + + if (!(ccd_info->c_uart)) { + STE_CONN_ERR("Trying to transmit on an unopened UART"); + return -ENODEV; + } + + /* Queue the sk_buffer... */ + skb_queue_tail(&ccd_info->tx_queue, skb); + + /* ... and call the common UART TX function */ + err = uart_tx_wakeup(ccd_info->c_uart); + + if (err) { + STE_CONN_ERR("Failed to queue skb transmission over uart, freeing skb."); + skb = skb_dequeue_tail(&ccd_info->tx_queue); + kfree_skb(skb); + } + + return err; +} + +/** + * uart_tx_wakeup() - Creates a transmit data work item that will transmit data packet to connectivity controller over UART. + * @c_uart: connectivity UART structure. + * + * Returns: + * 0 if success. + * Error code from ccd_create_work_item. + */ + +static int uart_tx_wakeup(struct ccd_uart *c_uart) + { + int err = 0; + + err = ccd_create_work_item(c_uart->tx_wq, work_uart_do_transmit, (void *)c_uart); + + return err; +} + +/** + * work_uart_do_transmit() - Transmit data packet to connectivity controller over UART. + * @work: Pointer to work info structure. Contains ccd_uart structure pointer. + */ +static void work_uart_do_transmit(struct work_struct *work) +{ + struct sk_buff *skb; + struct tty_struct *tty = NULL; + struct ccd_uart *c_uart = NULL; + struct ccd_work_struct *current_work = NULL; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ccd_work_struct, work); + + c_uart = (struct ccd_uart *)(current_work->data); + + if (c_uart && c_uart->tty) { + tty = c_uart->tty; + } else { + STE_CONN_ERR("Important structs not allocated!"); + goto finished; + } + + mutex_lock(&c_uart->tx_mutex); + + /* Retreive the first packet in the queue */ + skb = skb_dequeue(&ccd_info->tx_queue); + while (skb) { + int len; + + /* Tell TTY that there is data waiting and call the write function */ + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + len = tty->ops->write(tty, skb->data, skb->len); + STE_CONN_INFO("Written %d bytes to UART of %d bytes in packet", len, skb->len); + + /* If it's set baud rate cmd set correct baud state and after sending is finished + * inform the tty driver about the new baud rate. + */ + if ((BAUD_START == ccd_info->baud_rate_state) && + (is_set_baud_rate_cmd(skb->data))) { + STE_CONN_INFO("UART set baud rate cmd found."); + ccd_info->baud_rate_state = BAUD_SENDING; + } + + /* Remove the bytes written from the sk_buffer */ + skb_pull(skb, len); + + /* If there is more data in this sk_buffer, put it at the start of the list + * and exit the loop + */ + if (skb->len) { + skb_queue_head(&ccd_info->tx_queue, skb); + break; + } + /* No more data in the sk_buffer. Free it and get next packet in queue. + * Check if set baud rate cmd is in sending progress, if so call proper + * function to handle that cmd since it requires special attention. + */ + if (BAUD_SENDING == ccd_info->baud_rate_state) { + uart_finish_setting_baud_rate(tty); + } + + kfree_skb(skb); + skb = skb_dequeue(&ccd_info->tx_queue); + } + + mutex_unlock(&c_uart->tx_mutex); + +finished: + kfree(current_work); +} + +/** + * uart_finish_setting_baud_rate() - handles sending the ste baud rate hci cmd. + * @tty: pointer to a tty_struct used to communicate with tty driver. + * + * uart_finish_setting_baud_rate() makes sure that the set baud rate cmd has been really + * sent out on the wire and then switches the tty driver to new baud rate. + */ +static void uart_finish_setting_baud_rate(struct tty_struct *tty) +{ + struct ktermios *old_termios; + + /* Give the tty driver 100 msec to send data and proceed, if it hasn't been send + * we can't do much about it anyway. + */ + schedule_timeout_interruptible(timeval_to_jiffies(&time_100ms)); + + old_termios = kzalloc(sizeof(*old_termios), GFP_KERNEL); + if (!old_termios) { + STE_CONN_ERR("Could not allocate termios"); + return; + } + mutex_lock(&(tty->termios_mutex)); + + /* Now set the termios struct to the new baudrate. Start by storing + * the old termios. + */ + memcpy(old_termios, tty->termios, sizeof(*old_termios)); + /* Then set the new baudrate into the TTY struct */ + tty_encode_baud_rate(tty, ccd_info->baud_rate, ccd_info->baud_rate); + /* Finally inform the driver */ + if (tty->ops->set_termios) { + STE_CONN_DBG("Setting termios to new baud rate"); + tty->ops->set_termios(tty, old_termios); + ccd_info->baud_rate_state = BAUD_WAITING; + } else { + STE_CONN_ERR("Not possible to set new baud rate"); + ccd_info->baud_rate_state = BAUD_IDLE; + } + mutex_unlock(&(tty->termios_mutex)); + kfree(old_termios); +} + + +/** + * uart_tty_open() - Called when UART line discipline changed to N_HCI. + * @tty: Pointer to associated TTY instance data. + * + * Returns: + * 0 if there is no error. + * -EEXIST if UART was already opened. + * -ENOMEM if allocation fails. + * -EBUSY if another transport was already opened. + * -ECHILD if work queue could not be created. + */ +static int uart_tty_open(struct tty_struct *tty) +{ + struct ccd_uart *c_uart = (struct ccd_uart *) tty->disc_data; + int err = 0; + + STE_CONN_INFO("uart_tty_open"); + + if (c_uart) { + err = -EEXIST; + goto finished; + } + + /* Allocate the CCD UART structure */ + c_uart = kzalloc(sizeof(*c_uart), GFP_KERNEL); + if (!c_uart) { + STE_CONN_ERR("Can't allocate control structure"); + err = -ENOMEM; + goto finished; + } + + /* Initialize the tx_mutex here. It will be destroyed in uart_tty_close. */ + mutex_init(&c_uart->tx_mutex); + + /* Set the structure pointers and set the UART receive room */ + tty->disc_data = c_uart; + c_uart->tty = tty; + ccd_info->c_uart = c_uart; + tty->receive_room = CCD_UART_RECEIVE_ROOM; + + /* Initialize the spin lock */ + spin_lock_init(&c_uart->rx_lock); + + /* Flush any pending characters in the driver and line discipline. + * Don't use ldisc_ref here as the open path is before the ldisc is referencable + */ + if (tty->ldisc.ops->flush_buffer) { + tty->ldisc.ops->flush_buffer(tty); + } + tty_driver_flush_buffer(tty); + + ccd_info->dev->parent = NULL; + + if (CCD_TRANSPORT_UNKNOWN == ste_conn_transport) { + ste_conn_transport = CCD_TRANSPORT_UART; + } else { + STE_CONN_ERR( + "Trying to set transport to UART when transport 0x%X already registered", + (uint32_t)ste_conn_transport); + err = -EBUSY; + kfree(c_uart); + goto finished; + } + + /*Init uart tx workqueue */ + ccd_info->c_uart->tx_wq = create_singlethread_workqueue(STE_CONN_UART_TX_WQ_NAME); + if (!ccd_info->c_uart->tx_wq) { + STE_CONN_ERR("Could not create workqueue"); + err = -ECHILD; /* No child processes */ + goto finished; + } + + /* Tell CPD that HW is connected */ + err = ccd_create_work_item(ccd_info->wq, ccd_work_hw_registered, NULL); + +finished: + return err; +} + +/** + * uart_tty_close() - Close UART tty. + * @tty: Pointer to associated TTY instance data. + * + * The uart_tty_close() function is called when the line discipline is changed to something + * else, the TTY is closed, or the TTY detects a hangup. + */ +static void uart_tty_close(struct tty_struct *tty) +{ + struct ccd_uart *c_uart = (struct ccd_uart *)tty->disc_data; + + STE_CONN_INFO("uart_tty_close"); + + /* We can now destroy the tx_mutex initialized in uart_tty_open. */ + mutex_destroy(&c_uart->tx_mutex); + + /*Close uart rx work queue*/ + if (c_uart && c_uart->tx_wq) { + destroy_workqueue(c_uart->tx_wq); + } else { + STE_CONN_ERR("Important structs not allocated!"); + return; + } + + /* Detach from the TTY */ + tty->disc_data = NULL; + c_uart->tty = NULL; + + /* Tell CPD that HW is disconnected */ + if (ccd_info) { + (void)ccd_create_work_item(ccd_info->wq, ccd_work_hw_deregistered, (void *)c_uart); + } else { + STE_CONN_ERR("Important structs not allocated!"); + } +} + +/** + * uart_tty_wakeup() - callback function for transmit wakeup. + * @tty: Pointer to associated TTY instance data. + * + * The uart_tty_wakeup() callback function is called when low level + * device driver can accept more send data. + */ +static void uart_tty_wakeup(struct tty_struct *tty) +{ + struct ccd_uart *c_uart = (struct ccd_uart *)tty->disc_data; + + STE_CONN_INFO("uart_tty_wakeup"); + + if (!c_uart) { + return; + } + + /* Clear the TTY_DO_WRITE_WAKEUP bit that is set in work_uart_do_transmit(). */ + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + + if (tty != c_uart->tty) { + return; + } + + /* Start TX operation */ + uart_tx_wakeup(c_uart); +} + +/** + * uart_tty_receive() - Called by TTY low level driver when receive data is available. + * @tty: Pointer to TTY instance data + * @data: Pointer to received data + * @flags: Pointer to flags for data + * @count: Count of received data in bytes + */ +static void uart_tty_receive(struct tty_struct *tty, const uint8_t *data, char *flags, int count) +{ + struct ccd_uart *c_uart = (struct ccd_uart *)tty->disc_data; + + STE_CONN_INFO("uart_tty_receive"); + + if (!c_uart || (tty != c_uart->tty)) { + return; + } + + STE_CONN_DBG_DATA("Received data with length = %d and first byte 0x%02X", count, data[0]); + STE_CONN_DBG_DATA_CONTENT("Data: %02X %02X %02X %02X %02X %02X %02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + + /* Restart data ccd timer */ + spin_lock(&c_uart->rx_lock); + update_timer(); + uart_receive_skb(c_uart, data, count); + spin_unlock(&c_uart->rx_lock); + + /* Open TTY for more data */ + tty_unthrottle(tty); +} + +/** + * uart_tty_ioctl() - Process IOCTL system call for the TTY device. + * @tty: Pointer to TTY instance data. + * @file: Pointer to open file object for device. + * @cmd: IOCTL command code. + * @arg: Argument for IOCTL call (cmd dependent). + * + * Returns: + * 0 if there is no error. + * -EBADF if supplied TTY struct is not correct. + * Error codes from n_tty_iotcl_helper. + */ +static int uart_tty_ioctl(struct tty_struct *tty, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct ccd_uart *c_uart = (struct ccd_uart *)tty->disc_data; + int err = 0; + + STE_CONN_INFO("uart_tty_ioctl cmd %d", cmd); + STE_CONN_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d", _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); + + /* Verify the status of the device */ + if (!c_uart) { + return -EBADF; + } + + switch (cmd) { + case HCIUARTSETPROTO: + /* We don't do anything special here, but we have to show we handle it */ + break; + + case HCIUARTGETPROTO: + /* We don't do anything special here, but we have to show we handle it */ + break; + + case HCIUARTGETDEVICE: + /* We don't do anything special here, but we have to show we handle it */ + break; + + default: + err = n_tty_ioctl_helper(tty, file, cmd, arg); + break; + }; + + return err; +} + +/* + * We don't provide read/write/poll interface for user space. + */ +static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file, + unsigned char __user *buf, size_t nr) +{ + STE_CONN_INFO("uart_tty_read"); + return 0; +} + +static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file, + const unsigned char *data, size_t count) +{ + STE_CONN_INFO("uart_tty_write"); + return count; +} + +static unsigned int uart_tty_poll(struct tty_struct *tty, + struct file *filp, poll_table *wait) +{ + return 0; +} + +/** + * uart_check_data_len() - Check number of bytes to receive. + * @rx_info: UART RX info structure. + * @len: Number of bytes left to receive. + */ +static void uart_check_data_len(struct ccd_uart_rx *rx_info, int len) +{ + /* First get number of bytes left in the sk_buffer */ + register int room = skb_tailroom(rx_info->rx_skb); + + if (!len) { + /* No data left to receive. Transmit to CPD */ + uart_send_skb_to_cpd(rx_info->rx_skb); + } else if (len > room) { + STE_CONN_ERR("Data length is too large"); + kfree_skb(rx_info->rx_skb); + } else { + /* "Normal" case. Switch to data receiving state and store data length */ + rx_info->rx_state = W4_DATA; + rx_info->rx_count = len; + return; + } + + rx_info->rx_state = W4_PACKET_TYPE; + rx_info->rx_skb = NULL; + rx_info->rx_count = 0; +} + +/** + * uart_receive_skb() - Handles received UART data. + * @c_uart: CCD UART info structure + * @data: Data received + * @count: Number of bytes received + * + * The uart_receive_skb() function handles received UART data and puts it together + * to one complete packet. + * + * Returns: + * Number of bytes not handled, i.e. 0 = no error. + */ +static int uart_receive_skb(struct ccd_uart *c_uart, const uint8_t *data, int count) +{ + struct ccd_uart_rx *rx_info = NULL; + const uint8_t *r_ptr; + uint8_t *w_ptr; + uint8_t len_8; + uint16_t len_16; + int len; + + if (c_uart) { + rx_info = &(c_uart->rx_info); + } else { + STE_CONN_ERR("Important structs not allocated!"); + return count; + } + + r_ptr = data; + /* Continue while there is data left to handle */ + while (count) { + /* If we have already received a packet we know how many bytes there are left */ + if (rx_info->rx_count) { + /* First copy received data into the skb_rx */ + len = min_t(unsigned int, rx_info->rx_count, count); + memcpy(skb_put(rx_info->rx_skb, len), r_ptr, len); + /* Update counters from the length and step the data pointer */ + rx_info->rx_count -= len; + count -= len; + r_ptr += len; + + if (rx_info->rx_count) { + /* More data to receive to current packet. Continue */ + continue; + } + + /* Handle the different states */ + switch (rx_info->rx_state) { + case W4_DATA: + /* Whole data packet has been received. Transmit it to CPD */ + uart_send_skb_to_cpd(rx_info->rx_skb); + + rx_info->rx_state = W4_PACKET_TYPE; + rx_info->rx_skb = NULL; + continue; + + case W4_EVENT_HDR: + /* Event op code is not used */ + len_8 = rx_info->rx_skb->data[CCD_HCI_EVT_LEN_POS]; + uart_check_data_len(rx_info, len_8); + /* Header read. Continue with next bytes */ + continue; + + case W4_ACL_HDR: + /* Handle is not used */ + len_16 = __le16_to_cpu((uint16_t)((rx_info->rx_skb->data[CCD_HCI_ACL_LEN_POS]) | + (rx_info->rx_skb->data[CCD_HCI_ACL_LEN_POS + 1] << 8))); + uart_check_data_len(rx_info, len_16); + /* Header read. Continue with next bytes */ + continue; + + case W4_FM_RADIO_HDR: + /* FM Opcode is not used */ + len_8 = rx_info->rx_skb->data[CCD_FM_RADIO_LEN_POS]; + uart_check_data_len(rx_info, len_8); + /* Header read. Continue with next bytes */ + continue; + + case W4_GNSS_HDR: + /* GNSS Opcode is not used */ + len_16 = __le16_to_cpu((uint16_t)((rx_info->rx_skb->data[CCD_GNSS_LEN_POS]) | + (rx_info->rx_skb->data[CCD_GNSS_LEN_POS + 1] << 8))); + uart_check_data_len(rx_info, len_16); + /* Header read. Continue with next bytes */ + continue; + + default: + STE_CONN_ERR("Bad state indicating memory overwrite (0x%X)", (uint8_t)(rx_info->rx_state)); + break; + } + } + + /* Check which H:4 packet this is and update RX states */ + if (*r_ptr == ccd_info->h4_channels.bt_evt_channel) { + rx_info->rx_state = W4_EVENT_HDR; + rx_info->rx_count = CCD_HCI_BT_EVT_HDR_SIZE; + } else if (*r_ptr == ccd_info->h4_channels.bt_acl_channel) { + rx_info->rx_state = W4_ACL_HDR; + rx_info->rx_count = CCD_HCI_BT_ACL_HDR_SIZE; + } else if (*r_ptr == ccd_info->h4_channels.fm_radio_channel) { + rx_info->rx_state = W4_FM_RADIO_HDR; + rx_info->rx_count = CCD_HCI_FM_RADIO_HDR_SIZE; + } else if (*r_ptr == ccd_info->h4_channels.gnss_channel) { + rx_info->rx_state = W4_GNSS_HDR; + rx_info->rx_count = CCD_HCI_GNSS_HDR_SIZE; + } else { + STE_CONN_ERR("Unknown HCI packet type 0x%X", (uint8_t)*r_ptr); + r_ptr++; + count--; + continue; + } + + /* Allocate packet. We do not yet know the size and therefore allocate max size */ + rx_info->rx_skb = alloc_rx_skb(CCD_RX_SKB_MAX_SIZE, GFP_ATOMIC); + if (!rx_info->rx_skb) { + STE_CONN_ERR("Can't allocate memory for new packet"); + rx_info->rx_state = W4_PACKET_TYPE; + rx_info->rx_count = 0; + return 0; + } + + /* Write the H:4 header first in the sk_buffer */ + w_ptr = skb_put(rx_info->rx_skb, 1); + *w_ptr = *r_ptr; + + /* First byte (H4 header) read. Goto next byte */ + r_ptr++; + count--; + } + + return count; +} + +/* -------------- Generic functions --------------------------- */ + +/** + * ccd_work_hw_registered() - Work function called when the interface to HW has been established (e.g. during uart_tty_open). + * @work: Reference to work data. + * + * Notifies CPD that the HW is registered now. + */ +static void ccd_work_hw_registered(struct work_struct *work) +{ + struct ccd_work_struct *current_work = NULL; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ccd_work_struct, work); + + ste_conn_cpd_hw_registered(); + + kfree(current_work); +} + +/** + * ccd_work_hw_deregistered() - Handle HW deregistered. + * @work: Reference to work data. + * + * Handle work. + */ +static void ccd_work_hw_deregistered(struct work_struct *work) +{ + struct ccd_work_struct *current_work = NULL; + struct ccd_uart *c_uart = NULL; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ccd_work_struct, work); + c_uart = (struct ccd_uart *)current_work->data; + + if (c_uart) { + /* Tell CPD that HW is disconnected */ + ste_conn_cpd_hw_deregistered(); + + /* Purge any stored sk_buffers */ + skb_queue_purge(&ccd_info->tx_queue); + if (c_uart->rx_info.rx_skb) { + kfree_skb(c_uart->rx_info.rx_skb); + c_uart->rx_info.rx_skb = NULL; + } + + ccd_info->state = CCD_STATE_INITIALIZING; + ste_conn_transport = CCD_TRANSPORT_UNKNOWN; + /* Finally free the UART structure */ + kfree(c_uart); + } +} + +/** + * ccd_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. + * + * The ccd_create_work_item() function creates work item and + * add it to the work queue. + * + * Returns: + * 0 if there is no error. + * -EBUSY if not possible to queue work. + * -ENOMEM if allocation fails. + */ +static int ccd_create_work_item(struct workqueue_struct *wq, work_func_t work_func, void *data) +{ + struct ccd_work_struct *new_work; + int wq_err; + int err = 0; + + new_work = kmalloc(sizeof(*new_work), GFP_KERNEL); + + if (new_work) { + new_work->data = data; + INIT_WORK(&new_work->work, work_func); + + wq_err = queue_work(wq, &new_work->work); + + if (!wq_err) { + STE_CONN_ERR("Failed to queue work_struct because it's already in the queue!"); + kfree(new_work); + err = -EBUSY; + } + } else { + STE_CONN_ERR("Failed to alloc memory for ccd_work_struct!"); + err = -ENOMEM; + } + + return err; +} + +/** + * update_timer() - Updates or starts the ccd timer used to detect when there are no current data transmissions. + */ +static void update_timer(void) +{ + if (!use_sleep_timeout) { + return; + } + + /* This function indicates data is transmitted. + * Therefore see to that the chip is awake + */ + if (CHIP_ASLEEP == ccd_info->sleep_state) { + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + { + struct tty_struct *tty; + + if (ccd_info->c_uart && ccd_info->c_uart->tty) { + tty = ccd_info->c_uart->tty; + } else { + return; + } + if (tty->ops && tty->ops->break_ctl) { + tty->ops->break_ctl(tty, TTY_BREAK_OFF); + } + break; + } + case CCD_TRANSPORT_CHAR_DEV: + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + break; + }; + + ccd_info->sleep_state = CHIP_AWAKE; + } + /* If timer is running restart it. If not, start it. + * All this is handled by mod_timer() + */ + mod_timer(&(ccd_info->timer), jiffies + ccd_timeout_jiffies); +} + +/** + * ccd_timer_expired() - Called when timer expires. + * @data: Value supplied when starting the timer. + * + * The ccd_timer_expired() function is called if there are no ongoing data + * transmissions it tries to put the chip in sleep mode. + * + */ +static void ccd_timer_expired(unsigned long data) +{ + if (!use_sleep_timeout) { + return; + } + + switch (ste_conn_transport) { + case CCD_TRANSPORT_UART: + { + struct tty_struct *tty; + + if (ccd_info->c_uart && ccd_info->c_uart->tty) { + tty = ccd_info->c_uart->tty; + } else { + return; + } + if (tty->ops && tty->ops->break_ctl) { + tty->ops->break_ctl(tty, TTY_BREAK_ON); + } + break; + } + case CCD_TRANSPORT_CHAR_DEV: + break; + default: + STE_CONN_ERR("Unknown ste_conn_transport: 0x%X", ste_conn_transport); + break; + }; + + ccd_info->sleep_state = CHIP_ASLEEP; +} + +/** + * alloc_rx_skb() - Alloc an sk_buff structure for receiving data from controller. + * @size: Size in number of octets. + * @priority: Allocation priority, e.g. GFP_KERNEL. + * + * Returns: + * Pointer to sk_buff structure. + */ +static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + CCD_RX_SKB_RESERVE, priority); + if (skb) { + skb_reserve(skb, CCD_RX_SKB_RESERVE); + } + return skb; +} + +/** + * test_char_device_create() - Create 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_device_create(void) +{ + int err = 0; + + if (!ccd_info->char_test_dev) { + ccd_info->char_test_dev = + kzalloc(sizeof(*(ccd_info->char_test_dev)), GFP_KERNEL); + if (!ccd_info->char_test_dev) { + STE_CONN_ERR("Couldn't allocate char_test_dev"); + err = -ENOMEM; + goto finished; + } + } else { + STE_CONN_ERR("Trying to allocate char_test_dev twice"); + err = -EBUSY; + goto finished; + } + + /* Initialize the RX queue */ + skb_queue_head_init(&ccd_info->char_test_dev->rx_queue); + + /* Prepare miscdevice struct before registering the device */ + ccd_info->char_test_dev->test_miscdev.minor = MISC_DYNAMIC_MINOR; + ccd_info->char_test_dev->test_miscdev.name = STE_CONN_CDEV_NAME; + ccd_info->char_test_dev->test_miscdev.fops = &test_char_dev_fops; + ccd_info->char_test_dev->test_miscdev.parent = ccd_info->dev; + + err = misc_register(&ccd_info->char_test_dev->test_miscdev); + if (err) { + STE_CONN_ERR("Error %d registering misc dev!", err); + goto error_handling_w_free; + } + + goto finished; + +error_handling_w_free: + kfree(ccd_info->char_test_dev); + ccd_info->char_test_dev = NULL; +finished: + return err; +} + +/** + * test_char_device_destroy() - Clean up after test_char_device_create(). + */ +static void test_char_device_destroy(void) +{ + int err=0; + + if (ccd_info && ccd_info->char_test_dev && (ste_conn_transport == CCD_TRANSPORT_CHAR_DEV)) { + err = misc_deregister(&ccd_info->char_test_dev->test_miscdev); + if (err) { + STE_CONN_ERR("Error %d deregistering misc dev!", err); + } + /*Clean the message queue*/ + skb_queue_purge(&ccd_info->char_test_dev->rx_queue); + + kfree(ccd_info->char_test_dev); + ccd_info->char_test_dev = NULL; + } +} + +/** + * test_char_dev_read() - queue and copy buffer to user. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Count of received data in bytes. + * @f_pos: Position in buffer. + * + * The ste_conn_char_device_read() function queues and copy the received buffer to the user space char device. + * + * 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 = &ccd_info->char_test_dev->rx_queue; + + STE_CONN_INFO("test_char_dev_read"); + + if (skb_queue_empty(rx_queue)) { + wait_event_interruptible(test_char_rx_wait_queue, !(skb_queue_empty(rx_queue))); + } + + skb = skb_dequeue(rx_queue); + + 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); + } + return bytes_to_copy; +} + +/** + * test_char_dev_write() - copy buffer from user and write to cpd. + * @filp: Pointer to the file struct. + * @buf: Read buffer. + * @count: Size of the buffer write. + * @f_pos: Position in buffer. + * + * The ste_conn_char_device_write() function copy buffer from user and write to cpd. + * + * 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; + + STE_CONN_INFO("test_char_dev_write count %d", count); + + skb = alloc_rx_skb(count, GFP_KERNEL); + if (skb) { + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + return -EFAULT; + } + ste_conn_cpd_data_received(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. + * + * The test_char_dev_poll() function handles IOCTL call to the interface. + * + * 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, &test_char_rx_wait_queue, wait); + + if (!(skb_queue_empty(&ccd_info->char_test_dev->rx_queue))) { + mask |= POLLIN | POLLRDNORM; + } + + return mask; +} + + +/** + * test_char_dev_tx_received() - handle data received from connectivity protocol driver. + * @rx_queue: pointer to rx queue where the skb shall be put. + * @skb: buffer with data coming form device. + * + * The test_char_dev_tx_received() function handles data received from connectivity protocol driver. + */ +static void test_char_dev_tx_received(struct sk_buff_head *rx_queue, struct sk_buff *skb) +{ + + skb_queue_tail(rx_queue, skb); + + wake_up_interruptible(&test_char_rx_wait_queue); +} + +/* + * struct ste_conn_hci_uart_ldisc - UART TTY line discipline. + * + * The ste_conn_hci_uart_ldisc structure is used when + * registering to the UART framework. + */ +static struct tty_ldisc_ops ste_conn_hci_uart_ldisc = { + .magic = TTY_LDISC_MAGIC, + .name = "n_hci", + .open = uart_tty_open, + .close = uart_tty_close, + .read = uart_tty_read, + .write = uart_tty_write, + .ioctl = uart_tty_ioctl, + .poll = uart_tty_poll, + .receive_buf = uart_tty_receive, + .write_wakeup = uart_tty_wakeup, + .owner = THIS_MODULE +}; + +/** + * ste_conn_init() - Initialize module. + * + * The ste_conn_init() function initialize CCD and CPD, + * then register to the transport framework. + * + * Returns: + * 0 if success. + * -ENOMEM for failed alloc or structure creation. + * Error codes generated by ste_conn_devices_init, alloc_chrdev_region, + * class_create, device_create, ste_conn_cpd_init, tty_register_ldisc, + * ccd_create_work_item. + */ +static int __init ste_conn_init(void) +{ + int err = 0; + dev_t temp_devt; + struct timeval time_50ms = { + .tv_sec = 0, + .tv_usec = 50 * USEC_PER_MSEC + }; + struct ste_conn_ccd_driver_data *dev_data; + + STE_CONN_INFO("ste_conn_init"); + + err = ste_conn_devices_init(); + if (err) { + STE_CONN_ERR("Couldn't initialize ste_conn_devices"); + goto finished; + } + + ccd_info = kzalloc(sizeof(*ccd_info), GFP_KERNEL); + if (!ccd_info) { + STE_CONN_ERR("Couldn't allocate ccd_info"); + err = -ENOMEM; + goto finished; + } + ccd_info->state = CCD_STATE_INITIALIZING; + ccd_info->sleep_state = CHIP_AWAKE; + skb_queue_head_init(&ccd_info->tx_queue); + + ccd_info->wq = create_singlethread_workqueue(STE_CONN_CCD_WQ_NAME); + if (!ccd_info->wq) { + STE_CONN_ERR("Could not create workqueue"); + err = -ENOMEM; + goto error_handling; + } + + /* Get the H4 channel ID for all channels */ + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_CMD, + &(ccd_info->h4_channels.bt_cmd_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_ACL, + &(ccd_info->h4_channels.bt_acl_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_EVT, + &(ccd_info->h4_channels.bt_evt_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_GNSS, + &(ccd_info->h4_channels.gnss_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_FM_RADIO, + &(ccd_info->h4_channels.fm_radio_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_DEBUG, + &(ccd_info->h4_channels.debug_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_STE_TOOLS, + &(ccd_info->h4_channels.ste_tools_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_HCI_LOGGER, + &(ccd_info->h4_channels.hci_logger_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_US_CTRL, + &(ccd_info->h4_channels.us_ctrl_channel)); + + /* Convert the time to jiffies and setup the timer structure */ + ccd_timeout_jiffies = timeval_to_jiffies(&time_50ms); + ccd_info->timer.function = ccd_timer_expired; + ccd_info->timer.expires = jiffies + ccd_timeout_jiffies; + ccd_info->timer.data = 0; + + /* Create major device number */ + err = alloc_chrdev_region (&temp_devt, 0, 33, STE_CONN_DEVICE_NAME); + if (err) { + STE_CONN_ERR("Can't get major number (%d)", err); + goto error_handling_destroy_wq; + } + + /* Create the device class */ + ccd_info->ccd_class = class_create(THIS_MODULE, STE_CONN_CLASS_NAME); + if (IS_ERR(ccd_info->ccd_class)) { + STE_CONN_ERR("Error creating class"); + err = (int)ccd_info->ccd_class; + goto error_handling_class_create; + } + + /* Create the main device */ + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + STE_CONN_ERR("Couldn't allocate dev_data"); + err = -ENOMEM; + goto error_handling_dev_data_alloc; + } + + ccd_info->dev = device_create(ccd_info->ccd_class, NULL, temp_devt, + dev_data, STE_CONN_DEVICE_NAME "%d", + dev_data->next_free_minor); + if (IS_ERR(ccd_info->dev)) { + STE_CONN_ERR("Error creating main device"); + err = (int)ccd_info->dev; + goto error_handling_dev_create; + } + dev_data->next_free_minor++; + + ste_conn_transport = CCD_TRANSPORT_UNKNOWN; + + /* Check if verification mode is enabled to stubb transport interface. */ + if (char_dev_usage & STE_CONN_CHAR_TEST_DEV) { + /* Create and add test char device. */ + if (test_char_device_create() == 0) { + ste_conn_transport = CCD_TRANSPORT_CHAR_DEV; + STE_CONN_INFO("Test char device detected, ccd will be stubbed."); + } + char_dev_usage &= ~STE_CONN_CHAR_TEST_DEV; + } + + /* Initialize the CPD */ + err = ste_conn_cpd_init(char_dev_usage, ccd_info->dev); + if (err) { + STE_CONN_ERR("ste_conn_cpd_init failed %d", err); + goto error_handling; + } + + if (CCD_TRANSPORT_UNKNOWN == ste_conn_transport) { + /* Register the tty discipline. + * We will see what will be used. + */ + err = tty_register_ldisc(N_HCI, &ste_conn_hci_uart_ldisc); + if (err) { + STE_CONN_ERR("HCI line discipline registration failed. (0x%X)", err); + goto error_handling_register; + } + } + + /* If we are using a character device as transport we already have the HW */ + if (CCD_TRANSPORT_CHAR_DEV == ste_conn_transport) { + + err = ccd_create_work_item(ccd_info->wq, ccd_work_hw_registered, NULL); + } + + goto finished; + +error_handling_register: + device_destroy(ccd_info->ccd_class, ccd_info->dev->devt); + ccd_info->dev = NULL; +error_handling_dev_create: + kfree(dev_data); +error_handling_dev_data_alloc: + class_destroy(ccd_info->ccd_class); +error_handling_class_create: + unregister_chrdev_region(temp_devt, 32); +error_handling_destroy_wq: + destroy_workqueue(ccd_info->wq); +error_handling: + kfree(ccd_info); + ccd_info = NULL; +finished: + return err; +} + +/** + * ste_conn_exit() - Remove module. + */ +static void __exit ste_conn_exit(void) +{ + int err = 0; + dev_t temp_devt; + + STE_CONN_INFO("ste_conn_exit"); + + /* Release everything allocated in cpd */ + ste_conn_cpd_exit(); + + if (CCD_TRANSPORT_CHAR_DEV != ste_conn_transport) { + /* Release tty registration of line discipline */ + err = tty_unregister_ldisc(N_HCI); + if (err) { + STE_CONN_ERR("Can't unregister HCI line discipline (0x%X)", err); + } + } + + test_char_device_destroy(); + if (ccd_info) { + temp_devt = ccd_info->dev->devt; + kfree(ccd_info->dev->driver_data); + device_destroy(ccd_info->ccd_class, temp_devt); + class_destroy(ccd_info->ccd_class); + unregister_chrdev_region(temp_devt, 32); + + destroy_workqueue(ccd_info->wq); + + kfree(ccd_info); + ccd_info = NULL; + } + + ste_conn_devices_exit(); +} + +module_init(ste_conn_init); +module_exit(ste_conn_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson Connectivity Controller ver " VERSION); +MODULE_VERSION(VERSION); + +module_param(use_sleep_timeout, bool, S_IRUGO); +MODULE_PARM_DESC(use_sleep_timeout, "Use sleep timer for data transmissions: true=1/false=0"); + +module_param(char_dev_usage, int, S_IRUGO); +MODULE_PARM_DESC(char_dev_usage, "Character devices to enable (bitmask):0x00 = No char devs, \ + 0x01 = BT, \ + 0x02 = FM radio, \ + 0x04 = GNSS, \ + 0x08 = Debug, \ + 0x10 = STE tools, \ + 0x20 = CCD debug, \ + 0x40 = HCI Logger, \ + 0x80 = US Ctrl, \ + 0x100 = STE_CONN test"); + +module_param(uart_default_baud, int, S_IRUGO); +MODULE_PARM_DESC(uart_default_baud, "Default UART baud rate, e.g. 115200. If not set 115200 will be used."); + +module_param(uart_high_baud, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(uart_high_baud, "High speed UART baud rate, e.g. 4000000. If not set 921600 will be used."); + +module_param(ste_debug_level, int, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(ste_debug_level, "Debug level. Default 1. Possible values:\n \ + 0 = No debug\n \ + 1 = Error prints\n \ + 10 = General info, e.g. function entries\n \ + 20 = Debug info, e.g. steps in a functionality\n \ + 25 = Data info, i.e. prints when data is transferred\n \ + 30 = Data content, i.e. contents of the transferred data"); + + +EXPORT_SYMBOL(ste_debug_level); + +module_param_array(ste_conn_bd_address, byte, &bd_addr_count, S_IRUGO | S_IWUSR | S_IWGRP); +MODULE_PARM_DESC(ste_conn_bd_address, "Bluetooth Device address. Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. \ +Enter as comma separated value."); + + +EXPORT_SYMBOL(ste_conn_bd_address); + diff --git a/drivers/mfd/ste_conn/ste_conn_ccd.h b/drivers/mfd/ste_conn/ste_conn_ccd.h new file mode 100755 index 00000000000..067f628ef63 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_ccd.h @@ -0,0 +1,83 @@ +/* + * file ste_conn_ccd.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_CCD_H_ +#define _STE_CONN_CCD_H_ + +#include <linux/skbuff.h> + +#define BT_BDADDR_SIZE 6 + +struct ste_conn_ccd_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; +}; + +struct ste_conn_ccd_driver_data { + int next_free_minor; +}; + +/* module_param declared in ste_conn_ccd.c */ +extern uint8_t ste_conn_bd_address[BT_BDADDR_SIZE]; + +/** + * ste_conn_ccd_open() - Open the ste_conn CCD for data transfers. + * + * The ste_conn_ccd_open() function opens the ste_conn CCD 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. + */ +extern int ste_conn_ccd_open(void); + +/** + * ste_conn_ccd_close() - Close the ste_conn CCD for data transfers. + * + * The ste_conn_ccd_close() function closes the ste_conn CCD for data transfers. + */ +extern void ste_conn_ccd_close(void); + +/** + * ste_conn_ccd_set_chip_power() - Enable or disable the connectivity controller. + * @chip_on: true if chip shall be enabled. + * + * The ste_conn_ccd_set_chip_power() function enable or disable the connectivity controller. + */ +extern void ste_conn_ccd_set_chip_power(bool chip_on); + +/** + * ste_conn_ccd_write() - Transmit data packet to connectivity controller. + * @skb: data packet. + * + * The ste_conn_ccd_write() function transmit data packet to connectivity controller. + * + * Returns: + * 0 if there is no error. + * -EPERM if CCD has not been opened. + * Error codes from uart_send_skb_to_chip. + */ +extern int ste_conn_ccd_write(struct sk_buff *skb); + +#endif /* _STE_CONN_CCD_H_ */ diff --git a/drivers/mfd/ste_conn/ste_conn_char_devices.c b/drivers/mfd/ste_conn/ste_conn_char_devices.c new file mode 100755 index 00000000000..e1598b0b092 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_char_devices.c @@ -0,0 +1,775 @@ +/* + * file ste_conn_char_devices.c + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson Connectivity Controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/skbuff.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/device.h> +#include <linux/poll.h> +#include <linux/mutex.h> + +#include <linux/mfd/ste_conn.h> +#include <mach/ste_conn_devices.h> +#include "ste_conn_cpd.h" +#include "ste_conn_ccd.h" +#include "ste_conn_debug.h" + +#define VERSION "1.0" + +/* Ioctls */ +#define STE_CONN_CHAR_DEV_IOCTL_RESET _IOW('U', 210, int) +#define STE_CONN_CHAR_DEV_IOCTL_CHECK4RESET _IOR('U', 212, int) + +#define STE_CONN_CHAR_DEV_IOCTL_EVENT_RESET 1 +#define STE_CONN_CHAR_DEV_IOCTL_EVENT_CLOSED 2 + +/* ------------------ Internal type definitions --------------------------- */ + +/** + * enum ste_conn_char_reset_state - Reset state. + * @STE_CONN_CHAR_IDLE: Idle state. + * @STE_CONN_CHAR_RESET: Reset state. + * @STE_CONN_CHAR_WAIT4RESET: Wait for reset state. + */ +enum ste_conn_char_reset_state { + STE_CONN_CHAR_IDLE, + STE_CONN_CHAR_RESET, + STE_CONN_CHAR_WAIT4RESET +}; + +/** + * struct ste_conn_char_dev_user - Stores device information. + * @cpd_dev: Registered CPD device. + * @cdev_device: Registered device struct. + * @cdev: Registered Char Device. + * @devt: Assigned dev_t. + * @name: Name of device. + * @rx_queue: Data queue. + * @rx_wait_queue: Wait queue. + * @reset_wait_queue: Reset Wait queue. + * @reset_state: Reset state. + * @read_mutex: Read mutex. + * @write_mutex: Write mutex. + */ +struct ste_conn_char_dev_user { + struct ste_conn_device *cpd_dev; + struct device *cdev_device; + struct cdev cdev; + dev_t devt; + char *name; + struct sk_buff_head rx_queue; + wait_queue_head_t rx_wait_queue; + wait_queue_head_t reset_wait_queue; + enum ste_conn_char_reset_state reset_state; + struct mutex read_mutex; + struct mutex write_mutex; +}; + +/** + * struct ste_conn_char_info - Stores all current users. + * @bt_cmd_user: BT command channel user. + * @bt_acl_user: BT ACL channel user. + * @bt_evt_user: BT event channel user. + * @fm_radio_user: FM radio channel user. + * @gnss_user: GNSS channel user. + * @debug_user: Debug channel user. + * @ste_tools_user: ST-E tools channel user. + * @hci_logger_user: HCI logger channel user. + * @us_ctrl_user: User space control channel user. + * @bt_audio_user: BT audio command channel user. + * @fm_audio_user: FM audio command channel user. + * @open_mutex: Open mutex (used for both open and release). + */ +struct ste_conn_char_info { + struct ste_conn_char_dev_user bt_cmd_user; + struct ste_conn_char_dev_user bt_acl_user; + struct ste_conn_char_dev_user bt_evt_user; + struct ste_conn_char_dev_user fm_radio_user; + struct ste_conn_char_dev_user gnss_user; + struct ste_conn_char_dev_user debug_user; + struct ste_conn_char_dev_user ste_tools_user; + struct ste_conn_char_dev_user hci_logger_user; + struct ste_conn_char_dev_user us_ctrl_user; + struct ste_conn_char_dev_user bt_audio_user; + struct ste_conn_char_dev_user fm_audio_user; + struct mutex open_mutex; +}; + +/* ------------------ Internal function declarations ---------------------- */ + +static int char_dev_setup_cdev(struct ste_conn_char_dev_user *dev_usr, + struct device *dev, + char *name); +static void char_dev_remove_cdev(struct ste_conn_char_dev_user *dev_usr); +static int ste_conn_char_device_open(struct inode *inode, + struct file *filp); +static int ste_conn_char_device_release(struct inode *inode, + struct file *filp); +static ssize_t ste_conn_char_device_read(struct file *filp, + char __user *buf, + size_t count, + loff_t *f_pos); +static ssize_t ste_conn_char_device_write(struct file *filp, + const char __user *buf, + size_t count, + loff_t *f_pos); +static long ste_conn_char_device_unlocked_ioctl(struct file *filp, + unsigned int cmd, + unsigned long arg); +static unsigned int ste_conn_char_device_poll(struct file *filp, + poll_table *wait); +static void ste_conn_char_cpd_read_cb(struct ste_conn_device *dev, + struct sk_buff *skb); +static void ste_conn_char_cpd_reset_cb(struct ste_conn_device *dev); + +/* ------------------ Internal variable declarations ---------------------- */ + +/* + * char_info - Main information object for char devices. + */ +static struct ste_conn_char_info *char_info; + +/* + * struct ste_conn_char_fops - Char devices file operations. + * @read: Function that reads from the char device. + * @write: Function that writes to the char device. + * @unlocked_ioctl: Function that performs IO operations with the char device. + * @poll: Function that checks if there are possible operations with the char device. + * @open: Function that opens the char device. + * @release: Function that release the char device. + */ +static struct file_operations ste_conn_char_fops = { + .read = ste_conn_char_device_read, + .write = ste_conn_char_device_write, + .unlocked_ioctl = ste_conn_char_device_unlocked_ioctl, + .poll = ste_conn_char_device_poll, + .open = ste_conn_char_device_open, + .release = ste_conn_char_device_release +}; + +/* + * struct ste_conn_char_cb - Callback structure for ste_conn user. + * @read_cb: Callback function called when data is received from the ste_conn. + * @reset_cb: Callback function called when the controller has been reset. + */ +static struct ste_conn_callbacks ste_conn_char_cb = { + .read_cb = ste_conn_char_cpd_read_cb, + .reset_cb = ste_conn_char_cpd_reset_cb +}; + +/* ---------------- CPD callbacks --------------------------------- */ + +/** + * ste_conn_char_cpd_read_cb() - handle data received from controller. + * @dev: device receiving data. + * @skb: buffer with data coming from controller. + * + * The ste_conn_char_cpd_read_cb() function handles data received + * from connectivity protocol driver. + */ +static void ste_conn_char_cpd_read_cb(struct ste_conn_device *dev, + struct sk_buff *skb) +{ + struct ste_conn_char_dev_user *char_dev = + (struct ste_conn_char_dev_user *)dev->user_data; + + STE_CONN_INFO("ste_conn_char_cpd_read_cb"); + + if (!char_dev) { + STE_CONN_ERR("No char dev! Exiting"); + kfree_skb(skb); + return; + } + + skb_queue_tail(&char_dev->rx_queue, skb); + + wake_up_interruptible(&char_dev->rx_wait_queue); +} + +/** + * ste_conn_char_cpd_reset_cb() - handle reset from controller. + * @dev: device reseting. + * + * The ste_conn_char_cpd_reset_cb() function handles reset from + * connectivity protocol driver. + */ +static void ste_conn_char_cpd_reset_cb(struct ste_conn_device *dev) +{ + struct ste_conn_char_dev_user *char_dev = + (struct ste_conn_char_dev_user *)dev->user_data; + + STE_CONN_INFO("ste_conn_char_cpd_reset_cb"); + + if (!char_dev) { + STE_CONN_ERR("char_dev == NULL"); + return; + } + + char_dev->reset_state = STE_CONN_CHAR_RESET; + + wake_up_interruptible(&char_dev->rx_wait_queue); + wake_up_interruptible(&char_dev->reset_wait_queue); +} + +/* ---------------- File operation functions --------------------------------- */ + +/** + * ste_conn_char_device_open() - open char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The ste_conn_char_device_open() function opens the char device. + * + * Returns: + * 0 if there is no error. + * -EACCES if device was already registered to driver or if registration + * failed. + */ +static int ste_conn_char_device_open(struct inode *inode, + struct file *filp) +{ + int err = 0; + struct ste_conn_char_dev_user *dev; /*device information*/ + + mutex_lock(&char_info->open_mutex); + dev = container_of(inode->i_cdev, struct ste_conn_char_dev_user, cdev); + filp->private_data = dev; + + STE_CONN_INFO("ste_conn_char_device_open %s", dev->name); + + if (dev->cpd_dev) { + STE_CONN_ERR("Device already registered to CPD"); + err = -EACCES; + goto error_handling; + } + /* First initiate wait queues for this device. */ + init_waitqueue_head(&dev->rx_wait_queue); + init_waitqueue_head(&dev->reset_wait_queue); + + dev->reset_state = STE_CONN_CHAR_IDLE; + + /*Register to ste_conn*/ + dev->cpd_dev = ste_conn_register(dev->name, &ste_conn_char_cb); + if (dev->cpd_dev) { + dev->cpd_dev->user_data = dev; + } else { + STE_CONN_ERR("Couldn't register to CPD for H:4 channel %s", + dev->name); + err = -EACCES; + } + +error_handling: + mutex_unlock(&char_info->open_mutex); + return err; +} + +/** + * ste_conn_char_device_release() - release char device. + * @inode: Device driver information. + * @filp: Pointer to the file struct. + * + * The ste_conn_char_device_release() function release the char device. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + */ +static int ste_conn_char_device_release(struct inode *inode, + struct file *filp) +{ + int err = 0; + struct ste_conn_char_dev_user *dev = + (struct ste_conn_char_dev_user *)filp->private_data; + + mutex_lock(&dev->read_mutex); + mutex_lock(&dev->write_mutex); + mutex_lock(&char_info->open_mutex); + + STE_CONN_INFO("ste_conn_char_device_release %s", dev->name); + + if (!dev) { + STE_CONN_ERR("Calling with NULL pointer"); + err = -EBADF; + goto error_handling; + + } + + if (dev->reset_state == STE_CONN_CHAR_IDLE) { + ste_conn_deregister(dev->cpd_dev); + } + + dev->cpd_dev = NULL; + filp->private_data = NULL; + wake_up_interruptible(&dev->rx_wait_queue); + wake_up_interruptible(&dev->reset_wait_queue); + +error_handling: + mutex_unlock(&char_info->open_mutex); + mutex_unlock(&dev->write_mutex); + mutex_unlock(&dev->read_mutex); + + return err; +} + +/** + * ste_conn_char_device_read() - queue and copy buffer to user. + * @filp: Pointer to the file struct. + * @buf: Received buffer. + * @count: Size of buffer. + * @f_pos: Position in buffer. + * + * The ste_conn_char_device_read() function queues and copy + * the received buffer to the user space char device. + * + * Returns: + * Bytes successfully read (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_to_user fails. + * Error codes from wait_event_interruptible. + */ +static ssize_t ste_conn_char_device_read(struct file *filp, + char __user *buf, size_t count, + loff_t *f_pos) +{ + struct ste_conn_char_dev_user *dev = + (struct ste_conn_char_dev_user *)filp->private_data; + struct sk_buff *skb; + int bytes_to_copy; + int err = 0; + + mutex_lock(&dev->read_mutex); + STE_CONN_INFO("ste_conn_char_device_read"); + + if (!dev) { + STE_CONN_ERR("Calling with NULL pointer"); + err = -EBADF; + goto error_handling; + } + + if (skb_queue_empty(&dev->rx_queue)) { + err = wait_event_interruptible(dev->rx_wait_queue, + (!(skb_queue_empty(&dev->rx_queue))) || + (STE_CONN_CHAR_RESET == dev->reset_state) || + (dev->cpd_dev == NULL)); + if (err) { + STE_CONN_ERR("Failed to wait for event"); + goto error_handling; + } + } + + if (!dev->cpd_dev) { + STE_CONN_INFO("cpd_dev is empty - return with negative bytes"); + err = -EBADF; + goto error_handling; + } + + skb = skb_dequeue(&dev->rx_queue); + + if (!skb) { + STE_CONN_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(&dev->rx_queue, skb); + err = -EFAULT; + goto error_handling; + } + skb_pull(skb, bytes_to_copy); + + if (skb->len > 0) { + skb_queue_head(&dev->rx_queue, skb); + } else { + kfree_skb(skb); + } + goto finished; + +error_handling: + mutex_unlock(&dev->read_mutex); + return (ssize_t)err; +finished: + mutex_unlock(&dev->read_mutex); + return bytes_to_copy; +} + +/** + * ste_conn_char_device_write() - copy buffer from user and write to cpd. + * @filp: Pointer to the file struct. + * @buf: Write buffer. + * @count: Size of the buffer write. + * @f_pos: Position of buffer. + * + * The ste_conn_char_device_write() function copy buffer + * from user and write to cpd. + * + * Returns: + * Bytes successfully written (could be 0). + * -EBADF if NULL pointer was supplied in private data. + * -EFAULT if copy_from_user fails. + */ +static ssize_t ste_conn_char_device_write(struct file *filp, + const char __user *buf, size_t count, + loff_t *f_pos) +{ + struct sk_buff *skb; + struct ste_conn_char_dev_user *dev = + (struct ste_conn_char_dev_user *)filp->private_data; + int err = 0; + + mutex_lock(&dev->write_mutex); + STE_CONN_INFO("ste_conn_char_device_write"); + + if (!dev) { + err = -EBADF; + goto error_handling; + } + + skb = ste_conn_alloc_skb(count, GFP_KERNEL); + if (skb) { + if (copy_from_user(skb_put(skb, count), buf, count)) { + kfree_skb(skb); + err = -EFAULT; + goto error_handling; + } + ste_conn_write(dev->cpd_dev, skb); + mutex_unlock(&dev->write_mutex); + return count; + } else { + STE_CONN_ERR("Couldn't allocate sk_buff with length %d", count); + } + +error_handling: + mutex_unlock(&dev->write_mutex); + return err; +} + +/** + * ste_conn_char_device_unlocked_ioctl() - handle IOCTL call to the interface. + * @filp: Pointer to the file struct. + * @cmd: IOCTL cmd. + * @arg: IOCTL arg. + * + * The ste_conn_char_device_unlocked_ioctl() function handles IOCTL call to the interface. + * + * Returns: + * 0 if there is no error. + * -EBADF if NULL pointer was supplied in private data. + * -EINVAL if supplied cmd is not supported. + * For cmd STE_CONN_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is + * reset and 0x02 is returned if device is closed. + */ +static long ste_conn_char_device_unlocked_ioctl(struct file *filp, + unsigned int cmd, + unsigned long arg) +{ + struct ste_conn_char_dev_user *dev = + (struct ste_conn_char_dev_user *)filp->private_data; + int err = 0; + + switch (cmd) { + case STE_CONN_CHAR_DEV_IOCTL_RESET: + if (!dev) { + err = -EBADF; + goto error_handling; + } + STE_CONN_INFO("ioctl reset command for device %s", dev->name); + err = ste_conn_reset(dev->cpd_dev); + break; + + case STE_CONN_CHAR_DEV_IOCTL_CHECK4RESET: + if (!dev) { + STE_CONN_INFO("ioctl check for reset command for device"); + /*Return positive value if closed */ + err = STE_CONN_CHAR_DEV_IOCTL_EVENT_CLOSED; + } else if (dev->reset_state == STE_CONN_CHAR_RESET) { + STE_CONN_INFO("ioctl check for reset command for device %s", dev->name); + /*Return positive value if reset */ + err = STE_CONN_CHAR_DEV_IOCTL_EVENT_RESET; + } + break; + default: + STE_CONN_ERR("Unknown ioctl command"); + err = -EINVAL; + break; + }; + +error_handling: + return err; +} + +/** + * ste_conn_char_device_poll() - handle POLL call to the interface. + * @filp: Pointer to the file struct. + * @wait: Poll table supplied to caller. + * + * The ste_conn_char_device_poll() function handles IOCTL call to the interface. + * + * Returns: + * Mask of current set POLL values + */ +static unsigned int ste_conn_char_device_poll(struct file *filp, + poll_table *wait) +{ + struct ste_conn_char_dev_user *dev = + (struct ste_conn_char_dev_user *)filp->private_data; + unsigned int mask = 0; + + if (!dev) { + STE_CONN_DBG("device not open"); + return POLLERR | POLLRDHUP; + } + + poll_wait(filp, &dev->reset_wait_queue, wait); + poll_wait(filp, &dev->rx_wait_queue, wait); + + if (!dev->cpd_dev) { + mask |= POLLERR | POLLRDHUP; + } + + if (!(skb_queue_empty(&dev->rx_queue))) { + mask |= POLLIN | POLLRDNORM; + } + + if (STE_CONN_CHAR_RESET == dev->reset_state) { + mask |= POLLPRI; + } + + return mask; +} + +/* ---------------- Internal functions --------------------------------- */ + +/** + * char_dev_setup_cdev() - Set up the char device structure for device. + * @dev_usr: Char device user. + * @parent: Parent device pointer. + * @name: Name of registered device. + * + * The char_dev_setup_cdev() function sets up the char_dev + * structure for this device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer has been supplied. + * Error codes from cdev_add and device_create. + */ +static int char_dev_setup_cdev(struct ste_conn_char_dev_user *dev_usr, + struct device *parent, + char *name) +{ + int err = 0; + struct ste_conn_ccd_driver_data *driver_data = + (struct ste_conn_ccd_driver_data *)parent->driver_data; + + if (!driver_data) { + STE_CONN_ERR("Received driver data is empty"); + err = EINVAL; + goto finished; + } + + dev_usr->devt = MKDEV(MAJOR(parent->devt), driver_data->next_free_minor); + + STE_CONN_INFO("char_dev_setup_cdev"); + + /* Store device name */ + dev_usr->name = name; + + cdev_init(&dev_usr->cdev, &ste_conn_char_fops); + dev_usr->cdev.owner = THIS_MODULE; + err = cdev_add(&dev_usr->cdev, dev_usr->devt , 1); + if (err) { + STE_CONN_ERR("Failed to add char dev %d, error %d", err, dev_usr->devt); + goto finished; + } else { + STE_CONN_INFO( + "Added char device %s with major 0x%X and minor 0x%X", + name, MAJOR(dev_usr->devt), MINOR(dev_usr->devt)); + } + + /* Create device node in file system. */ + dev_usr->cdev_device = device_create(parent->class, + parent, dev_usr->devt, NULL, name); + if (IS_ERR(dev_usr->cdev_device)) { + STE_CONN_ERR("Error adding %s device!", name); + err = (int)dev_usr->cdev_device; + goto error_handling_cdev_del; + } + + /*Init mutexs*/ + mutex_init(&dev_usr->read_mutex); + mutex_init(&dev_usr->write_mutex); + + skb_queue_head_init(&dev_usr->rx_queue); + + driver_data->next_free_minor++; + goto finished; + +error_handling_cdev_del: + cdev_del(&(dev_usr->cdev)); + +finished: + return err; +} + +/** + * char_dev_remove_cdev() - Remove char device structure for device. + * @dev_usr: Char device user. + * + * The char_dev_remove_cdev() function releases the char_dev + * structure for this device. + */ +static void char_dev_remove_cdev(struct ste_conn_char_dev_user *dev_usr) +{ + STE_CONN_INFO("char_dev_remove_cdev"); + + if (!dev_usr) { + return; + } + + /*Delete device*/ + cdev_del(&(dev_usr->cdev)); + + /* Remove device node in file system. */ + device_destroy(dev_usr->cdev_device->class, dev_usr->devt); + + kfree(dev_usr->cdev_device); + dev_usr->cdev_device = NULL; + + kfree(dev_usr->name); + dev_usr->name = NULL; + + mutex_destroy(&dev_usr->read_mutex); + mutex_destroy(&dev_usr->write_mutex); +} + +/* ---------------- External functions -------------- */ + +void ste_conn_char_devices_init(int char_dev_usage, struct device *dev) +{ + STE_CONN_INFO("ste_conn_char_devices_init"); + + if (!dev) { + STE_CONN_ERR("NULL supplied for dev"); + return; + } + + if (!char_dev_usage) { + STE_CONN_INFO("No char dev used in ste_conn."); + return; + } else { + STE_CONN_INFO("ste_conn char devices ver %s, char_dev_usage 0x%X", + VERSION, char_dev_usage); + } + + if (char_info) { + STE_CONN_ERR("Char devices already initiated"); + return; + } + + /* Initialize private data. */ + char_info = kzalloc(sizeof(*char_info), GFP_KERNEL); + if (!char_info) { + STE_CONN_ERR("Could not alloc ste_conn_char_info struct."); + return; + } + mutex_init(&char_info->open_mutex); + + if (char_dev_usage & STE_CONN_CHAR_DEV_BT) { + char_dev_setup_cdev(&char_info->bt_cmd_user, dev, + STE_CONN_DEVICES_BT_CMD); + char_dev_setup_cdev(&char_info->bt_acl_user, dev, + STE_CONN_DEVICES_BT_ACL); + char_dev_setup_cdev(&char_info->bt_evt_user, dev, + STE_CONN_DEVICES_BT_EVT); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_FM_RADIO) { + char_dev_setup_cdev(&char_info->fm_radio_user, dev, + STE_CONN_DEVICES_FM_RADIO); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_GNSS) { + char_dev_setup_cdev(&char_info->gnss_user, dev, + STE_CONN_DEVICES_GNSS); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_DEBUG) { + char_dev_setup_cdev(&char_info->debug_user, dev, + STE_CONN_DEVICES_DEBUG); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_STE_TOOLS) { + char_dev_setup_cdev(&char_info->ste_tools_user, dev, + STE_CONN_DEVICES_STE_TOOLS); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_HCI_LOGGER) { + char_dev_setup_cdev(&char_info->hci_logger_user, dev, + STE_CONN_DEVICES_HCI_LOGGER); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_US_CTRL) { + char_dev_setup_cdev(&char_info->us_ctrl_user, dev, + STE_CONN_DEVICES_US_CTRL); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_BT_AUDIO) { + char_dev_setup_cdev(&char_info->bt_audio_user, dev, + STE_CONN_DEVICES_BT_AUDIO); + } + + if (char_dev_usage & STE_CONN_CHAR_DEV_FM_RADIO_AUDIO) { + char_dev_setup_cdev(&char_info->fm_audio_user, dev, + STE_CONN_DEVICES_FM_RADIO_AUDIO); + } +} + +void ste_conn_char_devices_exit(void) +{ + STE_CONN_INFO("ste_conn_char_devices_exit"); + + if (char_info) { + char_dev_remove_cdev(&char_info->bt_cmd_user); + char_dev_remove_cdev(&char_info->bt_acl_user); + char_dev_remove_cdev(&char_info->bt_evt_user); + char_dev_remove_cdev(&char_info->fm_radio_user); + char_dev_remove_cdev(&char_info->gnss_user); + char_dev_remove_cdev(&char_info->debug_user); + char_dev_remove_cdev(&char_info->ste_tools_user); + char_dev_remove_cdev(&char_info->hci_logger_user); + char_dev_remove_cdev(&char_info->us_ctrl_user); + char_dev_remove_cdev(&char_info->bt_audio_user); + char_dev_remove_cdev(&char_info->fm_audio_user); + + mutex_destroy(&char_info->open_mutex); + + + kfree(char_info); + char_info = NULL; + } +} + +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ST-Ericsson Connectivity Char Devices Driver ver " VERSION); +MODULE_VERSION(VERSION); diff --git a/drivers/mfd/ste_conn/ste_conn_char_devices.h b/drivers/mfd/ste_conn/ste_conn_char_devices.h new file mode 100755 index 00000000000..bd1ca219e0e --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_char_devices.h @@ -0,0 +1,37 @@ +/* + * file ste_conn_char_devices.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson Connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_CHAR_DEVICES_H_ +#define _STE_CONN_CHAR_DEVICES_H_ + +/** + * ste_conn_char_devices_init() - Initialize char device module. + * @char_dev_usage: Bitmask indicating what char devs to enable. + * @dev: Parent device for the driver. + * + * The ste_conn_char_devices_init() function initialize the char device module. + */ +extern void ste_conn_char_devices_init(int char_dev_usage, struct device *dev); + +/** + * ste_conn_char_devices_exit() - Release the char device module. + * + * The ste_conn_char_devices_init() function releases + * the char device module. + */ +extern void ste_conn_char_devices_exit(void); + +#endif /* _STE_CONN_CHAR_DEVICES_H_ */ diff --git a/drivers/mfd/ste_conn/ste_conn_cpd.c b/drivers/mfd/ste_conn/ste_conn_cpd.c new file mode 100755 index 00000000000..edc7bd1dffa --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_cpd.c @@ -0,0 +1,2901 @@ +/* + * file ste_conn_cpd.c + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#include <linux/module.h> +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/skbuff.h> +#include <linux/gfp.h> +#include <linux/stat.h> +#include <linux/types.h> +#include <linux/time.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/firmware.h> +#include <linux/mutex.h> +#include <linux/list.h> + +#include <linux/mfd/ste_conn.h> +#include <mach/ste_conn_devices.h> +#include "ste_conn_cpd.h" +#include "ste_conn_ccd.h" +#include "ste_conn_char_devices.h" + +/* #define STE_CONN_DEBUG_LEVEL 20 */ /* Remove comment to enable higher debug */ +#include "ste_conn_debug.h" + +#define VERSION "1.0" + +/* Reserve 1 byte for the HCI H:4 header */ +#define STE_CONN_SKB_RESERVE 1 + +/* Bluetooth Opcode Group Field */ +#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 */ +#define HCI_BT_OCF_READ_LOCAL_VERSION_INFO 0x0001 +#define HCI_BT_OCF_RESET 0x0003 +#define HCI_BT_OCF_VS_STORE_IN_FS 0x0022 +#define HCI_BT_OCF_VS_WRITE_FILE_BLOCK 0x002E +#define HCI_BT_OCF_VS_POWER_SWITCH_OFF 0x0140 +#define HCI_BT_OCF_VS_SYSTEM_RESET 0x0312 + +#define HCI_BT_EVT_CMD_COMPLETE 0x0E +#define HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS 0x02 +#define HCI_BT_EVT_CMD_STATUS 0x0F +#define HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS 0x03 + +/* FM do-command identifiers. */ +#define CPD_FM_DO_AIP_FADE_START 0x0046 +#define CPD_FM_DO_AUP_BT_FADE_START 0x01C2 +#define CPD_FM_DO_AUP_EXT_FADE_START 0x0102 +#define CPD_FM_DO_AUP_FADE_START 0x00A2 +#define CPD_FM_DO_FMR_SETANTENNA 0x0663 +#define CPD_FM_DO_FMR_SP_AFSWITCH_START 0x04A3 +#define CPD_FM_DO_FMR_SP_AFUPDATE_START 0x0463 +#define CPD_FM_DO_FMR_SP_BLOCKSCAN_START 0x0683 +#define CPD_FM_DO_FMR_SP_PRESETPI_START 0x0443 +#define CPD_FM_DO_FMR_SP_SCAN_START 0x0403 +#define CPD_FM_DO_FMR_SP_SEARCH_START 0x03E3 +#define CPD_FM_DO_FMR_SP_SEARCHPI_START 0x0703 +#define CPD_FM_DO_FMR_SP_TUNE_SETCHANNEL 0x03C3 +#define CPD_FM_DO_FMR_SP_TUNE_STEPCHANNEL 0x04C3 +#define CPD_FM_DO_FMT_PA_SETCONTROL 0x01A4 +#define CPD_FM_DO_FMT_PA_SETMODE 0x01E4 +#define CPD_FM_DO_FMT_SP_TUNE_SETCHANNEL 0x0064 +#define CPD_FM_DO_GEN_ANTENNACHECK_START 0x02A1 +#define CPD_FM_DO_GEN_GOTOMODE 0x0041 +#define CPD_FM_DO_GEN_POWERSUPPLY_SETMODE 0x0221 +#define CPD_FM_DO_GEN_SELECTREFERENCECLOCK 0x0201 +#define CPD_FM_DO_GEN_SETPROCESSINGCLOCK 0x0241 +#define CPD_FM_DO_GEN_SETREFERENCECLOCKPLL 0x01A1 +#define CPD_FM_DO_TST_TX_RAMP_START 0x0147 + +/* FM interrupt values for do-command related interrupts. */ +#define CPD_FM_IRPT_OPERATION_SUCCEEDED 0x0000 +#define CPD_FM_IRPT_OPERATION_FAILED 0x0001 +//#define CPD_FM_IRPT_FIQ 0x???? + +/* BT VS OpCodes */ +#define CPD_BT_VS_BT_ENABLE_OPCODE 0xFF10 + +#define HCI_BT_ERROR_NO_ERROR 0x00 + +#define CPD_MAKE_FIRST_BYTE_IN_CMD(__ocf) ((uint8_t)(__ocf & 0x00FF)) +#define CPD_MAKE_SECOND_BYTE_IN_CMD(__ogf, __ocf) ((uint8_t)(__ogf << 2) | ((__ocf >> 8) & 0x0003)) + +#define STE_CONN_BT_PATCH_INFO_FILE "ste_conn_patch_info.fw" +#define STE_CONN_BT_FACTORY_SETTINGS_INFO_FILE "ste_conn_settings_info.fw" +#define STE_CONN_LINE_BUFFER_LENGTH 128 +#define STE_CONN_FILENAME_MAX 128 + +/* Maximum file chunk size which can be sent in one HCI packet */ +#define CPD_SEND_FILE_MAX_CHUNK_SIZE 254 +/* Size of file chunk ID */ +#define CPD_FILE_CHUNK_ID_SIZE 1 +#define CPD_VS_SEND_FILE_START_OFFSET_IN_CMD 4 +#define CPD_BT_CMD_LENGTH_POSITION 3 + +#define STE_CONN_CPD_NAME "ste_conn_cpd\0" +#define STE_CONN_CPD_WQ_NAME "ste_conn_cpd_wq\0" + +/* Defines needed for creation of some hci packets. */ +#define HCI_CMD_H4_POS 0 +#define HCI_CMD_OPCODE_POS 1 +#define HCI_CMD_PARAM_LEN_POS 3 +#define HCI_CMD_PARAM_POS 4 +#define HCI_CMD_HDR_SIZE 4 + +/* Defines needed for HCI VS Store In FS cmd creation. */ +#define HCI_VS_STORE_IN_FS_USR_ID_POS HCI_CMD_PARAM_POS +#define HCI_VS_STORE_IN_FS_USR_ID_SIZE 1 +#define HCI_VS_STORE_IN_FS_PARAM_LEN_POS (HCI_VS_STORE_IN_FS_USR_ID_POS + \ + HCI_VS_STORE_IN_FS_USR_ID_SIZE) +#define HCI_VS_STORE_IN_FS_PARAM_LEN_SIZE 1 +#define HCI_VS_STORE_IN_FS_PARAM_POS (HCI_VS_STORE_IN_FS_PARAM_LEN_POS + \ + HCI_VS_STORE_IN_FS_PARAM_LEN_SIZE) +#define HCI_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE + +#define CPD_SET_MAIN_STATE(__cpd_new_state) \ + STE_CONN_SET_STATE("main_state", cpd_info->main_state, __cpd_new_state) +#define CPD_SET_BOOT_STATE(__cpd_new_state) \ + STE_CONN_SET_STATE("boot_state", cpd_info->boot_state, __cpd_new_state) +#define CPD_SET_CLOSING_STATE(__cpd_new_state) \ + STE_CONN_SET_STATE("closing_state", cpd_info->closing_state, __cpd_new_state) +#define CPD_SET_BOOT_FILE_LOAD_STATE(__cpd_new_state) \ + STE_CONN_SET_STATE("boot_file_load_state", cpd_info->boot_file_load_state, __cpd_new_state) +#define CPD_SET_BOOT_DOWNLOAD_STATE(__cpd_new_state) \ + STE_CONN_SET_STATE("boot_download_state", cpd_info->boot_download_state, __cpd_new_state) + + +/* ------------------ Internal type definitions --------------------------- */ + +#define LOGGER_DIRECTION_TX 0 +#define LOGGER_DIRECTION_RX 1 + +/** + * struct ste_conn_cpd_work_struct - Work structure for ste_conn CPD module. + * @work: Work structure. + * @skb: Data packet. + * @data: Private data for ste_conn CPD. + * + * This structure is used to pack work for work queue. + */ +struct ste_conn_cpd_work_struct{ + struct work_struct work; + struct sk_buff *skb; + void *data; +}; + +/** + * enum cpd_main_state - Main-state for CPD. + * @CPD_STATE_INITIALIZING: CPD initializing. + * @CPD_STATE_IDLE: No user registered to CPD. + * @CPD_STATE_BOOTING: CPD booting after first user is registered. + * @CPD_STATE_CLOSING: CPD closing after last user has deregistered. + * @CPD_STATE_RESETING: CPD reset requested. + * @CPD_STATE_ACTIVE: CPD up and running with at least one user. + */ +enum cpd_main_state { + CPD_STATE_INITIALIZING, + CPD_STATE_IDLE, + CPD_STATE_BOOTING, + CPD_STATE_CLOSING, + CPD_STATE_RESETING, + CPD_STATE_ACTIVE +}; + +/** + * enum cpd_boot_state - BOOT-state for CPD. + * @BOOT_STATE_NOT_STARTED: Boot has not yet started. + * @BOOT_STATE_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation command has been sent. + * @BOOT_STATE_SEND_BD_ADDRESS: VS Store In FS cmd with bd address has been sent. + * @BOOT_STATE_GET_FILES_TO_LOAD: CPD is retreiving file to load. + * @BOOT_STATE_DOWNLOAD_PATCH: CPD is downloading patches. + * @BOOT_STATE_ACTIVATE_PATCHES_AND_SETTINGS: CPD is activating patches and settings. + * @BOOT_STATE_READY: CPD boot is ready. + * @BOOT_STATE_FAILED: CPD boot failed. + */ +enum cpd_boot_state { + BOOT_STATE_NOT_STARTED, + BOOT_STATE_READ_LOCAL_VERSION_INFORMATION, + BOOT_STATE_SEND_BD_ADDRESS, + BOOT_STATE_GET_FILES_TO_LOAD, + BOOT_STATE_DOWNLOAD_PATCH, + BOOT_STATE_ACTIVATE_PATCHES_AND_SETTINGS, + BOOT_STATE_READY, + BOOT_STATE_FAILED +}; + +/** + * enum cpd_closing_state - CLOSING-state for CPD. + * @CLOSING_STATE_RESET: HCI RESET_CMD has been sent. + * @CLOSING_STATE_POWER_SWITCH_OFF: HCI VS_POWER_SWITCH_OFF command has been sent. + * @CLOSING_STATE_GPIO_DEREGISTER: Time to deregister GPIOs. + */ +enum cpd_closing_state { + CLOSING_STATE_RESET, + CLOSING_STATE_POWER_SWITCH_OFF, + CLOSING_STATE_GPIO_DEREGISTER +}; + +/** + * enum cpd_boot_file_load_state - BOOT_FILE_LOAD-state for CPD. + * @BOOT_FILE_LOAD_STATE_LOAD_PATCH: Loading patches. + * @BOOT_FILE_LOAD_STATE_LOAD_STATIC_SETTINGS: Loading static settings. + * @BOOT_FILE_LOAD_STATE_NO_MORE_FILES: No more files to load. + * @BOOT_FILE_LOAD_STATE_FAILED: File loading failed. + */ +enum cpd_boot_file_load_state { + BOOT_FILE_LOAD_STATE_LOAD_PATCH, + BOOT_FILE_LOAD_STATE_LOAD_STATIC_SETTINGS, + BOOT_FILE_LOAD_STATE_NO_MORE_FILES, + BOOT_FILE_LOAD_STATE_FAILED +}; + +/** + * enum cpd_boot_download_state - BOOT_DOWNLOAD state. + * @BOOT_DOWNLOAD_STATE_PENDING: Download in progress. + * @BOOT_DOWNLOAD_STATE_SUCCESS: Download successfully finished. + * @BOOT_DOWNLOAD_STATE_FAILED: Downloading failed. + */ +enum cpd_boot_download_state { + BOOT_DOWNLOAD_STATE_PENDING, + BOOT_DOWNLOAD_STATE_SUCCESS, + BOOT_DOWNLOAD_STATE_FAILED +}; + +/** + * enum cpd_fm_radio_mode - FM Radio mode. + * It's needed because some FM do-commands generate interrupts + * only when the fm driver is in specific mode and we need to know + * if we should expect the interrupt. + * @CPD_FM_RADIO_MODE_IDLE: Radio mode is Idle (default). + * @CPD_FM_RADIO_MODE_TRANSMITTER: Radio mode is set to FMT. + * @CPD_FM_RADIO_MODE_RECEIVER: Radio mode is set to FMR. + */ +enum cpd_fm_radio_mode { + CPD_FM_RADIO_MODE_IDLE, + CPD_FM_RADIO_MODE_FMT, + CPD_FM_RADIO_MODE_FMR +}; + +/** + * struct ste_conn_cpd_users - Stores all current users of CPD. + * @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. + * @nbr_of_users: Number of users currently registered (not including the HCI logger). + */ +struct ste_conn_cpd_users { + struct ste_conn_device *bt_cmd; + struct ste_conn_device *bt_acl; + struct ste_conn_device *bt_evt; + struct ste_conn_device *fm_radio; + struct ste_conn_device *gnss; + struct ste_conn_device *debug; + struct ste_conn_device *ste_tools; + struct ste_conn_device *hci_logger; + struct ste_conn_device *us_ctrl; + struct ste_conn_device *bt_audio; + struct ste_conn_device *fm_radio_audio; + int nbr_of_users; +}; + +/** + * struct ste_conn_local_chip_info - Stores local controller info. + * @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 subbversion of local controller. + * + * According to Bluetooth HCI Read Local Version Information command. + */ +struct ste_conn_local_chip_info { + uint8_t hci_version; + uint16_t hci_revision; + uint8_t lmp_pal_version; + uint16_t manufacturer; + uint16_t lmp_pal_subversion; +}; + +/** + * struct tx_list_item - Structure to store skb and sender name if skb can't be sent due to flow control. + * @list: list_head struct. + * @skb: sk_buff struct. + * @dev: sending device struct pointer. + */ +struct tx_list_item { + struct list_head list; + struct sk_buff *skb; + struct ste_conn_device *dev; +}; + +/** + * struct ste_conn_cpd_info - Main info structure for CPD. + * @users: Stores all users of CPD. + * @local_chip_info: Stores information of local controller. + * @patch_file_name: Stores patch file name. + * @settings_file_name: Stores settings file name. + * @fw_file: Stores firmware file (patch or settings). + * @fw_file_rd_offset: Current read offset in firmware file. + * @chunk_id: Stores current chunk ID of write file operations. + * @main_state: Current Main-state of CPD. + * @boot_state: Current BOOT-state of CPD. + * @closing_state: Current CLOSING-state of CPD. + * @boot_file_load_state: Current BOOT_FILE_LOAD-state of CPD. + * @boot_download_state: Current BOOT_DOWNLOAD-state of CPD. + * @wq: CPD workqueue. + * @hci_logger_config: Stores HCI logger configuration. + * @setup_lock: Spinlock for setup of CPD. + * @dev: Device structure for STE Connectivity driver. + * @h4_channels: HCI H:4 channel used by this device. + * @tx_list_bt: TX queue for HCI BT cmds when nr of cmds allowed is 0 (ste_conn internal flow ctrl). + * @tx_list_fm: TX queue for HCI FM cmds when nr of cmds allowed is 0 (ste_conn internal flow ctrl). + * @tx_bt_mutex: Mutex used to protect some global structures related to internal BT cmd flow control. + * @tx_fm_mutex: Mutex used to protect some global structures related to internal FM cmd flow control. + * @tx_fm_audio_awaiting_irpt: Indicates if an FM interrupt evt related to audio driver cmd is expected. + * @cpd_fm_radio_mode: Current FM radio mode. + * @tx_nr_pkts_allowed_bt: Number of packets allowed to send on BT HCI CMD H4 channel. + * @tx_nr_pkts_allowed_fm: Number of packets allowed to send on BT FM RADIO H4 channel. + * @tx_nr_outstanding_cmds_bt: Number of packets sent but not confirmed on BT HCI CMD H4 channel. + * @hci_audio_cmd_opcode_bt: Stores the OpCode of the last sent audio driver HCI BT CMD. + * @hci_audio_fm_cmd_id: Stores the Cmd Id of the last sent audio driver HCI FM RADIO cmd. + */ +struct ste_conn_cpd_info { + struct ste_conn_cpd_users users; + struct ste_conn_local_chip_info local_chip_info; + char *patch_file_name; + char *settings_file_name; + const struct firmware *fw_file; + int fw_file_rd_offset; + uint8_t chunk_id; + enum cpd_main_state main_state; + enum cpd_boot_state boot_state; + enum cpd_closing_state closing_state; + enum cpd_boot_file_load_state boot_file_load_state; + enum cpd_boot_download_state boot_download_state; + struct workqueue_struct *wq; + struct ste_conn_cpd_hci_logger_config hci_logger_config; + spinlock_t setup_lock; + struct device *dev; + struct ste_conn_ccd_h4_channels h4_channels; + struct list_head tx_list_bt; + struct list_head tx_list_fm; + struct mutex tx_bt_mutex; + struct mutex tx_fm_mutex; + bool tx_fm_audio_awaiting_irpt; + enum cpd_fm_radio_mode fm_radio_mode; + int tx_nr_pkts_allowed_bt; + int tx_nr_pkts_allowed_fm; + int tx_nr_outstanding_cmds_bt; + uint16_t hci_audio_cmd_opcode_bt; + uint16_t hci_audio_fm_cmd_id; +}; + +/* ------------------ Internal variable declarations ---------------------- */ + +/* + * cpd_info - Main information object for CPD. + */ +static struct ste_conn_cpd_info *cpd_info; + +/* + * cpd_msg_reset_cmd_req - Hardcoded HCI Reset command. + */ +static const uint8_t cpd_msg_reset_cmd_req[] = { + 0x00, /* Reserved for H4 channel*/ + CPD_MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_RESET), + CPD_MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_CTRL_BB, HCI_BT_OCF_RESET), + 0x00 +}; + +/* + * cpd_msg_read_local_version_information_cmd_req - + * Hardcoded HCI Read Local Version Information command + */ +static const uint8_t cpd_msg_read_local_version_information_cmd_req[] = { + 0x00, /* Reserved for H4 channel*/ + CPD_MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_READ_LOCAL_VERSION_INFO), + CPD_MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_LINK_INFO, HCI_BT_OCF_READ_LOCAL_VERSION_INFO), + 0x00 +}; + +/* + * cpd_msg_vs_store_in_fs_cmd_req - Hardcoded HCI Store in FS command. + */ +static const uint8_t cpd_msg_vs_store_in_fs_cmd_req[] = { + 0x00, /* Reserved for H4 channel*/ + CPD_MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_STORE_IN_FS), + CPD_MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_STORE_IN_FS), + 0x00, /* 1 byte for HCI cmd length. */ + 0x00, /* 1 byte for User_Id. */ + 0x00 /* 1 byte for vs_store_in_fs_cmd_req parameter data length. */ +}; + +/* + * cpd_msg_vs_write_file_block_cmd_req - + * Hardcoded HCI Write File Block vendor specific command + */ +static const uint8_t cpd_msg_vs_write_file_block_cmd_req[] = { + 0x00, /* Reserved for H4 channel*/ + CPD_MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_WRITE_FILE_BLOCK), + CPD_MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_WRITE_FILE_BLOCK), + 0x00 +}; + +/* + * cpd_msg_vs_system_reset_cmd_req - + * Hardcoded HCI System Reset vendor specific command + */ +static const uint8_t cpd_msg_vs_system_reset_cmd_req[] = { + 0x00, /* Reserved for H4 channel*/ + CPD_MAKE_FIRST_BYTE_IN_CMD(HCI_BT_OCF_VS_SYSTEM_RESET), + CPD_MAKE_SECOND_BYTE_IN_CMD(HCI_BT_OGF_VS, HCI_BT_OCF_VS_SYSTEM_RESET), + 0x00 +}; + +/* + * ste_conn_cpd_wait_queue - Main Wait Queue in CPD. + */ +static DECLARE_WAIT_QUEUE_HEAD(ste_conn_cpd_wait_queue); + +/* + * time_15s - 15 second time struct. + */ +static struct timeval time_15s = { + .tv_sec = 15, + .tv_usec = 0 +}; + +/* + * time_1s - 1 second time struct. + */ +static struct timeval time_1s = { + .tv_sec = 1, + .tv_usec = 0 +}; + +/* + * time_500ms - 500 millisecond time struct. + */ +static struct timeval time_500ms = { + .tv_sec = 0, + .tv_usec = 500 * USEC_PER_MSEC +}; + +/* + * time_100ms - 100 millisecond time struct. + */ +static struct timeval time_100ms = { + .tv_sec = 0, + .tv_usec = 100 * USEC_PER_MSEC +}; + +/* + * time_50ms - 50 millisecond time struct. + */ +static struct timeval time_50ms = { + .tv_sec = 0, + .tv_usec = 50 * USEC_PER_MSEC +}; + +/* ------------------ Internal function declarations ---------------------- */ +static int cpd_check_for_audio_evt(int h4_channel, struct ste_conn_device **dev, + const struct sk_buff * const skb); +static void cpd_update_internal_flow_control_h4_bt_cmd(const struct sk_buff * const skb); +static void cpd_update_internal_flow_control_fm(const struct sk_buff * const skb); +static bool cpd_fm_irpt_expected(uint16_t cmd_id); +static bool cpd_fm_is_do_cmd_irpt(uint16_t irpt_val); +static int cpd_enable_hci_logger(struct sk_buff *skb); +static int cpd_find_h4_user(int h4_channel, struct ste_conn_device **dev_store); +static int cpd_add_h4_user(struct ste_conn_device *dev, const char * const name); +static int cpd_remove_h4_user(struct ste_conn_device **dev); +static void cpd_chip_startup(void); +static void cpd_chip_shutdown(void); +static bool cpd_handle_internal_rx_data_bt_evt(struct sk_buff *skb); +static void cpd_create_work_item(work_func_t work_func, struct sk_buff *skb, + void *data); +static bool cpd_handle_reset_cmd_complete_evt(uint8_t *data); +static bool cpd_handle_read_local_version_info_cmd_complete_evt(uint8_t *data); +static bool cpd_handle_vs_store_in_fs_cmd_complete_evt(uint8_t *data); +static bool cpd_handle_vs_write_file_block_cmd_complete_evt(uint8_t *data); +static bool cpd_handle_vs_power_switch_off_cmd_complete_evt(uint8_t *data); +static bool cpd_handle_vs_system_reset_cmd_complete_evt(uint8_t *data); +static void cpd_work_power_off_chip(struct work_struct *work); +static void cpd_work_reset_after_error(struct work_struct *work); +static void cpd_work_load_patch_and_settings(struct work_struct *work); +static void cpd_work_continue_with_file_download(struct work_struct *work); +static bool cpd_get_file_to_load(const struct firmware *fw, char **file_name_p); +static char *cpd_get_one_line_of_text(char *wr_buffer, int max_nbr_of_bytes, + char *rd_buffer, int *bytes_copied); +static void cpd_create_and_send_bt_cmd(const uint8_t *data, int length); +static void cpd_send_bd_address(void); +static void cpd_transmit_skb_to_ccd(struct sk_buff *skb, bool use_logger); +static void cpd_transmit_skb_to_ccd_with_flow_ctrl(struct sk_buff *skb, + struct ste_conn_device *dev, const uint8_t h4_header); +static void cpd_transmit_skb_to_ccd_with_flow_ctrl_bt(struct sk_buff *skb, + struct ste_conn_device *dev); +static void cpd_transmit_skb_to_ccd_with_flow_ctrl_fm(struct sk_buff *skb, + struct ste_conn_device *dev); +static void cpd_transmit_skb_from_tx_queue_bt(void); +static void cpd_transmit_skb_from_tx_queue_fm(void); +static void cpd_read_and_send_file_part(void); +static void cpd_send_patch_file(void); +static void cpd_send_settings_file(void); + +static void cpd_handle_reset_of_user(struct ste_conn_device **dev); +static void cpd_free_user_dev(struct ste_conn_device **dev); + +/* ------------------ ste_conn API functions ------------------------------ */ + +struct ste_conn_device *ste_conn_register(char *name, + struct ste_conn_callbacks *cb) +{ + struct ste_conn_device *current_dev; + int err = 0; + + STE_CONN_INFO("ste_conn_register %s", name); + + if (!cpd_info) { + STE_CONN_ERR("Hardware not started"); + return NULL; + } + + /* Wait for state CPD_STATE_IDLE or CPD_STATE_ACTIVE. */ + if (wait_event_interruptible_timeout(ste_conn_cpd_wait_queue, + ((CPD_STATE_IDLE == cpd_info->main_state) || + ( CPD_STATE_ACTIVE == cpd_info->main_state)), + timeval_to_jiffies(&time_50ms)) <= 0) { + STE_CONN_ERR( + "ste_conn_register currently busy (0x%X). Try again.", + cpd_info->main_state); + return NULL; + } + + /* Allocate device */ + current_dev = kzalloc(sizeof(*current_dev), GFP_KERNEL); + if (current_dev) { + err = ste_conn_devices_get_h4_channel(name, &(current_dev->h4_channel)); + if (err) { + STE_CONN_ERR("Couldn't find H4 channel for %s", name); + goto error_handling; + } + current_dev->dev = cpd_info->dev; + current_dev->callbacks = kmalloc(sizeof(*(current_dev->callbacks)), GFP_KERNEL); + if (current_dev->callbacks) { + memcpy((char *)current_dev->callbacks, (char *)cb, sizeof(*(current_dev->callbacks))); + } else { + STE_CONN_ERR("Couldn't allocate callbacks "); + goto error_handling; + } + + /* Retreive pointer to the correct CPD user structure */ + err = cpd_add_h4_user(current_dev, name); + + if (!err) { + STE_CONN_DBG( + "H:4 channel 0x%X registered", + current_dev->h4_channel); + } else { + STE_CONN_ERR( + "H:4 channel 0x%X already registered or other error (%d)", + current_dev->h4_channel, err); + goto error_handling; + } + } else { + STE_CONN_ERR("Couldn't allocate current dev"); + goto error_handling; + } + + if ((CPD_STATE_ACTIVE != cpd_info->main_state) && + (cpd_info->users.nbr_of_users == 1)) { + /* Open CCD and start-up the chip */ + ste_conn_ccd_set_chip_power(true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_interruptible(timeval_to_jiffies(&time_100ms)); + + err = ste_conn_ccd_open(); + + if (err) { + /* Remove the user. If there is no error it will be freed as well */ + cpd_remove_h4_user(¤t_dev); + goto finished; + } + + cpd_chip_startup(); + + /* Wait up to 15 seconds for chip to start */ + STE_CONN_DBG("Wait up to 15 seconds for chip to start.."); + wait_event_interruptible_timeout(ste_conn_cpd_wait_queue, + ((CPD_STATE_ACTIVE == cpd_info->main_state) || + (CPD_STATE_IDLE == cpd_info->main_state)), + timeval_to_jiffies(&time_15s)); + if (CPD_STATE_ACTIVE != cpd_info->main_state) { + STE_CONN_ERR( + "ST-Ericsson Connectivity Controller Driver failed to start"); + + /* Close CCD and power off the chip */ + ste_conn_ccd_close(); + + /* Remove the user. If there is no error it will be freed as well */ + cpd_remove_h4_user(¤t_dev); + + /* Chip shut-down finished, set correct state. */ + CPD_SET_MAIN_STATE(CPD_STATE_IDLE); + } + } + goto finished; + +error_handling: + cpd_free_user_dev(¤t_dev); +finished: + return current_dev; +} + +void ste_conn_deregister(struct ste_conn_device *dev) +{ + int h4_channel; + int err = 0; + + STE_CONN_INFO("ste_conn_deregister"); + + if (!cpd_info) { + STE_CONN_ERR("Hardware not started"); + return; + } + + if (!dev) { + STE_CONN_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 = cpd_remove_h4_user(&dev); + + if (!err) { + STE_CONN_DBG( + "H:4 channel 0x%X deregistered", + h4_channel); + + /* If this was the last user, shutdown the chip and close CCD */ + if (0 == cpd_info->users.nbr_of_users) { + /* Make sure that the chip not have been shut down already. */ + if (CPD_STATE_IDLE != cpd_info->main_state) { + CPD_SET_MAIN_STATE(CPD_STATE_CLOSING); + cpd_chip_shutdown(); + + /* Wait up to 15 seconds for chip to shut-down */ + STE_CONN_DBG("Wait up to 15 seconds for chip to shut-down.."); + wait_event_interruptible_timeout(ste_conn_cpd_wait_queue, + (CPD_STATE_IDLE == cpd_info->main_state), + timeval_to_jiffies(&time_15s)); + + if (CPD_STATE_IDLE != cpd_info->main_state) { + STE_CONN_ERR( + "ST-Ericsson Connectivity Controller Driver was shut-down with problems."); + + /* Close CCD and power off the chip */ + ste_conn_ccd_close(); + + /* Chip shut-down finished, set correct state. */ + CPD_SET_MAIN_STATE(CPD_STATE_IDLE); + } + + } + } + } else { + STE_CONN_ERR( + "Trying to deregister non-registered H:4 channel 0x%X or other error %d", + h4_channel, err); + } +} + +int ste_conn_reset(struct ste_conn_device *dev) +{ + int err = 0; + + STE_CONN_INFO("ste_conn_reset"); + + if (!cpd_info) { + STE_CONN_ERR("Hardware not started"); + return -EACCES; + } + + CPD_SET_CLOSING_STATE(CLOSING_STATE_POWER_SWITCH_OFF); + CPD_SET_MAIN_STATE(CPD_STATE_RESETING); + + /* Shutdown the chip */ + cpd_chip_shutdown(); + + /* Inform all registered users about the reset and free the user devices + * Don't send reset for debug and logging channels + */ + cpd_handle_reset_of_user(&(cpd_info->users.bt_cmd)); + cpd_handle_reset_of_user(&(cpd_info->users.bt_acl)); + cpd_handle_reset_of_user(&(cpd_info->users.bt_evt)); + cpd_handle_reset_of_user(&(cpd_info->users.fm_radio)); + cpd_handle_reset_of_user(&(cpd_info->users.gnss)); + + cpd_info->users.nbr_of_users = 0; + + /* Reset finished. We are now idle until first user is registered */ + CPD_SET_MAIN_STATE(CPD_STATE_IDLE); + + /* Send wake-up since this might have been called from a failed boot. + * No harm done if it is a CPD user who called. + */ + wake_up_interruptible(&ste_conn_cpd_wait_queue); + + return err; +} + +struct sk_buff *ste_conn_alloc_skb(unsigned int size, gfp_t priority) +{ + struct sk_buff *skb; + + STE_CONN_INFO("ste_conn_alloc_skb"); + + /* Allocate the SKB and reserve space for the header */ + skb = alloc_skb(size + STE_CONN_SKB_RESERVE, priority); + + if (skb) { + skb_reserve(skb, STE_CONN_SKB_RESERVE); + } + return skb; +} + +int ste_conn_write(struct ste_conn_device *dev, struct sk_buff *skb) +{ + int err = 0; + uint8_t *h4_header; + + STE_CONN_INFO("ste_conn_write"); + + if (!cpd_info) { + STE_CONN_ERR("Hardware not started"); + return -EACCES; + } + + if (!dev) { + STE_CONN_ERR("ste_conn_write with no device"); + return -EINVAL; + } + + if (!skb) { + STE_CONN_ERR("ste_conn_write with no sk_buffer"); + return -EINVAL; + } + + + if (cpd_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 = cpd_enable_hci_logger(skb); + } else if (CPD_STATE_ACTIVE == cpd_info->main_state) { + /* Move the data pointer to the H:4 header position and store the H4 header */ + h4_header = skb_push(skb, STE_CONN_SKB_RESERVE); + *h4_header = (uint8_t)dev->h4_channel; + + /* Because there are more users of some H4 channels (currently audio application + * for BT cmd and FM channel) we need to have an internal HCI cmd flow control + * in ste_conn driver. */ + if (cpd_info->h4_channels.bt_cmd_channel == *h4_header || + cpd_info->h4_channels.fm_radio_channel == *h4_header) { + cpd_transmit_skb_to_ccd_with_flow_ctrl(skb, dev, *h4_header); + } else { + /* Other channels are not affected by the flow control so transmit the sk_buffer to CCD */ + cpd_transmit_skb_to_ccd(skb, dev->logger_enabled); + } + } else { + STE_CONN_ERR("Trying to transmit data when ste_conn CPD is not active"); + err = -EACCES; + kfree_skb(skb); + } + + return err; +} + +/* ---------------- External functions -------------- */ + +void ste_conn_cpd_hw_registered(void) +{ + STE_CONN_INFO("ste_conn_cpd_hw_registered"); + + /* Now it is time to shutdown the controller to reduce + * power consumption until any users register + */ + cpd_chip_shutdown(); +} + +void ste_conn_cpd_hw_deregistered(void) +{ + STE_CONN_INFO("ste_conn_cpd_hw_deregistered"); + + CPD_SET_MAIN_STATE(CPD_STATE_INITIALIZING); +} + +void ste_conn_cpd_data_received(struct sk_buff *skb) +{ + struct ste_conn_device *dev = NULL; + uint8_t h4_channel; + int err = 0; + bool pkt_handled = false; + + STE_CONN_INFO("ste_conn_cpd_data_received"); + + if (!skb) { + STE_CONN_ERR("No data supplied"); + return; + } + + h4_channel = *(skb->data); + + STE_CONN_DBG_DATA_CONTENT("Received %d bytes: 0x %02X %02X %02X %02X %02X %02X %02X %02X", + skb->len, skb->data[0], skb->data[1], + skb->data[2], skb->data[3], skb->data[4], + skb->data[5], skb->data[6], skb->data[7]); + + /* If this is a BT HCI event we might have to handle it internally */ + if (cpd_info->h4_channels.bt_evt_channel == h4_channel) { + pkt_handled = cpd_handle_internal_rx_data_bt_evt(skb); + STE_CONN_DBG("pkt_handled %d", pkt_handled); + + /* If packet has been handled internally just return. */ + if (pkt_handled) { + return; + } + } + + /* If it's BT EVT or FM channel data check if it's evt generated by previously sent audio app cmd. + * If so then that evt should be dispatched to BT or FM audio user, respectively. */ + err = cpd_check_for_audio_evt(h4_channel, &dev, skb); + + /* If it wasn't an audio related evt and no error occured proceed with checking other users. */ + if (!err && !dev) { + err = cpd_find_h4_user(h4_channel, &dev); + } + + if (!err) { + /* If HCI logging is enabled for this channel, copy the data to + * the HCI logging output. + */ + if (cpd_info->users.hci_logger && dev->logger_enabled) { + /* Alloc a new sk_buffer and copy the data into it. + * Then send it to the HCI logger. */ + struct sk_buff *skb_log = alloc_skb(skb->len + 1, GFP_KERNEL); + if (skb_log) { + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + skb_log->data[0] = (uint8_t) LOGGER_DIRECTION_RX; + + cpd_info->users.hci_logger->callbacks->read_cb(cpd_info->users.hci_logger, skb_log); + } else { + STE_CONN_ERR("Couldn't allocate skb_log"); + } + } + + /* Remove the H4 header */ + (void)skb_pull(skb, STE_CONN_SKB_RESERVE); + + /* Call the Read callback */ + if (dev->callbacks->read_cb) { + dev->callbacks->read_cb(dev, skb); + } + } else { + STE_CONN_ERR("H:4 channel: 0x%X, does not match device", h4_channel); + kfree_skb(skb); + } + +} + +int ste_conn_cpd_init(int char_dev_usage, struct device *dev) +{ + int err = 0; + + STE_CONN_INFO("ste_conn_cpd_init"); + + if (cpd_info) { + STE_CONN_ERR("CPD already initiated"); + err = -EBUSY; + goto finished; + } + /* First allocate our info structure and initialize the parameters */ + cpd_info = kzalloc(sizeof(*cpd_info), GFP_KERNEL); + + if (cpd_info) { + cpd_info->main_state = CPD_STATE_INITIALIZING; + cpd_info->boot_state = BOOT_STATE_NOT_STARTED; + cpd_info->closing_state = CLOSING_STATE_POWER_SWITCH_OFF; + cpd_info->boot_file_load_state = BOOT_FILE_LOAD_STATE_NO_MORE_FILES; + + cpd_info->dev = dev; + + cpd_info->tx_nr_pkts_allowed_bt = 1; + cpd_info->tx_nr_pkts_allowed_fm = 1; + cpd_info->tx_nr_outstanding_cmds_bt = 0; + cpd_info->hci_audio_cmd_opcode_bt = 0xFFFF; + cpd_info->hci_audio_fm_cmd_id = 0xFFFF; + + /* Get the H4 channel ID for all channels */ + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_CMD, + &(cpd_info->h4_channels.bt_cmd_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_ACL, + &(cpd_info->h4_channels.bt_acl_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_BT_EVT, + &(cpd_info->h4_channels.bt_evt_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_GNSS, + &(cpd_info->h4_channels.gnss_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_FM_RADIO, + &(cpd_info->h4_channels.fm_radio_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_DEBUG, + &(cpd_info->h4_channels.debug_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_STE_TOOLS, + &(cpd_info->h4_channels.ste_tools_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_HCI_LOGGER, + &(cpd_info->h4_channels.hci_logger_channel)); + ste_conn_devices_get_h4_channel(STE_CONN_DEVICES_US_CTRL, + &(cpd_info->h4_channels.us_ctrl_channel)); + + cpd_info->wq = create_singlethread_workqueue(STE_CONN_CPD_WQ_NAME); + if (!cpd_info->wq) { + STE_CONN_ERR("Could not create workqueue"); + err = -ENOMEM; + goto error_handling; + } + + /* Initialize linked lists for HCI BT and FM cmds that can't be sent due to internal ste_conn flow control. */ + INIT_LIST_HEAD(&cpd_info->tx_list_bt); + INIT_LIST_HEAD(&cpd_info->tx_list_fm); + + /* Initialize tx mutexes. */ + mutex_init(&cpd_info->tx_bt_mutex); + mutex_init(&cpd_info->tx_fm_mutex); + + /* Initialize the spin lock */ + spin_lock_init(&cpd_info->setup_lock); + + /* Initialize the character devices */ + ste_conn_char_devices_init(char_dev_usage, dev); + + /* Allocate file names that will be used, deallocated in ste_conn_cpd_exit */ + cpd_info->patch_file_name = kzalloc(STE_CONN_FILENAME_MAX + 1, GFP_KERNEL); + if (!cpd_info->patch_file_name) { + STE_CONN_ERR("Couldn't allocate name buffer for patch file."); + err = -ENOMEM; + goto error_handling_destroy_wq; + } + /* Allocate file names that will be used, deallocaded in ste_conn_cpd_exit */ + cpd_info->settings_file_name = kzalloc(STE_CONN_FILENAME_MAX + 1, GFP_KERNEL); + if (!cpd_info->settings_file_name) { + STE_CONN_ERR("Couldn't allocate name buffers settings file."); + err = -ENOMEM; + goto error_handling_destroy_wq; + } + } else { + STE_CONN_ERR("Couldn't allocate cpd_info"); + err = -ENOMEM; + goto finished; + } + + goto finished; + +error_handling_destroy_wq: + destroy_workqueue(cpd_info->wq); +error_handling: + if (cpd_info) { + kfree(cpd_info->patch_file_name); + kfree(cpd_info->settings_file_name); + kfree(cpd_info); + cpd_info = NULL; + } + +finished: + return err; +} + +void ste_conn_cpd_exit(void) +{ + STE_CONN_INFO("ste_conn_cpd_exit"); + + if (!cpd_info) { + STE_CONN_ERR("cpd not initiated"); + return; + } + + /* Remove initialized character devices */ + ste_conn_char_devices_exit(); + + /* Free the user devices */ + cpd_free_user_dev(&(cpd_info->users.bt_cmd)); + cpd_free_user_dev(&(cpd_info->users.bt_acl)); + cpd_free_user_dev(&(cpd_info->users.bt_evt)); + cpd_free_user_dev(&(cpd_info->users.fm_radio)); + cpd_free_user_dev(&(cpd_info->users.gnss)); + cpd_free_user_dev(&(cpd_info->users.debug)); + cpd_free_user_dev(&(cpd_info->users.ste_tools)); + cpd_free_user_dev(&(cpd_info->users.hci_logger)); + cpd_free_user_dev(&(cpd_info->users.us_ctrl)); + + /* Free everything allocated in cpd_info */ + kfree(cpd_info->patch_file_name); + kfree(cpd_info->settings_file_name); + + /* Destroy mutexes. */ + mutex_destroy(&cpd_info->tx_bt_mutex); + mutex_destroy(&cpd_info->tx_fm_mutex); + + if (cpd_info->fw_file) { + release_firmware(cpd_info->fw_file); + } + destroy_workqueue(cpd_info->wq); + kfree(cpd_info); + cpd_info = NULL; +} + +/* ---------------- Internal functions --------------------------------- */ + +/** + * cpd_check_for_audio_evt() - Check if incoming data packet is an audio related packet. + * @h4_channel: H4 channel. + * @dev: Stored ste_conn device. + * @skb: skb with received packet. + * + * The cpd_check_for_audio_evt() checks if incoming data packet is an audio related + * BT Cmd Cmpl/Cmd Status Evt or FM Evt. + * + * Returns: + * 0 - if no error occured. + * -EINVAL - if ste_conn_device not found. + */ +static int cpd_check_for_audio_evt(int h4_channel, struct ste_conn_device **dev, + const struct sk_buff * const skb) +{ + uint8_t *data = &skb->data[STE_CONN_SKB_RESERVE]; + uint8_t event_code = data[0]; + int err = 0; + + /* BT evt */ + if (h4_channel == cpd_info->h4_channels.bt_evt_channel) { + uint16_t opcode = 0; + + cpd_update_internal_flow_control_h4_bt_cmd(skb); + + if (HCI_BT_EVT_CMD_COMPLETE == event_code) { + opcode = (uint16_t)(data[3] | (data[4] << 8)); + } else if (HCI_BT_EVT_CMD_STATUS == event_code) { + opcode = (uint16_t)(data[4] | (data[5] << 8)); + } + + if (opcode != 0 && opcode == cpd_info->hci_audio_cmd_opcode_bt) { + STE_CONN_INFO("BT OpCode match = 0x%x", opcode); + *dev = cpd_info->users.bt_audio; + + if (!*dev) { + STE_CONN_ERR("H:4 channel not registered in cpd_info: 0x%X", h4_channel); + err = -EINVAL; + } + } + /* FM evt */ + } else if (h4_channel == cpd_info->h4_channels.fm_radio_channel) { + + cpd_update_internal_flow_control_fm(skb); + + /* 2nd byte for cmd cmpl evt is Status and equals always 0x00. */ + if (data[1] == 0x00) { + /* Get the cmd id - bits 15-3 within cmd cmpl evt. */ + uint16_t cmd_id = (uint16_t)(((data[6] | (data[7] << 8)) & 0xFFF8) >> 3); + + if (cmd_id == cpd_info->hci_audio_fm_cmd_id) { + STE_CONN_INFO("FM Audio Function Code match = 0x%x", cmd_id); + *dev = cpd_info->users.fm_radio_audio; + + if (!*dev) { + STE_CONN_ERR("H:4 channel not registered in cpd_info: 0x%X", h4_channel); + err = -EINVAL; + } + } + /* In case of an Interrupt 2nd byte is always 0xFE. */ + } else if (data[1] == 0xFE) { + uint16_t irpt_val = (uint16_t)(data[3] | (data[4] << 8)); + + if (cpd_fm_is_do_cmd_irpt(irpt_val) && (cpd_info->tx_fm_audio_awaiting_irpt)) { + STE_CONN_INFO("FM Audio Interrupt match."); + *dev = cpd_info->users.fm_radio_audio; + + cpd_info->tx_fm_audio_awaiting_irpt = false; + + if (!*dev) { + STE_CONN_ERR("H:4 channel not registered in cpd_info: 0x%X", h4_channel); + err = -EINVAL; + } + } + } + } + + return err; +} + +/** + * cpd_update_internal_flow_control_h4_bt_cmd() - Update number of tickets and number of outstanding commands for BT CMD channel. + * @skb: skb with received packet. + * + * The cpd_update_internal_flow_control_h4_bt_cmd() checks if incoming data packet is BT Cmd Cmpl/Cmd Status + * Evt and if so updates number of tickets and number of outstanding commands. It also calls function to send queued + * cmds (if the list of queued cmds is not empty). + */ +static void cpd_update_internal_flow_control_h4_bt_cmd(const struct sk_buff * const skb) +{ + uint8_t *data = &skb->data[STE_CONN_SKB_RESERVE]; + uint8_t event_code = data[0]; + uint16_t opcode; + + if (HCI_BT_EVT_CMD_COMPLETE == event_code) { + /* Get the OpCode */ + opcode = (uint16_t)(data[3] | (data[4] << 8)); + + /* If it's HCI Cmd Cmpl Evt then we might get some HCI tickets back. Also we can decrease the nr + * outstanding HCI cmds (if it's not NOP cmd or one of the cmds that generate both Cmd Status Evt + * and Cmd Cmpl Evt). Check if we have any HCI cmds waiting in the tx list + * and send them if there are tickets available. */ + if (opcode != 0 && opcode != CPD_BT_VS_BT_ENABLE_OPCODE) { + (cpd_info->tx_nr_outstanding_cmds_bt)--; + } + cpd_info->tx_nr_pkts_allowed_bt = data[HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS]; + STE_CONN_DBG("New tx_nr_pkts_allowed_bt = %d", cpd_info->tx_nr_pkts_allowed_bt); + STE_CONN_DBG("New tx_nr_outstanding_cmds_bt = %d", cpd_info->tx_nr_outstanding_cmds_bt); + + if (!list_empty(&cpd_info->tx_list_bt)) { + cpd_transmit_skb_from_tx_queue_bt(); + } + } else if (HCI_BT_EVT_CMD_STATUS == event_code) { + opcode = (uint16_t)(data[4] | (data[5] << 8)); + + /* If it's HCI Cmd Status Evt then we might get some HCI tickets back. Also we can decrease the nr + * outstanding HCI cmds (if it's not NOP cmd). Check if we have any HCI cmds waiting in the tx queue + * and send them if there are tickets available. */ + if (opcode != 0) { + (cpd_info->tx_nr_outstanding_cmds_bt)--; + } + cpd_info->tx_nr_pkts_allowed_bt = data[HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS]; + STE_CONN_DBG("New tx_nr_pkts_allowed_bt = %d", cpd_info->tx_nr_pkts_allowed_bt); + STE_CONN_DBG("New tx_nr_outstanding_cmds_bt = %d", cpd_info->tx_nr_outstanding_cmds_bt); + + if (!list_empty(&cpd_info->tx_list_bt)) { + cpd_transmit_skb_from_tx_queue_bt(); + } + } +} + +/** + * cpd_update_internal_flow_control_fm() - Update number of packets allowed for FM channel. + * @skb: skb with received packet. + * + * The cpd_update_internal_flow_control_fm() checks if incoming data packet is FM packet indicating that + * the previous command has been handled and if so updates number of allowed packets. It also calls + * function to send queued cmds (if the list of queued cmds is not empty). + */ +static void cpd_update_internal_flow_control_fm(const struct sk_buff * const skb) +{ + uint8_t *data = &skb->data[STE_CONN_SKB_RESERVE]; + + /* 2nd byte for cmd cmpl evt is Status and equals always 0x00. */ + if (data[1] == 0x00) { + /* Get the cmd id - bits 15-3 within cmd cmpl evt. */ + uint16_t cmd_id = (uint16_t)(((data[6] | (data[7] << 8)) & 0xFFF8) >> 3); + + /* If it's not an evt related to do-command update nbr of allowed pckts for FM + * (only one outstanding pckt allowed). */ + if (!cpd_fm_irpt_expected(cmd_id)) { + cpd_info->tx_nr_pkts_allowed_fm = 1; + STE_CONN_DBG("New tx_nr_pkts_allowed_fm = %d", cpd_info->tx_nr_pkts_allowed_fm); + cpd_transmit_skb_from_tx_queue_fm(); + } else if (cmd_id == cpd_info->hci_audio_fm_cmd_id) { + /* Remember if it is do-command cmpl evt related to cmd sent by the audio driver. + * We need that to dispatch correctly the interrupt that will come later. */ + cpd_info->tx_fm_audio_awaiting_irpt = true; + } + /* In case of an Interrupt 2nd byte is always 0xFE. */ + } else if (data[1] == 0xFE) { + uint16_t irpt_val = (uint16_t)(data[3] | (data[4] << 8)); + + if (cpd_fm_is_do_cmd_irpt(irpt_val)) { + + /* If it is an interrupt related to a do-command update the number of allowed FM cmds. */ + cpd_info->tx_nr_pkts_allowed_fm = 1; + STE_CONN_DBG("New tx_nr_pkts_allowed_fm = %d", cpd_info->tx_nr_pkts_allowed_fm); + + cpd_transmit_skb_from_tx_queue_fm(); + } + } +} + +/** + * cpd_fm_irpt_expected() - check if this FM command will generate an interrupt. + * @cmd_id: command identifier. + * + * The cpd_fm_irpt_expected() checks if this FM command will generate an interrupt. + * + * Returns: + * true if the command will generate an interrupt. + * false if it won't. + */ +static bool cpd_fm_irpt_expected(uint16_t cmd_id) { + bool retval = false; + + switch (cmd_id) { + case CPD_FM_DO_AIP_FADE_START: + if (cpd_info->fm_radio_mode == CPD_FM_RADIO_MODE_FMT) { + retval = true; + } + break; + + case CPD_FM_DO_AUP_BT_FADE_START: + case CPD_FM_DO_AUP_EXT_FADE_START: + case CPD_FM_DO_AUP_FADE_START: + if (cpd_info->fm_radio_mode == CPD_FM_RADIO_MODE_FMR) { + retval = true; + } + break; + + case CPD_FM_DO_FMR_SETANTENNA: + case CPD_FM_DO_FMR_SP_AFSWITCH_START: + case CPD_FM_DO_FMR_SP_AFUPDATE_START: + case CPD_FM_DO_FMR_SP_BLOCKSCAN_START: + case CPD_FM_DO_FMR_SP_PRESETPI_START: + case CPD_FM_DO_FMR_SP_SCAN_START: + case CPD_FM_DO_FMR_SP_SEARCH_START: + case CPD_FM_DO_FMR_SP_SEARCHPI_START: + case CPD_FM_DO_FMR_SP_TUNE_SETCHANNEL: + case CPD_FM_DO_FMR_SP_TUNE_STEPCHANNEL: + case CPD_FM_DO_FMT_PA_SETCONTROL: + case CPD_FM_DO_FMT_PA_SETMODE: + case CPD_FM_DO_FMT_SP_TUNE_SETCHANNEL: + case CPD_FM_DO_GEN_ANTENNACHECK_START: + case CPD_FM_DO_GEN_GOTOMODE: + case CPD_FM_DO_GEN_POWERSUPPLY_SETMODE: + case CPD_FM_DO_GEN_SELECTREFERENCECLOCK: + case CPD_FM_DO_GEN_SETPROCESSINGCLOCK: + case CPD_FM_DO_GEN_SETREFERENCECLOCKPLL: + case CPD_FM_DO_TST_TX_RAMP_START: + retval = true; + break; + + default: + break; + } + + if (retval) { + STE_CONN_INFO("Cmd cmpl evt for FM do-command found, cmd_id = 0x%x.", cmd_id); + } + + return retval; +} + +/** + * cpd_fm_is_do_cmd_irpt() - check if irpt_val is one of the FM do-command related interrupts. + * @cmd_id: interrupt value. + * + * The cpd_fm_is_do_cmd_irpt() checks if irpt_val is one of the FM do-command related interrupts. + * + * Returns: + * true if it's do-command related interrupt value. + * false if it's not. + */ +static bool cpd_fm_is_do_cmd_irpt(uint16_t irpt_val) { + bool retval = false; + + switch (irpt_val) { + case CPD_FM_IRPT_OPERATION_SUCCEEDED: + case CPD_FM_IRPT_OPERATION_FAILED: + retval = true; + break; + + default: + break; + } + + if (retval) { + STE_CONN_INFO("Irpt evt for FM do-command found, irpt_val = 0x%x.", irpt_val); + } + + return retval; +} + + +/** + * cpd_enable_hci_logger() - Enable HCI logger for each device. + * @skb: Received sk buffer. + * + * The cpd_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 cpd_enable_hci_logger(struct sk_buff *skb) +{ + int err = 0; + + if (skb->len == sizeof(struct ste_conn_cpd_hci_logger_config)) { + /* First store the logger config */ + memcpy(&(cpd_info->hci_logger_config), skb->data, + sizeof(struct ste_conn_cpd_hci_logger_config)); + + /* Then go through all devices and set the right settings */ + if (cpd_info->users.bt_cmd) { + if (cpd_info->hci_logger_config.bt_cmd_enable) { + cpd_info->users.bt_cmd->logger_enabled = true; + } else { + cpd_info->users.bt_cmd->logger_enabled = false; + } + } + if (cpd_info->users.bt_acl) { + if (cpd_info->hci_logger_config.bt_acl_enable) { + cpd_info->users.bt_acl->logger_enabled = true; + } else { + cpd_info->users.bt_acl->logger_enabled = false; + } + } + if (cpd_info->users.bt_evt) { + if (cpd_info->hci_logger_config.bt_evt_enable) { + cpd_info->users.bt_evt->logger_enabled = true; + } else { + cpd_info->users.bt_evt->logger_enabled = false; + } + } + if (cpd_info->users.fm_radio) { + if (cpd_info->hci_logger_config.fm_radio_enable) { + cpd_info->users.fm_radio->logger_enabled = true; + } else { + cpd_info->users.fm_radio->logger_enabled = false; + } + } + if (cpd_info->users.gnss) { + if (cpd_info->hci_logger_config.gnss_enable) { + cpd_info->users.gnss->logger_enabled = true; + } else { + cpd_info->users.gnss->logger_enabled = false; + } + } + } else { + STE_CONN_ERR("Trying to configure HCI logger with bad structure"); + err = -EACCES; + } + kfree_skb(skb); + return err; +} + +/** + * cpd_find_h4_user() - Get H4 user based on supplied H4 channel. + * @h4_channel: H4 channel. + * @dev: Stored ste_conn device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if bad channel is supplied or no user was found. + */ +static int cpd_find_h4_user(int h4_channel, struct ste_conn_device **dev) +{ + int err = 0; + + if (h4_channel == cpd_info->h4_channels.bt_cmd_channel) { + *dev = cpd_info->users.bt_cmd; + } else if (h4_channel == cpd_info->h4_channels.bt_acl_channel) { + *dev = cpd_info->users.bt_acl; + } else if (h4_channel == cpd_info->h4_channels.bt_evt_channel) { + *dev = cpd_info->users.bt_evt; + } else if (h4_channel == cpd_info->h4_channels.gnss_channel) { + *dev = cpd_info->users.gnss; + } else if (h4_channel == cpd_info->h4_channels.fm_radio_channel) { + *dev = cpd_info->users.fm_radio; + } else if (h4_channel == cpd_info->h4_channels.debug_channel) { + *dev = cpd_info->users.debug; + } else if (h4_channel == cpd_info->h4_channels.ste_tools_channel) { + *dev = cpd_info->users.ste_tools; + } else if (h4_channel == cpd_info->h4_channels.hci_logger_channel) { + *dev = cpd_info->users.hci_logger; + } else if (h4_channel == cpd_info->h4_channels.us_ctrl_channel) { + *dev = cpd_info->users.us_ctrl; + } else { + *dev = NULL; + STE_CONN_ERR("Bad H:4 channel supplied: 0x%X", h4_channel); + return -EINVAL; + } + STE_CONN_DBG("*dev (0x%X)", (uint32_t) *dev); + if (!*dev) { + STE_CONN_ERR("H:4 channel not registered in cpd_info: 0x%X", h4_channel); + err = -EINVAL; + } + return err; +} + +/** + * cpd_add_h4_user() - Add H4 user to user storage based on supplied H4 channel. + * @dev: Stored ste_conn 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 cpd_add_h4_user(struct ste_conn_device *dev, const char * const name) +{ + int err = 0; + + if (!dev) { + STE_CONN_ERR("NULL device supplied"); + err = -EINVAL; + goto error_handling; + } + + if (dev->h4_channel == cpd_info->h4_channels.bt_cmd_channel) { + if (!cpd_info->users.bt_cmd && + 0 == strncmp(name, STE_CONN_DEVICES_BT_CMD, STE_CONN_MAX_NAME_SIZE)) { + cpd_info->users.bt_cmd = dev; + cpd_info->users.bt_cmd->logger_enabled = + cpd_info->hci_logger_config.bt_cmd_enable; + cpd_info->users.nbr_of_users++; + } else if (!cpd_info->users.bt_audio && + 0 == strncmp(name, STE_CONN_DEVICES_BT_AUDIO, STE_CONN_MAX_NAME_SIZE)) { + cpd_info->users.bt_audio = dev; + cpd_info->users.bt_audio->logger_enabled = + cpd_info->hci_logger_config.bt_audio_enable; + cpd_info->users.nbr_of_users++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.bt_acl_channel) { + if (!cpd_info->users.bt_acl) { + cpd_info->users.bt_acl = dev; + cpd_info->users.bt_acl->logger_enabled = + cpd_info->hci_logger_config.bt_acl_enable; + cpd_info->users.nbr_of_users++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.bt_evt_channel) { + if (!cpd_info->users.bt_evt) { + cpd_info->users.bt_evt = dev; + cpd_info->users.bt_evt->logger_enabled = + cpd_info->hci_logger_config.bt_evt_enable; + cpd_info->users.nbr_of_users++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.gnss_channel) { + if (!cpd_info->users.gnss) { + cpd_info->users.gnss = dev; + cpd_info->users.gnss->logger_enabled = + cpd_info->hci_logger_config.gnss_enable; + cpd_info->users.nbr_of_users++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.fm_radio_channel) { + if (!cpd_info->users.fm_radio && + 0 == strncmp(name, STE_CONN_DEVICES_FM_RADIO, STE_CONN_MAX_NAME_SIZE)) { + cpd_info->users.fm_radio = dev; + cpd_info->users.fm_radio->logger_enabled = + cpd_info->hci_logger_config.fm_radio_enable; + cpd_info->users.nbr_of_users++; + } else if (!cpd_info->users.fm_radio_audio && + 0 == strncmp(name, STE_CONN_DEVICES_FM_RADIO_AUDIO, STE_CONN_MAX_NAME_SIZE)) { + cpd_info->users.fm_radio_audio = dev; + cpd_info->users.fm_radio_audio->logger_enabled = + cpd_info->hci_logger_config.fm_radio_audio_enable; + cpd_info->users.nbr_of_users++; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.debug_channel) { + if (!cpd_info->users.debug) { + cpd_info->users.debug = dev; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.ste_tools_channel) { + if (!cpd_info->users.ste_tools) { + cpd_info->users.ste_tools = dev; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.hci_logger_channel) { + if (!cpd_info->users.hci_logger) { + cpd_info->users.hci_logger = dev; + } else { + err = -EBUSY; + } + } else if (dev->h4_channel == cpd_info->h4_channels.us_ctrl_channel) { + if (!cpd_info->users.us_ctrl) { + cpd_info->users.us_ctrl = dev; + } else { + err = -EBUSY; + } + } else { + err = -EINVAL; + STE_CONN_ERR("Bad H:4 channel supplied: 0x%X", dev->h4_channel); + } + + if (err) { + STE_CONN_ERR("H:4 channel 0x%X, not registered", dev->h4_channel); + } + +error_handling: + return err; +} + +/** + * cpd_remove_h4_user() - Remove H4 user from user storage. + * @dev: Stored ste_conn 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 cpd_remove_h4_user(struct ste_conn_device **dev) +{ + int err = 0; + + if (!dev || !(*dev)) { + STE_CONN_ERR("NULL device supplied: dev 0x%X *dev 0x%X", + (uint32_t)dev, (uint32_t)*dev); + err = -EINVAL; + goto error_handling; + } + + if ((*dev)->h4_channel == cpd_info->h4_channels.bt_cmd_channel) { + if (*dev == cpd_info->users.bt_cmd) { + cpd_info->users.bt_cmd = NULL; + cpd_info->users.nbr_of_users--; + } else if (*dev == cpd_info->users.bt_audio) { + cpd_info->users.bt_audio = NULL; + cpd_info->users.nbr_of_users--; + } else { + err = -EINVAL; + } + + /* If both BT Cmd channel users are de-registered we can clean the tx cmd list. */ + if (cpd_info->users.bt_cmd == NULL && cpd_info->users.bt_audio == NULL) { + struct list_head *cursor, *next; + struct tx_list_item *tmp; + + mutex_lock(&cpd_info->tx_bt_mutex); + list_for_each_safe(cursor, next, &cpd_info->tx_list_bt) { + tmp = list_entry(cursor, struct tx_list_item, list); + list_del(cursor); + kfree_skb(tmp->skb); + kfree(tmp); + } + + /* Reset nbr of pckts allowed and number of outstanding bt cmds. */ + cpd_info->tx_nr_pkts_allowed_bt = 1; + cpd_info->tx_nr_outstanding_cmds_bt = 0; + /* Reset the hci_audio_cmd_opcode_bt. */ + cpd_info->hci_audio_cmd_opcode_bt = 0xFFFF; + mutex_unlock(&cpd_info->tx_bt_mutex); + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.bt_acl_channel) { + if (*dev == cpd_info->users.bt_acl) { + cpd_info->users.bt_acl = NULL; + cpd_info->users.nbr_of_users--; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.bt_evt_channel) { + if (*dev == cpd_info->users.bt_evt) { + cpd_info->users.bt_evt = NULL; + cpd_info->users.nbr_of_users--; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.gnss_channel) { + if (*dev == cpd_info->users.gnss) { + cpd_info->users.gnss = NULL; + cpd_info->users.nbr_of_users--; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.fm_radio_channel) { + if (*dev == cpd_info->users.fm_radio) { + cpd_info->users.fm_radio = NULL; + cpd_info->users.nbr_of_users--; + } else if (*dev == cpd_info->users.fm_radio_audio) { + cpd_info->users.fm_radio_audio = NULL; + cpd_info->users.nbr_of_users--; + } else { + err = -EINVAL; + } + + /* If both FM Radio channel users are de-registered we can clean the tx cmd list. */ + if (cpd_info->users.fm_radio == NULL && cpd_info->users.fm_radio_audio == NULL) { + struct list_head *cursor, *next; + struct tx_list_item *tmp; + + mutex_lock(&cpd_info->tx_fm_mutex); + list_for_each_safe(cursor, next, &cpd_info->tx_list_fm) { + tmp = list_entry(cursor, struct tx_list_item, list); + list_del(cursor); + kfree(tmp->skb); + kfree(tmp); + } + + /* Reset nbr of pckts allowed. */ + cpd_info->tx_nr_pkts_allowed_fm = 1; + /* Reset the hci_audio_cmd_opcode_bt. */ + cpd_info->hci_audio_fm_cmd_id = 0xFFFF; + mutex_unlock(&cpd_info->tx_fm_mutex); + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.debug_channel) { + if (*dev == cpd_info->users.debug) { + cpd_info->users.debug = NULL; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.ste_tools_channel) { + if (*dev == cpd_info->users.ste_tools) { + cpd_info->users.ste_tools = NULL; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.hci_logger_channel) { + if (*dev == cpd_info->users.hci_logger) { + cpd_info->users.hci_logger = NULL; + } else { + err = -EINVAL; + } + } else if ((*dev)->h4_channel == cpd_info->h4_channels.us_ctrl_channel) { + if (*dev == cpd_info->users.us_ctrl) { + cpd_info->users.us_ctrl = NULL; + } else { + err = -EINVAL; + } + } else { + err = -EINVAL; + STE_CONN_ERR("Bad H:4 channel supplied: 0x%X", (*dev)->h4_channel); + goto error_handling; + } + + if (err) { + STE_CONN_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. + */ + cpd_free_user_dev(dev); + +error_handling: + return err; +} + +/** + * cpd_chip_startup() - Start the connectivity controller and download patches and settings. + */ +static void cpd_chip_startup(void) +{ + STE_CONN_INFO("cpd_chip_startup"); + + CPD_SET_MAIN_STATE(CPD_STATE_BOOTING); + CPD_SET_BOOT_STATE(BOOT_STATE_NOT_STARTED); + + /* Transmit HCI reset command to ensure the chip is using + * the correct transport + */ + cpd_create_and_send_bt_cmd(cpd_msg_reset_cmd_req, + sizeof(cpd_msg_reset_cmd_req)); +} + +/** + * cpd_chip_shutdown() - Reset and power the chip off. + */ +static void cpd_chip_shutdown(void) +{ + STE_CONN_INFO("cpd_chip_shutdown"); + + /* First do a quick power switch of the chip to assure a good state */ + ste_conn_ccd_set_chip_power(false); + + /* Wait 50ms before continuing to be sure that the chip detects chip power off */ + schedule_timeout_interruptible(timeval_to_jiffies(&time_50ms)); + + ste_conn_ccd_set_chip_power(true); + + /* Wait 100ms before continuing to be sure that the chip is ready */ + schedule_timeout_interruptible(timeval_to_jiffies(&time_100ms)); + + /* Then transmit HCI reset command to ensure the chip is using + * the correct transport */ + CPD_SET_CLOSING_STATE(CLOSING_STATE_RESET); + cpd_create_and_send_bt_cmd(cpd_msg_reset_cmd_req, + sizeof(cpd_msg_reset_cmd_req)); +} + +/** + * cpd_handle_internal_rx_data_bt_evt() - Check if received data should be handled in CPD. + * @skb: Data packet + * The cpd_handle_internal_rx_data_bt_evt() function checks if received data should be handled in CPD. + * If so handle it correctly. Received data is always HCI BT Event. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool cpd_handle_internal_rx_data_bt_evt(struct sk_buff *skb) +{ + bool pkt_handled = false; + uint8_t *data = &skb->data[STE_CONN_SKB_RESERVE]; + uint8_t event_code = data[0]; + + /* First check the event code */ + if (HCI_BT_EVT_CMD_COMPLETE == event_code) { + uint8_t hci_ogf = data[4] >> 2; + uint16_t hci_ocf = data[3] | ((data[4] & 0x03) << 8); + + STE_CONN_DBG_DATA("hci_ogf = 0X%x", hci_ogf); + STE_CONN_DBG_DATA("hci_ocf = 0X%x", hci_ocf); + data += 5; /* Move to first byte after OCF */ + + switch (hci_ogf) { + case HCI_BT_OGF_LINK_CTRL: + break; + + case HCI_BT_OGF_LINK_POLICY: + break; + + case HCI_BT_OGF_CTRL_BB: + switch (hci_ocf) { + case HCI_BT_OCF_RESET: + pkt_handled = cpd_handle_reset_cmd_complete_evt(data); + break; + + default: + break; + }; /* switch (hci_ocf) */ + break; + + case HCI_BT_OGF_LINK_INFO: + switch (hci_ocf) { + case HCI_BT_OCF_READ_LOCAL_VERSION_INFO: + pkt_handled = cpd_handle_read_local_version_info_cmd_complete_evt(data); + break; + + default: + break; + }; /* switch (hci_ocf) */ + break; + + case HCI_BT_OGF_LINK_STATUS: + break; + + case HCI_BT_OGF_LINK_TESTING: + break; + + case HCI_BT_OGF_VS: + switch (hci_ocf) { + case HCI_BT_OCF_VS_STORE_IN_FS: + pkt_handled = cpd_handle_vs_store_in_fs_cmd_complete_evt(data); + break; + case HCI_BT_OCF_VS_WRITE_FILE_BLOCK: + pkt_handled = cpd_handle_vs_write_file_block_cmd_complete_evt(data); + break; + + case HCI_BT_OCF_VS_POWER_SWITCH_OFF: + pkt_handled = cpd_handle_vs_power_switch_off_cmd_complete_evt(data); + break; + + case HCI_BT_OCF_VS_SYSTEM_RESET: + pkt_handled = cpd_handle_vs_system_reset_cmd_complete_evt(data); + break; + + default: + break; + }; /* switch (hci_ocf) */ + break; + + default: + break; + }; /* switch (hci_ogf) */ + } + + if (pkt_handled && skb) { + kfree_skb(skb); + } + return pkt_handled; +} + +/** + * cpd_create_work_item() - Create work item and add it to the work queue. + * @work_func: Work function. + * @skb: Data packet. + * @data: Private data for ste_conn CPD. + * + * The cpd_create_work_item() function creates work item and + * add it to the work queue. + */ +static void cpd_create_work_item(work_func_t work_func, struct sk_buff *skb, void *data) +{ + struct ste_conn_cpd_work_struct *new_work; + int wq_err = 1; + + new_work = kmalloc(sizeof(*new_work), GFP_KERNEL); + + if (new_work) { + new_work->skb = skb; + new_work->data = data; + INIT_WORK(&new_work->work, work_func); + + wq_err = queue_work(cpd_info->wq, &new_work->work); + + if (!wq_err) { + STE_CONN_ERR("Failed to queue work_struct because it's already in the queue!"); + kfree(new_work); + } + } else { + STE_CONN_ERR("Failed to alloc memory for ste_conn_cpd_work_struct!"); + } +} + +/** + * cpd_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 cpd_handle_reset_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + uint8_t status = data[0]; + + STE_CONN_DBG("main_state = %x, boot_state = %x", cpd_info->main_state, cpd_info->boot_state); + + STE_CONN_INFO("Received Reset complete event with status 0x%X", status); + + if (cpd_info->main_state == CPD_STATE_BOOTING && + cpd_info->boot_state == BOOT_STATE_NOT_STARTED) { + + /* Transmit HCI Read Local Version Information command */ + CPD_SET_BOOT_STATE(BOOT_STATE_READ_LOCAL_VERSION_INFORMATION); + cpd_create_and_send_bt_cmd(cpd_msg_read_local_version_information_cmd_req, + sizeof(cpd_msg_read_local_version_information_cmd_req)); + + pkt_handled = true; + } else if (cpd_info->main_state == CPD_STATE_BOOTING && + cpd_info->boot_state == BOOT_STATE_ACTIVATE_PATCHES_AND_SETTINGS) { + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + CPD_SET_BOOT_STATE(BOOT_STATE_READY); + CPD_SET_MAIN_STATE(CPD_STATE_ACTIVE); + wake_up_interruptible(&ste_conn_cpd_wait_queue); + } else { + STE_CONN_ERR("Received Reset complete event with status 0x%X", status); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); + } + + pkt_handled = true; + + } else if ((cpd_info->main_state == CPD_STATE_INITIALIZING) || + ((CPD_STATE_CLOSING == cpd_info->main_state) && + (CLOSING_STATE_RESET == cpd_info->closing_state))) { + + if (HCI_BT_ERROR_NO_ERROR != status) { + /* Continue in case of error, the chip is going to be shut down anyway. */ + STE_CONN_ERR("CmdComplete for HciReset received with error 0x%X !", status); + } + + cpd_create_work_item(cpd_work_power_off_chip, NULL, NULL); + pkt_handled = true; + + } + + return pkt_handled; +} + +/** + * cpd_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 cpd_handle_read_local_version_info_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + + if (cpd_info->main_state == CPD_STATE_BOOTING && + cpd_info->boot_state == BOOT_STATE_READ_LOCAL_VERSION_INFORMATION) { + /* We got an answer for our HCI command. Extract data */ + uint8_t status = data[0]; + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* The command worked. Store the data */ + cpd_info->local_chip_info.hci_version = data[1]; + cpd_info->local_chip_info.hci_revision = data[2] | (data[3] << 8); + cpd_info->local_chip_info.lmp_pal_version = data[4]; + cpd_info->local_chip_info.manufacturer = data[5] | (data[6] << 8); + cpd_info->local_chip_info.lmp_pal_subversion = data[7] | (data[8] << 8); + STE_CONN_DBG("Received Read Local Version Information with:\n \ + hci_version: 0x%X\n hci_revision: 0x%X\n lmp_pal_version: 0x%X\n \ + manufacturer: 0x%X\n lmp_pal_subversion: 0x%X", \ + cpd_info->local_chip_info.hci_version, cpd_info->local_chip_info.hci_revision, \ + cpd_info->local_chip_info.lmp_pal_version, cpd_info->local_chip_info.manufacturer, \ + cpd_info->local_chip_info.lmp_pal_subversion); + + ste_conn_devices_set_hci_revision(cpd_info->local_chip_info.hci_version, + cpd_info->local_chip_info.hci_revision, + cpd_info->local_chip_info.lmp_pal_version, + cpd_info->local_chip_info.lmp_pal_subversion, + cpd_info->local_chip_info.manufacturer); + + /* Received good confirmation. Start work to continue. */ + CPD_SET_BOOT_STATE(BOOT_STATE_GET_FILES_TO_LOAD); + cpd_create_work_item(cpd_work_load_patch_and_settings, NULL, NULL); + + } else { + STE_CONN_ERR("Received Read Local Version Information with status 0x%X", status); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); + } + /* We have now handled the packet */ + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * cpd_handle_vs_store_in_fs_cmd_complete_evt() - Handle a received HCI Command Complete event + * for a VS StoreInFS command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool cpd_handle_vs_store_in_fs_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + uint8_t status = data[0]; + + STE_CONN_INFO("Received Store_in_FS complete event with status 0x%X", status); + + if (cpd_info->boot_state == BOOT_STATE_SEND_BD_ADDRESS) { + if (HCI_BT_ERROR_NO_ERROR == status) { + + struct sk_buff *skb = NULL; + + /* Send HCI Reset command to activate patches */ + CPD_SET_BOOT_STATE(BOOT_STATE_ACTIVATE_PATCHES_AND_SETTINGS); + + /* Get HCI reset command to use based + * on connected connectivity controller. */ + skb = ste_conn_devices_get_reset_cmd(NULL, NULL); + + /* Transmit the received command */ + if (skb) { + uint8_t *h4_header; + + STE_CONN_DBG("Got reset command, add H4 header and transmit"); + + /* Move the data pointer to the H:4 header position and store the H4 header */ + h4_header = skb_push(skb, STE_CONN_SKB_RESERVE); + *h4_header = (uint8_t)cpd_info->h4_channels.bt_cmd_channel; + + cpd_transmit_skb_to_ccd(skb, cpd_info->hci_logger_config.bt_cmd_enable); + } else { + STE_CONN_DBG("No reset command found for this device"); + CPD_SET_BOOT_STATE(BOOT_STATE_READY); + CPD_SET_MAIN_STATE(CPD_STATE_ACTIVE); + wake_up_interruptible(&ste_conn_cpd_wait_queue); + } + + } else { + STE_CONN_ERR("CmdComplete for StoreInFS received with error 0x%X", status); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + cpd_create_work_item(cpd_work_reset_after_error, NULL, NULL); + } + /* We have now handled the packet */ + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * cpd_handle_vs_write_file_block_cmd_complete_evt() - Handle a received HCI Command Complete event + * for a VS WriteFileBlock command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool cpd_handle_vs_write_file_block_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + + if ((cpd_info->boot_state == BOOT_STATE_DOWNLOAD_PATCH) && + cpd_info->boot_download_state == BOOT_DOWNLOAD_STATE_PENDING) { + uint8_t status = data[0]; + if (HCI_BT_ERROR_NO_ERROR == status) { + /* Received good confirmation. Start work to continue. */ + cpd_create_work_item(cpd_work_continue_with_file_download, NULL, NULL); + } else { + STE_CONN_ERR("CmdComplete for WriteFileBlock received with error 0x%X", status); + CPD_SET_BOOT_DOWNLOAD_STATE(BOOT_DOWNLOAD_STATE_FAILED); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + if (cpd_info->fw_file) { + release_firmware(cpd_info->fw_file); + cpd_info->fw_file = NULL; + } + cpd_create_work_item(cpd_work_reset_after_error, NULL, NULL); + } + /* We have now handled the packet */ + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * cpd_handle_vs_power_switch_off_cmd_complete_evt() - Handle a received HCI Command Complete event + * for a VS PowerSwitchOff command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool cpd_handle_vs_power_switch_off_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + + if (CLOSING_STATE_POWER_SWITCH_OFF == cpd_info->closing_state) { + /* We were waiting for this but we don't need to do anything upon receival + * except warn for error status + */ + uint8_t status = data[0]; + + if (HCI_BT_ERROR_NO_ERROR != status) { + STE_CONN_ERR("CmdComplete for PowerSwitchOff received with error 0x%X", status); + } + + pkt_handled = true; + } + + return pkt_handled; +} + +/** + * cpd_handle_vs_system_reset_cmd_complete_evt() - Handle a received HCI Command Complete event for a VS SystemReset command. + * @data: Pointer to received HCI data packet. + * + * Returns: + * True, if packet was handled internally, + * False, otherwise. + */ +static bool cpd_handle_vs_system_reset_cmd_complete_evt(uint8_t *data) +{ + bool pkt_handled = false; + uint8_t status = data[0]; + + if (cpd_info->main_state == CPD_STATE_BOOTING && + cpd_info->boot_state == BOOT_STATE_ACTIVATE_PATCHES_AND_SETTINGS) { + + if (HCI_BT_ERROR_NO_ERROR == status) { + /* The boot sequence is now finished successfully. + * Set states and signal to waiting thread. + */ + CPD_SET_BOOT_STATE(BOOT_STATE_READY); + CPD_SET_MAIN_STATE(CPD_STATE_ACTIVE); + wake_up_interruptible(&ste_conn_cpd_wait_queue); + } else { + STE_CONN_ERR("Received Reset complete event with status 0x%X", status); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); + } + pkt_handled = true; + } + + return pkt_handled; +} + +/** Transmit VS Power Switch Off + * cpd_work_power_off_chip() - Work item to power off the chip. + * @work: Reference to work data. + * + * The cpd_work_power_off_chip() function handles transmission of + * the HCI command vs_power_switch_off command, closes the CCD, + * and then powers off the chip. + */ +static void cpd_work_power_off_chip(struct work_struct *work) +{ + struct ste_conn_cpd_work_struct *current_work = NULL; + struct sk_buff *skb = NULL; + uint8_t *h4_header; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ste_conn_cpd_work_struct, work); + + /* Get the VS Power Switch Off command to use based on + * connected connectivity controller */ + skb = ste_conn_devices_get_power_switch_off_cmd(NULL, NULL); + + /* Transmit the received command. If no command found for the device, just continue */ + if (skb) { + STE_CONN_DBG("Got power_switch_off command, add H4 header and transmit"); + /* Move the data pointer to the H:4 header position and store the H4 header */ + h4_header = skb_push(skb, STE_CONN_SKB_RESERVE); + *h4_header = (uint8_t)cpd_info->h4_channels.bt_cmd_channel; + + CPD_SET_CLOSING_STATE(CLOSING_STATE_POWER_SWITCH_OFF); + + cpd_transmit_skb_to_ccd(skb, cpd_info->hci_logger_config.bt_cmd_enable); + + /* Mandatory to wait 500ms after the power_switch_off command has been + * transmitted, in order to make sure that the controller is ready. */ + schedule_timeout_interruptible(timeval_to_jiffies(&time_500ms)); + + } else { + STE_CONN_DBG("No power_switch_off command found for this device"); + } + + CPD_SET_CLOSING_STATE(CLOSING_STATE_GPIO_DEREGISTER); + + /* Close CCD, which will power off the chip */ + ste_conn_ccd_close(); + + /* Chip shut-down finished, set correct state and wake up the cpd. */ + CPD_SET_MAIN_STATE(CPD_STATE_IDLE); + wake_up_interruptible(&ste_conn_cpd_wait_queue); + + kfree(current_work); +} + +/** + * cpd_work_reset_after_error() - Handle reset. + * @work: Reference to work data. + * + * Handle a reset after received command complete event. + */ +static void cpd_work_reset_after_error(struct work_struct *work) +{ + struct ste_conn_cpd_work_struct *current_work = NULL; + struct sk_buff *skb = NULL; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ste_conn_cpd_work_struct, work); + skb = current_work->skb; + + ste_conn_reset(NULL); + + kfree(current_work); +} + +/** + * cpd_work_load_patch_and_settings() - Start loading patches and settings. + * @work: Reference to work data. + */ +static void cpd_work_load_patch_and_settings(struct work_struct *work) +{ + struct ste_conn_cpd_work_struct *current_work = NULL; + struct sk_buff *skb = NULL; + int err = 0; + bool file_found; + const struct firmware *patch_info; + const struct firmware *settings_info; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ste_conn_cpd_work_struct, work); + skb = current_work->skb; + + + /* Check that we are in the right state */ + if (cpd_info->main_state == CPD_STATE_BOOTING && + cpd_info->boot_state == BOOT_STATE_GET_FILES_TO_LOAD) { + /* Open patch info file. */ + err = request_firmware(&patch_info, STE_CONN_BT_PATCH_INFO_FILE, cpd_info->dev); + if (err) { + STE_CONN_ERR("Couldn't get patch info file"); + goto error_handling; + } + + /* Now we have the patch info file. See if we can find the right patch file as well */ + file_found = cpd_get_file_to_load(patch_info, &(cpd_info->patch_file_name)); + + /* Now we are finished with the patch info file */ + release_firmware(patch_info); + + if (!file_found) { + STE_CONN_ERR("Couldn't find patch file! Major error!"); + goto error_handling; + } + + /* Open settings info file. */ + err = request_firmware(&settings_info, STE_CONN_BT_FACTORY_SETTINGS_INFO_FILE, cpd_info->dev); + if (err) { + STE_CONN_ERR("Couldn't get settings info file"); + goto error_handling; + } + + /* Now we have the settings info file. See if we can find the right settings file as well */ + file_found = cpd_get_file_to_load(settings_info, &(cpd_info->settings_file_name)); + + /* Now we are finished with the patch info file */ + release_firmware(settings_info); + + if (!file_found) { + STE_CONN_ERR("Couldn't find settings file! Major error!"); + goto error_handling; + } + + /* We now all info needed */ + CPD_SET_BOOT_STATE(BOOT_STATE_DOWNLOAD_PATCH); + CPD_SET_BOOT_DOWNLOAD_STATE(BOOT_DOWNLOAD_STATE_PENDING); + CPD_SET_BOOT_FILE_LOAD_STATE(BOOT_FILE_LOAD_STATE_LOAD_PATCH); + cpd_info->chunk_id = 0; + cpd_info->fw_file_rd_offset = 0; + cpd_info->fw_file = NULL; + + /* OK. Now it is time to download the patches */ + err = request_firmware(&(cpd_info->fw_file), cpd_info->patch_file_name, cpd_info->dev); + if (err < 0) { + STE_CONN_ERR("Couldn't get patch file"); + goto error_handling; + } + cpd_send_patch_file(); + } + goto finished; + +error_handling: + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); +finished: + kfree(current_work); +} + +/** + * cpd_work_continue_with_file_download() - A file block has been written. + * @work: Reference to work data. + * + * Handle a received HCI VS Write File Block Complete event. + * Normally this means continue to send files to the controller. + */ +static void cpd_work_continue_with_file_download(struct work_struct *work) +{ + struct ste_conn_cpd_work_struct *current_work = NULL; + struct sk_buff *skb = NULL; + + if (!work) { + STE_CONN_ERR("Wrong pointer (work = 0x%x)", (uint32_t) work); + return; + } + + current_work = container_of(work, struct ste_conn_cpd_work_struct, work); + skb = current_work->skb; + + /* Continue to send patches or settings to the controller */ + if (cpd_info->boot_file_load_state == BOOT_FILE_LOAD_STATE_LOAD_PATCH) { + cpd_send_patch_file(); + } else if (cpd_info->boot_file_load_state == BOOT_FILE_LOAD_STATE_LOAD_STATIC_SETTINGS) { + cpd_send_settings_file(); + } else { + STE_CONN_INFO("No more files to load"); + } + + kfree(current_work); +} + +/** + * cpd_get_file_to_load() - Parse info file and find correct target file. + * @fw: Firmware structure containing file data. + * @file_name: (out) Pointer to name of requested file. + * + * Returns: + * True, if target file was found, + * False, otherwise. + */ +static bool cpd_get_file_to_load(const struct firmware *fw, char **file_name) +{ + char *line_buffer; + char *curr_file_buffer; + int bytes_left_to_parse = fw->size; + int bytes_read = 0; + bool file_found = false; + + curr_file_buffer = (char *)&(fw->data[0]); + + line_buffer = kzalloc(STE_CONN_LINE_BUFFER_LENGTH, GFP_KERNEL); + + if (line_buffer) { + while (!file_found) { + /* Get one line of text from the file to parse */ + curr_file_buffer = cpd_get_one_line_of_text(line_buffer, + min(STE_CONN_LINE_BUFFER_LENGTH, + (int)(fw->size - bytes_read)), + curr_file_buffer, + &bytes_read); + + bytes_left_to_parse -= bytes_read; + if (bytes_left_to_parse <= 0) { + /* End of file => Leave while loop */ + STE_CONN_ERR("Reached end of file. No file found!"); + break; + } + + /* Check if the line of text is a comment or not, comments begin with '#' */ + if (*line_buffer != '#') { + uint32_t hci_rev = 0; + uint32_t lmp_sub = 0; + + STE_CONN_DBG("Found a valid line <%s>", line_buffer); + + /* Check if we can find the correct HCI revision and LMP subversion + * as well as a file name in the text line + * Store the filename if the actual file can be found in the file system + */ + if (sscanf(line_buffer, "%x%x%s", &hci_rev, &lmp_sub, *file_name) == 3 + && hci_rev == cpd_info->local_chip_info.hci_revision + && lmp_sub == cpd_info->local_chip_info.lmp_pal_subversion) { + STE_CONN_DBG( \ + "File name = %s HCI Revision = 0x%X LMP PAL Subversion = 0x%X", \ + *file_name, hci_rev, lmp_sub); + + /* Name has already been stored above. Nothing more to do */ + file_found = true; + } else { + /* Zero the name buffer so it is clear to next read */ + memset(*file_name, 0x00, STE_CONN_FILENAME_MAX + 1); + } + } + } + kfree(line_buffer); + } else { + STE_CONN_ERR("Failed to allocate: file_name 0x%X, line_buffer 0x%X", + (uint32_t)file_name, (uint32_t)line_buffer); + } + + return file_found; +} + +/** + * cpd_get_one_line_of_text()- Replacement function for stdio function fgets. + * @wr_buffer: Buffer to copy text to. + * @max_nbr_of_bytes: Max number of bytes to read, i.e. size of rd_buffer. + * @rd_buffer: Data to parse. + * @bytes_copied: Number of bytes copied to wr_buffer. + * + * The cpd_get_one_line_of_text() function extracts one line of text from input file. + * + * Returns: + * Pointer to next data to read. + */ +static char *cpd_get_one_line_of_text(char *wr_buffer, int max_nbr_of_bytes, + char *rd_buffer, int *bytes_copied) +{ + char *curr_wr = wr_buffer; + char *curr_rd = rd_buffer; + char in_byte; + + *bytes_copied = 0; + + do { + *curr_wr = *curr_rd; + in_byte = *curr_wr; + curr_wr++; + curr_rd++; + (*bytes_copied)++; + } while ((*bytes_copied <= max_nbr_of_bytes) && (in_byte != '\0') && (in_byte != '\n')); + *curr_wr = '\0'; + return curr_rd; +} + +/** + * cpd_read_and_send_file_part() - Transmit a part of the supplied file to the controller + * + * The cpd_read_and_send_file_part() function transmit a part of the supplied file to the controller. + * If nothing more to read, set the correct states. + */ +static void cpd_read_and_send_file_part(void) +{ + int bytes_to_copy; + + /* Calculate number of bytes to copy; + * either max bytes for HCI packet or number of bytes left in file + */ + bytes_to_copy = min((int)CPD_SEND_FILE_MAX_CHUNK_SIZE, + (int)(cpd_info->fw_file->size - cpd_info->fw_file_rd_offset)); + + if (bytes_to_copy > 0) { + struct sk_buff *skb; + + /* There are bytes to transmit. Allocate a sk_buffer. */ + skb = alloc_skb(sizeof(cpd_msg_vs_write_file_block_cmd_req) + + CPD_SEND_FILE_MAX_CHUNK_SIZE + + CPD_FILE_CHUNK_ID_SIZE, + GFP_KERNEL); + if (!skb) { + STE_CONN_ERR("Couldn't allocate sk_buffer"); + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); + return; + } + + /* Copy the data from the HCI template */ + memcpy(skb_put(skb, sizeof(cpd_msg_vs_write_file_block_cmd_req)), + cpd_msg_vs_write_file_block_cmd_req, + sizeof(cpd_msg_vs_write_file_block_cmd_req)); + skb->data[0] = (uint8_t)cpd_info->h4_channels.bt_cmd_channel; + + /* Store the chunk ID */ + skb->data[CPD_VS_SEND_FILE_START_OFFSET_IN_CMD] = cpd_info->chunk_id; + skb_put(skb, 1); + + /* Copy the data from offset position and store the length */ + memcpy(skb_put(skb, bytes_to_copy), + &(cpd_info->fw_file->data[cpd_info->fw_file_rd_offset]), + bytes_to_copy); + skb->data[CPD_BT_CMD_LENGTH_POSITION] = bytes_to_copy + CPD_FILE_CHUNK_ID_SIZE; + + /* Increase offset with number of bytes copied */ + cpd_info->fw_file_rd_offset += bytes_to_copy; + + cpd_transmit_skb_to_ccd(skb, cpd_info->hci_logger_config.bt_cmd_enable); + cpd_info->chunk_id++; + } else { + /* Nothing more to read in file. */ + CPD_SET_BOOT_DOWNLOAD_STATE(BOOT_DOWNLOAD_STATE_SUCCESS); + cpd_info->chunk_id = 0; + cpd_info->fw_file_rd_offset = 0; + } +} + +/** + * cpd_send_patch_file - Transmit patch file. + * + * The cpd_send_patch_file() function transmit patch file. The file is + * read in parts to fit in HCI packets. When the complete file is transmitted, + * the file is closed. When finished, continue with settings file. + */ +static void cpd_send_patch_file(void) +{ + int err = 0; + + /* Transmit a part of the supplied file to the controller. + * When nothing more to read, continue to close the patch file. */ + cpd_read_and_send_file_part(); + + if (cpd_info->boot_download_state == BOOT_DOWNLOAD_STATE_SUCCESS) { + /* Patch file finished. Release used resources */ + STE_CONN_DBG("Patch file finished, release used resources"); + if (cpd_info->fw_file) { + release_firmware(cpd_info->fw_file); + cpd_info->fw_file = NULL; + } + err = request_firmware(&(cpd_info->fw_file), cpd_info->settings_file_name, cpd_info->dev); + if (err < 0) { + STE_CONN_ERR("Couldn't get settings file (%d)", err); + goto error_handling; + } + /* Now send the settings file */ + CPD_SET_BOOT_FILE_LOAD_STATE(BOOT_FILE_LOAD_STATE_LOAD_STATIC_SETTINGS); + CPD_SET_BOOT_DOWNLOAD_STATE(BOOT_DOWNLOAD_STATE_PENDING); + cpd_send_settings_file(); + } + return; + +error_handling: + CPD_SET_BOOT_STATE(BOOT_STATE_FAILED); + ste_conn_reset(NULL); +} + +/** + * cpd_send_settings_file() - Transmit settings file. + * + * The cpd_send_settings_file() function transmit settings file. + * The file is read in parts to fit in HCI packets. When finished, + * close the settings file and send HCI reset to activate settings and patches. + */ +static void cpd_send_settings_file(void) +{ + /* Transmit a file part */ + cpd_read_and_send_file_part(); + + if (cpd_info->boot_download_state == BOOT_DOWNLOAD_STATE_SUCCESS) { + + /* Settings file finished. Release used resources */ + STE_CONN_DBG("Settings file finished, release used resources"); + if (cpd_info->fw_file) { + release_firmware(cpd_info->fw_file); + cpd_info->fw_file = NULL; + } + + CPD_SET_BOOT_FILE_LOAD_STATE(BOOT_FILE_LOAD_STATE_NO_MORE_FILES); + + /* Create and send HCI VS Store In FS cmd with bd address. */ + cpd_send_bd_address(); + } +} + +/** + * cpd_create_and_send_bt_cmd() - Copy and send sk_buffer. + * @data: Data to send. + * @length: Length in bytes of data. + * + * The cpd_create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data to it, + * and send the sk_buffer to CCD. Note that the data must contain the H:4 header. + */ +static void cpd_create_and_send_bt_cmd(const uint8_t *data, int length) +{ + struct sk_buff *skb; + + skb = alloc_skb(length, GFP_KERNEL); + if (skb) { + memcpy(skb_put(skb, length), data, length); + skb->data[0] = (uint8_t)cpd_info->h4_channels.bt_cmd_channel; + + STE_CONN_DBG_DATA_CONTENT("Sent %d bytes: 0x %02X %02X %02X %02X %02X %02X %02X %02X", + skb->len, skb->data[0], skb->data[1], + skb->data[2], skb->data[3], skb->data[4], + skb->data[5], skb->data[6], skb->data[7]); + + cpd_transmit_skb_to_ccd(skb, cpd_info->hci_logger_config.bt_cmd_enable); + } else { + STE_CONN_ERR("Couldn't allocate sk_buff with length %d", length); + } +} + +/** + * cpd_send_bd_address() - Send HCI VS cmd with BD addres to the chip. + * @work: Reference to work data. + */ +static void cpd_send_bd_address(void) +{ + uint8_t tmp[sizeof(cpd_msg_vs_store_in_fs_cmd_req) + BT_BDADDR_SIZE]; + + /* Send vs_store_in_fs_cmd with BD address. */ + memcpy(tmp, cpd_msg_vs_store_in_fs_cmd_req, + sizeof(cpd_msg_vs_store_in_fs_cmd_req)); + + tmp[HCI_CMD_PARAM_LEN_POS] = HCI_VS_STORE_IN_FS_USR_ID_SIZE + + HCI_VS_STORE_IN_FS_PARAM_LEN_SIZE + BT_BDADDR_SIZE; + tmp[HCI_VS_STORE_IN_FS_USR_ID_POS] = HCI_VS_STORE_IN_FS_USR_ID_BD_ADDR; + tmp[HCI_VS_STORE_IN_FS_PARAM_LEN_POS] = BT_BDADDR_SIZE; + + /* Now copy the bd address received from user space control app. */ + memcpy(&(tmp[HCI_VS_STORE_IN_FS_PARAM_POS]), ste_conn_bd_address, sizeof(ste_conn_bd_address)); + + CPD_SET_BOOT_STATE(BOOT_STATE_SEND_BD_ADDRESS); + + cpd_create_and_send_bt_cmd(tmp, sizeof(tmp)); +} + +/** + * cpd_transmit_skb_to_ccd() - Transmit buffer to CCD. + * @skb: Data packet. + * @use_logger: True if HCI logger shall be used, false otherwise. + * + * The cpd_transmit_skb_to_ccd() function transmit buffer to CCD. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void cpd_transmit_skb_to_ccd(struct sk_buff *skb, bool use_logger) +{ + /* If HCI logging is enabled for this channel, copy the data to + * the HCI logging output. + */ + if (use_logger && cpd_info->users.hci_logger) { + /* Alloc a new sk_buffer and copy the data into it. Then send it to the HCI logger. */ + struct sk_buff *skb_log = alloc_skb(skb->len +1, GFP_KERNEL); + if (skb_log) { + memcpy(skb_put(skb_log, skb->len), skb->data, skb->len); + skb_log->data[0] = (uint8_t) LOGGER_DIRECTION_TX; + + cpd_info->users.hci_logger->callbacks->read_cb(cpd_info->users.hci_logger, skb_log); + } else { + STE_CONN_ERR("Couldn't allocate skb_log"); + } + } + + ste_conn_ccd_write(skb); +} + +/** + * cpd_transmit_skb_to_ccd_with_flow_ctrl() - Check the h4_header and call appropriate function to handle the skb. + * @skb: Data packet. + * @dev: pointer to ste_conn_device struct. + * @h4_header: H4 channel id. + * + * The cpd_transmit_skb_to_ccd_with_flow_ctrl() Checks the h4_header and call appropriate function to handle the skb. + */ +static void cpd_transmit_skb_to_ccd_with_flow_ctrl(struct sk_buff *skb, + struct ste_conn_device *dev, const uint8_t h4_header) +{ + if (cpd_info->h4_channels.bt_cmd_channel == h4_header) { + cpd_transmit_skb_to_ccd_with_flow_ctrl_bt(skb, dev); + } else if (cpd_info->h4_channels.fm_radio_channel == h4_header) { + cpd_transmit_skb_to_ccd_with_flow_ctrl_fm(skb, dev); + } +} + +/** + * cpd_transmit_skb_to_ccd_with_flow_ctrl_bt() - Send the BT skb to the CCD if it is allowed or queue it. + * @skb: Data packet. + * @dev: pointer to ste_conn_device struct. + * + * The cpd_transmit_skb_to_ccd_with_flow_ctrl_bt() function checks if there are tickets available and if so + * transmits buffer to CCD. Otherwise the skb and user name is stored in a list for later sending. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void cpd_transmit_skb_to_ccd_with_flow_ctrl_bt(struct sk_buff *skb, + struct ste_conn_device *dev) +{ + /* Because there are more users of some H4 channels (currently audio application + * for BT cmd and FM channel) we need to have an internal HCI cmd flow control + * in ste_conn driver. So check here how many tickets we have and store skb in a queue + * if there are no tickets left. The skb will be sent later when we get more ticket(s). */ + mutex_lock(&cpd_info->tx_bt_mutex); + + if ((cpd_info->tx_nr_pkts_allowed_bt - cpd_info->tx_nr_outstanding_cmds_bt) > 0) { + (cpd_info->tx_nr_pkts_allowed_bt)--; + (cpd_info->tx_nr_outstanding_cmds_bt)++; + STE_CONN_DBG("New tx_nr_pkts_allowed_bt = %d", cpd_info->tx_nr_pkts_allowed_bt); + STE_CONN_DBG("New tx_nr_outstanding_cmds_bt = %d", cpd_info->tx_nr_outstanding_cmds_bt); + + /* If it's cmd from audio app store the OpCode, + * it'll be used later to decide where to dispatch cmd cmpl evt. */ + if (cpd_info->users.bt_audio == dev) { + cpd_info->hci_audio_cmd_opcode_bt = (uint16_t)(skb->data[1] | (skb->data[2] << 8)); + STE_CONN_INFO("Sending cmd from audio driver, saving OpCode = 0x%x", + cpd_info->hci_audio_cmd_opcode_bt); + } + + cpd_transmit_skb_to_ccd(skb, dev->logger_enabled); + } else { + struct tx_list_item *item = kzalloc(sizeof(*item), GFP_KERNEL); + STE_CONN_INFO("Not allowed to send cmd to CCD, storing in tx queue."); + if (item) { + item->dev = dev; + item->skb = skb; + list_add_tail(&item->list, &cpd_info->tx_list_bt); + } else { + STE_CONN_ERR("Failed to alloc memory!"); + } + } + mutex_unlock(&cpd_info->tx_bt_mutex); +} + +/** + * cpd_transmit_skb_to_ccd_with_flow_ctrl_fm() - Send the FM skb to the CCD if it is allowed or queue it. + * @skb: Data packet. + * @dev: pointer to ste_conn_device struct. + * + * The cpd_transmit_skb_to_ccd_with_flow_ctrl_fm() function checks if there are tickets available and if so + * transmits buffer to CCD. Otherwise the skb and user name is stored in a list for later sending. + * Also it updates the FM radio mode if it's FM GOTOMODE cmd, this is needed to + * know how to handle some FM do-commands cmpl events. + * If enabled, copy the transmitted data to the HCI logger as well. + */ +static void cpd_transmit_skb_to_ccd_with_flow_ctrl_fm(struct sk_buff *skb, + struct ste_conn_device *dev) +{ + uint16_t cmd_id = (uint16_t)(((skb->data[5] | (skb->data[6] << 8)) & 0xFFF8) >> 3); + + if (CPD_FM_DO_GEN_GOTOMODE == cmd_id) { + cpd_info->fm_radio_mode = skb->data[7]; + STE_CONN_INFO("FM Radio mode changed to %d", cpd_info->fm_radio_mode); + } + + /* Because there are more users of some H4 channels (currently audio application + * for BT cmd and FM channel) we need to have an internal HCI cmd flow control + * in ste_conn driver. So check here how many tickets we have and store skb in a queue + * there are no tickets left. The skb will be sent later when we get more ticket(s). */ + mutex_lock(&cpd_info->tx_fm_mutex); + + if (cpd_info->tx_nr_pkts_allowed_fm) { + (cpd_info->tx_nr_pkts_allowed_fm)--; + STE_CONN_DBG("FM Nr_of_pckts_allowed changed to %d", cpd_info->tx_nr_pkts_allowed_fm); + + /* If it's cmd from audio app store the cmd id (bits 15-3), + * it'll be used later to decide where to dispatch cmd cmpl evt. */ + if (cpd_info->users.fm_radio_audio == dev) { + cpd_info->hci_audio_fm_cmd_id = cmd_id; + } + + cpd_transmit_skb_to_ccd(skb, dev->logger_enabled); + } else { + struct tx_list_item *item = kzalloc(sizeof(*item), GFP_KERNEL); + STE_CONN_INFO("Not allowed to send cmd to CCD, storing in tx queue."); + if (item) { + item->dev = dev; + item->skb = skb; + list_add_tail(&item->list, &cpd_info->tx_list_fm); + } else { + STE_CONN_ERR("Failed to alloc memory!"); + } + } + mutex_unlock(&cpd_info->tx_fm_mutex); +} + + +/** + * cpd_transmit_skb_from_tx_queue_bt() - Check/update flow control info and transmit skb from + * the BT tx_queue to CCD if allowed. + * + * The cpd_transmit_skb_from_tx_queue_bt() function checks if there are tickets available and cmds waiting in the tx queue + * and if so transmits them to CCD. + */ +static void cpd_transmit_skb_from_tx_queue_bt(void) +{ + struct list_head *cursor, *next; + struct tx_list_item *tmp; + struct ste_conn_device *dev; + struct sk_buff *skb; + + STE_CONN_INFO("cpd_transmit_skb_from_tx_queue_bt"); + + mutex_lock(&cpd_info->tx_bt_mutex); + + list_for_each_safe(cursor, next, &cpd_info->tx_list_bt) { + tmp = list_entry(cursor, struct tx_list_item, list); + skb = tmp->skb; + dev = tmp->dev; + + if (dev) { + if ((cpd_info->tx_nr_pkts_allowed_bt - cpd_info->tx_nr_outstanding_cmds_bt) > 0) { + + (cpd_info->tx_nr_pkts_allowed_bt)--; + (cpd_info->tx_nr_outstanding_cmds_bt)++; + STE_CONN_DBG("New tx_nr_pkts_allowed_bt = %d", cpd_info->tx_nr_pkts_allowed_bt); + STE_CONN_DBG("New tx_nr_outstanding_cmds_bt = %d", cpd_info->tx_nr_outstanding_cmds_bt); + + /* If it's cmd from audio app store the OpCode, + * it'll be used later to decide where to dispatch cmd cmpl evt. */ + if (cpd_info->users.bt_audio == dev) { + cpd_info->hci_audio_cmd_opcode_bt = (uint16_t)(skb->data[1] | (skb->data[2] << 8)); + STE_CONN_INFO("Sending cmd from audio driver, saving OpCode = 0x%x", + cpd_info->hci_audio_cmd_opcode_bt); + } + + cpd_transmit_skb_to_ccd(skb, dev->logger_enabled); + list_del(cursor); + kfree(tmp); + } else { + /* If no more pckts allowed just return, we'll get back here after next cmd cmpl/cmd status evt. */ + return; + } + } else { + STE_CONN_ERR("H4 user not found!"); + kfree_skb(skb); + } + } + + mutex_unlock(&cpd_info->tx_bt_mutex); +} + +/** + * cpd_transmit_skb_from_tx_queue_fm() - Check/update flow control info and transmit skb from + * the FM tx_queue to CCD if allowed. + * + * The cpd_transmit_skb_from_tx_queue_fm() function checks if there are tickets available and cmds waiting in the tx queue + * and if so transmits them to CCD. + */ +static void cpd_transmit_skb_from_tx_queue_fm(void) +{ + struct list_head *cursor, *next; + struct tx_list_item *tmp; + struct ste_conn_device *dev; + struct sk_buff *skb; + + STE_CONN_INFO("cpd_transmit_skb_from_tx_queue_fm"); + + mutex_lock(&cpd_info->tx_fm_mutex); + + list_for_each_safe(cursor, next, &cpd_info->tx_list_fm) { + tmp = list_entry(cursor, struct tx_list_item, list); + skb = tmp->skb; + dev = tmp->dev; + + if (dev) { + if (cpd_info->tx_nr_pkts_allowed_fm) { + (cpd_info->tx_nr_pkts_allowed_fm)--; + STE_CONN_DBG("New tx_nr_pkts_allowed_fm = %d", cpd_info->tx_nr_pkts_allowed_fm); + + /* If it's cmd from audio app store the cmd id (bits 15-3), + * it'll be used later to decide where to dispatch cmd cmpl evt. */ + if (cpd_info->users.fm_radio_audio == dev) { + uint16_t cmd_id = (uint16_t)(((skb->data[5] | (skb->data[6] << 8)) & 0xFFF8) >> 3); + cpd_info->hci_audio_fm_cmd_id = cmd_id; + } + + cpd_transmit_skb_to_ccd(skb, dev->logger_enabled); + list_del(cursor); + kfree(tmp); + } else { + /* If no more pckts allowed just return, we'll get back here after next cmd cmpl/cmd status evt. */ + return; + } + } else { + STE_CONN_ERR("H4 user not found!"); + kfree_skb(skb); + } + } + + mutex_unlock(&cpd_info->tx_fm_mutex); +} + +/** + * cpd_handle_reset_of_user - Calls the reset callback and frees the device. + * @dev: Pointer to ste_conn device. + */ +static void cpd_handle_reset_of_user(struct ste_conn_device **dev) +{ + if (*dev) { + if ((*dev)->callbacks->reset_cb) { + (*dev)->callbacks->reset_cb((*dev)); + } + cpd_free_user_dev(dev); + } +} + +/** + * cpd_free_user_dev - Frees user device and also sets it to NULL to inform caller. + * @dev: Pointer to user device. + */ +static void cpd_free_user_dev(struct ste_conn_device **dev) +{ + if (*dev) { + if ((*dev)->callbacks) { + kfree((*dev)->callbacks); + } + kfree(*dev); + *dev = NULL; + } +} + + +EXPORT_SYMBOL(ste_conn_register); +EXPORT_SYMBOL(ste_conn_deregister); +EXPORT_SYMBOL(ste_conn_reset); +EXPORT_SYMBOL(ste_conn_alloc_skb); +EXPORT_SYMBOL(ste_conn_write); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Connectivity Device Driver ver " VERSION); +MODULE_VERSION(VERSION); diff --git a/drivers/mfd/ste_conn/ste_conn_cpd.h b/drivers/mfd/ste_conn/ste_conn_cpd.h new file mode 100755 index 00000000000..0d73f5942b2 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_cpd.h @@ -0,0 +1,92 @@ +/* + * file ste_conn_cpd.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_CPD_H_ +#define _STE_CONN_CPD_H_ + +#include <linux/skbuff.h> +#include <linux/device.h> + +/** + * struct ste_conn_cpd_hci_logger_config - Configures the HCI logger. + * @bt_cmd_enable: Enable BT cmd logging. + * @bt_acl_enable: Enable BT acl logging. + * @bt_evt_enable: Enable BT evt logging. + * @gnss_enable: Enable GNSS logging. + * @fm_radio_enable: Enable FM radio logging. + * @bt_audio_enable: Enable BT audio cmd logging. + * @fm_radio_audio_enable: Enable FM radio audio cmd logging. + * + * Set using ste_conn_write on STE_CONN_H4_CHANNEL_HCI_LOGGER H4 channel. + */ +struct ste_conn_cpd_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; +}; + +/** + * ste_conn_cpd_init() - Initialize module. + * @char_dev_usage: Bitmask indicating what char devs to enable. + * @dev: Parent device this device is connected to. + * + * The ste_conn_cpd_init() function initialize the CPD module at system start-up. + * + * Returns: + * 0 if there is no error. + * -EBUSY if CPD has already been initiated. + * -ENOMEM if allocation fails or work queue can't be created. + */ +extern int ste_conn_cpd_init(int char_dev_usage, struct device *dev); + +/** + * ste_conn_cpd_exit() - Release module. + * + * The ste_conn_cpd_exit() function remove CPD module at system shut-down. + */ +extern void ste_conn_cpd_exit(void); + + +/** + * ste_conn_cpd_hw_registered() - Informs that driver is now connected to HW transport. + * + * The ste_conn_cpd_hw_registered() is called to inform that the driver is + * connected to the HW transport. + */ +extern void ste_conn_cpd_hw_registered(void); + +/** + * ste_conn_cpd_hw_deregistered() - Informs that driver is disconnected from HW transport. + * + * The ste_conn_cpd_hw_deregistered() is called to inform that the driver is + * disconnected from the HW transport. + */ +extern void ste_conn_cpd_hw_deregistered(void); + +/** + * ste_conn_cpd_data_received() - Data received from connectivity controller. + * @skb: Data packet + * + * The ste_conn_cpd_data_received() function check which channel + * the data was received on and send to the right user. + */ +extern void ste_conn_cpd_data_received(struct sk_buff *skb); + +#endif /* _STE_CONN_CPD_H_ */ diff --git a/drivers/mfd/ste_conn/ste_conn_debug.h b/drivers/mfd/ste_conn/ste_conn_debug.h new file mode 100755 index 00000000000..03199675519 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_debug.h @@ -0,0 +1,74 @@ +/* + * file ste_conn_debug.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_DEBUG_H_ +#define _STE_CONN_DEBUG_H_ + +#include <linux/kernel.h> +#include <linux/types.h> + +#define STE_CONN_DEFAULT_DEBUG_LEVEL 1 + +/* module_param declared in ste_conn_ccd.c */ +extern int ste_debug_level; + +#if defined(NDEBUG) || STE_CONN_DEFAULT_DEBUG_LEVEL == 0 + #define STE_CONN_DBG_DATA_CONTENT(fmt, arg...) + #define STE_CONN_DBG_DATA(fmt, arg...) + #define STE_CONN_DBG(fmt, arg...) + #define STE_CONN_INFO(fmt, arg...) + #define STE_CONN_ERR(fmt, arg...) +#else + #define STE_CONN_DBG_DATA_CONTENT(fmt, arg...) \ + if (ste_debug_level >= 30) \ + { \ + printk(KERN_DEBUG "STE_CONN %s: " fmt "\n" , __func__ , ## arg); \ + } + + #define STE_CONN_DBG_DATA(fmt, arg...) \ + if (ste_debug_level >= 25) \ + { \ + printk(KERN_DEBUG "STE_CONN %s: " fmt "\n" , __func__ , ## arg); \ + } + + #define STE_CONN_DBG(fmt, arg...) \ + if (ste_debug_level >= 20) \ + { \ + printk(KERN_DEBUG "STE_CONN %s: " fmt "\n" , __func__ , ## arg); \ + } + + #define STE_CONN_INFO(fmt, arg...) \ + if (ste_debug_level >= 10) \ + { \ + printk(KERN_INFO "STE_CONN: " fmt "\n" , ## arg); \ + } + + + #define STE_CONN_ERR(fmt, arg...) \ + if (ste_debug_level >= 1) \ + { \ + printk(KERN_ERR "STE_CONN %s: " fmt "\n" , __func__ , ## arg); \ + } + +#endif /* NDEBUG */ + +#define STE_CONN_SET_STATE(__ste_conn_name, __ste_conn_var, __ste_conn_new_state) \ +{ \ + STE_CONN_DBG("New %s: 0x%X", __ste_conn_name, (uint32_t)__ste_conn_new_state); \ + __ste_conn_var = __ste_conn_new_state; \ +} + +#endif /* _STE_CONN_DEBUG_H_ */ diff --git a/drivers/mfd/ste_conn/ste_conn_hci_driver.c b/drivers/mfd/ste_conn/ste_conn_hci_driver.c new file mode 100755 index 00000000000..42e6f6c5088 --- /dev/null +++ b/drivers/mfd/ste_conn/ste_conn_hci_driver.c @@ -0,0 +1,1115 @@ +/* + * file ste_conn_hci_driver.c + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#include <linux/module.h> +#include <linux/skbuff.h> +#include <net/bluetooth/bluetooth.h> +#include <net/bluetooth/hci.h> +#include <net/bluetooth/hci_core.h> + +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/time.h> +#include <linux/jiffies.h> +#include <linux/rfkill.h> +#include <linux/sched.h> +#include <linux/timer.h> + + +#include <linux/mfd/ste_conn.h> +#include <mach/ste_conn_devices.h> +#include "ste_conn_cpd.h" +#include "ste_conn_debug.h" + +#define VERSION "1.0" + +/* HCI device type */ +#define HCI_STE_CONN HCI_VIRTUAL + +/* HCI Driver name */ +#define STE_CONN_HCI_NAME "ste_conn_hci_driver\0" + +/* BT HCI command OpCodes (LSB and MSB) */ +#define HCI_RESET_CMD_LSB 0x03 +#define HCI_RESET_CMD_MSB 0x0C + +/* BT HCI Event OpCodes */ +#define HCI_BT_EVT_CMD_COMPLETE 0x0E +#define HCI_BT_EVT_CMD_STATUS 0x0F + +/* BT HCI Error codes */ +#define HCI_BT_ERROR_NO_ERROR 0x00 +#define HCI_BT_ERROR_CMD_DISALLOWED 0x0C + +/* BT HCI Reset command parameters */ +#define HCI_RESET_LEN 3 +#define HCI_RESET_PARAM_LEN 0 + +/* BT HCI event positions */ +#define HCI_EVT_OP_CODE_POS 0 +#define HCI_EVT_LEN_POS 1 +#define HCI_EVT_CMD_COMPLETE_CMD_LSB_POS 3 +#define HCI_EVT_CMD_COMPLETE_CMD_MSB_POS 4 +#define HCI_EVT_CMD_COMPLETE_STATUS_POS 5 +#define HCI_EVT_CMD_STATUS_STATUS_POS 2 +#define HCI_EVT_CMD_STATUS_CMD_LSB_POS 4 +#define HCI_EVT_CMD_STATUS_CMD_MSB_POS 5 + +/* State-setting defines */ +#define HCI_SET_RESET_STATE(__hci_reset_new_state) \ + STE_CONN_SET_STATE("hci reset_state", hci_info->hreset_state, __hci_reset_new_state) +#define HCI_SET_ENABLE_STATE(__hci_enable_new_state) \ + STE_CONN_SET_STATE("hci enable_state", hci_info->enable_state, __hci_enable_new_state) +#define SET_RFKILL_STATE(__new_state) \ + STE_CONN_SET_STATE("hci rfkill_state", hci_info->rfkill_state, __new_state) +#define SET_RFKILL_RF_STATE(__new_state) \ + STE_CONN_SET_STATE("hci rfkill_rf_state", hci_info->rfkill_rf_state, __new_state) + +/** + * enum ste_conn_hci_rfkill_rf_state - RFKill RF state. + * @RFKILL_RF_ENABLED: Radio is not disabled. + * @RFKILL_HCI_RESET_SENT: HCI reset has been sent. Waiting for reply. + * @RFKILL_RF_DISABLED: Radio is disabled. + */ +enum ste_conn_hci_rfkill_rf_state { + RFKILL_RF_ENABLED, + RFKILL_HCI_RESET_SENT, + RFKILL_RF_DISABLED +}; + +/** + * enum ste_conn_hci_reset_state - RESET-state for hci driver. + * + * @HCI_RESET_STATE_IDLE: No reset in progress. + * @HCI_RESET_STATE_1_CB_RECIVED: One reset callback of three callbacks received. + * @HCI_RESET_STATE_2_CB_RECIVED: Two reset callbacks of three callbacks received. + * @HCI_RESET_STATE_UNREGISTER: Unregister and free hci device. + * @HCI_RESET_STATE_REGISTER: Alloc and register hci device. + * @HCI_RESET_STATE_FAILED: Reset failed. + */ + +enum ste_conn_hci_reset_state { + HCI_RESET_STATE_IDLE, + HCI_RESET_STATE_1_CB_RECIVED, + HCI_RESET_STATE_2_CB_RECIVED, + HCI_RESET_STATE_UNREGISTER, + HCI_RESET_STATE_REGISTER, + HCI_RESET_STATE_SUCCESS, + HCI_RESET_STATE_FAILED +}; + +/** + * This enum holds the different internal states of the HCI driver. + * + * @HCI_ENABLE_STATE_IDLE The HCI driver is loaded but not opened. + * @HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC + * The HCI driver is waiting for a command complete event from + * the BT chip as a response to a BT Enable (true) command. + * @HCI_ENABLE_STATE_BT_ENABLED + * The BT chip is enabled. + * @HCI_ENABLE_STATE_WAITING_BT_DISABLED_CC + * The HCI driver is waiting for a command complete event from + * the BT chip as a response to a BT Enable (false) command. + * @HCI_ENABLE_STATE_BT_DISABLED + * The BT chip is disabled. + * @HCI_ENABLE_STATE_BT_ERROR The HCI driver is in a bad state, some thing has failed and + * is not expected to work properly. + */ +enum ste_conn_hci_enable_state { + HCI_ENABLE_STATE_IDLE, + HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC, + HCI_ENABLE_STATE_BT_ENABLED, + HCI_ENABLE_STATE_WAITING_BT_DISABLED_CC, + HCI_ENABLE_STATE_BT_DISABLED, + HCI_ENABLE_STATE_BT_ERROR +}; + +/** + * struct ste_conn_hci_info - Specifies hci driver private data. + * + * This type specifies ste_conn hci driver private data. + * + * @cpd_bt_cmd: Device structure for bt cmd channel. + * @cpd_bt_evt: Device structure for bt evt channel. + * @cpd_bt_acl: Device structure for bt acl channel. + * @hdev: Device structure for hci device. + * @hreset_state: Device enum for hci driver reset state. + * @enable_state: Device enum for hci driver BT enable state. + * @rfkill: RFKill structure. + * @rfkill_state: Current RFKill state. + * @rfkill_rf_state: Current RFKill RF state. + */ +struct ste_conn_hci_info { + struct ste_conn_device *cpd_bt_cmd; + struct ste_conn_device *cpd_bt_evt; + struct ste_conn_device *cpd_bt_acl; + struct hci_dev *hdev; + enum ste_conn_hci_reset_state hreset_state; + enum ste_conn_hci_enable_state enable_state; + struct rfkill *rfkill; + enum rfkill_state rfkill_state; + enum ste_conn_hci_rfkill_rf_state rfkill_rf_state; +}; + +/** + * struct hci_driver_dev_info - Specifies private data used when receiving + * callbacks from CPD. + * + * @hdev: Device structure for hci device. + * @hci_data_type: Type of data according to BlueZ. + */ +struct hci_driver_dev_info { + struct hci_dev *hdev; + uint8_t hci_data_type; +}; + + +/* + * time_5s - 5 second time struct. + */ +static struct timeval time_5s = { + .tv_sec = 5, + .tv_usec = 0 +}; + +/* + * ste_conn_hci_driver_wait_queue - Main Wait Queue in hci driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(ste_conn_hci_driver_wait_queue); + +/* Variables where LSB and MSB for bt_enable cmd is stored */ +static uint8_t bt_enable_cmd_lsb; +static uint8_t bt_enable_cmd_msb; + +/* Module init and exit procedures */ +static int __init ste_conn_hci_driver_init(void); +static void __exit ste_conn_hci_driver_exit(void); + +/* HCI handlers */ +static int ste_conn_hci_open(struct hci_dev *hdev); +static int ste_conn_hci_close(struct hci_dev *hdev); +static int ste_conn_hci_flush(struct hci_dev *hdev); +static int ste_conn_hci_send(struct sk_buff *skb); +static void ste_conn_hci_destruct(struct hci_dev *hdev); +static void ste_conn_hci_notify(struct hci_dev *hdev, unsigned int evt); +static int ste_conn_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg); + +/* ste_conn driver handlers */ +static void ste_conn_hci_cpd_read_cb(struct ste_conn_device *dev, struct sk_buff *skb); +static void ste_conn_hci_cpd_reset_cb(struct ste_conn_device *dev); + +/* RFKill handling functions */ +static int ste_conn_hci_rfkill_register(void); +static void ste_conn_hci_rfkill_deregister(void); +static int ste_conn_hci_rfkill_toggle_radio(void *data, enum rfkill_state state); +static int ste_conn_hci_rfkill_get_state(void *data, enum rfkill_state *state); + +/* + * struct ste_conn_hci_cb - Specifies callback structure for ste_conn user. + * + * This type specifies callback structure for ste_conn user. + * + * @read_cb: Callback function called when data is received from the controller + * @reset_cb: Callback function called when the controller has been reset + */ +static struct ste_conn_callbacks ste_conn_hci_cb = { + .read_cb = ste_conn_hci_cpd_read_cb, + .reset_cb = ste_conn_hci_cpd_reset_cb +}; + +struct ste_conn_hci_info *hci_info; + +/* time_1s - 1 second time struct.*/ +static struct timeval time_1s = { + .tv_sec = 1, + .tv_usec = 0 +}; + +/* + * ste_conn_hci_wait_queue - Main Wait Queue in HCI driver. + */ +static DECLARE_WAIT_QUEUE_HEAD(ste_conn_hci_wait_queue); + +/* Internal functions */ + +/** + * ste_conn_hci_open() - open ste_conn hci interface. + * @hdev: HCI device being opened. + * + * BlueZ callback function for opening hci interface to device. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is already opened. + * -EACCES if opening of channels failed. + */ +static int ste_conn_hci_open(struct hci_dev *hdev) +{ + struct ste_conn_hci_info *info; + struct hci_driver_dev_info *dev_info; + struct sk_buff *enable_cmd; + int err = 0; + + STE_CONN_INFO("Open ST-Ericsson connectivity hci driver ver %s", VERSION); + + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + err = -EINVAL; + goto finished; + } + + info = (struct ste_conn_hci_info *)hdev->driver_data; + if (!info) { + STE_CONN_ERR("NULL supplied for driver_data"); + err = -EINVAL; + goto finished; + } + + if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) { + STE_CONN_ERR("Device already opened!"); + err = -EBUSY; + goto finished; + } + + if (!(info->cpd_bt_cmd)) { + info->cpd_bt_cmd = ste_conn_register(STE_CONN_DEVICES_BT_CMD, &ste_conn_hci_cb); + if (info->cpd_bt_cmd) { + dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL); + dev_info->hdev = hdev; + dev_info->hci_data_type = HCI_COMMAND_PKT; + info->cpd_bt_cmd->user_data = dev_info; + } else { + STE_CONN_ERR("Couldn't register STE_CONN_DEVICES_BT_CMD to CPD"); + err = -EACCES; + goto finished; + } + } + + if (!(info->cpd_bt_evt)) { + info->cpd_bt_evt = ste_conn_register(STE_CONN_DEVICES_BT_EVT, &ste_conn_hci_cb); + if (info->cpd_bt_evt) { + dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL); + dev_info->hdev = hdev; + dev_info->hci_data_type = HCI_EVENT_PKT; + info->cpd_bt_evt->user_data = dev_info; + } else { + STE_CONN_ERR("Couldn't register STE_CONN_DEVICES_BT_EVT to CPD"); + err = -EACCES; + goto finished; + } + } + + if (!(info->cpd_bt_acl)) { + info->cpd_bt_acl = ste_conn_register(STE_CONN_DEVICES_BT_ACL, &ste_conn_hci_cb); + if (info->cpd_bt_acl) { + dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL); + dev_info->hdev = hdev; + dev_info->hci_data_type = HCI_ACLDATA_PKT; + info->cpd_bt_acl->user_data = dev_info; + } else { + STE_CONN_ERR("Couldn't register STE_CONN_DEVICES_BT_ACL to CPD"); + err = -EACCES; + goto finished; + } + } + + if(info->hreset_state == HCI_RESET_STATE_REGISTER){ + HCI_SET_RESET_STATE(HCI_RESET_STATE_IDLE); + } + + /* Call ste_conn_devices.c implemented function that returns the chip dependant bt enable(true) HCI command. + * If NULL is returned, then no bt enable command should be sent to the chip. + */ + enable_cmd = ste_conn_devices_get_bt_enable_cmd(&bt_enable_cmd_lsb, &bt_enable_cmd_msb, true); + if(enable_cmd != NULL) { + /* Set the HCI state before sending command to chip. */ + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC); + + /* Send command to chip */ + ste_conn_write(info->cpd_bt_cmd, enable_cmd); + + /* Wait for callback to receive command complete and then wake us up again. */ + wait_event_interruptible_timeout(ste_conn_hci_driver_wait_queue, + (info->enable_state == HCI_ENABLE_STATE_BT_ENABLED), + timeval_to_jiffies(&time_5s)); + /* Check the current state to se that it worked. */ + if (info->enable_state != HCI_ENABLE_STATE_BT_ENABLED) { + STE_CONN_ERR("Could not enable BT core (%d).", info->enable_state); + err = -EACCES; + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_DISABLED); + goto finished; + } + } else { + /* The chip is enabled by default */ + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_ENABLED); + } + +finished: + return err; + +} + +/** + * ste_conn_hci_close() - close hci interface. + * @hdev: HCI device being closed. + * + * BlueZ callback function for closing hci interface. + * It flushes the interface first. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EBUSY if device is not opened. + */ +static int ste_conn_hci_close(struct hci_dev *hdev) +{ + struct ste_conn_hci_info *info = NULL; + struct sk_buff *enable_cmd; + int err = 0; + + STE_CONN_INFO("ste_conn_hci_close"); + + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + err = -EINVAL; + goto finished; + } + + info = (struct ste_conn_hci_info *)hdev->driver_data; + if (!info) { + STE_CONN_ERR("NULL supplied for driver_data"); + err = -EINVAL; + goto finished; + } + + if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) { + STE_CONN_ERR("Device already closed!"); + err = -EBUSY; + goto finished; + } + + ste_conn_hci_flush(hdev); + + /*If reset has occured cpd will handle the deregistration of all users*/ + if(info->hreset_state == HCI_RESET_STATE_UNREGISTER){ + info->cpd_bt_cmd = NULL; + info->cpd_bt_evt = NULL; + info->cpd_bt_acl = NULL; + goto finished; + } + + /* Call ste_conn_devices.c implemented function that returns the chip dependant bt enable(false) HCI command. + * If NULL is returned, then no bt enable(false) command should be sent to the chip. + */ + enable_cmd = ste_conn_devices_get_bt_enable_cmd(&bt_enable_cmd_lsb, &bt_enable_cmd_msb, false); + if(enable_cmd != NULL) { + /* Set the HCI state before sending command to chip. */ + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_WAITING_BT_DISABLED_CC); + + /* Send command to chip */ + ste_conn_write(info->cpd_bt_cmd, enable_cmd); + + /* Wait for callback to receive command complete and then wake us up again. */ + wait_event_interruptible_timeout(ste_conn_hci_driver_wait_queue, + (info->enable_state == HCI_ENABLE_STATE_BT_DISABLED), + timeval_to_jiffies(&time_5s)); + /* Check the current state to se that it worked. */ + if (info->enable_state != HCI_ENABLE_STATE_BT_DISABLED) { + STE_CONN_ERR("Could not disable BT core."); + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_ENABLED); + } + } else { + /* The chip is enabled by default and we should not disable it */ + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_ENABLED); + } + + if (info->cpd_bt_cmd) { + if (info->cpd_bt_cmd->user_data) { + kfree(info->cpd_bt_cmd->user_data); + info->cpd_bt_cmd->user_data = NULL; + } + ste_conn_deregister(info->cpd_bt_cmd); + info->cpd_bt_cmd = NULL; + } + + if (info->cpd_bt_evt) { + if (info->cpd_bt_evt->user_data) { + kfree(info->cpd_bt_evt->user_data); + info->cpd_bt_evt->user_data = NULL; + } + ste_conn_deregister(info->cpd_bt_evt); + info->cpd_bt_evt = NULL; + } + + if (info->cpd_bt_acl) { + if (info->cpd_bt_acl->user_data) { + kfree(info->cpd_bt_acl->user_data); + info->cpd_bt_acl->user_data = NULL; + } + ste_conn_deregister(info->cpd_bt_acl); + info->cpd_bt_acl = NULL; + } + +finished: + + return err; +} + +/** + * ste_conn_hci_flush() - flush hci interface. + * @hdev: HCI device being flushed. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + */ +static int ste_conn_hci_flush(struct hci_dev *hdev) +{ + STE_CONN_INFO("ste_conn_hci_flush"); + + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + return -EINVAL; + } + + return 0; +} + +/** + * ste_conn_hci_send() - send packet to device. + * @skb: sk buffer to be sent. + * + * BlueZ callback function for sending sk buffer. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL pointer is supplied. + * -EOPNOTSUPP if supplied packet type is not supported. + * -EACCES if write operation is blocked by RFKill. + * Error codes from ste_conn_write. + */ +static int ste_conn_hci_send(struct sk_buff *skb) +{ + struct hci_dev *hdev; + struct ste_conn_hci_info *info; + int err = 0; + + if (!skb) { + STE_CONN_ERR("NULL supplied for skb"); + return -EINVAL; + } + + hdev = (struct hci_dev *)(skb->dev); + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + return -EINVAL; + } + + info = (struct ste_conn_hci_info *)hdev->driver_data; + if (!info) { + STE_CONN_ERR("NULL supplied for info"); + return -EINVAL; + } + + if (RFKILL_STATE_UNBLOCKED != info->rfkill_state) { + STE_CONN_ERR("RF disabled by RFKill. Packet blocked"); + return -EACCES; + } + + /* Update BlueZ stats */ + hdev->stat.byte_tx += skb->len; + + STE_CONN_DBG_DATA("Data transmit %d bytes", skb->len); + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + STE_CONN_DBG("Sending HCI_COMMAND_PKT"); + err = + ste_conn_write(info->cpd_bt_cmd, skb); + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + STE_CONN_DBG("Sending HCI_ACLDATA_PKT"); + err = + ste_conn_write(info->cpd_bt_acl, skb); + hdev->stat.acl_tx++; + break; + default: + STE_CONN_ERR + ("Trying to transmit unsupported packet type (0x%.2X)", + bt_cb(skb)->pkt_type); + err = -EOPNOTSUPP; + break; + }; + + return err; +} + +/** + * ste_conn_hci_destruct() - destruct hci interface. + * @hdev: HCI device being destructed. + */ +static void ste_conn_hci_destruct(struct hci_dev *hdev) +{ + STE_CONN_INFO("ste_conn_hci_destruct"); + + if (!hci_info) { + goto finish; + } + + if (hci_info->hreset_state == HCI_RESET_STATE_UNREGISTER){ + if (hci_info->hdev) { + hci_free_dev(hci_info->hdev); + } + HCI_SET_RESET_STATE(HCI_RESET_STATE_REGISTER); + } +finish: + return; +} + +/** + * hci_caif_hci_notify() - notify hci interface. + * @hdev: HCI device notifying. + * @evt: Notification event. + */ +static void ste_conn_hci_notify(struct hci_dev *hdev, unsigned int evt) +{ + STE_CONN_INFO("ste_conn_hci_notify - evt = 0x%.2X", evt); +} + +/** + * ste_conn_hci_ioctl() - handle IOCTL call to the interface. + * @hdev: HCI device. + * @cmd: IOCTL cmd. + * @arg: IOCTL arg. + * + * Returns: + * 0 if there is no error. + * -EINVAL if NULL is supplied for hdev. + */ +static int ste_conn_hci_ioctl(struct hci_dev *hdev, unsigned int cmd, unsigned long arg) +{ + STE_CONN_INFO("ste_conn_hci_ioctl - cmd 0x%X", cmd); + + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + return -EINVAL; + } + + return 0; +} + +/** + * ste_conn_hci_cpd_read_cb() - handle data received from connectivity protocol driver. + * @dev: Device receiving data. + * @skb: Buffer with data coming form device. + */ +static void ste_conn_hci_cpd_read_cb(struct ste_conn_device *dev, struct sk_buff *skb) +{ + int err = 0; + struct hci_driver_dev_info *dev_info; + uint8_t status; + + if (!dev) { + STE_CONN_ERR("dev == NULL"); + return; + } + + dev_info = (struct hci_driver_dev_info *)dev->user_data; + + if (!dev_info) { + STE_CONN_ERR("dev_info == NULL"); + return; + } + + /* Check if HCI Driver it self is excpecting a CC packet from the chip after a BT Enable cmd. */ + if ((hci_info->enable_state == HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC || + hci_info->enable_state == HCI_ENABLE_STATE_WAITING_BT_DISABLED_CC) && + hci_info->cpd_bt_evt->h4_channel == dev->h4_channel && + skb->data[HCI_EVT_OP_CODE_POS] == HCI_BT_EVT_CMD_COMPLETE && + skb->data[HCI_EVT_CMD_COMPLETE_CMD_LSB_POS] == bt_enable_cmd_lsb && + skb->data[HCI_EVT_CMD_COMPLETE_CMD_MSB_POS] == bt_enable_cmd_msb) { + /* This is the command complete event for the HCI_Cmd_VS_Bluetooth_Enable. + * Check result and update state. + */ + status = skb->data[HCI_EVT_CMD_COMPLETE_STATUS_POS]; + if (status == HCI_BT_ERROR_NO_ERROR || status == HCI_BT_ERROR_CMD_DISALLOWED) { + /* The BT chip is enabled(disabled. Either it was enabled/disabled now (status 0x00) + * or it was already enabled/disabled (assuming status 0x0c is already enabled/disabled). + */ + if (hci_info->enable_state == HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC) { + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_ENABLED); + STE_CONN_DBG("BT core is enabled."); + } else { + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_DISABLED); + STE_CONN_DBG("BT core is disabled."); + } + } else { + STE_CONN_ERR("Could not enable/disable BT core (0x%X).", status); + /* This should not happend. Put state to ERROR. */ + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_BT_ERROR); + } + + kfree_skb(skb); + + /* Wake up whom ever is waiting for this result. */ + wake_up_interruptible(&ste_conn_hci_driver_wait_queue); + } else if ((hci_info->enable_state == HCI_ENABLE_STATE_WAITING_BT_DISABLED_CC || + hci_info->enable_state == HCI_ENABLE_STATE_WAITING_BT_ENABLED_CC) && + hci_info->cpd_bt_evt->h4_channel == dev->h4_channel && + skb->data[HCI_EVT_OP_CODE_POS] == HCI_BT_EVT_CMD_STATUS && + skb->data[HCI_EVT_CMD_STATUS_CMD_LSB_POS] == bt_enable_cmd_lsb && + skb->data[HCI_EVT_CMD_STATUS_CMD_MSB_POS] == bt_enable_cmd_msb ) { + /* Clear the status events since the Bluez is not expecting them. */ + STE_CONN_DBG("HCI Driver received Command Status(for BT enable):%x", skb->data[HCI_EVT_CMD_STATUS_STATUS_POS]); + /* This is the command status event for the HCI_Cmd_VS_Bluetooth_Enable. + * Just free the packet. + */ + kfree_skb(skb); + } else if ((RFKILL_HCI_RESET_SENT == hci_info->rfkill_rf_state) && + (dev == hci_info->cpd_bt_evt) && + (HCI_BT_EVT_CMD_COMPLETE == skb->data[HCI_EVT_OP_CODE_POS]) && + (HCI_RESET_CMD_LSB == skb->data[HCI_EVT_CMD_COMPLETE_CMD_LSB_POS]) && + (HCI_RESET_CMD_MSB == skb->data[HCI_EVT_CMD_COMPLETE_CMD_MSB_POS])) { + STE_CONN_DBG("Received command complete for HCI reset with status 0x%X", + skb->data[HCI_EVT_CMD_COMPLETE_STATUS_POS]); + SET_RFKILL_RF_STATE(RFKILL_RF_DISABLED); + wake_up_interruptible(&ste_conn_hci_wait_queue); + kfree_skb(skb); + } else { + + bt_cb(skb)->pkt_type = dev_info->hci_data_type; + skb->dev = (struct net_device *)dev_info->hdev; + /* Update BlueZ stats */ + dev_info->hdev->stat.byte_rx += skb->len; + if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT) { + dev_info->hdev->stat.acl_rx++; + } else { + dev_info->hdev->stat.evt_rx++; + } + + STE_CONN_DBG_DATA("Data receive %d bytes", skb->len); + + /* provide BlueZ with received frame*/ + err = hci_recv_frame(skb); + + if (err) { + STE_CONN_ERR("Failed in supplying packet to BlueZ. Error %d", err); + + if (skb) { + kfree_skb(skb); + } + } + } +} + +/** + * ste_conn_hci_cpd_reset_cb() - Reset callback fuction. + * @dev: CPD device reseting. + */ +static void ste_conn_hci_cpd_reset_cb(struct ste_conn_device *dev) +{ + int err = 0; + struct hci_dev *hdev; + struct hci_driver_dev_info *dev_info; + + STE_CONN_INFO("ste_conn_hci_cpd_reset_cb"); + + if (!dev) { + STE_CONN_ERR("NULL supplied for dev"); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + goto finished; + } + + dev_info = (struct hci_driver_dev_info *)dev->user_data; + if (!dev_info) { + STE_CONN_ERR("NULL supplied for dev_info"); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + goto finished; + } + + hdev = dev_info->hdev; + + //free userdata because dev will be deallocated when this cb returns. + if (dev->user_data) { + kfree(dev->user_data); + dev->user_data = NULL; + } + + /* bypass if reset is already in progress*/ + if((hci_info->hreset_state != HCI_RESET_STATE_IDLE) && + (hci_info->hreset_state != HCI_RESET_STATE_1_CB_RECIVED) && + (hci_info->hreset_state != HCI_RESET_STATE_2_CB_RECIVED)){ + STE_CONN_ERR("This should not happen here, bypass reset is already in progress"); + goto finished; + } + + /*3 callback is expected one per h4 channel, eg cmd, acl and evt. + Unregister hdev when we have recived reset from all channels */ + if(hci_info->hreset_state == HCI_RESET_STATE_IDLE){ + HCI_SET_RESET_STATE(HCI_RESET_STATE_1_CB_RECIVED); + goto finished; + } else if(hci_info->hreset_state == HCI_RESET_STATE_1_CB_RECIVED){ + HCI_SET_RESET_STATE(HCI_RESET_STATE_2_CB_RECIVED); + goto finished; + } else { + HCI_SET_RESET_STATE(HCI_RESET_STATE_UNREGISTER); + } + + /*Unregister hci device, close and destruct should be called by the bluetooth stack*/ + if (!hdev) { + STE_CONN_ERR("NULL supplied for hdev"); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + goto finished; + } + + err = hci_unregister_dev(hdev); + if (err) { + STE_CONN_ERR("Can not unregister hci device! ERROR %d", err); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + goto finished; + } + + /*Wait until hdev is unregistered and deallocated*/ + wait_event_interruptible_timeout(ste_conn_hci_driver_wait_queue, + (hci_info->hreset_state == HCI_RESET_STATE_REGISTER), + timeval_to_jiffies(&time_5s)); + + if(hci_info->hreset_state != HCI_RESET_STATE_REGISTER){ + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + STE_CONN_ERR("Unregister hci device timed out"); + goto finished; + } + + /*Alloc and register new hdev*/ + hci_info->hdev = hci_alloc_dev(); + if (!hci_info->hdev) { + STE_CONN_ERR("Could not allocate mem for hci driver."); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + goto finished; + } + + hci_info->hdev->type = HCI_STE_CONN; + hci_info->hdev->driver_data = hci_info; + hci_info->hdev->owner = THIS_MODULE; + hci_info->hdev->open = ste_conn_hci_open; + hci_info->hdev->close = ste_conn_hci_close; + hci_info->hdev->flush = ste_conn_hci_flush; + hci_info->hdev->send = ste_conn_hci_send; + hci_info->hdev->destruct = ste_conn_hci_destruct; + hci_info->hdev->notify = ste_conn_hci_notify; + hci_info->hdev->ioctl = ste_conn_hci_ioctl; + + + err = hci_register_dev(hci_info->hdev); + if (err) { + STE_CONN_ERR("Can not register hci device! ERROR %d", err); + hci_free_dev(hci_info->hdev); + HCI_SET_RESET_STATE(HCI_RESET_STATE_FAILED); + + } + +finished: + return; +} + +/** + * ste_conn_hci_rfkill_register() - Register to RFKill. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from rfkill_register. + */ +static int ste_conn_hci_rfkill_register(void) +{ + int err = 0; + + STE_CONN_INFO("ste_conn_hci_rfkill_register"); + + SET_RFKILL_STATE(RFKILL_STATE_SOFT_BLOCKED); + SET_RFKILL_RF_STATE(RFKILL_RF_DISABLED); + + hci_info->rfkill = rfkill_allocate(&(hci_info->hdev->dev), RFKILL_TYPE_BLUETOOTH); + if (!hci_info->rfkill) { + STE_CONN_ERR("Could not allocate rfkill device."); + err = -ENOMEM; + goto finished; + } + + hci_info->rfkill->name = STE_CONN_HCI_NAME; + hci_info->rfkill->data = NULL; + hci_info->rfkill->state = hci_info->rfkill_state; + + hci_info->rfkill->toggle_radio = ste_conn_hci_rfkill_toggle_radio; + hci_info->rfkill->get_state = ste_conn_hci_rfkill_get_state; + + err = rfkill_register(hci_info->rfkill); + if (err) { + STE_CONN_ERR("Could not register rfkill device."); + goto err_free_rfkill; + } + STE_CONN_DBG("RFKill registration ... OK."); + + goto finished; + +err_free_rfkill: + STE_CONN_ERR("RFKill freeing rfkill device."); + if (hci_info->rfkill) { + rfkill_free(hci_info->rfkill); + hci_info->rfkill = NULL; + } + +finished: + return err; +} + +/** + * ste_conn_hci_rfkill_deregister() - Deregister from RFKill. + */ +static void ste_conn_hci_rfkill_deregister(void) +{ + if (hci_info->rfkill) { + /* Unregister RFKill */ + rfkill_unregister(hci_info->rfkill); + rfkill_free(hci_info->rfkill); + hci_info->rfkill = NULL; + } +} + +/** + * ste_conn_hci_rfkill_toggle_radio() - Called from RFKill upon state change. + * @data: Private data (NULL used). + * @state: New RFKill state. + * + * Returns: + * 0 if there is no error. + * -EBUSY if supplied new state is equal to current state or if disabling of + * state fails. + * -ENOTSUPP if supplied new state is not supported. + * -ENOMEM if allocation failed. + */ +static int ste_conn_hci_rfkill_toggle_radio(void *data, enum rfkill_state state) +{ + int err = 0; + struct sk_buff *skb; + uint8_t *data_ptr; + + STE_CONN_INFO("ste_conn_hci_rfkill_toggle_radio new state = %d", state); + + if (state == hci_info->rfkill_state) { + STE_CONN_ERR("ste_conn_hci_rfkill_toggle_radio called without state change (%d)", state); + err = -EBUSY; + goto finished; + } + + /* Store new state */ + SET_RFKILL_STATE(state); + + switch (state) { + case RFKILL_STATE_SOFT_BLOCKED: + STE_CONN_DBG("RFKILL disable radio."); + + /* We only need to disable if BT is actually running */ + if (!test_bit(HCI_RUNNING, &(hci_info->hdev->flags))) { + STE_CONN_DBG("Setting blocked when device is not opened"); + SET_RFKILL_RF_STATE(RFKILL_RF_DISABLED); + goto finished; + } + + skb = ste_conn_alloc_skb(HCI_RESET_LEN, GFP_KERNEL); + if (!skb) { + STE_CONN_ERR("Could not allocate sk_buffer for HCI reset"); + err = -ENOMEM; + SET_RFKILL_RF_STATE(RFKILL_RF_DISABLED); + goto finished; + } + + data_ptr = skb_put(skb, HCI_RESET_LEN); + data_ptr[0] = HCI_RESET_CMD_LSB; + data_ptr[1] = HCI_RESET_CMD_MSB; + data_ptr[2] = HCI_RESET_PARAM_LEN; + + err = ste_conn_write(hci_info->cpd_bt_cmd, skb); + if (err) { + STE_CONN_ERR("ste_conn_write failed when sending HCI reset (%d)", err); + kfree_skb(skb); + goto finished; + } + + SET_RFKILL_RF_STATE(RFKILL_HCI_RESET_SENT); + + /* Wait for reset to finish */ + if (wait_event_interruptible_timeout(ste_conn_hci_wait_queue, + (RFKILL_RF_DISABLED == hci_info->rfkill_rf_state), + timeval_to_jiffies(&time_1s)) <= 0) { + STE_CONN_ERR("Couldn't disable RF in time"); + err = -EBUSY; + SET_RFKILL_RF_STATE(RFKILL_RF_DISABLED); + } + break; + + case RFKILL_STATE_UNBLOCKED: + STE_CONN_DBG("RFKILL enable radio."); + /* Remove block of data TX. User will have to handle the reset + * of the chip + */ + SET_RFKILL_RF_STATE(RFKILL_RF_ENABLED); + break; + + case RFKILL_STATE_HARD_BLOCKED: + STE_CONN_ERR("RFKill hard disable radio. Should never happen!"); + err = -ENOTSUPP; + break; + + default: + STE_CONN_ERR("RFKill unknown state %d. Should never happen!", state); + err = -ENOTSUPP; + break; + } + +finished: + return err; +} + +/** + * ste_conn_hci_rfkill_get_state - Called from RFKill to read current RFKill state. + * @data: Data supplied when registering (NULL). + * @state: Current RFKill state (to be returned). + * + * Returns: + * 0 if there is no error (no error can occur). + */ +static int ste_conn_hci_rfkill_get_state(void *data, enum rfkill_state *state) +{ + STE_CONN_INFO("ste_conn_hci_rfkill_get_state (%d)", hci_info->rfkill_state); + + *state = hci_info->rfkill_state; + return 0; +} + +/** + * ste_conn_hci_driver_init() - Initialize module. + * + * Add hci device to ste_conn driver and exit. + * + * Returns: + * 0 if there is no error. + * -ENOMEM if allocation fails. + * Error codes from hci_register_dev. + */ +static int __init ste_conn_hci_driver_init(void) +{ + int err = 0; + + STE_CONN_INFO("ST-Ericsson connectivity hci driver ver %s", VERSION); + + /* Initialize private data. */ + hci_info = kzalloc(sizeof(*hci_info), GFP_KERNEL); + if (!hci_info) { + STE_CONN_ERR("Could not alloc hci_info struct."); + err = -ENOMEM; + goto finished; + } + + hci_info->hdev = hci_alloc_dev(); + if (!hci_info->hdev) { + STE_CONN_ERR("Could not allocate mem for hci driver."); + err = -ENOMEM; + goto err_free_info; + } + + hci_info->hdev->type = HCI_STE_CONN; + hci_info->hdev->driver_data = hci_info; + hci_info->hdev->owner = THIS_MODULE; + hci_info->hdev->open = ste_conn_hci_open; + hci_info->hdev->close = ste_conn_hci_close; + hci_info->hdev->flush = ste_conn_hci_flush; + hci_info->hdev->send = ste_conn_hci_send; + hci_info->hdev->destruct = ste_conn_hci_destruct; + hci_info->hdev->notify = ste_conn_hci_notify; + hci_info->hdev->ioctl = ste_conn_hci_ioctl; + + HCI_SET_ENABLE_STATE(HCI_ENABLE_STATE_IDLE); + HCI_SET_RESET_STATE(HCI_RESET_STATE_IDLE); + + err = hci_register_dev(hci_info->hdev); + if (err) { + STE_CONN_ERR("Can not register hci device! ERROR %d", err); + goto err_free_hci; + } + + /* Register to RFKill */ + err = ste_conn_hci_rfkill_register(); + if (err) { + STE_CONN_ERR("RFKill registration error %d.", err); + goto err_hci_dereg; + } + + goto finished; + +err_hci_dereg: + hci_unregister_dev(hci_info->hdev); +err_free_hci: + hci_free_dev(hci_info->hdev); +err_free_info: + kfree(hci_info); + hci_info = NULL; +finished: + return err; +} + +/** + * ste_conn_hci_driver_exit() - Remove module. + * + * Remove hci device from ste_conn driver. + */ +static void __exit ste_conn_hci_driver_exit(void) +{ + int err = 0; + + STE_CONN_INFO("ste_conn hci driver exit."); + + if (hci_info) { + /* Deregister from RFKill */ + ste_conn_hci_rfkill_deregister(); + + if (hci_info->hdev) { + err = hci_unregister_dev(hci_info->hdev); + if (err) { + STE_CONN_ERR("Can not unregister hci device! ERROR %d", err); + } + hci_free_dev(hci_info->hdev); + } + + kfree(hci_info); + hci_info = NULL; + } +} + + +module_init(ste_conn_hci_driver_init); +module_exit(ste_conn_hci_driver_exit); + +MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson"); +MODULE_AUTHOR("Henrik Possung ST-Ericsson"); +MODULE_AUTHOR("Josef Kindberg ST-Ericsson"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson controller ver " VERSION); +MODULE_VERSION(VERSION); diff --git a/firmware/Makefile b/firmware/Makefile index 8af0fc7210b..e19ec4f76b6 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -137,6 +137,10 @@ fw-shipped-$(CONFIG_USB_SERIAL_XIRCOM) += keyspan_pda/xircom_pgs.fw fw-shipped-$(CONFIG_USB_VICAM) += vicam/firmware.fw fw-shipped-$(CONFIG_VIDEO_CPIA2) += cpia2/stv0672_vp4.bin fw-shipped-$(CONFIG_YAM) += yam/1200.bin yam/9600.bin +fw-shipped-$(CONFIG_MFD_STE_CONN) += ste_conn_patch_info.fw ste_conn_settings_info.fw \ + STLC2600_R6_03_01.fw STLC2690_R6_03_A1_E5.fw \ + STLC2600_R6_04_02.fw STLC2690_R6_04_A2.fw \ + STLC2600_R7_00_01.fw STLC2690_R7_00_A1_E5.fw fw-shipped-all := $(fw-shipped-y) $(fw-shipped-m) $(fw-shipped-) @@ -149,6 +153,9 @@ quiet_cmd_mkdir = MKDIR $(patsubst $(objtree)/%,%,$@) quiet_cmd_ihex = IHEX $@ cmd_ihex = $(OBJCOPY) -Iihex -Obinary $< $@ +quiet_cmd_copy = COPY $@ + cmd_copy = cp $< $@ + quiet_cmd_ihex2fw = IHEX2FW $@ cmd_ihex2fw = $(objtree)/$(obj)/ihex2fw $< $@ @@ -212,6 +219,9 @@ $(patsubst %,$(obj)/%.gen.o, $(fw-external-y)): $(obj)/%.gen.o: $(fwdir)/% $(obj)/%: $(obj)/%.ihex | $(objtree)/$(obj)/$$(dir %) $(call cmd,ihex) +$(obj)/%: $(obj)/%.org | $(objtree)/$(obj)/$$(dir %) + $(call cmd,copy) + # Don't depend on ihex2fw if we're installing and it already exists. # Putting it after | in the dependencies doesn't seem sufficient when # we're installing after a cross-compile, because ihex2fw has dependencies diff --git a/firmware/STLC2600_R6_03_01.fw.org b/firmware/STLC2600_R6_03_01.fw.org Binary files differnew file mode 100755 index 00000000000..5bde4069f85 --- /dev/null +++ b/firmware/STLC2600_R6_03_01.fw.org diff --git a/firmware/STLC2600_R6_04_02.fw.org b/firmware/STLC2600_R6_04_02.fw.org Binary files differnew file mode 100755 index 00000000000..3b29496254b --- /dev/null +++ b/firmware/STLC2600_R6_04_02.fw.org diff --git a/firmware/STLC2600_R7_00_01.fw.org b/firmware/STLC2600_R7_00_01.fw.org Binary files differnew file mode 100755 index 00000000000..1a2d14b1e0a --- /dev/null +++ b/firmware/STLC2600_R7_00_01.fw.org diff --git a/firmware/STLC2690_R6_03_A1_E5.fw.org b/firmware/STLC2690_R6_03_A1_E5.fw.org Binary files differnew file mode 100755 index 00000000000..6a97766ac7a --- /dev/null +++ b/firmware/STLC2690_R6_03_A1_E5.fw.org diff --git a/firmware/STLC2690_R6_04_A2.fw.org b/firmware/STLC2690_R6_04_A2.fw.org Binary files differnew file mode 100755 index 00000000000..b9f97246ddb --- /dev/null +++ b/firmware/STLC2690_R6_04_A2.fw.org diff --git a/firmware/STLC2690_R7_00_A1_E5.fw.org b/firmware/STLC2690_R7_00_A1_E5.fw.org Binary files differnew file mode 100755 index 00000000000..1f73bc1a04a --- /dev/null +++ b/firmware/STLC2690_R7_00_A1_E5.fw.org diff --git a/firmware/ste_conn_patch_info.fw.org b/firmware/ste_conn_patch_info.fw.org new file mode 100755 index 00000000000..722f1bddb23 --- /dev/null +++ b/firmware/ste_conn_patch_info.fw.org @@ -0,0 +1,24 @@ +######################################################################## +# ST-Ericsson Connectivity Chip Patch Information File +# +# All patch information must be stored as follows +# +# HCI_revision LMP_Subversion File_name (without .org extension) +# +######################################################################## +# +# +################# +# +# Patch file for ST Microelectronics - STLC2690 +# +0x0603 0x0014 STLC2690_R6_03_A1_E5.fw +0x0604 0x001D STLC2690_R6_04_A2.fw +# +################# +# +# Patch file for ST-Ericsson - CG2900 +# +0x0700 0x0011 STLC2690_R7_00_A1_E5.fw +# +################# diff --git a/firmware/ste_conn_settings_info.fw.org b/firmware/ste_conn_settings_info.fw.org new file mode 100755 index 00000000000..89399d3670b --- /dev/null +++ b/firmware/ste_conn_settings_info.fw.org @@ -0,0 +1,24 @@ +######################################################################## +# ST-Ericsson Connectivity Chip Factory Settings Information File +# +# All information must be stored as follows +# +# HCI_revision LMP_Subversion File_name (without .org extension) +# +######################################################################## +# +# +################# +# +# Factory Settings for ST Microelectronics - STLC2690 +# +0x0603 0x0014 STLC2600_R6_03_01.fw +0x0604 0x001D STLC2600_R6_04_02.fw +# +################# +# +# Factory Settings for ST-Ericsson - CG2900 +# +0x0700 0x0011 STLC2600_R7_00_01.fw +# +################# diff --git a/include/linux/mfd/ste_conn.h b/include/linux/mfd/ste_conn.h new file mode 100755 index 00000000000..c9176422988 --- /dev/null +++ b/include/linux/mfd/ste_conn.h @@ -0,0 +1,117 @@ +/* + * file ste_conn.h + * + * Copyright (C) ST-Ericsson AB 2010 + * + * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller. + * License terms: GNU General Public License (GPL), version 2 + * + * Authors: + * Pär-Gunnar Hjälmdahl (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. + */ + +#ifndef _STE_CONN_H_ +#define _STE_CONN_H_ + +#include <linux/skbuff.h> +#include <mach/ste_conn_devices.h> + +struct ste_conn_callbacks; + +/** + * struct ste_conn_device - Device structure for ste_conn user. + * @h4_channel: HCI H:4 channel used by this device. + * @callbacks: 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 ste_conn_device { + int h4_channel; + struct ste_conn_callbacks *callbacks; + bool logger_enabled; + void *user_data; + struct device *dev; +}; + +/** + * struct ste_conn_callbacks - Callback structure for ste_conn 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 ste_conn_callbacks { + void (*read_cb) (struct ste_conn_device *dev, struct sk_buff *skb); + void (*reset_cb) (struct ste_conn_device *dev); +}; + +/** + * ste_conn_register() - Register ste_conn user. + * @name: Name of HCI H:4 channel to register to. + * @cb: Callback structure to use for the H:4 channel. + * + * The ste_conn_register() function register ste_conn user. + * + * Returns: + * Pointer to ste_conn device structure if successful. + * NULL upon failure. + */ +extern struct ste_conn_device *ste_conn_register(char *name, struct ste_conn_callbacks *cb); + +/** + * ste_conn_deregister() - Remove registration of ste_conn user. + * @dev: ste_conn device. + * + * The ste_conn_deregister() function removes the ste_conn user. + */ +extern void ste_conn_deregister(struct ste_conn_device *dev); + +/** + * ste_conn_reset() - Reset the ste_conn controller. + * @dev: ste_conn device. + * + * The ste_conn_reset() function reset the ste_conn controller. + * + * Returns: + * 0 if there is no error. + * -EACCES if driver has not been initialized. + */ +extern int ste_conn_reset(struct ste_conn_device *dev); + +/** + * ste_conn_alloc_skb() - Alloc an sk_buff structure for ste_conn handling. + * @size: Size in number of octets. + * @priority: Allocation priority, e.g. GFP_KERNEL. + * + * The ste_conn_alloc_skb() function allocates an sk_buff structure for ste_conn + * handling. + * + * Returns: + * Pointer to sk_buff buffer structure if successful. + * NULL upon allocation failure. + */ +extern struct sk_buff *ste_conn_alloc_skb(unsigned int size, gfp_t priority); + +/** + * ste_conn_write() - Send data to the connectivity controller. + * @dev: ste_conn device. + * @skb: Data packet. + * + * The ste_conn_write() function sends data to the connectivity controller. + * + * 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. + * Error codes returned from cpd_enable_hci_logger. + */ +extern int ste_conn_write(struct ste_conn_device *dev, struct sk_buff *skb); + +#endif /* _STE_CONN_H_ */ |