#include <linux/gpio/consumer.h>
#include <linux/acpi.h>
#include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#define LPM_OP_SUSPEND_ACK 0x02
#define LPM_OP_RESUME_ACK 0x03
+#define LPM_SUSPEND_DELAY_MS 1000
+
struct hci_lpm_pkt {
__u8 opcode;
__u8 dlen;
struct intel_data {
struct sk_buff *rx_skb;
struct sk_buff_head txq;
+ struct work_struct busy_work;
+ struct hci_uart *hu;
unsigned long flags;
};
return err;
}
+#ifdef CONFIG_PM
static int intel_wait_lpm_transaction(struct hci_uart *hu)
{
struct intel_data *intel = hu->priv;
set_bit(STATE_LPM_TRANSACTION, &intel->flags);
- skb_queue_tail(&intel->txq, skb);
+ /* LPM flow is a priority, enqueue packet at list head */
+ skb_queue_head(&intel->txq, skb);
hci_uart_tx_wakeup(hu);
intel_wait_lpm_transaction(hu);
set_bit(STATE_LPM_TRANSACTION, &intel->flags);
- skb_queue_tail(&intel->txq, skb);
+ /* LPM flow is a priority, enqueue packet at list head */
+ skb_queue_head(&intel->txq, skb);
hci_uart_tx_wakeup(hu);
intel_wait_lpm_transaction(hu);
return 0;
}
+#endif /* CONFIG_PM */
static int intel_lpm_host_wake(struct hci_uart *hu)
{
sizeof(lpm_resume_ack));
bt_cb(skb)->pkt_type = HCI_LPM_PKT;
- skb_queue_tail(&intel->txq, skb);
+ /* LPM flow is a priority, enqueue packet at list head */
+ skb_queue_head(&intel->txq, skb);
hci_uart_tx_wakeup(hu);
bt_dev_dbg(hu->hdev, "Resumed by controller");
intel_lpm_host_wake(idev->hu);
mutex_unlock(&idev->hu_lock);
+ /* Host/Controller are now LPM resumed, trigger a new delayed suspend */
+ pm_runtime_get(&idev->pdev->dev);
+ pm_runtime_mark_last_busy(&idev->pdev->dev);
+ pm_runtime_put_autosuspend(&idev->pdev->dev);
+
return IRQ_HANDLED;
}
}
device_wakeup_enable(&idev->pdev->dev);
+
+ pm_runtime_set_active(&idev->pdev->dev);
+ pm_runtime_use_autosuspend(&idev->pdev->dev);
+ pm_runtime_set_autosuspend_delay(&idev->pdev->dev,
+ LPM_SUSPEND_DELAY_MS);
+ pm_runtime_enable(&idev->pdev->dev);
} else if (!powered && device_may_wakeup(&idev->pdev->dev)) {
devm_free_irq(&idev->pdev->dev, idev->irq, idev);
device_wakeup_disable(&idev->pdev->dev);
+
+ pm_runtime_disable(&idev->pdev->dev);
}
}
return err;
}
+static void intel_busy_work(struct work_struct *work)
+{
+ struct list_head *p;
+ struct intel_data *intel = container_of(work, struct intel_data,
+ busy_work);
+
+ /* Link is busy, delay the suspend */
+ mutex_lock(&intel_device_list_lock);
+ list_for_each(p, &intel_device_list) {
+ struct intel_device *idev = list_entry(p, struct intel_device,
+ list);
+
+ if (intel->hu->tty->dev->parent == idev->pdev->dev.parent) {
+ pm_runtime_get(&idev->pdev->dev);
+ pm_runtime_mark_last_busy(&idev->pdev->dev);
+ pm_runtime_put_autosuspend(&idev->pdev->dev);
+ break;
+ }
+ }
+ mutex_unlock(&intel_device_list_lock);
+}
+
static int intel_open(struct hci_uart *hu)
{
struct intel_data *intel;
return -ENOMEM;
skb_queue_head_init(&intel->txq);
+ INIT_WORK(&intel->busy_work, intel_busy_work);
+
+ intel->hu = hu;
hu->priv = intel;
BT_DBG("hu %p", hu);
+ cancel_work_sync(&intel->busy_work);
+
intel_set_power(hu, false);
skb_queue_purge(&intel->txq);
bt_dev_info(hdev, "Found device firmware: %s", fwname);
+ /* Save the DDC file name for later */
+ snprintf(fwname, sizeof(fwname), "intel/ibt-11-%u.ddc",
+ le16_to_cpu(params->dev_revid));
+
kfree_skb(skb);
if (fw->size < 644) {
set_bit(STATE_LPM_ENABLED, &intel->flags);
no_lpm:
+ /* Ignore errors, device can work without DDC parameters */
+ btintel_load_ddc_config(hdev, fwname);
+
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL, HCI_CMD_TIMEOUT);
if (IS_ERR(skb))
return PTR_ERR(skb);
bt_dev_dbg(hdev, "TX idle notification (%d)", value);
- if (value)
+ if (value) {
set_bit(STATE_TX_ACTIVE, &intel->flags);
- else
+ schedule_work(&intel->busy_work);
+ } else {
clear_bit(STATE_TX_ACTIVE, &intel->flags);
+ }
}
static int intel_recv_lpm(struct hci_dev *hdev, struct sk_buff *skb)
switch (lpm->opcode) {
case LPM_OP_TX_NOTIFY:
- if (lpm->dlen)
- intel_recv_lpm_notify(hdev, lpm->data[0]);
+ if (lpm->dlen < 1) {
+ bt_dev_err(hu->hdev, "Invalid LPM notification packet");
+ break;
+ }
+ intel_recv_lpm_notify(hdev, lpm->data[0]);
break;
case LPM_OP_SUSPEND_ACK:
set_bit(STATE_SUSPENDED, &intel->flags);
static int intel_enqueue(struct hci_uart *hu, struct sk_buff *skb)
{
struct intel_data *intel = hu->priv;
+ struct list_head *p;
BT_DBG("hu %p skb %p", hu, skb);
+ /* Be sure our controller is resumed and potential LPM transaction
+ * completed before enqueuing any packet.
+ */
+ mutex_lock(&intel_device_list_lock);
+ list_for_each(p, &intel_device_list) {
+ struct intel_device *idev = list_entry(p, struct intel_device,
+ list);
+
+ if (hu->tty->dev->parent == idev->pdev->dev.parent) {
+ pm_runtime_get_sync(&idev->pdev->dev);
+ pm_runtime_mark_last_busy(&idev->pdev->dev);
+ pm_runtime_put_autosuspend(&idev->pdev->dev);
+ break;
+ }
+ }
+ mutex_unlock(&intel_device_list_lock);
+
skb_queue_tail(&intel->txq, skb);
return 0;
{ },
};
MODULE_DEVICE_TABLE(acpi, intel_acpi_match);
+#endif
-static int intel_acpi_probe(struct intel_device *idev)
+#ifdef CONFIG_PM
+static int intel_suspend_device(struct device *dev)
{
- const struct acpi_device_id *id;
+ struct intel_device *idev = dev_get_drvdata(dev);
- id = acpi_match_device(intel_acpi_match, &idev->pdev->dev);
- if (!id)
- return -ENODEV;
+ mutex_lock(&idev->hu_lock);
+ if (idev->hu)
+ intel_lpm_suspend(idev->hu);
+ mutex_unlock(&idev->hu_lock);
return 0;
}
-#else
-static int intel_acpi_probe(struct intel_device *idev)
+
+static int intel_resume_device(struct device *dev)
{
- return -ENODEV;
+ struct intel_device *idev = dev_get_drvdata(dev);
+
+ mutex_lock(&idev->hu_lock);
+ if (idev->hu)
+ intel_lpm_resume(idev->hu);
+ mutex_unlock(&idev->hu_lock);
+
+ return 0;
}
#endif
{
struct intel_device *idev = dev_get_drvdata(dev);
- dev_dbg(dev, "intel_suspend");
-
- mutex_lock(&idev->hu_lock);
- if (idev->hu)
- intel_lpm_suspend(idev->hu);
- mutex_unlock(&idev->hu_lock);
+ if (device_may_wakeup(dev))
+ enable_irq_wake(idev->irq);
- return 0;
+ return intel_suspend_device(dev);
}
static int intel_resume(struct device *dev)
{
struct intel_device *idev = dev_get_drvdata(dev);
- dev_dbg(dev, "intel_resume");
+ if (device_may_wakeup(dev))
+ disable_irq_wake(idev->irq);
- mutex_lock(&idev->hu_lock);
- if (idev->hu)
- intel_lpm_resume(idev->hu);
- mutex_unlock(&idev->hu_lock);
-
- return 0;
+ return intel_resume_device(dev);
}
#endif
static const struct dev_pm_ops intel_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
+ SET_RUNTIME_PM_OPS(intel_suspend_device, intel_resume_device, NULL)
};
static int intel_probe(struct platform_device *pdev)
idev->pdev = pdev;
- if (ACPI_HANDLE(&pdev->dev)) {
- int err = intel_acpi_probe(idev);
- if (err)
- return err;
- } else {
- return -ENODEV;
- }
-
idev->reset = devm_gpiod_get_optional(&pdev->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(idev->reset)) {