blob: aa958ec335d67ad3597e5008d205e447bb295589 [file] [log] [blame]
/*
* Copyright (c) 2015 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.
*/
#ifdef HIF_PCI
#include "icnss_stub.h"
#include "hif_io32.h"
#include <hif.h>
#include "regtable.h"
#include "hif_debug.h"
#include "cds_api.h"
#include "cdf_status.h"
#include "qwlan_version.h"
#include <net/cnss.h>
static int icnss_get_irq_num(int ce_id);
/**
* struct icnss_stub_entry
*
* @irq_handler: irq_handler
* @data: data
* @name: name
* @ce_id: ce_id
*/
struct icnss_stub_entry {
irqreturn_t (*irq_handler)(int, void *);
void *data;
const char *name;
int ce_id;
};
/**
* struct icnss_stub_context
*
* @stub: icnss_stub_entry
* @regged_irq: regged_irq
*/
struct icnss_stub_context {
struct icnss_stub_entry stub[ICNSS_MAX_IRQ_REGISTRATIONS];
uint32_t regged_irq;
};
static struct icnss_stub_context cnss_stub;
#ifndef QCA_WIFI_3_0_ADRASTEA
/**
* icnss_wlan_enable() - icnss_wlan_enable
* @config: ce configuration information
* @mode: driver_mode
* @host_version: version string to send to the fw
*
* Return: int
*/
int icnss_wlan_enable(struct icnss_wlan_enable_cfg *config,
enum icnss_driver_mode mode, const char *host_version)
{
return 0;
}
/**
* icnss_wlan_disable() - icnss_wlan_disable
* @mode: driver_mode
*
* Return: int
*/
int icnss_wlan_disable(enum icnss_driver_mode mode)
{
return 0;
}
/**
* icnss_set_fw_debug_mode() - icnss_set_fw_debug_mode
* @mode: fw debug mode, 0 for QXDM, 1 for WMI
*
* Return: int
*/
int icnss_set_fw_debug_mode(bool mode)
{
return 0;
}
#else
/**
* icnss_wlan_enable(): call the platform driver to enable wlan
* @config: ce configuration information
* @mode: driver_mode
* @host_version: version string to send to the fw
*
* This function passes the con_mode and CE configuration to
* platform driver to enable wlan.
* cnss_wlan_enable has been hacked to do a qmi handshake with fw.
* this is not needed for rome.
*
* Return: 0 on success, error number otherwise.
*/
int icnss_wlan_enable(struct icnss_wlan_enable_cfg *config,
enum icnss_driver_mode mode, const char *host_version)
{
struct cnss_wlan_enable_cfg cfg;
enum cnss_driver_mode cnss_mode;
cfg.num_ce_tgt_cfg = config->num_ce_tgt_cfg;
cfg.ce_tgt_cfg = (struct cnss_ce_tgt_pipe_cfg *)
config->ce_tgt_cfg;
cfg.num_ce_svc_pipe_cfg = config->num_ce_svc_pipe_cfg;
cfg.ce_svc_cfg = (struct cnss_ce_svc_pipe_cfg *)
config->ce_svc_cfg;
cfg.num_shadow_reg_cfg = config->num_shadow_reg_cfg;
cfg.shadow_reg_cfg = (struct cnss_shadow_reg_cfg *)
config->shadow_reg_cfg;
switch (mode) {
case ICNSS_FTM:
cnss_mode = CNSS_FTM;
break;
case ICNSS_EPPING:
cnss_mode = CNSS_EPPING;
break;
default:
cnss_mode = CNSS_MISSION;
break;
}
return cnss_wlan_enable(&cfg, cnss_mode, host_version);
}
/**
* icnss_wlan_disable(): call the platform driver to disable wlan
*
* This function passes the con_mode to platform driver to disable wlan.
* cnss_wlan_disable has been hacked to do a qmi handshake with fw.
* this is not needed for rome.
*
* Return: void
*/
int icnss_wlan_disable(enum icnss_driver_mode con_mode)
{
enum cnss_driver_mode mode;
switch (con_mode) {
case ICNSS_FTM:
mode = CNSS_FTM;
break;
case ICNSS_EPPING:
mode = CNSS_EPPING;
break;
default:
mode = CNSS_MISSION;
break;
}
cnss_wlan_disable(mode);
return 0;
}
/**
* icnss_set_fw_debug_mode() - call the platform driver to set fw
* debug mode
* @mode: fw debug mode, 0 for QXDM, 1 for WMI
*
* This function passes the fw debug mode to platform driver.
* cnss_set_fw_debug_mode has been hacked to do a qmi handshake with fw.
* This is not needed for rome.
*
* Return: int
*/
int icnss_set_fw_debug_mode(bool mode)
{
return cnss_set_fw_debug_mode(mode);
}
#endif
/**
* icnss_ce_request_irq() - register an irq handler
* @ce_id: ce_id
* @handler: handler
* @flags: flags to pass to the kernel api
* @name: name
* @context: context to pass to the irq handler
*
* Return: integer status
*/
int icnss_ce_request_irq(int ce_id,
irqreturn_t (*handler)(int, void *),
unsigned long flags, const char *name,
void *context)
{
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
HIF_ERROR("%s: invalid ce_id = %d", __func__, ce_id);
return -EINVAL;
}
cnss_stub.stub[ce_id].irq_handler = handler;
cnss_stub.stub[ce_id].ce_id = ce_id;
cnss_stub.stub[ce_id].data = context;
cnss_stub.stub[ce_id].name = name;
cnss_stub.regged_irq |= (1 << ce_id);
return 0;
}
/**
* icnss_ce_free_irq() - icnss_unregister_irq
* @ce_id: the ce_id that the irq belongs to
* @context: context with witch the irq was requested.
* Return: integer status
*/
int icnss_ce_free_irq(int ce_id, void *context)
{
if (ce_id >= ICNSS_MAX_IRQ_REGISTRATIONS) {
HIF_ERROR("%s: invalid ce_id = %d", __func__, ce_id);
return -EINVAL;
}
if (cnss_stub.stub[ce_id].data != context) {
HIF_ERROR("%s: context match failure for ce_id %d",
__func__, ce_id);
return -EINVAL;
}
if (cnss_stub.regged_irq & (1 << ce_id)) {
cnss_stub.stub[ce_id].irq_handler = NULL;
cnss_stub.stub[ce_id].ce_id = 0;
cnss_stub.stub[ce_id].data = 0;
cnss_stub.stub[ce_id].name = NULL;
cnss_stub.regged_irq &= ~(1 << ce_id);
}
return 0;
}
/**
* icnss_dispatch_one_ce_irq() - icnss_dispatch_one_ce_irq
* @ce_id: ce_id
*
* Return: irqreturn_t
*/
static irqreturn_t icnss_dispatch_one_ce_irq(int ce_id)
{
irqreturn_t ret = IRQ_NONE;
if (cnss_stub.stub[ce_id].irq_handler)
ret = cnss_stub.stub[ce_id].irq_handler(
icnss_get_irq_num(ce_id),
(void *)cnss_stub.stub[ce_id].data);
else
HIF_ERROR(
"%sd: error - ce_id = %d, no IRQ handler",
__func__, ce_id);
return ret;
}
/**
* icnss_dispatch_ce_irq() - icnss_dispatch_ce_irq
* @scn: scn
*
* Return: N/A
*/
void icnss_dispatch_ce_irq(struct ol_softc *scn)
{
uint32_t intr_summary;
int id;
irqreturn_t ret;
if (scn->hif_init_done != true)
return;
A_TARGET_ACCESS_BEGIN(scn);
intr_summary = CE_INTERRUPT_SUMMARY(scn);
if (intr_summary == 0) {
if ((scn->target_status != OL_TRGET_STATUS_RESET) &&
(!cdf_atomic_read(&scn->link_suspended))) {
hif_write32_mb(scn->mem +
(SOC_CORE_BASE_ADDRESS |
PCIE_INTR_ENABLE_ADDRESS),
HOST_GROUP0_MASK);
hif_read32_mb(scn->mem +
(SOC_CORE_BASE_ADDRESS |
PCIE_INTR_ENABLE_ADDRESS));
}
A_TARGET_ACCESS_END(scn);
return;
} else {
A_TARGET_ACCESS_END(scn);
}
scn->ce_irq_summary = intr_summary;
for (id = 0; intr_summary && (id < scn->ce_count); id++) {
if (intr_summary & (1 << id)) {
intr_summary &= ~(1 << id);
ret = icnss_dispatch_one_ce_irq(id);
}
}
}
/**
* icnss_get_soc_info() - get soc info
*
* This function query the soc information from the platform
* driver
*
* @info: struct icnss_soc_info
*
* Return: 0 for success
*/
int icnss_get_soc_info(struct icnss_soc_info *info)
{
struct ol_softc *scn = cds_get_context(CDF_MODULE_ID_HIF);
if (!scn) {
HIF_ERROR("%s: SCN = NULL", __func__);
return -EINVAL;
}
info->v_addr = scn->mem;
info->p_addr = scn->mem_pa;
info->version = 0;
return 0;
}
/* icnss_get_irq_num() - generate a number to represent an irq number
*/
static int icnss_get_irq_num(int ce_id)
{
if (ce_id < CE_COUNT_MAX && ce_id >= 0)
return ce_id + 100;
pr_err("icnss: No irq registered for CE id %d\n", ce_id);
return -EINVAL;
}
int icnss_get_ce_id(int irq)
{
int ce_id = irq - 100;
if (ce_id < CE_COUNT_MAX && ce_id >= 0)
return ce_id;
pr_err("icnss: No matching CE id for irq %d\n", irq);
return -EINVAL;
}
#endif /* HIF_PCI */