/*
 * Copyright (c) 2019 Aurelien Jarno
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/device.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/init.h>
#include <soc.h>
#include <string.h>

static uint8_t sam_uid[16];

ssize_t z_impl_hwinfo_get_device_id(uint8_t *buffer, size_t length)
{
	if (length > sizeof(sam_uid)) {
		length = sizeof(sam_uid);
	}

	memcpy(buffer, sam_uid, length);

	return length;
}

/* On the Atmel SAM SoC series, the device id is located in the flash
 * controller. The controller can either present the flash area containing
 * the code, the unique identifier or the user signature area at the flash
 * location. Therefore the function reading the device id must be executed
 * from RAM with the interrupts disabled. To avoid executing this complex
 * code each time the device id is requested, we do this at boot time at save
 * the 128-bit value into RAM.
 */
__ramfunc static void hwinfo_sam_read_device_id(void)
{
	Efc *efc = (Efc *)DT_REG_ADDR(DT_INST(0, atmel_sam_flash_controller));
	uint8_t *flash = (uint8_t *)CONFIG_FLASH_BASE_ADDRESS;
	int i;

	/* Switch the flash controller to the unique identifier area. The flash
	 * is not available anymore, hence we have to wait for it to be *NOT*
	 * ready.
	 */
	efc->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | EEFC_FCR_FCMD_STUI;
	while ((efc->EEFC_FSR & EEFC_FSR_FRDY) == EEFC_FSR_FRDY) {
		/* Wait */
	}

	/* Copy the 128-bit unique ID. We cannot use memcpy as it would
	 * execute code from flash.
	 */
	for (i = 0; i < sizeof(sam_uid); i++) {
		sam_uid[i] = flash[i];
	}

	/* Switch back the controller to the flash area and wait for it to
	 * be ready.
	 */
	efc->EEFC_FCR = EEFC_FCR_FKEY_PASSWD | EEFC_FCR_FCMD_SPUI;
	while ((efc->EEFC_FSR & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) {
		/* Wait */
	}
}

static int hwinfo_sam_init(void)
{
	Efc *efc = (Efc *)DT_REG_ADDR(DT_INST(0, atmel_sam_flash_controller));
	uint32_t fmr;
	unsigned int key;

	/* Disable interrupts. */
	key = irq_lock();

	/* Disable code loop optimization and sequential code optimization. */
	fmr = efc->EEFC_FMR;

#ifndef CONFIG_SOC_SERIES_SAM3X
	efc->EEFC_FMR = (fmr & (~EEFC_FMR_CLOE)) | EEFC_FMR_SCOD;
#else
	/* SAM3x does not have loop optimization (EEFC_FMR_CLOE) */
	efc->EEFC_FMR |= EEFC_FMR_SCOD;
#endif

	/* Read the device ID using code in RAM */
	hwinfo_sam_read_device_id();

	/* Restore code optimization settings. */
	efc->EEFC_FMR = fmr;

	/* Re-enable interrupts */
	irq_unlock(key);

	return 0;
}

SYS_INIT(hwinfo_sam_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);
