0
0
mirror of https://git.code.sf.net/p/openocd/code synced 2025-02-07 05:39:50 +00:00
Sergey Matsievskiy eb6f2745b7 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>
2025-01-31 03:25:53 +00:00

247 lines
6.6 KiB
C

// 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;
}