diff --git a/hif/inc/regtable_pcie.h b/hif/inc/regtable_pcie.h index 753271b75d..c3ebb64996 100644 --- a/hif/inc/regtable_pcie.h +++ b/hif/inc/regtable_pcie.h @@ -364,7 +364,7 @@ /* PLL end */ #define FW_CPU_PLL_CONFIG \ - (sc->targetdef->d_FW_CPU_PLL_CONFIG) + (scn->targetdef->d_FW_CPU_PLL_CONFIG) #define WIFICMN_PCIE_BAR_REG_ADDRESS \ (sc->targetdef->d_WIFICMN_PCIE_BAR_REG_ADDRESS) diff --git a/hif/src/dispatcher/ahb_api.h b/hif/src/dispatcher/ahb_api.h new file mode 100644 index 0000000000..e53c17545b --- /dev/null +++ b/hif/src/dispatcher/ahb_api.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef __AHB_API_H +#define __AHB_API_H + +QDF_STATUS hif_ahb_open(struct hif_softc *hif_ctx, + enum qdf_bus_type bus_type); +void hif_ahb_close(struct hif_softc *hif_ctx); + + +void hif_ahb_disable_isr(struct hif_softc *hif_ctx); +void hif_ahb_nointrs(struct hif_softc *scn); +QDF_STATUS hif_ahb_enable_bus(struct hif_softc *ol_sc, + struct device *dev, void *bdev, + const hif_bus_id *bid, + enum hif_enable_type type); +void hif_ahb_disable_bus(struct hif_softc *scn); +int hif_ahb_bus_configure(struct hif_softc *scn); +void hif_ahb_irq_disable(struct hif_softc *scn, int ce_id); +void hif_ahb_irq_enable(struct hif_softc *scn, int ce_id); +int hif_ahb_dump_registers(struct hif_softc *scn); + +#endif diff --git a/hif/src/dispatcher/multibus.c b/hif/src/dispatcher/multibus.c index 4c2def66bb..d26de4228b 100644 --- a/hif/src/dispatcher/multibus.c +++ b/hif/src/dispatcher/multibus.c @@ -86,6 +86,8 @@ int hif_bus_get_context_size(enum qdf_bus_type bus_type) switch (bus_type) { case QDF_BUS_TYPE_PCI: return hif_pci_get_context_size(); + case QDF_BUS_TYPE_AHB: + return hif_ahb_get_context_size(); case QDF_BUS_TYPE_SNOC: return hif_snoc_get_context_size(); default: @@ -114,6 +116,9 @@ QDF_STATUS hif_bus_open(struct hif_softc *hif_sc, case QDF_BUS_TYPE_SNOC: status = hif_initialize_snoc_ops(&hif_sc->bus_ops); break; + case QDF_BUS_TYPE_AHB: + status = hif_initialize_ahb_ops(&hif_sc->bus_ops); + break; default: status = QDF_STATUS_E_NOSUPPORT; break; diff --git a/hif/src/dispatcher/multibus.h b/hif/src/dispatcher/multibus.h index afa36f9142..a331206846 100644 --- a/hif/src/dispatcher/multibus.h +++ b/hif/src/dispatcher/multibus.h @@ -100,4 +100,31 @@ static inline int hif_pci_get_context_size(void) } #endif /* HIF_PCI */ +#ifdef HIF_AHB +QDF_STATUS hif_initialize_ahb_ops(struct hif_bus_ops *bus_ops); +int hif_ahb_get_context_size(void); +#else +/** + * hif_initialize_ahb_ops() - dummy for when ahb not supported + * + * Return: QDF_STATUS_E_NOSUPPORT + */ +static inline QDF_STATUS hif_initialize_ahb_ops(struct hif_bus_ops *bus_ops) +{ + HIF_ERROR("%s: not supported", __func__); + return QDF_STATUS_E_NOSUPPORT; +} + +/** + * hif_ahb_get_context_size() - dummy for when ahb not supported + * + * Return: 0 as an invalid size to indicate no support + */ +static inline int hif_ahb_get_context_size(void) +{ + return 0; +} +#endif + + #endif /* _MULTIBUS_H_ */ diff --git a/hif/src/dispatcher/multibus_ahb.c b/hif/src/dispatcher/multibus_ahb.c new file mode 100644 index 0000000000..dc8966a66b --- /dev/null +++ b/hif/src/dispatcher/multibus_ahb.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 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. + */ + +#include "hif.h" +#include "hif_main.h" +#include "multibus.h" +#include "ce_main.h" +#include "ahb_api.h" +#include "dummy.h" + +/** + * hif_initialize_ahb_ops() - initialize the ahb ops + * @bus_ops: hif_bus_ops table pointer to initialize + * + * This function will assign the set of callbacks that needs + * to be called for ipq4019 platform + * + * Return: QDF_STATUS_SUCCESS + */ +QDF_STATUS hif_initialize_ahb_ops(struct hif_bus_ops *bus_ops) +{ + bus_ops->hif_bus_open = &hif_ahb_open; + bus_ops->hif_bus_close = &hif_ahb_close; + bus_ops->hif_bus_prevent_linkdown = &hif_dummy_bus_prevent_linkdown; + bus_ops->hif_reset_soc = &hif_dummy_reset_soc; + bus_ops->hif_bus_suspend = &hif_dummy_bus_suspend; + bus_ops->hif_bus_resume = &hif_dummy_bus_resume; + bus_ops->hif_target_sleep_state_adjust = + &hif_dummy_target_sleep_state_adjust; + + bus_ops->hif_disable_isr = &hif_ahb_disable_isr; + bus_ops->hif_nointrs = &hif_ahb_nointrs; + bus_ops->hif_enable_bus = &hif_ahb_enable_bus; + bus_ops->hif_disable_bus = &hif_ahb_disable_bus; + bus_ops->hif_bus_configure = &hif_ahb_bus_configure; + bus_ops->hif_irq_disable = &hif_ahb_irq_disable; + bus_ops->hif_irq_enable = &hif_ahb_irq_enable; + bus_ops->hif_dump_registers = &hif_ahb_dump_registers; + bus_ops->hif_enable_power_management = + &hif_dummy_enable_power_management; + bus_ops->hif_disable_power_management = + &hif_dummy_disable_power_management; + + return QDF_STATUS_SUCCESS; +} + +/** + * hif_ahb_get_context_size() - return the size of the snoc context + * + * Return the size of the context. (0 for invalid bus) + */ +int hif_ahb_get_context_size(void) +{ + return sizeof(struct HIF_CE_state); +} diff --git a/hif/src/hif_main.c b/hif/src/hif_main.c index 8510537e43..2dac2f4ef0 100644 --- a/hif/src/hif_main.c +++ b/hif/src/hif_main.c @@ -487,7 +487,7 @@ struct hif_opaque_softc *hif_open(qdf_device_t qdf_ctx, uint32_t mode, qdf_atomic_init(&scn->link_suspended); qdf_atomic_init(&scn->tasklet_from_intr); qdf_mem_copy(&scn->callbacks, cbk, sizeof(struct hif_driver_state_callbacks)); - + scn->bus_type = bus_type; status = hif_bus_open(scn, bus_type); if (status != QDF_STATUS_SUCCESS) { HIF_ERROR("%s: hif_bus_open error = %d, bus_type = %d", @@ -804,6 +804,12 @@ int hif_get_device_type(uint32_t device_id, HIF_INFO(" *********** AR900B *************\n"); break; + case IPQ4019_DEVICE_ID: + *hif_type = HIF_TYPE_IPQ4019; + *target_type = TARGET_TYPE_IPQ4019; + HIF_INFO(" *********** IPQ4019 *************\n"); + break; + default: HIF_ERROR("%s: Unsupported device ID!", __func__); ret = -ENODEV; diff --git a/hif/src/hif_main.h b/hif/src/hif_main.h index db6fd77808..66f7b29bec 100644 --- a/hif/src/hif_main.h +++ b/hif/src/hif_main.h @@ -90,6 +90,7 @@ #define AR900B_DEVICE_ID (0x0040) #define QCA9984_DEVICE_ID (0x0046) #define QCA9888_DEVICE_ID (0x0056) +#define IPQ4019_DEVICE_ID (0x12ef) #define HIF_GET_PCI_SOFTC(scn) ((struct hif_pci_softc *)scn) #define HIF_GET_CE_STATE(scn) ((struct HIF_CE_state *)scn) diff --git a/hif/src/pcie/if_pci.c b/hif/src/pcie/if_pci.c index 63ae29e1eb..3130891b9f 100644 --- a/hif/src/pcie/if_pci.c +++ b/hif/src/pcie/if_pci.c @@ -176,7 +176,7 @@ static void pci_dispatch_interrupt(struct hif_softc *scn) } } -static irqreturn_t hif_pci_interrupt_handler(int irq, void *arg) +irqreturn_t hif_pci_interrupt_handler(int irq, void *arg) { struct hif_pci_softc *sc = (struct hif_pci_softc *)arg; struct hif_softc *scn = HIF_GET_SOFTC(sc); @@ -891,7 +891,7 @@ static void hif_init_reschedule_tasklet_work(struct hif_pci_softc *sc) static void hif_init_reschedule_tasklet_work(struct hif_pci_softc *sc) { } #endif /* CONFIG_SLUB_DEBUG_ON */ -static void wlan_tasklet(unsigned long data) +void wlan_tasklet(unsigned long data) { struct hif_pci_softc *sc = (struct hif_pci_softc *)data; struct hif_softc *scn = HIF_GET_SOFTC(sc); @@ -2170,7 +2170,7 @@ end: return ret; } -static void wlan_tasklet_msi(unsigned long data) +void wlan_tasklet_msi(unsigned long data) { struct hif_tasklet_entry *entry = (struct hif_tasklet_entry *)data; struct hif_pci_softc *sc = (struct hif_pci_softc *) entry->hif_handler; @@ -2338,6 +2338,9 @@ static int hif_pci_configure_legacy_irq(struct hif_pci_softc *sc) HIF_ERROR("%s: request_irq failed, ret = %d", __func__, ret); goto end; } + /* Use sc->irq instead of sc->pdev-irq + platform_device pdev doesn't have an irq field */ + sc->irq = sc->pdev->irq; /* Use Legacy PCI Interrupts */ hif_write32_mb(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS), @@ -2372,12 +2375,14 @@ void hif_pci_nointrs(struct hif_softc *scn) if (sc->num_msi_intrs > 0) { /* MSI interrupt(s) */ for (i = 0; i < sc->num_msi_intrs; i++) { - free_irq(sc->pdev->irq + i, sc); + free_irq(sc->irq + i, sc); } sc->num_msi_intrs = 0; } else { - /* Legacy PCI line interrupt */ - free_irq(sc->pdev->irq, sc); + /* Legacy PCI line interrupt + Use sc->irq instead of sc->pdev-irq + platform_device pdev doesn't have an irq field */ + free_irq(sc->irq, sc); } ce_unregister_irq(hif_state, 0xfff); scn->request_irq_done = false; @@ -3262,8 +3267,16 @@ void hif_target_dump_access_log(void) } #endif +#ifndef HIF_AHB +int hif_ahb_configure_legacy_irq(struct hif_pci_softc *sc) +{ + QDF_BUG(0); + return -EINVAL; +} +#endif + /** - * hif_configure_irq(): configure interrupt + * hif_configure_irq() - configure interrupt * * This function configures interrupt(s) * @@ -3287,7 +3300,14 @@ int hif_configure_irq(struct hif_softc *scn) goto end; } /* MSI failed. Try legacy irq */ - ret = hif_pci_configure_legacy_irq(sc); + switch (scn->target_info.target_type) { + case TARGET_TYPE_IPQ4019: + ret = hif_ahb_configure_legacy_irq(sc); + break; + default: + ret = hif_pci_configure_legacy_irq(sc); + break; + } if (ret < 0) { HIF_ERROR("%s: hif_pci_configure_legacy_irq error = %d", __func__, ret); diff --git a/hif/src/pcie/if_pci.h b/hif/src/pcie/if_pci.h index 617086a1ce..9b59153657 100644 --- a/hif/src/pcie/if_pci.h +++ b/hif/src/pcie/if_pci.h @@ -147,6 +147,8 @@ struct hif_pci_softc { bool hif_pci_targ_is_present(struct hif_softc *scn, void *__iomem *mem); int hif_configure_irq(struct hif_softc *sc); void hif_pci_cancel_deferred_target_sleep(struct hif_softc *scn); +void wlan_tasklet(unsigned long data); +irqreturn_t hif_pci_interrupt_handler(int irq, void *arg); /* * A firmware interrupt to the Host is indicated by the diff --git a/hif/src/snoc/if_ahb.c b/hif/src/snoc/if_ahb.c new file mode 100644 index 0000000000..d8bf1fb4fc --- /dev/null +++ b/hif/src/snoc/if_ahb.c @@ -0,0 +1,401 @@ +/* + * 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.c + * + * c file for ahb 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 "if_ahb.h" +#include "if_pci.h" +#include "ahb_api.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +#define IRQF_DISABLED 0x00000020 +#endif +/** + * hif_disable_isr() - disable isr + * + * This function disables isr and kills tasklets + * + * @hif_ctx: struct hif_softc + * + * Return: void + */ +void hif_ahb_disable_isr(struct hif_softc *scn) +{ + struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn); + + hif_nointrs(scn); + ce_tasklet_kill(scn); + tasklet_kill(&sc->intr_tq); + qdf_atomic_set(&scn->active_tasklet_cnt, 0); +} + +/** + * hif_dump_registers() - dump bus debug registers + * @scn: struct hif_opaque_softc + * + * This function dumps hif bus debug registers + * + * Return: 0 for success or error code + */ +int hif_ahb_dump_registers(struct hif_softc *hif_ctx) +{ + int status; + struct hif_softc *scn = HIF_GET_SOFTC(hif_ctx); + + status = hif_dump_ce_registers(scn); + if (status) + HIF_ERROR("%s: Dump CE Registers Failed status %d", __func__, + status); + + return 0; +} + +/** + * hif_ahb_close() - hif_bus_close + * @scn: pointer to the hif context. + * + * This is a callback function for hif_bus_close. + * + * + * Return: n/a + */ +void hif_ahb_close(struct hif_softc *scn) +{ + hif_ce_close(scn); +} + +/** + * hif_bus_open() - hif_ahb open + * @hif_ctx: hif context + * @bus_type: bus type + * + * This is a callback function for hif_bus_open. + * + * Return: n/a + */ +QDF_STATUS hif_ahb_open(struct hif_softc *hif_ctx, enum qdf_bus_type bus_type) +{ + + struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(hif_ctx); + + qdf_spinlock_create(&sc->irq_lock); + return hif_ce_open(hif_ctx); +} + +/** + * hif_bus_configure() - Configure the bus + * @scn: pointer to the hif context. + * + * This function configure the ahb bus + * + * return: 0 for success. nonzero for failure. + */ +int hif_ahb_bus_configure(struct hif_softc *scn) +{ + return hif_pci_bus_configure(scn); +} + +/** + * hif_configure_msi_ahb - Configure MSI interrupts + * @sc : pointer to the hif context + * + * return: 0 for success. nonzero for failure. + */ + +int hif_configure_msi_ahb(struct hif_pci_softc *sc) +{ + return 0; +} + +/** + * hif_ahb_configure_legacy_irq() - Configure Legacy IRQ + * @sc: pointer to the hif context. + * + * This function registers the irq handler and enables legacy interrupts + * + * return: 0 for success. nonzero for failure. + */ +int hif_ahb_configure_legacy_irq(struct hif_pci_softc *sc) +{ + int ret = 0; + struct hif_softc *scn = HIF_GET_SOFTC(sc); + struct platform_device *pdev = (struct platform_device *)sc->pdev; + int irq = 0; + + /* do not support MSI or MSI IRQ failed */ + tasklet_init(&sc->intr_tq, wlan_tasklet, (unsigned long)sc); + irq = platform_get_irq_byname(pdev, "legacy"); + if (irq < 0) { + dev_err(&pdev->dev, "Unable to get irq\n"); + ret = -1; + goto end; + } + ret = request_irq(irq, hif_pci_interrupt_handler, + IRQF_DISABLED, "wlan_ahb", sc); + if (ret) { + dev_err(&pdev->dev, "ath_request_irq failed\n"); + ret = -1; + goto end; + } + sc->irq = irq; + + /* Use Legacy PCI Interrupts */ + hif_write32_mb(sc->mem+(SOC_CORE_BASE_ADDRESS | + PCIE_INTR_ENABLE_ADDRESS), + PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL); + /* read once to flush */ + hif_read32_mb(sc->mem+(SOC_CORE_BASE_ADDRESS | + PCIE_INTR_ENABLE_ADDRESS) + ); + +end: + return ret; +} + +/** + * hif_target_sync() : ensure the target is ready + * @scn: hif control structure + * + * Informs fw that we plan to use legacy interupts so that + * it can begin booting. Ensures that the fw finishes booting + * before continuing. Should be called before trying to write + * to the targets other registers for the first time. + * + * Return: none + */ +int hif_target_sync_ahb(struct hif_softc *scn) +{ + hif_write32_mb(scn->mem + FW_INDICATOR_ADDRESS, FW_IND_HOST_READY); + if (HAS_FW_INDICATOR) { + int wait_limit = 500; + int fw_ind = 0; + + while (1) { + fw_ind = hif_read32_mb(scn->mem + + FW_INDICATOR_ADDRESS); + if (fw_ind & FW_IND_INITIALIZED) + break; + if (wait_limit-- < 0) + break; + hif_write32_mb(scn->mem+(SOC_CORE_BASE_ADDRESS | + PCIE_INTR_ENABLE_ADDRESS), + PCIE_INTR_FIRMWARE_MASK); + qdf_mdelay(10); + } + if (wait_limit < 0) { + HIF_TRACE("%s: FW signal timed out", __func__); + return -EIO; + } else { + HIF_TRACE("%s: Got FW signal, retries = %x", __func__, + 500-wait_limit); + } + } + + return 0; +} + +/** + * hif_disable_bus() - Disable the bus + * @scn : pointer to the hif context + * + * This function disables the bus and helds the target in reset state + * + * Return: none + */ +void hif_ahb_disable_bus(struct hif_softc *scn) +{ + struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(scn); + void __iomem *mem; + struct platform_device *pdev = (struct platform_device *)sc->pdev; + + /*Disable WIFI clock input*/ + hif_ahb_clk_enable_disable(&pdev->dev, 0); + + hif_ahb_device_reset(scn); + mem = (void __iomem *)sc->mem; + if (mem) { + devm_iounmap(&pdev->dev, mem); + sc->mem = NULL; + } + scn->mem = NULL; +} + +/** + * hif_enable_bus() - Enable the bus + * @dev: dev + * @bdev: bus dev + * @bid: bus id + * @type: bus type + * + * This function enables the radio bus by enabling necessary + * clocks and waits for the target to get ready to proceed futher + * + * Return: QDF_STATUS + */ +QDF_STATUS hif_ahb_enable_bus(struct hif_softc *ol_sc, + struct device *dev, void *bdev, + const hif_bus_id *bid, + enum hif_enable_type type) +{ + int ret = 0; + int hif_type; + int target_type; + const struct platform_device_id *id = (struct platform_device_id *)bid; + struct platform_device *pdev = bdev; + struct hif_target_info *tgt_info = NULL; + struct resource *memres = NULL; + void __iomem *mem = NULL; + uint32_t revision_id = 0; + struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(ol_sc); + + sc->pdev = (struct pci_dev *)pdev; + sc->dev = &pdev->dev; + sc->devid = id->driver_data; + + ret = hif_get_device_type(id->driver_data, revision_id, + &hif_type, &target_type); + if (ret < 0) { + HIF_ERROR("%s: invalid device ret %d id %d revision_id %d", + __func__, ret, (int)id->driver_data, revision_id); + return QDF_STATUS_E_FAILURE; + } + + memres = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!memres) { + HIF_INFO("%s: Failed to get IORESOURCE_MEM\n", __func__); + return -EIO; + } + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + HIF_INFO("ath: 32-bit DMA not available\n"); + goto err_cleanup1; + } + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + HIF_ERROR("%s: failed to set dma mask error = %d", + __func__, ret); + return ret; + } + + /* Arrange for access to Target SoC registers. */ + mem = devm_ioremap_resource(&pdev->dev, memres); + if (IS_ERR(mem)) { + HIF_INFO("ath: ioremap error\n"); + ret = PTR_ERR(mem); + goto err_cleanup1; + } + + sc->mem = mem; + ol_sc->mem = mem; + ol_sc->mem_pa = memres->start; + tgt_info = hif_get_target_info_handle((struct hif_opaque_softc *)ol_sc); + + tgt_info->target_type = target_type; + hif_register_tbl_attach(ol_sc, hif_type); + target_register_tbl_attach(ol_sc, target_type); + + if (hif_ahb_enable_radio(sc, pdev, id) != 0) { + HIF_INFO("error in enabling soc\n"); + return -EIO; + } + + if (hif_target_sync_ahb(ol_sc) < 0) { + ret = -EIO; + goto err_target_sync; + } + HIF_TRACE("%s: X - hif_type = 0x%x, target_type = 0x%x", + __func__, hif_type, target_type); + + return QDF_STATUS_SUCCESS; +err_target_sync: + HIF_INFO("Error: Disabling target\n"); + hif_ahb_disable_bus(ol_sc); +err_cleanup1: + return ret; +} + + +/** + * hif_reset_soc() - reset soc + * + * @hif_ctx: HIF context + * + * This function resets soc and helds the + * target in reset state + * + * Return: void + */ +/* Function to reset SoC */ +void hif_ahb_reset_soc(struct hif_opaque_softc *hif_ctx) +{ + struct hif_pci_softc *sc = HIF_GET_PCI_SOFTC(hif_ctx); + hif_ahb_device_reset((struct hif_softc *)sc); +} + + +/** + * hif_nointrs() - disable IRQ + * + * @scn: struct hif_softc + * + * This function stops interrupt(s) + * + * Return: none + */ +void hif_ahb_nointrs(struct hif_softc *scn) +{ + hif_pci_nointrs(scn); +} + +/** + * ce_irq_enable() - enable copy engine IRQ + * @scn: struct hif_softc + * @ce_id: ce_id + * + * This function enables the interrupt for the radio. + * + * Return: N/A + */ +void hif_ahb_irq_enable(struct hif_softc *scn, int ce_id) +{ + hif_pci_irq_enable(scn, ce_id); +} + +/** + * ce_irq_disable() - disable copy engine IRQ + * @scn: struct hif_softc + * @ce_id: ce_id + * + * Return: N/A + */ +void hif_ahb_irq_disable(struct hif_softc *scn, int ce_id) +{ + +} diff --git a/hif/src/snoc/if_ahb.h b/hif/src/snoc/if_ahb.h new file mode 100644 index 0000000000..f5c9081a96 --- /dev/null +++ b/hif/src/snoc/if_ahb.h @@ -0,0 +1,49 @@ +/* + * 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.h + * + * h file for ahb specific implementations. + */ + +#ifndef __IF_AHB_H +#define __IF_AHB_H + +#define GCC_BASE 0x1800000 +#define GCC_SIZE 0x60000 +#define GCC_FEPLL_PLL_DIV 0x2f020 +#define GCC_FEPLL_PLL_CLK_WIFI_0_SEL_MASK 0x00000300 +#define GCC_FEPLL_PLL_CLK_WIFI_0_SEL_SHIFT 8 +#define GCC_FEPLL_PLL_CLK_WIFI_1_SEL_MASK 0x00003000 +#define GCC_FEPLL_PLL_CLK_WIFI_1_SEL_SHIFT 12 + + +/* These registers are outsize Wifi space. */ +/* TBD: Should we add these offsets as device tree properties? */ +#define TCSR_BASE 0x1900000 +#define TCSR_SIZE 0x80000 +#define TCSR_WIFI0_GLB_CFG 0x49000 +#define TCSR_WIFI1_GLB_CFG 0x49004 +#define TCSR_WCSS0_HALTREQ 0x52000 +#define TCSR_WCSS1_HALTREQ 0x52004 +#define TCSR_WCSS0_HALTACK 0x52010 +#define TCSR_WCSS1_HALTACK 0x52014 +#define ATH_AHB_RESET_WAIT_MAX 10 /* Ms */ +#endif + diff --git a/hif/src/snoc/if_ahb_reset.c b/hif/src/snoc/if_ahb_reset.c new file mode 100644 index 0000000000..b65edf499f --- /dev/null +++ b/hif/src/snoc/if_ahb_reset.c @@ -0,0 +1,372 @@ +/* + * 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 +#include +#include +#include +#include +#include + +/** + * 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); +} + + +