blob: cef1379660b957796834ddd740bf432786c49e03 [file] [log] [blame]
/*
* Copyright 2022 The ChromiumOS Authors.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
LOG_MODULE_REGISTER(soc_it8xxx2_ilm, CONFIG_LOG_DEFAULT_LEVEL);
/*
* Instruction Local Memory (ILM) support for IT8xxx2.
*
* IT8xxx2 allows 4-kilobyte blocks of RAM be configured individually as either Instruction- or
* Data Local Memory (ILM or DLM). Addresses from which instructions will be fetched by the CPU
* *must* be in the Flash memory space: it is not permitted to execute from RAM addresses, only
* through ILM mappings into RAM.
*
* When a RAM block is configured as ILM, accesses to addresses matching the corresponding Scratch
* SRAM address register (SCARn{H,M,L}) are redirected to the corresponding ILM block in RAM.
* If SCAR0 (corresponding to ILM0) has the value 0x8021532 and ILM0 is enabled, then instruction
* fetches from the memory range 0x8021532..0x8022532 will be redirected to physical addresses
* 0x80100000..0x80101000 (the first 4k block of RAM).
*
* Instruction fetch from Flash is normally cacheable, but configuring ILM for a region makes that
* address range non-cacheable (which is appropriate because Flash has high latency but RAM is
* essentially the same speed as cache).
*/
extern const uint8_t __ilm_flash_start[];
extern const uint8_t __ilm_flash_end[];
extern uint8_t __ilm_ram_start[];
extern uint8_t __ilm_ram_end[];
#define ILM_BLOCK_SIZE 0x1000
BUILD_ASSERT((ILM_BLOCK_SIZE & (ILM_BLOCK_SIZE - 1)) == 0, "ILM_BLOCK_SIZE must be a power of two");
#define FLASH_BASE CONFIG_FLASH_BASE_ADDRESS
#define RAM_BASE CONFIG_SRAM_BASE_ADDRESS
#define RAM_SIZE CONFIG_SRAM_SIZE
#define ILM_NODE DT_NODELABEL(ilm)
#define SCARH_ENABLE BIT(3)
#define SCARH_ADDR_BIT19 BIT(7)
/*
* SCAR registers contain 20-bit addresses in three registers, with one set
* of SCAR registers for each ILM block that may be configured.
*/
struct scar_reg {
/* Bits 0..7 of address; SCARnL */
uint8_t l;
/* Bits 8..15 of address; SCARnM */
uint8_t m;
/* Bits 16..18 and 19 of address, plus the enable bit for the entire SCAR; SCARnH */
uint8_t h;
};
struct ilm_config {
/* Points to the contiguous set of RVILMCRn registers */
volatile uint8_t *ilm_control_regs;
volatile struct scar_reg *scar_regs[15];
};
bool it8xxx2_is_ilm_configured(void)
{
return device_is_ready(DEVICE_DT_GET(ILM_NODE));
}
static bool __maybe_unused is_block_aligned(const void *const p)
{
return ((uintptr_t)p & (ILM_BLOCK_SIZE - 1)) == 0;
}
static int it8xxx2_configure_ilm_block(const struct ilm_config *const config, void *ram_addr,
const void *flash_addr, const size_t copy_sz)
{
if ((uintptr_t)ram_addr < RAM_BASE) {
return -EFAULT; /* Not in RAM */
}
const int dirmap_index = ((uintptr_t)ram_addr - RAM_BASE) / ILM_BLOCK_SIZE;
if (dirmap_index >= ARRAY_SIZE(config->scar_regs)) {
return -EFAULT; /* Past the end of RAM */
}
BUILD_ASSERT((FLASH_BASE & GENMASK(19, 0)) == 0,
"Flash is assumed to be aligned to SCAR register width");
if (((uintptr_t)flash_addr - FLASH_BASE) & ~GENMASK(19, 0)) {
return -EFAULT; /* Address doesn't fit in the SCAR */
}
if (!is_block_aligned(flash_addr)) {
/* Bits 0..11 of SCAR can be programmed but ILM only works if they're zero */
return -EFAULT;
}
LOG_DBG("Enabling ILM%d %p -> %p, copy %d", dirmap_index, flash_addr, ram_addr, copy_sz);
volatile uint8_t *const rvilmcr = &config->ilm_control_regs[dirmap_index / 8];
volatile struct scar_reg *const scar = config->scar_regs[dirmap_index];
const int cr_mask = BIT(dirmap_index % 8);
int irq_key = irq_lock();
/* Invalidate any possibly-existing mapping */
*rvilmcr &= ~cr_mask;
/* Ensure scratch RAM for block data access is enabled */
scar->h = SCARH_ENABLE;
/* Copy block contents from flash into RAM */
memcpy(ram_addr, flash_addr, copy_sz);
/* Enable ILM block */
*rvilmcr |= cr_mask;
/* Program SCAR */
scar->l = (uintptr_t)flash_addr & GENMASK(7, 0);
scar->m = ((uintptr_t)flash_addr & GENMASK(15, 8)) >> 8;
uint8_t scarh_value = ((uintptr_t)flash_addr & GENMASK(18, 16)) >> 16;
if ((uintptr_t)flash_addr & BIT(19)) {
scarh_value |= SCARH_ADDR_BIT19;
}
scar->h = scarh_value;
irq_unlock(irq_key);
return 0;
}
static int it8xxx2_ilm_init(const struct device *dev)
{
/* Invariants enforced by the linker script */
__ASSERT(is_block_aligned(__ilm_ram_start),
"ILM physical base address (%p) must be 4k-aligned", __ilm_ram_start);
__ASSERT(is_block_aligned(__ilm_flash_start),
"ILM flash base address (%p) must be 4k-aligned", __ilm_flash_start);
__ASSERT_NO_MSG((uintptr_t)__ilm_ram_end >= (uintptr_t)__ilm_ram_start &&
(uintptr_t)__ilm_flash_end >= (uintptr_t)__ilm_flash_start);
LOG_DBG("ILM init %p-%p -> %p-%p", __ilm_flash_start, __ilm_flash_end, __ilm_ram_start,
__ilm_ram_end);
for (uintptr_t block_base = (uintptr_t)__ilm_ram_start;
block_base < (uintptr_t)__ilm_ram_end; block_base += ILM_BLOCK_SIZE) {
uintptr_t flash_base =
(uintptr_t)__ilm_flash_start + (block_base - (uintptr_t)__ilm_ram_start);
/*
* Part of the target RAM block might be used for non-code data; avoid overwriting
* it by only copying as much data as the ILM flash region contains.
*/
size_t used_size = MIN((uintptr_t)__ilm_flash_end - flash_base, ILM_BLOCK_SIZE);
int rv = it8xxx2_configure_ilm_block(dev->config, (void *)block_base,
(const void *)flash_base, used_size);
if (rv) {
LOG_ERR("Unable to configure ILM block %p: %d", (void *)flash_base, rv);
return rv;
}
}
return 0;
}
#define SCAR_REG(n) (volatile struct scar_reg *)DT_REG_ADDR_BY_IDX(ILM_NODE, n)
static const struct ilm_config ilm_config = {
.ilm_control_regs = (volatile uint8_t *)DT_REG_ADDR_BY_IDX(ILM_NODE, 0),
.scar_regs = {
SCAR_REG(1),
SCAR_REG(2),
SCAR_REG(3),
SCAR_REG(4),
SCAR_REG(5),
SCAR_REG(6),
SCAR_REG(7),
SCAR_REG(8),
SCAR_REG(9),
SCAR_REG(10),
SCAR_REG(11),
SCAR_REG(12),
SCAR_REG(13),
SCAR_REG(14),
SCAR_REG(15),
}};
#if defined(CONFIG_SOC_IT81202_CX) || defined(CONFIG_SOC_IT81302_CX)
BUILD_ASSERT(ARRAY_SIZE(ilm_config.scar_regs) * ILM_BLOCK_SIZE == KB(60),
"Maximum number of SCAR registers defined for 60k RAM size");
#else
BUILD_ASSERT(ARRAY_SIZE(ilm_config.scar_regs) * ILM_BLOCK_SIZE == KB(RAM_SIZE),
"Wrong number of SCAR registers defined for RAM size");
#endif
BUILD_ASSERT(ARRAY_SIZE(ilm_config.scar_regs) <= DT_REG_SIZE_BY_IDX(ILM_NODE, 0) * 8,
"Size of ILM control register block is too small for number of SCARs");
DEVICE_DT_DEFINE(ILM_NODE, &it8xxx2_ilm_init, NULL, NULL, &ilm_config, PRE_KERNEL_1, 0, NULL);