Bluetooth: hci_intel: Add runtime PM support
Implement runtime PM suspend/resume callbacks. If LPM supported, controller is put into supsend after a delay of inactivity (1s). Inactivity is based on LPM idle notification and host TX traffic. Signed-off-by: Loic Poulain <loic.poulain@intel.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
		
							parent
							
								
									aa6802df09
								
							
						
					
					
						commit
						74cdad37cd
					
				| @ -32,6 +32,7 @@ | |||||||
| #include <linux/gpio/consumer.h> | #include <linux/gpio/consumer.h> | ||||||
| #include <linux/acpi.h> | #include <linux/acpi.h> | ||||||
| #include <linux/interrupt.h> | #include <linux/interrupt.h> | ||||||
|  | #include <linux/pm_runtime.h> | ||||||
| 
 | 
 | ||||||
| #include <net/bluetooth/bluetooth.h> | #include <net/bluetooth/bluetooth.h> | ||||||
| #include <net/bluetooth/hci_core.h> | #include <net/bluetooth/hci_core.h> | ||||||
| @ -58,6 +59,8 @@ | |||||||
| #define LPM_OP_SUSPEND_ACK 0x02 | #define LPM_OP_SUSPEND_ACK 0x02 | ||||||
| #define LPM_OP_RESUME_ACK 0x03 | #define LPM_OP_RESUME_ACK 0x03 | ||||||
| 
 | 
 | ||||||
|  | #define LPM_SUSPEND_DELAY_MS 1000 | ||||||
|  | 
 | ||||||
| struct hci_lpm_pkt { | struct hci_lpm_pkt { | ||||||
| 	__u8 opcode; | 	__u8 opcode; | ||||||
| 	__u8 dlen; | 	__u8 dlen; | ||||||
| @ -79,6 +82,8 @@ static DEFINE_MUTEX(intel_device_list_lock); | |||||||
| struct intel_data { | struct intel_data { | ||||||
| 	struct sk_buff *rx_skb; | 	struct sk_buff *rx_skb; | ||||||
| 	struct sk_buff_head txq; | 	struct sk_buff_head txq; | ||||||
|  | 	struct work_struct busy_work; | ||||||
|  | 	struct hci_uart *hu; | ||||||
| 	unsigned long flags; | 	unsigned long flags; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -284,6 +289,11 @@ static irqreturn_t intel_irq(int irq, void *dev_id) | |||||||
| 		intel_lpm_host_wake(idev->hu); | 		intel_lpm_host_wake(idev->hu); | ||||||
| 	mutex_unlock(&idev->hu_lock); | 	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; | 	return IRQ_HANDLED; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -339,9 +349,17 @@ static int intel_set_power(struct hci_uart *hu, bool powered) | |||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			device_wakeup_enable(&idev->pdev->dev); | 			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)) { | 		} else if (!powered && device_may_wakeup(&idev->pdev->dev)) { | ||||||
| 			devm_free_irq(&idev->pdev->dev, idev->irq, idev); | 			devm_free_irq(&idev->pdev->dev, idev->irq, idev); | ||||||
| 			device_wakeup_disable(&idev->pdev->dev); | 			device_wakeup_disable(&idev->pdev->dev); | ||||||
|  | 
 | ||||||
|  | 			pm_runtime_disable(&idev->pdev->dev); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -350,6 +368,28 @@ static int intel_set_power(struct hci_uart *hu, bool powered) | |||||||
| 	return err; | 	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) | static int intel_open(struct hci_uart *hu) | ||||||
| { | { | ||||||
| 	struct intel_data *intel; | 	struct intel_data *intel; | ||||||
| @ -361,6 +401,9 @@ static int intel_open(struct hci_uart *hu) | |||||||
| 		return -ENOMEM; | 		return -ENOMEM; | ||||||
| 
 | 
 | ||||||
| 	skb_queue_head_init(&intel->txq); | 	skb_queue_head_init(&intel->txq); | ||||||
|  | 	INIT_WORK(&intel->busy_work, intel_busy_work); | ||||||
|  | 
 | ||||||
|  | 	intel->hu = hu; | ||||||
| 
 | 
 | ||||||
| 	hu->priv = intel; | 	hu->priv = intel; | ||||||
| 
 | 
 | ||||||
| @ -376,6 +419,8 @@ static int intel_close(struct hci_uart *hu) | |||||||
| 
 | 
 | ||||||
| 	BT_DBG("hu %p", hu); | 	BT_DBG("hu %p", hu); | ||||||
| 
 | 
 | ||||||
|  | 	cancel_work_sync(&intel->busy_work); | ||||||
|  | 
 | ||||||
| 	intel_set_power(hu, false); | 	intel_set_power(hu, false); | ||||||
| 
 | 
 | ||||||
| 	skb_queue_purge(&intel->txq); | 	skb_queue_purge(&intel->txq); | ||||||
| @ -949,10 +994,12 @@ static void intel_recv_lpm_notify(struct hci_dev *hdev, int value) | |||||||
| 
 | 
 | ||||||
| 	bt_dev_dbg(hdev, "TX idle notification (%d)", value); | 	bt_dev_dbg(hdev, "TX idle notification (%d)", value); | ||||||
| 
 | 
 | ||||||
| 	if (value) | 	if (value) { | ||||||
| 		set_bit(STATE_TX_ACTIVE, &intel->flags); | 		set_bit(STATE_TX_ACTIVE, &intel->flags); | ||||||
| 	else | 		schedule_work(&intel->busy_work); | ||||||
|  | 	} else { | ||||||
| 		clear_bit(STATE_TX_ACTIVE, &intel->flags); | 		clear_bit(STATE_TX_ACTIVE, &intel->flags); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int intel_recv_lpm(struct hci_dev *hdev, struct sk_buff *skb) | static int intel_recv_lpm(struct hci_dev *hdev, struct sk_buff *skb) | ||||||
| @ -1027,9 +1074,27 @@ static int intel_recv(struct hci_uart *hu, const void *data, int count) | |||||||
| static int intel_enqueue(struct hci_uart *hu, struct sk_buff *skb) | static int intel_enqueue(struct hci_uart *hu, struct sk_buff *skb) | ||||||
| { | { | ||||||
| 	struct intel_data *intel = hu->priv; | 	struct intel_data *intel = hu->priv; | ||||||
|  | 	struct list_head *p; | ||||||
| 
 | 
 | ||||||
| 	BT_DBG("hu %p skb %p", hu, skb); | 	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); | 	skb_queue_tail(&intel->txq, skb); | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| @ -1103,7 +1168,7 @@ static int intel_acpi_probe(struct intel_device *idev) | |||||||
| } | } | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| #ifdef CONFIG_PM_SLEEP | #ifdef CONFIG_PM | ||||||
| static int intel_suspend(struct device *dev) | static int intel_suspend(struct device *dev) | ||||||
| { | { | ||||||
| 	struct intel_device *idev = dev_get_drvdata(dev); | 	struct intel_device *idev = dev_get_drvdata(dev); | ||||||
| @ -1135,6 +1200,7 @@ static int intel_resume(struct device *dev) | |||||||
| 
 | 
 | ||||||
| static const struct dev_pm_ops intel_pm_ops = { | static const struct dev_pm_ops intel_pm_ops = { | ||||||
| 	SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) | 	SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) | ||||||
|  | 	SET_RUNTIME_PM_OPS(intel_suspend, intel_resume, NULL) | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static int intel_probe(struct platform_device *pdev) | static int intel_probe(struct platform_device *pdev) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user