[PATCH 3/5] Bluetooth: hci_intel: Introduce LPM support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Enable controller Low-Power-Mode if we have a pdev to manage host
wake-up. Once LPM is enabled, controller notifies its TX status via
a vendor specific packet (tx_idle/tx_active).
tx_active means that there is more data upcoming from controller.
tx_idle means that controller can be put in suspended state.

Signed-off-by: Loic Poulain <loic.poulain@xxxxxxxxx>
---
 drivers/bluetooth/hci_intel.c | 76 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 75 insertions(+), 1 deletion(-)

diff --git a/drivers/bluetooth/hci_intel.c b/drivers/bluetooth/hci_intel.c
index 26a1314..f855515 100644
--- a/drivers/bluetooth/hci_intel.c
+++ b/drivers/bluetooth/hci_intel.c
@@ -44,6 +44,25 @@
 #define STATE_FIRMWARE_LOADED	2
 #define STATE_FIRMWARE_FAILED	3
 #define STATE_BOOTING		4
+#define STATE_TX_ACTIVE		5
+
+#define HCI_LPM_PKT 0xf1
+#define HCI_LPM_MAX_SIZE 10
+#define HCI_LPM_HDR_SIZE HCI_EVENT_HDR_SIZE
+#define H4_RECV_LPM \
+	.type = HCI_LPM_PKT, \
+	.hlen = HCI_LPM_HDR_SIZE, \
+	.loff = 1, \
+	.lsize = 1, \
+	.maxlen = HCI_LPM_MAX_SIZE
+
+#define LPM_OP_TX_NOTIFY 0x00
+
+struct hci_lpm_pkt {
+	__u8 opcode;
+	__u8 dlen;
+	__u8 data[0];
+} __packed;
 
 struct intel_device {
 	struct list_head list;
@@ -141,9 +160,10 @@ static int intel_set_power(struct hci_uart *hu, bool powered)
 	spin_lock(&intel_device_list_lock);
 
 	idev = intel_device_get(hu);
-	if (!idev)
+	if (!idev) {
 		err = -ENODEV;
 		goto done;
+	}
 
 	if (!idev->reset) {
 		err = -ENOTSUPP;
@@ -299,7 +319,9 @@ static int intel_setup(struct hci_uart *hu)
 {
 	static const u8 reset_param[] = { 0x00, 0x01, 0x00, 0x01,
 					  0x00, 0x08, 0x04, 0x00 };
+	static const u8 lpm_param[] = {0x03, 0x07, 0x01, 0x0b};
 	struct intel_data *intel = hu->priv;
+	struct intel_device *idev;
 	struct hci_dev *hdev = hu->hdev;
 	struct sk_buff *skb;
 	struct intel_version *ver;
@@ -663,6 +685,25 @@ done:
 
 	BT_INFO("%s: Device booted in %llu usecs", hdev->name, duration);
 
+	spin_lock(&intel_device_list_lock);
+	idev = intel_device_get(hu);
+	if (!idev || !device_may_wakeup(&idev->pdev->dev)) {
+		spin_unlock(&intel_device_list_lock);
+		goto no_lpm;
+	}
+	spin_unlock(&intel_device_list_lock);
+
+	BT_INFO("%s: Enabling LPM", hdev->name);
+
+	skb = __hci_cmd_sync(hdev, 0xfc8b, sizeof(lpm_param), lpm_param,
+			     HCI_CMD_TIMEOUT);
+	if (IS_ERR(skb)) {
+		BT_ERR("%s: Failed to enable LPM", hdev->name);
+		goto no_lpm;
+	}
+	kfree_skb(skb);
+
+no_lpm:
 	skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_CMD_TIMEOUT);
 	if (IS_ERR(skb))
 		return PTR_ERR(skb);
@@ -723,10 +764,43 @@ recv:
 	return hci_recv_frame(hdev, skb);
 }
 
+static void intel_recv_lpm_notification(struct hci_dev *hdev, int value)
+{
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+	struct intel_data *intel = hu->priv;
+
+	BT_DBG("%s: TX idle notification (%d)", hdev->name, value);
+
+	if (value)
+		set_bit(STATE_TX_ACTIVE, &intel->flags);
+	else
+		clear_bit(STATE_TX_ACTIVE, &intel->flags);
+}
+
+static int intel_recv_lpm(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct hci_lpm_pkt *lpm = (void *)skb->data;
+
+	switch (lpm->opcode) {
+	case LPM_OP_TX_NOTIFY:
+		if (lpm->dlen)
+			intel_recv_lpm_notification(hdev, lpm->data[0]);
+		break;
+	default:
+		BT_ERR("%s: unknown LPM opcode (%02x)", hdev->name,
+		       lpm->opcode);
+	}
+
+	kfree_skb(skb);
+
+	return 0;
+}
+
 static const struct h4_recv_pkt intel_recv_pkts[] = {
 	{ H4_RECV_ACL,   .recv = hci_recv_frame },
 	{ H4_RECV_SCO,   .recv = hci_recv_frame },
 	{ H4_RECV_EVENT, .recv = intel_recv_event },
+	{ H4_RECV_LPM,   .recv = intel_recv_lpm },
 };
 
 static int intel_recv(struct hci_uart *hu, const void *data, int count)
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux