blob: 0f68c58c354ee294b701cc220484f4ba92bc3ce1 [file] [log] [blame]
/*
* 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;
}