| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| #include "sw/device/lib/flash_ctrl.h" |
| |
| #include "sw/device/lib/common.h" |
| |
| #include "flash_ctrl_regs.h" // Generated. |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| |
| #define FLASH_CTRL0_BASE_ADDR TOP_EARLGREY_FLASH_CTRL_BASE_ADDR |
| |
| typedef enum flash_op { |
| FLASH_READ = 0, |
| FLASH_PROG = 1, |
| FLASH_ERASE = 2 |
| } flash_op_t; |
| |
| typedef enum erase_type { |
| FLASH_PAGE_ERASE = 0, |
| FLASH_BANK_ERASE = 1 |
| } erase_type_t; |
| |
| /* Wait for flash command to complete and set ACK in controller */ |
| static inline void wait_done_and_ack(void) { |
| while ((REG32(FLASH_CTRL_OP_STATUS(0)) & (1 << FLASH_CTRL_OP_STATUS_DONE)) == |
| 0) { |
| } |
| REG32(FLASH_CTRL_OP_STATUS(0)) = 0; |
| } |
| |
| void flash_init_block(void) { |
| while ((REG32(FLASH_CTRL_STATUS(0)) & (1 << FLASH_CTRL_STATUS_INIT_WIP)) > |
| 0) { |
| } |
| } |
| |
| /* Return status error and clear internal status register */ |
| static int get_clr_err(void) { |
| uint32_t err_status = |
| REG32(FLASH_CTRL_INTR_STATE(0)) & (0x1 << FLASH_CTRL_INTR_STATE_OP_ERROR); |
| REG32(FLASH_CTRL_INTR_STATE(0)) = err_status; |
| return err_status; |
| } |
| |
| int flash_check_empty(void) { |
| uint32_t mask = -1u; |
| uint32_t *p = (uint32_t *)FLASH_MEM_BASE_ADDR; |
| // TODO: Update range to cover entire flash. Limited now to one bank while |
| // we debu initialization. |
| for (; p < (uint32_t *)(FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ);) { |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| mask &= *p++; |
| if (mask != -1u) { |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| int flash_bank_erase(bank_index_t idx) { |
| flash_cfg_bank_erase(idx, /*erase_en=*/true); |
| |
| // TODO: Add timeout conditions and add error codes. |
| REG32(FLASH_CTRL_ADDR(0)) = (idx == FLASH_BANK_0) |
| ? FLASH_MEM_BASE_ADDR |
| : (FLASH_MEM_BASE_ADDR + FLASH_BANK_SZ); |
| REG32(FLASH_CTRL_CONTROL(0)) = |
| (FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET | |
| FLASH_BANK_ERASE << FLASH_CTRL_CONTROL_ERASE_SEL | |
| 0x1 << FLASH_CTRL_CONTROL_START); |
| wait_done_and_ack(); |
| |
| flash_cfg_bank_erase(idx, /*erase_en=*/false); |
| |
| return get_clr_err(); |
| } |
| |
| int flash_page_erase(uint32_t addr, part_type_t part) { |
| REG32(FLASH_CTRL_ADDR(0)) = addr; |
| REG32(FLASH_CTRL_CONTROL(0)) = FLASH_ERASE << FLASH_CTRL_CONTROL_OP_OFFSET | |
| FLASH_PAGE_ERASE |
| << FLASH_CTRL_CONTROL_ERASE_SEL | |
| part << FLASH_CTRL_CONTROL_PARTITION_SEL | |
| 0x1 << FLASH_CTRL_CONTROL_START; |
| wait_done_and_ack(); |
| return get_clr_err(); |
| } |
| |
| static int flash_write_internal(uint32_t addr, part_type_t part, |
| const uint32_t *data, uint32_t size) { |
| // TODO: Do we need to select bank as part of the write? |
| // TODO: Update with address alignment requirements. |
| REG32(FLASH_CTRL_ADDR(0)) = addr; |
| REG32(FLASH_CTRL_CONTROL(0)) = (FLASH_PROG << FLASH_CTRL_CONTROL_OP_OFFSET | |
| part << FLASH_CTRL_CONTROL_PARTITION_SEL | |
| (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET | |
| 0x1 << FLASH_CTRL_CONTROL_START); |
| for (int i = 0; i < size; ++i) { |
| REG32(FLASH_CTRL_PROG_FIFO(0)) = data[i]; |
| } |
| wait_done_and_ack(); |
| return get_clr_err(); |
| } |
| |
| int flash_write(uint32_t addr, part_type_t part, const uint32_t *data, |
| uint32_t size) { |
| // TODO: Breakdown into FIFO chunks if needed. |
| return flash_write_internal(addr, part, data, size); |
| } |
| |
| int flash_read(uint32_t addr, part_type_t part, uint32_t size, uint32_t *data) { |
| REG32(FLASH_CTRL_ADDR(0)) = addr; |
| REG32(FLASH_CTRL_CONTROL(0)) = FLASH_READ << FLASH_CTRL_CONTROL_OP_OFFSET | |
| part << FLASH_CTRL_CONTROL_PARTITION_SEL | |
| (size - 1) << FLASH_CTRL_CONTROL_NUM_OFFSET | |
| 0x1 << FLASH_CTRL_CONTROL_START; |
| for (uint32_t i = 0; i < size;) { |
| if (((REG32(FLASH_CTRL_STATUS(0)) >> FLASH_CTRL_STATUS_RD_EMPTY) & 0x1) == |
| 0) { |
| *data++ = REG32(FLASH_CTRL_RD_FIFO(0)); |
| i++; |
| } |
| } |
| wait_done_and_ack(); |
| return get_clr_err(); |
| } |
| |
| void flash_cfg_scramble_enable(bool en) { |
| REG32(FLASH_CTRL_SCRAMBLE_EN(0)) = en & 1; |
| } |
| |
| void flash_cfg_bank_erase(bank_index_t bank, bool erase_en) { |
| REG32(FLASH_CTRL_MP_BANK_CFG(0)) = |
| (erase_en) ? SETBIT(REG32(FLASH_CTRL_MP_BANK_CFG(0)), bank) |
| : CLRBIT(REG32(FLASH_CTRL_MP_BANK_CFG(0)), bank); |
| } |
| |
| void flash_default_region_access(bool rd_en, bool prog_en, bool erase_en) { |
| REG32(FLASH_CTRL_DEFAULT_REGION(0)) = |
| rd_en << FLASH_CTRL_DEFAULT_REGION_RD_EN | |
| prog_en << FLASH_CTRL_DEFAULT_REGION_PROG_EN | |
| erase_en << FLASH_CTRL_DEFAULT_REGION_ERASE_EN; |
| } |
| |
| void flash_cfg_region(const mp_region_t *region_cfg) { |
| REG32(FLASH_CTRL_MP_REGION_CFG0(0) + region_cfg->num * 4) = |
| region_cfg->base << FLASH_CTRL_MP_REGION_CFG0_BASE0_OFFSET | |
| region_cfg->size << FLASH_CTRL_MP_REGION_CFG0_SIZE0_OFFSET | |
| region_cfg->part << FLASH_CTRL_MP_REGION_CFG0_PARTITION0 | |
| region_cfg->rd_en << FLASH_CTRL_MP_REGION_CFG0_RD_EN0 | |
| region_cfg->prog_en << FLASH_CTRL_MP_REGION_CFG0_PROG_EN0 | |
| region_cfg->erase_en << FLASH_CTRL_MP_REGION_CFG0_ERASE_EN0 | |
| 0x1 << FLASH_CTRL_MP_REGION_CFG0_EN0; |
| } |
| |
| void flash_write_scratch_reg(uint32_t value) { |
| REG32(FLASH_CTRL_SCRATCH(0)) = value; |
| } |
| |
| uint32_t flash_read_scratch_reg(void) { return REG32(FLASH_CTRL_SCRATCH(0)); } |