diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c index 3573282beeef..d29fd5f3adcd 100644 --- a/drivers/soc/qcom/icnss.c +++ b/drivers/soc/qcom/icnss.c @@ -118,6 +118,7 @@ struct icnss_driver_event { enum cnss_driver_state { ICNSS_WLFW_QMI_CONNECTED, + ICNSS_POWER_ON, ICNSS_FW_READY, ICNSS_DRIVER_PROBED, ICNSS_FW_TEST_MODE, @@ -171,6 +172,7 @@ static struct icnss_data { icnss_mem_region[QMI_WLFW_MAX_NUM_MEMORY_REGIONS_V01]; bool skip_qmi; struct dentry *root_dentry; + spinlock_t on_off_lock; } *penv; static char *icnss_driver_event_to_str(enum icnss_driver_event_type type) @@ -391,33 +393,85 @@ static void icnss_hw_reset(struct icnss_data *pdata) static int icnss_hw_power_on(struct icnss_data *pdata) { int ret = 0; + unsigned long flags; icnss_pr_dbg("Power on: state: 0x%lx\n", pdata->state); + spin_lock_irqsave(&pdata->on_off_lock, flags); + if (test_bit(ICNSS_POWER_ON, &pdata->state)) { + spin_unlock_irqrestore(&pdata->on_off_lock, flags); + return ret; + } + set_bit(ICNSS_POWER_ON, &pdata->state); + spin_unlock_irqrestore(&pdata->on_off_lock, flags); + ret = icnss_vreg_set(VREG_ON); if (ret) goto out; icnss_hw_release_reset(pdata); + + return ret; out: + clear_bit(ICNSS_POWER_ON, &pdata->state); return ret; } static int icnss_hw_power_off(struct icnss_data *pdata) { int ret = 0; + unsigned long flags; icnss_pr_dbg("Power off: 0x%lx\n", pdata->state); + spin_lock_irqsave(&pdata->on_off_lock, flags); + if (!test_bit(ICNSS_POWER_ON, &pdata->state)) { + spin_unlock_irqrestore(&pdata->on_off_lock, flags); + return ret; + } + clear_bit(ICNSS_POWER_ON, &pdata->state); + spin_unlock_irqrestore(&pdata->on_off_lock, flags); + icnss_hw_reset(pdata); ret = icnss_vreg_set(VREG_OFF); if (ret) goto out; + + return ret; out: + set_bit(ICNSS_POWER_ON, &pdata->state); return ret; } +int icnss_power_on(struct device *dev) +{ + struct icnss_data *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %p, data %p\n", + dev, priv); + return -EINVAL; + } + + return icnss_hw_power_on(priv); +} +EXPORT_SYMBOL(icnss_power_on); + +int icnss_power_off(struct device *dev) +{ + struct icnss_data *priv = dev_get_drvdata(dev); + + if (!priv) { + icnss_pr_err("Invalid drvdata: dev %p, data %p\n", + dev, priv); + return -EINVAL; + } + + return icnss_hw_power_off(priv); +} +EXPORT_SYMBOL(icnss_power_off); + static int wlfw_msa_mem_info_send_sync_msg(void) { int ret = 0; @@ -1813,6 +1867,8 @@ static int icnss_probe(struct platform_device *pdev) if (!penv) return -ENOMEM; + dev_set_drvdata(dev, penv); + penv->pdev = pdev; ret = icnss_dt_parse_vreg_info(dev, &penv->vreg_info, "vdd-io"); @@ -1918,6 +1974,7 @@ static int icnss_probe(struct platform_device *pdev) "qcom,skip-qmi"); spin_lock_init(&penv->event_lock); + spin_lock_init(&penv->on_off_lock); penv->event_wq = alloc_workqueue("icnss_driver_event", 0, 0); if (!penv->event_wq) { @@ -1964,6 +2021,7 @@ unmap_mem_base: release_regulator: icnss_release_resources(); out: + dev_set_drvdata(dev, NULL); devm_kfree(&pdev->dev, penv); penv = NULL; return ret; @@ -2000,6 +2058,8 @@ static int icnss_remove(struct platform_device *pdev) icnss_release_resources(); + dev_set_drvdata(&pdev->dev, NULL); + return 0; } diff --git a/include/soc/qcom/icnss.h b/include/soc/qcom/icnss.h index 49bfd4fb5c9c..6c3ec33cc613 100644 --- a/include/soc/qcom/icnss.h +++ b/include/soc/qcom/icnss.h @@ -98,5 +98,7 @@ extern int icnss_ce_request_irq(unsigned int ce_id, extern int icnss_get_ce_id(int irq); extern int icnss_set_fw_debug_mode(bool enable_fw_log); extern int icnss_get_irq(int ce_id); +extern int icnss_power_on(struct device *dev); +extern int icnss_power_off(struct device *dev); #endif /* _ICNSS_WLAN_H_ */