// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright 2019 Broadcom.
 */

#include <drivers/bcm/bnxt.h>
#include <initcall.h>
#include <io.h>
#include <kernel/delay.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define BNXT_REG_CTRL_BASE			0x3040000
#define BNXT_REG_ECO_RESERVED			0x3042400
#define BNXT_FLASH_ACCESS_DONE_BIT		2
#define NIC400_BNXT_IDM_IO_CONTROL_DIRECT	0x60e00408
#define BNXT_INDIRECT_BASE			0x60800000
#define BNXT_INDIRECT_ADDR_MASK			0x3fffffu
#define BNXT_INDIRECT_BASE_MASK			(~BNXT_INDIRECT_ADDR_MASK)
#define BNXT_INDIRECT_WINDOW_SIZE		(BNXT_INDIRECT_ADDR_MASK + 1)
#define BNXT_REG_CTRL_BPE_MODE_REG		0x0
#define BNXT_REG_CTRL_BPE_MODE_FASTBOOT_MODE_BIT	2
#define BNXT_REG_CTRL_BPE_MODE_CM3_RST_BIT	1
#define BNXT_REG_CTRL_BPE_STAT_REG		0x4
#define BNXT_REG_CTRL_FSTBOOT_PTR_REG		0x8
#define BNXT_ERROR_MASK				0xf0000000
#define BNXT_CTRL_ADDR(x)			(BNXT_REG_CTRL_BASE + (x))
#define BNXT_HANDSHAKE_TIMEOUT_MS		1000

#define KONG_REG_CTRL_MODE_REG			0x03900000
#define KONG_REG_CTRL_MODE_CPUHALT_N_BIT	0

#define BNXT_STICKY_BYTE_POR			0x04030088
#define BNXT_STICKY_BYTE_POR_MHB_BIT		4

#define BNXT_HEALTH_CHECK_REG			0x03100008

enum bnxt_handshake_sts {
	BNXT_HANDSHAKE_SUCCESS = 0,
	BNXT_HANDSHAKE_WAIT_ERROR,
	BNXT_HANDSHAKE_WAIT_TIMEOUT
};

static vaddr_t bnxt_access_window_virt_addr;
static vaddr_t bnxt_indirect_dest_addr;

static void bnxt_prepare_access_window(uint32_t addr)
{
	addr &= BNXT_INDIRECT_BASE_MASK;
	io_write32(bnxt_access_window_virt_addr, addr);
}

static vaddr_t bnxt_indirect_tgt_addr(uint32_t addr)
{
	addr &= BNXT_INDIRECT_ADDR_MASK;
	return (vaddr_t)(bnxt_indirect_dest_addr + addr);
}

uint32_t bnxt_write32_multiple(uintptr_t dst,
			       uintptr_t src,
			       uint32_t num_entries,
			       int src_4byte_increment)
{
	uint32_t i = 0;
	vaddr_t target = 0;

	if (num_entries == 0)
		return 0;

	/* Only write up to the next window boundary */
	if ((dst & BNXT_INDIRECT_BASE_MASK) !=
	    ((dst + num_entries * sizeof(uint32_t)) & BNXT_INDIRECT_BASE_MASK))
		num_entries = (((dst + BNXT_INDIRECT_WINDOW_SIZE) &
				BNXT_INDIRECT_BASE_MASK) -
			       dst) /
			      sizeof(uint32_t);

	bnxt_prepare_access_window(dst);
	target = bnxt_indirect_tgt_addr(dst);
	for (i = 0; i < num_entries; i++) {
		io_write32(target, *(uint32_t *)src);
		target += sizeof(uint32_t);
		if (src_4byte_increment)
			src += sizeof(uint32_t);
	}

	return num_entries;
}

static uint32_t bnxt_read(uint32_t addr)
{
	bnxt_prepare_access_window(addr);
	return io_read32(bnxt_indirect_tgt_addr(addr));
}

static uint32_t bnxt_read_ctrl(uint32_t offset)
{
	return bnxt_read(BNXT_CTRL_ADDR(offset));
}

static void bnxt_write(uint32_t addr, uint32_t value)
{
	bnxt_prepare_access_window(addr);
	io_write32(bnxt_indirect_tgt_addr(addr), value);
}

static void bnxt_write_ctrl(uint32_t offset, uint32_t value)
{
	bnxt_write(BNXT_CTRL_ADDR(offset), value);
}

void bnxt_handshake_clear(void)
{
	uint32_t value = bnxt_read(BNXT_REG_ECO_RESERVED);

	value = value & ~BIT(BNXT_FLASH_ACCESS_DONE_BIT);
	bnxt_write(BNXT_REG_ECO_RESERVED, value);
}

static int bnxt_handshake_done(void)
{
	uint32_t value = 0;

	value = bnxt_read(BNXT_REG_ECO_RESERVED);
	value &= BIT(BNXT_FLASH_ACCESS_DONE_BIT);

	return value != 0;
}

uint32_t bnxt_wait_handshake(uint32_t max_timeout)
{
	int ret = 0;
	uint32_t status = 0;
	uint32_t timeout = 0;

	/* If no timeout given we go with max timeout */
	if (max_timeout == 0)
		max_timeout = BNXT_HANDSHAKE_TIMEOUT_MS;

	timeout = max_timeout;

	DMSG("Waiting for ChiMP handshake...");
	do {
		if (bnxt_handshake_done()) {
			ret = BNXT_HANDSHAKE_SUCCESS;
			break;
		}
		/* No need to wait if ChiMP reported an error */
		status = bnxt_read_ctrl(BNXT_REG_CTRL_BPE_STAT_REG);
		if (status & BNXT_ERROR_MASK) {
			EMSG("ChiMP error 0x%x. Wait aborted", status);
			ret = BNXT_HANDSHAKE_WAIT_ERROR;
			break;
		}
		mdelay(1);
	} while (--timeout);

	if (!bnxt_handshake_done()) {
		if (timeout == 0) {
			ret = BNXT_HANDSHAKE_WAIT_TIMEOUT;
			EMSG("Timeout waiting for ChiMP handshake");
		}
	} else {
		ret = BNXT_HANDSHAKE_SUCCESS;
		DMSG("ChiMP handshake successful");
	}

	return ret;
}

void bnxt_chimp_halt(void)
{
	uint32_t value = 0;

	value = bnxt_read_ctrl(BNXT_REG_CTRL_BPE_MODE_REG);
	value |= BIT(BNXT_REG_CTRL_BPE_MODE_CM3_RST_BIT);
	bnxt_write_ctrl(BNXT_REG_CTRL_BPE_MODE_REG, value);
}

void bnxt_kong_halt(void)
{
	uint32_t value = 0;

	value = bnxt_read(KONG_REG_CTRL_MODE_REG);
	value &= ~BIT(KONG_REG_CTRL_MODE_CPUHALT_N_BIT);
	bnxt_write(KONG_REG_CTRL_MODE_REG, value);
}

int bnxt_fastboot(uintptr_t addr)
{
	uint32_t value = 0;

	value = bnxt_read(BNXT_STICKY_BYTE_POR);
	value |= BIT(BNXT_STICKY_BYTE_POR_MHB_BIT);
	bnxt_write(BNXT_STICKY_BYTE_POR, value);

	/* Set the fastboot address and type */
	bnxt_write_ctrl(BNXT_REG_CTRL_FSTBOOT_PTR_REG, addr);

	/* Set fastboot mode & take BNXT CPU1 out of reset */
	value = bnxt_read_ctrl(BNXT_REG_CTRL_BPE_MODE_REG);
	value |= BIT(BNXT_REG_CTRL_BPE_MODE_FASTBOOT_MODE_BIT);
	value &= ~BIT(BNXT_REG_CTRL_BPE_MODE_CM3_RST_BIT);
	bnxt_write_ctrl(BNXT_REG_CTRL_BPE_MODE_REG, value);

	return 0;
}

uint32_t bnxt_health_status(void)
{
	return bnxt_read(BNXT_HEALTH_CHECK_REG);
}

static TEE_Result bnxt_init(void)
{
	bnxt_access_window_virt_addr =
		(vaddr_t)phys_to_virt(NIC400_BNXT_IDM_IO_CONTROL_DIRECT,
				      MEM_AREA_IO_SEC);
	bnxt_indirect_dest_addr =
		(vaddr_t)phys_to_virt(BNXT_INDIRECT_BASE,
				      MEM_AREA_IO_SEC);
	return TEE_SUCCESS;
}
driver_init(bnxt_init);
