* Re: [PATCH 1/6] This patch adds support for the ST-Ericsson CG2900
From: Mark Brown @ 2010-09-27 15:15 UTC (permalink / raw)
To: Par-Gunnar Hjalmdahl
Cc: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
In-Reply-To: <AANLkTin=PPfgnD5tKBPR1QXf2OeBKBH-kXtoT+s-KPhk@mail.gmail.com>
On Mon, Sep 27, 2010 at 10:16:11AM +0200, Par-Gunnar Hjalmdahl wrote:
> 2010/9/24 Mark Brown <broonie@opensource.wolfsonmicro.com>:
> > On Fri, Sep 24, 2010 at 03:46:44PM +0200, Par-Gunnar Hjalmdahl wrote:
> >> +static irqreturn_t cg2900_devices_interrupt(int irq, void *dev_id)
> >> +{
> >> + disable_irq_nosync(irq);
> >> + if (cg2900_dev_callback && cg2900_dev_callback->interrupt_cb)
> >> + cg2900_dev_callback->interrupt_cb();
> >> +
> >> + return IRQ_HANDLED;
> >> +}
> > Why is there this callback mechanism - I'd expect the users of this code
> > to just be using the standard IRQ infrastructure?
> This is our local IRQ which is handled in cg2900_uart.c by creating a work:
I can tell it's an IRQ - that's my point; the kernel has an interrupt
handling subsystem which this isn't using.
> I understand your concern that client implementing the
> cg2900_dev_callback->interrupt_cb();
> might not know realized that this is irq context which might cause problems.
No, you're misunderstanding my point. The kernel has facilities for
representing interrupt controllers which this is not using.
> If not then suggest what is expected way of handling it ? eg. moving
> workqueue down to cg2900_devices
> or other solution?
Use the standard kernel IRQ framework to report interrupts to the
clients - there's quite a few examples of using this in the drivers/mfd
code. This will make your code more idiomatic and give you access to
all the diagnostic infrastructure the kernel provides for interrupts.
^ permalink raw reply
* Re: [PATCH] Add test/test-attrib for testing Attribute API
From: Anderson Lizardo @ 2010-09-27 15:14 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Anderson Lizardo
In-Reply-To: <1285248924-12834-1-git-send-email-anderson.lizardo@openbossa.org>
On Thu, Sep 23, 2010 at 9:35 AM, Anderson Lizardo
<anderson.lizardo@openbossa.org> wrote:
> This is an initial version. Watcher registration is not working properly
> yet. For now it will list all characteristics and its properties.
Please ignore this pull request. I'm updating attrib/test-attrib to
have a working Watcher registration implementation.
Regards,
--
Anderson Lizardo
OpenBossa Labs - INdT
Manaus - Brazil
^ permalink raw reply
* Re: [PATCHv4 0/3] Bluetooth: L2CAP robustness fixes
From: Andrei Emeltchenko @ 2010-09-27 12:50 UTC (permalink / raw)
To: linux-bluetooth
In-Reply-To: <1284550124-31201-1-git-send-email-Andrei.Emeltchenko.news@gmail.com>
Ping
On Wed, Sep 15, 2010 at 2:28 PM, Emeltchenko Andrei
<Andrei.Emeltchenko.news@gmail.com> wrote:
> From: Andrei Emeltchenko <andrei.emeltchenko@nokia.com>
>
> Fixes increase L2CAP robustness to DOS attacks to BT stack.
> Updated version after comments from Mat Martineau <mathewm@codeaurora.org>
> and Gustavo Padovan <padovan@profusion.mobi>.
>
> Andrei Emeltchenko (3):
> Bluetooth: fix MTU L2CAP configuration parameter
> Bluetooth: check for l2cap header in start fragment
> Bluetooth: check L2CAP length in first ACL fragment
>
> net/bluetooth/l2cap.c | 25 +++++++++++++++++++++----
> 1 files changed, 21 insertions(+), 4 deletions(-)
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* Re: [PATCH 1/6] This patch adds support for the ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-09-27 8:16 UTC (permalink / raw)
To: Mark Brown; +Cc: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
In-Reply-To: <20100924141046.GA26454@sirena.org.uk>
Hi again Mark,
I've not got an answer from the developer of the particular piece of code.
2010/9/24 Mark Brown <broonie@opensource.wolfsonmicro.com>:
> On Fri, Sep 24, 2010 at 03:46:44PM +0200, Par-Gunnar Hjalmdahl wrote:
>
>> +static irqreturn_t cg2900_devices_interrupt(int irq, void *dev_id)
>> +{
>> + disable_irq_nosync(irq);
>> + if (cg2900_dev_callback && cg2900_dev_callback->interrupt_cb)
>> + cg2900_dev_callback->interrupt_cb();
>> +
>> + return IRQ_HANDLED;
>> +}
>
> Why is there this callback mechanism - I'd expect the users of this code
> to just be using the standard IRQ infrastructure?
>
This is our local IRQ which is handled in cg2900_uart.c by creating a work:
static void cg2900_devices_irq_cb(void)
{
/* Create work and leave irq context. */
(void)create_work_item(uart_info->wq, handle_cts_irq, NULL);
}
I understand your concern that client implementing the
cg2900_dev_callback->interrupt_cb();
might not know realized that this is irq context which might cause problems.
My motivation for doing like this that we do not expect other clients
of this code, so there
is no such risk. We create work in cg2900_uart and leave function.
Let me know if it is ok.
If not then suggest what is expected way of handling it ? eg. moving
workqueue down to cg2900_devices
or other solution?
Best regards,
/Lukasz via P-G
^ permalink raw reply
* Re: Bluez simple-agent vs agent
From: Ville Tervo @ 2010-09-27 8:00 UTC (permalink / raw)
To: ext Viswanathan Sankararam; +Cc: linux-bluetooth@vger.kernel.org
In-Reply-To: <AANLkTik_cwho9JNBPFhCDx0EGFcLN2+VTG8LT-D_Hw_3@mail.gmail.com>
Hi,
On Sat, Sep 25, 2010 at 03:11:32AM +0200, ext Viswanathan Sankararam wrote:
> Thanks Ville for letting me know about this. Is there a possibility to
> back port? Can I just blindly copy the net/bluetooth drivers to a
> linux-2.6.23 kernel and compile?
It won't work directly most probably.
--
Ville
^ permalink raw reply
* Spurious authentication request?
From: Daniel Örstadius @ 2010-09-27 7:52 UTC (permalink / raw)
To: linux-bluetooth
Hi,
Occasionally when pairing with a headset BlueZ issues what looks
to be an unnecessary "Authentication Requested" after
receiving "Auth Complete" and "Simple Pairing Complete".
2010-09-22 11:23:45.282348 > HCI Event: Auth Complete (0x06) plen 3
status 0x00 handle 2
2010-09-22 11:23:45.282378 < HCI Command: Set Connection Encryption
(0x01|0x0013) plen 3
handle 2 encrypt 0x01
[..]
2010-09-22 11:23:45.363646 < HCI Command: Authentication Requested
(0x01|0x0011) plen 2
handle 2
This request is coming from bonding_connect_cb and occurs if that
callback happens to be executed before the function
hcid_dbus_bonding_process_complete (triggered by the auth complete
event). The order between those functions might have been affected
by this patch
http://git.kernel.org/?p=bluetooth/bluez.git;a=commit;h=be35d00671519b8d92361c2caad9abeedda151f8
The request for authentication seems to be used for legacy pairing and
should probably not be made for simple pairing. Also, it might be
inconsistent that the kernel issues "Authentication Requested" for
simple pairing, but bluetoothd does it for legacy?
I guess enclosing the call in a check for legacy pairing would
work, if that information is or can be made available to
bluetoothd.
Have I understood the situation correctly and if so, what would
be a good way to fix this issue?
Thanks,
Daniel
^ permalink raw reply
* Re: Support for Device ID profile
From: Jose Antonio Santos Cadenas @ 2010-09-27 7:04 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: steven bluez, linux-bluetooth
In-Reply-To: <AANLkTi=aX_XQqEwa3q=1krNfpYxGFbkc2V4wEGcT9oHA@mail.gmail.com>
Hi,
2010/9/24 Luiz Augusto von Dentz <luiz.dentz@gmail.com>:
> Hi Steven,
>
> Yes, you can use DeviceID on main.conf to add that, but as you see it
> is pretty limited so we are planning to have a better support for
> this, perhaps extend adapter driver interface where you can write a
> plugin to fill this information.
Is anyone working on this plugin now. I'd like to work on it if nobody
is working yet.
Regards.
>
> Regards,
>
> On Fri, Sep 24, 2010 at 11:54 AM, steven bluez <steven.bluez@gmail.com> wrote:
>> Hi all,
>> Is there any support for Device ID Profile in Bluez 4.XX? versions ?\
>>
>> Regards,
>> Steven
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
>
>
> --
> Luiz Augusto von Dentz
> Computer Engineer
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply
* Re: Deadlock in Bluetooth code in 2.6.36
From: Gustavo F. Padovan @ 2010-09-26 0:14 UTC (permalink / raw)
To: David Miller; +Cc: netdev, linux-bluetooth, linux-kernel, marcel, mathewm
In-Reply-To: <20100924.211824.242130957.davem@davemloft.net>
* David Miller <davem@davemloft.net> [2010-09-24 21:18:24 -0700]:
> From: "Gustavo F. Padovan" <padovan@profusion.mobi>
> Date: Tue, 21 Sep 2010 18:20:12 -0300
>
> > My questions here is on how to fix this properly. Maybe
> > sock_alloc_send_skb() should not be used with SOCK_STREAM and reliable
> > protocols and I'm not aware of that? And should I use something like
> > sk_stream_alloc_skb() instead?
> >
> > Any comments are welcome. With lucky we can fix that for 2.6.36 and
> > together with others fixes we have queued deliver a fully functional
> > L2CAP layer on 2.6.36.
>
> Use sock_alloc_send_skb() as you do now, but if it fails wait for socket
> space to become available just like TCP does, then loop back and try to
> allocate again if the space-wait doesn't return an error.
sock_alloc_send_skb() doesn't fail when there is no space available
it sleeps and try again later. That is the problem. So if
sock_alloc_send_skb() is called with the socket lock held potentially we
can have a starvation in the backlog queue and then the deadlock due to
be unable to read the acknowledgments packets residing in the backlog
queue.
Our current solution is release the lock before call
sock_alloc_send_skb() and acquire it again when sock_alloc_send_skb()
returns. Patch follows:
------
Bluetooth: Fix deadlock in the ERTM logic
The Enhanced Retransmission Mode(ERTM) is a reliable mode of operation
of the Bluetooth L2CAP layer. Think on it like a simplified version of
TCP.
The problem we were facing here was a deadlock. ERTM uses a backlog
queue to queue incomimg packets while the user is helding the lock. At
some moment the sk_sndbuf can be exceeded and we can't alloc new skbs
then the code sleep with the lock to wait for memory, that stalls the
ERTM connection once we can't read the acknowledgements packets in the
backlog queue to free memory and make the allocation of outcoming skb
successful.
This patch actually affect all users of bt_skb_send_alloc(), i.e., all
L2CAP modes and SCO.
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
Signed-off-by: Ulisses Furquim <ulisses@profusion.mobi>
---
include/net/bluetooth/bluetooth.h | 11 +++++++++++
1 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index 27a902d..118b69b 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -161,10 +161,21 @@ static inline struct sk_buff *bt_skb_send_alloc(struct sock *sk, unsigned long l
{
struct sk_buff *skb;
+ release_sock(sk);
if ((skb = sock_alloc_send_skb(sk, len + BT_SKB_RESERVE, nb, err))) {
skb_reserve(skb, BT_SKB_RESERVE);
bt_cb(skb)->incoming = 0;
}
+ lock_sock(sk);
+
+ if (*err)
+ return NULL;
+
+ *err = sock_error(sk);
+ if (*err) {
+ kfree_skb(skb);
+ return NULL;
+ }
return skb;
}
--
1.7.2.2
--
Gustavo F. Padovan
ProFUSION embedded systems - http://profusion.mobi
^ permalink raw reply related
* Re: Deadlock in Bluetooth code in 2.6.36
From: David Miller @ 2010-09-25 4:18 UTC (permalink / raw)
To: padovan; +Cc: netdev, linux-bluetooth, linux-kernel, marcel, mathewm
In-Reply-To: <1285104013-9946-1-git-send-email-padovan@profusion.mobi>
From: "Gustavo F. Padovan" <padovan@profusion.mobi>
Date: Tue, 21 Sep 2010 18:20:12 -0300
> My questions here is on how to fix this properly. Maybe
> sock_alloc_send_skb() should not be used with SOCK_STREAM and reliable
> protocols and I'm not aware of that? And should I use something like
> sk_stream_alloc_skb() instead?
>
> Any comments are welcome. With lucky we can fix that for 2.6.36 and
> together with others fixes we have queued deliver a fully functional
> L2CAP layer on 2.6.36.
Use sock_alloc_send_skb() as you do now, but if it fails wait for socket
space to become available just like TCP does, then loop back and try to
allocate again if the space-wait doesn't return an error.
I think you should be able to reuse sk_stream_wait_memory() for this
purpose just fine and without any problems.
^ permalink raw reply
* Re: Bluez simple-agent vs agent
From: Viswanathan Sankararam @ 2010-09-25 1:11 UTC (permalink / raw)
To: Ville Tervo, linux-bluetooth
In-Reply-To: <20100924151552.GG4061@null>
Thanks Ville for letting me know about this. Is there a possibility to
back port? Can I just blindly copy the net/bluetooth drivers to a
linux-2.6.23 kernel and compile?
On Fri, Sep 24, 2010 at 8:15 AM, Ville Tervo <ville.tervo@nokia.com> wrote:
> Hi,
>
> On Fri, Sep 24, 2010 at 04:09:08PM +0200, ext Viswanathan Sankararam wrote:
>> Im using linux2.6.23... by todays standard, this is old I guess but
>> could it be the cause?
>
> Yes that ioctl was introduced in 2.6.27.
>
> And please don't remove mailing list from discussion.
>
> --
> Ville
>
^ permalink raw reply
* [PATCH 6/6] Bluetooth: Fix deadlock in the ERTM logic
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Gustavo F. Padovan
In-Reply-To: <1285376504-3541-6-git-send-email-gustavo@padovan.org>
From: Gustavo F. Padovan <padovan@profusion.mobi>
The Enhanced Retransmission Mode(ERTM) is a realiable mode of operation
of the Bluetooth L2CAP layer. Think on it like a simplified version of
TCP.
The problem we were facing here was a deadlock. ERTM uses a backlog
queue to queue incomimg packets while the user is helding the lock. At
some moment the sk_sndbuf can be exceeded and we can't alloc new skbs
then the code sleep with the lock to wait for memory, that stalls the
ERTM connection once we can't read the acknowledgements packets in the
backlog queue to free memory and make the allocation of outcoming skb
successful.
This patch actually affect all users of bt_skb_send_alloc(), i.e., all
L2CAP modes and SCO.
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
Signed-off-by: Ulisses Furquim <ulisses@profusion.mobi>
---
include/net/bluetooth/bluetooth.h | 11 +++++++++++
1 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index 27a902d..118b69b 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -161,10 +161,21 @@ static inline struct sk_buff *bt_skb_send_alloc(struct sock *sk, unsigned long l
{
struct sk_buff *skb;
+ release_sock(sk);
if ((skb = sock_alloc_send_skb(sk, len + BT_SKB_RESERVE, nb, err))) {
skb_reserve(skb, BT_SKB_RESERVE);
bt_cb(skb)->incoming = 0;
}
+ lock_sock(sk);
+
+ if (*err)
+ return NULL;
+
+ *err = sock_error(sk);
+ if (*err) {
+ kfree_skb(skb);
+ return NULL;
+ }
return skb;
}
--
1.7.2.2
^ permalink raw reply related
* [PATCH 5/6] Revert "Bluetooth: Don't accept ConfigReq if we aren't in the BT_CONFIG state"
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Gustavo F. Padovan
In-Reply-To: <1285376504-3541-5-git-send-email-gustavo@padovan.org>
From: Gustavo F. Padovan <padovan@profusion.mobi>
This reverts commit 8cb8e6f1684be13b51f8429b15f39c140326b327.
That commit introduced a regression with the Bluetooth Profile Tuning
Suite(PTS), Reverting this make sure that L2CAP is in a qualificable
state.
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
---
net/bluetooth/l2cap.c | 8 +-------
1 files changed, 1 insertions(+), 7 deletions(-)
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index f2062aa..44a8fb0 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -3089,14 +3089,8 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr
if (!sk)
return -ENOENT;
- if (sk->sk_state != BT_CONFIG) {
- struct l2cap_cmd_rej rej;
-
- rej.reason = cpu_to_le16(0x0002);
- l2cap_send_cmd(conn, cmd->ident, L2CAP_COMMAND_REJ,
- sizeof(rej), &rej);
+ if (sk->sk_state == BT_DISCONN)
goto unlock;
- }
/* Reject if config buffer is too small. */
len = cmd_len - sizeof(*req);
--
1.7.2.2
^ permalink raw reply related
* [PATCH 4/6] Bluetooth: Fix inconsistent lock state with RFCOMM
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Gustavo F. Padovan
In-Reply-To: <1285376504-3541-4-git-send-email-gustavo@padovan.org>
From: Gustavo F. Padovan <padovan@profusion.mobi>
When receiving a rfcomm connection with the old dund deamon a
inconsistent lock state happens. That's because interrupts were already
disabled by l2cap_conn_start() when rfcomm_sk_state_change() try to lock
the spin_lock.
As result we may have a inconsistent lock state for l2cap_conn_start()
after rfcomm_sk_state_change() calls bh_lock_sock() and disable interrupts
as well.
[ 2833.151999]
[ 2833.151999] =================================
[ 2833.151999] [ INFO: inconsistent lock state ]
[ 2833.151999] 2.6.36-rc3 #2
[ 2833.151999] ---------------------------------
[ 2833.151999] inconsistent {IN-SOFTIRQ-W} -> {SOFTIRQ-ON-W} usage.
[ 2833.151999] krfcommd/2306 [HC0[0]:SC0[0]:HE1:SE1] takes:
[ 2833.151999] (slock-AF_BLUETOOTH){+.?...}, at: [<ffffffffa00bcb56>] rfcomm_sk_state_change+0x46/0x170 [rfcomm]
[ 2833.151999] {IN-SOFTIRQ-W} state was registered at:
[ 2833.151999] [<ffffffff81094346>] __lock_acquire+0x5b6/0x1560
[ 2833.151999] [<ffffffff8109534a>] lock_acquire+0x5a/0x70
[ 2833.151999] [<ffffffff81392b6c>] _raw_spin_lock+0x2c/0x40
[ 2833.151999] [<ffffffffa00a5092>] l2cap_conn_start+0x92/0x640 [l2cap]
[ 2833.151999] [<ffffffffa00a6a3f>] l2cap_sig_channel+0x6bf/0x1320 [l2cap]
[ 2833.151999] [<ffffffffa00a9173>] l2cap_recv_frame+0x133/0x770 [l2cap]
[ 2833.151999] [<ffffffffa00a997b>] l2cap_recv_acldata+0x1cb/0x390 [l2cap]
[ 2833.151999] [<ffffffffa000db4b>] hci_rx_task+0x2ab/0x450 [bluetooth]
[ 2833.151999] [<ffffffff8106b22b>] tasklet_action+0xcb/0xe0
[ 2833.151999] [<ffffffff8106b91e>] __do_softirq+0xae/0x150
[ 2833.151999] [<ffffffff8102bc0c>] call_softirq+0x1c/0x30
[ 2833.151999] [<ffffffff8102ddb5>] do_softirq+0x75/0xb0
[ 2833.151999] [<ffffffff8106b56d>] irq_exit+0x8d/0xa0
[ 2833.151999] [<ffffffff8104484b>] smp_apic_timer_interrupt+0x6b/0xa0
[ 2833.151999] [<ffffffff8102b6d3>] apic_timer_interrupt+0x13/0x20
[ 2833.151999] [<ffffffff81029dfa>] cpu_idle+0x5a/0xb0
[ 2833.151999] [<ffffffff81381ded>] rest_init+0xad/0xc0
[ 2833.151999] [<ffffffff817ebc4d>] start_kernel+0x2dd/0x2e8
[ 2833.151999] [<ffffffff817eb2e6>] x86_64_start_reservations+0xf6/0xfa
[ 2833.151999] [<ffffffff817eb3ce>] x86_64_start_kernel+0xe4/0xeb
[ 2833.151999] irq event stamp: 731
[ 2833.151999] hardirqs last enabled at (731): [<ffffffff8106b762>] local_bh_enable_ip+0x82/0xe0
[ 2833.151999] hardirqs last disabled at (729): [<ffffffff8106b93e>] __do_softirq+0xce/0x150
[ 2833.151999] softirqs last enabled at (730): [<ffffffff8106b96e>] __do_softirq+0xfe/0x150
[ 2833.151999] softirqs last disabled at (711): [<ffffffff8102bc0c>] call_softirq+0x1c/0x30
[ 2833.151999]
[ 2833.151999] other info that might help us debug this:
[ 2833.151999] 2 locks held by krfcommd/2306:
[ 2833.151999] #0: (rfcomm_mutex){+.+.+.}, at: [<ffffffffa00bb744>] rfcomm_run+0x174/0xb20 [rfcomm]
[ 2833.151999] #1: (&(&d->lock)->rlock){+.+...}, at: [<ffffffffa00b9223>] rfcomm_dlc_accept+0x53/0x100 [rfcomm]
[ 2833.151999]
[ 2833.151999] stack backtrace:
[ 2833.151999] Pid: 2306, comm: krfcommd Tainted: G W 2.6.36-rc3 #2
[ 2833.151999] Call Trace:
[ 2833.151999] [<ffffffff810928e1>] print_usage_bug+0x171/0x180
[ 2833.151999] [<ffffffff810936c3>] mark_lock+0x333/0x400
[ 2833.151999] [<ffffffff810943ca>] __lock_acquire+0x63a/0x1560
[ 2833.151999] [<ffffffff810948b5>] ? __lock_acquire+0xb25/0x1560
[ 2833.151999] [<ffffffff8109534a>] lock_acquire+0x5a/0x70
[ 2833.151999] [<ffffffffa00bcb56>] ? rfcomm_sk_state_change+0x46/0x170 [rfcomm]
[ 2833.151999] [<ffffffff81392b6c>] _raw_spin_lock+0x2c/0x40
[ 2833.151999] [<ffffffffa00bcb56>] ? rfcomm_sk_state_change+0x46/0x170 [rfcomm]
[ 2833.151999] [<ffffffffa00bcb56>] rfcomm_sk_state_change+0x46/0x170 [rfcomm]
[ 2833.151999] [<ffffffffa00b9239>] rfcomm_dlc_accept+0x69/0x100 [rfcomm]
[ 2833.151999] [<ffffffffa00b9a49>] rfcomm_check_accept+0x59/0xd0 [rfcomm]
[ 2833.151999] [<ffffffffa00bacab>] rfcomm_recv_frame+0x9fb/0x1320 [rfcomm]
[ 2833.151999] [<ffffffff813932bb>] ? _raw_spin_unlock_irqrestore+0x3b/0x60
[ 2833.151999] [<ffffffff81093acd>] ? trace_hardirqs_on_caller+0x13d/0x180
[ 2833.151999] [<ffffffff81093b1d>] ? trace_hardirqs_on+0xd/0x10
[ 2833.151999] [<ffffffffa00bb7f1>] rfcomm_run+0x221/0xb20 [rfcomm]
[ 2833.151999] [<ffffffff813905e7>] ? schedule+0x287/0x780
[ 2833.151999] [<ffffffffa00bb5d0>] ? rfcomm_run+0x0/0xb20 [rfcomm]
[ 2833.151999] [<ffffffff81081026>] kthread+0x96/0xa0
[ 2833.151999] [<ffffffff8102bb14>] kernel_thread_helper+0x4/0x10
[ 2833.151999] [<ffffffff813936bc>] ? restore_args+0x0/0x30
[ 2833.151999] [<ffffffff81080f90>] ? kthread+0x0/0xa0
[ 2833.151999] [<ffffffff8102bb10>] ? kernel_thread_helper+0x0/0x10
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
---
net/bluetooth/rfcomm/sock.c | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c
index 44a6232..194b3a0 100644
--- a/net/bluetooth/rfcomm/sock.c
+++ b/net/bluetooth/rfcomm/sock.c
@@ -82,11 +82,14 @@ static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb)
static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err)
{
struct sock *sk = d->owner, *parent;
+ unsigned long flags;
+
if (!sk)
return;
BT_DBG("dlc %p state %ld err %d", d, d->state, err);
+ local_irq_save(flags);
bh_lock_sock(sk);
if (err)
@@ -108,6 +111,7 @@ static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err)
}
bh_unlock_sock(sk);
+ local_irq_restore(flags);
if (parent && sock_flag(sk, SOCK_ZAPPED)) {
/* We have to drop DLC lock here, otherwise
--
1.7.2.2
^ permalink raw reply related
* [PATCH 3/6] Bluetooth: Simplify L2CAP Streaming mode sending
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Gustavo F. Padovan
In-Reply-To: <1285376504-3541-3-git-send-email-gustavo@padovan.org>
From: Gustavo F. Padovan <padovan@profusion.mobi>
As we don't have any error control on the Streaming mode, i.e., we don't
need to keep a copy of the skb for later resending we don't need to
call skb_clone() on it.
Then we can go one further here, and dequeue the skb before sending it,
that also means we don't need to look to sk->sk_send_head anymore.
The patch saves memory and time when sending Streaming mode data, so
it is good to mainline.
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
---
net/bluetooth/l2cap.c | 24 +++++++-----------------
1 files changed, 7 insertions(+), 17 deletions(-)
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index 9fad312..f2062aa 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -1441,33 +1441,23 @@ static inline void l2cap_do_send(struct sock *sk, struct sk_buff *skb)
static void l2cap_streaming_send(struct sock *sk)
{
- struct sk_buff *skb, *tx_skb;
+ struct sk_buff *skb;
struct l2cap_pinfo *pi = l2cap_pi(sk);
u16 control, fcs;
- while ((skb = sk->sk_send_head)) {
- tx_skb = skb_clone(skb, GFP_ATOMIC);
-
- control = get_unaligned_le16(tx_skb->data + L2CAP_HDR_SIZE);
+ while ((skb = skb_dequeue(TX_QUEUE(sk)))) {
+ control = get_unaligned_le16(skb->data + L2CAP_HDR_SIZE);
control |= pi->next_tx_seq << L2CAP_CTRL_TXSEQ_SHIFT;
- put_unaligned_le16(control, tx_skb->data + L2CAP_HDR_SIZE);
+ put_unaligned_le16(control, skb->data + L2CAP_HDR_SIZE);
if (pi->fcs == L2CAP_FCS_CRC16) {
- fcs = crc16(0, (u8 *)tx_skb->data, tx_skb->len - 2);
- put_unaligned_le16(fcs, tx_skb->data + tx_skb->len - 2);
+ fcs = crc16(0, (u8 *)skb->data, skb->len - 2);
+ put_unaligned_le16(fcs, skb->data + skb->len - 2);
}
- l2cap_do_send(sk, tx_skb);
+ l2cap_do_send(sk, skb);
pi->next_tx_seq = (pi->next_tx_seq + 1) % 64;
-
- if (skb_queue_is_last(TX_QUEUE(sk), skb))
- sk->sk_send_head = NULL;
- else
- sk->sk_send_head = skb_queue_next(TX_QUEUE(sk), skb);
-
- skb = skb_dequeue(TX_QUEUE(sk));
- kfree_skb(skb);
}
}
--
1.7.2.2
^ permalink raw reply related
* [PATCH 2/6] Bluetooth: fix MTU L2CAP configuration parameter
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Andrei Emeltchenko
In-Reply-To: <1285376504-3541-2-git-send-email-gustavo@padovan.org>
From: Andrei Emeltchenko <andrei.emeltchenko@nokia.com>
When receiving L2CAP negative configuration response with respect
to MTU parameter we modify wrong field. MTU here means proposed
value of MTU that the remote device intends to transmit. So for local
L2CAP socket it is pi->imtu.
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@nokia.com>
Acked-by: Ville Tervo <ville.tervo@nokia.com>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
---
net/bluetooth/l2cap.c | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index c784703..9fad312 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -2771,10 +2771,10 @@ static int l2cap_parse_conf_rsp(struct sock *sk, void *rsp, int len, void *data,
case L2CAP_CONF_MTU:
if (val < L2CAP_DEFAULT_MIN_MTU) {
*result = L2CAP_CONF_UNACCEPT;
- pi->omtu = L2CAP_DEFAULT_MIN_MTU;
+ pi->imtu = L2CAP_DEFAULT_MIN_MTU;
} else
- pi->omtu = val;
- l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->omtu);
+ pi->imtu = val;
+ l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu);
break;
case L2CAP_CONF_FLUSH_TO:
--
1.7.2.2
^ permalink raw reply related
* [PATCH 1/6] Bluetooth: Only enable for L2CAP FCS for ERTM or streaming
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Mat Martineau
In-Reply-To: <1285376504-3541-1-git-send-email-gustavo@padovan.org>
From: Mat Martineau <mathewm@codeaurora.org>
This fixes a bug which caused the FCS setting to show L2CAP_FCS_CRC16
with L2CAP modes other than ERTM or streaming. At present, this only
affects the FCS value shown with getsockopt() for basic mode.
Signed-off-by: Mat Martineau <mathewm@codeaurora.org>
Signed-off-by: Gustavo F. Padovan <padovan@profusion.mobi>
---
net/bluetooth/l2cap.c | 19 +++++++++++++------
1 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/net/bluetooth/l2cap.c b/net/bluetooth/l2cap.c
index fadf26b..c784703 100644
--- a/net/bluetooth/l2cap.c
+++ b/net/bluetooth/l2cap.c
@@ -3071,6 +3071,17 @@ static inline int l2cap_connect_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hd
return 0;
}
+static inline void set_default_fcs(struct l2cap_pinfo *pi)
+{
+ /* FCS is enabled only in ERTM or streaming mode, if one or both
+ * sides request it.
+ */
+ if (pi->mode != L2CAP_MODE_ERTM && pi->mode != L2CAP_MODE_STREAMING)
+ pi->fcs = L2CAP_FCS_NONE;
+ else if (!(pi->conf_state & L2CAP_CONF_NO_FCS_RECV))
+ pi->fcs = L2CAP_FCS_CRC16;
+}
+
static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data)
{
struct l2cap_conf_req *req = (struct l2cap_conf_req *) data;
@@ -3135,9 +3146,7 @@ static inline int l2cap_config_req(struct l2cap_conn *conn, struct l2cap_cmd_hdr
goto unlock;
if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) {
- if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_NO_FCS_RECV) ||
- l2cap_pi(sk)->fcs != L2CAP_FCS_NONE)
- l2cap_pi(sk)->fcs = L2CAP_FCS_CRC16;
+ set_default_fcs(l2cap_pi(sk));
sk->sk_state = BT_CONNECTED;
@@ -3225,9 +3234,7 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn, struct l2cap_cmd_hdr
l2cap_pi(sk)->conf_state |= L2CAP_CONF_INPUT_DONE;
if (l2cap_pi(sk)->conf_state & L2CAP_CONF_OUTPUT_DONE) {
- if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_NO_FCS_RECV) ||
- l2cap_pi(sk)->fcs != L2CAP_FCS_NONE)
- l2cap_pi(sk)->fcs = L2CAP_FCS_CRC16;
+ set_default_fcs(l2cap_pi(sk));
sk->sk_state = BT_CONNECTED;
l2cap_pi(sk)->next_tx_seq = 0;
--
1.7.2.2
^ permalink raw reply related
* Pull resquest review to 2.6.36-rc5
From: Gustavo F. Padovan @ 2010-09-25 1:01 UTC (permalink / raw)
To: linux-bluetooth
Hi all,
These are the patches we have queued for the Bluetooth subsystem intended to be
pushed into 2.6.36. We are bit late already, please make any comment as soon as
possible then we can hurry and pull those to wireless-2.6 on Monday maybe.
The following changes since commit b30a3f6257ed2105259b404d419b4964e363928c:
Linux 2.6.36-rc5 (2010-09-20 16:56:53 -0700)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/padovan/bluetooth-2.6.git master
Andrei Emeltchenko (1):
Bluetooth: fix MTU L2CAP configuration parameter
Gustavo F. Padovan (4):
Bluetooth: Simplify L2CAP Streaming mode sending
Bluetooth: Fix inconsistent lock state with RFCOMM
Revert "Bluetooth: Don't accept ConfigReq if we aren't in the BT_CONFIG state"
Bluetooth: Fix deadlock in the ERTM logic
Mat Martineau (1):
Bluetooth: Only enable for L2CAP FCS for ERTM or streaming
include/net/bluetooth/bluetooth.h | 11 +++++++
net/bluetooth/l2cap.c | 57 +++++++++++++++---------------------
net/bluetooth/rfcomm/sock.c | 4 ++
3 files changed, 39 insertions(+), 33 deletions(-)
^ permalink raw reply
* RFC: btusb firmware load help
From: Luis R. Rodriguez @ 2010-09-24 23:07 UTC (permalink / raw)
To: Marcel Holtmann
Cc: linux-bluetooth, linux-kernel, linux-wireless, Deepak Dhamdhere,
Sree Durbha
Marcel, I was just poked about this thread:
http://www.spinics.net/lists/linux-bluetooth/msg06087.html
The hack is required because our BT hardware does not accept HCI commands
when the device is plugged in. If I understood your position you did not
want to accept the patch because our BT USB devices are violating the
specification by not acceping HCI commands and yet claiming to be BT
devices, is that right?
This position seems perfectly reasonable, and if it is violating the
specification this needs to be fixed in hardware for future devices. But
what is done is done, an dwe need to support existing customers with
current hardware, can you provide some recommendation as to an alternative
approach to resolve this upstream?
Without a patch like the one suggested Atheros' Bluetooth USB devices are
essentially not functional at all. I am in hopes we can come to some agreement
how to deal with a hardware quirk for now while we nag at our hardware team
to consider changing the way our devices work.
Someone from the Atheros BT team: can you send me the same patch to be
applied into compat-wireless under the linux-next-pending/ directory ?
That is, send me a patch which will apply onto compat-wireless.
This way we can actually get our hardware functional to customers while
we try to look for a better alternative directly upstream. You will
also need to document this issue on the wiki. I recommend you document
this here:
http://wireless.kernel.org/en/users/Drivers/ath3k
That is, create a new wiki page there for the driver and the things
needed for it. Once I get the patch into compat-wireless we can
refer users/customers to the compat-wireless stable releases with
the linux-next-pending/ patch you give to me applied.
Someone from the Atheros BT team: does the patch currently handle
suspend/resume?
Luis
^ permalink raw reply
* Re: [PATCH 1/6] This patch adds support for the ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-09-24 14:44 UTC (permalink / raw)
To: Mark Brown; +Cc: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
In-Reply-To: <20100924141046.GA26454@sirena.org.uk>
Hi Mark,
Thanks for your comments.
2010/9/24 Mark Brown <broonie@opensource.wolfsonmicro.com>:
> On Fri, Sep 24, 2010 at 03:46:44PM +0200, Par-Gunnar Hjalmdahl wrote:
>
>> +#ifndef PIN_INPUT_PULLUP
>> +#define PIN_INPUT_PULLUP (PIN_DIR_INPUT | PIN_PULL_UP)
>> +#endif
>> +
>> +#ifndef GPIO_LOW
>> +#define GPIO_LOW 0
>> +#endif
>> +
>> +#ifndef GPIO_HIGH
>> +#define GPIO_HIGH 1
>> +#endif
>> +
>> +#ifndef GPIO_TO_IRQ
>> +#define GPIO_TO_IRQ NOMADIK_GPIO_TO_IRQ
>> +#endif
>
> None of this looks like things that should be added in driver code -
> there should be standard ways of doing this stuff that you should use
> and if there aren't and they are useful they should be added in generic
> code so that other code can use them.
You are absolutely correct in this. As I wrote in the "cover letter"
[PATCH 0/6] we are already planning changes to this driver where we
instead register as platform driver and will use normal Kernel
methodology to implement this. As you can see in the patch these
comments are regarding a file located in arch/arm/mach-ux500 and the
driver therefore has a dependency to the Board configuration which
isn't particularly nice. This will be corrected. These type of defines
should of course not be located in a C-file either.
>
>> +/** BT_ENABLE_GPIO - GPIO to enable/disable the BT module.
>> + */
>> +#define BT_ENABLE_GPIO 170
>
> This sort of thing should be passed in from the board configuration
> normallly.
See answer above.
>
>> +void cg2900_devices_enable_chip(void)
>> +{
>> + gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_HIGH);
>> +}
>> +EXPORT_SYMBOL(cg2900_devices_enable_chip);
>
> This looks like something that the driver should be organising rather
> than something that should be exported for some random code to pick up?
> In general most of the code in here looks like it should have more
> device model usage and make more use of standard kernel infrastructure.
See answer above. Will be a callback function in a driver data struct.
>
>> +static irqreturn_t cg2900_devices_interrupt(int irq, void *dev_id)
>> +{
>> + disable_irq_nosync(irq);
>> + if (cg2900_dev_callback && cg2900_dev_callback->interrupt_cb)
>> + cg2900_dev_callback->interrupt_cb();
>> +
>> + return IRQ_HANDLED;
>> +}
>
> Why is there this callback mechanism - I'd expect the users of this code
> to just be using the standard IRQ infrastructure?
>
I will have to get back to you regarding this. I did not implement
that specific part and the guy who did is back on Monday.
/P-G
^ permalink raw reply
* Re: [PATCH 1/6] This patch adds support for the ST-Ericsson CG2900
From: Mark Brown @ 2010-09-24 14:10 UTC (permalink / raw)
To: pghatwork; +Cc: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
In-Reply-To: <AANLkTin9Qji_bdyd3yqrP_-ti5xRT9jJ4je2fPM=uHFX@mail.gmail.com>
On Fri, Sep 24, 2010 at 03:46:44PM +0200, Par-Gunnar Hjalmdahl wrote:
> +#ifndef PIN_INPUT_PULLUP
> +#define PIN_INPUT_PULLUP (PIN_DIR_INPUT | PIN_PULL_UP)
> +#endif
> +
> +#ifndef GPIO_LOW
> +#define GPIO_LOW 0
> +#endif
> +
> +#ifndef GPIO_HIGH
> +#define GPIO_HIGH 1
> +#endif
> +
> +#ifndef GPIO_TO_IRQ
> +#define GPIO_TO_IRQ NOMADIK_GPIO_TO_IRQ
> +#endif
None of this looks like things that should be added in driver code -
there should be standard ways of doing this stuff that you should use
and if there aren't and they are useful they should be added in generic
code so that other code can use them.
> +/** BT_ENABLE_GPIO - GPIO to enable/disable the BT module.
> + */
> +#define BT_ENABLE_GPIO 170
This sort of thing should be passed in from the board configuration
normallly.
> +void cg2900_devices_enable_chip(void)
> +{
> + gpio_set_value(GBF_ENA_RESET_GPIO, GPIO_HIGH);
> +}
> +EXPORT_SYMBOL(cg2900_devices_enable_chip);
This looks like something that the driver should be organising rather
than something that should be exported for some random code to pick up?
In general most of the code in here looks like it should have more
device model usage and make more use of standard kernel infrastructure.
> +static irqreturn_t cg2900_devices_interrupt(int irq, void *dev_id)
> +{
> + disable_irq_nosync(irq);
> + if (cg2900_dev_callback && cg2900_dev_callback->interrupt_cb)
> + cg2900_dev_callback->interrupt_cb();
> +
> + return IRQ_HANDLED;
> +}
Why is there this callback mechanism - I'd expect the users of this code
to just be using the standard IRQ infrastructure?
^ permalink raw reply
* a2dp, myth, pulse
From: Brad Midgley @ 2010-09-24 14:00 UTC (permalink / raw)
To: linux-bluetooth
Hey
I've been away from this for a bit... I've just gotten my mythtv set
top with an a2dp headset*
can avrcp be used to drive mythtv? I've got a setup where the myth
frontend is rarely the foreground app, so it wouldn't work to deliver
keystrokes to the foreground app. I'm not sure how lirc does this but
it delivers messages to myth even if it doesn't have x11 focus.
Is there anything in place to fix latency automatically? Can I
calibrate the delay for a headset and have pulse fix it for me? The
wired audio sync also needs to be adjusted fwiw... pulse delivers
audio about 300ms too soon in the wired case.
* for the record, what I had to do under ubuntu 10.04 was tell myth to
use the pulse device and add this line to /etc/pulse/default.pa:
load-module module-alsa-sink device=plug:dmix
then I click the headset button to connect it and use pavucontrol to
move the myth stream over to the headset (a bit of a pain)
--
Brad Midgley
^ permalink raw reply
* [PATCH 6/6] This patch adds support for using the ST-Ericsson CG2900
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:52 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for using the ST-Ericsson CG2900
connectivity controller as a driver for the BlueZ Bluetooth
stack.
This patch registers as a driver into the BlueZ framework and, when
opened by BlueZ, it registers as user for bt_cmd, bt_acl, and bt_evt
channels.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/bluetooth/Kconfig | 7 +
drivers/bluetooth/Makefile | 2 +
drivers/bluetooth/cg2900_hci.c | 896 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 905 insertions(+), 0 deletions(-)
create mode 100644 drivers/bluetooth/cg2900_hci.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..a479c16 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -19,6 +19,8 @@ obj-$(CONFIG_BT_ATH3K) += ath3k.o
obj-$(CONFIG_BT_MRVL) += btmrvl.o
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
+obj-$(CONFIG_BT_CG2900) += cg2900_hci.o
+
btmrvl-y := btmrvl_main.o
btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
diff --git a/drivers/bluetooth/cg2900_hci.c b/drivers/bluetooth/cg2900_hci.c
new file mode 100644
index 0000000..de1ada8
--- /dev/null
+++ b/drivers/bluetooth/cg2900_hci.c
@@ -0,0 +1,896 @@
+/*
+ * drivers/bluetooth/cg2900_hci.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 connectivity
controller
+ * towards the BlueZ Bluetooth stack.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+#include <net/bluetooth/hci_core.h>
+
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+
+/* module_param declared in cg2900_core.c */
+extern int cg2900_debug_level;
+
+#define NAME "CG2900 HCI"
+
+/* Debug defines */
+#define CG2900_DBG_DATA(fmt, arg...) \
+do { \
+ if (cg2900_debug_level >= 25) \
+ printk(KERN_DEBUG NAME " %s: " fmt "\n" , __func__ , ## arg); \
+} while (0)
+
+#define CG2900_DBG(fmt, arg...) \
+do { \
+ if (cg2900_debug_level >= 20) \
+ printk(KERN_DEBUG NAME " %s: " fmt "\n" , __func__ , ## arg); \
+} while (0)
+
+#define CG2900_INFO(fmt, arg...) \
+do { \
+ if (cg2900_debug_level >= 10) \
+ printk(KERN_INFO NAME ": " fmt "\n" , ## arg); \
+} while (0)
+
+#define CG2900_ERR(fmt, arg...) \
+do { \
+ if (cg2900_debug_level >= 1) \
+ printk(KERN_ERR NAME " %s: " fmt "\n" , __func__ , ## arg); \
+} while (0)
+
+#define CG2900_SET_STATE(__name, __var, __new_state) \
+do { \
+ CG2900_DBG("New %s: 0x%X", __name, (uint32_t)__new_state); \
+ __var = __new_state; \
+} while (0)
+
+/* HCI device type */
+#define HCI_CG2900 HCI_VIRTUAL
+
+/* Wait for 5 seconds for a response to our requests */
+#define RESP_TIMEOUT 5000
+
+/* State-setting defines */
+#define SET_RESET_STATE(__hci_reset_new_state) \
+ CG2900_SET_STATE("reset_state", hci_info->reset_state, \
+ __hci_reset_new_state)
+#define SET_ENABLE_STATE(__hci_enable_new_state) \
+ CG2900_SET_STATE("enable_state", hci_info->enable_state, \
+ __hci_enable_new_state)
+
+/* 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 hci_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.
+ * @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 hci_info {
+ struct cg2900_device *bt_cmd;
+ struct cg2900_device *bt_evt;
+ struct cg2900_device *bt_acl;
+ 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 CPD.
+ *
+ * @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;
+};
+
+/* Variables where LSB and MSB for bt_enable command is stored */
+static u16 bt_enable_cmd;
+
+static struct hci_info *hci_info;
+
+/*
+ * hci_wait_queue - Main Wait Queue in HCI driver.
+ */
+static DECLARE_WAIT_QUEUE_HEAD(hci_wait_queue);
+
+/* Internal function declarations */
+static int register_to_bluez(void);
+
+/* Internal functions */
+
+/**
+ * remove_bt_users() - Unregister and remove any existing BT users.
+ * @info: HCI driver info structure.
+ */
+static void remove_bt_users(struct hci_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) {
+ CG2900_ERR("NULL supplied for skb");
+ return;
+ }
+
+ if (!dev) {
+ CG2900_ERR("dev == NULL");
+ goto fin_free_skb;
+ }
+
+ dev_info = (struct dev_info *)dev->user_data;
+
+ if (!dev_info) {
+ CG2900_ERR("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 ((hci_info->enable_state == ENABLE_WAITING_BT_ENABLED_CC ||
+ hci_info->enable_state == ENABLE_WAITING_BT_DISABLED_CC) &&
+ hci_info->bt_evt->h4_channel == dev->h4_channel &&
+ evt->evt == HCI_EV_CMD_COMPLETE &&
+ le16_to_cpu(cmd_complete->opcode) == bt_enable_cmd) {
+ /*
+ * 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) {
+ CG2900_ERR("Could not enable/disable BT core (0x%X)",
+ status);
+ SET_ENABLE_STATE(ENABLE_BT_ERROR);
+ goto fin_free_skb;
+ }
+
+ if (hci_info->enable_state == ENABLE_WAITING_BT_ENABLED_CC) {
+ SET_ENABLE_STATE(ENABLE_BT_ENABLED);
+ CG2900_INFO("BT core is enabled");
+ } else {
+ SET_ENABLE_STATE(ENABLE_BT_DISABLED);
+ CG2900_INFO("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 ((hci_info->enable_state == ENABLE_WAITING_BT_DISABLED_CC ||
+ hci_info->enable_state == ENABLE_WAITING_BT_ENABLED_CC) &&
+ hci_info->bt_evt->h4_channel == dev->h4_channel &&
+ evt->evt == HCI_EV_CMD_STATUS &&
+ le16_to_cpu(cmd_status->opcode) == bt_enable_cmd) {
+ /*
+ * Clear the status events since the Bluez is not expecting
+ * them.
+ */
+ CG2900_DBG("HCI Driver received Command Status(for "
+ "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++;
+
+ CG2900_DBG_DATA("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)
+ CG2900_ERR("Failed in supplying packet to BlueZ (%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 hci_info *info;
+
+ CG2900_INFO("BluezDriver: hci_reset_cb");
+
+ if (!dev) {
+ CG2900_ERR("NULL supplied for dev");
+ return;
+ }
+
+ dev_info = (struct dev_info *)dev->user_data;
+ if (!dev_info) {
+ CG2900_ERR("NULL supplied for dev_info");
+ return;
+ }
+
+ hdev = dev_info->hdev;
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return;
+ }
+
+ info = (struct hci_info *)hdev->driver_data;
+ if (!info) {
+ CG2900_ERR("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:
+ CG2900_ERR("Unknown HCI data type:%d",
+ dev_info->hci_data_type);
+ return;
+ }
+
+ SET_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.
+ */
+ CG2900_INFO("Deregister HCI device");
+ err = hci_unregister_dev(hdev);
+ if (err)
+ CG2900_ERR("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 == hci_info->reset_state),
+ msecs_to_jiffies(RESP_TIMEOUT));
+ if (RESET_UNREGISTERED != hci_info->reset_state)
+ /*
+ * Now we are in trouble. Try to register a new hdev
+ * anyway even though this will cost some memory.
+ */
+ CG2900_ERR("Timeout expired. Could not deregister HCI device");
+
+ /* Init and register hdev */
+ CG2900_INFO("Register HCI device");
+ err = register_to_bluez();
+ if (err)
+ CG2900_ERR("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
+};
+
+/**
+ * open_from_hci() - 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 open_from_hci(struct hci_dev *hdev)
+{
+ struct hci_info *info;
+ struct dev_info *dev_info;
+ struct sk_buff *enable_cmd;
+ int err;
+
+ CG2900_INFO("Open ST-Ericsson connectivity HCI driver");
+
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct hci_info *)hdev->driver_data;
+ if (!info) {
+ CG2900_ERR("NULL supplied for driver_data");
+ return -EINVAL;
+ }
+
+ if (test_and_set_bit(HCI_RUNNING, &(hdev->flags))) {
+ CG2900_ERR("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 {
+ CG2900_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 {
+ CG2900_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 {
+ CG2900_ERR("Couldn't register CG2900_BT_ACL to CG2900");
+ err = -EACCES;
+ goto handle_error;
+ }
+ }
+
+ if (info->reset_state == RESET_ACTIVATED)
+ SET_RESET_STATE(RESET_IDLE);
+
+ /*
+ * Call platform specific function that returns the chip dependent
+ * bt enable(true) HCI command.
+ * If NULL is returned, then no bt_enable command should be sent to the
+ * chip.
+ */
+ enable_cmd = cg2900_devices_get_bt_enable_cmd(&bt_enable_cmd, true);
+ if (!enable_cmd) {
+ /* The chip is enabled by default */
+ SET_ENABLE_STATE(ENABLE_BT_ENABLED);
+ return 0;
+ }
+
+ /* Set the HCI state before sending command to chip. */
+ SET_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) {
+ CG2900_ERR("Could not enable BT core (%d)", info->enable_state);
+ err = -EACCES;
+ SET_ENABLE_STATE(ENABLE_BT_DISABLED);
+ goto handle_error;
+ }
+
+ return 0;
+
+handle_error:
+ remove_bt_users(info);
+ clear_bit(HCI_RUNNING, &(hdev->flags));
+ return err;
+
+}
+
+/**
+ * flush_from_hci() - Flush HCI interface.
+ * @hdev: HCI device being flushed.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -EINVAL if NULL pointer is supplied.
+ */
+static int flush_from_hci(struct hci_dev *hdev)
+{
+ CG2900_INFO("flush_from_hci");
+
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * close_from_hci() - 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 close_from_hci(struct hci_dev *hdev)
+{
+ struct hci_info *info = NULL;
+ struct sk_buff *enable_cmd;
+
+ CG2900_INFO("close_from_hci");
+
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct hci_info *)hdev->driver_data;
+ if (!info) {
+ CG2900_ERR("NULL supplied for driver_data");
+ return -EINVAL;
+ }
+
+ if (!test_and_clear_bit(HCI_RUNNING, &(hdev->flags))) {
+ CG2900_ERR("Device already closed!");
+ return -EBUSY;
+ }
+
+ flush_from_hci(hdev);
+
+ /* Do not do this if there is an reset ongoing */
+ if (hci_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 = cg2900_devices_get_bt_enable_cmd(&bt_enable_cmd, false);
+ if (!enable_cmd) {
+ /*
+ * The chip is enabled by default and we should not disable it.
+ */
+ SET_ENABLE_STATE(ENABLE_BT_ENABLED);
+ goto remove_users;
+ }
+
+ /* Set the HCI state before sending command to chip */
+ SET_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) {
+ CG2900_ERR("Could not disable BT core.");
+ SET_ENABLE_STATE(ENABLE_BT_ENABLED);
+ }
+
+remove_users:
+ /* Finally deregister all users and free allocated data */
+ remove_bt_users(info);
+ return 0;
+}
+
+/**
+ * send_from_hci() - 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 send_from_hci(struct sk_buff *skb)
+{
+ struct hci_dev *hdev;
+ struct hci_info *info;
+ int err = 0;
+
+ if (!skb) {
+ CG2900_ERR("NULL supplied for skb");
+ return -EINVAL;
+ }
+
+ hdev = (struct hci_dev *)(skb->dev);
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return -EINVAL;
+ }
+
+ info = (struct hci_info *)hdev->driver_data;
+ if (!info) {
+ CG2900_ERR("NULL supplied for info");
+ return -EINVAL;
+ }
+
+ /* Update BlueZ stats */
+ hdev->stat.byte_tx += skb->len;
+
+ CG2900_DBG_DATA("Data transmit %d bytes", skb->len);
+
+ switch (bt_cb(skb)->pkt_type) {
+ case HCI_COMMAND_PKT:
+ CG2900_DBG("Sending HCI_COMMAND_PKT");
+ err = cg2900_write(info->bt_cmd, skb);
+ hdev->stat.cmd_tx++;
+ break;
+ case HCI_ACLDATA_PKT:
+ CG2900_DBG("Sending HCI_ACLDATA_PKT");
+ err = cg2900_write(info->bt_acl, skb);
+ hdev->stat.acl_tx++;
+ break;
+ default:
+ CG2900_ERR("Trying to transmit unsupported packet type "
+ "(0x%.2X)", bt_cb(skb)->pkt_type);
+ err = -EOPNOTSUPP;
+ break;
+ };
+
+ return err;
+}
+
+/**
+ * destruct_from_hci() - Destruct HCI interface.
+ * @hdev: HCI device being destructed.
+ */
+static void destruct_from_hci(struct hci_dev *hdev)
+{
+ CG2900_INFO("destruct_from_hci");
+
+ if (!hci_info)
+ return;
+
+ /* When being reset, register a new hdev when hdev is deregistered */
+ if (hci_info->reset_state == RESET_ACTIVATED) {
+ if (hci_info->hdev)
+ hci_free_dev(hci_info->hdev);
+ SET_RESET_STATE(RESET_UNREGISTERED);
+ }
+}
+
+/**
+ * notify_from_hci() - Notification from the HCI interface.
+ * @hdev: HCI device notifying.
+ * @evt: Notification event.
+ */
+static void notify_from_hci(struct hci_dev *hdev, unsigned int evt)
+{
+ CG2900_INFO("notify_from_hci - evt = 0x%.2X", evt);
+}
+
+/**
+ * ioctl_from_hci() - Handle IOCTL call to the HCI interface.
+ * @hdev: HCI device.
+ * @cmd: IOCTL command.
+ * @arg: IOCTL argument.
+ *
+ * Returns:
+ * -EINVAL if NULL is supplied for hdev.
+ * -EPERM otherwise since current driver supports no IOCTL.
+ */
+static int ioctl_from_hci(struct hci_dev *hdev, unsigned int cmd,
+ unsigned long arg)
+{
+ CG2900_INFO("ioctl_from_hci - cmd 0x%X", cmd);
+ CG2900_DBG("DIR: %d, TYPE: %d, NR: %d, SIZE: %d",
+ _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
+ _IOC_SIZE(cmd));
+
+ if (!hdev) {
+ CG2900_ERR("NULL supplied for hdev");
+ return -EINVAL;;
+ }
+
+ return -EPERM;
+}
+
+/**
+ * register_to_bluez() - 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_to_bluez(void)
+{
+ int err;
+
+ hci_info->hdev = hci_alloc_dev();
+ if (!hci_info->hdev) {
+ CG2900_ERR("Could not allocate mem for HCI driver");
+ return -ENOMEM;
+ }
+
+ hci_info->hdev->bus = HCI_CG2900;
+ hci_info->hdev->driver_data = hci_info;
+ hci_info->hdev->owner = THIS_MODULE;
+ hci_info->hdev->open = open_from_hci;
+ hci_info->hdev->close = close_from_hci;
+ hci_info->hdev->flush = flush_from_hci;
+ hci_info->hdev->send = send_from_hci;
+ hci_info->hdev->destruct = destruct_from_hci;
+ hci_info->hdev->notify = notify_from_hci;
+ hci_info->hdev->ioctl = ioctl_from_hci;
+
+ err = hci_register_dev(hci_info->hdev);
+ if (err) {
+ CG2900_ERR("Can not register HCI device (%d)", err);
+ hci_free_dev(hci_info->hdev);
+ }
+
+ SET_ENABLE_STATE(ENABLE_IDLE);
+ SET_RESET_STATE(RESET_IDLE);
+
+ return err;
+}
+
+/**
+ * hci_init() - Initialize module.
+ *
+ * Allocate and initialize private data. Register to BlueZ.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENOMEM if allocation fails.
+ * Error codes from register_to_bluez.
+ */
+static int __init hci_init(void)
+{
+ int err;
+ CG2900_INFO("hci_init");
+
+ /* Initialize private data. */
+ hci_info = kzalloc(sizeof(*hci_info), GFP_KERNEL);
+ if (!hci_info) {
+ CG2900_ERR("Could not alloc hci_info struct.");
+ return -ENOMEM;
+ }
+
+ /* Init and register hdev */
+ err = register_to_bluez();
+ if (err) {
+ CG2900_ERR("HCI Device registration error (%d)", err);
+ kfree(hci_info);
+ hci_info = NULL;
+ return err;
+ }
+
+ return 0;
+}
+
+/**
+ * hci_exit() - Remove module.
+ *
+ * Remove HCI device from CG2900 driver.
+ */
+static void __exit hci_exit(void)
+{
+ int err = 0;
+
+ CG2900_INFO("hci_exit");
+
+ if (!hci_info)
+ return;
+
+ if (!hci_info->hdev)
+ goto finished;
+
+ err = hci_unregister_dev(hci_info->hdev);
+ if (err)
+ CG2900_ERR("Can not unregister HCI device (%d)", err);
+ hci_free_dev(hci_info->hdev);
+
+finished:
+ kfree(hci_info);
+ hci_info = NULL;
+}
+
+module_init(hci_init);
+module_exit(hci_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 5/6] This patch adds support for controlling the audio paths
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:51 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for controlling the audio paths
within the ST-Ericsson CG2900 connectivity controller.
This patch registers as a user into the CG2900 framework
for the bt_audio and fm_audio channels. It contains an API
for controlling the audio paths that can also be reached from
User space using a char device.
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_audio.c | 2475 +++++++++++++++++++++++++++++++++++++
include/linux/mfd/cg2900_audio.h | 583 +++++++++
4 files changed, 3067 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 2888358..7856fd5 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -603,6 +603,13 @@ config MFD_CG2900_UART
Support for UART as transport for ST-Ericsson CG2900 Connectivity
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.
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 02b7c25..3a6672b 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -10,3 +10,5 @@ obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
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..bcb0680
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_audio.c
@@ -0,0 +1,2475 @@
+/*
+ * 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/module.h>
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/poll.h>
+#include <linux/cdev.h>
+#include <linux/mfd/cg2900.h>
+#include <linux/mfd/cg2900_audio.h>
+#include <linux/miscdevice.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/sched.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
+
+/**
+ * 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.
+ * @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.
+ */
+struct audio_info {
+ enum main_state state;
+ 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;
+};
+
+/**
+ * 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 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: CPD device reseting.
+ */
+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);
+}
+
+/**
+ * receive_fm_legacy_response() - Wait for and handle an FM Legacy response.
+ * @audio_user: Audio user to check for.
+ * @rsp_lsb: LSB of FM command to wait for.
+ * @rsp_msb: MSB of FM command to wait for.
+ *
+ * This function first waits for FM response (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.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -EIO for other errors.
+ */
+static int receive_fm_legacy_response(struct audio_user *audio_user,
+ u8 rsp_lsb, u8 rsp_msb)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ u8 status;
+ u8 fm_function;
+ u8 fm_rsp_hdr_lsb;
+ u8 fm_rsp_hdr_msb;
+
+ /*
+ * 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;
+ }
+
+ /* Check if we received the correct event */
+ if (CG2900_FM_GEN_ID_LEGACY !=
+ skb->data[CG2900_FM_USER_GEN_ID_POS_CMD_CMPL]) {
+ 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 */
+ status = skb->data[CG2900_FM_USER_LEG_STATUS_POS_CMD_CMPL];
+ fm_function = skb->data[CG2900_FM_USER_LEG_FUNC_POS_CMD_CMPL];
+ fm_rsp_hdr_lsb = skb->data[CG2900_FM_USER_LEG_HDR_POS_CMD_CMPL];
+ fm_rsp_hdr_msb = skb->data[CG2900_FM_USER_LEG_HDR_POS_CMD_CMPL + 1];
+
+ if (fm_function != CG2900_FM_CMD_PARAM_WRITECOMMAND ||
+ fm_rsp_hdr_lsb != rsp_lsb || fm_rsp_hdr_msb != rsp_msb) {
+ CG2900_ERR("Received unexpected packet func 0x%X "
+ "cmd 0x%02X%02X",
+ fm_function, fm_rsp_hdr_msb, fm_rsp_hdr_lsb);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ if (status != CG2900_FM_CMD_STATUS_COMMAND_SUCCEEDED) {
+ CG2900_ERR("FM Command failed (%d)", 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_lsb: LSB of BT command to wait for.
+ * @rsp_msb: MSB 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, u8 rsp_lsb,
+ u8 rsp_msb, void *data, int data_len)
+{
+ int err = 0;
+ struct sk_buff *skb;
+ u8 evt_id;
+ u8 op_lsb;
+ u8 op_msb;
+ u8 status;
+
+ /*
+ * 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_id = skb->data[HCI_BT_EVT_ID_POS];
+ if (evt_id != HCI_BT_EVT_CMD_COMPLETE) {
+ CG2900_ERR("We did not receive the event we expected (0x%X)",
+ evt_id);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ op_lsb = skb->data[HCI_BT_EVT_CMD_COMPL_ID_POS];
+ op_msb = skb->data[HCI_BT_EVT_CMD_COMPL_ID_POS + 1];
+ status = skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS];
+
+ if (rsp_lsb != op_lsb || rsp_msb != op_msb) {
+ CG2900_ERR("Received cmd complete for unexpected command: "
+ "0x%02X%02X", op_msb, op_lsb);
+ err = -EIO;
+ goto error_handling_free_skb;
+ }
+
+ if (status != HCI_BT_ERROR_NO_ERROR) {
+ CG2900_ERR("Received command complete with err %d", 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, &(skb->data[HCI_BT_EVT_CMD_COMPL_STATUS_POS + 1]),
+ data_len);
+
+ /* Operation succeeded. We are now done */
+
+error_handling_free_skb:
+ kfree_skb(skb);
+ return err;
+}
+
+/**
+ * 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.
+ * Errors from @cg2900_write and @receive_fm_legacy_response.
+ * -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_rx(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ u8 temp_session_id;
+ union cg2900_endpoint_config_union *fm_config;
+ struct sk_buff *skb;
+ u8 *data;
+
+ 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.
+ */
+ CG2900_DBG("FM: AUP_EXT_SetMode");
+
+ skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_FM_CMD_LEN_AUP_EXT_SET_MODE);
+
+ data[0] = CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_MODE;
+ data[1] = CG2900_FM_GEN_ID_LEGACY;
+ data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ data[4] = CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_LSB;
+ data[5] = CG2900_FM_CMD_ID_AUP_EXT_SET_MODE_MSB;
+ data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+ data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AUP_EXT_SET_MODE_PARALLEL);
+
+ cb_info_fm.user = audio_user;
+ SET_RESP_STATE(audio_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);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_fm_legacy_response(audio_user,
+ CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_LSB,
+ CG2900_FM_RSP_ID_AUP_EXT_SET_MODE_MSB);
+ if (err)
+ goto finished_unlock_mutex;
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now configure the External Sample Rate Converter by sending
+ * HCI_Write command with AUP_EXT_SetControl.
+ */
+ CG2900_DBG("FM: AUP_EXT_SetControl");
+
+ skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_FM_CMD_LEN_AUP_EXT_SET_CTRL);
+
+ data[0] = CG2900_FM_CMD_PARAM_LEN_AUP_EXT_SET_CTRL;
+ data[1] = CG2900_FM_GEN_ID_LEGACY;
+ data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ data[4] = CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_LSB;
+ data[5] = CG2900_FM_CMD_ID_AUP_EXT_SET_CTRL_MSB;
+ if (fm_config->fm.sample_rate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) {
+ data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+ data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+ } else {
+ data[6] = HCI_SET_U16_DATA_LSB(
+ CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+ data[7] = HCI_SET_U16_DATA_MSB(
+ CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+ }
+
+ cb_info_fm.user = audio_user;
+ SET_RESP_STATE(audio_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);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_fm_legacy_response(audio_user,
+ CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_LSB,
+ CG2900_FM_RSP_ID_AUP_EXT_SET_CTRL_MSB);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /*
+ * Now send HCI_VS_Set_Session_Configuration command
+ */
+ CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+ /*
+ * Default all bytes to 0 so we don't have to set reserved bytes below.
+ */
+ memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+ data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+ data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+ data[3] = 1; /* Number of streams */
+ data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+ /* Media configuration: Set sample rate and audio channel setup */
+ data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+ fm_config->fm.sample_rate);
+ if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+ data[5] |= CG2900_BT_MEDIA_CONFIG_STEREO;
+ else
+ data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+ /* data[6] - data[10] SBC codec params (not used for FM RX) */
+ /* Input Virtual Port (VP) configuration */
+ data[11] = CG2900_BT_VP_TYPE_FM;
+ /* data[12] - data[23] reserved */
+ /* Output Virtual Port (VP) configuration */
+ data[24] = CG2900_BT_VP_TYPE_I2S;
+ data[25] = CG2900_BT_SESSION_I2S_INDEX_I2S;
+ data[26] = audio_info->i2s_config.channel_sel;
+ /* data[27] - data[36] reserved */
+
+ 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_SESSION_CONFIG_LSB,
+ CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+ &temp_session_id, CG2900_BT_PARAM_LEN_SESSION_ID);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* Store the stream handle (used for start and stop stream) */
+ *stream_handle = temp_session_id;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now start the stream by sending HCI_VS_Session_Control command
+ */
+ CG2900_DBG("BT: HCI_VS_Session_Control");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+ data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+ data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+ data[3] = (u8)(*stream_handle); /* Session ID */
+ data[4] = CG2900_BT_SESSION_START;
+
+ 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_SESSION_CTRL_LSB,
+ CG2900_BT_VS_SESSION_CTRL_MSB,
+ 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);
+ 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.
+ * Errors from @cg2900_write and @receive_fm_legacy_response.
+ * -EIO for other errors.
+ */
+static int conn_start_i2s_to_fm_tx(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ u8 temp_session_id;
+ union cg2900_endpoint_config_union *fm_config;
+ struct sk_buff *skb;
+ u8 *data;
+
+ 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");
+
+ skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_SET_MODE, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_SET_MODE);
+
+ data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_SET_MODE;
+ data[1] = CG2900_FM_GEN_ID_LEGACY;
+ data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ data[4] = CG2900_FM_CMD_ID_AIP_SET_MODE_LSB;
+ data[5] = CG2900_FM_CMD_ID_AIP_SET_MODE_MSB;
+ data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+ data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AIP_SET_MODE_INPUT_DIG);
+
+ cb_info_fm.user = audio_user;
+ SET_RESP_STATE(audio_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);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_fm_legacy_response(audio_user,
+ CG2900_FM_RSP_ID_AIP_SET_MODE_LSB,
+ CG2900_FM_RSP_ID_AIP_SET_MODE_MSB);
+ if (err)
+ goto finished_unlock_mutex;
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now configure the BT sample rate converter by sending HCI_Write
+ * command with AIP_BT_SetControl.
+ */
+ CG2900_DBG("FM: AIP_BT_SetControl");
+
+ skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_BT_SET_CTRL);
+
+ data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_CTRL;
+ data[1] = CG2900_FM_GEN_ID_LEGACY;
+ data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ data[4] = CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_LSB;
+ data[5] = CG2900_FM_CMD_ID_AIP_BT_SET_CTRL_MSB;
+ if (fm_config->fm.sample_rate >= ENDPOINT_SAMPLE_RATE_44_1_KHZ) {
+ data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+ data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_SET_CTRL_CONV_UP);
+ } else {
+ data[6] = HCI_SET_U16_DATA_LSB(
+ CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+ data[7] = HCI_SET_U16_DATA_MSB(
+ CG2900_FM_CMD_SET_CTRL_CONV_DOWN);
+ }
+
+ cb_info_fm.user = audio_user;
+ SET_RESP_STATE(audio_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);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_fm_legacy_response(audio_user,
+ CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_LSB,
+ CG2900_FM_RSP_ID_AIP_BT_SET_CTRL_MSB);
+ if (err)
+ goto finished_unlock_mutex;
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+
+ /*
+ * Now set input of the BT sample rate converter by sending HCI_Write
+ * command with AIP_BT_SetMode.
+ */
+ CG2900_DBG("FM: AIP_BT_SetMode");
+
+ skb = cg2900_alloc_skb(CG2900_FM_CMD_LEN_AIP_BT_SET_MODE, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_FM_CMD_LEN_AIP_BT_SET_MODE);
+
+ data[0] = CG2900_FM_CMD_PARAM_LEN_AIP_BT_SET_MODE;
+ data[1] = CG2900_FM_GEN_ID_LEGACY;
+ data[2] = CG2900_FM_CMD_LEG_PARAM_WRITE;
+ data[3] = CG2900_FM_CMD_PARAM_WRITECOMMAND;
+ data[4] = CG2900_FM_CMD_ID_AIP_BT_SET_MODE_LSB;
+ data[5] = CG2900_FM_CMD_ID_AIP_BT_SET_MODE_MSB;
+ data[6] = HCI_SET_U16_DATA_LSB(CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+ data[7] = HCI_SET_U16_DATA_MSB(CG2900_FM_CMD_AIP_BT_SET_MODE_INPUT_PAR);
+
+ cb_info_fm.user = audio_user;
+ SET_RESP_STATE(audio_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);
+ goto error_handling_free_skb;
+ }
+
+ err = receive_fm_legacy_response(audio_user,
+ CG2900_FM_RSP_ID_AIP_BT_SET_MODE_LSB,
+ CG2900_FM_RSP_ID_AIP_BT_SET_MODE_MSB);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /*
+ * Now send HCI_VS_Set_Session_Configuration command
+ */
+ CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+ /*
+ * Default all bytes to 0 so we don't have to set reserved bytes below.
+ */
+ memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+ data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+ data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+ data[3] = 1; /* Number of streams */
+ data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+ /* Media configuration: Set sample rate and audio channel setup */
+ data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+ fm_config->fm.sample_rate);
+ if (audio_info->i2s_config.channel_sel == CHANNEL_SELECTION_BOTH)
+ data[5] |= CG2900_BT_MEDIA_CONFIG_STEREO;
+ else
+ data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+ /* data[6] - data[10] SBC codec params (not used for FM TX) */
+ /* Input Virtual Port (VP) configuration */
+ data[11] = CG2900_BT_VP_TYPE_I2S;
+ data[12] = CG2900_BT_SESSION_I2S_INDEX_I2S;
+ data[13] = audio_info->i2s_config.channel_sel;
+ /* data[14] - data[23] reserved */
+ /* Output Virtual Port (VP) configuration */
+ data[24] = CG2900_BT_VP_TYPE_FM;
+ /* data[25] - data[36] reserved */
+
+ 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_SESSION_CONFIG_LSB,
+ CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+ &temp_session_id,
+ CG2900_BT_PARAM_LEN_SESSION_ID);
+ if (err)
+ goto finished_unlock_mutex;
+
+ /* Store the stream handle (used for start and stop stream) */
+ *stream_handle = temp_session_id;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now start the stream by sending HCI_VS_Session_Control command
+ */
+ CG2900_DBG("BT: HCI_VS_Session_Control");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+ data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+ data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+ data[3] = (u8)(*stream_handle); /* Session ID */
+ data[4] = CG2900_BT_SESSION_START;
+
+ 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_SESSION_CTRL_LSB,
+ CG2900_BT_VS_SESSION_CTRL_MSB,
+ 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);
+ mutex_unlock(&audio_info->fm_mutex);
+ return err;
+}
+
+/**
+ * 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 and @receive_bt_cmd_complete.
+ * -EIO for other errors.
+ */
+static int conn_start_pcm_to_sco(struct audio_user *audio_user,
+ unsigned int *stream_handle)
+{
+ int err = 0;
+ u8 temp_session_id;
+ union cg2900_endpoint_config_union *bt_config;
+ struct sk_buff *skb;
+ u8 *data;
+
+ 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);
+
+ /*
+ * First send HCI_VS_Set_Session_Configuration command
+ */
+ CG2900_DBG("BT: HCI_VS_Set_Session_Configuration");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SET_SESSION_CONFIG, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+ /*
+ * Default all bytes to 0 so we don't have to set reserved bytes below.
+ */
+ memset(data, 0, CG2900_BT_LEN_VS_SET_SESSION_CONFIG);
+
+ data[0] = CG2900_BT_VS_SET_SESSION_CONFIG_LSB;
+ data[1] = CG2900_BT_VS_SET_SESSION_CONFIG_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+ data[3] = 1; /* Number of streams */
+ data[4] = CG2900_BT_SESSION_MEDIA_TYPE_AUDIO;
+ /* Media configuration: Set sample rate and audio channel setup */
+ data[5] = CG2900_BT_SESSION_CONF_SET_SAMPLE_RATE(
+ bt_config->sco.sample_rate);
+ data[5] |= CG2900_BT_MEDIA_CONFIG_MONO;
+ /* data[6] - data[10] SBC codec params (not used for SCO) */
+ /* Input Virtual Port (VP) configuration */
+ data[11] = CG2900_BT_VP_TYPE_BT_SCO;
+ /*
+ * As SCO handle we just use a default value.
+ * The chip will automatically connect the right SCO handle
+ */
+ data[12] = HCI_SET_U16_DATA_LSB(DEFAULT_SCO_HANDLE);
+ data[13] = HCI_SET_U16_DATA_MSB(DEFAULT_SCO_HANDLE);
+ /* data[14] - data[23] reserved */
+ /* Output Virtual Port (VP) configuration */
+ data[24] = CG2900_BT_VP_TYPE_PCM;
+ data[25] = CG2900_BT_SESSION_PCM_INDEX_PCM_I2S;
+ if (audio_info->i2s_pcm_config.slot_0_used)
+ data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(0);
+ if (audio_info->i2s_pcm_config.slot_1_used)
+ data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(1);
+ if (audio_info->i2s_pcm_config.slot_2_used)
+ data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(2);
+ if (audio_info->i2s_pcm_config.slot_3_used)
+ data[26] |= CG2900_BT_SESSION_CONF_SET_PCM_SLOT_USE(3);
+ data[27] = audio_info->i2s_pcm_config.slot_0_start;
+ data[28] = audio_info->i2s_pcm_config.slot_1_start;
+ data[29] = audio_info->i2s_pcm_config.slot_2_start;
+ data[30] = audio_info->i2s_pcm_config.slot_3_start;
+ /* data[31] - data[36] reserved */
+
+ 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_SESSION_CONFIG_LSB,
+ CG2900_BT_VS_SET_SESSION_CONFIG_MSB,
+ &temp_session_id,
+ CG2900_BT_PARAM_LEN_SESSION_ID);
+ if (err)
+ goto finished_unlock_mutex;
+
+ *stream_handle = temp_session_id;
+ CG2900_DBG("stream_handle set to %d", *stream_handle);
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now start the stream by sending HCI_VS_Session_Control command
+ */
+ CG2900_DBG("BT: HCI_VS_Session_Control");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+ data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+ data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+ data[3] = (u8)(*stream_handle); /* Session ID */
+ data[4] = CG2900_BT_SESSION_START;
+
+ 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_SESSION_CTRL_LSB,
+ CG2900_BT_VS_SESSION_CTRL_MSB,
+ 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;
+}
+
+
+/**
+ * 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 stops an audio stream defined by a stream handle.
+ * It does this by first stopping the Audio Stream and then resetting the
+ * Session configuration.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ECOMM if no response was received.
+ * -ENOMEM upon allocation errors.
+ * Errors from @cg2900_write and @receive_bt_cmd_complete.
+ * -EIO for other errors.
+ */
+static int conn_stop_stream(struct audio_user *audio_user,
+ unsigned int stream_handle)
+{
+ int err = 0;
+ union cg2900_endpoint_config_union *bt_config;
+ struct sk_buff *skb;
+ u8 *data;
+
+ bt_config = find_endpoint(ENDPOINT_BT_SCO_INOUT,
+ &(audio_info->endpoints));
+ if (!bt_config) {
+ CG2900_ERR("BT 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);
+
+ /*
+ * Now stop the stream by sending HCI_VS_Session_Control command
+ */
+ CG2900_DBG("BT: HCI_VS_Session_Control");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_SESSION_CTRL, GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_SESSION_CTRL);
+
+ data[0] = CG2900_BT_VS_SESSION_CTRL_LSB;
+ data[1] = CG2900_BT_VS_SESSION_CTRL_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SESSION_CTRL;
+ data[3] = (u8)stream_handle; /* Session ID */
+ data[4] = CG2900_BT_SESSION_STOP;
+
+ 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_SESSION_CTRL_LSB,
+ CG2900_BT_VS_SESSION_CTRL_MSB,
+ NULL, 0);
+ if (err)
+ goto finished_unlock_mutex;
+
+ SET_RESP_STATE(audio_user->resp_state, IDLE);
+
+ /*
+ * Now delete the stream by sending HCI_VS_Reset_Session_Configuration
+ * command
+ */
+ CG2900_DBG("BT: HCI_VS_Reset_Session_Configuration");
+
+ skb = cg2900_alloc_skb(CG2900_BT_LEN_VS_RESET_SESSION_CONFIG,
+ GFP_KERNEL);
+ if (!skb) {
+ CG2900_ERR("Could not allocate skb");
+ err = -ENOMEM;
+ goto finished_unlock_mutex;
+ }
+
+ /* Enter data into the skb */
+ data = skb_put(skb, CG2900_BT_LEN_VS_RESET_SESSION_CONFIG);
+
+ data[0] = CG2900_BT_VS_RESET_SESSION_CONFIG_LSB;
+ data[1] = CG2900_BT_VS_RESET_SESSION_CONFIG_MSB;
+ data[2] = CG2900_BT_PARAM_LEN_VS_SET_SESSION_CONFIG;
+ data[3] = (u8)stream_handle; /* Session ID */
+
+ 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_RESET_SESSION_CONFIG_LSB,
+ CG2900_BT_VS_RESET_SESSION_CONFIG_MSB,
+ 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;
+}
+
+/*
+ * External methods
+ */
+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) {
+ /*
+ * 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;
+
+ audio_info->state = OPENED;
+ }
+
+ goto finished;
+
+error_handling:
+ 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);
+
+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);
+
+int cg2900_audio_set_dai_config(unsigned int session,
+ struct cg2900_dai_config *config)
+{
+ int err = 0;
+ struct audio_user *audio_user;
+ struct cg2900_dai_conf_i2s *i2s = NULL;
+ struct cg2900_dai_conf_i2s_pcm *i2s_pcm = NULL;
+ struct sk_buff *skb = NULL;
+ u8 *data = NULL;
+
+ 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;
+
+ /*
+ * Use mutex to assure that only ONE command is sent at any time on each
+ * channel.
+ */
+ mutex_lock(&audio_info->bt_mutex);
+
+ /*
+ * Send following commands for the supported chips.
+ * For CG2900 PG1 = HCI_Cmd_VS_Set_Hardware_Configuration
+ */
+
+ /* 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;
+ }
+
+ /* Format HCI command
+ *
+ * [vp][ltype][direction + mode][Bitclk][PCM duration]
+ * Start with the 2 byte op code.
+ */
+ data = skb_put(skb, 2);
+ data[0] = CG2900_BT_VS_SET_HARDWARE_CONFIG_LSB;
+ data[1] = CG2900_BT_VS_SET_HARDWARE_CONFIG_MSB;
+
+ /* Now create each command depending on received configuration */
+ switch (config->port) {
+ case PORT_0_I2S:
+ i2s = (struct cg2900_dai_conf_i2s *) &config->conf;
+
+ /* We will now have 5 bytes of data (including length field) */
+ data = skb_put(skb, 5);
+
+ /* Parameters begin -----*/
+ data[0] = 0x04; /* Parameter length */
+ data[1] = PORT_PROTOCOL_I2S;
+ /* 0 = Virtual port if multiple on vp, PCM /I2S index */
+ data[2] = 0x00;
+ data[3] = i2s->half_period; /* WS Half period size */
+ if (i2s->mode == DAI_MODE_MASTER)
+ data[4] = CG2900_BT_HW_CONFIG_I2S_WS_SEL_MASTER;
+ else
+ data[4] = CG2900_BT_HW_CONFIG_I2S_WS_SEL_SLAVE;
+
+ /* 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:
+ i2s_pcm = (struct cg2900_dai_conf_i2s_pcm *) &config->conf;
+
+ /* We will now have 7 bytes of data (including length field) */
+ data = skb_put(skb, 7);
+
+ /* Parameters begin -----*/
+ data[0] = 0x06; /* Parameter total length */
+ data[1] = PORT_PROTOCOL_PCM;
+
+ if (i2s_pcm->protocol != PORT_PROTOCOL_PCM) {
+ /*
+ * Short solution for PG1 chip, don't support I2S over
+ * the PCM/I2S bus...
+ */
+ CG2900_ERR("I2S not supported over the PCM/I2S bus");
+ err = -EACCES;
+ goto error_handling_free_skb;
+ }
+
+ /*
+ * PCM (logical)
+ * 0 = Virtual port if multiple on vp, PCM /I2S index
+ */
+ data[2] = 0x00;
+
+ /*
+ * Set PCM direction and mode. They are a bit field in one byte.
+ */
+ data[3] = CG2900_BT_HW_CONFIG_PCM_SET_DIR(0,
+ i2s_pcm->slot_0_dir);
+ data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(1,
+ i2s_pcm->slot_1_dir);
+ data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(2,
+ i2s_pcm->slot_2_dir);
+ data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_DIR(3,
+ i2s_pcm->slot_3_dir);
+ if (i2s_pcm->mode == DAI_MODE_MASTER)
+ data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_MODE(
+ CG2900_BT_HW_CONFIG_PCM_MODE_MASTER);
+ else
+ data[3] |= CG2900_BT_HW_CONFIG_PCM_SET_MODE(
+ CG2900_BT_HW_CONFIG_PCM_MODE_SLAVE);
+ data[4] = i2s_pcm->clk;
+ data[5] = HCI_SET_U16_DATA_LSB(i2s_pcm->duration);
+ data[6] = HCI_SET_U16_DATA_MSB(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_LSB,
+ CG2900_BT_VS_SET_HARDWARE_CONFIG_MSB,
+ 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;
+}
+EXPORT_SYMBOL(cg2900_audio_set_dai_config);
+
+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);
+
+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);
+
+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;
+
+ /* First handle the endpoints */
+ switch (ep_1) {
+ case ENDPOINT_PORT_0_I2S:
+ switch (ep_2) {
+ case ENDPOINT_FM_RX:
+ err = conn_start_i2s_to_fm_rx(audio_user,
+ stream_handle);
+ break;
+
+ case ENDPOINT_FM_TX:
+ err = conn_start_i2s_to_fm_tx(audio_user,
+ stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ return -EINVAL;
+ }
+ break;
+
+ case ENDPOINT_PORT_1_I2S_PCM:
+ switch (ep_2) {
+ case ENDPOINT_BT_SCO_INOUT:
+ err = conn_start_pcm_to_sco(audio_user, stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ return -EINVAL;
+ }
+ break;
+
+ case ENDPOINT_BT_SCO_INOUT:
+ switch (ep_2) {
+ case ENDPOINT_PORT_1_I2S_PCM:
+ err = conn_start_pcm_to_sco(audio_user, stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ return -EINVAL;
+ }
+ break;
+
+ case ENDPOINT_FM_RX:
+ switch (ep_2) {
+ case ENDPOINT_PORT_0_I2S:
+ err = conn_start_i2s_to_fm_rx(audio_user,
+ stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ return -EINVAL;
+ }
+ break;
+
+ case ENDPOINT_FM_TX:
+ switch (ep_2) {
+ case ENDPOINT_PORT_0_I2S:
+ err = conn_start_i2s_to_fm_tx(audio_user,
+ stream_handle);
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, "
+ "ep2: %d", ep_1, ep_2);
+ return -EINVAL;
+ }
+ break;
+
+ default:
+ CG2900_ERR("Endpoint config not handled: ep1: %d, ep2: %d",
+ ep_1, ep_2);
+ return -EINVAL;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_start_stream);
+
+int cg2900_audio_stop_stream(unsigned int session, unsigned int stream_handle)
+{
+ int err = 0;
+ 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;
+
+ err = conn_stop_stream(audio_user, stream_handle);
+
+ return err;
+}
+EXPORT_SYMBOL(cg2900_audio_stop_stream);
+
+/*
+ * Character devices for userspace module test
+ */
+
+/**
+ * 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.");
+
+ 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_init() - Initialize module.
+ *
+ * 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 __init cg2900_audio_init(void)
+{
+ int err;
+
+ CG2900_INFO("cg2900_audio_init");
+
+ 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 = NULL;
+
+ 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_exit() - Remove module.
+ *
+ * Remove misc device and free resources.
+ */
+static void __exit cg2900_audio_exit(void)
+{
+ int err;
+
+ CG2900_INFO("cg2900_audio_exit");
+
+ if (!audio_info)
+ return;
+
+ 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;
+}
+
+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..a423285
--- /dev/null
+++ b/include/linux/mfd/cg2900_audio.h
@@ -0,0 +1,583 @@
+/*
+ * 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 = 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
+};
+
+/**
+ * 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 = 0x00,
+ SAMPLE_RATE_16 = 0x01,
+ SAMPLE_RATE_44_1 = 0x02,
+ SAMPLE_RATE_48 = 0x04
+};
+
+/**
+ * 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.
+ * @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 = 0x01,
+ ENDPOINT_SAMPLE_RATE_16_KHZ = 0x02,
+ ENDPOINT_SAMPLE_RATE_44_1_KHZ = 0x04,
+ ENDPOINT_SAMPLE_RATE_48_KHZ = 0x05
+};
+
+
+/**
+ * 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;
+};
+
+/*
+ * ST-Ericsson CG2900 audio control driver interfaces methods
+ */
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+#endif /* _CG2900_AUDIO_H_ */
--
1.6.3.3
^ permalink raw reply related
* [PATCH 4/6] This patch adds support for using UART as transport
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:50 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for using UART as transport for
the ST-Ericsson CG2900 connectivity controller.
This patch registers into the CG2900 framework.
It handles chip enable and disable as well baud rate settings.
It also assembles received packets into full Bluetooth, GPS,
and FM packets.
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 | 1587 ++++++++++++++++++++++++++++++++++++++
3 files changed, 1596 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/cg2900_uart.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index d47a253..2888358 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -596,6 +596,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
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index 3b6e442..02b7c25 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -8,3 +8,5 @@ export-objs := cg2900_core.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..6e53c5a
--- /dev/null
+++ b/drivers/mfd/cg2900/cg2900_uart.c
@@ -0,0 +1,1587 @@
+/*
+ * 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 <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/skbuff.h>
+#include <linux/pm.h>
+#include <linux/gpio.h>
+#include <linux/tty.h>
+#include <linux/tty_ldisc.h>
+#include <linux/poll.h>
+#include <linux/timer.h>
+#include <linux/mutex.h>
+#include <linux/sched.h>
+#include <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "cg2900_core.h"
+#include "cg2900_chip.h"
+#include "cg2900_debug.h"
+#include "hci_defines.h"
+
+/* Workqueues' names */
+#define UART_WQ_NAME "cg2900_uart_wq"
+
+/* 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.
+ */
+enum sleep_state {
+ CHIP_AWAKE,
+ CHIP_FALLING_ASLEEP,
+ CHIP_ASLEEP
+};
+
+/**
+ * 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.
+ */
+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;
+};
+
+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);
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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)
+ 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) {
+ /* Disable IRQ only when it was enabled. */
+ cg2900_devices_unset_cts_irq();
+ (void)set_tty_baud(tty, uart_info->baud_rate);
+ }
+ /* Set FLOW on. */
+ tty_unthrottle(tty);
+
+ /* Unset 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 (cg2900_devices_set_cts_irq() < 0) {
+ CG2900_ERR("Can not set intterupt 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:
+
+ tty->ops->break_ctl(tty, TTY_BREAK_ON);
+
+ SET_SLEEP_STATE(CHIP_FALLING_ASLEEP);
+ goto run_timer;
+
+ case CHIP_ASLEEP: /* Fallthrough. */
+ default:
+ CG2900_ERR("Chip already sleeps.");
+ 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));
+}
+
+/**
+ * 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;
+}
+
+/**
+ * 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;
+}
+
+/**
+ * handle_cts_irq() - Called to interrupt.
+ * @work: work which needs to be done.
+ *
+ * The handle_cts_irq() function is called if interrupt on
+ * CTS occurred.
+ *
+ */
+static void handle_cts_irq(struct work_struct *work)
+{
+ /* Restart timer and disabale interrupt. */
+ update_timer();
+}
+
+/**
+ * cg2900_devices_irq_cb() - Called from ste_conn_devices.
+ *
+ * The cg2900_devices_irq_cb() function is called if interrupt on CTS occurred.
+ *
+ */
+static void cg2900_devices_irq_cb(void)
+{
+ /* Create work and leave irq context. */
+ (void)create_work_item(uart_info->wq, handle_cts_irq, NULL);
+}
+
+/*
+ * struct uart_cg2900_dev_cb - Interrupt callback.
+ * @interrupt_cb: Called when interrupt on CTS occurred.
+ */
+static struct cg2900_devices_cb uart_cg2900_dev_cb = {
+ .interrupt_cb = cg2900_devices_irq_cb
+};
+
+/**
+ * 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);
+}
+
+/**
+ * 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;
+ }
+
+ /*
+ * 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;
+ }
+ /* Register interrupt callback into cg2900_devices.*/
+ cg2900_devices_reg_cb(&uart_cg2900_dev_cb);
+
+ return set_baud_rate(uart_high_baud);;
+}
+
+/**
+ * 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;
+
+ 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;
+ }
+
+ if (chip_on)
+ cg2900_devices_enable_chip();
+ else {
+ cg2900_devices_disable_chip();
+ /*
+ * 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_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("Length: %d Data: %02X %02X %02X %02X %02X "
+ "%02X %02X %02X %02X %02X %02X %02X",
+ skb->len,
+ skb->data[0], skb->data[1], skb->data[2],
+ skb->data[3], skb->data[4], skb->data[5],
+ skb->data[6], skb->data[7], skb->data[8],
+ skb->data[9], skb->data[10], skb->data[11]);
+
+ /* 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
+};
+
+/**
+ * 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 && tty->ops->tiocmget && tty->ops->break_ctl)
+ uart_info->sleep_allowed = true;
+ else
+ CG2900_DBG("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("Data: %02X %02X %02X %02X %02X %02X %02X "
+ "%02X %02X %02X %02X %02X",
+ data[0], data[1], data[2], data[3], data[4],
+ data[5], data[6], data[7], data[8], data[9],
+ data[10], data[11]);
+
+ /* 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
+};
+
+/**
+ * uart_init() - Initialize module.
+ *
+ * The uart_init() 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 __init uart_init(void)
+{
+ int err = 0;
+
+ CG2900_INFO("uart_init");
+
+ 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_AWAKE;
+ 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;
+ }
+
+ 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;
+}
+
+/**
+ * uart_exit() - Remove module.
+ */
+static void __exit uart_exit(void)
+{
+ int err;
+
+ CG2900_INFO("uart_exit");
+
+ /* 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;
+
+ destroy_workqueue(uart_info->wq);
+ mutex_destroy(&uart_info->tx_mutex);
+
+ kfree(uart_info);
+ uart_info = NULL;
+}
+
+module_init(uart_init);
+module_exit(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.");
--
1.6.3.3
^ permalink raw reply related
* [PATCH 3/6] This patch adds support for the ST-Ericsson STLC2690
From: Par-Gunnar Hjalmdahl @ 2010-09-24 13:49 UTC (permalink / raw)
To: linux-bluetooth, linux-kernel, linus.walleij, Pavan Savoy
This patch adds support for the ST-Ericsson STLC2690 connectivity
controller. This patch registers into the CG2900 framework.
It handles chip startup and shutdown together with firmware download.
Signed-off-by: Par-Gunnar Hjalmdahl <par-gunnar.p.hjalmdahl@stericsson.com>
---
drivers/mfd/Kconfig | 6 +
drivers/mfd/cg2900/Makefile | 1 +
drivers/mfd/cg2900/stlc2690_chip.c | 1105 ++++++++++++++++++++++++++++++++++++
drivers/mfd/cg2900/stlc2690_chip.h | 37 ++
4 files changed, 1149 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.c
create mode 100644 drivers/mfd/cg2900/stlc2690_chip.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f1fb334..d47a253 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -590,6 +590,12 @@ config MFD_CG2900_CHIP
help
Support for ST-Ericsson CG2900 Connectivity Controller
+config MFD_STLC2690_CHIP
+ tristate "Support STLC2690 Connectivity controller"
+ depends on MFD_CG2900
+ help
+ Support for ST-Ericsson STLC2690 Connectivity Controller
+
endif # MFD_SUPPORT
menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/cg2900/Makefile b/drivers/mfd/cg2900/Makefile
index b49bf6b..3b6e442 100644
--- a/drivers/mfd/cg2900/Makefile
+++ b/drivers/mfd/cg2900/Makefile
@@ -7,3 +7,4 @@ cg2900-objs := cg2900_core.o
cg2900_char_devices.o
export-objs := cg2900_core.o
obj-$(CONFIG_MFD_CG2900_CHIP) += cg2900_chip.o
+obj-$(CONFIG_MFD_STLC2690_CHIP) += stlc2690_chip.o
diff --git a/drivers/mfd/cg2900/stlc2690_chip.c
b/drivers/mfd/cg2900/stlc2690_chip.c
new file mode 100644
index 0000000..dc2d802
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.c
@@ -0,0 +1,1105 @@
+/*
+ * drivers/mfd/cg2900/ste_stlc2690.c
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/skbuff.h>
+#include <linux/gfp.h>
+#include <linux/stat.h>
+#include <linux/types.h>
+#include <linux/time.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+#include <asm/byteorder.h>
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci.h>
+
+#include <linux/mfd/cg2900.h>
+#include <mach/cg2900_devices.h>
+#include "hci_defines.h"
+#include "stlc2690_chip.h"
+#include "cg2900_core.h"
+#include "cg2900_debug.h"
+
+#define LINE_BUFFER_LENGTH 128
+#define FILENAME_MAX 128
+
+#define WQ_NAME "stlc2690_wq"
+#define PATCH_INFO_FILE "cg2900_patch_info.fw"
+#define FACTORY_SETTINGS_INFO_FILE "cg2900_settings_info.fw"
+
+/* Supported chips */
+#define SUPP_MANUFACTURER 0x30
+#define SUPP_REVISION_MIN 0x0500
+#define SUPP_REVISION_MAX 0x06FF
+
+/* Size of file chunk ID */
+#define FILE_CHUNK_ID_SIZE 1
+#define VS_SEND_FILE_CHUNK_ID_POS 4
+#define BT_CMD_LEN_POS 3
+
+/* State setting macros */
+#define SET_BOOT_STATE(__new_state) \
+ CG2900_SET_STATE("boot_state", stlc2690_info->boot_state, __new_state)
+#define SET_FILE_LOAD_STATE(__new_state) \
+ CG2900_SET_STATE("file_load_state", stlc2690_info->file_load_state, \
+ __new_state)
+#define SET_DOWNLOAD_STATE(__new_state) \
+ CG2900_SET_STATE("download_state", stlc2690_info->download_state, \
+ __new_state)
+
+/** CHANNEL_BT_CMD - Bluetooth HCI H:4 channel
+ * for Bluetooth commands in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_CMD 0x01
+
+/** CHANNEL_BT_ACL - Bluetooth HCI H:4 channel
+ * for Bluetooth ACL data in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_ACL 0x02
+
+/** CHANNEL_BT_EVT - Bluetooth HCI H:4 channel
+ * for Bluetooth events in the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_BT_EVT 0x04
+
+/** CHANNEL_HCI_LOGGER - Bluetooth HCI H:4 channel
+ * for logging all transmitted H4 packets (on all channels).
+ */
+#define CHANNEL_HCI_LOGGER 0xFA
+
+/** CHANNEL_US_CTRL - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_US_CTRL 0xFC
+
+/** CHANNEL_CORE - Bluetooth HCI H:4 channel
+ * for user space control of the ST-Ericsson connectivity controller.
+ */
+#define CHANNEL_CORE 0xFD
+
+/**
+ * struct stlc2690_work_struct - Work structure for CG2900 Core module.
+ * @work: Work structure.
+ * @skb: Data packet.
+ * @data: Private data for user.
+ *
+ * This structure is used to pack work for work queue.
+ */
+struct stlc2690_work_struct {
+ struct work_struct work;
+ struct sk_buff *skb;
+ void *data;
+};
+
+/**
+ * enum boot_state - BOOT-state for CG2900 Core.
+ * @BOOT_NOT_STARTED: Boot has not yet started.
+ * @BOOT_SEND_BD_ADDRESS: VS Store In FS command with BD address
+ * has been sent.
+ * @BOOT_GET_FILES_TO_LOAD: CG2900 Core is retreiving file to load.
+ * @BOOT_DOWNLOAD_PATCH: CG2900 Core is downloading patches.
+ * @BOOT_ACTIVATE_PATCHES_AND_SETTINGS: CG2900 Core is activating patches and
+ * settings.
+ * @BOOT_READY: CG2900 Core boot is ready.
+ * @BOOT_FAILED: CG2900 Core boot failed.
+ */
+enum boot_state {
+ BOOT_NOT_STARTED,
+ BOOT_SEND_BD_ADDRESS,
+ BOOT_GET_FILES_TO_LOAD,
+ BOOT_DOWNLOAD_PATCH,
+ BOOT_ACTIVATE_PATCHES_AND_SETTINGS,
+ BOOT_READY,
+ BOOT_FAILED
+};
+
+/**
+ * enum file_load_state - BOOT_FILE_LOAD-state for CG2900 Core.
+ * @FILE_LOAD_GET_PATCH: Loading patches.
+ * @FILE_LOAD_GET_STATIC_SETTINGS: Loading static settings.
+ * @FILE_LOAD_NO_MORE_FILES: No more files to load.
+ * @FILE_LOAD_FAILED: File loading failed.
+ */
+enum file_load_state {
+ FILE_LOAD_GET_PATCH,
+ FILE_LOAD_GET_STATIC_SETTINGS,
+ FILE_LOAD_NO_MORE_FILES,
+ FILE_LOAD_FAILED
+};
+
+/**
+ * enum download_state - BOOT_DOWNLOAD state.
+ * @DOWNLOAD_PENDING: Download in progress.
+ * @DOWNLOAD_SUCCESS: Download successfully finished.
+ * @DOWNLOAD_FAILED: Downloading failed.
+ */
+enum download_state {
+ DOWNLOAD_PENDING,
+ DOWNLOAD_SUCCESS,
+ DOWNLOAD_FAILED
+};
+
+/**
+ * struct stlc2690_device_id - Structure for connecting H4 channel to user.
+ * @name: Name of device.
+ * @h4_channel: HCI H:4 channel used by this device.
+ */
+struct stlc2690_device_id {
+ char *name;
+ int h4_channel;
+};
+
+/**
+ * struct stlc2690_info - Main info structure for STLC2690.
+ * @patch_file_name: Stores patch file name.
+ * @settings_file_name: Stores settings file name.
+ * @fw_file: Stores firmware file (patch or settings).
+ * @file_offset: Current read offset in firmware file.
+ * @chunk_id: Stores current chunk ID of write file operations.
+ * @boot_state: Current BOOT-state of STLC2690.
+ * @file_load_state: Current BOOT_FILE_LOAD-state of STLC2690.
+ * @download_state: Current BOOT_DOWNLOAD-state of STLC2690.
+ * @wq: STLC2690 workqueue.
+ * @chip_dev: Chip info.
+ */
+struct stlc2690_info {
+ char *patch_file_name;
+ char *settings_file_name;
+ const struct firmware *fw_file;
+ int file_offset;
+ u8 chunk_id;
+ enum boot_state boot_state;
+ enum file_load_state file_load_state;
+ enum download_state download_state;
+ struct workqueue_struct *wq;
+ struct cg2900_chip_dev chip_dev;
+};
+
+static struct stlc2690_info *stlc2690_info;
+
+#define NBR_OF_DEVS 6
+
+/*
+ * stlc2690_channels() - Array containing available H4 channels for
the STLC2690
+ * ST-Ericsson Connectivity controller.
+ */
+struct stlc2690_device_id stlc2690_channels[NBR_OF_DEVS] = {
+ {CG2900_BT_CMD, CHANNEL_BT_CMD},
+ {CG2900_BT_ACL, CHANNEL_BT_ACL},
+ {CG2900_BT_EVT, CHANNEL_BT_EVT},
+ {CG2900_HCI_LOGGER, CHANNEL_HCI_LOGGER},
+ {CG2900_US_CTRL, CHANNEL_US_CTRL},
+ {CG2900_CORE, CHANNEL_CORE}
+};
+
+/*
+ * Internal functions
+ */
+
+/**
+ * create_and_send_bt_cmd() - Copy and send sk_buffer.
+ * @data: Data to send.
+ * @length: Length in bytes of data.
+ *
+ * The create_and_send_bt_cmd() function allocate sk_buffer, copy supplied data
+ * to it, and send the sk_buffer to CG2900 Core.
+ * Note that the data must contain the H:4 header.
+ */
+static void create_and_send_bt_cmd(void *data, int length)
+{
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ int err;
+
+ skb = alloc_skb(length, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buff with length %d", length);
+ return;
+ }
+
+ memcpy(skb_put(skb, length), data, length);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ logger_config = cg2900_get_hci_logger_config();
+ if (logger_config)
+ err = cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ err = cg2900_send_to_chip(skb, false);
+ if (err) {
+ CG2900_ERR("Failed to transmit to chip (%d)", err);
+ kfree_skb(skb);
+ }
+}
+
+/**
+ * send_bd_address() - Send HCI VS command with BD address to the chip.
+ */
+static void send_bd_address(void)
+{
+ struct bt_vs_store_in_fs_cmd *cmd;
+ struct hci_command_hdr *hdr;
+ u8 *tmp;
+ u8 *data;
+ u8 plen = sizeof(*cmd) + BT_BDADDR_SIZE - 1;
+
+ data = kmalloc(sizeof(*hdr) + plen, GFP_KERNEL);
+ if (!data)
+ return;
+ tmp = data;
+
+ hdr = (struct hci_command_hdr *)tmp;
+ hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_STORE_IN_FS);
+ hdr->plen = plen;
+
+ tmp += sizeof(*hdr);
+ cmd = (struct bt_vs_store_in_fs_cmd *)tmp;
+ cmd->user_id = STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR;
+ cmd->len = BT_BDADDR_SIZE;
+ /* Now copy the BD address received from user space control app. */
+ memcpy(&(cmd->data), bd_address, BT_BDADDR_SIZE);
+
+ SET_BOOT_STATE(BOOT_SEND_BD_ADDRESS);
+
+ create_and_send_bt_cmd(data, sizeof(*hdr) + plen);
+
+ kfree(data);
+}
+
+/**
+ * create_work_item() - Create work item and add it to the work queue.
+ * @work_func: Work function.
+ * @skb: Data packet.
+ * @data: Private data for caller.
+ */
+static void create_work_item(work_func_t work_func, struct sk_buff *skb,
+ void *data)
+{
+ struct stlc2690_work_struct *new_work;
+ int wq_err = 1;
+
+ new_work = kmalloc(sizeof(*new_work), GFP_ATOMIC);
+ if (!new_work) {
+ CG2900_ERR("Failed to alloc memory for stlc2690_work_struct!");
+ return;
+ }
+
+ new_work->skb = skb;
+ new_work->data = data;
+ INIT_WORK(&new_work->work, work_func);
+
+ wq_err = queue_work(stlc2690_info->wq, &new_work->work);
+ if (!wq_err) {
+ CG2900_ERR("Failed to queue work_struct because it's already in"
+ " the queue!");
+ kfree(new_work);
+ }
+}
+
+/**
+ * get_text_line()- Replacement function for stdio function fgets.
+ * @wr_buffer: Buffer to copy text to.
+ * @max_nbr_of_bytes: Max number of bytes to read, i.e. size of rd_buffer.
+ * @rd_buffer: Data to parse.
+ * @bytes_copied: Number of bytes copied to wr_buffer.
+ *
+ * The get_text_line() function extracts one line of text from input file.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *get_text_line(char *wr_buffer, int max_nbr_of_bytes,
+ char *rd_buffer, int *bytes_copied)
+{
+ char *curr_wr = wr_buffer;
+ char *curr_rd = rd_buffer;
+ char in_byte;
+
+ *bytes_copied = 0;
+
+ do {
+ *curr_wr = *curr_rd;
+ in_byte = *curr_wr;
+ curr_wr++;
+ curr_rd++;
+ (*bytes_copied)++;
+ } while ((*bytes_copied <= max_nbr_of_bytes) && (in_byte != '\0') &&
+ (in_byte != '\n'));
+ *curr_wr = '\0';
+ return curr_rd;
+}
+
+/**
+ * get_file_to_load() - Parse info file and find correct target file.
+ * @fw: Firmware structure containing file data.
+ * @file_name: (out) Pointer to name of requested file.
+ *
+ * Returns:
+ * True, if target file was found,
+ * False, otherwise.
+ */
+static bool get_file_to_load(const struct firmware *fw, char **file_name)
+{
+ char *line_buffer;
+ char *curr_file_buffer;
+ int bytes_left_to_parse = fw->size;
+ int bytes_read = 0;
+ bool file_found = false;
+ u32 hci_rev;
+ u32 lmp_sub;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kzalloc(LINE_BUFFER_LENGTH, GFP_ATOMIC);
+ if (!line_buffer) {
+ CG2900_ERR("Failed to allocate: file_name 0x%X, "
+ "line_buffer 0x%X",
+ (u32)file_name, (u32)line_buffer);
+ goto finished;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer = get_text_line(line_buffer,
+ min(LINE_BUFFER_LENGTH,
+ (int)(fw->size - bytes_read)),
+ curr_file_buffer,
+ &bytes_read);
+
+ bytes_left_to_parse -= bytes_read;
+ if (bytes_left_to_parse <= 0) {
+ /* End of file => Leave while loop */
+ CG2900_ERR("Reached end of file. No file found!");
+ break;
+ }
+
+ /*
+ * Check if the line of text is a comment or not,
+ * comments begin with '#'
+ */
+ if (*line_buffer == '#')
+ continue;
+
+ hci_rev = 0;
+ lmp_sub = 0;
+
+ CG2900_DBG("Found a valid line <%s>", line_buffer);
+
+ /*
+ * Check if we can find the correct HCI revision and
+ * LMP subversion as well as a file name in the text line.
+ * Store the filename if the actual file can be found in
+ * the file system.
+ */
+ if (sscanf(line_buffer, "%x%x%s", &hci_rev, &lmp_sub,
+ *file_name) == 3
+ && hci_rev == stlc2690_info->chip_dev.chip.hci_revision
+ && lmp_sub ==
+ stlc2690_info->chip_dev.chip.hci_sub_version) {
+ CG2900_DBG("File matching chip found\n"
+ "\tFile name = %s\n"
+ "\tHCI Revision = 0x%X\n"
+ "\tLMP PAL Subversion = 0x%X",
+ *file_name, hci_rev, lmp_sub);
+
+ /*
+ * Name has already been stored above. Nothing more to
+ * do.
+ */
+ file_found = true;
+ } else {
+ /* Zero the name buffer so it is clear to next read */
+ memset(*file_name, 0x00, FILENAME_MAX + 1);
+ }
+ }
+ kfree(line_buffer);
+
+finished:
+ return file_found;
+}
+
+/**
+ * read_and_send_file_part() - Transmit a part of the supplied file.
+ *
+ * The read_and_send_file_part() function transmit a part of the supplied file
+ * to the controller.
+ * If nothing more to read, set the correct states.
+ */
+static void read_and_send_file_part(void)
+{
+ int bytes_to_copy;
+ struct sk_buff *skb;
+ struct cg2900_hci_logger_config *logger_config;
+ struct hci_command_hdr *hdr;
+ struct bt_vs_write_file_block_cmd *cmd;
+ u8 *data;
+ u8 plen;
+
+ /* Calculate number of bytes to copy;
+ * either max bytes for HCI packet or number of bytes left in file
+ */
+ bytes_to_copy = min((int)HCI_BT_SEND_FILE_MAX_CHUNK_SIZE,
+ (int)(stlc2690_info->fw_file->size -
+ stlc2690_info->file_offset));
+
+ if (bytes_to_copy <= 0) {
+ /* Nothing more to read in file. */
+ SET_DOWNLOAD_STATE(DOWNLOAD_SUCCESS);
+ stlc2690_info->chunk_id = 0;
+ stlc2690_info->file_offset = 0;
+ return;
+ }
+
+ /* There is more data to send */
+ logger_config = cg2900_get_hci_logger_config();
+
+ /* There are bytes to transmit. Allocate a sk_buffer. */
+ plen = sizeof(*cmd) - 1 + bytes_to_copy;
+ skb = cg2900_alloc_skb(sizeof(*hdr) + plen, GFP_ATOMIC);
+ if (!skb) {
+ CG2900_ERR("Couldn't allocate sk_buffer");
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ return;
+ }
+
+ skb_put(skb, sizeof(*hdr) + plen);
+
+ data = skb->data;
+ hdr = (struct hci_command_hdr *)data;
+ hdr->opcode = cpu_to_le16(STLC2690_BT_OP_VS_WRITE_FILE_BLOCK);
+ hdr->plen = plen;
+
+ data += sizeof(*hdr);
+ cmd = (struct bt_vs_write_file_block_cmd *)data;
+ cmd->id = stlc2690_info->chunk_id;
+ stlc2690_info->chunk_id++;
+
+ /* Copy the data from offset position */
+ memcpy(&(cmd->data),
+ &(stlc2690_info->fw_file->data[stlc2690_info->file_offset]),
+ bytes_to_copy);
+
+ /* Increase offset with number of bytes copied */
+ stlc2690_info->file_offset += bytes_to_copy;
+
+ skb_push(skb, CG2900_SKB_RESERVE);
+ skb->data[0] = CHANNEL_BT_CMD;
+
+ if (logger_config)
+ cg2900_send_to_chip(skb, logger_config->bt_cmd_enable);
+ else
+ cg2900_send_to_chip(skb, false);
+}
+
+/**
+ * send_settings_file() - Transmit settings file.
+ *
+ * The send_settings_file() function transmit settings file.
+ * The file is read in parts to fit in HCI packets.
+ * When finished, close the settings file and send HCI reset to activate
+ * settings and patches.
+ */
+static void send_settings_file(void)
+{
+ /* Transmit a file part */
+ read_and_send_file_part();
+
+ if (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Settings file finished. Release used resources */
+ CG2900_DBG("Settings file finished, release used resources");
+
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+
+ SET_FILE_LOAD_STATE(FILE_LOAD_NO_MORE_FILES);
+
+ /* Create and send HCI VS Store In FS command with bd address. */
+ send_bd_address();
+}
+
+/**
+ * send_patch_file - Transmit patch file.
+ *
+ * The send_patch_file() function transmit patch file. The file is
read in parts
+ * to fit in HCI packets.
+ * When the complete file is transmitted, the file is closed.
+ * When finished, continue with settings file.
+ */
+static void send_patch_file(void)
+{
+ int err;
+
+ /*
+ * Transmit a part of the supplied file to the controller.
+ * When nothing more to read, continue to close the patch file.
+ */
+ read_and_send_file_part();
+
+ if (stlc2690_info->download_state != DOWNLOAD_SUCCESS)
+ return;
+
+ /* Patch file finished. Release used resources */
+ CG2900_DBG("Patch file finished, release used resources");
+
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+
+ err = request_firmware(&(stlc2690_info->fw_file),
+ stlc2690_info->settings_file_name,
+ stlc2690_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get settings file (%d)", err);
+ goto error_handling;
+ }
+
+ /* Now send the settings file */
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_STATIC_SETTINGS);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ send_settings_file();
+ return;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(err);
+}
+
+/**
+ * work_reset_after_error() - Handle reset.
+ * @work: Reference to work data.
+ *
+ * Handle a reset after received command complete event.
+ */
+static void work_reset_after_error(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work = NULL;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ cg2900_chip_startup_finished(-EIO);
+
+ kfree(current_work);
+}
+
+/**
+ * work_load_patch_and_settings() - Start loading patches and settings.
+ * @work: Reference to work data.
+ */
+static void work_load_patch_and_settings(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work;
+ int err = 0;
+ bool file_found;
+ const struct firmware *patch_info;
+ const struct firmware *settings_info;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ /* Check that we are in the right state */
+ if (stlc2690_info->boot_state != BOOT_GET_FILES_TO_LOAD)
+ goto finished;
+
+ /* Open patch info file. */
+ err = request_firmware(&patch_info, PATCH_INFO_FILE,
+ stlc2690_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get patch info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the patch info file.
+ * See if we can find the right patch file as well
+ */
+ file_found = get_file_to_load(patch_info,
+ &(stlc2690_info->patch_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(patch_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find patch file! Major error!");
+ goto error_handling;
+ }
+
+ /* Open settings info file. */
+ err = request_firmware(&settings_info, FACTORY_SETTINGS_INFO_FILE,
+ stlc2690_info->chip_dev.dev);
+ if (err) {
+ CG2900_ERR("Couldn't get settings info file (%d)", err);
+ goto error_handling;
+ }
+
+ /*
+ * Now we have the settings info file.
+ * See if we can find the right settings file as well
+ */
+ file_found = get_file_to_load(settings_info,
+ &(stlc2690_info->settings_file_name));
+
+ /* Now we are finished with the patch info file */
+ release_firmware(settings_info);
+
+ if (!file_found) {
+ CG2900_ERR("Couldn't find settings file! Major error!");
+ goto error_handling;
+ }
+
+ /* We now all info needed */
+ SET_BOOT_STATE(BOOT_DOWNLOAD_PATCH);
+ SET_DOWNLOAD_STATE(DOWNLOAD_PENDING);
+ SET_FILE_LOAD_STATE(FILE_LOAD_GET_PATCH);
+ stlc2690_info->chunk_id = 0;
+ stlc2690_info->file_offset = 0;
+ stlc2690_info->fw_file = NULL;
+
+ /* OK. Now it is time to download the patches */
+ err = request_firmware(&(stlc2690_info->fw_file),
+ stlc2690_info->patch_file_name,
+ stlc2690_info->chip_dev.dev);
+ if (err < 0) {
+ CG2900_ERR("Couldn't get patch file (%d)", err);
+ goto error_handling;
+ }
+ send_patch_file();
+
+ goto finished;
+
+error_handling:
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+finished:
+ kfree(current_work);
+}
+
+/**
+ * work_cont_with_file_download() - A file block has been written.
+ * @work: Reference to work data.
+ *
+ * Handle a received HCI VS Write File Block Complete event.
+ * Normally this means continue to send files to the controller.
+ */
+static void work_cont_with_file_download(struct work_struct *work)
+{
+ struct stlc2690_work_struct *current_work;
+
+ if (!work) {
+ CG2900_ERR("work == NULL");
+ return;
+ }
+
+ current_work = container_of(work, struct stlc2690_work_struct, work);
+
+ /* Continue to send patches or settings to the controller */
+ if (stlc2690_info->file_load_state == FILE_LOAD_GET_PATCH)
+ send_patch_file();
+ else if (stlc2690_info->file_load_state ==
+ FILE_LOAD_GET_STATIC_SETTINGS)
+ send_settings_file();
+ else
+ CG2900_INFO("No more files to load");
+
+ kfree(current_work);
+}
+
+/**
+ * handle_reset_cmd_complete() - Handle a received HCI Command
Complete event for a Reset command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_reset_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ CG2900_INFO("Received Reset complete event");
+
+ if (stlc2690_info->boot_state != BOOT_ACTIVATE_PATCHES_AND_SETTINGS)
+ return false;
+
+ status = data[0];
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /*
+ * The boot sequence is now finished successfully.
+ * Set states and signal to waiting thread.
+ */
+ SET_BOOT_STATE(BOOT_READY);
+ cg2900_chip_startup_finished(0);
+ } else {
+ CG2900_ERR("Received Reset complete event with status 0x%X",
+ status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ cg2900_chip_startup_finished(-EIO);
+ }
+ return true;
+}
+
+/**
+ * handle_vs_store_in_fs_cmd_complete() - Handle a received HCI
Command Complete event for a VS StoreInFS command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_vs_store_in_fs_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ CG2900_INFO("Received Store_in_FS complete event");
+
+ if (stlc2690_info->boot_state != BOOT_SEND_BD_ADDRESS)
+ return false;
+
+ status = data[0];
+
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ struct hci_command_hdr cmd;
+
+ /* Send HCI Reset command to activate patches */
+ SET_BOOT_STATE(BOOT_ACTIVATE_PATCHES_AND_SETTINGS);
+ cmd.opcode = cpu_to_le16(HCI_OP_RESET);
+ cmd.plen = 0; /* No parameters for HCI Reset */
+ create_and_send_bt_cmd(&cmd, sizeof(cmd));
+ } else {
+ CG2900_ERR("Command Complete for StoreInFS received with "
+ "error 0x%X", status);
+ SET_BOOT_STATE(BOOT_FAILED);
+ create_work_item(work_reset_after_error, NULL, NULL);
+ }
+ /* We have now handled the packet */
+ return true;
+}
+
+/**
+ * handle_vs_write_file_block_cmd_complete() - Handle a received HCI
Command Complete event for a VS WriteFileBlock command.
+ * @data: Pointer to received HCI data packet.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_vs_write_file_block_cmd_complete(u8 *data)
+{
+ u8 status;
+
+ if ((stlc2690_info->boot_state != BOOT_DOWNLOAD_PATCH) ||
+ (stlc2690_info->download_state != DOWNLOAD_PENDING))
+ return false;
+
+ status = data[0];
+ if (HCI_BT_ERROR_NO_ERROR == status) {
+ /* Received good confirmation. Start work to continue. */
+ create_work_item(work_cont_with_file_download, NULL, NULL);
+ } else {
+ CG2900_ERR("Command Complete for WriteFileBlock received with "
+ "error 0x%X", status);
+ SET_DOWNLOAD_STATE(DOWNLOAD_FAILED);
+ SET_BOOT_STATE(BOOT_FAILED);
+ if (stlc2690_info->fw_file) {
+ release_firmware(stlc2690_info->fw_file);
+ stlc2690_info->fw_file = NULL;
+ }
+ create_work_item(work_reset_after_error, NULL, NULL);
+ }
+ /* We have now handled the packet */
+ return true;
+}
+
+/**
+ * handle_rx_data_bt_evt() - Check if received data should be handled.
+ * @skb: Data packet
+ *
+ * The handle_rx_data_bt_evt() function checks if received data should be
+ * handled by STLC2690. If so handle it correctly.
+ * Received data is always HCI BT Event.
+ *
+ * Returns:
+ * True, if packet was handled internally,
+ * False, otherwise.
+ */
+static bool handle_rx_data_bt_evt(struct sk_buff *skb)
+{
+ bool pkt_handled = false;
+ /* skb cannot be NULL here so it is safe to de-reference */
+ u8 *data = &(skb->data[CG2900_SKB_RESERVE]);
+ struct hci_event_hdr *evt;
+ struct hci_ev_cmd_complete *cmd_complete;
+ u16 op_code;
+
+ evt = (struct hci_event_hdr *)data;
+
+ /* First check the event code. Only handle Command Complete Event */
+ if (HCI_EV_CMD_COMPLETE != evt->evt)
+ return false;
+
+ data += sizeof(*evt);
+ cmd_complete = (struct hci_ev_cmd_complete *)data;
+
+ op_code = le16_to_cpu(cmd_complete->opcode);
+
+ CG2900_DBG_DATA("Received Command Complete: op_code = 0x%04X", op_code);
+ data += sizeof(*cmd_complete); /* Move to first byte after OCF */
+
+ if (op_code == HCI_OP_RESET)
+ pkt_handled = handle_reset_cmd_complete(data);
+ else if (op_code == STLC2690_BT_OP_VS_STORE_IN_FS)
+ pkt_handled = handle_vs_store_in_fs_cmd_complete(data);
+ else if (op_code == STLC2690_BT_OP_VS_WRITE_FILE_BLOCK)
+ pkt_handled = handle_vs_write_file_block_cmd_complete(data);
+
+ if (pkt_handled)
+ kfree_skb(skb);
+
+ return pkt_handled;
+}
+
+/**
+ * chip_startup() - Start the chip.
+ * @dev: Chip info.
+ *
+ * The chip_startup() function downloads patches and other needed start
+ * procedures.
+ *
+ * Returns:
+ * 0 if there is no error.
+ */
+static int chip_startup(struct cg2900_chip_dev *dev)
+{
+ /* Start the boot sequence */
+ SET_BOOT_STATE(BOOT_GET_FILES_TO_LOAD);
+ create_work_item(work_load_patch_and_settings, NULL, NULL);
+
+ return 0;
+}
+
+/**
+ * data_from_chip() - Called when data shall be sent to the chip.
+ * @dev: Chip info.
+ * @cg2900_dev: CG2900 user for this packet.
+ * @skb: Packet received.
+ *
+ * The data_from_chip() function checks if packet is a response for a packet it
+ * itself has transmitted.
+ *
+ * Returns:
+ * true if packet is handled by this driver.
+ * false otherwise.
+ */
+static bool data_from_chip(struct cg2900_chip_dev *dev,
+ struct cg2900_device *cg2900_dev,
+ struct sk_buff *skb)
+{
+ /* Then check if this is a response to data we have sent */
+ return handle_rx_data_bt_evt(skb);
+}
+
+/**
+ * get_h4_channel() - Returns H:4 channel for the name.
+ * @name: Chip info.
+ * @h4_channel: CG2900 user for this packet.
+ *
+ * Returns:
+ * 0 if there is no error.
+ * -ENXIO if channel is not found.
+ */
+static int get_h4_channel(char *name, int *h4_channel)
+{
+ int i;
+ int err = -ENXIO;
+
+ *h4_channel = -1;
+
+ for (i = 0; *h4_channel == -1 && i < NBR_OF_DEVS; i++) {
+ if (0 == strncmp(name, stlc2690_channels[i].name,
+ CG2900_MAX_NAME_SIZE)) {
+ /* Device found. Return H4 channel */
+ *h4_channel = stlc2690_channels[i].h4_channel;
+ err = 0;
+ }
+ }
+
+ return err;
+}
+
+/**
+ * check_chip_support() - Checks if connected chip is handled by this driver.
+ * @dev: Chip info structure.
+ *
+ * If supported return true and fill in @callbacks.
+ *
+ * Returns:
+ * true if chip is handled by this driver.
+ * false otherwise.
+ */
+static bool check_chip_support(struct cg2900_chip_dev *dev)
+{
+ CG2900_INFO("check_chip_support");
+
+ /*
+ * Check if this is a CG2690 revision. We do not care about
+ * the sub-version at the moment.
+ * Change this if necessary.
+ */
+ if ((dev->chip.manufacturer != SUPP_MANUFACTURER) ||
+ (dev->chip.hci_revision < SUPP_REVISION_MIN) ||
+ (dev->chip.hci_revision > SUPP_REVISION_MAX)) {
+ CG2900_DBG("Chip not supported by STLC2690 driver\n"
+ "\tMan: 0x%02X\n"
+ "\tRev: 0x%04X\n"
+ "\tSub: 0x%04X",
+ dev->chip.manufacturer, dev->chip.hci_revision,
+ dev->chip.hci_sub_version);
+ return false;
+ }
+
+ CG2900_INFO("Chip supported by the STLC2690 driver");
+
+ /* Store needed data */
+ dev->user_data = stlc2690_info;
+ memcpy(&(stlc2690_info->chip_dev), dev, sizeof(*dev));
+ /* Set the callbacks */
+ dev->cb.chip_startup = chip_startup;
+ dev->cb.data_from_chip = data_from_chip;
+ dev->cb.get_h4_channel = get_h4_channel;
+
+ return true;
+}
+
+static struct cg2900_id_callbacks stlc2690_id_callbacks = {
+ .check_chip_support = check_chip_support
+};
+
+/**
+ * stlc2690_init() - Initialize module.
+ *
+ * The stlc2690_init() function initialize the CG2690 driver, then register to
+ * the CG2900 Core.
+ *
+ * Returns:
+ * 0 if success.
+ * -ENOMEM for failed alloc or structure creation.
+ * Error codes generated by cg2900_register_chip_driver.
+ */
+static int __init stlc2690_init(void)
+{
+ int err = 0;
+
+ CG2900_INFO("stlc2690_init");
+
+ stlc2690_info = kzalloc(sizeof(*stlc2690_info), GFP_ATOMIC);
+ if (!stlc2690_info) {
+ CG2900_ERR("Couldn't allocate stlc2690_info");
+ err = -ENOMEM;
+ goto finished;
+ }
+
+ stlc2690_info->wq = create_singlethread_workqueue(WQ_NAME);
+ if (!stlc2690_info->wq) {
+ CG2900_ERR("Could not create workqueue");
+ err = -ENOMEM;
+ goto err_handling_free_info;
+ }
+
+ /*
+ * Allocate file names that will be used, deallocated in stlc2690_exit.
+ */
+ stlc2690_info->patch_file_name = kzalloc(FILENAME_MAX + 1, GFP_ATOMIC);
+ if (!stlc2690_info->patch_file_name) {
+ CG2900_ERR("Couldn't allocate name buffer for patch file.");
+ err = -ENOMEM;
+ goto err_handling_destroy_wq;
+ }
+ /*
+ * Allocate file names that will be used, deallocated in stlc2690_exit.
+ */
+ stlc2690_info->settings_file_name = kzalloc(FILENAME_MAX + 1,
+ GFP_ATOMIC);
+ if (!stlc2690_info->settings_file_name) {
+ CG2900_ERR("Couldn't allocate name buffers settings file.");
+ err = -ENOMEM;
+ goto err_handling_free_patch_name;
+ }
+
+ err = cg2900_register_chip_driver(&stlc2690_id_callbacks);
+ if (err) {
+ CG2900_ERR("Couldn't register chip driver (%d)", err);
+ goto err_handling_free_settings_name;
+ }
+
+ goto finished;
+
+err_handling_free_settings_name:
+ kfree(stlc2690_info->settings_file_name);
+err_handling_free_patch_name:
+ kfree(stlc2690_info->patch_file_name);
+err_handling_destroy_wq:
+ destroy_workqueue(stlc2690_info->wq);
+err_handling_free_info:
+ kfree(stlc2690_info);
+ stlc2690_info = NULL;
+finished:
+ return err;
+}
+
+/**
+ * stlc2690_exit() - Remove module.
+ */
+static void __exit stlc2690_exit(void)
+{
+ CG2900_INFO("stlc2690_exit");
+
+ if (!stlc2690_info)
+ return;
+
+ kfree(stlc2690_info->settings_file_name);
+ kfree(stlc2690_info->patch_file_name);
+ destroy_workqueue(stlc2690_info->wq);
+ kfree(stlc2690_info);
+ stlc2690_info = NULL;
+}
+
+module_init(stlc2690_init);
+module_exit(stlc2690_exit);
+
+MODULE_AUTHOR("Par-Gunnar Hjalmdahl ST-Ericsson");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Linux STLC2690 Connectivity Device Driver");
diff --git a/drivers/mfd/cg2900/stlc2690_chip.h
b/drivers/mfd/cg2900/stlc2690_chip.h
new file mode 100644
index 0000000..deb974d
--- /dev/null
+++ b/drivers/mfd/cg2900/stlc2690_chip.h
@@ -0,0 +1,37 @@
+/*
+ * drivers/mfd/cg2900/stlc2690_chip.h
+ *
+ * Copyright (C) ST-Ericsson SA 2010
+ * Authors:
+ * Par-Gunnar Hjalmdahl (par-gunnar.p.hjalmdahl@stericsson.com) for
ST-Ericsson.
+ * Henrik Possung (henrik.possung@stericsson.com) for ST-Ericsson.
+ * Josef Kindberg (josef.kindberg@stericsson.com) for ST-Ericsson.
+ * Dariusz Szymszak (dariusz.xd.szymczak@stericsson.com) for ST-Ericsson.
+ * Kjell Andersson (kjell.k.andersson@stericsson.com) for ST-Ericsson.
+ * License terms: GNU General Public License (GPL), version 2
+ *
+ * Linux Bluetooth HCI H:4 Driver for ST-Ericsson STLC2690 BT/FM controller.
+ */
+
+#ifndef _STLC2690_CHIP_H_
+#define _STLC2690_CHIP_H_
+
+/* BT VS Store In FS command */
+#define STLC2690_BT_OP_VS_STORE_IN_FS 0xFC22
+struct bt_vs_store_in_fs_cmd {
+ __u8 user_id;
+ __u8 len;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* BT VS Write File Block command */
+#define STLC2690_BT_OP_VS_WRITE_FILE_BLOCK 0xFC2E
+struct bt_vs_write_file_block_cmd {
+ __u8 id;
+ __u8 data; /* Really a data array of variable size */
+} __attribute__((packed));
+
+/* User ID for storing BD address in chip using Store_In_FS command */
+#define STLC2690_VS_STORE_IN_FS_USR_ID_BD_ADDR 0xFE
+
+#endif /* _STLC2690_CHIP_H_ */
--
1.6.3.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox