diff options
author | Loic Poulain <loic.poulain@intel.com> | 2015-09-02 13:04:11 +0300 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2015-09-17 14:20:01 +0300 |
commit | 894365468ed4a569ece9de03abbf13f1af3042b8 (patch) | |
tree | 84efacacb94e3f02aff14f41785eea9bcc0115fb /drivers/bluetooth/hci_intel.c | |
parent | 65ad07c9e5f3935a06517c007e90d2b2ef4829ac (diff) | |
download | linux-894365468ed4a569ece9de03abbf13f1af3042b8.tar.xz |
Bluetooth: hci_intel: Implement LPM suspend/resume
Add LPM PM suspend/resume/host_wake LPM functions.
A LPM transaction is composed with a LPM request and ack/response.
Host can send a LPM suspend/resume request to the controller which
should respond with a LPM ack.
If resume is requested by the controller (irq), host has to send a LPM
ack once resumed.
Signed-off-by: Loic Poulain <loic.poulain@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
Diffstat (limited to 'drivers/bluetooth/hci_intel.c')
-rw-r--r-- | drivers/bluetooth/hci_intel.c | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c index 5d53185c2d99..2e15ca5c1fd7 100644 --- a/drivers/bluetooth/hci_intel.c +++ b/drivers/bluetooth/hci_intel.c @@ -46,12 +46,17 @@ #define STATE_BOOTING 4 #define STATE_LPM_ENABLED 5 #define STATE_TX_ACTIVE 6 +#define STATE_SUSPENDED 7 +#define STATE_LPM_TRANSACTION 8 +#define HCI_LPM_WAKE_PKT 0xf0 #define HCI_LPM_PKT 0xf1 #define HCI_LPM_MAX_SIZE 10 #define HCI_LPM_HDR_SIZE HCI_EVENT_HDR_SIZE #define LPM_OP_TX_NOTIFY 0x00 +#define LPM_OP_SUSPEND_ACK 0x02 +#define LPM_OP_RESUME_ACK 0x03 struct hci_lpm_pkt { __u8 opcode; @@ -129,6 +134,143 @@ static int intel_wait_booting(struct hci_uart *hu) return err; } +static int intel_wait_lpm_transaction(struct hci_uart *hu) +{ + struct intel_data *intel = hu->priv; + int err; + + err = wait_on_bit_timeout(&intel->flags, STATE_LPM_TRANSACTION, + TASK_INTERRUPTIBLE, + msecs_to_jiffies(1000)); + + if (err == 1) { + bt_dev_err(hu->hdev, "LPM transaction interrupted"); + return -EINTR; + } + + if (err) { + bt_dev_err(hu->hdev, "LPM transaction timeout"); + return -ETIMEDOUT; + } + + return err; +} + +static int intel_lpm_suspend(struct hci_uart *hu) +{ + static const u8 suspend[] = { 0x01, 0x01, 0x01 }; + struct intel_data *intel = hu->priv; + struct sk_buff *skb; + + if (!test_bit(STATE_LPM_ENABLED, &intel->flags) || + test_bit(STATE_SUSPENDED, &intel->flags)) + return 0; + + if (test_bit(STATE_TX_ACTIVE, &intel->flags)) + return -EAGAIN; + + bt_dev_dbg(hu->hdev, "Suspending"); + + skb = bt_skb_alloc(sizeof(suspend), GFP_KERNEL); + if (!skb) { + bt_dev_err(hu->hdev, "Failed to alloc memory for LPM packet"); + return -ENOMEM; + } + + memcpy(skb_put(skb, sizeof(suspend)), suspend, sizeof(suspend)); + bt_cb(skb)->pkt_type = HCI_LPM_PKT; + + set_bit(STATE_LPM_TRANSACTION, &intel->flags); + + skb_queue_tail(&intel->txq, skb); + hci_uart_tx_wakeup(hu); + + intel_wait_lpm_transaction(hu); + /* Even in case of failure, continue and test the suspended flag */ + + clear_bit(STATE_LPM_TRANSACTION, &intel->flags); + + if (!test_bit(STATE_SUSPENDED, &intel->flags)) { + bt_dev_err(hu->hdev, "Device suspend error"); + return -EINVAL; + } + + bt_dev_dbg(hu->hdev, "Suspended"); + + hci_uart_set_flow_control(hu, true); + + return 0; +} + +static int intel_lpm_resume(struct hci_uart *hu) +{ + struct intel_data *intel = hu->priv; + struct sk_buff *skb; + + if (!test_bit(STATE_LPM_ENABLED, &intel->flags) || + !test_bit(STATE_SUSPENDED, &intel->flags)) + return 0; + + bt_dev_dbg(hu->hdev, "Resuming"); + + hci_uart_set_flow_control(hu, false); + + skb = bt_skb_alloc(0, GFP_KERNEL); + if (!skb) { + bt_dev_err(hu->hdev, "Failed to alloc memory for LPM packet"); + return -ENOMEM; + } + + bt_cb(skb)->pkt_type = HCI_LPM_WAKE_PKT; + + set_bit(STATE_LPM_TRANSACTION, &intel->flags); + + skb_queue_tail(&intel->txq, skb); + hci_uart_tx_wakeup(hu); + + intel_wait_lpm_transaction(hu); + /* Even in case of failure, continue and test the suspended flag */ + + clear_bit(STATE_LPM_TRANSACTION, &intel->flags); + + if (test_bit(STATE_SUSPENDED, &intel->flags)) { + bt_dev_err(hu->hdev, "Device resume error"); + return -EINVAL; + } + + bt_dev_dbg(hu->hdev, "Resumed"); + + return 0; +} + +static int intel_lpm_host_wake(struct hci_uart *hu) +{ + static const u8 lpm_resume_ack[] = { LPM_OP_RESUME_ACK, 0x00 }; + struct intel_data *intel = hu->priv; + struct sk_buff *skb; + + hci_uart_set_flow_control(hu, false); + + clear_bit(STATE_SUSPENDED, &intel->flags); + + skb = bt_skb_alloc(sizeof(lpm_resume_ack), GFP_KERNEL); + if (!skb) { + bt_dev_err(hu->hdev, "Failed to alloc memory for LPM packet"); + return -ENOMEM; + } + + memcpy(skb_put(skb, sizeof(lpm_resume_ack)), lpm_resume_ack, + sizeof(lpm_resume_ack)); + bt_cb(skb)->pkt_type = HCI_LPM_PKT; + + skb_queue_tail(&intel->txq, skb); + hci_uart_tx_wakeup(hu); + + bt_dev_dbg(hu->hdev, "Resumed by controller"); + + return 0; +} + static irqreturn_t intel_irq(int irq, void *dev_id) { struct intel_device *idev = dev_id; @@ -800,12 +942,28 @@ static void intel_recv_lpm_notify(struct hci_dev *hdev, int value) static int intel_recv_lpm(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_lpm_pkt *lpm = (void *)skb->data; + struct hci_uart *hu = hci_get_drvdata(hdev); + struct intel_data *intel = hu->priv; switch (lpm->opcode) { case LPM_OP_TX_NOTIFY: if (lpm->dlen) intel_recv_lpm_notify(hdev, lpm->data[0]); break; + case LPM_OP_SUSPEND_ACK: + set_bit(STATE_SUSPENDED, &intel->flags); + if (test_and_clear_bit(STATE_LPM_TRANSACTION, &intel->flags)) { + smp_mb__after_atomic(); + wake_up_bit(&intel->flags, STATE_LPM_TRANSACTION); + } + break; + case LPM_OP_RESUME_ACK: + clear_bit(STATE_SUSPENDED, &intel->flags); + if (test_and_clear_bit(STATE_LPM_TRANSACTION, &intel->flags)) { + smp_mb__after_atomic(); + wake_up_bit(&intel->flags, STATE_LPM_TRANSACTION); + } + break; default: bt_dev_err(hdev, "Unknown LPM opcode (%02x)", lpm->opcode); break; |