| /* |
| * Copyright (c) 2024-2025 MASSDRIVER EI (massdriver.space) |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT bflb_flash_controller |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/flash.h> |
| #include <zephyr/sys/barrier.h> |
| #include <zephyr/arch/common/sys_io.h> |
| #include <zephyr/cache.h> |
| |
| #include <soc.h> |
| #include <bflb_soc.h> |
| #include <glb_reg.h> |
| #include <sf_ctrl_reg.h> |
| #include <common_defines.h> |
| #include <hbn_reg.h> |
| #include <zephyr/drivers/clock_control/clock_control_bflb_common.h> |
| |
| #if defined(CONFIG_SOC_SERIES_BL60X) || defined(CONFIG_SOC_SERIES_BL70X) |
| #include <l1c_reg.h> |
| #endif |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(flash_bflb, CONFIG_FLASH_LOG_LEVEL); |
| |
| #define ERASE_VALUE 0xFF |
| #define WRITE_SIZE DT_PROP(DT_CHOSEN(zephyr_flash), write_block_size) |
| #define ERASE_SIZE DT_PROP(DT_CHOSEN(zephyr_flash), erase_block_size) |
| #define TOTAL_SIZE DT_REG_SIZE(DT_CHOSEN(zephyr_flash)) |
| |
| #ifdef CONFIG_SOC_SERIES_BL60X |
| #define BFLB_XIP_BASE BL602_FLASH_XIP_BASE |
| #define BFLB_XIP_END BL602_FLASH_XIP_END |
| #elif defined(CONFIG_SOC_SERIES_BL70X) |
| #define BFLB_XIP_BASE BL702_FLASH_XIP_BASE |
| #define BFLB_XIP_END BL702_FLASH_XIP_END |
| #elif defined(CONFIG_SOC_SERIES_BL61X) |
| #define BFLB_XIP_BASE BL616_FLASH_XIP_BASE |
| #define BFLB_XIP_END BL616_FLASH_XIP_END |
| #endif |
| |
| #define BFLB_FLASH_CONTROLLER_BUSY_TIMEOUT_MS 200 |
| #define BFLB_FLASH_CHIP_BUSY_TIMEOUT_MS 5000 |
| |
| #define BFLB_FLASH_FLASH_BLOCK_PROTECT_MSK 0x1C |
| |
| #define FLASH_READ32(address) (*((volatile uint32_t *)(address))) |
| #define FLASH_WRITE32(value, address) (*((volatile uint32_t *)(address))) = value; |
| |
| struct flash_bflb_config { |
| uint32_t reg; |
| void (*irq_config_func)(const struct device *dev); |
| }; |
| |
| #define BFLB_FLASH_MAGIC_1 "BFNP" |
| #define BFLB_FLASH_MAGIC_2 "FCFG" |
| |
| struct bflb_flash_magic_1 { |
| char magic[4]; |
| uint32_t revision; |
| } __packed; |
| |
| struct bflb_flash_magic_2 { |
| char magic[4]; |
| } __packed; |
| |
| /* Raw flash configuration data structure */ |
| struct bflb_flash_cfg { |
| /* Serial flash interface mode, bit0-3:spi mode, bit4:unwrap, bit5:32-bits addr mode support */ |
| uint8_t io_mode; |
| /* Support continuous read mode, bit0:continuous read mode support, bit1:read mode cfg */ |
| uint8_t c_read_support; |
| /* SPI clock delay, bit0-3:delay,bit4-6:pad delay */ |
| uint8_t clk_delay; |
| /* SPI clock phase invert, bit0:clck invert, bit1:rx invert, bit2-4:pad delay, bit5-7:pad delay */ |
| uint8_t clk_invert; |
| /* Flash enable reset command */ |
| uint8_t reset_en_cmd; |
| /* Flash reset command */ |
| uint8_t reset_cmd; |
| /* Flash reset continuous read command */ |
| uint8_t reset_c_read_cmd; |
| /* Flash reset continuous read command size */ |
| uint8_t reset_c_read_cmd_size; |
| /* JEDEC ID command */ |
| uint8_t jedec_id_cmd; |
| /* JEDEC ID command dummy clock */ |
| uint8_t jedec_id_cmd_dmy_clk; |
| #if defined(CONFIG_SOC_SERIES_BL70X) || defined(CONFIG_SOC_SERIES_BL60X) |
| /* QPI JEDEC ID command */ |
| uint8_t qpi_jedec_id_cmd; |
| /* QPI JEDEC ID command dummy clock */ |
| uint8_t qpi_jedec_id_cmd_dmy_clk; |
| #else |
| /* Enter 32-bits addr command */ |
| uint8_t enter_32bits_addr_cmd; |
| /* Exit 32-bits addr command */ |
| uint8_t exit_32bits_addr_cmd; |
| #endif |
| /* (x * 1024) bytes */ |
| uint8_t sector_size; |
| /* Manufacturer ID */ |
| uint8_t mid; |
| /* Page size */ |
| uint16_t page_size; |
| /* Chip erase cmd */ |
| uint8_t chip_erase_cmd; |
| /* Sector erase command */ |
| uint8_t sector_erase_cmd; |
| /* Block 32K erase command*/ |
| uint8_t blk32_erase_cmd; |
| /* Block 64K erase command */ |
| uint8_t blk64_erase_cmd; |
| /* Write enable command, needed before every erase or program, or register write */ |
| uint8_t write_enable_cmd; |
| /* Page program command */ |
| uint8_t page_program_cmd; |
| /* QIO page program cmd */ |
| uint8_t qpage_program_cmd; |
| /* QIO page program address mode */ |
| uint8_t qpp_addr_mode; |
| /* Fast read command */ |
| uint8_t fast_read_cmd; |
| /* Fast read command dummy clock */ |
| uint8_t fr_dmy_clk; |
| /* QPI fast read command */ |
| uint8_t qpi_fast_read_cmd; |
| /* QPI fast read command dummy clock */ |
| uint8_t qpi_fr_dmy_clk; |
| /* Fast read dual output command */ |
| uint8_t fast_read_do_cmd; |
| /* Fast read dual output command dummy clock */ |
| uint8_t fr_do_dmy_clk; |
| /* Fast read dual io command */ |
| uint8_t fast_read_dio_cmd; |
| /* Fast read dual io command dummy clock */ |
| uint8_t fr_dio_dmy_clk; |
| /* Fast read quad output command */ |
| uint8_t fast_read_qo_cmd; |
| /* Fast read quad output command dummy clock */ |
| uint8_t fr_qo_dmy_clk; |
| /* Fast read quad io command */ |
| uint8_t fast_read_qio_cmd; |
| /* Fast read quad io command dummy clock */ |
| uint8_t fr_qio_dmy_clk; |
| /* QPI fast read quad io command */ |
| uint8_t qpi_fast_read_qio_cmd; |
| /* QPI fast read QIO dummy clock */ |
| uint8_t qpi_fr_qio_dmy_clk; |
| /* QPI program command */ |
| uint8_t qpi_page_program_cmd; |
| /* Enable write volatile reg */ |
| uint8_t write_vreg_enable_cmd; |
| /* Write enable register index */ |
| uint8_t wr_enable_index; |
| /* Quad mode enable register index */ |
| uint8_t qe_index; |
| /* Busy status register index */ |
| uint8_t busy_index; |
| /* Write enable register bit pos */ |
| uint8_t wr_enable_bit; |
| /* Quad enable register bit pos */ |
| uint8_t qe_bit; |
| /* Busy status register bit pos */ |
| uint8_t busy_bit; |
| /* Register length of write enable */ |
| uint8_t wr_enable_write_reg_len; |
| /* Register length of write enable status */ |
| uint8_t wr_enable_read_reg_len; |
| /* Register length of quad enable */ |
| uint8_t qe_write_reg_len; |
| /* Register length of quad enable status */ |
| uint8_t qe_read_reg_len; |
| /* Release power down command */ |
| uint8_t release_powerdown; |
| /* Register length of contain busy status */ |
| uint8_t busy_read_reg_len; |
| /* Read register command buffer */ |
| uint8_t read_reg_cmd[4]; |
| /* Write register command buffer */ |
| uint8_t write_reg_cmd[4]; |
| /* Enter qpi command */ |
| uint8_t enter_qpi; |
| /* Exit qpi command */ |
| uint8_t exit_qpi; |
| /* Config data for continuous read mode */ |
| uint8_t c_read_mode; |
| /* Config data for exit continuous read mode */ |
| uint8_t c_rexit; |
| /* Enable burst wrap command */ |
| uint8_t burst_wrap_cmd; |
| /* Enable burst wrap command dummy clock */ |
| uint8_t burst_wrap_cmd_dmy_clk; |
| /* Data and address mode for this command */ |
| uint8_t burst_wrap_data_mode; |
| /* Data to enable burst wrap */ |
| uint8_t burst_wrap_data; |
| /* Disable burst wrap command */ |
| uint8_t de_burst_wrap_cmd; |
| /* Disable burst wrap command dummy clock */ |
| uint8_t de_burst_wrap_cmd_dmy_clk; |
| /* Data and address mode for this command */ |
| uint8_t de_burst_wrap_data_mode; |
| /* Data to disable burst wrap */ |
| uint8_t de_burst_wrap_data; |
| /* Typical 4K(usually) erase time */ |
| uint16_t time_e_sector; |
| /* Typical 32K erase time */ |
| uint16_t time_e_32k; |
| /* Typical 64K erase time */ |
| uint16_t time_e_64k; |
| /* Typical Page program time */ |
| uint16_t time_page_pgm; |
| /* Typical Chip erase time in ms */ |
| uint16_t time_ce; |
| /* Release power down command delay time for wake up */ |
| uint8_t pd_delay; |
| /* QE set data */ |
| uint8_t qe_data; |
| } __packed; |
| |
| struct bflb_flash_header { |
| struct bflb_flash_magic_1 magic_1; |
| struct bflb_flash_magic_2 magic_2; |
| struct bflb_flash_cfg flash_cfg; |
| uint32_t flash_cfg_crc; |
| } __packed; |
| |
| struct bflb_flash_command { |
| /* Read write 0: read 1 : write */ |
| uint8_t rw; |
| /* Command mode 0: 1 line, 1: 4 lines */ |
| uint8_t cmd_mode; |
| /* SPI mode 0: IO 1: DO 2: QO 3: DIO 4: QIO */ |
| uint8_t spi_mode; |
| /* Address size */ |
| uint8_t addr_size; |
| /* Dummy clocks */ |
| uint8_t dummy_clks; |
| /* Transfer number of bytes */ |
| uint32_t nb_data; |
| /* Command buffer */ |
| uint32_t cmd_buf[2]; |
| }; |
| |
| struct flash_bflb_data { |
| struct bflb_flash_cfg flash_cfg; |
| uint32_t last_flash_offset; |
| uint32_t reg_copy; |
| uint32_t jedec_id; |
| }; |
| |
| /* Will using function cause error ? */ |
| static bool flash_bflb_is_in_xip(void *func) |
| { |
| if ((uint32_t)func > BFLB_XIP_BASE && (uint32_t)func < BFLB_XIP_END) { |
| LOG_ERR("function at %p is in XIP and will crash the device", func); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Are we doing something that makes sense? ? */ |
| static int flash_bflb_is_valid_range(off_t offset, size_t len) |
| { |
| if (offset < 0) { |
| LOG_WRN("0x%lx: before start of flash", (long)offset); |
| return -EINVAL; |
| } |
| if ((TOTAL_SIZE - offset) < len || len > TOTAL_SIZE) { |
| LOG_WRN("0x%lx: ends past the end of flash", (long)offset); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_SOC_SERIES_BL70X) || defined(CONFIG_SOC_SERIES_BL60X) |
| |
| static void flash_bflb_l1c_wrap(bool enable) |
| { |
| uint32_t tmp; |
| bool caching = false; |
| |
| tmp = FLASH_READ32(L1C_BASE + L1C_CONFIG_OFFSET); |
| /* disable cache */ |
| if ((tmp & L1C_CACHEABLE_MSK) != 0) { |
| caching = true; |
| tmp &= ~(1 << L1C_CACHEABLE_POS); |
| FLASH_WRITE32(tmp, L1C_BASE + L1C_CONFIG_OFFSET); |
| } |
| |
| tmp = FLASH_READ32(L1C_BASE + L1C_CONFIG_OFFSET); |
| |
| if (enable) { |
| tmp &= ~L1C_WRAP_DIS_MSK; |
| } else { |
| tmp |= L1C_WRAP_DIS_MSK; |
| } |
| |
| FLASH_WRITE32(tmp, L1C_BASE + L1C_CONFIG_OFFSET); |
| |
| if (caching) { |
| tmp |= (1 << L1C_CACHEABLE_POS); |
| FLASH_WRITE32(tmp, L1C_BASE + L1C_CONFIG_OFFSET); |
| } |
| } |
| |
| |
| #elif defined(CONFIG_SOC_SERIES_BL61X) |
| |
| static void flash_bflb_l1c_wrap(bool enable) |
| { |
| /* Do nothing on Bl61x: no L1C */ |
| ARG_UNUSED(enable); |
| } |
| |
| #endif |
| |
| /* Memcpy will not be in ram */ |
| static void flash_bflb_xip_memcpy(volatile uint8_t *address_from, volatile uint8_t *address_to, |
| size_t size) |
| { |
| for (size_t i = 0; i < size; i++) { |
| address_to[i] = address_from[i]; |
| } |
| } |
| |
| static bool flash_bflb_busy_wait(struct flash_bflb_data *data) |
| { |
| uint32_t counter = 0; |
| |
| while ((FLASH_READ32(data->reg_copy + SF_CTRL_SF_IF_SAHB_0_OFFSET + 0) |
| & SF_CTRL_SF_IF_BUSY_MSK) != 0 |
| && counter < BFLB_FLASH_CONTROLLER_BUSY_TIMEOUT_MS * 20000) { |
| clock_bflb_settle(); |
| counter++; |
| } |
| |
| if ((FLASH_READ32(data->reg_copy + SF_CTRL_SF_IF_SAHB_0_OFFSET + 0) |
| & SF_CTRL_SF_IF_BUSY_MSK) != 0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Sets which AHB the flash controller is being talked to from |
| * 0: System AHB (AHB connected to everything, E24 System Port) |
| * 1: Instruction AHB (a dedicated bus between flash controller and L1C) |
| */ |
| static int flash_bflb_set_bus(struct flash_bflb_data *data, uint8_t bus) |
| { |
| uint32_t tmp; |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_1_OFFSET); |
| |
| if (bus == 1) { |
| tmp |= SF_CTRL_SF_IF_FN_SEL_MSK; |
| tmp |= SF_CTRL_SF_AHB2SIF_EN_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_FN_SEL_MSK; |
| tmp &= ~SF_CTRL_SF_AHB2SIF_EN_MSK; |
| } |
| |
| FLASH_WRITE32(tmp, data->reg_copy + SF_CTRL_1_OFFSET); |
| |
| return 0; |
| } |
| |
| static uint8_t flash_bflb_admode_to_spimode(uint8_t addr_mode, uint8_t data_mode) |
| { |
| __ASSERT(addr_mode < 3, "addr_mode unhandled"); |
| __ASSERT(data_mode < 3, "data_mode unhandled"); |
| |
| if (addr_mode == 0) { |
| return data_mode; |
| } else if (addr_mode == 1) { |
| return 3; |
| } else if (addr_mode == 2) { |
| return 4; |
| } |
| |
| return 0; |
| } |
| |
| static int flash_bflb_set_command_iahb(struct flash_bflb_data *data, |
| struct bflb_flash_command *command, |
| bool doing_cmd) |
| { |
| uint32_t tmp; |
| uint32_t bank_offset = data->reg_copy + SF_CTRL_SF_IF_IAHB_0_OFFSET; |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_1_OFFSET); |
| |
| if ((tmp & SF_CTRL_SF_IF_FN_SEL_MSK) == 0) { |
| LOG_ERR("Flash's Bus must be Instruction AHB and not System AHB"); |
| return -EINVAL; |
| } |
| |
| FLASH_WRITE32(command->cmd_buf[0], bank_offset + 0x4); |
| FLASH_WRITE32(command->cmd_buf[1], bank_offset + 0x8); |
| |
| tmp = FLASH_READ32(bank_offset + 0); |
| |
| /* 4 lines or 1 line commands */ |
| if (command->cmd_mode == 0) { |
| tmp &= ~SF_CTRL_SF_IF_1_QPI_MODE_EN_MSK; |
| } else { |
| tmp |= SF_CTRL_SF_IF_1_QPI_MODE_EN_MSK; |
| } |
| |
| /* set SPI mode*/ |
| tmp &= ~SF_CTRL_SF_IF_1_SPI_MODE_MSK; |
| tmp |= command->spi_mode << SF_CTRL_SF_IF_1_SPI_MODE_POS; |
| |
| tmp &= ~SF_CTRL_SF_IF_1_CMD_BYTE_MSK; |
| /* we are doing a command */ |
| if (doing_cmd) { |
| tmp |= SF_CTRL_SF_IF_1_CMD_EN_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_1_CMD_EN_MSK; |
| } |
| |
| /* configure address */ |
| tmp &= ~SF_CTRL_SF_IF_1_ADR_BYTE_MSK; |
| if (command->addr_size != 0) { |
| tmp |= SF_CTRL_SF_IF_1_ADR_EN_MSK; |
| tmp |= ((command->addr_size - 1) << SF_CTRL_SF_IF_1_ADR_BYTE_POS); |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_1_ADR_EN_MSK; |
| } |
| |
| /* configure dummy */ |
| tmp &= ~SF_CTRL_SF_IF_1_DMY_BYTE_MSK; |
| if (command->dummy_clks != 0) { |
| tmp |= SF_CTRL_SF_IF_1_DMY_EN_MSK; |
| tmp |= ((command->dummy_clks - 1) << SF_CTRL_SF_IF_1_DMY_BYTE_POS); |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_1_DMY_EN_MSK; |
| } |
| |
| /* configure data */ |
| if (command->nb_data != 0) { |
| tmp |= SF_CTRL_SF_IF_1_DAT_EN_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_1_DAT_EN_MSK; |
| } |
| |
| /* are we writing ? */ |
| if (command->rw) { |
| tmp |= SF_CTRL_SF_IF_1_DAT_RW_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_1_DAT_RW_MSK; |
| } |
| |
| FLASH_WRITE32(tmp, bank_offset + 0); |
| |
| return 0; |
| } |
| |
| static int flash_bflb_set_command_sahb(struct flash_bflb_data *data, |
| struct bflb_flash_command *command, |
| bool doing_cmd) |
| { |
| uint32_t tmp; |
| uint32_t bank_offset = data->reg_copy + SF_CTRL_SF_IF_SAHB_0_OFFSET; |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| FLASH_WRITE32(command->cmd_buf[0], bank_offset + 0x4); |
| FLASH_WRITE32(command->cmd_buf[1], bank_offset + 0x8); |
| |
| tmp = FLASH_READ32(bank_offset + 0); |
| |
| /* 4 lines or 1 line commands */ |
| if (command->cmd_mode == 0) { |
| tmp &= ~SF_CTRL_SF_IF_0_QPI_MODE_EN_MSK; |
| } else { |
| tmp |= SF_CTRL_SF_IF_0_QPI_MODE_EN_MSK; |
| } |
| |
| /* set SPI mode */ |
| tmp &= ~SF_CTRL_SF_IF_0_SPI_MODE_MSK; |
| tmp |= command->spi_mode << SF_CTRL_SF_IF_0_SPI_MODE_POS; |
| |
| tmp &= ~SF_CTRL_SF_IF_0_CMD_BYTE_MSK; |
| /* we are doing a command */ |
| if (doing_cmd) { |
| tmp |= SF_CTRL_SF_IF_0_CMD_EN_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_0_CMD_EN_MSK; |
| } |
| |
| /* configure address */ |
| tmp &= ~SF_CTRL_SF_IF_0_ADR_BYTE_MSK; |
| if (command->addr_size != 0) { |
| tmp |= SF_CTRL_SF_IF_0_ADR_EN_MSK; |
| tmp |= ((command->addr_size - 1) << SF_CTRL_SF_IF_0_ADR_BYTE_POS); |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_0_ADR_EN_MSK; |
| } |
| |
| /* configure dummy */ |
| tmp &= ~SF_CTRL_SF_IF_0_DMY_BYTE_MSK; |
| if (command->dummy_clks != 0) { |
| tmp |= SF_CTRL_SF_IF_0_DMY_EN_MSK; |
| tmp |= ((command->dummy_clks - 1) << SF_CTRL_SF_IF_0_DMY_BYTE_POS); |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_0_DMY_EN_MSK; |
| } |
| |
| /* configure data */ |
| tmp &= ~SF_CTRL_SF_IF_0_DAT_BYTE_MSK; |
| if (command->nb_data != 0) { |
| tmp |= SF_CTRL_SF_IF_0_DAT_EN_MSK; |
| tmp |= ((command->nb_data - 1) << SF_CTRL_SF_IF_0_DAT_BYTE_POS); |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_0_DAT_EN_MSK; |
| } |
| |
| /* are we writing ? */ |
| if (command->rw) { |
| tmp |= SF_CTRL_SF_IF_0_DAT_RW_MSK; |
| } else { |
| tmp &= ~SF_CTRL_SF_IF_0_DAT_RW_MSK; |
| } |
| FLASH_WRITE32(tmp, bank_offset + 0); |
| |
| return 0; |
| } |
| |
| static int flash_bflb_send_command(struct flash_bflb_data *data, struct bflb_flash_command *command) |
| { |
| uint32_t tmp; |
| int ret; |
| uint32_t bank_offset = data->reg_copy + SF_CTRL_SF_IF_SAHB_0_OFFSET; |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_send_command)) { |
| return -ENOTSUP; |
| } |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_1_OFFSET); |
| if (tmp & SF_CTRL_SF_IF_FN_SEL_MSK) { |
| LOG_ERR("Flash's Bus must be System AHB and not Instruction AHB"); |
| return -EINVAL; |
| } |
| |
| /* make sure command detriggered */ |
| tmp = FLASH_READ32(bank_offset + 0); |
| tmp &= ~SF_CTRL_SF_IF_0_TRIG_MSK; |
| FLASH_WRITE32(tmp, bank_offset + 0); |
| |
| ret = flash_bflb_set_command_sahb(data, command, true); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| #if defined(CONFIG_SOC_SERIES_BL70X) || defined(CONFIG_SOC_SERIES_BL60X) |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_0_OFFSET); |
| tmp |= SF_CTRL_SF_CLK_SAHB_SRAM_SEL_MSK; |
| FLASH_WRITE32(tmp, data->reg_copy + SF_CTRL_0_OFFSET); |
| #endif |
| |
| /* trigger command */ |
| tmp = FLASH_READ32(bank_offset + 0); |
| tmp |= SF_CTRL_SF_IF_0_TRIG_MSK; |
| FLASH_WRITE32(tmp, bank_offset + 0); |
| |
| if (flash_bflb_busy_wait(data)) { |
| #if defined(CONFIG_SOC_SERIES_BL70X) || defined(CONFIG_SOC_SERIES_BL60X) |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_0_OFFSET); |
| tmp &= ~SF_CTRL_SF_CLK_SAHB_SRAM_SEL_MSK; |
| FLASH_WRITE32(tmp, data->reg_copy + SF_CTRL_0_OFFSET); |
| #endif |
| return -EBUSY; |
| } |
| |
| #if defined(CONFIG_SOC_SERIES_BL70X) || defined(CONFIG_SOC_SERIES_BL60X) |
| tmp = FLASH_READ32(data->reg_copy + SF_CTRL_0_OFFSET); |
| tmp &= ~SF_CTRL_SF_CLK_SAHB_SRAM_SEL_MSK; |
| FLASH_WRITE32(tmp, data->reg_copy + SF_CTRL_0_OFFSET); |
| #endif |
| |
| return 0; |
| } |
| |
| static int flash_bflb_flash_read_register(struct flash_bflb_data *data, uint8_t index, uint8_t *out, |
| uint8_t len) |
| { |
| struct bflb_flash_command read_reg = {0}; |
| int ret; |
| |
| read_reg.cmd_buf[0] = (data->flash_cfg.read_reg_cmd[index]) << 24; |
| read_reg.nb_data = len; |
| ret = flash_bflb_send_command(data, &read_reg); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| flash_bflb_xip_memcpy((uint8_t *)SF_CTRL_BUF_BASE, out, len); |
| |
| return 0; |
| } |
| |
| |
| static int flash_bflb_flash_write_register(struct flash_bflb_data *data, uint8_t index, uint8_t *in, |
| uint8_t len) |
| { |
| struct bflb_flash_command write_reg = {0}; |
| |
| flash_bflb_xip_memcpy(in, (uint8_t *)SF_CTRL_BUF_BASE, len); |
| |
| write_reg.cmd_buf[0] = (data->flash_cfg.write_reg_cmd[index]) << 24; |
| write_reg.nb_data = len; |
| write_reg.rw = 1; |
| |
| return flash_bflb_send_command(data, &write_reg); |
| } |
| |
| static int flash_bflb_flash_disable_continuous_read(struct flash_bflb_data *data) |
| { |
| struct bflb_flash_command disable_continuous_read = {0}; |
| |
| disable_continuous_read.addr_size = data->flash_cfg.reset_c_read_cmd_size; |
| disable_continuous_read.cmd_buf[0] = data->flash_cfg.reset_c_read_cmd << 24 | |
| data->flash_cfg.reset_c_read_cmd << 16 | data->flash_cfg.reset_c_read_cmd << 8 | |
| data->flash_cfg.reset_c_read_cmd; |
| |
| return flash_bflb_send_command(data, &disable_continuous_read); |
| } |
| |
| static int flash_bflb_flash_disable_burst(struct flash_bflb_data *data) |
| { |
| uint32_t tmp; |
| struct bflb_flash_command disable_burstwrap = {0}; |
| |
| disable_burstwrap.dummy_clks = data->flash_cfg.de_burst_wrap_cmd_dmy_clk; |
| disable_burstwrap.spi_mode = flash_bflb_admode_to_spimode( |
| data->flash_cfg.de_burst_wrap_data_mode, |
| data->flash_cfg.de_burst_wrap_data_mode); |
| disable_burstwrap.cmd_buf[0] = data->flash_cfg.de_burst_wrap_cmd << 24; |
| disable_burstwrap.nb_data = 1; |
| disable_burstwrap.rw = 1; |
| tmp = data->flash_cfg.de_burst_wrap_data; |
| FLASH_WRITE32(tmp, SF_CTRL_BUF_BASE); |
| |
| return flash_bflb_send_command(data, &disable_burstwrap); |
| } |
| |
| static int flash_bflb_flash_enable_burst(struct flash_bflb_data *data) |
| { |
| uint32_t tmp; |
| struct bflb_flash_command enable_burstwrap = {0}; |
| |
| enable_burstwrap.dummy_clks = data->flash_cfg.burst_wrap_cmd_dmy_clk; |
| enable_burstwrap.spi_mode = flash_bflb_admode_to_spimode( |
| data->flash_cfg.burst_wrap_data_mode, |
| data->flash_cfg.burst_wrap_data_mode); |
| enable_burstwrap.cmd_buf[0] = data->flash_cfg.burst_wrap_cmd << 24; |
| enable_burstwrap.nb_data = 1; |
| enable_burstwrap.rw = 1; |
| tmp = data->flash_cfg.burst_wrap_data; |
| FLASH_WRITE32(tmp, SF_CTRL_BUF_BASE); |
| |
| return flash_bflb_send_command(data, &enable_burstwrap); |
| } |
| |
| static int flash_bflb_enable_writable(struct flash_bflb_data *data) |
| { |
| struct bflb_flash_command write_enable = {0}; |
| int ret; |
| uint32_t write_reg; |
| |
| write_enable.cmd_buf[0] = (data->flash_cfg.write_enable_cmd) << 24; |
| ret = flash_bflb_send_command(data, &write_enable); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* check writable */ |
| ret = flash_bflb_flash_read_register(data, data->flash_cfg.wr_enable_index, |
| (uint8_t *)&write_reg, data->flash_cfg.wr_enable_read_reg_len); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if ((write_reg & (1 << data->flash_cfg.wr_enable_bit)) != 0) { |
| return 0; |
| } |
| |
| return -EINVAL; |
| } |
| |
| /* /!\ UNTESTED (no relevant hardware) */ |
| static int flash_bflb_enable_qspi(struct flash_bflb_data *data) |
| { |
| int ret; |
| uint32_t tmp = 0; |
| /* qe = quad enable */ |
| |
| /* writable command also enables writing to configuration registers, not just data*/ |
| ret = flash_bflb_enable_writable(data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (data->flash_cfg.qe_read_reg_len == 0) { |
| /* likely to write nothing (len = 0) */ |
| return flash_bflb_flash_write_register(data, data->flash_cfg.qe_index, |
| (uint8_t *)&tmp, |
| data->flash_cfg.qe_write_reg_len); |
| } |
| |
| /* get quad enable register value */ |
| ret = flash_bflb_flash_read_register(data, data->flash_cfg.qe_index, (uint8_t *)&tmp, |
| data->flash_cfg.qe_read_reg_len); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* qe is a bit */ |
| if (data->flash_cfg.qe_data == 0) { |
| /* qe is already enable*/ |
| if ((tmp & (1 << data->flash_cfg.qe_bit)) != 0) { |
| return 0; |
| } |
| /* qe is a specific value, not encountered in available flash chip configs */ |
| } else { |
| if (((tmp >> (data->flash_cfg.qe_bit & 0x08)) & 0xff) == data->flash_cfg.qe_data) { |
| return 0; |
| } |
| } |
| |
| /* all status registers must be read and written */ |
| if (data->flash_cfg.qe_write_reg_len != 1) { |
| ret = flash_bflb_flash_read_register(data, 0, (uint8_t *)&tmp, 1); |
| if (ret != 0) { |
| return ret; |
| } |
| ret = flash_bflb_flash_read_register(data, 1, (uint8_t *)&tmp + 1, 1); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (data->flash_cfg.qe_data == 0) { |
| tmp |= (1 << (data->flash_cfg.qe_bit + 8 * data->flash_cfg.qe_index)); |
| } else { |
| tmp = tmp & (~(0xff << (8 * data->flash_cfg.qe_index))); |
| tmp |= (data->flash_cfg.qe_data << (8 * data->flash_cfg.qe_index)); |
| } |
| |
| /* we only need to read and write the appropriate register (usually the second one) */ |
| } else { |
| if (data->flash_cfg.qe_data == 0) { |
| tmp |= (1 << (data->flash_cfg.qe_bit % 8)); |
| } else { |
| tmp = data->flash_cfg.qe_data; |
| } |
| } |
| |
| ret = flash_bflb_flash_write_register(data, data->flash_cfg.qe_index, (uint8_t *)&tmp, |
| data->flash_cfg.qe_write_reg_len); |
| if (ret != 0) { |
| return ret; |
| } |
| ret = flash_bflb_flash_read_register(data, data->flash_cfg.qe_index, (uint8_t *)&tmp, |
| data->flash_cfg.qe_write_reg_len); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* check Quad is Enabled */ |
| if (data->flash_cfg.qe_data == 0) { |
| if ((tmp & (1 << data->flash_cfg.qe_bit)) != 0) { |
| return 0; |
| } |
| } else { |
| if (((tmp >> (data->flash_cfg.qe_bit & 0x08)) & 0xff) == data->flash_cfg.qe_data) { |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| static uint32_t flash_bflb_get_offset(uint32_t base_reg) |
| { |
| uint32_t tmp; |
| |
| tmp = FLASH_READ32(base_reg + SF_CTRL_SF_ID0_OFFSET_OFFSET); |
| tmp &= SF_CTRL_SF_ID0_OFFSET_MSK; |
| tmp = tmp >> SF_CTRL_SF_ID0_OFFSET_POS; |
| |
| return tmp; |
| } |
| |
| static void flash_bflb_set_offset(uint32_t base_reg, uint32_t offset) |
| { |
| uint32_t tmp; |
| |
| tmp = FLASH_READ32(base_reg + SF_CTRL_SF_ID0_OFFSET_OFFSET); |
| tmp &= ~SF_CTRL_SF_ID0_OFFSET_MSK; |
| tmp |= offset << SF_CTRL_SF_ID0_OFFSET_POS; |
| FLASH_WRITE32(tmp, base_reg + SF_CTRL_SF_ID0_OFFSET_OFFSET); |
| } |
| |
| static int flash_bflb_save_xip_state(const struct device *dev) |
| { |
| const struct flash_bflb_config *cfg = dev->config; |
| struct flash_bflb_data *data = dev->data; |
| int ret; |
| |
| data->reg_copy = cfg->reg; |
| |
| /* bus to system AHB */ |
| ret = flash_bflb_set_bus(data, 0); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* command to disable continuous read */ |
| ret = flash_bflb_flash_disable_continuous_read(data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* disable burst with wrap*/ |
| ret = flash_bflb_flash_disable_burst(data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| /* enable quad previous command could've disabled it |
| * 0: io 1: do 2: qo 3: dio 4: qio |
| */ |
| if ((data->flash_cfg.io_mode & 0xf) == 2 || (data->flash_cfg.io_mode & 0xf) == 4) { |
| ret = flash_bflb_enable_qspi(data); |
| if (ret != 0) { |
| return ret; |
| } |
| } |
| |
| /* disable burst with wrap*/ |
| ret = flash_bflb_flash_disable_burst(data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| data->last_flash_offset = flash_bflb_get_offset(data->reg_copy); |
| sys_cache_data_flush_and_invd_all(); |
| flash_bflb_set_offset(data->reg_copy, 0); |
| |
| return 0; |
| } |
| |
| static int flash_bflb_xip_init(struct flash_bflb_data *data) |
| { |
| struct bflb_flash_command xip_cmd = {0}; |
| bool no_command = false; |
| int ret; |
| |
| /* bus to instruction AHB */ |
| ret = flash_bflb_set_bus(data, 1); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| xip_cmd.spi_mode = data->flash_cfg.io_mode & 0xf; |
| |
| switch (data->flash_cfg.io_mode & 0xf) { |
| default: |
| case 0: |
| xip_cmd.cmd_buf[0] = data->flash_cfg.fast_read_cmd << 24; |
| xip_cmd.dummy_clks = data->flash_cfg.fr_dmy_clk; |
| break; |
| case 1: |
| xip_cmd.cmd_buf[0] = data->flash_cfg.fast_read_do_cmd << 24; |
| xip_cmd.dummy_clks = data->flash_cfg.fr_do_dmy_clk; |
| break; |
| case 2: |
| xip_cmd.cmd_buf[0] = data->flash_cfg.fast_read_qo_cmd << 24; |
| xip_cmd.dummy_clks = data->flash_cfg.fr_qo_dmy_clk; |
| break; |
| case 3: |
| xip_cmd.cmd_buf[0] = data->flash_cfg.fast_read_dio_cmd << 24; |
| xip_cmd.dummy_clks = data->flash_cfg.fr_dio_dmy_clk; |
| break; |
| case 4: |
| xip_cmd.cmd_buf[0] = data->flash_cfg.fast_read_qio_cmd << 24; |
| xip_cmd.dummy_clks = data->flash_cfg.fr_qio_dmy_clk; |
| break; |
| } |
| xip_cmd.addr_size = 3; |
| |
| /* continuous read for qo and qio */ |
| if ((data->flash_cfg.io_mode & 0xf) == 2 || (data->flash_cfg.io_mode & 0xf) == 4) { |
| |
| if ((data->flash_cfg.c_read_support & 0x02) == 0) { |
| if ((data->flash_cfg.c_read_support & 0x01) == 0) { |
| /* "Not support cont read,but we still need set read mode(winbond |
| * 80dv)" |
| */ |
| xip_cmd.cmd_buf[1] = data->flash_cfg.c_read_mode << 24; |
| } else { |
| no_command = true; |
| xip_cmd.cmd_buf[0] = data->flash_cfg.c_read_mode; |
| } |
| xip_cmd.addr_size = 4; |
| } |
| } |
| xip_cmd.nb_data = 32; |
| |
| return flash_bflb_set_command_iahb(data, &xip_cmd, !no_command); |
| } |
| |
| static bool flash_bflb_flash_busy_wait(struct flash_bflb_data *data) |
| { |
| uint8_t tmp_bus = 0xFF; |
| uint32_t counter = 0; |
| |
| while ((tmp_bus & (1 << data->flash_cfg.busy_bit)) != 0 && counter < |
| BFLB_FLASH_CHIP_BUSY_TIMEOUT_MS * 20000) { |
| flash_bflb_flash_read_register(data, data->flash_cfg.busy_index, &tmp_bus, |
| data->flash_cfg.busy_read_reg_len); |
| clock_bflb_settle(); |
| counter++; |
| } |
| |
| |
| if ((tmp_bus & (1 << data->flash_cfg.busy_bit)) != 0) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int flash_bflb_restore_xip_state(struct flash_bflb_data *data) |
| { |
| int ret; |
| |
| sys_cache_data_flush_and_invd_all(); |
| flash_bflb_set_offset(data->reg_copy, data->last_flash_offset); |
| |
| /* reenable burst read */ |
| if ((data->flash_cfg.io_mode & 0x10) != 0) { |
| if ((data->flash_cfg.io_mode & 0xf) == 2 || (data->flash_cfg.io_mode & 0xf) == 4) { |
| ret = flash_bflb_flash_enable_burst(data); |
| if (ret != 0) { |
| return ret; |
| } |
| } |
| } |
| |
| ret = flash_bflb_xip_init(data); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_SOC_FLASH_BFLB_DIRECT_ACCESS) |
| |
| static int flash_bflb_read_sahb_do(struct flash_bflb_data *data, off_t address, void *buffer, |
| size_t length) |
| { |
| int ret; |
| struct bflb_flash_command read_cmd = {0}; |
| size_t i, cur_len; |
| |
| read_cmd.spi_mode = data->flash_cfg.io_mode & 0xf; |
| |
| switch (data->flash_cfg.io_mode & 0xf) { |
| default: |
| case 0: |
| read_cmd.cmd_buf[0] = data->flash_cfg.fast_read_cmd << 24; |
| read_cmd.dummy_clks = data->flash_cfg.fr_dmy_clk; |
| break; |
| case 1: |
| read_cmd.cmd_buf[0] = data->flash_cfg.fast_read_do_cmd << 24; |
| read_cmd.dummy_clks = data->flash_cfg.fr_do_dmy_clk; |
| break; |
| case 2: |
| read_cmd.cmd_buf[0] = data->flash_cfg.fast_read_qo_cmd << 24; |
| read_cmd.dummy_clks = data->flash_cfg.fr_qo_dmy_clk; |
| break; |
| case 3: |
| read_cmd.cmd_buf[0] = data->flash_cfg.fast_read_dio_cmd << 24; |
| read_cmd.dummy_clks = data->flash_cfg.fr_dio_dmy_clk; |
| break; |
| case 4: |
| read_cmd.cmd_buf[0] = data->flash_cfg.fast_read_qio_cmd << 24; |
| read_cmd.dummy_clks = data->flash_cfg.fr_qio_dmy_clk; |
| break; |
| } |
| read_cmd.addr_size = 3; |
| |
| /* continuous read for qo and qio */ |
| if ((data->flash_cfg.io_mode & 0xf) == 2 || (data->flash_cfg.io_mode & 0xf) == 4) { |
| |
| if ((data->flash_cfg.c_read_support & 0x02) == 0) { |
| if ((data->flash_cfg.c_read_support & 0x01) == 0) { |
| /* "Not support cont read,but we still need set read mode(winbond |
| * 80dv)" |
| */ |
| read_cmd.cmd_buf[1] = data->flash_cfg.c_read_mode << 24; |
| } else { |
| read_cmd.cmd_buf[1] = data->flash_cfg.c_read_mode << 24; |
| } |
| read_cmd.addr_size = 4; |
| } |
| } |
| |
| i = 0; |
| while (i < length) { |
| |
| cur_len = data->flash_cfg.page_size - ((address + i) % data->flash_cfg.page_size); |
| |
| if (cur_len > length - i) { |
| cur_len = length - i; |
| } |
| |
| read_cmd.cmd_buf[0] &= ~0xFFFFFF; |
| read_cmd.cmd_buf[0] |= (address + i); |
| read_cmd.nb_data = cur_len; |
| |
| ret = flash_bflb_send_command(data, &read_cmd); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| flash_bflb_xip_memcpy((uint8_t *)SF_CTRL_BUF_BASE, (uint8_t *)(buffer) + i, |
| cur_len); |
| |
| i += cur_len; |
| |
| if (flash_bflb_busy_wait(data)) { |
| return -EBUSY; |
| } |
| |
| if (flash_bflb_flash_busy_wait(data)) { |
| return -EBUSY; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* copies flash data using direct access */ |
| static int flash_bflb_read(const struct device *dev, off_t address, void *buffer, size_t length) |
| { |
| struct flash_bflb_data *data = dev->data; |
| unsigned int locker; |
| int ret; |
| |
| if (length == 0) { |
| return 0; |
| } |
| |
| ret = flash_bflb_is_valid_range(address, length); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_read)) { |
| return -ENOTSUP; |
| } |
| |
| /* interrupting would break, likely to access XIP*/ |
| locker = irq_lock(); |
| |
| ret = flash_bflb_save_xip_state(dev); |
| if (ret != 0) { |
| irq_unlock(locker); |
| return ret; |
| } |
| |
| ret = flash_bflb_read_sahb_do(data, address, buffer, length); |
| |
| if (ret != 0) { |
| flash_bflb_restore_xip_state(data); |
| } else { |
| ret = flash_bflb_restore_xip_state(data); |
| } |
| irq_unlock(locker); |
| |
| return ret; |
| } |
| |
| #else |
| |
| /* copies flash data using XIP access */ |
| static int flash_bflb_read(const struct device *dev, off_t address, void *buffer, size_t length) |
| { |
| struct flash_bflb_data *data = dev->data; |
| uint32_t img_offset; |
| unsigned int locker; |
| int ret; |
| |
| if (length == 0) { |
| return 0; |
| } |
| |
| ret = flash_bflb_is_valid_range(address, length); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_read)) { |
| return -ENOTSUP; |
| } |
| |
| /* interrupting would break, likely to access XIP*/ |
| locker = irq_lock(); |
| |
| /* get XIP offset / where code really is in flash, usually 0x2000 */ |
| img_offset = flash_bflb_get_offset(data->reg_copy); |
| |
| /* need set offset to 0 to access? */ |
| if (address < img_offset) { |
| |
| sys_cache_data_flush_and_invd_all(); |
| |
| /* set offset to 0 to access first (likely)0x2000 of flash */ |
| flash_bflb_set_offset(data->reg_copy, 0); |
| |
| /* copy data we need */ |
| flash_bflb_xip_memcpy((uint8_t *)(address + BFLB_XIP_BASE), |
| (uint8_t *)buffer, length); |
| |
| sys_cache_data_flush_and_invd_all(); |
| |
| flash_bflb_set_offset(data->reg_copy, img_offset); |
| } else { |
| /* copy data we need */ |
| flash_bflb_xip_memcpy((uint8_t *)(address + BFLB_XIP_BASE - img_offset), |
| (uint8_t *)buffer, length); |
| } |
| |
| /* done with interrupt breaking stuffs */ |
| irq_unlock(locker); |
| |
| return 0; |
| } |
| |
| #endif |
| |
| static int flash_bflb_write(const struct device *dev, |
| off_t address, |
| const void *buffer, |
| size_t length) |
| { |
| struct flash_bflb_data *data = dev->data; |
| uint32_t tmp; |
| unsigned int locker; |
| int ret, rete; |
| uint32_t cur_len, i; |
| struct bflb_flash_command write_cmd = {0}; |
| |
| if (length == 0) { |
| return 0; |
| } |
| |
| ret = flash_bflb_is_valid_range(address, length); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_write)) { |
| return -ENOTSUP; |
| } |
| |
| /* interrupting would break, likely to access XIP*/ |
| locker = irq_lock(); |
| |
| ret = flash_bflb_save_xip_state(dev); |
| if (ret != 0) { |
| irq_unlock(locker); |
| return ret; |
| } |
| |
| /* Check if the flash chip is OK to write to */ |
| ret = flash_bflb_flash_read_register(data, 0, (uint8_t *)(&tmp), 1); |
| if (ret != 0) { |
| goto exit_here; |
| } |
| if ((tmp & BFLB_FLASH_FLASH_BLOCK_PROTECT_MSK) != 0) { |
| ret = -EINVAL; |
| goto exit_here; |
| } |
| |
| if ((data->flash_cfg.io_mode & 0xf) == 0 |
| || (data->flash_cfg.io_mode & 0xf) == 1 |
| || (data->flash_cfg.io_mode & 0xf) == 3) { |
| write_cmd.cmd_buf[0] = data->flash_cfg.page_program_cmd << 24; |
| /* quad mode */ |
| } else { |
| write_cmd.cmd_buf[0] = data->flash_cfg.qpage_program_cmd << 24; |
| write_cmd.spi_mode = |
| flash_bflb_admode_to_spimode(data->flash_cfg.qpp_addr_mode, 2); |
| } |
| write_cmd.rw = 1; |
| write_cmd.addr_size = 3; |
| |
| i = 0; |
| while (i < length) { |
| /* Write enable is needed for every write */ |
| ret = flash_bflb_enable_writable(data); |
| if (ret != 0) { |
| goto exit_here; |
| } |
| |
| /* Get current position within page size, |
| * this assumes page_size <= CTRL_BUF_SIZE |
| */ |
| cur_len = data->flash_cfg.page_size - ((address + i) % data->flash_cfg.page_size); |
| |
| if (cur_len > length - i) { |
| cur_len = length - i; |
| } |
| |
| flash_bflb_xip_memcpy((uint8_t *)(buffer) + i, (uint8_t *)SF_CTRL_BUF_BASE, |
| cur_len); |
| write_cmd.cmd_buf[0] &= ~0xFFFFFF; |
| write_cmd.cmd_buf[0] |= (address + i); |
| write_cmd.nb_data = cur_len; |
| ret = flash_bflb_send_command(data, &write_cmd); |
| if (ret != 0) { |
| goto exit_here; |
| } |
| |
| i += cur_len; |
| |
| flash_bflb_busy_wait(data); |
| flash_bflb_flash_busy_wait(data); |
| } |
| |
| exit_here: |
| rete = flash_bflb_restore_xip_state(data); |
| irq_unlock(locker); |
| |
| return (ret != 0 ? ret : rete); |
| } |
| |
| static int flash_bflb_erase(const struct device *dev, off_t start, size_t len) |
| { |
| struct flash_bflb_data *data = dev->data; |
| unsigned int locker; |
| int ret, rete; |
| struct bflb_flash_command erase_cmd = {0}; |
| uint32_t erase_start = start / ERASE_SIZE; |
| uint32_t erase_end = (len / ERASE_SIZE) + start / ERASE_SIZE; |
| |
| if (len == 0) { |
| return 0; |
| } |
| |
| ret = flash_bflb_is_valid_range(start, len); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_write)) { |
| return -ENOTSUP; |
| } |
| |
| if ((len % ERASE_SIZE) != 0) { |
| LOG_WRN("Length is not a multiple of minimal erase block size"); |
| return -EINVAL; |
| } |
| |
| if ((start % ERASE_SIZE) != 0) { |
| LOG_WRN("Start address is not a multiple of minimal erase block size"); |
| return -EINVAL; |
| } |
| /* interrupting would break, likely to access XIP*/ |
| locker = irq_lock(); |
| |
| ret = flash_bflb_save_xip_state(dev); |
| if (ret != 0) { |
| irq_unlock(locker); |
| return ret; |
| } |
| |
| erase_cmd.rw = 0; |
| erase_cmd.addr_size = 3; |
| |
| for (uint32_t i = erase_start; i < erase_end; i++) { |
| /* Write enable is needed for every write */ |
| ret = flash_bflb_enable_writable(data); |
| if (ret != 0) { |
| goto exit_here; |
| } |
| |
| erase_cmd.cmd_buf[0] = data->flash_cfg.sector_erase_cmd << 24; |
| erase_cmd.cmd_buf[0] |= (i * data->flash_cfg.sector_size * 1024); |
| |
| ret = flash_bflb_send_command(data, &erase_cmd); |
| if (ret != 0) { |
| goto exit_here; |
| } |
| |
| flash_bflb_busy_wait(data); |
| flash_bflb_flash_busy_wait(data); |
| } |
| |
| exit_here: |
| rete = flash_bflb_restore_xip_state(data); |
| irq_unlock(locker); |
| |
| return (ret != 0 ? ret : rete); |
| } |
| |
| |
| #if CONFIG_FLASH_PAGE_LAYOUT |
| static struct flash_pages_layout flash_bflb_pages_layout = { |
| .pages_count = TOTAL_SIZE / ERASE_SIZE, |
| .pages_size = ERASE_SIZE, |
| }; |
| |
| void flash_bflb_page_layout(const struct device *dev, |
| const struct flash_pages_layout **layout, |
| size_t *layout_size) |
| { |
| *layout = &flash_bflb_pages_layout; |
| *layout_size = 1; |
| } |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
| |
| static const struct flash_parameters flash_bflb_parameters = { |
| .write_block_size = WRITE_SIZE, |
| .erase_value = ERASE_VALUE, |
| }; |
| |
| static const struct flash_parameters *flash_bflb_get_parameters(const struct device *dev) |
| { |
| return &flash_bflb_parameters; |
| } |
| |
| static void flash_bflb_isr(const struct device *dev) |
| { |
| /* No interrupts */ |
| } |
| |
| static DEVICE_API(flash, flash_bflb_api) = { |
| .read = flash_bflb_read, |
| .write = flash_bflb_write, |
| .erase = flash_bflb_erase, |
| .get_parameters = flash_bflb_get_parameters, |
| #if CONFIG_FLASH_PAGE_LAYOUT |
| .page_layout = flash_bflb_page_layout, |
| #endif /* CONFIG_FLASH_PAGE_LAYOUT */ |
| }; |
| |
| /* from SDK because there is no matching zephyr crc for the bflb flash crc, this is supposedly a |
| * implementation of ZIP crc32 |
| */ |
| static uint32_t bflb_soft_crc32(uint32_t initial, void *in, uint32_t len) |
| { |
| uint32_t crc = ~initial; |
| uint8_t *data = (uint8_t *)in; |
| |
| while (len--) { |
| crc ^= *data++; |
| for (uint8_t i = 0; i < 8; ++i) { |
| if (crc & 1) { |
| /* 0xEDB88320 = reverse 0x04C11DB7 */ |
| crc = (crc >> 1) ^ 0xEDB88320U; |
| } else { |
| crc = (crc >> 1); |
| } |
| } |
| } |
| |
| return ~crc; |
| } |
| |
| /* /!\ this function cannot run from XIP! */ |
| static int flash_bflb_config_init(const struct device *dev) |
| { |
| struct flash_bflb_data *data = dev->data; |
| const struct flash_bflb_config *cfg = dev->config; |
| uint32_t tmp; |
| uint32_t img_offset; |
| unsigned int locker; |
| struct bflb_flash_header header; |
| |
| if (flash_bflb_is_in_xip(&flash_bflb_config_init)) { |
| return -ENOTSUP; |
| } |
| |
| /* copy cfg reg to memory as cfg will not be in it and inaccessible */ |
| data->reg_copy = cfg->reg; |
| |
| /* get flash config using xip access */ |
| |
| /* interrupting would break, likely to access XIP*/ |
| locker = irq_lock(); |
| /* get XIP offset / where code really is in flash, usually 0x2000 */ |
| img_offset = flash_bflb_get_offset(data->reg_copy); |
| |
| sys_cache_data_flush_and_invd_all(); |
| |
| /* set offset to 0 to access first (likely)0x2000 of flash */ |
| flash_bflb_set_offset(data->reg_copy, 0); |
| |
| /* copy data we need */ |
| flash_bflb_xip_memcpy((uint8_t *)(BFLB_XIP_BASE), |
| (uint8_t *)&header, sizeof(struct bflb_flash_header)); |
| |
| sys_cache_data_flush_and_invd_all(); |
| |
| flash_bflb_set_offset(data->reg_copy, img_offset); |
| |
| /* done with interrupt breaking stuffs */ |
| irq_unlock(locker); |
| |
| /* magic */ |
| if (!(header.magic_2.magic[0] == BFLB_FLASH_MAGIC_2[0] |
| && header.magic_2.magic[1] == BFLB_FLASH_MAGIC_2[1] |
| && header.magic_2.magic[2] == BFLB_FLASH_MAGIC_2[2] |
| && header.magic_2.magic[3] == BFLB_FLASH_MAGIC_2[3])) { |
| LOG_ERR("Flash data magic is incorrect"); |
| return -EINVAL; |
| } |
| |
| tmp = bflb_soft_crc32(0, (uint8_t *)(&header.flash_cfg), sizeof(struct bflb_flash_cfg)); |
| if (tmp != header.flash_cfg_crc) { |
| LOG_ERR("Flash data crc is incorrect %d vs %d", tmp, header.flash_cfg_crc); |
| return -EINVAL; |
| } |
| flash_bflb_xip_memcpy((uint8_t *)&(header.flash_cfg), |
| (uint8_t *)&(data->flash_cfg), sizeof(struct bflb_flash_cfg)); |
| |
| return 0; |
| } |
| |
| /* grabs the id with byte order inverted (LSB to MSB) */ |
| static int flash_bflb_get_jedecid_live(struct flash_bflb_data *data, uint32_t *output) |
| { |
| struct bflb_flash_command get_jedecid = {0}; |
| int ret; |
| |
| get_jedecid.dummy_clks = data->flash_cfg.jedec_id_cmd_dmy_clk; |
| get_jedecid.cmd_buf[0] = data->flash_cfg.jedec_id_cmd << 24; |
| get_jedecid.nb_data = 3; |
| |
| ret = flash_bflb_send_command(data, &get_jedecid); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| *output = FLASH_READ32(SF_CTRL_BUF_BASE); |
| |
| return 0; |
| } |
| |
| static int flash_bflb_init(const struct device *dev) |
| { |
| const struct flash_bflb_config *cfg = dev->config; |
| struct flash_bflb_data *data = dev->data; |
| unsigned int locker; |
| int ret; |
| |
| ret = flash_bflb_config_init(dev); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| locker = irq_lock(); |
| |
| ret = flash_bflb_save_xip_state(dev); |
| if (ret != 0) { |
| irq_unlock(locker); |
| return ret; |
| } |
| |
| flash_bflb_get_jedecid_live(data, &(data->jedec_id)); |
| |
| /* operations done here in bflb driver but not here: |
| * - reenable qspi (already done in flash_bflb_save_xip_state in bflb driver too) |
| * - reenable flash-side burstwrap (already done in restore state, possibly need to be done |
| * before l1c wrap side) |
| */ |
| if ((data->flash_cfg.io_mode & 0x10) == 0) { |
| flash_bflb_l1c_wrap(true); |
| } else { |
| flash_bflb_l1c_wrap(false); |
| } |
| |
| ret = flash_bflb_restore_xip_state(data); |
| if (ret != 0) { |
| irq_unlock(locker); |
| return ret; |
| } |
| |
| irq_unlock(locker); |
| |
| cfg->irq_config_func(dev); |
| |
| return 0; |
| } |
| |
| #define FLASH_BFLB_DEVICE(n) \ |
| static void flash_bflb_irq_config_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), \ |
| flash_bflb_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } \ |
| static const struct flash_bflb_config flash_bflb_config_##n = { \ |
| .reg = DT_INST_REG_ADDR_BY_IDX(n, 0), \ |
| .irq_config_func = &flash_bflb_irq_config_##n, \ |
| }; \ |
| static struct flash_bflb_data flash_bflb_data_##n; \ |
| DEVICE_DT_INST_DEFINE(n, flash_bflb_init, NULL, \ |
| &flash_bflb_data_##n, \ |
| &flash_bflb_config_##n, POST_KERNEL, \ |
| CONFIG_FLASH_INIT_PRIORITY, \ |
| &flash_bflb_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(FLASH_BFLB_DEVICE) |