/*
 * Copyright (c) 2021 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "retained.h"

#include <stdint.h>
#include <string.h>

#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>

#include <hal/nrf_power.h>

/* nRF52 RAM (really, RAM AHB slaves) are partitioned as:
 * * Up to 8 blocks of two 4 KiBy byte "small" sections
 * * A 9th block of with 32 KiBy "large" sections
 *
 * At time of writing the maximum number of large sections is 6, all
 * within the first large block.  Theoretically there could be more
 * sections in the 9th block, and possibly more blocks.
 */

/* Inclusive address of RAM start */
#define SRAM_BEGIN (uintptr_t)DT_REG_ADDR(DT_NODELABEL(sram0))

/* Exclusive address of RAM end */
#define SRAM_END (SRAM_BEGIN + (uintptr_t)DT_REG_SIZE(DT_NODELABEL(sram0)))

/* Size of a controllable RAM section in the small blocks */
#define SMALL_SECTION_SIZE 4096

/* Number of controllable RAM sections in each of the lower blocks */
#define SMALL_SECTIONS_PER_BLOCK 2

/* Span of a small block */
#define SMALL_BLOCK_SIZE (SMALL_SECTIONS_PER_BLOCK * SMALL_SECTION_SIZE)

/* Number of small blocks */
#define SMALL_BLOCK_COUNT 8

/* Span of the SRAM area covered by small sections */
#define SMALL_SECTION_SPAN (SMALL_BLOCK_COUNT * SMALL_BLOCK_SIZE)

/* Inclusive address of the RAM range covered by large sections */
#define LARGE_SECTION_BEGIN (SRAM_BEGIN + SMALL_SECTION_SPAN)

/* Size of a controllable RAM section in large blocks */
#define LARGE_SECTION_SIZE 32768

/* Set or clear RAM retention in SYSTEM_OFF for the provided object.
 *
 * @note This only works for nRF52 with the POWER module.  The other
 * Nordic chips use a different low-level API, which is not currently
 * used by this function.
 *
 * @param ptr pointer to the start of the retainable object
 *
 * @param len length of the retainable object
 *
 * @param enable true to enable retention, false to clear retention
 */
static int ram_range_retain(const void *ptr,
			    size_t len,
			    bool enable)
{
	uintptr_t addr = (uintptr_t)ptr;
	uintptr_t addr_end = addr + len;

	/* Error if the provided range is empty or doesn't lie
	 * entirely within the SRAM address space.
	 */
	if ((len == 0U)
	    || (addr < SRAM_BEGIN)
	    || (addr > (SRAM_END - len))) {
		return -EINVAL;
	}

	/* Iterate over each section covered by the range, setting the
	 * corresponding RAM OFF retention bit in the parent block.
	 */
	do {
		uintptr_t block_base = SRAM_BEGIN;
		uint32_t section_size = SMALL_SECTION_SIZE;
		uint32_t sections_per_block = SMALL_SECTIONS_PER_BLOCK;
		bool is_large = (addr >= LARGE_SECTION_BEGIN);
		uint8_t block = 0;

		if (is_large) {
			block = 8;
			block_base = LARGE_SECTION_BEGIN;
			section_size = LARGE_SECTION_SIZE;

			/* RAM[x] supports only 16 sections, each its own bit
			 * for POWER (0..15) and RETENTION (16..31).  We don't
			 * know directly how many sections are present, so
			 * assume they all are; the true limit will be
			 * determined by the SRAM size.
			 */
			sections_per_block = 16;
		}

		uint32_t section = (addr - block_base) / section_size;

		if (section >= sections_per_block) {
			block += section / sections_per_block;
			section %= sections_per_block;
		}

		uint32_t section_mask =
			(POWER_RAM_POWERSET_S0RETENTION_On
					 << (section + POWER_RAM_POWERSET_S0RETENTION_Pos));

		if (enable) {
			nrf_power_rampower_mask_on(NRF_POWER, block, section_mask);
		} else {
			nrf_power_rampower_mask_off(NRF_POWER, block, section_mask);
		}

		/* Move to the first address in the next section. */
		addr += section_size - (addr % section_size);
	} while (addr < addr_end);

	return 0;
}

/* Retained data must be defined in a no-init section to prevent the C
 * runtime initialization from zeroing it before anybody can see it.
 */
__noinit struct retained_data retained;

#define RETAINED_CRC_OFFSET offsetof(struct retained_data, crc)
#define RETAINED_CHECKED_SIZE (RETAINED_CRC_OFFSET + sizeof(retained.crc))

bool retained_validate(void)
{
	/* The residue of a CRC is what you get from the CRC over the
	 * message catenated with its CRC.  This is the post-final-xor
	 * residue for CRC-32 (CRC-32/ISO-HDLC) which Zephyr calls
	 * crc32_ieee.
	 */
	const uint32_t residue = 0x2144df1c;
	uint32_t crc = crc32_ieee((const uint8_t *)&retained,
				  RETAINED_CHECKED_SIZE);
	bool valid = (crc == residue);

	/* If the CRC isn't valid, reset the retained data. */
	if (!valid) {
		memset(&retained, 0, sizeof(retained));
	}

	/* Reset to accrue runtime from this session. */
	retained.uptime_latest = 0;

	/* Reconfigure to retain the state during system off, regardless of
	 * whether validation succeeded.  Although these values can sometimes
	 * be observed to be preserved across System OFF, the product
	 * specification states they are not retained in that situation, and
	 * that can also be observed.
	 */
	(void)ram_range_retain(&retained, RETAINED_CHECKED_SIZE, true);

	return valid;
}

void retained_update(void)
{
	uint64_t now = k_uptime_ticks();

	retained.uptime_sum += (now - retained.uptime_latest);
	retained.uptime_latest = now;

	uint32_t crc = crc32_ieee((const uint8_t *)&retained,
				  RETAINED_CRC_OFFSET);

	retained.crc = sys_cpu_to_le32(crc);
}
