* [PATCH 1/1] Fix small coding style issues
From: Elvis Pfützenreuter @ 2010-10-22 19:18 UTC (permalink / raw)
To: linux-bluetooth; +Cc: epx
Based on http://lxr.linux.no/linux+v2.6.36/Documentation/CodingStyle#L171
---
health/hdp_util.c | 24 +++++++++++++-----------
health/mcap_lib.h | 8 ++++----
2 files changed, 17 insertions(+), 15 deletions(-)
diff --git a/health/hdp_util.c b/health/hdp_util.c
index b0399f8..fefb6d3 100644
--- a/health/hdp_util.c
+++ b/health/hdp_util.c
@@ -181,8 +181,9 @@ static gboolean parse_role(DBusMessageIter *iter, gpointer data, GError **err)
dbus_message_iter_recurse(iter, &value);
ctype = dbus_message_iter_get_arg_type(&value);
string = &value;
- } else
+ } else {
string = iter;
+ }
if (ctype != DBUS_TYPE_STRING) {
g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
@@ -191,11 +192,11 @@ static gboolean parse_role(DBusMessageIter *iter, gpointer data, GError **err)
}
dbus_message_iter_get_basic(string, &role);
- if (g_ascii_strcasecmp(role, HDP_SINK_ROLE_AS_STRING) == 0)
+ if (g_ascii_strcasecmp(role, HDP_SINK_ROLE_AS_STRING) == 0) {
app->role = HDP_SINK;
- else if (g_ascii_strcasecmp(role, HDP_SOURCE_ROLE_AS_STRING) == 0)
+ } else if (g_ascii_strcasecmp(role, HDP_SOURCE_ROLE_AS_STRING) == 0) {
app->role = HDP_SOURCE;
- else {
+ } else {
g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
"Role value should be \"source\" or \"sink\"");
return FALSE;
@@ -221,8 +222,9 @@ static gboolean parse_desc(DBusMessageIter *iter, gpointer data, GError **err)
dbus_message_iter_recurse(iter, &variant);
ctype = dbus_message_iter_get_arg_type(&variant);
string = &variant;
- } else
+ } else {
string = iter;
+ }
if (ctype != DBUS_TYPE_STRING) {
g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
@@ -395,12 +397,12 @@ static gboolean register_service_protocols(struct hdp_adapter *adapter,
goto end;
}
- if (!sdp_list_append( mcap_list, mcap_ver)) {
+ if (!sdp_list_append(mcap_list, mcap_ver)) {
ret = FALSE;
goto end;
}
- if (!sdp_list_append( proto_list, mcap_list)) {
+ if (!sdp_list_append(proto_list, mcap_list)) {
ret = FALSE;
goto end;
}
@@ -442,7 +444,7 @@ static gboolean register_service_profiles(sdp_record_t *sdp_record)
sdp_profile_desc_t hdp_profile;
/* set hdp information */
- sdp_uuid16_create( &hdp_profile.uuid, HDP_SVCLASS_ID);
+ sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID);
hdp_profile.version = HDP_VERSION;
profile_list = sdp_list_append(NULL, &hdp_profile);
if (!profile_list)
@@ -459,7 +461,7 @@ static gboolean register_service_profiles(sdp_record_t *sdp_record)
return ret;
}
-static gboolean register_service_aditional_protocols(
+static gboolean register_service_additional_protocols(
struct hdp_adapter *adapter,
sdp_record_t *sdp_record)
{
@@ -502,7 +504,7 @@ static gboolean register_service_aditional_protocols(
goto end;
}
- if (!sdp_list_append( proto_list, mcap_list)) {
+ if (!sdp_list_append(proto_list, mcap_list)) {
ret = FALSE;
goto end;
}
@@ -709,7 +711,7 @@ gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list)
goto fail;
if (!register_service_profiles(sdp_record))
goto fail;
- if (!register_service_aditional_protocols(adapter, sdp_record))
+ if (!register_service_additional_protocols(adapter, sdp_record))
goto fail;
sdp_set_info_attr(sdp_record, HDP_SERVICE_NAME, HDP_SERVICE_PROVIDER,
diff --git a/health/mcap_lib.h b/health/mcap_lib.h
index daee1f8..7740623 100644
--- a/health/mcap_lib.h
+++ b/health/mcap_lib.h
@@ -69,7 +69,7 @@ struct sync_info_ind_data;
/************ Callbacks ************/
-/* mdl callbacks */
+/* MDL callbacks */
typedef void (* mcap_mdl_event_cb) (struct mcap_mdl *mdl, gpointer data);
typedef void (* mcap_mdl_operation_conf_cb) (struct mcap_mdl *mdl, uint8_t conf,
@@ -85,7 +85,7 @@ typedef uint8_t (* mcap_remote_mdl_conn_req_cb) (struct mcap_mcl *mcl,
typedef uint8_t (* mcap_remote_mdl_reconn_req_cb) (struct mcap_mdl *mdl,
gpointer data);
-/* mcl callbacks */
+/* MCL callbacks */
typedef void (* mcap_mcl_event_cb) (struct mcap_mcl *mcl, gpointer data);
typedef void (* mcap_mcl_connect_cb) (struct mcap_mcl *mcl, GError *err,
@@ -115,7 +115,7 @@ typedef void (* mcap_sync_set_cb) (struct mcap_mcl *mcl,
/************ Operations ************/
-/* Mdl operations*/
+/* MDL operations */
gboolean mcap_create_mdl(struct mcap_mcl *mcl,
uint8_t mdepid,
@@ -155,7 +155,7 @@ gboolean mcap_mdl_abort(struct mcap_mdl *mdl,
int mcap_mdl_get_fd(struct mcap_mdl *mdl);
uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl);
-/* Mcl operations*/
+/* MCL operations */
gboolean mcap_create_mcl(struct mcap_instance *ms,
const bdaddr_t *addr,
--
1.7.0.4
^ permalink raw reply related
* Re: [PATCH 4/6] Bluetooth: Add LE connection support to L2CAP
From: Gustavo F. Padovan @ 2010-10-22 19:14 UTC (permalink / raw)
To: Ville Tervo; +Cc: linux-bluetooth
In-Reply-To: <1287406976-13463-5-git-send-email-ville.tervo@nokia.com>
Hi Ville,
* Ville Tervo <ville.tervo@nokia.com> [2010-10-18 16:02:54 +0300]:
> Add basic LE connection support to L2CAP. LE
> connection can be created by specifying cid
> in struct sockaddr_l2
>
> Signed-off-by: Ville Tervo <ville.tervo@nokia.com>
> ---
> include/net/bluetooth/l2cap.h | 3 +++
> net/bluetooth/l2cap.c | 32 ++++++++++++++++++++++++--------
> 2 files changed, 27 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
> index c819c8b..cc3a140 100644
> --- a/include/net/bluetooth/l2cap.h
> +++ b/include/net/bluetooth/l2cap.h
> @@ -160,6 +160,9 @@ struct l2cap_conn_rsp {
> /* channel indentifier */
> #define L2CAP_CID_SIGNALING 0x0001
> #define L2CAP_CID_CONN_LESS 0x0002
> +#define L2CAP_CID_LE_DATA 0x0004
> +#define L2CAP_CID_LE_SIGNALING 0x0005
> +#define L2CAP_CID_SMP 0x0006
> #define L2CAP_CID_DYN_START 0x0040
> #define L2CAP_CID_DYN_END 0xffff
>
> diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
> index 16049de..bf5daf3 100644
> --- a/net/bluetooth/l2cap.c
> +++ b/net/bluetooth/l2cap.c
> @@ -617,6 +617,12 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
> for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
> bh_lock_sock(sk);
>
> + if (conn->hcon->type == LE_LINK) {
> + l2cap_sock_clear_timer(sk);
> + sk->sk_state = BT_CONNECTED;
> + sk->sk_state_change(sk);
> + }
> +
> if (sk->sk_type != SOCK_SEQPACKET &&
> sk->sk_type != SOCK_STREAM) {
> l2cap_sock_clear_timer(sk);
> @@ -675,7 +681,11 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, u8 status)
>
> BT_DBG("hcon %p conn %p", hcon, conn);
>
> - conn->mtu = hcon->hdev->acl_mtu;
> + if (hcon->hdev->le_mtu && hcon->type == LE_LINK)
> + conn->mtu = hcon->hdev->le_mtu;
> + else
> + conn->mtu = hcon->hdev->acl_mtu;
> +
> conn->src = &hcon->hdev->bdaddr;
> conn->dst = &hcon->dst;
>
> @@ -1102,8 +1112,13 @@ static int l2cap_do_connect(struct sock *sk)
> }
> }
>
> - hcon = hci_connect(hdev, ACL_LINK, dst,
> + if (l2cap_pi(sk)->dcid == L2CAP_CID_LE_DATA)
> + hcon = hci_connect(hdev, LE_LINK, dst,
> + l2cap_pi(sk)->sec_level, auth_type);
> + else
I think that "else if (l2cap_pi(sk)->psm)" is better here, we do not
want to permit go ahead with psm 0.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: [PATCH 3/6] Bluetooth: Use LE buffers for LE traffic
From: Gustavo F. Padovan @ 2010-10-22 18:53 UTC (permalink / raw)
To: Ville Tervo; +Cc: linux-bluetooth
In-Reply-To: <1287406976-13463-4-git-send-email-ville.tervo@nokia.com>
Hi Ville,
* Ville Tervo <ville.tervo@nokia.com> [2010-10-18 16:02:53 +0300]:
> BLuetooth chips may have separate buffers for
> LE traffic. This patch add support to use LE
> buffers provided by the chip.
>
> Signed-off-by: Ville Tervo <ville.tervo@nokia.com>
> ---
> include/net/bluetooth/hci.h | 2 +
> include/net/bluetooth/hci_core.h | 5 +++
> net/bluetooth/hci_conn.c | 6 +++
> net/bluetooth/hci_core.c | 74 +++++++++++++++++++++++++++++++++++--
> net/bluetooth/hci_event.c | 40 +++++++++++++++++++-
> 5 files changed, 121 insertions(+), 6 deletions(-)
>
> diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> index 02055b9..b42edf0 100644
> --- a/include/net/bluetooth/hci.h
> +++ b/include/net/bluetooth/hci.h
> @@ -189,6 +189,8 @@ enum {
>
> #define LMP_EV4 0x01
> #define LMP_EV5 0x02
> +#define LMP_NO_BREDR 0x20
You are not using this one.
> +#define LMP_LE 0x40
>
> #define LMP_SNIFF_SUBR 0x02
> #define LMP_EDR_ESCO_2M 0x20
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index fc2aaee..326d290 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -103,15 +103,19 @@ struct hci_dev {
> atomic_t cmd_cnt;
> unsigned int acl_cnt;
> unsigned int sco_cnt;
> + unsigned int le_cnt;
>
> unsigned int acl_mtu;
> unsigned int sco_mtu;
> + unsigned int le_mtu;
> unsigned int acl_pkts;
> unsigned int sco_pkts;
> + unsigned int le_pkts;
>
> unsigned long cmd_last_tx;
> unsigned long acl_last_tx;
> unsigned long sco_last_tx;
> + unsigned long le_last_tx;
>
> struct workqueue_struct *workqueue;
>
> @@ -469,6 +473,7 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
> #define lmp_sniffsubr_capable(dev) ((dev)->features[5] & LMP_SNIFF_SUBR)
> #define lmp_esco_capable(dev) ((dev)->features[3] & LMP_ESCO)
> #define lmp_ssp_capable(dev) ((dev)->features[6] & LMP_SIMPLE_PAIR)
> +#define lmp_le_capable(dev) ((dev)->features[4] & LMP_LE)
>
> /* ----- HCI protocols ----- */
> struct hci_proto {
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index c1eb8e0..c7309e4 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -324,6 +324,11 @@ int hci_conn_del(struct hci_conn *conn)
>
> /* Unacked frames */
> hdev->acl_cnt += conn->sent;
> + } else if (conn->type == LE_LINK) {
> + if (hdev->le_pkts)
> + hdev->le_cnt += conn->sent;
> + else
> + hdev->acl_cnt += conn->sent;
> } else {
> struct hci_conn *acl = conn->link;
> if (acl) {
> @@ -409,6 +414,7 @@ struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8
> return NULL;
>
> hci_le_connect(le);
> + hci_conn_hold(le);
>
This should be in 2/6, right?
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: [PATCH 2/6] Bluetooth: Add LE connect support
From: Gustavo F. Padovan @ 2010-10-22 18:46 UTC (permalink / raw)
To: Ville Tervo; +Cc: linux-bluetooth
In-Reply-To: <1287406976-13463-3-git-send-email-ville.tervo@nokia.com>
* Ville Tervo <ville.tervo@nokia.com> [2010-10-18 16:02:52 +0300]:
> Bluetooth V4.0 adds support for Low Energy (LE)
> connections. Specification introduses new set
> of hci commands to control LE connection.
> This patch adds logic to create, cancel and
> disconnect LE connections
>
> Signed-off-by: Ville Tervo <ville.tervo@nokia.com>
> ---
> include/net/bluetooth/hci.h | 2 +
> include/net/bluetooth/hci_core.h | 21 +++++++--
> net/bluetooth/hci_conn.c | 51 +++++++++++++++++++-
> net/bluetooth/hci_event.c | 94 +++++++++++++++++++++++++++++++++++++-
> 4 files changed, 160 insertions(+), 8 deletions(-)
>
> diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
> index ee5beec..02055b9 100644
> --- a/include/net/bluetooth/hci.h
> +++ b/include/net/bluetooth/hci.h
> @@ -159,6 +159,8 @@ enum {
> #define SCO_LINK 0x00
> #define ACL_LINK 0x01
> #define ESCO_LINK 0x02
> +/* Low Energy links do not have defined link type. Use invented one */
> +#define LE_LINK 0x80
>
> /* LMP features */
> #define LMP_3SLOT 0x01
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index ebec8c9..fc2aaee 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -60,6 +60,7 @@ struct hci_conn_hash {
> spinlock_t lock;
> unsigned int acl_num;
> unsigned int sco_num;
> + unsigned int le_num;
> };
>
> struct bdaddr_list {
> @@ -272,20 +273,32 @@ static inline void hci_conn_hash_add(struct hci_dev *hdev, struct hci_conn *c)
> {
> struct hci_conn_hash *h = &hdev->conn_hash;
> list_add(&c->list, &h->list);
> - if (c->type == ACL_LINK)
> + switch (c->type) {
> + case ACL_LINK:
> h->acl_num++;
> - else
> + break;
> + case LE_LINK:
> + h->le_num++;
> + break;
> + default:
I would add a 'case SCO_LINK' here. It changes nothing actually, but
make switch easy to understand.
> h->sco_num++;
> + }
> }
>
> static inline void hci_conn_hash_del(struct hci_dev *hdev, struct hci_conn *c)
> {
> struct hci_conn_hash *h = &hdev->conn_hash;
> list_del(&c->list);
> - if (c->type == ACL_LINK)
> + switch (c->type) {
> + case ACL_LINK:
> h->acl_num--;
> - else
> + break;
> + case LE_LINK:
> + h->le_num--;
> + break;
> + default:
Same here.
> h->sco_num--;
> + }
> }
>
> static inline struct hci_conn *hci_conn_hash_lookup_handle(struct hci_dev *hdev,
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 0b1e460..c1eb8e0 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -45,6 +45,32 @@
> #include <net/bluetooth/bluetooth.h>
> #include <net/bluetooth/hci_core.h>
>
> +void hci_le_connect(struct hci_conn *conn)
> +{
> + struct hci_dev *hdev = conn->hdev;
> + struct hci_cp_le_create_conn cp;
> +
> + conn->state = BT_CONNECT;
> + conn->out = 1;
> +
> + memset(&cp, 0, sizeof(cp));
> + cp.scan_interval = cpu_to_le16(0x0004);
> + cp.scan_window = cpu_to_le16(0x0004);
> + bacpy(&cp.peer_addr, &conn->dst);
> + cp.conn_interval_min = cpu_to_le16(0x0008);
> + cp.conn_interval_max = cpu_to_le16(0x0100);
> + cp.supervision_timeout = cpu_to_le16(0x0064);
> + cp.min_ce_len = cpu_to_le16(0x0001);
> + cp.max_ce_len = cpu_to_le16(0x0001);
> +
> + hci_send_cmd(hdev, HCI_OP_LE_CREATE_CONN, sizeof(cp), &cp);
> +}
> +
> +static void hci_le_connect_cancel(struct hci_conn *conn)
> +{
> + hci_send_cmd(conn->hdev, HCI_OP_LE_CREATE_CONN_CANCEL, 0, NULL);
> +}
> +
> void hci_acl_connect(struct hci_conn *conn)
> {
> struct hci_dev *hdev = conn->hdev;
> @@ -192,8 +218,12 @@ static void hci_conn_timeout(unsigned long arg)
> switch (conn->state) {
> case BT_CONNECT:
> case BT_CONNECT2:
> - if (conn->type == ACL_LINK && conn->out)
> - hci_acl_connect_cancel(conn);
> + if (conn->out) {
> + if (conn->type == ACL_LINK)
> + hci_acl_connect_cancel(conn);
> + else if (conn->type == LE_LINK)
> + hci_le_connect_cancel(conn);
> + }
> break;
> case BT_CONFIG:
> case BT_CONNECTED:
> @@ -359,15 +389,30 @@ struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
> }
> EXPORT_SYMBOL(hci_get_route);
>
> -/* Create SCO or ACL connection.
> +/* Create SCO, ACL or LE connection.
> * Device _must_ be locked */
> struct hci_conn *hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst, __u8 sec_level, __u8 auth_type)
> {
> struct hci_conn *acl;
> struct hci_conn *sco;
> + struct hci_conn *le;
>
> BT_DBG("%s dst %s", hdev->name, batostr(dst));
>
> + if (type == LE_LINK) {
> + le = hci_conn_hash_lookup_ba(hdev, LE_LINK, dst);
> +
> + if (!le)
> + le = hci_conn_add(hdev, LE_LINK, dst);
> +
> + if (!le)
> + return NULL;
> +
> + hci_le_connect(le);
> +
> + return le;
> + }
> +
> if (!(acl = hci_conn_hash_lookup_ba(hdev, ACL_LINK, dst))) {
> if (!(acl = hci_conn_add(hdev, ACL_LINK, dst)))
> return NULL;
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index 84093b0..4061613 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -822,6 +822,43 @@ static void hci_cs_exit_sniff_mode(struct hci_dev *hdev, __u8 status)
> hci_dev_unlock(hdev);
> }
>
> +static void hci_cs_le_create_conn(struct hci_dev *hdev, __u8 status)
> +{
> + struct hci_cp_le_create_conn *cp;
> + struct hci_conn *conn;
> +
> + BT_DBG("%s status 0x%x", hdev->name, status);
> +
> + cp = hci_sent_cmd_data(hdev, HCI_OP_LE_CREATE_CONN);
> + if (!cp)
> + return;
> +
> + hci_dev_lock(hdev);
> +
> + conn = hci_conn_hash_lookup_ba(hdev, LE_LINK, &cp->peer_addr);
> +
> + BT_DBG("%s bdaddr %s conn %p", hdev->name, batostr(&cp->peer_addr),
> + conn);
> +
> + if (status) {
> + if (conn && conn->state == BT_CONNECT) {
> + conn->state = BT_CLOSED;
> + hci_proto_connect_cfm(conn, status);
> + hci_conn_del(conn);
> + }
> + } else {
> + if (!conn) {
> + conn = hci_conn_add(hdev, LE_LINK, &cp->peer_addr);
> + if (conAvoid things like that in your patch.n)
> + conn->out = 1;
> + else
> + BT_ERR("No memory for new connection");
> + }
> + }
> +
> + hci_dev_unlock(hdev);
> +}
> +
> static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
> {
> __u8 status = *((__u8 *) skb->data);
> @@ -1024,7 +1061,6 @@ static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff
> conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
> if (conn) {
> conn->state = BT_CLOSED;
> -
Avoid things like that in your patch.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: [PATCH 2/2 v2] Bluetooth: Fix system crash bug of no send queue protect
From: Gustavo F. Padovan @ 2010-10-22 17:34 UTC (permalink / raw)
To: Haijun Liu; +Cc: linux-bluetooth
In-Reply-To: <1287714419-13545-2-git-send-email-haijun.liu@atheros.com>
* Haijun Liu <haijun.liu@atheros.com> [2010-10-22 10:26:59 +0800]:
> During test session with another vendor's bt stack, found that
> without lock protect for TX_QUEUE(sk) will cause system crash while
> data transfer over AMP controller. So I just add lock protect for
> TX_QUEUE(sk).
We already use the default socket lock protection. Is it not working for
you? Why? Could you show a crash case that requires your patch to fix
it?
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: [PATCH 1/2 v2] Bluetooth: Fix system crash caused by del_timer()
From: Gustavo F. Padovan @ 2010-10-22 17:18 UTC (permalink / raw)
To: Haijun Liu; +Cc: linux-bluetooth
In-Reply-To: <1287714419-13545-1-git-send-email-haijun.liu@atheros.com>
* Haijun Liu <haijun.liu@atheros.com> [2010-10-22 10:26:58 +0800]:
> During test session with another vendor's bt stack, found that in
> l2cap_chan_del() using del_timer() caused l2cap_monitor_timeout()
> be called after the sock was freed, so it raised a system crash.
> So I just replaced del_timer() with del_timer_sync() to solve it.
NAK on this. If you read the del_timer_sync() documentation you can
see that you can't call del_timer_sync() on interrupt context. The
possible solution here is to check in the beginning of
l2cap_monitor_timeout() if your sock is still valid.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: [PATCH 2/9] mfd: Add char devices for the ST-Ericsson CG2900.
From: Arnd Bergmann @ 2010-10-22 15:49 UTC (permalink / raw)
To: pghatwork; +Cc: linus.walleij, linux-bluetooth, linux-kernel
In-Reply-To: <AANLkTimzWPqHBSDyeoObAcxewg_adubgkt1KQYUmpBRA@mail.gmail.com>
On Friday 22 October 2010 12:36:09 Par-Gunnar Hjalmdahl wrote:
> This patch adds char devices to the ST-Ericsson CG2900 driver.
> The reason for this is to allow users of CG2900, such as GPS, to
> be placed in user space.
Can you be more specific how you expect this to be used?
I guess you have hardware units that then each correspond to
one character device and can be talked to over a pseudo socket
interface, right?
For most devices (radio, audio, bluetooth, gps, ...), we already
have existing user interfaces, so how do they interact with this
one?
> +#define NAME "CharDev "
?
> +/* 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)
These definitions look wrong -- you never use the ioctl argument...
> + *
> + * 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)
The same comment applies here that I made for the test interface:
Why is this not an AF_BLUETOOTH socket instead of a chardev?
Unless I'm mistaken, you actually send bluetooth frames after all.
> + 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;
Strange interface. Why do you need to check for the reset?
> + 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;
These look like could better live in a sysfs attribute of the platform device.
Arnd
^ permalink raw reply
* Re: [PATCH 1/9] mfd: Add support for the ST-Ericsson CG2900.
From: Arnd Bergmann @ 2010-10-22 15:36 UTC (permalink / raw)
To: Par-Gunnar Hjalmdahl; +Cc: linus.walleij, linux-bluetooth, linux-kernel
In-Reply-To: <AANLkTineXA2ZY0J7+dZhqywz+4aAVgzihu+LDDbsL8mu@mail.gmail.com>
On Friday 22 October 2010 12:35:16 Par-Gunnar Hjalmdahl wrote:
> This patch adds support for the ST-Ericsson CG2900 Connectivity
> Combo controller.
> This patch adds the central framework to be able to register CG2900 users,
> transports, and chip handlers.
>
> Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
Looks ok from a coding style perspective, but some important information is
missing from the description:
* What is a CG2900?
* Why is it a MFD device rather than e.g. a bus or a subsystem?
> +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.
> +
Can you explain what it means to mux over a H:4 interface? Does this mean
you use bluetooth infrastructure that is designed for wireless communication
in order to connect on-board or on-chip devices?
> @@ -0,0 +1,2401 @@
> +/*
> + * drivers/mfd/cg2900/cg2900_core.c
> + *
> + * Copyright (C) ST-Ericsson SA 2010
> + * Authors:
> + * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
> ST-Ericsson.
Your email client rewraps lines. You need to fix so that other people
can apply your patches.
> +/*
> + * chip_handlers - List of the register handlers for different chips.
> + */
> +LIST_HEAD(chip_handlers);
Should this be static? Don't you need a lock to access the list?
> +/**
> + * 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;
> +}
You seem to have a number of functions that need to go through each
possible user/channel/submodule. This looks like somethin is not
abstracted well enough.
> +
> +/**
> + * 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)
> +{
Where does that name come from? Why not just use an enum if the name is
only use for strncmp?
> +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;
What is this read/write interface used for?
The name suggests that this is only for testing and not an essential
part of your driver. Could this be made a separate driver?
It also looks like you do socket communication here, can't you use
a proper AF_BLUETOOTH socket to do the same?
> + 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;
> +}
This does not handle short/continued reads.
> +EXPORT_SYMBOL(cg2900_reset);
How about making your symbols EXPORT_SYMBOL_GPL instead of EXPORT_SYMBOL?
> +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");
Please don't introduce new debugging frameworks, we have enough of them
already. Syslog handles different levels of output just fine, so just
remove all your debugging stuff and use dev_dbg, dev_info, etc to print
messages about the device if you must.
Many messages can probably be removed entirely.
> +#define CG2900_DEFAULT_DEBUG_LEVEL 1
> +
> +/* module_param declared in cg2900_core.c */
> +extern int cg2900_debug_level;
> +
> +#if defined(NDEBUG) || CG2900_DEFAULT_DEBUG_LEVEL == 0
> + #define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len)
> + #define CG2900_DBG_DATA(fmt, arg...)
> + #define CG2900_DBG(fmt, arg...)
> + #define CG2900_INFO(fmt, arg...)
> + #define CG2900_ERR(fmt, arg...)
> +#else
> +
> + #define CG2900_DBG_DATA_CONTENT(__prefix, __buf, __len) \
> + do { \
> + if (cg2900_debug_level >= 30) \
> + print_hex_dump_bytes("CG2900 " __prefix ": " , \
> + DUMP_PREFIX_NONE, __buf, __len); \
> + } while (0)
> +
> + #define CG2900_DBG_DATA(fmt, arg...) \
> + do { \
> + if (cg2900_debug_level >= 25) \
> + pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
> + } while (0)
> +
> + #define CG2900_DBG(fmt, arg...) \
> + do { \
> + if (cg2900_debug_level >= 20) \
> + pr_debug("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
> + } while (0)
> +
> + #define CG2900_INFO(fmt, arg...) \
> + do { \
> + if (cg2900_debug_level >= 10) \
> + pr_info("CG2900: " fmt "\n" , ## arg); \
> + } while (0)
> +
> + #define CG2900_ERR(fmt, arg...) \
> + do { \
> + if (cg2900_debug_level >= 1) \
> + pr_err("CG2900 %s: " fmt "\n" , __func__ , ## arg); \
> + } while (0)
> +
> +#endif /* NDEBUG */
You'll feel relieved when all this is gone...
> +
> +#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
These all look like generic bluetooth definitions, not cg2900
specific. Should they be part of global headers? Are they perhaps
already?
Arnd
^ permalink raw reply
* Re: [PATCH 5/9] mfd: Add UART support for the ST-Ericsson CG2900.
From: Alan Cox @ 2010-10-22 15:33 UTC (permalink / raw)
To: Par-Gunnar Hjalmdahl
Cc: linus.walleij, linux-bluetooth, linux-kernel, Lukasz.Rymanowski
In-Reply-To: <AANLkTik-S50ZX6mFG+vWhBv0x_WmipP=t+4VqmAgssRC@mail.gmail.com>
> The existence of the callback is checked in the function
> uart_tty_open. If either break_ctl or tiocmget is NULL we do not allow
> sleep and this code will never run.
OK yes see this now.
> >> + CG2900_ERR("Failed to alloc memory for uart_work_struct!");
> >
> > Please use the standard dev_dbg etc functionality - or pr_err etc when
> > you have no device pointer. The newest kernel tty object has a device
> > pointer added so you could use that.
> >
>
> OK. I like the debug system we have now (using module parameter to set
> debug level in runtime), but I've received comments regarding this
> before so I assume we will have to switch to standard printouts.
> Is it OK to use defines or inline functions to add "CG2900" before and
> '\n' after (as BT_INFO in include/net/bluetooth/bluetooth.h)?
The pr_fmt bit will do what you want for a non dev_dbg type thing. See
include/linux/kernel.h
> >> + * unset_cts_irq() - Disable interrupt on CTS.
> >> + */
> >> +static void unset_cts_irq(void)
> >
> > And no ability to support multiple devices
> >
>
> OK. We will try to fix this.
That may go away when you clean up the device side. The line discipline
can be attached to any device so must be multi-device aware, the hardware
driver can certainly be single device only if you can only ever have one
[Although its a good idea to design it so it can be fixed because you
never know when you'll find your sales team just sold someone a two
device solution ;) ]
> >> + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
> >> + len = tty->ops->write(tty, skb->data, skb->len);
> >
> > A tty isn't required to have a write function
>
> I don't quite understand your comment here. This particular code is
> inspired of the Bluetooth ldisc code and there it is used like. It
> seems to work fine so we do it the same way.
You copied a security hole from the bluetooth driver which we found a
couple of weeks ago
> How would you prefer it to be?
Check it is valid, in your case probably on open just refuse to attach to
a read only port.
> OK. We will try to figure out a new design.
> I'm not too happy about putting the ldisc part in Bluetooth though
> since it is only partly Bluetooth, it is also GPS and FM. Better could
> maybe be under char/?
Works for me - there is an ongoing "we must move tty ldiscs and core tty
code somewhere more sensible of their own" discussion, so if it is
dropped into char, it'll move with them just fine.
Alan
^ permalink raw reply
* Re: [PATCH 5/9] mfd: Add UART support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 14:54 UTC (permalink / raw)
To: Alan Cox; +Cc: linus.walleij, linux-bluetooth, linux-kernel, Lukasz.Rymanowski
In-Reply-To: <20101022135130.617f0ce8@lxorguk.ukuu.org.uk>
Hi and thanks for your comments Alan.
See below for answers.
/P-G
2010/10/22 Alan Cox <alan@lxorguk.ukuu.org.uk>:
>
>> + * is_chip_flow_off() - Check if chip has set flow off.
>> + * @tty: Pointer to tty.
>> + *
>> + * Returns:
>> + * true - chip flows off.
>> + * false - chip flows on.
>> + */
>> +static bool is_chip_flow_off(struct tty_struct *tty)
>> +{
>> + int lines;
>> +
>> + lines = tty->ops->tiocmget(tty, uart_info->fd);
>> +
>> + if (lines & TIOCM_CTS)
>> + return false;
>> + else
>> + return true;
>> +}
>
> What if the device doesn't have a tiocmget ? You must check this. If you
> want to call into the ttys own tiocmget froma sleeping context fine, but
> see tty_tiocmget and you'll notice you need to check the op is non NULL
> somewhere. You could do this when the ldisc is opened and refuse to open
> - some ldiscs do that
>
The existence of the callback is checked in the function
uart_tty_open. If either break_ctl or tiocmget is NULL we do not allow
sleep and this code will never run.
>
>> +
>> +/**
>> + * 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 uart_work_struct *new_work;
>> + int err;
>> +
>> + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
>
> So instead of a tiny object embedded in your device you've got a whole
> error path to worry about, a printk disguised in a macro and a text
> string longer than the struct size ? Surely this should be part of the
> device
>
OK. We can embed the work struct in the device structure.
>> + if (!new_work) {
>> + CG2900_ERR("Failed to alloc memory for uart_work_struct!");
>
> Please use the standard dev_dbg etc functionality - or pr_err etc when
> you have no device pointer. The newest kernel tty object has a device
> pointer added so you could use that.
>
OK. I like the debug system we have now (using module parameter to set
debug level in runtime), but I've received comments regarding this
before so I assume we will have to switch to standard printouts.
Is it OK to use defines or inline functions to add "CG2900" before and
'\n' after (as BT_INFO in include/net/bluetooth/bluetooth.h)?
>
>> + * set_tty_baud() - Called to set specific baud in TTY.
>> + * @tty: Tty device.
>> + * @baud: Baud to set.
>> + *
>> + * Returns:
>> + * true - baudrate set with success.
>> + * false - baundrate set failure.
>> + */
>> +static bool set_tty_baud(struct tty_struct *tty, int baud)
>> +{
>> + struct ktermios *old_termios;
>> + bool retval = true;
>> +
>> + old_termios = kmalloc(sizeof(*old_termios), GFP_ATOMIC);
>
> termios struct is easily small enough to be fine on the stack
>
OK.
>> + /* Let's mark that CG2900 driver uses c_ispeed and c_ospeed fields. */
>> + tty->termios->c_cflag |= BOTHER;
>
> This shouldn't be needed - the tty_encode_baud_rate logic works out of
> BOTHER must be set
>
I will have to check with the guy who wrote this code. I think he put
it there for a reason.
>> + tty->termios->c_cflag &= ~BOTHER;
>
> And your termios is now potentially invalid
>
As above, I will check.
>
>> + /* Set IRQ on CTS. */
>> + err = request_irq(pf_data->uart.cts_irq,
>> + cts_interrupt,
>> + IRQF_TRIGGER_FALLING,
>> + UART_NAME,
>> + NULL);
>
> So we now ave terminal tty/ldisc confusion
>
The guy responsible for this is thinking through a new design, where
we can move the hardware specific handling to another place.
>> + if (err) {
>> + CG2900_ERR("Could not request CTS IRQ (%d)", err);
>> + goto error;
>> + }
>> +
>> +#ifdef CONFIG_PM
>> + enable_irq_wake(pf_data->uart.cts_irq);
>> +#endif
>> + return 0;
>> +
>> +error:
>> + if (pf_data->uart.enable_uart)
>> + (void)pf_data->uart.enable_uart();
>> + return err;
>
>> +}
>> +
>> +/**
>> + * unset_cts_irq() - Disable interrupt on CTS.
>> + */
>> +static void unset_cts_irq(void)
>
> And no ability to support multiple devices
>
OK. We will try to fix this.
>> + CG2900_DBG("Clear break");
>> + tty->ops->break_ctl(tty, TTY_BREAK_OFF);
>
> What if the tty doesn't have one ?
>
See answer above. It is checked in another place.
>
>> + if (CHIP_AWAKE == uart_info->sleep_state) {
>> + uart_info->tty->ops->break_ctl(uart_info->tty, TTY_BREAK_ON);
>
> Ditto
>
And ditto as well :-)
>
>> + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
>> + len = tty->ops->write(tty, skb->data, skb->len);
>
> A tty isn't required to have a write function
I don't quite understand your comment here. This particular code is
inspired of the Bluetooth ldisc code and there it is used like. It
seems to work fine so we do it the same way.
How would you prefer it to be?
>
>> + if ((BAUD_START == uart_info->baud_rate_state) &&
>> + (is_set_baud_rate_cmd(skb->data))) {
>> + CG2900_INFO("UART set baud rate cmd found.");
>> + SET_BAUD_STATE(BAUD_SENDING);
>
> Do we really need this all in capitals ?
>
We use a define to get a debug printout when setting state, but I
understand it's not so popular so I guess we will have to skip it.
I guess it won't help if we would use an inline function instead of a
define (which would mean we could skip the capitals)?
>
>
>> + * uart_tty_open() - Called when UART line discipline changed to N_HCI.
>> + * @tty: Pointer to associated TTY instance data.
>> + *
>> + * Returns:
>> + * 0 if there is no error.
>> + * Errors from cg2900_register_trans_driver.
>> + */
>> +static int uart_tty_open(struct tty_struct *tty)
>> +{
>> + int err;
>> +
>> + CG2900_INFO("uart_tty_open");
>> +
>> + /* Set the structure pointers and set the UART receive room */
>> + uart_info->tty = tty;
>
> You must respect the kref handling in the kernel tty code. Take
> references by all means but do it properly.
>
OK. We will add tty_kref_get/put handling.
>
>> +static void uart_tty_wakeup(struct tty_struct *tty)
>> +{
>> + int err;
>> +
>> + CG2900_INFO("uart_tty_wakeup");
>> +
>> + /*
>> + * Clear the TTY_DO_WRITE_WAKEUP bit that is set in
>> + * work_do_transmit().
>> + */
>> + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
>> +
>> + if (tty != uart_info->tty)
>> + return;
>
> How can this occur - and why check it after you've changed tty->flags ???
>
OK. Will be removed.
>
>> +/**
>> + * uart_tty_receive() - Called by TTY low level driver when receive
>> data is available.
>> + * @tty: Pointer to TTY instance data
>> + * @data: Pointer to received data
>> + * @flags: Pointer to flags for data
>> + * @count: Count of received data in bytes
>> + */
>> +static void uart_tty_receive(struct tty_struct *tty, const u8 *data,
>> + char *flags, int count)
>> +{
>> + CG2900_INFO("uart_tty_receive");
>> +
>> + if (tty != uart_info->tty)
>> + return;
>
> Again this is nonsense
>
OK.Will be removed.
>
>> +/*
>> + * We don't provide read/write/poll interface for user space.
>> + */
>> +static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file,
>> + unsigned char __user *buf, size_t nr)
>> +{
>> + CG2900_INFO("uart_tty_read");
>> + return 0;
>> +}
>
> -EINVAL then
>
OK. We can probably skip registering the callback function completely otherwise.
>> +
>> +static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file,
>> + const unsigned char *data, size_t count)
>> +{
>> + CG2900_INFO("uart_tty_write");
>> + return count;
>
> This is wrong - you can't jusr vanish the data
OK. To be changed.
>> +}
>
>
>
> This needs some restructuring I think
>
> A line discipline should contain no hardware awareness, that goes in the
> actual uart hardware driver. So we shouldn't have magic irq code in this
> part of things.
>
> You also need to sort out the tty kref handling in open/close and the
> fact you've got magic hardcoded single instance stuff buried i nit.
>
> Finally tty ldiscs don't go buried in the mfd directory, or they'll get
> missed during chanegs/updates. The ldisc probably belongs in bluetooth a
> and the hardware support in the mfd directory.
>
>
> So - NAK this for the moment, it needs to be split cleanly into ldisc
> (thing which speaks the protocol and eg sees "speed change required" and
> acts on it) and device (thing which knows about the hardware).
>
>
OK. We will try to figure out a new design.
I'm not too happy about putting the ldisc part in Bluetooth though
since it is only partly Bluetooth, it is also GPS and FM. Better could
maybe be under char/?
^ permalink raw reply
* Re: [PATCH] bluetooth: Fix NULL pointer dereference issue
From: Gustavo F. Padovan @ 2010-10-22 13:58 UTC (permalink / raw)
To: Yuri Ershov
Cc: marcel, davem, jprvita, linux-bluetooth, ville.tervo,
andrei.emeltchenko
In-Reply-To: <1987fd374e92ea2e4ebb06b24c6321e65ab933c6.1287676475.git.ext-yuri.ershov@nokia.com>
Hi Yuri,
* Yuri Ershov <ext-yuri.ershov@nokia.com> [2010-10-21 20:08:58 +0400]:
> This patch fixes NULL pointer dereference at running test with
> connect-transfer-disconnect in loop. Sometimes sk_state is
> BT_CLOSED and sk_refcnt equal to 0, so there is oops in
> bt_accept_unlink. In normal case removed block is not used.
Question here is: Why sk_refcnt is 0 at that point of the code? The
socket should be destroyed if it ref is 0, but it wasn't, so something
in another point of the code went is wrong. "Sometimes" is not a good
description of the problem, you have to show why that happened.
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply
* Re: Pull request git://gitorious.org/bluez-mcap-hdp/mcap-hdp.git for_upstream
From: Johan Hedberg @ 2010-10-22 13:43 UTC (permalink / raw)
To: Jose Antonio Santos Cadenas; +Cc: linux-bluetooth
In-Reply-To: <AANLkTikei6=MbLUQBYNmzbkEQ-12ufzVAf0CA2f3uLu2@mail.gmail.com>
Hi Jose,
On Thu, Oct 21, 2010, Jose Antonio Santos Cadenas wrote:
> The following changes since commit a4b1673b878a358d2492f24b46e92ea36d8f8bbf:
>
> Fix hidd to use ServiceName attribute for device name (2010-10-20
> 13:37:36 +0300)
>
> are available in the git repository at:
> git://gitorious.org/bluez-mcap-hdp/mcap-hdp.git for_upstream
>
> José Antonio Santos Cadenas (6):
> Receive an string instead of an integer for ChannelType
> Modify test-health script to make it more interactive
> Notify main channel when it is locally opened
> Emit a valid path when main channel is deleted
> Delete data channels when their device is removed.
> Check if the channel mode is correct when opening data channels
>
> Santiago Carot-Nemesio (6):
> Remove obsolete comment from MCAP
> Fix segmentation fault freeing uninitialized pointers
> Check l2cap configuration when data channels are connected
> Close the data channel if remote side changes the configuration
> Enable support to change mode for incoming data channels connections
> Change data channel mode for incoming connections
>
> health/hdp.c | 106 +++++++++++++++++++++++++--
> health/hdp_util.c | 35 +++++++---
> health/mcap.c | 15 ++++-
> health/mcap_lib.h | 3 +
> test/test-health | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++--
> 5 files changed, 340 insertions(+), 26 deletions(-)
Pushed upstream. Thanks.
Johan
^ permalink raw reply
* Re: [PATCH 6/9] mfd: Add support for the ST-Ericsson CG2900 Audio.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 13:40 UTC (permalink / raw)
To: Alan Cox; +Cc: linus.walleij, linux-bluetooth, linux-kernel
In-Reply-To: <20101022135804.6f55be40@lxorguk.ukuu.org.uk>
Thanks for you comments Alan.
I guess I didn't put enough comments in the code or the commit message
regarding this.
In fact we (ST-Ericsson) are writing drivers that e.g. maps to the V4L
framework. They then
call the API in this driver. This driver has never been intended to
map directly towards ALSA
or V4L. We have other drivers under development that does that.
/P-G
2010/10/22 Alan Cox <alan@lxorguk.ukuu.org.uk>:
> On Fri, 22 Oct 2010 12:39:06 +0200
> Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com> wrote:
>
>> This patch adds support for controlling audio paths within
>> the ST-Ericsson CG2900 Connectivity Combo controller.
>> This patch adds API for the Kernel as well as a char device for
>> user space stacks to control the CG2900 audio paths.
>
> I don't see any ALSA hooks for audio or any V4L hooks for the FM radio.
> Surely the audio routing wants exposing as a mixer and the audio via
> ALSA, likewise FM radio is video4linux so that it'll have the standard
> interfaces that are expected by the rest of the Linux OS.
>
> Alan
>
^ permalink raw reply
* RFCOMM connection from android device to desktop pc
From: Leandro de Oliveira @ 2010-10-22 13:21 UTC (permalink / raw)
To: linux-bluetooth
Hi,
I'm trying to open an RFCOMM connection from my android device to my
desktop pc running Ubuntu 10.04, send some data, close the connection
and then do it again. My desktop program is written in Java and relies
on BlueCove[1] to handle bluetooth details which calls Bluez 4.x in
this case.
The problem is that I get a successful connection and data transfer in
the first communication attempt but nothing in subsequent attempts.
There is this thread[2] in the android-platform mailing list with much
more detail, but I think you could just take a look at this hcidump
that has been requested there to let me know if this is a problem in
bluez on the android or desktop side:
http://dl.dropbox.com/u/1401233/pc-hcidump.txt
Please, let me know if you need more info.
The same program works perfectly when the android application connects
to it running on Windows 7 using the Microsoft Bluetooth Stack.
[1] http://www.bluecove.org/
[2] http://groups.google.com/group/android-platform/browse_thread/thread/485db57409304d3e?hl=en
Thank you very much
^ permalink raw reply
* Re: [PATCH] Fix crash when GetProperties req is received before any adapters are set up
From: Johan Hedberg @ 2010-10-22 13:12 UTC (permalink / raw)
To: ext-tommi.keisala; +Cc: linux-bluetooth
In-Reply-To: <1287743558-17002-2-git-send-email-ext-tommi.keisala@nokia.com>
Hi Tommi,
On Fri, Oct 22, 2010, ext-tommi.keisala@nokia.com wrote:
> This patch avoids a crash when org.bluez.Manager GetProperties request is
> received and there is not yet any adapters ready. Happens often for example
> when bluetoothd and ofonod is started next ot each other.
>
> Signed-off-by: Tommi Keisala <ext-tommi.keisala@nokia.com>
> ---
> src/manager.c | 6 +++++-
> 1 files changed, 5 insertions(+), 1 deletions(-)
Looks like a bugfix is definitely in order here, but why introduce a new
variable to the function? Wouldn't something like the folling be good
enough:
--- a/src/manager.c
+++ b/src/manager.c
@@ -197,13 +197,14 @@ static DBusMessage *get_properties(DBusConnection *conn,
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
array = g_new0(char *, g_slist_length(adapters) + 1);
- for (i = 0, list = adapters; list; list = list->next, i++) {
+ for (i = 0, list = adapters; list; list = list->next) {
struct btd_adapter *adapter = list->data;
if (!adapter_is_ready(adapter))
continue;
array[i] = (char *) adapter_get_path(adapter);
+ i++;
}
dict_append_array(&dict, "Adapters", DBUS_TYPE_OBJECT_PATH, &array, i);
g_free(array);
Could you send a new patch which doesn't introduce a new variable? Also,
please leave out the signed-off-by from the commit message since it's
not used for userspace bluez patches. Thanks.
Johan
^ permalink raw reply
* Re: [PATCH] Fix history listing queries in phonebook-tracker
From: Johan Hedberg @ 2010-10-22 13:06 UTC (permalink / raw)
To: Radoslaw Jablonski; +Cc: linux-bluetooth
In-Reply-To: <1287663999-22701-1-git-send-email-ext-jablonski.radoslaw@nokia.com>
Hi Radek,
On Thu, Oct 21, 2010, Radoslaw Jablonski wrote:
> Now we are able to display contact's name in vcard-listing when
> contact has entry in phonebook available.
> ---
> plugins/phonebook-tracker.c | 140 ++++++++++++++++++++++++++++++++++++-------
> 1 files changed, 118 insertions(+), 22 deletions(-)
Pushed upstream. Thanks.
Johan
^ permalink raw reply
* Re: [PATCH 6/9] mfd: Add support for the ST-Ericsson CG2900 Audio.
From: Alan Cox @ 2010-10-22 12:58 UTC (permalink / raw)
To: pghatwork
Cc: par-gunnar.p.hjalmdahl, linus.walleij, linux-bluetooth,
linux-kernel
In-Reply-To: <AANLkTinHV3qyMRsjAn92uZZp22whESpJDZUfCHcy=Eh9@mail.gmail.com>
On Fri, 22 Oct 2010 12:39:06 +0200
Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com> wrote:
> This patch adds support for controlling audio paths within
> the ST-Ericsson CG2900 Connectivity Combo controller.
> This patch adds API for the Kernel as well as a char device for
> user space stacks to control the CG2900 audio paths.
I don't see any ALSA hooks for audio or any V4L hooks for the FM radio.
Surely the audio routing wants exposing as a mixer and the audio via
ALSA, likewise FM radio is video4linux so that it'll have the standard
interfaces that are expected by the rest of the Linux OS.
Alan
^ permalink raw reply
* Re: [PATCH 5/9] mfd: Add UART support for the ST-Ericsson CG2900.
From: Alan Cox @ 2010-10-22 12:51 UTC (permalink / raw)
To: pghatwork
Cc: par-gunnar.p.hjalmdahl, linus.walleij, linux-bluetooth,
linux-kernel
In-Reply-To: <AANLkTi=XTUau_GbASJEUBgsZ_FYYmRAX6W43YtRdv2wS@mail.gmail.com>
> + * is_chip_flow_off() - Check if chip has set flow off.
> + * @tty: Pointer to tty.
> + *
> + * Returns:
> + * true - chip flows off.
> + * false - chip flows on.
> + */
> +static bool is_chip_flow_off(struct tty_struct *tty)
> +{
> + int lines;
> +
> + lines = tty->ops->tiocmget(tty, uart_info->fd);
> +
> + if (lines & TIOCM_CTS)
> + return false;
> + else
> + return true;
> +}
What if the device doesn't have a tiocmget ? You must check this. If you
want to call into the ttys own tiocmget froma sleeping context fine, but
see tty_tiocmget and you'll notice you need to check the op is non NULL
somewhere. You could do this when the ldisc is opened and refuse to open
- some ldiscs do that
> +
> +/**
> + * 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 uart_work_struct *new_work;
> + int err;
> +
> + new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
So instead of a tiny object embedded in your device you've got a whole
error path to worry about, a printk disguised in a macro and a text
string longer than the struct size ? Surely this should be part of the
device
> + if (!new_work) {
> + CG2900_ERR("Failed to alloc memory for uart_work_struct!");
Please use the standard dev_dbg etc functionality - or pr_err etc when
you have no device pointer. The newest kernel tty object has a device
pointer added so you could use that.
> + * set_tty_baud() - Called to set specific baud in TTY.
> + * @tty: Tty device.
> + * @baud: Baud to set.
> + *
> + * Returns:
> + * true - baudrate set with success.
> + * false - baundrate set failure.
> + */
> +static bool set_tty_baud(struct tty_struct *tty, int baud)
> +{
> + struct ktermios *old_termios;
> + bool retval = true;
> +
> + old_termios = kmalloc(sizeof(*old_termios), GFP_ATOMIC);
termios struct is easily small enough to be fine on the stack
> + /* Let's mark that CG2900 driver uses c_ispeed and c_ospeed fields. */
> + tty->termios->c_cflag |= BOTHER;
This shouldn't be needed - the tty_encode_baud_rate logic works out of
BOTHER must be set
> + tty->termios->c_cflag &= ~BOTHER;
And your termios is now potentially invalid
> + /* Set IRQ on CTS. */
> + err = request_irq(pf_data->uart.cts_irq,
> + cts_interrupt,
> + IRQF_TRIGGER_FALLING,
> + UART_NAME,
> + NULL);
So we now ave terminal tty/ldisc confusion
> + if (err) {
> + CG2900_ERR("Could not request CTS IRQ (%d)", err);
> + goto error;
> + }
> +
> +#ifdef CONFIG_PM
> + enable_irq_wake(pf_data->uart.cts_irq);
> +#endif
> + return 0;
> +
> +error:
> + if (pf_data->uart.enable_uart)
> + (void)pf_data->uart.enable_uart();
> + return err;
> +}
> +
> +/**
> + * unset_cts_irq() - Disable interrupt on CTS.
> + */
> +static void unset_cts_irq(void)
And no ability to support multiple devices
> + CG2900_DBG("Clear break");
> + tty->ops->break_ctl(tty, TTY_BREAK_OFF);
What if the tty doesn't have one ?
> + if (CHIP_AWAKE == uart_info->sleep_state) {
> + uart_info->tty->ops->break_ctl(uart_info->tty, TTY_BREAK_ON);
Ditto
> + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
> + len = tty->ops->write(tty, skb->data, skb->len);
A tty isn't required to have a write function
> + if ((BAUD_START == uart_info->baud_rate_state) &&
> + (is_set_baud_rate_cmd(skb->data))) {
> + CG2900_INFO("UART set baud rate cmd found.");
> + SET_BAUD_STATE(BAUD_SENDING);
Do we really need this all in capitals ?
> + * uart_tty_open() - Called when UART line discipline changed to N_HCI.
> + * @tty: Pointer to associated TTY instance data.
> + *
> + * Returns:
> + * 0 if there is no error.
> + * Errors from cg2900_register_trans_driver.
> + */
> +static int uart_tty_open(struct tty_struct *tty)
> +{
> + int err;
> +
> + CG2900_INFO("uart_tty_open");
> +
> + /* Set the structure pointers and set the UART receive room */
> + uart_info->tty = tty;
You must respect the kref handling in the kernel tty code. Take
references by all means but do it properly.
> +static void uart_tty_wakeup(struct tty_struct *tty)
> +{
> + int err;
> +
> + CG2900_INFO("uart_tty_wakeup");
> +
> + /*
> + * Clear the TTY_DO_WRITE_WAKEUP bit that is set in
> + * work_do_transmit().
> + */
> + clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
> +
> + if (tty != uart_info->tty)
> + return;
How can this occur - and why check it after you've changed tty->flags ???
> +/**
> + * uart_tty_receive() - Called by TTY low level driver when receive
> data is available.
> + * @tty: Pointer to TTY instance data
> + * @data: Pointer to received data
> + * @flags: Pointer to flags for data
> + * @count: Count of received data in bytes
> + */
> +static void uart_tty_receive(struct tty_struct *tty, const u8 *data,
> + char *flags, int count)
> +{
> + CG2900_INFO("uart_tty_receive");
> +
> + if (tty != uart_info->tty)
> + return;
Again this is nonsense
> +/*
> + * We don't provide read/write/poll interface for user space.
> + */
> +static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file,
> + unsigned char __user *buf, size_t nr)
> +{
> + CG2900_INFO("uart_tty_read");
> + return 0;
> +}
-EINVAL then
> +
> +static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file,
> + const unsigned char *data, size_t count)
> +{
> + CG2900_INFO("uart_tty_write");
> + return count;
This is wrong - you can't jusr vanish the data
> +}
This needs some restructuring I think
A line discipline should contain no hardware awareness, that goes in the
actual uart hardware driver. So we shouldn't have magic irq code in this
part of things.
You also need to sort out the tty kref handling in open/close and the
fact you've got magic hardcoded single instance stuff buried i nit.
Finally tty ldiscs don't go buried in the mfd directory, or they'll get
missed during chanegs/updates. The ldisc probably belongs in bluetooth a
and the hardware support in the mfd directory.
So - NAK this for the moment, it needs to be split cleanly into ldisc
(thing which speaks the protocol and eg sees "speed change required" and
acts on it) and device (thing which knows about the hardware).
^ permalink raw reply
* [PATCH 9/9] DocBook: Add ST-Ericsson CG2900 docs
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:41 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds documentation for the ST-Ericsson CG2900
Connectivity controller.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
Documentation/DocBook/Makefile | 2 +-
Documentation/DocBook/cg2900.tmpl | 1332 +++++++++++++++++++++++++++++++++++++
2 files changed, 1333 insertions(+), 1 deletions(-)
create mode 100644 Documentation/DocBook/cg2900.tmpl
diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile
index 8b6e00a..e8319ec 100644
--- a/Documentation/DocBook/Makefile
+++ b/Documentation/DocBook/Makefile
@@ -14,7 +14,7 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \
genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \
80211.xml debugobjects.xml sh.xml regulator.xml \
alsa-driver-api.xml writing-an-alsa-driver.xml \
- tracepoint.xml media.xml drm.xml
+ tracepoint.xml media.xml drm.xml cg2900.xml
###
# The build process is as follows (targets):
diff --git a/Documentation/DocBook/cg2900.tmpl
b/Documentation/DocBook/cg2900.tmpl
new file mode 100644
index 0000000..e7eef1b
--- /dev/null
+++ b/Documentation/DocBook/cg2900.tmpl
@@ -0,0 +1,1332 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []>
+
+<book id="STE-Connectivity-template">
+ <bookinfo>
+ <title>CG2900 Driver</title>
+
+ <authorgroup>
+ <author>
+ <firstname>Henrik</firstname>
+ <surname>Possung</surname>
+ <affiliation>
+ <address>
+ <email>henrik.possung@stericsson.com</email>
+ </address>
+ </affiliation>
+ </author>
+ <author>
+ <firstname>Par-Gunnar</firstname>
+ <surname>Hjalmdahl</surname>
+ <affiliation>
+ <address>
+ <email>par-gunnar.p.hjalmdahl@stericsson.com</email>
+ </address>
+ </affiliation>
+ </author>
+ </authorgroup>
+
+ <copyright>
+ <year>2010</year>
+ <holder>ST-Ericsson SA</holder>
+ </copyright>
+
+ <subjectset>
+ <subject>
+ <subjectterm>Connectivity</subjectterm>
+ </subject>
+ </subjectset>
+
+ <legalnotice>
+ <!-- Do NOT remove the legal notice below -->
+
+ <para>
+ This documentation is free software; you can redistribute
+ it and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later
+ version.
+ </para>
+
+ <para>
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ See the GNU General Public License for more details.
+ </para>
+
+ <para>
+ You should have received a copy of the GNU General Public
+ License along with this program; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ MA 02111-1307 USA
+ </para>
+
+ <para>
+ For more details see the file COPYING in the source
+ distribution of Linux.
+ </para>
+ </legalnotice>
+ </bookinfo>
+
+ <toc></toc>
+
+ <chapter id="intro">
+ <title>Introduction</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ This documentation describes the functions provided by the
ST-Ericsson Connectivity Driver for enabling
+ ST-Ericsson Connectivity Combo Controller Hardware.
+
+ </para>
+ </chapter>
+
+ <chapter id="gettingstarted">
+ <title>Getting Started</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ There are no special compilation flags needed to build the STE
connectivity driver.
+ </para>
+ <para>
+ There must be firmware and settings files that match the used
chip version inside the firmware folder.
+ The files:
+ <itemizedlist>
+ <listitem><para>cg2900_patch_info.fw.org</para></listitem>
+ <listitem><para>cg2900_settings_info.fw.org</para></listitem>
+ </itemizedlist>
+ handle the mapping between chip version and correct firmware file
(patch resp static settings file).
+ The necessary patch and settings files should be placed with the
extension <constant>.fw.org.</constant>.
+ Note that there is a limitation in the Kernel firmware system
regarding name length of a file.
+ </para>
+
+ <!-- TODO: If the driver needs preparations to be used
+ (special compilation flags, files in the file system,
+ knowledge about a specific domain etc), specify it here.
+ Remove this chapter completely if there is nothing
+ to mention and there is no tutorial needed.
+ Do NOT change the chapter id or title! -->
+ <!-- TODO: This guideline for this chapter may be extended
+ during the user-guide guidelines drop. -->
+
+ <section id="basic-tutorial">
+ <title>Basic Tutorial</title>
+ <para>
+ To enable the ST-Ericsson connectivity driver using KConfig go
to <constant>Device Drivers -> Multifunction Device Drivers</constant>
+ and enable the STE Connectivity Driver. If BlueZ shall be used
as Bluetooth stack also enable the STE HCI Connectivity driver.
+ Depending on choice the driver will either be included as LKM
or built into the Kernel.
+ If building as LKM, 2 files will be generated:
+ <itemizedlist>
+ <listitem><para>cg2900.ko which contains the main
driver</para></listitem>
+ <listitem><para>btcg2900.ko which contains the registration
and mapping towards the BlueZ Bluetooth stack</para></listitem>
+ </itemizedlist>
+
+ <!-- TODO: Provide a basic tutorial, outlining how
+ to test the presence of the driver,
+ for example how to configure, compile and run the
+ example(s).
+ Several sections with different tutorials,
+ all located within the Getting Started
+ chapter may be provided. -->
+ </para>
+
+ <para>
+ <!-- TODO: This guideline for this section may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+ </section>
+
+ </chapter>
+
+ <chapter id="concepts">
+ <title>Concepts</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ The ST-Ericsson Connectivity driver works as a multiplexer
between different users, such as a Bluetooth stack and a FM driver,
+ and the connectivity chip. The driver supports multiple physical
transports, currently SPI and UART.
+ Apart from just transporting data between stacks and the chip,
the ST-Ericsson Connectivity driver also deals with power handling,
+ powering up and down the chip and also downloading necessary
patches and settings for the chip to start up properly.
+ <!-- TODO: A brief introduction about the concepts
+ which are introduced by the driver.
+ Remove this chapter completely if there are no
+ special concepts introduced by this driver.
+ Do NOT change the chapter id or title! -->
+ <!-- TODO: This guideline for this chapter may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+ </chapter>
+
+ <chapter id="tasks">
+ <title>Tasks</title>
+ <!-- Do NOT change the chapter id or title! -->
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Opening a channel</term>
+ <listitem>
+ <para>
+ In order to be able to send and receive data on an H:4
channel, the user (i.e. respective stack) must open the channel.
+ Opening a channel will make it possible to send data to
and receive data from the connectivity controller.
+ If the controller were earlier powered down, opening a
channel will also cause the controller to be powered up.
+ When chip is powered up, patches and settings for the ARM
subsystem will be downloaded as well.
+ Other IPs within the controller must however download each
respective patches and settings.
+ If chip was already powered up when opening the channel no
patch will be automatically downloaded.
+
+ <variablelist>
+ <varlistentry>
+ <term>Opening a channel from Kernel space</term>
+ <listitem>
+ <para>
+ When a stack is placed in Kernel space, it shall
open a channel by calling the API function
<function>cg2900_register_user</function>.
+ This function will search for the supplied channel
by using name look-up and open the channel.
+ The function will return with a device reference
that shall be used when calling the other CG2900 API functions.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Opening a channel from User space</term>
+ <listitem>
+ <para>
+ When a stack is placed in User space, it shall open
a channel by calling the syscall function <function>open</function> on
the corresponding file.
+ The files are located in folder
<filename>/dev/</filename> and are named
<filename>cg2900_gnss</filename> and similar. Each file
+ corresponds to one H:4 channel.
+ This function will open the channel.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Closing a channel</term>
+ <listitem>
+ <para>
+ When a user, i.e. a stack has no need for a functionality,
it should close the corresponding H:4 channel.
+ This is usually done when a user disables a certain
feature, for example Bluetooth. The reason why the channels
+ need to be closed is that the ST-E connectivity driver
will free the resources and also shutdown the controller if there are
+ no more active users of the chip. This will lower the
power consumption thereby increasing battery life.
+
+ <variablelist>
+ <varlistentry>
+ <term>Closing a channel from Kernel space</term>
+ <listitem>
+ <para>
+ When a stack is placed in Kernel space, it shall
close a channel by calling the API function
+ <function>cg2900_deregister_user</function>.
+ This function will close the channel and also free
the allocated device that was allocated when registering.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Closing a channel from User space</term>
+ <listitem>
+ <para>
+ When a stack is placed in User space, it shall close
a channel by calling the syscall function
+ <function>close</function> on the corresponding file.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Writing to a channel</term>
+ <listitem>
+ <para>
+ When a stack (Bluetooth, FM, or GNSS) wants to send a
packet it shall perform a write operation.
+ The packet shall not contain the H:4 header since this is
added by the ST-E connectivity driver.
+ All other data in the packet shall however exist in the
packet in the format correct for that HCI channel.
+ The ST-E connectivity users need to perform flow control
over channels so any ticket handling
+ or similar must be handled by respective stack.
+
+ <variablelist>
+ <varlistentry>
+ <term>Writing to a channel from Kernel space</term>
+ <listitem>
+ <para>
+ When a stack is placed in Kernel space, it shall
start with allocating a packet of the correct size using
+ <function>cg2900_alloc_skb</function>. This function
will return an sk_buff (Socket buffer) structure that
+ has necessary space reserved for ST-E driver operation.
+ The stack shall then copy the data, preferrably
using <function>skb_put</function>, and then call
+ <function>cg2900_write</function> to perform the
write operation. When the function returns, the buffer has
+ been transferred and there is no need for the
calling function to free the buffer. If the operation fails, i.e.
+ an error code is returned, the caller must however
free the buffer.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Writing to a channel from User space</term>
+ <listitem>
+ <para>
+ When a stack is placed in User space, it shall call
the <function>write</function> function on
+ the corresponding file to perform a transmit
operation. After function returns the data has been
+ copied and is considered sent.
+ The caller does not need to preserve the data.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Writing to FM_Radio and FM_Audio channel</term>
+ <listitem>
+ <para>
+ CG2900 driver only supports FM legacy commands. The reason is that
the FM_Radio and FM_Audio uses the same H4 channel aginst the chip,
+ in order to multiplex the FM user commands the data pakets are
parsed by the CG2900 driver.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Reading from a channel</term>
+ <listitem>
+ <para>
+ When a stack (Bluetooth, FM, or GNSS) wants to receive a
packet it shall perform a receive operation.
+ The packet returned does not contain the H:4 header since
this is removed by the ST-E connectivity driver.
+ All other data in the packet in the packet is in the
format correct for that HCI channel.
+ The ST-E connectivity driver does not perform any flow
control over the H:4 channel so any ticket handling
+ or similar must be handled by respective stack.
+
+ <variablelist>
+ <varlistentry>
+ <term>Reading from a channel from Kernel space</term>
+ <listitem>
+ <para>
+ When a stack is placed in Kernel space, it has to
supply a callback function for the receive functionality when calling
+ <function>cg2900_register_user</function>. This
callback function will be called when the ST-E connectivity driver has
+ received a packet. The packet received will always
be a complete HCI packet, i.e. no fragmention on HCI layer.
+ When the packet has been received it is the
responsability of the receiver to see to that the packet is freed
using
+ <function>kfree_skb</function> when it is no more needed.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Reading from a channel from User space</term>
+ <listitem>
+ <para>
+ When a stack is placed in User space, it shall call
the <function>read</function> function on
+ the corresponding file to perform a receive
operation. This function will read as many bytes as there are present
+ up to the size of the supplied buffer. If no data is
available the function will hang until data becomes available, reset
+ occurs, or the channel is closed.
+ For smooth operation it is recommended to use the
<function>poll</function> functionality on the file, preferrably
+ from a dedicated thread. This way one thread can
monitor both read and reset operations in one common thread while
transmit
+ operations may continue unblocked in a separate thread.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Reset handling</term>
+ <listitem>
+ <para>
+ The stacks shall always try to avoid performing Reset
operations. The Reset will result in a hardware reset of the
controller
+ and will therefore cause all existing links and settings
to be lost. All stacks using the controller must also be informed
+ about the reset and handle it in a proper way.
+ The reset operation should only be used when there is no
other option to get the controller into a working state, for example
+ if the controller has stopped answering to commands.
+ After the hardware reset, the ST-E connectivity driver
will automatically perform deregister the channel so it has to be
reopened again.
+
+ <variablelist>
+ <varlistentry>
+ <term>Reset handling from Kernel space</term>
+ <listitem>
+ <para>
+ When a stack is placed in Kernel space, it initiates
a Reset operation by calling <function>cg2900_reset</function>.
+ This will trigger a hardware reset of the
controller. When the hardware reset is finished all registered users
will be called
+ through respective reset callback. When the callback
function is finished the registered device will be removed and when
all
+ registered users have been informed and removed, the
chip is shutdown. This is similar to a deregistration of all
registered
+ channels. The stack will then have to reregister to
the ST-E connectivity driver in order to use the channel once again.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <variablelist>
+ <varlistentry>
+ <term>Reset handling from User space</term>
+ <listitem>
+ <para>
+ When a stack is placed in User space, it shall call
the <function>ioctl</function> function on
+ the corresponding file to perform a reset operation.
The command parameter <constant>CG2900_CHAR_DEV_IOCTL_RESET</constant>
+ shall be used when calling <function>ioctl</function>.
+ When the <function>ioctl</function> returns, the
stack shall close the channel and then re-open it again. This must be
done so
+ the channel is registered correctly in Kernel space.
+ For smooth operation it is recommended to use the
<function>poll</function> functionality on the file, preferrably
+ from a dedicated thread. This way one thread can
monitor both read and reset operations in one common thread while
transmit
+ operations may continue unblocked in a separate thread.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Example code Kernel space</term>
+ <listitem>
+ <para>
+ This example will register to the FM channel, write a
packet, read a packet and then deregister.
+
+ <programlisting>
+ struct cg2900_device *my_dev;
+ bool event_received;
+
+ void read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+ {
+ event_received = true;
+ kfree_skb(skb);
+ }
+
+ void reset_cb(struct cg2900_device *dev)
+ {
+ /* Handle reset. Device will be automatically freed by
the ST-E driver */
+ my_dev = NULL;
+ }
+
+ static struct cg2900_callbacks my_cb = {
+ .read_cb = read_cb,
+ .reset_cb = reset_cb
+ };
+
+ void example_open(void)
+ {
+ my_dev = cg2900_register_user(CG2900_FM_RADIO, &my_cb);
+ if (!my_dev) {
+ printk("Error! Couldn't register!\n");
+ }
+ }
+
+ void example_close(void)
+ {
+ cg2900_deregister_user(my_dev);
+ my_dev = NULL;
+ }
+
+ void example_write_and_read(uint8_t *data, int len)
+ {
+ int err;
+ struct sk_buff *skb = cg2900_alloc_skb(len, GFP_KERNEL);
+
+ if (skb) {
+ memcpy(skb_put(skb, len), data, len);
+ err = cg2900_write(my_dev, skb);
+ if (!err) {
+ event_received = false;
+
+ while (!event_received) {
+ /* Wait for ack event. Received in read_cb() above */
+ schedule_timeout_interruptible(jiffies + 50);
+ }
+ } else {
+ printk("Couldn't write to controller (%d)\n", err);
+ kfree_skb(skb);
+ }
+ }
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Example code User space</term>
+ <listitem>
+ <para>
+ This example will open the GNSS channel, write a packet,
read a packet and then close the channel.
+ In this example all functions are performed in the same thread.
+ It is however adviced to perform <function>read</function>
and <function>ioctl</function> read through a separate thread,
+ preferrably using <function>poll</function>.
+
+ <programlisting>
+ struct my_info_t {
+ int fd;
+ };
+
+ static struct my_info_t my_info;
+
+ /* This is a fake command and has nothing to do with
real GNSS commands.
+ * Note that the command does NOT contain the H:4 header.
+ * The header is added by the ST-E Connectivity driver.
+ */
+ static const uint8_t tx_cmd[] = {0x12, 0x34, 0x56};
+
+ int main(int argc, char **argv)
+ {
+ uint8_t rx_buffer[100];
+ int rx_bytes = 0;
+ int err;
+
+ my_info.fd = open("/dev/cg2900_gnss", O_RDWR);
+ if (my_info.fd < 0) {
+ printf("Error on open file: %d (%s)\n", errno,
strerror(errno));
+ return errno;
+ }
+ if (0 > write(my_info.fd, tx_cmd, sizeof(tx_cmd))) {
+ printf("Error on write file: %d (%s)\n", errno,
strerror(errno));
+ return errno;
+ }
+ /* Read will sleep until there is data available */
+ rx_bytes = read(my_info.fd, rx_buffer, 100);
+ if (rx_bytes >= 0) {
+ printf("Received %d bytes\n", rx_bytes);
+ } else {
+ printf("Error on read file: %d (%s)\n", errno,
strerror(errno));
+ return errno;
+ }
+ err = close(my_info.fd);
+ if (err) {
+ printf("Error on close file: %d (%s)\n", errno,
strerror(errno));
+ return errno;
+ }
+ return 0;
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <!-- TODO: Task descriptions are step by step instructions
+ for performing specific actions and tasks.
+ Each task is typically one scenario.
+ Each task is described in a separate (section).
+ (section) tags can be nested, which is
+ especially recommended if
+ the task consists of several scenarios.
+ Remove this chapter completely if there are no
+ tasks to mention and there is no tutorial needed.
+ Do NOT change the chapter id or title! -->
+ <!-- TODO: This guideline for this chapter may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+ </chapter>
+
+ <chapter id="driver-configuration">
+ <title>Driver Configuration and Interaction</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ For debug purposes the define CG2900_DEBUG_LEVEL in the file
cg2900_debug.h can be changed to set how much debug printouts
+ that shall be generated.
+ <itemizedlist>
+ <listitem><para>0 - No debug</para></listitem>
+ <listitem><para>1 - Error printouts</para></listitem>
+ <listitem><para>10 - Info printouts such as start of each
function</para></listitem>
+ <listitem><para>20 - Debug printouts such as descriptions of
operations</para></listitem>
+ <listitem><para>25 - Data printouts without content</para></listitem>
+ <listitem><para>30 - Data printouts with content</para></listitem>
+ </itemizedlist>
+ <!-- TODO: Use this paragraph as an introduction to driver
+ configuration and interaction. Describe the big picture. -->
+ <!-- TODO: This chapter contains driver specific way to perform
+ configuration and interaction. The chapter includes a
+ number of sections. They should not be removed and if
+ the driver does not have the specific support for
+ configuration or interaction should the text "not
+ applicable" be inserted. Do NOT change the chapter id
+ or title! -->
+ <!-- TODO: This guideline for this chapter may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+
+ <section id="driver-implemented-operations">
+ <title>Implemented operations in driver</title>
+ <para>
+ <!-- TODO: Describe the actual usage of the driver. Specify the actual
+ implemented operations in struct
<structname>file_operations</structname>
+ and any other set of operations. Create a table with two columns
+ (see example in intro chapter how to create a table).
+ Column one list all operations supported (read,
+ write, open, close, ioctl etc) and column two a description of the
+ semantics of the operations in the specific context of the device
+ driver from the users perspective. Document the operations in a way
+ that a user of the driver can be helped. -->
+ </para>
+ <para>
+ <table>
+ <title> Supported device driver operations when using
character device </title>
+ <tgroup cols="2"><tbody>
+ <row><entry> open </entry> <entry> Opening a character
device will register the caller to that HCI channel.</entry> </row>
+ <row><entry> release </entry> <entry> Releasing a
character device will deregister the caller from that HCI
channel</entry> </row>
+ <row><entry> poll </entry> <entry> Polling a character
device will check if there is data to read on that HCI channel</entry>
</row>
+ <row><entry> read </entry> <entry> Reading from a
character device reads from that HCI channel</entry> </row>
+ <row><entry> write </entry> <entry> Writing to a character
device writes to that HCI channel</entry> </row>
+ <row><entry> unlocked_ioctl </entry> <entry> Performing IO
control on a character device will perform special operations such as
reset on that HCI channel</entry> </row>
+ </tbody></tgroup>
+ </table>
+ </para>
+ </section>
+
+ <section id="driver-loading">
+ <title>Driver loading parameters</title>
+ <para>
+ <!-- TODO: Describe parameters that can be specified at kernel
+ driver loading with insmod or modprobe. If the driver
+ has no parameters to be specified at load time, replace this
+ text with "Not Applicable". -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>uart_default_baud</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>115200</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Readable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter uart_default_baud in cg2900_uart.c
defines the baud rate used after a chip has just been powered up.
+ It shall be set to the default baud rate of the controller.
+ For ST-Ericsson controllers STLC2690 and CG2900 this
value shall be 115200.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>uart_high_baud</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>3000000</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Modifiable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter uart_high_baud in cg2900_uart.c
defines the baud rate to use for normal data transfer.
+ This should normally be the highest allowed by the
system with regards to flow control, clocks, etc.
+ For ST-Ericsson controllers STLC2690 and CG2900 the
following values are supported:
+ <itemizedlist>
+ <listitem><para>57600</para></listitem>
+ <listitem><para>115200</para></listitem>
+ <listitem><para>230400</para></listitem>
+ <listitem><para>460800</para></listitem>
+ <listitem><para>921600</para></listitem>
+ <listitem><para>2000000</para></listitem>
+ <listitem><para>3000000</para></listitem>
+ <listitem><para>4000000</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>bd_address</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>array (Entered as comma
separated value)</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>0x00 0x80 0xDE 0xAD 0xBE 0xEF</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Modifiable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter bd_address in cg2900_core.c defines
the Bluetooth device address to use for the current device.
+ The value is an array of 6 bytes and shall be
entered as a comma separated value.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>debug_level</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>1</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Modifiable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter debug_level in cg2900_core.c defines
the debug level that is currently used.
+ The higher the debug level the more print-outs are
received in the terminal window.
+ The following values are supported:
+ <itemizedlist>
+ <listitem><para>0 = No debug</para></listitem>
+ <listitem><para>1 = Error prints</para></listitem>
+ <listitem><para>10 = General info, e.g. function
entries</para></listitem>
+ <listitem><para>20 = Debug info, e.g. steps in a
functionality</para></listitem>
+ <listitem><para>25 = Data info, i.e. prints when
data is transferred</para></listitem>
+ <listitem><para>30 = Data content, i.e. contents
of the transferred data</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>sleep_timeout_ms</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>0</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Modifiable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter sleep_timeout_ms in cg2900_core.c
defines the sleep timeout for data transmission in milliseconds.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ <!-- TODO: This guideline for this section may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+ </section>
+
+ <section id="driver-ioctl">
+ <title>Driver IO Control</title>
+ <para>
+ <!-- TODO: Describe driver parameters that can be modified
+ in runtime. Make a list of all device-dependent request code with
+ description of arguments, meaning etc. If the driver has
no IO control
+ interface, replace this text with "Not Applicable". -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><constant>CG2900_CHAR_DEV_IOCTL_RESET</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>void</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>CG2900_CHAR_DEV_IOCTL_RESET</constant>
IOCTL starts a reset
+ of the connectivity chip. This will affect the current
open channel and
+ all other open channels as well.
+ </para><para>
+ IOCTL value created using <constant>_IOW('U', 210,
int)</constant>.
+ </para><para>
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If reset is performed without errors
the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate
error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>CG2900_CHAR_DEV_IOCTL_CHECK4RESET</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Query</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>void</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The
<constant>CG2900_CHAR_DEV_IOCTL_CHECK4RESET</constant> IOCTL checks if
a reset
+ has been performed on a device.
+ </para><para>
+ IOCTL value created using <constant>_IOR('U', 212,
int)</constant>.
+ </para><para>
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If device is still open the IOCTL
function will return 0.</para></listitem>
+ <listitem><para>If reset has occurred the IOCTL
function will return 1.</para></listitem>
+ <listitem><para>If device has been closed the
IOCTL function will return 2.</para></listitem>
+ <listitem><para>A negative value will indicate
error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>CG2900_CHAR_DEV_IOCTL_GET_REVISION</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Query</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>void</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The
<constant>CG2900_CHAR_DEV_IOCTL_GET_REVISION</constant> IOCTL returns
the revision value
+ of the local connectivity controller if such
information is available.
+ </para><para>
+ IOCTL value created using <constant>_IOR('U', 213,
int)</constant>.
+ </para><para>
+ Returned values are according to information that
may be retrieved from chip manufacturer.
+ It is however possible to get indications of the
value by looking in the file
+ <constant>firmware/cg2900_patch_info.fw.org</constant>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><constant>CG2900_CHAR_DEV_IOCTL_GET_SUB_VER</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Query</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>void</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The
<constant>CG2900_CHAR_DEV_IOCTL_GET_SUB_VER</constant> IOCTL returns
the sub-version value
+ of the local connectivity controller if such
information is available.
+ </para><para>
+ IOCTL value created using <constant>_IOR('U', 214,
int)</constant>.
+ </para><para>
+ Returned values are according to information that
may be retrieved from chip manufacturer.
+ It is however possible to get indications of the
value by looking in the file
+ <constant>firmware/cg2900_patch_info.fw.org</constant>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </section>
+
+ <section id="driver-sysfs">
+ <title>Driver Interaction with Sysfs</title>
+ <para>
+ <!-- TODO: Describe data available for read and write on the drivers
+ Sysfs entry. Specify where the entry for the device is located in
+ Sysfs such as <filename>/sys/devices/*</filename>,
<filename>/sys/devices/*</filename>
+ , etc.
+ Specify the data types for the attributes. Specify if the
+ attributes are read-only or write-only. If the driver has no Sysfs
+ interface, replace this text with "Not Applicable". -->
+ Not Applicable
+ </para>
+ </section>
+
+ <section id="driver-proc">
+ <title>Driver Interaction using /proc filesystem</title>
+ <para>
+ Not Applicable
+ <!-- TODO: Describe data available for read and write on the drivers
+ /proc entry. Specify where the entry for the device is located.
+ Specify the data types for the attributes. Specify if the
+ attributes are read-only or writeonly. If the driver has no /proc
+ interface, replace this text with "Not Applicable". -->
+ </para>
+ </section>
+
+ <section id="driver-other">
+ <title>Other means for Driver Interaction</title>
+ <para>
+ <!-- TODO: Does the driver have any configurations files?
Describe other means
+ for driver status access or configuration. If the driver
has no other
+ means (besides the one in already described in this
chapter), replace
+ this text with "Not Applicable". -->
+ Not Applicable
+ </para>
+ </section>
+
+ <section id="driver-node">
+ <title>Driver Node File</title>
+ <variablelist>
+ <varlistentry>
+ <term>CG2900 main device</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_driver0</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_driver represents the main parent node
for all other character devices supplied in the ST-Ericsson
connectivity driver except for the CCD Test device. It does not
support any operations such as read or write.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>BT Command</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_bt_cmd</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_bt_cmd is the device for the HCI
Bluetooth command channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>BT ACL</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_bt_acl</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_bt_acl is the device for the HCI
Bluetooth ACL channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>BT Event</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_bt_evt</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_bt_evt is the device for the HCI
Bluetooth event channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>FM Radio</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_fm_radio</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_fm_radio is the device for the HCI FM
Radio channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>GNSS</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_gnss</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_gnss is the device for the HCI GNSS
channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Debug</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_debug</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_debug is the device for the HCI Debug
channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>ST-Ericsson Tools</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_ste_tools</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_ste_tools is the device for the HCI
ST-Ericsson tools channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>HCI Logger</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_hci_logger</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_hci_logger is the device for the HCI
logger channel.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>User Space Control</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_us_ctrl</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_us_ctrl is the device for
initialization and control of the STE CONN driver.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>CCD Test stub</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_ccd_test</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_ccd_test is the device for performing
module tests of the ST-Ericsson connectivity driver. It acts as a stub
replacing the transport towards the chip. It is of the type Misc
devices.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>BT Audio</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_bt_audio</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_bt_audio is the device for sending HCI
BT Audio controll commands to the chip. </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>FM Audio</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_fm_audio</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_fm_audio is the device for sending HCI
BT Audio controll commands to the chip.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Core</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_core</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The cg2900_core is a device for turn on/off the
chip. NOTE other devices will also turn on/off the chip if
needed.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>CG29XX Audio</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/cg2900_audio</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The cg2900_audio is a device for testing the CG2900
Audio driver from User space.
+ It replicates the normal CG2900 Audio interface
through <constant>write/read</constant> operations.
+ The <constant>write</constant> command is used as following:
+ <itemizedlist>
+ <listitem><para>4 byte op code (see below)</para></listitem>
+ <listitem><para>Data field according to respective
CG29XX Audio function (no session ID needed)</para></listitem>
+ </itemizedlist>
+ If the operation fails the <constant>write</constant>
command operation will return the error.
+ Op codes are (4 bytes size):
+ <itemizedlist>
+ <listitem><para>0x00000001 =
CHAR_DEV_OP_CODE_SET_DAI_CONF</para></listitem>
+ <listitem><para>0x00000002 =
CHAR_DEV_OP_CODE_GET_DAI_CONF</para></listitem>
+ <listitem><para>0x00000003 =
CHAR_DEV_OP_CODE_CONFIGURE_ENDPOINT</para></listitem>
+ <listitem><para>0x00000004 =
CHAR_DEV_OP_CODE_CONNECT_AND_START_STREAM</para></listitem>
+ <listitem><para>0x00000005 =
CHAR_DEV_OP_CODE_STOP_STREAM</para></listitem>
+ </itemizedlist>
+
+ The <constant>read</constant> command is used for the
commands <constant>CHAR_DEV_OP_CODE_GET_DAI_CONF</constant>
+ and
<constant>CHAR_DEV_OP_CODE_CONNECT_AND_START_STREAM</constant> if the
corresponding commands are successful.
+ The returned data will be formatted accordingly:
+ <itemizedlist>
+ <listitem><para>4 byte op code (see below)</para></listitem>
+ <listitem><para>Data field according to normal
CG29XX Audio functions, e.g. stream handle or
configuration</para></listitem>
+ </itemizedlist>
+ The <constant>CHAR_DEV_OP_CODE_GET_DAI_CONF</constant>
is a bit special since it require an endpoint in-parameter
+ (when calling <constant>write</constant>) to return
the corresponding DAI configuration when calling
<constant>read</constant>.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </section>
+
+
+ </chapter>
+
+
+ <chapter id="bugs">
+ <title>Known Bugs And Assumptions</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Driver supports only one user per HCI channel.</term>
+ <listitem>
+ <para>
+ To simplify design and limitation as well as keeping the
API simple and reliable, the driver only supports one user per HCI
channel.
+ <!-- TODO: Briefly describe the limitation, unless all
+ information is already present in the title.
+ Use full english sentences.
+ Repeat the varlistentry for each limitation.
+ If none are known, replace this varlistentry
+ with the one below. -->
+ <!-- TODO: This guideline for this chapter may be extended
+ during the user-guide guidelines drop. -->
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </chapter>
+
+<chapter id="pubfunctions">
+ <title>Public Functions Provided</title>
+ <para>
+ List of public functions.
+ </para>
+ <!-- Do NOT change the chapter id or title! -->
+ <!-- TODO: Replace with link to appropriate headerfile(s).
+ One per row, ensure the
+ exclamation mark is on the first column! If no
+ appropriate header file exist describing a public interface,
+ replace the inclusion with a paragraph containing the text
+ "Not Applicable" -->
+ <section id="cg2900.h">
+ <title>cg2900.h</title>
+!Edrivers/mfd/cg2900/cg2900_core.c
+ </section>
+ <section id="cg2900_audio.h">
+ <title>cg2900_audio.h</title>
+!Edrivers/mfd/cg2900/cg2900_audio.c
+ </section>
+
+</chapter>
+
+<chapter id="internal-functions">
+ <title>Internal Functions Provided</title>
+ <para>
+ List of internal functions.
+ </para>
+ <!-- Do NOT change the chapter id or title! -->
+ <!-- TODO: Replace with link to appropriate headerfile(s),
+ source file(s), or both. One per row, ensure the
+ exclamation mark is on the first column! If no
+ appropriate header or source file exist describing a public interface,
+ replace the inclusion with a paragraph containing the text
+ "Not Applicable"-->
+ <section id="cg2900_core.h">
+ <title>cg2900_core.h</title>
+!Edrivers/mfd/cg2900/cg2900_core.h
+ </section>
+</chapter>
+</book>
--
1.6.3.3
^ permalink raw reply related
* [PATCH 8/9] Bluetooth: Add support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:40 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds support for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch registers to the Bluetooth stack and, when opened, it
registers to the Bluetooth channels in the CG2900 MFD driver.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/bluetooth/Kconfig | 7 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btcg2900.c | 925 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 933 insertions(+), 0 deletions(-)
create mode 100644 drivers/bluetooth/btcg2900.c
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 02deef4..9ca8d69 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -219,4 +219,11 @@ config BT_ATH3K
Say Y here to compile support for "Atheros firmware download driver"
into the kernel or say M to compile it as module (ath3k).
+config BT_CG2900
+ tristate "ST-Ericsson CG2900 driver"
+ depends on MFD_CG2900 && BT
+ help
+ Select if ST-Ericsson CG2900 Connectivity controller shall be used as
+ Bluetooth controller for BlueZ.
+
endmenu
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index 71bdf13..1f8ce2d 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_BT_HCIBTUSB) += btusb.o
obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o
obj-$(CONFIG_BT_ATH3K) += ath3k.o
+obj-$(CONFIG_BT_CG2900) += btcg2900.o
obj-$(CONFIG_BT_MRVL) += btmrvl.o
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
diff --git a/drivers/bluetooth/btcg2900.c b/drivers/bluetooth/btcg2900.c
new file mode 100644
index 0000000..11d6518
--- /dev/null
+++ b/drivers/bluetooth/btcg2900.c
@@ -0,0 +1,925 @@
+/*
+ * Bluetooth driver for ST-Ericsson CG2900 connectivity controller.
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com)
+ * Henrik Possung (henrik.possung@stericsson.com)
+ * Josef Kindberg (josef.kindberg@stericsson.com)
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com)
+ * Kjell Andersson (kjell.k.andersson@stericsson.com)
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <asm/byteorder.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+#include <net/bluetooth/hci_core.h>
+
+#define BT_VS_BT_ENABLE 0xFF10
+
+#define VS_BT_DISABLE 0x00
+#define VS_BT_ENABLE 0x01
+
+#define BT_HEADER_LENGTH 0x03
+
+#define STLC2690_HCI_REV 0x0600
+#define CG2900_PG1_HCI_REV 0x0101
+#define CG2900_PG2_HCI_REV 0x0200
+#define CG2900_PG1_SPECIAL_HCI_REV 0x0700
+
+#define NAME "BTCG2900 "
+
+/* Wait for 5 seconds for a response to our requests */
+#define RESP_TIMEOUT 5000
+
+/* Bluetooth error codes */
+#define HCI_ERR_NO_ERROR 0x00
+#define HCI_ERR_CMD_DISALLOWED 0x0C
+
+/**
+ * enum reset_state - RESET-states of the HCI driver.
+ *
+ * @RESET_IDLE: No reset in progress.
+ * @RESET_ACTIVATED: Reset in progress.
+ * @RESET_UNREGISTERED: hdev is unregistered.
+ */
+
+enum reset_state {
+ RESET_IDLE,
+ RESET_ACTIVATED,
+ RESET_UNREGISTERED
+};
+
+/**
+ * enum enable_state - ENABLE-states of the HCI driver.
+ *
+ * @ENABLE_IDLE: The HCI driver is loaded but not opened.
+ * @ENABLE_WAITING_BT_ENABLED_CC: The HCI driver is waiting for a command
+ * complete event from the BT chip as a
+ * response to a BT Enable (true) command.
+ * @ENABLE_BT_ENABLED: The BT chip is enabled.
+ * @ENABLE_WAITING_BT_DISABLED_CC: The HCI driver is waiting for a command
+ * complete event from the BT chip as a
+ * response to a BT Enable (false) command.
+ * @ENABLE_BT_DISABLED: The BT chip is disabled.
+ * @ENABLE_BT_ERROR: The HCI driver is in a bad state, some
+ * thing has failed and is not expected to
+ * work properly.
+ */
+enum enable_state {
+ ENABLE_IDLE,
+ ENABLE_WAITING_BT_ENABLED_CC,
+ ENABLE_BT_ENABLED,
+ ENABLE_WAITING_BT_DISABLED_CC,
+ ENABLE_BT_DISABLED,
+ ENABLE_BT_ERROR
+};
+
+/**
+ * struct btcg2900_info - Specifies HCI driver private data.
+ *
+ * This type specifies CG2900 HCI driver private data.
+ *
+ * @bt_cmd: Device structure for BT command channel.
+ * @bt_evt: Device structure for BT event channel.
+ * @bt_acl: Device structure for BT ACL channel.
+ * @pdev: Device structure for platform device.
+ * @hdev: Device structure for HCI device.
+ * @reset_state: Device enum for HCI driver reset state.
+ * @enable_state: Device enum for HCI driver BT enable state.
+ */
+struct btcg2900_info {
+ struct cg2900_device *bt_cmd;
+ struct cg2900_device *bt_evt;
+ struct cg2900_device *bt_acl;
+ struct platform_device *pdev;
+ struct hci_dev *hdev;
+ enum reset_state reset_state;
+ enum enable_state enable_state;
+};
+
+/**
+ * struct dev_info - Specifies private data used when receiving
callbacks from CG2900 driver.
+ *
+ * @hdev: Device structure for HCI device.
+ * @hci_data_type: Type of data according to BlueZ.
+ */
+struct dev_info {
+ struct hci_dev *hdev;
+ u8 hci_data_type;
+};
+
+/**
+ * struct vs_bt_enable_cmd - Specifies HCI VS Bluetooth_Enable command.
+ *
+ * @op_code: HCI command op code.
+ * @len: Parameter length of command.
+ * @enable: 0 for disable BT, 1 for enable BT.
+ */
+struct vs_bt_enable_cmd {
+ __le16 op_code;
+ u8 len;
+ u8 enable;
+} __attribute__((packed));
+
+static struct btcg2900_info *btcg2900_info;
+
+/*
+ * hci_wait_queue - Main Wait Queue in HCI driver.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(hci_wait_queue);
+
+/* Internal function declarations */
+static int register_bluetooth(void);
+
+/* Internal functions */
+
+/**
+ * get_bt_enable_cmd() - Get HCI BT enable command.
+ * @bt_enable: true if Bluetooth IP shall be enabled, false otherwise.
+ *
+ * Returns:
+ * NULL if no command shall be sent,
+ * sk_buffer with command otherwise.
+ */
+struct sk_buff *get_bt_enable_cmd(bool bt_enable)
+{
+ struct sk_buff *skb;
+ struct vs_bt_enable_cmd *cmd;
+ struct cg2900_rev_data rev_data;
+
+ if (!cg2900_get_local_revision(&rev_data)) {
+ BT_ERR(NAME "Couldn't get revision");
+ return NULL;
+ }
+
+ /* If connected chip does not support the command return NULL */
+ if (CG2900_PG1_SPECIAL_HCI_REV != rev_data.revision &&
+ CG2900_PG1_HCI_REV != rev_data.revision &&
+ CG2900_PG2_HCI_REV != rev_data.revision)
+ return NULL;
+
+ /* CG2900 used */
+ skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+ if (!skb) {
+ BT_ERR(NAME "Could not allocate skb");
+ return NULL;
+ }
+
+ 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;
+
+ return skb;
+}
+
+/**
+ * remove_bt_users() - Unregister and remove any existing BT users.
+ * @info: HCI driver info structure.
+ */
+static void remove_bt_users(struct btcg2900_info *info)
+{
+ if (info->bt_cmd) {
+ kfree(info->bt_cmd->user_data);
+ info->bt_cmd->user_data = NULL;
+ cg2900_deregister_user(info->bt_cmd);
+ info->bt_cmd = NULL;
+ }
+
+ if (info->bt_evt) {
+ kfree(info->bt_evt->user_data);
+ info->bt_evt->user_data = NULL;
+ cg2900_deregister_user(info->bt_evt);
+ info->bt_evt = NULL;
+ }
+
+ if (info->bt_acl) {
+ kfree(info->bt_acl->user_data);
+ info->bt_acl->user_data = NULL;
+ cg2900_deregister_user(info->bt_acl);
+ info->bt_acl = NULL;
+ }
+}
+
+/**
+ * hci_read_cb() - Callback for handling data received from CG2900 driver.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming from device.
+ */
+static void hci_read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+ int err = 0;
+ struct dev_info *dev_info;
+ struct hci_event_hdr *evt;
+ struct hci_ev_cmd_complete *cmd_complete;
+ struct hci_ev_cmd_status *cmd_status;
+ u8 status;
+
+ if (!skb) {
+ BT_ERR(NAME "NULL supplied for skb");
+ return;
+ }
+
+ if (!dev) {
+ BT_ERR(NAME "dev == NULL");
+ goto fin_free_skb;
+ }
+
+ dev_info = (struct dev_info *)dev->user_data;
+
+ if (!dev_info) {
+ BT_ERR(NAME "dev_info == NULL");
+ goto fin_free_skb;
+ }
+
+ evt = (struct hci_event_hdr *)skb->data;
+ cmd_complete = (struct hci_ev_cmd_complete *)(skb->data + sizeof(*evt));
+ cmd_status = (struct hci_ev_cmd_status *)(skb->data + sizeof(*evt));
+
+ /*
+ * Check if HCI Driver it self is expecting a Command Complete packet
+ * from the chip after a BT Enable command.
+ */
+ if ((btcg2900_info->enable_state == ENABLE_WAITING_BT_ENABLED_CC ||
+ btcg2900_info->enable_state == ENABLE_WAITING_BT_DISABLED_CC) &&
+ btcg2900_info->bt_evt->h4_channel == dev->h4_channel &&
+ evt->evt == HCI_EV_CMD_COMPLETE &&
+ le16_to_cpu(cmd_complete->opcode) == BT_VS_BT_ENABLE) {
+ /*
+ * This is the command complete event for
+ * the HCI_Cmd_VS_Bluetooth_Enable.
+ * Check result and update state.
+ *
+ * The BT chip is enabled/disabled. Either it was enabled/
+ * disabled now (status NO_ERROR) or it was already enabled/
+ * disabled (assuming status CMD_DISALLOWED is already enabled/
+ * disabled).
+ */
+ status = *(skb->data + sizeof(*evt) + sizeof(*cmd_complete));
+ if (status != HCI_ERR_NO_ERROR &&
+ status != HCI_ERR_CMD_DISALLOWED) {
+ BT_ERR(NAME "Could not enable/disable BT core (0x%X)",
+ status);
+ BT_DBG("New enable_state: ENABLE_BT_ERROR");
+ btcg2900_info->enable_state = ENABLE_BT_ERROR;
+ goto fin_free_skb;
+ }
+
+ if (btcg2900_info->enable_state ==
+ ENABLE_WAITING_BT_ENABLED_CC) {
+ BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+ btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+ BT_INFO("CG2900 BT core is enabled");
+ } else {
+ BT_DBG("New enable_state: ENABLE_BT_DISABLED");
+ btcg2900_info->enable_state = ENABLE_BT_DISABLED;
+ BT_INFO("CG2900 BT core is disabled");
+ }
+
+ /* Wake up whom ever is waiting for this result. */
+ wake_up_interruptible(&hci_wait_queue);
+ goto fin_free_skb;
+ } else if ((btcg2900_info->enable_state ==
+ ENABLE_WAITING_BT_DISABLED_CC ||
+ btcg2900_info->enable_state ==
+ ENABLE_WAITING_BT_ENABLED_CC) &&
+ btcg2900_info->bt_evt->h4_channel == dev->h4_channel &&
+ evt->evt == HCI_EV_CMD_STATUS &&
+ le16_to_cpu(cmd_status->opcode) == BT_VS_BT_ENABLE) {
+ /*
+ * Clear the status events since the Bluez is not expecting
+ * them.
+ */
+ BT_DBG("HCI Driver received Command Status (BT enable): 0x%X",
+ cmd_status->status);
+ /*
+ * This is the command status event for
+ * the HCI_Cmd_VS_Bluetooth_Enable.
+ * Just free the packet.
+ */
+ goto fin_free_skb;
+ } else {
+ bt_cb(skb)->pkt_type = dev_info->hci_data_type;
+ skb->dev = (struct net_device *)dev_info->hdev;
+ /* Update BlueZ stats */
+ dev_info->hdev->stat.byte_rx += skb->len;
+ if (bt_cb(skb)->pkt_type == HCI_ACLDATA_PKT)
+ dev_info->hdev->stat.acl_rx++;
+ else
+ dev_info->hdev->stat.evt_rx++;
+
+ BT_DBG("Data receive %d bytes", skb->len);
+
+ /* Provide BlueZ with received frame*/
+ err = hci_recv_frame(skb);
+ /* If err, skb have been freed in hci_recv_frame() */
+ if (err)
+ BT_ERR(NAME "Failed in supplying packet to Bluetooth"
+ " stack (%d)", err);
+ }
+
+ return;
+
+fin_free_skb:
+ kfree_skb(skb);
+}
+
+/**
+ * hci_reset_cb() - Callback for handling reset from CG2900 driver.
+ * @dev: CPD device resetting.
+ */
+static void hci_reset_cb(struct cg2900_device *dev)
+{
+ int err;
+ struct hci_dev *hdev;
+ struct dev_info *dev_info;
+ struct btcg2900_info *info;
+
+ BT_INFO(NAME "hci_reset_cb");
+
+ if (!dev) {
+ BT_ERR(NAME "NULL supplied for dev");
+ return;
+ }
+
+ dev_info = (struct dev_info *)dev->user_data;
+ if (!dev_info) {
+ BT_ERR(NAME "NULL supplied for dev_info");
+ return;
+ }
+
+ hdev = dev_info->hdev;
+ if (!hdev) {
+ BT_ERR(NAME "NULL supplied for hdev");
+ return;
+ }
+
+ info = (struct btcg2900_info *)hdev->driver_data;
+ if (!info) {
+ BT_ERR(NAME "NULL supplied for driver_data");
+ return;
+ }
+
+ switch (dev_info->hci_data_type) {
+
+ case HCI_EVENT_PKT:
+ info->bt_evt = NULL;
+ break;
+
+ case HCI_COMMAND_PKT:
+ info->bt_cmd = NULL;
+ break;
+
+ case HCI_ACLDATA_PKT:
+ info->bt_acl = NULL;
+ break;
+
+ default:
+ BT_ERR(NAME "Unknown HCI data type:%d",
+ dev_info->hci_data_type);
+ return;
+ }
+
+ BT_DBG("New reset_state: RESET_ACTIVATED");
+ btcg2900_info->reset_state = RESET_ACTIVATED;
+
+ /*
+ * Free userdata as device info structure will be freed by CG2900
+ * when this callback returns.
+ */
+ kfree(dev->user_data);
+ dev->user_data = NULL;
+
+ /*
+ * Continue to deregister hdev if all channels has been reset else
+ * return.
+ */
+ if (info->bt_evt || info->bt_cmd || info->bt_acl)
+ return;
+
+ /*
+ * Deregister HCI device. Close and Destruct functions should
+ * in turn be called by BlueZ.
+ */
+ BT_DBG("Deregister HCI device");
+ err = hci_unregister_dev(hdev);
+ if (err)
+ BT_ERR(NAME "Can not deregister HCI device! (%d)", err);
+ /*
+ * Now we are in trouble. Try to register a new hdev
+ * anyway even though this will cost some memory.
+ */
+
+ wait_event_interruptible_timeout(hci_wait_queue,
+ (RESET_UNREGISTERED == btcg2900_info->reset_state),
+ msecs_to_jiffies(RESP_TIMEOUT));
+ if (RESET_UNREGISTERED != btcg2900_info->reset_state)
+ /*
+ * Now we are in trouble. Try to register a new hdev
+ * anyway even though this will cost some memory.
+ */
+ BT_ERR(NAME "Timeout expired. Could not deregister HCI device");
+
+ /* Init and register hdev */
+ BT_DBG("Register HCI device");
+ err = register_bluetooth();
+ if (err)
+ BT_ERR(NAME "HCI Device registration error (%d).", err);
+}
+
+/*
+ * struct cg2900_cb - Specifies callback structure for CG2900 user.
+ *
+ * @read_cb: Callback function called when data is received from
+ * the controller.
+ * @reset_cb: Callback function called when the controller has been reset.
+ */
+static struct cg2900_callbacks cg2900_cb = {
+ .read_cb = hci_read_cb,
+ .reset_cb = hci_reset_cb
+};
+
+/**
+ * btcg2900_open() - Open HCI interface.
+ * @hdev: HCI device being opened.
+ *
+ * BlueZ callback function for opening HCI interface to device.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer is supplied.
+ * -EOPNOTSUPP if supplied packet type is not supported.
+ * -EBUSY if device is already opened.
+ * -EACCES if opening of channels failed.
+ */
+static int btcg2900_open(struct hci_dev *hdev)
+{
+ struct btcg2900_info *info;
+ struct dev_info *dev_info;
+ struct sk_buff *enable_cmd;
+ int err;
+
+ BT_INFO("Open ST-Ericsson CG2900 driver");
+
+ if (!hdev) {
+ BT_ERR(NAME "NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct btcg2900_info *)hdev->driver_data;
+ if (!info) {
+ BT_ERR(NAME "NULL supplied for driver_data");
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) {
+ BT_ERR(NAME "Device already opened!");
+ return -EBUSY;
+ }
+
+ if (!(info->bt_cmd)) {
+ info->bt_cmd = cg2900_register_user(CG2900_BT_CMD,
+ &cg2900_cb);
+ if (info->bt_cmd) {
+ dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (dev_info) {
+ dev_info->hdev = hdev;
+ dev_info->hci_data_type = HCI_COMMAND_PKT;
+ }
+ info->bt_cmd->user_data = dev_info;
+ } else {
+ BT_ERR("Couldn't register CG2900_BT_CMD to CG2900");
+ err = -EACCES;
+ goto handle_error;
+ }
+ }
+
+ if (!(info->bt_evt)) {
+ info->bt_evt = cg2900_register_user(CG2900_BT_EVT,
+ &cg2900_cb);
+ if (info->bt_evt) {
+ dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (dev_info) {
+ dev_info->hdev = hdev;
+ dev_info->hci_data_type = HCI_EVENT_PKT;
+ }
+ info->bt_evt->user_data = dev_info;
+ } else {
+ BT_ERR("Couldn't register CG2900_BT_EVT to CG2900");
+ err = -EACCES;
+ goto handle_error;
+ }
+ }
+
+ if (!(info->bt_acl)) {
+ info->bt_acl = cg2900_register_user(CG2900_BT_ACL,
+ &cg2900_cb);
+ if (info->bt_acl) {
+ dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (dev_info) {
+ dev_info->hdev = hdev;
+ dev_info->hci_data_type = HCI_ACLDATA_PKT;
+ }
+ info->bt_acl->user_data = dev_info;
+ } else {
+ BT_ERR("Couldn't register CG2900_BT_ACL to CG2900");
+ err = -EACCES;
+ goto handle_error;
+ }
+ }
+
+ if (info->reset_state == RESET_ACTIVATED) {
+ BT_DBG("New reset_state: RESET_IDLE");
+ btcg2900_info->reset_state = RESET_IDLE;
+ }
+
+ /*
+ * Call function that returns the chip dependent vs_bt_enable(true)
+ * HCI command.
+ * If NULL is returned, then no bt_enable command should be sent to the
+ * chip.
+ */
+ enable_cmd = get_bt_enable_cmd(true);
+ if (!enable_cmd) {
+ /* The chip is enabled by default */
+ BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+ btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+ return 0;
+ }
+
+ /* Set the HCI state before sending command to chip. */
+ BT_DBG("New enable_state: ENABLE_WAITING_BT_ENABLED_CC");
+ btcg2900_info->enable_state = ENABLE_WAITING_BT_ENABLED_CC;
+
+ /* Send command to chip */
+ cg2900_write(info->bt_cmd, enable_cmd);
+
+ /*
+ * Wait for callback to receive command complete and then wake us up
+ * again.
+ */
+ wait_event_interruptible_timeout(hci_wait_queue,
+ (info->enable_state == ENABLE_BT_ENABLED),
+ msecs_to_jiffies(RESP_TIMEOUT));
+ /* Check the current state to se that it worked. */
+ if (info->enable_state != ENABLE_BT_ENABLED) {
+ BT_ERR("Could not enable CG2900 BT core (%d)",
+ info->enable_state);
+ err = -EACCES;
+ BT_DBG("New enable_state: ENABLE_BT_DISABLED");
+ btcg2900_info->enable_state = ENABLE_BT_DISABLED;
+ goto handle_error;
+ }
+
+ return 0;
+
+handle_error:
+ remove_bt_users(info);
+ clear_bit(HCI_RUNNING, &(hdev->flags));
+ return err;
+
+}
+
+/**
+ * btcg2900_close() - Close HCI interface.
+ * @hdev: HCI device being closed.
+ *
+ * BlueZ callback function for closing HCI interface.
+ * It flushes the interface first.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer is supplied.
+ * -EOPNOTSUPP if supplied packet type is not supported.
+ * -EBUSY if device is not opened.
+ */
+static int btcg2900_close(struct hci_dev *hdev)
+{
+ struct btcg2900_info *info = NULL;
+ struct sk_buff *enable_cmd;
+
+ BT_DBG("btcg2900_close");
+
+ if (!hdev) {
+ BT_ERR(NAME "NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct btcg2900_info *)hdev->driver_data;
+ if (!info) {
+ BT_ERR(NAME "NULL supplied for driver_data");
+ return -EINVAL;
+ }
+
+ if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) {
+ BT_ERR(NAME "Device already closed!");
+ return -EBUSY;
+ }
+
+ /* Do not do this if there is an reset ongoing */
+ if (btcg2900_info->reset_state == RESET_ACTIVATED)
+ goto remove_users;
+
+ /*
+ * Get the chip dependent BT Enable HCI command. The command parameter
+ * shall be set to false to disable the BT core.
+ * If NULL is returned, then no BT Enable command should be sent to the
+ * chip.
+ */
+ enable_cmd = get_bt_enable_cmd(false);
+ if (!enable_cmd) {
+ /*
+ * The chip is enabled by default and we should not disable it.
+ */
+ BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+ btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+ goto remove_users;
+ }
+
+ /* Set the HCI state before sending command to chip */
+ BT_DBG("New enable_state: ENABLE_WAITING_BT_DISABLED_CC");
+ btcg2900_info->enable_state = ENABLE_WAITING_BT_DISABLED_CC;
+
+ /* Send command to chip */
+ cg2900_write(info->bt_cmd, enable_cmd);
+
+ /*
+ * Wait for callback to receive command complete and then wake us up
+ * again.
+ */
+ wait_event_interruptible_timeout(hci_wait_queue,
+ (info->enable_state == ENABLE_BT_DISABLED),
+ msecs_to_jiffies(RESP_TIMEOUT));
+ /* Check the current state to se that it worked. */
+ if (info->enable_state != ENABLE_BT_DISABLED) {
+ BT_ERR("Could not disable CG2900 BT core.");
+ BT_DBG("New enable_state: ENABLE_BT_ENABLED");
+ btcg2900_info->enable_state = ENABLE_BT_ENABLED;
+ }
+
+remove_users:
+ /* Finally deregister all users and free allocated data */
+ remove_bt_users(info);
+ return 0;
+}
+
+/**
+ * btcg2900_send() - Send packet to device.
+ * @skb: sk buffer to be sent.
+ *
+ * BlueZ callback function for sending sk buffer.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer is supplied.
+ * -EOPNOTSUPP if supplied packet type is not supported.
+ * Error codes from cg2900_write.
+ */
+static int btcg2900_send(struct sk_buff *skb)
+{
+ struct hci_dev *hdev;
+ struct btcg2900_info *info;
+ int err = 0;
+
+ if (!skb) {
+ BT_ERR(NAME "NULL supplied for skb");
+ return -EINVAL;
+ }
+
+ hdev = (struct hci_dev *)(skb->dev);
+ if (!hdev) {
+ BT_ERR(NAME "NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct btcg2900_info *)hdev->driver_data;
+ if (!info) {
+ BT_ERR(NAME "NULL supplied for info");
+ return -EINVAL;
+ }
+
+ /* Update BlueZ stats */
+ hdev->stat.byte_tx += skb->len;
+
+ BT_DBG("Data transmit %d bytes", skb->len);
+
+ switch (bt_cb(skb)->pkt_type) {
+ case HCI_COMMAND_PKT:
+ BT_DBG("Sending HCI_COMMAND_PKT");
+ err = cg2900_write(info->bt_cmd, skb);
+ hdev->stat.cmd_tx++;
+ break;
+ case HCI_ACLDATA_PKT:
+ BT_DBG("Sending HCI_ACLDATA_PKT");
+ err = cg2900_write(info->bt_acl, skb);
+ hdev->stat.acl_tx++;
+ break;
+ default:
+ BT_ERR(NAME "Trying to transmit unsupported packet type"
+ " (0x%.2X)", bt_cb(skb)->pkt_type);
+ err = -EOPNOTSUPP;
+ break;
+ };
+
+ return err;
+}
+
+/**
+ * btcg2900_destruct() - Destruct HCI interface.
+ * @hdev: HCI device being destructed.
+ */
+static void btcg2900_destruct(struct hci_dev *hdev)
+{
+ BT_DBG("btcg2900_destruct");
+
+ if (!btcg2900_info)
+ return;
+
+ /*
+ * When destruct is called it means that the Bluetooth stack is done
+ * with the HCI device and we can now free it.
+ * Normally we do this only when removing the whole module through
+ * btcg2900_remove(), but when being reset we free the device here and
+ * we then set the reset state so that the reset handler can allocate a
+ * new HCI device and then register it to the Bluetooth stack.
+ */
+ if (btcg2900_info->reset_state == RESET_ACTIVATED) {
+ if (btcg2900_info->hdev)
+ hci_free_dev(btcg2900_info->hdev);
+ BT_DBG("New reset_state: RESET_UNREGISTERED");
+ btcg2900_info->reset_state = RESET_UNREGISTERED;
+ wake_up_interruptible(&hci_wait_queue);
+ }
+}
+
+/**
+ * register_bluetooth() - Initialize module.
+ *
+ * Alloc, init, and register HCI device to BlueZ.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation fails.
+ * Error codes from hci_register_dev.
+ */
+static int register_bluetooth(void)
+{
+ int err;
+ static struct cg2900_bt_platform_data *pf_data;
+
+ pf_data = dev_get_platdata(&btcg2900_info->pdev->dev);
+
+ btcg2900_info->hdev = hci_alloc_dev();
+ if (!btcg2900_info->hdev) {
+ BT_ERR("Could not allocate mem for CG2900 BT driver");
+ return -ENOMEM;
+ }
+
+ SET_HCIDEV_DEV(btcg2900_info->hdev, &btcg2900_info->pdev->dev);
+ if (pf_data) {
+ btcg2900_info->hdev->bus = pf_data->bus;
+ } else {
+ BT_DBG(NAME "Missing platform data. Defaulting to UART");
+ btcg2900_info->hdev->bus = HCI_UART;
+ }
+ btcg2900_info->hdev->driver_data = btcg2900_info;
+ btcg2900_info->hdev->owner = THIS_MODULE;
+ btcg2900_info->hdev->open = btcg2900_open;
+ btcg2900_info->hdev->close = btcg2900_close;
+ btcg2900_info->hdev->send = btcg2900_send;
+ btcg2900_info->hdev->destruct = btcg2900_destruct;
+
+ err = hci_register_dev(btcg2900_info->hdev);
+ if (err) {
+ BT_ERR(NAME "Can not register HCI device (%d)", err);
+ hci_free_dev(btcg2900_info->hdev);
+ }
+
+ BT_DBG("New enable_state: ENABLE_IDLE");
+ btcg2900_info->enable_state = ENABLE_IDLE;
+ BT_DBG("New reset_state: RESET_IDLE");
+ btcg2900_info->reset_state = RESET_IDLE;
+
+ return err;
+}
+
+/**
+ * btcg2900_probe() - Initialize module.
+ *
+ * Allocate and initialize private data. Register to Bluetooth stack.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation fails.
+ * Error codes from register_bluetooth.
+ */
+static int __devinit btcg2900_probe(struct platform_device *pdev)
+{
+ int err;
+
+ BT_INFO("btcg2900_probe");
+
+ /* Initialize private data. */
+ btcg2900_info = kzalloc(sizeof(*btcg2900_info), GFP_KERNEL);
+ if (!btcg2900_info) {
+ BT_ERR("Could not alloc btcg2900_info struct.");
+ return -ENOMEM;
+ }
+
+ btcg2900_info->pdev = pdev;
+
+ /* Init and register hdev */
+ err = register_bluetooth();
+ if (err) {
+ BT_ERR("HCI Device registration error (%d)", err);
+ kfree(btcg2900_info);
+ btcg2900_info = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * btcg2900_remove() - Remove module.
+ */
+static int __devexit btcg2900_remove(struct platform_device *pdev)
+{
+ int err = 0;
+
+ BT_INFO("btcg2900_remove");
+
+ if (!btcg2900_info)
+ return 0;
+
+ if (!btcg2900_info->hdev)
+ goto finished;
+
+ err = hci_unregister_dev(btcg2900_info->hdev);
+ if (err)
+ BT_ERR("Can not unregister HCI device (%d)", err);
+ hci_free_dev(btcg2900_info->hdev);
+
+finished:
+ kfree(btcg2900_info);
+ btcg2900_info = NULL;
+ return err;
+}
+
+static struct platform_driver btcg2900_driver = {
+ .driver = {
+ .name = "cg2900-bt",
+ .owner = THIS_MODULE,
+ },
+ .probe = btcg2900_probe,
+ .remove = __devexit_p(btcg2900_remove),
+};
+
+/**
+ * btcg2900_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init btcg2900_init(void)
+{
+ BT_INFO("btcg2900_init");
+ return platform_driver_register(&btcg2900_driver);
+}
+
+/**
+ * btcg2900_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit btcg2900_exit(void)
+{
+ BT_INFO("btcg2900_exit");
+ platform_driver_unregister(&btcg2900_driver);
+}
+
+module_init(btcg2900_init);
+module_exit(btcg2900_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_AUTHOR("Henrik Possung ST-Ericsson");
+MODULE_AUTHOR("Josef Kindberg ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth HCI H:4 Driver for ST-Ericsson
controller");
--
1.6.3.3
^ permalink raw reply related
* [PATCH 7/9] mach-ux500: Add support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:39 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds board specific support for the ST-Ericsson CG2900
Connectivity Combo controller.
It contains device structures as well as necessary callback
functions.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
arch/arm/mach-ux500/Makefile | 1 +
arch/arm/mach-ux500/board-mop500.c | 3 +
arch/arm/mach-ux500/devices-cg2900.c | 261 ++++++++++++++++++++++++++++
arch/arm/mach-ux500/include/mach/devices.h | 4 +
4 files changed, 269 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-ux500/devices-cg2900.c
diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile
index 9e27a84..7754aff 100644
--- a/arch/arm/mach-ux500/Makefile
+++ b/arch/arm/mach-ux500/Makefile
@@ -13,3 +13,4 @@ obj-$(CONFIG_LOCAL_TIMERS) += localtimer.o
obj-$(CONFIG_REGULATOR_AB8500) += board-mop500-regulators.o
obj-$(CONFIG_U5500_MODEM_IRQ) += modem_irq.o
obj-$(CONFIG_U5500_MBOX) += mbox.o
+obj-$(CONFIG_MFD_CG2900) += devices-cg2900.o
diff --git a/arch/arm/mach-ux500/board-mop500.c
b/arch/arm/mach-ux500/board-mop500.c
index 09fba17..589921b 100644
--- a/arch/arm/mach-ux500/board-mop500.c
+++ b/arch/arm/mach-ux500/board-mop500.c
@@ -303,6 +303,9 @@ static struct platform_device *platform_devs[]
__initdata = {
&ux500_i2c2_device,
&ux500_i2c3_device,
&ux500_ske_keypad_device,
+#ifdef CONFIG_MFD_CG2900
+ &ux500_cg2900_device,
+#endif
};
static void __init u8500_init_machine(void)
diff --git a/arch/arm/mach-ux500/devices-cg2900.c
b/arch/arm/mach-ux500/devices-cg2900.c
new file mode 100644
index 0000000..b5c60d5
--- /dev/null
+++ b/arch/arm/mach-ux500/devices-cg2900.c
@@ -0,0 +1,261 @@
+/*
+ * arch/arm/mach-ux500/devices-cg2900.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 <asm/byteorder.h>
+#include <asm-generic/errno-base.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+#include <plat/pincfg.h>
+
+#include "pins-db8500.h"
+
+#ifdef CONFIG_MFD_CG2900
+
+#define BT_ENABLE_GPIO 170
+#define GBF_ENA_RESET_GPIO 171
+#define BT_CTS_GPIO 0
+
+#define GBF_ENA_RESET_NAME "gbf_ena_reset"
+#define BT_ENABLE_NAME "bt_enable"
+#define CG2900_NAME "cg2900_devices"
+
+#define UART_LINES_NUM 4
+
+#define BT_VS_POWER_SWITCH_OFF 0xFD40
+
+#define H4_HEADER_LENGTH 0x01
+#define BT_HEADER_LENGTH 0x03
+
+#define STLC2690_HCI_REV 0x0600
+#define CG2900_PG1_HCI_REV 0x0101
+#define CG2900_PG2_HCI_REV 0x0200
+#define CG2900_PG1_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));
+
+static u16 cg2900_hci_revision;
+
+/* Pin configuration for UART functions. */
+static pin_cfg_t uart0_enabled[] = {
+ GPIO0_U0_CTSn | (PIN_DIR_INPUT | PIN_PULL_UP),
+ GPIO1_U0_RTSn | PIN_OUTPUT_HIGH,
+ GPIO2_U0_RXD | (PIN_DIR_INPUT | PIN_PULL_UP),
+ GPIO3_U0_TXD | PIN_OUTPUT_HIGH
+};
+
+/* Pin configuration for sleep mode. */
+static pin_cfg_t uart0_disabled[] = {
+ GPIO0_GPIO | (PIN_DIR_INPUT | PIN_PULL_UP), /* CTS pull up. */
+ GPIO1_GPIO | PIN_OUTPUT_HIGH, /* RTS high - flow off. */
+ GPIO2_GPIO | (PIN_DIR_INPUT | PIN_PULL_UP), /* RX pull down. */
+ GPIO3_GPIO | PIN_OUTPUT_LOW /* TX low - break on. */
+};
+
+static void cg2900_enable_chip(void)
+{
+ gpio_set_value(GBF_ENA_RESET_GPIO, 1);
+}
+
+static void cg2900_disable_chip(void)
+{
+ gpio_set_value(GBF_ENA_RESET_GPIO, 0);
+}
+
+static void cg2900_set_hci_revision(u8 hci_version, u16 hci_revision,
+ u8 lmp_version, u8 lmp_subversion,
+ u16 manufacturer)
+{
+ cg2900_hci_revision = hci_revision;
+ /* We don't care about the other values */
+}
+
+static struct sk_buff *cg2900_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_PG1_SPECIAL_HCI_REV != cg2900_hci_revision &&
+ CG2900_PG1_HCI_REV != cg2900_hci_revision &&
+ CG2900_PG2_HCI_REV != cg2900_hci_revision)
+ return NULL;
+
+ skb = alloc_skb(sizeof(*cmd) + H4_HEADER_LENGTH, GFP_KERNEL);
+ if (!skb) {
+ pr_err(CG2900_NAME "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;
+}
+static int cg2900_init(void)
+{
+ int err = 0;
+
+ err = gpio_request(GBF_ENA_RESET_GPIO, GBF_ENA_RESET_NAME);
+ if (err < 0) {
+ pr_err(CG2900_NAME "gpio_request failed with err: %d", err);
+ goto finished;
+ }
+
+ err = gpio_direction_output(GBF_ENA_RESET_GPIO, 1);
+ if (err < 0) {
+ pr_err(CG2900_NAME "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(CG2900_NAME "gpio_request failed with err: %d", err);
+ goto finished;
+ }
+
+ err = gpio_direction_output(BT_ENABLE_GPIO, 1);
+ if (err < 0) {
+ pr_err(CG2900_NAME "gpio_direction_output failed with err: %d",
+ err);
+ goto error_handling;
+ }
+
+ goto finished;
+
+error_handling:
+ gpio_free(GBF_ENA_RESET_GPIO);
+
+finished:
+ cg2900_disable_chip();
+ return err;
+}
+
+void cg2900_exit(void)
+{
+ cg2900_disable_chip();
+ gpio_free(GBF_ENA_RESET_GPIO);
+}
+
+#ifdef CONFIG_MFD_CG2900_UART
+
+static int cg2900_disable_uart(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;
+
+ return 0;
+
+error:
+ (void)nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+ pr_err(CG2900_NAME "Cannot set interrupt (%d)", err);
+ return err;
+}
+
+
+static int cg2900_enable_uart(void)
+{
+ int err;
+
+ /* Restore UART settings. */
+ err = nmk_config_pins(uart0_enabled, UART_LINES_NUM);
+ if (err)
+ pr_err(CG2900_NAME "Unable to enable UART (%d)", err);
+
+ return err;
+}
+
+#endif /* CONFIG_MFD_CG2900_UART */
+
+struct cg2900_platform_data cg2900_platform_data = {
+ .init = cg2900_init,
+ .exit = cg2900_exit,
+ .enable_chip = cg2900_enable_chip,
+ .disable_chip = cg2900_disable_chip,
+ .set_hci_revision = cg2900_set_hci_revision,
+ .get_power_switch_off_cmd = cg2900_get_power_switch_off_cmd,
+
+ .bus = HCI_UART,
+
+#ifdef CONFIG_MFD_CG2900_UART
+ .uart = {
+ .cts_irq = NOMADIK_GPIO_TO_IRQ(BT_CTS_GPIO),
+ .enable_uart = cg2900_enable_uart,
+ .disable_uart = cg2900_disable_uart
+ },
+#endif /* CONFIG_MFD_CG2900_UART */
+};
+
+struct platform_device ux500_cg2900_device = {
+ .name = "cg2900",
+ .dev = {
+ .platform_data = &cg2900_platform_data,
+ }
+};
+#endif /* CONFIG_MFD_CG2900 */
diff --git a/arch/arm/mach-ux500/include/mach/devices.h
b/arch/arm/mach-ux500/include/mach/devices.h
index b91a4d1..cbc7897 100644
--- a/arch/arm/mach-ux500/include/mach/devices.h
+++ b/arch/arm/mach-ux500/include/mach/devices.h
@@ -28,6 +28,10 @@ extern struct platform_device u8500_i2c4_device;
extern struct platform_device u8500_dma40_device;
extern struct platform_device ux500_ske_keypad_device;
+#ifdef CONFIG_MFD_CG2900
+extern struct platform_device ux500_cg2900_device;
+#endif
+
extern struct amba_device u8500_sdi0_device;
extern struct amba_device u8500_sdi1_device;
extern struct amba_device u8500_sdi2_device;
--
1.6.3.3
^ permalink raw reply related
* [PATCH 6/9] mfd: Add support for the ST-Ericsson CG2900 Audio.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:39 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds support for controlling audio paths within
the ST-Ericsson CG2900 Connectivity Combo controller.
This patch adds API for the Kernel as well as a char device for
user space stacks to control the CG2900 audio paths.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/cg2900/Makefile | 2 +
drivers/mfd/cg2900/cg2900_audio.c | 3026 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/cg2900_audio.h | 460 ++++++
4 files changed, 3496 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/cg2900_audio.c
create mode 100644 include/linux/mfd/cg2900_audio.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 6fcd8b6..6f92b04 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -288,6 +288,14 @@ config MFD_CG2900_UART
help
Support for UART as transport for ST-Ericsson CG2900 Connectivity
Controller
+ Controller
+
+config MFD_CG2900_AUDIO
+ tristate "Support CG2900 audio interface"
+ depends on MFD_CG2900
+ help
+ Support for ST-Ericsson CG2900 Connectivity audio interface. Gives a
+ module the ability to setup audio paths within the CG2900 controller.
config PMIC_DA903X
bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index c8dd713..94095c0 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -12,3 +12,5 @@ obj-$(CONFIG_MFD_STLC2690_CHIP) += stlc2690_chip.o
obj-$(CONFIG_MFD_CG2900_UART) += cg2900_uart.o
+obj-$(CONFIG_MFD_CG2900_AUDIO) += cg2900_audio.o
+
diff --git a/drivers/mfd/cg2900/cg2900_audio.c
b/drivers/mfd/cg2900/cg2900_audio.c
new file mode 100644
index 0000000..b9ab504
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_audio.c
@@ -0,0 +1,3026 @@
+/*
+ * drivers/mfd/cg2900/cg2900_audio.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@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 Audio Driver for ST-Ericsson CG2900 controller.
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/cg2900_audio.h>
+
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+#include "cg2900_chip.h"
+
+/* Char device op codes */
+#define OP_CODE_SET_DAI_CONF 0x01
+#define OP_CODE_GET_DAI_CONF 0x02
+#define OP_CODE_CONFIGURE_ENDPOINT 0x03
+#define OP_CODE_START_STREAM 0x04
+#define OP_CODE_STOP_STREAM 0x05
+
+/* Device names */
+#define DEVICE_NAME "cg2900_audio"
+
+/* Type of channel used */
+#define BT_CHANNEL_USED 0x00
+#define FM_CHANNEL_USED 0x01
+
+#define MAX_NBR_OF_USERS 10
+#define FIRST_USER 1
+
+#define SET_RESP_STATE(__state_var, __new_state) \
+ CG2900_SET_STATE("resp_state", __state_var, __new_state)
+
+#define DEFAULT_SCO_HANDLE 0x0008
+
+/* Use a timeout of 5 seconds when waiting for a command response */
+#define RESP_TIMEOUT 5000
+
+/* Used to select proper API, ignoring subrevisions etc */
+enum chip_revision {
+ CHIP_REV_PG1,
+ CHIP_REV_PG2
+};
+
+/**
+ * enum chip_resp_state - State when communicating with the CG2900 controller.
+ * @IDLE: No outstanding packets to the controller.
+ * @WAITING: Packet has been sent to the controller. Waiting for
+ * response.
+ * @RESP_RECEIVED: Response from controller has been received but not yet
+ * handled.
+ */
+enum chip_resp_state {
+ IDLE,
+ WAITING,
+ RESP_RECEIVED
+};
+
+/**
+ * enum main_state - Main state for the CG2900 Audio driver.
+ * @OPENED: Audio driver has registered to CG2900 Core.
+ * @CLOSED: Audio driver is not registered to CG2900 Core.
+ * @RESET: A reset of CG2900 Core has occurred and no user has re-opened
+ * the audio driver.
+ */
+enum main_state {
+ OPENED,
+ CLOSED,
+ RESET
+};
+
+/**
+ * struct char_dev_info - CG2900 character device info structure.
+ * @session: Stored session for the char device.
+ * @stored_data: Data returned when executing last command, if any.
+ * @stored_data_len: Length of @stored_data in bytes.
+ * @management_mutex: Mutex for handling access to char dev management.
+ * @rw_mutex: Mutex for handling access to char dev writes and reads.
+ */
+struct char_dev_info {
+ int session;
+ u8 *stored_data;
+ int stored_data_len;
+ struct mutex management_mutex;
+ struct mutex rw_mutex;
+};
+
+/**
+ * struct audio_user - CG2900 audio user info structure.
+ * @session: Stored session for the char device.
+ * @resp_state: State for controller communications.
+ */
+struct audio_user {
+ int session;
+ enum chip_resp_state resp_state;
+};
+
+/**
+ * struct endpoint_list - List for storing endpoint configuration nodes.
+ * @ep_list: Pointer to first node in list.
+ * @management_mutex: Mutex for handling access to list.
+ */
+struct endpoint_list {
+ struct list_head ep_list;
+ struct mutex management_mutex;
+};
+
+/**
+ * struct endpoint_config_node - Node for storing endpoint configuration.
+ * @list: list_head struct.
+ * @endpoint_id: Endpoint ID.
+ * @config: Stored configuration for this endpoint.
+ */
+struct endpoint_config_node {
+ struct list_head list;
+ enum cg2900_audio_endpoint_id endpoint_id;
+ union cg2900_endpoint_config_union config;
+};
+
+/**
+ * struct audio_info - Main CG2900 Audio driver info structure.
+ * @state: Current state of the CG2900 Audio driver.
+ * @revision: Chip revision, used to select API.
+ * @dev: The misc device created by this driver.
+ * @dev_bt: CG2900 Core device registered by this driver for
+ * the BT audio channel.
+ * @dev_fm: CG2900 Core device registered by this driver for
+ * the FM audio channel.
+ * @management_mutex: Mutex for handling access to CG2900 Audio driver
+ * management.
+ * @bt_mutex: Mutex for handling access to BT audio channel.
+ * @fm_mutex: Mutex for handling access to FM audio channel.
+ * @nbr_of_users_active: Number of sessions open in the CG2900 Audio
+ * driver.
+ * @bt_queue: Received BT events.
+ * @fm_queue: Received FM events.
+ * @audio_sessions: Pointers to currently opened sessions (maps
+ * session ID to user info).
+ * @i2s_config: DAI I2S configuration.
+ * @i2s_pcm_config: DAI PCM_I2S configuration.
+ * @i2s_config_known: @true if @i2s_config has been set,
+ * @false otherwise.
+ * @i2s_pcm_config_known: @true if @i2s_pcm_config has been set,
+ * @false otherwise.
+ * @endpoints: List containing the endpoint configurations.
+ * @stream_ids: Bitmask for in-use stream ids (only used with
+ * PG2 chip API).
+ */
+struct audio_info {
+ enum main_state state;
+ enum chip_revision revision;
+ struct miscdevice dev;
+ struct cg2900_device *dev_bt;
+ struct cg2900_device *dev_fm;
+ struct mutex management_mutex;
+ struct mutex bt_mutex;
+ struct mutex fm_mutex;
+ int nbr_of_users_active;
+ struct sk_buff_head bt_queue;
+ struct sk_buff_head fm_queue;
+ struct audio_user *audio_sessions[MAX_NBR_OF_USERS];
+ struct cg2900_dai_conf_i2s i2s_config;
+ struct cg2900_dai_conf_i2s_pcm i2s_pcm_config;
+ bool i2s_config_known;
+ bool i2s_pcm_config_known;
+ struct endpoint_list endpoints;
+ u32 stream_ids;
+};
+
+/**
+ * struct audio_cb_info - Callback info structure registered in @user_data.
+ * @channel: Stores if this device handles BT or FM events.
+ * @user: Audio user currently awaiting data on the channel.
+ */
+struct audio_cb_info {
+ int channel;
+ struct audio_user *user;
+};
+
+/* cg2900_audio wait queues */
+static DECLARE_WAIT_QUEUE_HEAD(wq_bt);
+static DECLARE_WAIT_QUEUE_HEAD(wq_fm);
+
+static struct audio_info *audio_info;
+
+static struct audio_cb_info cb_info_bt = {
+ .channel = BT_CHANNEL_USED,
+ .user = NULL
+};
+static struct audio_cb_info cb_info_fm = {
+ .channel = FM_CHANNEL_USED,
+ .user = NULL
+};
+
+/*
+ * Internal conversion functions
+ *
+ * Since the CG2900 apis uses several different ways to encode the
+ * same parameter in different cases, we have to use translator
+ * functions.
+ */
+
+/**
+ * session_config_sample_rate() - Convert sample rate to format used
in VS_Set_SessionConfiguration.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 session_config_sample_rate(enum cg2900_endpoint_sample_rate rate)
+{
+ static const u8 codes[] = {
+ [ENDPOINT_SAMPLE_RATE_8_KHZ] = CG2900_BT_SESSION_RATE_8K,
+ [ENDPOINT_SAMPLE_RATE_16_KHZ] = CG2900_BT_SESSION_RATE_16K,
+ [ENDPOINT_SAMPLE_RATE_44_1_KHZ] = CG2900_BT_SESSION_RATE_44_1K,
+ [ENDPOINT_SAMPLE_RATE_48_KHZ] = CG2900_BT_SESSION_RATE_48K
+ };
+
+ return codes[rate];
+}
+
+/**
+ * mc_i2s_sample_rate() - Convert sample rate to format used in
VS_Port_Config for I2S.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 mc_i2s_sample_rate(enum cg2900_dai_sample_rate rate)
+{
+ static const u8 codes[] = {
+ [SAMPLE_RATE_8] = CG2900_MC_I2S_SAMPLE_RATE_8,
+ [SAMPLE_RATE_16] = CG2900_MC_I2S_SAMPLE_RATE_16,
+ [SAMPLE_RATE_44_1] = CG2900_MC_I2S_SAMPLE_RATE_44_1,
+ [SAMPLE_RATE_48] = CG2900_MC_I2S_SAMPLE_RATE_48
+ };
+
+ return codes[rate];
+}
+
+/**
+ * mc_pcm_sample_rate() - Convert sample rate to format used in
VS_Port_Config for PCM/I2S.
+ * @rate: Sample rate in API encoding.
+ */
+static u8 mc_pcm_sample_rate(enum cg2900_dai_sample_rate rate)
+{
+ static const u8 codes[] = {
+ [SAMPLE_RATE_8] = CG2900_MC_PCM_SAMPLE_RATE_8,
+ [SAMPLE_RATE_16] = CG2900_MC_PCM_SAMPLE_RATE_16,
+ [SAMPLE_RATE_44_1] = CG2900_MC_PCM_SAMPLE_RATE_44_1,
+ [SAMPLE_RATE_48] = CG2900_MC_PCM_SAMPLE_RATE_48
+ };
+
+ return codes[rate];
+}
+
+/**
+ * mc_i2s_channel_select() - Convert channel selection to format used
in VS_Port_Config.
+ * @sel: Channel selection in API encoding.
+ */
+static u8 mc_i2s_channel_select(enum cg2900_dai_channel_sel sel)
+{
+ static const u8 codes[] = {
+ [CHANNEL_SELECTION_RIGHT] = CG2900_MC_I2S_RIGHT_CHANNEL,
+ [CHANNEL_SELECTION_LEFT] = CG2900_MC_I2S_LEFT_CHANNEL,
+ [CHANNEL_SELECTION_BOTH] = CG2900_MC_I2S_BOTH_CHANNELS
+ };
+ return codes[sel];
+}
+
+/**
+ * get_fs_duration() - Convert framesync-enumeration to real value.
+ * @duration: Framsync duration (API encoding).
+ *
+ * Returns:
+ * Duration in bits.
+ */
+static u16 get_fs_duration(enum cg2900_dai_fs_duration duration)
+{
+ static const u16 values[] = {
+ [SYNC_DURATION_8] = 8,
+ [SYNC_DURATION_16] = 16,
+ [SYNC_DURATION_24] = 24,
+ [SYNC_DURATION_32] = 32,
+ [SYNC_DURATION_48] = 48,
+ [SYNC_DURATION_50] = 50,
+ [SYNC_DURATION_64] = 64,
+ [SYNC_DURATION_75] = 75,
+ [SYNC_DURATION_96] = 96,
+ [SYNC_DURATION_125] = 125,
+ [SYNC_DURATION_128] = 128,
+ [SYNC_DURATION_150] = 150,
+ [SYNC_DURATION_192] = 192,
+ [SYNC_DURATION_250] = 250,
+ [SYNC_DURATION_256] = 256,
+ [SYNC_DURATION_300] = 300,
+ [SYNC_DURATION_384] = 384,
+ [SYNC_DURATION_500] = 500,
+ [SYNC_DURATION_512] = 512,
+ [SYNC_DURATION_600] = 600,
+ [SYNC_DURATION_768] = 768
+ };
+ return values[duration];
+}
+
+/**
+ * mc_i2s_role() - Convert master/slave encoding to format for I2S-ports.
+ * @mode: Master/slave in API encoding.
+ */
+static u8 mc_i2s_role(enum cg2900_dai_mode mode)
+{
+ if (mode == DAI_MODE_SLAVE)
+ return CG2900_I2S_MODE_SLAVE;
+ else
+ return CG2900_I2S_MODE_MASTER;
+}
+
+/**
+ * mc_pcm_role() - Convert master/slave encoding to format for PCM/I2S-port.
+ * @mode: Master/slave in API encoding.
+ */
+static u8 mc_pcm_role(enum cg2900_dai_mode mode)
+{
+ if (mode == DAI_MODE_SLAVE)
+ return CG2900_PCM_MODE_SLAVE;
+ else
+ return CG2900_PCM_MODE_MASTER;
+}
+
+/**
+ * fm_get_conversion() - Convert sample rate to convert up/down used
in X_Set_Control FM commands.
+ * @srate: Sample rate.
+ */
+static u16 fm_get_conversion(enum cg2900_endpoint_sample_rate srate)
+{
+ if (srate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ)
+ return CG2900_FM_CMD_SET_CTRL_CONV_UP;
+ else
+ return CG2900_FM_CMD_SET_CTRL_CONV_DOWN;
+}
+
+/*
+ * Internal helper functions
+ */
+
+/**
+ * read_cb() - Handle data received from STE connectivity driver.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming form device.
+ */
+static void read_cb(struct cg2900_device *dev, struct sk_buff *skb)
+{
+ struct audio_cb_info *cb_info;
+
+ CG2900_INFO("CG2900 Audio: read_cb");
+
+ if (!dev) {
+ CG2900_ERR("NULL supplied as dev");
+ return;
+ }
+
+ if (!skb) {
+ CG2900_ERR("NULL supplied as skb");
+ return;
+ }
+
+ cb_info = (struct audio_cb_info *)dev->user_data;
+ if (!cb_info) {
+ CG2900_ERR("NULL supplied as cb_info");
+ return;
+ }
+ if (!(cb_info->user)) {
+ CG2900_ERR("NULL supplied as cb_info->user");
+ return;
+ }
+
+ /* Mark that packet has been received */
+ SET_RESP_STATE(cb_info->user->resp_state, RESP_RECEIVED);
+
+ /* Handle packet depending on channel */
+ if (cb_info->channel == BT_CHANNEL_USED) {
+ skb_queue_tail(&(audio_info->bt_queue), skb);
+ wake_up_interruptible(&wq_bt);
+ } else if (cb_info->channel == FM_CHANNEL_USED) {
+ skb_queue_tail(&(audio_info->fm_queue), skb);
+ wake_up_interruptible(&wq_fm);
+ } else {
+ /* Unhandled channel; free the packet */
+ CG2900_ERR("Received callback on bad channel %d",
+ cb_info->channel);
+ kfree_skb(skb);
+ }
+}
+
+/**
+ * reset_cb() - Reset callback function.
+ * @dev: CG2900_Core device resetting.
+ */
+static void reset_cb(struct cg2900_device *dev)
+{
+ CG2900_INFO("CG2900 Audio: reset_cb");
+ mutex_lock(&audio_info->management_mutex);
+ audio_info->nbr_of_users_active = 0;
+ audio_info->state = RESET;
+ mutex_unlock(&audio_info->management_mutex);
+}
+
+static struct cg2900_callbacks cg2900_cb = {
+ .read_cb = read_cb,
+ .reset_cb = reset_cb
+};
+
+/**
+ * get_session_user() - Check that supplied session is within valid range.
+ * @session: Session ID.
+ *
+ * Returns:
+ * Audio_user if there is no error.
+ * NULL for bad session ID.
+ */
+static struct audio_user *get_session_user(int session)
+{
+ struct audio_user *audio_user;
+
+ if (session < FIRST_USER || session >= MAX_NBR_OF_USERS) {
+ CG2900_ERR("Calling with invalid session %d", session);
+ return NULL;
+ }
+
+ audio_user = audio_info->audio_sessions[session];
+ if (!audio_user)
+ CG2900_ERR("Calling with non-opened session %d", session);
+ return audio_user;
+}
+
+/**
+ * del_endpoint_private() - Deletes an endpoint from @list.
+ * @endpoint_id: Endpoint ID.
+ * @list: List of endpoints.
+ *
+ * Deletes an endpoint from the supplied endpoint list.
+ * This function is not protected by any semaphore.
+ */
+static void del_endpoint_private(enum cg2900_audio_endpoint_id endpoint_id,
+ struct endpoint_list *list)
+{
+ struct list_head *cursor, *next;
+ struct endpoint_config_node *tmp;
+
+ list_for_each_safe(cursor, next, &(list->ep_list)) {
+ tmp = list_entry(cursor, struct endpoint_config_node, list);
+ if (tmp->endpoint_id == endpoint_id) {
+ list_del(cursor);
+ kfree(tmp);
+ }
+ }
+}
+
+/**
+ * add_endpoint() - Add endpoint node to @list.
+ * @ep_config: Endpoint configuration.
+ * @list: List of endpoints.
+ *
+ * Add endpoint node to the supplied list and copies supplied config to node.
+ * If a node already exists for the supplied endpoint, the old node is removed
+ * and replaced by the new node.
+ */
+static void add_endpoint(struct cg2900_endpoint_config *ep_config,
+ struct endpoint_list *list)
+{
+ struct endpoint_config_node *item;
+
+ item = kzalloc(sizeof(*item), GFP_KERNEL);
+ if (!item) {
+ CG2900_ERR("Failed to alloc memory!");
+ return;
+ }
+
+ /* Store values */
+ item->endpoint_id = ep_config->endpoint_id;
+ memcpy(&(item->config), &(ep_config->config), sizeof(item->config));
+
+ mutex_lock(&(list->management_mutex));
+
+ /*
+ * Check if endpoint ID already exist in list.
+ * If that is the case, remove it.
+ */
+ if (!list_empty(&(list->ep_list)))
+ del_endpoint_private(ep_config->endpoint_id, list);
+
+ list_add_tail(&(item->list), &(list->ep_list));
+
+ mutex_unlock(&(list->management_mutex));
+}
+
+/**
+ * find_endpoint() - Finds endpoint identified by @endpoint_id in @list.
+ * @endpoint_id: Endpoint ID.
+ * @list: List of endpoints.
+ *
+ * Returns:
+ * Endpoint configuration if there is no error.
+ * NULL if no configuration can be found for @endpoint_id.
+ */
+static union cg2900_endpoint_config_union *
+find_endpoint(enum cg2900_audio_endpoint_id endpoint_id,
+ struct endpoint_list *list)
+{
+ struct list_head *cursor, *next;
+ struct endpoint_config_node *tmp;
+ struct endpoint_config_node *ret_ep = NULL;
+
+ mutex_lock(&list->management_mutex);
+ list_for_each_safe(cursor, next, &(list->ep_list)) {
+ tmp = list_entry(cursor, struct endpoint_config_node, list);
+ if (tmp->endpoint_id == endpoint_id) {
+ ret_ep = tmp;
+ break;
+ }
+ }
+ mutex_unlock(&list->management_mutex);
+
+ if (ret_ep)
+ return &(ret_ep->config);
+ else
+ return NULL;
+}
+
+/**
+ * flush_endpoint_list() - Deletes all stored endpoints in @list.
+ * @list: List of endpoints.
+ */
+static void flush_endpoint_list(struct endpoint_list *list)
+{
+ struct list_head *cursor, *next;
+ struct endpoint_config_node *tmp;
+
+ mutex_lock(&list->management_mutex);
+ list_for_each_safe(cursor, next, &(list->ep_list)) {
+ tmp = list_entry(cursor, struct endpoint_config_node, list);
+ list_del(cursor);
+ kfree(tmp);
+ }
+ mutex_unlock(&list->management_mutex);
+}
+
+/**
+ * new_stream_id() - Allocate a new stream id.
+ *
+ * Returns:
+ * 0-127 new valid id.
+ * -ENOMEM if no id is available.
+ */
+static s8 new_stream_id(void)
+{
+ int r;
+
+ mutex_lock(&audio_info->management_mutex);
+
+ r = find_first_zero_bit(&audio_info->stream_ids,
+ 8 * sizeof(audio_info->stream_ids));
+
+ if (r >= 8 * sizeof(audio_info->stream_ids)) {
+ r = -ENOMEM;
+ goto out;
+ }
+
+ audio_info->stream_ids |= (0x01u << r);
+
+out:
+ mutex_unlock(&audio_info->management_mutex);
+ return r;
+}
+
+/**
+ * release_stream_id() - Release a stream id.
+ * @id: stream to release.
+ */
+static void release_stream_id(u8 id)
+{
+ if (id >= 8 * sizeof(audio_info->stream_ids))
+ return;
+
+ mutex_lock(&audio_info->management_mutex);
+ audio_info->stream_ids &= ~(0x01u << id);
+ mutex_unlock(&audio_info->management_mutex);
+}
+
+/**
+ * receive_fm_write_response() - Wait for and handle the response to
an FM Legacy WriteCommand request.
+ * @audio_user: Audio user to check for.
+ * @command: FM command to wait for.
+ *
+ * This function first waits (up to 5 seconds) for a response to an FM
+ * write command and when one arrives, it checks that it is the one we
+ * are waiting for and also that no error has occurred.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int receive_fm_write_response(struct audio_user *audio_user,
+ u16 command)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct fm_leg_cmd_cmpl *pkt;
+ u16 rsp_cmd;
+
+ /*
+ * Wait for callback to receive command complete and then wake us up
+ * again.
+ */
+ if (0 >= wait_event_interruptible_timeout(wq_fm,
+ audio_user->resp_state == RESP_RECEIVED,
+ msecs_to_jiffies(RESP_TIMEOUT))) {
+ /* We timed out or an error occurred */
+ CG2900_ERR("Error occurred while waiting for return packet.");
+ return -ECOMM;
+ }
+
+ /* OK, now we should have received answer. Let's check it. */
+ skb = skb_dequeue_tail(&audio_info->fm_queue);
+ if (!skb) {
+ CG2900_ERR("No skb in queue when it should be there");
+ return -EIO;
+ }
+
+ pkt = (struct fm_leg_cmd_cmpl *)skb->data;
+
+ /* Check if we received the correct event */
+ if (pkt->opcode != CG2900_FM_GEN_ID_LEGACY) {
+ CG2900_ERR("Received unknown FM packet. 0x%X %X %X %X %X",
+ skb->data[0], skb->data[1], skb->data[2],
+ skb->data[3], skb->data[4]);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ /* FM Legacy Command complete event */
+ rsp_cmd = cg2900_get_fm_cmd_id(le16_to_cpu(pkt->response_head));
+
+ if (pkt->fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND ||
+ rsp_cmd != command) {
+ CG2900_ERR("Received unexpected packet func 0x%X cmd 0x%04X",
+ pkt->fm_function, rsp_cmd);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ if (pkt->cmd_status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) {
+ CG2900_ERR("FM Command failed (%d)", pkt->cmd_status);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+ /* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+ kfree_skb(skb);
+ return err;
+}
+
+/**
+ * receive_bt_cmd_complete() - Wait for and handle an BT Command
Complete event.
+ * @audio_user: Audio user to check for.
+ * @rsp: Opcode of BT command to wait for.
+ * @data: Pointer to buffer if any received data should be stored (except
+ * status).
+ * @data_len: Length of @data in bytes.
+ *
+ * This function first waits for BT Command Complete event (up to 5 seconds)
+ * and when one arrives, it checks that it is the one we are waiting for and
+ * also that no error has occurred.
+ * If @data is supplied it also copies received data into @data.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int receive_bt_cmd_complete(struct audio_user *audio_user, u16 rsp,
+ void *data, int data_len)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct bt_cmd_cmpl_event *evt;
+ u16 opcode;
+
+ /*
+ * Wait for callback to receive command complete and then wake us up
+ * again.
+ */
+ if (0 >= wait_event_interruptible_timeout(wq_bt,
+ audio_user->resp_state == RESP_RECEIVED,
+ msecs_to_jiffies(RESP_TIMEOUT))) {
+ /* We timed out or an error occurred */
+ CG2900_ERR("Error occurred while waiting for return packet.");
+ return -ECOMM;
+ }
+
+ /* OK, now we should have received answer. Let's check it. */
+ skb = skb_dequeue_tail(&audio_info->bt_queue);
+ if (!skb) {
+ CG2900_ERR("No skb in queue when it should be there");
+ return -EIO;
+ }
+
+ evt = (struct bt_cmd_cmpl_event *)skb->data;
+ if (evt->eventcode != HCI_BT_EVT_CMD_COMPLETE) {
+ CG2900_ERR("We did not receive the event we expected (0x%X)",
+ evt->eventcode);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ opcode = le16_to_cpu(evt->opcode);
+ if (opcode != rsp) {
+ CG2900_ERR("Received cmd complete for unexpected command: "
+ "0x%04X", opcode);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ if (evt->status != HCI_BT_ERROR_NO_ERROR) {
+ CG2900_ERR("Received command complete with err %d",
+ evt->status);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ /* Copy the rest of the parameters if a buffer has been supplied.
+ * The caller must have set the length correctly.
+ */
+ if (data)
+ memcpy(data, evt->data, data_len);
+
+ /* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+ kfree_skb(skb);
+ return err;
+}
+
+/**
+ * send_vs_session_ctrl() - Formats an sends a
CG2900_BT_VS_SESSION_CTRL command.
+ * @user: Audio user this command belongs to.
+ * @stream_handle: Handle to stream.
+ * @command: Command to execute on stream, should be one of
+ * CG2900_BT_SESSION_START, CG2900_BT_SESSION_STOP,
+ * CG2900_BT_SESSION_PAUSE, CG2900_BT_SESSION_RESUME.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_vs_session_ctrl(struct audio_user *user,
+ u8 stream_handle, u8 command)
+{
+ int err = 0;
+ struct bt_vs_session_ctrl_cmd *pkt;
+ struct sk_buff *skb;
+
+ CG2900_INFO("BT: HCI_VS_Session_Control");
+
+ skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ return -ENOMEM;
+ }
+
+ /* Enter data into the skb */
+ pkt = (struct bt_vs_session_ctrl_cmd *) skb_put(skb, sizeof(*pkt));
+
+ pkt->opcode = cpu_to_le16(CG2900_BT_VS_SESSION_CTRL);
+ pkt->plen = BT_PARAM_LEN(sizeof(*pkt));
+ pkt->id = stream_handle;
+ pkt->control = command; /* Start/stop etc */
+
+ cb_info_bt.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished;
+ }
+
+ err = receive_bt_cmd_complete(user, CG2900_BT_VS_SESSION_CTRL,
+ NULL, 0);
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * send_vs_session_config() - Formats an sends a
CG2900_BT_VS_SESSION_CONFIG command.
+ * @user: Audio user this command belongs to.
+ * @config_stream: Custom function for configuring the stream.
+ * @priv_data: Private data passed to @config_stream untouched.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Space is allocated for one stream and a custom function is used to
+ * fill in the stream configuration.
+ *
+ * Returns:
+ * 0-255 stream handle if no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_vs_session_config(struct audio_user *user,
+ void(*config_stream)(void *, struct session_config_stream *),
+ void *priv_data)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct bt_vs_session_config_cmd *pkt;
+ u8 session_id;
+
+ CG2900_INFO("BT: HCI_VS_Set_Session_Configuration");
+
+ skb = cg2900_alloc_skb(sizeof(*pkt), GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ return -ENOMEM;
+ }
+
+ pkt = (struct bt_vs_session_config_cmd *)skb_put(skb, sizeof(*pkt));
+ /* zero the packet so we don't have to set all reserved fields */
+ memset(pkt, 0, sizeof(*pkt));
+
+ /* Common parameters */
+ pkt->opcode = cpu_to_le16(CG2900_BT_VS_SET_SESSION_CONFIG);
+ pkt->plen = BT_PARAM_LEN(sizeof(*pkt));
+ pkt->n_streams = 1; /* 1 stream configuration supplied */
+
+ /* Let the custom-function fill in the rest */
+ config_stream(priv_data, &pkt->stream);
+
+ cb_info_bt.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished;
+ }
+
+ err = receive_bt_cmd_complete(user,
+ CG2900_BT_VS_SET_SESSION_CONFIG,
+ &session_id, sizeof(session_id));
+ /* Return session id/stream handle if success */
+ if (!err)
+ err = session_id;
+
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * send_fm_write_1_param() - Formats and sends an FM legacy write
command with one parameter.
+ * @user: Audio user this command belongs to.
+ * @command: Command.
+ * @param: Parameter for command.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the fm_mutex held.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_fm_write_1_param(struct audio_user *user,
+ u16 command, u16 param)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct fm_leg_cmd *cmd;
+ size_t len;
+
+ /* base package + one parameter */
+ len = sizeof(*cmd) + sizeof(cmd->fm_cmd.data[0]);
+
+ skb = cg2900_alloc_skb(len, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ return -ENOMEM;
+ }
+
+ cmd = (struct fm_leg_cmd *)skb_put(skb, len);
+
+ cmd->length = CG2900_FM_CMD_PARAM_LEN(len);
+ cmd->opcode = CG2900_FM_GEN_ID_LEGACY;
+ cmd->read_write = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ cmd->fm_function = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ /* one parameter - builtin assumption for this function */
+ cmd->fm_cmd.head = cpu_to_le16(cg2900_make_fm_cmd_id(command, 1));
+ cmd->fm_cmd.data[0] = cpu_to_le16(param);
+
+ cb_info_fm.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_fm, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished;
+ }
+
+ err = receive_fm_write_response(user, command);
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * send_vs_stream_ctrl() - Formats an sends a
CG2900_MC_VS_STREAM_CONTROL command.
+ * @user: Audio user this command belongs to.
+ * @stream: Stream id.
+ * @command: Start/stop etc.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * While the HCI command allows for multiple streams in one command,
+ * this function only handles one.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_vs_stream_ctrl(struct audio_user *user, u8 stream, u8 command)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct mc_vs_stream_ctrl_cmd *cmd;
+ size_t len;
+ u8 vs_err;
+
+ /* basic length + one stream */
+ len = sizeof(*cmd) + sizeof(cmd->stream[0]);
+
+ skb = cg2900_alloc_skb(len, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ return -ENOMEM;
+ }
+
+ cmd = (struct mc_vs_stream_ctrl_cmd *)skb_put(skb, len);
+
+ cmd->opcode = cpu_to_le16(CG2900_MC_VS_STREAM_CONTROL);
+ cmd->plen = BT_PARAM_LEN(len);
+ cmd->command = command;
+
+ /* one stream */
+ cmd->n_streams = 1;
+ cmd->stream[0] = stream;
+
+ cb_info_bt.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished;
+ }
+
+ /* All commands in PG2 API returns one byte with extra status */
+ err = receive_bt_cmd_complete(user,
+ CG2900_MC_VS_STREAM_CONTROL,
+ &vs_err, sizeof(vs_err));
+ if (err)
+ CG2900_DBG("VS_STREAM_CONTROL - failed with error %02x",
+ vs_err);
+
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * send_vs_create_stream() - Formats an sends a
CG2900_MC_VS_CREATE_STREAM command.
+ * @user: Audio user this command belongs to.
+ * @inport: Stream id.
+ * @outport: Start/stop etc.
+ * @order: Activation order.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_vs_create_stream(struct audio_user *user, u8 inport,
+ u8 outport, u8 order)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct mc_vs_create_stream_cmd *cmd;
+ s8 id;
+ u8 vs_err;
+
+ id = new_stream_id();
+ if (id < 0) {
+ CG2900_ERR("No free stream id");
+ err = -EIO;
+ goto finished;
+ }
+
+ skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_release_id;
+ }
+
+ cmd = (struct mc_vs_create_stream_cmd *)skb_put(skb, sizeof(*cmd));
+
+ cmd->opcode = cpu_to_le16(CG2900_MC_VS_CREATE_STREAM);
+ cmd->plen = BT_PARAM_LEN(sizeof(*cmd));
+ cmd->id = (u8)id;
+ cmd->inport = inport;
+ cmd->outport = outport;
+ cmd->order = order;
+
+ cb_info_bt.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished_release_id;
+ }
+
+ /* All commands in PG2 API returns one byte with extra status */
+ err = receive_bt_cmd_complete(user,
+ CG2900_MC_VS_CREATE_STREAM,
+ &vs_err, sizeof(vs_err));
+ if (err) {
+ CG2900_DBG("VS_CREATE_STREAM - failed with error %02x",
+ vs_err);
+ goto finished_release_id;
+ }
+
+ err = id;
+ goto finished;
+
+finished_release_id:
+ release_stream_id(id);
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * send_vs_port_cfg() - Formats an sends a CG2900_MC_VS_PORT_CONFIG command.
+ * @user: Audio user this command belongs to.
+ * @port: Port id to configure.
+ * @cfg: Pointer to specific configuration.
+ * @cfglen: Length of configuration.
+ *
+ * Packs and sends a command packet and waits for the response. Must
+ * be called with the bt_mutex held.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int send_vs_port_cfg(struct audio_user *user, u8 port,
+ const void *cfg, size_t cfglen)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ struct mc_vs_port_cfg_cmd *cmd;
+ void *ptr;
+ u8 vs_err;
+
+ skb = cg2900_alloc_skb(sizeof(*cmd) + cfglen, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ return -ENOMEM;
+ }
+
+ /* Fill in common part */
+ cmd = (struct mc_vs_port_cfg_cmd *) skb_put(skb, sizeof(*cmd));
+ cmd->opcode = cpu_to_le16(CG2900_MC_VS_PORT_CONFIG);
+ cmd->plen = BT_PARAM_LEN(sizeof(*cmd) + cfglen);
+ cmd->type = port;
+
+ /* Copy specific configuration */
+ ptr = skb_put(skb, cfglen);
+ memcpy(ptr, cfg, cfglen);
+
+ /* Send */
+ cb_info_bt.user = user;
+ SET_RESP_STATE(user->resp_state, WAITING);
+
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ kfree_skb(skb);
+ goto finished;
+ }
+
+ /* All commands in PG2 API returns one byte with extra status */
+ err = receive_bt_cmd_complete(user, CG2900_MC_VS_PORT_CONFIG,
+ &vs_err, sizeof(vs_err));
+ if (err)
+ CG2900_DBG("VS_PORT_CONFIG - failed with error %02x",
+ vs_err);
+
+finished:
+ SET_RESP_STATE(user->resp_state, IDLE);
+ return err;
+}
+
+/**
+ * set_dai_config_pg1() - Internal implementation of
@cg2900_audio_set_dai_config for PG1 hardware.
+ * @audio_user: Pointer to audio user struct.
+ * @config: Pointer to the configuration to set.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration for PG1
+ * hardware. This is and internal function and basic
+ * argument-verification should have been done by the caller.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCESS if port is not supported.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int set_dai_config_pg1(struct audio_user *audio_user,
+ struct cg2900_dai_config *config)
+{
+ int err = 0;
+ struct cg2900_dai_conf_i2s_pcm *i2s_pcm;
+ struct sk_buff *skb = NULL;
+ struct bt_vs_set_hw_cfg_cmd_i2s *i2s_cmd;
+ struct bt_vs_set_hw_cfg_cmd_pcm *pcm_cmd;
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any time on
+ * each channel.
+ */
+ mutex_lock(&audio_info->bt_mutex);
+
+ /* Allocate the sk_buffer. The length is actually a max length since
+ * length varies depending on logical transport.
+ */
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG,
+ GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Fill in hci-command according to received configuration */
+ switch (config->port) {
+ case PORT_0_I2S:
+ i2s_cmd = (struct bt_vs_set_hw_cfg_cmd_i2s *)
+ skb_put(skb, sizeof(*i2s_cmd));
+
+ i2s_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG);
+ i2s_cmd->plen = BT_PARAM_LEN(sizeof(*i2s_cmd));
+
+ i2s_cmd->vp_type = PORT_PROTOCOL_I2S;
+ i2s_cmd->port_id = 0x00; /* First/only I2S port */
+ i2s_cmd->half_period = config->conf.i2s.half_period;
+
+ i2s_cmd->master_slave = mc_i2s_role(config->conf.i2s.mode);
+
+ /* Store the new configuration */
+ mutex_lock(&audio_info->management_mutex);
+ memcpy(&(audio_info->i2s_config), &(config->conf.i2s),
+ sizeof(config->conf.i2s));
+ audio_info->i2s_config_known = true;
+ mutex_unlock(&audio_info->management_mutex);
+ break;
+
+ case PORT_1_I2S_PCM:
+ pcm_cmd = (struct bt_vs_set_hw_cfg_cmd_pcm *)
+ skb_put(skb, sizeof(*pcm_cmd));
+
+ pcm_cmd->opcode = cpu_to_le16(CG2900_BT_VS_SET_HARDWARE_CONFIG);
+ pcm_cmd->plen = BT_PARAM_LEN(sizeof(*pcm_cmd));
+
+ i2s_pcm = &config->conf.i2s_pcm;
+
+ /*
+ * PG1 chips don't support I2S over the PCM/I2S bus,
+ * and PG2 chips don't use this command
+ */
+ if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) {
+ CG2900_ERR("I2S not supported over the PCM/I2S bus");
+ err = -EACCES;
+ goto error_handling_free_skb;
+ }
+
+ pcm_cmd->vp_type = PORT_PROTOCOL_PCM;
+ pcm_cmd->port_id = 0x00; /* First/only PCM port */
+
+ HWCONFIG_PCM_SET_MODE(pcm_cmd, mc_pcm_role(i2s_pcm->mode));
+
+ HWCONFIG_PCM_SET_DIR(pcm_cmd, 0, i2s_pcm->slot_0_dir);
+ HWCONFIG_PCM_SET_DIR(pcm_cmd, 1, i2s_pcm->slot_1_dir);
+ HWCONFIG_PCM_SET_DIR(pcm_cmd, 2, i2s_pcm->slot_2_dir);
+ HWCONFIG_PCM_SET_DIR(pcm_cmd, 3, i2s_pcm->slot_3_dir);
+
+ pcm_cmd->bit_clock = i2s_pcm->clk;
+ pcm_cmd->frame_len =
+ cpu_to_le16(get_fs_duration(i2s_pcm->duration));
+
+ /* Store the new configuration */
+ mutex_lock(&audio_info->management_mutex);
+ memcpy(&(audio_info->i2s_pcm_config), &(config->conf.i2s_pcm),
+ sizeof(config->conf.i2s_pcm));
+ audio_info->i2s_pcm_config_known = true;
+ mutex_unlock(&audio_info->management_mutex);
+ break;
+
+ default:
+ CG2900_ERR("Unknown port configuration %d", config->port);
+ err = -EACCES;
+ goto error_handling_free_skb;
+ };
+
+ cb_info_bt.user = audio_user;
+ SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+ /* Send packet to controller */
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_bt_cmd_complete(audio_user,
+ CG2900_BT_VS_SET_HARDWARE_CONFIG,
+ NULL, 0);
+
+ goto finished_unlock_mutex;
+
+error_handling_free_skb:
+ kfree_skb(skb);
+finished_unlock_mutex:
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+ mutex_unlock(&audio_info->bt_mutex);
+ return err;
+}
+
+/**
+ * set_dai_config_pg2() - Internal implementation of
@cg2900_audio_set_dai_config for PG2 hardware.
+ * @audio_user: Pointer to audio user struct.
+ * @config: Pointer to the configuration to set.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration for PG2
+ * hardware. This is an internal function and basic
+ * argument-verification should have been done by the caller.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EACCESS if port is not supported.
+ * -ENOMEM if not possible to allocate packet.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int set_dai_config_pg2(struct audio_user *audio_user,
+ struct cg2900_dai_config *config)
+{
+ int err = 0;
+ struct cg2900_dai_conf_i2s *i2s;
+ struct cg2900_dai_conf_i2s_pcm *i2s_pcm;
+
+ struct mc_vs_port_cfg_i2s i2s_cfg;
+ struct mc_vs_port_cfg_pcm_i2s pcm_cfg;
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any time on
+ * each channel.
+ */
+ mutex_lock(&audio_info->bt_mutex);
+
+ switch (config->port) {
+ case PORT_0_I2S:
+ i2s = &config->conf.i2s;
+
+ memset(&i2s_cfg, 0, sizeof(i2s_cfg)); /* just to be safe */
+
+ /* master/slave */
+ PORTCFG_I2S_SET_ROLE(i2s_cfg, mc_i2s_role(i2s->mode));
+
+ PORTCFG_I2S_SET_HALFPERIOD(i2s_cfg, i2s->half_period);
+ PORTCFG_I2S_SET_CHANNELS(i2s_cfg,
+ mc_i2s_channel_select(i2s->channel_sel));
+ PORTCFG_I2S_SET_SRATE(i2s_cfg,
+ mc_i2s_sample_rate(i2s->sample_rate));
+ switch (i2s->word_width) {
+ case WORD_WIDTH_16:
+ PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_16);
+ break;
+ case WORD_WIDTH_32:
+ PORTCFG_I2S_SET_WORDLEN(i2s_cfg, CG2900_MC_I2S_WORD_32);
+ break;
+ }
+
+ /* Store the new configuration */
+ mutex_lock(&audio_info->management_mutex);
+ memcpy(&(audio_info->i2s_config), &(config->conf.i2s),
+ sizeof(config->conf.i2s));
+ audio_info->i2s_config_known = true;
+ mutex_unlock(&audio_info->management_mutex);
+
+ /* Send */
+ err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_I2S,
+ &i2s_cfg, sizeof(i2s_cfg));
+ break;
+
+ case PORT_1_I2S_PCM:
+ i2s_pcm = &config->conf.i2s_pcm;
+
+ memset(&pcm_cfg, 0, sizeof(pcm_cfg)); /* just to be safe */
+
+ /* master/slave */
+ PORTCFG_PCM_SET_ROLE(pcm_cfg, mc_pcm_role(i2s_pcm->mode));
+
+ /* set direction for all 4 slots */
+ PORTCFG_PCM_SET_DIR(pcm_cfg, 0, i2s_pcm->slot_0_dir);
+ PORTCFG_PCM_SET_DIR(pcm_cfg, 1, i2s_pcm->slot_1_dir);
+ PORTCFG_PCM_SET_DIR(pcm_cfg, 2, i2s_pcm->slot_2_dir);
+ PORTCFG_PCM_SET_DIR(pcm_cfg, 3, i2s_pcm->slot_3_dir);
+
+ /* set used SCO slots, other use cases not supported atm */
+ PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 0, i2s_pcm->slot_0_used);
+ PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 1, i2s_pcm->slot_1_used);
+ PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 2, i2s_pcm->slot_2_used);
+ PORTCFG_PCM_SET_SCO_USED(pcm_cfg, 3, i2s_pcm->slot_3_used);
+
+ /* slot starts */
+ pcm_cfg.slot_start[0] = i2s_pcm->slot_0_start;
+ pcm_cfg.slot_start[1] = i2s_pcm->slot_1_start;
+ pcm_cfg.slot_start[2] = i2s_pcm->slot_2_start;
+ pcm_cfg.slot_start[3] = i2s_pcm->slot_3_start;
+
+ /* audio/voice sample-rate ratio */
+ PORTCFG_PCM_SET_RATIO(pcm_cfg, i2s_pcm->ratio);
+
+ /* PCM or I2S mode */
+ PORTCFG_PCM_SET_MODE(pcm_cfg, i2s_pcm->protocol);
+
+ pcm_cfg.frame_len = i2s_pcm->duration;
+
+ PORTCFG_PCM_SET_BITCLK(pcm_cfg, i2s_pcm->clk);
+ PORTCFG_PCM_SET_SRATE(pcm_cfg,
+ mc_pcm_sample_rate(i2s_pcm->sample_rate));
+
+ /* Store the new configuration */
+ mutex_lock(&audio_info->management_mutex);
+ memcpy(&(audio_info->i2s_pcm_config), &(config->conf.i2s_pcm),
+ sizeof(config->conf.i2s_pcm));
+ audio_info->i2s_pcm_config_known = true;
+ mutex_unlock(&audio_info->management_mutex);
+
+ /* Send */
+ err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_PCM_I2S,
+ &pcm_cfg, sizeof(pcm_cfg));
+ break;
+
+ default:
+ CG2900_ERR("Unknown port configuration %d", config->port);
+ err = -EACCES;
+ };
+
+ mutex_unlock(&audio_info->bt_mutex);
+ return err;
+}
+
+/**
+ * struct i2s_fm_stream_config_priv - Helper struct for stream i2s-fm streams.
+ * @fm_config: FM endpoint configuration.
+ * @rx: true for FM-RX, false for FM-TX.
+ */
+struct i2s_fm_stream_config_priv {
+ struct cg2900_endpoint_config_fm *fm_config;
+ bool rx;
+
+};
+
+/**
+ * config_i2s_fm_stream() - Callback for @send_vs_session_config.
+ * @_priv: Pointer to a @i2s_fm_stream_config_priv struct.
+ * @cfg: Pointer to stream config block in command packet.
+ *
+ * Fills in stream configuration for I2S-FM RX/TX.
+ */
+
+static void config_i2s_fm_stream(void *_priv,
+ struct session_config_stream *cfg)
+{
+ struct i2s_fm_stream_config_priv *priv = _priv;
+ struct session_config_vport *fm;
+ struct session_config_vport *i2s;
+
+ cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+
+ if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+ SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_STEREO);
+ else
+ SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO);
+
+ SESSIONCFG_I2S_SET_SRATE(cfg,
+ session_config_sample_rate(priv->fm_config->sample_rate));
+
+ cfg->codec_type = CG2900_CODEC_TYPE_NONE;
+ /* codec mode and parameters not used */
+
+ if (priv->rx) {
+ fm = &cfg->inport; /* FM is input */
+ i2s = &cfg->outport; /* I2S is output */
+ } else {
+ i2s = &cfg->inport; /* I2S is input */
+ fm = &cfg->outport; /* FM is output */
+ }
+
+ fm->type = CG2900_BT_VP_TYPE_FM;
+
+ i2s->type = CG2900_BT_VP_TYPE_I2S;
+ i2s->i2s.index = CG2900_BT_SESSION_I2S_INDEX_I2S;
+ i2s->i2s.channel = audio_info->i2s_config.channel_sel;
+}
+
+/**
+ * conn_start_i2s_to_fm_rx() - Start an audio stream connecting FM RX to I2S.
+ * @audio_user: Audio user to check for.
+ * @stream_handle: [out] Pointer where to store the stream handle.
+ *
+ * This function sets up an FM RX to I2S stream.
+ * It does this by first setting the output mode and then the configuration of
+ * the External Sample Rate Converter.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -ENOMEM upon allocation errors.
+ * -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ union cg2900_endpoint_config_union *fm_config;
+
+ fm_config = find_endpoint(ENDPOINT_FM_RX,
+ &(audio_info->endpoints));
+ if (!fm_config) {
+ CG2900_ERR("FM RX not configured before stream start");
+ return -EIO;
+ }
+
+ if (!(audio_info->i2s_config_known)) {
+ CG2900_ERR("I2S DAI not configured before stream start");
+ return -EIO;
+ }
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any
+ * time on each channel.
+ */
+ mutex_lock(&audio_info->fm_mutex);
+ mutex_lock(&audio_info->bt_mutex);
+
+ /*
+ * Now set the output mode of the External Sample Rate Converter by
+ * sending HCI_Write command with AUP_EXT_SetMode.
+ */
+ err = send_fm_write_1_param(audio_user,
+ CG2900_FM_CMD_ID_AUP_EXT_SET_MODE,
+ CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /*
+ * Now configure the External Sample Rate Converter by sending
+ * HCI_Write command with AUP_EXT_SetControl.
+ */
+ err = send_fm_write_1_param(
+ audio_user, CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL,
+ fm_get_conversion(fm_config->fm.sample_rate));
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* Set up the stream */
+ if (audio_info->revision == CHIP_REV_PG1) {
+ struct i2s_fm_stream_config_priv stream_priv;
+
+ /* Now send HCI_VS_Set_Session_Configuration command */
+ stream_priv.fm_config = &fm_config->fm;
+ stream_priv.rx = true;
+ err = send_vs_session_config(audio_user, config_i2s_fm_stream,
+ &stream_priv);
+ } else {
+ struct mc_vs_port_cfg_fm fm_cfg;
+
+ memset(&fm_cfg, 0, sizeof(fm_cfg));
+
+ /* Configure port FM RX */
+ /* Expects 0-3 - same as user API - so no conversion needed */
+ PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate);
+
+ err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_RX_1,
+ &fm_cfg, sizeof(fm_cfg));
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* CreateStream */
+ err = send_vs_create_stream(audio_user,
+ CG2900_MC_PORT_FM_RX_1,
+ CG2900_MC_PORT_I2S,
+ 0); /* chip doesn't care */
+ }
+
+ if (err < 0)
+ goto finished_unlock_mutex;
+
+ /* Store the stream handle (used for start and stop stream) */
+ *stream_handle = (u8)err;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ /* Now start the stream */
+ if (audio_info->revision == CHIP_REV_PG1)
+ err = send_vs_session_ctrl(audio_user, *stream_handle,
+ CG2900_BT_SESSION_START);
+ else
+ err = send_vs_stream_ctrl(audio_user, *stream_handle,
+ CG2900_MC_STREAM_START);
+
+finished_unlock_mutex:
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+ mutex_unlock(&audio_info->bt_mutex);
+ mutex_unlock(&audio_info->fm_mutex);
+ return err;
+}
+
+/**
+ * conn_start_i2s_to_fm_tx() - Start an audio stream connecting FM TX to I2S.
+ * @audio_user: Audio user to check for.
+ * @stream_handle: [out] Pointer where to store the stream handle.
+ *
+ * This function sets up an I2S to FM TX stream.
+ * It does this by first setting the Audio Input source and then setting the
+ * configuration and input source of BT sample rate converter.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -ENOMEM upon allocation errors.
+ * -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ union cg2900_endpoint_config_union *fm_config;
+
+ fm_config = find_endpoint(ENDPOINT_FM_TX, &(audio_info->endpoints));
+ if (!fm_config) {
+ CG2900_ERR("FM TX not configured before stream start");
+ return -EIO;
+ }
+
+ if (!(audio_info->i2s_config_known)) {
+ CG2900_ERR("I2S DAI not configured before stream start");
+ return -EIO;
+ }
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any time
+ * on each channel.
+ */
+ mutex_lock(&audio_info->fm_mutex);
+ mutex_lock(&audio_info->bt_mutex);
+
+ /*
+ * Select Audio Input Source by sending HCI_Write command with
+ * AIP_SetMode.
+ */
+ CG2900_DBG("FM: AIP_SetMode");
+ err = send_fm_write_1_param(audio_user, CG2900_FM_CMD_ID_AIP_SET_MODE,
+ CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /*
+ * Now configure the BT sample rate converter by sending HCI_Write
+ * command with AIP_BT_SetControl.
+ */
+ CG2900_DBG("FM: AIP_BT_SetControl");
+ err = send_fm_write_1_param(
+ audio_user, CG2900_FM_CMD_ID_AIP_BT_SET_CTRL,
+ fm_get_conversion(fm_config->fm.sample_rate));
+ if (err)
+ goto finished_unlock_mutex;
+
+ /*
+ * Now set input of the BT sample rate converter by sending HCI_Write
+ * command with AIP_BT_SetMode.
+ */
+ CG2900_DBG("FM: AIP_BT_SetMode");
+ err = send_fm_write_1_param(audio_user,
+ CG2900_FM_CMD_ID_AIP_BT_SET_MODE,
+ CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* Set up the stream */
+ if (audio_info->revision == CHIP_REV_PG1) {
+ struct i2s_fm_stream_config_priv stream_priv;
+
+ /* Now send HCI_VS_Set_Session_Configuration command */
+ stream_priv.fm_config = &fm_config->fm;
+ stream_priv.rx = false;
+ err = send_vs_session_config(audio_user, config_i2s_fm_stream,
+ &stream_priv);
+ } else {
+ struct mc_vs_port_cfg_fm fm_cfg;
+
+ memset(&fm_cfg, 0, sizeof(fm_cfg));
+
+ /* Configure port FM TX */
+ /* Expects 0-3 - same as user API - so no conversion needed */
+ PORTCFG_FM_SET_SRATE(fm_cfg, (u8)fm_config->fm.sample_rate);
+
+ err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_FM_TX,
+ &fm_cfg, sizeof(fm_cfg));
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* CreateStream */
+ err = send_vs_create_stream(audio_user,
+ CG2900_MC_PORT_I2S,
+ CG2900_MC_PORT_FM_TX,
+ 0); /* chip doesn't care */
+ }
+
+ if (err < 0)
+ goto finished_unlock_mutex;
+
+ /* Store the stream handle (used for start and stop stream) */
+ *stream_handle = (u8)err;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ /* Now start the stream */
+ if (audio_info->revision == CHIP_REV_PG1)
+ err = send_vs_session_ctrl(audio_user, *stream_handle,
+ CG2900_BT_SESSION_START);
+ else
+ err = send_vs_stream_ctrl(audio_user, *stream_handle,
+ CG2900_MC_STREAM_START);
+
+finished_unlock_mutex:
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+ mutex_unlock(&audio_info->bt_mutex);
+ mutex_unlock(&audio_info->fm_mutex);
+ return err;
+}
+
+/**
+ * config_pcm_sco_stream() - Callback for @send_vs_session_config.
+ * @_priv: Pointer to a @cg2900_endpoint_config_sco_in_out struct.
+ * @cfg: Pointer to stream config block in command packet.
+ *
+ * Fills in stream configuration for PCM-SCO.
+ */
+static void config_pcm_sco_stream(void *_priv,
+ struct session_config_stream *cfg)
+{
+ struct cg2900_endpoint_config_sco_in_out *sco_ep = _priv;
+
+ cfg->media_type = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+
+ SESSIONCFG_SET_CHANNELS(cfg, CG2900_BT_MEDIA_CONFIG_MONO);
+ SESSIONCFG_I2S_SET_SRATE(cfg,
+ session_config_sample_rate(sco_ep->sample_rate));
+
+ cfg->codec_type = CG2900_CODEC_TYPE_NONE;
+ /* codec mode and parameters not used */
+
+ cfg->inport.type = CG2900_BT_VP_TYPE_BT_SCO;
+ cfg->inport.sco.acl_handle = cpu_to_le16(DEFAULT_SCO_HANDLE);
+
+ cfg->outport.type = CG2900_BT_VP_TYPE_PCM;
+ cfg->outport.pcm.index = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S;
+
+ SESSIONCFG_PCM_SET_USED(cfg->outport, 0,
+ audio_info->i2s_pcm_config.slot_0_used);
+ SESSIONCFG_PCM_SET_USED(cfg->outport, 1,
+ audio_info->i2s_pcm_config.slot_1_used);
+ SESSIONCFG_PCM_SET_USED(cfg->outport, 2,
+ audio_info->i2s_pcm_config.slot_2_used);
+ SESSIONCFG_PCM_SET_USED(cfg->outport, 3,
+ audio_info->i2s_pcm_config.slot_3_used);
+
+ cfg->outport.pcm.slot_start[0] =
+ audio_info->i2s_pcm_config.slot_0_start;
+ cfg->outport.pcm.slot_start[1] =
+ audio_info->i2s_pcm_config.slot_1_start;
+ cfg->outport.pcm.slot_start[2] =
+ audio_info->i2s_pcm_config.slot_2_start;
+ cfg->outport.pcm.slot_start[3] =
+ audio_info->i2s_pcm_config.slot_3_start;
+}
+
+/**
+ * conn_start_pcm_to_sco() - Start an audio stream connecting
Bluetooth (e)SCO to PCM_I2S.
+ * @audio_user: Audio user to check for.
+ * @stream_handle: [out] Pointer where to store the stream handle.
+ *
+ * This function sets up a BT to_from PCM_I2S stream. It does this by
+ * first setting the Session configuration and then starting the Audio
+ * Stream.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -ENOMEM upon allocation errors.
+ * Errors from @cg2900_write
+ * -EIO for other errors.
+ */
+static int conn_start_pcm_to_sco(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ union cg2900_endpoint_config_union *bt_config;
+
+ bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT,
+ &(audio_info->endpoints));
+ if (!bt_config) {
+ CG2900_ERR("BT not configured before stream start");
+ return -EIO;
+ }
+
+ if (!(audio_info->i2s_pcm_config_known)) {
+ CG2900_ERR("I2S_PCM DAI not configured before stream start");
+ return -EIO;
+ }
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any time on each
+ * channel.
+ */
+ mutex_lock(&audio_info->bt_mutex);
+
+ /* Set up the stream */
+ if (audio_info->revision == CHIP_REV_PG1) {
+ err = send_vs_session_config(audio_user, config_pcm_sco_stream,
+ &bt_config->sco);
+ } else {
+ struct mc_vs_port_cfg_sco sco_cfg;
+
+ /* zero codec params etc */
+ memset(&sco_cfg, 0, sizeof(sco_cfg));
+ sco_cfg.acl_id = DEFAULT_SCO_HANDLE;
+ PORTCFG_SCO_SET_WBS(sco_cfg, 0); /* No WBS yet */
+ PORTCFG_SCO_SET_CODEC(sco_cfg, CG2900_CODEC_TYPE_NONE);
+
+ err = send_vs_port_cfg(audio_user, CG2900_MC_PORT_BT_SCO,
+ &sco_cfg, sizeof(sco_cfg));
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* CreateStream */
+ err = send_vs_create_stream(audio_user,
+ CG2900_MC_PORT_PCM_I2S,
+ CG2900_MC_PORT_BT_SCO,
+ 0); /* chip doesn't care */
+ }
+
+ if (err < 0)
+ goto finished_unlock_mutex;
+
+ /* Store the stream handle (used for start and stop stream) */
+ *stream_handle = (u8)err;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ /* Now start the stream by sending HCI_VS_Session_Control command */
+ err = send_vs_session_ctrl(audio_user, *stream_handle,
+ CG2900_BT_SESSION_START);
+
+finished_unlock_mutex:
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+ mutex_unlock(&audio_info->bt_mutex);
+ return err;
+}
+
+/**
+ * conn_stop_stream() - Stops an audio stream defined by @stream_handle.
+ * @audio_user: Audio user to check for.
+ * @stream_handle: Handle of the audio stream.
+ *
+ * This function is used to stop an audio stream defined by a stream
+ * handle. It does this by first stopping the stream and then
+ * resetting the session/stream.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -ENOMEM upon allocation errors.
+ * Errors from @cg2900_write.
+ * -EIO for other errors.
+ */
+static int conn_stop_stream(struct audio_user *audio_user,
+ unsigned int stream_handle)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ u16 opcode;
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any
+ * time on each channel.
+ */
+ mutex_lock(&audio_info->bt_mutex);
+
+ /* Now stop the stream */
+ if (audio_info->revision == CHIP_REV_PG1)
+ err = send_vs_session_ctrl(audio_user, stream_handle,
+ CG2900_BT_SESSION_STOP);
+ else
+ err = send_vs_stream_ctrl(audio_user, stream_handle,
+ CG2900_MC_STREAM_STOP);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* Now delete the stream - format command... */
+ if (audio_info->revision == CHIP_REV_PG1) {
+ struct bt_vs_reset_session_cfg_cmd *cmd;
+
+ CG2900_DBG("BT: HCI_VS_Reset_Session_Configuration");
+
+ skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ cmd = (struct bt_vs_reset_session_cfg_cmd *)
+ skb_put(skb, sizeof(*cmd));
+
+ opcode = CG2900_BT_VS_RESET_SESSION_CONFIG;
+ cmd->opcode = cpu_to_le16(opcode);
+ cmd->plen = BT_PARAM_LEN(sizeof(*cmd));
+ cmd->id = (u8)stream_handle;
+ } else {
+ struct mc_vs_delete_stream_cmd *cmd;
+
+ CG2900_DBG("BT: HCI_VS_Delete_Stream");
+
+ skb = cg2900_alloc_skb(sizeof(*cmd), GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ cmd = (struct mc_vs_delete_stream_cmd *)
+ skb_put(skb, sizeof(*cmd));
+
+ opcode = CG2900_MC_VS_DELETE_STREAM;
+ cmd->opcode = cpu_to_le16(opcode);
+ cmd->plen = BT_PARAM_LEN(sizeof(*cmd));
+ cmd->stream = (u8)stream_handle;
+ }
+
+ /* ...and send it */
+ cb_info_bt.user = audio_user;
+ SET_RESP_STATE(audio_user->resp_state, WAITING);
+
+ err = cg2900_write(audio_info->dev_bt, skb);
+ if (err) {
+ CG2900_ERR("Error occurred while transmitting skb (%d)", err);
+ goto error_handling_free_skb;
+ }
+
+ /* wait for response */
+ if (audio_info->revision == CHIP_REV_PG1) {
+ err = receive_bt_cmd_complete(audio_user, opcode, NULL, 0);
+ } else {
+ u8 vs_err;
+
+ /* All commands in PG2 API returns one byte extra status */
+ err = receive_bt_cmd_complete(audio_user, opcode,
+ &vs_err, sizeof(vs_err));
+
+ if (err)
+ CG2900_DBG("VS_DELETE_STREAM - failed with error %02x",
+ vs_err);
+ else
+ release_stream_id(stream_handle);
+
+ }
+
+ goto finished_unlock_mutex;
+
+error_handling_free_skb:
+ kfree_skb(skb);
+finished_unlock_mutex:
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+ mutex_unlock(&audio_info->bt_mutex);
+ return err;
+}
+
+/**
+ * cg2900_audio_open() - Opens a session to the ST-Ericsson CG2900
Audio control interface.
+ * @session: [out] Address where to store the session identifier.
+ * Allocated by caller, must not be NULL.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -ENOMEM upon allocation failure.
+ * -EMFILE if no more user session could be opened.
+ * -EIO upon failure to register to CG2900.
+ */
+int cg2900_audio_open(unsigned int *session)
+{
+ int err = 0;
+ int i;
+
+ CG2900_INFO("cg2900_audio_open");
+
+ if (!session) {
+ CG2900_ERR("NULL supplied as session.");
+ return -EINVAL;
+ }
+
+ mutex_lock(&audio_info->management_mutex);
+
+ *session = 0;
+
+ /*
+ * First find a free session to use and allocate the session structure.
+ */
+ for (i = FIRST_USER;
+ i < MAX_NBR_OF_USERS && audio_info->audio_sessions[i];
+ i++)
+ ; /* Just loop until found or end reached */
+
+ if (i >= MAX_NBR_OF_USERS) {
+ CG2900_ERR("Couldn't find free user");
+ err = -EMFILE;
+ goto finished;
+ }
+
+ audio_info->audio_sessions[i] =
+ kzalloc(sizeof(*(audio_info->audio_sessions[0])),
+ GFP_KERNEL);
+ if (!audio_info->audio_sessions[i]) {
+ CG2900_ERR("Could not allocate user");
+ err = -ENOMEM;
+ goto finished;
+ }
+ CG2900_DBG("Found free session %d", i);
+ *session = i;
+ audio_info->nbr_of_users_active++;
+
+ SET_RESP_STATE(audio_info->audio_sessions[*session]->resp_state, IDLE);
+ audio_info->audio_sessions[*session]->session = *session;
+
+ if (audio_info->nbr_of_users_active == 1) {
+ struct cg2900_rev_data rev_data;
+
+ /*
+ * First user so register to CG2900 Core.
+ * First the BT audio device.
+ */
+ audio_info->dev_bt = cg2900_register_user(CG2900_BT_AUDIO,
+ &cg2900_cb);
+ if (!audio_info->dev_bt) {
+ CG2900_ERR("Failed to register BT audio channel");
+ err = -EIO;
+ goto error_handling;
+ }
+
+ /* Store the callback info structure */
+ audio_info->dev_bt->user_data = &cb_info_bt;
+
+ /* Then the FM audio device */
+ audio_info->dev_fm = cg2900_register_user(CG2900_FM_RADIO_AUDIO,
+ &cg2900_cb);
+ if (!audio_info->dev_fm) {
+ CG2900_ERR("Failed to register FM audio channel");
+ err = -EIO;
+ goto error_handling;
+ }
+
+ /* Store the callback info structure */
+ audio_info->dev_fm->user_data = &cb_info_fm;
+
+ /* Read chip revision data */
+ if (!cg2900_get_local_revision(&rev_data)) {
+ CG2900_ERR("Couldn't retrieve revision data");
+ err = -EIO;
+ goto error_handling;
+ }
+
+ /* Decode revision data */
+ switch (rev_data.revision) {
+ case CG2900_PG1_REV:
+ case CG2900_PG1_SPECIAL_REV:
+ audio_info->revision = CHIP_REV_PG1;
+ break;
+
+ case CG2900_PG2_REV:
+ audio_info->revision = CHIP_REV_PG2;
+ break;
+
+ default:
+ CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported",
+ rev_data.revision, rev_data.sub_version);
+ err = -EIO;
+ goto error_handling;
+ }
+
+ audio_info->state = OPENED;
+ }
+
+ goto finished;
+
+error_handling:
+ if (audio_info->dev_fm) {
+ cg2900_deregister_user(audio_info->dev_fm);
+ audio_info->dev_fm = NULL;
+ }
+ if (audio_info->dev_bt) {
+ cg2900_deregister_user(audio_info->dev_bt);
+ audio_info->dev_bt = NULL;
+ }
+ audio_info->nbr_of_users_active--;
+ kfree(audio_info->audio_sessions[*session]);
+ audio_info->audio_sessions[*session] = NULL;
+finished:
+ mutex_unlock(&audio_info->management_mutex);
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_open);
+
+/**
+ * cg2900_audio_close() - Closes an opened session to the ST-Ericsson
CG2900 audio control interface.
+ * @session: [in_out] Pointer to session identifier to close.
+ * Will be 0 after this call.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -EIO if driver has not been opened.
+ * -EACCES if session has not opened.
+ */
+int cg2900_audio_close(unsigned int *session)
+{
+ int err = 0;
+ struct audio_user *audio_user;
+
+ CG2900_INFO("cg2900_audio_close");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ if (!session) {
+ CG2900_ERR("NULL pointer supplied");
+ return -EINVAL;
+ }
+
+ audio_user = get_session_user(*session);
+ if (!audio_user) {
+ CG2900_ERR("Invalid session ID");
+ return -EINVAL;
+ }
+
+ mutex_lock(&audio_info->management_mutex);
+
+ if (!(audio_info->audio_sessions[*session])) {
+ CG2900_ERR("Session %d not opened", *session);
+ err = -EACCES;
+ goto err_unlock_mutex;
+ }
+
+ kfree(audio_info->audio_sessions[*session]);
+ audio_info->audio_sessions[*session] = NULL;
+ audio_info->nbr_of_users_active--;
+
+ if (audio_info->nbr_of_users_active == 0) {
+ /* No more sessions open. Deregister from CG2900 Core */
+ cg2900_deregister_user(audio_info->dev_fm);
+ cg2900_deregister_user(audio_info->dev_bt);
+ audio_info->state = CLOSED;
+ }
+
+ *session = 0;
+
+err_unlock_mutex:
+ mutex_unlock(&audio_info->management_mutex);
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_close);
+
+/**
+ * cg2900_audio_set_dai_config() - Sets the Digital Audio Interface
configuration.
+ * @session: Session identifier this call is related to.
+ * @config: Pointer to the configuration to set.
+ * Allocated by caller, must not be NULL.
+ *
+ * Sets the Digital Audio Interface (DAI) configuration. The DAI is
the external
+ * interface between the combo chip and the platform.
+ * For example the PCM or I2S interface.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -EIO if driver has not been opened.
+ * -ENOMEM upon allocation failure.
+ * -EACCES if trying to set unsupported configuration.
+ * Errors from @receive_bt_cmd_complete.
+ */
+int cg2900_audio_set_dai_config(unsigned int session,
+ struct cg2900_dai_config *config)
+{
+ int err = 0;
+ struct audio_user *audio_user;
+ struct cg2900_rev_data rev_data;
+
+ CG2900_INFO("cg2900_audio_set_dai_config");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ audio_user = get_session_user(session);
+ if (!audio_user)
+ return -EINVAL;
+
+ if (!cg2900_get_local_revision(&rev_data)) {
+ CG2900_ERR("Couldn't retrieve revision data");
+ return -EIO;
+ }
+
+ /* Different commands are used for PG1 and PG2 */
+ switch (rev_data.revision) {
+ case CG2900_PG1_REV:
+ case CG2900_PG1_SPECIAL_REV:
+ err = set_dai_config_pg1(audio_user, config);
+ break;
+
+ case CG2900_PG2_REV:
+ err = set_dai_config_pg2(audio_user, config);
+ break;
+
+ default:
+ CG2900_ERR("Chip rev 0x%04X sub 0x%04X not supported",
+ rev_data.revision, rev_data.sub_version);
+ err = -EIO;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_set_dai_config);
+
+/**
+ * cg2900_audio_get_dai_config() - Gets the current Digital Audio
Interface configuration.
+ * @session: Session identifier this call is related to.
+ * @config: [out] Pointer to the configuration to get.
+ * Allocated by caller, must not be NULL.
+ *
+ * Gets the current Digital Audio Interface configuration. Currently
this method
+ * can only be called after some one has called
+ * cg2900_audio_set_dai_config(), there is today no way of getting
+ * the static settings file parameters from this method.
+ * Note that the @port parameter within @config must be set when calling this
+ * function so that the ST-Ericsson CG2900 Audio driver will know which
+ * configuration to return.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -EIO if driver has not been opened or configuration has not been set.
+ */
+int cg2900_audio_get_dai_config(unsigned int session,
+ struct cg2900_dai_config *config)
+{
+ int err = 0;
+ struct audio_user *audio_user;
+
+ CG2900_INFO("cg2900_audio_get_dai_config");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ if (!config) {
+ CG2900_ERR("NULL supplied as config structure");
+ return -EINVAL;
+ }
+
+ audio_user = get_session_user(session);
+ if (!audio_user)
+ return -EINVAL;
+
+ /*
+ * Return DAI configuration based on the received port.
+ * If port has not been configured return error.
+ */
+ switch (config->port) {
+ case PORT_0_I2S:
+ mutex_lock(&audio_info->management_mutex);
+ if (audio_info->i2s_config_known)
+ memcpy(&(config->conf.i2s),
+ &(audio_info->i2s_config),
+ sizeof(config->conf.i2s));
+ else
+ err = -EIO;
+ mutex_unlock(&audio_info->management_mutex);
+ break;
+
+ case PORT_1_I2S_PCM:
+ mutex_lock(&audio_info->management_mutex);
+ if (audio_info->i2s_pcm_config_known)
+ memcpy(&(config->conf.i2s_pcm),
+ &(audio_info->i2s_pcm_config),
+ sizeof(config->conf.i2s_pcm));
+ else
+ err = -EIO;
+ mutex_unlock(&audio_info->management_mutex);
+ break;
+
+ default:
+ CG2900_ERR("Unknown port configuration %d", config->port);
+ err = -EIO;
+ break;
+ };
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_get_dai_config);
+
+/**
+ * cg2900_audio_config_endpoint() - Configures one endpoint in the
combo chip's audio system.
+ * @session: Session identifier this call is related to.
+ * @config: Pointer to the endpoint's configuration structure.
+ *
+ * Configures one endpoint in the combo chip's audio system.
+ * Supported @endpoint_id values are:
+ * * ENDPOINT_BT_SCO_INOUT
+ * * ENDPOINT_BT_A2DP_SRC
+ * * ENDPOINT_FM_RX
+ * * ENDPOINT_FM_TX
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -EIO if driver has not been opened.
+ * -EACCES if supplied cg2900_dai_config struct contains not supported
+ * endpoint_id.
+ */
+int cg2900_audio_config_endpoint(unsigned int session,
+ struct cg2900_endpoint_config *config)
+{
+ struct audio_user *audio_user;
+
+ CG2900_INFO("cg2900_audio_config_endpoint");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ if (!config) {
+ CG2900_ERR("NULL supplied as configuration structure");
+ return -EINVAL;
+ }
+
+ audio_user = get_session_user(session);
+ if (!audio_user)
+ return -EINVAL;
+
+ switch (config->endpoint_id) {
+ case ENDPOINT_BT_SCO_INOUT:
+ case ENDPOINT_BT_A2DP_SRC:
+ case ENDPOINT_FM_RX:
+ case ENDPOINT_FM_TX:
+ add_endpoint(config, &(audio_info->endpoints));
+ break;
+
+ case ENDPOINT_PORT_0_I2S:
+ case ENDPOINT_PORT_1_I2S_PCM:
+ case ENDPOINT_SLIMBUS_VOICE:
+ case ENDPOINT_SLIMBUS_AUDIO:
+ case ENDPOINT_BT_A2DP_SNK:
+ case ENDPOINT_ANALOG_OUT:
+ case ENDPOINT_DSP_AUDIO_IN:
+ case ENDPOINT_DSP_AUDIO_OUT:
+ case ENDPOINT_DSP_VOICE_IN:
+ case ENDPOINT_DSP_VOICE_OUT:
+ case ENDPOINT_DSP_TONE_IN:
+ case ENDPOINT_BURST_BUFFER_IN:
+ case ENDPOINT_BURST_BUFFER_OUT:
+ case ENDPOINT_MUSIC_DECODER:
+ case ENDPOINT_HCI_AUDIO_IN:
+ default:
+ CG2900_ERR("Unknown endpoint_id %d", config->endpoint_id);
+ return -EACCES;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(cg2900_audio_config_endpoint);
+
+static bool is_dai_port(enum cg2900_audio_endpoint_id ep)
+{
+ /* These are the only supported ones */
+ return (ep == ENDPOINT_PORT_0_I2S) || (ep == ENDPOINT_PORT_1_I2S_PCM);
+}
+
+/**
+ * cg2900_audio_start_stream() - Connects two endpoints and starts
the audio stream.
+ * @session: Session identifier this call is related to.
+ * @ep_1: One of the endpoints, no relation to direction or role.
+ * @ep_2: The other endpoint, no relation to direction or role.
+ * @stream_handle: Pointer where to store the stream handle.
+ * Allocated by caller, must not be NULL.
+ *
+ * Connects two endpoints and starts the audio stream.
+ * Note that the endpoints need to be configured before the stream is started;
+ * DAI endpoints, such as ENDPOINT_PORT_0_I2S, are
+ * configured through @cg2900_audio_set_dai_config() while other
+ * endpoints are configured through @cg2900_audio_config_endpoint().
+ *
+ * Supported @endpoint_id values are:
+ * * ENDPOINT_PORT_0_I2S
+ * * ENDPOINT_PORT_1_I2S_PCM
+ * * ENDPOINT_BT_SCO_INOUT
+ * * ENDPOINT_FM_RX
+ * * ENDPOINT_FM_TX
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter or unsupported configuration.
+ * -EIO if driver has not been opened.
+ * Errors from @conn_start_i2s_to_fm_rx, @conn_start_i2s_to_fm_tx, and
+ * @conn_start_pcm_to_sco.
+ */
+int cg2900_audio_start_stream(unsigned int session,
+ enum cg2900_audio_endpoint_id ep_1,
+ enum cg2900_audio_endpoint_id ep_2,
+ unsigned int *stream_handle)
+{
+ int err;
+ struct audio_user *audio_user;
+
+ CG2900_INFO("cg2900_audio_start_stream");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ audio_user = get_session_user(session);
+ if (!audio_user)
+ return -EINVAL;
+
+ /* put digital interface in ep_1 to simplify comparison below */
+ if (!is_dai_port(ep_1)) {
+ /* Swap endpoints */
+ enum cg2900_audio_endpoint_id t = ep_1;
+ ep_1 = ep_2;
+ ep_2 = t;
+ }
+
+ if (ep_1 == ENDPOINT_PORT_1_I2S_PCM && ep_2 == ENDPOINT_BT_SCO_INOUT) {
+ err = conn_start_pcm_to_sco(audio_user, stream_handle);
+ } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_RX) {
+ err = conn_start_i2s_to_fm_rx(audio_user, stream_handle);
+ } else if (ep_1 == ENDPOINT_PORT_0_I2S && ep_2 == ENDPOINT_FM_TX) {
+ err = conn_start_i2s_to_fm_tx(audio_user, stream_handle);
+ } else {
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ err = -EINVAL;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_start_stream);
+
+/**
+ * cg2900_audio_stop_stream() - Stops a stream and disconnects the endpoints.
+ * @session: Session identifier this call is related to.
+ * @stream_handle: Handle to the stream to stop.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL upon bad input parameter.
+ * -EIO if driver has not been opened.
+ */
+int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle)
+{
+ struct audio_user *audio_user;
+
+ CG2900_INFO("cg2900_audio_stop_stream");
+
+ if (audio_info->state != OPENED) {
+ CG2900_ERR("Audio driver not open");
+ return -EIO;
+ }
+
+ audio_user = get_session_user(session);
+ if (!audio_user)
+ return -EINVAL;
+
+ return conn_stop_stream(audio_user, stream_handle);
+}
+EXPORT_SYMBOL(cg2900_audio_stop_stream);
+
+/**
+ * audio_dev_open() - Open char device.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation failed.
+ * Errors from @cg2900_audio_open.
+ */
+static int audio_dev_open(struct inode *inode, struct file *filp)
+{
+ int err;
+ struct char_dev_info *char_dev_info;
+
+ CG2900_INFO("CG2900 Audio: audio_dev_open");
+
+ /*
+ * Allocate the char dev info structure. It will be stored inside
+ * the file pointer and supplied when file_ops are called.
+ * It's free'd in audio_dev_release.
+ */
+ char_dev_info = kzalloc(sizeof(*char_dev_info), GFP_KERNEL);
+ if (!char_dev_info) {
+ CG2900_ERR("Couldn't allocate char_dev_info");
+ return -ENOMEM;
+ }
+ filp->private_data = char_dev_info;
+
+ mutex_init(&char_dev_info->management_mutex);
+ mutex_init(&char_dev_info->rw_mutex);
+
+ mutex_lock(&char_dev_info->management_mutex);
+ err = cg2900_audio_open(&char_dev_info->session);
+ mutex_unlock(&char_dev_info->management_mutex);
+ if (err) {
+ CG2900_ERR("Failed to open CG2900 Audio driver (%d)", err);
+ goto error_handling_free_mem;
+ }
+
+ return 0;
+
+error_handling_free_mem:
+ kfree(char_dev_info);
+ filp->private_data = NULL;
+ return err;
+}
+
+/**
+ * audio_dev_release() - Release char device.
+ * @inode: Device driver information.
+ * @filp: Pointer to the file struct.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EBADF if NULL pointer was supplied in private data.
+ * Errors from @cg2900_audio_close.
+ */
+static int audio_dev_release(struct inode *inode, struct file *filp)
+{
+ int err = 0;
+ struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+
+ CG2900_INFO("CG2900 Audio: audio_dev_release");
+
+ if (!dev) {
+ CG2900_ERR("No dev supplied in private data");
+ return -EBADF;
+ }
+
+ mutex_lock(&dev->management_mutex);
+ err = cg2900_audio_close(&dev->session);
+ if (err)
+ /*
+ * Just print the error. Still free the char_dev_info since we
+ * don't know the filp structure is valid after this call
+ */
+ CG2900_ERR("Error when closing CG2900 audio driver (%d)", err);
+
+ mutex_unlock(&dev->management_mutex);
+
+ kfree(dev);
+ filp->private_data = NULL;
+
+ return err;
+}
+
+/**
+ * audio_dev_read() - Return information to the user from last @write call.
+ * @filp: Pointer to the file struct.
+ * @buf: Received buffer.
+ * @count: Size of buffer.
+ * @f_pos: Position in buffer.
+ *
+ * The audio_dev_read() function returns information from
+ * the last @write call to same char device.
+ * The data is in the following format:
+ * * OpCode of command for this data
+ * * Data content (Length of data is determined by the command OpCode, i.e.
+ * fixed for each command)
+ *
+ * Returns:
+ * Bytes successfully read (could be 0).
+ * -EBADF if NULL pointer was supplied in private data.
+ * -EFAULT if copy_to_user fails.
+ * -ENOMEM upon allocation failure.
+ */
+static ssize_t audio_dev_read(struct file *filp, char __user *buf,
size_t count,
+ loff_t *f_pos)
+{
+ struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+ unsigned int bytes_to_copy = 0;
+ int err = 0;
+
+ CG2900_INFO("CG2900 Audio: audio_dev_read");
+
+ if (!dev) {
+ CG2900_ERR("No dev supplied in private data");
+ return -EBADF;
+ }
+ mutex_lock(&dev->rw_mutex);
+
+ if (dev->stored_data_len == 0) {
+ /* No data to read */
+ bytes_to_copy = 0;
+ goto finished;
+ }
+
+ bytes_to_copy = min(count, (unsigned int)(dev->stored_data_len));
+ if (bytes_to_copy < dev->stored_data_len)
+ CG2900_ERR("Not enough buffer to store all data. Throwing away "
+ "rest of data. Saved len: %d, stored_len: %d",
+ count, dev->stored_data_len);
+
+ err = copy_to_user(buf, dev->stored_data, bytes_to_copy);
+ /*
+ * Throw away all data, even though not all was copied.
+ * This char device is primarily for testing purposes so we can keep
+ * such a limitation.
+ */
+ kfree(dev->stored_data);
+ dev->stored_data = NULL;
+ dev->stored_data_len = 0;
+
+ if (err) {
+ CG2900_ERR("copy_to_user error %d", err);
+ err = -EFAULT;
+ goto error_handling;
+ }
+
+ goto finished;
+
+error_handling:
+ mutex_unlock(&dev->rw_mutex);
+ return (ssize_t)err;
+finished:
+ mutex_unlock(&dev->rw_mutex);
+ return bytes_to_copy;
+}
+
+/**
+ * audio_dev_write() - Call CG2900 Audio API function.
+ * @filp: Pointer to the file struct.
+ * @buf: Write buffer.
+ * @count: Size of the buffer write.
+ * @f_pos: Position of buffer.
+ *
+ * audio_dev_write() function executes supplied data and
+ * interprets it as if it was a function call to the CG2900 Audio API.
+ * The data is according to:
+ * * OpCode (4 bytes)
+ * * Data according to OpCode (see API). No padding between parameters
+ *
+ * OpCodes are:
+ * * OP_CODE_SET_DAI_CONF 0x00000001
+ * * OP_CODE_GET_DAI_CONF 0x00000002
+ * * OP_CODE_CONFIGURE_ENDPOINT 0x00000003
+ * * OP_CODE_START_STREAM 0x00000004
+ * * OP_CODE_STOP_STREAM 0x00000005
+ *
+ * Returns:
+ * Bytes successfully written (could be 0). Equals input @count if
successful.
+ * -EBADF if NULL pointer was supplied in private data.
+ * -EFAULT if copy_from_user fails.
+ * Error codes from all CG2900 Audio API functions.
+ */
+static ssize_t audio_dev_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *f_pos)
+{
+ u8 *rec_data;
+ struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+ int err = 0;
+ int op_code = 0;
+ u8 *curr_data;
+ unsigned int stream_handle;
+ struct cg2900_dai_config dai_config;
+ struct cg2900_endpoint_config ep_config;
+ enum cg2900_audio_endpoint_id ep_1;
+ enum cg2900_audio_endpoint_id ep_2;
+ int bytes_left = count;
+
+ CG2900_INFO("CG2900 Audio: audio_dev_write count %d", count);
+
+ if (!dev) {
+ CG2900_ERR("No dev supplied in private data");
+ return -EBADF;
+ }
+
+ rec_data = kmalloc(count, GFP_KERNEL);
+ if (!rec_data) {
+ CG2900_ERR("kmalloc failed");
+ return -ENOMEM;
+ }
+
+ mutex_lock(&dev->rw_mutex);
+
+ err = copy_from_user(rec_data, buf, count);
+ if (err) {
+ CG2900_ERR("copy_from_user failed (%d)", err);
+ err = -EFAULT;
+ goto finished_mutex_unlock;
+ }
+
+ /* Initialize temporary data pointer used to traverse the packet */
+ curr_data = rec_data;
+
+ op_code = curr_data[0];
+ CG2900_DBG("op_code %d", op_code);
+ /* OpCode is int size to keep data int aligned */
+ curr_data += sizeof(unsigned int);
+ bytes_left -= sizeof(unsigned int);
+
+ switch (op_code) {
+ case OP_CODE_SET_DAI_CONF:
+ CG2900_DBG("OP_CODE_SET_DAI_CONF %d", sizeof(dai_config));
+ if (bytes_left < sizeof(dai_config)) {
+ CG2900_ERR("Not enough data supplied for "
+ "OP_CODE_SET_DAI_CONF");
+ err = -EINVAL;
+ goto finished_mutex_unlock;
+ }
+ memcpy(&dai_config, curr_data, sizeof(dai_config));
+ CG2900_DBG("dai_config.port %d", dai_config.port);
+ err = cg2900_audio_set_dai_config(dev->session, &dai_config);
+ break;
+
+ case OP_CODE_GET_DAI_CONF:
+ CG2900_DBG("OP_CODE_GET_DAI_CONF %d", sizeof(dai_config));
+ if (bytes_left < sizeof(dai_config)) {
+ CG2900_ERR("Not enough data supplied for "
+ "OP_CODE_GET_DAI_CONF");
+ err = -EINVAL;
+ goto finished_mutex_unlock;
+ }
+ /*
+ * Only need to copy the port really, but let's copy
+ * like this for simplicity. It's only test functionality
+ * after all.
+ */
+ memcpy(&dai_config, curr_data, sizeof(dai_config));
+ CG2900_DBG("dai_config.port %d", dai_config.port);
+ err = cg2900_audio_get_dai_config(dev->session, &dai_config);
+ if (!err) {
+ /*
+ * Command succeeded. Store data so it can be returned
+ * when calling read.
+ */
+ if (dev->stored_data) {
+ CG2900_ERR("Data already allocated (%d bytes). "
+ "Throwing it away.",
+ dev->stored_data_len);
+ kfree(dev->stored_data);
+ }
+ dev->stored_data_len = sizeof(op_code) +
+ sizeof(dai_config);
+ dev->stored_data = kmalloc(dev->stored_data_len,
+ GFP_KERNEL);
+ if (dev->stored_data) {
+ memcpy(dev->stored_data, &op_code,
+ sizeof(op_code));
+ memcpy(&(dev->stored_data[sizeof(op_code)]),
+ &dai_config, sizeof(dai_config));
+ }
+ }
+ break;
+
+ case OP_CODE_CONFIGURE_ENDPOINT:
+ CG2900_DBG("OP_CODE_CONFIGURE_ENDPOINT %d", sizeof(ep_config));
+ if (bytes_left < sizeof(ep_config)) {
+ CG2900_ERR("Not enough data supplied for "
+ "OP_CODE_CONFIGURE_ENDPOINT");
+ err = -EINVAL;
+ goto finished_mutex_unlock;
+ }
+ memcpy(&ep_config, curr_data, sizeof(ep_config));
+ CG2900_DBG("ep_config.endpoint_id %d", ep_config.endpoint_id);
+ err = cg2900_audio_config_endpoint(dev->session, &ep_config);
+ break;
+
+ case OP_CODE_START_STREAM:
+ CG2900_DBG("OP_CODE_START_STREAM %d",
+ (sizeof(ep_1) + sizeof(ep_2)));
+ if (bytes_left < (sizeof(ep_1) + sizeof(ep_2))) {
+ CG2900_ERR("Not enough data supplied for "
+ "OP_CODE_START_STREAM");
+ err = -EINVAL;
+ goto finished_mutex_unlock;
+ }
+ memcpy(&ep_1, curr_data, sizeof(ep_1));
+ curr_data += sizeof(ep_1);
+ memcpy(&ep_2, curr_data, sizeof(ep_2));
+ CG2900_DBG("ep_1 %d ep_2 %d", ep_1,
+ ep_2);
+
+ err = cg2900_audio_start_stream(dev->session,
+ ep_1, ep_2, &stream_handle);
+ if (!err) {
+ /*
+ * Command succeeded. Store data so it can be returned
+ * when calling read.
+ */
+ if (dev->stored_data) {
+ CG2900_ERR("Data already allocated (%d bytes). "
+ "Throwing it away.",
+ dev->stored_data_len);
+ kfree(dev->stored_data);
+ }
+ dev->stored_data_len = sizeof(op_code) +
+ sizeof(stream_handle);
+ dev->stored_data = kmalloc(dev->stored_data_len,
+ GFP_KERNEL);
+ if (dev->stored_data) {
+ memcpy(dev->stored_data, &op_code,
+ sizeof(op_code));
+ memcpy(&(dev->stored_data[sizeof(op_code)]),
+ &stream_handle, sizeof(stream_handle));
+ }
+ CG2900_DBG("stream_handle %d", stream_handle);
+ }
+ break;
+
+ case OP_CODE_STOP_STREAM:
+ if (bytes_left < sizeof(stream_handle)) {
+ CG2900_ERR("Not enough data supplied for "
+ "OP_CODE_STOP_STREAM");
+ err = -EINVAL;
+ goto finished_mutex_unlock;
+ }
+ CG2900_DBG("OP_CODE_STOP_STREAM %d", sizeof(stream_handle));
+ memcpy(&stream_handle, curr_data, sizeof(stream_handle));
+ CG2900_DBG("stream_handle %d", stream_handle);
+ err = cg2900_audio_stop_stream(dev->session, stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Received bad op_code %d", op_code);
+ break;
+ };
+
+finished_mutex_unlock:
+ kfree(rec_data);
+ mutex_unlock(&dev->rw_mutex);
+
+ if (err)
+ return err;
+ else
+ return count;
+}
+
+/**
+ * audio_dev_poll() - Handle POLL call to the interface.
+ * @filp: Pointer to the file struct.
+ * @wait: Poll table supplied to caller.
+ *
+ * This function is used by the User Space application to see if the device is
+ * still open and if there is any data available for reading.
+ *
+ * Returns:
+ * Mask of current set POLL values.
+ */
+static unsigned int audio_dev_poll(struct file *filp, poll_table *wait)
+{
+ struct char_dev_info *dev = (struct char_dev_info *)filp->private_data;
+ unsigned int mask = 0;
+
+ if (!dev) {
+ CG2900_ERR("No dev supplied in private data");
+ return POLLERR | POLLRDHUP;
+ }
+
+ if (RESET == audio_info->state)
+ mask |= POLLERR | POLLRDHUP | POLLPRI;
+ else
+ /* Unless RESET we can transmit */
+ mask |= POLLOUT;
+
+ if (dev->stored_data)
+ mask |= POLLIN | POLLRDNORM;
+
+ return mask;
+}
+
+static const struct file_operations char_dev_fops = {
+ .open = audio_dev_open,
+ .release = audio_dev_release,
+ .read = audio_dev_read,
+ .write = audio_dev_write,
+ .poll = audio_dev_poll
+};
+
+/*
+ * Module related methods
+ */
+
+/**
+ * cg2900_audio_probe() - Initialize CG2900 audio resources.
+ * @pdev: Platform device.
+ *
+ * Initialize the module and register misc device.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation fails.
+ * -EEXIST if device has already been started.
+ * Error codes from misc_register.
+ */
+static int __devinit cg2900_audio_probe(struct platform_device *pdev)
+{
+ int err;
+
+ CG2900_INFO("cg2900_audio_probe");
+
+ if (audio_info) {
+ CG2900_ERR("ST-Ericsson CG2900 Audio driver already initiated");
+ return -EEXIST;
+ }
+
+ /* Initialize private data. */
+ audio_info = kzalloc(sizeof(*audio_info), GFP_KERNEL);
+ if (!audio_info) {
+ CG2900_ERR("Could not alloc audio_info struct.");
+ return -ENOMEM;
+ }
+
+ /* Initiate the mutexes */
+ mutex_init(&(audio_info->management_mutex));
+ mutex_init(&(audio_info->bt_mutex));
+ mutex_init(&(audio_info->fm_mutex));
+ mutex_init(&(audio_info->endpoints.management_mutex));
+
+ /* Initiate the SKB queues */
+ skb_queue_head_init(&(audio_info->bt_queue));
+ skb_queue_head_init(&(audio_info->fm_queue));
+
+ /* Initiate the endpoint list */
+ INIT_LIST_HEAD(&(audio_info->endpoints.ep_list));
+
+ /* Prepare and register MISC device */
+ audio_info->dev.minor = MISC_DYNAMIC_MINOR;
+ audio_info->dev.name = DEVICE_NAME;
+ audio_info->dev.fops = &char_dev_fops;
+ audio_info->dev.parent = &(pdev->dev);
+
+ err = misc_register(&(audio_info->dev));
+ if (err) {
+ CG2900_ERR("Error %d registering misc dev!", err);
+ goto error_handling;
+ }
+
+ return 0;
+
+error_handling:
+ mutex_destroy(&audio_info->management_mutex);
+ mutex_destroy(&audio_info->bt_mutex);
+ mutex_destroy(&audio_info->fm_mutex);
+ mutex_destroy(&audio_info->endpoints.management_mutex);
+ kfree(audio_info);
+ audio_info = NULL;
+ return err;
+}
+
+/**
+ * cg2900_audio_remove() - Release CG2900 audio resources.
+ * @pdev: Platform device.
+ *
+ * Remove misc device and free resources.
+ *
+ * Returns:
+ * 0 if success.
+ * Error codes from misc_deregister.
+ */
+static int __devexit cg2900_audio_remove(struct platform_device *pdev)
+{
+ int err;
+
+ CG2900_INFO("cg2900_audio_remove");
+
+ if (!audio_info)
+ return 0;
+
+ err = misc_deregister(&audio_info->dev);
+ if (err)
+ CG2900_ERR("Error deregistering misc dev (%d)!", err);
+
+ mutex_destroy(&audio_info->management_mutex);
+ mutex_destroy(&audio_info->bt_mutex);
+ mutex_destroy(&audio_info->fm_mutex);
+
+ flush_endpoint_list(&(audio_info->endpoints));
+
+ skb_queue_purge(&(audio_info->bt_queue));
+ skb_queue_purge(&(audio_info->fm_queue));
+
+ mutex_destroy(&audio_info->endpoints.management_mutex);
+
+ kfree(audio_info);
+ audio_info = NULL;
+ return err;
+}
+
+static struct platform_driver cg2900_audio_driver = {
+ .driver = {
+ .name = "cg2900-audio",
+ .owner = THIS_MODULE,
+ },
+ .probe = cg2900_audio_probe,
+ .remove = __devexit_p(cg2900_audio_remove),
+};
+
+/**
+ * cg2900_audio_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_audio_init(void)
+{
+ CG2900_INFO("cg2900_audio_init");
+ return platform_driver_register(&cg2900_audio_driver);
+}
+
+/**
+ * cg2900_audio_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_audio_exit(void)
+{
+ CG2900_INFO("cg2900_audio_exit");
+ platform_driver_unregister(&cg2900_audio_driver);
+}
+
+module_init(cg2900_audio_init);
+module_exit(cg2900_audio_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_AUTHOR("Kjell Andersson ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux Bluetooth Audio ST-Ericsson controller");
diff --git a/include/linux/mfd/cg2900_audio.h b/include/linux/mfd/cg2900_audio.h
new file mode 100644
index 0000000..44ded01
--- /dev/null
+++ b/include/linux/mfd/cg2900_audio.h
@@ -0,0 +1,460 @@
+/*
+ * include/linux/mfd/cg2900_audio.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@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 Audio Driver for ST-Ericsson controller.
+ */
+
+#ifndef _CG2900_AUDIO_H_
+#define _CG2900_AUDIO_H_
+
+#include <linux/types.h>
+
+/*
+ * Digital Audio Interface configuration types
+ */
+
+/** CG2900_A2DP_MAX_AVDTP_HDR_LEN - Max length of a AVDTP header.
+ * Max length of a AVDTP header for an A2DP packet.
+ */
+#define CG2900_A2DP_MAX_AVDTP_HDR_LEN 25
+
+/**
+ * enum cg2900_dai_dir - Contains the DAI port directions alternatives.
+ * @DAI_DIR_B_RX_A_TX: Port B as Rx and port A as Tx.
+ * @DAI_DIR_B_TX_A_RX: Port B as Tx and port A as Rx.
+ */
+enum cg2900_dai_dir {
+ DAI_DIR_B_RX_A_TX = 0x00,
+ DAI_DIR_B_TX_A_RX = 0x01
+};
+
+/**
+ * enum cg2900_dai_mode - DAI mode alternatives.
+ * @DAI_MODE_SLAVE: Slave.
+ * @DAI_MODE_MASTER: Master.
+ */
+enum cg2900_dai_mode {
+ DAI_MODE_SLAVE = 0x00,
+ DAI_MODE_MASTER = 0x01
+};
+
+/**
+ * enum cg2900_dai_stream_ratio - Voice stream ratio alternatives.
+ * @STREAM_RATIO_FM16_VOICE16: FM 16kHz, Voice 16kHz.
+ * @STREAM_RATIO_FM16_VOICE8: FM 16kHz, Voice 8kHz.
+ * @STREAM_RATIO_FM48_VOICE16: FM 48kHz, Voice 16Khz.
+ * @STREAM_RATIO_FM48_VOICE8: FM 48kHz, Voice 8kHz.
+ *
+ * Contains the alternatives for the voice stream ratio between the
Audio stream
+ * sample rate and the Voice stream sample rate.
+ */
+enum cg2900_dai_stream_ratio {
+ STREAM_RATIO_FM16_VOICE16 = 0x01,
+ STREAM_RATIO_FM16_VOICE8 = 0x02,
+ STREAM_RATIO_FM48_VOICE16 = 0x03,
+ STREAM_RATIO_FM48_VOICE8 = 0x06
+};
+
+/**
+ * enum cg2900_dai_fs_duration - Frame sync duration alternatives.
+ * @SYNC_DURATION_8: 8 frames sync duration.
+ * @SYNC_DURATION_16: 16 frames sync duration.
+ * @SYNC_DURATION_24: 24 frames sync duration.
+ * @SYNC_DURATION_32: 32 frames sync duration.
+ * @SYNC_DURATION_48: 48 frames sync duration.
+ * @SYNC_DURATION_50: 50 frames sync duration.
+ * @SYNC_DURATION_64: 64 frames sync duration.
+ * @SYNC_DURATION_75: 75 frames sync duration.
+ * @SYNC_DURATION_96: 96 frames sync duration.
+ * @SYNC_DURATION_125: 125 frames sync duration.
+ * @SYNC_DURATION_128: 128 frames sync duration.
+ * @SYNC_DURATION_150: 150 frames sync duration.
+ * @SYNC_DURATION_192: 192 frames sync duration.
+ * @SYNC_DURATION_250: 250 frames sync duration.
+ * @SYNC_DURATION_256: 256 frames sync duration.
+ * @SYNC_DURATION_300: 300 frames sync duration.
+ * @SYNC_DURATION_384: 384 frames sync duration.
+ * @SYNC_DURATION_500: 500 frames sync duration.
+ * @SYNC_DURATION_512: 512 frames sync duration.
+ * @SYNC_DURATION_600: 600 frames sync duration.
+ * @SYNC_DURATION_768: 768 frames sync duration.
+ *
+ * This parameter sets the PCM frame sync duration. It is calculated as the
+ * ratio between the bit clock and the frame rate. For example, if the bit
+ * clock is 512 kHz and the stream sample rate is 8 kHz, the PCM frame sync
+ * duration is 512 / 8 = 64.
+ */
+enum cg2900_dai_fs_duration {
+ SYNC_DURATION_8 = 0,
+ SYNC_DURATION_16 = 1,
+ SYNC_DURATION_24 = 2,
+ SYNC_DURATION_32 = 3,
+ SYNC_DURATION_48 = 4,
+ SYNC_DURATION_50 = 5,
+ SYNC_DURATION_64 = 6,
+ SYNC_DURATION_75 = 7,
+ SYNC_DURATION_96 = 8,
+ SYNC_DURATION_125 = 9,
+ SYNC_DURATION_128 = 10,
+ SYNC_DURATION_150 = 11,
+ SYNC_DURATION_192 = 12,
+ SYNC_DURATION_250 = 13,
+ SYNC_DURATION_256 = 14,
+ SYNC_DURATION_300 = 15,
+ SYNC_DURATION_384 = 16,
+ SYNC_DURATION_500 = 17,
+ SYNC_DURATION_512 = 18,
+ SYNC_DURATION_600 = 19,
+ SYNC_DURATION_768 = 20
+};
+
+/**
+ * enum cg2900_dai_bit_clk - Bit Clock alternatives.
+ * @BIT_CLK_128: 128 Kbits clock.
+ * @BIT_CLK_256: 256 Kbits clock.
+ * @BIT_CLK_512: 512 Kbits clock.
+ * @BIT_CLK_768: 768 Kbits clock.
+ * @BIT_CLK_1024: 1024 Kbits clock.
+ * @BIT_CLK_1411_76: 1411.76 Kbits clock.
+ * @BIT_CLK_1536: 1536 Kbits clock.
+ * @BIT_CLK_2000: 2000 Kbits clock.
+ * @BIT_CLK_2048: 2048 Kbits clock.
+ * @BIT_CLK_2400: 2400 Kbits clock.
+ * @BIT_CLK_2823_52: 2823.52 Kbits clock.
+ * @BIT_CLK_3072: 3072 Kbits clock.
+ *
+ * This parameter sets the bit clock speed. This is the clocking of the actual
+ * data. A usual parameter for eSCO voice is 512 kHz.
+ */
+enum cg2900_dai_bit_clk {
+ BIT_CLK_128 = 0x00,
+ BIT_CLK_256 = 0x01,
+ BIT_CLK_512 = 0x02,
+ BIT_CLK_768 = 0x03,
+ BIT_CLK_1024 = 0x04,
+ BIT_CLK_1411_76 = 0x05,
+ BIT_CLK_1536 = 0x06,
+ BIT_CLK_2000 = 0x07,
+ BIT_CLK_2048 = 0x08,
+ BIT_CLK_2400 = 0x09,
+ BIT_CLK_2823_52 = 0x0A,
+ BIT_CLK_3072 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_sample_rate - Sample rates alternatives.
+ * @SAMPLE_RATE_8: 8 kHz sample rate.
+ * @SAMPLE_RATE_16: 16 kHz sample rate.
+ * @SAMPLE_RATE_44_1: 44.1 kHz sample rate.
+ * @SAMPLE_RATE_48: 48 kHz sample rate.
+ */
+enum cg2900_dai_sample_rate {
+ SAMPLE_RATE_8 = 0,
+ SAMPLE_RATE_16 = 1,
+ SAMPLE_RATE_44_1 = 2,
+ SAMPLE_RATE_48 = 3
+};
+
+/**
+ * enum cg2900_dai_port_protocol - Port protocol alternatives.
+ * @PORT_PROTOCOL_PCM: Protocol PCM.
+ * @PORT_PROTOCOL_I2S: Protocol I2S.
+ */
+enum cg2900_dai_port_protocol {
+ PORT_PROTOCOL_PCM = 0x00,
+ PORT_PROTOCOL_I2S = 0x01
+};
+
+/**
+ * enum cg2900_dai_channel_sel - The channel selection alternatives.
+ * @CHANNEL_SELECTION_RIGHT: Right channel used.
+ * @CHANNEL_SELECTION_LEFT: Left channel used.
+ * @CHANNEL_SELECTION_BOTH: Both channels used.
+ */
+enum cg2900_dai_channel_sel {
+ CHANNEL_SELECTION_RIGHT = 0x00,
+ CHANNEL_SELECTION_LEFT = 0x01,
+ CHANNEL_SELECTION_BOTH = 0x02
+};
+
+/**
+ * struct cg2900_dai_conf_i2s_pcm - Port configuration structure.
+ * @mode: Operational mode of the port configured.
+ * @i2s_channel_sel: I2S channels used. Only valid if used in I2S mode.
+ * @slot_0_used: True if SCO slot 0 is used.
+ * @slot_1_used: True if SCO slot 1 is used.
+ * @slot_2_used: True if SCO slot 2 is used.
+ * @slot_3_used: True if SCO slot 3 is used.
+ * @slot_0_dir: Direction of slot 0.
+ * @slot_1_dir: Direction of slot 1.
+ * @slot_2_dir: Direction of slot 2.
+ * @slot_3_dir: Direction of slot 3.
+ * @slot_0_start: Slot 0 start (relative to the PCM frame sync).
+ * @slot_1_start: Slot 1 start (relative to the PCM frame sync)
+ * @slot_2_start: Slot 2 start (relative to the PCM frame sync)
+ * @slot_3_start: Slot 3 start (relative to the PCM frame sync)
+ * @ratio: Voice stream ratio between the Audio stream sample rate
+ * and the Voice stream sample rate.
+ * @protocol: Protocol used on port.
+ * @duration: Frame sync duration.
+ * @clk: Bit clock.
+ * @sample_rate: Sample rate.
+ */
+struct cg2900_dai_conf_i2s_pcm {
+ enum cg2900_dai_mode mode;
+ enum cg2900_dai_channel_sel i2s_channel_sel;
+ bool slot_0_used;
+ bool slot_1_used;
+ bool slot_2_used;
+ bool slot_3_used;
+ enum cg2900_dai_dir slot_0_dir;
+ enum cg2900_dai_dir slot_1_dir;
+ enum cg2900_dai_dir slot_2_dir;
+ enum cg2900_dai_dir slot_3_dir;
+ __u8 slot_0_start;
+ __u8 slot_1_start;
+ __u8 slot_2_start;
+ __u8 slot_3_start;
+ enum cg2900_dai_stream_ratio ratio;
+ enum cg2900_dai_port_protocol protocol;
+ enum cg2900_dai_fs_duration duration;
+ enum cg2900_dai_bit_clk clk;
+ enum cg2900_dai_sample_rate sample_rate;
+};
+
+/**
+ * enum cg2900_dai_half_period - Half period duration alternatives.
+ * @HALF_PER_DUR_8: 8 Bits.
+ * @HALF_PER_DUR_16: 16 Bits.
+ * @HALF_PER_DUR_24: 24 Bits.
+ * @HALF_PER_DUR_25: 25 Bits.
+ * @HALF_PER_DUR_32: 32 Bits.
+ * @HALF_PER_DUR_48: 48 Bits.
+ * @HALF_PER_DUR_64: 64 Bits.
+ * @HALF_PER_DUR_75: 75 Bits.
+ * @HALF_PER_DUR_96: 96 Bits.
+ * @HALF_PER_DUR_128: 128 Bits.
+ * @HALF_PER_DUR_150: 150 Bits.
+ * @HALF_PER_DUR_192: 192 Bits.
+ *
+ * This parameter sets the number of bits contained in each I2S half period,
+ * i.e. each channel slot. A usual value is 16 bits.
+ */
+enum cg2900_dai_half_period {
+ HALF_PER_DUR_8 = 0x00,
+ HALF_PER_DUR_16 = 0x01,
+ HALF_PER_DUR_24 = 0x02,
+ HALF_PER_DUR_25 = 0x03,
+ HALF_PER_DUR_32 = 0x04,
+ HALF_PER_DUR_48 = 0x05,
+ HALF_PER_DUR_64 = 0x06,
+ HALF_PER_DUR_75 = 0x07,
+ HALF_PER_DUR_96 = 0x08,
+ HALF_PER_DUR_128 = 0x09,
+ HALF_PER_DUR_150 = 0x0A,
+ HALF_PER_DUR_192 = 0x0B
+};
+
+/**
+ * enum cg2900_dai_word_width - Word width alternatives.
+ * @WORD_WIDTH_16: 16 bits words.
+ * @WORD_WIDTH_32: 32 bits words.
+ */
+enum cg2900_dai_word_width {
+ WORD_WIDTH_16 = 0x00,
+ WORD_WIDTH_32 = 0x01
+};
+
+/**
+ * struct cg2900_dai_conf_i2s - Port configuration struct for I2S.
+ * @mode: Operational mode of the port.
+ * @half_period: Half period duration.
+ * @channel_sel: Channel selection.
+ * @sample_rate: Sample rate.
+ * @word_width: Word width.
+ */
+struct cg2900_dai_conf_i2s {
+ enum cg2900_dai_mode mode;
+ enum cg2900_dai_half_period half_period;
+ enum cg2900_dai_channel_sel channel_sel;
+ enum cg2900_dai_sample_rate sample_rate;
+ enum cg2900_dai_word_width word_width;
+};
+
+/**
+ * union cg2900_dai_port_conf - DAI port configuration union.
+ * @i2s: The configuration struct for a port supporting only I2S.
+ * @i2s_pcm: The configuration struct for a port supporting both PCM and I2S.
+ */
+union cg2900_dai_port_conf {
+ struct cg2900_dai_conf_i2s i2s;
+ struct cg2900_dai_conf_i2s_pcm i2s_pcm;
+};
+
+/**
+ * enum cg2900_dai_ext_port_id - DAI external port id alternatives.
+ * @PORT_0_I2S: Port id is 0 and it supports only I2S.
+ * @PORT_1_I2S_PCM: Port id is 1 and it supports both I2S and PCM.
+ */
+enum cg2900_dai_ext_port_id {
+ PORT_0_I2S,
+ PORT_1_I2S_PCM
+};
+
+/**
+ * enum cg2900_audio_endpoint_id - Audio endpoint id alternatives.
+ * @ENDPOINT_PORT_0_I2S: Internal audio endpoint of the external I2S
+ * interface.
+ * @ENDPOINT_PORT_1_I2S_PCM: Internal audio endpoint of the external I2S/PCM
+ * interface.
+ * @ENDPOINT_SLIMBUS_VOICE: Internal audio endpoint of the external Slimbus
+ * voice interface. (Currently not supported)
+ * @ENDPOINT_SLIMBUS_AUDIO: Internal audio endpoint of the external Slimbus
+ * audio interface. (Currently not supported)
+ * @ENDPOINT_BT_SCO_INOUT: Bluetooth SCO bidirectional.
+ * @ENDPOINT_BT_A2DP_SRC: Bluetooth A2DP source.
+ * @ENDPOINT_BT_A2DP_SNK: Bluetooth A2DP sink.
+ * @ENDPOINT_FM_RX: FM receive.
+ * @ENDPOINT_FM_TX: FM transmit.
+ * @ENDPOINT_ANALOG_OUT: Analog out.
+ * @ENDPOINT_DSP_AUDIO_IN: DSP audio in.
+ * @ENDPOINT_DSP_AUDIO_OUT: DSP audio out.
+ * @ENDPOINT_DSP_VOICE_IN: DSP voice in.
+ * @ENDPOINT_DSP_VOICE_OUT: DSP voice out.
+ * @ENDPOINT_DSP_TONE_IN: DSP tone in.
+ * @ENDPOINT_BURST_BUFFER_IN: Burst buffer in.
+ * @ENDPOINT_BURST_BUFFER_OUT: Burst buffer out.
+ * @ENDPOINT_MUSIC_DECODER: Music decoder.
+ * @ENDPOINT_HCI_AUDIO_IN: HCI audio in.
+ */
+enum cg2900_audio_endpoint_id {
+ ENDPOINT_PORT_0_I2S,
+ ENDPOINT_PORT_1_I2S_PCM,
+ ENDPOINT_SLIMBUS_VOICE,
+ ENDPOINT_SLIMBUS_AUDIO,
+ ENDPOINT_BT_SCO_INOUT,
+ ENDPOINT_BT_A2DP_SRC,
+ ENDPOINT_BT_A2DP_SNK,
+ ENDPOINT_FM_RX,
+ ENDPOINT_FM_TX,
+ ENDPOINT_ANALOG_OUT,
+ ENDPOINT_DSP_AUDIO_IN,
+ ENDPOINT_DSP_AUDIO_OUT,
+ ENDPOINT_DSP_VOICE_IN,
+ ENDPOINT_DSP_VOICE_OUT,
+ ENDPOINT_DSP_TONE_IN,
+ ENDPOINT_BURST_BUFFER_IN,
+ ENDPOINT_BURST_BUFFER_OUT,
+ ENDPOINT_MUSIC_DECODER,
+ ENDPOINT_HCI_AUDIO_IN
+};
+
+/**
+ * struct cg2900_dai_config - Configuration struct for Digital Audio Interface.
+ * @port: The port id to configure. Acts as a discriminator for @conf parameter
+ * which is a union.
+ * @conf: The configuration union that contains the parameters for the port.
+ */
+struct cg2900_dai_config {
+ enum cg2900_dai_ext_port_id port;
+ union cg2900_dai_port_conf conf;
+};
+
+/*
+ * Endpoint configuration types
+ */
+
+/**
+ * enum cg2900_endpoint_sample_rate - Audio endpoint configuration
sample rate alternatives.
+ *
+ * This enum defines the same values as @cg2900_dai_sample_rate, but
+ * is kept to preserve the API.
+ *
+ * @ENDPOINT_SAMPLE_RATE_8_KHZ: 8 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_16_KHZ: 16 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_44_1_KHZ: 44.1 kHz sample rate.
+ * @ENDPOINT_SAMPLE_RATE_48_KHZ: 48 kHz sample rate.
+ */
+enum cg2900_endpoint_sample_rate {
+ ENDPOINT_SAMPLE_RATE_8_KHZ = SAMPLE_RATE_8,
+ ENDPOINT_SAMPLE_RATE_16_KHZ = SAMPLE_RATE_16,
+ ENDPOINT_SAMPLE_RATE_44_1_KHZ = SAMPLE_RATE_44_1,
+ ENDPOINT_SAMPLE_RATE_48_KHZ = SAMPLE_RATE_48
+};
+
+
+/**
+ * struct cg2900_endpoint_config_a2dp_src - A2DP source audio
endpoint configurations.
+ * @sample_rate: Sample rate.
+ * @channel_count: Number of channels.
+ */
+struct cg2900_endpoint_config_a2dp_src {
+ enum cg2900_endpoint_sample_rate sample_rate;
+ unsigned int channel_count;
+};
+
+/**
+ * struct cg2900_endpoint_config_fm - Configuration parameters for an
FM endpoint.
+ * @sample_rate: The sample rate alternatives for the FM audio endpoints.
+ */
+struct cg2900_endpoint_config_fm {
+ enum cg2900_endpoint_sample_rate sample_rate;
+};
+
+
+/**
+ * struct cg2900_endpoint_config_sco_in_out - SCO audio endpoint
configuration structure.
+ * @sample_rate: Sample rate, valid values are
+ * * ENDPOINT_SAMPLE_RATE_8_KHZ
+ * * ENDPOINT_SAMPLE_RATE_16_KHZ.
+ */
+struct cg2900_endpoint_config_sco_in_out {
+ enum cg2900_endpoint_sample_rate sample_rate;
+};
+
+/**
+ * union cg2900_endpoint_config - Different audio endpoint configurations.
+ * @sco: SCO audio endpoint configuration structure.
+ * @a2dp_src: A2DP source audio endpoint configuration structure.
+ * @fm: FM audio endpoint configuration structure.
+ */
+union cg2900_endpoint_config_union {
+ struct cg2900_endpoint_config_sco_in_out sco;
+ struct cg2900_endpoint_config_a2dp_src a2dp_src;
+ struct cg2900_endpoint_config_fm fm;
+};
+
+/**
+ * struct cg2900_endpoint_config - Audio endpoint configuration.
+ * @endpoint_id: Identifies the audio endpoint. Works as a discriminator
+ * for the config union.
+ * @config: Union holding the configuration parameters for
+ * the endpoint.
+ */
+struct cg2900_endpoint_config {
+ enum cg2900_audio_endpoint_id endpoint_id;
+ union cg2900_endpoint_config_union config;
+};
+
+int cg2900_audio_open(unsigned int *session);
+int cg2900_audio_close(unsigned int *session);
+int cg2900_audio_set_dai_config(unsigned int session,
+ struct cg2900_dai_config *config);
+int cg2900_audio_get_dai_config(unsigned int session,
+ struct cg2900_dai_config *config);
+int cg2900_audio_config_endpoint(unsigned int session,
+ struct cg2900_endpoint_config *config);
+int cg2900_audio_start_stream(unsigned int session,
+ enum cg2900_audio_endpoint_id ep_1,
+ enum cg2900_audio_endpoint_id ep_2,
+ unsigned int *stream_handle);
+int cg2900_audio_stop_stream(unsigned int session,
+ unsigned int stream_handle);
+
+#endif /* _CG2900_AUDIO_H_ */
--
1.6.3.3
^ permalink raw reply related
* [PATCH 5/9] mfd: Add UART support for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:38 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds UART support for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch registers to the TTY framework as a line discipline
driver for the N_HCI ldisc. When opened it registers as a transport
to the CG2900 framework. This patch also handles the low power
operation (suspend/resume) for the CG2900 when using UART transport.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/mfd/Kconfig | 7 +
drivers/mfd/cg2900/Makefile | 2 +
drivers/mfd/cg2900/cg2900_uart.c | 1851 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1860 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/cg2900_uart.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index a8e790f..6fcd8b6 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -282,6 +282,13 @@ config MFD_STLC2690_CHIP
help
Support for ST-Ericsson STLC2690 Connectivity Controller
+config MFD_CG2900_UART
+ tristate "Support CG2900 UART transport"
+ depends on MFD_CG2900
+ help
+ Support for UART as transport for ST-Ericsson CG2900 Connectivity
+ Controller
+
config PMIC_DA903X
bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
depends on I2C=y
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 4935daa..c8dd713 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_MFD_CG2900) += cg2900_char_devices.o
obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
obj-$(CONFIG_MFD_STLC2690_CHIP) += stlc2690_chip.o
+obj-$(CONFIG_MFD_CG2900_UART) += cg2900_uart.o
+
diff --git a/drivers/mfd/cg2900/cg2900_uart.c b/drivers/mfd/cg2900/cg2900_uart.c
new file mode 100644
index 0000000..f5e287d
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_uart.c
@@ -0,0 +1,1851 @@
+/*
+ * drivers/mfd/cg2900/cg2900_uart.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 UART Driver for ST-Ericsson CG2900 connectivity controller.
+ */
+
+#include <asm/byteorder.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/timer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/tty.h>
+#include <linux/tty_ldisc.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "cg2900_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+
+/* Workqueues' names */
+#define UART_WQ_NAME "cg2900_uart_wq"
+#define UART_NAME "cg2900_uart"
+
+/* Standardized Bluetooth command channels */
+#define HCI_BT_CMD_H4_CHANNEL 0x01
+#define HCI_BT_ACL_H4_CHANNEL 0x02
+#define HCI_BT_EVT_H4_CHANNEL 0x04
+
+/* H4 channels specific for CG2900 */
+#define HCI_FM_RADIO_H4_CHANNEL 0x08
+#define HCI_GNSS_H4_CHANNEL 0x09
+
+/* Timers used in milliseconds */
+#define UART_TX_TIMEOUT 100
+#define UART_RESP_TIMEOUT 1000
+
+/* State-setting defines */
+#define SET_BAUD_STATE(__new_state) \
+ CG2900_SET_STATE("baud_rate_state", uart_info->baud_rate_state, \
+ __new_state)
+#define SET_SLEEP_STATE(__new_state) \
+ CG2900_SET_STATE("sleep_state", uart_info->sleep_state, __new_state)
+
+/* Number of bytes to reserve at start of sk_buffer when receiving packet */
+#define RX_SKB_RESERVE 8
+/* Max size of received packet (not including reserved bytes) */
+#define RX_SKB_MAX_SIZE 1024
+
+/* Max size of bytes we can receive on the UART */
+#define UART_RECEIVE_ROOM 65536
+
+/* Size of the header in the different packets */
+#define HCI_BT_EVT_HDR_SIZE 2
+#define HCI_BT_ACL_HDR_SIZE 4
+#define HCI_FM_RADIO_HDR_SIZE 1
+#define HCI_GNSS_HDR_SIZE 3
+
+/* Position of length field in the different packets */
+#define HCI_EVT_LEN_POS 2
+#define HCI_ACL_LEN_POS 3
+#define FM_RADIO_LEN_POS 1
+#define GNSS_LEN_POS 2
+
+/* Bytes in the command Hci_Cmd_ST_Set_Uart_Baud_Rate */
+#define SET_BAUD_RATE_LSB 0x09
+#define SET_BAUD_RATE_MSB 0xFC
+#define SET_BAUD_RATE_PAYL_LEN 0x01
+#define SET_BAUD_RATE_LEN 0x04
+#define BAUD_RATE_57600 0x03
+#define BAUD_RATE_115200 0x02
+#define BAUD_RATE_230400 0x01
+#define BAUD_RATE_460800 0x00
+#define BAUD_RATE_921600 0x20
+#define BAUD_RATE_2000000 0x25
+#define BAUD_RATE_3000000 0x27
+#define BAUD_RATE_4000000 0x2B
+
+/* Baud rate defines */
+#define ZERO_BAUD_RATE 0
+#define DEFAULT_BAUD_RATE 115200
+#define HIGH_BAUD_RATE 3000000
+
+/* HCI TTY line discipline value */
+#ifndef N_HCI
+#define N_HCI 15
+#endif
+
+/* IOCTLs for UART */
+#define HCIUARTSETPROTO _IOW('U', 200, int)
+#define HCIUARTGETPROTO _IOR('U', 201, int)
+#define HCIUARTGETDEVICE _IOR('U', 202, int)
+#define HCIUARTSETFD _IOW('U', 203, int)
+
+
+/* UART break control parameters */
+#define TTY_BREAK_ON (-1)
+#define TTY_BREAK_OFF (0)
+
+/**
+ * enum uart_rx_state - UART RX-state for UART.
+ * @W4_PACKET_TYPE: Waiting for packet type.
+ * @W4_EVENT_HDR: Waiting for BT event header.
+ * @W4_ACL_HDR: Waiting for BT ACL header.
+ * @W4_FM_RADIO_HDR: Waiting for FM header.
+ * @W4_GNSS_HDR: Waiting for GNSS header.
+ * @W4_DATA: Waiting for data in rest of the packet (after header).
+ */
+enum uart_rx_state {
+ W4_PACKET_TYPE,
+ W4_EVENT_HDR,
+ W4_ACL_HDR,
+ W4_FM_RADIO_HDR,
+ W4_GNSS_HDR,
+ W4_DATA
+};
+
+/**
+ * enum sleep_state - Sleep-state for UART.
+ * @CHIP_AWAKE: Chip is awake.
+ * @CHIP_FALLING_ASLEEP: Chip is falling asleep.
+ * @CHIP_ASLEEP: Chip is asleep.
+ * @CHIP_SUSPENDED: Chip in suspend state.
+ * @CHIP_POWERED_DOWN: Chip is off.
+ */
+enum sleep_state {
+ CHIP_AWAKE,
+ CHIP_FALLING_ASLEEP,
+ CHIP_ASLEEP,
+ CHIP_SUSPENDED,
+ CHIP_POWERED_DOWN
+};
+
+/**
+ * enum baud_rate_change_state - Baud rate-state for UART.
+ * @BAUD_IDLE: No baud rate change is ongoing.
+ * @BAUD_SENDING_RESET: HCI reset has been sent. Waiting for command complete
+ * event.
+ * @BAUD_START: Set baud rate cmd scheduled for sending.
+ * @BAUD_SENDING: Set baud rate cmd sending in progress.
+ * @BAUD_WAITING: Set baud rate cmd sent, waiting for command complete
+ * event.
+ * @BAUD_SUCCESS: Baud rate change has succeeded.
+ * @BAUD_FAIL: Baud rate change has failed.
+ */
+enum baud_rate_change_state {
+ BAUD_IDLE,
+ BAUD_SENDING_RESET,
+ BAUD_START,
+ BAUD_SENDING,
+ BAUD_WAITING,
+ BAUD_SUCCESS,
+ BAUD_FAIL
+};
+
+/**
+ * struct uart_work_struct - Work structure for UART module.
+ * @work: Work structure.
+ * @data: Pointer to private data.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct uart_work_struct{
+ struct work_struct work;
+ void *data;
+};
+
+/**
+ * struct test_char_dev_info - Main UART info structure.
+ * @wq: UART work queue.
+ * @tx_queue: TX queue for sending data to chip.
+ * @tty: TTY info structure.
+ * @rx_lock: RX spin lock.
+ * @rx_state: Current RX state.
+ * @rx_count: Number of bytes left to receive.
+ * @rx_skb: SK_buffer to store the received data into.
+ * @tx_mutex: TX mutex.
+ * @baud_rate_state: UART baud rate change state.
+ * @baud_rate: Current baud rate setting.
+ * @sleep_state: UART sleep state.
+ * @timer: UART timer (for chip sleep).
+ * @fd: File object to device.
+ * @sleep_state_lock: Used to protect chip state.
+ * @sleep_allowed: Indicate if tty has functions needed for sleep mode.
+ * @regulator: Regulator.
+ * @regulator_enabled: True if regulator is enabled.
+ * @dev: Pointer to CG2900 uart device.
+ */
+struct uart_info {
+ struct workqueue_struct *wq;
+ struct sk_buff_head tx_queue;
+ struct tty_struct *tty;
+ spinlock_t rx_lock;
+ enum uart_rx_state rx_state;
+ unsigned long rx_count;
+ struct sk_buff *rx_skb;
+ struct mutex tx_mutex;
+ enum baud_rate_change_state baud_rate_state;
+ int baud_rate;
+ enum sleep_state sleep_state;
+ struct timer_list timer;
+ struct file *fd;
+ struct mutex sleep_state_lock;
+ bool sleep_allowed;
+ struct regulator *regulator;
+ bool regulator_enabled;
+ struct device *dev;
+};
+
+static struct uart_info *uart_info;
+
+/* Module parameters */
+static int uart_default_baud = DEFAULT_BAUD_RATE;
+static int uart_high_baud = HIGH_BAUD_RATE;
+
+static DECLARE_WAIT_QUEUE_HEAD(uart_wait_queue);
+
+static void update_timer(void);
+
+/**
+ * is_chip_flow_off() - Check if chip has set flow off.
+ * @tty: Pointer to tty.
+ *
+ * Returns:
+ * true - chip flows off.
+ * false - chip flows on.
+ */
+static bool is_chip_flow_off(struct tty_struct *tty)
+{
+ int lines;
+
+ lines = tty->ops->tiocmget(tty, uart_info->fd);
+
+ if (lines & TIOCM_CTS)
+ return false;
+ else
+ return true;
+}
+
+/**
+ * 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 uart_work_struct *new_work;
+ int err;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for uart_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;
+}
+
+/**
+ * set_tty_baud() - Called to set specific baud in TTY.
+ * @tty: Tty device.
+ * @baud: Baud to set.
+ *
+ * Returns:
+ * true - baudrate set with success.
+ * false - baundrate set failure.
+ */
+static bool set_tty_baud(struct tty_struct *tty, int baud)
+{
+ struct ktermios *old_termios;
+ bool retval = true;
+
+ old_termios = kmalloc(sizeof(*old_termios), GFP_ATOMIC);
+ if (!old_termios) {
+ CG2900_ERR("Could not allocate termios");
+ return false;
+ }
+
+ mutex_lock(&(tty->termios_mutex));
+ /* Start by storing the old termios. */
+ memcpy(old_termios, tty->termios, sizeof(*old_termios));
+
+ /* Let's mark that CG2900 driver uses c_ispeed and c_ospeed fields. */
+ tty->termios->c_cflag |= BOTHER;
+
+ tty_encode_baud_rate(tty, baud, baud);
+
+ /* Finally inform the driver */
+ if (tty->ops->set_termios)
+ tty->ops->set_termios(tty, old_termios);
+ else {
+ CG2900_ERR("Can not set new baudrate.");
+ /* Copy back the old termios to restore old setting. */
+ memcpy(tty->termios, old_termios, sizeof(*old_termios));
+ retval = false;
+ }
+
+ tty->termios->c_cflag &= ~BOTHER;
+
+ mutex_unlock(&(tty->termios_mutex));
+ kfree(old_termios);
+
+ return retval;
+}
+
+/**
+ * handle_cts_irq() - Called to handle CTS interrupt in work context.
+ * @work: work which needs to be done.
+ *
+ * The handle_cts_irq() function is a work handler called if interrupt on CTS
+ * occurred. It updates the sleep timer which will wake up the transport.
+ */
+static void handle_cts_irq(struct work_struct *work)
+{
+ /* Restart timer and disable interrupt. */
+ update_timer();
+}
+
+/**
+ * cts_interrupt() - Called to handle CTS interrupt.
+ * @irq: Interrupt that occurred.
+ * @dev_id: Device ID where interrupt occurred (not used).
+ *
+ * The handle_cts_irq() function is called if interrupt on CTS occurred.
+ * It disables the interrupt and starts a new work thread to handle
+ * the interrupt.
+ */
+static irqreturn_t cts_interrupt(int irq, void *dev_id)
+{
+#ifdef CONFIG_PM
+ disable_irq_wake(irq);
+#endif
+ disable_irq_nosync(irq);
+
+ /* If chip is suspended, resume callback will be called. */
+ if (CHIP_SUSPENDED != uart_info->sleep_state)
+ /* Create work and leave IRQ context. */
+ (void)create_work_item(uart_info->wq, handle_cts_irq, NULL);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * set_cts_irq() - Enable interrupt on CTS.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * Error codes from request_irq and disable_uart.
+ */
+static int set_cts_irq(void)
+{
+ int err;
+ struct cg2900_platform_data *pf_data;
+
+ pf_data = dev_get_platdata(uart_info->dev->parent);
+
+ /* First disable the UART so we can use IRQ on the GPIOs */
+ if (pf_data->uart.disable_uart) {
+ err = pf_data->uart.disable_uart();
+ if (err) {
+ CG2900_ERR("Could not disable UART (%d)", err);
+ goto error;
+ }
+ }
+
+ /* Set IRQ on CTS. */
+ err = request_irq(pf_data->uart.cts_irq,
+ cts_interrupt,
+ IRQF_TRIGGER_FALLING,
+ UART_NAME,
+ NULL);
+ if (err) {
+ CG2900_ERR("Could not request CTS IRQ (%d)", err);
+ goto error;
+ }
+
+#ifdef CONFIG_PM
+ enable_irq_wake(pf_data->uart.cts_irq);
+#endif
+ return 0;
+
+error:
+ if (pf_data->uart.enable_uart)
+ (void)pf_data->uart.enable_uart();
+ return err;
+}
+
+/**
+ * unset_cts_irq() - Disable interrupt on CTS.
+ */
+static void unset_cts_irq(void)
+{
+ int err = 0;
+ struct cg2900_platform_data *pf_data;
+
+ pf_data = dev_get_platdata(uart_info->dev->parent);
+
+ /* Free CTS interrupt and restore UART settings. */
+ free_irq(pf_data->uart.cts_irq, NULL);
+
+ if (pf_data->uart.enable_uart) {
+ err = pf_data->uart.enable_uart();
+ if (err)
+ CG2900_ERR("Unable to enable UART Hardware (%d)", err);
+ }
+}
+
+/**
+ * update_timer() - Updates or starts the sleep timer.
+ *
+ * Updates or starts the sleep timer used to detect when there are no current
+ * data transmissions.
+ */
+static void update_timer(void)
+{
+ unsigned long timeout_jiffies = cg2900_get_sleep_timeout();
+ struct tty_struct *tty;
+
+ if ((!timeout_jiffies || !uart_info->fd || !uart_info->sleep_allowed)
+ && (uart_info->sleep_state != CHIP_SUSPENDED))
+ return;
+
+ mutex_lock(&(uart_info->sleep_state_lock));
+ /*
+ * This function indicates data is transmitted.
+ * Therefore see to that the chip is awake.
+ */
+ if (CHIP_AWAKE == uart_info->sleep_state)
+ goto finished;
+
+ tty = uart_info->tty;
+
+ if (CHIP_ASLEEP == uart_info->sleep_state ||
+ CHIP_SUSPENDED == uart_info->sleep_state) {
+ /* Disable IRQ only when it was enabled. */
+ unset_cts_irq();
+ (void)set_tty_baud(tty, uart_info->baud_rate);
+ }
+ /* Set FLOW on. */
+ tty_unthrottle(tty);
+
+ /* Unset BREAK. */
+ CG2900_DBG("Clear break");
+ tty->ops->break_ctl(tty, TTY_BREAK_OFF);
+
+ SET_SLEEP_STATE(CHIP_AWAKE);
+
+finished:
+ mutex_unlock(&(uart_info->sleep_state_lock));
+ /*
+ * If timer is running restart it. If not, start it.
+ * All this is handled by mod_timer().
+ */
+ mod_timer(&(uart_info->timer), jiffies + timeout_jiffies);
+}
+
+/**
+ * sleep_timer_expired() - Called when sleep timer expires.
+ * @data: Value supplied when starting the timer.
+ *
+ * The sleep_timer_expired() function is called if there are no ongoing data
+ * transmissions. It tries to put the chip in sleep mode.
+ *
+ */
+static void sleep_timer_expired(unsigned long data)
+{
+ unsigned long timeout_jiffies = cg2900_get_sleep_timeout();
+ struct tty_struct *tty;
+
+ if (!timeout_jiffies || !uart_info->sleep_allowed || !uart_info->fd)
+ return;
+
+ mutex_lock(&(uart_info->sleep_state_lock));
+
+ tty = uart_info->tty;
+
+ switch (uart_info->sleep_state) {
+ case CHIP_FALLING_ASLEEP:
+ if (!is_chip_flow_off(tty))
+ goto run_timer;
+
+ /* Flow OFF. */
+ tty_throttle(tty);
+
+ /*
+ * Set baud zero.
+ * This cause shut off UART clock as well.
+ */
+ (void)set_tty_baud(tty, ZERO_BAUD_RATE);
+
+ if (set_cts_irq() < 0) {
+ CG2900_ERR("Can not set interrupt on CTS.");
+ (void)set_tty_baud(tty, uart_info->baud_rate);
+ tty_unthrottle(tty);
+ SET_SLEEP_STATE(CHIP_AWAKE);
+ goto error;
+ }
+
+ SET_SLEEP_STATE(CHIP_ASLEEP);
+ break;
+ case CHIP_AWAKE:
+
+ CG2900_DBG("Set break");
+ tty->ops->break_ctl(tty, TTY_BREAK_ON);
+
+ SET_SLEEP_STATE(CHIP_FALLING_ASLEEP);
+ goto run_timer;
+
+ case CHIP_POWERED_DOWN:
+ case CHIP_SUSPENDED:
+ case CHIP_ASLEEP: /* Fallthrough. */
+ default:
+ CG2900_DBG("Chip sleeps, is suspended or powered down.");
+ break;
+ }
+
+ mutex_unlock(&(uart_info->sleep_state_lock));
+
+ return;
+
+run_timer:
+ mutex_unlock(&(uart_info->sleep_state_lock));
+ mod_timer(&(uart_info->timer), jiffies + timeout_jiffies);
+ return;
+error:
+ /* Disable sleep mode.*/
+ CG2900_ERR("Disable sleep mode.");
+ uart_info->sleep_allowed = false;
+ uart_info->fd = NULL;
+ mutex_unlock(&(uart_info->sleep_state_lock));
+}
+
+#ifdef CONFIG_PM
+/**
+ * cg2900_uart_suspend() - Called by Linux PM to put the device in a
low power mode.
+ * @pdev: Pointer to platform device.
+ * @state: New state.
+ *
+ * In UART case, CG2900 driver does nothing on suspend.
+ *
+ * Returns:
+ * 0 - Success.
+ */
+static int cg2900_uart_suspend(struct platform_device *pdev,
pm_message_t state)
+{
+ if (uart_info->sleep_state == CHIP_POWERED_DOWN)
+ return 0;
+
+ /* Timer is mostlikely running. Delete it. */
+ del_timer(&uart_info->timer);
+
+ if (CHIP_ASLEEP == uart_info->sleep_state)
+ goto finished;
+
+ if (CHIP_AWAKE == uart_info->sleep_state) {
+ uart_info->tty->ops->break_ctl(uart_info->tty, TTY_BREAK_ON);
+ SET_SLEEP_STATE(CHIP_FALLING_ASLEEP);
+ msleep(10);
+ }
+
+ if (CHIP_FALLING_ASLEEP == uart_info->sleep_state) {
+ int err;
+
+ /* Flow OFF. */
+ tty_throttle(uart_info->tty);
+ (void)set_tty_baud(uart_info->tty, ZERO_BAUD_RATE);
+
+ err = set_cts_irq();
+ if (err < 0) {
+ CG2900_ERR("Can not suspend");
+ SET_SLEEP_STATE(CHIP_AWAKE);
+ return err;
+ }
+ }
+
+finished:
+ SET_SLEEP_STATE(CHIP_SUSPENDED);
+ return 0;
+}
+
+/**
+ * cg2900_uart_resume() - Called to bring a device back from a low power state.
+ * @pdev: Pointer to platform device.
+ *
+ * In UART case, CG2900 driver does nothing on resume.
+ *
+ * Returns:
+ * 0 - Success.
+ */
+static int cg2900_uart_resume(struct platform_device *pdev)
+{
+ if (uart_info->sleep_state != CHIP_POWERED_DOWN)
+ update_timer();
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+/**
+ * cg2900_enable_regulator() - Enable regulator.
+ *
+ * Returns:
+ * 0 - Success.
+ * Error from regulator_get, regulator_enable.
+ */
+static int cg2900_enable_regulator(void)
+{
+#ifdef CONFIG_REGULATOR
+ int err;
+
+ /* Get and enable regulator. */
+ uart_info->regulator = regulator_get(uart_info->dev->parent, "gbf_1v8");
+ if (IS_ERR(uart_info->regulator)) {
+ CG2900_ERR("Not able to find regulator.");
+ err = PTR_ERR(uart_info->regulator);
+ } else {
+ err = regulator_enable(uart_info->regulator);
+ if (err)
+ CG2900_ERR("Not able to enable regulator.");
+ else
+ uart_info->regulator_enabled = true;
+ }
+ return err;
+#else
+ return 0;
+#endif
+}
+
+/**
+ * cg2900_disable_regulator() - Disable regulator.
+ *
+ */
+static void cg2900_disable_regulator(void)
+{
+#ifdef CONFIG_REGULATOR
+ /* Disable and put regulator. */
+ if (uart_info->regulator && uart_info->regulator_enabled) {
+ regulator_disable(uart_info->regulator);
+ uart_info->regulator_enabled = false;
+ }
+ regulator_put(uart_info->regulator);
+ uart_info->regulator = NULL;
+#endif
+}
+
+/**
+ * is_set_baud_rate_cmd() - Checks if data contains set baud rate hci cmd.
+ * @data: Pointer to data array to check.
+ *
+ * Returns:
+ * true - if cmd found;
+ * false - otherwise.
+ */
+static bool is_set_baud_rate_cmd(const char *data)
+{
+ bool cmd_match = false;
+
+ if ((data[0] == HCI_BT_CMD_H4_CHANNEL) &&
+ (data[1] == SET_BAUD_RATE_LSB) &&
+ (data[2] == SET_BAUD_RATE_MSB) &&
+ (data[3] == SET_BAUD_RATE_PAYL_LEN)) {
+ cmd_match = true;
+ }
+ return cmd_match;
+}
+
+/**
+ * is_bt_cmd_complete_no_param() - Checks if data contains command
complete event for a certain command.
+ * @skb: sk_buffer containing the data including H:4 header.
+ * @cmd_lsb: Command LSB.
+ * @cmd_msb: Command MSB.
+ *
+ * Returns:
+ * true - If this is the command complete we were looking for;
+ * false - otherwise.
+ */
+static bool is_bt_cmd_complete_no_param(struct sk_buff *skb, u8 cmd_lsb,
+ u8 cmd_msb)
+{
+ if ((HCI_BT_EVT_H4_CHANNEL == skb->data[0]) &&
+ (HCI_BT_EVT_CMD_COMPLETE == skb->data[1]) &&
+ (HCI_BT_CMD_COMPLETE_NO_PARAM_LEN == skb->data[2]) &&
+ (cmd_lsb == skb->data[4]) &&
+ (cmd_msb == skb->data[5]))
+ return true;
+
+ return false;
+}
+
+/**
+ * alloc_rx_skb() - Alloc an sk_buff structure for receiving data
from controller.
+ * @size: Size in number of octets.
+ * @priority: Allocation priority, e.g. GFP_KERNEL.
+ *
+ * Returns:
+ * Pointer to sk_buff structure.
+ */
+static struct sk_buff *alloc_rx_skb(unsigned int size, gfp_t priority)
+{
+ struct sk_buff *skb;
+
+ /* Allocate the SKB and reserve space for the header */
+ skb = alloc_skb(size + RX_SKB_RESERVE, priority);
+ if (skb)
+ skb_reserve(skb, RX_SKB_RESERVE);
+
+ return skb;
+}
+
+/**
+ * finish_setting_baud_rate() - Handles sending the ste baud rate hci cmd.
+ * @tty: Pointer to a tty_struct used to communicate with tty driver.
+ *
+ * finish_setting_baud_rate() makes sure that the set baud rate cmd has
+ * been really sent out on the wire and then switches the tty driver to new
+ * baud rate.
+ */
+static void finish_setting_baud_rate(struct tty_struct *tty)
+{
+ /*
+ * Give the tty driver time to send data and proceed. If it hasn't
+ * been sent we can't do much about it anyway.
+ */
+ schedule_timeout_interruptible(msecs_to_jiffies(UART_TX_TIMEOUT));
+
+ /*
+ * Now set the termios struct to the new baudrate. Start by storing
+ * the old termios.
+ */
+ if (set_tty_baud(tty, uart_info->baud_rate)) {
+ CG2900_DBG("Setting termios to new baud rate");
+ SET_BAUD_STATE(BAUD_WAITING);
+ } else
+ SET_BAUD_STATE(BAUD_IDLE);
+
+ tty_unthrottle(tty);
+}
+
+/**
+ * alloc_set_baud_rate_cmd() - Allocates new sk_buff and fills in the
change baud rate hci cmd.
+ * @baud: (in/out) Requested new baud rate. Updated to default baud rate
+ * upon invalid value.
+ *
+ * Returns:
+ * Pointer to allocated sk_buff if successful;
+ * NULL otherwise.
+ */
+static struct sk_buff *alloc_set_baud_rate_cmd(int *baud)
+{
+ struct sk_buff *skb;
+ u8 data[SET_BAUD_RATE_LEN];
+ u8 *h4;
+
+ skb = cg2900_alloc_skb(SET_BAUD_RATE_LEN, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Failed to alloc skb!");
+ return NULL;
+ }
+
+ /* Create the Hci_Cmd_ST_Set_Uart_Baud_Rate packet */
+ data[0] = SET_BAUD_RATE_LSB;
+ data[1] = SET_BAUD_RATE_MSB;
+ data[2] = SET_BAUD_RATE_PAYL_LEN;
+
+ switch (*baud) {
+ case 57600:
+ data[3] = BAUD_RATE_57600;
+ break;
+ case 115200:
+ data[3] = BAUD_RATE_115200;
+ break;
+ case 230400:
+ data[3] = BAUD_RATE_230400;
+ break;
+ case 460800:
+ data[3] = BAUD_RATE_460800;
+ break;
+ case 921600:
+ data[3] = BAUD_RATE_921600;
+ break;
+ case 2000000:
+ data[3] = BAUD_RATE_2000000;
+ break;
+ case 3000000:
+ data[3] = BAUD_RATE_3000000;
+ break;
+ case 4000000:
+ data[3] = BAUD_RATE_4000000;
+ break;
+ default:
+ CG2900_ERR("Invalid speed requested (%d), using 115200 bps "
+ "instead\n", *baud);
+ data[3] = BAUD_RATE_115200;
+ *baud = 115200;
+ break;
+ };
+
+ memcpy(skb_put(skb, SET_BAUD_RATE_LEN), data, SET_BAUD_RATE_LEN);
+ h4 = skb_push(skb, HCI_H4_SIZE);
+ *h4 = HCI_BT_CMD_H4_CHANNEL;
+
+ return skb;
+}
+
+/**
+ * work_do_transmit() - Transmit data packet to connectivity
controller over UART.
+ * @work: Pointer to work info structure. Contains uart_info structure
+ * pointer.
+ */
+static void work_do_transmit(struct work_struct *work)
+{
+ struct sk_buff *skb;
+ struct tty_struct *tty;
+ struct uart_work_struct *current_work;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /* Restart timer. */
+ update_timer();
+
+ current_work = container_of(work, struct uart_work_struct, work);
+
+ if (uart_info->tty)
+ tty = uart_info->tty;
+ else {
+ CG2900_ERR("Important structs not allocated!");
+ goto finished;
+ }
+
+ mutex_lock(&uart_info->tx_mutex);
+
+ /* Retrieve the first packet in the queue */
+ skb = skb_dequeue(&uart_info->tx_queue);
+ while (skb) {
+ int len;
+
+ /*
+ * Tell TTY that there is data waiting and call the write
+ * function.
+ */
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ len = tty->ops->write(tty, skb->data, skb->len);
+ CG2900_INFO("Written %d bytes to UART of %d bytes in packet",
+ len, skb->len);
+
+ /*
+ * If it's set baud rate cmd set correct baud state and after
+ * sending is finished inform the tty driver about the new
+ * baud rate.
+ */
+ if ((BAUD_START == uart_info->baud_rate_state) &&
+ (is_set_baud_rate_cmd(skb->data))) {
+ CG2900_INFO("UART set baud rate cmd found.");
+ SET_BAUD_STATE(BAUD_SENDING);
+ }
+
+ /* Remove the bytes written from the sk_buffer */
+ skb_pull(skb, len);
+
+ /*
+ * If there is more data in this sk_buffer, put it at the start
+ * of the list and exit the loop
+ */
+ if (skb->len) {
+ skb_queue_head(&uart_info->tx_queue, skb);
+ break;
+ }
+ /*
+ * No more data in the sk_buffer. Free it and get next packet in
+ * queue.
+ * Check if set baud rate cmd is in sending progress, if so call
+ * proper function to handle that cmd since it requires special
+ * attention.
+ */
+ if (BAUD_SENDING == uart_info->baud_rate_state)
+ finish_setting_baud_rate(tty);
+
+ kfree_skb(skb);
+ skb = skb_dequeue(&uart_info->tx_queue);
+ }
+
+ mutex_unlock(&uart_info->tx_mutex);
+
+finished:
+ kfree(current_work);
+}
+
+/**
+ * work_hw_deregistered() - Handle HW deregistered.
+ * @work: Reference to work data.
+ */
+static void work_hw_deregistered(struct work_struct *work)
+{
+ struct uart_work_struct *current_work;
+ int err;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct uart_work_struct, work);
+
+ /* Purge any stored sk_buffers */
+ skb_queue_purge(&uart_info->tx_queue);
+ if (uart_info->rx_skb) {
+ kfree_skb(uart_info->rx_skb);
+ uart_info->rx_skb = NULL;
+ }
+
+ err = cg2900_deregister_trans_driver();
+ if (err)
+ CG2900_ERR("Could not deregister UART from Core (%d)", err);
+
+ kfree(current_work);
+}
+
+/**
+ * set_baud_rate() - Sets new baud rate for the UART.
+ * @baud: New baud rate.
+ *
+ * This function first sends the HCI command
+ * Hci_Cmd_ST_Set_Uart_Baud_Rate. It then changes the baud rate in HW, and
+ * finally it waits for the Command Complete event for the
+ * Hci_Cmd_ST_Set_Uart_Baud_Rate command.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EALREADY if baud rate change is already in progress.
+ * -EFAULT if one or more of the UART related structs is not allocated.
+ * -ENOMEM if skb allocation has failed.
+ * -EPERM if setting the new baud rate has failed.
+ * Error codes generated by create_work_item.
+ */
+static int set_baud_rate(int baud)
+{
+ struct tty_struct *tty = NULL;
+ int err = 0;
+ struct sk_buff *skb;
+ int old_baud_rate;
+
+ CG2900_INFO("set_baud_rate (%d baud)", baud);
+
+ if (uart_info->baud_rate_state != BAUD_IDLE) {
+ CG2900_ERR("Trying to set new baud rate before old setting "
+ "is finished");
+ return -EALREADY;
+ }
+
+ if (uart_info->tty)
+ tty = uart_info->tty;
+ else {
+ CG2900_ERR("Important structs not allocated!");
+ return -EFAULT;
+ }
+
+ tty_throttle(tty);
+
+ /*
+ * Store old baud rate so that we can restore it if something goes
+ * wrong.
+ */
+ old_baud_rate = uart_info->baud_rate;
+
+ skb = alloc_set_baud_rate_cmd(&baud);
+ if (!skb) {
+ CG2900_ERR("alloc_set_baud_rate_cmd failed");
+ return -ENOMEM;
+ }
+
+ SET_BAUD_STATE(BAUD_START);
+ uart_info->baud_rate = baud;
+
+ /* Queue the sk_buffer... */
+ skb_queue_tail(&uart_info->tx_queue, skb);
+
+ /* ... and call the common UART TX function */
+ err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+ if (err) {
+ CG2900_ERR("Failed to send change baud rate cmd, freeing "
+ "skb.");
+ skb = skb_dequeue_tail(&uart_info->tx_queue);
+ SET_BAUD_STATE(BAUD_IDLE);
+ uart_info->baud_rate = old_baud_rate;
+ kfree_skb(skb);
+ return err;
+ }
+
+ CG2900_DBG("Set baud rate cmd scheduled for sending.");
+
+ /*
+ * Now wait for the command complete.
+ * It will come at the new baudrate.
+ */
+ wait_event_interruptible_timeout(uart_wait_queue,
+ ((BAUD_SUCCESS == uart_info->baud_rate_state) ||
+ (BAUD_FAIL == uart_info->baud_rate_state)),
+ msecs_to_jiffies(UART_RESP_TIMEOUT));
+ if (BAUD_SUCCESS == uart_info->baud_rate_state)
+ CG2900_DBG("Baudrate changed to %d baud", baud);
+ else {
+ CG2900_ERR("Failed to set new baudrate (%d)",
+ uart_info->baud_rate_state);
+ err = -EPERM;
+ }
+
+ /* Finally flush the TTY so we are sure that is no bad data there */
+ if (tty->ops->flush_buffer) {
+ CG2900_DBG("Flushing TTY after baud rate change");
+ tty->ops->flush_buffer(tty);
+ }
+
+ /* Finished. Set state to IDLE */
+ SET_BAUD_STATE(BAUD_IDLE);
+
+ return err;
+}
+
+/**
+ * uart_open() - Open the CG2900 UART for data transfers.
+ * @dev: Transport device information.
+ *
+ * Returns:
+ * 0 if there is no error,
+ * -EACCES if write to transport failed,
+ * -EIO if chip did not answer to commands.
+ */
+static int uart_open(struct cg2900_trans_dev *dev)
+{
+ u8 data[HCI_BT_RESET_LEN + HCI_H4_SIZE];
+ struct tty_struct *tty;
+ int bytes_written;
+
+ /*
+ * Chip has just been started up. It has a system to autodetect
+ * exact baud rate and transport to use. There are only a few commands
+ * it will recognize and HCI Reset is one of them.
+ * We therefore start with sending that before actually changing
+ * baud rate.
+ *
+ * Create the Hci_Reset packet
+ */
+ data[0] = HCI_BT_CMD_H4_CHANNEL;
+ data[1] = HCI_BT_RESET_CMD_LSB;
+ data[2] = HCI_BT_RESET_CMD_MSB;
+ data[3] = HCI_BT_RESET_PARAM_LEN;
+
+ /* Get the TTY info and send the packet */
+ tty = uart_info->tty;
+ SET_BAUD_STATE(BAUD_SENDING_RESET);
+ CG2900_DBG("Sending HCI reset before baud rate change");
+ bytes_written = tty->ops->write(tty, data,
+ HCI_BT_RESET_LEN + HCI_H4_SIZE);
+ if (bytes_written != HCI_BT_RESET_LEN + HCI_H4_SIZE) {
+ CG2900_ERR("Only wrote %d bytes", bytes_written);
+ SET_BAUD_STATE(BAUD_IDLE);
+ return -EACCES;
+ }
+
+ /*
+ * Wait for command complete. If error, exit without changing
+ * baud rate.
+ */
+ wait_event_interruptible_timeout(uart_wait_queue,
+ BAUD_IDLE == uart_info->baud_rate_state,
+ msecs_to_jiffies(UART_RESP_TIMEOUT));
+ if (BAUD_IDLE != uart_info->baud_rate_state) {
+ CG2900_ERR("Failed to send HCI Reset");
+ SET_BAUD_STATE(BAUD_IDLE);
+ return -EIO;
+ }
+
+ /* Just return if there will be no change of baud rate */
+ if (uart_default_baud != uart_high_baud)
+ return set_baud_rate(uart_high_baud);
+ else
+ return 0;
+}
+
+/**
+ * uart_set_chip_power() - Enable or disable the CG2900.
+ * @chip_on: true if chip shall be enabled, false otherwise.
+ */
+static void uart_set_chip_power(bool chip_on)
+{
+ int uart_baudrate = uart_default_baud;
+ struct tty_struct *tty;
+ struct cg2900_platform_data *pf_data;
+
+ CG2900_INFO("uart_set_chip_power: %s",
+ (chip_on ? "ENABLE" : "DISABLE"));
+
+ if (uart_info->tty)
+ tty = uart_info->tty;
+ else {
+ CG2900_ERR("Important structs not allocated!");
+ return;
+ }
+
+ pf_data = dev_get_platdata(uart_info->dev->parent);
+
+ if (chip_on) {
+ if (cg2900_enable_regulator())
+ return;
+ if (pf_data->enable_chip) {
+ pf_data->enable_chip();
+ SET_SLEEP_STATE(CHIP_AWAKE);
+ }
+ } else {
+ if (pf_data->disable_chip) {
+ pf_data->disable_chip();
+ SET_SLEEP_STATE(CHIP_POWERED_DOWN);
+ }
+
+ cg2900_disable_regulator();
+ /*
+ * Setting baud rate to 0 will tell UART driver to shut off its
+ * clocks.
+ */
+ uart_baudrate = ZERO_BAUD_RATE;
+ }
+ /*
+ * Now we have to set the digital baseband UART
+ * to default baudrate if chip is ON or to zero baudrate if
+ * chip is turning OFF.
+ */
+ (void)set_tty_baud(tty, uart_baudrate);
+}
+
+/**
+ * uart_chip_startup_finished() - CG2900 startup finished.
+ */
+static void uart_chip_startup_finished(void)
+{
+ /* Run the timer. */
+ update_timer();
+}
+/**
+ * uart_close() - Close the CG2900 UART for data transfers.
+ * @dev: Transport device information.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int uart_close(struct cg2900_trans_dev *dev)
+{
+ /* The chip is already shut down. Power off the chip. */
+ uart_set_chip_power(false);
+
+ return 0;
+}
+
+/**
+ * uart_write() - Transmit data to CG2900 over UART.
+ * @dev: Transport device information.
+ * @skb: SK buffer to transmit.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * Errors from create_work_item.
+ */
+static int uart_write(struct cg2900_trans_dev *dev, struct sk_buff *skb)
+{
+ int err;
+
+ /* Delete sleep timer. */
+ (void)del_timer(&uart_info->timer);
+
+ CG2900_DBG_DATA_CONTENT("uart_write", skb->data, skb->len);
+
+ /* Queue the sk_buffer... */
+ skb_queue_tail(&uart_info->tx_queue, skb);
+
+ /* ...and start TX operation */
+ err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+ if (err)
+ CG2900_ERR("Failed to create work item (%d) uart_tty_wakeup",
+ err);
+
+ return err;
+}
+
+/**
+ * send_skb_to_core() - Sends packet received from UART to CG2900 Core.
+ * @skb: Received data packet.
+ *
+ * This function checks if UART is waiting for Command complete event,
+ * see set_baud_rate.
+ * If it is waiting it checks if it is the expected packet and the status.
+ * If not is passes the packet to CG2900 Core.
+ */
+static void send_skb_to_core(struct sk_buff *skb)
+{
+ u8 status;
+
+ if (!skb) {
+ CG2900_ERR("Received NULL as skb");
+ return;
+ }
+
+ if (BAUD_WAITING == uart_info->baud_rate_state) {
+ /*
+ * Should only really be one packet received now:
+ * the CmdComplete for the SetBaudrate command
+ * Let's see if this is the packet we are waiting for.
+ */
+ if (!is_bt_cmd_complete_no_param(skb, SET_BAUD_RATE_LSB,
+ SET_BAUD_RATE_MSB)) {
+ /*
+ * Received other event. Should not really happen,
+ * but pass the data to CG2900 Core anyway.
+ */
+ CG2900_DBG_DATA("Sending packet to CG2900 Core while "
+ "waiting for CmdComplete");
+ cg2900_data_from_chip(skb);
+ return;
+ }
+
+ /*
+ * We have received complete event for our baud rate
+ * change command
+ */
+ status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS +
+ HCI_H4_SIZE];
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ CG2900_DBG("Received baud rate change complete "
+ "event OK");
+ SET_BAUD_STATE(BAUD_SUCCESS);
+ } else {
+ CG2900_ERR("Received baud rate change complete event "
+ "with status 0x%X", status);
+ SET_BAUD_STATE(BAUD_FAIL);
+ }
+ wake_up_interruptible(&uart_wait_queue);
+ kfree_skb(skb);
+ } else if (BAUD_SENDING_RESET == uart_info->baud_rate_state) {
+ /*
+ * Should only really be one packet received now:
+ * the CmdComplete for the Reset command
+ * Let's see if this is the packet we are waiting for.
+ */
+ if (!is_bt_cmd_complete_no_param(skb, HCI_BT_RESET_CMD_LSB,
+ HCI_BT_RESET_CMD_MSB)) {
+ /*
+ * Received other event. Should not really happen,
+ * but pass the data to CG2900 Core anyway.
+ */
+ CG2900_DBG_DATA("Sending packet to CG2900 Core while "
+ "waiting for CmdComplete");
+ cg2900_data_from_chip(skb);
+ return;
+ }
+
+ /*
+ * We have received complete event for our baud rate
+ * change command
+ */
+ status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS +
+ HCI_H4_SIZE];
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ CG2900_DBG("Received HCI reset complete event OK");
+ /*
+ * Go back to BAUD_IDLE since this was not really
+ * baud rate change but just a preparation of the chip
+ * to be ready to receive commands.
+ */
+ SET_BAUD_STATE(BAUD_IDLE);
+ } else {
+ CG2900_ERR("Received HCI reset complete event with "
+ "status 0x%X", status);
+ SET_BAUD_STATE(BAUD_FAIL);
+ }
+ wake_up_interruptible(&uart_wait_queue);
+ kfree_skb(skb);
+ } else {
+ /* Just pass data to CG2900 Core */
+ cg2900_data_from_chip(skb);
+ }
+}
+
+static struct cg2900_trans_callbacks uart_cb = {
+ .open = uart_open,
+ .close = uart_close,
+ .write = uart_write,
+ .set_chip_power = uart_set_chip_power,
+ .chip_startup_finished = uart_chip_startup_finished
+};
+
+/**
+ * check_data_len() - Check number of bytes to receive.
+ * @len: Number of bytes left to receive.
+ */
+static void check_data_len(int len)
+{
+ /* First get number of bytes left in the sk_buffer */
+ register int room = skb_tailroom(uart_info->rx_skb);
+
+ if (!len) {
+ /* No data left to receive. Transmit to CG2900 Core */
+ send_skb_to_core(uart_info->rx_skb);
+ } else if (len > room) {
+ CG2900_ERR("Data length is too large (%d > %d)", len, room);
+ kfree_skb(uart_info->rx_skb);
+ } else {
+ /*
+ * "Normal" case. Switch to data receiving state and store
+ * data length.
+ */
+ uart_info->rx_state = W4_DATA;
+ uart_info->rx_count = len;
+ return;
+ }
+
+ uart_info->rx_state = W4_PACKET_TYPE;
+ uart_info->rx_skb = NULL;
+ uart_info->rx_count = 0;
+}
+
+/**
+ * uart_receive_skb() - Handles received UART data.
+ * @data: Data received
+ * @count: Number of bytes received
+ *
+ * The uart_receive_skb() function handles received UART data and puts it
+ * together to one complete packet.
+ *
+ * Returns:
+ * Number of bytes not handled, i.e. 0 = no error.
+ */
+static int uart_receive_skb(const u8 *data, int count)
+{
+ const u8 *r_ptr;
+ u8 *w_ptr;
+ int len;
+ struct hci_event_hdr *evt;
+ struct hci_acl_hdr *acl;
+ union fm_leg_evt_or_irq *fm;
+ struct gnss_hci_hdr *gnss;
+ u8 *tmp;
+
+ r_ptr = data;
+ /* Continue while there is data left to handle */
+ while (count) {
+ /*
+ * If we have already received a packet we know how many bytes
+ * there are left.
+ */
+ if (!uart_info->rx_count)
+ goto check_h4_header;
+
+ /* First copy received data into the skb_rx */
+ len = min_t(unsigned int, uart_info->rx_count, count);
+ memcpy(skb_put(uart_info->rx_skb, len), r_ptr, len);
+ /* Update counters from the length and step the data pointer */
+ uart_info->rx_count -= len;
+ count -= len;
+ r_ptr += len;
+
+ if (uart_info->rx_count)
+ /*
+ * More data to receive to current packet. Break and
+ * wait for next data on the UART.
+ */
+ break;
+
+ /* Handle the different states */
+ tmp = uart_info->rx_skb->data + CG2900_SKB_RESERVE;
+ switch (uart_info->rx_state) {
+ case W4_DATA:
+ /*
+ * Whole data packet has been received.
+ * Transmit it to CG2900 Core.
+ */
+ send_skb_to_core(uart_info->rx_skb);
+
+ uart_info->rx_state = W4_PACKET_TYPE;
+ uart_info->rx_skb = NULL;
+ continue;
+
+ case W4_EVENT_HDR:
+ evt = (struct hci_event_hdr *)tmp;
+ check_data_len(evt->plen);
+ /* Header read. Continue with next bytes */
+ continue;
+
+ case W4_ACL_HDR:
+ acl = (struct hci_acl_hdr *)tmp;
+ check_data_len(le16_to_cpu(acl->dlen));
+ /* Header read. Continue with next bytes */
+ continue;
+
+ case W4_FM_RADIO_HDR:
+ fm = (union fm_leg_evt_or_irq *)tmp;
+ check_data_len(fm->param_length);
+ /* Header read. Continue with next bytes */
+ continue;
+
+ case W4_GNSS_HDR:
+ gnss = (struct gnss_hci_hdr *)tmp;
+ check_data_len(le16_to_cpu(gnss->plen));
+ /* Header read. Continue with next bytes */
+ continue;
+
+ default:
+ CG2900_ERR("Bad state indicating memory overwrite "
+ "(0x%X)", (u8)(uart_info->rx_state));
+ break;
+ }
+
+check_h4_header:
+ /* Check which H:4 packet this is and update RX states */
+ if (*r_ptr == HCI_BT_EVT_H4_CHANNEL) {
+ uart_info->rx_state = W4_EVENT_HDR;
+ uart_info->rx_count = HCI_BT_EVT_HDR_SIZE;
+ } else if (*r_ptr == HCI_BT_ACL_H4_CHANNEL) {
+ uart_info->rx_state = W4_ACL_HDR;
+ uart_info->rx_count = HCI_BT_ACL_HDR_SIZE;
+ } else if (*r_ptr == HCI_FM_RADIO_H4_CHANNEL) {
+ uart_info->rx_state = W4_FM_RADIO_HDR;
+ uart_info->rx_count = HCI_FM_RADIO_HDR_SIZE;
+ } else if (*r_ptr == HCI_GNSS_H4_CHANNEL) {
+ uart_info->rx_state = W4_GNSS_HDR;
+ uart_info->rx_count = HCI_GNSS_HDR_SIZE;
+ } else {
+ CG2900_ERR("Unknown HCI packet type 0x%X", (u8)*r_ptr);
+ r_ptr++;
+ count--;
+ continue;
+ }
+
+ /*
+ * Allocate packet. We do not yet know the size and therefore
+ * allocate max size.
+ */
+ uart_info->rx_skb = alloc_rx_skb(RX_SKB_MAX_SIZE, GFP_ATOMIC);
+ if (!uart_info->rx_skb) {
+ CG2900_ERR("Can't allocate memory for new packet");
+ uart_info->rx_state = W4_PACKET_TYPE;
+ uart_info->rx_count = 0;
+ return 0;
+ }
+
+ /* Write the H:4 header first in the sk_buffer */
+ w_ptr = skb_put(uart_info->rx_skb, 1);
+ *w_ptr = *r_ptr;
+
+ /* First byte (H4 header) read. Goto next byte */
+ r_ptr++;
+ count--;
+ }
+
+ return count;
+}
+
+/**
+ * uart_tty_open() - Called when UART line discipline changed to N_HCI.
+ * @tty: Pointer to associated TTY instance data.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * Errors from cg2900_register_trans_driver.
+ */
+static int uart_tty_open(struct tty_struct *tty)
+{
+ int err;
+
+ CG2900_INFO("uart_tty_open");
+
+ /* Set the structure pointers and set the UART receive room */
+ uart_info->tty = tty;
+ tty->disc_data = NULL;
+ tty->receive_room = UART_RECEIVE_ROOM;
+
+ /*
+ * Flush any pending characters in the driver and line discipline.
+ * Don't use ldisc_ref here as the open path is before the ldisc is
+ * referencable.
+ */
+ if (tty->ldisc->ops->flush_buffer)
+ tty->ldisc->ops->flush_buffer(tty);
+
+ tty_driver_flush_buffer(tty);
+
+ /* Tell CG2900 Core that UART is connected */
+ err = cg2900_register_trans_driver(&uart_cb, NULL);
+ if (err)
+ CG2900_ERR("Could not register transport driver (%d)", err);
+
+ if (tty->ops->tiocmget && tty->ops->break_ctl)
+ uart_info->sleep_allowed = true;
+ else
+ CG2900_ERR("Sleep mode not available.");
+
+ return err;
+
+}
+
+/**
+ * uart_tty_close() - Close UART tty.
+ * @tty: Pointer to associated TTY instance data.
+ *
+ * The uart_tty_close() function is called when the line discipline is changed
+ * to something else, the TTY is closed, or the TTY detects a hangup.
+ */
+static void uart_tty_close(struct tty_struct *tty)
+{
+ int err;
+
+ CG2900_INFO("uart_tty_close");
+
+ BUG_ON(!uart_info);
+ BUG_ON(!uart_info->wq);
+
+ err = create_work_item(uart_info->wq, work_hw_deregistered, NULL);
+ if (err)
+ CG2900_ERR("Failed to create work item (%d) "
+ "work_hw_deregistered", err);
+}
+
+/**
+ * uart_tty_wakeup() - Callback function for transmit wake up.
+ * @tty: Pointer to associated TTY instance data.
+ *
+ * The uart_tty_wakeup() callback function is called when low level
+ * device driver can accept more send data.
+ */
+static void uart_tty_wakeup(struct tty_struct *tty)
+{
+ int err;
+
+ CG2900_INFO("uart_tty_wakeup");
+
+ /*
+ * Clear the TTY_DO_WRITE_WAKEUP bit that is set in
+ * work_do_transmit().
+ */
+ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+
+ if (tty != uart_info->tty)
+ return;
+
+ /* Delete sleep timer. */
+ (void)del_timer(&uart_info->timer);
+
+ /* Start TX operation */
+ err = create_work_item(uart_info->wq, work_do_transmit, NULL);
+ if (err)
+ CG2900_ERR("Failed to create work item (%d) uart_tty_wakeup",
+ err);
+}
+
+/**
+ * uart_tty_receive() - Called by TTY low level driver when receive
data is available.
+ * @tty: Pointer to TTY instance data
+ * @data: Pointer to received data
+ * @flags: Pointer to flags for data
+ * @count: Count of received data in bytes
+ */
+static void uart_tty_receive(struct tty_struct *tty, const u8 *data,
+ char *flags, int count)
+{
+ CG2900_INFO("uart_tty_receive");
+
+ if (tty != uart_info->tty)
+ return;
+
+ CG2900_DBG_DATA("Received data with length = %d and first byte 0x%02X",
+ count, data[0]);
+ CG2900_DBG_DATA_CONTENT("uart_tty_receive", data, count);
+
+ /* Restart data timer */
+ update_timer();
+ spin_lock(&uart_info->rx_lock);
+ uart_receive_skb(data, count);
+ spin_unlock(&uart_info->rx_lock);
+
+}
+
+/**
+ * uart_tty_ioctl() - Process IOCTL system call for the TTY device.
+ * @tty: Pointer to TTY instance data.
+ * @file: Pointer to open file object for device.
+ * @cmd: IOCTL command code.
+ * @arg: Argument for IOCTL call (cmd dependent).
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EBADF if supplied TTY struct is not correct.
+ * Error codes from n_tty_iotcl_helper.
+ */
+static int uart_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+
+ CG2900_INFO("uart_tty_ioctl cmd %d", cmd);
+ 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 HCIUARTSETFD:
+ /* Save file object to device. */
+ if (!uart_info->fd)
+ uart_info->fd = file;
+ else
+ CG2900_DBG("Cannot store file object to device.");
+ break;
+ case HCIUARTSETPROTO: /* Fallthrough */
+ case HCIUARTGETPROTO:
+ case HCIUARTGETDEVICE:
+ /*
+ * We don't do anything special here, but we have to show we
+ * handle it.
+ */
+ break;
+
+ default:
+ err = n_tty_ioctl_helper(tty, file, cmd, arg);
+ break;
+ };
+
+ return err;
+}
+
+/*
+ * We don't provide read/write/poll interface for user space.
+ */
+static ssize_t uart_tty_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t nr)
+{
+ CG2900_INFO("uart_tty_read");
+ return 0;
+}
+
+static ssize_t uart_tty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *data, size_t count)
+{
+ CG2900_INFO("uart_tty_write");
+ return count;
+}
+
+static unsigned int uart_tty_poll(struct tty_struct *tty, struct file *filp,
+ poll_table *wait)
+{
+ return 0;
+}
+
+/* Generic functions */
+
+/* The uart_ldisc structure is used when registering to the UART framework. */
+static struct tty_ldisc_ops uart_ldisc = {
+ .magic = TTY_LDISC_MAGIC,
+ .name = "n_hci",
+ .open = uart_tty_open,
+ .close = uart_tty_close,
+ .read = uart_tty_read,
+ .write = uart_tty_write,
+ .ioctl = uart_tty_ioctl,
+ .poll = uart_tty_poll,
+ .receive_buf = uart_tty_receive,
+ .write_wakeup = uart_tty_wakeup,
+ .owner = THIS_MODULE
+};
+
+/**
+ * cg2900_uart_probe() - Initialize CG2900 UART resources.
+ * @pdev: Platform device.
+ *
+ * This function initializes the module and registers to the UART framework.
+ *
+ * Returns:
+ * 0 if success.
+ * -ENOMEM for failed alloc or structure creation.
+ * -ECHILD for failed work queue creation.
+ * Error codes generated by tty_register_ldisc.
+ */
+static int __devinit cg2900_uart_probe(struct platform_device *pdev)
+{
+ int err = 0;
+
+ CG2900_INFO("cg2900_uart_probe");
+
+ uart_info = kzalloc(sizeof(*uart_info), GFP_KERNEL);
+ if (!uart_info) {
+ CG2900_ERR("Couldn't allocate uart_info");
+ return -ENOMEM;
+ }
+
+ uart_info->sleep_state = CHIP_POWERED_DOWN;
+ skb_queue_head_init(&uart_info->tx_queue);
+ mutex_init(&uart_info->tx_mutex);
+ spin_lock_init(&uart_info->rx_lock);
+ mutex_init(&(uart_info->sleep_state_lock));
+
+ /* Init UART TX work queue */
+ uart_info->wq = create_singlethread_workqueue(UART_WQ_NAME);
+ if (!uart_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ECHILD; /* No child processes */
+ goto error_handling_wq;
+ }
+ init_timer(&uart_info->timer);
+ uart_info->timer.function = sleep_timer_expired;
+ uart_info->timer.expires = jiffies + cg2900_get_sleep_timeout();
+
+ /* Register the tty discipline. We will see what will be used. */
+ err = tty_register_ldisc(N_HCI, &uart_ldisc);
+ if (err) {
+ CG2900_ERR("HCI line discipline registration failed. (0x%X)",
+ err);
+ goto error_handling_register;
+ }
+
+ uart_info->dev = &pdev->dev;
+
+ goto finished;
+
+error_handling_register:
+ destroy_workqueue(uart_info->wq);
+error_handling_wq:
+ mutex_destroy(&uart_info->tx_mutex);
+ kfree(uart_info);
+ uart_info = NULL;
+finished:
+ return err;
+}
+
+/**
+ * cg2900_uart_remove() - Release CG2900 UART resources.
+ * @pdev: Platform device.
+ *
+ * Returns:
+ * 0 if success.
+ * Error codes generated by tty_unregister_ldisc.
+ */
+static int __devexit cg2900_uart_remove(struct platform_device *pdev)
+{
+ int err;
+
+ CG2900_INFO("cg2900_uart_remove");
+
+ /* Release tty registration of line discipline */
+ err = tty_unregister_ldisc(N_HCI);
+ if (err)
+ CG2900_ERR("Can't unregister HCI line discipline (%d)", err);
+
+ if (!uart_info)
+ return err;
+
+ destroy_workqueue(uart_info->wq);
+ mutex_destroy(&uart_info->tx_mutex);
+
+ kfree(uart_info);
+ uart_info = NULL;
+ return err;
+}
+
+static struct platform_driver cg2900_uart_driver = {
+ .driver = {
+ .name = "cg2900-uart",
+ .owner = THIS_MODULE,
+ },
+ .probe = cg2900_uart_probe,
+ .remove = __devexit_p(cg2900_uart_remove),
+#ifdef CONFIG_PM
+ .suspend = cg2900_uart_suspend,
+ .resume = cg2900_uart_resume
+#endif
+};
+
+/**
+ * cg2900_uart_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_uart_init(void)
+{
+ CG2900_INFO("cg2900_uart_init");
+ return platform_driver_register(&cg2900_uart_driver);
+}
+
+/**
+ * cg2900_uart_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_uart_exit(void)
+{
+ CG2900_INFO("cg2900_uart_exit");
+ platform_driver_unregister(&cg2900_uart_driver);
+}
+
+module_init(cg2900_uart_init);
+module_exit(cg2900_uart_exit);
+
+module_param(uart_default_baud, int, S_IRUGO);
+MODULE_PARM_DESC(uart_default_baud,
+ "Default UART baud rate, e.g. 115200. If not set 115200 will "
+ "be used.");
+
+module_param(uart_high_baud, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(uart_high_baud,
+ "High speed UART baud rate, e.g. 4000000. If not set 3000000 "
+ "will be used.");
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("ST-Ericsson CG2900 UART Driver");
--
1.6.3.3
^ permalink raw reply related
* [PATCH 4/9] mfd: Add chip handler for the ST-Ericsson STLC2690.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:37 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds a chip handler for the ST-Ericsson STLC2690 Connectivity
Combo controller.
This patch adds all functionality needed towards the STLC2690, including
patch downloading, chip startup, and chip specific functionality.
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 | 1150 ++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/stlc2690_chip.h | 37 ++
4 files changed, 1194 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 fca7e29..a8e790f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -276,6 +276,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
+
config PMIC_DA903X
bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
depends on I2C=y
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index c4aabf3..4935daa 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -8,4 +8,5 @@ export-objs := cg2900_core.o
obj-$(CONFIG_MFD_CG2900) += cg2900_char_devices.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..a7b81b9
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.c
@@ -0,0 +1,1150 @@
+/*
+ * 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 <asm/byteorder.h>
+#include <linux/firmware.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/stat.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "hci_defines.h"
+#include "stlc2690_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+/*
+ * Max length in bytes for line buffer used to parse settings and patch file.
+ * Must be max length of name plus characters used to define chip version.
+ */
+#define LINE_BUFFER_LENGTH (NAME_MAX + 30)
+
+#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.
+ * @dev: Device structure.
+ * @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 {
+ struct device *dev;
+ 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, NAME_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->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->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->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->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_chip_probe() - Initialize STLC2690 chip handler resources.
+ * @pdev: Platform device.
+ *
+ * This function initialize the STLC2690 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 __devinit stlc2690_chip_probe(struct platform_device *pdev)
+{
+ int err = 0;
+
+ CG2900_INFO("stlc2690_chip_probe");
+
+ stlc2690_info = kzalloc(sizeof(*stlc2690_info), GFP_ATOMIC);
+ if (!stlc2690_info) {
+ CG2900_ERR("Couldn't allocate stlc2690_info");
+ err = -ENOMEM;
+ goto finished;
+ }
+
+ stlc2690_info->dev = &(pdev->dev);
+
+ 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(NAME_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(NAME_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_chip_remove() - Release STLC2690 chip handler resources.
+ * @pdev: Platform device.
+ *
+ * Returns:
+ * 0 if success (always success).
+ */
+static int __devexit stlc2690_chip_remove(struct platform_device *pdev)
+{
+ CG2900_INFO("stlc2690_chip_remove");
+
+ if (!stlc2690_info)
+ return 0;
+
+ kfree(stlc2690_info->settings_file_name);
+ kfree(stlc2690_info->patch_file_name);
+ destroy_workqueue(stlc2690_info->wq);
+ kfree(stlc2690_info);
+ stlc2690_info = NULL;
+ return 0;
+}
+
+static struct platform_driver stlc2690_chip_driver = {
+ .driver = {
+ .name = "stlc2690-chip",
+ .owner = THIS_MODULE,
+ },
+ .probe = stlc2690_chip_probe,
+ .remove = __devexit_p(stlc2690_chip_remove),
+};
+
+/**
+ * stlc2690_chip_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init stlc2690_chip_init(void)
+{
+ CG2900_INFO("stlc2690_chip_init");
+ return platform_driver_register(&stlc2690_chip_driver);
+}
+
+/**
+ * stlc2690_chip_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit stlc2690_chip_exit(void)
+{
+ CG2900_INFO("stlc2690_chip_exit");
+ platform_driver_unregister(&stlc2690_chip_driver);
+}
+
+module_init(stlc2690_chip_init);
+module_exit(stlc2690_chip_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
* [PATCH 3/9] MFD: Add chip handler for the ST-Ericsson CG2900.
From: Par-Gunnar Hjalmdahl @ 2010-10-22 10:36 UTC (permalink / raw)
To: linus.walleij, linux-bluetooth, linux-kernel
This patch adds a chip handler for the ST-Ericsson CG2900 Connectivity
Combo controller.
This patch adds all functionality needed towards the CG2900, including
patch downloading, chip startup, and chip specific functionality.
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 | 2238 ++++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/cg2900_chip.h | 588 ++++++++++
4 files changed, 2834 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 3ee9c66..fca7e29 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -270,6 +270,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
+
config PMIC_DA903X
bool "Dialog Semiconductor DA9030/DA9034 PMIC Support"
depends on I2C=y
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 0ac9bc6..c4aabf3 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -7,3 +7,5 @@ export-objs := cg2900_core.o
obj-$(CONFIG_MFD_CG2900) += cg2900_char_devices.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..2e3c167
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.c
@@ -0,0 +1,2238 @@
+/*
+ * 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 <asm/byteorder.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/stat.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/cg2900.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include "cg2900_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+
+/*
+ * Max length in bytes for line buffer used to parse settings and patch file.
+ * Must be max length of name plus characters used to define chip version.
+ */
+#define LINE_BUFFER_LENGTH (NAME_MAX + 30)
+
+#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_DISABLE_BT: Disable BT Core.
+ * @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_DISABLE_BT,
+ 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.
+ * @dev: Device structure.
+ * @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 {
+ struct device *dev;
+ 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) {
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)
+ (skb->data + HCI_H4_SIZE);
+
+ cg2900_info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode);
+ 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, NAME_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->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 = NULL;
+ u8 *h4_header;
+ struct cg2900_hci_logger_config *logger_config;
+ struct cg2900_platform_data *pf_data;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ /*
+ * Get the VS Power Switch Off command to use based on connected
+ * connectivity controller
+ */
+ pf_data = (struct cg2900_platform_data *)
+ cg2900_info->dev->parent->platform_data;
+ if (pf_data->get_power_switch_off_cmd)
+ skb = pf_data->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->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->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->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_write_file_block_cmd_status() - Handles HCI VS
WriteFileBlock Command Status event.
+ * @status: Returned status of WriteFileBlock command.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_write_file_block_cmd_status(u8 status)
+{
+ if ((cg2900_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (cg2900_info->download_state != DOWNLOAD_PENDING))
+ return false;
+
+ /*
+ * Only do something if there is an error. Otherwise we will wait for
+ * CmdComplete.
+ */
+ if (HCI_BT_ERROR_NO_ERROR != status) {
+ CG2900_ERR("Command status 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;
+
+ CG2900_INFO("handle_vs_power_switch_off_cmd_complete");
+
+ /*
+ * 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];
+ struct bt_vs_bt_enable_cmd cmd;
+
+ if (cg2900_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+ return false;
+
+ CG2900_INFO("handle_vs_system_reset_cmd_complete");
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * We are now almost finished. Shut off BT Core. It will be
+ * re-enabled by the Bluetooth driver when needed.
+ */
+ SET_BOOT_STATE(BOOT_DISABLE_BT);
+ cmd.op_code = cpu_to_le16(CG2900_BT_OP_VS_BT_ENABLE);
+ cmd.plen = BT_PARAM_LEN(sizeof(cmd));
+ cmd.enable = CG2900_BT_DISABLE;
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } 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_bt_enable_cmd_status() - Handles HCI VS BtEnable Command
Status event.
+ * @status: Returned status of BtEnable command.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_bt_enable_cmd_status(u8 status)
+{
+ if (cg2900_info->boot_state != BOOT_DISABLE_BT)
+ return false;
+
+ CG2900_INFO("handle_vs_bt_enable_cmd_status");
+
+ /*
+ * Only do something if there is an error. Otherwise we will wait for
+ * CmdComplete.
+ */
+ if (HCI_BT_ERROR_NO_ERROR != status) {
+ CG2900_ERR("Received BtEnable status event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ }
+
+ return true;
+}
+
+/**
+ * handle_vs_bt_enable_cmd_complete() - Handle HCI VS BtEnable
Command Complete event.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * true, if packet was handled internally,
+ * false, otherwise.
+ */
+static bool handle_vs_bt_enable_cmd_complete(u8 *data)
+{
+ u8 status = data[0];
+
+ if (cg2900_info->boot_state != BOOT_DISABLE_BT)
+ return false;
+
+ CG2900_INFO("handle_vs_bt_enable_cmd_complete");
+
+ 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 BtEnable 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;
+ u16 op_code;
+
+ evt = (struct hci_event_hdr *)data;
+ data += sizeof(*evt);
+
+ /* First check the event code. */
+ if (HCI_EV_CMD_COMPLETE == evt->evt) {
+ struct hci_ev_cmd_complete *cmd_complete;
+
+ 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);
+ /* Move to first byte after OCF */
+ data += sizeof(*cmd_complete);
+
+ 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);
+ else if (op_code == CG2900_BT_OP_VS_BT_ENABLE)
+ pkt_handled = handle_vs_bt_enable_cmd_complete(data);
+ } else if (HCI_EV_CMD_STATUS == evt->evt) {
+ struct hci_ev_cmd_status *cmd_status;
+
+ cmd_status = (struct hci_ev_cmd_status *)data;
+
+ op_code = le16_to_cpu(cmd_status->opcode);
+
+ CG2900_DBG_DATA("Received Command Status: op_code = 0x%04X",
+ op_code);
+
+ if (op_code == CG2900_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled = handle_vs_write_file_block_cmd_status
+ (cmd_status->status);
+ else if (op_code == CG2900_BT_OP_VS_BT_ENABLE)
+ pkt_handled = handle_vs_bt_enable_cmd_status
+ (cmd_status->status);
+ } else
+ return false;
+
+ 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) {
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)
+ (skb->data + HCI_H4_SIZE);
+
+ cg2900_info->audio_bt_cmd_op = le16_to_cpu(hdr->opcode);
+ 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)
+{
+ struct hci_event_hdr *hdr = (struct hci_event_hdr *)
+ &(skb->data[CG2900_SKB_RESERVE]);
+ u8 *payload = (u8 *)(hdr + 1); /* follows header */
+ u16 opcode = 0;
+
+ if (h4_channel != CHANNEL_BT_EVT)
+ return false;
+
+ if (HCI_BT_EVT_CMD_COMPLETE == hdr->evt)
+ opcode = le16_to_cpu(
+ ((struct hci_ev_cmd_complete *)payload)->opcode);
+ else if (HCI_BT_EVT_CMD_STATUS == hdr->evt)
+ opcode = le16_to_cpu(
+ ((struct hci_ev_cmd_status *)payload)->opcode);
+
+ 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;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * 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_chip_probe() - Initialize CG2900 chip handler resources.
+ * @pdev: Platform device.
+ *
+ * This 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 __devinit cg2900_chip_probe(struct platform_device *pdev)
+{
+ int err = 0;
+
+ CG2900_INFO("cg2900_chip_probe");
+
+ 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->dev = &(pdev->dev);
+
+ 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(NAME_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(NAME_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_chip_remove() - Release CG2900 chip handler resources.
+ * @pdev: Platform device.
+ *
+ * Returns:
+ * 0 if success (always success).
+ */
+static int __devexit cg2900_chip_remove(struct platform_device *pdev)
+{
+ CG2900_INFO("cg2900_chip_remove");
+
+ if (!cg2900_info)
+ return 0;
+
+ kfree(cg2900_info->settings_file_name);
+ kfree(cg2900_info->patch_file_name);
+ destroy_workqueue(cg2900_info->wq);
+ kfree(cg2900_info);
+ cg2900_info = NULL;
+ return 0;
+}
+
+static struct platform_driver cg2900_chip_driver = {
+ .driver = {
+ .name = "cg2900-chip",
+ .owner = THIS_MODULE,
+ },
+ .probe = cg2900_chip_probe,
+ .remove = __devexit_p(cg2900_chip_remove),
+};
+
+/**
+ * cg2900_chip_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init cg2900_chip_init(void)
+{
+ CG2900_INFO("cg2900_chip_init");
+ return platform_driver_register(&cg2900_chip_driver);
+}
+
+/**
+ * cg2900_chip_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit cg2900_chip_exit(void)
+{
+ CG2900_INFO("cg2900_chip_exit");
+ platform_driver_unregister(&cg2900_chip_driver);
+}
+
+module_init(cg2900_chip_init);
+module_exit(cg2900_chip_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..5f1fe7a
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_chip.h
@@ -0,0 +1,588 @@
+/*
+ * 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"
+
+/*
+ * Utility
+ */
+
+static inline void set_low_nibble(__u8 *var, __u8 value)
+{
+ *var = (*var & 0xf0) | (value & 0x0f);
+}
+
+static inline void set_high_nibble(__u8 *var, __u8 value)
+{
+ *var = (*var & 0x0f) | (value << 4);
+}
+
+static inline void store_bit(__u8 *var, size_t bit, __u8 value)
+{
+ *var = (*var & ~(1u << bit)) | (value << bit);
+}
+
+/*
+ * General chip defines
+ */
+
+/* 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)
+
+struct bt_cmd_cmpl_event {
+ __u8 eventcode;
+ __u8 plen;
+ __u8 n_commands;
+ __le16 opcode;
+ /*
+ * According to BT-specification what follows is "parameters"
+ * and unique to every command, but all commands start the
+ * parameters with the status field so include it here for
+ * convenience
+ */
+ __u8 status;
+ __u8 data[];
+} __attribute__((packed));
+
+/* 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));
+
+#define CG2900_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE
+
+/* 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));
+
+#define CG2900_BT_DISABLE 0x00
+#define CG2900_BT_ENABLE 0x01
+
+/* BT VS BT Enable command */
+#define CG2900_BT_OP_VS_BT_ENABLE 0xFF10
+struct bt_vs_bt_enable_cmd {
+ __le16 op_code;
+ u8 plen;
+ u8 enable;
+} __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
+
+/*
+ * Common multimedia
+ */
+
+#define CG2900_CODEC_TYPE_NONE 0x00
+#define CG2900_CODEC_TYPE_SBC 0x01
+
+#define CG2900_PCM_MODE_SLAVE 0x00
+#define CG2900_PCM_MODE_MASTER 0x01
+
+#define CG2900_I2S_MODE_MASTER 0x00
+#define CG2900_I2S_MODE_SLAVE 0x01
+
+/*
+ * CG2900 PG1 multimedia API
+ */
+
+#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
+
+#define CG2900_BT_VS_SET_HARDWARE_CONFIG 0xFD54
+/* These don't have the same length, so a union won't work */
+struct bt_vs_set_hw_cfg_cmd_pcm {
+ __le16 opcode;
+ __u8 plen;
+ __u8 vp_type;
+ __u8 port_id;
+ __u8 mode_dir; /* NB: mode is in bit 1 (not 0) */
+ __u8 bit_clock;
+ __le16 frame_len;
+} __attribute__((packed));
+#define HWCONFIG_PCM_SET_MODE(pcfg, mode) \
+ set_low_nibble(&(pcfg)->mode_dir, (mode) << 1)
+#define HWCONFIG_PCM_SET_DIR(pcfg, idx, dir) \
+ store_bit(&(pcfg)->mode_dir, (idx) + 4, (dir))
+
+struct bt_vs_set_hw_cfg_cmd_i2s {
+ __le16 opcode;
+ __u8 plen;
+ __u8 vp_type;
+ __u8 port_id;
+ __u8 half_period;
+ __u8 master_slave;
+} __attribute__((packed));
+
+/* Max length for allocating */
+#define CG2900_BT_LEN_VS_SET_HARDWARE_CONFIG \
+ (sizeof(struct bt_vs_set_hw_cfg_cmd_pcm))
+
+#define CG2900_BT_VS_SET_SESSION_CONFIG 0xFD55
+struct session_config_vport {
+ __u8 type;
+ union {
+ struct {
+ __le16 acl_handle;
+ __u8 reserved[10];
+ } sco;
+ struct {
+ __u8 reserved[12];
+ } fm;
+ struct {
+ __u8 index;
+ __u8 slots_used;
+ __u8 slot_start[4];
+ __u8 reserved[6];
+ } pcm;
+ struct {
+ __u8 index;
+ __u8 channel;
+ __u8 reserved[10];
+ } i2s;
+ };
+} __attribute__((packed));
+#define SESSIONCFG_PCM_SET_USED(port, idx, use) \
+ store_bit(&(port).pcm.slots_used, (idx), (use))
+
+struct session_config_stream {
+ __u8 media_type;
+ __u8 csel_srate;
+ __u8 codec_type;
+ __u8 codec_mode;
+ __u8 codec_params[3];
+ struct session_config_vport inport;
+ struct session_config_vport outport;
+} __attribute__((packed));
+#define SESSIONCFG_SET_CHANNELS(pcfg, chnl) \
+ set_low_nibble(&(pcfg)->csel_srate, (chnl))
+#define SESSIONCFG_I2S_SET_SRATE(pcfg, rate) \
+ set_high_nibble(&(pcfg)->csel_srate, (rate))
+
+struct bt_vs_session_config_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 n_streams; /* we only support one here */
+ struct session_config_stream stream;
+} __attribute__((packed));
+
+#define CG2900_BT_SESSION_MEDIA_TYPE_AUDIO 0x00
+
+#define CG2900_BT_SESSION_RATE_8K 0x01
+#define CG2900_BT_SESSION_RATE_16K 0x02
+#define CG2900_BT_SESSION_RATE_44_1K 0x04
+#define CG2900_BT_SESSION_RATE_48K 0x05
+
+#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
+
+#define CG2900_BT_SESSION_I2S_INDEX_I2S 0x00
+#define CG2900_BT_SESSION_PCM_INDEX_PCM_I2S 0x00
+
+
+#define CG2900_BT_VS_SESSION_CTRL 0xFD57
+struct bt_vs_session_ctrl_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 id;
+ __u8 control;
+} __attribute__((packed));
+
+#define CG2900_BT_SESSION_START 0x00
+#define CG2900_BT_SESSION_STOP 0x01
+#define CG2900_BT_SESSION_PAUSE 0x02
+#define CG2900_BT_SESSION_RESUME 0x03
+
+#define CG2900_BT_VS_RESET_SESSION_CONFIG 0xFD56
+struct bt_vs_reset_session_cfg_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 id;
+} __attribute__((packed));
+
+/*
+ * CG2900 PG2 multimedia API
+ */
+
+#define CG2900_MC_PORT_PCM_I2S 0x00
+#define CG2900_MC_PORT_I2S 0x01
+#define CG2900_MC_PORT_BT_SCO 0x04
+#define CG2900_MC_PORT_FM_RX_0 0x07
+#define CG2900_MC_PORT_FM_RX_1 0x08
+#define CG2900_MC_PORT_FM_TX 0x09
+
+#define CG2900_MC_VS_PORT_CONFIG 0xFD64
+struct mc_vs_port_cfg_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 type;
+ /*
+ * one of the following configuration structs should follow, but they
+ * have different lengths so a union will not work
+ */
+} __attribute__((packed));
+
+struct mc_vs_port_cfg_pcm_i2s {
+ __u8 role_dir;
+ __u8 sco_a2dp_slots_used;
+ __u8 fm_slots_used;
+ __u8 ring_slots_used;
+ __u8 slot_start[4];
+ __u8 ratio_mode;
+ __u8 frame_len;
+ __u8 bitclk_srate;
+} __attribute__((packed));
+#define PORTCFG_PCM_SET_ROLE(cfg, role) \
+ set_low_nibble(&(cfg).role_dir, (role))
+#define PORTCFG_PCM_SET_DIR(cfg, idx, dir) \
+ store_bit(&(cfg).role_dir, (idx) + 4, (dir))
+static inline void portcfg_pcm_set_sco_used(struct mc_vs_port_cfg_pcm_i2s *cfg,
+ size_t index, __u8 use)
+{
+ if (use) {
+ /* clear corresponding slot in all cases */
+ cfg->sco_a2dp_slots_used &= ~(0x11 << index);
+ cfg->fm_slots_used &= ~(0x11 << index);
+ cfg->ring_slots_used &= ~(0x11 << index);
+ /* set for sco */
+ cfg->sco_a2dp_slots_used |= (1u << index);
+ } else {
+ /* only clear for sco */
+ cfg->sco_a2dp_slots_used &= ~(1u << index);
+ }
+}
+#define PORTCFG_PCM_SET_SCO_USED(cfg, idx, use) \
+ portcfg_pcm_set_sco_used(&cfg, idx, use)
+#define PORTCFG_PCM_SET_RATIO(cfg, r) \
+ set_low_nibble(&(cfg).ratio_mode, (r))
+#define PORTCFG_PCM_SET_MODE(cfg, mode) \
+ set_high_nibble(&(cfg).ratio_mode, (mode))
+#define PORTCFG_PCM_SET_BITCLK(cfg, clk) \
+ set_low_nibble(&(cfg).bitclk_srate, (clk))
+#define PORTCFG_PCM_SET_SRATE(cfg, rate) \
+ set_high_nibble(&(cfg).bitclk_srate, (rate))
+
+#define CG2900_MC_PCM_SAMPLE_RATE_8 1
+#define CG2900_MC_PCM_SAMPLE_RATE_16 2
+#define CG2900_MC_PCM_SAMPLE_RATE_44_1 4
+#define CG2900_MC_PCM_SAMPLE_RATE_48 6
+
+struct mc_vs_port_cfg_i2s {
+ __u8 role_hper;
+ __u8 csel_srate;
+ __u8 wordlen;
+};
+#define PORTCFG_I2S_SET_ROLE(cfg, role) \
+ set_low_nibble(&(cfg).role_hper, (role))
+#define PORTCFG_I2S_SET_HALFPERIOD(cfg, hper) \
+ set_high_nibble(&(cfg).role_hper, (hper))
+#define PORTCFG_I2S_SET_CHANNELS(cfg, chnl) \
+ set_low_nibble(&(cfg).csel_srate, (chnl))
+#define PORTCFG_I2S_SET_SRATE(cfg, rate) \
+ set_high_nibble(&(cfg).csel_srate, (rate))
+#define PORTCFG_I2S_SET_WORDLEN(cfg, len) \
+ set_low_nibble(&(cfg).wordlen, len)
+
+#define CG2900_MC_I2S_RIGHT_CHANNEL 1
+#define CG2900_MC_I2S_LEFT_CHANNEL 2
+#define CG2900_MC_I2S_BOTH_CHANNELS 3
+
+#define CG2900_MC_I2S_SAMPLE_RATE_8 0
+#define CG2900_MC_I2S_SAMPLE_RATE_16 1
+#define CG2900_MC_I2S_SAMPLE_RATE_44_1 2
+#define CG2900_MC_I2S_SAMPLE_RATE_48 4
+
+#define CG2900_MC_I2S_WORD_16 1
+#define CG2900_MC_I2S_WORD_32 3
+
+struct mc_vs_port_cfg_fm {
+ __u8 srate; /* NB: value goes in _upper_ nibble! */
+};
+#define PORTCFG_FM_SET_SRATE(cfg, rate) \
+ set_high_nibble(&(cfg).srate, (rate))
+
+struct mc_vs_port_cfg_sco {
+ __le16 acl_id;
+ __u8 wbs_codec;
+ __u8 sbc_params[3]; /* replace when we actually enable WBS... */
+} __attribute__((packed));
+#define PORTCFG_SCO_SET_WBS(cfg, wbs) \
+ set_low_nibble(&(cfg).wbs_codec, (wbs))
+#define PORTCFG_SCO_SET_CODEC(cfg, codec) \
+ set_high_nibble(&(cfg).wbs_codec, (codec))
+
+#define CG2900_MC_VS_CREATE_STREAM 0xFD66
+struct mc_vs_create_stream_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 id;
+ __u8 inport;
+ __u8 outport;
+ __u8 order; /* NB: not used by chip */
+} __attribute__((packed));
+
+#define CG2900_MC_VS_DELETE_STREAM 0xFD67
+struct mc_vs_delete_stream_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 stream;
+} __attribute__((packed));
+
+#define CG2900_MC_VS_STREAM_CONTROL 0xFD68
+struct mc_vs_stream_ctrl_cmd {
+ __le16 opcode;
+ __u8 plen;
+ __u8 command;
+ __u8 n_streams;
+ __u8 stream[];
+} __attribute__((packed));
+
+#define CG2900_MC_STREAM_START 0x00
+#define CG2900_MC_STREAM_STOP 0x01
+#define CG2900_MC_STREAM_STOP_FLUSH 0x02
+
+#define CG2900_MC_VS_SET_FM_START_MODE 0xFD69
+
+/*
+ * 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 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 Parameter Lengths = FM command length - length field (1 byte) */
+#define CG2900_FM_CMD_PARAM_LEN(len) (len - 1)
+
+/*
+ * FM Command ID mapped per byte and shifted 3 bits left
+ * Also adds number of parameters at first 3 bits of LSB.
+ */
+static inline __u16 cg2900_get_fm_cmd_id(__u16 opcode)
+{
+ return opcode >> 3;
+}
+
+static inline __u16 cg2900_make_fm_cmd_id(__u16 id, __u8 num_params)
+{
+ return (id << 3) | num_params;
+}
+
+/*
+ * GNSS
+ */
+
+struct gnss_hci_hdr {
+ __u8 op_code;
+ __le16 plen;
+} __attribute__((packed));
+
+#endif /* _CG2900_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