* [PATCH] Fix headset disconnecting via device disconnect
From: Radoslaw Jablonski @ 2010-09-23 10:32 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Radoslaw Jablonski
Headsets for proper disconnecting need to disconnect profiles in specified
order(by ex. disconnect a2dp, then sink and hfp at the end). Instead of
adding separate callbacks for disconnecting each profile, now adding only
one callback in audio/device.c for calling audio disconnect functions in
correct order. New disconnect callback works similarly to dev_disconnect
from audio/device.c
---
audio/device.c | 138 +++++++++++++++++++++++++++++++++++++------------------
audio/headset.c | 19 --------
audio/sink.c | 29 ------------
audio/source.c | 33 -------------
4 files changed, 93 insertions(+), 126 deletions(-)
diff --git a/audio/device.c b/audio/device.c
index b30590e..eec67be 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -91,7 +91,9 @@ struct dev_priv {
guint control_timer;
guint avdtp_timer;
guint headset_timer;
+ guint dc_id;
+ gboolean disconnecting;
gboolean authorized;
guint auth_idle_id;
};
@@ -123,6 +125,9 @@ static void device_free(struct audio_device *dev)
dbus_message_unref(priv->dc_req);
if (priv->conn_req)
dbus_message_unref(priv->conn_req);
+ if (priv->dc_id)
+ device_remove_disconnect_watch(dev->btd_dev,
+ priv->dc_id);
g_free(priv);
}
@@ -145,6 +150,69 @@ static const char *state2str(audio_state_t state)
}
}
+static gboolean control_connect_timeout(gpointer user_data)
+{
+ struct audio_device *dev = user_data;
+
+ dev->priv->control_timer = 0;
+
+ if (dev->control)
+ avrcp_connect(dev);
+
+ return FALSE;
+}
+
+static gboolean device_set_control_timer(struct audio_device *dev)
+{
+ struct dev_priv *priv = dev->priv;
+
+ if (!dev->control)
+ return FALSE;
+
+ if (priv->control_timer)
+ return FALSE;
+
+ priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT,
+ control_connect_timeout,
+ dev);
+
+ return TRUE;
+}
+
+static void device_remove_control_timer(struct audio_device *dev)
+{
+ if (dev->priv->control_timer)
+ g_source_remove(dev->priv->control_timer);
+ dev->priv->control_timer = 0;
+}
+
+static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
+ void *user_data)
+{
+ struct audio_device *dev = user_data;
+ struct dev_priv *priv = dev->priv;
+
+ if (priv->state == AUDIO_STATE_DISCONNECTED)
+ return;
+
+ if (priv->disconnecting)
+ return;
+
+ priv->disconnecting = TRUE;
+
+ if (dev->control) {
+ device_remove_control_timer(dev);
+ avrcp_disconnect(dev);
+ }
+
+ if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+ sink_shutdown(dev->sink);
+ else if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+ headset_shutdown(dev);
+ else
+ priv->disconnecting = FALSE;
+}
+
static void device_set_state(struct audio_device *dev, audio_state_t new_state)
{
struct dev_priv *priv = dev->priv;
@@ -155,9 +223,21 @@ static void device_set_state(struct audio_device *dev, audio_state_t new_state)
if (!state_str)
return;
- if (new_state == AUDIO_STATE_DISCONNECTED)
+ if (new_state == AUDIO_STATE_DISCONNECTED) {
priv->authorized = FALSE;
+ if (priv->dc_id) {
+ device_remove_disconnect_watch(dev->btd_dev,
+ priv->dc_id);
+ priv->dc_id = 0;
+ }
+ }
+ else if (new_state == AUDIO_STATE_CONNECTED) {
+ priv->disconnecting = FALSE;
+ priv->dc_id = device_add_disconnect_watch(dev->btd_dev,
+ disconnect_cb, dev, NULL);
+ }
+
if (dev->priv->state == new_state) {
DBG("state change attempted from %s to %s",
state_str, state_str);
@@ -166,11 +246,15 @@ static void device_set_state(struct audio_device *dev, audio_state_t new_state)
dev->priv->state = new_state;
- if (priv->dc_req && new_state == AUDIO_STATE_DISCONNECTED) {
- reply = dbus_message_new_method_return(priv->dc_req);
- dbus_message_unref(priv->dc_req);
- priv->dc_req = NULL;
- g_dbus_send_message(dev->conn, reply);
+ if (new_state == AUDIO_STATE_DISCONNECTED) {
+ if (priv->dc_req) {
+ reply = dbus_message_new_method_return(priv->dc_req);
+ dbus_message_unref(priv->dc_req);
+ priv->dc_req = NULL;
+ g_dbus_send_message(dev->conn, reply);
+ }
+ else if (priv->disconnecting)
+ priv->disconnecting = FALSE;
}
if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
@@ -191,42 +275,6 @@ static void device_set_state(struct audio_device *dev, audio_state_t new_state)
DBUS_TYPE_STRING, &state_str);
}
-static gboolean control_connect_timeout(gpointer user_data)
-{
- struct audio_device *dev = user_data;
-
- dev->priv->control_timer = 0;
-
- if (dev->control)
- avrcp_connect(dev);
-
- return FALSE;
-}
-
-static gboolean device_set_control_timer(struct audio_device *dev)
-{
- struct dev_priv *priv = dev->priv;
-
- if (!dev->control)
- return FALSE;
-
- if (priv->control_timer)
- return FALSE;
-
- priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT,
- control_connect_timeout,
- dev);
-
- return TRUE;
-}
-
-static void device_remove_control_timer(struct audio_device *dev)
-{
- if (dev->priv->control_timer)
- g_source_remove(dev->priv->control_timer);
- dev->priv->control_timer = 0;
-}
-
static gboolean avdtp_connect_timeout(gpointer user_data)
{
struct audio_device *dev = user_data;
@@ -348,7 +396,7 @@ static void device_sink_cb(struct audio_device *dev,
avrcp_disconnect(dev);
}
if (priv->hs_state != HEADSET_STATE_DISCONNECTED &&
- priv->dc_req) {
+ (priv->dc_req || priv->disconnecting)) {
headset_shutdown(dev);
break;
}
@@ -427,8 +475,8 @@ static void device_headset_cb(struct audio_device *dev,
switch (new_state) {
case HEADSET_STATE_DISCONNECTED:
device_remove_avdtp_timer(dev);
- if (priv->sink_state != SINK_STATE_DISCONNECTED &&
- dev->sink && priv->dc_req) {
+ if (priv->sink_state != SINK_STATE_DISCONNECTED && dev->sink &&
+ (priv->dc_req || priv->disconnecting)) {
sink_shutdown(dev->sink);
break;
}
diff --git a/audio/headset.c b/audio/headset.c
index dad0716..9955d4b 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -158,7 +158,6 @@ struct headset {
GIOChannel *tmp_rfcomm;
GIOChannel *sco;
guint sco_id;
- guint dc_id;
gboolean auto_dc;
@@ -2162,9 +2161,6 @@ static void headset_free(struct audio_device *dev)
hs->dc_timer = 0;
}
- if (hs->dc_id)
- device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
-
close_sco(dev);
headset_close_rfcomm(dev);
@@ -2477,16 +2473,6 @@ int headset_connect_sco(struct audio_device *dev, GIOChannel *io)
return 0;
}
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
- void *user_data)
-{
- struct audio_device *device = user_data;
-
- info("Headset: disconnect %s", device->path);
-
- headset_shutdown(device);
-}
-
void headset_set_state(struct audio_device *dev, headset_state_t state)
{
struct headset *hs = dev->headset;
@@ -2520,8 +2506,6 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
telephony_device_disconnected(dev);
}
active_devices = g_slist_remove(active_devices, dev);
- device_remove_disconnect_watch(dev->btd_dev, hs->dc_id);
- hs->dc_id = 0;
break;
case HEADSET_STATE_CONNECTING:
emit_property_changed(dev->conn, dev->path,
@@ -2550,9 +2534,6 @@ void headset_set_state(struct audio_device *dev, headset_state_t state)
DBUS_TYPE_BOOLEAN, &value);
active_devices = g_slist_append(active_devices, dev);
telephony_device_connected(dev);
- hs->dc_id = device_add_disconnect_watch(dev->btd_dev,
- disconnect_cb,
- dev, NULL);
} else if (hs->state == HEADSET_STATE_PLAYING) {
value = FALSE;
g_dbus_emit_signal(dev->conn, dev->path,
diff --git a/audio/sink.c b/audio/sink.c
index 67cffee..eb90c21 100644
--- a/audio/sink.c
+++ b/audio/sink.c
@@ -61,7 +61,6 @@ struct sink {
struct avdtp *session;
struct avdtp_stream *stream;
unsigned int cb_id;
- guint dc_id;
guint retry_id;
avdtp_session_state_t session_state;
avdtp_state_t stream_state;
@@ -140,11 +139,6 @@ static void avdtp_state_callback(struct audio_device *dev,
emit_property_changed(dev->conn, dev->path,
AUDIO_SINK_INTERFACE, "Connected",
DBUS_TYPE_BOOLEAN, &value);
- if (sink->dc_id) {
- device_remove_disconnect_watch(dev->btd_dev,
- sink->dc_id);
- sink->dc_id = 0;
- }
}
sink_set_state(dev, SINK_STATE_DISCONNECTED);
break;
@@ -171,17 +165,6 @@ static void pending_request_free(struct audio_device *dev,
g_free(pending);
}
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
- void *user_data)
-{
- struct audio_device *device = user_data;
- struct sink *sink = device->sink;
-
- DBG("Sink: disconnect %s", device->path);
-
- avdtp_close(sink->session, sink->stream, TRUE);
-}
-
static void stream_state_changed(struct avdtp_stream *stream,
avdtp_state_t old_state,
avdtp_state_t new_state,
@@ -209,12 +192,6 @@ static void stream_state_changed(struct avdtp_stream *stream,
pending_request_free(dev, p);
}
- if (sink->dc_id) {
- device_remove_disconnect_watch(dev->btd_dev,
- sink->dc_id);
- sink->dc_id = 0;
- }
-
if (sink->session) {
avdtp_unref(sink->session);
sink->session = NULL;
@@ -234,9 +211,6 @@ static void stream_state_changed(struct avdtp_stream *stream,
AUDIO_SINK_INTERFACE,
"Connected",
DBUS_TYPE_BOOLEAN, &value);
- sink->dc_id = device_add_disconnect_watch(dev->btd_dev,
- disconnect_cb,
- dev, NULL);
} else if (old_state == AVDTP_STATE_STREAMING) {
value = FALSE;
g_dbus_emit_signal(dev->conn, dev->path,
@@ -601,9 +575,6 @@ static void sink_free(struct audio_device *dev)
avdtp_stream_remove_cb(sink->session, sink->stream,
sink->cb_id);
- if (sink->dc_id)
- device_remove_disconnect_watch(dev->btd_dev, sink->dc_id);
-
if (sink->session)
avdtp_unref(sink->session);
diff --git a/audio/source.c b/audio/source.c
index 01173f5..e8671ed 100644
--- a/audio/source.c
+++ b/audio/source.c
@@ -62,7 +62,6 @@ struct source {
struct avdtp *session;
struct avdtp_stream *stream;
unsigned int cb_id;
- guint dc_id;
guint retry_id;
avdtp_session_state_t session_state;
avdtp_state_t stream_state;
@@ -133,12 +132,6 @@ static void avdtp_state_callback(struct audio_device *dev,
switch (new_state) {
case AVDTP_SESSION_STATE_DISCONNECTED:
- if (source->state != SOURCE_STATE_CONNECTING &&
- source->dc_id) {
- device_remove_disconnect_watch(dev->btd_dev,
- source->dc_id);
- source->dc_id = 0;
- }
source_set_state(dev, SOURCE_STATE_DISCONNECTED);
break;
case AVDTP_SESSION_STATE_CONNECTING:
@@ -164,17 +157,6 @@ static void pending_request_free(struct audio_device *dev,
g_free(pending);
}
-static void disconnect_cb(struct btd_device *btd_dev, gboolean removal,
- void *user_data)
-{
- struct audio_device *device = user_data;
- struct source *source = device->source;
-
- DBG("Source: disconnect %s", device->path);
-
- avdtp_close(source->session, source->stream, TRUE);
-}
-
static void stream_state_changed(struct avdtp_stream *stream,
avdtp_state_t old_state,
avdtp_state_t new_state,
@@ -201,12 +183,6 @@ static void stream_state_changed(struct avdtp_stream *stream,
pending_request_free(dev, p);
}
- if (source->dc_id) {
- device_remove_disconnect_watch(dev->btd_dev,
- source->dc_id);
- source->dc_id = 0;
- }
-
if (source->session) {
avdtp_unref(source->session);
source->session = NULL;
@@ -215,12 +191,6 @@ static void stream_state_changed(struct avdtp_stream *stream,
source->cb_id = 0;
break;
case AVDTP_STATE_OPEN:
- if (old_state == AVDTP_STATE_CONFIGURED &&
- source->state == SOURCE_STATE_CONNECTING) {
- source->dc_id = device_add_disconnect_watch(dev->btd_dev,
- disconnect_cb,
- dev, NULL);
- }
source_set_state(dev, SOURCE_STATE_CONNECTED);
break;
case AVDTP_STATE_STREAMING:
@@ -536,9 +506,6 @@ static void source_free(struct audio_device *dev)
avdtp_stream_remove_cb(source->session, source->stream,
source->cb_id);
- if (source->dc_id)
- device_remove_disconnect_watch(dev->btd_dev, source->dc_id);
-
if (source->session)
avdtp_unref(source->session);
--
1.7.0.4
^ permalink raw reply related
* Re: [PATCH v4 2/2] Bluetooth: hidp: Add support for hidraw HIDIOCGFEATURE and HIDIOCSFEATURE
From: Ville Tervo @ 2010-09-23 11:51 UTC (permalink / raw)
To: ext Alan Ott
Cc: Jiri Kosina, Stefan Achatz, Antonio Ospite, Alexey Dobriyan,
Tejun Heo, Alan Stern, Greg Kroah-Hartman, Marcel Holtmann,
Stephane Chatty, Michael Poole, David S. Miller, Bastien Nocera,
Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <1281990059-3562-3-git-send-email-alan@signal11.us>
Hi Alan,
One comment.
On Mon, Aug 16, 2010 at 10:20:59PM +0200, ext Alan Ott wrote:
> This patch adds support or getting and setting feature reports for bluetooth
> HID devices from HIDRAW.
>
> Signed-off-by: Alan Ott <alan@signal11.us>
> ---
> net/bluetooth/hidp/core.c | 114 +++++++++++++++++++++++++++++++++++++++++++--
> net/bluetooth/hidp/hidp.h | 8 +++
> 2 files changed, 118 insertions(+), 4 deletions(-)
>
> diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
> index bfe641b..0e4880e 100644
> --- a/net/bluetooth/hidp/core.c
> +++ b/net/bluetooth/hidp/core.c
> @@ -36,6 +36,7 @@
> #include <linux/file.h>
> #include <linux/init.h>
> #include <linux/wait.h>
> +#include <linux/mutex.h>
> #include <net/sock.h>
>
> #include <linux/input.h>
> @@ -313,6 +314,86 @@ static int hidp_send_report(struct hidp_session *session, struct hid_report *rep
> return hidp_queue_report(session, buf, rsize);
> }
>
> +static int hidp_get_raw_report(struct hid_device *hid,
> + unsigned char report_number,
> + unsigned char *data, size_t count,
> + unsigned char report_type)
> +{
> + struct hidp_session *session = hid->driver_data;
> + struct sk_buff *skb;
> + size_t len;
> + int numbered_reports = hid->report_enum[report_type].numbered;
> +
> + switch (report_type) {
> + case HID_FEATURE_REPORT:
> + report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE;
> + break;
> + case HID_INPUT_REPORT:
> + report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT;
> + break;
> + case HID_OUTPUT_REPORT:
> + report_type = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUPUT;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + if (mutex_lock_interruptible(&session->report_mutex))
> + return -ERESTARTSYS;
> +
> + /* Set up our wait, and send the report request to the device. */
> + session->waiting_report_type = report_type & HIDP_DATA_RTYPE_MASK;
> + session->waiting_report_number = numbered_reports ? report_number : -1;
> + set_bit(HIDP_WAITING_FOR_RETURN, &session->flags);
> + data[0] = report_number;
> + if (hidp_send_ctrl_message(hid->driver_data, report_type, data, 1))
> + goto err_eio;
> +
> + /* Wait for the return of the report. The returned report
> + gets put in session->report_return. */
> + while (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)) {
> + int res;
> +
> + res = wait_event_interruptible_timeout(session->report_queue,
> + !test_bit(HIDP_WAITING_FOR_RETURN, &session->flags),
> + 5*HZ);
> + if (res == 0) {
> + /* timeout */
> + goto err_eio;
> + }
> + if (res < 0) {
> + /* signal */
> + goto err_restartsys;
> + }
> + }
> +
> + skb = session->report_return;
> + if (skb) {
> + len = skb->len < count ? skb->len : count;
> + memcpy(data, skb->data, len);
> +
> + kfree_skb(skb);
> + session->report_return = NULL;
> + } else {
> + /* Device returned a HANDSHAKE, indicating protocol error. */
> + len = -EIO;
> + }
> +
> + clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags);
> + mutex_unlock(&session->report_mutex);
> +
> + return len;
> +
> +err_restartsys:
> + clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags);
> + mutex_unlock(&session->report_mutex);
> + return -ERESTARTSYS;
> +err_eio:
> + clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags);
> + mutex_unlock(&session->report_mutex);
> + return -EIO;
> +}
How about a variable called ret and using that to return len or errno? It
would eliminate code dublication.
--
Ville
^ permalink raw reply
* Where "destroying" function definition is ?
From: Rafał Michalski @ 2010-09-23 12:06 UTC (permalink / raw)
To: linux-bluetooth
Hi
Does anybody know where "destroying" function is ? - pointer to this function is
stored in "destroy" field of agent_request structure, but I can't find
definition
of this function and place where this pointer (non-NULL) is passed.
This pointer is used as below - this is a piece of code from BlueZ
(release 4.71)
- src/agent.c file.
static void agent_request_free(struct agent_request *req, gboolean destroy)
{
if (req->msg)
dbus_message_unref(req->msg);
if (req->call)
dbus_pending_call_unref(req->call);
if (req->agent && req->agent->request)
req->agent->request = NULL;
if (destroy && req->destroy)
req->destroy(req->user_data);
/* where is the function that destroy points to ??? */
g_free(req);
}
Thanks for any help.
Best regards
Rafał Michalski
^ permalink raw reply
* [PATCH] Add test/test-attrib for testing Attribute API
From: Anderson Lizardo @ 2010-09-23 13:35 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Anderson Lizardo
This is an initial version. Watcher registration is not working properly
yet. For now it will list all characteristics and its properties.
---
test/test-attrib | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 93 insertions(+), 0 deletions(-)
create mode 100755 test/test-attrib
diff --git a/test/test-attrib b/test/test-attrib
new file mode 100755
index 0000000..bf3334b
--- /dev/null
+++ b/test/test-attrib
@@ -0,0 +1,93 @@
+#!/usr/bin/python
+import sys
+from optparse import OptionParser
+from xml.etree import ElementTree as ET
+from binascii import hexlify
+
+#import gobject
+import dbus
+#import dbus.service
+#import dbus.mainloop.glib
+
+#TEMPERATURE_UUID = "0000a006-0000-1000-8000-00805f9b34fb"
+
+def command_parse():
+ """Parse command line options."""
+
+ usage = """
+ Usage: %s [options]"""
+ parser = OptionParser(usage=usage)
+ parser.add_option("-i", "--adapter", action="store", type="string",
+ dest="adapter", help="Specify local adapter interface")
+ return parser.parse_args()
+
+# FIXME: implement a better way of discovering GATT services
+def gatt_services(device_path):
+ """Get GATT services for a given device."""
+
+ intro = dbus.Interface(bus.get_object("org.bluez", device_path),
+ "org.freedesktop.DBus.Introspectable")
+ tree = ET.fromstring(intro.Introspect())
+ services = [n.attrib["name"] for n in tree.getiterator("node")]
+ services = filter(lambda x: x.startswith("service"), services)
+ services = map(lambda x: device_path + "/" + x, services)
+
+ return services
+
+def characteristics(service_path):
+ """Get characteristics for a given GATT service."""
+
+ char = dbus.Interface(bus.get_object("org.bluez", service_path),
+ "org.bluez.Characteristic")
+
+ return char.GetCharacteristics()
+
+def dbus_type_str(d):
+ """Convert a D-Bus array to a hexdump."""
+
+ if isinstance(d, dbus.Array):
+ return hexlify("".join([str(x) for x in d]))
+ else:
+ return str(d)
+
+#class Watcher(dbus.service.Object):
+# @dbus.service.method("org.bluez.Watcher", in_signature="oay", out_signature="")
+# def ValueChanged(self, char, newvalue):
+# print "XXX: new value for %s: %s" % (char, newvalue)
+
+if __name__ == "__main__":
+ #dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ bus = dbus.SystemBus()
+ manager = dbus.Interface(bus.get_object("org.bluez", "/"),
+ "org.bluez.Manager")
+
+ (options, args) = command_parse()
+
+ if options.adapter:
+ path = manager.FindAdapter(options.adapter)
+ else:
+ path = manager.DefaultAdapter()
+
+ adapter = dbus.Interface(bus.get_object("org.bluez", path),
+ "org.bluez.Adapter")
+
+ #watcher = None
+
+ for d in adapter.GetProperties()["Devices"]:
+ for s in gatt_services(d):
+ for (path, props) in characteristics(s).iteritems():
+ ret = "Characteristic: %s\nProperties:\n" % path
+ for (k, v) in props.iteritems():
+ # FIXME: Watcher registration not working properly. We
+ # still need to figure out how to properly create the
+ # Object whose ValueChanged() method will be called
+ #if (k, v) == ["UUID", TEMPERATURE_UUID]:
+ # char = dbus.Interface(bus.get_object("org.bluez", s),
+ # "org.bluez.Characteristic")
+ # watcher = Watcher(bus, path)
+ # char.RegisterCharacteristicsWatcher(path)
+ ret += "\t%s: %s\n" % (k, dbus_type_str(v))
+ print ret
+
+ #mainloop = gobject.MainLoop()
+ #mainloop.run()
--
1.7.0.4
^ permalink raw reply related
* Fwd: Bluez simple-agent vs agent
From: Paul Matz @ 2010-09-23 13:36 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <AANLkTikS2-eKiEP2ngqz6rW5JB-+eudtffw9QHykhbqq@mail.gmail.com>
This is a script that will allow us to generate the proper dbus
messages for the pin exchange. I'm not really sure how it works since
I haven't read much about the dbus IPC protocol. Interestingly
enough, it's checked into the linux kernel tree. Could this be the
source to the simple-agent binary in the bluez tar file?
http://git.kernel.org/?p=bluetooth/bluez.git;a=blob_plain;f=test/simple-agent;hb=HEAD
This is the python library that is required by the simple-agent script.
http://dbus.freedesktop.org/releases/dbus-python/
This works on a desktop, but we have an embedded system we are working
on that does not have the python stuff installed.
What I'm trying to figure out is how to create an agent that does what
the python script does.
One other problem I've got is that I can't get bluetoothd to generate
debug output. Starting it with -d, where does it write it's debug
output? /var/log/messages? Doesn't seem to.
-PEM
On Thu, Sep 23, 2010 at 12:46 AM, Viswanathan Sankararam
<developervishwa@gmail.com> wrote:
>
> All,
>
> I am new to bluetooth and bluez. I am having problems pairing my
> bluetooth keyboard to my target that does not have python and so in
> order to pair, I have compiled the agent application. My target has
> Bluez 4.47. But I have not been able to get it to pair. After running
>
> agent -a hci0 1234 00:1F:20:06:47:65
>
> I get asked to enter the pin on the bt keyboard and then it says Agent
> is released. When I list devices using
>
> dbus-send --system --dest=org.bluez --print-reply $BTADAPTER
> org.bluez.Adapter.ListDevices
>
> I see it in the list and when I repeat the command, it disappears.
>
> When I log on to my gentoo machine to pair the bt keyboard, I am able
> to pair with the simple-agent python script but not the agent
> application. The way the agent application behaves is the same as it
> behaves when I use it on the target.My gentoo system uses Bluez 4.69.
> Below is the log when I run bluetoothd with the -d option for the
> simple-agent case and the agent C program case. As you can see, in the
> simple-agent case, the device_probe_drivers gets called but in the
> agent.c case, discover_services_req_exit gets called. This is the
> place where they diverge. Also, I am not sure why the dbus system
> daemon is rejecting a message from agent to org.bluez. I am nto sure
> if its related. I am not able to proceed from here. Has any one faced
> this kind of issue? Please advice me on how to proceed. I would really
> appreciate it.
>
> Thanks
> Vishwa
>
> simple-agent:
>
> Sep 22 22:59:40 Zatoichi
> bluetoothd[7042]:src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> src/device.c:device_create() Creating device
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> src/device.c:bonding_request_new()
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> src/device.c:bonding_request_new() Temporary agent registered for
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at :1.29:/test/agent
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: Authentication requested
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: link_key_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> src/security.c:link_key_request() kernel auth requirements = 0x03
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: pin_code_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> src/device.c:device_request_authentication()
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
> authentication
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: link_key_notify
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
> 0xff new key type 0x00
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
> auth 0xff
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
> 0x00
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
> Releasing agent :1.29, /test/agent
> Sep 22 22:59:47 Zatoichi dbus-daemon: [system] Rejected send message,
> 1 matched rules; type="method_return", sender=":1.29" (uid=0 pid=7790
> comm="/usr/bin/python2.6) interface="(unset)" member="(unset)" error
> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
> comm="/usr/sbin/bluetoothd))
> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> src/device.c:device_probe_drivers() Probe drivers for
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> input/manager.c:hid_device_probe() path
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_ref() 0xb899f6b8: ref=3
> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> input/device.c:input_device_new() Registered interface org.bluez.Input
> on path /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_unref() 0xb899f6b8: ref=2
>
> agent.c:
>
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/device.c:device_create() Creating device
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/device.c:bonding_request_new()
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> src/device.c:bonding_request_new() Temporary agent registered for
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at
> :1.35:/org/bluez/agent_8004
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: Authentication requested
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: link_key_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> src/security.c:link_key_request() kernel auth requirements = 0x03
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: pin_code_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> src/device.c:device_request_authentication()
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
> authentication
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: link_key_notify
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
> 0xff new key type 0x00
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
> auth 0xff
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
> 0x00
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
> Releasing agent :1.35, /org/bluez/agent_8004
> Sep 23 00:14:38 Zatoichi dbus-daemon: [system] Rejected send message,
> 1 matched rules; type="method_return", sender=":1.35" (uid=1000
> pid=8004 comm="./agent) interface="(unset)" member="(unset)" error
> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
> comm="/usr/sbin/bluetoothd))
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/device.c:discover_services_req_exit() DiscoverServices requestor
> exited
> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_unref() 0xb899f6b8: ref=1
> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> src/adapter.c:adapter_remove_connection() Removing temporary device
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> src/device.c:device_remove() Removing device
> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> src/device.c:btd_device_unref() 0xb899f6b8: ref=0
> Sep 23 00:14:40 Zatoichi bluetoothd[7042]: src/device.c:device_free() 0xb899f6b8
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Re: [PATCH v4 2/2] Bluetooth: hidp: Add support for hidraw HIDIOCGFEATURE and HIDIOCSFEATURE
From: Alan Ott @ 2010-09-23 14:16 UTC (permalink / raw)
To: Ville Tervo
Cc: Jiri Kosina, Stefan Achatz, Antonio Ospite, Alexey Dobriyan,
Tejun Heo, Alan Stern, Greg Kroah-Hartman, Marcel Holtmann,
Stephane Chatty, Michael Poole, David S. Miller, Bastien Nocera,
Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <20100923115108.GC2379@null>
On Sep 23, 2010, at 7:51 AM, Ville Tervo wrote:
> Hi Alan,
>
> One comment.
>
> How about a variable called ret and using that to return len or
> errno? It
> would eliminate code dublication.
>
Hi Ville,
Where specifically? In which function? I've gone through it a couple
of times and failed to find return statements which are superfluous.
Maybe I'm missing something fundamental?
Alan.
^ permalink raw reply
* RE: Where "destroying" function definition is ?
From: Waldemar.Rymarkiewicz @ 2010-09-23 14:32 UTC (permalink / raw)
To: michalski.raf, linux-bluetooth
In-Reply-To: <AANLkTinyQbC30-11CgVtjAhcnbykatBFJJ7rxC4v019L@mail.gmail.com>
Hi Robert,
>-----Original Message-----
>From: linux-bluetooth-owner@vger.kernel.org
>[mailto:linux-bluetooth-owner@vger.kernel.org] On Behalf Of
>Rafał Michalski
>Sent: Thursday, September 23, 2010 2:07 PM
>To: linux-bluetooth@vger.kernel.org
>Subject: Where "destroying" function definition is ?
>
>Hi
>
>Does anybody know where "destroying" function is ? - pointer
>to this function is stored in "destroy" field of agent_request
>structure, but I can't find definition of this function and
>place where this pointer (non-NULL) is passed.
>This pointer is used as below - this is a piece of code from
>BlueZ (release 4.71)
>- src/agent.c file.
>
>static void agent_request_free(struct agent_request *req,
>gboolean destroy) {
> if (req->msg)
> dbus_message_unref(req->msg);
> if (req->call)
> dbus_pending_call_unref(req->call);
> if (req->agent && req->agent->request)
> req->agent->request = NULL;
> if (destroy && req->destroy)
> req->destroy(req->user_data);
> /* where is the function that destroy
>points to ??? */
> g_free(req);
>}
>
>Thanks for any help.
>
>Best regards
>Rafał Michalski
I'm not pretty sure but this feature seems be not used :)
In device.c function device_request_authentication() calls agent_request_* with NULL as the last param which is fact is a pointer to "distroying" function.
Regards,
/Waldek
^ permalink raw reply
* [PATCH] Fix problem with EIR data when setting the name
From: Lukasz Pawlik @ 2010-09-23 14:53 UTC (permalink / raw)
To: linux-bluetooth
[-- Attachment #1: Type: text/plain, Size: 1 bytes --]
[-- Attachment #2: 0001-Fix-problem-with-EIR-data-when-setting-the-name.patch --]
[-- Type: text/x-patch, Size: 814 bytes --]
From 32c3108cc8a0734f3ee1187e9326dc7b84a332d7 Mon Sep 17 00:00:00 2001
From: Lukasz Pawlik <lucas.pawlik@gmail.com>
Date: Thu, 23 Sep 2010 16:27:39 +0200
Subject: [PATCH] Fix problem with EIR data when setting the name
Previously changed BT fiendly name was not reflected until BT adapter reboot.
This patch fix this problem. Now changed name is reflected without need of
rebooting adapter.
---
src/adapter.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/src/adapter.c b/src/adapter.c
index 9b638cf..0e9be5f 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1031,6 +1031,7 @@ static DBusMessage *set_name(DBusConnection *conn, DBusMessage *msg,
return failed_strerror(msg, err);
adapter->name_stored = TRUE;
+ update_ext_inquiry_response(adapter);
}
done:
--
1.7.0.4
^ permalink raw reply related
* Pull request: git://git.infradead.org/users/cktakahasi/bluez.git for-upstream
From: Claudio Takahasi @ 2010-09-23 16:19 UTC (permalink / raw)
To: BlueZ development
The following changes since commit e7b1d878d15c0eb1bc0109c19cbdf5f84a29394d:
Fix coding style issues in attribute client code (2010-09-22 16:33:15 -0400)
are available in the git repository at:
git://git.infradead.org/users/cktakahasi/bluez.git for-upstream
Claudio Takahasi (23):
Add LE start and stop scanning
Remove RSSI field from the advertising report event
Decoding the RSSI parameter from the advertising report event
Send Discovering property "FALSE" when the interleave finishes
Add length argument on hciops start discovery function
Stop inquiry using the length parameter
Fix remote name resolution for interleave discovery
Add Write LE host supported function
Set the LE host supported and disable simultaneous LE and BR/EDR flags
Add extended feature mask constants definition
Read the local extended features
Stop LE scanning when discovery is suspended
Rename hciops {start, stop}_discovery to {start, stop}_inquiry
Don't enter on interleave mode if there isn't active sessions
Code cleanup: improving inquiry logic
Clear the remote device found list in the state transition
Fix periodic inquiry signals
Fixing DeviceDisappeared signal
Postpone discovery if still resolving names
Add adapter discovery type function
Do not send another Discovering TRUE signal if still resolving names
Forcing periodic inquiry exit
Fix interleave scanning
Vinicius Costa Gomes (1):
Add BR/EDR LE interleaved discovery
lib/hci.c | 29 +++++
lib/hci.h | 17 +++-
lib/hci_lib.h | 1 +
plugins/hciops.c | 53 +++++++++-
src/adapter.c | 302 +++++++++++++++++++++++++++++++++++++-----------------
src/adapter.h | 37 ++++---
src/dbus-hci.c | 45 +++++++--
src/dbus-hci.h | 1 +
src/security.c | 133 ++++++++++++++----------
9 files changed, 436 insertions(+), 182 deletions(-)
Implements interleave discovery for dual mode adapters(LE and BR/EDR).
Now, the behavior is:
1) for single mode: LE scanning for 5.12 seconds every DiscoverSchedulerInterval
2) for BR/EDR only: still the same
3) for dual mode: inquiry(5.12) sec, LE scanning(5.12) sec, resolve
names. The next discovery interval is controlled by
DiscoverSchedulerInterval.
Signals still the same, the idea is try to hide from the users/apps
the discovery internals. No mater the adapter type(LE only, BR/EDR or
Dual Mode)
PropertyChanged("Discovering", TRUE/FALSE) is sent to notify when a
new discovery "session" starts/finishes. Found LE devices are reported
using DeviceFound() signals.
Regards,
Claudio
--
--
Claudio Takahasi
Instituto Nokia de Tecnologia
Recife - Pernambuco - Brasil
+55 81 30879999
^ permalink raw reply
* Re: [PATCH v4 0/2] Get and Set Feature Reports on HIDRAW (USB and Bluetooth)
From: Ville Tervo @ 2010-09-23 16:25 UTC (permalink / raw)
To: ext Alan Ott
Cc: Jiri Kosina, Stefan Achatz, Antonio Ospite, Alexey Dobriyan,
Tejun Heo, Alan Stern, Greg Kroah-Hartman, Marcel Holtmann,
Stephane Chatty, Michael Poole, David S. Miller, Bastien Nocera,
Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <1281990059-3562-1-git-send-email-alan@signal11.us>
Hi Alan,
On Mon, Aug 16, 2010 at 10:20:57PM +0200, ext Alan Ott wrote:
> This is version 4. Built against 2.6.35+ revision 320b2b8de12698 .
I gave a try to to this patch using your test tool [1] and very old BT
keyboard. I don't have anything else ATM to test with. Is there some BT hid
devices which support setting and getting features?
Shouldn't HIDIOCSFEATURE's bt version have similar wait as HIDIOCGFEATURE to
get report status back from the device? or is there even any status coming back
in successful case? Sorry I'm a newbie with HID and trying to understand how
this should work.
Now it just returns num of send bytes even if the remote device returned some
error. Which one is the expected behavior?
Other problem is that Get report is getting now handshake from set report.
2010-09-23 17:55:46.680612 < ACL data: handle 38 flags 0x02 dlen 9
L2CAP(d): cid 0x008b len 5 [psm 17]
HIDP: Set report: Feature report
0000: 09 ff ff ff ....
2010-09-23 17:55:46.680653 < ACL data: handle 38 flags 0x02 dlen 6
L2CAP(d): cid 0x008b len 2 [psm 17]
HIDP: Get report: Feature report
0000: 09 .
2010-09-23 17:55:46.697577 > HCI Event: Number of Completed Packets (0x13) plen 5
handle 38 packets 1
2010-09-23 17:55:46.698579 > HCI Event: Number of Completed Packets (0x13) plen 5
handle 38 packets 1
2010-09-23 17:55:46.776827 > ACL data: handle 38 flags 0x02 dlen 5
L2CAP(d): cid 0x0040 len 1 [psm 17]
HIDP: Handshake: Invalid parameter
2010-09-23 17:55:46.777069 < ACL data: handle 38 flags 0x02 dlen 7
L2CAP(d): cid 0x008b len 3 [psm 17]
HIDP: Data: Output report
0000: 01 77 .w
2010-09-23 17:55:46.797577 > HCI Event: Number of Completed Packets (0x13) plen 5
handle 38 packets 1
2010-09-23 17:55:46.816826 > ACL data: handle 38 flags 0x02 dlen 5
L2CAP(d): cid 0x0040 len 1 [psm 17]
HIDP: Handshake: Invalid parameter
2010-09-23 17:55:46.856828 > ACL data: handle 38 flags 0x02 dlen 5
L2CAP(d): cid 0x0040 len 1 [psm 17]
HIDP: Handshake: Unsupported request
[1] http://lkml.org/lkml/2010/6/17/414
--
Ville the HID newbie
^ permalink raw reply
* Re: [PATCH v4 0/2] Get and Set Feature Reports on HIDRAW (USB and Bluetooth)
From: Ping Cheng @ 2010-09-23 17:07 UTC (permalink / raw)
To: Ville Tervo, Przemysław Firszt
Cc: ext Alan Ott, Jiri Kosina, Stefan Achatz, Antonio Ospite,
Alexey Dobriyan, Tejun Heo, Alan Stern, Greg Kroah-Hartman,
Marcel Holtmann, Stephane Chatty, Michael Poole, David S. Miller,
Bastien Nocera, Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <20100923162521.GA15641@null>
On Thu, Sep 23, 2010 at 9:25 AM, Ville Tervo <ville.tervo@nokia.com> wrote:
> Hi Alan,
>
> On Mon, Aug 16, 2010 at 10:20:57PM +0200, ext Alan Ott wrote:
>> This is version 4. Built against 2.6.35+ revision 320b2b8de12698 .
>
> I gave a try to to this patch using your test tool [1] and very old BT
> keyboard. I don't have anything else ATM to test with. Is there some BT hid
> devices which support setting and getting features?
As far as I know Wacom BT devices (Graphire and Intuos4) need to get
and set features.
Przemo,
Do you have time to test the patchset with your Graphire BT and
provide your result here?
Thank you,
Ping
^ permalink raw reply
* Re: Pull request: git://git.infradead.org/users/cktakahasi/bluez.git for-upstream
From: Luiz Augusto von Dentz @ 2010-09-23 17:55 UTC (permalink / raw)
To: Claudio Takahasi; +Cc: BlueZ development
In-Reply-To: <AANLkTindKFz1fx9cJMu9aSpehG5LaUHHO7TkgWqMzqj=@mail.gmail.com>
Hi Claudio,
On Thu, Sep 23, 2010 at 7:19 PM, Claudio Takahasi
<claudio.takahasi@openbossa.org> wrote:
> Implements interleave discovery for dual mode adapters(LE and BR/EDR).
> Now, the behavior is:
> 1) for single mode: LE scanning for 5.12 seconds every DiscoverSchedulerInterval
> 2) for BR/EDR only: still the same
> 3) for dual mode: inquiry(5.12) sec, LE scanning(5.12) sec, resolve
> names. The next discovery interval is controlled by
> DiscoverSchedulerInterval.
Is that enough time to discover devices around? I remember tweaking
the interval so that it doesn't cause too much noise/too many new
devices found or disappearing in each round, so at least to BR/EDR
5.12 sec. sounds too little time, also does LE needs name resolving
too?
--
Luiz Augusto von Dentz
Computer Engineer
^ permalink raw reply
* Re: Pull request: git://git.infradead.org/users/cktakahasi/bluez.git for-upstream
From: Claudio Takahasi @ 2010-09-23 18:07 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: BlueZ development
In-Reply-To: <AANLkTinAd=8HhDWBwgikS36zni6arb8WP9iXAZpqJ0Lk@mail.gmail.com>
On Thu, Sep 23, 2010 at 2:55 PM, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
> Hi Claudio,
>
> On Thu, Sep 23, 2010 at 7:19 PM, Claudio Takahasi
> <claudio.takahasi@openbossa.org> wrote:
>> Implements interleave discovery for dual mode adapters(LE and BR/EDR).
>> Now, the behavior is:
>> 1) for single mode: LE scanning for 5.12 seconds every DiscoverSchedulerInterval
>> 2) for BR/EDR only: still the same
>> 3) for dual mode: inquiry(5.12) sec, LE scanning(5.12) sec, resolve
>> names. The next discovery interval is controlled by
>> DiscoverSchedulerInterval.
>
> Is that enough time to discover devices around? I remember tweaking
> the interval so that it doesn't cause too much noise/too many new
> devices found or disappearing in each round, so at least to BR/EDR
> 5.12 sec. sounds too little time, also does LE needs name resolving
> too?
>
> --
> Luiz Augusto von Dentz
> Computer Engineer
>
Hi Luiz,
5.12sec is based on the SPEC recommendation(core page 1746), we can
change it to 10.24s or add a config option.
For LE, there is a characteristic called "Device Name" UUID 0x2A00,
this piece is missing.
Regards,
Claudio
--
--
Claudio Takahasi
Instituto Nokia de Tecnologia
Recife - Pernambuco - Brasil
+55 81 30879999
^ permalink raw reply
* Re: Bluez simple-agent vs agent
From: Viswanathan Sankararam @ 2010-09-23 18:50 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <AANLkTikAHAKe2WDMuAKEvCXh6jxGXFYYhfWPJkCGXUSp@mail.gmail.com>
Anyone, any ideas?
On Thu, Sep 23, 2010 at 6:36 AM, Paul Matz <paul@osterhoutgroup.com> wrote:
> This is a script that will allow us to generate the proper dbus
> messages for the pin exchange. I'm not really sure how it works since
> I haven't read much about the dbus IPC protocol. Interestingly
> enough, it's checked into the linux kernel tree. Could this be the
> source to the simple-agent binary in the bluez tar file?
> http://git.kernel.org/?p=bluetooth/bluez.git;a=blob_plain;f=test/simple-agent;hb=HEAD
>
> This is the python library that is required by the simple-agent script.
> http://dbus.freedesktop.org/releases/dbus-python/
>
> This works on a desktop, but we have an embedded system we are working
> on that does not have the python stuff installed.
> What I'm trying to figure out is how to create an agent that does what
> the python script does.
> One other problem I've got is that I can't get bluetoothd to generate
> debug output. Starting it with -d, where does it write it's debug
> output? /var/log/messages? Doesn't seem to.
>
> -PEM
>
> On Thu, Sep 23, 2010 at 12:46 AM, Viswanathan Sankararam
> <developervishwa@gmail.com> wrote:
>>
>> All,
>>
>> I am new to bluetooth and bluez. I am having problems pairing my
>> bluetooth keyboard to my target that does not have python and so in
>> order to pair, I have compiled the agent application. My target has
>> Bluez 4.47. But I have not been able to get it to pair. After running
>>
>> agent -a hci0 1234 00:1F:20:06:47:65
>>
>> I get asked to enter the pin on the bt keyboard and then it says Agent
>> is released. When I list devices using
>>
>> dbus-send --system --dest=org.bluez --print-reply $BTADAPTER
>> org.bluez.Adapter.ListDevices
>>
>> I see it in the list and when I repeat the command, it disappears.
>>
>> When I log on to my gentoo machine to pair the bt keyboard, I am able
>> to pair with the simple-agent python script but not the agent
>> application. The way the agent application behaves is the same as it
>> behaves when I use it on the target.My gentoo system uses Bluez 4.69.
>> Below is the log when I run bluetoothd with the -d option for the
>> simple-agent case and the agent C program case. As you can see, in the
>> simple-agent case, the device_probe_drivers gets called but in the
>> agent.c case, discover_services_req_exit gets called. This is the
>> place where they diverge. Also, I am not sure why the dbus system
>> daemon is rejecting a message from agent to org.bluez. I am nto sure
>> if its related. I am not able to proceed from here. Has any one faced
>> this kind of issue? Please advice me on how to proceed. I would really
>> appreciate it.
>>
>> Thanks
>> Vishwa
>>
>> simple-agent:
>>
>> Sep 22 22:59:40 Zatoichi
>> bluetoothd[7042]:src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>> src/device.c:device_create() Creating device
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>> src/device.c:bonding_request_new()
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>> src/device.c:bonding_request_new() Temporary agent registered for
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at :1.29:/test/agent
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: Authentication requested
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: link_key_request
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>> src/security.c:link_key_request() kernel auth requirements = 0x03
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: pin_code_request
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>> src/device.c:device_request_authentication()
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
>> authentication
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: link_key_notify
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
>> 0xff new key type 0x00
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
>> auth 0xff
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
>> 0x00
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
>> Releasing agent :1.29, /test/agent
>> Sep 22 22:59:47 Zatoichi dbus-daemon: [system] Rejected send message,
>> 1 matched rules; type="method_return", sender=":1.29" (uid=0 pid=7790
>> comm="/usr/bin/python2.6) interface="(unset)" member="(unset)" error
>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
>> comm="/usr/sbin/bluetoothd))
>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>> src/device.c:device_probe_drivers() Probe drivers for
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>> input/manager.c:hid_device_probe() path
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_ref() 0xb899f6b8: ref=3
>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>> input/device.c:input_device_new() Registered interface org.bluez.Input
>> on path /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_unref() 0xb899f6b8: ref=2
>>
>> agent.c:
>>
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/device.c:device_create() Creating device
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/device.c:bonding_request_new()
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>> src/device.c:bonding_request_new() Temporary agent registered for
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at
>> :1.35:/org/bluez/agent_8004
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: Authentication requested
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: link_key_request
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>> src/security.c:link_key_request() kernel auth requirements = 0x03
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: pin_code_request
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>> src/device.c:device_request_authentication()
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
>> authentication
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: link_key_notify
>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
>> 0xff new key type 0x00
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
>> auth 0xff
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
>> 0x00
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
>> Releasing agent :1.35, /org/bluez/agent_8004
>> Sep 23 00:14:38 Zatoichi dbus-daemon: [system] Rejected send message,
>> 1 matched rules; type="method_return", sender=":1.35" (uid=1000
>> pid=8004 comm="./agent) interface="(unset)" member="(unset)" error
>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
>> comm="/usr/sbin/bluetoothd))
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/device.c:discover_services_req_exit() DiscoverServices requestor
>> exited
>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_unref() 0xb899f6b8: ref=1
>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>> src/adapter.c:adapter_remove_connection() Removing temporary device
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>> src/device.c:device_remove() Removing device
>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>> src/device.c:btd_device_unref() 0xb899f6b8: ref=0
>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]: src/device.c:device_free() 0xb899f6b8
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* Re: [PATCH v4 0/2] Get and Set Feature Reports on HIDRAW (USB and Bluetooth)
From: Przemo Firszt @ 2010-09-23 20:16 UTC (permalink / raw)
To: Ping Cheng
Cc: Ville Tervo, ext Alan Ott, Jiri Kosina, Stefan Achatz,
Antonio Ospite, Alexey Dobriyan, Tejun Heo, Alan Stern,
Greg Kroah-Hartman, Marcel Holtmann, Stephane Chatty,
Michael Poole, David S. Miller, Bastien Nocera, Eric Dumazet,
linux-input@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-usb@vger.kernel.org, linux-bluetooth@vger.kernel.org,
netdev@vger.kernel.org
In-Reply-To: <AANLkTinJXYbZh_MvG81iJMu84LKBOKL3s-Gbfe77Vq7q@mail.gmail.com>
Dnia 2010-09-23, czw o godzinie 10:07 -0700, Ping Cheng pisze:
> On Thu, Sep 23, 2010 at 9:25 AM, Ville Tervo <ville.tervo@nokia.com> wrote:
> > Hi Alan,
> >
> > On Mon, Aug 16, 2010 at 10:20:57PM +0200, ext Alan Ott wrote:
> >> This is version 4. Built against 2.6.35+ revision 320b2b8de12698 .
> >
> > I gave a try to to this patch using your test tool [1] and very old BT
> > keyboard. I don't have anything else ATM to test with. Is there some BT hid
> > devices which support setting and getting features?
>
> As far as I know Wacom BT devices (Graphire and Intuos4) need to get
> and set features.
>
> Przemo,
>
> Do you have time to test the patchset with your Graphire BT and
> provide your result here?
Hi Ping,
Speed switching works as expected.
Tested on HP NC4200 with internal and external bluetooth modules:
Linux pldmachine 2.6.35-07788-g320b2b8-dirty #15 Thu Sep 23 19:55:03 IST
2010 i686 Intel(R)_Pentium(R)_M_processor_1.86GHz PLD Linux
ID 03f0:011d Hewlett-Packard Integrated Bluetooth Module
ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
and Wacom Pen Tablet CTE-630BT
Tested-by: Przemo Firszt <przemo@firszt.eu>
--
Regards,
Przemo Firszt <przemo@firszt.eu>
^ permalink raw reply
* Re: [PATCH v4 0/2] Get and Set Feature Reports on HIDRAW (USB and Bluetooth)
From: Alan Ott @ 2010-09-23 23:40 UTC (permalink / raw)
To: Ville Tervo
Cc: Jiri Kosina, Stefan Achatz, Antonio Ospite, Alexey Dobriyan,
Tejun Heo, Alan Stern, Greg Kroah-Hartman, Marcel Holtmann,
Stephane Chatty, Michael Poole, David S. Miller, Bastien Nocera,
Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <20100923162521.GA15641@null>
On 09/23/2010 12:25 PM, Ville Tervo wrote:
> Hi Alan,
>
> On Mon, Aug 16, 2010 at 10:20:57PM +0200, ext Alan Ott wrote:
>
>> This is version 4. Built against 2.6.35+ revision 320b2b8de12698 .
>>
>
> I gave a try to to this patch using your test tool [1] and very old BT
> keyboard. I don't have anything else ATM to test with. Is there some BT hid
> devices which support setting and getting features?
>
A keyboard is the only BT device I've used which has feature reports.
> Shouldn't HIDIOCSFEATURE's bt version have similar wait as HIDIOCGFEATURE to
> get report status back from the device? or is there even any status coming back
> in successful case? Sorry I'm a newbie with HID and trying to understand how
> this should work.
>
> Now it just returns num of send bytes even if the remote device returned some
> error. Which one is the expected behavior?
>
I made it function the same as the existing hidp_output_raw_report().
Nothing in net/bluetooth/hidp/core.c is acked at all. I'm not sure of
the reasons.
Jiri, Marcel, any ideas?
> Other problem is that Get report is getting now handshake from set report.
>
>
>
Yeah, that makes sense. I hadn't considered the set_report failing. In
that case, I guess it means we _must_ wait for an ack for sent packets.
Anyone else agree or disagree?
Alan.
^ permalink raw reply
* Re: Bluez simple-agent vs agent
From: Viswanathan Sankararam @ 2010-09-24 1:25 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <AANLkTi=9o+9KFNTGcYBZd0z+kYVwBj-Z1LxwdPsjTSfw@mail.gmail.com>
All, the following is the log I get on my target when I try to pair.
Any help with this is apprecaited. I see a message saying
HCIGETAUTHINFO failed. Can someone explain what this is and if it may
the reason for the issue? It seems to happen when the function
hcid_dbus_link_key_notify executes.
Thanks
Vishwa
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_get_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_create_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Creating device
/org/bluez/1071/hci0/dev_00_1F_20_06_47_65
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
btd_device_ref(0x8052ae8): ref=1
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
/org/bluez/1071/hci0/dev_00_1F_20_06_47_65: requesting bonding
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Temporary agent
registered for /org/bluez/1071/hci0/dev_00_1F_20_06_47_65 at
:1.4:/org/bluez/agent_1101
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_get_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: link_key_request
(sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: kernel auth
requirements = 0x00
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: pin_code_request
(sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_get_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
/org/bluez/1071/hci0/dev_00_1F_20_06_47_65: requesting agent
authentication
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: link_key_notify
(sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_get_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: HCIGETAUTHINFO
failed: Invalid argument (22)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: local auth 0xff and
remote auth 0xff
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: storing link key of type 0x00
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
hcid_dbus_bonding_process_complete: status=00
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
adapter_get_device(00:1F:20:06:47:65)
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Releasing agent
:1.4, /org/bluez/agent_1101
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: DiscoverServices
requestor exited
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Removing temporary
device /org/bluez/1071/hci0/dev_00_1F_20_06_47_65
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Removing device
/org/bluez/1071/hci0/dev_00_1F_20_06_47_65
I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
btd_device_unref(0x8052ae8): ref=0
I//system/bin/bluetoothd( 1070): bluetoothd[1071]: device_free(0x8052ae8)
On Thu, Sep 23, 2010 at 11:50 AM, Viswanathan Sankararam
<developervishwa@gmail.com> wrote:
> Anyone, any ideas?
>
> On Thu, Sep 23, 2010 at 6:36 AM, Paul Matz <paul@osterhoutgroup.com> wrote:
>> This is a script that will allow us to generate the proper dbus
>> messages for the pin exchange. I'm not really sure how it works since
>> I haven't read much about the dbus IPC protocol. Interestingly
>> enough, it's checked into the linux kernel tree. Could this be the
>> source to the simple-agent binary in the bluez tar file?
>> http://git.kernel.org/?p=bluetooth/bluez.git;a=blob_plain;f=test/simple-agent;hb=HEAD
>>
>> This is the python library that is required by the simple-agent script.
>> http://dbus.freedesktop.org/releases/dbus-python/
>>
>> This works on a desktop, but we have an embedded system we are working
>> on that does not have the python stuff installed.
>> What I'm trying to figure out is how to create an agent that does what
>> the python script does.
>> One other problem I've got is that I can't get bluetoothd to generate
>> debug output. Starting it with -d, where does it write it's debug
>> output? /var/log/messages? Doesn't seem to.
>>
>> -PEM
>>
>> On Thu, Sep 23, 2010 at 12:46 AM, Viswanathan Sankararam
>> <developervishwa@gmail.com> wrote:
>>>
>>> All,
>>>
>>> I am new to bluetooth and bluez. I am having problems pairing my
>>> bluetooth keyboard to my target that does not have python and so in
>>> order to pair, I have compiled the agent application. My target has
>>> Bluez 4.47. But I have not been able to get it to pair. After running
>>>
>>> agent -a hci0 1234 00:1F:20:06:47:65
>>>
>>> I get asked to enter the pin on the bt keyboard and then it says Agent
>>> is released. When I list devices using
>>>
>>> dbus-send --system --dest=org.bluez --print-reply $BTADAPTER
>>> org.bluez.Adapter.ListDevices
>>>
>>> I see it in the list and when I repeat the command, it disappears.
>>>
>>> When I log on to my gentoo machine to pair the bt keyboard, I am able
>>> to pair with the simple-agent python script but not the agent
>>> application. The way the agent application behaves is the same as it
>>> behaves when I use it on the target.My gentoo system uses Bluez 4.69.
>>> Below is the log when I run bluetoothd with the -d option for the
>>> simple-agent case and the agent C program case. As you can see, in the
>>> simple-agent case, the device_probe_drivers gets called but in the
>>> agent.c case, discover_services_req_exit gets called. This is the
>>> place where they diverge. Also, I am not sure why the dbus system
>>> daemon is rejecting a message from agent to org.bluez. I am nto sure
>>> if its related. I am not able to proceed from here. Has any one faced
>>> this kind of issue? Please advice me on how to proceed. I would really
>>> appreciate it.
>>>
>>> Thanks
>>> Vishwa
>>>
>>> simple-agent:
>>>
>>> Sep 22 22:59:40 Zatoichi
>>> bluetoothd[7042]:src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_create() Creating device
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
>>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:bonding_request_new()
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
>>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:bonding_request_new() Temporary agent registered for
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at :1.29:/test/agent
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: Authentication requested
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: link_key_request
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>>> src/security.c:link_key_request() kernel auth requirements = 0x03
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: pin_code_request
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_request_authentication()
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
>>> authentication
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: link_key_notify
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
>>> 0xff new key type 0x00
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
>>> auth 0xff
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
>>> 0x00
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
>>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
>>> Releasing agent :1.29, /test/agent
>>> Sep 22 22:59:47 Zatoichi dbus-daemon: [system] Rejected send message,
>>> 1 matched rules; type="method_return", sender=":1.29" (uid=0 pid=7790
>>> comm="/usr/bin/python2.6) interface="(unset)" member="(unset)" error
>>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
>>> comm="/usr/sbin/bluetoothd))
>>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_probe_drivers() Probe drivers for
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>>> input/manager.c:hid_device_probe() path
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_ref() 0xb899f6b8: ref=3
>>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>>> input/device.c:input_device_new() Registered interface org.bluez.Input
>>> on path /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_unref() 0xb899f6b8: ref=2
>>>
>>> agent.c:
>>>
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_create() Creating device
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/device.c:bonding_request_new()
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
>>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
>>> src/device.c:bonding_request_new() Temporary agent registered for
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at
>>> :1.35:/org/bluez/agent_8004
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: Authentication requested
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: link_key_request
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>>> src/security.c:link_key_request() kernel auth requirements = 0x03
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: pin_code_request
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_request_authentication()
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
>>> authentication
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: link_key_notify
>>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
>>> 0xff new key type 0x00
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
>>> auth 0xff
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
>>> 0x00
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
>>> Releasing agent :1.35, /org/bluez/agent_8004
>>> Sep 23 00:14:38 Zatoichi dbus-daemon: [system] Rejected send message,
>>> 1 matched rules; type="method_return", sender=":1.35" (uid=1000
>>> pid=8004 comm="./agent) interface="(unset)" member="(unset)" error
>>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
>>> comm="/usr/sbin/bluetoothd))
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/device.c:discover_services_req_exit() DiscoverServices requestor
>>> exited
>>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_unref() 0xb899f6b8: ref=1
>>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>>> src/adapter.c:adapter_remove_connection() Removing temporary device
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:device_remove() Removing device
>>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
>>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
>>> src/device.c:btd_device_unref() 0xb899f6b8: ref=0
>>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]: src/device.c:device_free() 0xb899f6b8
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
^ permalink raw reply
* Re: Bluez simple-agent vs agent
From: Ville Tervo @ 2010-09-24 8:16 UTC (permalink / raw)
To: ext Viswanathan Sankararam; +Cc: linux-bluetooth@vger.kernel.org
In-Reply-To: <AANLkTikE91qpOgsio9WRaunaAyX3JDWXX0Oz4DvUvspU@mail.gmail.com>
Hi,
Are you using some really old kernel?
On Fri, Sep 24, 2010 at 03:25:12AM +0200, ext Viswanathan Sankararam wrote:
> All, the following is the log I get on my target when I try to pair.
> Any help with this is apprecaited. I see a message saying
> HCIGETAUTHINFO failed. Can someone explain what this is and if it may
> the reason for the issue? It seems to happen when the function
> hcid_dbus_link_key_notify executes.
>
> Thanks
> Vishwa
>
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_get_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_create_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Creating device
> /org/bluez/1071/hci0/dev_00_1F_20_06_47_65
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> btd_device_ref(0x8052ae8): ref=1
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> /org/bluez/1071/hci0/dev_00_1F_20_06_47_65: requesting bonding
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Temporary agent
> registered for /org/bluez/1071/hci0/dev_00_1F_20_06_47_65 at
> :1.4:/org/bluez/agent_1101
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_get_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: link_key_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: kernel auth
> requirements = 0x00
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: pin_code_request
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_get_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> /org/bluez/1071/hci0/dev_00_1F_20_06_47_65: requesting agent
> authentication
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: link_key_notify
> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_get_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: HCIGETAUTHINFO
> failed: Invalid argument (22)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: local auth 0xff and
> remote auth 0xff
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: storing link key of type 0x00
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> hcid_dbus_bonding_process_complete: status=00
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> adapter_get_device(00:1F:20:06:47:65)
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Releasing agent
> :1.4, /org/bluez/agent_1101
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: DiscoverServices
> requestor exited
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Removing temporary
> device /org/bluez/1071/hci0/dev_00_1F_20_06_47_65
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: Removing device
> /org/bluez/1071/hci0/dev_00_1F_20_06_47_65
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]:
> btd_device_unref(0x8052ae8): ref=0
> I//system/bin/bluetoothd( 1070): bluetoothd[1071]: device_free(0x8052ae8)
>
> On Thu, Sep 23, 2010 at 11:50 AM, Viswanathan Sankararam
> <developervishwa@gmail.com> wrote:
> > Anyone, any ideas?
> >
> > On Thu, Sep 23, 2010 at 6:36 AM, Paul Matz <paul@osterhoutgroup.com> wrote:
> >> This is a script that will allow us to generate the proper dbus
> >> messages for the pin exchange. I'm not really sure how it works since
> >> I haven't read much about the dbus IPC protocol. Interestingly
> >> enough, it's checked into the linux kernel tree. Could this be the
> >> source to the simple-agent binary in the bluez tar file?
> >> http://git.kernel.org/?p=bluetooth/bluez.git;a=blob_plain;f=test/simple-agent;hb=HEAD
> >>
> >> This is the python library that is required by the simple-agent script.
> >> http://dbus.freedesktop.org/releases/dbus-python/
> >>
> >> This works on a desktop, but we have an embedded system we are working
> >> on that does not have the python stuff installed.
> >> What I'm trying to figure out is how to create an agent that does what
> >> the python script does.
> >> One other problem I've got is that I can't get bluetoothd to generate
> >> debug output. Starting it with -d, where does it write it's debug
> >> output? /var/log/messages? Doesn't seem to.
> >>
> >> -PEM
> >>
> >> On Thu, Sep 23, 2010 at 12:46 AM, Viswanathan Sankararam
> >> <developervishwa@gmail.com> wrote:
> >>>
> >>> All,
> >>>
> >>> I am new to bluetooth and bluez. I am having problems pairing my
> >>> bluetooth keyboard to my target that does not have python and so in
> >>> order to pair, I have compiled the agent application. My target has
> >>> Bluez 4.47. But I have not been able to get it to pair. After running
> >>>
> >>> agent -a hci0 1234 00:1F:20:06:47:65
> >>>
> >>> I get asked to enter the pin on the bt keyboard and then it says Agent
> >>> is released. When I list devices using
> >>>
> >>> dbus-send --system --dest=org.bluez --print-reply $BTADAPTER
> >>> org.bluez.Adapter.ListDevices
> >>>
> >>> I see it in the list and when I repeat the command, it disappears.
> >>>
> >>> When I log on to my gentoo machine to pair the bt keyboard, I am able
> >>> to pair with the simple-agent python script but not the agent
> >>> application. The way the agent application behaves is the same as it
> >>> behaves when I use it on the target.My gentoo system uses Bluez 4.69.
> >>> Below is the log when I run bluetoothd with the -d option for the
> >>> simple-agent case and the agent C program case. As you can see, in the
> >>> simple-agent case, the device_probe_drivers gets called but in the
> >>> agent.c case, discover_services_req_exit gets called. This is the
> >>> place where they diverge. Also, I am not sure why the dbus system
> >>> daemon is rejecting a message from agent to org.bluez. I am nto sure
> >>> if its related. I am not able to proceed from here. Has any one faced
> >>> this kind of issue? Please advice me on how to proceed. I would really
> >>> appreciate it.
> >>>
> >>> Thanks
> >>> Vishwa
> >>>
> >>> simple-agent:
> >>>
> >>> Sep 22 22:59:40 Zatoichi
> >>> bluetoothd[7042]:src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_create() Creating device
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
> >>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:bonding_request_new()
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
> >>> Sep 22 22:59:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:bonding_request_new() Temporary agent registered for
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at :1.29:/test/agent
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: Authentication requested
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: link_key_request
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> >>> src/security.c:link_key_request() kernel auth requirements = 0x03
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]: pin_code_request
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:41 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_request_authentication()
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
> >>> authentication
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: link_key_notify
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
> >>> 0xff new key type 0x00
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
> >>> auth 0xff
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
> >>> 0x00
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
> >>> Sep 22 22:59:47 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
> >>> Releasing agent :1.29, /test/agent
> >>> Sep 22 22:59:47 Zatoichi dbus-daemon: [system] Rejected send message,
> >>> 1 matched rules; type="method_return", sender=":1.29" (uid=0 pid=7790
> >>> comm="/usr/bin/python2.6) interface="(unset)" member="(unset)" error
> >>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
> >>> comm="/usr/sbin/bluetoothd))
> >>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_probe_drivers() Probe drivers for
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> >>> input/manager.c:hid_device_probe() path
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_ref() 0xb899f6b8: ref=3
> >>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> >>> input/device.c:input_device_new() Registered interface org.bluez.Input
> >>> on path /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 22 22:59:48 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_unref() 0xb899f6b8: ref=2
> >>>
> >>> agent.c:
> >>>
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_create_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_create() Creating device
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_ref() 0xb899f6b8: ref=1
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/device.c:bonding_request_new()
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting bonding
> >>> Sep 23 00:14:34 Zatoichi bluetoothd[7042]:
> >>> src/device.c:bonding_request_new() Temporary agent registered for
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65 at
> >>> :1.35:/org/bluez/agent_8004
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: Authentication requested
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: link_key_request
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> >>> src/security.c:link_key_request() kernel auth requirements = 0x03
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]: pin_code_request
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65)
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:35 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_request_authentication()
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65: requesting agent
> >>> authentication
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: link_key_notify
> >>> (sba=40:61:86:A5:0E:E1, dba=00:1F:20:06:47:65, type=0)
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() key type 0x00 old key type
> >>> 0xff new key type 0x00
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() local auth 0x03 and remote
> >>> auth 0xff
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_link_key_notify() storing link key of type
> >>> 0x00
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/dbus-hci.c:hcid_dbus_bonding_process_complete() status=00
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_get_device() 00:1F:20:06:47:65
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_ref() 0xb899f6b8: ref=2
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]: src/agent.c:agent_release()
> >>> Releasing agent :1.35, /org/bluez/agent_8004
> >>> Sep 23 00:14:38 Zatoichi dbus-daemon: [system] Rejected send message,
> >>> 1 matched rules; type="method_return", sender=":1.35" (uid=1000
> >>> pid=8004 comm="./agent) interface="(unset)" member="(unset)" error
> >>> name="(unset)" requested_reply=0 destination=":1.12" (uid=0 pid=7041
> >>> comm="/usr/sbin/bluetoothd))
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/device.c:discover_services_req_exit() DiscoverServices requestor
> >>> exited
> >>> Sep 23 00:14:38 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_unref() 0xb899f6b8: ref=1
> >>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> >>> src/adapter.c:adapter_remove_connection() Removing temporary device
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:device_remove() Removing device
> >>> /org/bluez/7041/hci0/dev_00_1F_20_06_47_65
> >>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]:
> >>> src/device.c:btd_device_unref() 0xb899f6b8: ref=0
> >>> Sep 23 00:14:40 Zatoichi bluetoothd[7042]: src/device.c:device_free() 0xb899f6b8
> >>> --
> >>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> >>> the body of a message to majordomo@vger.kernel.org
> >>> More majordomo info at http://vger.kernel.org/majordomo-info.html
> >> --
> >> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> >> the body of a message to majordomo@vger.kernel.org
> >> More majordomo info at http://vger.kernel.org/majordomo-info.html
> >>
> >
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* Support for Device ID profile
From: steven bluez @ 2010-09-24 8:54 UTC (permalink / raw)
To: linux-bluetooth
Hi all,
Is there any support for Device ID Profile in Bluez 4.XX? versions ?\
Regards,
Steven
^ permalink raw reply
* Re: [PATCH v4 2/2] Bluetooth: hidp: Add support for hidraw HIDIOCGFEATURE and HIDIOCSFEATURE
From: Antonio Ospite @ 2010-09-24 10:47 UTC (permalink / raw)
To: Alan Ott
Cc: Ville Tervo, Jiri Kosina, Stefan Achatz, Alexey Dobriyan,
Tejun Heo, Alan Stern, Greg Kroah-Hartman, Marcel Holtmann,
Stephane Chatty, Michael Poole, David S. Miller, Bastien Nocera,
Eric Dumazet, linux-input@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org,
linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org
In-Reply-To: <89BCFDE1-0D39-4248-84B2-E02C1480E067@signal11.us>
[-- Attachment #1: Type: text/plain, Size: 1055 bytes --]
On Thu, 23 Sep 2010 10:16:43 -0400
Alan Ott <alan@signal11.us> wrote:
>
> On Sep 23, 2010, at 7:51 AM, Ville Tervo wrote:
>
> > Hi Alan,
> >
> > One comment.
> >
> > How about a variable called ret and using that to return len or
> > errno? It
> > would eliminate code dublication.
> >
>
> Hi Ville,
>
> Where specifically? In which function? I've gone through it a couple
> of times and failed to find return statements which are superfluous.
> Maybe I'm missing something fundamental?
>
I guess he's referring to the return paths (normal, err_restartsys,
err_eio) in hidp_get_raw_report(), they could be merged into a
generic "out:" path by parametrizing the return value.
I don't know if Alan prefers explicit error paths, for me it's OK either
way.
Regards,
Antonio
--
Antonio Ospite
http://ao2.it
PGP public key ID: 0x4553B001
A: Because it messes up the order in which people normally read text.
See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
[-- Attachment #2: Type: application/pgp-signature, Size: 198 bytes --]
^ permalink raw reply
* Re: Support for Device ID profile
From: Luiz Augusto von Dentz @ 2010-09-24 12:58 UTC (permalink / raw)
To: steven bluez; +Cc: linux-bluetooth
In-Reply-To: <AANLkTinQOKoBCXgh1=wb9Z7w8A4w7vYFkSCoZvkspyYo@mail.gmail.com>
Hi Steven,
Yes, you can use DeviceID on main.conf to add that, but as you see it
is pretty limited so we are planning to have a better support for
this, perhaps extend adapter driver interface where you can write a
plugin to fill this information.
Regards,
On Fri, Sep 24, 2010 at 11:54 AM, steven bluez <steven.bluez@gmail.com> wrote:
> Hi all,
> Is there any support for Device ID Profile in Bluez 4.XX? versions ?\
>
> Regards,
> Steven
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
Luiz Augusto von Dentz
Computer Engineer
^ permalink raw reply
* [PATCH 0/6] Support for ST-Ericsson CG2900 Connectivity Controller
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:44 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This set of patches adds support for the ST-Ericsson CG2900 Connectivity Combo
Controller (GPS & Bluetooth & FM : GBF).
It contains a core framework where users can register for specific channels.
The core API also allows registration of handlers for specific chips as well as
the transports supported.
The channels are also exposed to User space through character devices.
There is a BlueZ driver module included which connects BlueZ to the CG2900
Bluetooth channels.
There is also an audio driver where audio stacks can set the CG2900 internal
audio paths, e.g. routing FM to I2S and Bluetooth to PCM.
There are already changes planned for this driver that we will submit when
ready:
Currently board specific functionality is directly included through a header
file and source file (cg2900_devices.c and .h). This will be changed by using
platform driver mechanism instead.
The CG2900 core framework still has chip specific dependencies. We have plans
to remove them in order to be able to connect any connectivity chip to
the framework.
Par-Gunnar Hjalmdahl (6):
This patch adds support for the ST-Ericsson CG2900 connectivity
controller. This patch contains the framework for registering
users, chip handlers, and transports as well as the needed files
towards the mach-ux500 board.
This patch adds support for the ST-Ericsson CG2900 connectivity
controller. This patch registers into the CG2900 framework. It
handles chip startup and shutdown together with firmware download.
It also handles Bluetooth and FM flow control due to special H4
channel management.
This patch adds support for the ST-Ericsson CG2690 connectivity
controller. This patch registers into the CG2900 framework. It
handles chip startup and shutdown together with firmware download.
This patch adds support for using UART as transport for the
ST-Ericsson CG2690 connectivity controller. This patch registers
into the CG2900 framework. It handles chip enable and disable as
well baud rate settings. It also assembles received packets into
full Bluetooth, GPS, and FM packets.
This patch adds support for controlling the audio paths within the
ST-Ericsson CG2900 connectivity controller. This patch registers as
a user into the CG2900 framework for the bt_audio and fm_audio
channels. It contains an API for controlling the audio paths that
can also be reached from User space using a char device.
This patch adds support for using the ST-Ericsson CG2900 connectivity
controller as a driver for the BlueZ Bluetooth stack. This patch
registers as a driver into the BlueZ framework and, when opened by
BlueZ, it registers as user for bt_cmd, bt_acl, and bt_evt
channels.
arch/arm/mach-ux500/Makefile | 5 +
arch/arm/mach-ux500/cg2900_devices.c | 353 +++
arch/arm/mach-ux500/include/mach/cg2900_devices.h | 120 +
drivers/bluetooth/Kconfig | 7 +
drivers/bluetooth/Makefile | 2 +
drivers/bluetooth/cg2900_hci.c | 896 ++++++++
drivers/mfd/Kconfig | 34 +
drivers/mfd/Makefile | 1 +
drivers/mfd/cg2900/Makefile | 14 +
drivers/mfd/cg2900/cg2900_audio.c | 2475 +++++++++++++++++++++
drivers/mfd/cg2900/cg2900_char_devices.c | 709 ++++++
drivers/mfd/cg2900/cg2900_char_devices.h | 36 +
drivers/mfd/cg2900/cg2900_chip.c | 2060 +++++++++++++++++
drivers/mfd/cg2900/cg2900_chip.h | 421 ++++
drivers/mfd/cg2900/cg2900_core.c | 2287 +++++++++++++++++++
drivers/mfd/cg2900/cg2900_core.h | 303 +++
drivers/mfd/cg2900/cg2900_debug.h | 77 +
drivers/mfd/cg2900/cg2900_uart.c | 1587 +++++++++++++
drivers/mfd/cg2900/hci_defines.h | 102 +
drivers/mfd/cg2900/stlc2690_chip.c | 1105 +++++++++
drivers/mfd/cg2900/stlc2690_chip.h | 37 +
include/linux/mfd/cg2900.h | 205 ++
include/linux/mfd/cg2900_audio.h | 583 +++++
23 files changed, 13419 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-ux500/cg2900_devices.c
create mode 100644 arch/arm/mach-ux500/include/mach/cg2900_devices.h
create mode 100644 drivers/bluetooth/cg2900_hci.c
create mode 100644 drivers/mfd/cg2900/Makefile
create mode 100644 drivers/mfd/cg2900/cg2900_audio.c
create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.c
create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.h
create mode 100644 drivers/mfd/cg2900/cg2900_chip.c
create mode 100644 drivers/mfd/cg2900/cg2900_chip.h
create mode 100644 drivers/mfd/cg2900/cg2900_core.c
create mode 100644 drivers/mfd/cg2900/cg2900_core.h
create mode 100644 drivers/mfd/cg2900/cg2900_debug.h
create mode 100644 drivers/mfd/cg2900/cg2900_uart.c
create mode 100644 drivers/mfd/cg2900/hci_defines.h
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.c
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.h
create mode 100644 include/linux/mfd/cg2900.h
create mode 100644 include/linux/mfd/cg2900_audio.h
^ permalink raw reply
* [PATCH 1/6] This patch adds support for the ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:46 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for the ST-Ericsson CG2900
connectivity controller.
This patch contains the framework for registering users, chip
handlers, and transports as well as the needed files towards
the mach-ux500 board.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
arch/arm/mach-ux500/Makefile | 5 +
arch/arm/mach-ux500/cg2900_devices.c | 353 ++++
arch/arm/mach-ux500/include/mach/cg2900_devices.h | 120 ++
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/cg2900/Makefile | 7 +
drivers/mfd/cg2900/cg2900_char_devices.c | 709 +++++++
drivers/mfd/cg2900/cg2900_char_devices.h | 36 +
drivers/mfd/cg2900/cg2900_core.c | 2287 +++++++++++++++++++++
drivers/mfd/cg2900/cg2900_core.h | 303 +++
drivers/mfd/cg2900/cg2900_debug.h | 77 +
drivers/mfd/cg2900/hci_defines.h | 102 +
include/linux/mfd/cg2900.h | 205 ++
13 files changed, 4213 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-ux500/cg2900_devices.c
create mode 100644 arch/arm/mach-ux500/include/mach/cg2900_devices.h
create mode 100644 drivers/mfd/cg2900/Makefile
create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.c
create mode 100644 drivers/mfd/cg2900/cg2900_char_devices.h
create mode 100644 drivers/mfd/cg2900/cg2900_core.c
create mode 100644 drivers/mfd/cg2900/cg2900_core.h
create mode 100644 drivers/mfd/cg2900/cg2900_debug.h
create mode 100644 drivers/mfd/cg2900/hci_defines.h
create mode 100644 include/linux/mfd/cg2900.h
diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile
index 46dbaf6..afafdd6 100644
--- a/arch/arm/mach-ux500/Makefile
+++ b/arch/arm/mach-ux500/Makefile
@@ -11,3 +11,8 @@ obj-$(CONFIG_SMP) += platsmp.o headsmp.o
obj-$(CONFIG_HOTPLUG_CPU) += hotplug.o
obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
obj-$(CONFIG_REGULATOR_AB8500) += board-mop500-regulators.o
+ifeq ($(CONFIG_MFD_CG2900), m)
+obj-y += cg2900_devices.o
+else
+obj-$(CONFIG_MFD_CG2900) += cg2900_devices.o
+endif
diff --git a/arch/arm/mach-ux500/cg2900_devices.c
b/arch/arm/mach-ux500/cg2900_devices.c
new file mode 100644
index 0000000..0aa6a07
--- /dev/null
+++ b/arch/arm/mach-ux500/cg2900_devices.c
@@ -0,0 +1,353 @@
+/*
+ * arch/arm/mach-ux500/cg2900_devices.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Board specific device support for the Linux Bluetooth HCI H:4 Driver
+ * for ST-Ericsson connectivity controller.
+ */
+
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <asm-generic/errno-base.h>
+#include <asm/byteorder.h>
+#include <mach/cg2900_devices.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <plat/pincfg.h>
+#include "pins-db8500.h"
+
+#ifndef PIN_INPUT_PULLUP
+#define PIN_INPUT_PULLUP (PIN_DIR_INPUT | PIN_PULL_UP)
+#endif
+
+#ifndef GPIO_LOW
+#define GPIO_LOW 0
+#endif
+
+#ifndef GPIO_HIGH
+#define GPIO_HIGH 1
+#endif
+
+#ifndef GPIO_TO_IRQ
+#define GPIO_TO_IRQ NOMADIK_GPIO_TO_IRQ
+#endif
+
+/** BT_ENABLE_GPIO - GPIO to enable/disable the BT module.
+ */
+#define BT_ENABLE_GPIO 170
+
+/** GBF_ENA_RESET_GPIO - GPIO to enable/disable the controller.
+ */
+#define GBF_ENA_RESET_GPIO 171
+
+/** BT_CTS_GPIO - CTS GPIO.
+*/
+#define BT_CTS_GPIO 0
+
+/** 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"
+/** CG2900_DEVICE_NAME - Name for this module.
+*/
+#define CG2900_DEVICE_NAME "cg2900_driver"
+
+/** UART_LINES_NUM - Number of uart lines we want to configure.
+*/
+#define UART_LINES_NUM 4
+
+/* Bluetooth Opcode Group Field */
+#define BT_OGF_VS 0x3F
+
+/* Bluetooth Opcode Command Field */
+#define BT_OCF_VS_POWER_SWITCH_OFF 0x0140
+#define BT_OCF_VS_BT_ENABLE 0x0310
+
+#define MAKE_CMD(__ogf, __ocf) ((u16)(((u16)__ogf << 10) | __ocf))
+
+#define BT_VS_POWER_SWITCH_OFF MAKE_CMD(BT_OGF_VS, \
+ BT_OCF_VS_POWER_SWITCH_OFF)
+#define BT_VS_BT_ENABLE MAKE_CMD(BT_OGF_VS, BT_OCF_VS_BT_ENABLE)
+
+#define VS_BT_DISABLE 0x00
+#define VS_BT_ENABLE 0x01
+
+#define H4_HEADER_LENGTH 0x01
+#define BT_HEADER_LENGTH 0x03
+
+#define STLC2690_HCI_REV 0x0600
+#define CG2900_HCI_REV 0x0101
+#define CG2900_SPECIAL_HCI_REV 0x0700
+
+struct vs_power_sw_off_cmd {
+ __le16 op_code;
+ u8 len;
+ u8 gpio_0_7_pull_up;
+ u8 gpio_8_15_pull_up;
+ u8 gpio_16_20_pull_up;
+ u8 gpio_0_7_pull_down;
+ u8 gpio_8_15_pull_down;
+ u8 gpio_16_20_pull_down;
+} __attribute__((packed));
+
+struct vs_bt_enable_cmd {
+ __le16 op_code;
+ u8 len;
+ u8 enable;
+} __attribute__((packed));
+
+static u8 cg2900_hci_version;
+static u8 cg2900_lmp_version;
+static u8 cg2900_lmp_subversion;
+static u16 cg2900_hci_revision;
+static u16 cg2900_manufacturer;
+
+/* IRQ callback. */
+static struct cg2900_devices_cb *cg2900_dev_callback;
+
+/* Pin configuration for UART functions. */
+static pin_cfg_t uart0_enabled[] = {
+ GPIO0_U0_CTSn | PIN_INPUT_PULLUP,
+ GPIO1_U0_RTSn | PIN_OUTPUT_HIGH,
+ GPIO2_U0_RXD | PIN_INPUT_PULLUP,
+ GPIO3_U0_TXD | PIN_OUTPUT_HIGH,
+};
+
+/* Pin configuration for sleep mode. */
+static pin_cfg_t uart0_disabled[] = {
+ GPIO0_GPIO | PIN_INPUT_PULLUP, /* CTS pull up. */
+ GPIO1_GPIO | PIN_OUTPUT_HIGH, /* RTS high - flow off. */
+ GPIO2_GPIO | PIN_INPUT_PULLUP, /* RX pull down. */
+ GPIO3_GPIO | PIN_OUTPUT_LOW, /* TX low - break on. */
+};
+
+void cg2900_devices_enable_chip(void)
+{
+ gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_HIGH);
+}
+EXPORT_SYMBOL(cg2900_devices_enable_chip);
+
+void cg2900_devices_disable_chip(void)
+{
+ gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_LOW);
+ cg2900_dev_callback = NULL;
+}
+EXPORT_SYMBOL(cg2900_devices_disable_chip);
+
+void cg2900_devices_set_hci_revision(u8 hci_version,
+ u16 hci_revision,
+ u8 lmp_version,
+ u8 lmp_subversion,
+ u16 manufacturer)
+{
+ cg2900_hci_version = hci_version;
+ cg2900_hci_revision = hci_revision;
+ cg2900_lmp_version = lmp_version;
+ cg2900_lmp_subversion = lmp_subversion;
+ cg2900_manufacturer = manufacturer;
+}
+EXPORT_SYMBOL(cg2900_devices_set_hci_revision);
+
+struct sk_buff *cg2900_devices_get_power_switch_off_cmd(u16 *op_code)
+{
+ struct sk_buff *skb;
+ struct vs_power_sw_off_cmd *cmd;
+
+ /* If connected chip does not support the command return NULL */
+ if (CG2900_HCI_REV != cg2900_hci_revision &&
+ CG2900_SPECIAL_HCI_REV != cg2900_hci_revision)
+ return NULL;
+
+ skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL);
+ if (!skb) {
+ pr_err("Could not allocate skb");
+ return NULL;
+ }
+
+ skb_reserve(skb, H4_HEADER_LENGTH);
+ cmd = (struct vs_power_sw_off_cmd *)skb_put(skb, sizeof(*cmd));
+ cmd->op_code = cpu_to_le16(BT_VS_POWER_SWITCH_OFF);
+ cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH;
+ /*
+ * 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.
+ */
+ cmd->gpio_0_7_pull_up = 0x00;
+ cmd->gpio_8_15_pull_up = 0x00;
+ cmd->gpio_16_20_pull_up = 0x00;
+ cmd->gpio_0_7_pull_down = 0x00;
+ cmd->gpio_8_15_pull_down = 0x00;
+ cmd->gpio_16_20_pull_down = 0x00;
+
+ if (op_code)
+ *op_code = BT_VS_POWER_SWITCH_OFF;
+
+ return skb;
+}
+EXPORT_SYMBOL(cg2900_devices_get_power_switch_off_cmd);
+
+struct sk_buff *cg2900_devices_get_bt_enable_cmd(u16 *op_code, bool bt_enable)
+{
+ struct sk_buff *skb;
+ struct vs_bt_enable_cmd *cmd;
+
+ /* If connected chip does not support the command return NULL */
+ if (CG2900_HCI_REV != cg2900_hci_revision &&
+ CG2900_SPECIAL_HCI_REV != cg2900_hci_revision)
+ return NULL;
+
+ /* CG2900 used */
+ skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL);
+ if (!skb) {
+ pr_err("Could not allocate skb");
+ return NULL;
+ }
+
+ skb_reserve(skb, H4_HEADER_LENGTH);
+ cmd = (struct vs_bt_enable_cmd *)skb_put(skb, sizeof(*cmd));
+ cmd->op_code = cpu_to_le16(BT_VS_BT_ENABLE);
+ cmd->len = sizeof(*cmd) - BT_HEADER_LENGTH;
+ if (bt_enable)
+ cmd->enable = VS_BT_ENABLE;
+ else
+ cmd->enable = VS_BT_DISABLE;
+
+ if (op_code)
+ *op_code = BT_VS_BT_ENABLE;
+
+ return skb;
+}
+EXPORT_SYMBOL(cg2900_devices_get_bt_enable_cmd);
+
+static irqreturn_t cg2900_devices_interrupt(int irq, void *dev_id)
+{
+ disable_irq_nosync(irq);
+ if (cg2900_dev_callback && cg2900_dev_callback->interrupt_cb)
+ cg2900_dev_callback->interrupt_cb();
+
+ return IRQ_HANDLED;
+}
+
+int cg2900_devices_set_cts_irq(void)
+{
+ int err;
+
+ /*
+ * Without this delay we get interrupt on CTS immediately
+ * due to some turbulences on this line.
+ */
+ mdelay(4);
+
+ /* Disable UART functions. */
+ err = nmk_config_pins(uart0_disabled, UART_LINES_NUM);
+
+ if (err)
+ goto error;
+
+ /* Set IRQ on CTS. */
+ err = request_irq(GPIO_TO_IRQ(BT_CTS_GPIO),
+ cg2900_devices_interrupt,
+ IRQF_TRIGGER_FALLING,
+ CG2900_DEVICE_NAME,
+ NULL);
+ if (err)
+ goto error;
+
+ return 0;
+
+error:
+ (void)nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+ pr_err("Can not set intterupt.");
+ return err;
+}
+EXPORT_SYMBOL(cg2900_devices_set_cts_irq);
+
+void cg2900_devices_unset_cts_irq(void)
+{
+ int err;
+
+ /* Restore UART settings. */
+ free_irq(GPIO_TO_IRQ(BT_CTS_GPIO), NULL);
+ err = nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+ if (err)
+ pr_err("Unable to enable UART");
+}
+EXPORT_SYMBOL(cg2900_devices_unset_cts_irq);
+
+void cg2900_devices_reg_cb(struct cg2900_devices_cb *cb)
+{
+ if (!cg2900_dev_callback)
+ cg2900_dev_callback = cb;
+ else
+ pr_err("Callback already registered");
+}
+EXPORT_SYMBOL(cg2900_devices_reg_cb);
+int cg2900_devices_init(void)
+{
+ int err = 0;
+
+ err = gpio_request(GBF_ENA_RESET_GPIO, GBF_ENA_RESET_NAME);
+ if (err < 0) {
+ pr_err("gpio_request failed with err: %d", err);
+ goto finished;
+ }
+
+ err = gpio_direction_output(GBF_ENA_RESET_GPIO, GPIO_HIGH);
+ if (err < 0) {
+ pr_err("gpio_direction_output failed with err: %d", err);
+ goto error_handling;
+ }
+
+ err = gpio_request(BT_ENABLE_GPIO, BT_ENABLE_NAME);
+ if (err < 0) {
+ pr_err("gpio_request failed with err: %d", err);
+ goto finished;
+ }
+
+ err = gpio_direction_output(BT_ENABLE_GPIO, GPIO_HIGH);
+ if (err < 0) {
+ pr_err("gpio_direction_output failed with err: %d", err);
+ goto error_handling;
+ }
+
+ goto finished;
+
+error_handling:
+ gpio_free(GBF_ENA_RESET_GPIO);
+
+finished:
+ cg2900_devices_disable_chip();
+ return err;
+}
+EXPORT_SYMBOL(cg2900_devices_init);
+
+void cg2900_devices_exit(void)
+{
+ cg2900_devices_disable_chip();
+ gpio_free(GBF_ENA_RESET_GPIO);
+}
+EXPORT_SYMBOL(cg2900_devices_exit);
diff --git a/arch/arm/mach-ux500/include/mach/cg2900_devices.h
b/arch/arm/mach-ux500/include/mach/cg2900_devices.h
new file mode 100644
index 0000000..2db25b3
--- /dev/null
+++ b/arch/arm/mach-ux500/include/mach/cg2900_devices.h
@@ -0,0 +1,120 @@
+/*
+ * arch/arm/mach-ux500/include/mach/cg2900_devices.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Board specific device support for the Linux Bluetooth HCI H4 Driver
+ * for ST-Ericsson connectivity controller.
+ */
+
+#ifndef _CG2900_DEVICES_H_
+#define _CG2900_DEVICES_H_
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+
+/**
+ * struct cg2900_devices_cb - Callback structure for cg2900_devices user.
+ * @interrupt_cb: Callback function called when interrupt on CTS occurred.
+ *
+ * Defines the callback functions provided from the caller.
+ */
+struct cg2900_devices_cb {
+ void (*interrupt_cb)(void);
+};
+
+/**
+ * cg2900_devices_enable_chip() - Enable the controller.
+ */
+extern void cg2900_devices_enable_chip(void);
+
+/**
+ * cg2900_devices_disable_chip() - Disable the controller.
+ */
+extern void cg2900_devices_disable_chip(void);
+
+/**
+ * cg2900_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 cg2900_devices_set_hci_revision(u8 hci_version,
+ u16 hci_revision,
+ u8 lmp_version,
+ u8 lmp_subversion,
+ u16 manufacturer);
+
+/**
+ * cg2900_devices_get_power_switch_off_cmd() - Get HCI power switch
off command to use based on connected connectivity controller.
+ * @op_code: 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 *cg2900_devices_get_power_switch_off_cmd(u16 *op_code);
+
+/**
+ * cg2900_devices_get_bt_enable_cmd() - Get HCI BT enable command to
use based on connected connectivity controller.
+ * @op_code: 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 *cg2900_devices_get_bt_enable_cmd(u16 *op_code,
+ bool bt_enable);
+
+/**
+ * cg2900_devices_init() - Initialize the board config.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * Error codes from gpio_request and gpio_direction_output.
+ */
+extern int cg2900_devices_init(void);
+
+/**
+ * cg2900_devices_exit() - Exit function for the board config.
+ */
+extern void cg2900_devices_exit(void);
+
+/**
+ * cg2900_devices_unset_cts_irq() - Disable interrupt on CTS.
+ */
+extern void cg2900_devices_unset_cts_irq(void);
+
+/**
+ * cg2900_devices_set_cts_irq() - Enable interrupt on CTS.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * Error codes from request_irq and nmk_config_pins.
+ */
+extern int cg2900_devices_set_cts_irq(void);
+
+/**
+ * cg2900_devices_reg_cb() - Register callbacks from upper layer.
+ *@cb: Callback structure from upper layer.
+ *
+ */
+extern void cg2900_devices_reg_cb(struct cg2900_devices_cb *cb);
+#endif /* _CG2900_DEVICES_H_ */
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index fbfe14a..c0a6652 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -576,6 +576,14 @@ config MFD_TPS6586X
This driver can also be built as a module. If so, the module
will be called tps6586x.
+config MFD_CG2900
+ tristate "Support ST-Ericsson CG2900 main structure"
+ depends on NET
+ help
+ Support for ST-Ericsson CG2900 Connectivity Combo controller main structure.
+ Supports multiple functionalities muxed over a Bluetooth HCI H:4 interface.
+ CG2900 support Bluetooth, FM radio, and GPS.
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index d5968cd..e3d8206 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -78,3 +78,4 @@ obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o
+obj-y += cg2900/
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
new file mode 100644
index 0000000..76af761
--- /dev/null
+++ b/drivers/mfd/cg2900/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for ST-Ericsson CG2900 connectivity combo controller
+#
+
+obj-$(CONFIG_MFD_CG2900) += cg2900.o
+cg2900-objs := cg2900_core.o cg2900_char_devices.o
+export-objs := cg2900_core.o
diff --git a/drivers/mfd/cg2900/cg2900_char_devices.c
b/drivers/mfd/cg2900/cg2900_char_devices.c
new file mode 100644
index 0000000..709689b
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_char_devices.c
@@ -0,0 +1,709 @@
+/*
+ * drivers/mfd/cg2900/cg2900_char_devices.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.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/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/list.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+/* Ioctls */
+#define CG2900_CHAR_DEV_IOCTL_RESET _IOW('U', 210, int)
+#define CG2900_CHAR_DEV_IOCTL_CHECK4RESET _IOR('U', 212, int)
+#define CG2900_CHAR_DEV_IOCTL_GET_REVISION _IOR('U', 213, int)
+#define CG2900_CHAR_DEV_IOCTL_GET_SUB_VER _IOR('U', 214, int)
+
+#define CG2900_CHAR_DEV_IOCTL_EVENT_RESET 1
+#define CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED 2
+
+/* Internal type definitions */
+
+/**
+ * enum char_reset_state - Reset state.
+ * @CG2900_CHAR_IDLE: Idle state.
+ * @CG2900_CHAR_RESET: Reset state.
+ */
+enum char_reset_state {
+ CG2900_CHAR_IDLE,
+ CG2900_CHAR_RESET
+};
+
+/**
+ * struct char_dev_user - Stores device information.
+ * @dev: Registered CG2900 Core device.
+ * @miscdev: Registered device struct.
+ * @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.
+ * @list: List header for inserting into device list.
+ */
+struct char_dev_user {
+ struct cg2900_device *dev;
+ struct miscdevice *miscdev;
+ char *name;
+ struct sk_buff_head rx_queue;
+ wait_queue_head_t rx_wait_queue;
+ wait_queue_head_t reset_wait_queue;
+ enum char_reset_state reset_state;
+ struct mutex read_mutex;
+ struct mutex write_mutex;
+ struct list_head list;
+};
+
+/**
+ * struct char_info - Stores all current users.
+ * @open_mutex: Open mutex (used for both open and release).
+ * @dev_users: List of char dev users.
+ */
+struct char_info {
+ struct mutex open_mutex;
+ struct list_head dev_users;
+};
+
+/* Internal variable declarations */
+
+/*
+ * char_info - Main information object for char devices.
+ */
+static struct char_info *char_info;
+
+/* ST-Ericsson CG2900 driver callbacks */
+
+/**
+ * char_dev_read_cb() - Handle data received from controller.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming from controller.
+ *
+ * The char_dev_read_cb() function handles data received from
STE-CG2900 driver.
+ */
+static void char_dev_read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+ struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
+
+ CG2900_INFO("CharDev: char_dev_read_cb");
+
+ if (!char_dev) {
+ CG2900_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);
+}
+
+/**
+ * char_dev_reset_cb() - Handle reset from controller.
+ * @dev: Device resetting.
+ *
+ * The char_dev_reset_cb() function handles reset from the CG2900 driver.
+ */
+static void char_dev_reset_cb(struct cg2900_device *dev)
+{
+ struct char_dev_user *char_dev = (struct char_dev_user *)dev->user_data;
+
+ CG2900_INFO("CharDev: char_dev_reset_cb");
+
+ if (!char_dev) {
+ CG2900_ERR("char_dev == NULL");
+ return;
+ }
+
+ char_dev->reset_state = CG2900_CHAR_RESET;
+ /*
+ * The device will be freed by CG2900 Core when this function is
+ * finished.
+ */
+ char_dev->dev = NULL;
+
+ wake_up_interruptible(&char_dev->rx_wait_queue);
+ wake_up_interruptible(&char_dev->reset_wait_queue);
+}
+
+/*
+ * struct char_cb - Callback structure for CG2900 user.
+ * @read_cb: Callback function called when data is received from the
+ * CG2900 driver.
+ * @reset_cb: Callback function called when the controller has been reset.
+ */
+static struct cg2900_callbacks char_cb = {
+ .read_cb = char_dev_read_cb,
+ .reset_cb = char_dev_reset_cb
+};
+
+/* File operation functions */
+
+/**
+ * char_dev_open() - Open char device.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * The char_dev_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 char_dev_open(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ int minor;
+ struct char_dev_user *dev = NULL;
+ struct char_dev_user *tmp;
+ struct list_head *cursor;
+
+ mutex_lock(&char_info->open_mutex);
+
+ minor = iminor(inode);
+
+ /* Find the device for this file */
+ list_for_each(cursor, &char_info->dev_users) {
+ tmp = list_entry(cursor, struct char_dev_user, list);
+ if (tmp->miscdev->minor == minor) {
+ dev = tmp;
+ break;
+ }
+ }
+ if (!dev) {
+ CG2900_ERR("Could not identify device in inode");
+ err = -EINVAL;
+ goto error_handling;
+ }
+
+ filp->private_data = dev;
+
+ CG2900_INFO("CharDev: char_dev_open %s", dev->name);
+
+ if (dev->dev) {
+ CG2900_ERR("Device already registered to CG2900 Driver");
+ 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 = CG2900_CHAR_IDLE;
+
+ /* Register to CG2900 Driver */
+ dev->dev = cg2900_register_user(dev->name, &char_cb);
+ if (dev->dev)
+ dev->dev->user_data = dev;
+ else {
+ CG2900_ERR("Couldn't register to CG2900 for H:4 channel %s",
+ dev->name);
+ err = -EACCES;
+ }
+
+error_handling:
+ mutex_unlock(&char_info->open_mutex);
+ return err;
+}
+
+/**
+ * char_dev_release() - Release char device.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * The char_dev_release() function release the char device.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EBADF if NULL pointer was supplied in private data.
+ */
+static int char_dev_release(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+
+ CG2900_INFO("CharDev: char_dev_release");
+
+ if (!dev) {
+ CG2900_ERR("Calling with NULL pointer");
+ return -EBADF;
+ }
+
+ mutex_lock(&char_info->open_mutex);
+ mutex_lock(&dev->read_mutex);
+ mutex_lock(&dev->write_mutex);
+
+ if (dev->reset_state == CG2900_CHAR_IDLE)
+ cg2900_deregister_user(dev->dev);
+
+ dev->dev = NULL;
+ filp->private_data = NULL;
+ wake_up_interruptible(&dev->rx_wait_queue);
+ wake_up_interruptible(&dev->reset_wait_queue);
+
+ mutex_unlock(&dev->write_mutex);
+ mutex_unlock(&dev->read_mutex);
+ mutex_unlock(&char_info->open_mutex);
+
+ return err;
+}
+
+/**
+ * char_dev_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 char_dev_read() function queues and copy the received buffer to
+ * the user space char device. If no data is available this function
will block.
+ *
+ * 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 char_dev_read(struct file *filp, char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+ struct sk_buff *skb;
+ int bytes_to_copy;
+ int err = 0;
+
+ CG2900_INFO("CharDev: char_dev_read");
+
+ if (!dev) {
+ CG2900_ERR("Calling with NULL pointer");
+ return -EBADF;
+ }
+ mutex_lock(&dev->read_mutex);
+
+ if (skb_queue_empty(&dev->rx_queue)) {
+ err = wait_event_interruptible(dev->rx_wait_queue,
+ (!(skb_queue_empty(&dev->rx_queue))) ||
+ (CG2900_CHAR_RESET == dev->reset_state) ||
+ (dev->dev == NULL));
+ if (err) {
+ CG2900_ERR("Failed to wait for event");
+ goto error_handling;
+ }
+ }
+
+ if (!dev->dev) {
+ CG2900_DBG("dev is empty - return with negative bytes");
+ err = -EBADF;
+ goto error_handling;
+ }
+
+ skb = skb_dequeue(&dev->rx_queue);
+ if (!skb) {
+ CG2900_DBG("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;
+}
+
+/**
+ * char_dev_write() - Copy buffer from user and write to CG2900 driver.
+ * @filp: Pointer to the file struct.
+ * @buf: Write buffer.
+ * @count: Size of the buffer write.
+ * @f_pos: Position of buffer.
+ *
+ * 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 char_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct sk_buff *skb;
+ struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+ int err = 0;
+
+ CG2900_INFO("CharDev: char_dev_write");
+
+ if (!dev) {
+ CG2900_ERR("Calling with NULL pointer");
+ return -EBADF;
+ }
+ mutex_lock(&dev->write_mutex);
+
+ skb = cg2900_alloc_skb(count, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buff with length %d", count);
+ goto error_handling;
+ }
+
+ if (copy_from_user(skb_put(skb, count), buf, count)) {
+ kfree_skb(skb);
+ err = -EFAULT;
+ goto error_handling;
+ }
+
+ err = cg2900_write(dev->dev, skb);
+ if (err) {
+ CG2900_ERR("cg2900_write failed (%d)", err);
+ kfree_skb(skb);
+ goto error_handling;
+ }
+
+ mutex_unlock(&dev->write_mutex);
+ return count;
+
+error_handling:
+ mutex_unlock(&dev->write_mutex);
+ return err;
+}
+
+/**
+ * char_dev_unlocked_ioctl() - Handle IOCTL call to the interface.
+ * @filp: Pointer to the file struct.
+ * @cmd: IOCTL command.
+ * @arg: IOCTL argument.
+ *
+ * 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 CG2900_CHAR_DEV_IOCTL_CHECK4RESET 0x01 is returned if device is
+ * reset and 0x02 is returned if device is closed.
+ */
+static long char_dev_unlocked_ioctl(struct file *filp, unsigned int cmd,
+ unsigned long arg)
+{
+ struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+ struct cg2900_rev_data rev_data;
+ int err = 0;
+
+ CG2900_INFO("CharDev: char_dev_unlocked_ioctl cmd %d for %s", cmd,
+ dev->name);
+ CG2900_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d",
+ _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
+ _IOC_SIZE(cmd));
+
+ switch (cmd) {
+ case CG2900_CHAR_DEV_IOCTL_RESET:
+ if (!dev) {
+ err = -EBADF;
+ goto error_handling;
+ }
+ CG2900_INFO("ioctl reset command for device %s", dev->name);
+ err = cg2900_reset(dev->dev);
+ break;
+
+ case CG2900_CHAR_DEV_IOCTL_CHECK4RESET:
+ if (!dev) {
+ CG2900_INFO("ioctl check for reset command for device");
+ /* Return positive value if closed */
+ err = CG2900_CHAR_DEV_IOCTL_EVENT_CLOSED;
+ } else if (dev->reset_state == CG2900_CHAR_RESET) {
+ CG2900_INFO("ioctl check for reset command for device "
+ "%s", dev->name);
+ /* Return positive value if reset */
+ err = CG2900_CHAR_DEV_IOCTL_EVENT_RESET;
+ }
+ break;
+
+ case CG2900_CHAR_DEV_IOCTL_GET_REVISION:
+ CG2900_INFO("ioctl check for local revision info");
+ if (cg2900_get_local_revision(&rev_data)) {
+ CG2900_DBG("Read revision data revision %d "
+ "sub_version %d",
+ rev_data.revision, rev_data.sub_version);
+ err = rev_data.revision;
+ } else {
+ CG2900_DBG("No revision data available");
+ err = -EIO;
+ }
+ break;
+
+ case CG2900_CHAR_DEV_IOCTL_GET_SUB_VER:
+ CG2900_INFO("ioctl check for local sub-version info");
+ if (cg2900_get_local_revision(&rev_data)) {
+ CG2900_DBG("Read revision data revision %d "
+ "sub_version %d",
+ rev_data.revision, rev_data.sub_version);
+ err = rev_data.sub_version;
+ } else {
+ CG2900_DBG("No revision data available");
+ err = -EIO;
+ }
+ break;
+
+ default:
+ CG2900_ERR("Unknown ioctl command %08X", cmd);
+ err = -EINVAL;
+ break;
+ };
+
+error_handling:
+ return err;
+}
+
+/**
+ * char_dev_poll() - Handle POLL call to the interface.
+ * @filp: Pointer to the file struct.
+ * @wait: Poll table supplied to caller.
+ *
+ * Returns:
+ * Mask of current set POLL values
+ */
+static unsigned int char_dev_poll(struct file *filp, poll_table *wait)
+{
+ struct char_dev_user *dev = (struct char_dev_user *)filp->private_data;
+ unsigned int mask = 0;
+
+ if (!dev) {
+ CG2900_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->dev)
+ mask |= POLLERR | POLLRDHUP;
+ else
+ mask |= POLLOUT; /* We can TX unless there is an error */
+
+ if (!(skb_queue_empty(&dev->rx_queue)))
+ mask |= POLLIN | POLLRDNORM;
+
+ if (CG2900_CHAR_RESET == dev->reset_state)
+ mask |= POLLPRI;
+
+ return mask;
+}
+
+/*
+ * struct char_dev_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 const struct file_operations char_dev_fops = {
+ .read = char_dev_read,
+ .write = char_dev_write,
+ .unlocked_ioctl = char_dev_unlocked_ioctl,
+ .poll = char_dev_poll,
+ .open = char_dev_open,
+ .release = char_dev_release
+};
+
+/**
+ * setup_dev() - Set up the char device structure for device.
+ * @parent: Parent device pointer.
+ * @name: Name of registered device.
+ *
+ * The setup_dev() 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 setup_dev(struct device *parent, char *name)
+{
+ int err = 0;
+ struct char_dev_user *dev_usr;
+
+ CG2900_INFO("CharDev: setup_dev");
+
+ dev_usr = kzalloc(sizeof(*dev_usr), GFP_KERNEL);
+ if (!dev_usr) {
+ CG2900_ERR("Couldn't allocate dev_usr");
+ return -ENOMEM;
+ }
+
+ /* Store device name */
+ dev_usr->name = name;
+
+ dev_usr->miscdev = kzalloc(sizeof(*(dev_usr->miscdev)),
+ GFP_KERNEL);
+ if (!dev_usr->miscdev) {
+ CG2900_ERR("Couldn't allocate char_dev");
+ err = -ENOMEM;
+ goto err_free_usr;
+ }
+
+ /* Prepare miscdevice struct before registering the device */
+ dev_usr->miscdev->minor = MISC_DYNAMIC_MINOR;
+ dev_usr->miscdev->name = name;
+ dev_usr->miscdev->fops = &char_dev_fops;
+ dev_usr->miscdev->parent = parent;
+
+ err = misc_register(dev_usr->miscdev);
+ if (err) {
+ CG2900_ERR("Error %d registering misc dev!", err);
+ goto err_free_dev;
+ }
+
+ CG2900_INFO("Added char device %s with major 0x%X and minor 0x%X",
+ name, MAJOR(dev_usr->miscdev->this_device->devt),
+ MINOR(dev_usr->miscdev->this_device->devt));
+
+ mutex_init(&dev_usr->read_mutex);
+ mutex_init(&dev_usr->write_mutex);
+
+ skb_queue_head_init(&dev_usr->rx_queue);
+
+ list_add_tail(&dev_usr->list, &char_info->dev_users);
+ return 0;
+
+err_free_dev:
+ kfree(dev_usr->miscdev);
+ dev_usr->miscdev = NULL;
+err_free_usr:
+ kfree(dev_usr);
+ return err;
+}
+
+/**
+ * remove_dev() - Remove char device structure for device.
+ * @dev_usr: Char device user.
+ *
+ * The remove_dev() function releases the char_dev structure for this device.
+ */
+static void remove_dev(struct char_dev_user *dev_usr)
+{
+ CG2900_INFO("CharDev: remove_dev");
+
+ if (!dev_usr)
+ return;
+
+ skb_queue_purge(&dev_usr->rx_queue);
+
+ mutex_destroy(&dev_usr->read_mutex);
+ mutex_destroy(&dev_usr->write_mutex);
+
+ /* Remove device node in file system. */
+ misc_deregister(dev_usr->miscdev);
+ kfree(dev_usr->miscdev);
+ dev_usr->miscdev = NULL;
+
+ kfree(dev_usr);
+}
+
+/* External functions */
+
+void cg2900_char_devices_init(struct miscdevice *dev)
+{
+ CG2900_INFO("cg2900_char_devices_init");
+
+ if (!dev) {
+ CG2900_ERR("NULL supplied for dev");
+ return;
+ }
+
+ if (char_info) {
+ CG2900_ERR("Char devices already initiated");
+ return;
+ }
+
+ /* Initialize private data. */
+ char_info = kzalloc(sizeof(*char_info), GFP_ATOMIC);
+ if (!char_info) {
+ CG2900_ERR("Could not alloc char_info struct.");
+ return;
+ }
+
+ mutex_init(&char_info->open_mutex);
+ INIT_LIST_HEAD(&char_info->dev_users);
+
+ setup_dev(dev->this_device, CG2900_BT_CMD);
+ setup_dev(dev->this_device, CG2900_BT_ACL);
+ setup_dev(dev->this_device, CG2900_BT_EVT);
+ setup_dev(dev->this_device, CG2900_FM_RADIO);
+ setup_dev(dev->this_device, CG2900_GNSS);
+ setup_dev(dev->this_device, CG2900_DEBUG);
+ setup_dev(dev->this_device, CG2900_STE_TOOLS);
+ setup_dev(dev->this_device, CG2900_HCI_LOGGER);
+ setup_dev(dev->this_device, CG2900_US_CTRL);
+ setup_dev(dev->this_device, CG2900_BT_AUDIO);
+ setup_dev(dev->this_device, CG2900_FM_RADIO_AUDIO);
+ setup_dev(dev->this_device, CG2900_CORE);
+}
+
+void cg2900_char_devices_exit(void)
+{
+ struct list_head *cursor, *next;
+ struct char_dev_user *tmp;
+
+ CG2900_INFO("cg2900_char_devices_exit");
+
+ if (!char_info)
+ return;
+
+ list_for_each_safe(cursor, next, &char_info->dev_users) {
+ tmp = list_entry(cursor, struct char_dev_user, list);
+ list_del(cursor);
+ remove_dev(tmp);
+ }
+
+ mutex_destroy(&char_info->open_mutex);
+
+ kfree(char_info);
+ char_info = NULL;
+}
+
+MODULE_AUTHOR("Henrik Possung ST-Ericsson");
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ST-Ericsson CG2900 Char Devices Driver");
diff --git a/drivers/mfd/cg2900/cg2900_char_devices.h
b/drivers/mfd/cg2900/cg2900_char_devices.h
new file mode 100644
index 0000000..65d2b7f
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_char_devices.h
@@ -0,0 +1,36 @@
+/*
+ * drivers/mfd/cg2900/cg2900_char_devices.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson connectivity controller.
+ */
+
+#ifndef _CG2900_CHAR_DEVICES_H_
+#define _CG2900_CHAR_DEVICES_H_
+
+#include <linux/miscdevice.h>
+
+/**
+ * cg2900_char_devices_init() - Initialize char device module.
+ * @parent: Parent device for the driver.
+ *
+ * Returns:
+ * 0 if success.
+ * Negative value upon error.
+ */
+extern int cg2900_char_devices_init(struct miscdevice *parent);
+
+/**
+ * cg2900_char_devices_exit() - Release the char device module.
+ */
+extern void cg2900_char_devices_exit(void);
+
+#endif /* _CG2900_CHAR_DEVICES_H_ */
diff --git a/drivers/mfd/cg2900/cg2900_core.c b/drivers/mfd/cg2900/cg2900_core.c
new file mode 100644
index 0000000..8c26202
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_core.c
@@ -0,0 +1,2287 @@
+/*
+ * drivers/mfd/cg2900/cg2900_core.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#include <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/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "cg2900_core.h"
+#include "cg2900_char_devices.h"
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+
+/* Device names */
+#define CG2900_CDEV_NAME "cg2900_core_test"
+#define CG2900_CLASS_NAME "cg2900_class"
+#define CG2900_DEVICE_NAME "cg2900_driver"
+#define CORE_WQ_NAME "cg2900_core_wq"
+
+#define SET_MAIN_STATE(__core_new_state) \
+ CG2900_SET_STATE("main_state", core_info->main_state, \
+ __core_new_state)
+#define SET_BOOT_STATE(__core_new_state) \
+ CG2900_SET_STATE("boot_state", core_info->boot_state, __core_new_state)
+#define SET_TRANSPORT_STATE(__core_new_state) \
+ CG2900_SET_STATE("transport_state", core_info->transport_state, \
+ __core_new_state)
+
+#define LOGGER_DIRECTION_TX 0
+#define LOGGER_DIRECTION_RX 1
+
+/* Number of bytes to reserve at start of sk_buffer when receiving packet */
+#define RX_SKB_RESERVE 8
+
+/*
+ * Timeout values
+ */
+#define CHIP_STARTUP_TIMEOUT (15000) /* ms */
+#define CHIP_SHUTDOWN_TIMEOUT (15000) /* ms */
+#define LINE_TOGGLE_DETECT_TIMEOUT (50) /* ms */
+#define CHIP_READY_TIMEOUT (100) /* ms */
+#define REVISION_READOUT_TIMEOUT (500) /* ms */
+
+/*
+ * We can have up to 32 char devs with current bit mask and we also have
+ * the parent device here in the transport so that is 33 devices in total.
+ */
+#define MAX_NBR_OF_DEVS 33
+
+/* Default H4 channels which may change depending on connected controller */
+#define HCI_FM_RADIO_H4_CHANNEL 0x08
+#define HCI_GNSS_H4_CHANNEL 0x09
+
+/*
+ * Internal type definitions
+ */
+
+/**
+ * enum main_state - Main-state for CG2900 Core.
+ * @CORE_INITIALIZING: CG2900 Core initializing.
+ * @CORE_IDLE: No user registered to CG2900 Core.
+ * @CORE_BOOTING: CG2900 Core booting after first user is registered.
+ * @CORE_CLOSING: CG2900 Core closing after last user has deregistered.
+ * @CORE_RESETING: CG2900 Core reset requested.
+ * @CORE_ACTIVE: CG2900 Core up and running with at least one user.
+ */
+enum main_state {
+ CORE_INITIALIZING,
+ CORE_IDLE,
+ CORE_BOOTING,
+ CORE_CLOSING,
+ CORE_RESETING,
+ CORE_ACTIVE
+};
+
+/**
+ * enum boot_state - BOOT-state for CG2900 Core.
+ * @BOOT_NOT_STARTED: Boot has not yet started.
+ * @BOOT_READ_LOCAL_VERSION_INFORMATION: ReadLocalVersionInformation
+ * command has been sent.
+ * @BOOT_READY: CG2900 Core boot is ready.
+ * @BOOT_FAILED: CG2900 Core boot failed.
+ */
+enum boot_state {
+ BOOT_NOT_STARTED,
+ BOOT_READ_LOCAL_VERSION_INFORMATION,
+ BOOT_READY,
+ BOOT_FAILED
+};
+
+/**
+ * enum transport_state - State for the CG2900 transport.
+ * @TRANS_INITIALIZING: Transport initializing.
+ * @TRANS_OPENED: Transport is opened (data can be sent).
+ * @TRANS_CLOSED: Transport is closed (data cannot be sent).
+ */
+enum transport_state {
+ TRANS_INITIALIZING,
+ TRANS_OPENED,
+ TRANS_CLOSED
+};
+
+/**
+ * struct cg2900_users - Stores all current users of CG2900 Core.
+ * @bt_cmd: BT command channel user.
+ * @bt_acl: BT ACL channel user.
+ * @bt_evt: BT event channel user.
+ * @fm_radio: FM radio channel user.
+ * @gnss GNSS: GNSS channel user.
+ * @debug Debug: Internal debug channel user.
+ * @ste_tools: ST-E tools channel user.
+ * @hci_logger: HCI logger channel user.
+ * @us_ctrl: User space control channel user.
+ * @bt_audio: BT audio command channel user.
+ * @fm_radio_audio: FM audio command channel user.
+ * @core: Core command channel user.
+ * @nbr_of_users: Number of users currently registered (not including
+ * the HCI logger).
+ */
+struct cg2900_users {
+ struct cg2900_device *bt_cmd;
+ struct cg2900_device *bt_acl;
+ struct cg2900_device *bt_evt;
+ struct cg2900_device *fm_radio;
+ struct cg2900_device *gnss;
+ struct cg2900_device *debug;
+ struct cg2900_device *ste_tools;
+ struct cg2900_device *hci_logger;
+ struct cg2900_device *us_ctrl;
+ struct cg2900_device *bt_audio;
+ struct cg2900_device *fm_radio_audio;
+ struct cg2900_device *core;
+ unsigned int nbr_of_users;
+};
+
+/**
+ * struct local_chip_info - Stores local controller info.
+ * @version_set: true if version data is valid.
+ * @hci_version: HCI version of local controller.
+ * @hci_revision: HCI revision of local controller.
+ * @lmp_pal_version: LMP/PAL version of local controller.
+ * @manufacturer: Manufacturer of local controller.
+ * @lmp_pal_subversion: LMP/PAL sub-version of local controller.
+ *
+ * According to Bluetooth HCI Read Local Version Information command.
+ */
+struct local_chip_info {
+ bool version_set;
+ u8 hci_version;
+ u16 hci_revision;
+ u8 lmp_pal_version;
+ u16 manufacturer;
+ u16 lmp_pal_subversion;
+};
+
+/**
+ * struct chip_handler_item - Structure to store chip handler cb.
+ * @list: list_head struct.
+ * @cb: Chip handler callback struct.
+ */
+struct chip_handler_item {
+ struct list_head list;
+ struct cg2900_id_callbacks cb;
+};
+
+/**
+ * struct cg2900_work_struct - Work structure for CG2900 Core module.
+ * @work: Work structure.
+ * @data: Pointer to private data.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct cg2900_work_struct{
+ struct work_struct work;
+ void *data;
+};
+
+/**
+ * struct test_char_dev_info - Stores device information.
+ * @test_miscdev: Registered Misc Device.
+ * @rx_queue: RX data queue.
+ */
+struct test_char_dev_info {
+ struct miscdevice test_miscdev;
+ struct sk_buff_head rx_queue;
+};
+
+/**
+ * struct trans_info - Stores transport information.
+ * @dev: Transport device.
+ * @cb: Transport cb.
+ */
+struct trans_info {
+ struct cg2900_trans_dev dev;
+ struct cg2900_trans_callbacks cb;
+};
+
+/**
+ * struct core_info - Main info structure for CG2900 Core.
+ * @users: Stores all users of CG2900 Core.
+ * @local_chip_info: Stores information of local controller.
+ * @main_state: Current Main-state of CG2900 Core.
+ * @boot_state: Current BOOT-state of CG2900 Core.
+ * @transport_state: Current TRANSPORT-state of CG2900 Core.
+ * @wq: CG2900 Core workqueue.
+ * @hci_logger_config: Stores HCI logger configuration.
+ * @dev: Device structure for STE Connectivity driver.
+ * @chip_dev: Device structure for chip driver.
+ * @h4_channels: HCI H:4 channel used by this device.
+ * @test_char_dev: Stores information of test char dev.
+ * @trans_info: Stores information about current transport.
+ */
+struct core_info {
+ struct cg2900_users users;
+ struct local_chip_info local_chip_info;
+ enum main_state main_state;
+ enum boot_state boot_state;
+ enum transport_state transport_state;
+ struct workqueue_struct *wq;
+ struct cg2900_hci_logger_config hci_logger_config;
+ struct miscdevice *dev;
+ struct cg2900_chip_dev chip_dev;
+ struct cg2900_h4_channels h4_channels;
+ struct test_char_dev_info *test_char_dev;
+ struct trans_info *trans_info;
+};
+
+/*
+ * Internal variable declarations
+ */
+
+/*
+ * core_info - Main information object for CG2900 Core.
+ */
+static struct core_info *core_info;
+
+/* Module parameters */
+int cg2900_debug_level = CG2900_DEFAULT_DEBUG_LEVEL;
+EXPORT_SYMBOL(cg2900_debug_level);
+
+u8 bd_address[] = {0x00, 0xBE, 0xAD, 0xDE, 0x80, 0x00};
+EXPORT_SYMBOL(bd_address);
+int bd_addr_count = BT_BDADDR_SIZE;
+
+/* Setting default values to ST-E CG2900 */
+int default_manufacturer = 0x30;
+EXPORT_SYMBOL(default_manufacturer);
+int default_hci_revision = 0x0700;
+EXPORT_SYMBOL(default_hci_revision);
+int default_sub_version = 0x0011;
+EXPORT_SYMBOL(default_sub_version);
+
+static int sleep_timeout_ms;
+
+/*
+ * chip_handlers - List of the register handlers for different chips.
+ */
+LIST_HEAD(chip_handlers);
+
+/*
+ * main_wait_queue - Main Wait Queue in CG2900 Core.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(main_wait_queue);
+
+/*
+ * main_wait_queue - Char device Wait Queue in CG2900 Core.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(char_wait_queue);
+
+/*
+ * Internal functions
+ */
+
+/**
+ * free_user_dev - Frees user device and also sets it to NULL to inform caller.
+ * @dev: Pointer to user device.
+ */
+static void free_user_dev(struct cg2900_device **dev)
+{
+ if (*dev) {
+ kfree((*dev)->cb);
+ kfree(*dev);
+ *dev = NULL;
+ }
+}
+
+/**
+ * handle_reset_of_user - Calls the reset callback and frees the device.
+ * @dev: Pointer to CG2900 device.
+ */
+static void handle_reset_of_user(struct cg2900_device **dev)
+{
+ if (*dev) {
+ if ((*dev)->cb->reset_cb)
+ (*dev)->cb->reset_cb((*dev));
+ free_user_dev(dev);
+ }
+}
+
+/**
+ * transmit_skb_to_chip() - Transmit buffer to the transport.
+ * @skb: Data packet.
+ * @use_logger: True if HCI logger shall be used, false otherwise.
+ *
+ * The transmit_skb_to_chip() function transmit buffer to the transport.
+ * If enabled, copy the transmitted data to the HCI logger as well.
+ */
+static void transmit_skb_to_chip(struct sk_buff *skb, bool use_logger)
+{
+ int err;
+ struct sk_buff *skb_log;
+ struct trans_info *trans_info = core_info->trans_info;
+ struct cg2900_device *logger;
+
+ CG2900_DBG_DATA("transmit_skb_to_chip %d bytes. First byte 0x%02X",
+ skb->len, *(skb->data));
+
+ if (TRANS_CLOSED == core_info->transport_state) {
+ CG2900_ERR("Trying to write on a closed channel");
+ kfree_skb(skb);
+ return;
+ }
+
+ /*
+ * If HCI logging is enabled for this channel, copy the data to
+ * the HCI logging output.
+ */
+ logger = core_info->users.hci_logger;
+ if (!use_logger || !logger)
+ goto transmit;
+
+ /*
+ * Alloc a new sk_buff and copy the data into it. Then send it to
+ * the HCI logger.
+ */
+ skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC);
+ if (!skb_log) {
+ CG2900_ERR("Couldn't allocate skb_log");
+ goto transmit;
+ }
+
+ memcpy(skb_put(skb_log, skb->len), skb->data, skb->len);
+ skb_log->data[0] = (u8) LOGGER_DIRECTION_TX;
+
+ if (logger->cb->read_cb)
+ logger->cb->read_cb(logger, skb_log);
+
+transmit:
+ CG2900_DBG_DATA_CONTENT("Length: %d Data: %02X %02X %02X %02X %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], skb->data[8],
+ skb->data[9], skb->data[10], skb->data[11]);
+
+ if (trans_info && trans_info->cb.write) {
+ err = trans_info->cb.write(&trans_info->dev, skb);
+ if (err)
+ CG2900_ERR("Transport write failed (%d)", err);
+ } else {
+ CG2900_ERR("No way to write to chip");
+ err = -EPERM;
+ }
+
+ if (err)
+ kfree_skb(skb);
+}
+
+/**
+ * create_and_send_bt_cmd() - Copy and send sk_buffer.
+ * @data: Data to send.
+ * @length: Length in bytes of data.
+ *
+ * The create_and_send_bt_cmd() function allocates sk_buffer, copy supplied
+ * data to it, and send the sk_buffer to the transport.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+ struct sk_buff *skb;
+
+ skb = cg2900_alloc_skb(length, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buff with length %d",
+ length);
+ return;
+ }
+
+ memcpy(skb_put(skb, length), data, length);
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = HCI_BT_CMD_H4_CHANNEL;
+
+ transmit_skb_to_chip(skb, core_info->hci_logger_config.bt_cmd_enable);
+}
+
+/**
+ * chip_not_detected() - Called when it is not possible to detect the chip.
+ *
+ * This function sets chip information to default values if it is not possible
+ * to read out information from the chip. This is common when running module
+ * tests.
+ */
+static void chip_not_detected(void)
+{
+ struct list_head *cursor;
+ struct chip_handler_item *tmp;
+
+ CG2900_ERR("Could not read out revision from the chip. This is "
+ "typical when running stubbed CG2900.\n"
+ "Switching to default value:\n"
+ "\tman 0x%04X\n"
+ "\trev 0x%04X\n"
+ "\tsub 0x%04X",
+ default_manufacturer,
+ default_hci_revision,
+ default_sub_version);
+
+ core_info->chip_dev.chip.manufacturer = default_manufacturer;
+ core_info->chip_dev.chip.hci_revision = default_hci_revision;
+ core_info->chip_dev.chip.hci_sub_version = default_sub_version;
+
+ memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb));
+
+ /* Find the handler for our default chip */
+ list_for_each(cursor, &chip_handlers) {
+ tmp = list_entry(cursor, struct chip_handler_item, list);
+ if (tmp->cb.check_chip_support(&(core_info->chip_dev))) {
+ CG2900_INFO("Chip handler found");
+ SET_BOOT_STATE(BOOT_READY);
+ break;
+ }
+ }
+}
+
+/**
+ * enable_hci_logger() - Enable HCI logger for each device.
+ * @skb: Received sk buffer.
+ *
+ * The enable_hci_logger() change HCI logger configuration for all registered
+ * devices.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCES if bad structure was supplied.
+ */
+static int enable_hci_logger(struct sk_buff *skb)
+{
+ struct cg2900_users *users;
+ struct cg2900_hci_logger_config *config;
+
+ if (skb->len != sizeof(*config)) {
+ CG2900_ERR("Trying to configure HCI logger with bad structure");
+ return -EACCES;
+ }
+
+ users = &(core_info->users);
+ config = &(core_info->hci_logger_config);
+
+ /* First store the logger config */
+ memcpy(config, skb->data, sizeof(*config));
+
+ /* Then go through all devices and set the right settings */
+ if (users->bt_cmd)
+ users->bt_cmd->logger_enabled = config->bt_cmd_enable;
+ if (users->bt_audio)
+ users->bt_audio->logger_enabled = config->bt_audio_enable;
+ if (users->bt_acl)
+ users->bt_acl->logger_enabled = config->bt_acl_enable;
+ if (users->bt_evt)
+ users->bt_evt->logger_enabled = config->bt_evt_enable;
+ if (users->fm_radio)
+ users->fm_radio->logger_enabled = config->fm_radio_enable;
+ if (users->fm_radio_audio)
+ users->fm_radio_audio->logger_enabled =
+ config->fm_radio_audio_enable;
+ if (users->gnss)
+ users->gnss->logger_enabled = config->gnss_enable;
+
+ kfree_skb(skb);
+ return 0;
+}
+
+/**
+ * find_bt_audio_user() - Check if data packet is an audio related packet.
+ * @h4_channel: H4 channel.
+ * @dev: Stored CG2900 device.
+ * @skb: skb with received packet.
+ * Returns:
+ * 0 - if no error occurred.
+ * -ENXIO - if cg2900_device not found.
+ */
+static int find_bt_audio_user(int h4_channel, struct cg2900_device **dev,
+ const struct sk_buff * const skb)
+{
+ if (core_info->chip_dev.cb.is_bt_audio_user &&
+ core_info->chip_dev.cb.is_bt_audio_user(h4_channel, skb)) {
+ *dev = core_info->users.bt_audio;
+ if (!(*dev)) {
+ CG2900_ERR("H:4 channel not registered in core_info: "
+ "0x%X", h4_channel);
+ return -ENXIO;
+ }
+ }
+ return 0;
+}
+
+/**
+ * find_fm_audio_user() - Check if data packet is an audio related packet.
+ * @h4_channel: H4 channel.
+ * @dev: Stored CG2900 device.
+ * @skb: skb with received packet.
+ * Returns:
+ * 0 if no error occurred.
+ * -ENXIO if cg2900_device not found.
+ */
+static int find_fm_audio_user(int h4_channel, struct cg2900_device **dev,
+ const struct sk_buff * const skb)
+{
+ if (core_info->chip_dev.cb.is_fm_audio_user &&
+ core_info->chip_dev.cb.is_fm_audio_user(h4_channel, skb)) {
+ *dev = core_info->users.fm_radio_audio;
+ if (!(*dev)) {
+ CG2900_ERR("H:4 channel not registered in core_info: "
+ "0x%X", h4_channel);
+ return -ENXIO;
+ }
+ }
+ return 0;
+}
+
+/**
+ * find_h4_user() - Get H4 user based on supplied H4 channel.
+ * @h4_channel: H4 channel.
+ * @dev: Stored CG2900 device.
+ * @skb: (optional) skb with received packet. Set to NULL if NA.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if bad channel is supplied or no user was found.
+ * -ENXIO if channel is audio channel but not registered with CG2900.
+ */
+static int find_h4_user(int h4_channel, struct cg2900_device **dev,
+ const struct sk_buff * const skb)
+{
+ int err = 0;
+ struct cg2900_users *users = &(core_info->users);
+ struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+
+ if (h4_channel == chan->bt_cmd_channel) {
+ *dev = users->bt_cmd;
+ } else if (h4_channel == chan->bt_acl_channel) {
+ *dev = users->bt_acl;
+ } else if (h4_channel == chan->bt_evt_channel) {
+ *dev = users->bt_evt;
+ /* Check if it's event generated by previously sent audio user
+ * command. If so then that event should be dispatched to audio
+ * user*/
+ err = find_bt_audio_user(h4_channel, dev, skb);
+ } else if (h4_channel == chan->gnss_channel) {
+ *dev = users->gnss;
+ } else if (h4_channel == chan->fm_radio_channel) {
+ *dev = users->fm_radio;
+ /* Check if it's an event generated by previously sent audio
+ * user command. If so then that event should be dispatched to
+ * audio user */
+ err = find_fm_audio_user(h4_channel, dev, skb);
+ } else if (h4_channel == chan->debug_channel) {
+ *dev = users->debug;
+ } else if (h4_channel == chan->ste_tools_channel) {
+ *dev = users->ste_tools;
+ } else if (h4_channel == chan->hci_logger_channel) {
+ *dev = users->hci_logger;
+ } else if (h4_channel == chan->us_ctrl_channel) {
+ *dev = users->us_ctrl;
+ } else if (h4_channel == chan->core_channel) {
+ *dev = users->core;
+ } else {
+ *dev = NULL;
+ CG2900_ERR("Bad H:4 channel supplied: 0x%X", h4_channel);
+ return -EINVAL;
+ }
+
+ return err;
+}
+
+/**
+ * add_h4_user() - Add H4 user to user storage based on supplied H4 channel.
+ * @dev: Stored CG2900 device.
+ * @name: Device name to identify different devices that are using
+ * the same H4 channel.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer or bad channel is supplied.
+ * -EBUSY if there already is a user for supplied channel.
+ */
+static int add_h4_user(struct cg2900_device *dev, const char * const name)
+{
+ int err = 0;
+ struct cg2900_users *users = &(core_info->users);
+ struct cg2900_hci_logger_config *config =
+ &(core_info->hci_logger_config);
+ struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+
+ if (!dev) {
+ CG2900_ERR("NULL device supplied");
+ return -EINVAL;
+ }
+
+ if (dev->h4_channel == chan->bt_cmd_channel) {
+ if (!users->bt_cmd &&
+ 0 == strncmp(name, CG2900_BT_CMD, CG2900_MAX_NAME_SIZE)) {
+ users->bt_cmd = dev;
+ users->bt_cmd->logger_enabled = config->bt_cmd_enable;
+ (users->nbr_of_users)++;
+ } else if (!users->bt_audio &&
+ 0 == strncmp(name, CG2900_BT_AUDIO,
+ CG2900_MAX_NAME_SIZE)) {
+ users->bt_audio = dev;
+ users->bt_audio->logger_enabled =
+ config->bt_audio_enable;
+ (users->nbr_of_users)++;
+ } else {
+ err = -EBUSY;
+ CG2900_ERR("name %s bt_cmd 0x%X bt_audio 0x%X",
+ name, (int)users->bt_cmd,
+ (int)users->bt_audio);
+ }
+ } else if (dev->h4_channel == chan->bt_acl_channel) {
+ if (!users->bt_acl) {
+ users->bt_acl = dev;
+ users->bt_acl->logger_enabled = config->bt_acl_enable;
+ (users->nbr_of_users)++;
+ } else {
+ err = -EBUSY;
+ }
+ } else if (dev->h4_channel == chan->bt_evt_channel) {
+ if (!users->bt_evt) {
+ users->bt_evt = dev;
+ users->bt_evt->logger_enabled = config->bt_evt_enable;
+ (users->nbr_of_users)++;
+ } else {
+ err = -EBUSY;
+ }
+ } else if (dev->h4_channel == chan->gnss_channel) {
+ if (!users->gnss) {
+ users->gnss = dev;
+ users->gnss->logger_enabled = config->gnss_enable;
+ (users->nbr_of_users)++;
+ } else {
+ err = -EBUSY;
+ }
+ } else if (dev->h4_channel == chan->fm_radio_channel) {
+ if (!users->fm_radio &&
+ 0 == strncmp(name, CG2900_FM_RADIO,
+ CG2900_MAX_NAME_SIZE)) {
+ users->fm_radio = dev;
+ users->fm_radio->logger_enabled =
+ config->fm_radio_enable;
+ (users->nbr_of_users)++;
+ } else if (!users->fm_radio_audio &&
+ 0 == strncmp(name, CG2900_FM_RADIO_AUDIO,
+ CG2900_MAX_NAME_SIZE)) {
+ users->fm_radio_audio = dev;
+ users->fm_radio_audio->logger_enabled =
+ config->fm_radio_audio_enable;
+ (users->nbr_of_users)++;
+ } else {
+ err = -EBUSY;
+ }
+ } else if (dev->h4_channel == chan->debug_channel) {
+ if (!users->debug)
+ users->debug = dev;
+ else
+ err = -EBUSY;
+ } else if (dev->h4_channel == chan->ste_tools_channel) {
+ if (!users->ste_tools)
+ users->ste_tools = dev;
+ else
+ err = -EBUSY;
+ } else if (dev->h4_channel == chan->hci_logger_channel) {
+ if (!users->hci_logger)
+ users->hci_logger = dev;
+ else
+ err = -EBUSY;
+ } else if (dev->h4_channel == chan->us_ctrl_channel) {
+ if (!users->us_ctrl)
+ users->us_ctrl = dev;
+ else
+ err = -EBUSY;
+ } else if (dev->h4_channel == chan->core_channel) {
+ if (!users->core) {
+ (users->nbr_of_users)++;
+ users->core = dev;
+ } else {
+ err = -EBUSY;
+ }
+ } else {
+ err = -EINVAL;
+ CG2900_ERR("Bad H:4 channel supplied: 0x%X", dev->h4_channel);
+ }
+
+ if (err)
+ CG2900_ERR("H:4 channel 0x%X, not registered (%d)",
+ dev->h4_channel, err);
+
+ return err;
+}
+
+/**
+ * remove_h4_user() - Remove H4 user from user storage.
+ * @dev: Stored CG2900 device.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer is supplied, bad channel is supplied, or if there
+ * is no user for supplied channel.
+ */
+static int remove_h4_user(struct cg2900_device **dev)
+{
+ int err = 0;
+ struct cg2900_users *users = &(core_info->users);
+ struct cg2900_h4_channels *chan = &(core_info->h4_channels);
+ struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb);
+
+ if (!dev || !(*dev)) {
+ CG2900_ERR("NULL device supplied");
+ return -EINVAL;
+ }
+
+ if ((*dev)->h4_channel == chan->bt_cmd_channel) {
+ CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X",
+ (int)users->bt_cmd,
+ (int)users->bt_audio, (int)*dev);
+
+ if (*dev == users->bt_cmd) {
+ users->bt_cmd = NULL;
+ (users->nbr_of_users)--;
+ } else if (*dev == users->bt_audio) {
+ users->bt_audio = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+
+ CG2900_DBG("bt_cmd 0x%X bt_audio 0x%X dev 0x%X",
+ (int)users->bt_cmd,
+ (int)users->bt_audio, (int)*dev);
+
+ /*
+ * If both BT Command channel users are de-registered we
+ * inform the chip handler.
+ */
+ if (!users->bt_cmd && !users->bt_audio &&
+ cb->last_bt_user_removed)
+ cb->last_bt_user_removed(&(core_info->chip_dev));
+ } else if ((*dev)->h4_channel == chan->bt_acl_channel) {
+ if (*dev == users->bt_acl) {
+ users->bt_acl = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->bt_evt_channel) {
+ if (*dev == users->bt_evt) {
+ users->bt_evt = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->gnss_channel) {
+ if (*dev == users->gnss) {
+ users->gnss = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+
+ /*
+ * If the GNSS channel user is de-registered we inform
+ * the chip handler.
+ */
+ if (users->gnss == NULL && cb->last_gnss_user_removed)
+ cb->last_gnss_user_removed(&(core_info->chip_dev));
+ } else if ((*dev)->h4_channel == chan->fm_radio_channel) {
+ if (*dev == users->fm_radio) {
+ users->fm_radio = NULL;
+ (users->nbr_of_users)--;
+ } else if (*dev == users->fm_radio_audio) {
+ users->fm_radio_audio = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+
+ /*
+ * If both FM Radio channel users are de-registered we inform
+ * the chip handler.
+ */
+ if (!users->fm_radio && !users->fm_radio_audio &&
+ cb->last_fm_user_removed)
+ cb->last_fm_user_removed(&(core_info->chip_dev));
+ } else if ((*dev)->h4_channel == chan->debug_channel) {
+ if (*dev == users->debug)
+ users->debug = NULL;
+ else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->ste_tools_channel) {
+ if (*dev == users->ste_tools)
+ users->ste_tools = NULL;
+ else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->hci_logger_channel) {
+ if (*dev == users->hci_logger)
+ users->hci_logger = NULL;
+ else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->us_ctrl_channel) {
+ if (*dev == users->us_ctrl)
+ users->us_ctrl = NULL;
+ else
+ err = -EINVAL;
+ } else if ((*dev)->h4_channel == chan->core_channel) {
+ if (*dev == users->core) {
+ users->core = NULL;
+ (users->nbr_of_users)--;
+ } else
+ err = -EINVAL;
+ } else {
+ CG2900_ERR("Bad H:4 channel supplied: 0x%X",
+ (*dev)->h4_channel);
+ return -EINVAL;
+ }
+
+ if (err)
+ CG2900_ERR("Trying to remove device that was not registered");
+
+ /*
+ * Free the device even if there is an error with the device.
+ * Also set to NULL to inform caller about the free.
+ */
+ free_user_dev(dev);
+
+ return err;
+}
+
+/**
+ * chip_startup() - Start the connectivity controller and download
patches and settings.
+ */
+static void chip_startup(void)
+{
+ struct hci_command_hdr cmd;
+
+ CG2900_INFO("chip_startup");
+
+ SET_MAIN_STATE(CORE_BOOTING);
+ SET_BOOT_STATE(BOOT_NOT_STARTED);
+
+ /*
+ * Transmit HCI reset command to ensure the chip is using
+ * the correct transport
+ */
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+}
+
+/**
+ * chip_shutdown() - Reset and power the chip off.
+ */
+static void chip_shutdown(void)
+{
+ int err = 0;
+ struct trans_info *trans_info = core_info->trans_info;
+ struct cg2900_chip_callbacks *cb = &(core_info->chip_dev.cb);
+
+ CG2900_INFO("chip_shutdown");
+
+ /* First do a quick power switch of the chip to assure a good state */
+ if (trans_info && trans_info->cb.set_chip_power)
+ trans_info->cb.set_chip_power(false);
+
+ /*
+ * Wait 50ms before continuing to be sure that the chip detects
+ * chip power off.
+ */
+ schedule_timeout_interruptible(
+ msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT));
+
+ if (trans_info && trans_info->cb.set_chip_power)
+ trans_info->cb.set_chip_power(true);
+
+ /* Wait 100ms before continuing to be sure that the chip is ready */
+ schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+ /*
+ * Let the chip handler finish the reset if any callback is registered.
+ * Otherwise we are finished.
+ */
+ if (!cb->chip_shutdown) {
+ CG2900_DBG("No registered handler. Finishing shutdown.");
+ cg2900_chip_shutdown_finished(err);
+ return;
+ }
+
+ err = cb->chip_shutdown(&(core_info->chip_dev));
+ if (err) {
+ CG2900_ERR("chip_shutdown failed (%d). Finishing shutdown.",
+ err);
+ cg2900_chip_shutdown_finished(err);
+ }
+}
+
+/**
+ * handle_reset_cmd_complete_evt() - Handle a received HCI Command
Complete event for a Reset command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_reset_cmd_complete_evt(u8 *data)
+{
+ bool pkt_handled = false;
+ u8 status = data[0];
+ struct hci_command_hdr cmd;
+
+ CG2900_INFO("Received Reset complete event with status 0x%X", status);
+
+ if ((core_info->main_state == CORE_BOOTING ||
+ core_info->main_state == CORE_INITIALIZING) &&
+ core_info->boot_state == BOOT_NOT_STARTED) {
+ /* Transmit HCI Read Local Version Information command */
+ SET_BOOT_STATE(BOOT_READ_LOCAL_VERSION_INFORMATION);
+ cmd.opcode = cpu_to_le16(HCI_OP_READ_LOCAL_VERSION);
+ cmd.plen = 0; /* No parameters for HCI reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+ pkt_handled = true;
+ }
+
+ return pkt_handled;
+}
+
+/**
+ * handle_read_local_version_info_cmd_complete_evt() - Handle a
received HCI Command Complete event for a ReadLocalVersionInformation
command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_read_local_version_info_cmd_complete_evt(u8 *data)
+{
+ bool chip_handled = false;
+ struct list_head *cursor;
+ struct chip_handler_item *tmp;
+ struct local_chip_info *chip;
+ struct cg2900_chip_info *chip_info;
+ struct cg2900_chip_callbacks *cb;
+ int err;
+ struct hci_rp_read_local_version *evt;
+
+ /* Check we're in the right state */
+ if ((core_info->main_state != CORE_BOOTING &&
+ core_info->main_state != CORE_INITIALIZING) ||
+ core_info->boot_state != BOOT_READ_LOCAL_VERSION_INFORMATION)
+ return false;
+
+ /* We got an answer for our HCI command. Extract data */
+ evt = (struct hci_rp_read_local_version *)data;
+
+ /* We will handle the packet */
+ if (HCI_BT_ERROR_NO_ERROR != evt->status) {
+ CG2900_ERR("Received Read Local Version Information with "
+ "status 0x%X", evt->status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_reset(NULL);
+ return true;
+ }
+
+ /* The command worked. Store the data */
+ chip = &(core_info->local_chip_info);
+ chip->version_set = true;
+ chip->hci_version = evt->hci_ver;
+ chip->hci_revision = le16_to_cpu(evt->hci_rev);
+ chip->lmp_pal_version = evt->lmp_ver;
+ chip->manufacturer = le16_to_cpu(evt->manufacturer);
+ chip->lmp_pal_subversion = le16_to_cpu(evt->lmp_subver);
+ CG2900_DBG("Received Read Local Version Information with:\n"
+ "\thci_version: 0x%X\n"
+ "\thci_revision: 0x%X\n"
+ "\tlmp_pal_version: 0x%X\n"
+ "\tmanufacturer: 0x%X\n"
+ "\tlmp_pal_subversion: 0x%X",
+ chip->hci_version, chip->hci_revision,
+ chip->lmp_pal_version, chip->manufacturer,
+ chip->lmp_pal_subversion);
+
+ cg2900_devices_set_hci_revision(chip->hci_version,
+ chip->hci_revision,
+ chip->lmp_pal_version,
+ chip->lmp_pal_subversion,
+ chip->manufacturer);
+
+ /* Received good confirmation. Find handler for the chip. */
+ chip_info = &(core_info->chip_dev.chip);
+ chip_info->hci_revision = chip->hci_revision;
+ chip_info->hci_sub_version = chip->lmp_pal_subversion;
+ chip_info->manufacturer = chip->manufacturer;
+
+ memset(&(core_info->chip_dev.cb), 0, sizeof(core_info->chip_dev.cb));
+
+ list_for_each(cursor, &chip_handlers) {
+ tmp = list_entry(cursor, struct chip_handler_item, list);
+ chip_handled = tmp->cb.check_chip_support(
+ &(core_info->chip_dev));
+ if (chip_handled) {
+ CG2900_INFO("Chip handler found");
+ break;
+ }
+ }
+
+ if (core_info->main_state == CORE_INITIALIZING) {
+ /*
+ * We are now finished with the start-up during HwRegistered
+ * operation.
+ */
+ SET_BOOT_STATE(BOOT_READY);
+ wake_up_interruptible(&main_wait_queue);
+ } else if (!chip_handled) {
+ CG2900_INFO("No chip handler found. Start-up complete");
+ SET_BOOT_STATE(BOOT_READY);
+ cg2900_chip_startup_finished(0);
+ } else {
+ cb = &(core_info->chip_dev.cb);
+ if (!cb->chip_startup)
+ cg2900_chip_startup_finished(0);
+ else {
+ err = cb->chip_startup(&(core_info->chip_dev));
+ if (err)
+ cg2900_chip_startup_finished(err);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if data should be handled in CG2900 Core.
+ * @skb: Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled in CG2900 Core. If so handle it correctly.
+ * Received data is always HCI BT Event.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_rx_data_bt_evt(struct sk_buff *skb)
+{
+ bool pkt_handled = false;
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ struct hci_event_hdr *evt;
+ struct hci_ev_cmd_complete *cmd_complete;
+ u16 op_code;
+
+ evt = (struct hci_event_hdr *)data;
+
+ /* First check the event code */
+ if (HCI_EV_CMD_COMPLETE != evt->evt)
+ return false;
+
+ data += sizeof(*evt);
+ cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+ op_code = le16_to_cpu(cmd_complete->opcode);
+
+ CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+ data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+ if (op_code == HCI_OP_RESET)
+ pkt_handled = handle_reset_cmd_complete_evt(data);
+ else if (op_code == HCI_OP_READ_LOCAL_VERSION)
+ pkt_handled =
+ handle_read_local_version_info_cmd_complete_evt(data);
+
+ if (pkt_handled)
+ kfree_skb(skb);
+
+ return pkt_handled;
+}
+
+/**
+ * test_char_dev_tx_received() - Handle data received from CG2900 Core.
+ * @dev: Current transport device information.
+ * @skb: Buffer with data coming form device.
+ */
+static int test_char_dev_tx_received(struct cg2900_trans_dev *dev,
+ struct sk_buff *skb)
+{
+ skb_queue_tail(&core_info->test_char_dev->rx_queue, skb);
+ wake_up_interruptible(&char_wait_queue);
+ return 0;
+}
+
+/**
+ * test_char_dev_open() - User space char device has been opened.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCES if transport already exists.
+ * -ENOMEM if allocation fails.
+ * Errors from create_work_item.
+ */
+static int test_char_dev_open(struct inode *inode, struct file *filp)
+{
+ struct cg2900_trans_callbacks cb = {
+ .write = test_char_dev_tx_received,
+ .open = NULL,
+ .close = NULL,
+ .set_chip_power = NULL
+ };
+
+ CG2900_INFO("test_char_dev_open");
+ return cg2900_register_trans_driver(&cb, NULL);
+}
+
+/**
+ * test_char_dev_release() - User space char device has been closed.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int test_char_dev_release(struct inode *inode, struct file *filp)
+{
+ /* Clean the message queue */
+ skb_queue_purge(&core_info->test_char_dev->rx_queue);
+ return cg2900_deregister_trans_driver();
+}
+
+/**
+ * test_char_dev_read() - Queue and copy buffer to user space char device.
+ * @filp: Pointer to the file struct.
+ * @buf: Received buffer.
+ * @count: Count of received data in bytes.
+ * @f_pos: Position in buffer.
+ *
+ * Returns:
+ * >= 0 is number of bytes read.
+ * -EFAULT if copy_to_user fails.
+ */
+static ssize_t test_char_dev_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct sk_buff *skb;
+ int bytes_to_copy;
+ int err;
+ struct sk_buff_head *rx_queue = &core_info->test_char_dev->rx_queue;
+
+ CG2900_INFO("test_char_dev_read");
+
+ if (skb_queue_empty(rx_queue))
+ wait_event_interruptible(char_wait_queue,
+ !(skb_queue_empty(rx_queue)));
+
+ skb = skb_dequeue(rx_queue);
+ if (!skb) {
+ CG2900_INFO("skb queue is empty - return with zero bytes");
+ bytes_to_copy = 0;
+ goto finished;
+ }
+
+ bytes_to_copy = min(count, skb->len);
+ err = copy_to_user(buf, skb->data, bytes_to_copy);
+ if (err) {
+ skb_queue_head(rx_queue, skb);
+ return -EFAULT;
+ }
+
+ skb_pull(skb, bytes_to_copy);
+
+ if (skb->len > 0)
+ skb_queue_head(rx_queue, skb);
+ else
+ kfree_skb(skb);
+
+finished:
+ return bytes_to_copy;
+}
+
+/**
+ * test_char_dev_write() - Copy buffer from user and write to CG2900 Core.
+ * @filp: Pointer to the file struct.
+ * @buf: Read buffer.
+ * @count: Size of the buffer write.
+ * @f_pos: Position in buffer.
+ *
+ * Returns:
+ * >= 0 is number of bytes written.
+ * -EFAULT if copy_from_user fails.
+ */
+static ssize_t test_char_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ struct sk_buff *skb;
+
+ CG2900_INFO("test_char_dev_write count %d", count);
+
+ /* Allocate the SKB and reserve space for the header */
+ skb = alloc_skb(count + RX_SKB_RESERVE, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Failed to alloc skb");
+ return -ENOMEM;
+ }
+ skb_reserve(skb, RX_SKB_RESERVE);
+
+ if (copy_from_user(skb_put(skb, count), buf, count)) {
+ kfree_skb(skb);
+ return -EFAULT;
+ }
+ cg2900_data_from_chip(skb);
+
+ return count;
+}
+
+/**
+ * test_char_dev_poll() - Handle POLL call to the interface.
+ * @filp: Pointer to the file struct.
+ * @wait: Poll table supplied to caller.
+ *
+ * Returns:
+ * Mask of current set POLL values (0 or (POLLIN | POLLRDNORM))
+ */
+static unsigned int test_char_dev_poll(struct file *filp, poll_table *wait)
+{
+ unsigned int mask = 0;
+
+ poll_wait(filp, &char_wait_queue, wait);
+
+ if (!(skb_queue_empty(&core_info->test_char_dev->rx_queue)))
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+/*
+ * struct test_char_dev_fops - Test char devices file operations.
+ * @read: Function that reads from the char device.
+ * @write: Function that writes to the char device.
+ * @poll: Function that handles poll call to the fd.
+ */
+static const struct file_operations test_char_dev_fops = {
+ .open = test_char_dev_open,
+ .release = test_char_dev_release,
+ .read = test_char_dev_read,
+ .write = test_char_dev_write,
+ .poll = test_char_dev_poll
+};
+
+/**
+ * test_char_dev_create() - Create a char device for testing.
+ *
+ * Creates a separate char device that will interact directly with userspace
+ * test application.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation fails.
+ * -EBUSY if device has already been allocated.
+ * Error codes from misc_register.
+ */
+static int test_char_dev_create(void)
+{
+ int err;
+
+ if (core_info->test_char_dev) {
+ CG2900_ERR("Trying to allocate test_char_dev twice");
+ return -EBUSY;
+ }
+
+ core_info->test_char_dev = kzalloc(sizeof(*(core_info->test_char_dev)),
+ GFP_KERNEL);
+ if (!core_info->test_char_dev) {
+ CG2900_ERR("Couldn't allocate test_char_dev");
+ return -ENOMEM;
+ }
+
+ /* Initialize the RX queue */
+ skb_queue_head_init(&core_info->test_char_dev->rx_queue);
+
+ /* Prepare miscdevice struct before registering the device */
+ core_info->test_char_dev->test_miscdev.minor = MISC_DYNAMIC_MINOR;
+ core_info->test_char_dev->test_miscdev.name = CG2900_CDEV_NAME;
+ core_info->test_char_dev->test_miscdev.fops = &test_char_dev_fops;
+ core_info->test_char_dev->test_miscdev.parent =
+ core_info->dev->this_device;
+
+ err = misc_register(&core_info->test_char_dev->test_miscdev);
+ if (err) {
+ CG2900_ERR("Error %d registering misc dev!", err);
+ kfree(core_info->test_char_dev);
+ core_info->test_char_dev = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * test_char_dev_destroy() - Clean up after test_char_dev_create().
+ */
+static void test_char_dev_destroy(void)
+{
+ int err;
+
+ if (!core_info->test_char_dev)
+ return;
+
+ err = misc_deregister(&core_info->test_char_dev->test_miscdev);
+ if (err)
+ CG2900_ERR("Error %d deregistering misc dev!", err);
+
+ /* Clean the message queue */
+ skb_queue_purge(&core_info->test_char_dev->rx_queue);
+
+ kfree(core_info->test_char_dev);
+ core_info->test_char_dev = NULL;
+}
+
+/**
+ * open_transport() - Open the CG2900 transport for data transfers.
+ *
+ * Returns:
+ * 0 if there is no error,
+ * -EACCES if write to transport failed,
+ * -EIO if transport has not been selected or chip did not answer
to commands.
+ */
+static int open_transport(void)
+{
+ int err = 0;
+ struct trans_info *trans_info = core_info->trans_info;
+
+ CG2900_INFO("open_transport");
+
+ if (trans_info && trans_info->cb.open) {
+ err = trans_info->cb.open(&trans_info->dev);
+ if (err)
+ CG2900_ERR("Transport open failed (%d)", err);
+ }
+
+ if (!err)
+ SET_TRANSPORT_STATE(TRANS_OPENED);
+
+ return err;
+}
+
+/**
+ * close_transport() - Close the CG2900 transport for data transfers.
+ */
+static void close_transport(void)
+{
+ struct trans_info *trans_info = core_info->trans_info;
+
+ CG2900_INFO("close_transport");
+
+ /* Check so transport has not already been removed */
+ if (TRANS_OPENED == core_info->transport_state)
+ SET_TRANSPORT_STATE(TRANS_CLOSED);
+
+ if (trans_info && trans_info->cb.close) {
+ int err = trans_info->cb.close(&trans_info->dev);
+ if (err)
+ CG2900_ERR("Transport close failed (%d)", err);
+ }
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @wq: work queue struct where the work will be added.
+ * @work_func: Work function.
+ * @data: Private data for the work.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EBUSY if not possible to queue work.
+ * -ENOMEM if allocation fails.
+ */
+static int create_work_item(struct workqueue_struct *wq, work_func_t work_func,
+ void *data)
+{
+ struct cg2900_work_struct *new_work;
+ int err;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for cg2900_work_struct!");
+ return -ENOMEM;
+ }
+
+ new_work->data = data;
+ INIT_WORK(&new_work->work, work_func);
+
+ err = queue_work(wq, &new_work->work);
+ if (!err) {
+ CG2900_ERR("Failed to queue work_struct because it's already "
+ "in the queue!");
+ kfree(new_work);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+/**
+ * work_hw_registered() - Called when the interface to HW has been established.
+ * @work: Reference to work data.
+ *
+ * Since there now is a transport identify the connected chip and decide which
+ * chip handler to use.
+ */
+static void work_hw_registered(struct work_struct *work)
+{
+ struct cg2900_work_struct *current_work = NULL;
+ bool run_shutdown = true;
+ struct cg2900_chip_callbacks *cb;
+ struct cg2900_h4_channels *chan;
+ struct trans_info *trans_info = core_info->trans_info;
+ struct hci_command_hdr cmd;
+
+ CG2900_INFO("work_hw_registered");
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct cg2900_work_struct, work);
+
+ SET_MAIN_STATE(CORE_INITIALIZING);
+ SET_BOOT_STATE(BOOT_NOT_STARTED);
+
+ /*
+ * This might look strange, but we need to read out
+ * the revision info in order to be able to shutdown the chip properly.
+ */
+ if (trans_info && trans_info->cb.set_chip_power)
+ trans_info->cb.set_chip_power(true);
+
+ /* Wait 100ms before continuing to be sure that the chip is ready */
+ schedule_timeout_interruptible(msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+ /*
+ * Transmit HCI reset command to ensure the chip is using
+ * the correct transport
+ */
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+ /* Wait up to 500 milliseconds for revision to be read out */
+ CG2900_DBG("Wait up to 500 milliseconds for revision to be read.");
+ wait_event_interruptible_timeout(main_wait_queue,
+ (BOOT_READY == core_info->boot_state),
+ msecs_to_jiffies(REVISION_READOUT_TIMEOUT));
+
+ /*
+ * If we are in BOOT_READY we have a good revision.
+ * Otherwise handle this as an error and switch to default handler.
+ */
+ if (BOOT_READY != core_info->boot_state) {
+ chip_not_detected();
+ run_shutdown = false;
+ }
+
+ /* Read out the channels for connected chip */
+ cb = &(core_info->chip_dev.cb);
+ chan = &(core_info->h4_channels);
+ if (cb->get_h4_channel) {
+ /* Get the H4 channel ID for all channels */
+ cb->get_h4_channel(CG2900_BT_CMD, &(chan->bt_cmd_channel));
+ cb->get_h4_channel(CG2900_BT_ACL, &(chan->bt_acl_channel));
+ cb->get_h4_channel(CG2900_BT_EVT, &(chan->bt_evt_channel));
+ cb->get_h4_channel(CG2900_GNSS, &(chan->gnss_channel));
+ cb->get_h4_channel(CG2900_FM_RADIO, &(chan->fm_radio_channel));
+ cb->get_h4_channel(CG2900_DEBUG, &(chan->debug_channel));
+ cb->get_h4_channel(CG2900_STE_TOOLS,
+ &(chan->ste_tools_channel));
+ cb->get_h4_channel(CG2900_HCI_LOGGER,
+ &(chan->hci_logger_channel));
+ cb->get_h4_channel(CG2900_US_CTRL, &(chan->us_ctrl_channel));
+ cb->get_h4_channel(CG2900_CORE, &(chan->core_channel));
+ }
+
+ /*
+ * Now it is time to shutdown the controller to reduce
+ * power consumption until any users register
+ */
+ if (run_shutdown)
+ chip_shutdown();
+ else
+ cg2900_chip_shutdown_finished(0);
+
+ kfree(current_work);
+}
+
+/*
+ * CG2900 API functions
+ */
+
+struct cg2900_device *cg2900_register_user(char *name,
+ struct cg2900_callbacks *cb)
+{
+ struct cg2900_device *current_dev;
+ int err;
+ struct trans_info *trans_info = core_info->trans_info;
+
+ CG2900_INFO("cg2900_register_user %s", name);
+
+ BUG_ON(!core_info);
+
+ /* Wait for state CORE_IDLE or CORE_ACTIVE. */
+ err = wait_event_interruptible_timeout(main_wait_queue,
+ (CORE_IDLE == core_info->main_state ||
+ CORE_ACTIVE == core_info->main_state),
+ msecs_to_jiffies(LINE_TOGGLE_DETECT_TIMEOUT));
+
+ if (err <= 0) {
+ if (CORE_INITIALIZING == core_info->main_state)
+ CG2900_ERR("Transport not opened");
+ else
+ CG2900_ERR("cg2900_register_user currently busy (0x%X)."
+ " Try again.", core_info->main_state);
+ return NULL;
+ }
+
+ /* Allocate device */
+ current_dev = kzalloc(sizeof(*current_dev), GFP_ATOMIC);
+ if (!current_dev) {
+ CG2900_ERR("Couldn't allocate current dev");
+ goto error_handling;
+ }
+
+ if (!core_info->chip_dev.cb.get_h4_channel) {
+ CG2900_ERR("No channel handler registered");
+ goto error_handling;
+ }
+ err = core_info->chip_dev.cb.get_h4_channel(name,
+ &(current_dev->h4_channel));
+ if (err) {
+ CG2900_ERR("Couldn't find H4 channel for %s", name);
+ goto error_handling;
+ }
+ current_dev->dev = core_info->dev->this_device;
+ current_dev->cb = kmalloc(sizeof(*(current_dev->cb)),
+ GFP_ATOMIC);
+ if (!current_dev->cb) {
+ CG2900_ERR("Couldn't allocate cb ");
+ goto error_handling;
+ }
+ memcpy((char *)current_dev->cb, (char *)cb,
+ sizeof(*(current_dev->cb)));
+
+ /* Retrieve pointer to the correct CG2900 Core user structure */
+ err = add_h4_user(current_dev, name);
+
+ if (!err) {
+ CG2900_DBG("H:4 channel 0x%X registered",
+ current_dev->h4_channel);
+ } else {
+ CG2900_ERR("H:4 channel 0x%X already registered "
+ "or other error (%d)",
+ current_dev->h4_channel, err);
+ goto error_handling;
+ }
+
+ if (CORE_ACTIVE != core_info->main_state &&
+ core_info->users.nbr_of_users == 1) {
+ /* Open transport and start-up the chip */
+ if (trans_info && trans_info->cb.set_chip_power)
+ trans_info->cb.set_chip_power(true);
+
+ /* Wait 100ms to be sure that the chip is ready */
+ schedule_timeout_interruptible(
+ msecs_to_jiffies(CHIP_READY_TIMEOUT));
+
+ err = open_transport();
+ if (err) {
+ /*
+ * Remove the user. If there is no error it will be
+ * freed as well.
+ */
+ remove_h4_user(¤t_dev);
+ goto finished;
+ }
+
+ chip_startup();
+
+ /* Wait up to 15 seconds for chip to start */
+ CG2900_DBG("Wait up to 15 seconds for chip to start..");
+ wait_event_interruptible_timeout(main_wait_queue,
+ (CORE_ACTIVE == core_info->main_state ||
+ CORE_IDLE == core_info->main_state),
+ msecs_to_jiffies(CHIP_STARTUP_TIMEOUT));
+ if (CORE_ACTIVE != core_info->main_state) {
+ CG2900_ERR("ST-Ericsson CG2900 driver failed to "
+ "start");
+
+ /* Close the transport and power off the chip */
+ close_transport();
+
+ /*
+ * Remove the user. If there is no error it will be
+ * freed as well.
+ */
+ remove_h4_user(¤t_dev);
+
+ /* Chip shut-down finished, set correct state. */
+ SET_MAIN_STATE(CORE_IDLE);
+ }
+ }
+ goto finished;
+
+error_handling:
+ free_user_dev(¤t_dev);
+finished:
+ return current_dev;
+}
+EXPORT_SYMBOL(cg2900_register_user);
+
+void cg2900_deregister_user(struct cg2900_device *dev)
+{
+ int h4_channel;
+ int err = 0;
+
+ CG2900_INFO("cg2900_deregister_user");
+
+ BUG_ON(!core_info);
+
+ if (!dev) {
+ CG2900_ERR("Calling with NULL pointer");
+ return;
+ }
+
+ h4_channel = dev->h4_channel;
+
+ /* Remove the user. If there is no error it will be freed as well */
+ err = remove_h4_user(&dev);
+ if (err) {
+ CG2900_ERR("Trying to deregister non-registered "
+ "H:4 channel 0x%X or other error %d",
+ h4_channel, err);
+ return;
+ }
+
+ CG2900_DBG("H:4 channel 0x%X deregistered", h4_channel);
+
+ if (0 != core_info->users.nbr_of_users)
+ /* This was not the last user, we're done. */
+ return;
+
+ if (CORE_IDLE == core_info->main_state)
+ /* Chip has already been shut down. */
+ return;
+
+ SET_MAIN_STATE(CORE_CLOSING);
+ chip_shutdown();
+
+ /* Wait up to 15 seconds for chip to shut-down */
+ CG2900_DBG("Wait up to 15 seconds for chip to shut-down..");
+ wait_event_interruptible_timeout(main_wait_queue,
+ (CORE_IDLE == core_info->main_state),
+ msecs_to_jiffies(CHIP_SHUTDOWN_TIMEOUT));
+
+ /* Force shutdown if we timed out */
+ if (CORE_IDLE != core_info->main_state) {
+ CG2900_ERR("ST-Ericsson CG2900 Core Driver was shut-down with "
+ "problems.");
+
+ /* Close the transport and power off the chip */
+ close_transport();
+
+ /* Chip shut-down finished, set correct state. */
+ SET_MAIN_STATE(CORE_IDLE);
+ }
+}
+EXPORT_SYMBOL(cg2900_deregister_user);
+
+int cg2900_reset(struct cg2900_device *dev)
+{
+ CG2900_INFO("cg2900_reset");
+
+ BUG_ON(!core_info);
+
+ SET_MAIN_STATE(CORE_RESETING);
+
+ /* Shutdown the chip */
+ chip_shutdown();
+
+ /*
+ * Inform all registered users about the reset and free the user devices
+ * Don't send reset for debug and logging channels
+ */
+ handle_reset_of_user(&(core_info->users.bt_cmd));
+ handle_reset_of_user(&(core_info->users.bt_audio));
+ handle_reset_of_user(&(core_info->users.bt_acl));
+ handle_reset_of_user(&(core_info->users.bt_evt));
+ handle_reset_of_user(&(core_info->users.fm_radio));
+ handle_reset_of_user(&(core_info->users.fm_radio_audio));
+ handle_reset_of_user(&(core_info->users.gnss));
+ handle_reset_of_user(&(core_info->users.core));
+
+ core_info->users.nbr_of_users = 0;
+
+ /* Reset finished. We are now idle until first user is registered */
+ SET_MAIN_STATE(CORE_IDLE);
+
+ /*
+ * Send wake-up since this might have been called from a failed boot.
+ * No harm done if it is a CG2900 Core user who called.
+ */
+ wake_up_interruptible(&main_wait_queue);
+
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_reset);
+
+struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority)
+{
+ struct sk_buff *skb;
+
+ CG2900_INFO("cg2900_alloc_skb");
+ CG2900_DBG("size %d bytes", size);
+
+ /* Allocate the SKB and reserve space for the header */
+ skb = alloc_skb(size + CG2900_SKB_RESERVE, priority);
+ if (skb)
+ skb_reserve(skb, CG2900_SKB_RESERVE);
+
+ return skb;
+}
+EXPORT_SYMBOL(cg2900_alloc_skb);
+
+int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb)
+{
+ int err = 0;
+ u8 *h4_header;
+ struct cg2900_chip_callbacks *cb;
+
+ CG2900_DBG_DATA("cg2900_write");
+
+ BUG_ON(!core_info);
+
+ if (!dev) {
+ CG2900_ERR("cg2900_write with no device");
+ return -EINVAL;
+ }
+
+ if (!skb) {
+ CG2900_ERR("cg2900_write with no sk_buffer");
+ return -EINVAL;
+ }
+
+ CG2900_DBG_DATA("Length %d bytes", skb->len);
+
+ if (core_info->h4_channels.hci_logger_channel == dev->h4_channel) {
+ /*
+ * Treat the HCI logger write differently.
+ * A write can only mean a change of configuration.
+ */
+ err = enable_hci_logger(skb);
+ } else if (core_info->h4_channels.core_channel == dev->h4_channel) {
+ CG2900_ERR("Not possible to write data on core channel, "
+ "it only supports enable / disable chip");
+ err = -EPERM;
+ } else if (CORE_ACTIVE == core_info->main_state) {
+ /*
+ * Move the data pointer to the H:4 header position and
+ * store the H4 header.
+ */
+ h4_header = skb_push(skb, CG2900_SKB_RESERVE);
+ *h4_header = (u8)dev->h4_channel;
+
+ /*
+ * Check if the chip handler wants to handle this packet.
+ * If not, send it to the transport.
+ */
+ cb = &(core_info->chip_dev.cb);
+ if (!cb->data_to_chip ||
+ !(cb->data_to_chip(&(core_info->chip_dev), dev, skb)))
+ transmit_skb_to_chip(skb, dev->logger_enabled);
+ } else {
+ CG2900_ERR("Trying to transmit data when CG2900 Core is not "
+ "active");
+ err = -EACCES;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_write);
+
+bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data)
+{
+ BUG_ON(!core_info);
+
+ if (!rev_data) {
+ CG2900_ERR("Calling with rev_data NULL");
+ return false;
+ }
+
+ if (!core_info->local_chip_info.version_set)
+ return false;
+
+ rev_data->revision = core_info->local_chip_info.hci_revision;
+ rev_data->sub_version = core_info->local_chip_info.lmp_pal_subversion;
+
+ return true;
+}
+EXPORT_SYMBOL(cg2900_get_local_revision);
+
+int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb)
+{
+ struct chip_handler_item *item;
+
+ CG2900_INFO("cg2900_register_chip_driver");
+
+ if (!cb) {
+ CG2900_ERR("NULL supplied as cb");
+ return -EINVAL;
+ }
+
+ item = kzalloc(sizeof(*item), GFP_ATOMIC);
+ if (!item) {
+ CG2900_ERR("Failed to alloc memory!");
+ return -ENOMEM;
+ }
+
+ memcpy(&(item->cb), cb, sizeof(cb));
+ list_add_tail(&item->list, &chip_handlers);
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_register_chip_driver);
+
+int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb, void *data)
+{
+ int err;
+
+ BUG_ON(!core_info);
+
+ CG2900_INFO("cg2900_register_trans_driver");
+
+ if (core_info->trans_info) {
+ CG2900_ERR("trans_info already exists");
+ return -EACCES;
+ }
+
+ core_info->trans_info = kzalloc(sizeof(*(core_info->trans_info)),
+ GFP_KERNEL);
+ if (!core_info->trans_info) {
+ CG2900_ERR("Could not allocate trans_info");
+ return -ENOMEM;
+ }
+
+ memcpy(&(core_info->trans_info->cb), cb, sizeof(*cb));
+ core_info->trans_info->dev.dev = core_info->dev->this_device;
+ core_info->trans_info->dev.user_data = data;
+
+ err = create_work_item(core_info->wq, work_hw_registered, NULL);
+ if (err) {
+ CG2900_ERR("Could not create work item (%d) "
+ "work_hw_registered", err);
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_register_trans_driver);
+
+int cg2900_deregister_trans_driver(void)
+{
+ BUG_ON(!core_info);
+
+ CG2900_INFO("cg2900_deregister_trans_driver");
+
+ SET_MAIN_STATE(CORE_INITIALIZING);
+ SET_TRANSPORT_STATE(TRANS_INITIALIZING);
+
+ if (!core_info->trans_info)
+ return -EACCES;
+
+ kfree(core_info->trans_info);
+ core_info->trans_info = NULL;
+
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_deregister_trans_driver);
+
+int cg2900_chip_startup_finished(int err)
+{
+ CG2900_INFO("cg2900_chip_startup_finished (%d)", err);
+
+ if (err)
+ /* Shutdown the chip */
+ chip_shutdown();
+ else
+ SET_MAIN_STATE(CORE_ACTIVE);
+
+ wake_up_interruptible(&main_wait_queue);
+
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_chip_startup_finished);
+
+int cg2900_chip_shutdown_finished(int err)
+{
+ CG2900_INFO("cg2900_chip_shutdown_finished (%d)", err);
+
+ /* Close the transport, which will power off the chip */
+ close_transport();
+
+ /* Chip shut-down finished, set correct state and wake up the chip. */
+ SET_MAIN_STATE(CORE_IDLE);
+ wake_up_interruptible(&main_wait_queue);
+
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_chip_shutdown_finished);
+
+int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger)
+{
+ transmit_skb_to_chip(skb, use_logger);
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_send_to_chip);
+
+struct cg2900_device *cg2900_get_bt_cmd_dev(void)
+{
+ if (core_info)
+ return core_info->users.bt_cmd;
+ else
+ return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_bt_cmd_dev);
+
+struct cg2900_device *cg2900_get_fm_radio_dev(void)
+{
+ if (core_info)
+ return core_info->users.fm_radio;
+ else
+ return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_fm_radio_dev);
+
+struct cg2900_device *cg2900_get_bt_audio_dev(void)
+{
+ if (core_info)
+ return core_info->users.bt_audio;
+ else
+ return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_bt_audio_dev);
+
+struct cg2900_device *cg2900_get_fm_audio_dev(void)
+{
+ if (core_info)
+ return core_info->users.fm_radio_audio;
+ else
+ return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_fm_audio_dev);
+
+struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void)
+{
+ if (core_info)
+ return &(core_info->hci_logger_config);
+ else
+ return NULL;
+}
+EXPORT_SYMBOL(cg2900_get_hci_logger_config);
+
+unsigned long cg2900_get_sleep_timeout(void)
+{
+ if (CORE_ACTIVE != core_info->main_state || !sleep_timeout_ms)
+ return 0;
+
+ return msecs_to_jiffies(sleep_timeout_ms);
+}
+EXPORT_SYMBOL(cg2900_get_sleep_timeout);
+
+void cg2900_data_from_chip(struct sk_buff *skb)
+{
+ struct cg2900_device *dev = NULL;
+ u8 h4_channel;
+ int err = 0;
+ struct cg2900_chip_callbacks *cb;
+ struct sk_buff *skb_log;
+ struct cg2900_device *logger;
+
+ CG2900_INFO("cg2900_data_from_chip");
+
+ if (!skb) {
+ CG2900_ERR("No data supplied");
+ return;
+ }
+
+ h4_channel = *(skb->data);
+
+ /*
+ * First check if this is the response for something
+ * we have sent internally.
+ */
+ if ((core_info->main_state == CORE_BOOTING ||
+ core_info->main_state == CORE_INITIALIZING) &&
+ (HCI_BT_EVT_H4_CHANNEL == h4_channel) &&
+ handle_rx_data_bt_evt(skb)) {
+ CG2900_DBG("Received packet handled internally");
+ return;
+ }
+
+ /* Find out where to route the data */
+ err = find_h4_user(h4_channel, &dev, skb);
+
+ /* Check if the chip handler wants to deal with the packet. */
+ cb = &(core_info->chip_dev.cb);
+ if (!err && cb->data_from_chip &&
+ cb->data_from_chip(&(core_info->chip_dev), dev, skb))
+ return;
+
+ if (err || !dev) {
+ CG2900_ERR("H:4 channel: 0x%X, does not match device",
+ h4_channel);
+ kfree_skb(skb);
+ return;
+ }
+
+ /*
+ * If HCI logging is enabled for this channel, copy the data to
+ * the HCI logging output.
+ */
+ logger = core_info->users.hci_logger;
+ if (!logger || !dev->logger_enabled)
+ goto transmit;
+
+ /*
+ * Alloc a new sk_buffer and copy the data into it.
+ * Then send it to the HCI logger.
+ */
+ skb_log = alloc_skb(skb->len + 1, GFP_ATOMIC);
+ if (!skb_log) {
+ CG2900_ERR("Couldn't allocate skb_log");
+ goto transmit;
+ }
+
+ memcpy(skb_put(skb_log, skb->len), skb->data, skb->len);
+ skb_log->data[0] = (u8) LOGGER_DIRECTION_RX;
+
+ if (logger->cb->read_cb)
+ logger->cb->read_cb(logger, skb_log);
+
+transmit:
+ /* Remove the H4 header */
+ (void)skb_pull(skb, CG2900_SKB_RESERVE);
+
+ /* Call the Read callback */
+ if (dev->cb->read_cb)
+ dev->cb->read_cb(dev, skb);
+}
+EXPORT_SYMBOL(cg2900_data_from_chip);
+
+/*
+ * Module INIT and EXIT functions
+ */
+
+/**
+ * cg2900_init() - Initialize module.
+ *
+ * The cg2900_init() function initialize the transport and CG2900 Core, then
+ * register to the transport framework.
+ *
+ * Returns:
+ * 0 if success.
+ * -ENOMEM for failed alloc or structure creation.
+ * Error codes generated by cg2900_devices_init, alloc_chrdev_region,
+ * class_create, device_create, core_init, tty_register_ldisc,
+ * create_work_item, cg2900_char_devices_init.
+ */
+static int __init cg2900_init(void)
+{
+ int err;
+
+ CG2900_INFO("cg2900_init");
+
+ err = cg2900_devices_init();
+ if (err) {
+ CG2900_ERR("Couldn't initialize cg2900_devices");
+ return err;
+ }
+
+ core_info = kzalloc(sizeof(*core_info), GFP_KERNEL);
+ if (!core_info) {
+ CG2900_ERR("Couldn't allocate core_info");
+ return -ENOMEM;
+ }
+
+ /* Set the internal states */
+ core_info->main_state = CORE_INITIALIZING;
+ core_info->boot_state = BOOT_NOT_STARTED;
+ core_info->transport_state = TRANS_INITIALIZING;
+
+ /* Get the H4 channel ID for all channels */
+ core_info->h4_channels.bt_cmd_channel = HCI_BT_CMD_H4_CHANNEL;
+ core_info->h4_channels.bt_acl_channel = HCI_BT_ACL_H4_CHANNEL;
+ core_info->h4_channels.bt_evt_channel = HCI_BT_EVT_H4_CHANNEL;
+ core_info->h4_channels.gnss_channel = HCI_FM_RADIO_H4_CHANNEL;
+ core_info->h4_channels.fm_radio_channel = HCI_GNSS_H4_CHANNEL;
+
+ core_info->wq = create_singlethread_workqueue(CORE_WQ_NAME);
+ if (!core_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ENOMEM;
+ goto error_handling;
+ }
+
+ core_info->dev = kzalloc(sizeof(*(core_info->dev)), GFP_KERNEL);
+ if (!core_info->dev) {
+ CG2900_ERR("Couldn't allocate main device");
+ err = -ENOMEM;
+ goto error_handling_destroy_wq;
+ }
+
+ /* Prepare miscdevice struct before registering the device */
+ core_info->dev->minor = MISC_DYNAMIC_MINOR;
+ core_info->dev->name = CG2900_DEVICE_NAME;
+
+ err = misc_register(core_info->dev);
+ if (err) {
+ CG2900_ERR("Error %d registering main device!", err);
+ goto error_handling_dev_register;
+ }
+
+ core_info->chip_dev.dev = core_info->dev->this_device;
+
+ /* Create and add test char device. */
+ err = test_char_dev_create();
+ if (err)
+ goto error_handling_deregister;
+
+ /* Initialize the character devices */
+ err = cg2900_char_devices_init(core_info->dev);
+ if (err) {
+ CG2900_ERR("cg2900_char_devices_init failed %d", err);
+ goto error_handling_test_destroy;
+ }
+
+ return 0;
+
+error_handling_test_destroy:
+ test_char_dev_destroy();
+error_handling_deregister:
+ misc_deregister(core_info->dev);
+error_handling_dev_register:
+ kfree(core_info->dev);
+ core_info->dev = NULL;
+error_handling_destroy_wq:
+ destroy_workqueue(core_info->wq);
+error_handling:
+ kfree(core_info);
+ core_info = NULL;
+ return err;
+}
+
+/**
+ * cg2900_exit() - Remove module.
+ */
+static void __exit cg2900_exit(void)
+{
+ CG2900_INFO("cg2900_exit");
+
+ if (!core_info) {
+ CG2900_ERR("CG2900 Core not initiated");
+ return;
+ }
+
+ /* Remove initialized character devices */
+ cg2900_char_devices_exit();
+
+ test_char_dev_destroy();
+
+ /* Free the user devices */
+ free_user_dev(&(core_info->users.bt_cmd));
+ free_user_dev(&(core_info->users.bt_acl));
+ free_user_dev(&(core_info->users.bt_evt));
+ free_user_dev(&(core_info->users.fm_radio));
+ free_user_dev(&(core_info->users.gnss));
+ free_user_dev(&(core_info->users.debug));
+ free_user_dev(&(core_info->users.ste_tools));
+ free_user_dev(&(core_info->users.hci_logger));
+ free_user_dev(&(core_info->users.us_ctrl));
+ free_user_dev(&(core_info->users.core));
+
+ misc_deregister(core_info->dev);
+ kfree(core_info->dev);
+ core_info->dev = NULL;
+
+ destroy_workqueue(core_info->wq);
+
+ kfree(core_info);
+ core_info = NULL;
+
+ cg2900_devices_exit();
+}
+
+module_init(cg2900_init);
+module_exit(cg2900_exit);
+
+module_param(sleep_timeout_ms, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(sleep_timeout_ms,
+ "Sleep timeout for data transmissions:\n"
+ "\t0 = disable <default>\n"
+ "\t>0 = sleep timeout in milliseconds");
+
+module_param(cg2900_debug_level, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(cg2900_debug_level,
+ "Debug level. Default 1. Possible values:\n"
+ "\t0 = No debug\n"
+ "\t1 = Error prints\n"
+ "\t10 = General info, e.g. function entries\n"
+ "\t20 = Debug info, e.g. steps in a functionality\n"
+ "\t25 = Data info, i.e. prints when data is transferred\n"
+ "\t30 = Data content, i.e. contents of the transferred data");
+
+module_param_array(bd_address, byte, &bd_addr_count,
+ S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(bd_address,
+ "Bluetooth Device address. "
+ "Default 0x00 0x80 0xDE 0xAD 0xBE 0xEF. "
+ "Enter as comma separated value.");
+
+module_param(default_hci_revision, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_hci_revision,
+ "Default HCI revision according to Bluetooth Assigned "
+ "Numbers.");
+
+module_param(default_manufacturer, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_manufacturer,
+ "Default Manufacturer according to Bluetooth Assigned "
+ "Numbers.");
+
+module_param(default_sub_version, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(default_sub_version,
+ "Default HCI sub-version according to Bluetooth Assigned "
+ "Numbers.");
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 CG2900 Connectivity
Device Driver");
diff --git a/drivers/mfd/cg2900/cg2900_core.h b/drivers/mfd/cg2900/cg2900_core.h
new file mode 100644
index 0000000..dd07305
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_core.h
@@ -0,0 +1,303 @@
+/*
+ * drivers/mfd/cg2900/cg2900_core.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#ifndef _CG2900_CORE_H_
+#define _CG2900_CORE_H_
+
+#include <linux/skbuff.h>
+#include <linux/device.h>
+
+/* Reserve 1 byte for the HCI H:4 header */
+#define CG2900_SKB_RESERVE 1
+
+#define BT_BDADDR_SIZE 6
+
+struct cg2900_h4_channels {
+ int bt_cmd_channel;
+ int bt_acl_channel;
+ int bt_evt_channel;
+ int gnss_channel;
+ int fm_radio_channel;
+ int debug_channel;
+ int ste_tools_channel;
+ int hci_logger_channel;
+ int us_ctrl_channel;
+ int core_channel;
+};
+
+/**
+ * struct cg2900_hci_logger_config - Configures the HCI logger.
+ * @bt_cmd_enable: Enable BT command logging.
+ * @bt_acl_enable: Enable BT ACL logging.
+ * @bt_evt_enable: Enable BT event logging.
+ * @gnss_enable: Enable GNSS logging.
+ * @fm_radio_enable: Enable FM radio logging.
+ * @bt_audio_enable: Enable BT audio command logging.
+ * @fm_radio_audio_enable: Enable FM radio audio command logging.
+ *
+ * Set using cg2900_write on CHANNEL_HCI_LOGGER H4 channel.
+ */
+struct cg2900_hci_logger_config {
+ bool bt_cmd_enable;
+ bool bt_acl_enable;
+ bool bt_evt_enable;
+ bool gnss_enable;
+ bool fm_radio_enable;
+ bool bt_audio_enable;
+ bool fm_radio_audio_enable;
+};
+
+/**
+ * struct cg2900_chip_info - Chip info structure.
+ * @manufacturer: Chip manufacturer.
+ * @hci_revision: Chip revision, i.e. which chip is this.
+ * @hci_sub_version: Chip sub-version, i.e. which tape-out is this.
+ *
+ * Note that these values match the Bluetooth Assigned Numbers,
+ * see http://www.bluetooth.org/
+ */
+struct cg2900_chip_info {
+ int manufacturer;
+ int hci_revision;
+ int hci_sub_version;
+};
+
+struct cg2900_chip_dev;
+
+/**
+ * struct cg2900_chip_callbacks - Callback functions registered by
chip handler.
+ * @chip_startup: Called when chip is started up.
+ * @chip_shutdown: Called when chip is shut down.
+ * @data_to_chip: Called when data shall be transmitted to chip.
+ * Return true when CG2900 Core shall not send it
+ * to chip.
+ * @data_from_chip: Called when data shall be transmitted to user.
+ * Return true when packet is taken care of by
+ * Return chip return handler.
+ * @get_h4_channel: Connects channel name with H:4 channel number.
+ * @is_bt_audio_user: Return true if current packet is for
+ * the BT audio user.
+ * @is_fm_audio_user: Return true if current packet is for
+ * the FM audio user.
+ * @last_bt_user_removed: Last BT channel user has been removed.
+ * @last_fm_user_removed: Last FM channel user has been removed.
+ * @last_gnss_user_removed: Last GNSS channel user has been removed.
+ *
+ * Note that some callbacks may be NULL. They must always be NULL
checked before
+ * calling.
+ */
+struct cg2900_chip_callbacks {
+ int (*chip_startup)(struct cg2900_chip_dev *dev);
+ int (*chip_shutdown)(struct cg2900_chip_dev *dev);
+ bool (*data_to_chip)(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb);
+ bool (*data_from_chip)(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb);
+ int (*get_h4_channel)(char *name, int *h4_channel);
+ bool (*is_bt_audio_user)(int h4_channel,
+ const struct sk_buff * const skb);
+ bool (*is_fm_audio_user)(int h4_channel,
+ const struct sk_buff * const skb);
+ void (*last_bt_user_removed)(struct cg2900_chip_dev *dev);
+ void (*last_fm_user_removed)(struct cg2900_chip_dev *dev);
+ void (*last_gnss_user_removed)(struct cg2900_chip_dev *dev);
+};
+
+/**
+ * struct cg2900_chip_dev - Chip handler info structure.
+ * @dev: Parent device from CG2900 Core.
+ * @chip: Chip info such as manufacturer.
+ * @cb: Callback structure for the chip handler.
+ * @user_data: Arbitrary data set by chip handler.
+ */
+struct cg2900_chip_dev {
+ struct device *dev;
+ struct cg2900_chip_info chip;
+ struct cg2900_chip_callbacks cb;
+ void *user_data;
+};
+
+/**
+ * struct cg2900_id_callbacks - Chip handler identification callbacks.
+ * @check_chip_support: Called when chip is connected. If chip is supported by
+ * driver, return true and fill in @callbacks in @dev.
+ *
+ * Note that the callback may be NULL. It must always be NULL checked before
+ * calling.
+ */
+struct cg2900_id_callbacks {
+ bool (*check_chip_support)(struct cg2900_chip_dev *dev);
+};
+
+/**
+ * struct cg2900_trans_dev - CG2900 transport info structure.
+ * @dev: Parent device from CG2900 Core.
+ * @user_data: Arbitrary data set by chip handler.
+ */
+struct cg2900_trans_dev {
+ struct device *dev;
+ void *user_data;
+};
+
+/**
+ * struct cg2900_trans_callbacks - Callback functions registered by transport.
+ * @open: CG2900 Core needs a transport.
+ * @close: CG2900 Core does not need a transport.
+ * @write: CG2900 Core transmits to the chip.
+ * @set_chip_power: CG2900 Core enables or disables the chip.
+ *
+ * Note that some callbacks may be NULL. They must always be NULL
checked before
+ * calling.
+ */
+struct cg2900_trans_callbacks {
+ int (*open)(struct cg2900_trans_dev *dev);
+ int (*close)(struct cg2900_trans_dev *dev);
+ int (*write)(struct cg2900_trans_dev *dev, struct sk_buff *skb);
+ void (*set_chip_power)(bool chip_on);
+};
+
+/**
+ * cg2900_register_chip_driver() - Register a chip handler.
+ * @cb: Callbacks to call when chip is connected.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL is supplied as @cb.
+ * -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_register_chip_driver(struct cg2900_id_callbacks *cb);
+
+/**
+ * cg2900_register_trans_driver() - Register a transport driver.
+ * @cb: Callbacks to call when chip is connected.
+ * @data: Arbitrary data used by the transport driver.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL is supplied as @cb.
+ * -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_register_trans_driver(struct cg2900_trans_callbacks *cb,
+ void *data);
+
+/**
+ * cg2900_deregister_trans_driver() - Deregister a transport driver.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL is supplied as @cb.
+ * -ENOMEM if allocation fails or work queue can't be created.
+ */
+extern int cg2900_deregister_trans_driver(void);
+
+/**
+ * cg2900_chip_startup_finished() - Called from chip handler when
start-up is finished.
+ * @err: Result of the start-up.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+extern int cg2900_chip_startup_finished(int err);
+
+/**
+ * cg2900_chip_shutdown_finished() - Called from chip handler when
shutdown is finished.
+ * @err: Result of the shutdown.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+extern int cg2900_chip_shutdown_finished(int err);
+
+/**
+ * cg2900_send_to_chip() - Send data to chip.
+ * @skb: Packet to transmit.
+ * @use_logger: true if hci_logger should copy data content.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+extern int cg2900_send_to_chip(struct sk_buff *skb, bool use_logger);
+
+/**
+ * cg2900_get_bt_cmd_dev() - Return user of the BT command H:4 channel.
+ *
+ * Returns:
+ * User of the BT command H:4 channel.
+ * NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_bt_cmd_dev(void);
+
+/**
+ * cg2900_get_fm_radio_dev() - Return user of the FM radio H:4 channel.
+ *
+ * Returns:
+ * User of the FM radio H:4 channel.
+ * NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_fm_radio_dev(void);
+
+/**
+ * cg2900_get_bt_audio_dev() - Return user of the BT audio H:4 channel.
+ *
+ * Returns:
+ * User of the BT audio H:4 channel.
+ * NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_bt_audio_dev(void);
+
+/**
+ * cg2900_get_fm_audio_dev() - Return user of the FM audio H:4 channel.
+ *
+ * Returns:
+ * User of the FM audio H:4 channel.
+ * NULL if no user is registered.
+ */
+extern struct cg2900_device *cg2900_get_fm_audio_dev(void);
+
+/**
+ * cg2900_get_hci_logger_config() - Return HCI Logger configuration.
+ *
+ * Returns:
+ * HCI logger configuration.
+ * NULL if CG2900 Core has not yet been started.
+ */
+extern struct cg2900_hci_logger_config *cg2900_get_hci_logger_config(void);
+
+/**
+ * cg2900_get_sleep_timeout() - Return sleep timeout in jiffies.
+ *
+ * Returns:
+ * Sleep timeout in jiffies. 0 means that sleep timeout shall not be used.
+ */
+extern unsigned long cg2900_get_sleep_timeout(void);
+
+/**
+ * cg2900_data_from_chip() - Data received from connectivity controller.
+ * @skb: Data packet
+ *
+ * The cg2900_data_from_chip() function checks which channel
+ * the data was received on and send to the right user.
+ */
+extern void cg2900_data_from_chip(struct sk_buff *skb);
+
+/* module_param declared in cg2900_core.c */
+extern u8 bd_address[BT_BDADDR_SIZE];
+extern int default_manufacturer;
+extern int default_hci_revision;
+extern int default_sub_version;
+
+#endif /* _CG2900_CORE_H_ */
diff --git a/drivers/mfd/cg2900/cg2900_debug.h
b/drivers/mfd/cg2900/cg2900_debug.h
new file mode 100644
index 0000000..a3aeeaf
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_debug.h
@@ -0,0 +1,77 @@
+/*
+ * drivers/mfd/cg2900/cg2900_debug.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Debug functionality for the Linux Bluetooth HCI H:4 Driver for ST-Ericsson
+ * CG2900 connectivity controller.
+ */
+
+#ifndef _CG2900_DEBUG_H_
+#define _CG2900_DEBUG_H_
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#define CG2900_DEFAULT_DEBUG_LEVEL 1
+
+/* module_param declared in cg2900_core.c */
+extern int cg2900_debug_level;
+
+#if defined(NDEBUG) || CG2900_DEFAULT_DEBUG_LEVEL == 0
+ #define CG2900_DBG_DATA_CONTENT(fmt, arg...)
+ #define CG2900_DBG_DATA(fmt, arg...)
+ #define CG2900_DBG(fmt, arg...)
+ #define CG2900_INFO(fmt, arg...)
+ #define CG2900_ERR(fmt, arg...)
+#else
+ #define CG2900_DBG_DATA_CONTENT(fmt, arg...) \
+ do { \
+ if (cg2900_debug_level >= 30) \
+ printk(KERN_DEBUG "CG2900 %s: " fmt "\n" , __func__ , \
+ ## arg); \
+ } while (0)
+
+ #define CG2900_DBG_DATA(fmt, arg...) \
+ do { \
+ if (cg2900_debug_level >= 25) \
+ printk(KERN_DEBUG "CG2900 %s: " fmt "\n" , __func__ , \
+ ## arg); \
+ } while (0)
+
+ #define CG2900_DBG(fmt, arg...) \
+ do { \
+ if (cg2900_debug_level >= 20) \
+ printk(KERN_DEBUG "CG2900 %s: " fmt "\n" , __func__ , \
+ ## arg); \
+ } while (0)
+
+ #define CG2900_INFO(fmt, arg...) \
+ do { \
+ if (cg2900_debug_level >= 10) \
+ printk(KERN_INFO "CG2900: " fmt "\n" , ## arg); \
+ } while (0)
+
+ #define CG2900_ERR(fmt, arg...) \
+ do { \
+ if (cg2900_debug_level >= 1) \
+ printk(KERN_ERR "CG2900 %s: " fmt "\n" , __func__ , \
+ ## arg); \
+ } while (0)
+
+#endif /* NDEBUG */
+
+#define CG2900_SET_STATE(__name, __var, __new_state) \
+do { \
+ CG2900_DBG("New %s: 0x%X", __name, (uint32_t)__new_state); \
+ __var = __new_state; \
+} while (0)
+
+#endif /* _CG2900_DEBUG_H_ */
diff --git a/drivers/mfd/cg2900/hci_defines.h b/drivers/mfd/cg2900/hci_defines.h
new file mode 100644
index 0000000..e7f7c30
--- /dev/null
+++ b/drivers/mfd/cg2900/hci_defines.h
@@ -0,0 +1,102 @@
+/*
+ * drivers/mfd/cg2900/hci_defines.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI defines for ST-Ericsson CG2900 connectivity controller.
+ */
+
+#ifndef _BLUETOOTH_DEFINES_H_
+#define _BLUETOOTH_DEFINES_H_
+
+#include <linux/types.h>
+
+/* H:4 offset in an HCI packet */
+#define HCI_H4_POS 0
+#define HCI_H4_SIZE 1
+
+/* Standardized Bluetooth H:4 channels */
+#define HCI_BT_CMD_H4_CHANNEL 0x01
+#define HCI_BT_ACL_H4_CHANNEL 0x02
+#define HCI_BT_SCO_H4_CHANNEL 0x03
+#define HCI_BT_EVT_H4_CHANNEL 0x04
+
+/* Bluetooth Opcode Group Field (OGF) */
+#define HCI_BT_OGF_LINK_CTRL 0x01
+#define HCI_BT_OGF_LINK_POLICY 0x02
+#define HCI_BT_OGF_CTRL_BB 0x03
+#define HCI_BT_OGF_LINK_INFO 0x04
+#define HCI_BT_OGF_LINK_STATUS 0x05
+#define HCI_BT_OGF_LINK_TESTING 0x06
+#define HCI_BT_OGF_VS 0x3F
+
+/* Bluetooth Opcode Command Field (OCF) */
+#define HCI_BT_OCF_READ_LOCAL_VERSION_INFO 0x0001
+#define HCI_BT_OCF_RESET 0x0003
+
+/* Bluetooth HCI command OpCodes in LSB/MSB fashion */
+#define HCI_BT_RESET_CMD_LSB 0x03
+#define HCI_BT_RESET_CMD_MSB 0x0C
+#define HCI_BT_READ_LOCAL_VERSION_CMD_LSB 0x01
+#define HCI_BT_READ_LOCAL_VERSION_CMD_MSB 0x10
+
+/* Bluetooth Event OpCodes */
+#define HCI_BT_EVT_CMD_COMPLETE 0x0E
+#define HCI_BT_EVT_CMD_STATUS 0x0F
+
+/* Bluetooth Command offsets */
+#define HCI_BT_CMD_ID_POS 1
+#define HCI_BT_CMD_PARAM_LEN_POS 3
+#define HCI_BT_CMD_PARAM_POS 4
+#define HCI_BT_CMD_HDR_SIZE 4
+
+/* Bluetooth Event offsets for CG2900 users, i.e. not including H:4 channel */
+#define HCI_BT_EVT_ID_POS 0
+#define HCI_BT_EVT_LEN_POS 1
+#define HCI_BT_EVT_CMD_COMPL_ID_POS 3
+#define HCI_BT_EVT_CMD_STATUS_ID_POS 4
+#define HCI_BT_EVT_CMD_COMPL_STATUS_POS 5
+#define HCI_BT_EVT_CMD_STATUS_STATUS_POS 2
+#define HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS 2
+#define HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS 3
+
+#define HCI_BT_EVT_CMD_COMPL_ID_POS_LSB HCI_BT_EVT_CMD_COMPL_ID_POS
+#define HCI_BT_EVT_CMD_COMPL_ID_POS_MSB
(HCI_BT_EVT_CMD_COMPL_ID_POS + 1)
+#define HCI_BT_EVT_CMD_STATUS_ID_POS_LSB HCI_BT_EVT_CMD_STATUS_ID_POS
+#define HCI_BT_EVT_CMD_STATUS_ID_POS_MSB
(HCI_BT_EVT_CMD_STATUS_ID_POS + 1)
+
+/* Bluetooth error codes */
+#define HCI_BT_ERROR_NO_ERROR 0x00
+#define HCI_BT_ERROR_CMD_DISALLOWED 0x0C
+
+/* Bluetooth lengths */
+#define HCI_BT_SEND_FILE_MAX_CHUNK_SIZE 254
+
+#define HCI_BT_RESET_LEN 3
+#define HCI_BT_RESET_PARAM_LEN 0
+#define HCI_BT_CMD_COMPLETE_NO_PARAM_LEN 4
+
+/* Utility macros */
+#define GET_U16_FROM_L_ENDIAN(__u8_lsb, __u8_msb) ((u16)(__u8_lsb | \
+ (__u8_msb << 8)))
+#define HCI_BT_MAKE_FIRST_BYTE_IN_CMD(__ocf) ((u8)(__ocf & 0x00FF))
+#define HCI_BT_MAKE_SECOND_BYTE_IN_CMD(__ogf, __ocf) ((u8)(__ogf << 2) | \
+ ((__ocf >> 8) & 0x0003))
+#define HCI_BT_ID_TO_OCF(__1st_byte, __2nd_byte) ((u16)(__1st_byte | \
+ ((__2nd_byte & 0x03) << 8)))
+#define HCI_BT_ID_TO_OGF(__1st_byte) ((u8)(__1st_byte >> 2))
+#define HCI_BT_GET_ID GET_U16_FROM_L_ENDIAN
+#define HCI_BT_EVENT_GET_ID GET_U16_FROM_L_ENDIAN
+
+/* Macros to set multibyte words to correct HCI endian (always Little
Endian) */
+#define HCI_SET_U16_DATA_LSB(__u16_data) ((u8)(__u16_data & 0x00FF))
+#define HCI_SET_U16_DATA_MSB(__u16_data) ((u8)(__u16_data >> 8))
+
+#endif /* _BLUETOOTH_DEFINES_H_ */
diff --git a/include/linux/mfd/cg2900.h b/include/linux/mfd/cg2900.h
new file mode 100644
index 0000000..9c2afc5
--- /dev/null
+++ b/include/linux/mfd/cg2900.h
@@ -0,0 +1,205 @@
+/*
+ * include/linux/mfd/cg2900.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 connectivity
+ * controller.
+ */
+
+#ifndef _CG2900_H_
+#define _CG2900_H_
+
+#include <linux/skbuff.h>
+
+#define CG2900_MAX_NAME_SIZE 30
+
+/*
+ * Channel names to use when registering to CG2900 driver
+ */
+
+/** CG2900_BT_CMD - Bluetooth HCI H4 channel for Bluetooth commands.
+ */
+#define CG2900_BT_CMD "cg2900_bt_cmd"
+
+/** CG2900_BT_ACL - Bluetooth HCI H4 channel for Bluetooth ACL data.
+ */
+#define CG2900_BT_ACL "cg2900_bt_acl"
+
+/** CG2900_BT_EVT - Bluetooth HCI H4 channel for Bluetooth events.
+ */
+#define CG2900_BT_EVT "cg2900_bt_evt"
+
+/** CG2900_FM_RADIO - Bluetooth HCI H4 channel for FM radio.
+ */
+#define CG2900_FM_RADIO "cg2900_fm_radio"
+
+/** CG2900_GNSS - Bluetooth HCI H4 channel for GNSS.
+ */
+#define CG2900_GNSS "cg2900_gnss"
+
+/** CG2900_DEBUG - Bluetooth HCI H4 channel for internal debug data.
+ */
+#define CG2900_DEBUG "cg2900_debug"
+
+/** CG2900_STE_TOOLS - Bluetooth HCI H4 channel for development tools data.
+ */
+#define CG2900_STE_TOOLS "cg2900_ste_tools"
+
+/** CG2900_HCI_LOGGER - BT channel for logging all transmitted H4 packets.
+ * Data read is copy of all data transferred on the other channels.
+ * Only write allowed is configuration of the HCI Logger.
+ */
+#define CG2900_HCI_LOGGER "cg2900_hci_logger"
+
+/** CG2900_US_CTRL - Channel for user space init and control of CG2900.
+ */
+#define CG2900_US_CTRL "cg2900_us_ctrl"
+
+/** CG2900_BT_AUDIO - HCI Channel for BT audio configuration commands.
+ * Maps to Bluetooth command and event channels.
+ */
+#define CG2900_BT_AUDIO "cg2900_bt_audio"
+
+/** CG2900_FM_RADIO_AUDIO - HCI channel for FM audio configuration commands.
+ * Maps to FM Radio channel.
+ */
+#define CG2900_FM_RADIO_AUDIO "cg2900_fm_audio"
+
+/** CG2900_CORE- Channel for keeping ST-Ericsson CG2900 enabled.
+ * Opening this channel forces the chip to stay powered.
+ * No data can be written to or read from this channel.
+ */
+#define CG2900_CORE "cg2900_core"
+
+struct cg2900_callbacks;
+
+/**
+ * struct cg2900_device - Device structure for CG2900 user.
+ * @h4_channel: HCI H:4 channel used by this device.
+ * @cb: Callback functions registered by this device.
+ * @logger_enabled: true if HCI logger is enabled for this channel,
+ * false otherwise.
+ * @user_data: Arbitrary data used by caller.
+ * @dev: Parent device this driver is connected to.
+ *
+ * Defines data needed to access an HCI channel.
+ */
+struct cg2900_device {
+ int h4_channel;
+ struct cg2900_callbacks *cb;
+ bool logger_enabled;
+ void *user_data;
+ struct device *dev;
+};
+
+/**
+ * struct cg2900_callbacks - Callback structure for CG2900 user.
+ * @read_cb: Callback function called when data is received from
+ * the connectivity controller.
+ * @reset_cb: Callback function called when the connectivity controller has
+ * been reset.
+ *
+ * Defines the callback functions provided from the caller.
+ */
+struct cg2900_callbacks {
+ void (*read_cb) (struct cg2900_device *dev, struct sk_buff *skb);
+ void (*reset_cb) (struct cg2900_device *dev);
+};
+
+/**
+ * struct cg2900_rev_data - Contains revision data for the local controller.
+ * @revision: Revision of the controller, e.g. to indicate that it is
+ * a CG2900 controller.
+ * @sub_version: Subversion of the controller, e.g. to indicate a certain
+ * tape-out of the controller.
+ *
+ * The values to match retrieved values to each controller may be
retrieved from
+ * the manufacturer.
+ */
+struct cg2900_rev_data {
+ int revision;
+ int sub_version;
+};
+
+/**
+ * cg2900_register_user() - Register CG2900 user.
+ * @name: Name of HCI H:4 channel to register to.
+ * @cb: Callback structure to use for the H:4 channel.
+ *
+ * Returns:
+ * Pointer to CG2900 device structure if successful.
+ * NULL upon failure.
+ */
+extern struct cg2900_device *cg2900_register_user(char *name,
+ struct cg2900_callbacks *cb);
+
+/**
+ * cg2900_deregister_user() - Remove registration of CG2900 user.
+ * @dev: CG2900 device.
+ */
+extern void cg2900_deregister_user(struct cg2900_device *dev);
+
+/**
+ * cg2900_reset() - Reset the CG2900 controller.
+ * @dev: CG2900 device.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCES if driver has not been initialized.
+ */
+extern int cg2900_reset(struct cg2900_device *dev);
+
+/**
+ * cg2900_alloc_skb() - Alloc an sk_buff structure for CG2900 handling.
+ * @size: Size in number of octets.
+ * @priority: Allocation priority, e.g. GFP_KERNEL.
+ *
+ * Returns:
+ * Pointer to sk_buff buffer structure if successful.
+ * NULL upon allocation failure.
+ */
+extern struct sk_buff *cg2900_alloc_skb(unsigned int size, gfp_t priority);
+
+/**
+ * cg2900_write() - Send data to the connectivity controller.
+ * @dev: CG2900 device.
+ * @skb: Data packet.
+ *
+ * The cg2900_write() function sends data to the connectivity controller.
+ * If the return value is 0 the skb will be freed by the driver,
+ * otherwise it won't be freed.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCES if driver has not been initialized or trying to write while driver
+ * is not active.
+ * -EINVAL if NULL pointer was supplied.
+ * -EPERM if operation is not permitted, e.g. trying to write to a channel
+ * that doesn't handle write operations.
+ * Error codes returned from core_enable_hci_logger.
+ */
+extern int cg2900_write(struct cg2900_device *dev, struct sk_buff *skb);
+
+/**
+ * cg2900_get_local_revision() - Read revision of the connected controller.
+ * @rev_data: Revision data structure to fill. Must be allocated by caller.
+ *
+ * The cg2900_get_local_revision() function returns the revision data of the
+ * local controller if available. If data is not available, e.g. because the
+ * controller has not yet been started this function will return false.
+ *
+ * Returns:
+ * true if revision data is available.
+ * false if no revision data is available.
+ */
+extern bool cg2900_get_local_revision(struct cg2900_rev_data *rev_data);
+
+#endif /* _CG2900_H_ */
--
1.6.3.3
^ permalink raw reply related
* [PATCH 2/6] This patch adds support for the ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:48 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for the ST-Ericsson CG2900 connectivity
controller. This patch registers into the CG2900 framework.
It handles chip startup and shutdown together with firmware download.
It also handles Bluetooth and FM flow control due to special H4 channel
management.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/mfd/Kconfig | 6 +
drivers/mfd/cg2900/Makefile | 2 +
drivers/mfd/cg2900/cg2900_chip.c | 2060 ++++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/cg2900_chip.h | 421 ++++++++
4 files changed, 2489 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/cg2900_chip.c
create mode 100644 drivers/mfd/cg2900/cg2900_chip.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index c0a6652..f1fb334 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -584,6 +584,12 @@ config MFD_CG2900
Supports multiple functionalities muxed over a Bluetooth HCI H:4 interface.
CG2900 support Bluetooth, FM radio, and GPS.
+config MFD_CG2900_CHIP
+ tristate "Support CG2900 Connectivity controller"
+ depends on MFD_CG2900
+ help
+ Support for ST-Ericsson CG2900 Connectivity Controller
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 76af761..b49bf6b 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -5,3 +5,5 @@
obj-$(CONFIG_MFD_CG2900) += cg2900.o
cg2900-objs := cg2900_core.o cg2900_char_devices.o
export-objs := cg2900_core.o
+
+obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
diff --git a/drivers/mfd/cg2900/cg2900_chip.c b/drivers/mfd/cg2900/cg2900_chip.c
new file mode 100644
index 0000000..4c06416
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.c
@@ -0,0 +1,2060 @@
+/*
+ * drivers/mfd/cg2900/cg2900_chip.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#include <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 <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "hci_defines.h"
+#include "cg2900_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+#define LINE_BUFFER_LENGTH 128
+#define FILENAME_MAX 128
+
+#define WQ_NAME "cg2900_chip_wq"
+#define PATCH_INFO_FILE "cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE "cg2900_settings_info.fw"
+
+/* Size of file chunk ID */
+#define FILE_CHUNK_ID_SIZE 1
+#define FILE_CHUNK_ID_POS 4
+
+/* Times in milliseconds */
+#define POWER_SW_OFF_WAIT 500
+
+/* State setting macros */
+#define SET_BOOT_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("boot_state", cg2900_info->boot_state, \
+ __cg2900_new_state)
+#define SET_CLOSING_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("closing_state", cg2900_info->closing_state, \
+ __cg2900_new_state)
+#define SET_FILE_LOAD_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("file_load_state", cg2900_info->file_load_state, \
+ __cg2900_new_state)
+#define SET_DOWNLOAD_STATE(__cg2900_new_state) \
+ CG2900_SET_STATE("download_state", cg2900_info->download_state, \
+ __cg2900_new_state)
+
+/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel
+ * for Bluetooth commands in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_CMD 0x01
+
+/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel
+ * for Bluetooth ACL data in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_ACL 0x02
+
+/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel
+ * for Bluetooth events in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_EVT 0x04
+
+/** CHANNEL_FM_RADIO - Bluetooth HCI H:4 channel
+ * for FM radio in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_FM_RADIO 0x08
+
+/** CHANNEL_GNSS - Bluetooth HCI H:4 channel
+ * for GNSS in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_GNSS 0x09
+
+/** CHANNEL_DEBUG - Bluetooth HCI H:4 channel
+ * for internal debug data in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_DEBUG 0x0B
+
+/** CHANNEL_STE_TOOLS - Bluetooth HCI H:4 channel
+ * for development tools data in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_STE_TOOLS 0x0D
+
+/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel
+ * for logging all transmitted H4 packets (on all channels).
+ */
+#define CHANNEL_HCI_LOGGER 0xFA
+
+/** CHANNEL_US_CTRL - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_US_CTRL 0xFC
+
+/** CHANNEL_CORE - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_CORE 0xFD
+
+/**
+ * enum boot_state - BOOT-state for CG2900 chip driver.
+ * @BOOT_NOT_STARTED: Boot has not yet started.
+ * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address
+ * has been sent.
+ * @BOOT_GET_FILES_TO_LOAD: CG2900 chip driver is retrieving file to
+ * load.
+ * @BOOT_DOWNLOAD_PATCH: CG2900 chip driver is downloading
+ * patches.
+ * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 chip driver is
activating patches
+ * and settings.
+ * @BOOT_READY: CG2900 chip driver boot is ready.
+ * @BOOT_FAILED: CG2900 chip driver boot failed.
+ */
+enum boot_state {
+ BOOT_NOT_STARTED,
+ BOOT_SEND_BD_ADDRESS,
+ BOOT_GET_FILES_TO_LOAD,
+ BOOT_DOWNLOAD_PATCH,
+ BOOT_ACTIVATE_PATCHES_AND_SETTINGS,
+ BOOT_READY,
+ BOOT_FAILED
+};
+
+/**
+ * enum closing_state - CLOSING-state for CG2900 chip driver.
+ * @CLOSING_RESET: HCI RESET_CMD has been sent.
+ * @CLOSING_POWER_SWITCH_OFF: HCI VS_POWER_SWITCH_OFF command has been sent.
+ * @CLOSING_SHUT_DOWN: We have now shut down the chip.
+ */
+enum closing_state {
+ CLOSING_RESET,
+ CLOSING_POWER_SWITCH_OFF,
+ CLOSING_SHUT_DOWN
+};
+
+/**
+ * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 chip driver.
+ * @FILE_LOAD_GET_PATCH: Loading patches.
+ * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings.
+ * @FILE_LOAD_NO_MORE_FILES: No more files to load.
+ * @FILE_LOAD_FAILED: File loading failed.
+ */
+enum file_load_state {
+ FILE_LOAD_GET_PATCH,
+ FILE_LOAD_GET_STATIC_SETTINGS,
+ FILE_LOAD_NO_MORE_FILES,
+ FILE_LOAD_FAILED
+};
+
+/**
+ * enum download_state - BOOT_DOWNLOAD state.
+ * @DOWNLOAD_PENDING: Download in progress.
+ * @DOWNLOAD_SUCCESS: Download successfully finished.
+ * @DOWNLOAD_FAILED: Downloading failed.
+ */
+enum download_state {
+ DOWNLOAD_PENDING,
+ DOWNLOAD_SUCCESS,
+ DOWNLOAD_FAILED
+};
+
+/**
+ * enum 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.
+ * @FM_RADIO_MODE_IDLE: Radio mode is Idle (default).
+ * @FM_RADIO_MODE_FMT: Radio mode is set to FMT (transmitter).
+ * @FM_RADIO_MODE_FMR: Radio mode is set to FMR (receiver).
+ */
+enum fm_radio_mode {
+ FM_RADIO_MODE_IDLE = 0,
+ FM_RADIO_MODE_FMT = 1,
+ FM_RADIO_MODE_FMR = 2
+};
+
+/**
+ * struct cg2900_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 cg2900_device_id {
+ char *name;
+ int h4_channel;
+};
+
+/**
+ * struct cg2900_skb_data - Structure for storing private data in an sk_buffer.
+ * @dev: CG2900 device for this sk_buffer.
+ */
+struct cg2900_skb_data {
+ struct cg2900_device *dev;
+};
+#define cg2900_skb_data(__skb) ((struct cg2900_skb_data *)((__skb)->cb))
+
+/**
+ * struct cg2900_info - Main info structure for CG2900 chip driver.
+ * @patch_file_name: Stores patch file name.
+ * @settings_file_name: Stores settings file name.
+ * @fw_file: Stores firmware file (patch or settings).
+ * @file_offset: Current read offset in firmware file.
+ * @chunk_id: Stores current chunk ID of write file
+ * operations.
+ * @boot_state: Current BOOT-state of CG2900 chip driver.
+ * @closing_state: Current CLOSING-state of CG2900 chip driver.
+ * @file_load_state: Current BOOT_FILE_LOAD-state of CG2900 chip
+ * driver.
+ * @download_state: Current BOOT_DOWNLOAD-state of CG2900 chip
+ * driver.
+ * @wq: CG2900 chip driver workqueue.
+ * @chip_dev: Chip handler info.
+ * @tx_bt_lock: Spinlock used to protect some global structures
+ * related to internal BT command flow control.
+ * @tx_fm_lock: Spinlock used to protect some global structures
+ * related to internal FM command flow control.
+ * @tx_fm_audio_awaiting_irpt: Indicates if an FM interrupt event related to
+ * audio driver command is expected.
+ * @fm_radio_mode: Current FM radio mode.
+ * @tx_nr_pkts_allowed_bt: Number of packets allowed to send on BT HCI CMD
+ * H4 channel.
+ * @audio_bt_cmd_op: Stores the OpCode of the last sent audio driver
+ * HCI BT CMD.
+ * @audio_fm_cmd_id: Stores the command id of the last sent
+ * HCI FM RADIO command by the fm audio user.
+ * @hci_fm_cmd_func: Stores the command function of the last sent
+ * HCI FM RADIO command by the fm radio user.
+ * @tx_queue_bt: TX queue for HCI BT commands when nr of commands
+ * allowed is 0 (CG2900 internal flow control).
+ * @tx_queue_fm: TX queue for HCI FM commands when nr of commands
+ * allowed is 0 (CG2900 internal flow control).
+ */
+struct cg2900_info {
+ char *patch_file_name;
+ char *settings_file_name;
+ const struct firmware *fw_file;
+ int file_offset;
+ u8 chunk_id;
+ enum boot_state boot_state;
+ enum closing_state closing_state;
+ enum file_load_state file_load_state;
+ enum download_state download_state;
+ struct workqueue_struct *wq;
+ struct cg2900_chip_dev chip_dev;
+ spinlock_t tx_bt_lock;
+ spinlock_t tx_fm_lock;
+ bool tx_fm_audio_awaiting_irpt;
+ enum fm_radio_mode fm_radio_mode;
+ int tx_nr_pkts_allowed_bt;
+ u16 audio_bt_cmd_op;
+ u16 audio_fm_cmd_id;
+ u16 hci_fm_cmd_func;
+ struct sk_buff_head tx_queue_bt;
+ struct sk_buff_head tx_queue_fm;
+};
+
+static struct cg2900_info *cg2900_info;
+
+/*
+ * cg2900_channels() - Array containing available H4 channels for the CG2900
+ * ST-Ericsson Connectivity controller.
+ */
+struct cg2900_device_id cg2900_channels[] = {
+ {CG2900_BT_CMD, CHANNEL_BT_CMD},
+ {CG2900_BT_ACL, CHANNEL_BT_ACL},
+ {CG2900_BT_EVT, CHANNEL_BT_EVT},
+ {CG2900_GNSS, CHANNEL_GNSS},
+ {CG2900_FM_RADIO, CHANNEL_FM_RADIO},
+ {CG2900_DEBUG, CHANNEL_DEBUG},
+ {CG2900_STE_TOOLS, CHANNEL_STE_TOOLS},
+ {CG2900_HCI_LOGGER, CHANNEL_HCI_LOGGER},
+ {CG2900_US_CTRL, CHANNEL_US_CTRL},
+ {CG2900_BT_AUDIO, CHANNEL_BT_CMD},
+ {CG2900_FM_RADIO_AUDIO, CHANNEL_FM_RADIO},
+ {CG2900_CORE, CHANNEL_CORE}
+};
+
+/*
+ * Internal function
+ */
+
+/**
+ * create_and_send_bt_cmd() - Copy and send sk_buffer.
+ * @data: Data to send.
+ * @length: Length in bytes of data.
+ *
+ * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data
+ * to it, and send the sk_buffer to controller.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ int err;
+
+ skb = cg2900_alloc_skb(length, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't alloc sk_buff with length %d", length);
+ return;
+ }
+
+ memcpy(skb_put(skb, length), data, length);
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ logger_config = cg2900_get_hci_logger_config();
+ if (logger_config)
+ err = cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ err = cg2900_send_to_chip(skb, false);
+
+ if (err) {
+ CG2900_ERR("Failed to transmit to chip (%d)", err);
+ kfree_skb(skb);
+ }
+}
+
+/**
+ * fm_irpt_expected() - check if this FM command will generate an interrupt.
+ * @cmd_id: command identifier.
+ *
+ * Returns:
+ * true if the command will generate an interrupt.
+ * false if it won't.
+ */
+static bool fm_irpt_expected(u16 cmd_id)
+{
+ bool retval = false;
+
+ switch (cmd_id) {
+ case CG2900_FM_DO_AIP_FADE_START:
+ if (cg2900_info->fm_radio_mode == FM_RADIO_MODE_FMT)
+ retval = true;
+ break;
+
+ case CG2900_FM_DO_AUP_BT_FADE_START:
+ case CG2900_FM_DO_AUP_EXT_FADE_START:
+ case CG2900_FM_DO_AUP_FADE_START:
+ if (cg2900_info->fm_radio_mode == FM_RADIO_MODE_FMR)
+ retval = true;
+ break;
+
+ case CG2900_FM_DO_FMR_SETANTENNA:
+ case CG2900_FM_DO_FMR_SP_AFSWITCH_START:
+ case CG2900_FM_DO_FMR_SP_AFUPDATE_START:
+ case CG2900_FM_DO_FMR_SP_BLOCKSCAN_START:
+ case CG2900_FM_DO_FMR_SP_PRESETPI_START:
+ case CG2900_FM_DO_FMR_SP_SCAN_START:
+ case CG2900_FM_DO_FMR_SP_SEARCH_START:
+ case CG2900_FM_DO_FMR_SP_SEARCHPI_START:
+ case CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL:
+ case CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL:
+ case CG2900_FM_DO_FMT_PA_SETCTRL:
+ case CG2900_FM_DO_FMT_PA_SETMODE:
+ case CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL:
+ case CG2900_FM_DO_GEN_ANTENNACHECK_START:
+ case CG2900_FM_DO_GEN_GOTOMODE:
+ case CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE:
+ case CG2900_FM_DO_GEN_SELECTREFERENCECLOCK:
+ case CG2900_FM_DO_GEN_SETPROCESSINGCLOCK:
+ case CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL:
+ case CG2900_FM_DO_TST_TX_RAMP_START:
+ retval = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if (retval)
+ CG2900_INFO("Following interrupt event expected for this "
+ "Cmd complete evt, cmd_id = 0x%x.", cmd_id);
+
+ return retval;
+}
+
+/**
+ * fm_is_do_cmd_irpt() - Check if irpt_val is one of the FM DO
command related interrupts.
+ * @irpt_val: interrupt value.
+ *
+ * Returns:
+ * true if it's do-command related interrupt value.
+ * false if it's not.
+ */
+static bool fm_is_do_cmd_irpt(u16 irpt_val)
+{
+ if ((irpt_val & CG2900_FM_IRPT_OPERATION_SUCCEEDED) ||
+ (irpt_val & CG2900_FM_IRPT_OPERATION_FAILED)) {
+ CG2900_INFO("Irpt evt for FM do-command found, "
+ "irpt_val = 0x%x.", irpt_val);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @work_func: Work function.
+ *
+ * The create_work_item() function creates work item and add it to
+ * the work queue.
+ */
+static void create_work_item(work_func_t work_func)
+{
+ struct work_struct *new_work;
+ int wq_err;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for work_struct!");
+ return;
+ }
+
+ INIT_WORK(new_work, work_func);
+
+ wq_err = queue_work(cg2900_info->wq, new_work);
+ if (!wq_err) {
+ CG2900_ERR("Failed to queue work_struct because it's already "
+ "in the queue!");
+ kfree(new_work);
+ }
+}
+
+/**
+ * fm_reset_flow_ctrl - Clears up internal FM flow control.
+ *
+ * Resets outstanding commands and clear FM TX list and set CG2900 FM mode to
+ * idle.
+ */
+static void fm_reset_flow_ctrl(void)
+{
+ CG2900_INFO("fm_reset_flow_ctrl");
+
+ skb_queue_purge(&cg2900_info->tx_queue_fm);
+
+ /* Reset the fm_cmd_id. */
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+
+ cg2900_info->fm_radio_mode = FM_RADIO_MODE_IDLE;
+}
+
+
+/**
+ * fm_parse_cmd - Parses a FM command packet.
+ * @data: FM command packet.
+ * @cmd_func: Out: FM legacy command function.
+ * @cmd_id: Out: FM legacy command ID.
+ */
+static void fm_parse_cmd(u8 *data, u8 *cmd_func, u16 *cmd_id)
+{
+ /* Move past H4-header to start of actual package */
+ struct fm_leg_cmd *pkt = (struct fm_leg_cmd *)(data + HCI_H4_SIZE);
+
+ *cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ *cmd_id = CG2900_FM_CMD_NONE;
+
+ if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) {
+ CG2900_ERR("Not an FM legacy command 0x%X", pkt->opcode);
+ return;
+ }
+
+ *cmd_func = pkt->fm_function;
+ CG2900_DBG("cmd_func 0x%X", *cmd_func);
+ if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) {
+ *cmd_id = CG2900_GET_FM_CMD_ID(le16_to_cpu(pkt->fm_cmd.head));
+ CG2900_DBG("cmd_id 0x%X", *cmd_id);
+ }
+}
+
+
+/**
+ * fm_parse_event - Parses a FM event packet
+ * @data: FM event packet.
+ * @event Out: FM event.
+ * @cmd_func: Out: FM legacy command function.
+ * @cmd_id: Out: FM legacy command ID.
+ * @intr_val: Out: FM interrupt value.
+ */
+static void fm_parse_event(u8 *data, u8 *event, u8 *cmd_func, u16 *cmd_id,
+ u16 *intr_val)
+{
+ /* Move past H4-header to start of actual package */
+ union fm_leg_evt_or_irq *pkt =
+ (union fm_leg_evt_or_irq *)(data + HCI_H4_SIZE);
+
+ *cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ *cmd_id = CG2900_FM_CMD_NONE;
+ *intr_val = 0;
+ *event = CG2900_FM_EVENT_UNKNOWN;
+
+ if (pkt->evt.opcode == CG2900_FM_GEN_ID_LEGACY &&
+ pkt->evt.read_write == CG2900_FM_CMD_LEG_PARAM_WRITE) {
+ /* Command complete */
+ *event = CG2900_FM_EVENT_CMD_COMPLETE;
+ *cmd_func = pkt->evt.fm_function;
+ CG2900_DBG("cmd_func 0x%X", *cmd_func);
+ if (*cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) {
+ *cmd_id = CG2900_GET_FM_CMD_ID(
+ le16_to_cpu(pkt->evt.response_head));
+ CG2900_DBG("cmd_id 0x%X", *cmd_id);
+ }
+ } else if (pkt->irq_v2.opcode == CG2900_FM_GEN_ID_LEGACY &&
+ pkt->irq_v2.event_type == CG2900_FM_CMD_LEG_PARAM_IRQ) {
+ /* Interrupt, PG2 style */
+ *event = CG2900_FM_EVENT_INTERRUPT;
+ *intr_val = le16_to_cpu(pkt->irq_v2.irq);
+ CG2900_DBG("intr_val 0x%X", *intr_val);
+ } else if (pkt->irq_v1.opcode == CG2900_FM_GEN_ID_LEGACY) {
+ /* Interrupt, PG1 style */
+ *event = CG2900_FM_EVENT_INTERRUPT;
+ *intr_val = le16_to_cpu(pkt->irq_v1.irq);
+ CG2900_DBG("intr_val 0x%X", *intr_val);
+ } else {
+ CG2900_ERR("Not an FM legacy command 0x%X %X %X %X ...",
+ data[0], data[1], data[2], data[3]);
+ }
+}
+
+/**
+ * fm_update_mode - Updates the FM mode state machine.
+ * @data: FM command packet.
+ *
+ * Parses a FM command packet and updates the FM mode state machine.
+ */
+static void fm_update_mode(u8 *data)
+{
+ u8 cmd_func;
+ u16 cmd_id;
+
+ fm_parse_cmd(data, &cmd_func, &cmd_id);
+
+ if (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND &&
+ cmd_id == CG2900_FM_DO_GEN_GOTOMODE) {
+ /* Move past H4-header to start of actual package */
+ struct fm_leg_cmd *pkt =
+ (struct fm_leg_cmd *)(data + HCI_H4_SIZE);
+
+ cg2900_info->fm_radio_mode = le16_to_cpu(pkt->fm_cmd.data[0]);
+ CG2900_INFO("FM Radio mode changed to 0x%x",
+ cg2900_info->fm_radio_mode);
+ }
+}
+
+
+/**
+ * transmit_skb_from_tx_queue_bt() - Check flow control info and transmit skb.
+ *
+ * The transmit_skb_from_tx_queue_bt() function checks if there are tickets
+ * available and commands waiting in the TX queue and if so transmits them
+ * to the controller.
+ * It shall always be called within spinlock_bh.
+ */
+static void transmit_skb_from_tx_queue_bt(void)
+{
+ struct cg2900_device *dev;
+ struct sk_buff *skb;
+
+ CG2900_INFO("transmit_skb_from_tx_queue_bt");
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_bt);
+ while (skb) {
+ if ((cg2900_info->tx_nr_pkts_allowed_bt) <= 0) {
+ /*
+ * If no more packets allowed just return, we'll get
+ * back here after next Command Complete/Status event.
+ * Put skb back at head of queue.
+ */
+ skb_queue_head(&cg2900_info->tx_queue_bt, skb);
+ return;
+ }
+
+ (cg2900_info->tx_nr_pkts_allowed_bt)--;
+ CG2900_DBG("tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ dev = cg2900_skb_data(skb)->dev; /* dev is never NULL */
+
+ /*
+ * If it's a command from audio application, store the OpCode,
+ * it'll be used later to decide where to dispatch
+ * the Command Complete event.
+ */
+ if (cg2900_get_bt_audio_dev() == dev) {
+ cg2900_info->audio_bt_cmd_op =
+ HCI_BT_GET_ID(skb->data[1],
+ skb->data[2]);
+ CG2900_DBG("Sending cmd from audio driver, saving "
+ "OpCode = 0x%X",
+ cg2900_info->audio_bt_cmd_op);
+ }
+
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_bt);
+ }
+}
+
+/**
+ * transmit_skb_from_tx_queue_fm() - Check flow control info and transmit skb.
+ *
+ * The transmit_skb_from_tx_queue_fm() function checks if it possible to
+ * transmit and commands waiting in the TX queue and if so transmits them
+ * to the controller.
+ * It shall always be called within spinlock_bh.
+ */
+static void transmit_skb_from_tx_queue_fm(void)
+{
+ struct cg2900_device *dev;
+ struct sk_buff *skb;
+
+ CG2900_INFO("transmit_skb_from_tx_queue_fm");
+
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_fm);
+ while (skb) {
+ u16 cmd_id;
+ u8 cmd_func;
+ bool do_transmit = false;
+
+ if (cg2900_info->audio_fm_cmd_id != CG2900_FM_CMD_NONE ||
+ cg2900_info->hci_fm_cmd_func != CG2900_FM_CMD_PARAM_NONE) {
+ /*
+ * There are currently outstanding FM commands.
+ * Wait for them to finish. We will get back here later.
+ * Queue back the skb at head of list.
+ */
+ skb_queue_head(&cg2900_info->tx_queue_bt, skb);
+ return;
+ }
+
+ dev = cg2900_skb_data(skb)->dev; /* dev is never NULL */
+
+ fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id);
+
+ /*
+ * Store the FM command function , it'll be used later to decide
+ * where to dispatch the Command Complete event.
+ */
+ if (cg2900_get_fm_audio_dev() == dev) {
+ cg2900_info->audio_fm_cmd_id = cmd_id;
+ CG2900_DBG("audio_fm_cmd_id 0x%X",
+ cg2900_info->audio_fm_cmd_id);
+ do_transmit = true;
+ }
+ if (cg2900_get_fm_radio_dev() == dev) {
+ cg2900_info->hci_fm_cmd_func = cmd_func;
+ fm_update_mode(&(skb->data[0]));
+ CG2900_DBG("hci_fm_cmd_func 0x%X",
+ cg2900_info->hci_fm_cmd_func);
+ do_transmit = true;
+ }
+
+ if (do_transmit) {
+ /*
+ * We have only one ticket on FM. Just return after
+ * sending the skb.
+ */
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ return;
+ }
+
+ /*
+ * This packet was neither FM or FM audio. That means that
+ * the user that originally sent it has deregistered.
+ * Just throw it away and check the next skb in the queue.
+ */
+ kfree_skb(skb);
+ /* Dequeue an skb from the head of the list */
+ skb = skb_dequeue(&cg2900_info->tx_queue_fm);
+ }
+}
+
+/**
+ * update_flow_ctrl_bt() - Update number of outstanding commands for BT CMD.
+ * @skb: skb with received packet.
+ *
+ * The update_flow_ctrl_bt() checks if incoming data packet is
+ * BT Command Complete/Command Status Event and if so updates number of tickets
+ * and number of outstanding commands. It also calls function to send queued
+ * commands (if the list of queued commands is not empty).
+ */
+static void update_flow_ctrl_bt(const struct sk_buff * const skb)
+{
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ u8 event_code = data[0];
+
+ if (HCI_BT_EVT_CMD_COMPLETE == event_code) {
+ /*
+ * If it's HCI Command Complete Event then we might get some
+ * HCI tickets back. Also we can decrease the number outstanding
+ * HCI commands (if it's not NOP command or one of the commands
+ * that generate both Command Status Event and Command Complete
+ * Event).
+ * Check if we have any HCI commands waiting in the TX list and
+ * send them if there are tickets available.
+ */
+ spin_lock_bh(&(cg2900_info->tx_bt_lock));
+ cg2900_info->tx_nr_pkts_allowed_bt =
+ data[HCI_BT_EVT_CMD_COMPL_NR_OF_PKTS_POS];
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ if (!skb_queue_empty(&cg2900_info->tx_queue_bt))
+ transmit_skb_from_tx_queue_bt();
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+ } else if (HCI_BT_EVT_CMD_STATUS == event_code) {
+ /*
+ * If it's HCI Command Status Event then we might get some
+ * HCI tickets back. Also we can decrease the number outstanding
+ * HCI commands (if it's not NOP command).
+ * Check if we have any HCI commands waiting in the TX queue and
+ * send them if there are tickets available.
+ */
+ spin_lock_bh(&(cg2900_info->tx_bt_lock));
+ cg2900_info->tx_nr_pkts_allowed_bt =
+ data[HCI_BT_EVT_CMD_STATUS_NR_OF_PKTS_POS];
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ if (!skb_queue_empty(&cg2900_info->tx_queue_bt))
+ transmit_skb_from_tx_queue_bt();
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+ }
+}
+
+/**
+ * update_flow_ctrl_fm() - Update packets allowed for FM channel.
+ * @skb: skb with received packet.
+ *
+ * The update_flow_ctrl_fm() checks if incoming data packet is FM packet
+ * indicating that the previous command has been handled and if so update
+ * packets. It also calls function to send queued commands (if the list of
+ * queued commands is not empty).
+ */
+static void update_flow_ctrl_fm(const struct sk_buff * const skb)
+{
+ u8 cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ u16 cmd_id = CG2900_FM_CMD_NONE;
+ u16 irpt_val = 0;
+ u8 event = CG2900_FM_EVENT_UNKNOWN;
+
+ fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val);
+
+ if (event == CG2900_FM_EVENT_CMD_COMPLETE) {
+ /* FM legacy command complete event */
+ spin_lock_bh(&(cg2900_info->tx_fm_lock));
+ /*
+ * Check if it's not an write command complete event, because
+ * then it cannot be a DO command.
+ * If it's a write command complete event check that is not a
+ * DO command complete event before setting the outstanding
+ * FM packets to none.
+ */
+ if (cmd_func != CG2900_FM_CMD_PARAM_WRITECOMMAND ||
+ !fm_irpt_expected(cmd_id)) {
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ CG2900_DBG("FM cmd outstanding cmd func 0x%x",
+ cg2900_info->hci_fm_cmd_func);
+ CG2900_DBG("FM cmd Audio outstanding cmd id 0x%x",
+ cg2900_info->audio_fm_cmd_id);
+ transmit_skb_from_tx_queue_fm();
+
+ /*
+ * If there was a write do command complete event check if it is
+ * DO command previously sent by the FM audio user. If that's
+ * the case we need remember that in order to be able to
+ * dispatch the interrupt to the correct user.
+ */
+ } else if (cmd_id == cg2900_info->audio_fm_cmd_id) {
+ cg2900_info->tx_fm_audio_awaiting_irpt = true;
+ CG2900_DBG("FM Audio waiting for interrupt = true.");
+ }
+ spin_unlock_bh(&(cg2900_info->tx_fm_lock));
+ } else if (event == CG2900_FM_EVENT_INTERRUPT) {
+ /* FM legacy interrupt */
+ if (fm_is_do_cmd_irpt(irpt_val)) {
+ /*
+ * If it is an interrupt related to a DO command update
+ * the outstanding flow control and transmit blocked
+ * FM commands.
+ */
+ spin_lock_bh(&(cg2900_info->tx_fm_lock));
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ CG2900_DBG("FM cmd outstanding cmd func 0x%x",
+ cg2900_info->hci_fm_cmd_func);
+ CG2900_DBG("FM cmd Audio outstanding cmd id 0x%x",
+ cg2900_info->audio_fm_cmd_id);
+ cg2900_info->tx_fm_audio_awaiting_irpt = false;
+ CG2900_DBG("FM Audio waiting for interrupt = false.");
+ transmit_skb_from_tx_queue_fm();
+ spin_unlock_bh(&(cg2900_info->tx_fm_lock));
+ }
+ }
+}
+
+/**
+ * send_bd_address() - Send HCI VS command with BD address to the chip.
+ */
+static void send_bd_address(void)
+{
+ struct bt_vs_store_in_fs_cmd *cmd;
+ /*
+ * The '-1' is for the first byte of the data field that's already
+ * there.
+ */
+ u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE - 1;
+
+ cmd = kmalloc(plen, GFP_KERNEL);
+ if (!cmd)
+ return;
+
+ cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_STORE_IN_FS);
+ cmd->plen = BT_PARAM_LEN(plen);
+ cmd->user_id = CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR;
+ cmd->len = BT_BDADDR_SIZE;
+ /* Now copy the BD address received from user space control app. */
+ memcpy(&(cmd->data), bd_address, BT_BDADDR_SIZE);
+
+ SET_BOOT_STATE(BOOT_SEND_BD_ADDRESS);
+
+ create_and_send_bt_cmd(cmd, plen);
+
+ kfree(cmd);
+}
+
+/**
+ * get_text_line()- 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 get_text_line() function extracts one line of text from input file.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *get_text_line(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;
+}
+
+/**
+ * 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 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;
+ u32 hci_rev;
+ u32 lmp_sub;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kzalloc(LINE_BUFFER_LENGTH, GFP_ATOMIC);
+ if (!line_buffer) {
+ CG2900_ERR("Failed to allocate line_buffer");
+ return false;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer = get_text_line(line_buffer,
+ min(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 */
+ CG2900_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 == '#')
+ continue;
+
+ hci_rev = 0;
+ lmp_sub = 0;
+
+ CG2900_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.
+ */
+ if (sscanf(line_buffer, "%x%x%s", &hci_rev, &lmp_sub,
+ *file_name) == 3
+ && hci_rev == cg2900_info->chip_dev.chip.hci_revision
+ && lmp_sub == cg2900_info->chip_dev.chip.hci_sub_version) {
+ CG2900_DBG("File found for chip\n"
+ "\tFile name = %s\n"
+ "\tHCI Revision = 0x%X\n"
+ "\tLMP 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, FILENAME_MAX + 1);
+ }
+ kfree(line_buffer);
+
+ return file_found;
+}
+
+/**
+ * read_and_send_file_part() - Transmit a part of the supplied file.
+ *
+ * The 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 read_and_send_file_part(void)
+{
+ int bytes_to_copy;
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ struct bt_vs_write_file_block_cmd *cmd;
+ int plen;
+
+ /*
+ * Calculate number of bytes to copy;
+ * either max bytes for HCI packet or number of bytes left in file
+ */
+ bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE,
+ (int)(cg2900_info->fw_file->size -
+ cg2900_info->file_offset));
+
+ if (bytes_to_copy <= 0) {
+ /* Nothing more to read in file. */
+ SET_DOWNLOAD_STATE(DOWNLOAD_SUCCESS);
+ cg2900_info->chunk_id = 0;
+ cg2900_info->file_offset = 0;
+ return;
+ }
+
+ /* There is more data to send */
+ logger_config = cg2900_get_hci_logger_config();
+
+ /*
+ * There are bytes to transmit. Allocate a sk_buffer.
+ * When calculating length to alloc the '-1' is because of the first
+ * byte of the data field that is already defined in the struct.
+ */
+ plen = sizeof(*cmd) - 1 + bytes_to_copy;
+ skb = cg2900_alloc_skb(plen, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buffer");
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ return;
+ }
+
+ skb_put(skb, plen);
+
+ cmd = (struct bt_vs_write_file_block_cmd *)skb->data;
+ cmd->opcode = cpu_to_le16(CG2900_BT_OP_VS_WRITE_FILE_BLOCK);
+ cmd->plen = BT_PARAM_LEN(plen);
+ cmd->id = cg2900_info->chunk_id;
+ cg2900_info->chunk_id++;
+
+ /* Copy the data from offset position */
+ memcpy(&(cmd->data),
+ &(cg2900_info->fw_file->data[cg2900_info->file_offset]),
+ bytes_to_copy);
+
+ /* Increase offset with number of bytes copied */
+ cg2900_info->file_offset += bytes_to_copy;
+
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+}
+
+/**
+ * send_settings_file() - Transmit settings file.
+ *
+ * The 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 send_settings_file(void)
+{
+ /* Transmit a file part */
+ read_and_send_file_part();
+
+ if (cg2900_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Settings file finished. Release used resources */
+ CG2900_DBG("Settings file finished, release used resources");
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+
+ SET_FILE_LOAD_STATE(FILE_LOAD_NO_MORE_FILES);
+
+ /* Create and send HCI VS Store In FS command with bd address. */
+ send_bd_address();
+}
+
+/**
+ * send_patch_file - Transmit patch file.
+ *
+ * The 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 send_patch_file(void)
+{
+ int err;
+
+ /*
+ * Transmit a part of the supplied file to the controller.
+ * When nothing more to read, continue to close the patch file.
+ */
+ read_and_send_file_part();
+
+ if (cg2900_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Patch file finished. Release used resources */
+ CG2900_DBG("Patch file finished, release used resources");
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+ /* Retrieve the settings file */
+ err = request_firmware(&(cg2900_info->fw_file),
+ cg2900_info->settings_file_name,
+ cg2900_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get settings file (%d)", err);
+ goto error_handling;
+ }
+ /* Now send the settings file */
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_STATIC_SETTINGS);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ send_settings_file();
+ return;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(err);
+}
+
+/**
+ * work_power_off_chip() - Work item to power off the chip.
+ * @work: Reference to work data.
+ *
+ * The work_power_off_chip() function handles transmission of the HCI command
+ * vs_power_switch_off and then informs the CG2900 Core that this
chip driver is
+ * finished and the Core driver can now shut off the chip.
+ */
+static void work_power_off_chip(struct work_struct *work)
+{
+ struct sk_buff *skb;
+ u8 *h4_header;
+ struct cg2900_hci_logger_config *logger_config;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /*
+ * Get the VS Power Switch Off command to use based on connected
+ * connectivity controller
+ */
+ skb = cg2900_devices_get_power_switch_off_cmd(NULL);
+
+ /*
+ * Transmit the received command.
+ * If no command found for the device, just continue
+ */
+ if (!skb) {
+ CG2900_ERR("Could not retrieve PowerSwitchOff command");
+ goto shut_down_chip;
+ }
+
+ logger_config = cg2900_get_hci_logger_config();
+
+ CG2900_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, CG2900_SKB_RESERVE);
+ *h4_header = CHANNEL_BT_CMD;
+
+ SET_CLOSING_STATE(CLOSING_POWER_SWITCH_OFF);
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+
+ /*
+ * 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(msecs_to_jiffies(POWER_SW_OFF_WAIT));
+
+shut_down_chip:
+ SET_CLOSING_STATE(CLOSING_SHUT_DOWN);
+
+ (void)cg2900_chip_shutdown_finished(0);
+
+ kfree(work);
+}
+
+/**
+ * work_reset_after_error() - Handle reset.
+ * @work: Reference to work data.
+ *
+ * Handle a reset after received Command Complete event.
+ */
+static void work_reset_after_error(struct work_struct *work)
+{
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ cg2900_chip_startup_finished(-EIO);
+
+ kfree(work);
+}
+
+/**
+ * work_load_patch_and_settings() - Start loading patches and settings.
+ * @work: Reference to work data.
+ */
+static void work_load_patch_and_settings(struct work_struct *work)
+{
+ int err = 0;
+ bool file_found;
+ const struct firmware *patch_info;
+ const struct firmware *settings_info;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /* Check that we are in the right state */
+ if (cg2900_info->boot_state != BOOT_GET_FILES_TO_LOAD)
+ goto finished;
+
+ /* Open patch info file. */
+ err = request_firmware(&patch_info, PATCH_INFO_FILE,
+ cg2900_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get patch info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the patch info file.
+ * See if we can find the right patch file as well
+ */
+ file_found = get_file_to_load(patch_info,
+ &(cg2900_info->patch_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(patch_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find patch file! Major error!");
+ goto error_handling;
+ }
+
+ /* Open settings info file. */
+ err = request_firmware(&settings_info,
+ FACTORY_SETTINGS_INFO_FILE,
+ cg2900_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get settings info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the settings info file.
+ * See if we can find the right settings file as well.
+ */
+ file_found = get_file_to_load(settings_info,
+ &(cg2900_info->settings_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(settings_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find settings file! Major error!");
+ goto error_handling;
+ }
+
+ /* We now all info needed */
+ SET_BOOT_STATE(BOOT_DOWNLOAD_PATCH);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_PATCH);
+ cg2900_info->chunk_id = 0;
+ cg2900_info->file_offset = 0;
+ cg2900_info->fw_file = NULL;
+
+ /* OK. Now it is time to download the patches */
+ err = request_firmware(&(cg2900_info->fw_file),
+ cg2900_info->patch_file_name,
+ cg2900_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get patch file (%d)", err);
+ goto error_handling;
+ }
+ send_patch_file();
+
+ goto finished;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+finished:
+ kfree(work);
+}
+
+/**
+ * work_cont_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 work_cont_file_download(struct work_struct *work)
+{
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /* Continue to send patches or settings to the controller */
+ if (cg2900_info->file_load_state == FILE_LOAD_GET_PATCH)
+ send_patch_file();
+ else if (cg2900_info->file_load_state == FILE_LOAD_GET_STATIC_SETTINGS)
+ send_settings_file();
+ else
+ CG2900_INFO("No more files to load");
+
+ kfree(work);
+}
+
+/**
+ * handle_reset_cmd_complete() - Handles HCI Reset Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_reset_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ CG2900_INFO("Received Reset complete event with status 0x%X", status);
+
+ if (CLOSING_RESET != cg2900_info->closing_state)
+ return false;
+
+ if (HCI_BT_ERROR_NO_ERROR != status) {
+ /*
+ * Continue in case of error, the chip is going to be shut down
+ * anyway.
+ */
+ CG2900_ERR("Command complete for HciReset received with "
+ "error 0x%X !", status);
+ }
+
+ create_work_item(work_power_off_chip);
+
+ return true;
+}
+
+
+/**
+ * handle_vs_store_in_fs_cmd_complete() - Handles HCI VS StoreInFS
Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_store_in_fs_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ CG2900_INFO("Received Store_in_FS complete event with status 0x%X",
+ status);
+
+ if (cg2900_info->boot_state != BOOT_SEND_BD_ADDRESS)
+ return false;
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ struct hci_command_hdr cmd;
+
+ /* Send HCI SystemReset command to activate patches */
+ SET_BOOT_STATE(BOOT_ACTIVATE_PATCHES_AND_SETTINGS);
+
+ cmd.opcode = cpu_to_le16(CG2900_BT_OP_VS_SYSTEM_RESET);
+ cmd.plen = 0; /* No parameters for System Reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } else {
+ CG2900_ERR("Command complete for StoreInFS received with error "
+ "0x%X", status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ create_work_item(work_reset_after_error);
+ }
+
+ return true;
+}
+
+/**
+ * handle_vs_write_file_block_cmd_complete() - Handles HCI VS
WriteFileBlock Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_write_file_block_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ if ((cg2900_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (cg2900_info->download_state != DOWNLOAD_PENDING))
+ return false;
+
+ if (HCI_BT_ERROR_NO_ERROR == status)
+ create_work_item(work_cont_file_download);
+ else {
+ CG2900_ERR("Command complete for WriteFileBlock received with"
+ " error 0x%X", status);
+ SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+ SET_BOOT_STATE(BOOT_FAILED);
+ if (cg2900_info->fw_file) {
+ release_firmware(cg2900_info->fw_file);
+ cg2900_info->fw_file = NULL;
+ }
+ create_work_item(work_reset_after_error);
+ }
+
+ return true;
+}
+
+/**
+ * handle_vs_power_switch_off_cmd_complete() - Handles HCI VS
PowerSwitchOff Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_power_switch_off_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ if (CLOSING_POWER_SWITCH_OFF != cg2900_info->closing_state)
+ return false;
+
+ /*
+ * We were waiting for this but we don't need to do anything upon
+ * reception except warn for error status
+ */
+ if (HCI_BT_ERROR_NO_ERROR != status)
+ CG2900_ERR("Command Complete for PowerSwitchOff received with "
+ "error 0x%X", status);
+
+ return true;
+}
+
+/**
+ * handle_vs_system_reset_cmd_complete() - Handle HCI VS SystemReset
Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_system_reset_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ if (cg2900_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+ return false;
+
+ CG2900_INFO("SYS_CLK_OUT Disabled");
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * The boot sequence is now finished successfully.
+ * Set states and signal to waiting thread.
+ */
+ SET_BOOT_STATE(BOOT_READY);
+ cg2900_chip_startup_finished(0);
+ } else {
+ CG2900_ERR("Received Reset complete event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ }
+
+ return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if received data should be handled
in CG2900 chip driver.
+ * @skb: Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled in CG2900 chip driver. If so handle it correctly.
+ * Received data is always HCI BT Event.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_rx_data_bt_evt(struct sk_buff *skb)
+{
+ bool pkt_handled = false;
+ /* skb cannot be NULL here so it is safe to de-reference */
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ struct hci_event_hdr *evt;
+ struct hci_ev_cmd_complete *cmd_complete;
+ u16 op_code;
+
+ evt = (struct hci_event_hdr *)data;
+
+ /* First check the event code. Only handle Command Complete Event */
+ if (HCI_EV_CMD_COMPLETE != evt->evt)
+ return false;
+
+ data += sizeof(*evt);
+ cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+ op_code = le16_to_cpu(cmd_complete->opcode);
+
+ CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+ data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+ if (op_code == HCI_OP_RESET)
+ pkt_handled = handle_reset_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_STORE_IN_FS)
+ pkt_handled = handle_vs_store_in_fs_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled = handle_vs_write_file_block_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_POWER_SWITCH_OFF)
+ pkt_handled = handle_vs_power_switch_off_cmd_complete(data);
+ else if (op_code == CG2900_BT_OP_VS_SYSTEM_RESET)
+ pkt_handled = handle_vs_system_reset_cmd_complete(data);
+
+ if (pkt_handled)
+ kfree_skb(skb);
+
+ return pkt_handled;
+}
+
+/**
+ * transmit_skb_with_flow_ctrl_bt() - Send the BT skb to the
controller if it is allowed or queue it.
+ * @skb: Data packet.
+ * @dev: Pointer to cg2900_device struct.
+ *
+ * The transmit_skb_with_flow_ctrl_bt() function checks if there are
+ * tickets available and if so transmits buffer to controller.
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 transmit_skb_with_flow_ctrl_bt(struct sk_buff *skb,
+ struct cg2900_device *dev)
+{
+ /*
+ * Because there are more users of some H4 channels (currently audio
+ * application for BT command and FM channel) we need to have an
+ * internal HCI command flow control in CG2900 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).
+ */
+ spin_lock_bh(&(cg2900_info->tx_bt_lock));
+
+ if ((cg2900_info->tx_nr_pkts_allowed_bt) > 0) {
+ (cg2900_info->tx_nr_pkts_allowed_bt)--;
+ CG2900_DBG("New tx_nr_pkts_allowed_bt = %d",
+ cg2900_info->tx_nr_pkts_allowed_bt);
+
+ /*
+ * If it's command from audio app store the OpCode,
+ * it'll be used later to decide where to dispatch Command
+ * Complete event.
+ */
+ if (cg2900_get_bt_audio_dev() == dev) {
+ cg2900_info->audio_bt_cmd_op =
+ HCI_BT_GET_ID(skb->data[1],
+ skb->data[2]);
+ CG2900_DBG("Sending cmd from audio driver, saving "
+ "OpCode = 0x%x",
+ cg2900_info->audio_bt_cmd_op);
+ }
+
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else {
+ CG2900_DBG("Not allowed to send cmd to controller, "
+ "storing in TX queue.");
+
+ cg2900_skb_data(skb)->dev = dev;
+ skb_queue_tail(&cg2900_info->tx_queue_bt, skb);
+ }
+ spin_unlock_bh(&(cg2900_info->tx_bt_lock));
+}
+
+/**
+ * transmit_skb_with_flow_ctrl_fm() - Send the FM skb to the
controller if it is allowed or queue it.
+ * @skb: Data packet.
+ * @dev: Pointer to cg2900_device struct.
+ *
+ * The transmit_skb_with_flow_ctrl_fm() function checks if chip is
available and
+ * if so transmits buffer to controller. 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 command,
this is needed
+ * to know how to handle some FM DO commands complete events.
+ * If enabled, copy the transmitted data to the HCI logger as well.
+ */
+static void transmit_skb_with_flow_ctrl_fm(struct sk_buff *skb,
+ struct cg2900_device *dev)
+{
+ u8 cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ u16 cmd_id = CG2900_FM_CMD_NONE;
+
+ fm_parse_cmd(&(skb->data[0]), &cmd_func, &cmd_id);
+
+ /*
+ * If there is an FM IP disable or reset send command and also reset
+ * the flow control and audio user.
+ */
+ if (cmd_func == CG2900_FM_CMD_PARAM_DISABLE ||
+ cmd_func == CG2900_FM_CMD_PARAM_RESET) {
+ spin_lock_bh(&cg2900_info->tx_fm_lock);
+ fm_reset_flow_ctrl();
+ spin_unlock_bh(&cg2900_info->tx_fm_lock);
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ return;
+ }
+
+ /*
+ * If there is a FM user and no FM audio user command pending just send
+ * FM command. It is up to the user of the FM channel to handle its own
+ * flow control.
+ */
+ spin_lock_bh(&cg2900_info->tx_fm_lock);
+ if (cg2900_get_fm_radio_dev() == dev &&
+ cg2900_info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) {
+ cg2900_info->hci_fm_cmd_func = cmd_func;
+ CG2900_DBG("hci_fm_cmd_func 0x%X",
+ cg2900_info->hci_fm_cmd_func);
+ /* If a GotoMode command update FM mode */
+ fm_update_mode(&(skb->data[0]));
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else if (cg2900_get_fm_audio_dev() == dev &&
+ cg2900_info->hci_fm_cmd_func == CG2900_FM_CMD_PARAM_NONE &&
+ cg2900_info->audio_fm_cmd_id == CG2900_FM_CMD_NONE) {
+ /*
+ * If it's command from fm audio user store the command id.
+ * It'll be used later to decide where to dispatch
+ * command complete event.
+ */
+ cg2900_info->audio_fm_cmd_id = cmd_id;
+ CG2900_DBG("audio_fm_cmd_id 0x%X",
+ cg2900_info->audio_fm_cmd_id);
+ cg2900_send_to_chip(skb, dev->logger_enabled);
+ } else {
+ CG2900_DBG("Not allowed to send cmd to controller, storing in "
+ "TX queue");
+
+ cg2900_skb_data(skb)->dev = dev;
+ skb_queue_tail(&cg2900_info->tx_queue_fm, skb);
+ }
+ spin_unlock_bh(&(cg2900_info->tx_fm_lock));
+}
+
+/**
+ * chip_startup() - Start the chip.
+ * @dev: Chip info.
+ *
+ * The chip_startup() function downloads patches and other needed start
+ * procedures.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_startup(struct cg2900_chip_dev *dev)
+{
+ /* Start the boot sequence */
+ SET_BOOT_STATE(BOOT_GET_FILES_TO_LOAD);
+ create_work_item(work_load_patch_and_settings);
+
+ return 0;
+}
+
+/**
+ * chip_shutdown() - Shut down the chip.
+ * @dev: Chip info.
+ *
+ * The chip_shutdown() function shuts down the chip by sending PowerSwitchOff
+ * command.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_shutdown(struct cg2900_chip_dev *dev)
+{
+ struct hci_command_hdr cmd;
+
+ /*
+ * Transmit HCI reset command to ensure the chip is using
+ * the correct transport and to put BT part in reset.
+ */
+ SET_CLOSING_STATE(CLOSING_RESET);
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+
+ return 0;
+}
+
+/**
+ * data_to_chip() - Called when data shall be sent to the chip.
+ * @dev: Chip info.
+ * @cg2900_dev: CG2900 user for this packet.
+ * @skb: Packet to transmit.
+ *
+ * The data_to_chip() function updates flow control and itself
+ * transmits packet to controller if packet is BT command or FM radio.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_to_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ bool packet_handled = false;
+
+ if (cg2900_dev->h4_channel == CHANNEL_BT_CMD) {
+ transmit_skb_with_flow_ctrl_bt(skb, cg2900_dev);
+ packet_handled = true;
+ } else if (cg2900_dev->h4_channel == CHANNEL_FM_RADIO) {
+ transmit_skb_with_flow_ctrl_fm(skb, cg2900_dev);
+ packet_handled = true;
+ }
+
+ return packet_handled;
+}
+
+/**
+ * data_from_chip() - Called when data shall be sent to the chip.
+ * @dev: Chip info.
+ * @cg2900_dev: CG2900 user for this packet.
+ * @skb: Packet received.
+ *
+ * The data_from_chip() function updates flow control and checks
+ * if packet is a response for a packet it itself has transmitted.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_from_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ bool packet_handled;
+ int h4_channel;
+
+ h4_channel = skb->data[0];
+
+ /* First check if we should update flow control */
+ if (h4_channel == CHANNEL_BT_EVT)
+ update_flow_ctrl_bt(skb);
+ else if (h4_channel == CHANNEL_FM_RADIO)
+ update_flow_ctrl_fm(skb);
+
+ /* Then check if this is a response to data we have sent */
+ packet_handled = handle_rx_data_bt_evt(skb);
+
+ return packet_handled;
+}
+
+/**
+ * get_h4_channel() - Returns H:4 channel for the name.
+ * @name: Chip info.
+ * @h4_channel: CG2900 user for this packet.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENXIO if channel is not found.
+ */
+static int get_h4_channel(char *name, int *h4_channel)
+{
+ int i;
+ int err = -ENXIO;
+
+ *h4_channel = -1;
+
+ for (i = 0; *h4_channel == -1 && i < ARRAY_SIZE(cg2900_channels); i++) {
+ if (0 == strncmp(name, cg2900_channels[i].name,
+ CG2900_MAX_NAME_SIZE)) {
+ /* Device found. Return H4 channel */
+ *h4_channel = cg2900_channels[i].h4_channel;
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+/**
+ * is_bt_audio_user() - Checks if this packet is for the BT audio user.
+ * @h4_channel: H:4 channel for this packet.
+ * @skb: Packet to check.
+ *
+ * Returns:
+ * true if packet is for BT audio user.
+ * false otherwise.
+ */
+static bool is_bt_audio_user(int h4_channel, const struct sk_buff * const skb)
+{
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ u8 event_code = data[0];
+ bool bt_audio = false;
+
+ if (h4_channel == CHANNEL_BT_EVT) {
+ u16 opcode = 0;
+
+ if (HCI_BT_EVT_CMD_COMPLETE == event_code)
+ opcode = HCI_BT_EVENT_GET_ID(data[3], data[4]);
+ else if (HCI_BT_EVT_CMD_STATUS == event_code)
+ opcode = HCI_BT_EVENT_GET_ID(data[4], data[5]);
+
+ if (opcode != 0 && opcode == cg2900_info->audio_bt_cmd_op) {
+ CG2900_DBG("BT OpCode match = 0x%04X", opcode);
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ bt_audio = true;
+ }
+ }
+
+ return bt_audio;
+}
+
+/**
+ * is_fm_audio_user() - Checks if this packet is for the FM audio user.
+ * @h4_channel: H:4 channel for this packet.
+ * @skb: Packet to check.
+ *
+ * Returns:
+ * true if packet is for BT audio user.
+ * false otherwise.
+ */
+static bool is_fm_audio_user(int h4_channel, const struct sk_buff * const skb)
+{
+ u8 cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ u16 cmd_id = CG2900_FM_CMD_NONE;
+ u16 irpt_val = 0;
+ u8 event = CG2900_FM_EVENT_UNKNOWN;
+ bool bt_audio = false;
+
+ fm_parse_event(&(skb->data[0]), &event, &cmd_func, &cmd_id, &irpt_val);
+
+ if (h4_channel == CHANNEL_FM_RADIO) {
+ /* Check if command complete event FM legacy interface. */
+ if ((event == CG2900_FM_EVENT_CMD_COMPLETE) &&
+ (cmd_func == CG2900_FM_CMD_PARAM_WRITECOMMAND) &&
+ (cmd_id == cg2900_info->audio_fm_cmd_id)) {
+ CG2900_DBG("FM Audio Function Code match = 0x%04X",
+ cmd_id);
+ bt_audio = true;
+ goto finished;
+ }
+
+ /* Check if Interrupt legacy interface. */
+ if ((event == CG2900_FM_EVENT_INTERRUPT) &&
+ (fm_is_do_cmd_irpt(irpt_val)) &&
+ (cg2900_info->tx_fm_audio_awaiting_irpt))
+ bt_audio = true;
+ }
+
+finished:
+ return bt_audio;
+}
+
+/**
+ * last_bt_user_removed() - Called when last BT user is removed.
+ * @dev: Chip handler info.
+ *
+ * Clears out TX queue for BT.
+ */
+static void last_bt_user_removed(struct cg2900_chip_dev *dev)
+{
+ spin_lock_bh(&cg2900_info->tx_bt_lock);
+
+ skb_queue_purge(&cg2900_info->tx_queue_bt);
+
+ /*
+ * Reset number of packets allowed and number of outstanding
+ * BT commands.
+ */
+ cg2900_info->tx_nr_pkts_allowed_bt = 1;
+ /* Reset the audio_bt_cmd_op. */
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ spin_unlock_bh(&cg2900_info->tx_bt_lock);
+}
+
+/**
+ * last_fm_user_removed() - Called when last FM user is removed.
+ * @dev: Chip handler info.
+ *
+ * Clears out TX queue for BT.
+ */
+static void last_fm_user_removed(struct cg2900_chip_dev *dev)
+{
+ spin_lock_bh(&cg2900_info->tx_fm_lock);
+ fm_reset_flow_ctrl();
+ spin_unlock_bh(&cg2900_info->tx_fm_lock);
+}
+
+/**
+ * check_chip_support() - Checks if connected chip is handled by this driver.
+ * @dev: Chip info structure.
+ *
+ * If supported return true and fill in @callbacks.
+ *
+ * Returns:
+ * true if chip is handled by this driver.
+ * false otherwise.
+ */
+static bool check_chip_support(struct cg2900_chip_dev *dev)
+{
+ CG2900_INFO("CG2900: check_chip_support");
+
+ /*
+ * Check if this is a CG2900 revision.
+ * We do not care about the sub-version at the moment. Change this if
+ * necessary.
+ */
+ if ((dev->chip.manufacturer != CG2900_SUPP_MANUFACTURER) ||
+ (dev->chip.hci_revision != CG2900_PG1_SPECIAL_REV &&
+ (dev->chip.hci_revision < CG2900_SUPP_REVISION_MIN ||
+ dev->chip.hci_revision > CG2900_SUPP_REVISION_MAX))) {
+ CG2900_DBG("Chip not supported by CG2900 driver\n"
+ "\tMan: 0x%02X\n\tRev: 0x%04X\n\tSub: 0x%04X",
+ dev->chip.manufacturer, dev->chip.hci_revision,
+ dev->chip.hci_sub_version);
+ return false;
+ }
+
+ CG2900_INFO("Chip supported by the CG2900 driver");
+ /* Store needed data */
+ dev->user_data = cg2900_info;
+ memcpy(&(cg2900_info->chip_dev), dev, sizeof(*dev));
+ /* Set the callbacks */
+ dev->cb.chip_shutdown = chip_shutdown;
+ dev->cb.chip_startup = chip_startup;
+ dev->cb.data_from_chip = data_from_chip;
+ dev->cb.data_to_chip = data_to_chip;
+ dev->cb.get_h4_channel = get_h4_channel;
+ dev->cb.is_bt_audio_user = is_bt_audio_user;
+ dev->cb.is_fm_audio_user = is_fm_audio_user;
+ dev->cb.last_bt_user_removed = last_bt_user_removed;
+ dev->cb.last_fm_user_removed = last_fm_user_removed;
+
+ return true;
+}
+
+static struct cg2900_id_callbacks chip_support_callbacks = {
+ .check_chip_support = check_chip_support
+};
+
+/**
+ * cg2900_init() - Initialize module.
+ *
+ * The cg2900_init() function initializes the CG2900 driver,
+ * then registers to the CG2900 Core.
+ *
+ * Returns:
+ * 0 if success.
+ * -ENOMEM for failed alloc or structure creation.
+ * Error codes generated by cg2900_register_chip_driver.
+ */
+static int __init cg2900_init(void)
+{
+ int err = 0;
+
+ CG2900_INFO("cg2900_init");
+
+ cg2900_info = kzalloc(sizeof(*cg2900_info), GFP_ATOMIC);
+ if (!cg2900_info) {
+ CG2900_ERR("Couldn't allocate cg2900_info");
+ err = -ENOMEM;
+ goto finished;
+ }
+
+ /*
+ * Initialize linked lists for HCI BT and FM commands
+ * that can't be sent due to internal CG2900 flow control.
+ */
+ skb_queue_head_init(&cg2900_info->tx_queue_bt);
+ skb_queue_head_init(&cg2900_info->tx_queue_fm);
+
+ /* Initialize the spin locks */
+ spin_lock_init(&(cg2900_info->tx_bt_lock));
+ spin_lock_init(&(cg2900_info->tx_fm_lock));
+
+ cg2900_info->tx_nr_pkts_allowed_bt = 1;
+ cg2900_info->audio_bt_cmd_op = CG2900_BT_OPCODE_NONE;
+ cg2900_info->audio_fm_cmd_id = CG2900_FM_CMD_NONE;
+ cg2900_info->hci_fm_cmd_func = CG2900_FM_CMD_PARAM_NONE;
+ cg2900_info->fm_radio_mode = FM_RADIO_MODE_IDLE;
+
+ cg2900_info->wq = create_singlethread_workqueue(WQ_NAME);
+ if (!cg2900_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ENOMEM;
+ goto err_handling_free_info;
+ }
+
+ /* Allocate file names that will be used, deallocated in cg2900_exit */
+ cg2900_info->patch_file_name = kzalloc(FILENAME_MAX + 1, GFP_ATOMIC);
+ if (!cg2900_info->patch_file_name) {
+ CG2900_ERR("Couldn't allocate name buffer for patch file.");
+ err = -ENOMEM;
+ goto err_handling_destroy_wq;
+ }
+ /* Allocate file names that will be used, deallocated in cg2900_exit */
+ cg2900_info->settings_file_name = kzalloc(FILENAME_MAX + 1, GFP_ATOMIC);
+ if (!cg2900_info->settings_file_name) {
+ CG2900_ERR("Couldn't allocate name buffers settings file.");
+ err = -ENOMEM;
+ goto err_handling_free_patch_name;
+ }
+
+ err = cg2900_register_chip_driver(&chip_support_callbacks);
+ if (err) {
+ CG2900_ERR("Couldn't register chip driver (%d)", err);
+ goto err_handling_free_settings_name;
+ }
+
+ goto finished;
+
+err_handling_free_settings_name:
+ kfree(cg2900_info->settings_file_name);
+err_handling_free_patch_name:
+ kfree(cg2900_info->patch_file_name);
+err_handling_destroy_wq:
+ destroy_workqueue(cg2900_info->wq);
+err_handling_free_info:
+ kfree(cg2900_info);
+ cg2900_info = NULL;
+finished:
+ return err;
+}
+
+/**
+ * cg2900_exit() - Remove module.
+ */
+static void __exit cg2900_exit(void)
+{
+ CG2900_INFO("cg2900_exit");
+
+ if (!cg2900_info)
+ return;
+
+ kfree(cg2900_info->settings_file_name);
+ kfree(cg2900_info->patch_file_name);
+ destroy_workqueue(cg2900_info->wq);
+ kfree(cg2900_info);
+ cg2900_info = NULL;
+}
+
+module_init(cg2900_init);
+module_exit(cg2900_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux CG2900 Connectivity Device Driver");
diff --git a/drivers/mfd/cg2900/cg2900_chip.h b/drivers/mfd/cg2900/cg2900_chip.h
new file mode 100644
index 0000000..973e305
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.h
@@ -0,0 +1,421 @@
+/*
+ * drivers/mfd/cg2900/cg2900_chip.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson CG2900 GPS/BT/FM controller.
+ */
+
+#ifndef _CG2900_CHIP_H_
+#define _CG2900_CHIP_H_
+
+#include "hci_defines.h"
+
+/* Supported chips */
+#define CG2900_SUPP_MANUFACTURER 0x30
+#define CG2900_SUPP_REVISION_MIN 0x0100
+#define CG2900_SUPP_REVISION_MAX 0x0200
+
+/* Specific chip version data */
+#define CG2900_PG1_REV 0x0101
+#define CG2900_PG2_REV 0x0200
+#define CG2900_PG1_SPECIAL_REV 0x0700
+
+/*
+ * Bluetooth
+ */
+
+#define BT_SIZE_OF_HDR (sizeof(__le16) + sizeof(__u8))
+#define BT_PARAM_LEN(__pkt_len) (__pkt_len - BT_SIZE_OF_HDR)
+
+/* BT VS Store In FS command */
+#define CG2900_BT_OP_VS_STORE_IN_FS 0xFC22
+struct bt_vs_store_in_fs_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 user_id;
+ __u8 len;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* BT VS Write File Block command */
+#define CG2900_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E
+struct bt_vs_write_file_block_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 id;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* Bluetooth Vendor Specific Opcodes */
+#define CG2900_BT_OP_VS_POWER_SWITCH_OFF 0xFD40
+#define CG2900_BT_OP_VS_SYSTEM_RESET 0xFF12
+
+#define CG2900_BT_OPCODE_NONE 0xFFFF
+
+/* BT HCI VS command op codes in LSB-MSB format */
+#define CG2900_BT_VS_SET_HARDWARE_CONFIG_LSB 0x54
+#define CG2900_BT_VS_SET_HARDWARE_CONFIG_MSB 0xFD
+#define CG2900_BT_VS_SET_SESSION_CONFIG_LSB 0x55
+#define CG2900_BT_VS_SET_SESSION_CONFIG_MSB 0xFD
+#define CG2900_BT_VS_SESSION_CTRL_LSB 0x57
+#define CG2900_BT_VS_SESSION_CTRL_MSB 0xFD
+#define CG2900_BT_VS_RESET_SESSION_CONFIG_LSB 0x56
+#define CG2900_BT_VS_RESET_SESSION_CONFIG_MSB 0xFD
+
+/* Defines needed for HCI VS Store In FS cmd creation. */
+#define CG2900_VS_STORE_IN_FS_USR_ID_POS HCI_BT_CMD_PARAM_POS
+#define CG2900_VS_STORE_IN_FS_USR_ID_SIZE 1
+#define CG2900_VS_STORE_IN_FS_PARAM_LEN_POS \
+ (CG2900_VS_STORE_IN_FS_USR_ID_POS + CG2900_VS_STORE_IN_FS_USR_ID_SIZE)
+#define CG2900_VS_STORE_IN_FS_PARAM_LEN_SIZE 1
+#define CG2900_VS_STORE_IN_FS_PARAM_POS \
+ (CG2900_VS_STORE_IN_FS_PARAM_LEN_POS + \
+ CG2900_VS_STORE_IN_FS_PARAM_LEN_SIZE)
+#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE
+
+/* BT HCI Command parameters */
+
+/* VS_Set_Hardware_configuration I2S WS Sel param */
+#define CG2900_BT_HW_CONFIG_I2S_WS_SEL_MASTER 0x00
+#define CG2900_BT_HW_CONFIG_I2S_WS_SEL_SLAVE 0x01
+
+/* VS_Set_Hardware_configuration PCM Mode param */
+#define CG2900_BT_HW_CONFIG_PCM_MODE_SLAVE 0x00
+#define CG2900_BT_HW_CONFIG_PCM_MODE_MASTER 0x01
+
+/* VS_Session_Control SessionControl param */
+#define CG2900_BT_SESSION_START 0x00
+#define CG2900_BT_SESSION_STOP 0x01
+#define CG2900_BT_SESSION_PAUSE 0x02
+#define CG2900_BT_SESSION_RESUME 0x03
+
+/* VS_Session_Configuration MediaType param */
+#define CG2900_BT_SESSION_MEDIA_TYPE_AUDIO 0x00
+
+/* VS_Session_Configuration MediaConfiguration param */
+#define CG2900_BT_MEDIA_CONFIG_MONO 0x00
+#define CG2900_BT_MEDIA_CONFIG_STEREO 0x01
+#define CG2900_BT_MEDIA_CONFIG_JOINT_STEREO 0x02
+#define CG2900_BT_MEDIA_CONFIG_DUAL_CHANNEL 0x03
+
+/* VS_Session_Configuration VP Type param */
+#define CG2900_BT_VP_TYPE_PCM 0x00
+#define CG2900_BT_VP_TYPE_I2S 0x01
+#define CG2900_BT_VP_TYPE_SLIMBUS 0x02
+#define CG2900_BT_VP_TYPE_FM 0x03
+#define CG2900_BT_VP_TYPE_BT_SCO 0x04
+#define CG2900_BT_VP_TYPE_BT_A2DP 0x05
+#define CG2900_BT_VP_TYPE_ANALOG 0x07
+
+/* VS_Session_Configuration I2S Index param */
+#define CG2900_BT_SESSION_I2S_INDEX_I2S 0x00
+#define CG2900_BT_SESSION_I2S_INDEX_PCM_I2S 0x01
+
+/* VS_Session_Configuration PCM Index param */
+#define CG2900_BT_SESSION_PCM_INDEX_PCM_I2S 0x00
+
+/* VS_Set_Hardware_Configuration Macros */
+#define CG2900_BT_HW_CONFIG_PCM_SET_DIR(__slot, __val) (__val << (__slot + 4))
+#define CG2900_BT_HW_CONFIG_PCM_SET_MODE(__mode) (__mode << 1)
+
+/* VS_Session_Configuration Macros */
+#define CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(__rate) (__rate << 4)
+#define CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(__slot) (0x01 << __slot)
+
+/* BT command lengths. Header is always first 3 bytes */
+#define CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG 10
+#define CG2900_BT_LEN_VS_SET_SESSION_CONFIG 37
+#define CG2900_BT_LEN_VS_SESSION_CTRL 5
+#define CG2900_BT_LEN_VS_RESET_SESSION_CONFIG 4
+
+/*
+ * BT command parameter lengths, i.e. command length minus header length
+ * (3 bytes).
+ */
+#define CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG \
+ (CG2900_BT_LEN_VS_SET_SESSION_CONFIG - 3)
+#define CG2900_BT_PARAM_LEN_VS_SESSION_CTRL \
+ (CG2900_BT_LEN_VS_SESSION_CTRL - 3)
+
+/* BT parameter lengths */
+#define CG2900_BT_PARAM_LEN_SESSION_ID 1
+
+/*
+ * FM
+ */
+
+/* FM legacy command packet */
+struct fm_leg_cmd {
+ __u8 length;
+ __u8 opcode;
+ __u8 read_write;
+ __u8 fm_function;
+ union { /* Payload varies with function */
+ __le16 irqmask;
+ struct fm_leg_fm_cmd {
+ __le16 head;
+ __le16 data[];
+ } fm_cmd;
+ };
+} __attribute__((packed));
+
+/* FM legacy command complete packet */
+struct fm_leg_cmd_cmpl {
+ __u8 param_length;
+ __u8 status;
+ __u8 opcode;
+ __u8 read_write;
+ __u8 cmd_status;
+ __u8 fm_function;
+ __le16 response_head;
+ __le16 data[];
+} __attribute__((packed));
+
+/* FM legacy interrupt packet, PG2 style */
+struct fm_leg_irq_v2 {
+ __u8 param_length;
+ __u8 status;
+ __u8 opcode;
+ __u8 event_type;
+ __u8 event_id;
+ __le16 irq;
+} __attribute__((packed));
+
+/* FM legacy interrupt packet, PG1 style */
+struct fm_leg_irq_v1 {
+ __u8 param_length;
+ __u8 opcode;
+ __u8 event_id;
+ __le16 irq;
+} __attribute__((packed));
+
+union fm_leg_evt_or_irq {
+ __u8 param_length;
+ struct fm_leg_cmd_cmpl evt;
+ struct fm_leg_irq_v2 irq_v2;
+ struct fm_leg_irq_v1 irq_v1;
+} __attribute__((packed));
+
+/* FM Opcode generic*/
+#define CG2900_FM_GEN_ID_LEGACY 0xFE
+
+/* FM Opcode offset generic*/
+#define CG2900_FM_GEN_ID_POS_CMD 2
+#define CG2900_FM_GEN_ID_POS_CMD_CMPL 3
+#define CG2900_FM_GEN_ID_POS_INTERRUPT 2
+
+/* FM Opcode offset legacy */
+#define CG2900_FM_LEG_STATUS_POS_CMD_CMPL 5
+#define CG2900_FM_LEG_FUNC_POS_CMD_CMPL 6
+#define CG2900_FM_LEG_HDR_POS_CMD_CMPL 7
+
+/* FM event*/
+#define CG2900_FM_EVENT_UNKNOWN 0
+#define CG2900_FM_EVENT_CMD_COMPLETE 1
+#define CG2900_FM_EVENT_INTERRUPT 2
+
+/* FM do-command identifiers. */
+#define CG2900_FM_DO_AIP_FADE_START 0x0046
+#define CG2900_FM_DO_AUP_BT_FADE_START 0x01C2
+#define CG2900_FM_DO_AUP_EXT_FADE_START 0x0102
+#define CG2900_FM_DO_AUP_FADE_START 0x00A2
+#define CG2900_FM_DO_FMR_SETANTENNA 0x0663
+#define CG2900_FM_DO_FMR_SP_AFSWITCH_START 0x04A3
+#define CG2900_FM_DO_FMR_SP_AFUPDATE_START 0x0463
+#define CG2900_FM_DO_FMR_SP_BLOCKSCAN_START 0x0683
+#define CG2900_FM_DO_FMR_SP_PRESETPI_START 0x0443
+#define CG2900_FM_DO_FMR_SP_SCAN_START 0x0403
+#define CG2900_FM_DO_FMR_SP_SEARCH_START 0x03E3
+#define CG2900_FM_DO_FMR_SP_SEARCHPI_START 0x0703
+#define CG2900_FM_DO_FMR_SP_TUNE_SETCHANNEL 0x03C3
+#define CG2900_FM_DO_FMR_SP_TUNE_STEPCHANNEL 0x04C3
+#define CG2900_FM_DO_FMT_PA_SETCTRL 0x01A4
+#define CG2900_FM_DO_FMT_PA_SETMODE 0x01E4
+#define CG2900_FM_DO_FMT_SP_TUNE_SETCHANNEL 0x0064
+#define CG2900_FM_DO_GEN_ANTENNACHECK_START 0x02A1
+#define CG2900_FM_DO_GEN_GOTOMODE 0x0041
+#define CG2900_FM_DO_GEN_POWERSUPPLY_SETMODE 0x0221
+#define CG2900_FM_DO_GEN_SELECTREFERENCECLOCK 0x0201
+#define CG2900_FM_DO_GEN_SETPROCESSINGCLOCK 0x0241
+#define CG2900_FM_DO_GEN_SETREFERENCECLOCKPLL 0x01A1
+#define CG2900_FM_DO_TST_TX_RAMP_START 0x0147
+#define CG2900_FM_CMD_NONE 0xFFFF
+#define CG2900_FM_CMD_ID_GEN_GOTO_POWER_DOWN 0x0081
+#define CG2900_FM_CMD_ID_GEN_GOTO_STANDBY 0x0061
+
+/* FM Command IDs */
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE 0x0162
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL 0x0182
+#define CG2900_FM_CMD_ID_AIP_SET_MODE 0x01C6
+#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL 0x01A6
+#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE 0x01E6
+
+/* FM Command Parameters. */
+#define CG2900_FM_CMD_PARAM_ENABLE 0x00
+#define CG2900_FM_CMD_PARAM_DISABLE 0x01
+#define CG2900_FM_CMD_PARAM_RESET 0x02
+#define CG2900_FM_CMD_PARAM_WRITECOMMAND 0x10
+#define CG2900_FM_CMD_PARAM_SET_INT_MASK_ALL 0x20
+#define CG2900_FM_CMD_PARAM_GET_INT_MASK_ALL 0x21
+#define CG2900_FM_CMD_PARAM_SET_INT_MASK 0x22
+#define CG2900_FM_CMD_PARAM_GET_INT_MASK 0x23
+#define CG2900_FM_CMD_PARAM_FM_FW_DOWNLOAD 0x30
+#define CG2900_FM_CMD_PARAM_NONE 0xFF
+
+/* FM Legacy Command Parameters */
+#define CG2900_FM_CMD_LEG_PARAM_WRITE 0x00
+#define CG2900_FM_CMD_LEG_PARAM_IRQ 0x01
+
+/* FM Command Status. */
+#define CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED 0x00
+#define CG2900_FM_CMD_STATUS_HW_FAILURE 0x03
+#define CG2900_FM_CMD_STATUS_INVALID_PARAMS 0x12
+#define CG2900_FM_CMD_STATUS_UNINITILIZED 0x15
+#define CG2900_FM_CMD_STATUS_UNSPECIFIED_ERROR 0x1F
+#define CG2900_FM_CMD_STATUS_COMMAND_DISALLOWED 0x0C
+#define CG2900_FM_CMD_STATUS_FW_WRONG_SEQUENCE_NR 0xF1
+#define CG2900_FM_CMD_STATUS_FW_UNKNOWN_FILE 0xF2
+#define CG2900_FM_CMD_STATUS_FW_FILE_VER_MISMATCH 0xF3
+
+/* FM Interrupts. */
+#define CG2900_FM_IRPT_FIQ 0x0000
+#define CG2900_FM_IRPT_OPERATION_SUCCEEDED 0x0001
+#define CG2900_FM_IRPT_OPERATION_FAILED 0x0002
+#define CG2900_FM_IRPT_BUFFER_FULL 0x0008
+#define CG2900_FM_IRPT_BUFFER_EMPTY 0x0008
+#define CG2900_FM_IRPT_SIGNAL_QUALITY_LOW 0x0010
+#define CG2900_FM_IRPT_MUTE_STATUS_CHANGED 0x0010
+#define CG2900_FM_IRPT_MONO_STEREO_TRANSITION 0x0020
+#define CG2900_FM_IRPT_OVER_MODULATION 0x0020
+#define CG2900_FM_IRPT_RDS_SYNC_FOUND 0x0040
+#define CG2900_FM_IRPT_INPUT_OVERDRIVE 0x0040
+#define CG2900_FM_IRPT_RDS_SYNC_LOST 0x0080
+#define CG2900_FM_IRPT_PI_CODE_CHANGED 0x0100
+#define CG2900_FM_IRPT_REQUEST_BLOCK_AVALIBLE 0x0200
+#define CG2900_FM_IRPT_BUFFER_CLEARED 0x2000
+#define CG2900_FM_IRPT_WARM_BOOT_READY 0x4000
+#define CG2900_FM_IRPT_COLD_BOOT_READY 0x8000
+
+/* FM Legacy Function Command Parameters */
+
+/* AUP_EXT_SetMode Output enum */
+#define CG2900_FM_CMD_AUP_EXT_SET_MODE_DISABLED 0x0000
+#define CG2900_FM_CMD_AUP_EXT_SET_MODE_I2S 0x0001
+#define CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL 0x0002
+
+/* SetControl Conversion enum */
+#define CG2900_FM_CMD_SET_CTRL_CONV_UP 0x0000
+#define CG2900_FM_CMD_SET_CTRL_CONV_DOWN 0x0001
+
+/* AIP_SetMode Input enum */
+#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_ANA 0x0000
+#define CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG 0x0001
+
+/* AIP_BT_SetMode Input enum */
+#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_RESERVED 0x0000
+#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_I2S 0x0001
+#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR 0x0002
+#define CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_FIFO 0x0003
+
+/* FM Lengths */
+#define CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE 8
+#define CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL 8
+#define CG2900_FM_CMD_LEN_AIP_SET_MODE 8
+#define CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL 8
+#define CG2900_FM_CMD_LEN_AIP_BT_SET_MODE 8
+
+/* FM Parameter Lengths = FM command length - length field (1 byte) */
+#define CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_MODE \
+ (CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE - 1)
+#define CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_CTRL \
+ (CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL - 1)
+#define CG2900_FM_CMD_PARAM_LEN_AIP_SET_MODE \
+ (CG2900_FM_CMD_LEN_AIP_SET_MODE - 1)
+#define CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_CTRL \
+ (CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL - 1)
+#define CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_MODE \
+ (CG2900_FM_CMD_LEN_AIP_BT_SET_MODE - 1)
+
+/*
+ * Following defines should be used by users of CG2900 Core, i.e. having no
+ * H:4 header.
+ */
+#define CG2900_FM_USER_GEN_ID_POS_CMD_CMPL \
+ (CG2900_FM_GEN_ID_POS_CMD_CMPL - HCI_H4_SIZE)
+#define CG2900_FM_USER_LEG_STATUS_POS_CMD_CMPL \
+ (CG2900_FM_LEG_STATUS_POS_CMD_CMPL - HCI_H4_SIZE)
+#define CG2900_FM_USER_LEG_FUNC_POS_CMD_CMPL \
+ (CG2900_FM_LEG_FUNC_POS_CMD_CMPL - HCI_H4_SIZE)
+#define CG2900_FM_USER_LEG_HDR_POS_CMD_CMPL \
+ (CG2900_FM_LEG_HDR_POS_CMD_CMPL - HCI_H4_SIZE)
+
+/*
+ * FM Command ID mapped per byte and shifted 3 bits left
+ * Also adds number of parameters at first 3 bits of LSB.
+ */
+#define CG2900_SET_FM_CMD_ID_LSB(__u16_id, __n_data) \
+ ((u8)(((__u16_id << 3) & 0x00FF) | __n_data))
+#define CG2900_SET_FM_CMD_ID_MSB(__u16_id) ((u8)(__u16_id >> 5))
+#define CG2900_GET_FM_CMD_ID(__u16_arg) (__u16_arg >> 3)
+
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AUP_EXT_SET_MODE, 1)
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AUP_EXT_SET_MODE)
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL, 1)
+#define CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL)
+#define CG2900_FM_CMD_ID_AIP_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_SET_MODE, 1)
+#define CG2900_FM_CMD_ID_AIP_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_SET_MODE)
+#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, 1)
+#define CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_BT_SET_CTRL)
+#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_BT_SET_MODE, 1)
+#define CG2900_FM_CMD_ID_AIP_BT_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_BT_SET_MODE)
+
+/* FM Response IDs. Same construction as FM Command IDs */
+#define CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AUP_EXT_SET_MODE, 0)
+#define CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AUP_EXT_SET_MODE)
+#define CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL, 0)
+#define CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL)
+#define CG2900_FM_RSP_ID_AIP_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_SET_MODE, 0)
+#define CG2900_FM_RSP_ID_AIP_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_SET_MODE)
+#define CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_BT_SET_CTRL, 0)
+#define CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_BT_SET_CTRL)
+#define CG2900_FM_RSP_ID_AIP_BT_SET_MODE_LSB \
+ CG2900_SET_FM_CMD_ID_LSB(CG2900_FM_CMD_ID_AIP_BT_SET_MODE, 0)
+#define CG2900_FM_RSP_ID_AIP_BT_SET_MODE_MSB \
+ CG2900_SET_FM_CMD_ID_MSB(CG2900_FM_CMD_ID_AIP_BT_SET_MODE)
+
+/*
+ * GNSS
+ */
+
+struct gnss_hci_hdr {
+ __u8 op_code;
+ __le16 plen;
+} __attribute__((packed));
+
+#endif /* _CG2900_CHIP_H_ */
--
1.6.3.3
^ permalink raw reply related
* [PATCH 3/6] This patch adds support for the ST-Ericsson STLC2690
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:49 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for the ST-Ericsson STLC2690 connectivity
controller. This patch registers into the CG2900 framework.
It handles chip startup and shutdown together with firmware download.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/mfd/Kconfig | 6 +
drivers/mfd/cg2900/Makefile | 1 +
drivers/mfd/cg2900/stlc2690_chip.c | 1105 ++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/stlc2690_chip.h | 37 ++
4 files changed, 1149 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.c
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f1fb334..d47a253 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -590,6 +590,12 @@ config MFD_CG2900_CHIP
help
Support for ST-Ericsson CG2900 Connectivity Controller
+config MFD_STLC2690_CHIP
+ tristate "Support STLC2690 Connectivity controller"
+ depends on MFD_CG2900
+ help
+ Support for ST-Ericsson STLC2690 Connectivity Controller
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index b49bf6b..3b6e442 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -7,3 +7,4 @@ cg2900-objs := cg2900_core.o
cg2900_char_devices.o
export-objs := cg2900_core.o
obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
+obj-$(CONFIG_MFD_STLC2690_CHIP) += stlc2690_chip.o
diff --git a/drivers/mfd/cg2900/stlc2690_chip.c
b/drivers/mfd/cg2900/stlc2690_chip.c
new file mode 100644
index 0000000..dc2d802
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.c
@@ -0,0 +1,1105 @@
+/*
+ * drivers/mfd/cg2900/ste_stlc2690.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#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 <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "hci_defines.h"
+#include "stlc2690_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+#define LINE_BUFFER_LENGTH 128
+#define FILENAME_MAX 128
+
+#define WQ_NAME "stlc2690_wq"
+#define PATCH_INFO_FILE "cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE "cg2900_settings_info.fw"
+
+/* Supported chips */
+#define SUPP_MANUFACTURER 0x30
+#define SUPP_REVISION_MIN 0x0500
+#define SUPP_REVISION_MAX 0x06FF
+
+/* Size of file chunk ID */
+#define FILE_CHUNK_ID_SIZE 1
+#define VS_SEND_FILE_CHUNK_ID_POS 4
+#define BT_CMD_LEN_POS 3
+
+/* State setting macros */
+#define SET_BOOT_STATE(__new_state) \
+ CG2900_SET_STATE("boot_state", stlc2690_info->boot_state, __new_state)
+#define SET_FILE_LOAD_STATE(__new_state) \
+ CG2900_SET_STATE("file_load_state", stlc2690_info->file_load_state, \
+ __new_state)
+#define SET_DOWNLOAD_STATE(__new_state) \
+ CG2900_SET_STATE("download_state", stlc2690_info->download_state, \
+ __new_state)
+
+/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel
+ * for Bluetooth commands in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_CMD 0x01
+
+/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel
+ * for Bluetooth ACL data in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_ACL 0x02
+
+/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel
+ * for Bluetooth events in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_EVT 0x04
+
+/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel
+ * for logging all transmitted H4 packets (on all channels).
+ */
+#define CHANNEL_HCI_LOGGER 0xFA
+
+/** CHANNEL_US_CTRL - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_US_CTRL 0xFC
+
+/** CHANNEL_CORE - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_CORE 0xFD
+
+/**
+ * struct stlc2690_work_struct - Work structure for CG2900 Core module.
+ * @work: Work structure.
+ * @skb: Data packet.
+ * @data: Private data for user.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct stlc2690_work_struct {
+ struct work_struct work;
+ struct sk_buff *skb;
+ void *data;
+};
+
+/**
+ * enum boot_state - BOOT-state for CG2900 Core.
+ * @BOOT_NOT_STARTED: Boot has not yet started.
+ * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address
+ * has been sent.
+ * @BOOT_GET_FILES_TO_LOAD: CG2900 Core is retreiving file to load.
+ * @BOOT_DOWNLOAD_PATCH: CG2900 Core is downloading patches.
+ * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 Core is activating patches and
+ * settings.
+ * @BOOT_READY: CG2900 Core boot is ready.
+ * @BOOT_FAILED: CG2900 Core boot failed.
+ */
+enum boot_state {
+ BOOT_NOT_STARTED,
+ BOOT_SEND_BD_ADDRESS,
+ BOOT_GET_FILES_TO_LOAD,
+ BOOT_DOWNLOAD_PATCH,
+ BOOT_ACTIVATE_PATCHES_AND_SETTINGS,
+ BOOT_READY,
+ BOOT_FAILED
+};
+
+/**
+ * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 Core.
+ * @FILE_LOAD_GET_PATCH: Loading patches.
+ * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings.
+ * @FILE_LOAD_NO_MORE_FILES: No more files to load.
+ * @FILE_LOAD_FAILED: File loading failed.
+ */
+enum file_load_state {
+ FILE_LOAD_GET_PATCH,
+ FILE_LOAD_GET_STATIC_SETTINGS,
+ FILE_LOAD_NO_MORE_FILES,
+ FILE_LOAD_FAILED
+};
+
+/**
+ * enum download_state - BOOT_DOWNLOAD state.
+ * @DOWNLOAD_PENDING: Download in progress.
+ * @DOWNLOAD_SUCCESS: Download successfully finished.
+ * @DOWNLOAD_FAILED: Downloading failed.
+ */
+enum download_state {
+ DOWNLOAD_PENDING,
+ DOWNLOAD_SUCCESS,
+ DOWNLOAD_FAILED
+};
+
+/**
+ * struct stlc2690_device_id - Structure for connecting H4 channel to user.
+ * @name: Name of device.
+ * @h4_channel: HCI H:4 channel used by this device.
+ */
+struct stlc2690_device_id {
+ char *name;
+ int h4_channel;
+};
+
+/**
+ * struct stlc2690_info - Main info structure for STLC2690.
+ * @patch_file_name: Stores patch file name.
+ * @settings_file_name: Stores settings file name.
+ * @fw_file: Stores firmware file (patch or settings).
+ * @file_offset: Current read offset in firmware file.
+ * @chunk_id: Stores current chunk ID of write file operations.
+ * @boot_state: Current BOOT-state of STLC2690.
+ * @file_load_state: Current BOOT_FILE_LOAD-state of STLC2690.
+ * @download_state: Current BOOT_DOWNLOAD-state of STLC2690.
+ * @wq: STLC2690 workqueue.
+ * @chip_dev: Chip info.
+ */
+struct stlc2690_info {
+ char *patch_file_name;
+ char *settings_file_name;
+ const struct firmware *fw_file;
+ int file_offset;
+ u8 chunk_id;
+ enum boot_state boot_state;
+ enum file_load_state file_load_state;
+ enum download_state download_state;
+ struct workqueue_struct *wq;
+ struct cg2900_chip_dev chip_dev;
+};
+
+static struct stlc2690_info *stlc2690_info;
+
+#define NBR_OF_DEVS 6
+
+/*
+ * stlc2690_channels() - Array containing available H4 channels for
the STLC2690
+ * ST-Ericsson Connectivity controller.
+ */
+struct stlc2690_device_id stlc2690_channels[NBR_OF_DEVS] = {
+ {CG2900_BT_CMD, CHANNEL_BT_CMD},
+ {CG2900_BT_ACL, CHANNEL_BT_ACL},
+ {CG2900_BT_EVT, CHANNEL_BT_EVT},
+ {CG2900_HCI_LOGGER, CHANNEL_HCI_LOGGER},
+ {CG2900_US_CTRL, CHANNEL_US_CTRL},
+ {CG2900_CORE, CHANNEL_CORE}
+};
+
+/*
+ * Internal functions
+ */
+
+/**
+ * create_and_send_bt_cmd() - Copy and send sk_buffer.
+ * @data: Data to send.
+ * @length: Length in bytes of data.
+ *
+ * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data
+ * to it, and send the sk_buffer to CG2900 Core.
+ * Note that the data must contain the H:4 header.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ int err;
+
+ skb = alloc_skb(length, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buff with length %d", length);
+ return;
+ }
+
+ memcpy(skb_put(skb, length), data, length);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ logger_config = cg2900_get_hci_logger_config();
+ if (logger_config)
+ err = cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ err = cg2900_send_to_chip(skb, false);
+ if (err) {
+ CG2900_ERR("Failed to transmit to chip (%d)", err);
+ kfree_skb(skb);
+ }
+}
+
+/**
+ * send_bd_address() - Send HCI VS command with BD address to the chip.
+ */
+static void send_bd_address(void)
+{
+ struct bt_vs_store_in_fs_cmd *cmd;
+ struct hci_command_hdr *hdr;
+ u8 *tmp;
+ u8 *data;
+ u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE - 1;
+
+ data = kmalloc(sizeof(*hdr) + plen, GFP_KERNEL);
+ if (!data)
+ return;
+ tmp = data;
+
+ hdr = (struct hci_command_hdr *)tmp;
+ hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS);
+ hdr->plen = plen;
+
+ tmp += sizeof(*hdr);
+ cmd = (struct bt_vs_store_in_fs_cmd *)tmp;
+ cmd->user_id = STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR;
+ cmd->len = BT_BDADDR_SIZE;
+ /* Now copy the BD address received from user space control app. */
+ memcpy(&(cmd->data), bd_address, BT_BDADDR_SIZE);
+
+ SET_BOOT_STATE(BOOT_SEND_BD_ADDRESS);
+
+ create_and_send_bt_cmd(data, sizeof(*hdr) + plen);
+
+ kfree(data);
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @work_func: Work function.
+ * @skb: Data packet.
+ * @data: Private data for caller.
+ */
+static void create_work_item(work_func_t work_func, struct sk_buff *skb,
+ void *data)
+{
+ struct stlc2690_work_struct *new_work;
+ int wq_err = 1;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for stlc2690_work_struct!");
+ return;
+ }
+
+ new_work->skb = skb;
+ new_work->data = data;
+ INIT_WORK(&new_work->work, work_func);
+
+ wq_err = queue_work(stlc2690_info->wq, &new_work->work);
+ if (!wq_err) {
+ CG2900_ERR("Failed to queue work_struct because it's already in"
+ " the queue!");
+ kfree(new_work);
+ }
+}
+
+/**
+ * get_text_line()- 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 get_text_line() function extracts one line of text from input file.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *get_text_line(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;
+}
+
+/**
+ * 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 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;
+ u32 hci_rev;
+ u32 lmp_sub;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kzalloc(LINE_BUFFER_LENGTH, GFP_ATOMIC);
+ if (!line_buffer) {
+ CG2900_ERR("Failed to allocate: file_name 0x%X, "
+ "line_buffer 0x%X",
+ (u32)file_name, (u32)line_buffer);
+ goto finished;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer = get_text_line(line_buffer,
+ min(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 */
+ CG2900_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 == '#')
+ continue;
+
+ hci_rev = 0;
+ lmp_sub = 0;
+
+ CG2900_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 == stlc2690_info->chip_dev.chip.hci_revision
+ && lmp_sub ==
+ stlc2690_info->chip_dev.chip.hci_sub_version) {
+ CG2900_DBG("File matching chip found\n"
+ "\tFile name = %s\n"
+ "\tHCI Revision = 0x%X\n"
+ "\tLMP 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, FILENAME_MAX + 1);
+ }
+ }
+ kfree(line_buffer);
+
+finished:
+ return file_found;
+}
+
+/**
+ * read_and_send_file_part() - Transmit a part of the supplied file.
+ *
+ * The 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 read_and_send_file_part(void)
+{
+ int bytes_to_copy;
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ struct hci_command_hdr *hdr;
+ struct bt_vs_write_file_block_cmd *cmd;
+ u8 *data;
+ u8 plen;
+
+ /* Calculate number of bytes to copy;
+ * either max bytes for HCI packet or number of bytes left in file
+ */
+ bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE,
+ (int)(stlc2690_info->fw_file->size -
+ stlc2690_info->file_offset));
+
+ if (bytes_to_copy <= 0) {
+ /* Nothing more to read in file. */
+ SET_DOWNLOAD_STATE(DOWNLOAD_SUCCESS);
+ stlc2690_info->chunk_id = 0;
+ stlc2690_info->file_offset = 0;
+ return;
+ }
+
+ /* There is more data to send */
+ logger_config = cg2900_get_hci_logger_config();
+
+ /* There are bytes to transmit. Allocate a sk_buffer. */
+ plen = sizeof(*cmd) - 1 + bytes_to_copy;
+ skb = cg2900_alloc_skb(sizeof(*hdr) + plen, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buffer");
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ return;
+ }
+
+ skb_put(skb, sizeof(*hdr) + plen);
+
+ data = skb->data;
+ hdr = (struct hci_command_hdr *)data;
+ hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_WRITE_FILE_BLOCK);
+ hdr->plen = plen;
+
+ data += sizeof(*hdr);
+ cmd = (struct bt_vs_write_file_block_cmd *)data;
+ cmd->id = stlc2690_info->chunk_id;
+ stlc2690_info->chunk_id++;
+
+ /* Copy the data from offset position */
+ memcpy(&(cmd->data),
+ &(stlc2690_info->fw_file->data[stlc2690_info->file_offset]),
+ bytes_to_copy);
+
+ /* Increase offset with number of bytes copied */
+ stlc2690_info->file_offset += bytes_to_copy;
+
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+}
+
+/**
+ * send_settings_file() - Transmit settings file.
+ *
+ * The 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 send_settings_file(void)
+{
+ /* Transmit a file part */
+ read_and_send_file_part();
+
+ if (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Settings file finished. Release used resources */
+ CG2900_DBG("Settings file finished, release used resources");
+
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+
+ SET_FILE_LOAD_STATE(FILE_LOAD_NO_MORE_FILES);
+
+ /* Create and send HCI VS Store In FS command with bd address. */
+ send_bd_address();
+}
+
+/**
+ * send_patch_file - Transmit patch file.
+ *
+ * The 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 send_patch_file(void)
+{
+ int err;
+
+ /*
+ * Transmit a part of the supplied file to the controller.
+ * When nothing more to read, continue to close the patch file.
+ */
+ read_and_send_file_part();
+
+ if (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Patch file finished. Release used resources */
+ CG2900_DBG("Patch file finished, release used resources");
+
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+
+ err = request_firmware(&(stlc2690_info->fw_file),
+ stlc2690_info->settings_file_name,
+ stlc2690_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get settings file (%d)", err);
+ goto error_handling;
+ }
+
+ /* Now send the settings file */
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_STATIC_SETTINGS);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ send_settings_file();
+ return;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(err);
+}
+
+/**
+ * work_reset_after_error() - Handle reset.
+ * @work: Reference to work data.
+ *
+ * Handle a reset after received command complete event.
+ */
+static void work_reset_after_error(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work = NULL;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ cg2900_chip_startup_finished(-EIO);
+
+ kfree(current_work);
+}
+
+/**
+ * work_load_patch_and_settings() - Start loading patches and settings.
+ * @work: Reference to work data.
+ */
+static void work_load_patch_and_settings(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work;
+ int err = 0;
+ bool file_found;
+ const struct firmware *patch_info;
+ const struct firmware *settings_info;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ /* Check that we are in the right state */
+ if (stlc2690_info->boot_state != BOOT_GET_FILES_TO_LOAD)
+ goto finished;
+
+ /* Open patch info file. */
+ err = request_firmware(&patch_info, PATCH_INFO_FILE,
+ stlc2690_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get patch info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the patch info file.
+ * See if we can find the right patch file as well
+ */
+ file_found = get_file_to_load(patch_info,
+ &(stlc2690_info->patch_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(patch_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find patch file! Major error!");
+ goto error_handling;
+ }
+
+ /* Open settings info file. */
+ err = request_firmware(&settings_info, FACTORY_SETTINGS_INFO_FILE,
+ stlc2690_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get settings info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the settings info file.
+ * See if we can find the right settings file as well
+ */
+ file_found = get_file_to_load(settings_info,
+ &(stlc2690_info->settings_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(settings_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find settings file! Major error!");
+ goto error_handling;
+ }
+
+ /* We now all info needed */
+ SET_BOOT_STATE(BOOT_DOWNLOAD_PATCH);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_PATCH);
+ stlc2690_info->chunk_id = 0;
+ stlc2690_info->file_offset = 0;
+ stlc2690_info->fw_file = NULL;
+
+ /* OK. Now it is time to download the patches */
+ err = request_firmware(&(stlc2690_info->fw_file),
+ stlc2690_info->patch_file_name,
+ stlc2690_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get patch file (%d)", err);
+ goto error_handling;
+ }
+ send_patch_file();
+
+ goto finished;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+finished:
+ kfree(current_work);
+}
+
+/**
+ * work_cont_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 work_cont_with_file_download(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ /* Continue to send patches or settings to the controller */
+ if (stlc2690_info->file_load_state == FILE_LOAD_GET_PATCH)
+ send_patch_file();
+ else if (stlc2690_info->file_load_state ==
+ FILE_LOAD_GET_STATIC_SETTINGS)
+ send_settings_file();
+ else
+ CG2900_INFO("No more files to load");
+
+ kfree(current_work);
+}
+
+/**
+ * handle_reset_cmd_complete() - Handle a received HCI Command
Complete event for a Reset command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_reset_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ CG2900_INFO("Received Reset complete event");
+
+ if (stlc2690_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+ return false;
+
+ status = data[0];
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * The boot sequence is now finished successfully.
+ * Set states and signal to waiting thread.
+ */
+ SET_BOOT_STATE(BOOT_READY);
+ cg2900_chip_startup_finished(0);
+ } else {
+ CG2900_ERR("Received Reset complete event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ }
+ return true;
+}
+
+/**
+ * handle_vs_store_in_fs_cmd_complete() - 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 handle_vs_store_in_fs_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ CG2900_INFO("Received Store_in_FS complete event");
+
+ if (stlc2690_info->boot_state != BOOT_SEND_BD_ADDRESS)
+ return false;
+
+ status = data[0];
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ struct hci_command_hdr cmd;
+
+ /* Send HCI Reset command to activate patches */
+ SET_BOOT_STATE(BOOT_ACTIVATE_PATCHES_AND_SETTINGS);
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI Reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } else {
+ CG2900_ERR("Command Complete for StoreInFS received with "
+ "error 0x%X", status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ create_work_item(work_reset_after_error, NULL, NULL);
+ }
+ /* We have now handled the packet */
+ return true;
+}
+
+/**
+ * handle_vs_write_file_block_cmd_complete() - 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 handle_vs_write_file_block_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ if ((stlc2690_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (stlc2690_info->download_state != DOWNLOAD_PENDING))
+ return false;
+
+ status = data[0];
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /* Received good confirmation. Start work to continue. */
+ create_work_item(work_cont_with_file_download, NULL, NULL);
+ } else {
+ CG2900_ERR("Command Complete for WriteFileBlock received with "
+ "error 0x%X", status);
+ SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+ SET_BOOT_STATE(BOOT_FAILED);
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+ create_work_item(work_reset_after_error, NULL, NULL);
+ }
+ /* We have now handled the packet */
+ return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if received data should be handled.
+ * @skb: Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled by STLC2690. If so handle it correctly.
+ * Received data is always HCI BT Event.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_rx_data_bt_evt(struct sk_buff *skb)
+{
+ bool pkt_handled = false;
+ /* skb cannot be NULL here so it is safe to de-reference */
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ struct hci_event_hdr *evt;
+ struct hci_ev_cmd_complete *cmd_complete;
+ u16 op_code;
+
+ evt = (struct hci_event_hdr *)data;
+
+ /* First check the event code. Only handle Command Complete Event */
+ if (HCI_EV_CMD_COMPLETE != evt->evt)
+ return false;
+
+ data += sizeof(*evt);
+ cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+ op_code = le16_to_cpu(cmd_complete->opcode);
+
+ CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+ data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+ if (op_code == HCI_OP_RESET)
+ pkt_handled = handle_reset_cmd_complete(data);
+ else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS)
+ pkt_handled = handle_vs_store_in_fs_cmd_complete(data);
+ else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled = handle_vs_write_file_block_cmd_complete(data);
+
+ if (pkt_handled)
+ kfree_skb(skb);
+
+ return pkt_handled;
+}
+
+/**
+ * chip_startup() - Start the chip.
+ * @dev: Chip info.
+ *
+ * The chip_startup() function downloads patches and other needed start
+ * procedures.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_startup(struct cg2900_chip_dev *dev)
+{
+ /* Start the boot sequence */
+ SET_BOOT_STATE(BOOT_GET_FILES_TO_LOAD);
+ create_work_item(work_load_patch_and_settings, NULL, NULL);
+
+ return 0;
+}
+
+/**
+ * data_from_chip() - Called when data shall be sent to the chip.
+ * @dev: Chip info.
+ * @cg2900_dev: CG2900 user for this packet.
+ * @skb: Packet received.
+ *
+ * The data_from_chip() function checks if packet is a response for a packet it
+ * itself has transmitted.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_from_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ /* Then check if this is a response to data we have sent */
+ return handle_rx_data_bt_evt(skb);
+}
+
+/**
+ * get_h4_channel() - Returns H:4 channel for the name.
+ * @name: Chip info.
+ * @h4_channel: CG2900 user for this packet.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENXIO if channel is not found.
+ */
+static int get_h4_channel(char *name, int *h4_channel)
+{
+ int i;
+ int err = -ENXIO;
+
+ *h4_channel = -1;
+
+ for (i = 0; *h4_channel == -1 && i < NBR_OF_DEVS; i++) {
+ if (0 == strncmp(name, stlc2690_channels[i].name,
+ CG2900_MAX_NAME_SIZE)) {
+ /* Device found. Return H4 channel */
+ *h4_channel = stlc2690_channels[i].h4_channel;
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+/**
+ * check_chip_support() - Checks if connected chip is handled by this driver.
+ * @dev: Chip info structure.
+ *
+ * If supported return true and fill in @callbacks.
+ *
+ * Returns:
+ * true if chip is handled by this driver.
+ * false otherwise.
+ */
+static bool check_chip_support(struct cg2900_chip_dev *dev)
+{
+ CG2900_INFO("check_chip_support");
+
+ /*
+ * Check if this is a CG2690 revision. We do not care about
+ * the sub-version at the moment.
+ * Change this if necessary.
+ */
+ if ((dev->chip.manufacturer != SUPP_MANUFACTURER) ||
+ (dev->chip.hci_revision < SUPP_REVISION_MIN) ||
+ (dev->chip.hci_revision > SUPP_REVISION_MAX)) {
+ CG2900_DBG("Chip not supported by STLC2690 driver\n"
+ "\tMan: 0x%02X\n"
+ "\tRev: 0x%04X\n"
+ "\tSub: 0x%04X",
+ dev->chip.manufacturer, dev->chip.hci_revision,
+ dev->chip.hci_sub_version);
+ return false;
+ }
+
+ CG2900_INFO("Chip supported by the STLC2690 driver");
+
+ /* Store needed data */
+ dev->user_data = stlc2690_info;
+ memcpy(&(stlc2690_info->chip_dev), dev, sizeof(*dev));
+ /* Set the callbacks */
+ dev->cb.chip_startup = chip_startup;
+ dev->cb.data_from_chip = data_from_chip;
+ dev->cb.get_h4_channel = get_h4_channel;
+
+ return true;
+}
+
+static struct cg2900_id_callbacks stlc2690_id_callbacks = {
+ .check_chip_support = check_chip_support
+};
+
+/**
+ * stlc2690_init() - Initialize module.
+ *
+ * The stlc2690_init() function initialize the CG2690 driver, then register to
+ * the CG2900 Core.
+ *
+ * Returns:
+ * 0 if success.
+ * -ENOMEM for failed alloc or structure creation.
+ * Error codes generated by cg2900_register_chip_driver.
+ */
+static int __init stlc2690_init(void)
+{
+ int err = 0;
+
+ CG2900_INFO("stlc2690_init");
+
+ stlc2690_info = kzalloc(sizeof(*stlc2690_info), GFP_ATOMIC);
+ if (!stlc2690_info) {
+ CG2900_ERR("Couldn't allocate stlc2690_info");
+ err = -ENOMEM;
+ goto finished;
+ }
+
+ stlc2690_info->wq = create_singlethread_workqueue(WQ_NAME);
+ if (!stlc2690_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ENOMEM;
+ goto err_handling_free_info;
+ }
+
+ /*
+ * Allocate file names that will be used, deallocated in stlc2690_exit.
+ */
+ stlc2690_info->patch_file_name = kzalloc(FILENAME_MAX + 1, GFP_ATOMIC);
+ if (!stlc2690_info->patch_file_name) {
+ CG2900_ERR("Couldn't allocate name buffer for patch file.");
+ err = -ENOMEM;
+ goto err_handling_destroy_wq;
+ }
+ /*
+ * Allocate file names that will be used, deallocated in stlc2690_exit.
+ */
+ stlc2690_info->settings_file_name = kzalloc(FILENAME_MAX + 1,
+ GFP_ATOMIC);
+ if (!stlc2690_info->settings_file_name) {
+ CG2900_ERR("Couldn't allocate name buffers settings file.");
+ err = -ENOMEM;
+ goto err_handling_free_patch_name;
+ }
+
+ err = cg2900_register_chip_driver(&stlc2690_id_callbacks);
+ if (err) {
+ CG2900_ERR("Couldn't register chip driver (%d)", err);
+ goto err_handling_free_settings_name;
+ }
+
+ goto finished;
+
+err_handling_free_settings_name:
+ kfree(stlc2690_info->settings_file_name);
+err_handling_free_patch_name:
+ kfree(stlc2690_info->patch_file_name);
+err_handling_destroy_wq:
+ destroy_workqueue(stlc2690_info->wq);
+err_handling_free_info:
+ kfree(stlc2690_info);
+ stlc2690_info = NULL;
+finished:
+ return err;
+}
+
+/**
+ * stlc2690_exit() - Remove module.
+ */
+static void __exit stlc2690_exit(void)
+{
+ CG2900_INFO("stlc2690_exit");
+
+ if (!stlc2690_info)
+ return;
+
+ kfree(stlc2690_info->settings_file_name);
+ kfree(stlc2690_info->patch_file_name);
+ destroy_workqueue(stlc2690_info->wq);
+ kfree(stlc2690_info);
+ stlc2690_info = NULL;
+}
+
+module_init(stlc2690_init);
+module_exit(stlc2690_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux STLC2690 Connectivity Device Driver");
diff --git a/drivers/mfd/cg2900/stlc2690_chip.h
b/drivers/mfd/cg2900/stlc2690_chip.h
new file mode 100644
index 0000000..deb974d
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.h
@@ -0,0 +1,37 @@
+/*
+ * drivers/mfd/cg2900/stlc2690_chip.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#ifndef _STLC2690_CHIP_H_
+#define _STLC2690_CHIP_H_
+
+/* BT VS Store In FS command */
+#define STLC2690_BT_OP_VS_STORE_IN_FS 0xFC22
+struct bt_vs_store_in_fs_cmd {
+ __u8 user_id;
+ __u8 len;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* BT VS Write File Block command */
+#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E
+struct bt_vs_write_file_block_cmd {
+ __u8 id;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* User ID for storing BD address in chip using Store_In_FS command */
+#define STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE
+
+#endif /* _STLC2690_CHIP_H_ */
--
1.6.3.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox