| /* |
| * 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); |