// 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);