diff options
Diffstat (limited to 'contrib/loaders/flash/dw-spi/dw-spi.c')
-rw-r--r-- | contrib/loaders/flash/dw-spi/dw-spi.c | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/contrib/loaders/flash/dw-spi/dw-spi.c b/contrib/loaders/flash/dw-spi/dw-spi.c new file mode 100644 index 0000000..66b7439 --- /dev/null +++ b/contrib/loaders/flash/dw-spi/dw-spi.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Helper functions for DesignWare SPI Core driver. + * These helpers are loaded into CPU and execute Flash manipulation algorithms + * at full CPU speed. Due to inability to control nCS pin, this is the only way + * to communicate with Flash chips connected via DW SPI serial interface. + * + * In order to avoid using stack, all functions used in helpers are inlined. + * Software breakpoints are used to terminate helpers. + * + * Pushing byte to TX FIFO does not make byte immediately available in RX FIFO + * and nCS is only asserted when TX FIFO is not empty. General approach is to + * fill TX FIFO with as many bytes as possible, at the same time reading + * available bytes from RX FIFO. + * + * This file contains helper functions. + */ + +#include "dw-spi.h" + +#include "../../../../src/flash/nor/dw-spi-helper.h" + +/** + * @brief Generic flash transaction. + * + * @param[in] arg: Function arguments. + */ +__attribute__((section(".transaction"))) void +transaction(struct dw_spi_transaction *arg) +{ + register uint8_t *buffer_tx = (uint8_t *)arg->buffer; + register uint8_t *buffer_rx = buffer_tx; + register uint32_t size = arg->size; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + wait_tx_finish(status); + flush_rx(status, data); + + for (; size > 0; size--) { + send_u8(status, data, *buffer_tx++); + if (arg->read_flag && rx_available(status)) + *buffer_rx++ = rcv_byte(data); + } + + // Pushed all data to TX FIFO. Read bytes left in RX FIFO. + if (arg->read_flag) { + while (buffer_rx < buffer_tx) { + wait_rx_available(status); + *buffer_rx++ = rcv_byte(data); + } + } + + RETURN; +} + +/** + * @brief Check flash sectors are filled with pattern. Primary use for + * checking sector erase state. + * + * @param[in] arg: Function arguments. + */ +__attribute__((section(".check_fill"))) void +check_fill(struct dw_spi_check_fill *arg) +{ + register uint32_t tx_size; + register uint32_t rx_size; + register uint32_t dummy_count; + register uint8_t filled; + register uint8_t *fill_status_array = (uint8_t *)arg->fill_status_array; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + for (; arg->sector_count > 0; arg->sector_count--, + arg->address += arg->sector_size, + fill_status_array++) { + wait_tx_finish(status); + flush_rx(status, data); + + /* + * Command byte and address bytes make up for dummy_count number of + * bytes, that must be skipped in RX FIFO before actual data arrives. + */ + send_u8(status, data, arg->read_cmd); + if (arg->four_byte_mode) { + dummy_count = 1 + 4; // Command byte + 4 address bytes + send_u32(status, data, arg->address); + } else { + dummy_count = 1 + 3; // Command byte + 3 address bytes + send_u24(status, data, arg->address); + } + + for (tx_size = arg->sector_size, rx_size = arg->sector_size, filled = 1; + tx_size > 0; tx_size--) { + send_u8(status, data, 0); // Dummy write to push out read data. + if (rx_available(status)) { + if (dummy_count > 0) { + // Read data not arrived yet. + rcv_byte(data); + dummy_count--; + } else { + if (rcv_byte(data) != arg->pattern) { + filled = 0; + break; + } + rx_size--; + } + } + } + if (filled) { + for (; rx_size > 0; rx_size--) { + wait_rx_available(status); + if (rcv_byte(data) != arg->pattern) { + filled = 0; + break; + } + } + } + *fill_status_array = filled; + } + + RETURN; +} + +/** + * @brief Erase flash sectors. + * + * @param[in] arg: Function arguments. + */ +__attribute__((section(".erase"))) void +erase(struct dw_spi_erase *arg) +{ + register uint32_t address = arg->address; + register uint32_t count = arg->sector_count; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + for (; count > 0; count--, address += arg->sector_size) { + write_enable(status, data, arg->write_enable_cmd); + wait_write_enable(status, data, arg->read_status_cmd, + arg->write_enable_mask); + + erase_sector(status, data, arg->erase_sector_cmd, address, + arg->four_byte_mode); + wait_busy(status, data, arg->read_status_cmd, arg->busy_mask); + } + + RETURN; +} + +/** + * @brief Flash program. + * + * @param[in] arg: Function arguments. + */ +__attribute__((section(".program"))) void +program(struct dw_spi_program *arg) +{ + register uint8_t *buffer = (uint8_t *)arg->buffer; + register uint32_t buffer_size = arg->buffer_size; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + register uint32_t page_size; + + while (buffer_size > 0) { + write_enable(status, data, arg->write_enable_cmd); + wait_write_enable(status, data, arg->read_status_cmd, + arg->write_enable_mask); + + wait_tx_finish(status); + + send_u8(status, data, arg->program_cmd); + if (arg->four_byte_mode) + send_u32(status, data, arg->address); + else + send_u24(status, data, arg->address); + + for (page_size = MIN(arg->page_size, buffer_size); page_size > 0; + page_size--, buffer_size--) { + send_u8(status, data, *buffer++); + } + arg->address += arg->page_size; + wait_busy(status, data, arg->read_status_cmd, arg->busy_mask); + } + + RETURN; +} + +/** + * @brief Read data from flash. + * + * @param[in] arg: Function arguments. + */ +__attribute__((section(".read"))) void +read(struct dw_spi_read *arg) +{ + register uint32_t tx_size = arg->buffer_size; + register uint32_t rx_size = arg->buffer_size; + register uint32_t dummy_count; + register uint8_t *buffer = (uint8_t *)arg->buffer; + register volatile uint8_t *status = (uint8_t *)arg->status_reg; + register volatile uint8_t *data = (uint8_t *)arg->data_reg; + + wait_tx_finish(status); + flush_rx(status, data); + + /* + * Command byte and address bytes make up for dummy_count number of + * bytes, that must be skipped in RX FIFO before actual data arrives. + */ + send_u8(status, data, arg->read_cmd); + if (arg->four_byte_mode) { + dummy_count = 1 + 4; // Command byte + 4 address bytes + send_u32(status, data, arg->address); + } else { + dummy_count = 1 + 3; // Command byte + 3 address bytes + send_u24(status, data, arg->address); + } + + for (; tx_size > 0; tx_size--) { + send_u8(status, data, 0); // Dummy write to push out read data. + if (rx_available(status)) { + if (dummy_count > 0) { + rcv_byte(data); + dummy_count--; + } else { + *buffer++ = rcv_byte(data); + rx_size--; + } + } + } + while (rx_size > 0) { + wait_rx_available(status); + if (dummy_count > 0) { + // Read data not arrived yet. + rcv_byte(data); + dummy_count--; + } else { + *buffer++ = rcv_byte(data); + rx_size--; + } + } + + RETURN; +} |