-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 repoted writing speed is about 60kb/s. Lint, sanitizer and valgraind repoted warnings were not related to the driver. Change-Id: Id3df5626ab88055f034f74f274823051dedefeb1 Signed-off-by: Sergey Matsievskiy <[email protected]>
- Loading branch information
Sergey Matsievskiy
committed
Sep 18, 2024
1 parent
fd7b66c
commit 8d55de7
Showing
14 changed files
with
2,562 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
TOOLCHAIN:=mipsel-linux-gnu- | ||
CC:=$(TOOLCHAIN)gcc | ||
OBJCOPY:=$(TOOLCHAIN)objcopy | ||
CFLAGS:=-O2 -Wall -Wextra -fpic | ||
SRC=dw-spi.c | ||
OBJ=$(patsubst %.c, %.o,$(SRC)) | ||
|
||
# sparx-iv | ||
ifeq ($(TOOLCHAIN),mipsel-linux-gnu-) | ||
CFLAGS+= -march=24kec | ||
endif | ||
|
||
all: \ | ||
$(TOOLCHAIN)transaction.inc \ | ||
$(TOOLCHAIN)erase.inc \ | ||
$(TOOLCHAIN)check_fill.inc \ | ||
$(TOOLCHAIN)program.inc \ | ||
$(TOOLCHAIN)read.inc | ||
|
||
$(TOOLCHAIN)%.bin: $(OBJ) | ||
$(OBJCOPY) --dump-section .$*=$@ $< | ||
|
||
%.inc: %.bin | ||
xxd -i > $@ < $< | ||
|
||
.PHONY: clean | ||
clean: | ||
rm -rf .ccls-cache | ||
find . \( \ | ||
-iname "*.o" \ | ||
-o -iname "*.bin" \ | ||
-o -iname "*.inc" \ | ||
\) -delete |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
|
||
#include "dw-spi.h" | ||
|
||
#include "../../../../src/flash/nor/dw-spi-helper.h" | ||
|
||
/** | ||
* @brief Generic flash transaction. | ||
* | ||
* nCS is only asserted when TX FIFO is not empty. | ||
* Must supply TX FIFO with data for the whole duration of transaction. | ||
* | ||
* @param[in] arg Function arguments. | ||
* @retval 0 Success. Returned in argument register. | ||
* @retval 1 Failure. Returned in argument register. | ||
*/ | ||
__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--) { | ||
wait_tx_available(status); | ||
push_byte(data, *buffer_tx++); | ||
if (!tx_in_progress(status)) { | ||
RET_ERROR; | ||
BKPT; | ||
return; | ||
} | ||
if (arg->read_flag && rx_available(status)) | ||
*buffer_rx++ = rcv_byte(data); | ||
} | ||
|
||
if (arg->read_flag) { | ||
while (buffer_rx < buffer_tx) { | ||
wait_rx_available(status); | ||
*buffer_rx++ = rcv_byte(data); | ||
} | ||
} | ||
|
||
RET_OK; | ||
BKPT; | ||
} | ||
|
||
/** | ||
* @brief Check flash sectors are filled with pattern. Primary use for | ||
* checking sector erase state. | ||
* | ||
* nCS is only asserted when TX FIFO is not empty. | ||
* Must supply TX FIFO with data for the whole duration of transaction. | ||
* | ||
* @param[in] arg Function arguments. | ||
* @retval 0 Success. Returned in argument register. | ||
* @retval 1 Failure. Returned in argument register. | ||
*/ | ||
__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); | ||
|
||
push_byte(data, arg->read_cmd); | ||
if (arg->four_byte_mode) { | ||
dummy_count = 5; | ||
push_u32(status, data, arg->address); | ||
} else { | ||
dummy_count = 4; | ||
push_u24(status, data, arg->address); | ||
} | ||
|
||
for (tx_size = arg->sector_size, rx_size = arg->sector_size, filled = 1; | ||
tx_size > 0; tx_size--) { | ||
wait_tx_available(status); | ||
if (!tx_in_progress(status)) { | ||
RET_ERROR; | ||
BKPT; | ||
return; | ||
} | ||
push_byte(data, 0); // dummy write | ||
if (rx_available(status)) { | ||
if (dummy_count > 0) { | ||
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; | ||
} | ||
|
||
RET_OK; | ||
BKPT; | ||
} | ||
|
||
/** | ||
* @brief Erase flash sectors. | ||
* | ||
* @param[in] arg Function arguments. | ||
* @retval 0 Success. Returned in argument register. | ||
* @retval 1 Failure. Returned in argument register. | ||
*/ | ||
__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); | ||
} | ||
|
||
RET_OK; | ||
BKPT; | ||
} | ||
|
||
/** | ||
* @brief Flash program. | ||
* | ||
* @param[in] arg Function arguments. | ||
* @retval 0 Success. Returned in argument register. | ||
* @retval 1 Failure. Returned in argument register. | ||
*/ | ||
__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); | ||
|
||
push_byte(data, arg->program_cmd); | ||
if (arg->four_byte_mode) | ||
push_u32(status, data, arg->address); | ||
else | ||
push_u24(status, data, arg->address); | ||
for (page_size = MIN(arg->page_size, buffer_size); page_size > 0; | ||
page_size--, buffer_size--) { | ||
wait_tx_available(status); | ||
push_byte(data, *buffer++); | ||
} | ||
arg->address += arg->page_size; | ||
wait_busy(status, data, arg->read_status_cmd, arg->busy_mask); | ||
} | ||
|
||
RET_OK; | ||
BKPT; | ||
} | ||
|
||
/** | ||
* @brief Read data from flash. | ||
* | ||
* nCS is only asserted when TX FIFO is not empty. | ||
* Must supply TX FIFO with data for the whole duration of transaction. | ||
* | ||
* @param[in] arg Function arguments. | ||
* @retval 0 Success. Returned in argument register. | ||
* @retval 1 Failure. Returned in argument register. | ||
*/ | ||
__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); | ||
|
||
push_byte(data, arg->read_cmd); | ||
if (arg->four_byte_mode) { | ||
dummy_count = 5; | ||
push_u32(status, data, arg->address); | ||
} else { | ||
dummy_count = 4; | ||
push_u24(status, data, arg->address); | ||
} | ||
|
||
for (; tx_size > 0; tx_size--) { | ||
wait_tx_available(status); | ||
if (!tx_in_progress(status)) { | ||
RET_ERROR; | ||
BKPT; | ||
return; | ||
} | ||
push_byte(data, 0); // dummy write | ||
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) { | ||
rcv_byte(data); | ||
dummy_count--; | ||
} else { | ||
*buffer++ = rcv_byte(data); | ||
rx_size--; | ||
} | ||
} | ||
|
||
RET_OK; | ||
BKPT; | ||
} |
Oops, something went wrong.