blob: 41bd55c655fa670f888028effcb975993f652233 [file] [log] [blame]
/*
* Copyright (c) 2013-2014 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.
*/
#include <osdep.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/if_arp.h>
#include "if_pci.h"
#include "hif_msg_based.h"
#include "hif_pci.h"
#include "copy_engine_api.h"
#include "copy_engine_internal.h"
#include "bmi_msg.h" /* TARGET_TYPE_ */
#include "regtable.h"
#include "ol_fw.h"
#include <osapi_linux.h>
#include "vos_api.h"
#include "wma_api.h"
#include "adf_os_atomic.h"
#if defined(QCA_WIFI_2_0) && !defined(QCA_WIFI_ISOC)
#include "wlan_hdd_power.h"
#endif
#ifdef CONFIG_CNSS
#include <net/cnss.h>
#endif
#ifdef WLAN_BTAMP_FEATURE
#include "wlan_btc_svc.h"
#include "wlan_nlink_common.h"
#endif
#ifndef REMOVE_PKT_LOG
#include "ol_txrx_types.h"
#include "pktlog_ac_api.h"
#include "pktlog_ac.h"
#endif
#define AR9888_DEVICE_ID (0x003c)
#define AR6320_DEVICE_ID (0x003e)
#define AR6320_FW_1_1 (0x11)
#define AR6320_FW_1_3 (0x13)
#define AR6320_FW_2_0 (0x20)
#define MAX_NUM_OF_RECEIVES 1000 /* Maximum number of Rx buf to process before break out */
#define PCIE_WAKE_TIMEOUT 1000 /* Maximum ms timeout for host to wake up target */
#define RAMDUMP_EVENT_TIMEOUT 2500
unsigned int msienable = 0;
module_param(msienable, int, 0644);
int hif_pci_configure(struct hif_pci_softc *sc, hif_handle_t *hif_hdl);
void hif_nointrs(struct hif_pci_softc *sc);
static struct pci_device_id hif_pci_id_table[] = {
{ 0x168c, 0x003c, PCI_ANY_ID, PCI_ANY_ID },
{ 0x168c, 0x003e, PCI_ANY_ID, PCI_ANY_ID },
{ 0 }
};
#ifndef REMOVE_PKT_LOG
struct ol_pl_os_dep_funcs *g_ol_pl_os_dep_funcs = NULL;
#endif
/* Setting SOC_GLOBAL_RESET during driver unload causes intermittent PCIe data bus error
* As workaround for this issue - changing the reset sequence to use TargetCPU warm reset
* instead of SOC_GLOBAL_RESET
*/
#define CPU_WARM_RESET_WAR
/*
* Top-level interrupt handler for all PCI interrupts from a Target.
* When a block of MSI interrupts is allocated, this top-level handler
* is not used; instead, we directly call the correct sub-handler.
*/
static irqreturn_t
hif_pci_interrupt_handler(int irq, void *arg)
{
struct hif_pci_softc *sc = (struct hif_pci_softc *) arg;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
volatile int tmp;
if (LEGACY_INTERRUPTS(sc)) {
if (sc->hif_init_done == TRUE)
A_TARGET_ACCESS_BEGIN(hif_state->targid);
/* Clear Legacy PCI line interrupts */
/* IMPORTANT: INTR_CLR regiser has to be set after INTR_ENABLE is set to 0, */
/* otherwise interrupt can not be really cleared */
A_PCI_WRITE32(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS), 0);
A_PCI_WRITE32(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_CLR_ADDRESS), PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
/* IMPORTANT: this extra read transaction is required to flush the posted write buffer */
tmp = A_PCI_READ32(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS));
if (tmp == 0xdeadbeef) {
printk(KERN_ERR "BUG(%s): SoC returns 0xdeadbeef!!\n", __func__);
VOS_BUG(0);
}
if (sc->hif_init_done == TRUE)
A_TARGET_ACCESS_END(hif_state->targid);
}
/* TBDXXX: Add support for WMAC */
sc->irq_event = irq;
adf_os_atomic_set(&sc->tasklet_from_intr, 1);
tasklet_schedule(&sc->intr_tq);
return IRQ_HANDLED;
}
static irqreturn_t
hif_pci_msi_fw_handler(int irq, void *arg)
{
struct hif_pci_softc *sc = (struct hif_pci_softc *) arg;
(irqreturn_t)HIF_fw_interrupt_handler(sc->irq_event, sc);
return IRQ_HANDLED;
}
bool
hif_pci_targ_is_awake(struct hif_pci_softc *sc, void *__iomem *mem)
{
A_UINT32 val;
val = A_PCI_READ32(mem + PCIE_LOCAL_BASE_ADDRESS + RTC_STATE_ADDRESS);
return (RTC_STATE_V_GET(val) == RTC_STATE_V_ON);
}
bool hif_pci_targ_is_present(A_target_id_t targetid, void *__iomem *mem)
{
return 1; /* FIX THIS */
}
bool hif_max_num_receives_reached(unsigned int count)
{
#ifdef EPPING_TEST
return (count > 120);
#else
return (count > MAX_NUM_OF_RECEIVES);
#endif
}
void hif_init_adf_ctx(adf_os_device_t adf_dev, void *ol_sc)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
adf_dev->drv = &hif_sc->aps_osdev;
adf_dev->drv_hdl = hif_sc->aps_osdev.bdev;
adf_dev->dev = hif_sc->aps_osdev.device;
sc->adf_dev = adf_dev;
}
#define A_PCIE_LOCAL_REG_READ(mem, addr) \
A_PCI_READ32((char *)(mem) + PCIE_LOCAL_BASE_ADDRESS + (A_UINT32)(addr))
#define A_PCIE_LOCAL_REG_WRITE(mem, addr, val) \
A_PCI_WRITE32(((char *)(mem) + PCIE_LOCAL_BASE_ADDRESS + (A_UINT32)(addr)), (val))
#define ATH_PCI_RESET_WAIT_MAX 10 /* Ms */
static void
hif_pci_device_reset(struct hif_pci_softc *sc)
{
void __iomem *mem = sc->mem;
int i;
u_int32_t val;
/* NB: Don't check resetok here. This form of reset is integral to correct operation. */
if (!SOC_GLOBAL_RESET_ADDRESS) {
return;
}
if (!mem) {
return;
}
printk("Reset Device \n");
/*
* NB: If we try to write SOC_GLOBAL_RESET_ADDRESS without first
* writing WAKE_V, the Target may scribble over Host memory!
*/
A_PCIE_LOCAL_REG_WRITE(mem, PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
for (i=0; i<ATH_PCI_RESET_WAIT_MAX; i++) {
if (hif_pci_targ_is_awake(sc, mem)) {
break;
}
A_MDELAY(1);
}
/* Put Target, including PCIe, into RESET. */
val = A_PCIE_LOCAL_REG_READ(mem, SOC_GLOBAL_RESET_ADDRESS);
val |= 1;
A_PCIE_LOCAL_REG_WRITE(mem, SOC_GLOBAL_RESET_ADDRESS, val);
for (i=0; i<ATH_PCI_RESET_WAIT_MAX; i++) {
if (A_PCIE_LOCAL_REG_READ(mem, RTC_STATE_ADDRESS) & RTC_STATE_COLD_RESET_MASK) {
break;
}
A_MDELAY(1);
}
/* Pull Target, including PCIe, out of RESET. */
val &= ~1;
A_PCIE_LOCAL_REG_WRITE(mem, SOC_GLOBAL_RESET_ADDRESS, val);
for (i=0; i<ATH_PCI_RESET_WAIT_MAX; i++) {
if (!(A_PCIE_LOCAL_REG_READ(mem, RTC_STATE_ADDRESS) & RTC_STATE_COLD_RESET_MASK)) {
break;
}
A_MDELAY(1);
}
A_PCIE_LOCAL_REG_WRITE(mem, PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
}
/* CPU warm reset function
* Steps:
* 1. Disable all pending interrupts - so no pending interrupts on WARM reset
* 2. Clear the FW_INDICATOR_ADDRESS -so Traget CPU intializes FW correctly on WARM reset
* 3. Clear TARGET CPU LF timer interrupt
* 4. Reset all CEs to clear any pending CE tarnsactions
* 5. Warm reset CPU
*/
void
hif_pci_device_warm_reset(struct hif_pci_softc *sc)
{
void __iomem *mem = sc->mem;
int i;
u_int32_t val;
u_int32_t fw_indicator;
/* NB: Don't check resetok here. This form of reset is integral to correct operation. */
if (!mem) {
return;
}
printk("Target Warm Reset\n");
/*
* NB: If we try to write SOC_GLOBAL_RESET_ADDRESS without first
* writing WAKE_V, the Target may scribble over Host memory!
*/
A_PCIE_LOCAL_REG_WRITE(mem, PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
for (i=0; i<ATH_PCI_RESET_WAIT_MAX; i++) {
if (hif_pci_targ_is_awake(sc, mem)) {
break;
}
A_MDELAY(1);
}
/*
* Disable Pending interrupts
*/
val = A_PCI_READ32(mem + (SOC_CORE_BASE_ADDRESS | PCIE_INTR_CAUSE_ADDRESS));
printk("Host Intr Cause reg 0x%x : value : 0x%x \n", (SOC_CORE_BASE_ADDRESS | PCIE_INTR_CAUSE_ADDRESS), val);
/* Target CPU Intr Cause */
val = A_PCI_READ32(mem + (SOC_CORE_BASE_ADDRESS | CPU_INTR_ADDRESS));
printk("Target CPU Intr Cause 0x%x \n", val);
val = A_PCI_READ32(mem + (SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS));
A_PCI_WRITE32((mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS)), 0);
A_PCI_WRITE32((mem+(SOC_CORE_BASE_ADDRESS+PCIE_INTR_CLR_ADDRESS)), 0xffffffff);
A_MDELAY(100);
/* Clear FW_INDICATOR_ADDRESS */
fw_indicator = A_PCI_READ32(mem + FW_INDICATOR_ADDRESS);
A_PCI_WRITE32(mem+FW_INDICATOR_ADDRESS, 0);
/* Clear Target LF Timer interrupts */
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS + SOC_LF_TIMER_CONTROL0_ADDRESS));
printk("addr 0x%x : 0x%x \n", (RTC_SOC_BASE_ADDRESS + SOC_LF_TIMER_CONTROL0_ADDRESS), val);
val &= ~SOC_LF_TIMER_CONTROL0_ENABLE_MASK;
A_PCI_WRITE32(mem+(RTC_SOC_BASE_ADDRESS + SOC_LF_TIMER_CONTROL0_ADDRESS), val);
/* Reset CE */
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS));
val |= SOC_RESET_CONTROL_CE_RST_MASK;
A_PCI_WRITE32((mem+(RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS)), val);
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS));
A_MDELAY(10);
/* CE unreset */
val &= ~SOC_RESET_CONTROL_CE_RST_MASK;
A_PCI_WRITE32(mem+(RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS), val);
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS));
A_MDELAY(10);
/* Read Target CPU Intr Cause */
val = A_PCI_READ32(mem + (SOC_CORE_BASE_ADDRESS | CPU_INTR_ADDRESS));
printk("Target CPU Intr Cause after CE reset 0x%x \n", val);
/* CPU warm RESET */
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS));
val |= SOC_RESET_CONTROL_CPU_WARM_RST_MASK;
A_PCI_WRITE32(mem+(RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS), val);
val = A_PCI_READ32(mem + (RTC_SOC_BASE_ADDRESS | SOC_RESET_CONTROL_ADDRESS));
printk("RESET_CONTROL after cpu warm reset 0x%x \n", val);
A_MDELAY(100);
printk("Target Warm reset complete\n");
}
int hif_pci_check_soc_status(struct hif_pci_softc *sc)
{
u_int16_t device_id;
u_int32_t val;
u_int16_t timeout_count = 0;
/* Check device ID from PCIe configuration space for link status */
pci_read_config_word(sc->pdev, PCI_DEVICE_ID, &device_id);
if(device_id != sc->devid) {
printk(KERN_ERR "PCIe link is down!\n");
return -EACCES;
}
/* Check PCIe local register for bar/memory access */
val = A_PCI_READ32(sc->mem + PCIE_LOCAL_BASE_ADDRESS +
RTC_STATE_ADDRESS);
printk("RTC_STATE_ADDRESS is %08x\n", val);
/* Try to wake up taget if it sleeps */
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
printk("PCIE_SOC_WAKE_ADDRESS is %08x\n",
A_PCI_READ32(sc->mem + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS));
/* Check if taget can be woken up */
while(!hif_pci_targ_is_awake(sc, sc->mem)) {
if(timeout_count >= PCIE_WAKE_TIMEOUT) {
printk(KERN_ERR "Target cannot be woken up! "
"RTC_STATE_ADDRESS is %08x, PCIE_SOC_WAKE_ADDRESS is %08x\n",
A_PCI_READ32(sc->mem + PCIE_LOCAL_BASE_ADDRESS +
RTC_STATE_ADDRESS), A_PCI_READ32(sc->mem +
PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS));
return -EACCES;
}
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
A_MDELAY(100);
timeout_count += 100;
}
/* Check Power register for SoC internal bus issues */
val = A_PCI_READ32(sc->mem + RTC_SOC_BASE_ADDRESS + SOC_POWER_REG_OFFSET);
printk("Power register is %08x\n", val);
return EOK;
}
void dump_CE_debug_register(struct hif_pci_softc *sc)
{
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
A_target_id_t targid = hif_state->targid;
void __iomem *mem = sc->mem;
u_int32_t val, i, j;
u_int32_t wrapper_idx[] = {1, 2, 3, 4, 5, 6, 8, 9};
u_int32_t ce_base;
A_TARGET_ACCESS_BEGIN(targid);
/* DEBUG_INPUT_SEL_SRC = 0x6 */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_INPUT_SEL_OFFSET);
val &= ~WLAN_DEBUG_INPUT_SEL_SRC_MASK;
val |= WLAN_DEBUG_INPUT_SEL_SRC_SET(0x6);
A_PCI_WRITE32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_INPUT_SEL_OFFSET, val);
/* DEBUG_CONTROL_ENABLE = 0x1 */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_CONTROL_OFFSET);
val &= ~WLAN_DEBUG_CONTROL_ENABLE_MASK;
val |= WLAN_DEBUG_CONTROL_ENABLE_SET(0x1);
A_PCI_WRITE32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_CONTROL_OFFSET, val);
printk("Debug: inputsel: %x dbgctrl: %x\n",
A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_INPUT_SEL_OFFSET),
A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_CONTROL_OFFSET));
printk("Debug CE: \n");
/* Loop CE debug output */
/* AMBA_DEBUG_BUS_SEL = 0xc */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET);
val &= ~AMBA_DEBUG_BUS_SEL_MASK;
val |= AMBA_DEBUG_BUS_SEL_SET(0xc);
A_PCI_WRITE32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET, val);
for (i = 0; i < sizeof(wrapper_idx)/sizeof(A_UINT32); i++) {
/* For (i=1,2,3,4,8,9) write CE_WRAPPER_DEBUG_SEL = i */
val = A_PCI_READ32(mem + CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_DEBUG_OFFSET);
val &= ~CE_WRAPPER_DEBUG_SEL_MASK;
val |= CE_WRAPPER_DEBUG_SEL_SET(wrapper_idx[i]);
A_PCI_WRITE32(mem + CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_DEBUG_OFFSET, val);
printk("ce wrapper: %d amdbg: %x cewdbg: %x\n", wrapper_idx[i],
A_PCI_READ32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET),
A_PCI_READ32(mem + CE_WRAPPER_BASE_ADDRESS +
CE_WRAPPER_DEBUG_OFFSET));
if (wrapper_idx[i] <= 7) {
for (j = 0; j <= 5; j++) {
ce_base = CE_BASE_ADDRESS(wrapper_idx[i]);
/* For (j=0~5) write CE_DEBUG_SEL = j */
val = A_PCI_READ32(mem + ce_base + CE_DEBUG_OFFSET);
val &= ~CE_DEBUG_SEL_MASK;
val |= CE_DEBUG_SEL_SET(j);
A_PCI_WRITE32(mem + ce_base + CE_DEBUG_OFFSET, val);
/* read (@gpio_athr_wlan_reg) WLAN_DEBUG_OUT_DATA */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS +
WLAN_DEBUG_OUT_OFFSET);
val = WLAN_DEBUG_OUT_DATA_GET(val);
printk(" module%d: cedbg: %x out: %x\n", j,
A_PCI_READ32(mem + ce_base + CE_DEBUG_OFFSET), val);
}
} else {
/* read (@gpio_athr_wlan_reg) WLAN_DEBUG_OUT_DATA */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_OUT_OFFSET);
val = WLAN_DEBUG_OUT_DATA_GET(val);
printk(" out: %x\n", val);
}
msleep(1);
}
printk("Debug PCIe: \n");
/* Loop PCIe debug output */
/* Write AMBA_DEBUG_BUS_SEL = 0x1c */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET);
val &= ~AMBA_DEBUG_BUS_SEL_MASK;
val |= AMBA_DEBUG_BUS_SEL_SET(0x1c);
A_PCI_WRITE32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET, val);
for (i = 0; i <= 8; i++) {
/* For (i=1~8) write AMBA_DEBUG_BUS_PCIE_DEBUG_SEL = i */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET);
val &= ~AMBA_DEBUG_BUS_PCIE_DEBUG_SEL_MASK;
val |= AMBA_DEBUG_BUS_PCIE_DEBUG_SEL_SET(i);
A_PCI_WRITE32(mem + GPIO_BASE_ADDRESS + AMBA_DEBUG_BUS_OFFSET, val);
/* read (@gpio_athr_wlan_reg) WLAN_DEBUG_OUT_DATA */
val = A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_OUT_OFFSET);
val = WLAN_DEBUG_OUT_DATA_GET(val);
printk("amdbg: %x out: %x %x\n",
A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_OUT_OFFSET), val,
A_PCI_READ32(mem + GPIO_BASE_ADDRESS + WLAN_DEBUG_OUT_OFFSET));
}
A_TARGET_ACCESS_END(targid);
}
/*
* Handler for a per-engine interrupt on a PARTICULAR CE.
* This is used in cases where each CE has a private
* MSI interrupt.
*/
static irqreturn_t
CE_per_engine_handler(int irq, void *arg)
{
struct hif_pci_softc *sc = (struct hif_pci_softc *) arg;
int CE_id = irq - MSI_ASSIGN_CE_INITIAL;
/*
* NOTE: We are able to derive CE_id from irq because we
* use a one-to-one mapping for CE's 0..5.
* CE's 6 & 7 do not use interrupts at all.
*
* This mapping must be kept in sync with the mapping
* used by firmware.
*/
CE_per_engine_service(sc, CE_id);
return IRQ_HANDLED;
}
static void
wlan_tasklet(unsigned long data)
{
struct hif_pci_softc *sc = (struct hif_pci_softc *) data;
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
volatile int tmp;
if (sc->hif_init_done == FALSE) {
goto irq_handled;
}
(irqreturn_t)HIF_fw_interrupt_handler(sc->irq_event, sc);
CE_per_engine_service_any(sc->irq_event, sc);
adf_os_atomic_set(&sc->tasklet_from_intr, 0);
if (CE_get_rx_pending(sc)) {
/*
* There are frames pending, schedule tasklet to process them.
* Enable the interrupt only when there is no pending frames in
* any of the Copy Engine pipes.
*/
tasklet_schedule(&sc->intr_tq);
return;
}
irq_handled:
if (LEGACY_INTERRUPTS(sc)) {
if (sc->hif_init_done == TRUE)
A_TARGET_ACCESS_BEGIN(hif_state->targid);
/* Enable Legacy PCI line interrupts */
A_PCI_WRITE32(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS),
PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
/* IMPORTANT: this extra read transaction is required to flush the posted write buffer */
tmp = A_PCI_READ32(sc->mem+(SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS));
if (sc->hif_init_done == TRUE)
A_TARGET_ACCESS_END(hif_state->targid);
}
}
#define ATH_PCI_PROBE_RETRY_MAX 3
int
hif_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
void __iomem *mem;
int ret = 0;
u_int32_t hif_type, target_type;
struct hif_pci_softc *sc;
struct ol_softc *ol_sc;
int probe_again = 0;
u_int16_t device_id;
u_int16_t revision_id;
u_int32_t lcr_val;
printk(KERN_INFO "hif_pci_probe\n");
again:
ret = 0;
#define BAR_NUM 0
/*
* Without any knowledge of the Host, the Target
* may have been reset or power cycled and its
* Config Space may no longer reflect the PCI
* address space that was assigned earlier
* by the PCI infrastructure. Refresh it now.
*/
/*WAR for EV#117307, if PCI link is down, return from probe() */
pci_read_config_word(pdev,PCI_DEVICE_ID,&device_id);
printk("PCI device id is %04x :%04x\n",device_id,id->device);
if(device_id != id->device) {
printk(KERN_ERR "ath: PCI link is down.\n");
/* pci link is down, so returing with error code */
return -EIO;
}
/* FIXME: temp. commenting out assign_resource
* call for dev_attach to work on 2.6.38 kernel
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) && !defined(__LINUX_ARM_ARCH__)
if (pci_assign_resource(pdev, BAR_NUM)) {
printk(KERN_ERR "ath: cannot assign PCI space\n");
return -EIO;
}
#endif
if (pci_enable_device(pdev)) {
printk(KERN_ERR "ath: cannot enable PCI device\n");
return -EIO;
}
#define BAR_NUM 0
/* Request MMIO resources */
ret = pci_request_region(pdev, BAR_NUM, "ath");
if (ret) {
dev_err(&pdev->dev, "ath: PCI MMIO reservation error\n");
ret = -EIO;
goto err_region;
}
#ifdef CONFIG_ARM_LPAE
/* if CONFIG_ARM_LPAE is enabled, we have to set 64 bits mask
* for 32 bits device also. */
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 64-bit pci DMA\n");
goto err_dma;
}
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 64-bit consistent DMA\n");
goto err_dma;
}
#else
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 32-bit pci DMA\n");
goto err_dma;
}
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_ERR "%s: Cannot enable 32-bit consistent DMA!\n",
__func__);
goto err_dma;
}
#endif
/* Set bus master bit in PCI_COMMAND to enable DMA */
pci_set_master(pdev);
/* Temporary FIX: disable ASPM on peregrine. Will be removed after the OTP is programmed */
pci_read_config_dword(pdev, 0x80, &lcr_val);
pci_write_config_dword(pdev, 0x80, (lcr_val & 0xffffff00));
/* Arrange for access to Target SoC registers. */
mem = pci_iomap(pdev, BAR_NUM, 0);
if (!mem) {
printk(KERN_ERR "ath: PCI iomap error\n") ;
ret = -EIO;
goto err_iomap;
}
/* Disable asynchronous suspend */
device_disable_async_suspend(&pdev->dev);
sc = A_MALLOC(sizeof(*sc));
if (!sc) {
ret = -ENOMEM;
goto err_alloc;
}
OS_MEMZERO(sc, sizeof(*sc));
sc->mem = mem;
sc->pdev = pdev;
sc->dev = &pdev->dev;
sc->aps_osdev.bdev = pdev;
sc->aps_osdev.device = &pdev->dev;
sc->aps_osdev.bc.bc_handle = (void *)mem;
sc->aps_osdev.bc.bc_bustype = HAL_BUS_TYPE_PCI;
sc->devid = id->device;
adf_os_spinlock_init(&sc->target_lock);
sc->cacheline_sz = dma_get_cache_alignment();
pci_read_config_word(pdev, 0x08, &revision_id);
switch (id->device) {
case AR9888_DEVICE_ID:
hif_type = HIF_TYPE_AR9888;
target_type = TARGET_TYPE_AR9888;
break;
case AR6320_DEVICE_ID:
switch(revision_id) {
case AR6320_FW_1_1:
case AR6320_FW_1_3:
hif_type = HIF_TYPE_AR6320;
target_type = TARGET_TYPE_AR6320;
break;
case AR6320_FW_2_0:
hif_type = HIF_TYPE_AR6320V2;
target_type = TARGET_TYPE_AR6320V2;
break;
default:
printk(KERN_ERR "unsupported revision id\n");
ret = -ENODEV;
goto err_tgtstate;
}
break;
default:
printk(KERN_ERR "unsupported device id\n");
ret = -ENODEV;
goto err_tgtstate;
}
/*
* 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).
*/
hif_register_tbl_attach(sc, hif_type);
target_register_tbl_attach(sc, target_type);
{
A_UINT32 fw_indicator;
#if PCIE_BAR0_READY_CHECKING
int wait_limit = 200;
#endif
/*
* Verify that the Target was started cleanly.
*
* The case where this is most likely is with an AUX-powered
* Target and a Host in WoW mode. If the Host crashes,
* loses power, or is restarted (without unloading the driver)
* then the Target is left (aux) powered and running. On a
* subsequent driver load, the Target is in an unexpected state.
* We try to catch that here in order to reset the Target and
* retry the probe.
*/
A_PCI_WRITE32(mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
while (!hif_pci_targ_is_awake(sc, mem)) {
;
}
#if PCIE_BAR0_READY_CHECKING
/* Synchronization point: wait the BAR0 is configured */
while (wait_limit-- &&
!(A_PCI_READ32(mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_RDY_STATUS_ADDRESS) \
& PCIE_SOC_RDY_STATUS_BAR_MASK)) {
A_MDELAY(10);
}
if (wait_limit < 0) {
/* AR6320v1 doesn't support checking of BAR0 configuration,
takes one sec to wait BAR0 ready */
printk(KERN_INFO "AR6320v1 waits two sec for BAR0 ready.\n");
}
#endif
fw_indicator = A_PCI_READ32(mem + FW_INDICATOR_ADDRESS);
A_PCI_WRITE32(mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
if (fw_indicator & FW_IND_INITIALIZED) {
probe_again++;
printk(KERN_ERR "ath: Target is in an unknown state. Resetting (attempt %d).\n", probe_again);
/* hif_pci_device_reset, below, will reset the target */
ret = -EIO;
goto err_tgtstate;
}
}
ol_sc = A_MALLOC(sizeof(*ol_sc));
if (!ol_sc)
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;
if (hif_pci_configure(sc, &ol_sc->hif_hdl))
goto err_config;
ol_sc->enableuartprint = 0;
ol_sc->enablefwlog = 0;
ol_sc->enablesinglebinary = FALSE;
ol_sc->max_no_of_peers = 1;
adf_os_atomic_init(&sc->tasklet_from_intr);
init_waitqueue_head(&ol_sc->sc_osdev->event_queue);
init_completion(&ol_sc->ramdump_event);
ret = hdd_wlan_startup(&pdev->dev, ol_sc);
if (ret) {
hif_nointrs(sc);
HIFShutDownDevice(ol_sc->hif_hdl);
goto err_config;
}
/* Re-enable ASPM after firmware/OTP download is complete */
pci_write_config_dword(pdev, 0x80, lcr_val);
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE) {
/*
* pktlog initialization
*/
ol_pl_sethandle(&ol_sc->pdev_txrx_handle->pl_dev, ol_sc);
if (pktlogmod_init(ol_sc))
printk(KERN_ERR "%s: pktlogmod_init failed\n", __func__);
}
#endif
#ifdef WLAN_BTAMP_FEATURE
/* Send WLAN UP indication to Nlink Service */
send_btc_nlink_msg(WLAN_MODULE_UP_IND, 0);
#endif
return 0;
err_config:
A_FREE(ol_sc);
err_attach:
ret = -EIO;
err_tgtstate:
pci_set_drvdata(pdev, NULL);
hif_pci_device_reset(sc);
A_FREE(sc);
err_alloc:
/* call HIF PCI free here */
printk("%s: HIF PCI Free needs to happen here \n", __func__);
pci_iounmap(pdev, mem);
err_iomap:
pci_clear_master(pdev);
err_dma:
pci_release_region(pdev, BAR_NUM);
err_region:
pci_disable_device(pdev);
if (probe_again && (probe_again <= ATH_PCI_PROBE_RETRY_MAX)) {
int delay_time;
/*
* We can get here after a Host crash or power fail when
* the Target has aux power. We just did a device_reset,
* so we need to delay a short while before we try to
* reinitialize. Typically only one retry with the smallest
* delay is needed. Target should never need more than a 100Ms
* delay; that would not conform to the PCIe std.
*/
printk(KERN_INFO "pci reprobe.\n");
delay_time = max(100, 10 * (probe_again * probe_again)); /* 10, 40, 90, 100, 100, ... */
A_MDELAY(delay_time);
goto again;
}
return ret;
}
/* This function will be called when SSR frame work wants to
* power up WLAN host driver when SSR happens. Most of this
* function is duplicated from hif_pci_probe().
*/
#if defined(QCA_WIFI_2_0) && !defined(QCA_WIFI_ISOC)
int hif_pci_reinit(struct pci_dev *pdev, const struct pci_device_id *id)
{
void __iomem *mem;
struct hif_pci_softc *sc;
struct ol_softc *ol_sc;
int probe_again = 0;
int ret = 0;
u_int16_t device_id;
u_int32_t hif_type;
u_int32_t target_type;
u_int32_t lcr_val;
u_int16_t revision_id;
again:
ret = 0;
#define BAR_NUM 0
/*
* Without any knowledge of the Host, the Target
* may have been reset or power cycled and its
* Config Space may no longer reflect the PCI
* address space that was assigned earlier
* by the PCI infrastructure. Refresh it now.
*/
/* If PCI link is down, return from reinit() */
pci_read_config_word(pdev,PCI_DEVICE_ID,&device_id);
printk("PCI device id is %04x :%04x\n", device_id, id->device);
if (device_id != id->device) {
printk(KERN_ERR "%s: PCI link is down!\n", __func__);
/* PCI link is down, so return with error code. */
return -EIO;
}
/* FIXME: Commenting out assign_resource
* call for dev_attach to work on 2.6.38 kernel
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,0,0) && !defined(__LINUX_ARM_ARCH__)
if (pci_assign_resource(pdev, BAR_NUM)) {
printk(KERN_ERR "%s: Cannot assign PCI space!\n", __func__);
return -EIO;
}
#endif
if (pci_enable_device(pdev)) {
printk(KERN_ERR "%s: Cannot enable PCI device!\n", __func__);
return -EIO;
}
/* Request MMIO resources */
ret = pci_request_region(pdev, BAR_NUM, "ath");
if (ret) {
dev_err(&pdev->dev, "%s: PCI MMIO reservation error!\n", __func__);
ret = -EIO;
goto err_region;
}
#ifdef CONFIG_ARM_LPAE
/* if CONFIG_ARM_LPAE is enabled, we have to set 64 bits mask
* for 32 bits device also. */
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(64));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 64-bit pci DMA\n");
goto err_dma;
}
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 64-bit consistent DMA\n");
goto err_dma;
}
#else
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_ERR "ath: Cannot enable 32-bit pci DMA\n");
goto err_dma;
}
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_ERR "%s: Cannot enable 32-bit consistent DMA!\n",
__func__);
goto err_dma;
}
#endif
/* Set bus master bit in PCI_COMMAND to enable DMA */
pci_set_master(pdev);
/* Temporary FIX: disable ASPM. Will be removed after
the OTP is programmed. */
pci_read_config_dword(pdev, 0x80, &lcr_val);
pci_write_config_dword(pdev, 0x80, (lcr_val & 0xffffff00));
/* Arrange for access to Target SoC registers */
mem = pci_iomap(pdev, BAR_NUM, 0);
if (!mem) {
printk(KERN_ERR "%s: PCI iomap error!\n", __func__) ;
ret = -EIO;
goto err_iomap;
}
sc = A_MALLOC(sizeof(*sc));
if (!sc) {
ret = -ENOMEM;
goto err_alloc;
}
OS_MEMZERO(sc, sizeof(*sc));
sc->mem = mem;
sc->pdev = pdev;
sc->dev = &pdev->dev;
sc->aps_osdev.bdev = pdev;
sc->aps_osdev.device = &pdev->dev;
sc->aps_osdev.bc.bc_handle = (void *)mem;
sc->aps_osdev.bc.bc_bustype = HAL_BUS_TYPE_PCI;
sc->devid = id->device;
adf_os_spinlock_init(&sc->target_lock);
sc->cacheline_sz = dma_get_cache_alignment();
pci_read_config_word(pdev, 0x08, &revision_id);
switch (id->device) {
case AR9888_DEVICE_ID:
hif_type = HIF_TYPE_AR9888;
target_type = TARGET_TYPE_AR9888;
break;
case AR6320_DEVICE_ID:
switch(revision_id) {
case AR6320_FW_1_1:
case AR6320_FW_1_3:
hif_type = HIF_TYPE_AR6320;
target_type = TARGET_TYPE_AR6320;
break;
case AR6320_FW_2_0:
hif_type = HIF_TYPE_AR6320V2;
target_type = TARGET_TYPE_AR6320V2;
break;
default:
printk(KERN_ERR "unsupported revision id\n");
ret = -ENODEV;
goto err_tgtstate;
}
break;
default:
printk(KERN_ERR "%s: Unsupported device ID!\n", __func__);
ret = -ENODEV;
goto err_tgtstate;
}
/*
* 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).
*/
hif_register_tbl_attach(sc, hif_type);
target_register_tbl_attach(sc, target_type);
{
A_UINT32 fw_indicator;
#if PCIE_BAR0_READY_CHECKING
int wait_limit = 200;
#endif
/*
* Verify that the Target was started cleanly.
*
* The case where this is most likely is with an AUX-powered
* Target and a Host in WoW mode. If the Host crashes,
* loses power, or is restarted (without unloading the driver)
* then the Target is left (aux) powered and running. On a
* subsequent driver load, the Target is in an unexpected state.
* We try to catch that here in order to reset the Target and
* retry the probe.
*/
A_PCI_WRITE32(mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS,
PCIE_SOC_WAKE_V_MASK);
while (!hif_pci_targ_is_awake(sc, mem)) {
;
}
#if PCIE_BAR0_READY_CHECKING
/* Synchronization point: wait the BAR0 is configured. */
while (wait_limit-- &&
!(A_PCI_READ32(mem + PCIE_LOCAL_BASE_ADDRESS +
PCIE_SOC_RDY_STATUS_ADDRESS) & PCIE_SOC_RDY_STATUS_BAR_MASK)) {
A_MDELAY(10);
}
if (wait_limit < 0) {
/* AR6320v1 doesn't support checking of BAR0 configuration,
takes one sec to wait BAR0 ready. */
printk(KERN_INFO "AR6320v1 waits two sec for BAR0 ready.\n");
}
#endif
fw_indicator = A_PCI_READ32(mem + FW_INDICATOR_ADDRESS);
A_PCI_WRITE32(mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS,
PCIE_SOC_WAKE_RESET);
if (fw_indicator & FW_IND_INITIALIZED) {
probe_again++;
printk(KERN_ERR "%s: Target is in an unknown state. "
"Resetting (attempt %d).\n", __func__, probe_again);
/* hif_pci_device_reset, below will reset the target. */
ret = -EIO;
goto err_tgtstate;
}
}
ol_sc = A_MALLOC(sizeof(*ol_sc));
if (!ol_sc)
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;
if (hif_pci_configure(sc, &ol_sc->hif_hdl))
goto err_config;
ol_sc->enableuartprint = 0;
ol_sc->enablefwlog = 0;
ol_sc->enablesinglebinary = FALSE;
ol_sc->max_no_of_peers = 1;
adf_os_atomic_init(&sc->tasklet_from_intr);
init_waitqueue_head(&ol_sc->sc_osdev->event_queue);
init_completion(&ol_sc->ramdump_event);
if (VOS_STATUS_SUCCESS == hdd_wlan_re_init(ol_sc)) {
ret = 0;
}
if (ret) {
hif_nointrs(sc);
goto err_config;
}
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE) {
/*
* pktlog initialization
*/
ol_pl_sethandle(&ol_sc->pdev_txrx_handle->pl_dev, ol_sc);
if (pktlogmod_init(ol_sc))
printk(KERN_ERR "%s: pktlogmod_init failed!\n", __func__);
}
#endif
#ifdef WLAN_BTAMP_FEATURE
/* Send WLAN UP indication to Nlink Service */
send_btc_nlink_msg(WLAN_MODULE_UP_IND, 0);
#endif
printk("%s: WLAN host driver reinitiation completed!\n", __func__);
return 0;
err_config:
A_FREE(ol_sc);
err_attach:
ret = -EIO;
err_tgtstate:
pci_set_drvdata(pdev, NULL);
hif_pci_device_reset(sc);
A_FREE(sc);
err_alloc:
/* Call HIF PCI free here */
printk("%s: HIF PCI free needs to happen here.\n", __func__);
pci_iounmap(pdev, mem);
err_iomap:
pci_clear_master(pdev);
err_dma:
pci_release_region(pdev, BAR_NUM);
err_region:
pci_disable_device(pdev);
if (probe_again && (probe_again <= ATH_PCI_PROBE_RETRY_MAX)) {
int delay_time;
/*
* We can get here after a Host crash or power fail when
* the Target has aux power. We just did a device_reset,
* so we need to delay a short while before we try to
* reinitialize. Typically only one retry with the smallest
* delay is needed. Target should never need more than a 100Ms
* delay; that would not conform to the PCIe std.
*/
printk(KERN_INFO "%s: PCI rereinit\n", __func__);
/* 10, 40, 90, 100, 100, ... */
delay_time = max(100, 10 * (probe_again * probe_again));
A_MDELAY(delay_time);
goto again;
}
return ret;
}
#endif
void
hif_nointrs(struct hif_pci_softc *sc)
{
int i;
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);
}
sc->num_msi_intrs = 0;
} else {
/* Legacy PCI line interrupt */
free_irq(sc->pdev->irq, sc);
}
}
int
hif_pci_configure(struct hif_pci_softc *sc, hif_handle_t *hif_hdl)
{
int ret = 0;
int num_msi_desired;
u_int32_t val;
BUG_ON(pci_get_drvdata(sc->pdev) != NULL);
pci_set_drvdata(sc->pdev, sc);
tasklet_init(&sc->intr_tq, wlan_tasklet, (unsigned long)sc);
/*
* Interrupt Management is divided into these scenarios :
* A) We wish to use MSI and Multiple MSI is supported and we
* are able to obtain the number of MSI interrupts desired
* (best performance)
* B) We wish to use MSI and Single MSI is supported and we are
* able to obtain a single MSI interrupt
* C) We don't want to use MSI or MSI is not supported and we
* are able to obtain a legacy interrupt
* D) Failure
*/
#if defined(FORCE_LEGACY_PCI_INTERRUPTS)
num_msi_desired = 0; /* Use legacy PCI line interrupts rather than MSI */
#else
num_msi_desired = MSI_NUM_REQUEST; /* Multiple MSI */
if (!msienable) {
num_msi_desired = 0;
}
#endif
printk("\n %s : num_desired MSI set to %d\n", __func__, num_msi_desired);
if (num_msi_desired > 1) {
int i;
int rv;
rv = pci_enable_msi_block(sc->pdev, MSI_NUM_REQUEST);
if (rv == 0) { /* successfully allocated all MSI interrupts */
/*
* TBDXXX: This path not yet tested,
* since Linux x86 does not currently
* support "Multiple MSIs".
*/
sc->num_msi_intrs = MSI_NUM_REQUEST;
ret = request_irq(sc->pdev->irq+MSI_ASSIGN_FW, hif_pci_msi_fw_handler,
IRQF_SHARED, "wlan_pci", sc);
if(ret) {
dev_err(&sc->pdev->dev, "request_irq failed\n");
goto err_intr;
}
for (i=MSI_ASSIGN_CE_INITIAL; i<=MSI_ASSIGN_CE_MAX; i++) {
ret = request_irq(sc->pdev->irq+i, CE_per_engine_handler, IRQF_SHARED,
"wlan_pci", sc);
if(ret) {
dev_err(&sc->pdev->dev, "request_irq failed\n");
goto err_intr;
}
}
} else {
if (rv < 0) {
/* Can't get any MSI -- try for legacy line interrupts */
num_msi_desired = 0;
} else {
/* Can't get enough MSI interrupts -- try for just 1 */
printk("\n %s : Can't allocate requested number of MSI, just use 1\n", __func__);
num_msi_desired = 1;
}
}
}
if (num_msi_desired == 1) {
/*
* We are here because either the platform only supports
* single MSI OR because we couldn't get all the MSI interrupts
* that we wanted so we fall back to a single MSI.
*/
if (pci_enable_msi(sc->pdev) < 0) {
printk(KERN_ERR "ath: single MSI interrupt allocation failed\n");
/* Try for legacy PCI line interrupts */
num_msi_desired = 0;
} else {
/* Use a single Host-side MSI interrupt handler for all interrupts */
num_msi_desired = 1;
}
}
if ( num_msi_desired <= 1) {
/* We are here because we want to multiplex a single host interrupt among all
* Target interrupt sources
*/
ret = request_irq(sc->pdev->irq, hif_pci_interrupt_handler, IRQF_SHARED,
"wlan_pci", sc);
if(ret) {
dev_err(&sc->pdev->dev, "request_irq failed\n");
goto err_intr;
}
}
#if CONFIG_PCIE_64BIT_MSI
{
struct ol_ath_softc_net80211 *scn = sc->scn;
u_int8_t MSI_flag;
u_int32_t reg;
#define OL_ATH_PCI_MSI_POS 0x50
#define MSI_MAGIC_RDY_MASK 0x00000001
#define MSI_MAGIC_EN_MASK 0x80000000
pci_read_config_byte(sc->pdev, OL_ATH_PCI_MSI_POS + PCI_MSI_FLAGS, &MSI_flag);
if (MSI_flag & PCI_MSI_FLAGS_ENABLE) {
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
while (!ath_pci_targ_is_awake(sc->mem)) {
;
}
scn->MSI_magic = OS_MALLOC_CONSISTENT(scn->sc_osdev, 4, &scn->MSI_magic_dma, \
OS_GET_DMA_MEM_CONTEXT(scn, MSI_dmacontext), 0);
A_PCI_WRITE32(sc->mem + SOC_PCIE_BASE_ADDRESS + MSI_MAGIC_ADR_ADDRESS,
scn->MSI_magic_dma);
reg = A_PCI_READ32(sc->mem + SOC_PCIE_BASE_ADDRESS + MSI_MAGIC_ADDRESS);
A_PCI_WRITE32(sc->mem + SOC_PCIE_BASE_ADDRESS + MSI_MAGIC_ADDRESS, reg | MSI_MAGIC_RDY_MASK);
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
}
}
#endif
if(num_msi_desired == 0) {
printk("\n Using PCI Legacy Interrupt\n");
/* Make sure to wake the Target before enabling Legacy Interrupt */
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS,
PCIE_SOC_WAKE_V_MASK);
while (!hif_pci_targ_is_awake(sc, sc->mem)) {
;
}
/* Use Legacy PCI Interrupts */
/*
* A potential race occurs here: The CORE_BASE write depends on
* target correctly decoding AXI address but host won't know
* when target writes BAR to CORE_CTRL. This write might get lost
* if target has NOT written BAR. For now, fix the race by repeating
* the write in below synchronization checking.
*/
A_PCI_WRITE32(sc->mem+(SOC_CORE_BASE_ADDRESS |
PCIE_INTR_ENABLE_ADDRESS),
PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS,
PCIE_SOC_WAKE_RESET);
}
sc->num_msi_intrs = num_msi_desired;
sc->ce_count = CE_COUNT;
{ /* Synchronization point: Wait for Target to finish initialization before we proceed. */
int wait_limit = 1000; /* 10 sec */
/* Make sure to wake Target before accessing Target memory */
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_V_MASK);
while (!hif_pci_targ_is_awake(sc, sc->mem)) {
;
}
while (wait_limit-- && !(A_PCI_READ32(sc->mem + FW_INDICATOR_ADDRESS) & FW_IND_INITIALIZED)) {
if (num_msi_desired == 0) {
/* Fix potential race by repeating CORE_BASE writes */
A_PCI_WRITE32(sc->mem + (SOC_CORE_BASE_ADDRESS | PCIE_INTR_ENABLE_ADDRESS),
PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL);
}
A_MDELAY(10);
}
if (wait_limit < 0) {
printk(KERN_ERR "ath: %s: TARGET STALLED: .\n", __FUNCTION__);
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
ret = -EIO;
goto err_stalled;
}
A_PCI_WRITE32(sc->mem + PCIE_LOCAL_BASE_ADDRESS + PCIE_SOC_WAKE_ADDRESS, PCIE_SOC_WAKE_RESET);
}
if (HIF_PCIDeviceProbed(sc)) {
printk(KERN_ERR "ath: %s: Target probe failed.\n", __FUNCTION__);
ret = -EIO;
goto err_stalled;
}
*hif_hdl = sc->hif_device;
/*
* Flag to avoid potential unallocated memory access from MSI
* interrupt handler which could get scheduled as soon as MSI
* is enabled, i.e to take care of the race due to the order
* in where MSI is enabled before the memory, that will be
* in interrupt handlers, is allocated.
*/
sc->hif_init_done = TRUE;
return 0;
err_stalled:
/* Read Target CPU Intr Cause for debug */
val = A_PCI_READ32(sc->mem + (SOC_CORE_BASE_ADDRESS | CPU_INTR_ADDRESS));
printk("ERROR: Target Stalled : Target CPU Intr Cause 0x%x \n", val);
hif_nointrs(sc);
err_intr:
if (num_msi_desired) {
pci_disable_msi(sc->pdev);
}
pci_set_drvdata(sc->pdev, NULL);
return ret;
}
void
hif_pci_remove(struct pci_dev *pdev)
{
struct hif_pci_softc *sc = pci_get_drvdata(pdev);
struct ol_softc *scn;
void __iomem *mem;
/* Attach did not succeed, all resources have been
* freed in error handler
*/
if (!sc)
return;
scn = sc->ol_sc;
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE)
pktlogmod_exit(scn);
#endif
__hdd_wlan_exit();
mem = (void __iomem *)sc->mem;
#if defined(CPU_WARM_RESET_WAR)
/* Currently CPU warm reset sequence is tested only for AR9888_REV2
* Need to enable for AR9888_REV1 once CPU warm reset sequence is
* verified for AR9888_REV1
*/
if (scn->target_version == AR9888_REV2_VERSION) {
hif_pci_device_warm_reset(sc);
}
else {
hif_pci_device_reset(sc);
}
#else
hif_pci_device_reset(sc);
#endif
pci_disable_msi(pdev);
A_FREE(scn);
A_FREE(sc);
pci_set_drvdata(pdev, NULL);
pci_iounmap(pdev, mem);
pci_release_region(pdev, BAR_NUM);
pci_clear_master(pdev);
pci_disable_device(pdev);
printk(KERN_INFO "pci_remove\n");
}
/* This function will be called when SSR framework wants to
* shutdown WLAN host driver when SSR happens. Most of this
* function is duplicated from hif_pci_remove().
*/
#if defined(QCA_WIFI_2_0) && !defined(QCA_WIFI_ISOC)
void hif_pci_shutdown(struct pci_dev *pdev)
{
void __iomem *mem;
struct hif_pci_softc *sc;
struct ol_softc *scn;
sc = pci_get_drvdata(pdev);
/* Attach did not succeed, all resources have been
* freed in error handler.
*/
if (!sc)
return;
scn = sc->ol_sc;
#ifndef REMOVE_PKT_LOG
if (vos_get_conparam() != VOS_FTM_MODE)
pktlogmod_exit(scn);
#endif
hdd_wlan_shutdown();
mem = (void __iomem *)sc->mem;
#if defined(CPU_WARM_RESET_WAR)
/* Currently CPU warm reset sequence is tested only for AR9888_REV2
* Need to enable for AR9888_REV1 once CPU warm reset sequence is
* verified for AR9888_REV1.
*/
if (scn->target_version == AR9888_REV2_VERSION) {
hif_pci_device_warm_reset(sc);
}
else {
hif_pci_device_reset(sc);
}
#else
hif_pci_device_reset(sc);
#endif
pci_disable_msi(pdev);
A_FREE(scn);
A_FREE(sc);
pci_set_drvdata(pdev, NULL);
pci_iounmap(pdev, mem);
pci_release_region(pdev, BAR_NUM);
pci_clear_master(pdev);
pci_disable_device(pdev);
printk("%s: WLAN host driver shutting down completed!\n", __func__);
}
void hif_pci_crash_shutdown(struct pci_dev *pdev)
{
struct hif_pci_softc *sc;
struct ol_softc *scn;
int status;
sc = pci_get_drvdata(pdev);
if (!sc)
return;
scn = sc->ol_sc;
if (!scn)
return;
if (OL_TRGET_STATUS_RESET == scn->target_status) {
printk("%s: Target is already asserted, ignore!\n", __func__);
return;
}
scn->crash_shutdown = true;
process_wma_set_command(0,(int)GEN_PARAM_CRASH_INJECT,
0, GEN_CMD);
status = wait_for_completion_interruptible_timeout(
&scn->ramdump_event,
msecs_to_jiffies(RAMDUMP_EVENT_TIMEOUT));
if (!status) {
printk("%s: RAM dump collecting timeout!\n", __func__);
return;
}
}
#endif
#define OL_ATH_PCI_PM_CONTROL 0x44
static int
hif_pci_suspend(struct pci_dev *pdev, pm_message_t state)
{
struct hif_pci_softc *sc = pci_get_drvdata(pdev);
void *vos = vos_get_global_context(VOS_MODULE_ID_HIF, NULL);
ol_txrx_pdev_handle txrx_pdev = vos_get_context(VOS_MODULE_ID_TXRX, vos);
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
A_target_id_t targid = hif_state->targid;
u32 tx_drain_wait_cnt = 0;
u32 val;
v_VOID_t * temp_module;
A_TARGET_ACCESS_BEGIN(targid);
A_PCI_WRITE32(sc->mem + FW_INDICATOR_ADDRESS, (state.event << 16));
A_TARGET_ACCESS_END(targid);
if (!txrx_pdev) {
printk("%s: txrx_pdev is NULL\n", __func__);
return (-1);
}
/* Wait for pending tx completion */
while (ol_txrx_get_tx_pending(txrx_pdev)) {
msleep(OL_ATH_TX_DRAIN_WAIT_DELAY);
if (++tx_drain_wait_cnt > OL_ATH_TX_DRAIN_WAIT_CNT) {
printk("%s: tx frames are pending\n", __func__);
return (-1);
}
}
/* No need to send WMI_PDEV_SUSPEND_CMDID to FW if WOW is enabled */
temp_module = vos_get_context(VOS_MODULE_ID_WDA, vos);
if (!temp_module) {
printk("%s: WDA module is NULL\n", __func__);
return (-1);
}
if (wma_is_wow_mode_selected(temp_module)) {
if(wma_enable_wow_in_fw(temp_module))
return (-1);
} else if (state.event == PM_EVENT_FREEZE || state.event == PM_EVENT_SUSPEND) {
if (wma_suspend_target(temp_module, 0))
return (-1);
}
pci_read_config_dword(pdev, OL_ATH_PCI_PM_CONTROL, &val);
if ((val & 0x000000ff) != 0x3) {
pci_save_state(pdev);
pci_disable_device(pdev);
pci_write_config_dword(pdev, OL_ATH_PCI_PM_CONTROL, (val & 0xffffff00) | 0x03);
}
return 0;
}
static int
hif_pci_resume(struct pci_dev *pdev)
{
struct hif_pci_softc *sc = pci_get_drvdata(pdev);
void *vos_context = vos_get_global_context(VOS_MODULE_ID_HIF, NULL);
struct HIF_CE_state *hif_state = (struct HIF_CE_state *)sc->hif_device;
A_target_id_t targid = hif_state->targid;
u32 val;
int err;
v_VOID_t * temp_module;
err = pci_enable_device(pdev);
if (err)
return err;
pci_read_config_dword(pdev, OL_ATH_PCI_PM_CONTROL, &val);
if ((val & 0x000000ff) != 0) {
pci_restore_state(pdev);
pci_write_config_dword(pdev, OL_ATH_PCI_PM_CONTROL, val & 0xffffff00);
/*
* Suspend/Resume resets the PCI configuration space, so we have to
* re-disable the RETRY_TIMEOUT register (0x41) to keep
* PCI Tx retries from interfering with C3 CPU state
*
*/
pci_read_config_dword(pdev, 0x40, &val);
if ((val & 0x0000ff00) != 0)
pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
}
A_TARGET_ACCESS_BEGIN(targid);
val = A_PCI_READ32(sc->mem + FW_INDICATOR_ADDRESS) >> 16;
A_TARGET_ACCESS_END(targid);
/* No need to send WMI_PDEV_RESUME_CMDID to FW if WOW is enabled */
temp_module = vos_get_context(VOS_MODULE_ID_WDA, vos_context);
if (!temp_module) {
printk("%s: WDA module is NULL\n", __func__);
return (-1);
}
if (!wma_is_wow_mode_selected(temp_module) &&
(val == PM_EVENT_HIBERNATE || val == PM_EVENT_SUSPEND)) {
return wma_resume_target(temp_module);
}
return 0;
}
/* routine to modify the initial buffer count to be allocated on an os
* platform basis. Platform owner will need to modify this as needed
*/
adf_os_size_t initBufferCount(adf_os_size_t maxSize)
{
return maxSize;
}
#ifdef CONFIG_CNSS
struct cnss_wlan_driver cnss_wlan_drv_id = {
.name = "hif_pci",
.id_table = hif_pci_id_table,
.probe = hif_pci_probe,
.remove = hif_pci_remove,
.reinit = hif_pci_reinit,
.shutdown = hif_pci_shutdown,
.crash_shutdown = hif_pci_crash_shutdown,
#ifdef ATH_BUS_PM
.suspend = hif_pci_suspend,
.resume = hif_pci_resume,
#endif
};
#else
MODULE_DEVICE_TABLE(pci, hif_pci_id_table);
struct pci_driver hif_pci_drv_id = {
.name = "hif_pci",
.id_table = hif_pci_id_table,
.probe = hif_pci_probe,
.remove = hif_pci_remove,
#ifdef ATH_BUS_PM
.suspend = hif_pci_suspend,
.resume = hif_pci_resume,
#endif
};
#endif
int hif_register_driver(void)
{
#ifdef CONFIG_CNSS
return cnss_wlan_register_driver(&cnss_wlan_drv_id);
#else
return pci_register_driver(&hif_pci_drv_id);
#endif
}
void hif_unregister_driver(void)
{
#ifdef CONFIG_CNSS
cnss_wlan_unregister_driver(&cnss_wlan_drv_id);
#else
pci_unregister_driver(&hif_pci_drv_id);
#endif
}
void hif_init_pdev_txrx_handle(void *ol_sc, void *txrx_handle)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
sc->pdev_txrx_handle = txrx_handle;
}
void hif_disable_isr(void *ol_sc)
{
struct ol_softc *sc = (struct ol_softc *)ol_sc;
struct hif_pci_softc *hif_sc = sc->hif_sc;
struct ol_softc *scn;
scn = hif_sc->ol_sc;
hif_nointrs(hif_sc);
#if CONFIG_PCIE_64BIT_MSI
OS_FREE_CONSISTENT(scn->sc_osdev, 4, scn->MSI_magic, scn->MSI_magic_dma,
OS_GET_DMA_MEM_CONTEXT(scn, MSI_dmacontext));
scn->MSI_magic = NULL;
scn->MSI_magic_dma = 0;
#endif
/* Cancel the pending tasklet */
tasklet_kill(&hif_sc->intr_tq);
}