flash/nor: add DesignWare SPI controller driver
Driver for DesignWare SPI controller, found on many SoCs (see compatible list in Linux device tree bindings Documentation/devicetree/bindings/spi/snps,dw-apb-ssi.yaml). This implementation only supports MIPS as it was the only one available for the tests, however, adding support for other architectures should require only few adjustments. Driver relies on flash/nor/spi.h to find Flash chip info. Driver internal functions support 24bit addressing mode, but due to limitations of flash/nor/spi.h, it is not used. The reported writing speed is about 60kb/s. Lint, sanitizer and valgrind reported warnings were not related to the driver. Change-Id: Id3df5626ab88055f034f74f274823051dedefeb1 Signed-off-by: Sergey Matsievskiy <matsievskiysv@gmail.com> Reviewed-on: https://review.openocd.org/c/openocd/+/8400 Tested-by: jenkins Reviewed-by: Tomas Vanek <vanekt@fbl.cz>
This commit is contained in:
committed by
Tomas Vanek
parent
ce38758e3d
commit
eb6f2745b7
246
contrib/loaders/flash/dw-spi/dw-spi.c
Normal file
246
contrib/loaders/flash/dw-spi/dw-spi.c
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user