aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/DocBook/Makefile5
-rwxr-xr-xDocumentation/DocBook/ste_conn.tmpl1213
-rw-r--r--arch/arm/configs/mop500_defconfig41
-rw-r--r--arch/arm/configs/mop500_ed_defconfig30
-rw-r--r--arch/arm/mach-u8500/Makefile6
-rwxr-xr-xarch/arm/mach-u8500/include/mach/ste_conn_devices.h218
-rwxr-xr-xarch/arm/mach-u8500/ste_conn_devices.c381
-rw-r--r--drivers/mfd/Kconfig13
-rw-r--r--drivers/mfd/Makefile1
-rwxr-xr-xdrivers/mfd/ste_conn/Makefile10
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_ccd.c2023
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_ccd.h83
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_char_devices.c775
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_char_devices.h37
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_cpd.c2901
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_cpd.h92
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_debug.h74
-rwxr-xr-xdrivers/mfd/ste_conn/ste_conn_hci_driver.c1115
-rw-r--r--firmware/Makefile10
-rwxr-xr-xfirmware/STLC2600_R6_03_01.fw.orgbin0 -> 436 bytes
-rwxr-xr-xfirmware/STLC2600_R6_04_02.fw.orgbin0 -> 424 bytes
-rwxr-xr-xfirmware/STLC2600_R7_00_01.fw.orgbin0 -> 363 bytes
-rwxr-xr-xfirmware/STLC2690_R6_03_A1_E5.fw.orgbin0 -> 6156 bytes
-rwxr-xr-xfirmware/STLC2690_R6_04_A2.fw.orgbin0 -> 5320 bytes
-rwxr-xr-xfirmware/STLC2690_R7_00_A1_E5.fw.orgbin0 -> 6384 bytes
-rwxr-xr-xfirmware/ste_conn_patch_info.fw.org24
-rwxr-xr-xfirmware/ste_conn_settings_info.fw.org24
-rwxr-xr-xinclude/linux/mfd/ste_conn.h117
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, &amp;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 &lt; 0) {
+ printf("Error on open file: %d (%s)\n", errno, strerror(errno));
+ return errno;
+ }
+ if (0 &gt; 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 &gt;= 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(&current_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(&current_dev);
+
+ /* Chip shut-down finished, set correct state. */
+ CPD_SET_MAIN_STATE(CPD_STATE_IDLE);
+ }
+ }
+ goto finished;
+
+error_handling:
+ cpd_free_user_dev(&current_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
new file mode 100755
index 00000000000..5bde4069f85
--- /dev/null
+++ b/firmware/STLC2600_R6_03_01.fw.org
Binary files differ
diff --git a/firmware/STLC2600_R6_04_02.fw.org b/firmware/STLC2600_R6_04_02.fw.org
new file mode 100755
index 00000000000..3b29496254b
--- /dev/null
+++ b/firmware/STLC2600_R6_04_02.fw.org
Binary files differ
diff --git a/firmware/STLC2600_R7_00_01.fw.org b/firmware/STLC2600_R7_00_01.fw.org
new file mode 100755
index 00000000000..1a2d14b1e0a
--- /dev/null
+++ b/firmware/STLC2600_R7_00_01.fw.org
Binary files differ
diff --git a/firmware/STLC2690_R6_03_A1_E5.fw.org b/firmware/STLC2690_R6_03_A1_E5.fw.org
new file mode 100755
index 00000000000..6a97766ac7a
--- /dev/null
+++ b/firmware/STLC2690_R6_03_A1_E5.fw.org
Binary files differ
diff --git a/firmware/STLC2690_R6_04_A2.fw.org b/firmware/STLC2690_R6_04_A2.fw.org
new file mode 100755
index 00000000000..b9f97246ddb
--- /dev/null
+++ b/firmware/STLC2690_R6_04_A2.fw.org
Binary files differ
diff --git a/firmware/STLC2690_R7_00_A1_E5.fw.org b/firmware/STLC2690_R7_00_A1_E5.fw.org
new file mode 100755
index 00000000000..1f73bc1a04a
--- /dev/null
+++ b/firmware/STLC2690_R7_00_A1_E5.fw.org
Binary files differ
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_ */