| /* |
| * Copyright (c) 2013-2016 The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /** |
| * DOC: if_ahb_reset.c |
| * |
| * c file for ahb ipq4019 specific implementations. |
| */ |
| |
| #include "hif.h" |
| #include "hif_main.h" |
| #include "hif_debug.h" |
| #include "hif_io32.h" |
| #include "ce_main.h" |
| #include "ce_tasklet.h" |
| #include "ahb_api.h" |
| #include "if_ahb.h" |
| |
| #include <linux/clk.h> |
| #include <linux/reset.h> |
| #include <linux/of_address.h> |
| #include <linux/platform_device.h> |
| #include <linux/device.h> |
| #include <linux/of.h> |
| |
| /** |
| * clk_enable_disable() - Enable/disable clock |
| * @dev : pointer to device structure |
| * @str : clock name |
| * @enable : should be true, if the clock needs to be enabled |
| * should be false, if the clock needs to be enabled |
| * |
| * This is a helper function for hif_ahb_clk_enable_disable to enable |
| * disable clocks. |
| * clk_prepare_enable will enable the clock |
| * clk_disable_unprepare will disable the clock |
| * |
| * Return: zero on success, non-zero incase of error. |
| */ |
| |
| static int clk_enable_disable(struct device *dev, const char *str, int enable) |
| { |
| struct clk *clk_t = NULL; |
| int ret; |
| |
| clk_t = clk_get(dev, str); |
| if (IS_ERR(clk_t)) { |
| HIF_INFO("%s: Failed to get %s clk %ld\n", |
| __func__, str, PTR_ERR(clk_t)); |
| return -EFAULT; |
| } |
| if (TRUE == enable) { |
| /* Prepare and Enable clk */ |
| ret = clk_prepare_enable(clk_t); |
| if (ret) { |
| HIF_INFO("%s: err enabling clk %s , error:%d\n", |
| __func__, str, ret); |
| return ret; |
| } |
| } else { |
| /* Disable and unprepare clk */ |
| clk_disable_unprepare(clk_t); |
| } |
| return 0; |
| } |
| |
| |
| /** |
| * hif_ahb_clk_enable_disable() - Enable/disable ahb clock |
| * @dev : pointer to device structure |
| * @enable : should be true, if the clock needs to be enabled |
| * should be false, if the clock needs to be enabled |
| * |
| * This functions helps to enable/disable all the necesasary clocks |
| * for bus access. |
| * |
| * Return: zero on success, non-zero incase of error |
| */ |
| int hif_ahb_clk_enable_disable(struct device *dev, int enable) |
| { |
| int ret; |
| |
| ret = clk_enable_disable(dev, "wifi_wcss_cmd", enable); |
| if (ret) |
| return ret; |
| ret = clk_enable_disable(dev, "wifi_wcss_ref", enable); |
| if (ret) |
| return ret; |
| ret = clk_enable_disable(dev, "wifi_wcss_rtc", enable); |
| if (ret) |
| return ret; |
| return 0; |
| } |
| |
| /** |
| * hif_enable_radio() - Enable the target radio. |
| * @sc : pointer to the hif context |
| * |
| * This function helps to release the target from reset state |
| * |
| * Return : zero on success, non-zero incase of error. |
| */ |
| int hif_ahb_enable_radio(struct hif_pci_softc *sc, |
| struct platform_device *pdev, |
| const struct platform_device_id *id) |
| { |
| struct reset_control *reset_ctl = NULL; |
| uint32_t msi_addr, msi_base, wifi_core_id; |
| struct hif_softc *scn = HIF_GET_SOFTC(sc); |
| struct device_node *dev_node = pdev->dev.of_node; |
| bool msienable = false; |
| int ret = 0; |
| |
| ret = of_property_read_u32(dev_node, "qca,msi_addr", &msi_addr); |
| if (ret) { |
| HIF_INFO("%s: Unable to get msi_addr - error:%d\n", |
| __func__, ret); |
| return -EIO; |
| } |
| ret = of_property_read_u32(dev_node, "qca,msi_base", &msi_base); |
| if (ret) { |
| HIF_INFO("%s: Unable to get msi_base - error:%d\n", |
| __func__, ret); |
| return -EIO; |
| } |
| ret = of_property_read_u32(dev_node, "core-id", &wifi_core_id); |
| if (ret) { |
| HIF_INFO("%s: Unable to get core-id - error:%d\n", |
| __func__, ret); |
| return -EIO; |
| } |
| |
| /* Program the above values into Wifi scratch regists */ |
| if (msienable) { |
| hif_write32_mb(sc->mem + FW_AXI_MSI_ADDR, msi_addr); |
| hif_write32_mb(sc->mem + FW_AXI_MSI_DATA, msi_base); |
| } |
| |
| /* TBD: Temporary changes. Frequency should be |
| retrieved through clk_xxx once kernel GCC driver is available */ |
| { |
| void __iomem *mem_gcc; |
| uint32_t clk_sel; |
| uint32_t gcc_fepll_pll_div; |
| uint32_t wifi_cpu_freq[4] = {266700000, 250000000, 222200000, |
| 200000000}; |
| uint32_t current_freq = 0; |
| |
| /* Enable WIFI clock input */ |
| if (scn->target_info.target_type == TARGET_TYPE_IPQ4019) { |
| ret = hif_ahb_clk_enable_disable(&pdev->dev, 1); |
| if (ret) { |
| HIF_INFO("%s:Error while enabling clock :%d\n", |
| __func__, ret); |
| return ret; |
| } |
| } |
| |
| mem_gcc = ioremap_nocache(GCC_BASE, GCC_SIZE); |
| if (IS_ERR(mem_gcc)) { |
| HIF_INFO("%s: GCC ioremap failed\n", __func__); |
| return PTR_ERR(mem_gcc); |
| } |
| gcc_fepll_pll_div = hif_read32_mb(mem_gcc + GCC_FEPLL_PLL_DIV); |
| clk_sel = (wifi_core_id == 0) ? ((gcc_fepll_pll_div & |
| GCC_FEPLL_PLL_CLK_WIFI_0_SEL_MASK) >> |
| GCC_FEPLL_PLL_CLK_WIFI_0_SEL_SHIFT) : |
| ((gcc_fepll_pll_div & GCC_FEPLL_PLL_CLK_WIFI_1_SEL_MASK) |
| >> GCC_FEPLL_PLL_CLK_WIFI_1_SEL_SHIFT); |
| current_freq = wifi_cpu_freq[clk_sel]; |
| |
| HIF_INFO("Wifi%d CPU frequency %u\n", wifi_core_id, |
| current_freq); |
| hif_write32_mb(sc->mem + FW_CPU_PLL_CONFIG, gcc_fepll_pll_div); |
| iounmap(mem_gcc); |
| } |
| |
| /* De-assert radio cold reset */ |
| reset_ctl = reset_control_get(&pdev->dev, "wifi_radio_cold"); |
| if (IS_ERR(reset_ctl)) { |
| HIF_INFO("%s: Failed to get radio cold reset control\n", |
| __func__); |
| ret = PTR_ERR(reset_ctl); |
| goto err_reset; |
| } |
| reset_control_deassert(reset_ctl); |
| reset_control_put(reset_ctl); |
| |
| /* De-assert radio warm reset */ |
| reset_ctl = reset_control_get(&pdev->dev, "wifi_radio_warm"); |
| if (IS_ERR(reset_ctl)) { |
| HIF_INFO("%s: Failed to get radio warm reset control\n", |
| __func__); |
| ret = PTR_ERR(reset_ctl); |
| goto err_reset; |
| } |
| reset_control_deassert(reset_ctl); |
| reset_control_put(reset_ctl); |
| |
| /* De-assert radio srif reset */ |
| reset_ctl = reset_control_get(&pdev->dev, "wifi_radio_srif"); |
| if (IS_ERR(reset_ctl)) { |
| HIF_INFO("%s: Failed to get radio srif reset control\n", |
| __func__); |
| ret = PTR_ERR(reset_ctl); |
| goto err_reset; |
| } |
| reset_control_deassert(reset_ctl); |
| reset_control_put(reset_ctl); |
| |
| /* De-assert target CPU reset */ |
| reset_ctl = reset_control_get(&pdev->dev, "wifi_cpu_init"); |
| if (IS_ERR(reset_ctl)) { |
| HIF_INFO("%s: Failed to get cpu init reset control", __func__); |
| ret = PTR_ERR(reset_ctl); |
| goto err_reset; |
| } |
| reset_control_deassert(reset_ctl); |
| reset_control_put(reset_ctl); |
| |
| return 0; |
| |
| err_reset: |
| return -EIO; |
| |
| } |
| |
| /* "wifi_core_warm" is the other reset type */ |
| #define AHB_RESET_TYPE "wifi_core_cold" |
| |
| /** |
| * hif_ahb_device_reset() - Disable the radio and held the radio is reset state. |
| * @scn : pointer to the hif context |
| * |
| * This function will hold the target in reset state. |
| * Will be called while unload the driver or any graceful unload path. |
| * |
| * Return : n/a. |
| */ |
| void hif_ahb_device_reset(struct hif_softc *scn) |
| { |
| struct reset_control *resetctl = NULL; |
| struct reset_control *core_resetctl = NULL; |
| struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn); |
| struct platform_device *pdev = (struct platform_device *)(sc->pdev); |
| uint32_t glb_cfg_offset; |
| uint32_t haltreq_offset; |
| uint32_t haltack_offset; |
| void __iomem *mem_tcsr; |
| uint32_t wifi_core_id; |
| uint32_t reg_value; |
| int wait_limit = ATH_AHB_RESET_WAIT_MAX; |
| |
| |
| wifi_core_id = hif_read32_mb(sc->mem + WLAN_SUBSYSTEM_CORE_ID_ADDRESS); |
| glb_cfg_offset = (wifi_core_id == 0) ? TCSR_WIFI0_GLB_CFG : |
| TCSR_WIFI1_GLB_CFG; |
| haltreq_offset = (wifi_core_id == 0) ? TCSR_WCSS0_HALTREQ : |
| TCSR_WCSS1_HALTREQ; |
| haltack_offset = (wifi_core_id == 0) ? TCSR_WCSS0_HALTACK : |
| TCSR_WCSS1_HALTACK; |
| |
| mem_tcsr = ioremap_nocache(TCSR_BASE, TCSR_SIZE); |
| if (IS_ERR(mem_tcsr)) { |
| HIF_INFO("%s: TCSR ioremap failed\n", __func__); |
| return; |
| } |
| reg_value = hif_read32_mb(mem_tcsr + haltreq_offset); |
| hif_write32_mb(mem_tcsr + haltreq_offset, reg_value | 0x1); |
| /* Wait for halt ack before asserting reset */ |
| while (wait_limit) { |
| |
| if (hif_read32_mb(mem_tcsr + haltack_offset) & 0x1) |
| break; |
| |
| A_MDELAY(1); |
| wait_limit--; |
| } |
| |
| reg_value = hif_read32_mb(mem_tcsr + glb_cfg_offset); |
| hif_write32_mb(mem_tcsr + glb_cfg_offset, reg_value | (1 << 25)); |
| |
| core_resetctl = reset_control_get(&pdev->dev, AHB_RESET_TYPE); |
| if (IS_ERR(core_resetctl)) { |
| HIF_INFO("Failed to get wifi core cold reset control\n"); |
| return; |
| } |
| |
| /* Reset wifi core */ |
| reset_control_assert(core_resetctl); |
| |
| /* TBD: Check if we should also assert other bits (radio_cold, radio_ |
| warm, radio_srif, cpu_ini) */ |
| A_MDELAY(1); /* TBD: Get reqd delay from HW team */ |
| |
| /* Assert radio cold reset */ |
| resetctl = reset_control_get(&pdev->dev, "wifi_radio_cold"); |
| if (IS_ERR(resetctl)) { |
| HIF_INFO("%s: Failed to get radio cold reset control\n", |
| __func__); |
| return; |
| } |
| reset_control_assert(resetctl); |
| A_MDELAY(1); /* TBD: Get reqd delay from HW team */ |
| reset_control_put(resetctl); |
| |
| /* Assert radio warm reset */ |
| resetctl = reset_control_get(&pdev->dev, "wifi_radio_warm"); |
| if (IS_ERR(resetctl)) { |
| HIF_INFO("%s: Failed to get radio warm reset control\n", |
| __func__); |
| return; |
| } |
| reset_control_assert(resetctl); |
| A_MDELAY(1); /* TBD: Get reqd delay from HW team */ |
| reset_control_put(resetctl); |
| |
| /* Assert radio srif reset */ |
| resetctl = reset_control_get(&pdev->dev, "wifi_radio_srif"); |
| if (IS_ERR(resetctl)) { |
| HIF_INFO("%s: Failed to get radio srif reset control\n", |
| __func__); |
| return; |
| } |
| reset_control_assert(resetctl); |
| A_MDELAY(1); /* TBD: Get reqd delay from HW team */ |
| reset_control_put(resetctl); |
| |
| /* Assert target CPU reset */ |
| resetctl = reset_control_get(&pdev->dev, "wifi_cpu_init"); |
| if (IS_ERR(resetctl)) { |
| HIF_INFO("%s: Failed to get cpu init reset control", __func__); |
| return; |
| } |
| reset_control_assert(resetctl); |
| A_MDELAY(10); /* TBD: Get reqd delay from HW team */ |
| reset_control_put(resetctl); |
| |
| /* Clear gbl_cfg and haltreq before clearing Wifi core reset */ |
| reg_value = hif_read32_mb(mem_tcsr + haltreq_offset); |
| hif_write32_mb(mem_tcsr + haltreq_offset, reg_value & ~0x1); |
| reg_value = hif_read32_mb(mem_tcsr + glb_cfg_offset); |
| hif_write32_mb(mem_tcsr + glb_cfg_offset, reg_value & ~(1 << 25)); |
| |
| /* de-assert wifi core reset */ |
| reset_control_deassert(core_resetctl); |
| |
| A_MDELAY(1); /* TBD: Get reqd delay from HW team */ |
| |
| /* TBD: Check if we should de-assert other bits here */ |
| reset_control_put(core_resetctl); |
| iounmap(mem_tcsr); |
| HIF_INFO("Reset complete for wifi core id : %d\n", wifi_core_id); |
| } |
| |
| |
| |