From 2b0e0547961e0fd9dc9fa3d0a2f2bc8bab6dca71 Mon Sep 17 00:00:00 2001 From: Govind Singh Date: Sun, 17 Sep 2017 18:49:52 +0530 Subject: [PATCH 1/2] ARM: dts: msm: update wcn3990 wlan device node for APQ8098 Add clock and regulator nodes for WCN3990. During recovery, there are cases where in WLAN driver running on APPS can access CE register causing exceptions because of hardware in reset state. Fix the issue by not resetting the hardware in WLAN firmware during recovery but halt the CE and MAC and reset the hardware only when reinitialization starts. For this to work, wcn3990 ath10k driver needs to keep the clock and regulator votes so that WLAN hardware will not go into weird state because of not resetting it but cutting the resources. Change-Id: Iaaa793956bed687e4d76acda93f7fa467773f013 Signed-off-by: Govind Singh --- arch/arm/boot/dts/qcom/msm8998.dtsi | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/arch/arm/boot/dts/qcom/msm8998.dtsi b/arch/arm/boot/dts/qcom/msm8998.dtsi index eafa6b841c17..76e3282d327e 100644 --- a/arch/arm/boot/dts/qcom/msm8998.dtsi +++ b/arch/arm/boot/dts/qcom/msm8998.dtsi @@ -3106,6 +3106,8 @@ compatible = "qcom,wcn3990-wifi"; reg = <0x18800000 0x800000>; reg-names = "membase"; + clocks = <&clock_gcc clk_rf_clk2_pin>; + clock-names = "cxo_ref_clk_pin"; interrupts = <0 413 0 /* CE0 */ >, <0 414 0 /* CE1 */ >, @@ -3119,6 +3121,12 @@ <0 423 0 /* CE9 */ >, <0 424 0 /* CE10 */ >, <0 425 0 /* CE11 */ >; + vdd-0.8-cx-mx-supply = <&pm8998_l5>; + vdd-1.8-xo-supply = <&pm8998_l7_pin_ctrl>; + vdd-1.3-rfa-supply = <&pm8998_l17_pin_ctrl>; + vdd-3.3-ch0-supply = <&pm8998_l25_pin_ctrl>; + qcom,vdd-0.8-cx-mx-config = <800000 800000>; + qcom,vdd-3.3-ch0-config = <3104000 3312000>; }; qcom,icnss@18800000 { From 7235e034df96f8d2ccfd965d9940e1a80fe38f50 Mon Sep 17 00:00:00 2001 From: Govind Singh Date: Tue, 29 Aug 2017 20:27:11 +0530 Subject: [PATCH 2/2] ath10k: Vote for hardware resources for WCN3990 During recovery, there are cases where in WLAN driver running on APPS can access CE register causing exceptions because of hardware in reset state. Fix the issue by not resetting the hardware in WLAN firmware during recovery but halt the CE and MAC and reset the hardware only when reinitialization starts. For this to work, wcn3990 ath10k driver needs to keep the clock and regulator votes so that WLAN hardware will not go into weird state because of not resetting it but cutting the resources. Change-Id: I765fd4aaa2fea6f41f4760564b8d1cb8c3d9651b Signed-off-by: Govind Singh --- .../net/wireless/qcom,wcn3990-wifi.txt | 17 + drivers/net/wireless/ath/ath10k/snoc.c | 355 +++++++++++++++++- drivers/net/wireless/ath/ath10k/snoc.h | 35 ++ 3 files changed, 404 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,wcn3990-wifi.txt b/Documentation/devicetree/bindings/net/wireless/qcom,wcn3990-wifi.txt index acc850773210..c1a8d1bd697d 100644 --- a/Documentation/devicetree/bindings/net/wireless/qcom,wcn3990-wifi.txt +++ b/Documentation/devicetree/bindings/net/wireless/qcom,wcn3990-wifi.txt @@ -11,13 +11,24 @@ Required properties: - compatible: "qcom,wcn3990-wifi"; - reg: Memory regions defined as starting address and size - reg-names: Names of the memory regions defined in reg entry + - clocks: List of clock phandles + - clock-names: List of clock names corresponding to the "clocks" property - interrupts: Copy engine interrupt table +Optional properties: + - -supply: phandle to the regulator device tree node + optional "supply-name" is "vdd-0.8-cx-mx". + - qcom,-config: Specifies voltage levels for supply. Should be + specified in pairs (min, max), units uV. There can + be optional load in uA and Regulator settle delay in + uS. Example: msm_ath10k_wlan: qcom,msm_ath10k_wlan@18800000 { compatible = "qcom,wcn3990-wifi"; reg = <0x18800000 0x800000>; reg-names = "membase"; + clocks = <&clock_gcc clk_aggre2_noc_clk>; + clock-names = "smmu_aggre2_noc_clk"; interrupts = <0 130 0 /* CE0 */ >, <0 131 0 /* CE1 */ >, @@ -31,4 +42,10 @@ Example: <0 139 0 /* CE9 */ >, <0 140 0 /* CE10 */ >, <0 141 0 /* CE11 */ >; + vdd-0.8-cx-mx-supply = <&pm8998_l5>; + vdd-1.8-xo-supply = <&pm8998_l7_pin_ctrl>; + vdd-1.3-rfa-supply = <&pm8998_l17_pin_ctrl>; + vdd-3.3-ch0-supply = <&pm8998_l25_pin_ctrl>; + qcom,vdd-0.8-cx-mx-config = <800000 800000>; + qcom,vdd-3.3-ch0-config = <3104000 3312000>; }; diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c index f5aa88a76f17..c42d7eebf465 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.c +++ b/drivers/net/wireless/ath/ath10k/snoc.c @@ -27,6 +27,8 @@ #include "qmi.h" #include #include +#include +#include #define WCN3990_MAX_IRQ 12 @@ -48,6 +50,7 @@ const char *ce_name[WCN3990_MAX_IRQ] = { #define ATH10K_SNOC_TARGET_WAIT 3000 #define ATH10K_SNOC_NUM_WARM_RESET_ATTEMPTS 3 #define SNOC_HIF_POWER_DOWN_DELAY 30 +#define ATH10K_MAX_PROP_SIZE 32 static void ath10k_snoc_buffer_cleanup(struct ath10k *ar); static int ath10k_snoc_request_irq(struct ath10k *ar); @@ -1248,6 +1251,326 @@ int ath10k_snoc_pm_notifier(struct notifier_block *nb, return NOTIFY_DONE; } +static int ath10k_get_vreg_info(struct ath10k *ar, struct device *dev, + struct ath10k_wcn3990_vreg_info *vreg_info) +{ + int ret = 0; + char prop_name[ATH10K_MAX_PROP_SIZE]; + struct regulator *reg; + const __be32 *prop; + int len = 0; + int i; + + reg = devm_regulator_get_optional(dev, vreg_info->name); + if (PTR_ERR(reg) == -EPROBE_DEFER) { + ath10k_err(ar, "EPROBE_DEFER for regulator: %s\n", + vreg_info->name); + ret = PTR_ERR(reg); + goto out; + } + + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + + if (vreg_info->required) { + ath10k_err(ar, "Regulator %s doesn't exist: %d\n", + vreg_info->name, ret); + goto out; + } else { + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "Optional regulator %s doesn't exist: %d\n", + vreg_info->name, ret); + goto done; + } + } + + vreg_info->reg = reg; + + snprintf(prop_name, ATH10K_MAX_PROP_SIZE, + "qcom,%s-config", vreg_info->name); + + prop = of_get_property(dev->of_node, prop_name, &len); + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Got regulator cfg,prop: %s, len: %d\n", + prop_name, len); + + if (!prop || len < (2 * sizeof(__be32))) { + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Property %s %s\n", prop_name, + prop ? "invalid format" : "doesn't exist"); + goto done; + } + + for (i = 0; (i * sizeof(__be32)) < len; i++) { + switch (i) { + case 0: + vreg_info->min_v = be32_to_cpup(&prop[0]); + break; + case 1: + vreg_info->max_v = be32_to_cpup(&prop[1]); + break; + case 2: + vreg_info->load_ua = be32_to_cpup(&prop[2]); + break; + case 3: + vreg_info->settle_delay = be32_to_cpup(&prop[3]); + break; + default: + ath10k_dbg(ar, ATH10K_DBG_SNOC, "%s, ignoring val %d\n", + prop_name, i); + break; + } + } + +done: + ath10k_dbg(ar, ATH10K_DBG_SNOC, + "vreg: %s, min_v: %u, max_v: %u, load: %u, delay: %lu\n", + vreg_info->name, vreg_info->min_v, vreg_info->max_v, + vreg_info->load_ua, vreg_info->settle_delay); + + return 0; + +out: + return ret; +} + +static int ath10k_get_clk_info(struct ath10k *ar, struct device *dev, + struct ath10k_wcn3990_clk_info *clk_info) +{ + struct clk *handle; + int ret = 0; + + handle = devm_clk_get(dev, clk_info->name); + if (IS_ERR(handle)) { + ret = PTR_ERR(handle); + if (clk_info->required) { + ath10k_err(ar, "Clock %s isn't available: %d\n", + clk_info->name, ret); + goto out; + } else { + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Ignoring clk %s: %d\n", + clk_info->name, + ret); + ret = 0; + goto out; + } + } + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock: %s, freq: %u\n", + clk_info->name, clk_info->freq); + + clk_info->handle = handle; +out: + return ret; +} + +static int ath10k_wcn3990_vreg_on(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_wcn3990_vreg_info *vreg_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_VREG_INFO_SIZE; i++) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Regulator %s being enabled\n", + vreg_info->name); + + ret = regulator_set_voltage(vreg_info->reg, vreg_info->min_v, + vreg_info->max_v); + if (ret) { + ath10k_err(ar, + "vreg %s, set failed:min:%u,max:%u,ret: %d\n", + vreg_info->name, vreg_info->min_v, + vreg_info->max_v, ret); + break; + } + + if (vreg_info->load_ua) { + ret = regulator_set_load(vreg_info->reg, + vreg_info->load_ua); + if (ret < 0) { + ath10k_err(ar, + "Reg %s, can't set load:%u,ret: %d\n", + vreg_info->name, + vreg_info->load_ua, ret); + break; + } + } + + ret = regulator_enable(vreg_info->reg); + if (ret) { + ath10k_err(ar, "Regulator %s, can't enable: %d\n", + vreg_info->name, ret); + break; + } + + if (vreg_info->settle_delay) + udelay(vreg_info->settle_delay); + } + + if (!ret) + return 0; + + for (; i >= 0; i--) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + regulator_disable(vreg_info->reg); + regulator_set_load(vreg_info->reg, 0); + regulator_set_voltage(vreg_info->reg, 0, vreg_info->max_v); + } + + return ret; +} + +static int ath10k_wcn3990_vreg_off(struct ath10k *ar) +{ + int ret = 0; + struct ath10k_wcn3990_vreg_info *vreg_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = ATH10K_WCN3990_VREG_INFO_SIZE - 1; i >= 0; i--) { + vreg_info = &ar_snoc->vreg[i]; + + if (!vreg_info->reg) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Regulator %s being disabled\n", + vreg_info->name); + + ret = regulator_disable(vreg_info->reg); + if (ret) + ath10k_err(ar, "Regulator %s, can't disable: %d\n", + vreg_info->name, ret); + + ret = regulator_set_load(vreg_info->reg, 0); + if (ret < 0) + ath10k_err(ar, "Regulator %s, can't set load: %d\n", + vreg_info->name, ret); + + ret = regulator_set_voltage(vreg_info->reg, 0, + vreg_info->max_v); + if (ret) + ath10k_err(ar, "Regulator %s, can't set voltage: %d\n", + vreg_info->name, ret); + } + + return ret; +} + +static int ath10k_wcn3990_clk_init(struct ath10k *ar) +{ + struct ath10k_wcn3990_clk_info *clk_info; + int i; + int ret = 0; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock %s being enabled\n", + clk_info->name); + + if (clk_info->freq) { + ret = clk_set_rate(clk_info->handle, clk_info->freq); + + if (ret) { + ath10k_err(ar, "Clk %s,set err: %u,ret: %d\n", + clk_info->name, clk_info->freq, + ret); + break; + } + } + + ret = clk_prepare_enable(clk_info->handle); + if (ret) { + ath10k_err(ar, "Clock %s, can't enable: %d\n", + clk_info->name, ret); + break; + } + } + + if (ret == 0) + return 0; + + for (; i >= 0; i--) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + clk_disable_unprepare(clk_info->handle); + } + + return ret; +} + +static int ath10k_wcn3990_clk_deinit(struct ath10k *ar) +{ + struct ath10k_wcn3990_clk_info *clk_info; + int i; + struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar); + + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + clk_info = &ar_snoc->clk[i]; + + if (!clk_info->handle) + continue; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "Clock %s being disabled\n", + clk_info->name); + + clk_disable_unprepare(clk_info->handle); + } + + return 0; +} + +static int ath10k_hw_power_on(struct ath10k *ar) +{ + int ret = 0; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HW Power on\n"); + + ret = ath10k_wcn3990_vreg_on(ar); + if (ret) + goto out; + + ret = ath10k_wcn3990_clk_init(ar); + if (ret) + goto vreg_off; + + return ret; + +vreg_off: + ath10k_wcn3990_vreg_off(ar); +out: + return ret; +} + +static int ath10k_hw_power_off(struct ath10k *ar) +{ + int ret = 0; + + ath10k_dbg(ar, ATH10K_DBG_SNOC, "HW Power off\n"); + + ath10k_wcn3990_clk_deinit(ar); + + ret = ath10k_wcn3990_vreg_off(ar); + + return ret; +} + static const struct ath10k_hif_ops ath10k_snoc_hif_ops = { .tx_sg = ath10k_snoc_hif_tx_sg, .start = ath10k_snoc_hif_start, @@ -1275,6 +1598,7 @@ static int ath10k_snoc_probe(struct platform_device *pdev) enum ath10k_hw_rev hw_rev; struct device *dev; u32 chip_id; + u32 i; dev = &pdev->dev; hw_rev = ATH10K_HW_WCN3990; @@ -1308,22 +1632,43 @@ static int ath10k_snoc_probe(struct platform_device *pdev) setup_timer(&ar_snoc->rx_post_retry, ath10k_snoc_rx_replenish_retry, (unsigned long)ar); + memcpy(ar_snoc->vreg, vreg_cfg, sizeof(vreg_cfg)); + for (i = 0; i < ATH10K_WCN3990_VREG_INFO_SIZE; i++) { + ret = ath10k_get_vreg_info(ar, dev, &ar_snoc->vreg[i]); + if (ret) + goto err_core_destroy; + } + + memcpy(ar_snoc->clk, clk_cfg, sizeof(clk_cfg)); + for (i = 0; i < ATH10K_WCN3990_CLK_INFO_SIZE; i++) { + ret = ath10k_get_clk_info(ar, dev, &ar_snoc->clk[i]); + if (ret) + goto err_core_destroy; + } + + ret = ath10k_hw_power_on(ar); + if (ret) { + ath10k_err(ar, "failed to power on device: %d\n", ret); + goto err_stop_qmi_service; + } + ret = ath10k_snoc_claim(ar); if (ret) { ath10k_err(ar, "failed to claim device: %d\n", ret); - goto err_stop_qmi_service; + goto err_hw_power_off; } + ret = ath10k_snoc_bus_configure(ar); if (ret) { ath10k_err(ar, "failed to configure bus: %d\n", ret); - goto err_stop_qmi_service; + goto err_hw_power_off; } ret = ath10k_snoc_alloc_pipes(ar); if (ret) { ath10k_err(ar, "failed to allocate copy engine pipes: %d\n", ret); - goto err_stop_qmi_service; + goto err_hw_power_off; } netif_napi_add(&ar->napi_dev, &ar->napi, ath10k_snoc_napi_poll, @@ -1359,6 +1704,9 @@ err_free_irq: err_free_pipes: ath10k_snoc_free_pipes(ar); +err_hw_power_off: + ath10k_hw_power_off(ar); + err_stop_qmi_service: ath10k_snoc_stop_qmi_service(ar); @@ -1389,6 +1737,7 @@ static int ath10k_snoc_remove(struct platform_device *pdev) ath10k_snoc_release_resource(ar); ath10k_snoc_free_pipes(ar); ath10k_snoc_stop_qmi_service(ar); + ath10k_hw_power_off(ar); ath10k_core_destroy(ar); return 0; diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h index d6e05ba18cb8..a02cb2ad928e 100644 --- a/drivers/net/wireless/ath/ath10k/snoc.h +++ b/drivers/net/wireless/ath/ath10k/snoc.h @@ -17,6 +17,7 @@ #include "ce.h" #include "pci.h" #include "qmi.h" +#include #include #define ATH10K_SNOC_RX_POST_RETRY_MS 50 #define CE_POLL_PIPE 4 @@ -112,6 +113,38 @@ struct ath10k_snoc_ce_irq { u32 irq_line; }; +struct ath10k_wcn3990_vreg_info { + struct regulator *reg; + const char *name; + u32 min_v; + u32 max_v; + u32 load_ua; + unsigned long settle_delay; + bool required; +}; + +struct ath10k_wcn3990_clk_info { + struct clk *handle; + const char *name; + u32 freq; + bool required; +}; + +static struct ath10k_wcn3990_vreg_info vreg_cfg[] = { + {NULL, "vdd-0.8-cx-mx", 800000, 800000, 0, 0, false}, + {NULL, "vdd-1.8-xo", 1800000, 1800000, 0, 0, false}, + {NULL, "vdd-1.3-rfa", 1304000, 1304000, 0, 0, false}, + {NULL, "vdd-3.3-ch0", 3312000, 3312000, 0, 0, false}, +}; + +#define ATH10K_WCN3990_VREG_INFO_SIZE ARRAY_SIZE(vreg_cfg) + +static struct ath10k_wcn3990_clk_info clk_cfg[] = { + {NULL, "cxo_ref_clk_pin", 0, false}, +}; + +#define ATH10K_WCN3990_CLK_INFO_SIZE ARRAY_SIZE(clk_cfg) + /* struct ath10k_snoc: SNOC info struct * @dev: device structure * @ar:ath10k base structure @@ -157,6 +190,8 @@ struct ath10k_snoc { atomic_t fw_crashed; atomic_t pm_ops_inprogress; struct ath10k_snoc_qmi_config qmi_cfg; + struct ath10k_wcn3990_vreg_info vreg[ATH10K_WCN3990_VREG_INFO_SIZE]; + struct ath10k_wcn3990_clk_info clk[ATH10K_WCN3990_CLK_INFO_SIZE]; }; struct ath10k_event_pd_down_data {