/*
* Copyright (c) 2012 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.
*/
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include "wil6210.h"
#include "txrx.h"
#include "wmi.h"
#include "trace.h"
/**
* WMI event receiving - theory of operations
*
* When firmware about to report WMI event, it fills memory area
* in the mailbox and raises misc. IRQ. Thread interrupt handler invoked for
* the misc IRQ, function @wmi_recv_cmd called by thread IRQ handler.
*
* @wmi_recv_cmd reads event, allocates memory chunk and attaches it to the
* event list @wil->pending_wmi_ev. Then, work queue @wil->wmi_wq wakes up
* and handles events within the @wmi_event_worker. Every event get detached
* from list, processed and deleted.
*
* Purpose for this mechanism is to release IRQ thread; otherwise,
* if WMI event handling involves another WMI command flow, this 2-nd flow
* won't be completed because of blocked IRQ thread.
*/
/**
* Addressing - theory of operations
*
* There are several buses present on the WIL6210 card.
* Same memory areas are visible at different address on
* the different busses. There are 3 main bus masters:
* - MAC CPU (ucode)
* - User CPU (firmware)
* - AHB (host)
*
* On the PCI bus, there is one BAR (BAR0) of 2Mb size, exposing
* AHB addresses starting from 0x880000
*
* Internally, firmware uses addresses that allows faster access but
* are invisible from the host. To read from these addresses, alternative
* AHB address must be used.
*
* Memory mapping
* Linker address PCI/Host address
* 0x880000 .. 0xa80000 2Mb BAR0
* 0x800000 .. 0x807000 0x900000 .. 0x907000 28k DCCM
* 0x840000 .. 0x857000 0x908000 .. 0x91f000 92k PERIPH
*/
/**
* @fw_mapping provides memory remapping table
*/
static const struct {
u32 from; /* linker address - from, inclusive */
u32 to; /* linker address - to, exclusive */
u32 host; /* PCI/Host address - BAR0 + 0x880000 */
} fw_mapping[] = {
{0x000000, 0x040000, 0x8c0000}, /* FW code RAM 256k */
{0x800000, 0x808000, 0x900000}, /* FW data RAM 32k */
{0x840000, 0x860000, 0x908000}, /* peripheral data RAM 128k/96k used */
{0x880000, 0x88a000, 0x880000}, /* various RGF */
{0x8c0000, 0x949000, 0x8c0000}, /* trivial mapping for upper area */
/*
* 920000..930000 ucode code RAM
* 930000..932000 ucode data RAM
* 932000..949000 back-door debug data
*/
};
/**
* return AHB address for given firmware/ucode internal (linker) address
* @x - internal address
* If address have no valid AHB mapping, return 0
*/
static u32 wmi_addr_remap(u32 x)
{
uint i;
for (i = 0; i < ARRAY_SIZE(fw_mapping); i++) {
if ((x >= fw_mapping[i].from) && (x < fw_mapping[i].to))
return x + fw_mapping[i].host - fw_mapping[i].from;
}
return 0;
}
/**
* Check address validity for WMI buffer; remap if needed
* @ptr - internal (linker) fw/ucode address
*
* Valid buffer should be DWORD aligned
*
* return address for accessing buffer from the host;
* if buffer is not valid, return NULL.
*/
void __iomem *wmi_buffer(struct wil6210_priv *wil, __le32 ptr_)
{
u32 off;
u32 ptr = le32_to_cpu(ptr_);
if (ptr % 4)
return NULL;
ptr = wmi_addr_remap(ptr);
if (ptr < WIL6210_FW_HOST_OFF)
return NULL;
off = HOSTADDR(ptr);
if (off > WIL6210_MEM_SIZE - 4)
return NULL;
return wil->csr + off;
}
/**
* Check address validity
*/
void __iomem *wmi_addr(struct wil6210_priv *wil, u32 ptr)
{
u32 off;
if (ptr % 4)
return NULL;
if (ptr < WIL6210_FW_HOST_OFF)
return NULL;
off = HOSTADDR(ptr);
if (off > WIL6210_MEM_SIZE - 4)
return NULL;
return wil->csr + off;
}
int wmi_read_hdr(struct wil6210_priv *wil, __le32 ptr,
struct wil6210_mbox_hdr *hdr)
{
void __iomem *src = wmi_buffer(wil, ptr);
if (!src)
return -EINVAL;
wil_memcpy_fromio_32(hdr, src, sizeof(*hdr));
return 0;
}
static int __wmi_send(struct wil6210_priv *wil, u16 cmdid, void *buf, u16 len)
{
struct {
struct wil6210_mbox_hdr hdr;
struct wil6210_mbox_hdr_wmi wmi;
} __packed cmd = {
.hdr = {
.type = WIL_MBOX_HDR_TYPE_WMI,
.flags = 0