| /* |
| * Copyright (c) 2013-2017 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * 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. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| #ifndef EXPORT_SYMTAB |
| #define EXPORT_SYMTAB |
| #endif |
| |
| #include <osdep.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/if_arp.h> |
| #include <linux/mmc/card.h> |
| #include <linux/mmc/mmc.h> |
| #include <linux/mmc/host.h> |
| #include <linux/mmc/sdio_func.h> |
| #include <linux/mmc/sdio_ids.h> |
| #include <linux/mmc/sdio.h> |
| #include <linux/mmc/sd.h> |
| #include "vos_cnss.h" |
| #include "wlan_hdd_main.h" |
| #include "wlan_nlink_common.h" |
| #include "bmi_msg.h" /* TARGET_TYPE_ */ |
| #include "if_ath_sdio.h" |
| #include "vos_api.h" |
| #include "vos_sched.h" |
| #include "regtable.h" |
| #include "wlan_hdd_power.h" |
| #include "targaddrs.h" |
| |
| #ifndef REMOVE_PKT_LOG |
| #include "ol_txrx_types.h" |
| #include "pktlog_ac_api.h" |
| #include "pktlog_ac.h" |
| #endif |
| #include "ol_fw.h" |
| #include "epping_main.h" |
| |
| #ifndef ATH_BUS_PM |
| #ifdef CONFIG_PM |
| #define ATH_BUS_PM |
| #endif /* CONFIG_PM */ |
| #endif /* ATH_BUS_PM */ |
| |
| #ifndef offsetof |
| #define offsetof(type, field) ((adf_os_size_t)(&((type *)0)->field)) |
| #endif |
| |
| #define FW_IND_HELPER 0x8000 |
| |
| #ifndef REMOVE_PKT_LOG |
| struct ol_pl_os_dep_funcs *g_ol_pl_os_dep_funcs = NULL; |
| #endif |
| |
| typedef void * hif_handle_t; |
| typedef void * hif_softc_t; |
| |
| extern int hdd_wlan_startup(struct device *dev, void *hif_sc); |
| extern void __hdd_wlan_exit(void); |
| |
| struct ath_hif_sdio_softc *sc = NULL; |
| |
| #if defined(CONFIG_CNSS) && defined(HIF_SDIO) |
| static inline void *hif_get_virt_ramdump_mem(unsigned long *size) |
| { |
| if (!sc) |
| return NULL; |
| |
| return vos_get_virt_ramdump_mem(sc->dev, size); |
| } |
| |
| static inline void hif_release_ramdump_mem(unsigned long *address) |
| { |
| } |
| #else |
| #ifndef TARGET_DUMP_FOR_NON_QC_PLATFORM |
| static inline void *hif_get_virt_ramdump_mem(unsigned long *size) |
| { |
| void *addr; |
| addr = ioremap(RAMDUMP_ADDR, RAMDUMP_SIZE); |
| if (addr) |
| *size = RAMDUMP_SIZE; |
| return addr; |
| } |
| |
| static inline void hif_release_ramdump_mem(unsigned long *address) |
| { |
| if (address) |
| iounmap(address); |
| } |
| #else |
| static inline void *hif_get_virt_ramdump_mem(unsigned long *size) |
| { |
| size_t length = 0; |
| int flags = GFP_KERNEL; |
| |
| length = DRAM_SIZE + IRAM_SIZE + AXI_SIZE + REG_SIZE; |
| |
| if (size != NULL) |
| *size = (unsigned long)length; |
| |
| if (in_interrupt() || irqs_disabled() || in_atomic()) |
| flags = GFP_ATOMIC; |
| |
| return kzalloc(length, flags); |
| } |
| |
| static inline void hif_release_ramdump_mem(unsigned long *address) |
| { |
| if (address != NULL) |
| kfree(address); |
| } |
| #endif |
| #endif |
| static A_STATUS |
| ath_hif_sdio_probe(void *context, void *hif_handle) |
| { |
| int ret = 0; |
| struct ol_softc *ol_sc; |
| HIF_DEVICE_OS_DEVICE_INFO os_dev_info; |
| struct sdio_func *func = NULL; |
| const struct sdio_device_id *id; |
| u_int32_t target_type; |
| ENTER(); |
| |
| sc = (struct ath_hif_sdio_softc *) A_MALLOC(sizeof(*sc)); |
| if (!sc) { |
| ret = -ENOMEM; |
| goto err_alloc; |
| } |
| A_MEMZERO(sc,sizeof(*sc)); |
| |
| |
| sc->hif_handle = hif_handle; |
| HIFConfigureDevice(hif_handle, HIF_DEVICE_GET_OS_DEVICE, &os_dev_info, sizeof(os_dev_info)); |
| |
| sc->aps_osdev.device = os_dev_info.pOSDevice; |
| sc->aps_osdev.bc.bc_bustype = HAL_BUS_TYPE_SDIO; |
| spin_lock_init(&sc->target_lock); |
| |
| { |
| /* |
| * Attach Target register table. This is needed early on -- |
| * even before BMI -- since PCI and HIF initialization (and BMI init) |
| * directly access Target registers (e.g. CE registers). |
| * |
| * TBDXXX: targetdef should not be global -- should be stored |
| * in per-device struct so that we can support multiple |
| * different Target types with a single Host driver. |
| * The whole notion of an "hif type" -- (not as in the hif |
| * module, but generic "Host Interface Type") is bizarre. |
| * At first, one one expect it to be things like SDIO, USB, PCI. |
| * But instead, it's an actual platform type. Inexplicably, the |
| * values used for HIF platform types are *different* from the |
| * values used for Target Types. |
| */ |
| |
| #if defined(CONFIG_AR9888_SUPPORT) |
| hif_register_tbl_attach(HIF_TYPE_AR9888); |
| target_register_tbl_attach(TARGET_TYPE_AR9888); |
| target_type = TARGET_TYPE_AR9888; |
| #elif defined(CONFIG_AR6320_SUPPORT) |
| id = ((HIF_DEVICE*)hif_handle)->id; |
| if (((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) == |
| MANUFACTURER_ID_QCA9377_BASE) || |
| ((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) == |
| MANUFACTURER_ID_QCA9379_BASE)) { |
| hif_register_tbl_attach(HIF_TYPE_AR6320V2); |
| target_register_tbl_attach(TARGET_TYPE_AR6320V2); |
| } else if ((id->device & MANUFACTURER_ID_AR6K_BASE_MASK) == MANUFACTURER_ID_AR6320_BASE) { |
| int ar6kid = id->device & MANUFACTURER_ID_AR6K_REV_MASK; |
| if (ar6kid >= 1) { |
| /* v2 or higher silicon */ |
| hif_register_tbl_attach(HIF_TYPE_AR6320V2); |
| target_register_tbl_attach(TARGET_TYPE_AR6320V2); |
| } else { |
| /* legacy v1 silicon */ |
| hif_register_tbl_attach(HIF_TYPE_AR6320); |
| target_register_tbl_attach(TARGET_TYPE_AR6320); |
| } |
| } |
| target_type = TARGET_TYPE_AR6320; |
| |
| #endif |
| } |
| func = ((HIF_DEVICE*)hif_handle)->func; |
| sc->dev = &func->dev; |
| |
| ol_sc = A_MALLOC(sizeof(*ol_sc)); |
| if (!ol_sc){ |
| ret = -ENOMEM; |
| goto err_attach; |
| } |
| OS_MEMZERO(ol_sc, sizeof(*ol_sc)); |
| ol_sc->sc_osdev = &sc->aps_osdev; |
| ol_sc->hif_sc = (void *)sc; |
| sc->ol_sc = ol_sc; |
| ol_sc->target_type = target_type; |
| ol_sc->enableuartprint = 1; |
| ol_sc->enablefwlog = 0; |
| ol_sc->enablesinglebinary = FALSE; |
| ol_sc->max_no_of_peers = 1; |
| |
| ol_sc->hif_hdl = hif_handle; |
| |
| /* Get RAM dump memory address and size */ |
| ol_sc->ramdump_base = NULL;//hif_get_virt_ramdump_mem(&ol_sc->ramdump_size); |
| if (ol_sc->ramdump_base == NULL || !ol_sc->ramdump_size) { |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR, |
| "%s: Failed to get RAM dump memory address or size!\n", |
| __func__); |
| } else { |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO, |
| "%s: ramdump base 0x%pK size %d\n", |
| __func__, ol_sc->ramdump_base, (int)ol_sc->ramdump_size); |
| } |
| init_waitqueue_head(&ol_sc->sc_osdev->event_queue); |
| |
| if (athdiag_procfs_init(sc) != 0) { |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR, |
| "%s athdiag_procfs_init failed",__func__); |
| ret = A_ERROR; |
| goto err_attach1; |
| } |
| ret = hif_init_adf_ctx(ol_sc); |
| if (ret == 0) { |
| if (vos_is_logp_in_progress(VOS_MODULE_ID_HIF, NULL)) { |
| ret = hdd_wlan_re_init(ol_sc); |
| vos_set_logp_in_progress(VOS_MODULE_ID_HIF, FALSE); |
| } else{ |
| ret = hdd_wlan_startup(&(func->dev), ol_sc); |
| } |
| } |
| if ( ret ) { |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_FATAL," hdd_wlan_startup failed"); |
| goto err_attach2; |
| }else{ |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO," hdd_wlan_startup success!"); |
| } |
| |
| return 0; |
| |
| err_attach2: |
| athdiag_procfs_remove(); |
| if (sc->ol_sc->ramdump_base) |
| hif_release_ramdump_mem(sc->ol_sc->ramdump_base); |
| hif_deinit_adf_ctx(ol_sc); |
| err_attach1: |
| A_FREE(ol_sc); |
| err_attach: |
| A_FREE(sc); |
| sc = NULL; |
| err_alloc: |
| return ret; |
| } |
| |
| int |
| ol_ath_sdio_configure(hif_softc_t hif_sc, struct net_device *dev, hif_handle_t *hif_hdl) |
| { |
| struct ath_hif_sdio_softc *sc = (struct ath_hif_sdio_softc *) hif_sc; |
| int ret = 0; |
| |
| sc->aps_osdev.netdev = dev; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) |
| SET_MODULE_OWNER(dev); |
| #endif |
| |
| *hif_hdl = sc->hif_handle; |
| |
| return ret; |
| } |
| |
| static A_STATUS |
| ath_hif_sdio_remove(void *context, void *hif_handle) |
| { |
| ENTER(); |
| |
| if (!sc) { |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_ERROR, |
| "Global SDIO context is NULL"); |
| return A_ERROR; |
| } |
| |
| athdiag_procfs_remove(); |
| |
| if (sc && sc->ol_sc && sc->ol_sc->ramdump_base) |
| hif_release_ramdump_mem(sc->ol_sc->ramdump_base); |
| |
| #ifndef REMOVE_PKT_LOG |
| if (vos_get_conparam() != VOS_FTM_MODE && |
| !WLAN_IS_EPPING_ENABLED(vos_get_conparam())){ |
| if (sc && sc->ol_sc) |
| pktlogmod_exit(sc->ol_sc); |
| } |
| #endif |
| |
| //cleaning up the upper layers |
| if (vos_is_logp_in_progress(VOS_MODULE_ID_HIF, NULL)) { |
| hdd_wlan_shutdown(); |
| } else { |
| __hdd_wlan_exit(); |
| } |
| if (sc && sc->ol_sc){ |
| hif_deinit_adf_ctx(sc->ol_sc); |
| A_FREE(sc->ol_sc); |
| sc->ol_sc = NULL; |
| } |
| if (sc) { |
| A_FREE(sc); |
| sc = NULL; |
| } |
| |
| EXIT(); |
| return 0; |
| } |
| |
| static A_STATUS |
| ath_hif_sdio_suspend(void *context) |
| { |
| pr_debug("%s TODO\n", __func__); |
| return 0; |
| } |
| |
| static A_STATUS |
| ath_hif_sdio_resume(void *context) |
| { |
| pr_debug("%s TODO\n", __func__); |
| return 0; |
| } |
| |
| static A_STATUS |
| ath_hif_sdio_power_change(void *context, A_UINT32 config) |
| { |
| printk(KERN_INFO "ol_ath_sdio_power change TODO\n"); |
| return 0; |
| } |
| |
| /* |
| * Module glue. |
| */ |
| #include <linux/version.h> |
| static char *version = "HIF (Atheros/multi-bss)"; |
| static char *dev_info = "ath_hif_sdio"; |
| |
| static int init_ath_hif_sdio(void) |
| { |
| A_STATUS status; |
| OSDRV_CALLBACKS osdrvCallbacks; |
| ENTER(); |
| |
| A_MEMZERO(&osdrvCallbacks,sizeof(osdrvCallbacks)); |
| osdrvCallbacks.deviceInsertedHandler = ath_hif_sdio_probe; |
| osdrvCallbacks.deviceRemovedHandler = ath_hif_sdio_remove; |
| #ifdef CONFIG_PM |
| osdrvCallbacks.deviceSuspendHandler = ath_hif_sdio_suspend; |
| osdrvCallbacks.deviceResumeHandler = ath_hif_sdio_resume; |
| osdrvCallbacks.devicePowerChangeHandler = ath_hif_sdio_power_change; |
| #endif |
| |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_INFO,"%s %d",__func__,__LINE__); |
| status = HIFInit(&osdrvCallbacks); |
| if(status != A_OK){ |
| VOS_TRACE(VOS_MODULE_ID_HIF, VOS_TRACE_LEVEL_FATAL, "%s HIFInit failed!",__func__); |
| return -ENODEV; |
| } |
| |
| printk(KERN_INFO "%s: %s\n", dev_info, version); |
| |
| return 0; |
| } |
| |
| int hif_register_driver(void) |
| { |
| int status = 0; |
| ENTER(); |
| status = init_ath_hif_sdio(); |
| EXIT("status = %d", status); |
| return status; |
| |
| } |
| |
| void hif_unregister_driver(void) |
| { |
| ENTER(); |
| HIFShutDownDevice(NULL); |
| EXIT(); |
| return ; |
| } |
| |
| int hif_init_adf_ctx(void *ol_sc) |
| { |
| adf_os_device_t adf_ctx; |
| v_CONTEXT_t pVosContext = NULL; |
| struct ol_softc *ol_sc_local = (struct ol_softc *)ol_sc; |
| struct ath_hif_sdio_softc *hif_sc = ol_sc_local->hif_sc; |
| ENTER(); |
| pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL); |
| if(pVosContext == NULL) |
| return -EFAULT; |
| adf_ctx = vos_mem_malloc(sizeof(*adf_ctx)); |
| if (!adf_ctx) |
| return -ENOMEM; |
| vos_mem_zero(adf_ctx, sizeof(*adf_ctx)); |
| adf_ctx->drv = &hif_sc->aps_osdev; |
| adf_ctx->dev = hif_sc->aps_osdev.device; |
| ol_sc_local->adf_dev = adf_ctx; |
| ((VosContextType*)(pVosContext))->adf_ctx = adf_ctx; |
| EXIT(); |
| return 0; |
| } |
| |
| void hif_deinit_adf_ctx(void *ol_sc) |
| { |
| struct ol_softc *sc = (struct ol_softc *)ol_sc; |
| |
| if (sc == NULL) |
| return; |
| if (sc->adf_dev) { |
| v_CONTEXT_t pVosContext = NULL; |
| |
| pVosContext = vos_get_global_context(VOS_MODULE_ID_SYS, NULL); |
| vos_mem_free(sc->adf_dev); |
| sc->adf_dev = NULL; |
| if (pVosContext) |
| ((VosContextType*)(pVosContext))->adf_ctx = NULL; |
| } |
| } |
| |
| /* Function to set the TXRX handle in the ol_sc context */ |
| void hif_init_pdev_txrx_handle(void *ol_sc, void *txrx_handle) |
| { |
| struct ol_softc *sc = (struct ol_softc *)ol_sc; |
| ENTER(); |
| sc->pdev_txrx_handle = txrx_handle; |
| } |
| |
| void hif_disable_isr(void *ol_sc) |
| { |
| ENTER("- dummy function!"); |
| } |
| |
| void |
| HIFSetTargetSleep(HIF_DEVICE *hif_device, A_BOOL sleep_ok, A_BOOL wait_for_it) |
| { |
| ENTER("- dummy function!"); |
| } |
| |
| void |
| HIFCancelDeferredTargetSleep(HIF_DEVICE *hif_device) |
| { |
| ENTER("- dummy function!"); |
| |
| } |
| |
| /* Function to reset SoC */ |
| void hif_reset_soc(void *ol_sc) |
| { |
| ENTER("- dummy function!"); |
| } |
| |
| void hif_get_hw_info(void *ol_sc, u32 *version, u32 *revision) |
| { |
| struct ol_softc *ol_sc_local = (struct ol_softc *)ol_sc; |
| A_UINT32 chip_id = 0; |
| A_STATUS rv; |
| rv = HIFDiagReadAccess(ol_sc_local->hif_hdl, |
| (CHIP_ID_ADDRESS | RTC_SOC_BASE_ADDRESS), &chip_id); |
| if (rv != A_OK) { |
| pr_warn("%s[%d]: get chip id fail\n", __func__, __LINE__); |
| ol_sc_local->target_revision = -1; |
| } else { |
| ol_sc_local->target_revision = |
| CHIP_ID_REVISION_GET(chip_id); |
| } |
| *version = ol_sc_local->target_version; |
| *revision = ol_sc_local->target_revision; |
| } |
| |
| void hif_set_fw_info(void *ol_sc, u32 target_fw_version) |
| { |
| ((struct ol_softc *)ol_sc)->target_fw_version = target_fw_version; |
| } |
| |
| /** |
| * hif_sdio_check_fw_reg() - Check wether a self recovery is needed |
| * @ol_sc: os layser software context |
| * |
| * For scenario such as AXI error, cold reset is needed to recover FW. |
| * FW will set FW_IND_HELPER in such a scenario. |
| * |
| */ |
| int hif_sdio_check_fw_reg(void * ol_sc) |
| { |
| int ret = 1; |
| unsigned int addr = 0; |
| unsigned int fw_indication = 0; |
| struct ol_softc *scn = (struct ol_softc *) ol_sc; |
| |
| addr = host_interest_item_address(scn->target_type, |
| offsetof(struct host_interest_s, |
| hi_option_flag2)); |
| |
| if (HIFDiagReadMem(scn->hif_hdl, addr, |
| (unsigned char *)&fw_indication, |
| 4) != A_OK) { |
| printk("%s Get fw indication failed\n", __func__); |
| return -ENOENT; |
| } |
| |
| printk("%s: fw indication is 0x%x.\n", __func__, fw_indication); |
| |
| if (fw_indication & FW_IND_HELPER) |
| ret = 0; |
| |
| return ret; |
| } |