linux/drivers/net/wireless/morsemicro/mm81x/hw.c
Lachlan Hodges b1906cea00 wifi: mm81x: add mm81x Wi-Fi HaLow driver
mm81x is the first Wi-Fi HaLow driver to support the Morse Micro mm81x
chip family via USB and SDIO.

S1G support in the kernel is only new, and as a result this driver has
been scoped to be simple and only support station and AP interface.
The Wi-Fi specific features only cover the minimum required for basic
use such as powersave, aggregation, rate control and so on. The driver
will be extended into the future as S1G support for operations such as
ACS, channel switching and so on are added into the wireless stack.

The driver has been build tested on a long list of architectures and
compilers via Intels LKP.

The driver currently supports IEEE80211-2024 US channels only, with
AU 2020 also available. In order for this to be expanded additional
non-trivial kernel work is required which will begin once the
driver is upstream.

The driver has had many authors who are listed below in
alphabetical order:

Co-developed-by: Andrew Pope <andrew.pope@morsemicro.com>
Signed-off-by: Andrew Pope <andrew.pope@morsemicro.com>
Co-developed-by: Arien Judge <arien.judge@morsemicro.com>
Signed-off-by: Arien Judge <arien.judge@morsemicro.com>
Co-developed-by: Ayman Grais <ayman.grais@morsemicro.com>
Signed-off-by: Ayman Grais <ayman.grais@morsemicro.com>
Co-developed-by: Bassem Dawood <bassem@morsemicro.com>
Signed-off-by: Bassem Dawood <bassem@morsemicro.com>
Co-developed-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Signed-off-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Co-developed-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Signed-off-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Co-developed-by: James Herbert <james.herbert@morsemicro.com>
Signed-off-by: James Herbert <james.herbert@morsemicro.com>
Co-developed-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Signed-off-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Co-developed-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
2026-07-08 12:03:50 +10:00

368 lines
9.5 KiB
C

// 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, &reg32_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);