/* Copyright 2017 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

/* OTP implementation for STM32F411 */

#include "common.h"
#include "console.h"
#include "flash.h"
#include "otp.h"
#include "registers.h"
#include "util.h"

/*
 * OTP is only used for saving the USB serial number.
 */
#ifdef CONFIG_SERIALNO_LEN
/* Which block to use */
#define OTP_SERIAL_BLOCK 0
#define OTP_SERIAL_ADDR \
	REG32_ADDR(STM32_OTP_BLOCK_DATA(OTP_SERIAL_BLOCK, 0))

/* Number of word used in the block */
#define OTP_SERIAL_BLOCK_SIZE (CONFIG_SERIALNO_LEN / sizeof(uint32_t))
BUILD_ASSERT(CONFIG_SERIALNO_LEN % sizeof(uint32_t) == 0);
BUILD_ASSERT(OTP_SERIAL_BLOCK_SIZE < STM32_OTP_BLOCK_SIZE);

/*
 * Write an OTP block
 *
 * @param block         block to write.
 * @param size          Number of words to write.
 * @param data          Destination buffer for data.
 */
static int otp_write(uint8_t block, int size, const char *data)
{
	if (block >= STM32_OTP_BLOCK_NB)
		return EC_ERROR_PARAM1;
	if (size >= STM32_OTP_BLOCK_SIZE)
		return EC_ERROR_PARAM2;
	return flash_physical_write(STM32_OTP_BLOCK_DATA(block, 0) -
				    CONFIG_PROGRAM_MEMORY_BASE,
				    size * sizeof(uint32_t), data);
}

/*
 * Check if an OTP block is protected.
 *
 * @param block        protected block.
 * @return non-zero if that block is read only.
 */
static int otp_get_protect(uint8_t block)
{
	uint32_t lock;

	lock = REG32(STM32_OTP_LOCK(block));
	return ((lock & STM32_OPT_LOCK_MASK(block)) == 0);
}

/*
 * Set a particular OTP block as read only.
 *
 * @param block        block to protect.
 */
static int otp_set_protect(uint8_t block)
{
	int rv;
	uint32_t lock;

	if (otp_get_protect(block))
		return EC_SUCCESS;

	lock = REG32(STM32_OTP_LOCK(block));
	lock &= ~STM32_OPT_LOCK_MASK(block);
	rv = flash_physical_write(STM32_OTP_LOCK(block) -
				  CONFIG_PROGRAM_MEMORY_BASE,
				  sizeof(uint32_t), (char *)&lock);
	if (rv)
		return rv;
	else
		return EC_SUCCESS;
}

const char *otp_read_serial(void)
{
	int i;

	for (i = 0; i < OTP_SERIAL_BLOCK_SIZE; i++) {
		if (OTP_SERIAL_ADDR[i] != -1)
			return (char *)OTP_SERIAL_ADDR;
	}
	return NULL;
}

int otp_write_serial(const char *serialno)
{
	int i, ret;
	char otp_serial[CONFIG_SERIALNO_LEN];

	if (otp_get_protect(OTP_SERIAL_BLOCK))
		return EC_ERROR_ACCESS_DENIED;

	/* Copy in serialno. */
	for (i = 0; i < CONFIG_SERIALNO_LEN - 1; i++) {
		otp_serial[i] = serialno[i];
		if (serialno[i] == 0)
			break;
	}
	for (; i < CONFIG_SERIALNO_LEN; i++)
		otp_serial[i] = 0;

	ret = otp_write(OTP_SERIAL_BLOCK, OTP_SERIAL_BLOCK_SIZE, otp_serial);
	if (ret == EC_SUCCESS)
		return otp_set_protect(OTP_SERIAL_BLOCK);
	else
		return ret;
}
#endif
