Bluetooth: btmtksdio: Add runtime PM support to SDIO based Bluetooth

Add runtime PM support to btmtksdio. With this way, there will be the
benefit of the device entering the more power saving state once it is
been a while data traffic is idle.

Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
This commit is contained in:
Sean Wang 2019-04-18 17:08:02 +08:00 committed by Marcel Holtmann
parent bcaa7d72df
commit 7f3c563c57
1 changed files with 144 additions and 0 deletions

View File

@ -17,6 +17,7 @@
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/mmc/host.h> #include <linux/mmc/host.h>
@ -33,6 +34,10 @@
#define FIRMWARE_MT7663 "mediatek/mt7663pr2h.bin" #define FIRMWARE_MT7663 "mediatek/mt7663pr2h.bin"
#define FIRMWARE_MT7668 "mediatek/mt7668pr2h.bin" #define FIRMWARE_MT7668 "mediatek/mt7668pr2h.bin"
#define MTKBTSDIO_AUTOSUSPEND_DELAY 8000
static bool enable_autosuspend;
struct btmtksdio_data { struct btmtksdio_data {
const char *fwname; const char *fwname;
}; };
@ -150,6 +155,7 @@ struct btmtk_hci_wmt_params {
struct btmtksdio_dev { struct btmtksdio_dev {
struct hci_dev *hdev; struct hci_dev *hdev;
struct sdio_func *func; struct sdio_func *func;
struct device *dev;
struct work_struct tx_work; struct work_struct tx_work;
unsigned long tx_state; unsigned long tx_state;
@ -301,6 +307,8 @@ static void btmtksdio_tx_work(struct work_struct *work)
struct sk_buff *skb; struct sk_buff *skb;
int err; int err;
pm_runtime_get_sync(bdev->dev);
sdio_claim_host(bdev->func); sdio_claim_host(bdev->func);
while ((skb = skb_dequeue(&bdev->txq))) { while ((skb = skb_dequeue(&bdev->txq))) {
@ -313,6 +321,9 @@ static void btmtksdio_tx_work(struct work_struct *work)
} }
sdio_release_host(bdev->func); sdio_release_host(bdev->func);
pm_runtime_mark_last_busy(bdev->dev);
pm_runtime_put_autosuspend(bdev->dev);
} }
static int btmtksdio_recv_event(struct hci_dev *hdev, struct sk_buff *skb) static int btmtksdio_recv_event(struct hci_dev *hdev, struct sk_buff *skb)
@ -471,6 +482,18 @@ static void btmtksdio_interrupt(struct sdio_func *func)
u32 int_status; u32 int_status;
u16 rx_size; u16 rx_size;
/* It is required that the host gets ownership from the device before
* accessing any register, however, if SDIO host is not being released,
* a potential deadlock probably happens in a circular wait between SDIO
* IRQ work and PM runtime work. So, we have to explicitly release SDIO
* host here and claim again after the PM runtime work is all done.
*/
sdio_release_host(bdev->func);
pm_runtime_get_sync(bdev->dev);
sdio_claim_host(bdev->func);
/* Disable interrupt */ /* Disable interrupt */
sdio_writel(func, C_INT_EN_CLR, MTK_REG_CHLPCR, 0); sdio_writel(func, C_INT_EN_CLR, MTK_REG_CHLPCR, 0);
@ -507,6 +530,9 @@ static void btmtksdio_interrupt(struct sdio_func *func)
/* Enable interrupt */ /* Enable interrupt */
sdio_writel(func, C_INT_EN_SET, MTK_REG_CHLPCR, 0); sdio_writel(func, C_INT_EN_SET, MTK_REG_CHLPCR, 0);
pm_runtime_mark_last_busy(bdev->dev);
pm_runtime_put_autosuspend(bdev->dev);
} }
static int btmtksdio_open(struct hci_dev *hdev) static int btmtksdio_open(struct hci_dev *hdev)
@ -815,6 +841,23 @@ ignore_func_on:
delta = ktime_sub(rettime, calltime); delta = ktime_sub(rettime, calltime);
duration = (unsigned long long)ktime_to_ns(delta) >> 10; duration = (unsigned long long)ktime_to_ns(delta) >> 10;
pm_runtime_set_autosuspend_delay(bdev->dev,
MTKBTSDIO_AUTOSUSPEND_DELAY);
pm_runtime_use_autosuspend(bdev->dev);
err = pm_runtime_set_active(bdev->dev);
if (err < 0)
return err;
/* Default forbid runtime auto suspend, that can be allowed by
* enable_autosuspend flag or the PM runtime entry under sysfs.
*/
pm_runtime_forbid(bdev->dev);
pm_runtime_enable(bdev->dev);
if (enable_autosuspend)
pm_runtime_allow(bdev->dev);
bt_dev_info(hdev, "Device setup in %llu usecs", duration); bt_dev_info(hdev, "Device setup in %llu usecs", duration);
return 0; return 0;
@ -822,10 +865,16 @@ ignore_func_on:
static int btmtksdio_shutdown(struct hci_dev *hdev) static int btmtksdio_shutdown(struct hci_dev *hdev)
{ {
struct btmtksdio_dev *bdev = hci_get_drvdata(hdev);
struct btmtk_hci_wmt_params wmt_params; struct btmtk_hci_wmt_params wmt_params;
u8 param = 0x0; u8 param = 0x0;
int err; int err;
/* Get back the state to be consistent with the state
* in btmtksdio_setup.
*/
pm_runtime_get_sync(bdev->dev);
/* Disable the device */ /* Disable the device */
wmt_params.op = MTK_WMT_FUNC_CTRL; wmt_params.op = MTK_WMT_FUNC_CTRL;
wmt_params.flag = 0; wmt_params.flag = 0;
@ -839,6 +888,9 @@ static int btmtksdio_shutdown(struct hci_dev *hdev)
return err; return err;
} }
pm_runtime_put_noidle(bdev->dev);
pm_runtime_disable(bdev->dev);
return 0; return 0;
} }
@ -885,6 +937,7 @@ static int btmtksdio_probe(struct sdio_func *func,
if (!bdev->data) if (!bdev->data)
return -ENODEV; return -ENODEV;
bdev->dev = &func->dev;
bdev->func = func; bdev->func = func;
INIT_WORK(&bdev->tx_work, btmtksdio_tx_work); INIT_WORK(&bdev->tx_work, btmtksdio_tx_work);
@ -922,6 +975,25 @@ static int btmtksdio_probe(struct sdio_func *func,
sdio_set_drvdata(func, bdev); sdio_set_drvdata(func, bdev);
/* pm_runtime_enable would be done after the firmware is being
* downloaded because the core layer probably already enables
* runtime PM for this func such as the case host->caps &
* MMC_CAP_POWER_OFF_CARD.
*/
if (pm_runtime_enabled(bdev->dev))
pm_runtime_disable(bdev->dev);
/* As explaination in drivers/mmc/core/sdio_bus.c tells us:
* Unbound SDIO functions are always suspended.
* During probe, the function is set active and the usage count
* is incremented. If the driver supports runtime PM,
* it should call pm_runtime_put_noidle() in its probe routine and
* pm_runtime_get_noresume() in its remove routine.
*
* So, put a pm_runtime_put_noidle here !
*/
pm_runtime_put_noidle(bdev->dev);
return 0; return 0;
} }
@ -933,6 +1005,9 @@ static void btmtksdio_remove(struct sdio_func *func)
if (!bdev) if (!bdev)
return; return;
/* Be consistent the state in btmtksdio_probe */
pm_runtime_get_noresume(bdev->dev);
hdev = bdev->hdev; hdev = bdev->hdev;
sdio_set_drvdata(func, NULL); sdio_set_drvdata(func, NULL);
@ -940,15 +1015,84 @@ static void btmtksdio_remove(struct sdio_func *func)
hci_free_dev(hdev); hci_free_dev(hdev);
} }
#ifdef CONFIG_PM
static int btmtksdio_runtime_suspend(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct btmtksdio_dev *bdev;
u32 status;
int err;
bdev = sdio_get_drvdata(func);
if (!bdev)
return 0;
sdio_claim_host(bdev->func);
sdio_writel(bdev->func, C_FW_OWN_REQ_SET, MTK_REG_CHLPCR, &err);
if (err < 0)
goto out;
err = readx_poll_timeout(btmtksdio_drv_own_query, bdev, status,
!(status & C_COM_DRV_OWN), 2000, 1000000);
out:
bt_dev_info(bdev->hdev, "status (%d) return ownership to device", err);
sdio_release_host(bdev->func);
return err;
}
static int btmtksdio_runtime_resume(struct device *dev)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct btmtksdio_dev *bdev;
u32 status;
int err;
bdev = sdio_get_drvdata(func);
if (!bdev)
return 0;
sdio_claim_host(bdev->func);
sdio_writel(bdev->func, C_FW_OWN_REQ_CLR, MTK_REG_CHLPCR, &err);
if (err < 0)
goto out;
err = readx_poll_timeout(btmtksdio_drv_own_query, bdev, status,
status & C_COM_DRV_OWN, 2000, 1000000);
out:
bt_dev_info(bdev->hdev, "status (%d) get ownership from device", err);
sdio_release_host(bdev->func);
return err;
}
static UNIVERSAL_DEV_PM_OPS(btmtksdio_pm_ops, btmtksdio_runtime_suspend,
btmtksdio_runtime_resume, NULL);
#define BTMTKSDIO_PM_OPS (&btmtksdio_pm_ops)
#else /* CONFIG_PM */
#define BTMTKSDIO_PM_OPS NULL
#endif /* CONFIG_PM */
static struct sdio_driver btmtksdio_driver = { static struct sdio_driver btmtksdio_driver = {
.name = "btmtksdio", .name = "btmtksdio",
.probe = btmtksdio_probe, .probe = btmtksdio_probe,
.remove = btmtksdio_remove, .remove = btmtksdio_remove,
.id_table = btmtksdio_table, .id_table = btmtksdio_table,
.drv = {
.owner = THIS_MODULE,
.pm = BTMTKSDIO_PM_OPS,
}
}; };
module_sdio_driver(btmtksdio_driver); module_sdio_driver(btmtksdio_driver);
module_param(enable_autosuspend, bool, 0644);
MODULE_PARM_DESC(enable_autosuspend, "Enable autosuspend by default");
MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>"); MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
MODULE_DESCRIPTION("MediaTek Bluetooth SDIO driver ver " VERSION); MODULE_DESCRIPTION("MediaTek Bluetooth SDIO driver ver " VERSION);
MODULE_VERSION(VERSION); MODULE_VERSION(VERSION);