mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 19:14:35 -04:00
Merge tag 'mm81x-driver-08-07-2026' of https://github.com/MorseMicro/linux
Lachlan Hodges says: ==================== Introduce mm81x driver for mm81x based chipsets plus associated SDIO ids. ==================== [list the full vendor directory in MAINTAINERS] Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
@@ -18202,6 +18202,14 @@ F: drivers/regulator/mpq7920.c
|
||||
F: drivers/regulator/mpq7920.h
|
||||
F: include/linux/mfd/mp2629.h
|
||||
|
||||
MORSE MICRO MM81X WIRELESS DRIVER
|
||||
M: Lachlan Hodges <lachlan.hodges@morsemicro.com>
|
||||
M: Dan Callaghan <dan.callaghan@morsemicro.com>
|
||||
R: Arien Judge <arien.judge@morsemicro.com>
|
||||
L: linux-wireless@vger.kernel.org
|
||||
S: Supported
|
||||
F: drivers/net/wireless/morsemicro/
|
||||
|
||||
MOST(R) TECHNOLOGY DRIVER
|
||||
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
|
||||
M: Christian Gromm <christian.gromm@microchip.com>
|
||||
|
||||
@@ -27,6 +27,7 @@ source "drivers/net/wireless/intersil/Kconfig"
|
||||
source "drivers/net/wireless/marvell/Kconfig"
|
||||
source "drivers/net/wireless/mediatek/Kconfig"
|
||||
source "drivers/net/wireless/microchip/Kconfig"
|
||||
source "drivers/net/wireless/morsemicro/Kconfig"
|
||||
source "drivers/net/wireless/purelifi/Kconfig"
|
||||
source "drivers/net/wireless/ralink/Kconfig"
|
||||
source "drivers/net/wireless/realtek/Kconfig"
|
||||
|
||||
@@ -12,6 +12,7 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/
|
||||
obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/
|
||||
obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/
|
||||
obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/
|
||||
obj-$(CONFIG_WLAN_VENDOR_MORSEMICRO) += morsemicro/
|
||||
obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/
|
||||
obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/
|
||||
obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/
|
||||
|
||||
15
drivers/net/wireless/morsemicro/Kconfig
Normal file
15
drivers/net/wireless/morsemicro/Kconfig
Normal file
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config WLAN_VENDOR_MORSEMICRO
|
||||
bool "Morse Micro devices"
|
||||
default y
|
||||
help
|
||||
If you have a wireless card belonging to this class, say Y.
|
||||
|
||||
Note that the answer to this question doesn't directly affect the
|
||||
kernel: saying N will just cause the configurator to skip all the
|
||||
questions about these cards. If you say Y, you will be asked for
|
||||
your specific card in the following questions.
|
||||
|
||||
if WLAN_VENDOR_MORSEMICRO
|
||||
source "drivers/net/wireless/morsemicro/mm81x/Kconfig"
|
||||
endif # WLAN_VENDOR_MORSEMICRO
|
||||
2
drivers/net/wireless/morsemicro/Makefile
Normal file
2
drivers/net/wireless/morsemicro/Makefile
Normal file
@@ -0,0 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_MM81X) += mm81x/
|
||||
24
drivers/net/wireless/morsemicro/mm81x/Kconfig
Normal file
24
drivers/net/wireless/morsemicro/mm81x/Kconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
config MM81X
|
||||
tristate "Morse Micro MM81x wireless devices"
|
||||
depends on MAC80211
|
||||
select FW_LOADER
|
||||
select CRC7
|
||||
help
|
||||
This module adds support for wireless devices based
|
||||
on Morse Micro MM81xx chipsets.
|
||||
|
||||
config MM81X_USB
|
||||
tristate "Morse Micro MM81x USB support"
|
||||
depends on MM81X && USB
|
||||
help
|
||||
This module adds support for the USB interface of
|
||||
devices using the Morse Micro MM81x chipset.
|
||||
|
||||
config MM81X_SDIO
|
||||
tristate "Morse Micro MM81x SDIO support"
|
||||
depends on MM81X && MMC
|
||||
help
|
||||
This module adds support for the SDIO interface of
|
||||
devices using the Morse Micro MM81x chipset.
|
||||
21
drivers/net/wireless/morsemicro/mm81x/Makefile
Normal file
21
drivers/net/wireless/morsemicro/mm81x/Makefile
Normal file
@@ -0,0 +1,21 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_MM81X) += mm81x_core.o
|
||||
|
||||
mm81x_core-y += core.o
|
||||
mm81x_core-y += mac.o
|
||||
mm81x_core-y += hw.o
|
||||
mm81x_core-y += fw.o
|
||||
mm81x_core-y += command.o
|
||||
mm81x_core-y += ps.o
|
||||
mm81x_core-y += skbq.o
|
||||
mm81x_core-y += yaps_hw.o
|
||||
mm81x_core-y += yaps.o
|
||||
mm81x_core-y += rc.o
|
||||
mm81x_core-y += mmrc.o
|
||||
|
||||
obj-$(CONFIG_MM81X_USB) += mm81x_usb.o
|
||||
mm81x_usb-y += usb.o
|
||||
|
||||
obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o
|
||||
mm81x_sdio-y += sdio.o
|
||||
99
drivers/net/wireless/morsemicro/mm81x/bus.h
Normal file
99
drivers/net/wireless/morsemicro/mm81x/bus.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_BUS_H_
|
||||
#define _MM81X_BUS_H_
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#include "core.h"
|
||||
|
||||
enum mm81x_bus_type {
|
||||
MM81X_BUS_TYPE_USB,
|
||||
MM81X_BUS_TYPE_SDIO,
|
||||
};
|
||||
|
||||
struct mm81x_bus_ops {
|
||||
int (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len);
|
||||
int (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len);
|
||||
int (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data);
|
||||
int (*reg32_write)(struct mm81x *mors, u32 addr, u32 data);
|
||||
int (*digital_reset)(struct mm81x *mors);
|
||||
void (*set_bus_enable)(struct mm81x *mors, bool enable);
|
||||
void (*config_burst_mode)(struct mm81x *mors, bool enable_burst);
|
||||
void (*claim)(struct mm81x *mors);
|
||||
void (*set_irq)(struct mm81x *mors, bool enable);
|
||||
void (*release)(struct mm81x *mors);
|
||||
unsigned int bulk_alignment;
|
||||
};
|
||||
|
||||
/*
|
||||
* Default TX alignment for buses which don't care. mac80211 will give us
|
||||
* SKBs aligned to the 2 byte boundary, so 2 is effectively a noop.
|
||||
*/
|
||||
#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2)
|
||||
|
||||
/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */
|
||||
static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len)
|
||||
{
|
||||
return mors->bus_ops->dm_read(mors, addr, data, len);
|
||||
}
|
||||
|
||||
static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data,
|
||||
int len)
|
||||
{
|
||||
return mors->bus_ops->dm_write(mors, addr, data, len);
|
||||
}
|
||||
|
||||
static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data)
|
||||
{
|
||||
return mors->bus_ops->reg32_read(mors, addr, data);
|
||||
}
|
||||
|
||||
static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data)
|
||||
{
|
||||
return mors->bus_ops->reg32_write(mors, addr, data);
|
||||
}
|
||||
|
||||
static inline int mm81x_bus_digital_reset(struct mm81x *mors)
|
||||
{
|
||||
if (mors->bus_ops->digital_reset)
|
||||
return mors->bus_ops->digital_reset(mors);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable)
|
||||
{
|
||||
mors->bus_ops->set_bus_enable(mors, enable);
|
||||
}
|
||||
|
||||
static inline void mm81x_bus_config_burst_mode(struct mm81x *mors,
|
||||
bool enable_burst)
|
||||
{
|
||||
if (mors->bus_ops->config_burst_mode)
|
||||
mors->bus_ops->config_burst_mode(mors, enable_burst);
|
||||
}
|
||||
|
||||
static inline void mm81x_claim_bus(struct mm81x *mors)
|
||||
{
|
||||
mors->bus_ops->claim(mors);
|
||||
}
|
||||
|
||||
static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable)
|
||||
{
|
||||
mors->bus_ops->set_irq(mors, enable);
|
||||
}
|
||||
|
||||
static inline void mm81x_release_bus(struct mm81x *mors)
|
||||
{
|
||||
mors->bus_ops->release(mors);
|
||||
}
|
||||
|
||||
static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors)
|
||||
{
|
||||
return mors->bus_ops->bulk_alignment;
|
||||
}
|
||||
|
||||
#endif /* !_MM81X_BUS_H_ */
|
||||
563
drivers/net/wireless/morsemicro/mm81x/command.c
Normal file
563
drivers/net/wireless/morsemicro/mm81x/command.c
Normal file
@@ -0,0 +1,563 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "command.h"
|
||||
#include "mac.h"
|
||||
#include "ps.h"
|
||||
#include "hif.h"
|
||||
|
||||
#define MM_MAX_COMMAND_RETRY 2
|
||||
#define HOST_CMD_DEFAULT_TIMEOUT_MS 600
|
||||
#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000
|
||||
|
||||
#define INIT_CMD_HDR(_req, _cmd, _vif_id) \
|
||||
((struct host_cmd_header){ \
|
||||
.message_id = cpu_to_le16(_cmd), \
|
||||
.len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)), \
|
||||
.vif_id = cpu_to_le16(_vif_id), \
|
||||
})
|
||||
|
||||
struct host_cmd_resp_cb {
|
||||
int ret;
|
||||
u32 length;
|
||||
struct host_cmd_resp *dest_resp;
|
||||
};
|
||||
|
||||
static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp,
|
||||
struct host_cmd_req *req, u32 length, u32 timeout)
|
||||
{
|
||||
int cmd_len;
|
||||
int ret = 0;
|
||||
u16 host_id;
|
||||
int retry = 0;
|
||||
unsigned long wait_ret = 0;
|
||||
struct sk_buff *skb;
|
||||
struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
|
||||
struct host_cmd_resp_cb *resp_cb;
|
||||
DECLARE_COMPLETION_ONSTACK(cmd_comp);
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) >
|
||||
IEEE80211_TX_INFO_DRIVER_DATA_SIZE);
|
||||
|
||||
cmd_len = sizeof(*req) + le16_to_cpu(req->hdr.len);
|
||||
req->hdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ);
|
||||
|
||||
mutex_lock(&mors->cmd_wait);
|
||||
mors->cmd_seq++;
|
||||
if (mors->cmd_seq > HOST_CMD_HOST_ID_SEQ_MAX)
|
||||
mors->cmd_seq = 1;
|
||||
host_id = mors->cmd_seq << HOST_CMD_HOST_ID_SEQ_SHIFT;
|
||||
|
||||
mm81x_ps_disable(mors);
|
||||
|
||||
do {
|
||||
req->hdr.host_id = cpu_to_le16(host_id | retry);
|
||||
|
||||
skb = mm81x_skbq_alloc_skb(cmd_q, cmd_len);
|
||||
if (!skb) {
|
||||
ret = -ENOMEM;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(skb->data, req, cmd_len);
|
||||
resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb)
|
||||
->driver_data;
|
||||
resp_cb->length = length;
|
||||
resp_cb->dest_resp = resp;
|
||||
|
||||
dev_dbg(mors->dev, "CMD 0x%04x:%04x",
|
||||
le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id));
|
||||
|
||||
mutex_lock(&mors->cmd_lock);
|
||||
mors->cmd_comp = &cmd_comp;
|
||||
if (retry > 0)
|
||||
reinit_completion(&cmd_comp);
|
||||
timeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS;
|
||||
ret = mm81x_skbq_skb_tx(cmd_q, &skb, NULL,
|
||||
MM81X_SKB_CHAN_COMMAND);
|
||||
mutex_unlock(&mors->cmd_lock);
|
||||
|
||||
if (ret) {
|
||||
dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
wait_ret = wait_for_completion_timeout(
|
||||
&cmd_comp, msecs_to_jiffies(timeout));
|
||||
mutex_lock(&mors->cmd_lock);
|
||||
mors->cmd_comp = NULL;
|
||||
|
||||
if (!wait_ret) {
|
||||
dev_err(mors->dev,
|
||||
"Try:%d Command %04x:%04x timeout after %u ms",
|
||||
retry, le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id), timeout);
|
||||
ret = -ETIMEDOUT;
|
||||
} else {
|
||||
ret = (length && resp) ? le32_to_cpu(resp->status) :
|
||||
resp_cb->ret;
|
||||
if (ret > 0 || ret < -MAX_ERRNO)
|
||||
ret = -EIO;
|
||||
|
||||
dev_dbg(mors->dev, "Command 0x%04x:%04x status 0x%08x",
|
||||
le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id), ret);
|
||||
if (ret) {
|
||||
dev_err(mors->dev,
|
||||
"Command 0x%04x:%04x error %d",
|
||||
le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id), ret);
|
||||
}
|
||||
}
|
||||
/* Free the command request */
|
||||
spin_lock_bh(&cmd_q->lock);
|
||||
mm81x_skbq_skb_finish(cmd_q, skb, NULL);
|
||||
spin_unlock_bh(&cmd_q->lock);
|
||||
mutex_unlock(&mors->cmd_lock);
|
||||
|
||||
retry++;
|
||||
} while ((ret == -ETIMEDOUT) && retry < MM_MAX_COMMAND_RETRY);
|
||||
|
||||
mm81x_ps_enable(mors);
|
||||
mutex_unlock(&mors->cmd_wait);
|
||||
|
||||
if (ret == -ETIMEDOUT) {
|
||||
dev_err(mors->dev, "Command %02x:%02x timed out",
|
||||
le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id));
|
||||
} else if (ret != 0) {
|
||||
dev_err(mors->dev,
|
||||
"Command %02x:%02x failed with rc %d (0x%x)\n",
|
||||
le16_to_cpu(req->hdr.message_id),
|
||||
le16_to_cpu(req->hdr.host_id), ret, ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb)
|
||||
{
|
||||
int length, ret = -ESRCH; /* No such process */
|
||||
struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
|
||||
struct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb->data);
|
||||
struct sk_buff *cmd_skb = NULL;
|
||||
struct host_cmd_resp_cb *resp_cb;
|
||||
struct host_cmd_resp *dest_resp;
|
||||
struct host_cmd_req *req;
|
||||
u16 message_id = 0;
|
||||
u16 host_id = 0;
|
||||
u16 resp_message_id = le16_to_cpu(src_resp->hdr.message_id);
|
||||
u16 resp_host_id = le16_to_cpu(src_resp->hdr.host_id);
|
||||
bool is_late_response = false;
|
||||
|
||||
dev_dbg(mors->dev, "EVT 0x%04x:0x%04x", resp_message_id, resp_host_id);
|
||||
|
||||
if (!HOST_CMD_IS_RESP(src_resp)) {
|
||||
ret = mm81x_mac_event_recv(mors, skb);
|
||||
goto exit_free;
|
||||
}
|
||||
|
||||
mutex_lock(&mors->cmd_lock);
|
||||
|
||||
cmd_skb = mm81x_skbq_tx_pending(cmd_q);
|
||||
if (cmd_skb) {
|
||||
mm81x_skbq_pull_hdr_post_tx(cmd_skb);
|
||||
req = (struct host_cmd_req *)cmd_skb->data;
|
||||
message_id = le16_to_cpu(req->hdr.message_id);
|
||||
host_id = le16_to_cpu(req->hdr.host_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is no pending command or the sequence ID does not match,
|
||||
* this is a late response for a timed out command which has been
|
||||
* cleaned up, so just free up the response. If a command was retried,
|
||||
* the response may be from the retry or from the original command
|
||||
* (late response) but not from both because the firmware will silently
|
||||
* drop a retry if it received the initial request. So a mismatched
|
||||
* retry counter is treated as a matched command and response.
|
||||
*/
|
||||
if (!cmd_skb || message_id != resp_message_id ||
|
||||
(host_id & HOST_CMD_HOST_ID_SEQ_MASK) !=
|
||||
(resp_host_id & HOST_CMD_HOST_ID_SEQ_MASK)) {
|
||||
dev_err(mors->dev,
|
||||
"Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x",
|
||||
resp_message_id, resp_host_id, message_id, host_id,
|
||||
mors->cmd_seq);
|
||||
is_late_response = true;
|
||||
goto exit;
|
||||
}
|
||||
if ((host_id & HOST_CMD_HOST_ID_RETRY_MASK) !=
|
||||
(resp_host_id & HOST_CMD_HOST_ID_RETRY_MASK))
|
||||
dev_dbg(mors->dev,
|
||||
"Command retry mismatch 0x%04x:%04x 0x%04x:%04x",
|
||||
message_id, host_id, resp_message_id, resp_host_id);
|
||||
|
||||
resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb)
|
||||
->driver_data;
|
||||
length = resp_cb->length;
|
||||
dest_resp = resp_cb->dest_resp;
|
||||
if (length >= sizeof(struct host_cmd_resp) && dest_resp) {
|
||||
ret = 0;
|
||||
length = min_t(int, length,
|
||||
le16_to_cpu(src_resp->hdr.len) +
|
||||
sizeof(struct host_cmd_header));
|
||||
memcpy(dest_resp, src_resp, length);
|
||||
} else {
|
||||
ret = le32_to_cpu(src_resp->status);
|
||||
}
|
||||
|
||||
resp_cb->ret = ret;
|
||||
|
||||
exit:
|
||||
if (cmd_skb && !is_late_response) {
|
||||
/* Complete if not already timed out */
|
||||
if (mors->cmd_comp)
|
||||
complete(mors->cmd_comp);
|
||||
}
|
||||
|
||||
mutex_unlock(&mors->cmd_lock);
|
||||
exit_free:
|
||||
dev_kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
|
||||
struct ieee80211_sta *sta,
|
||||
enum ieee80211_sta_state state)
|
||||
{
|
||||
struct host_cmd_req_set_sta_state req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE,
|
||||
mors_vif->id),
|
||||
.aid = cpu_to_le16(aid),
|
||||
.state = cpu_to_le16(state),
|
||||
.uapsd_queues = sta->uapsd_queues,
|
||||
};
|
||||
|
||||
memcpy(req.sta_addr, sta->addr, sizeof(req.sta_addr));
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
|
||||
enum nl80211_iftype type)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_add_interface req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0),
|
||||
};
|
||||
struct host_cmd_resp_add_interface resp;
|
||||
|
||||
switch (type) {
|
||||
case NL80211_IFTYPE_STATION:
|
||||
req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA);
|
||||
break;
|
||||
case NL80211_IFTYPE_AP:
|
||||
req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP);
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
memcpy(req.addr.octet, addr, sizeof(req.addr.octet));
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
|
||||
(struct host_cmd_req *)&req, sizeof(resp), 0);
|
||||
if (!ret)
|
||||
*vif_id = le16_to_cpu(resp.hdr.vif_id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
|
||||
struct mm81x_fw_caps *capabilities)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
struct host_cmd_req_get_capabilities req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id),
|
||||
};
|
||||
struct host_cmd_resp_get_capabilities rsp;
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&rsp,
|
||||
(struct host_cmd_req *)&req, sizeof(rsp), 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
capabilities->ampdu_mss = rsp.capabilities.ampdu_mss;
|
||||
capabilities->mm81x_mmss_offset = rsp.morse_mmss_offset;
|
||||
capabilities->beamformee_sts_capability =
|
||||
rsp.capabilities.beamformee_sts_capability;
|
||||
capabilities->maximum_ampdu_length_exponent =
|
||||
rsp.capabilities.maximum_ampdu_length_exponent;
|
||||
capabilities->number_sounding_dimensions =
|
||||
rsp.capabilities.number_sounding_dimensions;
|
||||
for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++)
|
||||
capabilities->flags[i] = le32_to_cpu(rsp.capabilities.flags[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_get_max_txpower req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0),
|
||||
};
|
||||
struct host_cmd_resp_get_max_txpower resp;
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
|
||||
(struct host_cmd_req *)&req, sizeof(resp), 0);
|
||||
if (!ret)
|
||||
*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
|
||||
bool store)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_hw_scan *req;
|
||||
size_t cmd_size;
|
||||
u8 *buf;
|
||||
u32 flags = 0;
|
||||
|
||||
cmd_size = mm81x_hw_scan_h_get_cmd_size(params);
|
||||
cmd_size = ROUND_BYTES_TO_WORD(cmd_size);
|
||||
|
||||
req = kzalloc(cmd_size, GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
buf = req->variable;
|
||||
|
||||
if (store)
|
||||
flags = HOST_CMD_HW_SCAN_FLAGS_STORE;
|
||||
else if (params->operation == MM81X_HW_SCAN_OP_START)
|
||||
flags |= HOST_CMD_HW_SCAN_FLAGS_START;
|
||||
else if (params->operation == MM81X_HW_SCAN_OP_STOP)
|
||||
flags |= HOST_CMD_HW_SCAN_FLAGS_ABORT;
|
||||
|
||||
flags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES;
|
||||
|
||||
if (params->operation == MM81X_HW_SCAN_OP_START) {
|
||||
req->dwell_time_ms = cpu_to_le32(params->dwell_time_ms);
|
||||
buf = mm81x_hw_scan_h_insert_tlvs(params, buf);
|
||||
}
|
||||
|
||||
req->flags = cpu_to_le32(flags);
|
||||
req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0);
|
||||
req->hdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req->hdr)));
|
||||
ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
|
||||
kfree(req);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
|
||||
int txpower_mbm)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_set_txpower req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0),
|
||||
.power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)),
|
||||
};
|
||||
struct host_cmd_resp_set_txpower resp;
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
|
||||
(struct host_cmd_req *)&req, sizeof(resp), 0);
|
||||
if (!ret)
|
||||
*out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
|
||||
u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
|
||||
s32 *power_mbm)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_set_channel req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0),
|
||||
.op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz),
|
||||
.op_bw_mhz = op_bw_mhz,
|
||||
.pri_bw_mhz = pri_bw_mhz,
|
||||
.pri_1mhz_chan_idx = pri_1mhz_chan_idx,
|
||||
.dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH,
|
||||
};
|
||||
struct host_cmd_resp_set_channel resp;
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
|
||||
(struct host_cmd_req *)&req, sizeof(resp), 0);
|
||||
if (!ret)
|
||||
*power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
|
||||
u16 aid, struct ieee80211_key_conf *key)
|
||||
{
|
||||
struct host_cmd_req_disable_key req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif->id),
|
||||
.aid = cpu_to_le32(aid),
|
||||
.key_idx = key->hw_key_idx,
|
||||
.key_type =
|
||||
cpu_to_le32((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
|
||||
HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
|
||||
HOST_CMD_TEMPORAL_KEY_TYPE_GTK),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
|
||||
u16 aid, struct ieee80211_key_conf *key,
|
||||
enum host_cmd_key_cipher cipher,
|
||||
enum host_cmd_aes_key_len length)
|
||||
{
|
||||
int ret;
|
||||
struct host_cmd_req_install_key req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif->id),
|
||||
.pn = cpu_to_le64(atomic64_read(&key->tx_pn)),
|
||||
.aid = cpu_to_le32(aid),
|
||||
.cipher = cipher,
|
||||
.key_length = length,
|
||||
.key_idx = key->keyidx,
|
||||
.key_type = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
|
||||
HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
|
||||
HOST_CMD_TEMPORAL_KEY_TYPE_GTK,
|
||||
};
|
||||
struct host_cmd_resp_install_key resp;
|
||||
|
||||
if (key->keylen > sizeof(req.key))
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(req.key, key->key, key->keylen);
|
||||
|
||||
ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
|
||||
(struct host_cmd_req *)&req, sizeof(resp), 0);
|
||||
if (!ret) {
|
||||
key->hw_key_idx = resp.key_idx;
|
||||
dev_dbg(mors->dev, "Installed key @ hw index: %d",
|
||||
resp.key_idx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
|
||||
struct mm81x_vif *mors_vif)
|
||||
{
|
||||
struct host_cmd_req_mcast_filter *req;
|
||||
struct mcast_filter *filter = mors->mcast_filter;
|
||||
u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count;
|
||||
u16 alloc_len = filter_list_len + sizeof(*req);
|
||||
int ret = 0;
|
||||
|
||||
req = kzalloc(alloc_len, GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif->id);
|
||||
req->hdr.len = cpu_to_le16(alloc_len - sizeof(req->hdr));
|
||||
req->count = filter->count;
|
||||
memcpy(req->hw_addr, filter->addr_list, filter_list_len);
|
||||
|
||||
ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
|
||||
kfree(req);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
|
||||
u16 dtim_period, u32 cssid)
|
||||
{
|
||||
struct host_cmd_req_bss_config req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id),
|
||||
.beacon_interval_tu = cpu_to_le16(beacon_int),
|
||||
.cssid = cpu_to_le32(cssid),
|
||||
.dtim_period = cpu_to_le16(dtim_period),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
|
||||
bool enabled)
|
||||
{
|
||||
struct mm81x_vif *vif = mm81x_vif;
|
||||
struct host_cmd_req_bss_beacon_config req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG,
|
||||
vif->id),
|
||||
.enable = enabled,
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled)
|
||||
{
|
||||
struct host_cmd_req_config_ps req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0),
|
||||
.enabled = (u8)enabled,
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0,
|
||||
HOST_CMD_POWERSAVE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params)
|
||||
{
|
||||
struct host_cmd_req_set_qos_params req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0),
|
||||
.uapsd = params->uapsd,
|
||||
.queue_idx = params->aci,
|
||||
.aifs_slot_count = params->aifs,
|
||||
.contention_window_min = cpu_to_le16(params->cw_min),
|
||||
.contention_window_max = cpu_to_le16(params->cw_max),
|
||||
.max_txop_usec = cpu_to_le32(params->txop),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id)
|
||||
{
|
||||
struct host_cmd_req_remove_interface req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold)
|
||||
{
|
||||
struct host_cmd_req_get_set_generic_param req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0),
|
||||
.param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD),
|
||||
.action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET),
|
||||
.value = cpu_to_le32(frag_threshold),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
|
||||
}
|
||||
|
||||
int mm81x_cmd_get_disabled_channels(
|
||||
struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
|
||||
uint resp_len)
|
||||
{
|
||||
struct host_cmd_req req = {
|
||||
.hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0),
|
||||
};
|
||||
|
||||
return mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, &req, resp_len,
|
||||
0);
|
||||
}
|
||||
85
drivers/net/wireless/morsemicro/mm81x/command.h
Normal file
85
drivers/net/wireless/morsemicro/mm81x/command.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_COMMAND_H_
|
||||
#define _MM81X_COMMAND_H_
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "core.h"
|
||||
#include "command_defs.h"
|
||||
|
||||
#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_REQ)
|
||||
#define HOST_CMD_IS_RESP(cmd) \
|
||||
(le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_RESP)
|
||||
#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_EVT)
|
||||
|
||||
struct mm81x_queue_params;
|
||||
|
||||
enum mm81x_cmd_return_code {
|
||||
MM81X_RET_SUCCESS = 0,
|
||||
MM81X_RET_EPERM = -1,
|
||||
MM81X_RET_ENOMEM = -12,
|
||||
MM81X_RET_CMD_NOT_HANDLED = -32757,
|
||||
};
|
||||
|
||||
#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF
|
||||
#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F
|
||||
#define HOST_CMD_HOST_ID_SEQ_SHIFT 4
|
||||
#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0
|
||||
|
||||
struct host_cmd_req {
|
||||
struct host_cmd_header hdr;
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct host_cmd_resp {
|
||||
struct host_cmd_header hdr;
|
||||
__le32 status;
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
struct host_cmd_event {
|
||||
struct host_cmd_header hdr;
|
||||
u8 data[];
|
||||
} __packed;
|
||||
|
||||
int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb);
|
||||
int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
|
||||
enum nl80211_iftype type);
|
||||
int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
|
||||
struct mm81x_fw_caps *capabilities);
|
||||
int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params);
|
||||
int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
|
||||
bool enabled);
|
||||
int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
|
||||
u16 dtim_period, u32 cssid);
|
||||
int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
|
||||
u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
|
||||
s32 *power_mbm);
|
||||
int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm);
|
||||
int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
|
||||
int txpower_mbm);
|
||||
int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
|
||||
bool store);
|
||||
int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled);
|
||||
int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
|
||||
struct mm81x_vif *mors_vif);
|
||||
int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
|
||||
struct ieee80211_sta *sta,
|
||||
enum ieee80211_sta_state state);
|
||||
int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
|
||||
u16 aid, struct ieee80211_key_conf *key,
|
||||
enum host_cmd_key_cipher cipher,
|
||||
enum host_cmd_aes_key_len length);
|
||||
int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
|
||||
u16 aid, struct ieee80211_key_conf *key);
|
||||
int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id);
|
||||
int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold);
|
||||
int mm81x_cmd_get_disabled_channels(
|
||||
struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
|
||||
uint resp_len);
|
||||
|
||||
#endif /* !_MM81X_COMMAND_H_ */
|
||||
1658
drivers/net/wireless/morsemicro/mm81x/command_defs.h
Normal file
1658
drivers/net/wireless/morsemicro/mm81x/command_defs.h
Normal file
File diff suppressed because it is too large
Load Diff
138
drivers/net/wireless/morsemicro/mm81x/core.c
Normal file
138
drivers/net/wireless/morsemicro/mm81x/core.c
Normal file
@@ -0,0 +1,138 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "hif.h"
|
||||
#include "mac.h"
|
||||
|
||||
static int mm81x_core_attach_regs(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
ret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, &mors->chip_id);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "failed to read chip id %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch (mors->chip_id) {
|
||||
case (CHIP_ID_MM8108):
|
||||
mors->regs = &mm8108_regs;
|
||||
mors->hif.ops = &mm81x_yaps_ops;
|
||||
break;
|
||||
default:
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_core_init_mac_addr(struct mm81x *mors)
|
||||
{
|
||||
int ret = mm81x_hw_otp_get_mac_addr(mors);
|
||||
|
||||
if (ret || !is_valid_ether_addr(mors->macaddr))
|
||||
eth_random_addr(mors->macaddr);
|
||||
}
|
||||
|
||||
char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver)
|
||||
{
|
||||
const char *fw_base;
|
||||
|
||||
switch (chip_id) {
|
||||
case CHIP_ID_MM8108:
|
||||
fw_base = MM8108_FW_BASE;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return kasprintf(GFP_KERNEL, MM81X_FW_DIR "/v%u/%s" MM81X_FW_EXT,
|
||||
fw_ver, fw_base);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path);
|
||||
|
||||
struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev)
|
||||
{
|
||||
return mm81x_mac_alloc(priv_size, dev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_alloc);
|
||||
|
||||
int mm81x_core_init(struct mm81x *mors)
|
||||
{
|
||||
int ret;
|
||||
|
||||
set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
|
||||
set_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags);
|
||||
|
||||
mm81x_core_init_mac_addr(mors);
|
||||
|
||||
ret = mm81x_core_attach_regs(mors);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mors->chip_wq = create_singlethread_workqueue("chip_wq");
|
||||
if (!mors->chip_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
mors->net_wq = create_singlethread_workqueue("net_wq");
|
||||
if (!mors->net_wq) {
|
||||
ret = -ENOMEM;
|
||||
goto err_chip_wq;
|
||||
}
|
||||
|
||||
ret = mm81x_hif_init(mors);
|
||||
if (ret)
|
||||
goto err_wqs;
|
||||
|
||||
return 0;
|
||||
|
||||
err_wqs:
|
||||
flush_workqueue(mors->net_wq);
|
||||
destroy_workqueue(mors->net_wq);
|
||||
|
||||
err_chip_wq:
|
||||
flush_workqueue(mors->chip_wq);
|
||||
destroy_workqueue(mors->chip_wq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_init);
|
||||
|
||||
int mm81x_core_register(struct mm81x *mors)
|
||||
{
|
||||
return mm81x_mac_register(mors);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_register);
|
||||
|
||||
void mm81x_core_unregister(struct mm81x *mors)
|
||||
{
|
||||
mm81x_mac_unregister(mors);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_unregister);
|
||||
|
||||
void mm81x_core_deinit(struct mm81x *mors)
|
||||
{
|
||||
mm81x_hif_finish(mors);
|
||||
flush_workqueue(mors->net_wq);
|
||||
destroy_workqueue(mors->net_wq);
|
||||
flush_workqueue(mors->chip_wq);
|
||||
destroy_workqueue(mors->chip_wq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_deinit);
|
||||
|
||||
void mm81x_core_free(struct mm81x *mors)
|
||||
{
|
||||
mm81x_mac_free(mors);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_core_free);
|
||||
|
||||
MODULE_AUTHOR("Morse Micro");
|
||||
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X core");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
456
drivers/net/wireless/morsemicro/mm81x/core.h
Normal file
456
drivers/net/wireless/morsemicro/mm81x/core.h
Normal file
@@ -0,0 +1,456 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_CORE_H_
|
||||
#define _MM81X_CORE_H_
|
||||
|
||||
#include <net/mac80211.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/crc32.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/wait.h>
|
||||
#include "yaps.h"
|
||||
#include "yaps_hw.h"
|
||||
#include "hw.h"
|
||||
#include "fw.h"
|
||||
#include "rc.h"
|
||||
|
||||
#define MM81X_DRIVER_SEMVER_MAJOR 56
|
||||
#define MM81X_DRIVER_SEMVER_MINOR 3
|
||||
#define MM81X_DRIVER_SEMVER_PATCH 0
|
||||
|
||||
#define MM81X_SEMVER_GET_MAJOR(x) (((x) >> 22) & 0x3FF)
|
||||
#define MM81X_SEMVER_GET_MINOR(x) (((x) >> 10) & 0xFFF)
|
||||
#define MM81X_SEMVER_GET_PATCH(x) ((x) & 0x3FF)
|
||||
|
||||
#define DRV_VERSION __stringify(MM81X_VERSION)
|
||||
|
||||
#define MM8108_FW_BASE "mm8108"
|
||||
|
||||
#define BCF_SIZE_MAX 48
|
||||
|
||||
#define KHZ100_TO_MHZ(x) ((x) / 10)
|
||||
#define KHZ100_TO_KHZ(freq) ((freq) * 100)
|
||||
#define KHZ100_TO_HZ(freq) ((freq) * 100000)
|
||||
|
||||
#define QDBM_TO_MBM(gain) (((gain) * 100) >> 2)
|
||||
#define MBM_TO_QDBM(gain) (((gain) << 2) / 100)
|
||||
#define QDBM_TO_DBM(gain) ((gain) / 4)
|
||||
|
||||
#define BPS_TO_KBPS(x) ((x) / 1000)
|
||||
|
||||
#define NSS_IDX_TO_NSS(x) ((x) + 1)
|
||||
#define NSS_TO_NSS_IDX(x) ((x) - 1)
|
||||
|
||||
#define ROUND_BYTES_TO_WORD(_nbytes) \
|
||||
(((_nbytes) + 3) & ~((typeof(_nbytes))0x03))
|
||||
|
||||
struct mm81x_bus_ops;
|
||||
struct mm81x_hif_ops;
|
||||
|
||||
#define MM81X_CAPS_MAX_FW_VAL (128)
|
||||
|
||||
/* Max number of interfaces */
|
||||
#define MM81X_MAX_IF (2)
|
||||
|
||||
enum mm81x_caps_flags {
|
||||
MM81X_CAPS_FW_START = 0,
|
||||
MM81X_CAPS_2MHZ = MM81X_CAPS_FW_START,
|
||||
MM81X_CAPS_4MHZ,
|
||||
MM81X_CAPS_8MHZ,
|
||||
MM81X_CAPS_16MHZ,
|
||||
MM81X_CAPS_SGI,
|
||||
MM81X_CAPS_S1G_LONG,
|
||||
MM81X_CAPS_TRAVELING_PILOT_ONE_STREAM,
|
||||
MM81X_CAPS_TRAVELING_PILOT_TWO_STREAM,
|
||||
MM81X_CAPS_MU_BEAMFORMEE,
|
||||
MM81X_CAPS_MU_BEAMFORMER,
|
||||
MM81X_CAPS_RD_RESPONDER,
|
||||
MM81X_CAPS_STA_TYPE_SENSOR,
|
||||
MM81X_CAPS_STA_TYPE_NON_SENSOR,
|
||||
MM81X_CAPS_GROUP_AID,
|
||||
MM81X_CAPS_NON_TIM,
|
||||
MM81X_CAPS_TIM_ADE,
|
||||
MM81X_CAPS_BAT,
|
||||
MM81X_CAPS_DYNAMIC_AID,
|
||||
MM81X_CAPS_UPLINK_SYNC,
|
||||
MM81X_CAPS_FLOW_CONTROL,
|
||||
MM81X_CAPS_AMPDU,
|
||||
MM81X_CAPS_AMSDU,
|
||||
MM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE,
|
||||
MM81X_CAPS_PAGE_SLICING,
|
||||
MM81X_CAPS_RAW,
|
||||
MM81X_CAPS_MCS8,
|
||||
MM81X_CAPS_MCS9,
|
||||
MM81X_CAPS_ASYMMETRIC_BA_SUPPORT,
|
||||
MM81X_CAPS_DAC,
|
||||
MM81X_CAPS_CAC,
|
||||
MM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK,
|
||||
MM81X_CAPS_NDP_PSPOLL,
|
||||
MM81X_CAPS_FRAGMENT_BA,
|
||||
MM81X_CAPS_OBSS_MITIGATION,
|
||||
MM81X_CAPS_TMP_PS_MODE_SWITCH,
|
||||
MM81X_CAPS_SECTOR_TRAINING,
|
||||
MM81X_CAPS_UNSOLICIT_DYNAMIC_AID,
|
||||
MM81X_CAPS_NDP_BEAMFORMING_REPORT,
|
||||
MM81X_CAPS_MCS_NEGOTIATION,
|
||||
MM81X_CAPS_DUPLICATE_1MHZ,
|
||||
MM81X_CAPS_TACK_AS_PSPOLL,
|
||||
MM81X_CAPS_PV1,
|
||||
MM81X_CAPS_TWT_RESPONDER,
|
||||
MM81X_CAPS_TWT_REQUESTER,
|
||||
MM81X_CAPS_BDT,
|
||||
MM81X_CAPS_TWT_GROUPING,
|
||||
MM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC,
|
||||
MM81X_CAPS_LONG_MPDU,
|
||||
MM81X_CAPS_TXOP_SECTORIZATION,
|
||||
MM81X_CAPS_GROUP_SECTORIZATION,
|
||||
MM81X_CAPS_HTC_VHT,
|
||||
MM81X_CAPS_HTC_VHT_MFB,
|
||||
MM81X_CAPS_HTC_VHT_MRQ,
|
||||
MM81X_CAPS_2SS,
|
||||
MM81X_CAPS_3SS,
|
||||
MM81X_CAPS_4SS,
|
||||
MM81X_CAPS_SU_BEAMFORMEE,
|
||||
MM81X_CAPS_SU_BEAMFORMER,
|
||||
MM81X_CAPS_RX_STBC,
|
||||
MM81X_CAPS_TX_STBC,
|
||||
MM81X_CAPS_RX_LDPC,
|
||||
MM81X_CAPS_HW_FRAGMENT,
|
||||
|
||||
MM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL,
|
||||
MM81X_CAPS_LAST = MM81X_CAPS_FW_END,
|
||||
};
|
||||
|
||||
struct mm81x_fw_caps {
|
||||
u32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
|
||||
u8 ampdu_mss;
|
||||
u8 beamformee_sts_capability;
|
||||
u8 number_sounding_dimensions;
|
||||
u8 maximum_ampdu_length_exponent;
|
||||
u8 mm81x_mmss_offset;
|
||||
};
|
||||
|
||||
#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \
|
||||
mm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY)
|
||||
|
||||
static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps,
|
||||
enum mm81x_caps_flags flag)
|
||||
{
|
||||
const unsigned long *flags_ptr = (unsigned long *)caps->flags;
|
||||
|
||||
return test_bit(flag, flags_ptr);
|
||||
}
|
||||
|
||||
struct mm81x_ps {
|
||||
u32 wakers;
|
||||
bool enable;
|
||||
bool suspended;
|
||||
/* PS state lock */
|
||||
struct mutex lock;
|
||||
struct delayed_work delayed_eval_work;
|
||||
};
|
||||
|
||||
enum mm81x_page_aci {
|
||||
MM81X_ACI_BE = 0,
|
||||
MM81X_ACI_BK = 1,
|
||||
MM81X_ACI_VI = 2,
|
||||
MM81X_ACI_VO = 3,
|
||||
};
|
||||
|
||||
enum mm81x_qos_tid_up_index {
|
||||
MM81X_QOS_TID_UP_BK = 1,
|
||||
MM81X_QOS_TID_UP_XX = 2,
|
||||
MM81X_QOS_TID_UP_BE = 0,
|
||||
MM81X_QOS_TID_UP_EE = 3,
|
||||
MM81X_QOS_TID_UP_CL = 4,
|
||||
MM81X_QOS_TID_UP_VI = 5,
|
||||
MM81X_QOS_TID_UP_VO = 6,
|
||||
MM81X_QOS_TID_UP_NC = 7,
|
||||
|
||||
MM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK,
|
||||
MM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC
|
||||
};
|
||||
|
||||
struct mm81x_sw_version {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
u8 patch;
|
||||
};
|
||||
|
||||
struct mm81x_sta {
|
||||
const struct ieee80211_vif *vif;
|
||||
u8 addr[ETH_ALEN];
|
||||
enum ieee80211_sta_state state;
|
||||
bool tid_tx[IEEE80211_NUM_TIDS];
|
||||
bool tid_start_tx[IEEE80211_NUM_TIDS];
|
||||
u8 tid_params[IEEE80211_NUM_TIDS];
|
||||
int max_bw_mhz;
|
||||
struct mm81x_rc_sta rc;
|
||||
struct mmrc_rate last_sta_tx_rate;
|
||||
s16 avg_rssi;
|
||||
bool tx_ps_filter_en;
|
||||
};
|
||||
|
||||
struct mm81x_vif {
|
||||
struct mm81x *mors;
|
||||
u16 id;
|
||||
|
||||
union {
|
||||
struct {
|
||||
bool is_assoc;
|
||||
} sta;
|
||||
struct {
|
||||
u32 num_stas;
|
||||
struct work_struct beacon_work;
|
||||
} ap;
|
||||
} u;
|
||||
};
|
||||
|
||||
struct mm81x_stale_tx_status {
|
||||
/* Stale Tx lock */
|
||||
spinlock_t lock;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
struct mcast_filter {
|
||||
u8 count;
|
||||
/*
|
||||
* Integer representation of the last four bytes of a multicast MAC
|
||||
* address. The first two bytes are always 0x0100 (IPv4) or 0x3333
|
||||
* (IPv6).
|
||||
*/
|
||||
__le32 addr_list[];
|
||||
};
|
||||
|
||||
enum mm81x_hw_scan_op {
|
||||
MM81X_HW_SCAN_OP_START,
|
||||
MM81X_HW_SCAN_OP_STOP,
|
||||
};
|
||||
|
||||
struct mm81x_hw_scan_params {
|
||||
struct ieee80211_hw *hw;
|
||||
|
||||
/* vif which initiated the scan */
|
||||
struct ieee80211_vif *vif;
|
||||
bool has_directed_ssid;
|
||||
u32 dwell_time_ms;
|
||||
u32 dwell_on_home_ms;
|
||||
enum mm81x_hw_scan_op operation;
|
||||
bool store;
|
||||
struct sk_buff *probe_req;
|
||||
u16 num_chans;
|
||||
u16 allocated_chans;
|
||||
|
||||
struct {
|
||||
struct ieee80211_channel *channel;
|
||||
/* Index into @ref powers_qdbm for the power of this channel */
|
||||
u8 power_idx;
|
||||
} *channels;
|
||||
|
||||
s32 *powers_qdbm;
|
||||
u8 n_powers;
|
||||
};
|
||||
|
||||
enum mm81x_hw_scan_state {
|
||||
HW_SCAN_STATE_IDLE,
|
||||
HW_SCAN_STATE_RUNNING,
|
||||
HW_SCAN_STATE_ABORTING,
|
||||
};
|
||||
|
||||
struct mm81x_hw_scan {
|
||||
enum mm81x_hw_scan_state state;
|
||||
struct completion scan_done;
|
||||
struct mm81x_hw_scan_params *params;
|
||||
struct delayed_work timeout;
|
||||
u32 home_dwell_ms;
|
||||
};
|
||||
|
||||
enum mm81x_hif_event_flags {
|
||||
MM81X_HIF_EVT_RX_PEND,
|
||||
MM81X_HIF_EVT_PAGE_RETURN_PEND,
|
||||
MM81X_HIF_EVT_TX_COMMAND_PEND,
|
||||
MM81X_HIF_EVT_TX_BEACON_PEND,
|
||||
MM81X_HIF_EVT_TX_MGMT_PEND,
|
||||
MM81X_HIF_EVT_TX_DATA_PEND,
|
||||
MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
|
||||
MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
|
||||
MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
|
||||
MM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE,
|
||||
};
|
||||
|
||||
enum mm81x_state_flags {
|
||||
MM81X_STATE_CHIP_UNRESPONSIVE,
|
||||
MM81X_STATE_DATA_QS_STOPPED,
|
||||
MM81X_STATE_DATA_TX_STOPPED,
|
||||
MM81X_STATE_REGDOM_SET_BY_USER,
|
||||
MM81X_STATE_REGDOM_SET_BY_OTP,
|
||||
MM81X_STATE_RELOAD_FW_AFTER_START,
|
||||
MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,
|
||||
MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,
|
||||
};
|
||||
|
||||
#define MM81X_COUNTRY_LEN (3)
|
||||
#define INVALID_VIF_INDEX 0xFF
|
||||
|
||||
struct mm81x {
|
||||
u32 chip_id;
|
||||
u32 host_table_ptr;
|
||||
|
||||
/* Refer to @enum mm81x_bus_type */
|
||||
u32 bus_type;
|
||||
u32 bcf_address;
|
||||
|
||||
/*
|
||||
* Parsed from the release tag, which should be in the format
|
||||
* 'rel_<major>_<minor>_<patch>'. If the tag is not in this format
|
||||
* then corresponding version field will be 0.
|
||||
*/
|
||||
struct mm81x_sw_version sw_ver;
|
||||
u8 macaddr[ETH_ALEN];
|
||||
u8 country[MM81X_COUNTRY_LEN];
|
||||
|
||||
/* Mask of type @enum host_table_firmware_flags */
|
||||
u32 fw_flags;
|
||||
u32 fw_major;
|
||||
struct mm81x_fw_caps fw_caps;
|
||||
bool started;
|
||||
bool chip_was_reset;
|
||||
struct wiphy *wiphy;
|
||||
struct mm81x_hw_scan hw_scan;
|
||||
struct ieee80211_hw *hw;
|
||||
struct device *dev;
|
||||
|
||||
struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF];
|
||||
|
||||
/* @mm81x_state_flags */
|
||||
unsigned long state_flags;
|
||||
|
||||
u16 cmd_seq;
|
||||
struct completion *cmd_comp;
|
||||
/* Serialises commands */
|
||||
struct mutex cmd_lock;
|
||||
|
||||
/* Serialises command completion */
|
||||
struct mutex cmd_wait;
|
||||
|
||||
const struct mm81x_regs *regs;
|
||||
|
||||
struct {
|
||||
union {
|
||||
struct mm81x_yaps yaps;
|
||||
} u;
|
||||
const struct mm81x_hif_ops *ops;
|
||||
/* See @enum mm81x_hif_event_flags for values */
|
||||
unsigned long event_flags;
|
||||
bool validate_skb_checksum;
|
||||
} hif;
|
||||
|
||||
struct workqueue_struct *chip_wq;
|
||||
struct work_struct hif_work;
|
||||
struct work_struct usb_irq_work;
|
||||
struct mm81x_stale_tx_status stale_status;
|
||||
bool config_ps;
|
||||
struct mm81x_ps ps;
|
||||
|
||||
/* Tx power in mBm received from the FW before association */
|
||||
s32 tx_power_mbm;
|
||||
s32 tx_max_power_mbm;
|
||||
|
||||
const struct mm81x_bus_ops *bus_ops;
|
||||
struct mm81x_rc mrc;
|
||||
int rts_threshold;
|
||||
struct workqueue_struct *net_wq;
|
||||
struct work_struct tx_stale_work;
|
||||
wait_queue_head_t tx_empty_waitq;
|
||||
|
||||
struct cfg80211_chan_def chandef;
|
||||
struct mcast_filter *mcast_filter;
|
||||
atomic_t num_bcn_vifs;
|
||||
unsigned long beacon_irqs_enabled;
|
||||
u8 drv_priv[] __aligned(sizeof(void *));
|
||||
};
|
||||
|
||||
/* Map from mac80211 queue to Morse ACI value for page metadata */
|
||||
static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue)
|
||||
{
|
||||
switch (mac80211queue) {
|
||||
case IEEE80211_AC_VO:
|
||||
return MM81X_ACI_VO;
|
||||
case IEEE80211_AC_VI:
|
||||
return MM81X_ACI_VI;
|
||||
case IEEE80211_AC_BK:
|
||||
return MM81X_ACI_BK;
|
||||
default:
|
||||
return MM81X_ACI_BE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline enum mm81x_page_aci
|
||||
dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid)
|
||||
{
|
||||
switch (tid) {
|
||||
case MM81X_QOS_TID_UP_BK:
|
||||
case MM81X_QOS_TID_UP_XX:
|
||||
return MM81X_ACI_BK;
|
||||
case MM81X_QOS_TID_UP_CL:
|
||||
case MM81X_QOS_TID_UP_VI:
|
||||
return MM81X_ACI_VI;
|
||||
case MM81X_QOS_TID_UP_VO:
|
||||
case MM81X_QOS_TID_UP_NC:
|
||||
return MM81X_ACI_VO;
|
||||
case MM81X_QOS_TID_UP_BE:
|
||||
case MM81X_QOS_TID_UP_EE:
|
||||
default:
|
||||
return MM81X_ACI_BE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors)
|
||||
{
|
||||
return !test_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags) &&
|
||||
!test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
|
||||
&mors->hif.event_flags);
|
||||
}
|
||||
|
||||
static inline struct ieee80211_vif *
|
||||
mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif)
|
||||
{
|
||||
return container_of((void *)mors_vif, struct ieee80211_vif, drv_priv);
|
||||
}
|
||||
|
||||
static inline struct mm81x_vif *
|
||||
ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif)
|
||||
{
|
||||
return (struct mm81x_vif *)vif->drv_priv;
|
||||
}
|
||||
|
||||
static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif)
|
||||
{
|
||||
return mors_vif->mors;
|
||||
}
|
||||
|
||||
static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len)
|
||||
{
|
||||
return ~crc32(~0, ssid, len);
|
||||
}
|
||||
|
||||
int mm81x_beacon_init(struct mm81x_vif *mors_vif);
|
||||
void mm81x_beacon_finish(struct mm81x_vif *mors_vif);
|
||||
void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status);
|
||||
char *mm81x_core_get_fw_path(u32 chip_id, u32 fw_ver);
|
||||
struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev);
|
||||
int mm81x_core_init(struct mm81x *mors);
|
||||
int mm81x_core_register(struct mm81x *mors);
|
||||
void mm81x_core_unregister(struct mm81x *mors);
|
||||
void mm81x_core_deinit(struct mm81x *mors);
|
||||
void mm81x_core_free(struct mm81x *mors);
|
||||
|
||||
#endif /* !_MM81X_MM81X_H_ */
|
||||
752
drivers/net/wireless/morsemicro/mm81x/fw.c
Normal file
752
drivers/net/wireless/morsemicro/mm81x/fw.c
Normal file
@@ -0,0 +1,752 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <net/mac80211.h>
|
||||
#include <linux/elf.h>
|
||||
#include <linux/crc32.h>
|
||||
#include "fw.h"
|
||||
#include "mac.h"
|
||||
#include "bus.h"
|
||||
|
||||
/*
|
||||
* Maximum wait time (microseconds) for firmware to boot (for host table
|
||||
* pointer to be available)
|
||||
*/
|
||||
#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000
|
||||
#define HOST_TABLE_PTR_POLL_PERIOD_US 10000
|
||||
|
||||
/* Number of times to attempt flashing FW */
|
||||
#define FW_FLASH_ATTEMPT_COUNT 3
|
||||
|
||||
static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr)
|
||||
{
|
||||
const struct mm81x_elf32_ehdr *p =
|
||||
(const struct mm81x_elf32_ehdr *)data;
|
||||
|
||||
/* Magic check */
|
||||
if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 ||
|
||||
p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3)
|
||||
return -EINVAL;
|
||||
|
||||
/* elf32 and little endian */
|
||||
if (p->e_ident[EI_DATA] != ELFDATA2LSB ||
|
||||
p->e_ident[EI_CLASS] != ELFCLASS32)
|
||||
return -EINVAL;
|
||||
|
||||
ehdr->e_phoff = le32_to_cpu(p->e_phoff);
|
||||
ehdr->e_phentsize = le16_to_cpu(p->e_phentsize);
|
||||
ehdr->e_phnum = le16_to_cpu(p->e_phnum);
|
||||
ehdr->e_shoff = le32_to_cpu(p->e_shoff);
|
||||
ehdr->e_shentsize = le16_to_cpu(p->e_shentsize);
|
||||
ehdr->e_shnum = le16_to_cpu(p->e_shnum);
|
||||
ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx);
|
||||
ehdr->e_entry = le32_to_cpu(p->e_entry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length)
|
||||
{
|
||||
const struct mm81x_fw_info_tlv *tlv =
|
||||
(const struct mm81x_fw_info_tlv *)data;
|
||||
|
||||
while ((u8 *)tlv < (data + length)) {
|
||||
switch (le16_to_cpu(tlv->type)) {
|
||||
case MM81X_FW_INFO_TLV_BCF_ADDR:
|
||||
mors->bcf_address = get_unaligned_le32(tlv->val);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +
|
||||
le16_to_cpu(
|
||||
tlv->length) +
|
||||
sizeof(*tlv));
|
||||
}
|
||||
}
|
||||
|
||||
static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr,
|
||||
Elf32_Shdr *shdr, int i)
|
||||
{
|
||||
const struct mm81x_elf32_shdr *p =
|
||||
(void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize));
|
||||
|
||||
shdr->sh_name = le32_to_cpu(p->sh_name);
|
||||
shdr->sh_type = le32_to_cpu(p->sh_type);
|
||||
shdr->sh_offset = le32_to_cpu(p->sh_offset);
|
||||
shdr->sh_addr = le32_to_cpu(p->sh_addr);
|
||||
shdr->sh_size = le32_to_cpu(p->sh_size);
|
||||
shdr->sh_flags = le32_to_cpu(p->sh_flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr)
|
||||
{
|
||||
int status;
|
||||
|
||||
dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr);
|
||||
mm81x_claim_bus(mors);
|
||||
status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);
|
||||
mm81x_release_bus(mors);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
Elf32_Ehdr ehdr;
|
||||
Elf32_Phdr phdr;
|
||||
Elf32_Shdr shdr;
|
||||
Elf32_Shdr sh_strtab;
|
||||
const char *sh_strs;
|
||||
|
||||
u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size),
|
||||
GFP_KERNEL);
|
||||
|
||||
if (!fw_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (mm81x_fw_get_header(fw->data, &ehdr)) {
|
||||
dev_err(mors->dev, "Wrong file format");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
|
||||
ehdr.e_shstrndx)) {
|
||||
dev_err(mors->dev, "Invalid firmware. Missing string table");
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
sh_strs = (const char *)fw->data + sh_strtab.sh_offset;
|
||||
|
||||
for (i = 0; i < ehdr.e_phnum; i++) {
|
||||
int status;
|
||||
int address;
|
||||
const struct mm81x_elf32_phdr *p =
|
||||
(void *)(fw->data + ehdr.e_phoff +
|
||||
i * ehdr.e_phentsize);
|
||||
|
||||
phdr.p_type = le32_to_cpu(p->p_type);
|
||||
phdr.p_offset = le32_to_cpu(p->p_offset);
|
||||
phdr.p_paddr = le32_to_cpu(p->p_paddr);
|
||||
phdr.p_filesz = le32_to_cpu(p->p_filesz);
|
||||
phdr.p_memsz = le32_to_cpu(p->p_memsz);
|
||||
|
||||
address = phdr.p_paddr;
|
||||
|
||||
if (phdr.p_type != PT_LOAD || !phdr.p_memsz)
|
||||
continue;
|
||||
|
||||
if (phdr.p_filesz && phdr.p_offset &&
|
||||
(phdr.p_offset + phdr.p_filesz) < fw->size) {
|
||||
u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);
|
||||
|
||||
memcpy(fw_buf, fw->data + phdr.p_offset, padded_size);
|
||||
/* Set padding to 0xff */
|
||||
memset(fw_buf + phdr.p_filesz, 0xff,
|
||||
padded_size - phdr.p_filesz);
|
||||
mm81x_claim_bus(mors);
|
||||
status = mm81x_dm_write(mors, address, fw_buf,
|
||||
padded_size);
|
||||
mm81x_release_bus(mors);
|
||||
if (status) {
|
||||
ret = -EIO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < ehdr.e_shnum; i++) {
|
||||
if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i))
|
||||
continue;
|
||||
|
||||
/* This is the firmware info. Parse it */
|
||||
if (!strncmp(sh_strs + shdr.sh_name, ".fw_info",
|
||||
sizeof(".fw_info")))
|
||||
mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset,
|
||||
shdr.sh_size);
|
||||
}
|
||||
|
||||
if (ehdr.e_entry)
|
||||
ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);
|
||||
|
||||
devm_kfree(mors->dev, fw_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr,
|
||||
const void *src, size_t src_len, u8 *scratch,
|
||||
size_t scratch_cap)
|
||||
{
|
||||
size_t rounded = ROUND_BYTES_TO_WORD(src_len);
|
||||
int st;
|
||||
|
||||
if (rounded > scratch_cap)
|
||||
return -EINVAL;
|
||||
if (rounded > BCF_DATABASE_SIZE)
|
||||
return -EFBIG;
|
||||
|
||||
memcpy(scratch, src, src_len);
|
||||
if (rounded > src_len)
|
||||
memset(scratch + src_len, 0xff, rounded - src_len);
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
st = mm81x_dm_write(mors, addr, scratch, rounded);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
return st ? -EIO : 0;
|
||||
}
|
||||
|
||||
static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf,
|
||||
unsigned int bcf_address)
|
||||
{
|
||||
int i, ret = 0;
|
||||
size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;
|
||||
Elf32_Ehdr ehdr;
|
||||
Elf32_Shdr shdr, sh_strtab;
|
||||
const char *sh_strs, *reg_prefix = ".regdom_", *reg_src;
|
||||
size_t reg_len;
|
||||
u8 *bcf_buf;
|
||||
|
||||
bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size),
|
||||
GFP_KERNEL);
|
||||
if (!bcf_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
if (mm81x_fw_get_header(bcf->data, &ehdr)) {
|
||||
dev_err(mors->dev, "Wrong file format");
|
||||
ret = -EINVAL;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
|
||||
ehdr.e_shstrndx)) {
|
||||
dev_err(mors->dev, "Invalid BCF - missing string table");
|
||||
ret = -ENOENT;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
sh_strs = (const char *)bcf->data + sh_strtab.sh_offset;
|
||||
reg_prefix_len = strlen(reg_prefix);
|
||||
|
||||
for (i = 0; i < ehdr.e_shnum; i++) {
|
||||
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
|
||||
continue;
|
||||
if (strcmp(sh_strs + shdr.sh_name, ".board_config"))
|
||||
continue;
|
||||
|
||||
cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);
|
||||
dev_dbg(mors->dev,
|
||||
"Write BCF board_config - addr 0x%x size %zu",
|
||||
bcf_address, cfg_len_rounded);
|
||||
|
||||
ret = __mm81x_fw_load_bcf(mors, bcf_address,
|
||||
bcf->data + shdr.sh_offset,
|
||||
shdr.sh_size, bcf_buf,
|
||||
ROUND_BYTES_TO_WORD(bcf->size));
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
bcf_address += cfg_len_rounded;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = -EINVAL;
|
||||
for (; i < ehdr.e_shnum; i++) {
|
||||
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
|
||||
continue;
|
||||
if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))
|
||||
continue;
|
||||
if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,
|
||||
mors->country, 2))
|
||||
continue;
|
||||
|
||||
reg_src = bcf->data + shdr.sh_offset;
|
||||
reg_len = shdr.sh_size;
|
||||
dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu",
|
||||
sh_strs + shdr.sh_name, bcf_address,
|
||||
ROUND_BYTES_TO_WORD(reg_len));
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);
|
||||
if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) {
|
||||
ret = -EFBIG;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf,
|
||||
ROUND_BYTES_TO_WORD(bcf->size));
|
||||
|
||||
out_free:
|
||||
devm_kfree(mors->dev, bcf_buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_fw_clear_aon(struct mm81x *mors)
|
||||
{
|
||||
int idx;
|
||||
u8 count = MM81X_REG_AON_COUNT(mors);
|
||||
u32 address = MM81X_REG_AON_ADDR(mors);
|
||||
|
||||
for (idx = 0; idx < count; idx++, address += 4) {
|
||||
if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
|
||||
/* Keep the USB power domain enabled in AON. */
|
||||
mm81x_reg32_write(mors, address,
|
||||
MM81X_REG_AON_USB_RESET(mors));
|
||||
else
|
||||
/* clear AON */
|
||||
mm81x_reg32_write(mors, address, 0x0);
|
||||
}
|
||||
|
||||
mm81x_hw_toggle_aon_latch(mors);
|
||||
}
|
||||
|
||||
static void mm81x_fw_trigger(struct mm81x *mors)
|
||||
{
|
||||
const unsigned int wait_after_msi_trigger_ms = 1;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
/*
|
||||
* If not coming from a full reset, some AON flags may be latched.
|
||||
* Make sure to clear any hanging AON bits (can affect booting).
|
||||
*/
|
||||
mm81x_fw_clear_aon(mors);
|
||||
|
||||
if (MM81X_REG_CLK_CTRL(mors))
|
||||
mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),
|
||||
MM81X_REG_CLK_CTRL_VALUE(mors));
|
||||
|
||||
mm81x_reg32_write(mors, MM81X_REG_MSI(mors),
|
||||
MM81X_REG_MSI_HOST_INT(mors));
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
/* Give the chip a chance to boot */
|
||||
mdelay(wait_after_msi_trigger_ms);
|
||||
}
|
||||
|
||||
static int mm81x_fw_verify_magic(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_reg32_read(mors,
|
||||
mors->host_table_ptr +
|
||||
offsetof(struct host_table, magic_number),
|
||||
&magic);
|
||||
|
||||
if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {
|
||||
dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x",
|
||||
MM81X_REG_HOST_MAGIC_VALUE(mors), magic);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
mm81x_release_bus(mors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_fw_get_flags(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
int fw_flags = 0;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
ret = mm81x_reg32_read(mors,
|
||||
mors->host_table_ptr +
|
||||
offsetof(struct host_table, fw_flags),
|
||||
&fw_flags);
|
||||
mors->fw_flags = fw_flags;
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_fw_check_compatibility(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 fw_version;
|
||||
u32 major;
|
||||
u32 minor;
|
||||
u32 patch;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
ret = mm81x_reg32_read(mors,
|
||||
mors->host_table_ptr +
|
||||
offsetof(struct host_table,
|
||||
fw_version_number),
|
||||
&fw_version);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
major = MM81X_SEMVER_GET_MAJOR(fw_version);
|
||||
minor = MM81X_SEMVER_GET_MINOR(fw_version);
|
||||
patch = MM81X_SEMVER_GET_PATCH(fw_version);
|
||||
|
||||
/* Firmware on device must match the firmware file we requested */
|
||||
if (ret == 0 && major != mors->fw_major) {
|
||||
dev_err(mors->dev,
|
||||
"Incompatible FW version: (Requested) v%u, (Chip) %d.%d.%d\n",
|
||||
mors->fw_major, major, minor, patch);
|
||||
ret = -EPERM;
|
||||
} else if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) {
|
||||
dev_warn(
|
||||
mors->dev,
|
||||
"Running FW v%d.%d.%d, driver supports up to v%d, some features might not be supported",
|
||||
major, minor, patch, HOST_CMD_SEMVER_MAJOR);
|
||||
} else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) {
|
||||
dev_warn(
|
||||
mors->dev,
|
||||
"FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d",
|
||||
HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
|
||||
HOST_CMD_SEMVER_PATCH, major, minor, patch);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mors->host_table_ptr = 0;
|
||||
mm81x_claim_bus(mors);
|
||||
ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);
|
||||
mm81x_release_bus(mors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)
|
||||
{
|
||||
int ret, err;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
ret = read_poll_timeout(mm81x_reg32_read, err,
|
||||
err || mors->host_table_ptr,
|
||||
HOST_TABLE_PTR_POLL_PERIOD_US,
|
||||
HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors,
|
||||
MM81X_REG_HOST_MANIFEST_PTR(mors),
|
||||
&mors->host_table_ptr);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
return ret ? ret : err;
|
||||
}
|
||||
|
||||
static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
|
||||
struct ext_host_tbl **ext_host_table)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 host_tbl_ptr = mors->host_table_ptr;
|
||||
u32 ext_host_tbl_ptr;
|
||||
u32 ext_host_tbl_ptr_addr =
|
||||
host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);
|
||||
u32 ext_host_tbl_len;
|
||||
u32 ext_host_tbl_len_ptr_addr;
|
||||
struct ext_host_tbl *host_tbl = NULL;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
if (!ext_host_tbl_ptr) {
|
||||
ret = -ENXIO;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ext_host_tbl_len_ptr_addr =
|
||||
ext_host_tbl_ptr +
|
||||
offsetof(struct ext_host_tbl, ext_host_tbl_length);
|
||||
|
||||
ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr,
|
||||
&ext_host_tbl_len);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);
|
||||
if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);
|
||||
if (!host_tbl) {
|
||||
ret = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl,
|
||||
(int)ext_host_tbl_len);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mm81x_release_bus(mors);
|
||||
*ext_host_table = host_tbl;
|
||||
return ret;
|
||||
|
||||
exit:
|
||||
mm81x_release_bus(mors);
|
||||
kfree(host_tbl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_fw_update_capabilities(struct mm81x *mors,
|
||||
struct ext_host_tbl_s1g_caps *caps)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
|
||||
mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
|
||||
dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i,
|
||||
le32_to_cpu(caps->flags[i]));
|
||||
}
|
||||
mors->fw_caps.ampdu_mss = caps->ampdu_mss;
|
||||
mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
|
||||
mors->fw_caps.beamformee_sts_capability =
|
||||
caps->beamformee_sts_capability;
|
||||
mors->fw_caps.maximum_ampdu_length_exponent =
|
||||
caps->maximum_ampdu_length;
|
||||
mors->fw_caps.number_sounding_dimensions =
|
||||
caps->number_sounding_dimensions;
|
||||
|
||||
dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u",
|
||||
caps->ampdu_mss);
|
||||
dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u",
|
||||
caps->mm81x_mmss_offset);
|
||||
dev_dbg(mors->dev, "\tBeamformee STS Capability: %u",
|
||||
caps->beamformee_sts_capability);
|
||||
dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u",
|
||||
caps->number_sounding_dimensions);
|
||||
dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u",
|
||||
caps->maximum_ampdu_length);
|
||||
}
|
||||
|
||||
static void mm81x_fw_update_validate_skb_checksum(
|
||||
struct mm81x *mors,
|
||||
struct ext_host_tbl_insert_skb_checksum *validate_checksum)
|
||||
{
|
||||
mors->hif.validate_skb_checksum =
|
||||
validate_checksum->insert_and_validate_checksum;
|
||||
dev_dbg(mors->dev, "Validate checksum inserted by fw %s",
|
||||
str_enabled_disabled(mors->hif.validate_skb_checksum));
|
||||
}
|
||||
|
||||
int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
|
||||
{
|
||||
int ret;
|
||||
u8 *head;
|
||||
u8 *end;
|
||||
struct ext_host_tbl *ext_host_table = NULL;
|
||||
|
||||
ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table);
|
||||
if (ret || !ext_host_table)
|
||||
goto exit;
|
||||
|
||||
/* Parse the TLVs */
|
||||
head = ext_host_table->ext_host_table_data_tlvs;
|
||||
end = ((u8 *)ext_host_table) +
|
||||
le32_to_cpu(ext_host_table->ext_host_tbl_length);
|
||||
|
||||
while (head < end) {
|
||||
struct ext_host_tbl_tlv_hdr *hdr =
|
||||
(struct ext_host_tbl_tlv_hdr *)head;
|
||||
|
||||
switch (le16_to_cpu(hdr->tag)) {
|
||||
case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
|
||||
mm81x_fw_update_capabilities(
|
||||
mors, (struct ext_host_tbl_s1g_caps *)hdr);
|
||||
break;
|
||||
|
||||
case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
|
||||
mm81x_fw_update_validate_skb_checksum(
|
||||
mors,
|
||||
(struct ext_host_tbl_insert_skb_checksum *)hdr);
|
||||
break;
|
||||
|
||||
case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
|
||||
mm81x_yaps_hw_read_table(
|
||||
mors, &((struct ext_host_tbl_yaps_table *)hdr)
|
||||
->yaps_table);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
head += le16_to_cpu(hdr->length);
|
||||
if (!hdr->length)
|
||||
break;
|
||||
}
|
||||
|
||||
kfree(ext_host_table);
|
||||
return ret;
|
||||
exit:
|
||||
dev_err(mors->dev, "failed to parse ext host table %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
|
||||
const struct firmware *bcf, bool reset)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (reset || !mors->chip_was_reset) {
|
||||
ret = mm81x_hw_digital_reset(mors);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
mm81x_hw_pre_firmware_ndr_hook(mors);
|
||||
|
||||
ret = mm81x_fw_invalidate_host_ptr(mors);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mm81x_fw_load_fw(mors, fw);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mm81x_fw_trigger(mors);
|
||||
mm81x_hw_post_firmware_ndr_hook(mors);
|
||||
|
||||
ret = mm81x_fw_get_host_table_ptr(mors);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mm81x_fw_verify_magic(mors);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return mm81x_fw_check_compatibility(mors);
|
||||
}
|
||||
|
||||
static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
|
||||
const struct firmware *bcf, bool reset)
|
||||
{
|
||||
int ret;
|
||||
int retries = FW_FLASH_ATTEMPT_COUNT;
|
||||
|
||||
while (retries--) {
|
||||
ret = __mm81x_fw_flash(mors, fw, bcf, reset);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
mors->chip_was_reset = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t binary_crc(const struct firmware *fw)
|
||||
{
|
||||
return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) &
|
||||
0xffffffff;
|
||||
}
|
||||
|
||||
static int mm81x_fw_request(struct mm81x *mors, const struct firmware **fw)
|
||||
{
|
||||
int ret = -ENOENT;
|
||||
int ver;
|
||||
char *fw_path;
|
||||
|
||||
for (ver = MM81X_FW_VER_MAX; ver >= MM81X_FW_VER_MIN; ver--) {
|
||||
fw_path = mm81x_core_get_fw_path(mors->chip_id, ver);
|
||||
if (!fw_path)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = firmware_request_nowarn(fw, fw_path, mors->dev);
|
||||
if (!ret) {
|
||||
dev_info(
|
||||
mors->dev,
|
||||
"Loaded firmware from %s, size %zu, crc32 0x%08x\n",
|
||||
fw_path, (*fw)->size, binary_crc(*fw));
|
||||
mors->fw_major = ver;
|
||||
}
|
||||
|
||||
kfree(fw_path);
|
||||
if (!ret)
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_err(mors->dev, "no firmware found (tried v%d down to v%d): %d\n",
|
||||
MM81X_FW_VER_MAX, MM81X_FW_VER_MIN, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_fw_init(struct mm81x *mors, bool reset)
|
||||
{
|
||||
int ret;
|
||||
int board_id;
|
||||
char *bcf_path = NULL;
|
||||
const struct firmware *fw = NULL;
|
||||
const struct firmware *bcf = NULL;
|
||||
|
||||
board_id = mm81x_hw_otp_get_board_type(mors);
|
||||
|
||||
if (!mm81x_hw_otp_valid_board_type(board_id)) {
|
||||
dev_err(mors->dev,
|
||||
"OTP not set, unable to determine BCF to use");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_dbg(mors->dev, "Using board type 0x%04x from OTP", board_id);
|
||||
|
||||
ret = mm81x_fw_request(mors, &fw);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
bcf_path = kasprintf(GFP_KERNEL,
|
||||
MM81X_FW_DIR
|
||||
"/v%u/bcf_boardtype_%04x" MM81X_FW_EXT,
|
||||
mors->fw_major, board_id);
|
||||
if (!bcf_path) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = request_firmware(&bcf, bcf_path, mors->dev);
|
||||
if (ret) {
|
||||
if (ret == -ENOENT)
|
||||
dev_err(mors->dev, "BCF %s not found\n", bcf_path);
|
||||
goto out;
|
||||
}
|
||||
|
||||
dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
|
||||
bcf_path, bcf->size, binary_crc(bcf));
|
||||
|
||||
ret = mm81x_fw_flash(mors, fw, bcf, reset);
|
||||
if (ret) {
|
||||
dev_err(mors->dev, "failed to flash firmware: %d", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = mm81x_fw_get_flags(mors);
|
||||
|
||||
out:
|
||||
release_firmware(fw);
|
||||
release_firmware(bcf);
|
||||
kfree(bcf_path);
|
||||
|
||||
if (ret)
|
||||
dev_err(mors->dev, "failed to init firmware: %d", ret);
|
||||
else
|
||||
dev_dbg(mors->dev, "firmware initialised");
|
||||
|
||||
return ret;
|
||||
}
|
||||
143
drivers/net/wireless/morsemicro/mm81x/fw.h
Normal file
143
drivers/net/wireless/morsemicro/mm81x/fw.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_FW_H_
|
||||
#define _MM81X_FW_H_
|
||||
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/elf.h>
|
||||
#include "command_defs.h"
|
||||
#include "yaps_hw.h"
|
||||
|
||||
#define BCF_DATABASE_SIZE (1024)
|
||||
#define MM81X_FW_DIR "morsemicro/mm81x"
|
||||
#define MM81X_FW_EXT ".bin"
|
||||
|
||||
#define MM81X_FW_VER_MAX HOST_CMD_SEMVER_MAJOR
|
||||
#define MM81X_FW_VER_MIN 56
|
||||
|
||||
/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */
|
||||
#define FW_CAPABILITIES_FLAGS_WIDTH (4)
|
||||
|
||||
struct mm81x_elf32_ehdr {
|
||||
unsigned char e_ident[EI_NIDENT];
|
||||
__le16 e_type;
|
||||
__le16 e_machine;
|
||||
__le32 e_version;
|
||||
__le32 e_entry;
|
||||
__le32 e_phoff;
|
||||
__le32 e_shoff;
|
||||
__le32 e_flags;
|
||||
__le16 e_ehsize;
|
||||
__le16 e_phentsize;
|
||||
__le16 e_phnum;
|
||||
__le16 e_shentsize;
|
||||
__le16 e_shnum;
|
||||
__le16 e_shstrndx;
|
||||
} __packed;
|
||||
|
||||
struct mm81x_elf32_shdr {
|
||||
__le32 sh_name;
|
||||
__le32 sh_type;
|
||||
__le32 sh_flags;
|
||||
__le32 sh_addr;
|
||||
__le32 sh_offset;
|
||||
__le32 sh_size;
|
||||
__le32 sh_link;
|
||||
__le32 sh_info;
|
||||
__le32 sh_addralign;
|
||||
__le32 sh_entsize;
|
||||
} __packed;
|
||||
|
||||
struct mm81x_elf32_phdr {
|
||||
__le32 p_type;
|
||||
__le32 p_offset;
|
||||
__le32 p_vaddr;
|
||||
__le32 p_paddr;
|
||||
__le32 p_filesz;
|
||||
__le32 p_memsz;
|
||||
__le32 p_flags;
|
||||
__le32 p_align;
|
||||
} __packed;
|
||||
|
||||
enum mm81x_fw_info_tlv_type {
|
||||
MM81X_FW_INFO_TLV_BCF_ADDR = 1,
|
||||
};
|
||||
|
||||
struct mm81x_fw_info_tlv {
|
||||
__le16 type;
|
||||
__le16 length;
|
||||
u8 val[];
|
||||
} __packed;
|
||||
|
||||
enum mm81x_fw_ext_host_tbl_tag {
|
||||
/* The S1G capability tag */
|
||||
MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0,
|
||||
MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1,
|
||||
MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2,
|
||||
MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3,
|
||||
MM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4,
|
||||
MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5,
|
||||
};
|
||||
|
||||
struct ext_host_tbl_tlv_hdr {
|
||||
/* The tag used to identify which capability this represents */
|
||||
__le16 tag;
|
||||
/* The length of the capability structure including this header */
|
||||
__le16 length;
|
||||
} __packed;
|
||||
|
||||
struct ext_host_tbl_s1g_caps {
|
||||
struct ext_host_tbl_tlv_hdr header;
|
||||
__le32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
|
||||
/*
|
||||
* The minimum A-MPDU start spacing required by firmware.
|
||||
* Value | Description
|
||||
* ------|------------
|
||||
* 0 | No restriction
|
||||
* 1 | 1/4 us
|
||||
* 2 | 1/2 us
|
||||
* 3 | 1 us
|
||||
* 4 | 2 us
|
||||
* 5 | 4 us
|
||||
* 6 | 8 us
|
||||
* 7 | 16 us
|
||||
*/
|
||||
u8 ampdu_mss;
|
||||
u8 beamformee_sts_capability;
|
||||
u8 number_sounding_dimensions;
|
||||
/*
|
||||
* The maximum A-MPDU length. This is the exponent value such that
|
||||
* (2^(13 + exponent) - 1) is the length
|
||||
*/
|
||||
u8 maximum_ampdu_length;
|
||||
/*
|
||||
* Offset to apply to the specification's MMSS table to signal further
|
||||
* minimum MPDU start spacing.
|
||||
*/
|
||||
u8 mm81x_mmss_offset;
|
||||
} __packed;
|
||||
|
||||
struct ext_host_tbl_insert_skb_checksum {
|
||||
struct ext_host_tbl_tlv_hdr header;
|
||||
u8 insert_and_validate_checksum;
|
||||
};
|
||||
|
||||
struct ext_host_tbl_yaps_table {
|
||||
struct ext_host_tbl_tlv_hdr header;
|
||||
struct mm81x_yaps_hw_table yaps_table;
|
||||
} __packed;
|
||||
|
||||
struct ext_host_tbl {
|
||||
__le32 ext_host_tbl_length;
|
||||
u8 dev_mac_addr[6];
|
||||
u8 ext_host_table_data_tlvs[];
|
||||
} __packed;
|
||||
|
||||
int mm81x_fw_init(struct mm81x *mors, bool reset);
|
||||
int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors);
|
||||
|
||||
#endif /* !_MM81X_FW_H_ */
|
||||
117
drivers/net/wireless/morsemicro/mm81x/hif.h
Normal file
117
drivers/net/wireless/morsemicro/mm81x/hif.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_HIF_H_
|
||||
#define _MM81X_HIF_H_
|
||||
|
||||
#include "core.h"
|
||||
|
||||
struct mm81x_skbq;
|
||||
|
||||
#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15)
|
||||
#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29)
|
||||
#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \
|
||||
BIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM)
|
||||
#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \
|
||||
BIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM)
|
||||
|
||||
/* Hardware IF interrupt mask. We may use any interrupts in this range */
|
||||
#define MM81X_HIF_IRQ_MASK_ALL \
|
||||
(GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \
|
||||
MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE)
|
||||
|
||||
enum mm81x_hif_flags {
|
||||
MM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0),
|
||||
MM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1),
|
||||
MM81X_HIF_FLAGS_COMMAND = BIT(2),
|
||||
MM81X_HIF_FLAGS_BEACON = BIT(3),
|
||||
MM81X_HIF_FLAGS_DATA = BIT(4)
|
||||
};
|
||||
|
||||
struct mm81x_hif_ops {
|
||||
int (*init)(struct mm81x *mors);
|
||||
void (*flush_tx_data)(struct mm81x *mors);
|
||||
void (*flush_cmds)(struct mm81x *mors);
|
||||
void (*finish)(struct mm81x *mors);
|
||||
void (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs,
|
||||
int *num_qs);
|
||||
struct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors);
|
||||
struct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors);
|
||||
struct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors);
|
||||
struct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci);
|
||||
int (*handle_irq)(struct mm81x *mors, u32 status);
|
||||
int (*get_tx_buffered_count)(struct mm81x *mors);
|
||||
int (*get_tx_status_pending_count)(struct mm81x *mors);
|
||||
};
|
||||
|
||||
static inline void mm81x_hif_clear_events(struct mm81x *mors)
|
||||
{
|
||||
mors->hif.event_flags = 0;
|
||||
}
|
||||
|
||||
static inline int mm81x_hif_init(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->init(mors);
|
||||
}
|
||||
|
||||
static inline void mm81x_hif_flush_tx_data(struct mm81x *mors)
|
||||
{
|
||||
mors->hif.ops->flush_tx_data(mors);
|
||||
}
|
||||
|
||||
static inline void mm81x_hif_flush_cmds(struct mm81x *mors)
|
||||
{
|
||||
mors->hif.ops->flush_cmds(mors);
|
||||
}
|
||||
|
||||
static inline void mm81x_hif_finish(struct mm81x *mors)
|
||||
{
|
||||
mors->hif.ops->finish(mors);
|
||||
}
|
||||
|
||||
static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors,
|
||||
struct mm81x_skbq **qs, int *num_qs)
|
||||
{
|
||||
mors->hif.ops->skbq_get_tx_qs(mors, qs, num_qs);
|
||||
}
|
||||
|
||||
static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->get_tx_cmd_queue(mors);
|
||||
}
|
||||
|
||||
static inline struct mm81x_skbq *
|
||||
mm81x_hif_get_tx_beacon_queue(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->get_tx_beacon_queue(mors);
|
||||
}
|
||||
|
||||
static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->get_tx_mgmt_queue(mors);
|
||||
}
|
||||
|
||||
static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors,
|
||||
int aci)
|
||||
{
|
||||
return mors->hif.ops->get_tx_data_queue(mors, aci);
|
||||
}
|
||||
|
||||
static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status)
|
||||
{
|
||||
return mors->hif.ops->handle_irq(mors, status);
|
||||
}
|
||||
|
||||
static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->get_tx_buffered_count(mors);
|
||||
}
|
||||
|
||||
static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors)
|
||||
{
|
||||
return mors->hif.ops->get_tx_status_pending_count(mors);
|
||||
}
|
||||
|
||||
#endif /* _MM81X_HIF_H_ */
|
||||
367
drivers/net/wireless/morsemicro/mm81x/hw.c
Normal file
367
drivers/net/wireless/morsemicro/mm81x/hw.c
Normal file
@@ -0,0 +1,367 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/unaligned.h>
|
||||
#include <linux/gpio.h>
|
||||
#include "hif.h"
|
||||
#include "mac.h"
|
||||
#include "bus.h"
|
||||
#include "core.h"
|
||||
#include "fw.h"
|
||||
#include "yaps.h"
|
||||
|
||||
#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF
|
||||
#define MM8108_REG_RESET_VALUE 0xDEAD
|
||||
|
||||
#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C
|
||||
|
||||
#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9
|
||||
#define MM8108_REG_TRGR_BASE 0x00003c00
|
||||
#define MM8108_REG_INT_BASE 0x00003c50
|
||||
#define MM8108_REG_MSI_ADDRESS 0x00004100
|
||||
#define MM8108_REG_MSI_VALUE 0x1
|
||||
#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40
|
||||
#define MM8108_REG_APPS_BOOT_ADDR 0x00002084
|
||||
#define MM8108_REG_RESET 0x000020AC
|
||||
#define MM8108_REG_AON_COUNT 2
|
||||
|
||||
#define MM8108_REG_AON_ADDR 0x00002114
|
||||
#define MM8108_REG_AON_LATCH_ADDR 0x00405020
|
||||
#define MM8108_REG_AON_LATCH_MASK 0x1
|
||||
#define MM8108_REG_AON_RESET_USB_VALUE 0x8
|
||||
#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000
|
||||
|
||||
#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020
|
||||
#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040
|
||||
#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2
|
||||
|
||||
#define MM8108_RESET_DELAY_TIME_MS 400
|
||||
|
||||
#define MM8108_REG_OTPCTRL_PLDO 0x00004014
|
||||
#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010
|
||||
#define MM8108_REG_OTPCTRL_PDSTB 0x00004018
|
||||
#define MM8108_REG_OTPCTRL_PTM 0x0000401c
|
||||
#define MM8108_REG_OTPCTRL_PCE 0x00004020
|
||||
#define MM8108_REG_OTPCTRL_PA 0x00004034
|
||||
#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048
|
||||
#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c
|
||||
#define MM8108_REG_OTPCTRL_PDOUT 0x00004040
|
||||
|
||||
#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27
|
||||
#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26
|
||||
#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16)
|
||||
#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26
|
||||
#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0)
|
||||
|
||||
#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1)
|
||||
|
||||
static void mm81x_hw_otp_power_up(struct mm81x *mors)
|
||||
{
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1);
|
||||
udelay(2);
|
||||
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1);
|
||||
usleep_range(10, 20);
|
||||
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1);
|
||||
udelay(3);
|
||||
}
|
||||
|
||||
static void mm81x_hw_otp_power_down(struct mm81x *mors)
|
||||
{
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0);
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0);
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0);
|
||||
}
|
||||
|
||||
static void mm81x_hw_otp_read_enable(struct mm81x *mors)
|
||||
{
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0);
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1);
|
||||
usleep_range(10, 20);
|
||||
}
|
||||
|
||||
static void mm81x_hw_otp_read_disable(struct mm81x *mors)
|
||||
{
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0);
|
||||
udelay(1);
|
||||
}
|
||||
|
||||
static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf,
|
||||
u8 ignore_ecc)
|
||||
{
|
||||
u32 auto_rd_start_tmp;
|
||||
u32 auto_rd_start = 1;
|
||||
int i;
|
||||
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num);
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc);
|
||||
|
||||
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
|
||||
&auto_rd_start_tmp);
|
||||
auto_rd_start_tmp &= 0xfffffffe;
|
||||
|
||||
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
|
||||
auto_rd_start | auto_rd_start_tmp);
|
||||
|
||||
/* Attempt reading up to 5 times. */
|
||||
for (i = 0; i < 5 && auto_rd_start; i++) {
|
||||
usleep_range(15, 20);
|
||||
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
|
||||
&auto_rd_start_tmp);
|
||||
auto_rd_start = auto_rd_start_tmp & 0x1;
|
||||
}
|
||||
|
||||
if (i == 5)
|
||||
return -EIO;
|
||||
|
||||
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mm81x_hw_otp_get_board_type(struct mm81x *mors)
|
||||
{
|
||||
int board_type = 0;
|
||||
u32 otp_word = 0;
|
||||
int ret;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_hw_otp_power_up(mors);
|
||||
mm81x_hw_otp_read_enable(mors);
|
||||
|
||||
ret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, &otp_word,
|
||||
1);
|
||||
|
||||
mm81x_hw_otp_read_disable(mors);
|
||||
mm81x_hw_otp_power_down(mors);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
|
||||
board_type = otp_word & MM81X_OTP_BOARD_TYPE_MASK;
|
||||
|
||||
return board_type;
|
||||
}
|
||||
|
||||
bool mm81x_hw_otp_valid_board_type(u32 board_type)
|
||||
{
|
||||
return board_type > 0 && board_type < MM810X_BOARD_TYPE_MAX_VALUE;
|
||||
}
|
||||
|
||||
int mm81x_hw_otp_get_mac_addr(struct mm81x *mors)
|
||||
{
|
||||
u32 mac1 = 0;
|
||||
u32 mac2 = 0;
|
||||
int ret = 0;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_hw_otp_power_up(mors);
|
||||
mm81x_hw_otp_read_enable(mors);
|
||||
|
||||
ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, &mac1, 1);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, &mac2, 1);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
put_unaligned_le16((mac1 & MM81X_OTP_MAC_ADDR_1_MASK) >> 16,
|
||||
&mors->macaddr[0]);
|
||||
put_unaligned_le32(mac2, &mors->macaddr[2]);
|
||||
|
||||
exit:
|
||||
mm81x_hw_otp_read_disable(mors);
|
||||
mm81x_hw_otp_power_down(mors);
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable)
|
||||
{
|
||||
u32 irq_en, irq_en_addr = irq < 32 ? MM81X_REG_INT1_EN(mors) :
|
||||
MM81X_REG_INT2_EN(mors);
|
||||
u32 irq_clr_addr = irq < 32 ? MM81X_REG_INT1_CLR(mors) :
|
||||
MM81X_REG_INT2_CLR(mors);
|
||||
u32 mask = irq < 32 ? (1 << irq) : (1 << (irq - 32));
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_reg32_read(mors, irq_en_addr, &irq_en);
|
||||
if (enable)
|
||||
irq_en |= (mask);
|
||||
else
|
||||
irq_en &= ~(mask);
|
||||
mm81x_reg32_write(mors, irq_clr_addr, mask);
|
||||
mm81x_reg32_write(mors, irq_en_addr, irq_en);
|
||||
mm81x_release_bus(mors);
|
||||
}
|
||||
|
||||
int mm81x_hw_irq_handle(struct mm81x *mors)
|
||||
{
|
||||
u32 status1 = 0;
|
||||
|
||||
mm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), &status1);
|
||||
|
||||
if (status1 & MM81X_HIF_IRQ_MASK_ALL)
|
||||
mm81x_hif_handle_irq(mors, status1);
|
||||
|
||||
if (status1 & MM81X_INT_BEACON_VIF_MASK_ALL)
|
||||
mm81x_mac_beacon_irq_handle(mors, status1);
|
||||
|
||||
mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1);
|
||||
|
||||
return status1 ? 1 : 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle);
|
||||
|
||||
void mm81x_hw_irq_clear(struct mm81x *mors)
|
||||
{
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF);
|
||||
mm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF);
|
||||
mm81x_release_bus(mors);
|
||||
}
|
||||
|
||||
void mm81x_hw_toggle_aon_latch(struct mm81x *mors)
|
||||
{
|
||||
u32 address = MM81X_REG_AON_LATCH_ADDR(mors);
|
||||
u32 mask = MM81X_REG_AON_LATCH_MASK(mors);
|
||||
u32 latch;
|
||||
|
||||
mm81x_reg32_read(mors, address, &latch);
|
||||
mm81x_reg32_write(mors, address, latch & ~(mask));
|
||||
mdelay(5);
|
||||
mm81x_reg32_write(mors, address, latch | mask);
|
||||
mdelay(5);
|
||||
mm81x_reg32_write(mors, address, latch & ~(mask));
|
||||
mdelay(5);
|
||||
}
|
||||
|
||||
void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable)
|
||||
{
|
||||
mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable);
|
||||
}
|
||||
|
||||
void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode)
|
||||
{
|
||||
u32 reg32_value;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
if (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, ®32_value))
|
||||
goto end;
|
||||
|
||||
reg32_value &= ~(u32)(SDIO_WORD_BURST_MASK
|
||||
<< MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
|
||||
reg32_value |= (u32)(burst_mode << MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
|
||||
|
||||
dev_dbg(mors->dev,
|
||||
"Setting Burst mode to %d Writing 0x%08X to the register",
|
||||
burst_mode, reg32_value);
|
||||
|
||||
if (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value))
|
||||
goto end;
|
||||
|
||||
end:
|
||||
mm81x_release_bus(mors);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode);
|
||||
|
||||
static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors)
|
||||
{
|
||||
u32 rc_clock_reg_value;
|
||||
int ret = 0;
|
||||
|
||||
dev_dbg(mors->dev, "Enabling internal slow clock");
|
||||
|
||||
ret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
|
||||
&rc_clock_reg_value);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
rc_clock_reg_value &= ~MM8108_REG_RC_CLK_POWER_OFF_MASK;
|
||||
ret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
|
||||
rc_clock_reg_value);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
mm81x_hw_toggle_aon_latch(mors);
|
||||
|
||||
/* Wait for the clock to turn on and settle */
|
||||
mdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS);
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mm81x_hw_digital_reset(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
|
||||
/* This should be the first step in digital reset, do not reorder */
|
||||
ret = mm81x_hw_enable_internal_slow_clock(mors);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
if (mors->bus_type == MM81X_BUS_TYPE_USB) {
|
||||
ret = mm81x_bus_digital_reset(mors);
|
||||
goto usb_done;
|
||||
}
|
||||
|
||||
if (MM81X_REG_RESET(mors) != 0)
|
||||
ret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors),
|
||||
MM81X_REG_RESET_VALUE(mors));
|
||||
|
||||
usb_done:
|
||||
msleep(MM8108_RESET_DELAY_TIME_MS);
|
||||
exit:
|
||||
mm81x_release_bus(mors);
|
||||
|
||||
if (!ret)
|
||||
mors->chip_was_reset = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors)
|
||||
{
|
||||
/* We need disable bursting for firmware download/init procedure */
|
||||
mm81x_bus_config_burst_mode(mors, false);
|
||||
}
|
||||
|
||||
void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors)
|
||||
{
|
||||
/* We are safe here to re-enable bursting again, if supported */
|
||||
mm81x_bus_config_burst_mode(mors, true);
|
||||
}
|
||||
|
||||
const struct mm81x_regs mm8108_regs = {
|
||||
.chip_id_address = MM8108_REG_CHIP_ID,
|
||||
.irq_base_address = MM8108_REG_INT_BASE,
|
||||
.trgr_base_address = MM8108_REG_TRGR_BASE,
|
||||
.cpu_reset_address = MM8108_REG_RESET,
|
||||
.cpu_reset_value = MM8108_REG_RESET_VALUE,
|
||||
.manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS,
|
||||
.msi_address = MM8108_REG_MSI_ADDRESS,
|
||||
.msi_value = MM8108_REG_MSI_VALUE,
|
||||
.magic_num_value = MM8108_REG_HOST_MAGIC_VALUE,
|
||||
.early_clk_ctrl_value = 0,
|
||||
.pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START,
|
||||
.aon_latch = MM8108_REG_AON_LATCH_ADDR,
|
||||
.aon_latch_mask = MM8108_REG_AON_LATCH_MASK,
|
||||
.aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE,
|
||||
.aon = MM8108_REG_AON_ADDR,
|
||||
.aon_count = MM8108_REG_AON_COUNT,
|
||||
.boot_address = MM8108_REG_APPS_BOOT_ADDR,
|
||||
};
|
||||
|
||||
MODULE_FIRMWARE(MM81X_FW_DIR "/v" __stringify(MM81X_FW_VER_MAX) "/"
|
||||
MM8108_FW_BASE MM81X_FW_EXT);
|
||||
159
drivers/net/wireless/morsemicro/mm81x/hw.h
Normal file
159
drivers/net/wireless/morsemicro/mm81x/hw.h
Normal file
@@ -0,0 +1,159 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_HW_H_
|
||||
#define _MM81X_HW_H_
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include "core.h"
|
||||
#include "command_defs.h"
|
||||
|
||||
/* This should be at a fixed location for a family of chipset */
|
||||
#define MM8108_REG_CHIP_ID 0x00002d20
|
||||
|
||||
#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000)
|
||||
|
||||
#define MM81X_CONFIG_ACCESS_1BYTE 0
|
||||
#define MM81X_CONFIG_ACCESS_2BYTE 1
|
||||
#define MM81X_CONFIG_ACCESS_4BYTE 2
|
||||
|
||||
#define MM81X_REG_TRGR_BASE(mors) ((mors)->regs->trgr_base_address)
|
||||
#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00)
|
||||
#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04)
|
||||
#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08)
|
||||
#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C)
|
||||
#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10)
|
||||
#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14)
|
||||
#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18)
|
||||
#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C)
|
||||
|
||||
#define MM81X_REG_INT_BASE(mors) ((mors)->regs->irq_base_address)
|
||||
#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00)
|
||||
#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04)
|
||||
#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08)
|
||||
#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C)
|
||||
#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10)
|
||||
#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14)
|
||||
#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18)
|
||||
#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C)
|
||||
|
||||
#define MM81X_REG_CHIP_ID(mors) ((mors)->regs->chip_id_address)
|
||||
|
||||
#define MM81X_REG_MSI(mors) ((mors)->regs->msi_address)
|
||||
#define MM81X_REG_MSI_HOST_INT(mors) ((mors)->regs->msi_value)
|
||||
|
||||
#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)->regs->magic_num_value)
|
||||
|
||||
#define MM81X_REG_RESET(mors) ((mors)->regs->cpu_reset_address)
|
||||
#define MM81X_REG_RESET_VALUE(mors) ((mors)->regs->cpu_reset_value)
|
||||
|
||||
#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)->regs->manifest_ptr_address)
|
||||
|
||||
#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \
|
||||
((mors)->regs->early_clk_ctrl_value)
|
||||
|
||||
#define MM81X_REG_CLK_CTRL(mors) ((mors)->regs->clk_ctrl_address)
|
||||
#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)->regs->clk_ctrl_value)
|
||||
|
||||
#define MM81X_REG_BOOT_ADDR(mors) ((mors)->regs->boot_address)
|
||||
#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)->regs->boot_value)
|
||||
|
||||
#define MM81X_REG_AON_ADDR(mors) ((mors)->regs->aon)
|
||||
#define MM81X_REG_AON_COUNT(mors) ((mors)->regs->aon_count)
|
||||
#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)->regs->aon_latch)
|
||||
#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)->regs->aon_latch_mask)
|
||||
#define MM81X_REG_AON_USB_RESET(mors) ((mors)->regs->aon_reset_usb_value)
|
||||
|
||||
/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */
|
||||
#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17))
|
||||
#define MM81X_INT_BEACON_BASE_NUM (17)
|
||||
|
||||
/* PV0 NDP probe interrupts (VIF 0 and 1). */
|
||||
#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25))
|
||||
#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25)
|
||||
|
||||
/* Bit 27 Chip to Host stop notify */
|
||||
#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27)
|
||||
#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM)
|
||||
|
||||
/* Chip IDs */
|
||||
#define CHIP_ID_MM8108 0x809
|
||||
|
||||
/*
|
||||
* Minimum time we must wait between attempting to reload the HW after a
|
||||
* stop notification
|
||||
*/
|
||||
#define HW_RELOAD_AFTER_STOP_WINDOW 5
|
||||
|
||||
enum host_table_firmware_flags {
|
||||
MM81X_FW_FLAGS_SUPPORT_S1G = BIT(0),
|
||||
MM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1),
|
||||
MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2),
|
||||
MM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3),
|
||||
MM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4),
|
||||
};
|
||||
|
||||
struct host_table {
|
||||
__le32 magic_number;
|
||||
__le32 fw_version_number;
|
||||
__le32 host_flags;
|
||||
__le32 fw_flags;
|
||||
__le32 memcmd_cmd_addr;
|
||||
__le32 memcmd_resp_addr;
|
||||
__le32 ext_host_tbl_addr;
|
||||
} __packed;
|
||||
|
||||
struct mm81x_regs {
|
||||
u32 chip_id_address;
|
||||
u32 irq_base_address;
|
||||
u32 trgr_base_address;
|
||||
u32 cpu_reset_address;
|
||||
u32 cpu_reset_value;
|
||||
u32 msi_address;
|
||||
u32 msi_value;
|
||||
u32 manifest_ptr_address;
|
||||
u32 magic_num_value;
|
||||
u32 clk_ctrl_address;
|
||||
u32 clk_ctrl_value;
|
||||
u32 early_clk_ctrl_value;
|
||||
u32 boot_address;
|
||||
u32 boot_value;
|
||||
u32 pager_base_address;
|
||||
u32 aon_latch;
|
||||
u32 aon_latch_mask;
|
||||
u32 aon_reset_usb_value;
|
||||
u32 aon;
|
||||
u8 aon_count;
|
||||
};
|
||||
|
||||
int mm81x_hw_otp_get_board_type(struct mm81x *mors);
|
||||
bool mm81x_hw_otp_valid_board_type(u32 board_type);
|
||||
int mm81x_hw_otp_get_mac_addr(struct mm81x *mors);
|
||||
|
||||
void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable);
|
||||
int mm81x_hw_irq_handle(struct mm81x *mors);
|
||||
void mm81x_hw_irq_clear(struct mm81x *mors);
|
||||
void mm81x_hw_toggle_aon_latch(struct mm81x *mors);
|
||||
void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode);
|
||||
int mm81x_hw_digital_reset(struct mm81x *mors);
|
||||
void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors);
|
||||
void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors);
|
||||
|
||||
enum sdio_burst_mode {
|
||||
SDIO_WORD_BURST_DISABLE =
|
||||
0, /* Intentionally duplicate to make it clear it's disabled */
|
||||
SDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */
|
||||
SDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */
|
||||
SDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */
|
||||
SDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */
|
||||
SDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */
|
||||
SDIO_WORD_BURST_MASK = 7,
|
||||
};
|
||||
|
||||
extern const struct mm81x_regs mm8108_regs;
|
||||
|
||||
void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable);
|
||||
|
||||
#endif /* !_MM81X_HW_H_ */
|
||||
2443
drivers/net/wireless/morsemicro/mm81x/mac.c
Normal file
2443
drivers/net/wireless/morsemicro/mm81x/mac.c
Normal file
File diff suppressed because it is too large
Load Diff
63
drivers/net/wireless/morsemicro/mm81x/mac.h
Normal file
63
drivers/net/wireless/morsemicro/mm81x/mac.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_MAC_H_
|
||||
#define _MM81X_MAC_H_
|
||||
|
||||
#include "core.h"
|
||||
#include "command.h"
|
||||
|
||||
struct mm81x_queue_params {
|
||||
u8 uapsd;
|
||||
u8 aci;
|
||||
u8 aifs;
|
||||
u16 cw_min;
|
||||
u16 cw_max;
|
||||
u32 txop;
|
||||
};
|
||||
|
||||
static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif)
|
||||
{
|
||||
return mm81x_generate_cssid(vif->cfg.ssid, vif->cfg.ssid_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a little-endian word from the last four octets of a MAC address;
|
||||
* the first two octets are dropped.
|
||||
*/
|
||||
static inline __le32 mac2le32(const unsigned char *addr)
|
||||
{
|
||||
return cpu_to_le32(((u32)(addr[2]) << 24) | ((u32)(addr[3]) << 16) |
|
||||
((u32)(addr[4]) << 8) | ((u32)(addr[5])));
|
||||
}
|
||||
|
||||
static inline struct ieee80211_vif *
|
||||
mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu)
|
||||
{
|
||||
if (WARN_ON(vif_id >= ARRAY_SIZE(mors->vifs)))
|
||||
return NULL;
|
||||
|
||||
if (rcu)
|
||||
return rcu_dereference(mors->vifs[vif_id]);
|
||||
|
||||
return rcu_dereference_protected(mors->vifs[vif_id],
|
||||
lockdep_is_held(&mors->hw->wiphy->mtx));
|
||||
}
|
||||
|
||||
int mm81x_tx_h_get_attempts(struct mm81x *mors,
|
||||
struct mm81x_skb_tx_status *tx_sts);
|
||||
struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev);
|
||||
int mm81x_mac_register(struct mm81x *mors);
|
||||
void mm81x_mac_free(struct mm81x *mors);
|
||||
void mm81x_mac_unregister(struct mm81x *mors);
|
||||
int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb);
|
||||
void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,
|
||||
struct mm81x_skb_rx_status *hdr_rx_status);
|
||||
void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status);
|
||||
|
||||
u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf);
|
||||
size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params);
|
||||
void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb);
|
||||
#endif /* !_MM81X_MAC_H_ */
|
||||
1354
drivers/net/wireless/morsemicro/mm81x/mmrc.c
Normal file
1354
drivers/net/wireless/morsemicro/mm81x/mmrc.c
Normal file
File diff suppressed because it is too large
Load Diff
193
drivers/net/wireless/morsemicro/mm81x/mmrc.h
Normal file
193
drivers/net/wireless/morsemicro/mm81x/mmrc.h
Normal file
@@ -0,0 +1,193 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_MMRC_H_
|
||||
#define _MM81X_MMRC_H_
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/time.h>
|
||||
|
||||
/* The max length of a retry chain for a single packet transmission */
|
||||
#define MMRC_MAX_CHAIN_LENGTH 4
|
||||
|
||||
/* Rate minimum allowed attempts */
|
||||
#define MMRC_MIN_CHAIN_ATTEMPTS 1
|
||||
|
||||
/* Rate upper limit for attempts */
|
||||
#define MMRC_MAX_CHAIN_ATTEMPTS 2
|
||||
|
||||
/* The frequency of MMRC stat table updates */
|
||||
#define MMRC_UPDATE_FREQUENCY_MS 100
|
||||
|
||||
enum mmrc_flags {
|
||||
MMRC_FLAGS_CTS_RTS,
|
||||
};
|
||||
|
||||
enum mmrc_mcs_rate {
|
||||
MMRC_MCS0,
|
||||
MMRC_MCS1,
|
||||
MMRC_MCS2,
|
||||
MMRC_MCS3,
|
||||
MMRC_MCS4,
|
||||
MMRC_MCS5,
|
||||
MMRC_MCS6,
|
||||
MMRC_MCS7,
|
||||
MMRC_MCS8,
|
||||
MMRC_MCS9,
|
||||
MMRC_MCS10,
|
||||
MMRC_MCS_UNUSED,
|
||||
};
|
||||
|
||||
enum mmrc_bw {
|
||||
MMRC_BW_1MHZ = 0,
|
||||
MMRC_BW_2MHZ = 1,
|
||||
MMRC_BW_4MHZ = 2,
|
||||
MMRC_BW_8MHZ = 3,
|
||||
MMRC_BW_16MHZ = 4,
|
||||
MMRC_BW_MAX = 5,
|
||||
};
|
||||
|
||||
enum mmrc_spatial_stream {
|
||||
MMRC_SPATIAL_STREAM_1 = 0,
|
||||
MMRC_SPATIAL_STREAM_2 = 1,
|
||||
MMRC_SPATIAL_STREAM_3 = 2,
|
||||
MMRC_SPATIAL_STREAM_4 = 3,
|
||||
MMRC_SPATIAL_STREAM_MAX,
|
||||
};
|
||||
|
||||
enum mmrc_guard {
|
||||
MMRC_GUARD_LONG = 0,
|
||||
MMRC_GUARD_SHORT = 1,
|
||||
MMRC_GUARD_MAX,
|
||||
};
|
||||
|
||||
#define MMRC_RATE_TO_BITFIELD(x) ((x) & 0xF)
|
||||
#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) & 0x7)
|
||||
#define MMRC_GUARD_TO_BITFIELD(x) ((x) & 0x1)
|
||||
#define MMRC_SS_TO_BITFIELD(x) ((x) & 0x3)
|
||||
#define MMRC_BW_TO_BITFIELD(x) ((x) & 0x7)
|
||||
#define MMRC_FLAGS_TO_BITFIELD(x) ((x) & 0x7)
|
||||
|
||||
struct mmrc_rate {
|
||||
u8 rate : 4;
|
||||
u8 attempts : 3;
|
||||
u8 guard : 1;
|
||||
u8 ss : 2;
|
||||
u8 bw : 3;
|
||||
u8 flags : 3;
|
||||
u16 index;
|
||||
};
|
||||
|
||||
struct mmrc_rate_table {
|
||||
struct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH];
|
||||
};
|
||||
|
||||
#define SGI_PER_BW(bw) (1 << (bw))
|
||||
|
||||
struct mmrc_sta_capabilities {
|
||||
u8 max_rates : 3;
|
||||
u8 max_retries : 3;
|
||||
u8 bandwidth : 5;
|
||||
u8 spatial_streams : 4;
|
||||
u16 rates : 11;
|
||||
u8 guard : 2;
|
||||
u8 sta_flags : 4;
|
||||
u8 sgi_per_bw : 5;
|
||||
};
|
||||
|
||||
struct mmrc_stats_table {
|
||||
u32 avg_throughput_counter;
|
||||
u32 sum_throughput;
|
||||
u32 max_throughput;
|
||||
u16 sent;
|
||||
u16 sent_success;
|
||||
u16 back_mpdu_success;
|
||||
u16 back_mpdu_failure;
|
||||
u32 total_sent;
|
||||
u32 total_success;
|
||||
u16 evidence;
|
||||
u8 prob;
|
||||
bool have_sent_ampdus;
|
||||
};
|
||||
|
||||
struct mmrc_table {
|
||||
struct mmrc_sta_capabilities caps;
|
||||
struct mmrc_rate best_tp;
|
||||
struct mmrc_rate second_tp;
|
||||
struct mmrc_rate baseline;
|
||||
struct mmrc_rate best_prob;
|
||||
struct mmrc_rate fixed_rate;
|
||||
u32 cycle_cnt;
|
||||
u32 last_lookaround_cycle;
|
||||
u8 lookaround_cnt;
|
||||
|
||||
/* The ratio of using normal rate and sampling */
|
||||
u8 lookaround_wrap;
|
||||
|
||||
/*
|
||||
* A counter that is used to determine when we should force a
|
||||
* lookaround. Should be a portion of the above lookaround with
|
||||
* less constraints
|
||||
*/
|
||||
u8 forced_lookaround;
|
||||
|
||||
u8 current_lookaround_rate_attempts;
|
||||
u16 current_lookaround_rate_index;
|
||||
u32 total_lookaround;
|
||||
|
||||
/*
|
||||
* A counter to detect if the current best rate is optimal
|
||||
* and may slow down sample frequency.
|
||||
*/
|
||||
u32 stability_cnt;
|
||||
|
||||
u32 stability_cnt_threshold;
|
||||
u8 probability_variation;
|
||||
|
||||
/* The difference in MCS from each of the last 2 rate changes */
|
||||
s8 best_rate_diff[2];
|
||||
|
||||
/* Indication of random versus consistently one-sided variation */
|
||||
s8 probability_variation_direction;
|
||||
|
||||
/* Has rate control detected possible interference */
|
||||
bool interference_likely;
|
||||
|
||||
/* Has rate control detected the best rate is no longer converged */
|
||||
bool unconverged;
|
||||
|
||||
/* Is rate control just entering unconverged state */
|
||||
bool newly_unconverged;
|
||||
|
||||
/*
|
||||
* Number of rate control cycles the best rate has remained
|
||||
* unchanged
|
||||
*/
|
||||
s32 best_rate_cycle_count;
|
||||
|
||||
/*
|
||||
* The probability table for the STA. This MUST always be the last
|
||||
* element in the struct.
|
||||
*/
|
||||
struct mmrc_stats_table table[];
|
||||
};
|
||||
|
||||
void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,
|
||||
s8 rssi);
|
||||
size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps);
|
||||
void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,
|
||||
size_t size);
|
||||
void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,
|
||||
s32 retry_count, bool was_aggregated);
|
||||
void mmrc_update(struct mmrc_table *tb);
|
||||
bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate);
|
||||
u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate);
|
||||
u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size);
|
||||
|
||||
#endif /* _MMRC_H_ */
|
||||
120
drivers/net/wireless/morsemicro/mm81x/ps.c
Normal file
120
drivers/net/wireless/morsemicro/mm81x/ps.c
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "hif.h"
|
||||
#include "skbq.h"
|
||||
#include "mac.h"
|
||||
#include "bus.h"
|
||||
#include "ps.h"
|
||||
|
||||
static void mm81x_ps_wakeup(struct mm81x_ps *mps)
|
||||
{
|
||||
struct mm81x *mors = container_of(mps, struct mm81x, ps);
|
||||
|
||||
if (!mps->enable || !mps->suspended)
|
||||
return;
|
||||
|
||||
mm81x_set_bus_enable(mors, true);
|
||||
mps->suspended = false;
|
||||
}
|
||||
|
||||
static void mm81x_ps_sleep(struct mm81x_ps *mps)
|
||||
{
|
||||
struct mm81x *mors = container_of(mps, struct mm81x, ps);
|
||||
|
||||
if (!mps->enable || mps->suspended)
|
||||
return;
|
||||
|
||||
mps->suspended = true;
|
||||
mm81x_set_bus_enable(mors, false);
|
||||
}
|
||||
|
||||
static void mm81x_ps_evaluate(struct mm81x_ps *mps)
|
||||
{
|
||||
struct mm81x *mors = container_of(mps, struct mm81x, ps);
|
||||
bool needs_wake = false;
|
||||
unsigned long flags_on_entry =
|
||||
(mors->hif.event_flags &
|
||||
~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND));
|
||||
|
||||
if (!mps->enable)
|
||||
return;
|
||||
|
||||
needs_wake = (mps->wakers > 0);
|
||||
needs_wake |= (flags_on_entry > 0);
|
||||
needs_wake |= (mm81x_hif_get_tx_buffered_count(mors) > 0);
|
||||
|
||||
if (needs_wake) {
|
||||
mm81x_ps_wakeup(mps);
|
||||
return;
|
||||
}
|
||||
|
||||
mm81x_ps_sleep(mps);
|
||||
}
|
||||
|
||||
static void mm81x_ps_evaluate_work(struct work_struct *work)
|
||||
{
|
||||
struct mm81x_ps *mps =
|
||||
container_of(work, struct mm81x_ps, delayed_eval_work.work);
|
||||
|
||||
if (mps->enable) {
|
||||
mutex_lock(&mps->lock);
|
||||
mm81x_ps_evaluate(mps);
|
||||
mutex_unlock(&mps->lock);
|
||||
}
|
||||
}
|
||||
|
||||
void mm81x_ps_enable(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_ps *mps = &mors->ps;
|
||||
|
||||
if (mps->enable) {
|
||||
mutex_lock(&mps->lock);
|
||||
if (mps->wakers == 0) {
|
||||
WARN_ON_ONCE(1);
|
||||
} else {
|
||||
mps->wakers--;
|
||||
mm81x_ps_evaluate(mps);
|
||||
}
|
||||
mutex_unlock(&mps->lock);
|
||||
}
|
||||
}
|
||||
|
||||
void mm81x_ps_disable(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_ps *mps = &mors->ps;
|
||||
|
||||
if (mps->enable) {
|
||||
mutex_lock(&mps->lock);
|
||||
mps->wakers++;
|
||||
mm81x_ps_evaluate(mps);
|
||||
mutex_unlock(&mps->lock);
|
||||
}
|
||||
}
|
||||
|
||||
int mm81x_ps_init(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_ps *mps = &mors->ps;
|
||||
|
||||
mps->enable = (mors->bus_type == MM81X_BUS_TYPE_USB);
|
||||
mps->suspended = true;
|
||||
mps->wakers = 1; /* we default to being on */
|
||||
mutex_init(&mps->lock);
|
||||
INIT_DELAYED_WORK(&mps->delayed_eval_work, mm81x_ps_evaluate_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mm81x_ps_finish(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_ps *mps = &mors->ps;
|
||||
|
||||
if (mps->enable) {
|
||||
mps->enable = false;
|
||||
cancel_delayed_work_sync(&mps->delayed_eval_work);
|
||||
}
|
||||
}
|
||||
22
drivers/net/wireless/morsemicro/mm81x/ps.h
Normal file
22
drivers/net/wireless/morsemicro/mm81x/ps.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_PS_H_
|
||||
#define _MM81X_PS_H_
|
||||
|
||||
#include "core.h"
|
||||
|
||||
/* This should be nominally <= the dynamic ps timeout */
|
||||
#define NETWORK_BUS_TIMEOUT_MS (90)
|
||||
|
||||
/* The default period of time to wait to re-evaluate powersave */
|
||||
#define DEFAULT_BUS_TIMEOUT_MS (50)
|
||||
|
||||
void mm81x_ps_disable(struct mm81x *mors);
|
||||
void mm81x_ps_enable(struct mm81x *mors);
|
||||
int mm81x_ps_init(struct mm81x *mors);
|
||||
void mm81x_ps_finish(struct mm81x *mors);
|
||||
|
||||
#endif /* !_MM81X_PS_H_ */
|
||||
177
drivers/net/wireless/morsemicro/mm81x/rate_code.h
Normal file
177
drivers/net/wireless/morsemicro/mm81x/rate_code.h
Normal file
@@ -0,0 +1,177 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_RATE_CODE_H_
|
||||
#define _MM81X_RATE_CODE_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
enum dot11_bandwidth {
|
||||
DOT11_BANDWIDTH_1MHZ = 0,
|
||||
DOT11_BANDWIDTH_2MHZ = 1,
|
||||
DOT11_BANDWIDTH_4MHZ = 2,
|
||||
DOT11_BANDWIDTH_8MHZ = 3,
|
||||
DOT11_BANDWIDTH_16MHZ = 4,
|
||||
|
||||
DOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ,
|
||||
DOT11_INVALID_BANDWIDTH = 5
|
||||
};
|
||||
|
||||
enum mm81x_rate_preamble {
|
||||
/* S1G LONG format (with SIG-A and SIG-B) */
|
||||
MM81X_RATE_PREAMBLE_S1G_LONG = 0,
|
||||
/* This is the most common format used */
|
||||
MM81X_RATE_PREAMBLE_S1G_SHORT = 1,
|
||||
/* S1G 1M format */
|
||||
MM81X_RATE_PREAMBLE_S1G_1M = 2,
|
||||
|
||||
MM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M,
|
||||
MM81X_RATE_INVALID_PREAMBLE = 7
|
||||
};
|
||||
|
||||
typedef __le32 mm81x_rate_code_t;
|
||||
|
||||
#define MM81X_RATECODE_PREAMBLE (0x0000000F)
|
||||
#define MM81X_RATECODE_MCS_INDEX (0x000000F0)
|
||||
#define MM81X_RATECODE_NSS_INDEX (0x00000700)
|
||||
#define MM81X_RATECODE_BW_INDEX (0x00003800)
|
||||
#define MM81X_RATECODE_RTS_FLAG (0x00010000)
|
||||
#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000)
|
||||
#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000)
|
||||
|
||||
static inline enum mm81x_rate_preamble
|
||||
mm81x_ratecode_preamble_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return (enum mm81x_rate_preamble)(
|
||||
le32_get_bits(rc, MM81X_RATECODE_PREAMBLE));
|
||||
}
|
||||
|
||||
static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX);
|
||||
}
|
||||
|
||||
static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX);
|
||||
}
|
||||
|
||||
static inline enum dot11_bandwidth
|
||||
mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return (enum dot11_bandwidth)(
|
||||
le32_get_bits(rc, MM81X_RATECODE_BW_INDEX));
|
||||
}
|
||||
|
||||
static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG);
|
||||
}
|
||||
|
||||
static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG);
|
||||
}
|
||||
|
||||
static inline enum dot11_bandwidth
|
||||
mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc)
|
||||
{
|
||||
return (enum dot11_bandwidth)(
|
||||
le32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX));
|
||||
}
|
||||
|
||||
#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble) \
|
||||
(le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) | \
|
||||
le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \
|
||||
le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \
|
||||
le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE))
|
||||
|
||||
static inline mm81x_rate_code_t
|
||||
mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index,
|
||||
enum mm81x_rate_preamble preamble)
|
||||
{
|
||||
return MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble);
|
||||
}
|
||||
|
||||
static inline void
|
||||
mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc,
|
||||
enum mm81x_rate_preamble preamble)
|
||||
{
|
||||
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) |
|
||||
le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE);
|
||||
}
|
||||
|
||||
static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc,
|
||||
u32 mcs_index)
|
||||
{
|
||||
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) |
|
||||
le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX);
|
||||
}
|
||||
|
||||
static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc,
|
||||
u32 nss_index)
|
||||
{
|
||||
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) |
|
||||
le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX);
|
||||
}
|
||||
|
||||
static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc,
|
||||
enum dot11_bandwidth bw_index)
|
||||
{
|
||||
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) |
|
||||
le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX);
|
||||
}
|
||||
|
||||
static inline void
|
||||
mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc,
|
||||
enum dot11_bandwidth bw_index)
|
||||
{
|
||||
enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
|
||||
|
||||
if (bw_index == DOT11_BANDWIDTH_1MHZ)
|
||||
pream = MM81X_RATE_PREAMBLE_S1G_1M;
|
||||
|
||||
mm81x_ratecode_preamble_set(rc, pream);
|
||||
mm81x_ratecode_bw_index_set(rc, bw_index);
|
||||
}
|
||||
|
||||
static inline void
|
||||
mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc,
|
||||
enum dot11_bandwidth dup_bw_index)
|
||||
{
|
||||
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) |
|
||||
le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX);
|
||||
}
|
||||
|
||||
static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc)
|
||||
{
|
||||
*rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG);
|
||||
}
|
||||
|
||||
static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc)
|
||||
{
|
||||
*rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG);
|
||||
}
|
||||
|
||||
static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz)
|
||||
{
|
||||
return ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ :
|
||||
(bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ :
|
||||
(bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ :
|
||||
(bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ :
|
||||
DOT11_BANDWIDTH_2MHZ);
|
||||
}
|
||||
|
||||
static inline u8
|
||||
mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx)
|
||||
{
|
||||
return ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 :
|
||||
(bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 :
|
||||
(bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 :
|
||||
(bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 :
|
||||
2);
|
||||
}
|
||||
|
||||
#endif
|
||||
494
drivers/net/wireless/morsemicro/mm81x/rc.c
Normal file
494
drivers/net/wireless/morsemicro/mm81x/rc.c
Normal file
@@ -0,0 +1,494 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timer.h>
|
||||
#include "core.h"
|
||||
#include "mac.h"
|
||||
#include "bus.h"
|
||||
#include "rc.h"
|
||||
|
||||
#define MM81X_RC_BW_TO_MMRC_BW(X) \
|
||||
(((X) == 1) ? MMRC_BW_1MHZ : \
|
||||
((X) == 2) ? MMRC_BW_2MHZ : \
|
||||
((X) == 4) ? MMRC_BW_4MHZ : \
|
||||
((X) == 8) ? MMRC_BW_8MHZ : \
|
||||
MMRC_BW_2MHZ)
|
||||
|
||||
static void mm81x_rc_work(struct work_struct *work)
|
||||
{
|
||||
struct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work);
|
||||
struct list_head *pos;
|
||||
|
||||
spin_lock_bh(&mrc->lock);
|
||||
|
||||
list_for_each(pos, &mrc->stas) {
|
||||
struct mm81x_rc_sta *mrc_sta =
|
||||
container_of(pos, struct mm81x_rc_sta, list);
|
||||
unsigned long now = jiffies;
|
||||
|
||||
mrc_sta->last_update = now;
|
||||
|
||||
mmrc_update(mrc_sta->tb);
|
||||
}
|
||||
|
||||
spin_unlock_bh(&mrc->lock);
|
||||
|
||||
mod_timer(&mrc->timer, jiffies + msecs_to_jiffies(100));
|
||||
}
|
||||
|
||||
static void mm81x_rc_timer(struct timer_list *t)
|
||||
{
|
||||
struct mm81x_rc *mrc = timer_container_of(mrc, t, timer);
|
||||
struct mm81x *mors = mrc->mors;
|
||||
|
||||
queue_work(mors->net_wq, &mors->mrc.work);
|
||||
}
|
||||
|
||||
void mm81x_rc_init(struct mm81x *mors)
|
||||
{
|
||||
INIT_LIST_HEAD(&mors->mrc.stas);
|
||||
spin_lock_init(&mors->mrc.lock);
|
||||
|
||||
INIT_WORK(&mors->mrc.work, mm81x_rc_work);
|
||||
timer_setup(&mors->mrc.timer, mm81x_rc_timer, 0);
|
||||
|
||||
mors->mrc.mors = mors;
|
||||
mod_timer(&mors->mrc.timer, jiffies + msecs_to_jiffies(100));
|
||||
}
|
||||
|
||||
void mm81x_rc_deinit(struct mm81x *mors)
|
||||
{
|
||||
cancel_work_sync(&mors->mrc.work);
|
||||
timer_delete_sync_try(&mors->mrc.timer);
|
||||
}
|
||||
|
||||
static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta,
|
||||
struct mmrc_sta_capabilities *caps)
|
||||
{
|
||||
caps->guard = BIT(MMRC_GUARD_LONG);
|
||||
|
||||
if (caps->bandwidth & BIT(MMRC_BW_1MHZ)) {
|
||||
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ);
|
||||
caps->guard |= BIT(MMRC_GUARD_SHORT);
|
||||
}
|
||||
|
||||
if (caps->bandwidth & BIT(MMRC_BW_2MHZ)) {
|
||||
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ);
|
||||
caps->guard |= BIT(MMRC_GUARD_SHORT);
|
||||
}
|
||||
|
||||
if (caps->bandwidth & BIT(MMRC_BW_4MHZ)) {
|
||||
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ);
|
||||
caps->guard |= BIT(MMRC_GUARD_SHORT);
|
||||
}
|
||||
|
||||
if (caps->bandwidth & BIT(MMRC_BW_8MHZ)) {
|
||||
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ);
|
||||
caps->guard |= BIT(MMRC_GUARD_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors,
|
||||
struct mmrc_sta_capabilities *caps,
|
||||
struct ieee80211_sta_s1g_cap *s1g_cap)
|
||||
{
|
||||
int nss_idx = 0;
|
||||
u8 rx_mcs = s1g_cap->nss_mcs[0] & 0x3; /* 1SS */
|
||||
u8 tx_mcs = (s1g_cap->nss_mcs[2] >> 1) & 0x3; /* 1SS */
|
||||
u8 mcs = min(rx_mcs, tx_mcs);
|
||||
|
||||
switch (mcs) {
|
||||
case IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 -> S1G 9 */
|
||||
caps->rates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8);
|
||||
fallthrough;
|
||||
case IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -> S1G 7 */
|
||||
caps->rates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) |
|
||||
BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3);
|
||||
fallthrough;
|
||||
case IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -> S1G 2 */
|
||||
caps->rates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) |
|
||||
BIT(MMRC_MCS0) | BIT(MMRC_MCS10);
|
||||
caps->spatial_streams |= (BIT(nss_idx) & 0x0F);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_warn(mors->dev, "Invalid MCS encoding 0x%02x for stream %d",
|
||||
mcs, nss_idx);
|
||||
}
|
||||
}
|
||||
|
||||
int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta)
|
||||
{
|
||||
struct ieee80211_sta_s1g_cap *s1g_cap = &sta->deflink.s1g_cap;
|
||||
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
|
||||
struct mmrc_sta_capabilities caps;
|
||||
int oper_bw_mhz = cfg80211_chandef_get_width(&mors->chandef);
|
||||
size_t table_mem_size;
|
||||
struct mmrc_table *tb;
|
||||
|
||||
memset(&caps, 0, sizeof(caps));
|
||||
|
||||
mm81x_rc_sta_add_s1g_sta_caps(mors, &caps, s1g_cap);
|
||||
|
||||
/* Configure STA for support up to 8MHZ */
|
||||
while (oper_bw_mhz > 0) {
|
||||
caps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz));
|
||||
oper_bw_mhz >>= 1;
|
||||
}
|
||||
|
||||
/* Configure STA for short and long guard */
|
||||
mm81x_rc_sta_config_guard_per_bw(sta, &caps);
|
||||
|
||||
/* Set max rates */
|
||||
if (mors->hw->max_rates > 0 &&
|
||||
mors->hw->max_rates < IEEE80211_TX_MAX_RATES)
|
||||
caps.max_rates = mors->hw->max_rates;
|
||||
else
|
||||
caps.max_rates = IEEE80211_TX_MAX_RATES;
|
||||
|
||||
/* Set max reties */
|
||||
if (mors->hw->max_rate_tries >= MMRC_MIN_CHAIN_ATTEMPTS &&
|
||||
mors->hw->max_rate_tries < MMRC_MAX_CHAIN_ATTEMPTS)
|
||||
caps.max_retries = mors->hw->max_rate_tries;
|
||||
else
|
||||
caps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS;
|
||||
|
||||
WARN_ON(msta->rc.tb);
|
||||
table_mem_size = mmrc_memory_required_for_caps(&caps);
|
||||
tb = kzalloc(table_mem_size, GFP_KERNEL);
|
||||
if (!tb)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Initialise the STA rate control table */
|
||||
mmrc_sta_init(tb, &caps, msta->avg_rssi);
|
||||
|
||||
spin_lock_bh(&mors->mrc.lock);
|
||||
kfree(msta->rc.tb);
|
||||
msta->rc.tb = tb;
|
||||
list_add(&msta->rc.list, &mors->mrc.stas);
|
||||
msta->rc.last_update = jiffies;
|
||||
spin_unlock_bh(&mors->mrc.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta)
|
||||
{
|
||||
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
|
||||
|
||||
spin_lock_bh(&mors->mrc.lock);
|
||||
if (msta->rc.tb) {
|
||||
list_del_init(&msta->rc.list);
|
||||
kfree(msta->rc.tb);
|
||||
msta->rc.tb = NULL;
|
||||
}
|
||||
spin_unlock_bh(&mors->mrc.lock);
|
||||
}
|
||||
|
||||
static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info,
|
||||
struct ieee80211_tx_info *info,
|
||||
int tx_bw)
|
||||
{
|
||||
int i;
|
||||
enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw);
|
||||
enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
|
||||
|
||||
mm81x_ratecode_mcs_index_set(&tx_info->rates[0].mm81x_ratecode, 0);
|
||||
mm81x_ratecode_nss_index_set(&tx_info->rates[0].mm81x_ratecode,
|
||||
NSS_TO_NSS_IDX(1));
|
||||
mm81x_ratecode_bw_index_set(&tx_info->rates[0].mm81x_ratecode, bw_idx);
|
||||
if (bw_idx == DOT11_BANDWIDTH_1MHZ)
|
||||
pream = MM81X_RATE_PREAMBLE_S1G_1M;
|
||||
mm81x_ratecode_preamble_set(&tx_info->rates[0].mm81x_ratecode, pream);
|
||||
tx_info->rates[0].count = 4;
|
||||
|
||||
for (i = 1; i < IEEE80211_TX_MAX_RATES; i++)
|
||||
tx_info->rates[i].count = 0;
|
||||
|
||||
info->control.rates[0].idx = 0;
|
||||
info->control.rates[0].count = tx_info->rates[0].count;
|
||||
info->control.rates[0].flags = 0;
|
||||
info->control.rates[1].idx = -1;
|
||||
}
|
||||
|
||||
static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta,
|
||||
struct mmrc_rate_table *rates, size_t size)
|
||||
{
|
||||
int ret = -ENOENT;
|
||||
struct list_head *pos;
|
||||
|
||||
spin_lock_bh(&mors->mrc.lock);
|
||||
list_for_each(pos, &mors->mrc.stas) {
|
||||
struct mm81x_rc_sta *mrc_sta =
|
||||
list_entry(pos, struct mm81x_rc_sta, list);
|
||||
|
||||
if (&msta->rc == mrc_sta) {
|
||||
ret = 0;
|
||||
mmrc_get_rates(msta->rc.tb, rates, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&mors->mrc.lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta,
|
||||
struct sk_buff *skb,
|
||||
struct ieee80211_hdr *hdr)
|
||||
{
|
||||
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
||||
|
||||
if (!sta)
|
||||
return true;
|
||||
|
||||
if (ieee80211_is_qos_nullfunc(hdr->frame_control) ||
|
||||
ieee80211_is_nullfunc(hdr->frame_control))
|
||||
return true;
|
||||
|
||||
if (!ieee80211_is_data_qos(hdr->frame_control))
|
||||
return true;
|
||||
|
||||
/* Use basic rates for EAPOL exchanges or when instructed */
|
||||
if (unlikely((skb->protocol == cpu_to_be16(ETH_P_PAE) ||
|
||||
info->flags & IEEE80211_TX_CTL_USE_MINRATE)))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
|
||||
struct mm81x_skb_tx_info *tx_info,
|
||||
struct sk_buff *skb, struct ieee80211_sta *sta,
|
||||
int tx_bw, bool rts_allowed)
|
||||
{
|
||||
int ret, i;
|
||||
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
||||
struct mm81x_sta *msta;
|
||||
struct mmrc_rate_table rates;
|
||||
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
||||
|
||||
BUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ ||
|
||||
MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ ||
|
||||
MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ ||
|
||||
MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ));
|
||||
|
||||
memset(&info->control.rates, 0, sizeof(info->control.rates));
|
||||
memset(&info->status.rates, 0, sizeof(info->status.rates));
|
||||
mm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw);
|
||||
|
||||
/* Use basic rates for non data packets */
|
||||
if (mm81x_rc_use_basic_rates(sta, skb, hdr))
|
||||
return;
|
||||
|
||||
msta = (struct mm81x_sta *)sta->drv_priv;
|
||||
if (!msta)
|
||||
return;
|
||||
|
||||
ret = mm81x_rc_sta_get_rates(mors, msta, &rates, skb->len);
|
||||
if (ret != 0)
|
||||
return;
|
||||
|
||||
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
|
||||
info->control.rates[i].flags = 0;
|
||||
if (rates.rates[i].rate != MMRC_MCS_UNUSED) {
|
||||
u8 mcs = rates.rates[i].rate;
|
||||
u8 nss_index = rates.rates[i].ss;
|
||||
enum dot11_bandwidth bw_idx =
|
||||
(enum dot11_bandwidth)rates.rates[i].bw;
|
||||
enum mm81x_rate_preamble pream =
|
||||
MM81X_RATE_PREAMBLE_S1G_SHORT;
|
||||
|
||||
mm81x_ratecode_bw_index_set(
|
||||
&tx_info->rates[i].mm81x_ratecode, bw_idx);
|
||||
mm81x_ratecode_mcs_index_set(
|
||||
&tx_info->rates[i].mm81x_ratecode, mcs);
|
||||
mm81x_ratecode_nss_index_set(
|
||||
&tx_info->rates[i].mm81x_ratecode, nss_index);
|
||||
if (bw_idx == DOT11_BANDWIDTH_1MHZ)
|
||||
pream = MM81X_RATE_PREAMBLE_S1G_1M;
|
||||
mm81x_ratecode_preamble_set(
|
||||
&tx_info->rates[i].mm81x_ratecode, pream);
|
||||
tx_info->rates[i].count = rates.rates[i].attempts;
|
||||
|
||||
if (rts_allowed &&
|
||||
(rates.rates[i].flags & BIT(MMRC_FLAGS_CTS_RTS))) {
|
||||
mm81x_ratecode_enable_rts(
|
||||
&tx_info->rates[i].mm81x_ratecode);
|
||||
info->control.rates[i].flags |=
|
||||
IEEE80211_TX_RC_USE_RTS_CTS;
|
||||
}
|
||||
|
||||
if (rates.rates[i].guard == MMRC_GUARD_SHORT) {
|
||||
mm81x_ratecode_enable_sgi(
|
||||
&tx_info->rates[i].mm81x_ratecode);
|
||||
info->control.rates[i].flags |=
|
||||
IEEE80211_TX_RC_SHORT_GI;
|
||||
}
|
||||
|
||||
/* Update skb tx_info */
|
||||
info->control.rates[i].idx = rates.rates[i].rate;
|
||||
info->control.rates[i].count = rates.rates[i].attempts;
|
||||
} else {
|
||||
info->control.rates[i].idx = -1;
|
||||
info->control.rates[i].count = 0;
|
||||
tx_info->rates[i].count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta,
|
||||
struct mmrc_rate_table *rates, int attempts,
|
||||
bool was_aggregated)
|
||||
{
|
||||
struct list_head *pos;
|
||||
|
||||
spin_lock_bh(&mors->mrc.lock);
|
||||
list_for_each(pos, &mors->mrc.stas) {
|
||||
struct mm81x_rc_sta *mrc_sta =
|
||||
list_entry(pos, struct mm81x_rc_sta, list);
|
||||
|
||||
if (&msta->rc == mrc_sta) {
|
||||
mmrc_feedback(msta->rc.tb, rates, attempts,
|
||||
was_aggregated);
|
||||
break;
|
||||
}
|
||||
}
|
||||
spin_unlock_bh(&mors->mrc.lock);
|
||||
}
|
||||
|
||||
void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
|
||||
struct ieee80211_sta *sta,
|
||||
struct mm81x_skb_tx_status *tx_sts,
|
||||
int attempts)
|
||||
{
|
||||
int i;
|
||||
u32 tx_airtime = 0;
|
||||
struct mmrc_rate_table rates;
|
||||
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
|
||||
struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
|
||||
struct ieee80211_tx_rate *r = &txi->status.rates[0];
|
||||
int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);
|
||||
struct mm81x_sta *msta = msta = (struct mm81x_sta *)sta->drv_priv;
|
||||
|
||||
/* Don't update rate info if basic rates were used */
|
||||
if (mm81x_rc_use_basic_rates(sta, skb, hdr))
|
||||
goto exit;
|
||||
|
||||
if (attempts <= 0)
|
||||
/* Did we really send the packet? */
|
||||
goto exit;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
rates.rates[i].rate = mm81x_ratecode_mcs_index_get(
|
||||
tx_sts->rates[i].mm81x_ratecode);
|
||||
rates.rates[i].ss = mm81x_ratecode_nss_index_get(
|
||||
tx_sts->rates[i].mm81x_ratecode);
|
||||
rates.rates[i].guard =
|
||||
mm81x_ratecode_sgi_get(tx_sts->rates[i].mm81x_ratecode);
|
||||
rates.rates[i].bw = mm81x_ratecode_bw_index_get(
|
||||
tx_sts->rates[i].mm81x_ratecode);
|
||||
rates.rates[i].flags =
|
||||
mm81x_ratecode_rts_get(tx_sts->rates[i].mm81x_ratecode);
|
||||
rates.rates[i].attempts = tx_sts->rates[i].count;
|
||||
|
||||
tx_airtime +=
|
||||
mmrc_calculate_rate_tx_time(&rates.rates[i], skb->len);
|
||||
}
|
||||
|
||||
if (msta) {
|
||||
/*
|
||||
* Save the rate information. This will be used to update
|
||||
* station's tx rate stats
|
||||
*/
|
||||
msta->last_sta_tx_rate.bw = rates.rates[0].bw;
|
||||
msta->last_sta_tx_rate.rate = rates.rates[0].rate;
|
||||
msta->last_sta_tx_rate.ss = rates.rates[0].ss;
|
||||
msta->last_sta_tx_rate.guard = rates.rates[0].guard;
|
||||
}
|
||||
|
||||
mm81x_rc_sta_set_rates(mors, msta, &rates, attempts,
|
||||
!!(le32_to_cpu(tx_sts->flags) &
|
||||
MM81X_TX_STATUS_WAS_AGGREGATED));
|
||||
|
||||
ieee80211_sta_register_airtime(sta, tx_sts->tid, tx_airtime, 0);
|
||||
|
||||
exit:
|
||||
ieee80211_tx_info_clear_status(txi);
|
||||
|
||||
if (!(le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_NO_ACK) &&
|
||||
!(txi->flags & IEEE80211_TX_CTL_NO_ACK))
|
||||
txi->flags |= IEEE80211_TX_STAT_ACK;
|
||||
|
||||
if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED) {
|
||||
txi->flags |= IEEE80211_TX_STAT_TX_FILTERED;
|
||||
|
||||
/*
|
||||
* Clear TX CTL AMPDU flag so that this frame gets rescheduled
|
||||
* in ieee80211_handle_filtered_frame(). This flag will get set
|
||||
* again by mac80211's tx path on rescheduling.
|
||||
*/
|
||||
txi->flags &= ~IEEE80211_TX_CTL_AMPDU;
|
||||
if (msta) {
|
||||
if (!msta->tx_ps_filter_en)
|
||||
dev_dbg(mors->dev, "TX ps filter set sta[%pM]",
|
||||
msta->addr);
|
||||
msta->tx_ps_filter_en = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (tx_sts->rates[i].count > 0) {
|
||||
r[i].count = tx_sts->rates[i].count;
|
||||
r[i].flags |= IEEE80211_TX_RC_MCS;
|
||||
} else {
|
||||
r[i].idx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* single packet per A-MPDU (for now) */
|
||||
if (txi->flags & IEEE80211_TX_CTL_AMPDU) {
|
||||
txi->flags |= IEEE80211_TX_STAT_AMPDU;
|
||||
txi->status.ampdu_len = 1;
|
||||
txi->status.ampdu_ack_len =
|
||||
txi->flags & IEEE80211_TX_STAT_ACK ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is
|
||||
* over
|
||||
*/
|
||||
if (sta && (txi->flags & IEEE80211_TX_STATUS_EOSP)) {
|
||||
txi->flags &= ~IEEE80211_TX_STATUS_EOSP;
|
||||
ieee80211_sta_eosp(sta);
|
||||
}
|
||||
}
|
||||
|
||||
void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
enum ieee80211_sta_state old_state,
|
||||
enum ieee80211_sta_state new_state)
|
||||
{
|
||||
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
|
||||
|
||||
/* Add to Morse RC STA list */
|
||||
if (old_state < new_state && new_state == IEEE80211_STA_ASSOC) {
|
||||
/* Newly associated, add to RC */
|
||||
mm81x_rc_sta_add(mors, vif, sta);
|
||||
} else if (old_state > new_state && (old_state == IEEE80211_STA_ASSOC ||
|
||||
old_state == IEEE80211_STA_AUTH)) {
|
||||
/* Lost or failed association; remove from list */
|
||||
mm81x_rc_sta_remove(mors, sta);
|
||||
} else if (old_state < new_state && old_state == IEEE80211_STA_NONE &&
|
||||
msta->rc.list.prev) {
|
||||
/*
|
||||
* Special case for driver warning issue causing a sta to be
|
||||
* left on the list
|
||||
*/
|
||||
dev_dbg(mors->dev, "Remove stale sta from rc list");
|
||||
mm81x_rc_sta_remove(mors, sta);
|
||||
}
|
||||
}
|
||||
51
drivers/net/wireless/morsemicro/mm81x/rc.h
Normal file
51
drivers/net/wireless/morsemicro/mm81x/rc.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_RC_H_
|
||||
#define _MM81X_RC_H_
|
||||
|
||||
#include <linux/list.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "core.h"
|
||||
#include "mmrc.h"
|
||||
|
||||
struct mm81x_vif;
|
||||
|
||||
#define INIT_MAX_RATES_NUM 4
|
||||
|
||||
struct mm81x_rc {
|
||||
/* Serialise rate control queue manipulation and timer functions */
|
||||
spinlock_t lock;
|
||||
struct list_head stas;
|
||||
struct timer_list timer;
|
||||
struct work_struct work;
|
||||
struct mm81x *mors;
|
||||
};
|
||||
|
||||
struct mm81x_rc_sta {
|
||||
struct mmrc_table *tb;
|
||||
struct list_head list;
|
||||
unsigned long last_update;
|
||||
};
|
||||
|
||||
void mm81x_rc_init(struct mm81x *mors);
|
||||
void mm81x_rc_deinit(struct mm81x *mors);
|
||||
int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta);
|
||||
void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta);
|
||||
void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
|
||||
struct mm81x_skb_tx_info *tx_info,
|
||||
struct sk_buff *skb, struct ieee80211_sta *sta,
|
||||
int tx_bw, bool rts_allowed);
|
||||
void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
|
||||
struct ieee80211_sta *sta,
|
||||
struct mm81x_skb_tx_status *tx_sts,
|
||||
int tx_attempts);
|
||||
void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
enum ieee80211_sta_state old_state,
|
||||
enum ieee80211_sta_state new_state);
|
||||
|
||||
#endif /* !_MM81X_RC_H_ */
|
||||
613
drivers/net/wireless/morsemicro/mm81x/sdio.c
Normal file
613
drivers/net/wireless/morsemicro/mm81x/sdio.c
Normal file
@@ -0,0 +1,613 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.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 "hw.h"
|
||||
#include "core.h"
|
||||
#include "bus.h"
|
||||
#include "mac.h"
|
||||
#include "fw.h"
|
||||
#include "hif.h"
|
||||
|
||||
/*
|
||||
* Value to indicate that the base address for bulk/register
|
||||
* read/writes has yet to be set
|
||||
*/
|
||||
#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF
|
||||
|
||||
#define MM81X_SDIO_ALIGNMENT (8)
|
||||
|
||||
#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000
|
||||
#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE
|
||||
#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1)
|
||||
#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2)
|
||||
|
||||
struct mm81x_sdio {
|
||||
bool enabled;
|
||||
u32 bulk_addr_base;
|
||||
u32 register_addr_base;
|
||||
struct sdio_func *func;
|
||||
const struct sdio_device_id *id;
|
||||
};
|
||||
|
||||
static void irq_handler(struct sdio_func *func1)
|
||||
{
|
||||
struct sdio_func *func = func1->card->sdio_func[1];
|
||||
struct mm81x *mors = sdio_get_drvdata(func);
|
||||
|
||||
mm81x_hw_irq_handle(mors);
|
||||
}
|
||||
|
||||
static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func = sdio->func;
|
||||
struct sdio_func *func1 = func->card->sdio_func[0];
|
||||
struct mm81x *mors = sdio_get_drvdata(func);
|
||||
|
||||
sdio_claim_host(func);
|
||||
ret = sdio_claim_irq(func1, irq_handler);
|
||||
if (ret)
|
||||
dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret);
|
||||
|
||||
sdio_release_host(func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio)
|
||||
{
|
||||
struct sdio_func *func = sdio->func;
|
||||
struct sdio_func *func1 = func->card->sdio_func[0];
|
||||
|
||||
sdio_claim_host(func);
|
||||
sdio_release_irq(func1);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable)
|
||||
{
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
|
||||
if (enable)
|
||||
mm81x_sdio_enable_irq(sdio);
|
||||
else
|
||||
mm81x_sdio_disable_irq(sdio);
|
||||
}
|
||||
|
||||
static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access)
|
||||
{
|
||||
return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3);
|
||||
}
|
||||
|
||||
static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio)
|
||||
{
|
||||
sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
|
||||
sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio,
|
||||
struct sdio_func *func, u32 address,
|
||||
u8 access)
|
||||
{
|
||||
int ret = 0;
|
||||
int retries = 0;
|
||||
static const int max_retries = 3;
|
||||
struct sdio_func *func2 = sdio->func;
|
||||
struct mm81x *mors = sdio_get_drvdata(sdio->func);
|
||||
s32 calculated_addr_base =
|
||||
mm81x_sdio_calculate_base_address(address, access);
|
||||
u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base :
|
||||
&sdio->register_addr_base;
|
||||
|
||||
if ((*current_addr_base) == calculated_addr_base &&
|
||||
*current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET)
|
||||
return ret;
|
||||
|
||||
retry:
|
||||
sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)),
|
||||
MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)),
|
||||
MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
*current_addr_base = calculated_addr_base;
|
||||
if (retries)
|
||||
dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__,
|
||||
retries);
|
||||
|
||||
return ret;
|
||||
err:
|
||||
retries++;
|
||||
if (ret == -ETIMEDOUT && retries <= max_retries) {
|
||||
dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n",
|
||||
__func__, ret, retries, max_retries);
|
||||
goto retry;
|
||||
}
|
||||
|
||||
*current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address,
|
||||
u8 *data, ssize_t size)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func2 = sdio->func;
|
||||
struct mm81x *mors = sdio_get_drvdata(sdio->func);
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func2, address,
|
||||
MM81X_CONFIG_ACCESS_4BYTE);
|
||||
if (unlikely(!IS_ALIGNED((uintptr_t)data,
|
||||
mors->bus_ops->bulk_alignment))) {
|
||||
ret = -EBADE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
address &= 0x0000FFFF; /* remove base and keep offset */
|
||||
ret = sdio_memcpy_toio(func2, address, data, size);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
ret = size;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address,
|
||||
u8 *data, ssize_t size)
|
||||
{
|
||||
int i, ret;
|
||||
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func1, address,
|
||||
MM81X_CONFIG_ACCESS_1BYTE);
|
||||
|
||||
address &= 0x0000FFFF; /* remove base and keep offset */
|
||||
for (i = 0; i < size; i++) {
|
||||
sdio_writeb(func1, data[i], address + i, (int *)&ret);
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = size;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_sdio_claim_host(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
struct sdio_func *func = sdio->func;
|
||||
|
||||
sdio_claim_host(func);
|
||||
}
|
||||
|
||||
static void mm81x_sdio_release_host(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
struct sdio_func *func = sdio->func;
|
||||
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address,
|
||||
u8 *data, ssize_t size)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func2 = sdio->func;
|
||||
struct mm81x *mors = sdio_get_drvdata(sdio->func);
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func2, address,
|
||||
MM81X_CONFIG_ACCESS_4BYTE);
|
||||
if (unlikely(!IS_ALIGNED((uintptr_t)data,
|
||||
mors->bus_ops->bulk_alignment))) {
|
||||
ret = -EBADE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
address &= 0x0000FFFF; /* remove base and keep offset */
|
||||
ret = sdio_memcpy_fromio(func2, data, address, size);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
/*
|
||||
* Observed sometimes that SDIO read repeats the first 4-bytes
|
||||
* word twice, overwriting second word (hence, tail will be
|
||||
* overwritten with 'sync' byte). When this happens, reading
|
||||
* will fetch the correct word. NB: if repeated again, pass it
|
||||
* anyway and upper layers will handle it
|
||||
*/
|
||||
|
||||
if (size >= 8 && memcmp(data, data + 4, 4) == 0)
|
||||
sdio_memcpy_fromio(func2, data, address, 8);
|
||||
|
||||
ret = size;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address,
|
||||
u8 *data, ssize_t size)
|
||||
{
|
||||
int i, ret;
|
||||
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func1, address,
|
||||
MM81X_CONFIG_ACCESS_1BYTE);
|
||||
|
||||
address &= 0x0000FFFF; /* remove base and keep offset */
|
||||
for (i = 0; i < size; i++) {
|
||||
data[i] = sdio_readb(func1, address + i, (int *)&ret);
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
ret = size;
|
||||
exit:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data,
|
||||
int len)
|
||||
{
|
||||
int ret = 0;
|
||||
int block_len, byte_len;
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
int remaining = len;
|
||||
int offset = 0;
|
||||
|
||||
if (remaining > 0 && address & 0x3) {
|
||||
len = 4 - (address & 0x3);
|
||||
ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len);
|
||||
if (ret != len)
|
||||
return -EIO;
|
||||
|
||||
offset += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
while ((remaining) > 0) {
|
||||
/*
|
||||
* We can only write up to the end of a single window in
|
||||
* each write operation.
|
||||
*/
|
||||
u32 window_end = (address + offset) |
|
||||
~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
|
||||
|
||||
len = min(remaining, (int)(window_end + 1 - address - offset));
|
||||
block_len = len & ~0x3;
|
||||
byte_len = len & 0x3;
|
||||
|
||||
if (block_len) {
|
||||
ret = mm81x_sdio_mem_write_block(sdio, address + offset,
|
||||
(u8 *)(data + offset),
|
||||
block_len);
|
||||
if (ret != block_len)
|
||||
return -EIO;
|
||||
|
||||
offset += block_len;
|
||||
}
|
||||
|
||||
if (byte_len) {
|
||||
ret = mm81x_sdio_mem_write_byte(sdio, address + offset,
|
||||
(u8 *)(data + offset),
|
||||
byte_len);
|
||||
if (ret != byte_len)
|
||||
return -EIO;
|
||||
|
||||
offset += byte_len;
|
||||
}
|
||||
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data,
|
||||
int len)
|
||||
{
|
||||
int ret = 0;
|
||||
int block_len, byte_len;
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
int remaining = len;
|
||||
int offset = 0;
|
||||
|
||||
if (remaining > 0 && address & 0x3) {
|
||||
len = 4 - (address & 0x3);
|
||||
ret = mm81x_sdio_mem_read_byte(sdio, address, data, len);
|
||||
if (ret != len)
|
||||
return -EIO;
|
||||
|
||||
offset += len;
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
/*
|
||||
* We can only read up to the end of a single window in
|
||||
* each read operation.
|
||||
*/
|
||||
u32 window_end = (address + offset) |
|
||||
~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
|
||||
|
||||
len = min(remaining, (int)(window_end + 1 - address - offset));
|
||||
block_len = len & ~0x3;
|
||||
byte_len = len & 0x3;
|
||||
|
||||
if (block_len) {
|
||||
ret = mm81x_sdio_mem_read_block(sdio, address + offset,
|
||||
data + offset,
|
||||
block_len);
|
||||
if (ret != block_len)
|
||||
return -EIO;
|
||||
|
||||
offset += block_len;
|
||||
}
|
||||
|
||||
if (byte_len) {
|
||||
ret = mm81x_sdio_mem_read_byte(sdio, address + offset,
|
||||
data + offset, byte_len);
|
||||
if (ret != byte_len)
|
||||
return -EIO;
|
||||
|
||||
offset += byte_len;
|
||||
}
|
||||
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val)
|
||||
{
|
||||
ssize_t ret = 0;
|
||||
u32 original_address = address;
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func1, address,
|
||||
MM81X_CONFIG_ACCESS_4BYTE);
|
||||
|
||||
address &= 0x0000FFFF;
|
||||
sdio_writel(func1, (__force u32)cpu_to_le32(val),
|
||||
(__force u32)cpu_to_le32(address), (int *)&ret);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
if (original_address == MM81X_REG_RESET(mors) &&
|
||||
val == MM81X_REG_RESET_VALUE(mors)) {
|
||||
dev_dbg(mors->dev,
|
||||
"SDIO reset detected, invalidating base addr\n");
|
||||
mm81x_sdio_reset_base_address(sdio);
|
||||
}
|
||||
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val)
|
||||
{
|
||||
u32 value;
|
||||
ssize_t ret = 0;
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
|
||||
|
||||
mm81x_sdio_set_func_address_base(sdio, func1, address,
|
||||
MM81X_CONFIG_ACCESS_4BYTE);
|
||||
|
||||
address &= 0x0000FFFF;
|
||||
value = sdio_readl(func1, (__force u32)cpu_to_le32(address),
|
||||
(int *)&ret);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = le32_to_cpup((__le32 *)&value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable)
|
||||
{
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
struct sdio_func *func = sdio->func;
|
||||
struct mmc_host *host = func->card->host;
|
||||
|
||||
sdio_claim_host(func);
|
||||
|
||||
if (enable) {
|
||||
/*
|
||||
* No need to do anything special to re-enable the sdio bus.
|
||||
* This will happen automatically when a read/write is
|
||||
* attempted and sdio->bulk_addr_base == 0.
|
||||
*/
|
||||
sdio->enabled = true;
|
||||
host->ops->enable_sdio_irq(host, 1);
|
||||
dev_dbg(mors->dev, "%s: enabling bus\n", __func__);
|
||||
} else {
|
||||
host->ops->enable_sdio_irq(host, 0);
|
||||
mm81x_sdio_reset_base_address(sdio);
|
||||
sdio->enabled = false;
|
||||
dev_dbg(mors->dev, "%s: disabling bus\n", __func__);
|
||||
}
|
||||
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
static void mm81x_sdio_reset(struct sdio_func *func)
|
||||
{
|
||||
sdio_claim_host(func);
|
||||
sdio_disable_func(func);
|
||||
sdio_release_host(func);
|
||||
|
||||
mdelay(20);
|
||||
|
||||
sdio_claim_host(func);
|
||||
sdio_disable_func(func);
|
||||
mmc_hw_reset(func->card);
|
||||
sdio_enable_func(func);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst)
|
||||
{
|
||||
u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 :
|
||||
SDIO_WORD_BURST_DISABLE;
|
||||
|
||||
mm81x_hw_enable_burst_mode(mors, burst_mode);
|
||||
}
|
||||
|
||||
static const struct mm81x_bus_ops mm81x_sdio_ops = {
|
||||
.dm_read = mm81x_sdio_dm_read,
|
||||
.dm_write = mm81x_sdio_dm_write,
|
||||
.reg32_read = mm81x_sdio_reg32_read,
|
||||
.reg32_write = mm81x_sdio_reg32_write,
|
||||
.set_bus_enable = mm81x_sdio_bus_enable,
|
||||
.claim = mm81x_sdio_claim_host,
|
||||
.release = mm81x_sdio_release_host,
|
||||
.config_burst_mode = mm81x_sdio_config_burst_mode,
|
||||
.set_irq = mm81x_sdio_set_irq,
|
||||
.bulk_alignment = MM81X_SDIO_ALIGNMENT
|
||||
};
|
||||
|
||||
static int mm81x_sdio_enable(struct mm81x_sdio *sdio)
|
||||
{
|
||||
int ret;
|
||||
struct sdio_func *func = sdio->func;
|
||||
struct mm81x *mors = sdio_get_drvdata(func);
|
||||
|
||||
sdio_claim_host(func);
|
||||
ret = sdio_enable_func(func);
|
||||
if (ret)
|
||||
dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret);
|
||||
sdio_release_host(func);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_sdio_release(struct mm81x_sdio *sdio)
|
||||
{
|
||||
struct sdio_func *func = sdio->func;
|
||||
|
||||
sdio_claim_host(func);
|
||||
sdio_disable_func(func);
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
static int mm81x_sdio_probe(struct sdio_func *func,
|
||||
const struct sdio_device_id *id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mm81x *mors = NULL;
|
||||
struct mm81x_sdio *sdio;
|
||||
struct device *dev = &func->dev;
|
||||
|
||||
if (func->num == 1)
|
||||
return 0;
|
||||
|
||||
if (func->num != 2)
|
||||
return -ENODEV;
|
||||
|
||||
mors = mm81x_core_alloc(sizeof(*sdio), dev);
|
||||
if (!mors)
|
||||
return -ENOMEM;
|
||||
|
||||
mors->bus_ops = &mm81x_sdio_ops;
|
||||
mors->bus_type = MM81X_BUS_TYPE_SDIO;
|
||||
|
||||
sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
sdio->func = func;
|
||||
sdio->id = id;
|
||||
sdio->enabled = true;
|
||||
mm81x_sdio_reset_base_address(sdio);
|
||||
|
||||
sdio_set_drvdata(func, mors);
|
||||
|
||||
ret = mm81x_sdio_enable(sdio);
|
||||
if (ret)
|
||||
goto err_core_free;
|
||||
|
||||
mm81x_sdio_config_burst_mode(mors, true);
|
||||
|
||||
ret = mm81x_core_init(mors);
|
||||
if (ret)
|
||||
goto err_sdio_release;
|
||||
|
||||
ret = mm81x_sdio_enable_irq(sdio);
|
||||
if (ret)
|
||||
goto err_core_deinit;
|
||||
|
||||
ret = mm81x_core_register(mors);
|
||||
if (ret)
|
||||
goto err_disable_irq;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_irq:
|
||||
mm81x_sdio_disable_irq(sdio);
|
||||
err_core_deinit:
|
||||
mm81x_core_deinit(mors);
|
||||
err_sdio_release:
|
||||
mm81x_sdio_release(sdio);
|
||||
err_core_free:
|
||||
mm81x_core_free(mors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_sdio_remove(struct sdio_func *func)
|
||||
{
|
||||
struct mm81x *mors = sdio_get_drvdata(func);
|
||||
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
|
||||
|
||||
if (!mors)
|
||||
return;
|
||||
|
||||
mm81x_core_unregister(mors);
|
||||
mm81x_sdio_disable_irq(sdio);
|
||||
mm81x_core_deinit(mors);
|
||||
mm81x_sdio_release(sdio);
|
||||
mm81x_sdio_reset(func);
|
||||
mm81x_core_free(mors);
|
||||
sdio_set_drvdata(func, NULL);
|
||||
}
|
||||
|
||||
static const struct sdio_device_id mm81x_sdio_devices[] = {
|
||||
{ SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO,
|
||||
SDIO_DEVICE_ID_MORSEMICRO_MM8108) },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices);
|
||||
|
||||
static struct sdio_driver mm81x_sdio_driver = {
|
||||
.name = "mm81x_sdio",
|
||||
.id_table = mm81x_sdio_devices,
|
||||
.probe = mm81x_sdio_probe,
|
||||
.remove = mm81x_sdio_remove,
|
||||
};
|
||||
|
||||
module_sdio_driver(mm81x_sdio_driver);
|
||||
|
||||
MODULE_AUTHOR("Morse Micro");
|
||||
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
1064
drivers/net/wireless/morsemicro/mm81x/skbq.c
Normal file
1064
drivers/net/wireless/morsemicro/mm81x/skbq.c
Normal file
File diff suppressed because it is too large
Load Diff
218
drivers/net/wireless/morsemicro/mm81x/skbq.h
Normal file
218
drivers/net/wireless/morsemicro/mm81x/skbq.h
Normal file
@@ -0,0 +1,218 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_SKBQ_H_
|
||||
#define _MM81X_SKBQ_H_
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "rate_code.h"
|
||||
|
||||
/* Sync value of skb header to indicate a valid skb */
|
||||
#define MM81X_SKB_HEADER_SYNC (0xAA)
|
||||
/* Sync value indicating that the chip owns this skb */
|
||||
#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB)
|
||||
|
||||
enum mm81x_tx_status_and_conf_flags {
|
||||
MM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0),
|
||||
MM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1),
|
||||
MM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2),
|
||||
MM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3),
|
||||
MM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) |
|
||||
BIT(8) | BIT(9) | BIT(10) | BIT(11)),
|
||||
MM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)),
|
||||
MM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)),
|
||||
MM81X_TX_CONF_IGNORE_TWT = (BIT(16)),
|
||||
MM81X_TX_STATUS_PAGE_INVALID = (BIT(17)),
|
||||
MM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)),
|
||||
MM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)),
|
||||
MM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)),
|
||||
MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)),
|
||||
MM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)),
|
||||
MM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24),
|
||||
MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31))
|
||||
};
|
||||
|
||||
/* Getter and setter macros for vif id */
|
||||
#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF)
|
||||
#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \
|
||||
(((x) & MM81X_TX_CONF_FLAGS_VIF_ID_MASK) << 4)
|
||||
#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \
|
||||
(((x) & MM81X_TX_CONF_FLAGS_VIF_ID) >> 4)
|
||||
|
||||
/* Getter and setter macros for key index */
|
||||
#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) & 0x07) << 12)
|
||||
#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \
|
||||
(((x) & MM81X_TX_CONF_FLAGS_KEY_IDX) >> 12)
|
||||
|
||||
enum mm81x_rx_status_flags {
|
||||
MM81X_RX_STATUS_FLAGS_ERROR = BIT(0),
|
||||
MM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1),
|
||||
MM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2),
|
||||
MM81X_RX_STATUS_FLAGS_EOF = BIT(3),
|
||||
MM81X_RX_STATUS_FLAGS_AMPDU = BIT(4),
|
||||
MM81X_RX_STATUS_FLAGS_NDP = BIT(7),
|
||||
MM81X_RX_STATUS_FLAGS_UPLINK = BIT(8),
|
||||
MM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)),
|
||||
MM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)),
|
||||
MM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14),
|
||||
MM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17),
|
||||
};
|
||||
|
||||
/* Getter and Setter macros for vif id */
|
||||
#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF)
|
||||
#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \
|
||||
(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) << 17)
|
||||
#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \
|
||||
(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID) >> 17)
|
||||
#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \
|
||||
((x) & ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK << 17))
|
||||
|
||||
/* Getter macro for guard interval */
|
||||
#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \
|
||||
(((x) & MM81X_RX_STATUS_FLAGS_UPLINK) >> 8)
|
||||
|
||||
/* Getter macro for response indication */
|
||||
#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) & MM81X_RX_STATUS_FLAGS_RI) >> 9)
|
||||
|
||||
/* Getter macro for NDP type */
|
||||
#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \
|
||||
(((x) & MM81X_RX_STATUS_FLAGS_NDP_TYPE) >> 11)
|
||||
|
||||
enum mm81x_skb_channel {
|
||||
MM81X_SKB_CHAN_DATA = 0x0,
|
||||
MM81X_SKB_CHAN_NDP_FRAMES = 0x1,
|
||||
MM81X_SKB_CHAN_DATA_NOACK = 0x2,
|
||||
MM81X_SKB_CHAN_BEACON = 0x3,
|
||||
MM81X_SKB_CHAN_MGMT = 0x4,
|
||||
MM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80,
|
||||
MM81X_SKB_CHAN_COMMAND = 0xFE,
|
||||
MM81X_SKB_CHAN_TX_STATUS = 0xFF
|
||||
};
|
||||
|
||||
#define MM81X_SKB_MAX_RATES (4)
|
||||
|
||||
struct mm81x_skb_rate_info {
|
||||
mm81x_rate_code_t mm81x_ratecode;
|
||||
u8 count;
|
||||
} __packed;
|
||||
|
||||
struct mm81x_skb_tx_status {
|
||||
__le32 flags;
|
||||
__le32 pkt_id;
|
||||
u8 tid;
|
||||
u8 channel;
|
||||
__le16 ampdu_info;
|
||||
struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
|
||||
} __packed;
|
||||
|
||||
#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) >> 10) & 0x3F)
|
||||
#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) >> 5) & 0x1F)
|
||||
#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) & 0x1F)
|
||||
|
||||
struct mm81x_skb_tx_info {
|
||||
__le32 flags;
|
||||
__le32 pkt_id;
|
||||
u8 tid;
|
||||
u8 tid_params;
|
||||
u8 mmss_params;
|
||||
u8 padding[1];
|
||||
struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
|
||||
} __packed;
|
||||
|
||||
#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f
|
||||
#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20
|
||||
#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40
|
||||
#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80
|
||||
|
||||
/* Bitmap for MMSS (Minimum MPDU start spacing) parameters
|
||||
* +-----------+-----------+
|
||||
* | Morse | MMSS set |
|
||||
* | MMSS | by S1G cap|
|
||||
* | offset | IE |
|
||||
* |-----------|-----------|
|
||||
* |b7|b6|b5|b4|b3|b2|b1|b0|
|
||||
*/
|
||||
#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0)
|
||||
#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4
|
||||
#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4)
|
||||
#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) & TX_INFO_MMSS_PARAMS_MMSS_MASK)
|
||||
#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x) \
|
||||
(((x) << TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) & \
|
||||
TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK)
|
||||
|
||||
struct mm81x_skb_rx_status {
|
||||
__le32 flags;
|
||||
mm81x_rate_code_t mm81x_ratecode;
|
||||
__le16 rssi;
|
||||
__le16 freq_100khz;
|
||||
u8 bss_color;
|
||||
s8 noise_dbm;
|
||||
/** Padding for word alignment */
|
||||
u8 padding[2];
|
||||
__le64 rx_timestamp_us;
|
||||
} __packed;
|
||||
|
||||
struct mm81x_skb_hdr {
|
||||
u8 sync;
|
||||
u8 channel;
|
||||
__le16 len;
|
||||
u8 offset;
|
||||
u8 checksum_lower;
|
||||
__le16 checksum_upper;
|
||||
union {
|
||||
struct mm81x_skb_tx_info tx_info;
|
||||
struct mm81x_skb_tx_status tx_status;
|
||||
struct mm81x_skb_rx_status rx_status;
|
||||
};
|
||||
} __packed;
|
||||
|
||||
#define MM81X_SKBQ_SIZE (4 * 128 * 1024)
|
||||
|
||||
struct mm81x;
|
||||
|
||||
struct mm81x_skbq {
|
||||
struct mm81x *mors;
|
||||
u32 pkt_seq; /* SKB sequence used in tx_status */
|
||||
u16 flags;
|
||||
u32 skbq_size; /* current off loaded size */
|
||||
spinlock_t lock;
|
||||
struct sk_buff_head skbq;
|
||||
struct sk_buff_head pending; /* packets sent pending feedback */
|
||||
struct work_struct dispatch_work;
|
||||
};
|
||||
|
||||
void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
|
||||
void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq);
|
||||
u32 mm81x_skbq_space(struct mm81x_skbq *mq);
|
||||
u32 mm81x_skbq_size(struct mm81x_skbq *mq);
|
||||
int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,
|
||||
int num_skb);
|
||||
struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq,
|
||||
unsigned int length);
|
||||
int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb,
|
||||
struct mm81x_skb_tx_info *tx_info, u8 channel);
|
||||
int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb);
|
||||
void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
|
||||
void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
|
||||
void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
|
||||
struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq);
|
||||
void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags);
|
||||
void mm81x_skbq_finish(struct mm81x_skbq *mq);
|
||||
void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb);
|
||||
void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file);
|
||||
void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,
|
||||
struct mm81x_skb_tx_status *tx_sts);
|
||||
void mm81x_skbq_tx_flush(struct mm81x_skbq *mq);
|
||||
int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq);
|
||||
void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors);
|
||||
u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq);
|
||||
u32 mm81x_skbq_count(struct mm81x_skbq *mq);
|
||||
u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq);
|
||||
void mm81x_skbq_data_traffic_pause(struct mm81x *mors);
|
||||
void mm81x_skbq_data_traffic_resume(struct mm81x *mors);
|
||||
bool mm81x_skbq_validate_checksum(u8 *data);
|
||||
|
||||
#endif /* !_MM81X_SKBQ_H_ */
|
||||
943
drivers/net/wireless/morsemicro/mm81x/usb.c
Normal file
943
drivers/net/wireless/morsemicro/mm81x/usb.c
Normal file
@@ -0,0 +1,943 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/usb.h>
|
||||
#include "hif.h"
|
||||
#include "bus.h"
|
||||
#include "mac.h"
|
||||
#include "core.h"
|
||||
|
||||
/*
|
||||
* URB timeout in milliseconds. If an URB does not complete within this
|
||||
* time, it will be killed. This timeout needs to account for USB suspendand
|
||||
* resume occurring before the URB can be transferred, and it also needs to
|
||||
* account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially
|
||||
* slow, congested USB Full Speed link.
|
||||
*/
|
||||
#define URB_TIMEOUT_MS 250
|
||||
|
||||
/* High speed USB 2^(4-1) * 125usec = 1msec */
|
||||
#define MM81X_USB_INTERRUPT_INTERVAL 4
|
||||
|
||||
/* Max bytes per USB read/write */
|
||||
#define USB_MAX_TRANSFER_SIZE (16 * 1024)
|
||||
|
||||
/* INT EP buffer size */
|
||||
#define MM81X_EP_INT_BUFFER_SIZE 8
|
||||
|
||||
/* Morse vendor IDs*/
|
||||
#define MM81X_VENDOR_ID 0x325b
|
||||
#define MM81X_MM810X_PRODUCT_ID 0x8100
|
||||
|
||||
/* Power management runtime auto-suspend delay value in milliseconds */
|
||||
#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100
|
||||
|
||||
enum mm81x_usb_endpoints {
|
||||
MM81X_EP_CMD = 0,
|
||||
MM81X_EP_INT,
|
||||
MM81X_EP_MEM_RD,
|
||||
MM81X_EP_MEM_WR,
|
||||
MM81X_EP_REG_RD,
|
||||
MM81X_EP_REG_WR,
|
||||
MM81X_EP_EP_MAX,
|
||||
};
|
||||
|
||||
struct mm81x_usb_endpoint {
|
||||
unsigned char *buffer;
|
||||
struct urb *urb;
|
||||
__u8 addr;
|
||||
int size;
|
||||
};
|
||||
|
||||
enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED };
|
||||
|
||||
struct mm81x_usb {
|
||||
struct usb_device *udev;
|
||||
struct usb_interface *interface;
|
||||
struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX];
|
||||
int errors;
|
||||
|
||||
/* serialise USB device struct */
|
||||
struct mutex lock;
|
||||
|
||||
/* serialise USB bus access */
|
||||
struct mutex bus_lock;
|
||||
|
||||
bool ongoing_cmd;
|
||||
bool ongoing_rw;
|
||||
wait_queue_head_t rw_in_wait;
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
enum mm81x_usb_command_direction {
|
||||
MM81X_USB_WRITE = 0x00,
|
||||
MM81X_USB_READ = 0x80,
|
||||
MM81X_USB_RESET = 0x02,
|
||||
};
|
||||
|
||||
struct mm81x_usb_command {
|
||||
__le32 dir; /* Next BULK direction */
|
||||
__le32 address; /* Next BULK address */
|
||||
__le32 length; /* Next BULK size */
|
||||
};
|
||||
|
||||
static const struct usb_device_id mm81x_usb_table[] = {
|
||||
{ USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) },
|
||||
{} /* Terminating entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(usb, mm81x_usb_table);
|
||||
|
||||
static void mm81x_usb_irq_work(struct work_struct *work)
|
||||
{
|
||||
struct mm81x *mors = container_of(work, struct mm81x, usb_irq_work);
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
mm81x_hw_irq_handle(mors);
|
||||
mm81x_release_bus(mors);
|
||||
}
|
||||
|
||||
static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb)
|
||||
{
|
||||
return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) ||
|
||||
(urb->status == -ETIME) || (urb->status == -EPIPE));
|
||||
}
|
||||
|
||||
static void mm81x_usb_int_handler(struct urb *urb)
|
||||
{
|
||||
int ret;
|
||||
struct mm81x *mors = urb->context;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return;
|
||||
|
||||
if (urb->status) {
|
||||
if (mm81x_usb_urb_status_is_disconnect(urb)) {
|
||||
clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
|
||||
set_bit(MM81X_STATE_CHIP_UNRESPONSIVE,
|
||||
&mors->state_flags);
|
||||
dev_dbg(mors->dev,
|
||||
"USB sudden disconnect detected in %s",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
|
||||
urb->status == -ESHUTDOWN))
|
||||
dev_err(mors->dev, "- nonzero read status received: %d",
|
||||
urb->status);
|
||||
}
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
||||
|
||||
/* usb_kill_urb has been called */
|
||||
if (ret == -EPERM)
|
||||
return;
|
||||
else if (ret)
|
||||
dev_err(mors->dev, "error: resubmit urb %p err code %d", urb,
|
||||
ret);
|
||||
|
||||
queue_work(mors->chip_wq, &mors->usb_irq_work);
|
||||
}
|
||||
|
||||
static int mm81x_usb_int_enable(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
struct urb *urb;
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!urb) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_INT].urb = urb;
|
||||
|
||||
musb->endpoints[MM81X_EP_INT].buffer =
|
||||
usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
|
||||
GFP_KERNEL, &urb->transfer_dma);
|
||||
if (!musb->endpoints[MM81X_EP_INT].buffer) {
|
||||
dev_err(mors->dev, "couldn't allocate transfer_buffer");
|
||||
ret = -ENOMEM;
|
||||
goto error_set_urb_null;
|
||||
}
|
||||
|
||||
usb_fill_int_urb(
|
||||
musb->endpoints[MM81X_EP_INT].urb, musb->udev,
|
||||
usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr),
|
||||
musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE,
|
||||
mm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL);
|
||||
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
|
||||
ret = usb_submit_urb(urb, GFP_KERNEL);
|
||||
if (ret) {
|
||||
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
|
||||
musb->endpoints[MM81X_EP_INT].buffer,
|
||||
urb->transfer_dma);
|
||||
error_set_urb_null:
|
||||
musb->endpoints[MM81X_EP_INT].urb = NULL;
|
||||
usb_free_urb(urb);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_usb_int_stop(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb);
|
||||
cancel_work_sync(&mors->usb_irq_work);
|
||||
}
|
||||
|
||||
static void mm81x_usb_cmd_callback(struct urb *urb)
|
||||
{
|
||||
struct mm81x *mors = urb->context;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
/* sync/async unlink faults aren't errors */
|
||||
if (urb->status) {
|
||||
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
|
||||
urb->status == -ESHUTDOWN))
|
||||
dev_err(mors->dev,
|
||||
"nonzero write bulk status received: %d",
|
||||
urb->status);
|
||||
|
||||
musb->errors = urb->status;
|
||||
}
|
||||
|
||||
musb->ongoing_cmd = false;
|
||||
wake_up(&musb->rw_in_wait);
|
||||
}
|
||||
|
||||
static int mm81x_usb_cmd(struct mm81x_usb *musb,
|
||||
const struct mm81x_usb_command *cmd)
|
||||
{
|
||||
int retval = 0;
|
||||
struct mm81x *mors = usb_get_intfdata(musb->interface);
|
||||
struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD];
|
||||
size_t writesize = sizeof(*cmd);
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
memcpy(ep->buffer, cmd, writesize);
|
||||
|
||||
usb_fill_bulk_urb(ep->urb, musb->udev,
|
||||
usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer,
|
||||
writesize, mm81x_usb_cmd_callback, mors);
|
||||
ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
|
||||
|
||||
musb->ongoing_cmd = true;
|
||||
|
||||
retval = usb_submit_urb(ep->urb, GFP_KERNEL);
|
||||
if (retval) {
|
||||
dev_err(mors->dev, "- failed submitting write urb, error %d",
|
||||
retval);
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
retval = wait_event_interruptible_timeout(
|
||||
musb->rw_in_wait, (!musb->ongoing_cmd),
|
||||
msecs_to_jiffies(URB_TIMEOUT_MS));
|
||||
if (retval < 0) {
|
||||
dev_err(mors->dev, "error waiting for urb %d", retval);
|
||||
goto error;
|
||||
} else if (retval == 0) {
|
||||
dev_err(mors->dev, "timed out waiting for urb");
|
||||
usb_kill_urb(ep->urb);
|
||||
retval = -ETIMEDOUT;
|
||||
goto error;
|
||||
}
|
||||
|
||||
musb->ongoing_cmd = false;
|
||||
return writesize;
|
||||
|
||||
error:
|
||||
musb->ongoing_cmd = false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int mm81x_usb_ndr_reset(struct mm81x *mors)
|
||||
{
|
||||
int ret;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
struct mm81x_usb_command cmd;
|
||||
|
||||
mutex_lock(&musb->lock);
|
||||
|
||||
musb->ongoing_rw = true;
|
||||
musb->errors = 0;
|
||||
|
||||
cmd.dir = cpu_to_le32(MM81X_USB_RESET);
|
||||
cmd.address = cpu_to_le32(0);
|
||||
cmd.length = cpu_to_le32(0);
|
||||
|
||||
ret = mm81x_usb_cmd(musb, &cmd);
|
||||
if (ret < 0)
|
||||
dev_err(mors->dev, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n",
|
||||
ret);
|
||||
else
|
||||
ret = 0;
|
||||
|
||||
musb->ongoing_rw = false;
|
||||
mutex_unlock(&musb->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_usb_mem_rw_callback(struct urb *urb)
|
||||
{
|
||||
struct mm81x *mors = urb->context;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
/* sync/async unlink faults aren't errors */
|
||||
if (urb->status) {
|
||||
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
|
||||
urb->status == -ESHUTDOWN))
|
||||
dev_err(mors->dev,
|
||||
"nonzero write bulk status received: %d",
|
||||
urb->status);
|
||||
|
||||
musb->errors = urb->status;
|
||||
}
|
||||
|
||||
musb->ongoing_rw = false;
|
||||
wake_up(&musb->rw_in_wait);
|
||||
}
|
||||
|
||||
static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data,
|
||||
ssize_t size)
|
||||
{
|
||||
int ret;
|
||||
struct mm81x_usb_command cmd;
|
||||
struct mm81x *mors = usb_get_intfdata(musb->interface);
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&musb->lock);
|
||||
|
||||
musb->ongoing_rw = true;
|
||||
musb->errors = 0;
|
||||
|
||||
/* Send command ahead to prepare for Tokens */
|
||||
cmd.dir = cpu_to_le32(MM81X_USB_READ);
|
||||
cmd.address = cpu_to_le32(address);
|
||||
cmd.length = cpu_to_le32(size);
|
||||
|
||||
ret = mm81x_usb_cmd(musb, &cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "mm81x_usb_cmd error %d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Let's be fast push the next URB, don't wait until command is done */
|
||||
usb_fill_bulk_urb(
|
||||
musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev,
|
||||
usb_rcvbulkpipe(musb->udev,
|
||||
musb->endpoints[MM81X_EP_MEM_RD].addr),
|
||||
musb->endpoints[MM81X_EP_MEM_RD].buffer, size,
|
||||
mm81x_usb_mem_rw_callback, mors);
|
||||
|
||||
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "failed submitting read urb, error %d", ret);
|
||||
ret = (ret == -ENOMEM) ? ret : -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = wait_event_interruptible_timeout(
|
||||
musb->rw_in_wait, (!musb->ongoing_rw),
|
||||
msecs_to_jiffies(URB_TIMEOUT_MS));
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "wait_event_interruptible: error %d", ret);
|
||||
goto error;
|
||||
} else if (ret == 0) {
|
||||
/* Timed out. */
|
||||
usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
|
||||
}
|
||||
|
||||
if (musb->errors) {
|
||||
ret = musb->errors;
|
||||
dev_err(mors->dev, "mem read error %d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size);
|
||||
ret = size;
|
||||
|
||||
error:
|
||||
musb->ongoing_rw = false;
|
||||
mutex_unlock(&musb->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data,
|
||||
ssize_t size)
|
||||
{
|
||||
int ret;
|
||||
struct mm81x_usb_command cmd;
|
||||
struct mm81x *mors = usb_get_intfdata(musb->interface);
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&musb->lock);
|
||||
|
||||
musb->ongoing_rw = true;
|
||||
musb->errors = 0;
|
||||
|
||||
/* Send command ahead to prepare for Tokens */
|
||||
cmd.dir = cpu_to_le32(MM81X_USB_WRITE);
|
||||
cmd.address = cpu_to_le32(address);
|
||||
cmd.length = cpu_to_le32(size);
|
||||
ret = mm81x_usb_cmd(musb, &cmd);
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "mm81x_usb_mem_read error %d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size);
|
||||
|
||||
/* prepare a read */
|
||||
usb_fill_bulk_urb(
|
||||
musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev,
|
||||
usb_sndbulkpipe(musb->udev,
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr),
|
||||
musb->endpoints[MM81X_EP_MEM_WR].buffer, size,
|
||||
mm81x_usb_mem_rw_callback, mors);
|
||||
|
||||
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "- failed submitting write urb, error %d",
|
||||
ret);
|
||||
ret = (ret == -ENOMEM) ? ret : -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = wait_event_interruptible_timeout(
|
||||
musb->rw_in_wait, (!musb->ongoing_rw),
|
||||
msecs_to_jiffies(URB_TIMEOUT_MS));
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "error %d", ret);
|
||||
goto error;
|
||||
} else if (ret == 0) {
|
||||
/* Timed out. */
|
||||
usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
|
||||
}
|
||||
|
||||
if (musb->errors) {
|
||||
ret = musb->errors;
|
||||
dev_err(mors->dev, "error %d", ret);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = size;
|
||||
|
||||
error:
|
||||
musb->ongoing_rw = false;
|
||||
mutex_unlock(&musb->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len)
|
||||
{
|
||||
ssize_t offset = 0;
|
||||
int ret;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
while (offset < len) {
|
||||
ret = mm81x_usb_mem_read(musb, address + offset,
|
||||
(u8 *)(data + offset),
|
||||
min((ssize_t)(len - offset),
|
||||
(ssize_t)USB_MAX_TRANSFER_SIZE));
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset += ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data,
|
||||
int len)
|
||||
{
|
||||
ssize_t offset = 0;
|
||||
int ret;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
while (offset < len) {
|
||||
ret = mm81x_usb_mem_write(musb, address + offset,
|
||||
(u8 *)(data + offset),
|
||||
min((ssize_t)(len - offset),
|
||||
(ssize_t)USB_MAX_TRANSFER_SIZE));
|
||||
if (ret < 0) {
|
||||
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
offset += ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val));
|
||||
if (ret == sizeof(*val)) {
|
||||
*val = le32_to_cpup((__le32 *)val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_err(mors->dev, "usb reg32 read failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
__le32 val_le = cpu_to_le32(val);
|
||||
|
||||
ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le));
|
||||
if (ret == sizeof(val_le))
|
||||
return 0;
|
||||
|
||||
dev_err(mors->dev, "usb reg32 write failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable)
|
||||
{
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
if (enable)
|
||||
usb_autopm_get_interface(musb->interface);
|
||||
else
|
||||
usb_autopm_put_interface(musb->interface);
|
||||
}
|
||||
|
||||
static void mm81x_usb_claim_bus(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
mutex_lock(&musb->bus_lock);
|
||||
}
|
||||
|
||||
static void mm81x_usb_release_bus(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
|
||||
mutex_unlock(&musb->bus_lock);
|
||||
}
|
||||
|
||||
static void mm81x_usb_set_irq(struct mm81x *mors, bool enable)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct mm81x_bus_ops mm81x_usb_ops = {
|
||||
.dm_read = mm81x_usb_dm_read,
|
||||
.dm_write = mm81x_usb_dm_write,
|
||||
.reg32_read = mm81x_usb_reg32_read,
|
||||
.reg32_write = mm81x_usb_reg32_write,
|
||||
.digital_reset = mm81x_usb_ndr_reset,
|
||||
.set_bus_enable = mm81x_usb_bus_enable,
|
||||
.claim = mm81x_usb_claim_bus,
|
||||
.release = mm81x_usb_release_bus,
|
||||
.set_irq = mm81x_usb_set_irq,
|
||||
.bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT,
|
||||
};
|
||||
|
||||
static int mm81x_usb_detect_endpoints(struct mm81x *mors,
|
||||
const struct usb_interface *intf)
|
||||
{
|
||||
int ret;
|
||||
unsigned int i;
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
struct usb_endpoint_descriptor *ep_desc;
|
||||
struct usb_host_interface *intf_desc = intf->cur_altsetting;
|
||||
|
||||
for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
|
||||
ep_desc = &intf_desc->endpoint[i].desc;
|
||||
|
||||
if (usb_endpoint_is_bulk_in(ep_desc)) {
|
||||
if (!musb->endpoints[MM81X_EP_MEM_RD].addr) {
|
||||
musb->endpoints[MM81X_EP_MEM_RD].addr =
|
||||
usb_endpoint_num(ep_desc);
|
||||
musb->endpoints[MM81X_EP_MEM_RD].size =
|
||||
usb_endpoint_maxp(ep_desc);
|
||||
} else if (!musb->endpoints[MM81X_EP_REG_RD].addr) {
|
||||
musb->endpoints[MM81X_EP_REG_RD].addr =
|
||||
usb_endpoint_num(ep_desc);
|
||||
musb->endpoints[MM81X_EP_REG_RD].size =
|
||||
usb_endpoint_maxp(ep_desc);
|
||||
}
|
||||
} else if (usb_endpoint_is_bulk_out(ep_desc)) {
|
||||
if (!musb->endpoints[MM81X_EP_MEM_WR].addr) {
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr =
|
||||
usb_endpoint_num(ep_desc);
|
||||
musb->endpoints[MM81X_EP_MEM_WR].size =
|
||||
usb_endpoint_maxp(ep_desc);
|
||||
} else if (!musb->endpoints[MM81X_EP_REG_WR].addr) {
|
||||
musb->endpoints[MM81X_EP_REG_WR].addr =
|
||||
usb_endpoint_num(ep_desc);
|
||||
musb->endpoints[MM81X_EP_REG_WR].size =
|
||||
usb_endpoint_maxp(ep_desc);
|
||||
}
|
||||
} else if (usb_endpoint_is_int_in(ep_desc)) {
|
||||
musb->endpoints[MM81X_EP_INT].addr =
|
||||
usb_endpoint_num(ep_desc);
|
||||
musb->endpoints[MM81X_EP_INT].size =
|
||||
usb_endpoint_maxp(ep_desc);
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(mors->dev, "\tMemory Endpoint IN %s detected: %u size %u",
|
||||
musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not",
|
||||
musb->endpoints[MM81X_EP_MEM_RD].addr,
|
||||
musb->endpoints[MM81X_EP_MEM_RD].size);
|
||||
dev_dbg(mors->dev, "\tMemory Endpoint OUT %s detected: %u size %u",
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not",
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr,
|
||||
musb->endpoints[MM81X_EP_MEM_WR].size);
|
||||
dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u",
|
||||
musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not",
|
||||
musb->endpoints[MM81X_EP_REG_RD].addr);
|
||||
dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u",
|
||||
musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not",
|
||||
musb->endpoints[MM81X_EP_REG_WR].addr);
|
||||
dev_dbg(mors->dev, "\tStats IN endpoint %s detected: %u",
|
||||
musb->endpoints[MM81X_EP_INT].addr ? "" : "not",
|
||||
musb->endpoints[MM81X_EP_INT].addr);
|
||||
|
||||
/* Verify we have an IN and OUT */
|
||||
if (!(musb->endpoints[MM81X_EP_MEM_RD].addr &&
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr))
|
||||
return -ENODEV;
|
||||
|
||||
/* Verify the stats MM81X_EP_INT is detected */
|
||||
if (!musb->endpoints[MM81X_EP_INT].addr)
|
||||
return -ENODEV;
|
||||
|
||||
/* Verify minimum interrupt status read */
|
||||
if (musb->endpoints[MM81X_EP_INT].size < 8)
|
||||
return -ENODEV;
|
||||
|
||||
musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!musb->endpoints[MM81X_EP_CMD].urb) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!musb->endpoints[MM81X_EP_MEM_RD].urb) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL);
|
||||
if (!musb->endpoints[MM81X_EP_MEM_WR].urb) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_MEM_RD].buffer =
|
||||
kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
|
||||
if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_MEM_WR].buffer =
|
||||
kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
|
||||
if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent(
|
||||
musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL,
|
||||
&musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
|
||||
|
||||
if (!musb->endpoints[MM81X_EP_CMD].buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto err_ep;
|
||||
}
|
||||
|
||||
/* Assign command to memory out end point */
|
||||
musb->endpoints[MM81X_EP_CMD].addr =
|
||||
musb->endpoints[MM81X_EP_MEM_WR].addr;
|
||||
musb->endpoints[MM81X_EP_CMD].size =
|
||||
musb->endpoints[MM81X_EP_MEM_WR].size;
|
||||
|
||||
return 0;
|
||||
|
||||
err_ep:
|
||||
if (musb->endpoints[MM81X_EP_CMD].urb &&
|
||||
musb->endpoints[MM81X_EP_CMD].buffer)
|
||||
usb_free_coherent(
|
||||
musb->udev, sizeof(struct mm81x_usb_command),
|
||||
musb->endpoints[MM81X_EP_CMD].buffer,
|
||||
musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
|
||||
usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
|
||||
usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb);
|
||||
usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
|
||||
kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer);
|
||||
kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_urb_cleanup(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
|
||||
struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
|
||||
struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
|
||||
struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
|
||||
|
||||
usb_kill_urb(rd_ep->urb);
|
||||
usb_kill_urb(wr_ep->urb);
|
||||
usb_kill_urb(cmd_ep->urb);
|
||||
|
||||
if (int_ep->urb)
|
||||
usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
|
||||
int_ep->buffer, int_ep->urb->transfer_dma);
|
||||
|
||||
if (cmd_ep->urb)
|
||||
usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command),
|
||||
cmd_ep->buffer, cmd_ep->urb->transfer_dma);
|
||||
|
||||
kfree(wr_ep->buffer);
|
||||
kfree(rd_ep->buffer);
|
||||
|
||||
usb_free_urb(int_ep->urb);
|
||||
usb_free_urb(wr_ep->urb);
|
||||
usb_free_urb(rd_ep->urb);
|
||||
usb_free_urb(cmd_ep->urb);
|
||||
}
|
||||
|
||||
static int mm81x_usb_probe(struct usb_interface *interface,
|
||||
const struct usb_device_id *id)
|
||||
{
|
||||
int ret;
|
||||
struct mm81x *mors;
|
||||
struct mm81x_usb *musb;
|
||||
|
||||
mors = mm81x_core_alloc(sizeof(*musb), &interface->dev);
|
||||
if (!mors)
|
||||
return -ENOMEM;
|
||||
|
||||
mors->bus_ops = &mm81x_usb_ops;
|
||||
mors->bus_type = MM81X_BUS_TYPE_USB;
|
||||
|
||||
musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
musb->udev = usb_get_dev(interface_to_usbdev(interface));
|
||||
musb->interface = usb_get_intf(interface);
|
||||
|
||||
mutex_init(&musb->lock);
|
||||
mutex_init(&musb->bus_lock);
|
||||
init_waitqueue_head(&musb->rw_in_wait);
|
||||
usb_set_intfdata(interface, mors);
|
||||
|
||||
ret = mm81x_usb_detect_endpoints(mors, interface);
|
||||
if (ret < 0)
|
||||
goto err_core_free;
|
||||
|
||||
set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
|
||||
|
||||
ret = mm81x_core_init(mors);
|
||||
if (ret)
|
||||
goto err_urb_cleanup;
|
||||
|
||||
INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work);
|
||||
|
||||
ret = mm81x_usb_int_enable(mors);
|
||||
if (ret)
|
||||
goto err_core_deinit;
|
||||
|
||||
ret = mm81x_core_register(mors);
|
||||
if (ret)
|
||||
goto err_usb_int_stop;
|
||||
|
||||
/* USB requires remote wakeup functionality for suspend */
|
||||
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
|
||||
musb->interface->needs_remote_wakeup = 1;
|
||||
usb_enable_autosuspend(musb->udev);
|
||||
pm_runtime_set_autosuspend_delay(&musb->udev->dev,
|
||||
PM_RUNTIME_AUTOSUSPEND_DELAY_MS);
|
||||
|
||||
usb_autopm_get_interface(interface);
|
||||
return 0;
|
||||
|
||||
err_usb_int_stop:
|
||||
mm81x_usb_int_stop(mors);
|
||||
err_core_deinit:
|
||||
mm81x_core_deinit(mors);
|
||||
err_urb_cleanup:
|
||||
mm81x_urb_cleanup(mors);
|
||||
err_core_free:
|
||||
mm81x_core_free(mors);
|
||||
usb_put_intf(interface);
|
||||
usb_put_dev(interface_to_usbdev(interface));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mm81x_usb_disconnect(struct usb_interface *interface)
|
||||
{
|
||||
struct mm81x *mors = usb_get_intfdata(interface);
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
int minor = interface->minor;
|
||||
struct usb_device *udev = interface_to_usbdev(interface);
|
||||
|
||||
if (udev->state == USB_STATE_NOTATTACHED) {
|
||||
clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
|
||||
set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
|
||||
dev_dbg(mors->dev, "USB suddenly unplugged");
|
||||
}
|
||||
|
||||
usb_disable_autosuspend(udev);
|
||||
|
||||
if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) {
|
||||
dev_dbg(mors->dev, "USB was suspended: release locks");
|
||||
mm81x_usb_release_bus(mors);
|
||||
mutex_unlock(&musb->lock);
|
||||
}
|
||||
|
||||
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
|
||||
|
||||
mm81x_core_unregister(mors);
|
||||
mm81x_usb_int_stop(mors);
|
||||
mm81x_core_deinit(mors);
|
||||
mm81x_urb_cleanup(mors);
|
||||
mm81x_core_free(mors);
|
||||
|
||||
usb_autopm_put_interface(interface);
|
||||
usb_set_intfdata(interface, NULL);
|
||||
dev_info(&interface->dev, "USB Morse #%d now disconnected", minor);
|
||||
usb_put_intf(interface);
|
||||
usb_put_dev(udev);
|
||||
}
|
||||
|
||||
static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)
|
||||
{
|
||||
struct mm81x *mors = usb_get_intfdata(intf);
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
|
||||
struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
|
||||
struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
|
||||
struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
usb_kill_urb(int_ep->urb);
|
||||
usb_kill_urb(rd_ep->urb);
|
||||
usb_kill_urb(wr_ep->urb);
|
||||
usb_kill_urb(cmd_ep->urb);
|
||||
|
||||
/* Locking the bus. No USB communication after this point */
|
||||
mm81x_usb_claim_bus(mors);
|
||||
mutex_lock(&musb->lock);
|
||||
|
||||
set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_resume(struct usb_interface *intf)
|
||||
{
|
||||
struct mm81x *mors = usb_get_intfdata(intf);
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
int ret;
|
||||
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
|
||||
|
||||
mm81x_usb_release_bus(mors);
|
||||
mutex_unlock(&musb->lock);
|
||||
|
||||
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_reset_resume(struct usb_interface *intf)
|
||||
{
|
||||
struct mm81x *mors = usb_get_intfdata(intf);
|
||||
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
|
||||
int ret;
|
||||
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
|
||||
|
||||
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
|
||||
return -ENODEV;
|
||||
|
||||
ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
|
||||
if (ret)
|
||||
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
|
||||
|
||||
mm81x_usb_release_bus(mors);
|
||||
mutex_unlock(&musb->lock);
|
||||
|
||||
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_pre_reset(struct usb_interface *intf)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_usb_post_reset(struct usb_interface *intf)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct usb_driver mm81x_usb_driver = {
|
||||
.name = "mm81x_usb",
|
||||
.probe = mm81x_usb_probe,
|
||||
.disconnect = mm81x_usb_disconnect,
|
||||
.suspend = mm81x_usb_suspend,
|
||||
.resume = mm81x_usb_resume,
|
||||
.reset_resume = mm81x_usb_reset_resume,
|
||||
.pre_reset = mm81x_usb_pre_reset,
|
||||
.post_reset = mm81x_usb_post_reset,
|
||||
.id_table = mm81x_usb_table,
|
||||
.supports_autosuspend = 1,
|
||||
.soft_unbind = 1,
|
||||
};
|
||||
|
||||
module_usb_driver(mm81x_usb_driver);
|
||||
|
||||
MODULE_AUTHOR("Morse Micro");
|
||||
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices");
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
704
drivers/net/wireless/morsemicro/mm81x/yaps.c
Normal file
704
drivers/net/wireless/morsemicro/mm81x/yaps.c
Normal file
@@ -0,0 +1,704 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/slab.h>
|
||||
#include "hif.h"
|
||||
#include "ps.h"
|
||||
#include "bus.h"
|
||||
#include "command.h"
|
||||
#include "skbq.h"
|
||||
|
||||
/* This is a fail safe timeout */
|
||||
#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30
|
||||
|
||||
/* Defined as the max number of MPDUs per AMPDU */
|
||||
#define MAX_PKTS_PER_TX_TXN 16
|
||||
#define MAX_PKTS_PER_RX_TXN 32
|
||||
|
||||
static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps)
|
||||
{
|
||||
yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN,
|
||||
sizeof(*yaps->hw.to_chip_pkts),
|
||||
GFP_KERNEL);
|
||||
if (!yaps->hw.to_chip_pkts)
|
||||
return -ENOMEM;
|
||||
|
||||
yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN,
|
||||
sizeof(*yaps->hw.from_chip_pkts),
|
||||
GFP_KERNEL);
|
||||
if (!yaps->hw.from_chip_pkts) {
|
||||
kfree(yaps->hw.to_chip_pkts);
|
||||
yaps->hw.to_chip_pkts = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps)
|
||||
{
|
||||
kfree(yaps->hw.from_chip_pkts);
|
||||
yaps->hw.from_chip_pkts = NULL;
|
||||
kfree(yaps->hw.to_chip_pkts);
|
||||
yaps->hw.to_chip_pkts = NULL;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkts, int num_pkts,
|
||||
int *num_pkts_sent)
|
||||
{
|
||||
return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent);
|
||||
}
|
||||
|
||||
static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkts, int num_pkts_max,
|
||||
int *num_pkts_received)
|
||||
{
|
||||
return yaps->ops->read_pkts(yaps, pkts, num_pkts_max,
|
||||
num_pkts_received);
|
||||
}
|
||||
|
||||
static int mm81x_yaps_update_status(struct mm81x_yaps *yaps)
|
||||
{
|
||||
return yaps->ops->update_status(yaps);
|
||||
}
|
||||
|
||||
/* Mappings between sk_buff, skbq and yaps */
|
||||
static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci)
|
||||
{
|
||||
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
||||
|
||||
if (aci >= ARRAY_SIZE(yaps->data_tx_qs))
|
||||
return NULL;
|
||||
return &yaps->data_tx_qs[aci];
|
||||
}
|
||||
|
||||
static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs,
|
||||
int *num_qs)
|
||||
{
|
||||
*qs = mors->hif.u.yaps.data_tx_qs;
|
||||
*num_qs = YAPS_TX_SKBQ_MAX;
|
||||
}
|
||||
|
||||
static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors)
|
||||
{
|
||||
return &mors->hif.u.yaps.beacon_q;
|
||||
}
|
||||
|
||||
static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors)
|
||||
{
|
||||
return &mors->hif.u.yaps.mgmt_q;
|
||||
}
|
||||
|
||||
static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors)
|
||||
{
|
||||
return &mors->hif.u.yaps.cmd_q;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status)
|
||||
{
|
||||
if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN))
|
||||
set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags);
|
||||
|
||||
if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) {
|
||||
timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer);
|
||||
set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
|
||||
&mors->hif.event_flags);
|
||||
}
|
||||
|
||||
queue_work(mors->chip_wq, &mors->hif_work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct mm81x_hif_ops mm81x_yaps_ops = {
|
||||
.init = mm81x_yaps_init,
|
||||
.flush_tx_data = mm81x_yaps_flush_tx_data,
|
||||
.flush_cmds = mm81x_yaps_flush_cmds,
|
||||
.get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count,
|
||||
.get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count,
|
||||
.finish = mm81x_yaps_finish,
|
||||
.skbq_get_tx_qs = mm81x_yaps_get_tx_qs,
|
||||
.get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q,
|
||||
.get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q,
|
||||
.get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue,
|
||||
.get_tx_data_queue = mm81x_yaps_tc_q_from_aci,
|
||||
.handle_irq = mm81x_yaps_irq_handler
|
||||
};
|
||||
|
||||
static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb)
|
||||
{
|
||||
struct mm81x *mors = yaps->mors;
|
||||
struct sk_buff_head skbq;
|
||||
struct mm81x_skbq *mq = NULL;
|
||||
struct mm81x_skb_hdr *hdr;
|
||||
int skb_bytes_remaining;
|
||||
int skb_len;
|
||||
int ret = 0;
|
||||
|
||||
if (!skb) {
|
||||
ret = -EINVAL;
|
||||
goto exit_return_page;
|
||||
}
|
||||
|
||||
__skb_queue_head_init(&skbq);
|
||||
|
||||
hdr = (struct mm81x_skb_hdr *)skb->data;
|
||||
if (hdr->sync != MM81X_SKB_HEADER_SYNC) {
|
||||
dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d",
|
||||
hdr->sync, hdr->len);
|
||||
ret = -EIO;
|
||||
goto exit_return_page;
|
||||
}
|
||||
|
||||
if (yaps->mors->hif.validate_skb_checksum &&
|
||||
!mm81x_skbq_validate_checksum(skb->data)) {
|
||||
dev_dbg(yaps->mors->dev,
|
||||
"SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]",
|
||||
hdr->channel, hdr->sync, hdr->len);
|
||||
|
||||
if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) {
|
||||
ret = -EIO;
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
switch (hdr->channel) {
|
||||
case MM81X_SKB_CHAN_DATA:
|
||||
case MM81X_SKB_CHAN_NDP_FRAMES:
|
||||
case MM81X_SKB_CHAN_TX_STATUS:
|
||||
case MM81X_SKB_CHAN_DATA_NOACK:
|
||||
case MM81X_SKB_CHAN_BEACON:
|
||||
case MM81X_SKB_CHAN_MGMT:
|
||||
mq = &yaps->data_rx_q;
|
||||
break;
|
||||
case MM81X_SKB_CHAN_COMMAND:
|
||||
mq = &yaps->cmd_resp_q;
|
||||
break;
|
||||
default:
|
||||
dev_err(mors->dev, "channel value error [%d]", hdr->channel);
|
||||
ret = -EIO;
|
||||
goto exit_return_page;
|
||||
}
|
||||
|
||||
skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len);
|
||||
skb_bytes_remaining = mm81x_skbq_space(mq);
|
||||
|
||||
if (skb_len > skb_bytes_remaining) {
|
||||
dev_err(mors->dev,
|
||||
"Page will not fit in SKBQ, dropping - len %d remain %d",
|
||||
skb_len, skb_bytes_remaining);
|
||||
ret = -ENOMEM;
|
||||
/* Queue work to clear backlog */
|
||||
queue_work(mors->net_wq, &mq->dispatch_work);
|
||||
goto exit_return_page;
|
||||
}
|
||||
|
||||
skb_trim(skb, skb_len);
|
||||
__skb_queue_tail(&skbq, skb);
|
||||
|
||||
if (skb_queue_len(&skbq))
|
||||
mm81x_skbq_enq(mq, &skbq);
|
||||
|
||||
/* push packets up in a different context */
|
||||
queue_work(mors->net_wq, &mq->dispatch_work);
|
||||
|
||||
goto exit;
|
||||
|
||||
exit_return_page:
|
||||
if (ret && mq) {
|
||||
dev_err(mors->dev, "failed %d", ret);
|
||||
mm81x_skbq_purge(mq, &skbq);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (ret && skb)
|
||||
dev_kfree_skb(skb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
int num_skbs = 0;
|
||||
int tc_pkt_idx = 0;
|
||||
int num_pkts_sent = 0;
|
||||
struct sk_buff *skb;
|
||||
struct sk_buff_head skbq_to_send;
|
||||
struct sk_buff_head skbq_sent;
|
||||
struct sk_buff_head skbq_failed;
|
||||
struct sk_buff *pfirst, *pnext;
|
||||
struct mm81x *mors = yaps->mors;
|
||||
struct mm81x_skb_hdr *hdr;
|
||||
|
||||
/* Check there is something on the queue */
|
||||
spin_lock_bh(&mq->lock);
|
||||
skb = skb_peek(&mq->skbq);
|
||||
spin_unlock_bh(&mq->lock);
|
||||
if (!skb)
|
||||
return 0;
|
||||
|
||||
__skb_queue_head_init(&skbq_to_send);
|
||||
__skb_queue_head_init(&skbq_sent);
|
||||
__skb_queue_head_init(&skbq_failed);
|
||||
|
||||
if (mq == &yaps->cmd_q)
|
||||
/* Purge timed-out commands (this should not happen) */
|
||||
mm81x_skbq_purge(mq, &mq->pending);
|
||||
else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0)
|
||||
/*
|
||||
* Purge old mgmt frames that have not been sent due to
|
||||
* congestion
|
||||
*/
|
||||
mm81x_skbq_purge_aged(mors, mq);
|
||||
|
||||
num_skbs =
|
||||
mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN);
|
||||
|
||||
skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) {
|
||||
enum mm81x_yaps_to_chip_q tc_queue;
|
||||
|
||||
hdr = (struct mm81x_skb_hdr *)pfirst->data;
|
||||
switch (hdr->channel) {
|
||||
case MM81X_SKB_CHAN_COMMAND:
|
||||
tc_queue = MM81X_YAPS_CMD_Q;
|
||||
break;
|
||||
case MM81X_SKB_CHAN_BEACON:
|
||||
tc_queue = MM81X_YAPS_BEACON_Q;
|
||||
break;
|
||||
case MM81X_SKB_CHAN_MGMT:
|
||||
tc_queue = MM81X_YAPS_MGMT_Q;
|
||||
break;
|
||||
default:
|
||||
tc_queue = MM81X_YAPS_TX_Q;
|
||||
break;
|
||||
}
|
||||
yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue;
|
||||
yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst;
|
||||
tc_pkt_idx++;
|
||||
}
|
||||
|
||||
/* Send queued packets to chip */
|
||||
ret = mm81x_yaps_update_status(yaps);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx,
|
||||
&num_pkts_sent);
|
||||
|
||||
/* Move sent packets to done queue */
|
||||
for (i = 0; i < num_pkts_sent; ++i) {
|
||||
pfirst = __skb_dequeue(&skbq_to_send);
|
||||
__skb_queue_tail(&skbq_sent, pfirst);
|
||||
}
|
||||
|
||||
for (i = num_pkts_sent; i < num_skbs; ++i) {
|
||||
pfirst = __skb_dequeue(&skbq_to_send);
|
||||
__skb_queue_tail(&skbq_failed, pfirst);
|
||||
}
|
||||
|
||||
if (skb_queue_len(&skbq_failed) > 0) {
|
||||
mm81x_skbq_enq_prepend(mq, &skbq_failed);
|
||||
|
||||
/* queue full, can't requeue */
|
||||
if (skb_queue_len(&skbq_failed) > 0) {
|
||||
dev_warn(mors->dev,
|
||||
"can't requeue failed pkts, purging");
|
||||
__skb_queue_purge(&skbq_failed);
|
||||
}
|
||||
}
|
||||
|
||||
if (skb_queue_len(&skbq_sent) > 0)
|
||||
mm81x_skbq_tx_complete(mq, &skbq_sent);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Returns true if there are TX data pages waiting to be sent */
|
||||
static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps)
|
||||
{
|
||||
s16 aci;
|
||||
u32 count = 0;
|
||||
struct mm81x *mors = yaps->mors;
|
||||
|
||||
for (aci = MM81X_ACI_VO; aci >= 0; aci--) {
|
||||
struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci);
|
||||
|
||||
if (!mm81x_is_data_tx_allowed(mors))
|
||||
break;
|
||||
|
||||
yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q);
|
||||
count += mm81x_skbq_count(data_q);
|
||||
|
||||
if (yaps->chip_queue_full.is_full)
|
||||
break;
|
||||
|
||||
if (aci == MM81X_ACI_BE)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Data has potentially been transmitted from the data SKBQs.
|
||||
* If the mac80211 TX data Qs were previously stopped, now would
|
||||
* be a good time to check if they can be started again.
|
||||
*/
|
||||
mm81x_skbq_may_wake_tx_queues(mors);
|
||||
|
||||
return (count > 0) && mm81x_is_data_tx_allowed(mors);
|
||||
}
|
||||
|
||||
/* Returns true if there are commands waiting to be sent */
|
||||
static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps)
|
||||
{
|
||||
struct mm81x_skbq *cmd_q = &yaps->cmd_q;
|
||||
|
||||
mm81x_yaps_tx(yaps, cmd_q);
|
||||
|
||||
return mm81x_skbq_count(cmd_q) > 0;
|
||||
}
|
||||
|
||||
static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps)
|
||||
{
|
||||
struct mm81x_skbq *beacon_q = &yaps->beacon_q;
|
||||
|
||||
mm81x_yaps_tx(yaps, beacon_q);
|
||||
|
||||
return mm81x_skbq_count(beacon_q) > 0;
|
||||
}
|
||||
|
||||
static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps)
|
||||
{
|
||||
struct mm81x_skbq *mgmt_q = &yaps->mgmt_q;
|
||||
|
||||
mm81x_yaps_tx(yaps, mgmt_q);
|
||||
|
||||
return mm81x_skbq_count(mgmt_q) > 0;
|
||||
}
|
||||
|
||||
/* Returns true if there are populated RX pages left in the device */
|
||||
static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
int num_pks_received;
|
||||
|
||||
ret = mm81x_yaps_update_status(yaps);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts,
|
||||
MAX_PKTS_PER_RX_TXN, &num_pks_received);
|
||||
if (ret && ret != -EAGAIN) {
|
||||
dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
for (i = 0; i < num_pks_received; ++i) {
|
||||
mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb);
|
||||
yaps->hw.from_chip_pkts[i].skb = NULL;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (ret == -ENOMEM || ret == -EAGAIN)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void mm81x_yaps_stale_tx_work(struct work_struct *work)
|
||||
{
|
||||
int i;
|
||||
int flushed = 0;
|
||||
struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work);
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q);
|
||||
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
||||
flushed += mm81x_skbq_check_for_stale_tx(mors,
|
||||
&yaps->data_tx_qs[i]);
|
||||
|
||||
if (!flushed)
|
||||
return;
|
||||
|
||||
dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed);
|
||||
|
||||
if (mors->ps.enable && !mors->ps.suspended &&
|
||||
(mm81x_yaps_get_tx_buffered_count(mors) == 0)) {
|
||||
/* Evaluate ps to check if it was gated on a stale tx status */
|
||||
queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void mm81x_yaps_work(struct work_struct *work)
|
||||
{
|
||||
struct mm81x *mors = container_of(work, struct mm81x, hif_work);
|
||||
unsigned long *flags = &mors->hif.event_flags;
|
||||
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
||||
|
||||
if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags))
|
||||
return;
|
||||
|
||||
if (!*flags)
|
||||
return;
|
||||
|
||||
/* Disable power save in case it is running */
|
||||
mm81x_ps_disable(mors);
|
||||
mm81x_claim_bus(mors);
|
||||
|
||||
/*
|
||||
* Handle any populated RX pages from chip first to
|
||||
* avoid dropping pkts due to full on-chip buffers.
|
||||
* Check if all pages were removed, set event flags if not.
|
||||
*/
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) {
|
||||
if (mm81x_yaps_rx_handler(yaps))
|
||||
set_bit(MM81X_HIF_EVT_RX_PEND, flags);
|
||||
}
|
||||
|
||||
/* TX any commands before considering data */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) {
|
||||
if (mm81x_yaps_tx_cmd_handler(yaps))
|
||||
set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags);
|
||||
}
|
||||
|
||||
/* TX beacons before considering mgmt/data */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) {
|
||||
if (mm81x_yaps_tx_beacon_handler(yaps))
|
||||
set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags);
|
||||
}
|
||||
|
||||
/* TX mgmt before considering data */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) {
|
||||
if (mm81x_yaps_tx_mgmt_handler(yaps))
|
||||
set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags);
|
||||
}
|
||||
|
||||
/* Pause TX data Qs */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) {
|
||||
test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
|
||||
flags);
|
||||
mm81x_skbq_data_traffic_pause(mors);
|
||||
}
|
||||
|
||||
/* Resume TX data Qs */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags))
|
||||
mm81x_skbq_data_traffic_resume(mors);
|
||||
|
||||
/* Handle chip queue status */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags))
|
||||
yaps->chip_queue_full.is_full = false;
|
||||
|
||||
/* Check to see if the queue is full or
|
||||
* long enough has past since the queue was full
|
||||
*/
|
||||
if (yaps->chip_queue_full.is_full &&
|
||||
time_before(jiffies, yaps->chip_queue_full.retry_expiry))
|
||||
goto exit;
|
||||
|
||||
/* Finally TX any data */
|
||||
if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) {
|
||||
if (mm81x_yaps_tx_data_handler(yaps))
|
||||
set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags);
|
||||
|
||||
if (yaps->chip_queue_full.is_full) {
|
||||
yaps->chip_queue_full.retry_expiry =
|
||||
jiffies +
|
||||
msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS);
|
||||
mod_timer(&yaps->chip_queue_full.timer,
|
||||
yaps->chip_queue_full.retry_expiry);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
|
||||
/* Disable power save in case it is running */
|
||||
mm81x_release_bus(mors);
|
||||
mm81x_ps_enable(mors);
|
||||
|
||||
/* Don't requeue work if we are shutting down. */
|
||||
if (yaps->finish)
|
||||
return;
|
||||
/*
|
||||
* Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data
|
||||
* tx queue is full
|
||||
*/
|
||||
if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND))
|
||||
queue_work(mors->chip_wq, &mors->hif_work);
|
||||
/*
|
||||
* if data tx queue is not full and the work hasn't been queued let's
|
||||
* queue it
|
||||
*/
|
||||
else if (!yaps->chip_queue_full.is_full && *flags)
|
||||
queue_work(mors->chip_wq, &mors->hif_work);
|
||||
}
|
||||
|
||||
int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors)
|
||||
{
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
count += skb_queue_len(&yaps->beacon_q.pending);
|
||||
count += skb_queue_len(&yaps->mgmt_q.pending);
|
||||
count += skb_queue_len(&yaps->cmd_q.pending);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
||||
count += skb_queue_len(&yaps->data_tx_qs[i].pending);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors)
|
||||
{
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
count += skb_queue_len(&yaps->beacon_q.skbq) +
|
||||
skb_queue_len(&yaps->beacon_q.pending);
|
||||
count += skb_queue_len(&yaps->mgmt_q.skbq) +
|
||||
skb_queue_len(&yaps->mgmt_q.pending);
|
||||
count += skb_queue_len(&yaps->cmd_q.skbq) +
|
||||
skb_queue_len(&yaps->cmd_q.pending);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
||||
count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) +
|
||||
skb_queue_len(&yaps->data_tx_qs[i].pending);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void mm81x_yaps_tx_q_full_timer(struct timer_list *t)
|
||||
{
|
||||
struct mm81x_yaps *yaps =
|
||||
timer_container_of(yaps, t, chip_queue_full.timer);
|
||||
|
||||
queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work);
|
||||
}
|
||||
|
||||
static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps)
|
||||
{
|
||||
timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer,
|
||||
0);
|
||||
}
|
||||
|
||||
static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps)
|
||||
{
|
||||
timer_delete_sync_try(&yaps->chip_queue_full.timer);
|
||||
}
|
||||
|
||||
int mm81x_yaps_init(struct mm81x *mors)
|
||||
{
|
||||
int i, ret;
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
ret = mm81x_yaps_hw_init(mors);
|
||||
if (ret) {
|
||||
dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
yaps->mors = mors;
|
||||
|
||||
mm81x_claim_bus(mors);
|
||||
|
||||
ret = mm81x_yaps_alloc_pkt_buffers(yaps);
|
||||
if (ret) {
|
||||
dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d",
|
||||
ret);
|
||||
mm81x_yaps_hw_finish(mors);
|
||||
mm81x_release_bus(mors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* YAPS is bi-directional */
|
||||
mm81x_skbq_init(mors, &yaps->data_rx_q,
|
||||
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
||||
mm81x_skbq_init(mors, &yaps->beacon_q,
|
||||
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
||||
mm81x_skbq_init(mors, &yaps->mgmt_q,
|
||||
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) {
|
||||
mm81x_skbq_init(mors, &yaps->data_tx_qs[i],
|
||||
MM81X_HIF_FLAGS_DATA |
|
||||
MM81X_HIF_FLAGS_DIR_TO_CHIP);
|
||||
}
|
||||
|
||||
mm81x_skbq_init(mors, &yaps->cmd_q,
|
||||
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP);
|
||||
mm81x_skbq_init(mors, &yaps->cmd_resp_q,
|
||||
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST);
|
||||
|
||||
mm81x_yaps_q_chip_full_timer_init(yaps);
|
||||
INIT_WORK(&mors->hif_work, mm81x_yaps_work);
|
||||
INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work);
|
||||
mm81x_release_bus(mors);
|
||||
mm81x_hw_enable_stop_notifications(mors, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mm81x_yaps_finish(struct mm81x *mors)
|
||||
{
|
||||
int i;
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
mm81x_yaps_hw_enable_irqs(mors, false);
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
yaps->finish = true;
|
||||
|
||||
mm81x_skbq_finish(&yaps->data_rx_q);
|
||||
mm81x_skbq_finish(&yaps->beacon_q);
|
||||
mm81x_skbq_finish(&yaps->mgmt_q);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
||||
mm81x_skbq_finish(&yaps->data_tx_qs[i]);
|
||||
|
||||
mm81x_skbq_finish(&yaps->cmd_q);
|
||||
mm81x_skbq_finish(&yaps->cmd_resp_q);
|
||||
|
||||
mm81x_yaps_q_chip_full_timer_finish(yaps);
|
||||
|
||||
cancel_work_sync(&mors->hif_work);
|
||||
cancel_work_sync(&mors->tx_stale_work);
|
||||
|
||||
mm81x_yaps_free_pkt_buffers(yaps);
|
||||
mm81x_yaps_hw_finish(mors);
|
||||
}
|
||||
|
||||
void mm81x_yaps_flush_tx_data(struct mm81x *mors)
|
||||
{
|
||||
int i;
|
||||
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
||||
|
||||
mm81x_skbq_tx_flush(&yaps->beacon_q);
|
||||
mm81x_skbq_tx_flush(&yaps->mgmt_q);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
|
||||
mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]);
|
||||
}
|
||||
|
||||
void mm81x_yaps_flush_cmds(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
|
||||
|
||||
if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) {
|
||||
mm81x_skbq_finish(&yaps->cmd_q);
|
||||
mm81x_skbq_finish(&yaps->cmd_resp_q);
|
||||
}
|
||||
}
|
||||
77
drivers/net/wireless/morsemicro/mm81x/yaps.h
Normal file
77
drivers/net/wireless/morsemicro/mm81x/yaps.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_YAPS_H_
|
||||
#define _MM81X_YAPS_H_
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include "skbq.h"
|
||||
|
||||
#define YAPS_TX_SKBQ_MAX 4
|
||||
|
||||
struct mm81x_hif_ops;
|
||||
extern const struct mm81x_hif_ops mm81x_yaps_ops;
|
||||
|
||||
enum mm81x_yaps_to_chip_q {
|
||||
MM81X_YAPS_TX_Q = 0,
|
||||
MM81X_YAPS_CMD_Q,
|
||||
MM81X_YAPS_BEACON_Q,
|
||||
MM81X_YAPS_MGMT_Q,
|
||||
/* Keep this last */
|
||||
MM81X_YAPS_NUM_TC_Q
|
||||
};
|
||||
|
||||
struct mm81x_yaps_pkt {
|
||||
struct sk_buff *skb;
|
||||
enum mm81x_yaps_to_chip_q tc_queue;
|
||||
};
|
||||
|
||||
struct mm81x_yaps {
|
||||
struct mm81x *mors;
|
||||
struct mm81x_yaps_hw_aux_data *aux_data;
|
||||
const struct mm81x_yaps_ops *ops;
|
||||
u8 flags;
|
||||
struct {
|
||||
struct mm81x_yaps_pkt *to_chip_pkts;
|
||||
struct mm81x_yaps_pkt *from_chip_pkts;
|
||||
} hw;
|
||||
|
||||
/* Chip interface is stopping, new work should not be enqueued. */
|
||||
bool finish;
|
||||
|
||||
struct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX];
|
||||
struct mm81x_skbq beacon_q;
|
||||
struct mm81x_skbq mgmt_q;
|
||||
struct mm81x_skbq data_rx_q;
|
||||
struct mm81x_skbq cmd_q;
|
||||
struct mm81x_skbq cmd_resp_q;
|
||||
|
||||
struct {
|
||||
struct timer_list timer;
|
||||
unsigned long retry_expiry;
|
||||
bool is_full;
|
||||
} chip_queue_full;
|
||||
};
|
||||
|
||||
struct mm81x_yaps_ops {
|
||||
int (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
|
||||
int num_pkts, int *num_pkts_sent);
|
||||
int (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
|
||||
int num_pkts_max, int *num_pkts_received);
|
||||
int (*update_status)(struct mm81x_yaps *yaps);
|
||||
};
|
||||
|
||||
int mm81x_yaps_init(struct mm81x *mors);
|
||||
void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file);
|
||||
void mm81x_yaps_finish(struct mm81x *mors);
|
||||
void mm81x_yaps_flush_tx_data(struct mm81x *mors);
|
||||
void mm81x_yaps_flush_cmds(struct mm81x *mors);
|
||||
void mm81x_yaps_work(struct work_struct *work);
|
||||
void mm81x_yaps_stale_tx_work(struct work_struct *work);
|
||||
int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors);
|
||||
int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors);
|
||||
|
||||
#endif /* !_MM81X_YAPS_H_ */
|
||||
702
drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
Normal file
702
drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
Normal file
@@ -0,0 +1,702 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
#include "yaps_hw.h"
|
||||
#include "bus.h"
|
||||
#include "hif.h"
|
||||
#include "yaps.h"
|
||||
|
||||
#define YAPS_HW_WINDOW_SIZE_BYTES 32768
|
||||
#define YAPS_MAX_PKT_SIZE_BYTES 16128
|
||||
#define YAPS_METADATA_PAGE_COUNT 1
|
||||
|
||||
#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1
|
||||
|
||||
#define YAPS_PAGE_SIZE 256
|
||||
|
||||
/* Calculate padding required for yaps transaction */
|
||||
#define YAPS_CALC_PADDING(_bytes) ((_bytes) & 0x3 ? (4 - ((_bytes) & 0x3)) : 0)
|
||||
|
||||
#define YAPS_RESERVED_PAGE_SIZE 256
|
||||
|
||||
/*
|
||||
* Yaps data stream delimiter is a 32 bit word with the following fields:
|
||||
*
|
||||
* pkt_size (14 bits) - Packet size not including delimiter or padding
|
||||
* pool_id (3 bits) - Pool that pages should be allocated from.
|
||||
* padding (2 bits) - Padding required to bring packet to word (4 byte)
|
||||
* irq (1 bit ) - Raise a PKT_IRQ on the YDS this is sent to
|
||||
* reserved (5 bits) - Reserved, must write as 0
|
||||
* crc (7 bits) - YAPS CRC
|
||||
*/
|
||||
|
||||
/* Packet size not including delimiter or padding */
|
||||
#define YAPS_DELIM_GET_PKT_SIZE(_delim) \
|
||||
(((_delim) & 0x3FFF) - YAPS_RESERVED_PAGE_SIZE)
|
||||
#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \
|
||||
(((_pkt_size) & 0x3FFF) + YAPS_RESERVED_PAGE_SIZE)
|
||||
#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) & 0x3FFF))
|
||||
|
||||
/* Pool that pages should be allocated from. */
|
||||
#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) & 0x7) << 14)
|
||||
|
||||
/* Padding required to bring packet to word (4 byte) boundary */
|
||||
#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) >> 17) & 0x3)
|
||||
#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) & 0x3) << 17)
|
||||
|
||||
/* Raise a PKT_IRQ on the YDS this is sent to */
|
||||
#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) & 0x1) << 19)
|
||||
|
||||
/* YAPS CRC */
|
||||
#define YAPS_DELIM_GET_CRC(_delim) (((_delim) >> 25) & 0x7F)
|
||||
#define YAPS_DELIM_SET_CRC(_crc) (((_crc) & 0x7F) << 25)
|
||||
|
||||
struct mm81x_yaps_status_regs {
|
||||
/* Allocation pools */
|
||||
u32 tc_tx_pool_num_pages;
|
||||
u32 tc_cmd_pool_num_pages;
|
||||
u32 tc_beacon_pool_num_pages;
|
||||
u32 tc_mgmt_pool_num_pages;
|
||||
u32 fc_rx_pool_num_pages;
|
||||
u32 fc_resp_pool_num_pages;
|
||||
u32 fc_tx_sts_pool_num_pages;
|
||||
u32 fc_aux_pool_num_pages;
|
||||
u32 tc_tx_num_pkts;
|
||||
u32 tc_cmd_num_pkts;
|
||||
u32 tc_beacon_num_pkts;
|
||||
u32 tc_mgmt_num_pkts;
|
||||
u32 fc_num_pkts;
|
||||
u32 fc_done_num_pkts;
|
||||
u32 fc_rx_bytes_in_queue;
|
||||
u32 tc_delim_crc_fail_detected;
|
||||
u32 fc_host_ysl_status;
|
||||
u32 lock;
|
||||
} __packed __aligned(8);
|
||||
|
||||
struct mm81x_yaps_hw_status_regs {
|
||||
__le32 tc_tx_pool_num_pages;
|
||||
__le32 tc_cmd_pool_num_pages;
|
||||
__le32 tc_beacon_pool_num_pages;
|
||||
__le32 tc_mgmt_pool_num_pages;
|
||||
__le32 fc_rx_pool_num_pages;
|
||||
__le32 fc_resp_pool_num_pages;
|
||||
__le32 fc_tx_sts_pool_num_pages;
|
||||
__le32 fc_aux_pool_num_pages;
|
||||
__le32 tc_tx_num_pkts;
|
||||
__le32 tc_cmd_num_pkts;
|
||||
__le32 tc_beacon_num_pkts;
|
||||
__le32 tc_mgmt_num_pkts;
|
||||
__le32 fc_num_pkts;
|
||||
__le32 fc_done_num_pkts;
|
||||
__le32 fc_rx_bytes_in_queue;
|
||||
__le32 tc_delim_crc_fail_detected;
|
||||
__le32 fc_host_ysl_status;
|
||||
__le32 lock;
|
||||
} __packed __aligned(8);
|
||||
|
||||
struct mm81x_yaps_hw_aux_data {
|
||||
unsigned long access_lock;
|
||||
|
||||
u32 yds_addr;
|
||||
u32 ysl_addr;
|
||||
u32 status_regs_addr;
|
||||
|
||||
/* Alloc pool sizes */
|
||||
u16 tc_tx_pool_size;
|
||||
u16 tc_cmd_pool_size;
|
||||
u8 tc_beacon_pool_size;
|
||||
u8 tc_mgmt_pool_size;
|
||||
u8 fc_rx_pool_size;
|
||||
u8 fc_resp_pool_size;
|
||||
u8 fc_tx_sts_pool_size;
|
||||
u8 fc_aux_pool_size;
|
||||
|
||||
/* To chip/from chip queue sizes */
|
||||
u8 tc_tx_q_size;
|
||||
u8 tc_cmd_q_size;
|
||||
u8 tc_beacon_q_size;
|
||||
u8 tc_mgmt_q_size;
|
||||
u8 fc_q_size;
|
||||
u8 fc_done_q_size;
|
||||
|
||||
u16 reserved_yaps_page_size;
|
||||
|
||||
/* Buffers to/from chip to support large contiguous reads/writes */
|
||||
char *to_chip_buffer;
|
||||
char *from_chip_buffer;
|
||||
|
||||
/* status registers in host endian */
|
||||
struct mm81x_yaps_status_regs status_regs;
|
||||
|
||||
/* DMA target buffer in firmware endian */
|
||||
struct mm81x_yaps_hw_status_regs hw_status_regs;
|
||||
};
|
||||
|
||||
static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps)
|
||||
{
|
||||
if (test_and_set_bit_lock(0, &yaps->aux_data->access_lock))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps)
|
||||
{
|
||||
clear_bit_unlock(0, &yaps->aux_data->access_lock);
|
||||
}
|
||||
|
||||
static void
|
||||
mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a,
|
||||
struct mm81x_yaps_hw_table *t)
|
||||
{
|
||||
a->ysl_addr = __le32_to_cpu(t->ysl_addr);
|
||||
a->yds_addr = __le32_to_cpu(t->yds_addr);
|
||||
a->status_regs_addr = __le32_to_cpu(t->status_regs_addr);
|
||||
a->tc_tx_pool_size = __le16_to_cpu(t->tc_tx_pool_size);
|
||||
a->fc_rx_pool_size = __le16_to_cpu(t->fc_rx_pool_size);
|
||||
a->tc_cmd_pool_size = t->tc_cmd_pool_size;
|
||||
a->tc_beacon_pool_size = t->tc_beacon_pool_size;
|
||||
a->tc_mgmt_pool_size = t->tc_mgmt_pool_size;
|
||||
a->fc_resp_pool_size = t->fc_resp_pool_size;
|
||||
a->fc_tx_sts_pool_size = t->fc_tx_sts_pool_size;
|
||||
a->fc_aux_pool_size = t->fc_aux_pool_size;
|
||||
a->tc_tx_q_size = t->tc_tx_q_size;
|
||||
a->tc_cmd_q_size = t->tc_cmd_q_size;
|
||||
a->tc_beacon_q_size = t->tc_beacon_q_size;
|
||||
a->tc_mgmt_q_size = t->tc_mgmt_q_size;
|
||||
a->fc_q_size = t->fc_q_size;
|
||||
a->fc_done_q_size = t->fc_done_q_size;
|
||||
a->reserved_yaps_page_size = le16_to_cpu(t->yaps_reserved_page_size);
|
||||
}
|
||||
|
||||
static u8 mm81x_yaps_hw_crc(u32 word)
|
||||
{
|
||||
u8 crc = 0;
|
||||
u8 byte;
|
||||
int i;
|
||||
|
||||
/* Mask to look at only non-CRC bits */
|
||||
word &= 0x1ffffff;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
byte = (word >> 24) & 0xff;
|
||||
crc = crc7_be(crc, &byte, 1);
|
||||
word <<= 8;
|
||||
}
|
||||
|
||||
return crc >> 1;
|
||||
}
|
||||
|
||||
static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps,
|
||||
unsigned int size, u8 pool_id,
|
||||
bool irq)
|
||||
{
|
||||
u32 delim = 0;
|
||||
|
||||
delim |= YAPS_DELIM_SET_PKT_SIZE(size);
|
||||
delim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size));
|
||||
delim |= YAPS_DELIM_SET_POOL_ID(pool_id);
|
||||
delim |= YAPS_DELIM_SET_IRQ(irq);
|
||||
delim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim));
|
||||
return delim;
|
||||
}
|
||||
|
||||
void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable)
|
||||
{
|
||||
mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable);
|
||||
mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN,
|
||||
enable);
|
||||
}
|
||||
|
||||
void mm81x_yaps_hw_read_table(struct mm81x *mors,
|
||||
struct mm81x_yaps_hw_table *tbl_ptr)
|
||||
{
|
||||
mm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors->hif.u.yaps.aux_data,
|
||||
tbl_ptr);
|
||||
mm81x_yaps_hw_enable_irqs(mors, true);
|
||||
}
|
||||
|
||||
static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps,
|
||||
unsigned int size_bytes)
|
||||
{
|
||||
/* Always account for the first metadata page */
|
||||
return DIV_ROUND_UP(size_bytes +
|
||||
yaps->aux_data->reserved_yaps_page_size,
|
||||
YAPS_PAGE_SIZE) +
|
||||
YAPS_METADATA_PAGE_COUNT +
|
||||
YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if a single pkt will fit in the chip using the pool/alloc holding
|
||||
* information from the last status register read.
|
||||
*/
|
||||
static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkt, bool update)
|
||||
{
|
||||
bool will_fit = true;
|
||||
const int pages_required =
|
||||
mm81x_write_pkts_h_pages_required(yaps, pkt->skb->len);
|
||||
int *pool_pages_avail = NULL;
|
||||
int *pkts_in_queue = NULL;
|
||||
int queue_pkts_avail = 0;
|
||||
|
||||
switch (pkt->tc_queue) {
|
||||
case MM81X_YAPS_TX_Q:
|
||||
pool_pages_avail =
|
||||
&yaps->aux_data->status_regs.tc_tx_pool_num_pages;
|
||||
pkts_in_queue = &yaps->aux_data->status_regs.tc_tx_num_pkts;
|
||||
queue_pkts_avail =
|
||||
yaps->aux_data->tc_tx_q_size - *pkts_in_queue;
|
||||
break;
|
||||
case MM81X_YAPS_CMD_Q:
|
||||
pool_pages_avail =
|
||||
&yaps->aux_data->status_regs.tc_cmd_pool_num_pages;
|
||||
pkts_in_queue = &yaps->aux_data->status_regs.tc_cmd_num_pkts;
|
||||
queue_pkts_avail =
|
||||
yaps->aux_data->tc_cmd_q_size - *pkts_in_queue;
|
||||
break;
|
||||
case MM81X_YAPS_BEACON_Q:
|
||||
pool_pages_avail =
|
||||
&yaps->aux_data->status_regs.tc_beacon_pool_num_pages;
|
||||
pkts_in_queue = &yaps->aux_data->status_regs.tc_beacon_num_pkts;
|
||||
queue_pkts_avail =
|
||||
yaps->aux_data->tc_beacon_q_size - *pkts_in_queue;
|
||||
break;
|
||||
case MM81X_YAPS_MGMT_Q:
|
||||
pool_pages_avail =
|
||||
&yaps->aux_data->status_regs.tc_mgmt_pool_num_pages;
|
||||
pkts_in_queue = &yaps->aux_data->status_regs.tc_mgmt_num_pkts;
|
||||
queue_pkts_avail =
|
||||
yaps->aux_data->tc_mgmt_q_size - *pkts_in_queue;
|
||||
break;
|
||||
default:
|
||||
dev_err(yaps->mors->dev, "yaps invalid tc queue");
|
||||
return false;
|
||||
}
|
||||
|
||||
WARN_ON(queue_pkts_avail < 0);
|
||||
|
||||
if (pages_required > *pool_pages_avail)
|
||||
will_fit = false;
|
||||
|
||||
if (queue_pkts_avail == 0)
|
||||
will_fit = false;
|
||||
|
||||
if (will_fit && update) {
|
||||
*pool_pages_avail -= pages_required;
|
||||
*pkts_in_queue += 1;
|
||||
}
|
||||
|
||||
return will_fit;
|
||||
}
|
||||
|
||||
static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkt)
|
||||
{
|
||||
if (pkt->skb->len + yaps->aux_data->reserved_yaps_page_size >
|
||||
YAPS_MAX_PKT_SIZE_BYTES)
|
||||
return -EMSGSIZE;
|
||||
if (pkt->tc_queue >= MM81X_YAPS_NUM_TC_Q)
|
||||
return -EINVAL;
|
||||
if (!mm81x_write_pkts_h_will_fit(yaps, pkt, true))
|
||||
return -EAGAIN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkts, int num_pkts,
|
||||
int *num_pkts_sent)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
u32 delim = 0;
|
||||
int tx_len;
|
||||
int batch_txn_len = 0;
|
||||
int pkts_pending = 0;
|
||||
bool delim_irq = false;
|
||||
char *to_chip_buffer_aligned =
|
||||
PTR_ALIGN(yaps->aux_data->to_chip_buffer,
|
||||
mm81x_bus_get_alignment(yaps->mors));
|
||||
char *write_buf = to_chip_buffer_aligned;
|
||||
|
||||
ret = mm81x_yaps_hw_lock(yaps);
|
||||
if (ret) {
|
||||
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*num_pkts_sent = 0;
|
||||
|
||||
/* Check packet conditions */
|
||||
ret = mm81x_write_pkts_h_err_check(yaps, &pkts[0]);
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
/* Batch packets into larger transactions */
|
||||
for (i = 0; i < num_pkts; ++i) {
|
||||
u32 pkt_size =
|
||||
pkts[i].skb->len + YAPS_CALC_PADDING(pkts[i].skb->len);
|
||||
tx_len = pkt_size + sizeof(delim);
|
||||
|
||||
/*
|
||||
* Send when we have reached window size, don't split pkt over
|
||||
* boundary
|
||||
*/
|
||||
if ((batch_txn_len + tx_len) > YAPS_HW_WINDOW_SIZE_BYTES) {
|
||||
ret = mm81x_dm_write(yaps->mors,
|
||||
yaps->aux_data->yds_addr,
|
||||
to_chip_buffer_aligned,
|
||||
batch_txn_len);
|
||||
|
||||
batch_txn_len = 0;
|
||||
if (ret)
|
||||
goto exit;
|
||||
write_buf = to_chip_buffer_aligned;
|
||||
*num_pkts_sent += pkts_pending;
|
||||
pkts_pending = 0;
|
||||
}
|
||||
|
||||
if ((i + 1) == num_pkts) {
|
||||
/* The last packet in the queue has IRQ set */
|
||||
delim_irq = true;
|
||||
} else {
|
||||
/*
|
||||
* Since this is not the last packet, we can check for
|
||||
* the next one. In case of errors in the next packet
|
||||
* set the IRQ
|
||||
*/
|
||||
ret = mm81x_write_pkts_h_err_check(yaps, &pkts[i + 1]);
|
||||
if (ret)
|
||||
delim_irq = true;
|
||||
}
|
||||
|
||||
/* Build stream header*/
|
||||
delim = mm81x_write_pkts_h_build_delim(
|
||||
yaps, pkt_size, pkts[i].tc_queue, delim_irq);
|
||||
*((__le32 *)write_buf) = cpu_to_le32(delim);
|
||||
memcpy(write_buf + sizeof(delim), pkts[i].skb->data,
|
||||
pkts[i].skb->len);
|
||||
|
||||
write_buf += tx_len;
|
||||
batch_txn_len += tx_len;
|
||||
pkts_pending++;
|
||||
|
||||
if (ret)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (batch_txn_len > 0) {
|
||||
ret = mm81x_dm_write(yaps->mors, yaps->aux_data->yds_addr,
|
||||
to_chip_buffer_aligned, batch_txn_len);
|
||||
*num_pkts_sent += pkts_pending;
|
||||
}
|
||||
|
||||
mm81x_yaps_hw_unlock(yaps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool mm81x_read_pkts_h_is_valid_delim(u32 delim)
|
||||
{
|
||||
u8 calc_crc = mm81x_yaps_hw_crc(delim);
|
||||
int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim);
|
||||
int padding = YAPS_DELIM_GET_PADDING(delim);
|
||||
|
||||
if (calc_crc != YAPS_DELIM_GET_CRC(delim))
|
||||
return false;
|
||||
|
||||
if (pkt_size == 0)
|
||||
return false;
|
||||
|
||||
if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES)
|
||||
return false;
|
||||
|
||||
/* Pkt length + padding should not require more padding */
|
||||
if (YAPS_CALC_PADDING(pkt_size) != padding)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps)
|
||||
{
|
||||
u32 bytes_in_queue = yaps->aux_data->status_regs.fc_rx_bytes_in_queue;
|
||||
u32 delim_overhead =
|
||||
yaps->aux_data->status_regs.fc_num_pkts * sizeof(u32);
|
||||
u32 reserved_bytes = yaps->aux_data->status_regs.fc_num_pkts *
|
||||
yaps->aux_data->reserved_yaps_page_size;
|
||||
|
||||
if (WARN_ON(bytes_in_queue > INT_MAX) ||
|
||||
WARN_ON(delim_overhead > INT_MAX) ||
|
||||
WARN_ON(reserved_bytes > INT_MAX))
|
||||
return -EIO;
|
||||
|
||||
return (int)bytes_in_queue;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps,
|
||||
struct mm81x_yaps_pkt *pkts,
|
||||
int num_pkts_max, int *num_pkts_received)
|
||||
{
|
||||
int ret;
|
||||
int i = 0;
|
||||
char *from_chip_buffer_aligned =
|
||||
PTR_ALIGN(yaps->aux_data->from_chip_buffer,
|
||||
mm81x_bus_get_alignment(yaps->mors));
|
||||
char *read_ptr = from_chip_buffer_aligned;
|
||||
int bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps);
|
||||
bool again = false;
|
||||
|
||||
*num_pkts_received = 0;
|
||||
|
||||
if (num_pkts_max == 0 || bytes_remaining == 0)
|
||||
return 0;
|
||||
if (bytes_remaining < 0)
|
||||
return bytes_remaining;
|
||||
|
||||
if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) {
|
||||
bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES;
|
||||
again = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is more coarse-grained than it needs to be - once the data
|
||||
* is read into a local buffer the lock can be released, however
|
||||
* access to from_chip_buffer will need to be protected with its
|
||||
* own lock
|
||||
*/
|
||||
ret = mm81x_yaps_hw_lock(yaps);
|
||||
if (ret) {
|
||||
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Read all available packets to the buffer */
|
||||
ret = mm81x_dm_read(yaps->mors, yaps->aux_data->ysl_addr,
|
||||
from_chip_buffer_aligned, bytes_remaining);
|
||||
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
/* Split serialised packets from buffer */
|
||||
while (i < num_pkts_max && bytes_remaining > 0) {
|
||||
u32 delim;
|
||||
int total_len;
|
||||
int pkt_size;
|
||||
|
||||
delim = le32_to_cpu(*((__le32 *)read_ptr));
|
||||
read_ptr += sizeof(delim);
|
||||
bytes_remaining -= sizeof(delim);
|
||||
|
||||
/* End of stream */
|
||||
if (!delim)
|
||||
break;
|
||||
|
||||
if (!mm81x_read_pkts_h_is_valid_delim(delim)) {
|
||||
/*
|
||||
* This will start a hunt for a valid delimiter. Given
|
||||
* the CRC is only 7 bit it's possible to find an
|
||||
* invalid block with a valid delimiter, leading to
|
||||
* desynchronisation.
|
||||
*/
|
||||
dev_warn(yaps->mors->dev, "yaps invalid delim");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Total length in chip */
|
||||
pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim);
|
||||
total_len = pkt_size + YAPS_DELIM_GET_PADDING(delim);
|
||||
|
||||
if (pkts[i].skb)
|
||||
dev_err(yaps->mors->dev, "yaps packet leak");
|
||||
|
||||
/* SKB doesn't want padding */
|
||||
pkts[i].skb = dev_alloc_skb(pkt_size);
|
||||
if (!pkts[i].skb) {
|
||||
ret = -ENOMEM;
|
||||
dev_err(yaps->mors->dev, "yaps no mem for skb");
|
||||
goto exit;
|
||||
}
|
||||
skb_put(pkts[i].skb, pkt_size);
|
||||
|
||||
if (total_len <= bytes_remaining) {
|
||||
memcpy(pkts[i].skb->data, read_ptr, pkt_size);
|
||||
read_ptr += total_len;
|
||||
bytes_remaining -= total_len;
|
||||
} else {
|
||||
const int read_overhang_len =
|
||||
total_len - bytes_remaining;
|
||||
const int pkt_overhang_len = pkt_size - bytes_remaining;
|
||||
|
||||
memcpy(pkts[i].skb->data, read_ptr, bytes_remaining);
|
||||
read_ptr = from_chip_buffer_aligned;
|
||||
|
||||
ret = mm81x_dm_read(
|
||||
yaps->mors,
|
||||
/* Offset by 4 to avoid retry logic */
|
||||
yaps->aux_data->ysl_addr + 4, read_ptr,
|
||||
read_overhang_len);
|
||||
|
||||
if (ret)
|
||||
goto exit;
|
||||
|
||||
memcpy(pkts[i].skb->data + bytes_remaining, read_ptr,
|
||||
pkt_overhang_len);
|
||||
read_ptr += read_overhang_len;
|
||||
bytes_remaining = 0;
|
||||
}
|
||||
|
||||
*num_pkts_received += 1;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (again)
|
||||
ret = -EAGAIN;
|
||||
|
||||
exit:
|
||||
mm81x_yaps_hw_unlock(yaps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps)
|
||||
{
|
||||
int ret;
|
||||
int tc_total_pkt_count;
|
||||
unsigned long reg_read_timeout;
|
||||
struct mm81x_yaps_status_regs *r = &yaps->aux_data->status_regs;
|
||||
struct mm81x_yaps_hw_status_regs *hw_r = &yaps->aux_data->hw_status_regs;
|
||||
|
||||
ret = mm81x_yaps_hw_lock(yaps);
|
||||
if (ret) {
|
||||
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
reg_read_timeout = jiffies + msecs_to_jiffies(100);
|
||||
do {
|
||||
if (time_after(jiffies, reg_read_timeout)) {
|
||||
dev_err(yaps->mors->dev,
|
||||
"timed out reading status registers: %d", ret);
|
||||
ret = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
ret = mm81x_dm_read(yaps->mors,
|
||||
yaps->aux_data->status_regs_addr,
|
||||
(u8 *)hw_r, sizeof(*hw_r));
|
||||
} while (!ret && le32_to_cpu(hw_r->lock));
|
||||
|
||||
if (ret) {
|
||||
if (ret != -ENODEV) {
|
||||
dev_err(yaps->mors->dev,
|
||||
"error reading yaps status registers: %d", ret);
|
||||
}
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
r->tc_tx_pool_num_pages = le32_to_cpu(hw_r->tc_tx_pool_num_pages);
|
||||
r->tc_cmd_pool_num_pages = le32_to_cpu(hw_r->tc_cmd_pool_num_pages);
|
||||
r->tc_beacon_pool_num_pages = le32_to_cpu(hw_r->tc_beacon_pool_num_pages);
|
||||
r->tc_mgmt_pool_num_pages = le32_to_cpu(hw_r->tc_mgmt_pool_num_pages);
|
||||
r->fc_rx_pool_num_pages = le32_to_cpu(hw_r->fc_rx_pool_num_pages);
|
||||
r->fc_resp_pool_num_pages = le32_to_cpu(hw_r->fc_resp_pool_num_pages);
|
||||
r->fc_tx_sts_pool_num_pages = le32_to_cpu(hw_r->fc_tx_sts_pool_num_pages);
|
||||
r->fc_aux_pool_num_pages = le32_to_cpu(hw_r->fc_aux_pool_num_pages);
|
||||
r->tc_tx_num_pkts = le32_to_cpu(hw_r->tc_tx_num_pkts);
|
||||
r->tc_cmd_num_pkts = le32_to_cpu(hw_r->tc_cmd_num_pkts);
|
||||
r->tc_beacon_num_pkts = le32_to_cpu(hw_r->tc_beacon_num_pkts);
|
||||
r->tc_mgmt_num_pkts = le32_to_cpu(hw_r->tc_mgmt_num_pkts);
|
||||
r->fc_num_pkts = le32_to_cpu(hw_r->fc_num_pkts);
|
||||
r->fc_done_num_pkts = le32_to_cpu(hw_r->fc_done_num_pkts);
|
||||
r->fc_rx_bytes_in_queue = le32_to_cpu(hw_r->fc_rx_bytes_in_queue);
|
||||
r->tc_delim_crc_fail_detected = le32_to_cpu(hw_r->tc_delim_crc_fail_detected);
|
||||
r->lock = le32_to_cpu(hw_r->lock);
|
||||
r->fc_host_ysl_status = le32_to_cpu(hw_r->fc_host_ysl_status);
|
||||
|
||||
tc_total_pkt_count = r->tc_tx_num_pkts + r->tc_cmd_num_pkts +
|
||||
r->tc_beacon_num_pkts + r->tc_mgmt_num_pkts;
|
||||
|
||||
if (r->tc_delim_crc_fail_detected) {
|
||||
/*
|
||||
* Host and chip have become desynchronised. This can happen if
|
||||
* the chip crashes during a YAPS transaction. We cannot
|
||||
* recover from this.
|
||||
*/
|
||||
dev_err(yaps->mors->dev,
|
||||
"to-chip yaps delimiter CRC fail, pkt_count=%d",
|
||||
tc_total_pkt_count);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
if (mm81x_read_pkts_h_bytes_remaining(yaps))
|
||||
set_bit(MM81X_HIF_EVT_RX_PEND, &yaps->mors->hif.event_flags);
|
||||
|
||||
exit_unlock:
|
||||
mm81x_yaps_hw_unlock(yaps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = {
|
||||
.write_pkts = mm81x_yaps_hw_write_pkts,
|
||||
.read_pkts = mm81x_yaps_hw_read_pkts,
|
||||
.update_status = mm81x_yaps_hw_update_status,
|
||||
};
|
||||
|
||||
int mm81x_yaps_hw_init(struct mm81x *mors)
|
||||
{
|
||||
int ret = 0;
|
||||
struct mm81x_yaps *yaps = NULL;
|
||||
int aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data);
|
||||
int alignment = mm81x_bus_get_alignment(mors);
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
yaps->aux_data = kzalloc(aux_data_len, GFP_KERNEL);
|
||||
if (!yaps->aux_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
yaps->aux_data->to_chip_buffer =
|
||||
kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
|
||||
if (!yaps->aux_data->to_chip_buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
yaps->aux_data->from_chip_buffer =
|
||||
kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
|
||||
if (!yaps->aux_data->from_chip_buffer) {
|
||||
ret = -ENOMEM;
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
if (!IS_ALIGNED((uintptr_t)&yaps->aux_data->status_regs, alignment)) {
|
||||
dev_warn(mors->dev,
|
||||
"Status registers are not aligned to %d bytes",
|
||||
alignment);
|
||||
}
|
||||
|
||||
yaps->ops = &mm81x_yaps_hw_ops;
|
||||
return ret;
|
||||
|
||||
err_exit:
|
||||
mm81x_yaps_hw_finish(mors);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mm81x_yaps_hw_finish(struct mm81x *mors)
|
||||
{
|
||||
struct mm81x_yaps *yaps;
|
||||
|
||||
yaps = &mors->hif.u.yaps;
|
||||
if (yaps->aux_data) {
|
||||
kfree(yaps->aux_data->from_chip_buffer);
|
||||
yaps->aux_data->from_chip_buffer = NULL;
|
||||
kfree(yaps->aux_data->to_chip_buffer);
|
||||
yaps->aux_data->to_chip_buffer = NULL;
|
||||
kfree(yaps->aux_data);
|
||||
yaps->aux_data = NULL;
|
||||
}
|
||||
}
|
||||
52
drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
Normal file
52
drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2017-2026 Morse Micro
|
||||
*/
|
||||
|
||||
#ifndef _MM81X_YAPS_HW_H_
|
||||
#define _MM81X_YAPS_HW_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/crc7.h>
|
||||
|
||||
#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0
|
||||
#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1
|
||||
|
||||
struct mm81x_yaps_hw_table {
|
||||
/* NOTE: We need these padding bytes for yaps to work */
|
||||
u8 padding[4];
|
||||
__le32 ysl_addr;
|
||||
__le32 yds_addr;
|
||||
__le32 status_regs_addr;
|
||||
|
||||
/* Alloc pool sizes */
|
||||
__le16 tc_tx_pool_size;
|
||||
__le16 fc_rx_pool_size;
|
||||
u8 tc_cmd_pool_size;
|
||||
u8 tc_beacon_pool_size;
|
||||
u8 tc_mgmt_pool_size;
|
||||
u8 fc_resp_pool_size;
|
||||
u8 fc_tx_sts_pool_size;
|
||||
u8 fc_aux_pool_size;
|
||||
|
||||
/* To chip/from chip queue sizes */
|
||||
u8 tc_tx_q_size;
|
||||
u8 tc_cmd_q_size;
|
||||
u8 tc_beacon_q_size;
|
||||
u8 tc_mgmt_q_size;
|
||||
u8 fc_q_size;
|
||||
u8 fc_done_q_size;
|
||||
|
||||
__le16 yaps_reserved_page_size;
|
||||
__le16 reserved_unused;
|
||||
} __packed;
|
||||
|
||||
struct mm81x;
|
||||
|
||||
void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable);
|
||||
int mm81x_yaps_hw_init(struct mm81x *mors);
|
||||
void mm81x_yaps_hw_finish(struct mm81x *mors);
|
||||
void mm81x_yaps_hw_read_table(struct mm81x *mors,
|
||||
struct mm81x_yaps_hw_table *tbl_ptr);
|
||||
|
||||
#endif /* !_MM81X_YAPS_HW_H_ */
|
||||
@@ -117,6 +117,9 @@
|
||||
#define SDIO_VENDOR_ID_MICROCHIP_WILC 0x0296
|
||||
#define SDIO_DEVICE_ID_MICROCHIP_WILC1000 0x5347
|
||||
|
||||
#define SDIO_VENDOR_ID_MORSEMICRO 0x325b
|
||||
#define SDIO_DEVICE_ID_MORSEMICRO_MM8108 0x0809
|
||||
|
||||
#define SDIO_VENDOR_ID_NXP 0x0471
|
||||
#define SDIO_DEVICE_ID_NXP_IW61X 0x0205
|
||||
|
||||
|
||||
Reference in New Issue
Block a user