diff options
| author | Komal Seelam <kseelam@codeaurora.org> | 2016-07-26 16:58:00 +0530 |
|---|---|---|
| committer | Anand Kumar <anandkumar@codeaurora.org> | 2016-08-29 17:03:49 +0530 |
| commit | 9bb7d6728fd1956bb3e3328d6f6dabfee0f57ae7 (patch) | |
| tree | 2490b23a90f3256beebc5a09027ddc37ea42e789 /drivers/net | |
| parent | 0e0c3c54aaeec4441b1e92c6ac1984868498d9a9 (diff) | |
net: cnss: Release QCA chip resources when Wi-Fi is turned off
When WiFi is turned off from userspace, save power by toggling
WLAN_EN gpio and restore power when wifi is loaded again.
CRs-Fixed: 1058794
Change-Id: I0257698d9d168d7c889436a05693061cafe5ea7c
Signed-off-by: Komal Seelam <kseelam@codeaurora.org>
Diffstat (limited to 'drivers/net')
| -rw-r--r-- | drivers/net/wireless/cnss/cnss_sdio.c | 174 |
1 files changed, 156 insertions, 18 deletions
diff --git a/drivers/net/wireless/cnss/cnss_sdio.c b/drivers/net/wireless/cnss/cnss_sdio.c index 6ad0c7065f7a..cd8b8538bc96 100644 --- a/drivers/net/wireless/cnss/cnss_sdio.c +++ b/drivers/net/wireless/cnss/cnss_sdio.c @@ -21,6 +21,8 @@ #include <linux/slab.h> #include <linux/mmc/sdio_func.h> #include <linux/mmc/sdio_ids.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> #include <linux/io.h> #include <soc/qcom/subsystem_restart.h> #include <soc/qcom/subsystem_notif.h> @@ -60,7 +62,11 @@ struct cnss_sdio_regulator { struct cnss_sdio_info { struct cnss_sdio_wlan_driver *wdrv; struct sdio_func *func; + struct mmc_card *card; + struct mmc_host *host; + struct device *dev; const struct sdio_device_id *id; + bool skip_wlan_en_toggle; }; struct cnss_ssr_info { @@ -212,6 +218,80 @@ void cnss_sdio_remove_pm_qos(void) } EXPORT_SYMBOL(cnss_sdio_remove_pm_qos); +static int cnss_put_hw_resources(struct device *dev) +{ + int ret = -EINVAL; + struct cnss_sdio_info *info; + struct mmc_host *host; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("%s: HW doesn't support wlan toggling\n", __func__); + return 0; + } + + host = info->host; + + if (!host) { + pr_err("%s: MMC host is invalid\n", __func__); + return 0; + } + + ret = mmc_power_save_host(host); + if (ret) { + pr_err("%s: Failed to Power Save Host err:%d\n", __func__, + ret); + return ret; + } + + if (!cnss_pdata->regulator.wlan_vreg) { + pr_debug("%s: wlan_vreg regulator is invalid\n", __func__); + return ret; + } + + regulator_disable(cnss_pdata->regulator.wlan_vreg); + + return ret; +} + +static int cnss_get_hw_resources(struct device *dev) +{ + int ret = -EINVAL; + struct mmc_host *host; + struct cnss_sdio_info *info; + + if (!cnss_pdata) + return ret; + + info = &cnss_pdata->cnss_sdio_info; + + if (info->skip_wlan_en_toggle) { + pr_debug("%s: HW doesn't support wlan toggling\n", __func__); + return 0; + } + + host = info->host; + + ret = regulator_enable(cnss_pdata->regulator.wlan_vreg); + if (ret) { + pr_err("%s: Failed to enable wlan vreg\n", __func__); + return ret; + } + + ret = mmc_power_restore_host(host); + if (ret) { + pr_err("%s: Failed to restore host power ret:%d\n", __func__, + ret); + regulator_disable(cnss_pdata->regulator.wlan_vreg); + } + + return ret; +} + static int cnss_sdio_shutdown(const struct subsys_desc *subsys, bool force_stop) { struct cnss_sdio_info *cnss_info; @@ -551,25 +631,41 @@ int cnss_get_restart_level(void) } EXPORT_SYMBOL(cnss_get_restart_level); -static int cnss_sdio_wlan_inserted( - struct sdio_func *func, - const struct sdio_device_id *id) +static int cnss_sdio_wlan_inserted(struct sdio_func *func, + const struct sdio_device_id *id) { + struct cnss_sdio_info *info; + if (!cnss_pdata) return -ENODEV; - cnss_pdata->cnss_sdio_info.func = func; - cnss_pdata->cnss_sdio_info.id = id; + info = &cnss_pdata->cnss_sdio_info; + + info->func = func; + info->card = func->card; + info->host = func->card->host; + info->id = id; + info->dev = &func->dev; + + cnss_put_hw_resources(cnss_pdata->cnss_sdio_info.dev); + + pr_info("%s: SDIO Device is Probed\n", __func__); return 0; } static void cnss_sdio_wlan_removed(struct sdio_func *func) { + struct cnss_sdio_info *info; + if (!cnss_pdata) return; - cnss_pdata->cnss_sdio_info.func = NULL; - cnss_pdata->cnss_sdio_info.id = NULL; + info = &cnss_pdata->cnss_sdio_info; + + info->host = NULL; + info->card = NULL; + info->func = NULL; + info->id = NULL; } #if defined(CONFIG_PM) @@ -577,6 +673,8 @@ static int cnss_sdio_wlan_suspend(struct device *dev) { struct cnss_sdio_wlan_driver *wdrv; struct cnss_sdio_bus_bandwidth *bus_bandwidth; + struct sdio_func *func; + int error = 0; if (!cnss_pdata) @@ -588,11 +686,13 @@ static int cnss_sdio_wlan_suspend(struct device *dev) bus_bandwidth->bus_client, CNSS_BUS_WIDTH_NONE); } + func = cnss_pdata->cnss_sdio_info.func; wdrv = cnss_pdata->cnss_sdio_info.wdrv; if (!wdrv) { /* This can happen when no wlan driver loaded (no register to * platform driver). */ + sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER); pr_debug("wlan driver not registered\n"); return 0; } @@ -692,29 +792,49 @@ EXPORT_SYMBOL(cnss_sdio_configure_spdt); int cnss_sdio_wlan_register_driver(struct cnss_sdio_wlan_driver *driver) { struct cnss_sdio_info *cnss_info; - int error = 0; + struct device *dev; + int error = -EINVAL; if (!cnss_pdata) return -ENODEV; cnss_info = &cnss_pdata->cnss_sdio_info; + dev = cnss_info->dev; + if (cnss_info->wdrv) pr_debug("%s:wdrv already exists wdrv(%p)\n", __func__, cnss_info->wdrv); + cnss_info->wdrv = driver; + + if (!driver) + return error; + + error = cnss_get_hw_resources(dev); + if (error) { + pr_err("%s: Failed to restore power err:%d\n", __func__, error); + return error; + } + error = cnss_set_pinctrl_state(cnss_pdata, PINCTRL_ACTIVE); if (error) { pr_err("%s: Fail to set pinctrl to active state\n", __func__); - return -EFAULT; + goto put_hw; } - cnss_info->wdrv = driver; - if (driver->probe) { - error = driver->probe(cnss_info->func, cnss_info->id); - if (error) - pr_err("%s: wlan probe failed error=%d\n", __func__, - error); + error = driver->probe ? driver->probe(cnss_info->func, + cnss_info->id) : error; + if (error) { + pr_err("%s: wlan probe failed error=%d\n", __func__, error); + goto pinctrl_sleep; } + + return error; + +pinctrl_sleep: + cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); +put_hw: + cnss_put_hw_resources(dev); return error; } EXPORT_SYMBOL(cnss_sdio_wlan_register_driver); @@ -746,10 +866,17 @@ cnss_sdio_wlan_unregister_driver(struct cnss_sdio_wlan_driver *driver) pr_err("%s: driver not registered\n", __func__); return; } - if (cnss_info->wdrv->remove) - cnss_info->wdrv->remove(cnss_info->func); + + if (!driver) + return; + + if (!driver->remove) + return; + + driver->remove(cnss_info->func); cnss_info->wdrv = NULL; cnss_set_pinctrl_state(cnss_pdata, PINCTRL_SLEEP); + cnss_put_hw_resources(cnss_info->dev); } EXPORT_SYMBOL(cnss_sdio_wlan_unregister_driver); @@ -1051,6 +1178,8 @@ static int cnss_sdio_init_bus_bandwidth(void) static int cnss_sdio_probe(struct platform_device *pdev) { int error; + struct device *dev = &pdev->dev; + struct cnss_sdio_info *info; if (pdev->dev.of_node) { cnss_pdata = devm_kzalloc( @@ -1065,6 +1194,7 @@ static int cnss_sdio_probe(struct platform_device *pdev) return -EINVAL; cnss_pdata->pdev = pdev; + info = &cnss_pdata->cnss_sdio_info; error = cnss_sdio_pinctrl_init(cnss_pdata, pdev); if (error) { @@ -1103,6 +1233,9 @@ static int cnss_sdio_probe(struct platform_device *pdev) } } + info->skip_wlan_en_toggle = of_property_read_bool(dev->of_node, + "qcom,skip-wlan-en-toggle"); + error = cnss_sdio_wlan_init(); if (error) { dev_err(&pdev->dev, "cnss wlan init failed error=%d\n", error); @@ -1152,15 +1285,20 @@ err_wlan_enable_regulator: static int cnss_sdio_remove(struct platform_device *pdev) { + struct cnss_sdio_info *info; + if (!cnss_pdata) return -ENODEV; + info = &cnss_pdata->cnss_sdio_info; + cnss_sdio_deinit_bus_bandwidth(); cnss_sdio_wlan_exit(); cnss_subsys_exit(); cnss_ramdump_cleanup(); + cnss_put_hw_resources(info->dev); cnss_sdio_release_resource(); - + cnss_pdata = NULL; return 0; } |
