/*
 * Copyright (c) 2024-2025, STMicroelectronics - All Rights Reserved
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <limits.h>

#include <arch_helpers.h>
#include <common/debug.h>
#include <drivers/st/bsec.h>
#include <drivers/st/bsec3_reg.h>
#include <drivers/st/stm32mp_reset.h>
#include <lib/mmio.h>
#include <lib/spinlock.h>
#include <libfdt.h>

#include <platform_def.h>

#define BSEC_IP_VERSION_1_0	U(0x10)
#define BSEC_IP_ID_3		U(0x100033)

#define MAX_NB_TRIES		U(3)

/*
 * IP configuration
 */
#define BSEC_OTP_MASK			GENMASK_32(4, 0)
#define BSEC_OTP_BANK_SHIFT		U(5)
#define BSEC_TIMEOUT_VALUE		U(0x800000) /* ~7sec @1.2GHz */

static uint32_t otp_bank(uint32_t otp)
{
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	return (otp & ~BSEC_OTP_MASK) >> BSEC_OTP_BANK_SHIFT;
}

static uint32_t otp_bit_mask(uint32_t otp)
{
	return BIT(otp & BSEC_OTP_MASK);
}

/*
 * bsec_get_status: return status register value.
 */
static uint32_t bsec_get_status(void)
{
	return mmio_read_32(BSEC_BASE + BSEC_OTPSR);
}

/*
 * bsec_get_version: return BSEC version.
 */
static uint32_t bsec_get_version(void)
{
	return mmio_read_32(BSEC_BASE + BSEC_VERR) & BSEC_VERR_MASK;
}

/*
 * bsec_get_id: return BSEC ID.
 */
static uint32_t bsec_get_id(void)
{
	return mmio_read_32(BSEC_BASE + BSEC_IPIDR);
}

static bool is_fuse_shadowed(uint32_t otp)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);
	uint32_t bank_value;

	bank_value = mmio_read_32(BSEC_BASE + BSEC_SFSR(bank));

	if ((bank_value & otp_mask) != 0U) {
		return true;
	}

	return false;
}

static void poll_otp_status_busy(void)
{
	uint32_t timeout = BSEC_TIMEOUT_VALUE;

	while (((bsec_get_status() & BSEC_OTPSR_BUSY) != 0U) && (timeout != 0U)) {
		timeout--;
	}

	if ((bsec_get_status() & BSEC_OTPSR_BUSY) != 0U) {
		ERROR("BSEC timeout\n");
		panic();
	}
}

static uint32_t check_read_error(uint32_t otp)
{
	uint32_t status = bsec_get_status();

	if ((status & BSEC_OTPSR_SECF) != 0U) {
		VERBOSE("BSEC read %u single error correction detected\n", otp);
	}

	if ((status & BSEC_OTPSR_PPLF) != 0U) {
		VERBOSE("BSEC read %u permanent programming lock detected.\n", otp);
	}

	if ((status & BSEC_OTPSR_PPLMF) != 0U) {
		ERROR("BSEC read %u error 0x%x\n", otp, status);
		return BSEC_ERROR;
	}

	if ((status & (BSEC_OTPSR_DISTURBF | BSEC_OTPSR_DEDF | BSEC_OTPSR_AMEF)) != 0U) {
		ERROR("BSEC read %u error 0x%x with invalid FVR\n", otp, status);
		return BSEC_RETRY;
	}

	return BSEC_OK;
}

static uint32_t check_program_error(uint32_t otp)
{
	uint32_t status = bsec_get_status();

	if ((status & BSEC_OTPSR_PROGFAIL) != 0U) {
		ERROR("BSEC program %u error 0x%x\n", otp, status);
		return BSEC_RETRY;
	}

	return BSEC_OK;
}

static void check_reset_error(void)
{
	uint32_t status = bsec_get_status();

	/* check initial status reporting */
	if ((status & BSEC_OTPSR_BUSY) != 0U) {
		VERBOSE("BSEC reset and busy when OTPSR read\n");
	}
	if ((status & BSEC_OTPSR_HIDEUP) != 0U) {
		VERBOSE("BSEC upper fuse are not accessible (HIDEUP)\n");
	}
	if ((status & BSEC_OTPSR_OTPSEC) != 0U) {
		VERBOSE("BSEC reset single error correction detected\n");
	}
	if ((status & BSEC_OTPSR_OTPNVIR) == 0U) {
		VERBOSE("BSEC reset first fuse word 0 is detected zero\n");
	}
	if ((status & BSEC_OTPSR_OTPERR) != 0U) {
		ERROR("BSEC reset critical error 0x%x\n", status);
		panic();
	}
	if ((status & BSEC_OTPSR_INIT_DONE) != BSEC_OTPSR_INIT_DONE) {
		ERROR("BSEC reset critical error 0x%x\n", status);
		panic();
	}
}

static bool is_bsec_write_locked(void)
{
	return (mmio_read_32(BSEC_BASE + BSEC_LOCKR) & BSEC_LOCKR_GWLOCK_MASK) != 0U;
}

/*
 * bsec_probe: initialize BSEC driver.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_probe(void)
{
	uint32_t version = bsec_get_version();
	uint32_t id = bsec_get_id();

	if ((version != BSEC_IP_VERSION_1_0) || (id != BSEC_IP_ID_3)) {
		EARLY_ERROR("%s: version = 0x%x, id = 0x%x\n", __func__, version, id);
		panic();
	}

	check_reset_error();

	return BSEC_OK;
}

/*
 * bsec_shadow_register: copy SAFMEM OTP to BSEC data.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
static uint32_t bsec_shadow_register(uint32_t otp)
{
	uint32_t result;
	uint32_t i;
	bool value;

	result = bsec_read_sr_lock(otp, &value);
	if (result != BSEC_OK) {
		WARN("BSEC: %u Sticky-read bit read Error %u\n", otp, result);
	} else if (value) {
		VERBOSE("BSEC: OTP %u is locked and will not be refreshed\n", otp);
	}

	for (i = 0U; i < MAX_NB_TRIES; i++) {
		mmio_write_32(BSEC_BASE + BSEC_OTPCR, otp);

		poll_otp_status_busy();

		result = check_read_error(otp);
		if (result != BSEC_RETRY) {
			break;
		}
	}

	return result;
}

/*
 * bsec_write_otp: write a value in shadow OTP.
 * val: value to program.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_write_otp(uint32_t val, uint32_t otp)
{
	bool state;
	uint32_t result;

	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	if (!is_fuse_shadowed(otp)) {
		return BSEC_ERROR;
	}

	if (is_bsec_write_locked()) {
		return BSEC_WRITE_LOCKED;
	}

	result = bsec_read_sw_lock(otp, &state);
	if (result != BSEC_OK) {
		WARN("Shadow register is SW locked\n");
		return result;
	}

	mmio_write_32(BSEC_BASE + BSEC_FVR(otp), val);

	return BSEC_OK;
}

/*
 * bsec_program_otp: program a bit in SAFMEM after the prog.
 *	The OTP data is not refreshed.
 * val: value to program.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_program_otp(uint32_t val, uint32_t otp)
{
	uint32_t result;
	uint32_t i;
	bool value;

	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	if (is_bsec_write_locked() == true) {
		return BSEC_WRITE_LOCKED;
	}

	result = bsec_read_sp_lock(otp, &value);
	if (result != BSEC_OK) {
		WARN("BSEC: %u Sticky-prog bit read Error %u\n", otp, result);
	} else if (value) {
		WARN("BSEC: OTP locked, prog will be ignored\n");
		return BSEC_WRITE_LOCKED;
	}

	mmio_write_32(BSEC_BASE + BSEC_WDR, val);

	for (i = 0U; i < MAX_NB_TRIES; i++) {
		mmio_write_32(BSEC_BASE + BSEC_OTPCR, otp | BSEC_OTPCR_PROG);

		poll_otp_status_busy();

		result = check_program_error(otp);
		if (result != BSEC_RETRY) {
			break;
		}
	}

	return result;
}

/*
 * bsec_read_debug_conf: read debug configuration.
 */
uint32_t bsec_read_debug_conf(void)
{
	return mmio_read_32(BSEC_BASE + BSEC_DENR);
}

static uint32_t bsec_lock_register_set(uint32_t offset, uint32_t mask)
{
	uint32_t value = mmio_read_32(BSEC_BASE + offset);

	/* The lock is already set */
	if ((value & mask) != 0U) {
		return BSEC_OK;
	}

	if (is_bsec_write_locked()) {
		return BSEC_WRITE_LOCKED;
	}

	value |= mask;

	mmio_write_32(BSEC_BASE + offset, value);

	return BSEC_OK;
}

static bool bsec_lock_register_get(uint32_t offset, uint32_t mask)
{
	uint32_t value = mmio_read_32(BSEC_BASE + offset);

	return (value & mask) != 0U;
}

/*
 * bsec_set_sr_lock: set shadow-read lock.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_set_sr_lock(uint32_t otp)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	return bsec_lock_register_set(BSEC_SRLOCK(bank), otp_mask);
}

/*
 * bsec_read_sr_lock: read shadow-read lock.
 * otp: OTP number.
 * value: read value (true or false).
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_read_sr_lock(uint32_t otp, bool *value)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	assert(value != NULL);
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	*value = bsec_lock_register_get(BSEC_SRLOCK(bank), otp_mask);

	return BSEC_OK;
}

/*
 * bsec_set_sw_lock: set shadow-write lock.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_set_sw_lock(uint32_t otp)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	return bsec_lock_register_set(BSEC_SWLOCK(bank), otp_mask);
}

/*
 * bsec_read_sw_lock: read shadow-write lock.
 * otp: OTP number.
 * value: read value (true or false).
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_read_sw_lock(uint32_t otp, bool *value)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	assert(value != NULL);
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	*value = bsec_lock_register_get(BSEC_SWLOCK(bank), otp_mask);

	return BSEC_OK;
}

/*
 * bsec_set_sp_lock: set shadow-program lock.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_set_sp_lock(uint32_t otp)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	return bsec_lock_register_set(BSEC_SPLOCK(bank), otp_mask);
}

/*
 * bsec_read_sp_lock: read shadow-program lock.
 * otp: OTP number.
 * value: read value (true or false).
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_read_sp_lock(uint32_t otp, bool *value)
{
	uint32_t bank = otp_bank(otp);
	uint32_t otp_mask = otp_bit_mask(otp);

	assert(value != NULL);
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	*value = bsec_lock_register_get(BSEC_SPLOCK(bank), otp_mask);

	return BSEC_OK;
}

/*
 * bsec_get_secure_state: read state in BSEC status register.
 * return: secure state
 */
uint32_t bsec_get_secure_state(void)
{
	uint32_t state = BSEC_STATE_INVALID;
	uint32_t status = bsec_get_status();
	uint32_t bsec_sr = mmio_read_32(BSEC_BASE + BSEC_SR);

	if ((status & BSEC_OTPSR_INIT_DONE) == BSEC_OTPSR_INIT_DONE) {
		/* NVSTATE is only valid if INIT_DONE */
		uint32_t nvstates = (bsec_sr & BSEC_SR_NVSTATE_MASK) >> BSEC_SR_NVSTATE_SHIFT;

		if (nvstates == BSEC_SR_NVSTATE_OPEN) {
			state = BSEC_STATE_SEC_OPEN;
		} else if (nvstates == BSEC_SR_NVSTATE_CLOSED) {
			state = BSEC_STATE_SEC_CLOSED;
		} else {
			VERBOSE("%s nvstates = %u\n", __func__, nvstates);
		}
	}

	return state;
}

/*
 * bsec_shadow_read_otp: Load OTP from SAFMEM and provide its value
 * val: read value.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_shadow_read_otp(uint32_t *val, uint32_t otp)
{
	assert(val != NULL);
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	*val = 0U;

	if (is_bsec_write_locked()) {
		return BSEC_WRITE_LOCKED;
	}

	if (!is_fuse_shadowed(otp)) {
		uint32_t result = bsec_shadow_register(otp);

		if (result != BSEC_OK) {
			ERROR("BSEC: %u Shadowing Error %u\n", otp, result);
			return result;
		}
	}

	*val = mmio_read_32(BSEC_BASE + BSEC_FVR(otp));

	return BSEC_OK;
}

/*
 * bsec_read_otp: read an OTP data value.
 * val: read value.
 * otp: OTP number.
 * return value: BSEC_OK if no error.
 */
uint32_t bsec_read_otp(uint32_t *val, uint32_t otp)
{
	assert(val != NULL);
	if (otp > STM32MP2_OTP_MAX_ID) {
		panic();
	}

	return bsec_shadow_read_otp(val, otp);
}
