// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Driver for SPI NOR flash chips connected via DesignWare SPI Core. * Controller's Linux driver is located at drivers/spi/spi-dw-mmio.c. * DW-SPI is used in a number of processors, including Microsemi Jaguar2 and * Ocelot switch chips. * * Serial interface (SI) nCS0 pin, which is usually connected to the external * flash, cannot be controlled via GPIO controller: it is asserted only when * TX FIFO is not empty. Since JTAG is not fast enough to fill TX FIFO and * collect data from RX FIFO at the same time even on the slowest SPI clock * speeds, driver can only operate using functions, loaded in target's memory. * Therefore, it requires the user to set up DRAM controller and provide * work-area. * * In Microsemi devices, serial interface pins may be governed either * by Boot or Master controller. For these devices, additional configuration of * spi_mst address is required to switch between the two. * * Currently supported devices typically have much more RAM then NOR Flash * (Jaguar2 reference design has 256MB RAM and 32MB NOR Flash), so supporting * work-area sizes smaller then transfer buffer seems like the unnecessary * complication. * * This code was tested on Jaguar2 VSC7448 connected to Macronix MX25L25635F. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "dw-spi-helper.h" #include "imp.h" #include "spi.h" #include #include #include #include #include #include /** * @brief IP block placement map. * * Used for dynamic definition of the register map. * * IP block is used on different chips and placed in different locations. * This structure defines some implementation specific variables. */ struct dw_spi_regmap { uint32_t freq; ///< Clock frequency. target_addr_t simc; ///< Absolute offset of SIMC register block. target_addr_t spi_mst; ///< Absolute offset of ICPU_CFG:SPI_MST register. 0 if not available. uint8_t si_if_owner_offset; ///< Offset of \ref si_mode bits in ICPU_CFG:SPI_MST. }; /** * @brief Register map for Jaguar2 switch devices. */ static const struct dw_spi_regmap jaguar2_regmap = { .freq = 250000000UL, .simc = 0x70101000UL, .spi_mst = 0x70000024UL, .si_if_owner_offset = 6, }; /** * @brief Register map for Ocelot switch devices. */ static const struct dw_spi_regmap ocelot_regmap = { .freq = 250000000UL, .simc = 0x70101000UL, .spi_mst = 0x70000024UL, .si_if_owner_offset = 4, }; #define DW_SPI_IF_OWNER_WIDTH 2 ///< IF owner register field width. /** * @brief Owner of the SI interface. */ enum dw_spi_si_mode { DW_SPI_SI_MODE_BOOT = 1, ///< Boot controller maps contents of SPI Flash to memory in read-only mode. DW_SPI_SI_MODE_MASTER = 2, ///< SPI controller mode for reading/writing SPI Flash. }; #define DW_SPI_REG_CTRLR0 0x00 ///< General configuration register. #define DW_SPI_REG_SIMCEN 0x08 ///< Master controller enable register. #define DW_SPI_REG_SER 0x10 ///< Slave select register. #define DW_SPI_REG_BAUDR 0x14 ///< Baud rate configuration register. #define DW_SPI_REG_SR 0x28 ///< Status register. #define DW_SPI_REG_IMR 0x2c ///< Interrupt configuration register. #define DW_SPI_REG_DR 0x60 ///< Data register. #define DW_SPI_REG_CTRLR0_DFS(x) ((x) & GENMASK(3, 0)) ///< Data frame size. #define DW_SPI_REG_CTRLR0_FRF(x) (((x) << 4) & GENMASK(5, 4)) ///< SI protocol. #define DW_SPI_REG_CTRLR0_SCPH(x) ((!!(x)) << 6) ///< Probe position. #define DW_SPI_REG_CTRLR0_SCPOL(x) ((!!(x)) << 7) ///< Probe polarity. #define DW_SPI_REG_CTRLR0_TMOD(x) (((x) << 8) & GENMASK(9, 8)) ///< SI mode. #define DW_SPI_REG_SIMCEN_SIMCEN(x) (!!(x)) ///< Controller enable. #define DW_SPI_REG_SER_SER(x) ((x) & GENMASK(15, 0)) ///< Slave select bitmask. #define DW_SPI_REG_BAUDR_SCKDV(x) ((x) & GENMASK(15, 0)) ///< Clock divisor. /** * @brief Driver private state. */ struct dw_spi_driver { bool probed; ///< Bank is probed. uint32_t id; ///< Chip ID. unsigned int speed; ///< Flash speed. unsigned int timeout; ///< Flash timeout in milliseconds. uint8_t chip_select_bitmask; ///< Chip select bitmask. bool four_byte_mode; ///< Flash chip is in 32bit address mode. enum dw_spi_si_mode saved_ctrl_mode; ///< Previously selected controller mode. struct dw_spi_regmap regmap; ///< SI controller regmap. const struct flash_device *spi_flash; ///< SPI flash device info. }; /** * @brief Register used to pass argument struct to helper functions. */ #define DW_SPI_ARG_REG "r4" /** * @brief Default timeout value in ms for flash transaction jobs. */ #define DW_SPI_TIMEOUT_DEFAULT (600 * 1000) /** * @brief Timeout value in ms for short flash transactions, * e.g. reading flash ID and status register. */ #define DW_SPI_TIMEOUT_TRANSACTION 1000 /** * @brief Select SI interface owner. * * Mode selection is skipped if Boot controller not available on target * (i.e. spi_mst command argument is not configured). * * @param[in] bank: Flash bank. * @param[in] mode: New controller mode. * @return Command execution status. */ static int dw_spi_ctrl_mode(const struct flash_bank *const bank, enum dw_spi_si_mode mode) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; if (!regmap->spi_mst) return ERROR_OK; uint32_t ctrl; int ret = target_read_u32(target, regmap->spi_mst, &ctrl); if (ret) { LOG_ERROR("DW SPI SPI:MST register read error"); return ret; } ctrl &= ~GENMASK(DW_SPI_IF_OWNER_WIDTH + driver->regmap.si_if_owner_offset, driver->regmap.si_if_owner_offset); ctrl |= mode << driver->regmap.si_if_owner_offset; ret = target_write_u32(target, regmap->spi_mst, ctrl); if (ret) LOG_ERROR("DW SPI controller mode configuration error"); return ret; } /** * @brief Select master controller as SI interface owner. * * Previous interface owner is restored via dw_spi_ctrl_mode_restore() function. * Mode selection is skipped if Boot controller not available on target * (i.e. spi_mst command argument is not configured). * * @param[in] bank: Flash bank. * @param[in] mode: New controller mode. * @return Command execution status. */ static int dw_spi_ctrl_mode_configure(const struct flash_bank *const bank, enum dw_spi_si_mode mode) { struct target *const target = bank->target; struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; if (!regmap->spi_mst) return ERROR_OK; uint32_t ctrl; int ret = target_read_u32(target, regmap->spi_mst, &ctrl); if (ret) { LOG_ERROR("DW SPI controller mode query error"); return ret; } driver->saved_ctrl_mode = (enum dw_spi_si_mode)((ctrl >> driver->regmap.si_if_owner_offset) & GENMASK(DW_SPI_IF_OWNER_WIDTH, 0)); return dw_spi_ctrl_mode(bank, mode); } /** * @brief Restore SI controller mode. * * Restore initially configured SI controller mode. Undo configuration done by * dw_spi_ctrl_mode_configure() function. * Mode selection is skipped if Boot controller not available on target * (i.e. spi_mst command argument is not configured). * * @param[in] bank: Flash bank. * @return Command execution status. */ static int dw_spi_ctrl_mode_restore(const struct flash_bank *const bank) { const struct dw_spi_driver *const driver = bank->driver_priv; return dw_spi_ctrl_mode(bank, driver->saved_ctrl_mode); } /** * @brief Enable master controller. * * Configuration of the master controller must be done when it is disabled. * * @param[in] bank: Flash bank. * @param[in] value: New enable state. * @return Command execution status. */ static int dw_spi_master_ctrl_enable(const struct flash_bank *const bank, bool value) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_SIMCEN, DW_SPI_REG_SIMCEN_SIMCEN(value)); if (ret) LOG_ERROR("DW SPI master controller enable flag configuration error"); return ret; } /** * @brief Configure SI transfer mode. * * @param[in] bank: Flash bank. * @return Command execution status. */ static int dw_spi_ctrl_configure_si(const struct flash_bank *const bank) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; // 8 bit frame; Motorola protocol; middle lo probe; TX RX mode const uint32_t mode = DW_SPI_REG_CTRLR0_DFS(0x7) | DW_SPI_REG_CTRLR0_FRF(0) | DW_SPI_REG_CTRLR0_SCPH(0) | DW_SPI_REG_CTRLR0_SCPOL(0) | DW_SPI_REG_CTRLR0_TMOD(0); int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_CTRLR0, mode); if (ret) { LOG_ERROR("DW SPI master controller configuration query error"); return ret; } ret = target_write_u32(target, regmap->simc + DW_SPI_REG_SER, DW_SPI_REG_SER_SER(driver->chip_select_bitmask)); if (ret) LOG_ERROR("DW SPI slave select configuration error"); return ret; } /** * @brief Configure SI transfer speed. * * @param[in] bank: Flash bank. * @return Command execution status. */ static int dw_spi_ctrl_configure_speed(const struct flash_bank *const bank) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; // divisor LSB must be zero const uint16_t div = MIN((regmap->freq / driver->speed), 0xfffe) & 0xfffe; int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_BAUDR, DW_SPI_REG_BAUDR_SCKDV(div)); if (ret) { LOG_ERROR("DW SPI speed configuration error"); return ret; } unsigned int speed = regmap->freq / div; LOG_DEBUG("DW SPI setting NOR controller speed to %u kHz", speed / 1000); return ret; } /** * @brief Disable SI master controller interrupts. * * @param[in] bank: Flash bank. * @return Command execution status. */ static int dw_spi_ctrl_disable_interrupts(const struct flash_bank *const bank) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; int ret = target_write_u32(target, regmap->simc + DW_SPI_REG_IMR, 0); if (ret) LOG_ERROR("DW SPI disable interrupts error"); return ret; } /** * @brief Do data transaction. * * Buffer data are sent and replaced with received data. Total buffer size * is a sum of TX and RX messages. RX portion of the buffer is initially * filled with dummy values, which get replaced by the message. * * @param[in] bank: Flash bank. * @param[in,out] buffer: Data buffer. If \p read flag is set, buffer is * filled with received data. * @param[in] size: \p buffer size. * @param[in] read: The read flag. If set to true, read values will override * \p buffer. * @return Command execution status. */ static int dw_spi_ctrl_transaction(const struct flash_bank *const bank, uint8_t *const buffer, size_t size, bool read) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; static const uint8_t target_code[] = { #include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-transaction.inc" }; const size_t target_code_size = sizeof(target_code); const size_t total_working_area_size = target_code_size + sizeof(struct dw_spi_transaction) + size; // allocate working area, memory args and data buffer struct working_area *helper; int ret = target_alloc_working_area(target, target_code_size, &helper); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper; } struct working_area *helper_args; ret = target_alloc_working_area(target, sizeof(struct dw_spi_transaction), &helper_args); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper_args; } struct working_area *target_buffer; ret = target_alloc_working_area(target, size, &target_buffer); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_target_buffer; } // write algorithm code and buffer to working areas ret = target_write_buffer(target, helper->address, target_code_size, target_code); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } ret = target_write_buffer(target, target_buffer->address, size, buffer); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } // prepare helper execution struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC, .isa_mode = MIPS32_ISA_MIPS32 }; struct reg_param reg_param; init_reg_param(®_param, DW_SPI_ARG_REG, 32, PARAM_OUT); struct mem_param mem_param; init_mem_param(&mem_param, helper_args->address, helper_args->size, PARAM_OUT); // Set the arguments for the helper buf_set_u32(reg_param.value, 0, 32, helper_args->address); struct dw_spi_transaction *helper_args_val = (struct dw_spi_transaction *)mem_param.value; target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, target_buffer->address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->size, size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, regmap->simc + DW_SPI_REG_SR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, regmap->simc + DW_SPI_REG_DR); helper_args_val->read_flag = read; ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, helper->address, 0, DW_SPI_TIMEOUT_TRANSACTION, &mips32_algo); if (ret) { LOG_ERROR("DW SPI flash algorithm error"); goto cleanup; } if (read) { ret = target_read_buffer(target, target_buffer->address, size, buffer); if (ret) LOG_ERROR("DW SPI target buffer read error"); } cleanup: destroy_reg_param(®_param); destroy_mem_param(&mem_param); err_write_buffer: target_free_working_area(target, target_buffer); err_target_buffer: target_free_working_area(target, helper_args); err_helper_args: target_free_working_area(target, helper); err_helper: return ret; } /** * @brief Check that selected region is filled with pattern. * * This function is used for Flash erase checking. * * @param[in] bank: Flash bank. * @param[in] address: Starting address. Sector aligned. * @param[in] sector_size: Size of sector. * @param[in] sector_count: Number of sectors. * @param[in] pattern: Fill pattern. * @param[in] read_cmd: Flash read command. * @param[out] buffer: Filled flag array. Must hold \p sector_count number * of entries. * @return Command execution status. */ static int dw_spi_ctrl_check_sectors_fill(const struct flash_bank *const bank, uint32_t address, size_t sector_size, size_t sector_count, uint8_t pattern, uint8_t read_cmd, uint8_t *buffer) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; static const uint8_t target_code[] = { #include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-check_fill.inc" }; const size_t target_code_size = sizeof(target_code); const size_t total_working_area_size = target_code_size + sizeof(struct dw_spi_check_fill) + sector_count; // allocate working area, memory args and data buffer struct working_area *helper; int ret = target_alloc_working_area(target, target_code_size, &helper); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper; } struct working_area *helper_args; ret = target_alloc_working_area(target, sizeof(struct dw_spi_check_fill), &helper_args); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper_args; } struct working_area *target_buffer; ret = target_alloc_working_area(target, sector_count, &target_buffer); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_target_buffer; } // write algorithm code and buffer to working areas ret = target_write_buffer(target, helper->address, target_code_size, target_code); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } // prepare helper execution struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC, .isa_mode = MIPS32_ISA_MIPS32 }; struct reg_param reg_param; init_reg_param(®_param, DW_SPI_ARG_REG, 32, PARAM_OUT); struct mem_param mem_param; init_mem_param(&mem_param, helper_args->address, helper_args->size, PARAM_OUT); // Set the arguments for the helper buf_set_u32(reg_param.value, 0, 32, helper_args->address); struct dw_spi_check_fill *helper_args_val = (struct dw_spi_check_fill *)mem_param.value; target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_size, sector_size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_count, sector_count); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, regmap->simc + DW_SPI_REG_SR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, regmap->simc + DW_SPI_REG_DR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->fill_status_array, target_buffer->address); helper_args_val->pattern = pattern; helper_args_val->read_cmd = read_cmd; helper_args_val->four_byte_mode = driver->four_byte_mode; ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, helper->address, 0, driver->timeout, &mips32_algo); if (ret) { LOG_ERROR("DW SPI flash algorithm error"); goto cleanup; } ret = target_read_buffer(target, target_buffer->address, sector_count, buffer); if (ret) LOG_ERROR("DW SPI target buffer read error"); cleanup: destroy_reg_param(®_param); destroy_mem_param(&mem_param); err_write_buffer: target_free_working_area(target, target_buffer); err_target_buffer: target_free_working_area(target, helper_args); err_helper_args: target_free_working_area(target, helper); err_helper: return ret; } /** * @brief Write flash region. * * @param[in] bank: Flash bank. * @param[in] address: First page address. Page aligned when write is crossing * the page boundary. * @param[in] buffer: Data buffer. * @param[in] buffer_size: \p buffer size. * @param[in] page_size: Size of flash page. * @param[in] stat_cmd: Flash command to read chip status. * @param[in] we_cmd: Flash command to enable write. * @param[in] program_cmd: Flash command to program chip. * @param[in] we_mask: Status byte write enable mask. * @param[in] busy_mask: Status byte write busy mask. * @return Command execution status. */ static int dw_spi_ctrl_program(const struct flash_bank *const bank, uint32_t address, const uint8_t *const buffer, size_t buffer_size, uint32_t page_size, uint8_t stat_cmd, uint8_t we_cmd, uint8_t program_cmd, uint8_t we_mask, uint8_t busy_mask) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; static const uint8_t target_code[] = { #include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-program.inc" }; const size_t target_code_size = sizeof(target_code); const size_t total_working_area_size = target_code_size + sizeof(struct dw_spi_program) + buffer_size; // allocate working area, memory args and data buffer struct working_area *helper; int ret = target_alloc_working_area(target, target_code_size, &helper); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper; } struct working_area *helper_args; ret = target_alloc_working_area(target, sizeof(struct dw_spi_program), &helper_args); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper_args; } struct working_area *target_buffer; ret = target_alloc_working_area(target, buffer_size, &target_buffer); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_target_buffer; } // write algorithm code and buffer to working areas ret = target_write_buffer(target, helper->address, target_code_size, target_code); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } ret = target_write_buffer(target, target_buffer->address, buffer_size, buffer); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } // prepare helper execution struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC, .isa_mode = MIPS32_ISA_MIPS32 }; struct reg_param reg_param; init_reg_param(®_param, DW_SPI_ARG_REG, 32, PARAM_OUT); struct mem_param mem_param; init_mem_param(&mem_param, helper_args->address, helper_args->size, PARAM_OUT); // Set the arguments for the helper buf_set_u32(reg_param.value, 0, 32, helper_args->address); struct dw_spi_program *helper_args_val = (struct dw_spi_program *)mem_param.value; target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->page_size, page_size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, target_buffer->address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer_size, buffer_size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, regmap->simc + DW_SPI_REG_SR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, regmap->simc + DW_SPI_REG_DR); helper_args_val->read_status_cmd = stat_cmd; helper_args_val->write_enable_cmd = we_cmd; helper_args_val->program_cmd = program_cmd; helper_args_val->write_enable_mask = we_mask; helper_args_val->busy_mask = busy_mask; helper_args_val->four_byte_mode = driver->four_byte_mode; ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, helper->address, 0, driver->timeout, &mips32_algo); if (ret) LOG_ERROR("DW SPI flash algorithm error"); destroy_reg_param(®_param); destroy_mem_param(&mem_param); err_write_buffer: target_free_working_area(target, target_buffer); err_target_buffer: target_free_working_area(target, helper_args); err_helper_args: target_free_working_area(target, helper); err_helper: return ret; } /** * @brief Erase sectors. * * @param[in] bank: Flash bank. * @param[in] address: Flash address. Must be sector aligned. * @param[in] sector_size: Size of flash sector. * @param[in] sector_count: Number of sectors to erase. * @param[in] stat_cmd: Flash command to read chip status. * @param[in] we_cmd: Flash command to set enable write. * @param[in] erase_sector_cmd: Flash command to set erase sector. * @param[in] we_mask: Status byte write enable mask. * @param[in] busy_mask: Status byte write busy mask. * @return Command execution status. */ static int dw_spi_ctrl_erase_sectors(const struct flash_bank *const bank, uint32_t address, uint32_t sector_size, size_t sector_count, uint8_t stat_cmd, uint8_t we_cmd, uint8_t erase_sector_cmd, uint8_t we_mask, uint8_t busy_mask) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; static const uint8_t target_code[] = { #include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-erase.inc" }; const size_t target_code_size = sizeof(target_code); const size_t total_working_area_size = target_code_size + sizeof(struct dw_spi_erase); // allocate working area and memory args struct working_area *helper; int ret = target_alloc_working_area(target, target_code_size, &helper); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper; } struct working_area *helper_args; ret = target_alloc_working_area(target, sizeof(struct dw_spi_erase), &helper_args); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper_args; } // write algorithm code to working area ret = target_write_buffer(target, helper->address, target_code_size, target_code); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } // prepare helper execution struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC, .isa_mode = MIPS32_ISA_MIPS32 }; struct reg_param reg_param; init_reg_param(®_param, DW_SPI_ARG_REG, 32, PARAM_OUT); struct mem_param mem_param; init_mem_param(&mem_param, helper_args->address, helper_args->size, PARAM_OUT); // Set the arguments for the helper buf_set_u32(reg_param.value, 0, 32, helper_args->address); struct dw_spi_erase *helper_args_val = (struct dw_spi_erase *)mem_param.value; target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_size, sector_size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->sector_count, sector_count); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, regmap->simc + DW_SPI_REG_SR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, regmap->simc + DW_SPI_REG_DR); helper_args_val->read_status_cmd = stat_cmd; helper_args_val->write_enable_cmd = we_cmd; helper_args_val->erase_sector_cmd = erase_sector_cmd; helper_args_val->write_enable_mask = we_mask; helper_args_val->busy_mask = busy_mask; helper_args_val->four_byte_mode = driver->four_byte_mode; ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, helper->address, 0, driver->timeout, &mips32_algo); if (ret) LOG_ERROR("DW SPI flash algorithm error"); destroy_reg_param(®_param); destroy_mem_param(&mem_param); err_write_buffer: target_free_working_area(target, helper_args); err_helper_args: target_free_working_area(target, helper); err_helper: return ret; } /** * @brief Read flash data. * * @param[in] bank: Flash bank. * @param[in] address: Flash address. * @param[out] buffer: Data buffer. * @param[in] buffer_size: \p buffer size. * @param[in] read_cmd: Flash command to read data from flash. * @return Command execution status. */ static int dw_spi_ctrl_read(const struct flash_bank *const bank, uint32_t address, uint8_t *buffer, size_t buffer_size, uint8_t read_cmd) { struct target *const target = bank->target; const struct dw_spi_driver *const driver = bank->driver_priv; const struct dw_spi_regmap *const regmap = &driver->regmap; static const uint8_t target_code[] = { #include "../../../contrib/loaders/flash/dw-spi/mipsel-linux-gnu-read.inc" }; const size_t target_code_size = sizeof(target_code); const size_t total_working_area_size = target_code_size + sizeof(struct dw_spi_read) + buffer_size; // allocate working area and memory args struct working_area *helper; int ret = target_alloc_working_area(target, target_code_size, &helper); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper; } struct working_area *helper_args; ret = target_alloc_working_area(target, sizeof(struct dw_spi_read), &helper_args); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_helper_args; } struct working_area *target_buffer; ret = target_alloc_working_area(target, buffer_size, &target_buffer); if (ret) { LOG_ERROR("DW SPI could not allocate working area. Need %zx", total_working_area_size); goto err_target_buffer; } // write algorithm code to working area ret = target_write_buffer(target, helper->address, target_code_size, target_code); if (ret) { LOG_ERROR("DW SPI writing to working area error"); goto err_write_buffer; } // prepare helper execution struct mips32_algorithm mips32_algo = { .common_magic = MIPS32_COMMON_MAGIC, .isa_mode = MIPS32_ISA_MIPS32 }; struct reg_param reg_param; init_reg_param(®_param, DW_SPI_ARG_REG, 32, PARAM_OUT); struct mem_param mem_param; init_mem_param(&mem_param, helper_args->address, helper_args->size, PARAM_OUT); // Set the arguments for the helper buf_set_u32(reg_param.value, 0, 32, helper_args->address); struct dw_spi_read *helper_args_val = (struct dw_spi_read *)mem_param.value; target_buffer_set_u32(target, (uint8_t *)&helper_args_val->address, address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer, target_buffer->address); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->buffer_size, buffer_size); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->status_reg, regmap->simc + DW_SPI_REG_SR); target_buffer_set_u32(target, (uint8_t *)&helper_args_val->data_reg, regmap->simc + DW_SPI_REG_DR); helper_args_val->read_cmd = read_cmd; helper_args_val->four_byte_mode = driver->four_byte_mode; ret = target_run_algorithm(target, 1, &mem_param, 1, ®_param, helper->address, 0, driver->timeout, &mips32_algo); if (ret) { LOG_ERROR("DW SPI flash algorithm error"); goto cleanup; } ret = target_read_buffer(target, target_buffer->address, buffer_size, buffer); if (ret) LOG_ERROR("DW SPI target buffer read error"); cleanup: destroy_reg_param(®_param); destroy_mem_param(&mem_param); err_write_buffer: target_free_working_area(target, target_buffer); err_target_buffer: target_free_working_area(target, helper_args); err_helper_args: target_free_working_area(target, helper); err_helper: return ret; } /** * @brief Read Flash device ID. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_read_id(const struct flash_bank *const bank) { struct dw_spi_driver *const driver = bank->driver_priv; const size_t buffer_size = 1 + 3 + 1; uint8_t buffer[buffer_size]; memset(buffer, 0, buffer_size); buffer[0] = SPIFLASH_READ_ID; int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, true); if (ret) { LOG_ERROR("DW SPI flash ID read error"); return ret; } buffer[buffer_size - 1] = 0; // use le_to_h_u32 to decode flash ID as per JEDEC SFDP driver->id = le_to_h_u32(buffer + 1); LOG_DEBUG("DW SPI read flash ID %" PRIx32, driver->id); return ERROR_OK; } /** * @brief Read Flash device status. * * @param[in] bank: Flash bank handle. * @param[out] status: The status byte. * @return Command execution status. */ static int dw_spi_read_status(const struct flash_bank *const bank, uint8_t *const status) { const int buffer_size = 2; uint8_t buffer[buffer_size]; memset(buffer, 0, buffer_size); buffer[0] = SPIFLASH_READ_STATUS; int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, true); if (ret) { LOG_ERROR("DW SPI flash status read error"); return ret; } *status = buffer[1]; return ERROR_OK; } /** * @brief Wait for Flash command to finish. * * @param[in] bank: Flash bank handle. * @param[in] timeout: The timeout in ms. * @return Command execution status. */ static int dw_spi_wait_finish(const struct flash_bank *const bank, unsigned int timeout) { const int64_t end_time = timeval_ms() + timeout; while (timeval_ms() <= end_time) { uint8_t status; int ret = dw_spi_read_status(bank, &status); if (ret) { LOG_ERROR("DW SPI status query error"); return ret; } if (!(status & SPIFLASH_BSY_BIT)) return ERROR_OK; alive_sleep(1); } LOG_ERROR("DW SPI process timeout"); return ERROR_TIMEOUT_REACHED; } /** * @brief Flash device write enable. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_write_enable(const struct flash_bank *const bank) { const int buffer_size = 1; uint8_t buffer[buffer_size]; memset(buffer, 0, buffer_size); buffer[0] = SPIFLASH_WRITE_ENABLE; int ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, false); if (ret) { LOG_ERROR("DW SPI flash write enable error"); return ret; } uint8_t status; ret = dw_spi_read_status(bank, &status); if (ret) return ret; return status & SPIFLASH_WE_BIT ? ERROR_OK : ERROR_FAIL; } /** * @brief Erase Flash chip. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_erase_chip(const struct flash_bank *const bank) { const struct dw_spi_driver *const driver = bank->driver_priv; const int buffer_size = 1; uint8_t buffer[buffer_size]; int ret = dw_spi_write_enable(bank); if (ret) return ret; memset(buffer, 0, buffer_size); buffer[0] = driver->spi_flash->chip_erase_cmd; ret = dw_spi_ctrl_transaction(bank, buffer, buffer_size, false); if (ret) { LOG_ERROR("DW SPI erase flash error"); return ret; } ret = dw_spi_wait_finish(bank, driver->timeout); if (ret) { LOG_ERROR("DW SPI erase flash timeout"); return ret; } return ERROR_OK; } /** * @brief Flash device erase sectors. * * @param[in] bank: Flash bank handle. * @param[in] first: The first sector to erase. * @param[in] last: The last sector to erase. * @return Command execution status. */ static int dw_spi_erase_sectors(const struct flash_bank *const bank, unsigned int first, unsigned int last) { const struct dw_spi_driver *const driver = bank->driver_priv; if (first == 0 && last >= (bank->num_sectors - 1)) { // full erase int ret = dw_spi_erase_chip(bank); if (ret) return ret; } else { // partial erase int ret = dw_spi_ctrl_erase_sectors(bank, bank->sectors[first].offset, driver->spi_flash->sectorsize, last - first + 1, SPIFLASH_READ_STATUS, SPIFLASH_WRITE_ENABLE, driver->spi_flash->erase_cmd, SPIFLASH_WE_BIT, SPIFLASH_BSY_BIT); if (ret) { LOG_ERROR("DW SPI flash erase sectors error"); return ret; } } return ERROR_OK; } /** * @brief Flash bank blank check. */ static int dw_spi_blank_check(struct flash_bank *bank, size_t sector_count, uint8_t pattern) { const struct dw_spi_driver *const driver = bank->driver_priv; uint8_t *erased = malloc(sector_count); if (!erased) { LOG_ERROR("could not allocate memory"); return ERROR_FAIL; } // set initial erased value to unknown memset(erased, 2, sector_count); for (unsigned int sector_idx = 0; sector_idx < sector_count; sector_idx++) bank->sectors[sector_idx].is_erased = 2; int ret = dw_spi_ctrl_check_sectors_fill(bank, 0, bank->sectors[0].size, sector_count, pattern, driver->spi_flash->read_cmd, erased); if (!ret) { for (unsigned int sector_idx = 0; sector_idx < sector_count; sector_idx++) bank->sectors[sector_idx].is_erased = erased[sector_idx]; } else { LOG_ERROR("DW SPI flash erase check error"); } free(erased); return ret; } /** * @brief Write buffer to Flash. * * @param[in] bank: Flash bank handle. * @param[in] buffer: Data buffer. * @param[in] offset: Flash address offset. * @param[in] count: \p buffer size. * @return Command execution status. */ static int dw_spi_write_buffer(const struct flash_bank *const bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { const struct dw_spi_driver *const driver = bank->driver_priv; const size_t page_size = driver->spi_flash->pagesize; // Write unaligned first sector separately as helper function does // not handle this case well. struct { uint32_t address; const uint8_t *buffer; size_t count; } chunks[2] = { { .address = offset, .buffer = buffer, .count = 0 }, { .address = offset, .buffer = buffer, .count = count }, }; if (offset % page_size) { // start is not aligned chunks[0].count = MIN(page_size - (offset % page_size), count); chunks[1].count -= chunks[0].count; chunks[1].address += chunks[0].count; chunks[1].buffer += chunks[0].count; } for (unsigned int chunk_idx = 0; chunk_idx < ARRAY_SIZE(chunks); chunk_idx++) { if (chunks[chunk_idx].count > 0) { int ret = dw_spi_ctrl_program(bank, chunks[chunk_idx].address, chunks[chunk_idx].buffer, chunks[chunk_idx].count, page_size, SPIFLASH_READ_STATUS, SPIFLASH_WRITE_ENABLE, driver->spi_flash->pprog_cmd, SPIFLASH_WE_BIT, SPIFLASH_BSY_BIT); if (ret) { LOG_ERROR("DW SPI flash write error"); return ret; } } } return ERROR_OK; } /** * @brief Search for Flash chip info. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_spiflash_search(const struct flash_bank *const bank) { struct dw_spi_driver *const driver = bank->driver_priv; int ret = dw_spi_read_id(bank); if (ret) return ret; unsigned int idx = 0; while (flash_devices[idx].name) { if (flash_devices[idx].device_id == driver->id) { driver->spi_flash = &flash_devices[idx]; return ERROR_OK; } idx++; } LOG_ERROR("DW SPI could not find Flash with ID %" PRIx32 " in SPI Flash table: either Flash device is not supported " "or communication speed is too high", driver->id); return ERROR_FAIL; } /** * @brief Handle flash bank command. * * @param[in] CMD_ARGC: Number of arguments. * @param[in] CMD_ARGV: Command arguments. * @return Command execution status. */ FLASH_BANK_COMMAND_HANDLER(dw_spi_flash_bank_command) { unsigned int speed = 1000000; unsigned int timeout = DW_SPI_TIMEOUT_DEFAULT; uint8_t chip_select_bitmask = BIT(0); struct dw_spi_regmap regmap = { 0 }; if (CMD_ARGC < 6) return ERROR_COMMAND_SYNTAX_ERROR; for (unsigned int idx = 6; idx < CMD_ARGC; idx++) { if (strcmp(CMD_ARGV[idx], "-jaguar2") == 0) { // Fast config for Jaguar2 chips. memcpy(®map, &jaguar2_regmap, sizeof(jaguar2_regmap)); } else if (strcmp(CMD_ARGV[idx], "-ocelot") == 0) { // Fast config for Ocelot chips. memcpy(®map, &ocelot_regmap, sizeof(ocelot_regmap)); } else if (strcmp(CMD_ARGV[idx], "-freq") == 0) { COMMAND_PARSE_NUMBER(u32, CMD_ARGV[++idx], regmap.freq); } else if (strcmp(CMD_ARGV[idx], "-simc") == 0) { COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[++idx], regmap.simc); } else if (strcmp(CMD_ARGV[idx], "-spi_mst") == 0) { COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[++idx], regmap.spi_mst); } else if (strcmp(CMD_ARGV[idx], "-if_owner_offset") == 0) { COMMAND_PARSE_NUMBER(u8, CMD_ARGV[++idx], regmap.si_if_owner_offset); } else if (strcmp(CMD_ARGV[idx], "-speed") == 0) { COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], speed); } else if (strcmp(CMD_ARGV[idx], "-timeout") == 0) { COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], timeout); timeout *= 1000; // convert to ms } else if (strcmp(CMD_ARGV[idx], "-chip_select") == 0) { unsigned int cs_bit; COMMAND_PARSE_NUMBER(uint, CMD_ARGV[++idx], cs_bit); chip_select_bitmask = BIT(cs_bit); } else { LOG_WARNING("DW SPI unknown argument %s", CMD_ARGV[idx]); } } if (!regmap.simc) { LOG_ERROR("DW SPI cannot use boot controller with unconfigured simc"); return ERROR_COMMAND_SYNTAX_ERROR; } bank->driver_priv = malloc(sizeof(struct dw_spi_driver)); if (!bank->driver_priv) { LOG_ERROR("could not allocate memory"); return ERROR_FAIL; } struct dw_spi_driver *driver = bank->driver_priv; memset(driver, 0, sizeof(struct dw_spi_driver)); driver->speed = speed; driver->timeout = timeout; driver->chip_select_bitmask = chip_select_bitmask; driver->four_byte_mode = true; // 24bit commands not provided by spi.h memcpy(&driver->regmap, ®map, sizeof(regmap)); return ERROR_OK; } /** * @brief Assert target is halted. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_assert_halted(const struct flash_bank *const bank) { if (bank->target->state != TARGET_HALTED) { LOG_ERROR("target not halted"); return ERROR_TARGET_NOT_HALTED; } return ERROR_OK; } /** * @brief Prepare master controller for transaction. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_master_ctrl_configure(struct flash_bank *bank) { int ret = dw_spi_assert_halted(bank); if (ret) return ret; ret = dw_spi_ctrl_mode_configure(bank, DW_SPI_SI_MODE_MASTER); if (ret) { LOG_ERROR("DW SPI switch to master controller mode error"); return ret; } ret = dw_spi_master_ctrl_enable(bank, false); if (ret) { LOG_ERROR("DW SPI disable master controller error"); return ret; } ret = dw_spi_ctrl_configure_speed(bank); if (ret) { LOG_ERROR("DW SPI speed configuration error"); return ret; } ret = dw_spi_ctrl_disable_interrupts(bank); if (ret) { LOG_ERROR("DW SPI disable SPI interrupts error"); return ret; } ret = dw_spi_ctrl_configure_si(bank); if (ret) { LOG_ERROR("DW SPI controller configuration error"); return ret; } ret = dw_spi_master_ctrl_enable(bank, true); if (ret) { LOG_ERROR("DW SPI enable master controller error"); return ret; } return ERROR_OK; } /** * @brief Restore SI controller selection. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_master_ctrl_restore(struct flash_bank *bank) { int ret = dw_spi_master_ctrl_enable(bank, false); if (ret) { LOG_ERROR("DW SPI disable master controller error"); return ret; } ret = dw_spi_ctrl_mode_restore(bank); if (ret) { LOG_ERROR("DW SPI controller restore error"); return ret; } return ret; } /** * @brief Flash bank probe. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_probe(struct flash_bank *bank) { struct dw_spi_driver *const driver = bank->driver_priv; if (!driver) return ERROR_FAIL; if (strcmp(bank->target->type->name, mips_m4k_target.name) != 0 || bank->target->endianness != TARGET_LITTLE_ENDIAN) { LOG_ERROR("DW SPI currently only supports " "little endian mips_m4k target"); return ERROR_TARGET_INVALID; } int ret = dw_spi_master_ctrl_configure(bank); if (ret) return ret; ret = dw_spi_spiflash_search(bank); if (ret) goto err; bank->write_start_alignment = 0; bank->write_end_alignment = 0; uint32_t flash_size = driver->spi_flash->size_in_bytes; if (!bank->size) { bank->size = flash_size; LOG_INFO("DW SPI probed flash size 0x%" PRIx32, flash_size); } else { if (flash_size > bank->size) LOG_WARNING("DW SPI probed flash size 0x%" PRIx32 " is greater then declared 0x%" PRIx32, flash_size, bank->size); if (flash_size < bank->size) { LOG_ERROR("DW SPI probed flash size 0x%" PRIx32 " is smaller then declared 0x%" PRIx32, flash_size, bank->size); ret = ERROR_FLASH_BANK_INVALID; goto err; } } bank->num_sectors = bank->size / driver->spi_flash->sectorsize; // free previously allocated in case of reprobing free(bank->sectors); bank->sectors = alloc_block_array(0, driver->spi_flash->sectorsize, bank->num_sectors); if (!bank->sectors) { LOG_ERROR("could not allocate memory"); ret = ERROR_FAIL; goto err; } driver->probed = true; err: dw_spi_master_ctrl_restore(bank); return ret; } /** * @brief Flash bank erase sectors. * * @param[in] bank: Flash bank handle. * @param[in] first: The first sector to erase. * @param[in] last: The last sector to erase. * @return Command execution status. */ static int dw_spi_erase(struct flash_bank *bank, unsigned int first, unsigned int last) { int ret = dw_spi_master_ctrl_configure(bank); if (ret) return ret; ret = dw_spi_erase_sectors(bank, first, last); dw_spi_master_ctrl_restore(bank); return ret; } /** * @brief Flash bank write data. * * @param[in] bank: Flash bank handle. * @param[in] buffer: Data buffer. * @param[in] offset: Flash address offset. * @param[in] count: \p buffer size. * @return Command execution status. */ static int dw_spi_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { int ret = dw_spi_master_ctrl_configure(bank); if (ret) return ret; ret = dw_spi_write_buffer(bank, buffer, offset, count); dw_spi_master_ctrl_restore(bank); return ret; } /** * @brief Flash bank read data using master controller. * * @param[in] bank: Flash bank handle. * @param[out] buffer: Data buffer. * @param[in] offset: Flash address offset. * @param[in] count: \p buffer size. * @return Command execution status. */ static int dw_spi_read(struct flash_bank *bank, uint8_t *buffer, uint32_t offset, uint32_t count) { struct dw_spi_driver *const driver = bank->driver_priv; int ret = dw_spi_master_ctrl_configure(bank); if (ret) return ret; ret = dw_spi_ctrl_read(bank, offset, buffer, count, driver->spi_flash->read_cmd); dw_spi_master_ctrl_restore(bank); return ret; } /** * @brief Flash bank erase check. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_erase_check(struct flash_bank *bank) { int ret = dw_spi_master_ctrl_configure(bank); if (ret) return ret; ret = dw_spi_blank_check(bank, bank->num_sectors, bank->erased_value); dw_spi_master_ctrl_restore(bank); return ret; } /** * @brief Flash bank info. * * @param[in] bank: Flash bank handle. * @param[in,out] cmd Command invocation. * @return Command execution status. */ static int dw_spi_info(struct flash_bank *bank, struct command_invocation *cmd) { const struct dw_spi_driver *const driver = bank->driver_priv; command_print(cmd, "model %s", driver->spi_flash->name); command_print(cmd, "ID 0x%" PRIx32, driver->id); command_print_sameline(cmd, "size 0x%" PRIx32, bank->size); return ERROR_OK; } /** * @brief Autoprobe driver. * * @param[in] bank: Flash bank handle. * @return Command execution status. */ static int dw_spi_auto_probe(struct flash_bank *bank) { struct dw_spi_driver *driver = bank->driver_priv; if (!driver) return ERROR_FAIL; if (!driver->probed) return dw_spi_probe(bank); return ERROR_OK; } /** * @brief DW-SPI NOR flash functions. */ const struct flash_driver dw_spi_flash = { .name = "dw-spi", .flash_bank_command = dw_spi_flash_bank_command, .erase = dw_spi_erase, .write = dw_spi_write, .read = dw_spi_read, .probe = dw_spi_probe, .auto_probe = dw_spi_auto_probe, .erase_check = dw_spi_erase_check, .info = dw_spi_info, .verify = default_flash_verify, .free_driver_priv = default_flash_free_driver_priv, };