519 lines
15 KiB
C
519 lines
15 KiB
C
/*
|
|
* Copyright (c) 2021 ITE Corporation. All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT ite_it8xxx2_flash_controller
|
|
#define SOC_NV_FLASH_NODE DT_INST(0, soc_nv_flash)
|
|
|
|
#define FLASH_WRITE_BLK_SZ DT_PROP(SOC_NV_FLASH_NODE, write_block_size)
|
|
#define FLASH_ERASE_BLK_SZ DT_PROP(SOC_NV_FLASH_NODE, erase_block_size)
|
|
|
|
#include <string.h>
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/flash.h>
|
|
#include <zephyr/init.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/linker/linker-defs.h>
|
|
|
|
#include <ilm.h>
|
|
#include <soc.h>
|
|
|
|
#define LOG_LEVEL CONFIG_FLASH_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(flash_ite_it8xxx2);
|
|
|
|
#define FLASH_IT8XXX2_REG_BASE \
|
|
((struct smfi_it8xxx2_regs *)DT_INST_REG_ADDR(0))
|
|
|
|
struct flash_it8xxx2_dev_data {
|
|
struct k_sem sem;
|
|
};
|
|
|
|
/*
|
|
* One page program instruction allows maximum 256 bytes (a page) of data
|
|
* to be programmed.
|
|
*/
|
|
#define CHIP_FLASH_WRITE_PAGE_MAX_SIZE 256
|
|
/* Program is run directly from storage */
|
|
#define CHIP_MAPPED_STORAGE_BASE DT_REG_ADDR(DT_NODELABEL(flash0))
|
|
/* flash size */
|
|
#define CHIP_FLASH_SIZE_BYTES DT_REG_SIZE(DT_NODELABEL(flash0))
|
|
/* protect bank size */
|
|
#define CHIP_FLASH_BANK_SIZE 0x00001000
|
|
|
|
/*
|
|
* This is the block size of the ILM on the it8xxx2 chip.
|
|
* The ILM for static code cache, CPU fetch instruction from
|
|
* ILM(ILM -> CPU)instead of flash(flash -> I-Cache -> CPU) if enabled.
|
|
*/
|
|
#define IT8XXX2_ILM_BLOCK_SIZE 0x00001000
|
|
|
|
/* page program command */
|
|
#define FLASH_CMD_PAGE_WRITE 0x2
|
|
/* sector erase command (erase size is 4KB) */
|
|
#define FLASH_CMD_SECTOR_ERASE 0x20
|
|
/* command for flash write */
|
|
#define FLASH_CMD_WRITE FLASH_CMD_PAGE_WRITE
|
|
/* Write status register */
|
|
#define FLASH_CMD_WRSR 0x01
|
|
/* Write disable */
|
|
#define FLASH_CMD_WRDI 0x04
|
|
/* Write enable */
|
|
#define FLASH_CMD_WREN 0x06
|
|
/* Read status register */
|
|
#define FLASH_CMD_RS 0x05
|
|
|
|
/* Set FSCE# as high level by writing 0 to address xfff_fe00h */
|
|
#define FLASH_FSCE_HIGH_ADDRESS 0x0FFFFE00
|
|
/* Set FSCE# as low level by writing data to address xfff_fd00h */
|
|
#define FLASH_FSCE_LOW_ADDRESS 0x0FFFFD00
|
|
|
|
enum flash_status_mask {
|
|
FLASH_SR_NO_BUSY = 0,
|
|
/* Internal write operation is in progress */
|
|
FLASH_SR_BUSY = 0x01,
|
|
/* Device is memory Write enabled */
|
|
FLASH_SR_WEL = 0x02,
|
|
|
|
FLASH_SR_ALL = (FLASH_SR_BUSY | FLASH_SR_WEL),
|
|
};
|
|
|
|
enum flash_transaction_cmd {
|
|
CMD_CONTINUE,
|
|
CMD_END,
|
|
};
|
|
|
|
static const struct flash_parameters flash_it8xxx2_parameters = {
|
|
.write_block_size = FLASH_WRITE_BLK_SZ,
|
|
.erase_value = 0xff,
|
|
};
|
|
|
|
void __soc_ram_code ramcode_reset_i_cache(void)
|
|
{
|
|
struct gctrl_it8xxx2_regs *const gctrl_regs = GCTRL_IT8XXX2_REGS_BASE;
|
|
|
|
/* I-Cache tag sram reset */
|
|
gctrl_regs->GCTRL_MCCR |= IT8XXX2_GCTRL_ICACHE_RESET;
|
|
/* Make sure the I-Cache is reset */
|
|
__asm__ volatile ("fence.i" ::: "memory");
|
|
|
|
gctrl_regs->GCTRL_MCCR &= ~IT8XXX2_GCTRL_ICACHE_RESET;
|
|
__asm__ volatile ("fence.i" ::: "memory");
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_follow_mode(void)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
/*
|
|
* ECINDAR3-0 are EC-indirect memory address registers.
|
|
*
|
|
* Enter follow mode by writing 0xf to low nibble of ECINDAR3 register,
|
|
* and set high nibble as 0x4 to select internal flash.
|
|
*/
|
|
flash_regs->SMFI_ECINDAR3 = (EC_INDIRECT_READ_INTERNAL_FLASH |
|
|
((FLASH_FSCE_HIGH_ADDRESS >> 24) & GENMASK(3, 0)));
|
|
|
|
/* Set FSCE# as high level by writing 0 to address xfff_fe00h */
|
|
flash_regs->SMFI_ECINDAR2 = (FLASH_FSCE_HIGH_ADDRESS >> 16) & GENMASK(7, 0);
|
|
flash_regs->SMFI_ECINDAR1 = (FLASH_FSCE_HIGH_ADDRESS >> 8) & GENMASK(7, 0);
|
|
flash_regs->SMFI_ECINDAR0 = FLASH_FSCE_HIGH_ADDRESS & GENMASK(7, 0);
|
|
|
|
/* Writing 0 to EC-indirect memory data register */
|
|
flash_regs->SMFI_ECINDDR = 0x00;
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_follow_mode_exit(void)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
|
|
/* Exit follow mode, and keep the setting of selecting internal flash */
|
|
flash_regs->SMFI_ECINDAR3 = EC_INDIRECT_READ_INTERNAL_FLASH;
|
|
flash_regs->SMFI_ECINDAR2 = 0x00;
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_fsce_high(void)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
struct gctrl_it8xxx2_regs *const gctrl_regs = GCTRL_IT8XXX2_REGS_BASE;
|
|
|
|
/* FSCE# high level */
|
|
flash_regs->SMFI_ECINDAR1 = (FLASH_FSCE_HIGH_ADDRESS >> 8) & GENMASK(7, 0);
|
|
|
|
/*
|
|
* A short delay (15~30 us) before #CS be driven high to ensure
|
|
* last byte has been latched in.
|
|
*
|
|
* For a loop that writing 0 to WNCKR register for N times, the delay
|
|
* value will be: ((N-1) / 65.536 kHz) to (N / 65.536 kHz).
|
|
* So we perform 2 consecutive writes to WNCKR here to ensure the
|
|
* minimum delay is 15us.
|
|
*/
|
|
gctrl_regs->GCTRL_WNCKR = 0;
|
|
gctrl_regs->GCTRL_WNCKR = 0;
|
|
|
|
/* Writing 0 to EC-indirect memory data register */
|
|
flash_regs->SMFI_ECINDDR = 0x00;
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_write_dat(uint8_t wdata)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
|
|
/* Write data to FMOSI */
|
|
flash_regs->SMFI_ECINDDR = wdata;
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_transaction(int wlen, uint8_t *wbuf, int rlen, uint8_t *rbuf,
|
|
enum flash_transaction_cmd cmd_end)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
int i;
|
|
|
|
/* FSCE# with low level */
|
|
flash_regs->SMFI_ECINDAR1 = (FLASH_FSCE_LOW_ADDRESS >> 8) & GENMASK(7, 0);
|
|
/* Write data to FMOSI */
|
|
for (i = 0; i < wlen; i++) {
|
|
flash_regs->SMFI_ECINDDR = wbuf[i];
|
|
}
|
|
/* Read data from FMISO */
|
|
for (i = 0; i < rlen; i++) {
|
|
rbuf[i] = flash_regs->SMFI_ECINDDR;
|
|
}
|
|
/* FSCE# high level if transaction done */
|
|
if (cmd_end == CMD_END) {
|
|
ramcode_flash_fsce_high();
|
|
}
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_cmd_read_status(enum flash_status_mask mask,
|
|
enum flash_status_mask target)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
uint8_t cmd_rs[] = {FLASH_CMD_RS};
|
|
|
|
/* Send read status command */
|
|
ramcode_flash_transaction(sizeof(cmd_rs), cmd_rs, 0, NULL, CMD_CONTINUE);
|
|
|
|
/*
|
|
* We prefer no timeout here. We can always get the status
|
|
* we want, or wait for watchdog triggered to check
|
|
* e-flash's status instead of breaking loop.
|
|
* This will avoid fetching unknown instruction from e-flash
|
|
* and causing exception.
|
|
*/
|
|
while ((flash_regs->SMFI_ECINDDR & mask) != target) {
|
|
/* read status and check if it is we want. */
|
|
;
|
|
}
|
|
|
|
/* transaction done, drive #CS high */
|
|
ramcode_flash_fsce_high();
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_cmd_write_enable(void)
|
|
{
|
|
uint8_t cmd_we[] = {FLASH_CMD_WREN};
|
|
|
|
/* enter EC-indirect follow mode */
|
|
ramcode_flash_follow_mode();
|
|
/* send write enable command */
|
|
ramcode_flash_transaction(sizeof(cmd_we), cmd_we, 0, NULL, CMD_END);
|
|
/* read status and make sure busy bit cleared and write enabled. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_ALL, FLASH_SR_WEL);
|
|
/* exit EC-indirect follow mode */
|
|
ramcode_flash_follow_mode_exit();
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_cmd_write_disable(void)
|
|
{
|
|
uint8_t cmd_wd[] = {FLASH_CMD_WRDI};
|
|
|
|
/* enter EC-indirect follow mode */
|
|
ramcode_flash_follow_mode();
|
|
/* send write disable command */
|
|
ramcode_flash_transaction(sizeof(cmd_wd), cmd_wd, 0, NULL, CMD_END);
|
|
/* make sure busy bit cleared. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_ALL, FLASH_SR_NO_BUSY);
|
|
/* exit EC-indirect follow mode */
|
|
ramcode_flash_follow_mode_exit();
|
|
}
|
|
|
|
int __soc_ram_code ramcode_flash_verify(int addr, int size, const char *data)
|
|
{
|
|
int i;
|
|
uint8_t *wbuf = (uint8_t *)data;
|
|
uint8_t *flash = (uint8_t *)addr;
|
|
|
|
if (data == NULL) {
|
|
/* verify for erase */
|
|
for (i = 0; i < size; i++) {
|
|
if (flash[i] != 0xFF) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
} else {
|
|
/* verify for write */
|
|
for (i = 0; i < size; i++) {
|
|
if (flash[i] != wbuf[i]) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_cmd_write(int addr, int wlen, uint8_t *wbuf)
|
|
{
|
|
int i;
|
|
uint8_t flash_write[] = {FLASH_CMD_WRITE, ((addr >> 16) & 0xFF),
|
|
((addr >> 8) & 0xFF), (addr & 0xFF)};
|
|
|
|
/* enter EC-indirect follow mode */
|
|
ramcode_flash_follow_mode();
|
|
/* send flash write command (aai word or page program) */
|
|
ramcode_flash_transaction(sizeof(flash_write), flash_write, 0, NULL, CMD_CONTINUE);
|
|
|
|
for (i = 0; i < wlen; i++) {
|
|
/* send data byte */
|
|
ramcode_flash_write_dat(wbuf[i]);
|
|
|
|
/*
|
|
* we want to restart the write sequence every IDEAL_SIZE
|
|
* chunk worth of data.
|
|
*/
|
|
if (!(++addr % CHIP_FLASH_WRITE_PAGE_MAX_SIZE)) {
|
|
uint8_t w_en[] = {FLASH_CMD_WREN};
|
|
|
|
ramcode_flash_fsce_high();
|
|
/* make sure busy bit cleared. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_BUSY, FLASH_SR_NO_BUSY);
|
|
/* send write enable command */
|
|
ramcode_flash_transaction(sizeof(w_en), w_en, 0, NULL, CMD_END);
|
|
/* make sure busy bit cleared and write enabled. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_ALL, FLASH_SR_WEL);
|
|
/* re-send write command */
|
|
flash_write[1] = (addr >> 16) & GENMASK(7, 0);
|
|
flash_write[2] = (addr >> 8) & GENMASK(7, 0);
|
|
flash_write[3] = addr & GENMASK(7, 0);
|
|
ramcode_flash_transaction(sizeof(flash_write), flash_write,
|
|
0, NULL, CMD_CONTINUE);
|
|
}
|
|
}
|
|
ramcode_flash_fsce_high();
|
|
/* make sure busy bit cleared. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_BUSY, FLASH_SR_NO_BUSY);
|
|
/* exit EC-indirect follow mode */
|
|
ramcode_flash_follow_mode_exit();
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_write(int addr, int wlen, const char *wbuf)
|
|
{
|
|
ramcode_flash_cmd_write_enable();
|
|
ramcode_flash_cmd_write(addr, wlen, (uint8_t *)wbuf);
|
|
ramcode_flash_cmd_write_disable();
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_cmd_erase(int addr, int cmd)
|
|
{
|
|
uint8_t cmd_erase[] = {cmd, ((addr >> 16) & 0xFF),
|
|
((addr >> 8) & 0xFF), (addr & 0xFF)};
|
|
|
|
/* enter EC-indirect follow mode */
|
|
ramcode_flash_follow_mode();
|
|
/* send erase command */
|
|
ramcode_flash_transaction(sizeof(cmd_erase), cmd_erase, 0, NULL, CMD_END);
|
|
/* make sure busy bit cleared. */
|
|
ramcode_flash_cmd_read_status(FLASH_SR_BUSY, FLASH_SR_NO_BUSY);
|
|
/* exit EC-indirect follow mode */
|
|
ramcode_flash_follow_mode_exit();
|
|
}
|
|
|
|
void __soc_ram_code ramcode_flash_erase(int addr, int cmd)
|
|
{
|
|
ramcode_flash_cmd_write_enable();
|
|
ramcode_flash_cmd_erase(addr, cmd);
|
|
ramcode_flash_cmd_write_disable();
|
|
}
|
|
|
|
/* Read data from flash */
|
|
static int __soc_ram_code flash_it8xxx2_read(const struct device *dev, off_t offset, void *data,
|
|
size_t len)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
uint8_t *data_t = data;
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
flash_regs->SMFI_ECINDAR3 = EC_INDIRECT_READ_INTERNAL_FLASH;
|
|
flash_regs->SMFI_ECINDAR2 = (offset >> 16) & GENMASK(7, 0);
|
|
flash_regs->SMFI_ECINDAR1 = (offset >> 8) & GENMASK(7, 0);
|
|
flash_regs->SMFI_ECINDAR0 = (offset & GENMASK(7, 0));
|
|
|
|
/*
|
|
* Read/Write to this register will access one byte on the
|
|
* flash with the 32-bit flash address defined in ECINDAR3-0
|
|
*/
|
|
data_t[i] = flash_regs->SMFI_ECINDDR;
|
|
|
|
offset++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Write data to the flash, page by page */
|
|
static int __soc_ram_code flash_it8xxx2_write(const struct device *dev, off_t offset,
|
|
const void *src_data, size_t len)
|
|
{
|
|
struct flash_it8xxx2_dev_data *data = dev->data;
|
|
int ret = -EINVAL;
|
|
unsigned int key;
|
|
|
|
/*
|
|
* Check that the offset and length are multiples of the write
|
|
* block size.
|
|
*/
|
|
if ((offset % FLASH_WRITE_BLK_SZ) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
if ((len % FLASH_WRITE_BLK_SZ) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
if (!it8xxx2_is_ilm_configured()) {
|
|
return -EACCES;
|
|
}
|
|
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
/*
|
|
* CPU can't fetch instruction from flash while use
|
|
* EC-indirect follow mode to access flash, interrupts need to be
|
|
* disabled.
|
|
*/
|
|
key = irq_lock();
|
|
|
|
ramcode_flash_write(offset, len, src_data);
|
|
ramcode_reset_i_cache();
|
|
/* Get the ILM address of a flash offset. */
|
|
offset |= CHIP_MAPPED_STORAGE_BASE;
|
|
ret = ramcode_flash_verify(offset, len, src_data);
|
|
|
|
irq_unlock(key);
|
|
|
|
k_sem_give(&data->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Erase multiple blocks */
|
|
static int __soc_ram_code flash_it8xxx2_erase(const struct device *dev, off_t offset, size_t len)
|
|
{
|
|
struct flash_it8xxx2_dev_data *data = dev->data;
|
|
int v_size = len, v_addr = offset, ret = -EINVAL;
|
|
unsigned int key;
|
|
|
|
/*
|
|
* Check that the offset and length are multiples of the write
|
|
* erase block size.
|
|
*/
|
|
if ((offset % FLASH_ERASE_BLK_SZ) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
if ((len % FLASH_ERASE_BLK_SZ) != 0) {
|
|
return -EINVAL;
|
|
}
|
|
if (!it8xxx2_is_ilm_configured()) {
|
|
return -EACCES;
|
|
}
|
|
|
|
k_sem_take(&data->sem, K_FOREVER);
|
|
/*
|
|
* CPU can't fetch instruction from flash while use
|
|
* EC-indirect follow mode to access flash, interrupts need to be
|
|
* disabled.
|
|
*/
|
|
key = irq_lock();
|
|
|
|
/* Always use sector erase command */
|
|
for (; len > 0; len -= FLASH_ERASE_BLK_SZ) {
|
|
ramcode_flash_erase(offset, FLASH_CMD_SECTOR_ERASE);
|
|
offset += FLASH_ERASE_BLK_SZ;
|
|
}
|
|
ramcode_reset_i_cache();
|
|
/* get the ILM address of a flash offset. */
|
|
v_addr |= CHIP_MAPPED_STORAGE_BASE;
|
|
ret = ramcode_flash_verify(v_addr, v_size, NULL);
|
|
|
|
irq_unlock(key);
|
|
|
|
k_sem_give(&data->sem);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct flash_parameters *
|
|
flash_it8xxx2_get_parameters(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
return &flash_it8xxx2_parameters;
|
|
}
|
|
|
|
static int flash_it8xxx2_init(const struct device *dev)
|
|
{
|
|
struct smfi_it8xxx2_regs *const flash_regs = FLASH_IT8XXX2_REG_BASE;
|
|
struct flash_it8xxx2_dev_data *data = dev->data;
|
|
|
|
/* By default, select internal flash for indirect fast read. */
|
|
flash_regs->SMFI_ECINDAR3 = EC_INDIRECT_READ_INTERNAL_FLASH;
|
|
|
|
/*
|
|
* If the embedded flash's size of this part number is larger
|
|
* than 256K-byte, enable the page program cycle constructed
|
|
* by EC-Indirect Follow Mode.
|
|
*/
|
|
flash_regs->SMFI_FLHCTRL6R |= IT8XXX2_SMFI_MASK_ECINDPP;
|
|
|
|
/* Initialize mutex for flash controller */
|
|
k_sem_init(&data->sem, 1, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
static const struct flash_pages_layout dev_layout = {
|
|
.pages_count = DT_REG_SIZE(SOC_NV_FLASH_NODE) /
|
|
DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
|
|
.pages_size = DT_PROP(SOC_NV_FLASH_NODE, erase_block_size),
|
|
};
|
|
|
|
static void flash_it8xxx2_pages_layout(const struct device *dev,
|
|
const struct flash_pages_layout **layout,
|
|
size_t *layout_size)
|
|
{
|
|
*layout = &dev_layout;
|
|
*layout_size = 1;
|
|
}
|
|
#endif /* CONFIG_FLASH_PAGE_LAYOUT */
|
|
|
|
static const struct flash_driver_api flash_it8xxx2_api = {
|
|
.erase = flash_it8xxx2_erase,
|
|
.write = flash_it8xxx2_write,
|
|
.read = flash_it8xxx2_read,
|
|
.get_parameters = flash_it8xxx2_get_parameters,
|
|
#if defined(CONFIG_FLASH_PAGE_LAYOUT)
|
|
.page_layout = flash_it8xxx2_pages_layout,
|
|
#endif
|
|
};
|
|
|
|
static struct flash_it8xxx2_dev_data flash_it8xxx2_data;
|
|
|
|
DEVICE_DT_INST_DEFINE(0, flash_it8xxx2_init, NULL,
|
|
&flash_it8xxx2_data, NULL,
|
|
PRE_KERNEL_1,
|
|
CONFIG_FLASH_INIT_PRIORITY,
|
|
&flash_it8xxx2_api);
|