/*
 * Copyright (c) 2018, ARM Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <assert.h>
#include <stddef.h>

#include <common/debug.h>
#include <drivers/arm/tzc380.h>
#include <lib/mmio.h>

struct tzc380_instance {
	uintptr_t base;
	uint8_t addr_width;
	uint8_t num_regions;
};

struct tzc380_instance tzc380;

static unsigned int tzc380_read_build_config(uintptr_t base)
{
	return mmio_read_32(base + TZC380_CONFIGURATION_OFF);
}

static void tzc380_write_action(uintptr_t base, tzc_action_t action)
{
	mmio_write_32(base + ACTION_OFF, action);
}

static void tzc380_write_region_base_low(uintptr_t base, unsigned int region,
				      unsigned int val)
{
	mmio_write_32(base + REGION_SETUP_LOW_OFF(region), val);
}

static void tzc380_write_region_base_high(uintptr_t base, unsigned int region,
				       unsigned int val)
{
	mmio_write_32(base + REGION_SETUP_HIGH_OFF(region), val);
}

static void tzc380_write_region_attributes(uintptr_t base, unsigned int region,
					unsigned int val)
{
	mmio_write_32(base + REGION_ATTRIBUTES_OFF(region), val);
}

void tzc380_init(uintptr_t base)
{
	unsigned int tzc_build;

	assert(base != 0U);
	tzc380.base = base;

	/* Save values we will use later. */
	tzc_build = tzc380_read_build_config(tzc380.base);
	tzc380.addr_width  = ((tzc_build >> BUILD_CONFIG_AW_SHIFT) &
			      BUILD_CONFIG_AW_MASK) + 1;
	tzc380.num_regions = ((tzc_build >> BUILD_CONFIG_NR_SHIFT) &
			       BUILD_CONFIG_NR_MASK) + 1;
}

static uint32_t addr_low(uintptr_t addr)
{
	return (uint32_t)addr;
}

static uint32_t addr_high(uintptr_t addr __unused)
{
#if (UINTPTR_MAX == UINT64_MAX)
	return addr >> 32;
#else
	return 0;
#endif
}

/*
 * `tzc380_configure_region` is used to program regions into the TrustZone
 * controller.
 */
void tzc380_configure_region(uint8_t region, uintptr_t region_base, unsigned int attr)
{
	assert(tzc380.base != 0U);

	assert(region < tzc380.num_regions);

	tzc380_write_region_base_low(tzc380.base, region, addr_low(region_base));
	tzc380_write_region_base_high(tzc380.base, region, addr_high(region_base));
	tzc380_write_region_attributes(tzc380.base, region, attr);
}

void tzc380_set_action(tzc_action_t action)
{
	assert(tzc380.base != 0U);

	/*
	 * - Currently no handler is provided to trap an error via interrupt
	 *   or exception.
	 * - The interrupt action has not been tested.
	 */
	tzc380_write_action(tzc380.base, action);
}
